当文件路径超过PATH_MAX时,如何从文件描述符获取路径

7
我从fanotify中接收文件系统事件。 有时,我想获取正在访问的文件的绝对路径。
通常,这不是问题-fanotify_event_metadata包含文件描述符fd,因此我可以在/proc/self/fd/<fd>上调用readlink并获得路径。
但是,如果路径超过PATH_MAX,则无法再使用readlink,它将失败并出现ENAMETOOLONG。 我想知道是否有一种方法可以在这种情况下获取文件路径。
显然,我可以fstat来自fanotify的描述符,并遍历整个文件系统以查找具有相同设备ID和inode编号的文件。 但是,就性能而言,这种方法对我来说不可行(即使我优化它以忽略长度小于PATH_MAX的路径)。
我尝试通过使用O_PATH重新打开fd并调用openat(fd,"..",...)来获取父目录。 很明显,这失败了,因为fd不是指向目录的。 我还尝试检查readlink调用失败后缓冲区的内容(希望它包含部分路径)。 这也行不通。
到目前为止,我已经成功为打开它们的进程的工作目录内部的文件获得了长路径(fanotify事件包含目标进程的pid,因此我可以读取/proc/<pid>/cwd并从那里获取到根的路径)。 但这是一个局部解决方案。
是否有一种方法可以从文件描述符中获取绝对路径而无需遍历整个文件系统? 最好与kernel 2.6.32/glibc 2.11兼容。 更新: 对于好奇者。 我已经弄清楚为什么使用足够大以存储整个路径的缓冲区调用readlink("/proc/self/fd/<fd>",...不起作用。
看看do_proc_readlink的实现。注意它不直接使用提供的buffer。相反,它分配一个单独的页面,并在调用d_path时将其用作临时缓冲区。换句话说,无论buffer有多大,d_path始终会被限制为页面大小。在amd64上是4096字节。与PATH_MAX相同!当它用完所提到的页面时,prepend本身将返回-ENAMETOOLONG

最终解析的路径是否不包含链接和/或 ./.. 元素,长度超过了 PATH_MAX?如果没有,realpath() 可能适合您:http://man7.org/linux/man-pages/man3/realpath.3.html - Andrew Henle
1
路径不应该比PATH_MAX字符短吗?这听起来很奇怪。 - unwind
@unwind 不行。我不能代表所有的文件系统说话,但在像ext4这样的文件系统上,你几乎可以创建无限嵌套的目录,直到用完inode。 - Nikita Kakuev
@NikitaKakuev realpath需要首先提供路径。 readlink()也是如此。你是如何让工作的? - Andrew Henle
@AndrewHenle 正如我在答案中所述 - 通过调用readlink/proc/self/fd/<fd>上。 - Nikita Kakuev
显示剩余7条评论
2个回答

1

readlink可以用于具有比PATH_MAX更长的链接目标。有两个限制:链接本身的名称必须短于PATH_MAX(检查,“/proc/self/fd/<fd>”大约为20个字符),并且提供的输出缓冲区必须足够大。您可能希望首先调用lstat以确定输出缓冲区的大小,或者只需使用不断增长的缓冲区重复调用readlink


那是我尝试的第一件事情(它是由man 2 readlink建议的),但它没有起作用。尽管缓冲区大小足够,但对于较长的路径,我会得到ENAMETOOLONG错误。 - Nikita Kakuev
@NikitaKakuev:只是为了再确认一下:你确实将实际缓冲区大小作为第三个参数传递了吗? - MSalters

0

PATH_MAX 的限制源于 Unix(或 Linux,从现在开始)需要绑定传递给内核的参数大小。文件层次结构可以无限制地增长,并且始终有可能访问所有文件,而不管它们在文件系统层次结构中的深度如何。实际上受到限制的是你可以传递或接收的表示文件名的内核字符串的长度。这意味着你不能创建(因为你必须传递目标路径)比此长度更长的符号链接,但你可以轻松地拥有远远超过此限制的路径。

当您将文件名传递给内核时,可能有两个原因,一个是为了命名文件(或设备、套接字、FIFO等),另一个是为了打开它。您这样做后,您的文件名首先会传递到一个将该路径转换为inode的例程中(实际上内核管理的内容)。该例程从文件系统层次结构中的两个可能点开始扫描。这些点是根inode的inode引用和进程当前工作目录的inode引用。选择使用哪个inode作为出发inode取决于路径开头是否有前导/字符。从这一点开始,每次最多处理PATH_MAX个字符,但这可能会使我们深入到无法仅通过一步到达根目录的地步...
假设您使用路径更改当前目录,并执行chdir A/B/C/D/E/.../Z。一旦在那里,您可以创建新目录并执行相同的操作,chdir AA/AB/AC/AD/AE/.../AZ,然后chdir BA/BB/BC/BD/...等等...系统中没有禁止您深入文件系统的限制(您可以自行尝试,我以前已经完成并测试过)。您可以扩展到比PATH_MAX远大得多的地图。但这只意味着您无法直接从文件系统根目录进入那里。您可以按步骤前往,只要系统允许,并根据您修复的根目录(通过chroot(2)系统调用)或当前目录(通过chdir(2)系统调用)来定位。

也许您已经注意到(或者没有注意到)从根目录获取当前工作目录路径的系统调用不存在...这可能有几个原因:

  • 根Inode和当前工作Inode是两个本地到进程的概念。同一系统中的两个进程可以具有不同的工作目录,也可以具有不同的根目录,直到它们无法共享任何共同之处,也没有从一个目录到达另一个目录的方式。
  • Inode路径可能会产生歧义。对于目录来说,这并不是真的,因为不允许两个硬链接指向相同的目录inode(在早期的Unix中,需要使用mknod(2)系统调用创建目录,如果您可以访问某些hp-ux v6或旧的Unix SysV R4,您可以创建带有...条目的目录,指向目录的祖先或类似的事情,只要是根和知道如何使用mknod(2)系统调用)。其想法是当两个链接指向相同的inode时,哪个链接(或两个链接)到达根目录,哪个链接是从根inode到当前目录的正确路径?
  • 当前Inode和根可以被分隔开,距离足够远,无法适应PATH_MAX限制。
  • 在到达根的过程中,可能涉及到几个不同的文件系统(和文件系统类型)。因此,这不是仅凭存储在磁盘中的数据就能得到的东西,您必须了解挂载表。
由于这些原因,内核中没有直接支持知道文件根路径的方法。也没有获取路径的方式(这就是pwd(1)命令的作用),除了跟随..并进入父目录并在那里搜索链接以获得当前目录的inode号之外,并重复此过程,直到父节点inode与最后访问的inode相同。只有这样,您才会进入根目录(您的根目录通常与其他进程的根目录不同)
只需尝试此练习:
i=0
while [ "$i" -lt 10000 ]
do
    mkdir dir-$i
    cd dir-$i
    i=$(expr "$i" + 1)
done

看看你可以从根目录的层次结构中走多远。

注意事项1

无法从打开的描述符获取文件路径的另一个原因是,您只能访问inode(用于open(2)的路径可能与实际根路径没有关系,因为您可以使用符号链接和相对于工作目录或更改根目录在打开调用之间和您想要访问路径的时间之间,它甚至可能不存在,因为您可以unlink(2)它)。 inode信息没有参考inode的路径,因为可以有多个(甚至数百万个)文件路径。在inode中,您只有一个ref计数,这意味着实际上完成该inode的路径数量。


那是一个有趣的观点。但请查看我的问题更新。事实证明,我并没有遇到“PATH_MAX的限制”。问题在于procfsreadlink实现总是使用单页缓冲区。如果路径不适合此缓冲区,则返回-ENAMETOOLONG。如果do_proc_readlink分配了更大的缓冲区,则即使路径大于PATH_MAX,我也会获得完整路径。 - Nikita Kakuev
单页缓冲区是什么意思?系统调用的缓冲区不一定要对齐到页面边界,因此缓冲区很容易跨越两个连续的页面(只需两个字节的路径,即一个字符的文件名和最后的'\0'字符,您就可以获得使用两个系统页面的路径)。 - Luis Colorado

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接