Reputation: 1136
I receive filesystem events from fanotify. Sometimes I want to get an absolute path to a file that's being accessed.
Usually, it's not a problem - fanotify_event_metadata
contains a file descriptor fd
, so I can call readlink
on /proc/self/fd/<fd>
and get my path.
However, if a path exceeds PATH_MAX
readlink
can no longer be used - it fails with ENAMETOOLONG
. I'm wondering if there's a way to get a file path in this case.
Obviously, I can fstat
the descriptor I get from a fanotify and traverse the entire filesystem looking for files with identical device ID and inode number. But this approach is not feasible for me performance-wise (even if I optimize it to ignore paths shorter than PATH_MAX
).
I've tried getting a parent directory by reopening fd
with O_PATH
and calling openat(fd, "..", ...)
. Obviously, that failed because fd
doesn't refer to a directory. I've also tried examining contents of a buffer after a failed readlink
call (hoping it contains partial path). That didn't work either.
So far I've managed to get long paths for files inside the working directory of a process that opened them (fanotify events contain a pid
of a target process, so I can read /proc/<pid>/cwd
and get the path to the root from there). But that is a partial solution.
Is there a way to get an absolute path from a file descriptor without traversing the whole filesystem? Preferably the one that will work with kernel 2.6.32/glibc 2.11.
Update: For the curious. I've figured out why calling readlink("/proc/self/fd/<fd>", ...
with a buffer large enough to store the entire path doesn't work.
Look at the implementation of do_proc_readlink. Notice that it doesn't use provided buffer
directly. Instead, it allocates a single page and uses it as a temporary buffer when it calls d_path. In other words, no matter how large is buffer
, d_path
will always be limited to a size of a page. Which is 4096 bytes on amd64. Same as PATH_MAX
! The -ENAMETOOLONG
itself is returned by prepend when it runs out of mentioned page.
Upvotes: 7
Views: 1645
Reputation: 12668
the limitation of PATH_MAX
births from the fact that the unix (or linux, from now) needs to bind the size of parameters passed to the kernel. There's no limit on how deep a file hierarchy can grow, and always there's the possibility to access all files, independent on how deep they are in the filesystem hierarchy. What is actually limited is the lenght of the string you can pass or receive from the kernel representing a file name. This means you cannot create (because you have to pass the target path) a symlink longer than this length, but you can have easily paths far longer this limit.
When you pass a filename to the kernel, you can do that for two reasons, to name a file (or device, or socket, or fifo, or whatever), to open it, etc. YOu do this and your filename goes first to a routine that converts that path into an inode (which is what the kernel manages actually). That routine begins scanning from two possible point in the filesystem hierarchi. Those points are the inode reference of the root inode and the inode reference of the curren working diretory of a process. The selection of which inode to use as departure inode depends on the presence of a leading /
character at the begining of the path. From this point, up to PATH_MAX
characters will be processed each time, but that can lead us deep enough that we cannot get to the root in one step only...
Suppose you use the path to change your current directory, and do a chdir A/B/C/D/E/.../Z
. Once there, you create new directories and do the same thing, chdir AA/AB/AC/AD/AE/.../AZ
, then chdir BA/BB/BC/BD/...
and so on... there's nothing in the system that forbids you to get so deep in the filesystem (you can try that yourself, I have done and tested before) You can grow to a map that is by far larger than PATH_MAX
. But this only mean that you cannot get there directly from the filesystem root. You can go there in steps, as much as the system allows you, and depending on where you fix you root directory (by means of the chroot(2)
syscall) or your current directory (by means of the chdir(2)
syscall)
probably you have notice (or not) that there's no system call to get your curren working directory path from root... There are several reasons for this:
mknod(2)
system call, if you have access to some hp-ux v6 or old Unix SysV R4 you can create directories with a ...
entry ---pointing to the granparent of a directory or similar things, just being root and knowing how to use the mknod(2)
syscall) The idea is that when two links point to the same inode, which (or both) of then goes to the root, which one is the right path from the root inode to the current dir?PATH_MAX
limit.For these reasons, there's no direct support in the kernel to know the root path to a file. And also there's no way to get the path (and this is what the pwd(1)
command does) than to follow the ..
entry and get to the parent directory and search there a link that gets to the inode number of the current dir... and repeat this until the parent inode is the same as the last inode visited. Only then you'll be in the root directory (your root directory, that is different in general of other processes root directories)
Just try this exercise:
i=0
while [ "$i" -lt 10000 ]
do
mkdir dir-$i
cd dir-$i
i=$(expr "$i" + 1)
done
and see how far you can go from the root directory in your hierarchy.
Another reason to be impossible to get the path to a file from an open descriptor is that you have access only to the inode (the path you used to open(2)
it can have no relationship to the actual root path, as you can use symlinks and relative to the working directory, or changed root dir in between the open call and the time you want to access the path, it can even not exist, as you can have unlink(2)
d it) The inode information has no reference to the path to the inode, as there can be multiple (even millions) paths to a file. In the inode you have only a ref count, which means the number of paths that actually finish on that inode.
Upvotes: 0
Reputation: 179819
readlink
can be used with a link target that's longer than PATH_MAX
. There are two restrictions: the name of the link itself must be shorter than PATH_MAX
(check, "/proc/self/fd/<fd>"
is about 20 characters) and the provided output buffer must be large enough. You might want to call lstat
first to figure out how big the output buffer should be, or just call readlink
repeatedly with growing buffers.
Upvotes: 1