Preserve open inode identity across workspace renames
This commit is contained in:
parent
2df810f125
commit
982395bb28
4 changed files with 77 additions and 14 deletions
|
|
@ -346,6 +346,7 @@ impl IgnoredWorkspace {
|
||||||
handle,
|
handle,
|
||||||
Arc::new(OpenFile {
|
Arc::new(OpenFile {
|
||||||
file,
|
file,
|
||||||
|
inode,
|
||||||
backing: entry.backing,
|
backing: entry.backing,
|
||||||
path,
|
path,
|
||||||
access_mode: flags & libc::O_ACCMODE,
|
access_mode: flags & libc::O_ACCMODE,
|
||||||
|
|
@ -448,9 +449,12 @@ impl IgnoredWorkspace {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn attributes(&self, entry: &ResolvedEntry) -> io::Result<FileAttr> {
|
fn attributes(&self, entry: &ResolvedEntry) -> io::Result<FileAttr> {
|
||||||
let stat = entry.stat;
|
self.attributes_for_inode(self.inode_for(entry), entry.stat)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn attributes_for_inode(&self, inode: INodeNo, stat: rustix::fs::Stat) -> io::Result<FileAttr> {
|
||||||
Ok(FileAttr {
|
Ok(FileAttr {
|
||||||
ino: self.inode_for(entry),
|
ino: inode,
|
||||||
size: u64::try_from(stat.st_size)
|
size: u64::try_from(stat.st_size)
|
||||||
.map_err(|_| io::Error::other("negative file size"))?,
|
.map_err(|_| io::Error::other("negative file size"))?,
|
||||||
blocks: u64::try_from(stat.st_blocks)
|
blocks: u64::try_from(stat.st_blocks)
|
||||||
|
|
@ -485,6 +489,15 @@ impl IgnoredWorkspace {
|
||||||
operation(&open)
|
operation(&open)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn open_file_for_inode(&self, inode: INodeNo) -> Option<Arc<OpenFile>> {
|
||||||
|
self.files
|
||||||
|
.lock()
|
||||||
|
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
||||||
|
.values()
|
||||||
|
.find(|open| open.inode == inode)
|
||||||
|
.cloned()
|
||||||
|
}
|
||||||
|
|
||||||
fn posix_lock_file(&self, open: &OpenFile, owner: LockOwner) -> io::Result<Arc<File>> {
|
fn posix_lock_file(&self, open: &OpenFile, owner: LockOwner) -> io::Result<Arc<File>> {
|
||||||
let stat = fstat(open.file.as_fd())?;
|
let stat = fstat(open.file.as_fd())?;
|
||||||
let key = PosixLockKey {
|
let key = PosixLockKey {
|
||||||
|
|
|
||||||
|
|
@ -70,6 +70,7 @@ pub(super) struct ResolvedEntry {
|
||||||
|
|
||||||
pub(super) struct OpenFile {
|
pub(super) struct OpenFile {
|
||||||
pub(super) file: File,
|
pub(super) file: File,
|
||||||
|
pub(super) inode: INodeNo,
|
||||||
pub(super) backing: Classification,
|
pub(super) backing: Classification,
|
||||||
pub(super) path: PathBuf,
|
pub(super) path: PathBuf,
|
||||||
pub(super) access_mode: i32,
|
pub(super) access_mode: i32,
|
||||||
|
|
|
||||||
|
|
@ -57,13 +57,12 @@ impl Filesystem for IgnoredWorkspace {
|
||||||
let result = if let Some(handle) = handle {
|
let result = if let Some(handle) = handle {
|
||||||
self.with_file(handle, |open| {
|
self.with_file(handle, |open| {
|
||||||
let stat = fstat(open.file.as_fd())?;
|
let stat = fstat(open.file.as_fd())?;
|
||||||
let path = self.path_for(inode)?;
|
self.attributes_for_inode(inode, stat)
|
||||||
self.attributes(&ResolvedEntry {
|
|
||||||
path,
|
|
||||||
backing: open.backing,
|
|
||||||
stat,
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
} else if let Some(open) = self.open_file_for_inode(inode) {
|
||||||
|
fstat(open.file.as_fd())
|
||||||
|
.map_err(Into::into)
|
||||||
|
.and_then(|stat| self.attributes_for_inode(inode, stat))
|
||||||
} else {
|
} else {
|
||||||
self.path_for(inode).and_then(|path| {
|
self.path_for(inode).and_then(|path| {
|
||||||
let snapshot = self.classifier.snapshot_for(&path);
|
let snapshot = self.classifier.snapshot_for(&path);
|
||||||
|
|
@ -124,12 +123,7 @@ impl Filesystem for IgnoredWorkspace {
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
self.record_changed(open.backing, &open.path);
|
self.record_changed(open.backing, &open.path);
|
||||||
let path = self.path_for(inode)?;
|
self.attributes_for_inode(inode, fstat(open.file.as_fd())?)
|
||||||
self.attributes(&ResolvedEntry {
|
|
||||||
path,
|
|
||||||
backing: open.backing,
|
|
||||||
stat: fstat(open.file.as_fd())?,
|
|
||||||
})
|
|
||||||
};
|
};
|
||||||
let result = if let Some(handle) = handle {
|
let result = if let Some(handle) = handle {
|
||||||
self.with_file(handle, operation)
|
self.with_file(handle, operation)
|
||||||
|
|
@ -914,6 +908,7 @@ impl Filesystem for IgnoredWorkspace {
|
||||||
handle,
|
handle,
|
||||||
Arc::new(OpenFile {
|
Arc::new(OpenFile {
|
||||||
file,
|
file,
|
||||||
|
inode: attributes.ino,
|
||||||
backing,
|
backing,
|
||||||
path,
|
path,
|
||||||
access_mode: flags & libc::O_ACCMODE,
|
access_mode: flags & libc::O_ACCMODE,
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,7 @@ fn posix_lock_owners_use_distinct_backing_lock_domains() {
|
||||||
.write(true)
|
.write(true)
|
||||||
.open(path)
|
.open(path)
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
|
inode: fuser::INodeNo::ROOT,
|
||||||
backing: Classification::Host,
|
backing: Classification::Host,
|
||||||
path: std::path::PathBuf::from("ordinary"),
|
path: std::path::PathBuf::from("ordinary"),
|
||||||
access_mode: libc::O_RDWR,
|
access_mode: libc::O_RDWR,
|
||||||
|
|
@ -566,6 +567,59 @@ fn physical_router_replaces_isolated_regular_file_only_toward_isolated_state() {
|
||||||
session.umount_and_join().unwrap();
|
session.umount_and_join().unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[ignore = "requires a usable host FUSE device"]
|
||||||
|
fn physical_router_preserves_open_files_across_same_backing_renames() {
|
||||||
|
let fixture = Fixture::new();
|
||||||
|
repository(&fixture.workspace);
|
||||||
|
|
||||||
|
let filesystem = IgnoredWorkspace::open(&fixture.workspace, &fixture.isolated, None).unwrap();
|
||||||
|
let mut config = Config::default();
|
||||||
|
config.mount_options.extend([
|
||||||
|
MountOption::RW,
|
||||||
|
MountOption::DefaultPermissions,
|
||||||
|
MountOption::NoDev,
|
||||||
|
MountOption::NoSuid,
|
||||||
|
]);
|
||||||
|
let session = fuser::spawn_mount2(filesystem, &fixture.mountpoint, &config).unwrap();
|
||||||
|
|
||||||
|
fs::write(fixture.mountpoint.join("target"), b"old").unwrap();
|
||||||
|
let old = OpenOptions::new()
|
||||||
|
.read(true)
|
||||||
|
.write(true)
|
||||||
|
.open(fixture.mountpoint.join("target"))
|
||||||
|
.unwrap();
|
||||||
|
fs::write(fixture.mountpoint.join("replacement"), b"replacement").unwrap();
|
||||||
|
let replacement = OpenOptions::new()
|
||||||
|
.read(true)
|
||||||
|
.write(true)
|
||||||
|
.open(fixture.mountpoint.join("replacement"))
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
fs::rename(
|
||||||
|
fixture.mountpoint.join("target"),
|
||||||
|
fixture.mountpoint.join("backup"),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
fs::rename(
|
||||||
|
fixture.mountpoint.join("replacement"),
|
||||||
|
fixture.mountpoint.join("target"),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
fs::remove_file(fixture.mountpoint.join("backup")).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(old.metadata().unwrap().len(), 3);
|
||||||
|
assert_eq!(replacement.metadata().unwrap().len(), 11);
|
||||||
|
assert_eq!(
|
||||||
|
fs::read(fixture.mountpoint.join("target")).unwrap(),
|
||||||
|
b"replacement"
|
||||||
|
);
|
||||||
|
|
||||||
|
drop(old);
|
||||||
|
drop(replacement);
|
||||||
|
session.umount_and_join().unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
fn repository(root: &std::path::Path) {
|
fn repository(root: &std::path::Path) {
|
||||||
fs::create_dir_all(root.join(".git/info")).unwrap();
|
fs::create_dir_all(root.join(".git/info")).unwrap();
|
||||||
fs::create_dir_all(root.join(".git/hooks")).unwrap();
|
fs::create_dir_all(root.join(".git/hooks")).unwrap();
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue