use anyhow::Result;
use std::path::Path;
pub(super) mod fs_magic {
pub(super) const NFS: i64 = 0x6969;
pub(super) const CIFS: i64 = 0xFF53_4D42;
pub(super) const SMB2: i64 = 0xFE53_4D42;
pub(super) const CEPH: i64 = 0x00c3_6400;
pub(super) const AFS: i64 = 0x6B41_4653;
pub(super) const FUSE: i64 = 0x6573_5546;
}
pub(super) fn reject_remote_fs(path: &Path) -> Result<()> {
let target: &Path = if path.exists() {
path
} else {
path.parent().unwrap_or(Path::new("/"))
};
let sfs = match rustix::fs::statfs(target) {
Ok(s) => s,
Err(_) => return Ok(()),
};
classify_fs_magic(sfs.f_type as i64).map_err(|rejection| {
anyhow::anyhow!(
"{}: filesystem {rejection} Move the lockfile path to a \
local filesystem (tmpfs, ext4, xfs, btrfs, f2fs, bcachefs).",
path.display()
)
})
}
pub(super) fn classify_fs_magic(magic: i64) -> Result<()> {
let (name, reason) = match magic {
fs_magic::NFS => (
"NFS",
"NFSv3 is advisory-only without an NLM peer; NFSv4 byte-range \
locking does not cover flock(2)",
),
fs_magic::CIFS | fs_magic::SMB2 => (
"CIFS/SMB",
"SMB does not emit /proc/locks entries; ktstr cannot enumerate \
peer holders",
),
fs_magic::CEPH => (
"CephFS",
"Ceph MDS does not participate in flock serialization between \
ktstr peers on distinct nodes",
),
fs_magic::AFS => ("AFS", "AFS does not support flock(2)"),
fs_magic::FUSE => (
"FUSE",
"flock reliability depends on the userspace server's op \
implementation",
),
_ => return Ok(()),
};
anyhow::bail!("{name} is not supported for ktstr lockfiles ({reason}).")
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn fuse_magic_matches_linux_magic_h() {
assert_eq!(fs_magic::FUSE, 0x65735546);
}
#[test]
fn afs_magic_matches_in_tree_kafs() {
assert_eq!(fs_magic::AFS, 0x6B414653);
}
#[test]
fn classify_fs_magic_rejects_nfs() {
let err = classify_fs_magic(fs_magic::NFS).unwrap_err();
let msg = format!("{err:#}");
assert!(msg.contains("NFS"), "err={msg}");
assert!(
msg.contains("NFSv3"),
"err must name NFSv3 in reason: {msg}"
);
assert!(
msg.contains("is not supported"),
"err must contain the canonical rejection phrase: {msg}",
);
}
#[test]
fn classify_fs_magic_rejects_cifs_and_smb2() {
let err_cifs = classify_fs_magic(fs_magic::CIFS).unwrap_err();
let err_smb2 = classify_fs_magic(fs_magic::SMB2).unwrap_err();
let cifs_msg = format!("{err_cifs:#}");
let smb2_msg = format!("{err_smb2:#}");
assert!(cifs_msg.contains("CIFS/SMB"));
assert!(smb2_msg.contains("CIFS/SMB"));
assert!(
cifs_msg.contains("/proc/locks"),
"err must cite /proc/locks: {cifs_msg}",
);
assert!(
smb2_msg.contains("/proc/locks"),
"err must cite /proc/locks: {smb2_msg}",
);
assert!(
cifs_msg.contains("is not supported"),
"err must contain the canonical rejection phrase: {cifs_msg}",
);
assert!(
smb2_msg.contains("is not supported"),
"err must contain the canonical rejection phrase: {smb2_msg}",
);
}
#[test]
fn classify_fs_magic_rejects_ceph() {
let err = classify_fs_magic(fs_magic::CEPH).unwrap_err();
let msg = format!("{err:#}");
assert!(msg.contains("CephFS"));
assert!(msg.contains("MDS"), "err must name Ceph MDS: {msg}");
assert!(
msg.contains("is not supported"),
"err must contain the canonical rejection phrase: {msg}",
);
}
#[test]
fn classify_fs_magic_rejects_afs() {
let err = classify_fs_magic(fs_magic::AFS).unwrap_err();
let msg = format!("{err:#}");
assert!(msg.contains("AFS"));
assert!(msg.contains("flock(2)"), "err must cite flock(2): {msg}");
assert!(
msg.contains("is not supported"),
"err must contain the canonical rejection phrase: {msg}",
);
}
#[test]
fn classify_fs_magic_rejects_fuse() {
let err = classify_fs_magic(fs_magic::FUSE).unwrap_err();
let msg = format!("{err:#}");
assert!(msg.contains("FUSE"));
assert!(
msg.contains("userspace server"),
"err must name userspace server: {msg}",
);
assert!(
msg.contains("is not supported"),
"err must contain the canonical rejection phrase: {msg}",
);
}
#[test]
fn classify_fs_magic_accepts_local_filesystems() {
classify_fs_magic(0x01021994).expect("tmpfs accepted");
classify_fs_magic(0xEF53).expect("ext4 accepted");
classify_fs_magic(0x58465342).expect("xfs accepted");
classify_fs_magic(0x9123683E).expect("btrfs accepted");
classify_fs_magic(0xF2F52010).expect("f2fs accepted");
classify_fs_magic(0xCA451A4E).expect("bcachefs accepted");
classify_fs_magic(0xDEAD_BEEF).expect("unknown magic accepted");
}
}