mirror of
https://github.com/0xrsydn/idx-cli.git
synced 2026-11-07 09:45:36 +00:00
fix: lock sync's first inspection, resolve symlinks without their target
Fourth Codex (gpt-6.1-sol, xhigh) pass over 5e0831d, each item verified
before fixing:
- sync inspects the local database under the shared lock, so another
sync cannot swap it under that connection.
- symlinks are resolved by reading the link (db::resolve_db_path), not
canonicalize, so a symlinked path finds the same lock and install
target even while the real file is missing (fresh install, mid-swap)
or the link dangles.
- readers go ahead unlocked when no lock file can be created; nothing
can replace the database there. (A read-only data directory still
fails in SQLite's WAL setup, as in 0.2.4.)
- XLSX shared strings are capped at 2,000,000 entries.
Verified live: an install waits for a reader's shared lock; a symlinked
db_path waits on the real path's lock and stays a symlink. CLI goldens
unchanged; live A/B vs 0.2.4: 16 passed, discover hits the IDX 403 on
both builds (exit 4 vs 1, intended).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
5e0831d8fe
commit
c65982f407
4 changed files with 111 additions and 25 deletions
|
|
@ -133,6 +133,43 @@ pub fn open_db() -> Result<Connection, IdxError> {
|
|||
}
|
||||
|
||||
/// Open the ownership database at `db_path`, creating it and migrating the schema.
|
||||
/// Where the database file really lives: symlinks are followed by reading
|
||||
/// the link, so this works even while the target does not exist (a fresh
|
||||
/// install, or mid-replacement during sync). Creates the parent directory.
|
||||
pub(crate) fn resolve_db_path(path: &Path) -> Result<PathBuf, IdxError> {
|
||||
let mut current = path.to_path_buf();
|
||||
for _ in 0..16 {
|
||||
let parent = match current.parent() {
|
||||
Some(parent) if !parent.as_os_str().is_empty() => parent.to_path_buf(),
|
||||
_ => PathBuf::from("."),
|
||||
};
|
||||
fs::create_dir_all(&parent).map_err(|e| IdxError::Io(e.to_string()))?;
|
||||
let parent = fs::canonicalize(&parent).map_err(|e| IdxError::Io(e.to_string()))?;
|
||||
let name = current.file_name().ok_or_else(|| {
|
||||
IdxError::ConfigError(format!(
|
||||
"ownership database path has no file name: {}",
|
||||
path.display()
|
||||
))
|
||||
})?;
|
||||
let candidate = parent.join(name);
|
||||
match fs::symlink_metadata(&candidate) {
|
||||
Ok(meta) if meta.file_type().is_symlink() => {
|
||||
let target = fs::read_link(&candidate).map_err(|e| IdxError::Io(e.to_string()))?;
|
||||
current = if target.is_absolute() {
|
||||
target
|
||||
} else {
|
||||
parent.join(target)
|
||||
};
|
||||
}
|
||||
_ => return Ok(candidate),
|
||||
}
|
||||
}
|
||||
Err(IdxError::Io(format!(
|
||||
"too many symlink levels resolving {}",
|
||||
path.display()
|
||||
)))
|
||||
}
|
||||
|
||||
pub fn open_db_at(db_path: &Path) -> Result<Connection, IdxError> {
|
||||
if let Some(parent) = db_path.parent() {
|
||||
fs::create_dir_all(parent).map_err(|e| IdxError::DatabaseError(e.to_string()))?;
|
||||
|
|
@ -1965,4 +2002,37 @@ mod tests {
|
|||
.unwrap();
|
||||
assert_eq!(buyers, 1);
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
#[test]
|
||||
fn resolve_db_path_follows_symlinks_even_when_the_target_is_missing() {
|
||||
let root = std::env::temp_dir().join(format!(
|
||||
"idx-resolve-{}-{}",
|
||||
std::process::id(),
|
||||
fastrand::u64(..)
|
||||
));
|
||||
let real_dir = root.join("real");
|
||||
std::fs::create_dir_all(&real_dir).unwrap();
|
||||
let real_dir = std::fs::canonicalize(&real_dir).unwrap();
|
||||
let link = root.join("link.db");
|
||||
// Dangling: the target does not exist yet (fresh install, mid-swap).
|
||||
std::os::unix::fs::symlink(real_dir.join("ownership.db"), &link).unwrap();
|
||||
assert_eq!(
|
||||
super::resolve_db_path(&link).unwrap(),
|
||||
real_dir.join("ownership.db")
|
||||
);
|
||||
// Relative link, resolved against the link's directory.
|
||||
let relative = root.join("relative.db");
|
||||
std::os::unix::fs::symlink("real/ownership.db", &relative).unwrap();
|
||||
assert_eq!(
|
||||
super::resolve_db_path(&relative).unwrap(),
|
||||
real_dir.join("ownership.db")
|
||||
);
|
||||
// A plain path is returned as-is (canonical parent).
|
||||
assert_eq!(
|
||||
super::resolve_db_path(&real_dir.join("ownership.db")).unwrap(),
|
||||
real_dir.join("ownership.db")
|
||||
);
|
||||
let _ = std::fs::remove_dir_all(&root);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue