After creating a hardlink on a distributed filesystem of my own via:
% ln f.txt hlf.txt
Neither the original file, f.txt,
nor the hardlink, hlf.txt,
are immediately accessible, e.g. via cat(1)
with ENOENT
returned. A short time later though, both the original file and the hardlink are accessible. Both files can be stat(1)
ed though, which confirms that vnop_getattr
returns success for both files.
Dtruss(1)
indicates it's the open(2)
syscall that fails:
% sudo dtruss -f cat hlf.txt
2038/0x4f68: open("hlf.txt\0", 0x0, 0x0) = -1 Err#2 ;ENOENT
2038/0x4f68: write_nocancel(0x2, "cat: \0", 0x5) = 5 0
2038/0x4f68: write_nocancel(0x2, "hlf.txt\0", 0x7) = 7 0
2038/0x4f68: write_nocancel(0x2, ": \0", 0x2) = 2 0
2038/0x4f68: write_nocancel(0x2, "No such file or directory\n\0", 0x1A) = 26 0
Dtrace(1)ing
my KEXT no longer works on macOS Sequoia, so based on the diagnostics print statements I inserted into my KEXT, the following sequence of calls is observed:
vnop_lookup(hlf.txt) -> EJUSTRETURN ;ln(1)
vnop_link(hlf.txt) -> KERN_SUCCESS ;ln(1)
vnop_lookup(hlf.txt) -> KERN_SUCCESS ;cat(1)
vnop_open(/) ; I expected to see vnop_open(hlf.txt) here instead of the parent directory.
Internally, hardlinks are created in vnop_link
via a call to vnode_setmultipath
with cache_purge_negatives
called on the destination directory.
On macOS Monterey for example, where the same code does result in hardlinks being accessible, the following calls are made:
vnop_lookup(hlf.txt) -> EJUSTRETURN ;ln(1)
vnop_link(hlf.txt) -> KERN_SUCCESS ;ln(1)
vnop_lookup(hlf.txt) -> KERN_SUCCESS ;cat(1)
vnop_open(hlf.txt) -> KERN_SUCCESS ;cat(1)
Not sure how else to debug this.
Perusing the kernel sources for uses of VISHARDLINK
, VNOP_LINK
and vnode_setmultipath
call sites did not clear things up for me.
Any pointers would be greatly appreciated.
So, I can't dig into this in depth, but I can outline where to go from here.
If my vnop_lookup returns the correct vnode_t for the file being looked up, what causes ENOENT to be returned to open(2) in userspace?
First off, if you're not already, stop testing with standard command line tools and replicate this in your own code. One of the critical details here is exactly which syscall returned ENOENT. It's possible/likely that it was "open" but (as far as I can tell), you're assuming that based on the fact that vnop_open was the last vfs hook called. However, it's also possible that another syscall occurred and then returned ENOENT before it called into your driver (assuming it would have called into "you").
How do we track it down?
Once you're sure what syscall returned ENOENT, the next step is to dig into the source. The VFS system is largely open source, so it's basically a matter of starting from whatever known point you have and then following the logic until you find "ENOENT". As a starting point, vfs_subr.c is the entry point for most syscalls (including open).
__
Kevin Elliott
DTS Engineer, CoreOS/Hardware