mirror of
https://github.com/fish-shell/fish-shell.git
synced 2024-11-23 08:55:52 +08:00
Implement FileID in Rust
FileID tracks a File's identity, including its inode, device, and creation and modification times.
This commit is contained in:
parent
b435fc4539
commit
a20985c738
85
fish-rust/src/wutil/fileid.rs
Normal file
85
fish-rust/src/wutil/fileid.rs
Normal file
|
@ -0,0 +1,85 @@
|
|||
use crate::wutil::{wstat, wstr};
|
||||
use std::cmp::Ordering;
|
||||
use std::fs::{File, Metadata};
|
||||
use std::os::fd::RawFd;
|
||||
|
||||
use std::os::fd::{FromRawFd, IntoRawFd};
|
||||
#[cfg(target_os = "linux")]
|
||||
use std::os::linux::fs::MetadataExt;
|
||||
#[cfg(target_os = "macos")]
|
||||
use std::os::macos::fs::MetadataExt;
|
||||
#[cfg(not(any(target_os = "macos", target_os = "linux")))]
|
||||
use std::os::unix::fs::MetadataExt;
|
||||
|
||||
/// Struct for representing a file's inode. We use this to detect and avoid symlink loops, among
|
||||
/// other things. While an inode / dev pair is sufficient to distinguish co-existing files, Linux
|
||||
/// seems to aggressively re-use inodes, so it cannot determine if a file has been deleted (ABA
|
||||
/// problem). Therefore we include richer information.
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct FileId {
|
||||
pub device: u64,
|
||||
pub inode: u64,
|
||||
pub size: u64,
|
||||
pub change_seconds: i64,
|
||||
pub change_nanoseconds: i64,
|
||||
pub mod_seconds: i64,
|
||||
pub mod_nanoseconds: i64,
|
||||
}
|
||||
|
||||
impl FileId {
|
||||
pub fn from_stat(buf: Metadata) -> Self {
|
||||
// These "into()" calls are because the various fields have different types
|
||||
// on different platforms.
|
||||
#[allow(clippy::useless_conversion)]
|
||||
FileId {
|
||||
device: buf.st_dev(),
|
||||
inode: buf.st_ino(),
|
||||
size: buf.st_size(),
|
||||
change_seconds: buf.st_ctime().into(),
|
||||
change_nanoseconds: buf.st_ctime_nsec().into(),
|
||||
mod_seconds: buf.st_mtime().into(),
|
||||
mod_nanoseconds: buf.st_mtime_nsec().into(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn older_than(&self, rhs: &FileId) -> bool {
|
||||
match (self.change_seconds, self.change_nanoseconds)
|
||||
.cmp(&(rhs.change_seconds, rhs.change_nanoseconds))
|
||||
{
|
||||
Ordering::Less => true,
|
||||
Ordering::Equal | Ordering::Greater => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub const INVALID_FILE_ID: FileId = FileId {
|
||||
device: u64::MAX,
|
||||
inode: u64::MAX,
|
||||
size: u64::MAX,
|
||||
change_seconds: i64::MIN,
|
||||
change_nanoseconds: -1,
|
||||
mod_seconds: i64::MIN,
|
||||
mod_nanoseconds: -1,
|
||||
};
|
||||
|
||||
/// Get a FileID corresponding to a raw file descriptor, or INVALID_FILE_ID if it fails.
|
||||
pub fn file_id_for_fd(fd: RawFd) -> FileId {
|
||||
// Safety: we just want fstat(). Rust makes this stupidly hard.
|
||||
// The only way to get fstat from an fd is to use a File as an intermediary,
|
||||
// but File assumes ownership; so we have to use into_raw_fd() to release it.
|
||||
let file = unsafe { File::from_raw_fd(fd) };
|
||||
let res = file
|
||||
.metadata()
|
||||
.map(FileId::from_stat)
|
||||
.unwrap_or(INVALID_FILE_ID);
|
||||
let fd2 = file.into_raw_fd();
|
||||
assert_eq!(fd, fd2);
|
||||
res
|
||||
}
|
||||
|
||||
/// Get a FileID corresponding to a path, or INVALID_FILE_ID if it fails.
|
||||
pub fn file_id_for_path(path: &wstr) -> FileId {
|
||||
wstat(path)
|
||||
.map(FileId::from_stat)
|
||||
.unwrap_or(INVALID_FILE_ID)
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
pub mod encoding;
|
||||
pub mod errors;
|
||||
pub mod fileid;
|
||||
pub mod gettext;
|
||||
pub mod printf;
|
||||
pub mod wcstod;
|
||||
|
|
Loading…
Reference in New Issue
Block a user