要确定文件F是否位于目录D中,首先需要确定其设备号和inode编号(struct stat的st_dev和st_ino成员)的属性D.
然后stat F来确定它是否是一个目录。如果没有,请调用basename来确定包含它的目录的名称。将G设置为该目录的名称。如果F已经是目录,则设置G = F。
现在,当且仅当G在D之内时,F在D之内。接下来我们有一个循环。
while (1) {
if (samefile(d_statinfo.d_dev, d_statinfo.d_ino, G)) {
return 1; // F was within D
} else if (0 == strcmp("/", G) {
return 0; // F was not within D.
}
G = dirname(G);
}
的samefile功能很简单:
int samefile(dev_t ddev, ino_t dino, const char *path) {
struct stat st;
if (0 == stat(path, &st)) {
return ddev == st.st_dev && dino == st.st_no;
} else {
throw ...; // or return error value (but also change the caller to detect it)
}
}
这将在POSIX文件系统的工作。但是许多文件系统不是POSIX。需要注意的问题包括:
- 设备/ inode不唯一的文件系统。一些FUSE文件系统就是这样的例子;当底层文件系统没有它们时,它们有时会构成inode数字。他们不应该重新使用inode号码,但是一些FUSE文件系统有错误。
- 破坏的NFS实现。在某些系统上,所有的NFS文件系统都有相同的设备编号。如果它们通过服务器上存在的inode号码,这可能会导致问题(尽管我从来没有在实践中看到它发生过)。
- Linux绑定挂载点。如果
/a
是/b
的绑定挂载,则/a/1
正确地看起来在/a
内,但是对于上面的实现,/b/1
也似乎在/a
内。我认为这可能是正确的答案。但是,如果这不是您喜欢的结果,则可以通过更改return 1
大小写以调用strcmp()
来比较路径名,从而轻松修复此问题。然而,为了这个工作,您需要先在F和D上拨打realpath
开始。realpath
调用可能非常昂贵(因为它可能需要多次点击磁盘)。
- 特殊路径
//foo/bar
。 POSIX允许以//
开头的路径名称是特殊的,这种方式有点不明确。实际上,我忘记了POSIX提供的关于语义的确切级别的保证。我认为POSIX允许//foo/bar
和//baz/ugh
引用同一个文件。设备/ inode检查应该仍然适合您,但您可能会发现它不会(也就是说,您可能会发现//foo/bar
和//baz/ugh
可以引用相同的文件,但具有不同的设备/ inode编号)。
这个回答假设我们开始与F和D.如果不能保证这一点,你可能需要做使用realpath()
和getcwd()
一些转换的绝对路径。如果当前目录的名称长于PATH_MAX
(这肯定会发生),这将会成为问题。
我认为这是'chroot(2)'发明的原因! –
@Carl Norum:如果你给某人有限的shell权限,chroot会更好。如果你不想限制你创建的程序的访问权限,那么比chroot有更好的选择。 – Dani