Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 17 additions & 13 deletions crates/codegraph-store/src/index_lease.rs
Original file line number Diff line number Diff line change
Expand Up @@ -383,20 +383,24 @@ impl IndexLease {
let initial_identity =
identity_for_validated_path(&lock_path, &initial).map_err(|_| changed(&lock_path))?;
checkpoint(AcquireCheckpoint::InitialMetadataValidated);
let file = OpenOptions::new()
.read(true)
.write(true)
.open(&lock_path)
.map_err(|source| {
if source.kind() == std::io::ErrorKind::NotFound {
changed(&lock_path)
} else {
IndexLeaseError::OpenLock {
path: lock_path.clone(),
source,
}
// Shared readers only need a shared kernel lock. Keeping their handle
// read-only lets status/query commands work in read-only sandboxes and
// mounts; exclusive lifecycle operations retain write access.
let mut options = OpenOptions::new();
options.read(true);
if mode == LeaseMode::Exclusive {
options.write(true);
}
let file = options.open(&lock_path).map_err(|source| {
if source.kind() == std::io::ErrorKind::NotFound {
changed(&lock_path)
} else {
IndexLeaseError::OpenLock {
path: lock_path.clone(),
source,
}
})?;
}
})?;
let opened_identity = opened_identity(&file, &lock_path, Some(&initial))?;
if initial_identity != opened_identity {
return Err(changed(&lock_path));
Expand Down
56 changes: 56 additions & 0 deletions crates/codegraph-store/tests/index_lease.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,22 @@ static NEXT_TEMP: AtomicU64 = AtomicU64::new(0);

struct TempProject(PathBuf);

struct PermissionsGuard {
path: PathBuf,
original: std::fs::Permissions,
}

impl Drop for PermissionsGuard {
fn drop(&mut self) {
std::fs::set_permissions(&self.path, self.original.clone()).unwrap_or_else(|error| {
panic!(
"restore permissions for read-only lock fixture {}: {error}",
self.path.display()
)
});
}
}

impl TempProject {
fn new(label: &str) -> Self {
let serial = NEXT_TEMP.fetch_add(1, Ordering::Relaxed);
Expand Down Expand Up @@ -73,6 +89,20 @@ fn lock_bytes(paths: &IndexPaths) -> Vec<u8> {
std::fs::read(paths.permanent_lock()).expect("read permanent lock bytes")
}

fn make_read_only(path: &Path) -> PermissionsGuard {
let original = std::fs::metadata(path)
.unwrap_or_else(|error| panic!("inspect lock permissions {}: {error}", path.display()))
.permissions();
let mut read_only = original.clone();
read_only.set_readonly(true);
std::fs::set_permissions(path, read_only)
.unwrap_or_else(|error| panic!("make lock read-only {}: {error}", path.display()));
PermissionsGuard {
path: path.to_path_buf(),
original,
}
}

/// Windows `ERROR_LOCK_VIOLATION`.
///
/// A byte-range lock taken through `File::try_lock` is ADVISORY on Unix, so an
Expand Down Expand Up @@ -277,6 +307,32 @@ fn explicit_initial_creation_is_separate_and_never_truncates_an_existing_lock()
assert_eq!(lock_bytes(&paths), LOCK_BYTES);
}

#[test]
fn shared_existing_open_needs_only_read_access_to_the_permanent_lock() {
let project = TempProject::new("shared-read-only-lock");
let paths = project.paths();
stage_existing_lock(&paths, LOCK_BYTES);
let _permissions = make_read_only(&paths.permanent_lock());

let write_error = OpenOptions::new()
.read(true)
.write(true)
.open(paths.permanent_lock())
.expect_err("fixture must reject a write-capable lock handle");
assert_eq!(
write_error.kind(),
std::io::ErrorKind::PermissionDenied,
"fixture must fail specifically because write access is denied"
);

let shared = IndexLease::acquire_shared_existing(&paths, deadline_after(CHILD_WAIT), || false)
.expect("shared lease must need only read access to the permanent lock");
assert!(shared.is_shared());
assert_lock_bytes_preserved(&paths, LOCK_BYTES);
drop(shared);
assert_eq!(lock_bytes(&paths), LOCK_BYTES);
}

#[test]
fn shared_processes_coexist_but_shared_blocks_exclusive() {
let project = TempProject::new("shared-matrix");
Expand Down
Loading