具有自己文件偏移量的重复文件描述符

15
如何从现有的文件描述符创建一个新的文件描述符,以使新描述符不共享文件表中相同的内部文件结构/条目?具体而言,新旧文件描述符之间不应共享文件偏移量(最好也不共享权限、分享和模式)等属性。
在Windows和Linux下,dup()函数将复制文件描述符,但两个描述符仍指向进程文件表中的同一文件结构。任何一个描述符上的寻址调整都会对其他描述符的位置进行调整。
注意:
我已经得到了关于Windows和Linux的答案,并经常修改问题,这使得人们很难回答。我将调整我的票数并接受最干净的覆盖Windows和Linux的答案。对此我感到抱歉,我还是比较陌生stackoverflow的范式。感谢大家提供的非常好的答案!
3个回答

13
所以基本上,您真正想要的是获得一个文件描述符,并重新打开相同的文件,以获取单独的位置、共享、模式等。而且您想在Windows上执行此操作(其中“文件描述符”基本上是一个外部对象,不是直接由操作系统或运行时库使用的东西)。
令人惊讶的是,至少在MS VC++中有一种方法可以做到这一点。除了两个步骤之外,所有步骤都仅使用Win32 API,因此将其移植到其他编译器/库应该相当合理(我认为大多数都提供了这两个函数的版本)。这些函数用于将类Unix文件描述符转换为本机Win32文件句柄,并将本机Win32文件句柄转换回类Unix文件描述符。
1. 使用_get_osfhandle()将文件描述符转换为本机文件句柄 2. 使用GetFileInformationByHandleEx(FILE_NAME_INFO)获取文件的名称 3. 使用CreateFile打开该文件的新句柄 4. 使用_open_osfhandle()为该句柄创建文件描述符
我们现在拥有一个新的文件描述符,它指向相同的文件,但具有自己的权限、位置等。
在你的问题末尾,你似乎也想要"权限",但这看起来没有什么实际意义——权限附加到文件本身,而不附加到文件的打开方式,因此打开或重新打开文件对文件的权限没有影响。如果你真的想知道,可以使用GetFileInformationByHandle来获取,但是请注意,Windows中的文件权限与Unix中的(传统)文件权限相差很大。Unix对所有文件都有拥有者/组/世界权限,大多数系统也具有ACL(虽然它们工作的方式有所不同)。Windows要么根本没有权限(例如,FAT或FAT32上的文件),要么使用ACL(例如,在NTFS上的文件),但没有真正相当于Unix上大多数人习惯的传统拥有者/组/世界权限的东西。
也许你使用"权限"指的是文件是用于读取、写入还是两者兼备。获取它比之前提到的任何内容都要麻烦得多。问题在于它的大部分在库中,而不是Win32中,因此可能没有办法做到在编译器之间移植。在MS VC++ 9.0 SP1中(不能保证适用于任何其他编译器),你可以这样做:
#include <stdio.h>

int get_perms(int fd) {
    int i;
 FILE * base = __iob_func();

    for (i=0; i<_IOB_ENTRIES; i++) 
        if (base[i]._file == fd)
            return base[i]._flag;     // we've found our file
    return 0; // file wasn't found.
}

由于这涉及到一些探险,我写了一个快速测试来验证它是否真正起作用:

#ifdef TEST
#include <io.h>

void show_perms(int perms, char const *caption) { 
 printf("File opened for %s\n", caption);
 printf("Read permission = %d\n", (perms & _IOREAD)!=0);
 printf("Write permission = %d\n", (perms & _IOWRT)!=0);
}

int main(int argc, char **argv) { 
 FILE *file1, *file2;
 int perms1, perms2;

 file1=fopen(argv[1], "w");
 perms1 = get_perms(_fileno(file1));
 fclose(file1);

 file2=fopen(argv[1], "r");
 perms2 = get_perms(_fileno(file2));
 fclose(file2);

 show_perms(perms1, "writing");
 show_perms(perms2, "reading");
 return 0;
}
#endif

结果似乎表明成功:

File opened for writing
Read permission = 0
Write permission = 1
File opened for reading
Read permission = 1
Write permission = 0

您可以将返回的标志与stdio.h中定义的_IOREAD、_IOWRT和_IORW进行测试。尽管我之前有所警告,但我应该指出,我认为(虽然我当然不能保证)库的这个部分相当稳定,因此真正的重大更改可能非常小。

然而,在另一个方向上,它基本上不可能与任何其他库一起使用。它可能会(但肯定不保证)与使用MS库的其他编译器配合使用,例如Intel、MinGW或Comeau使用MS VC++作为其后端。其中,我认为最有可能起作用的是Comeau,最不可能起作用的是MinGW(但这只是猜测;很可能它无法与它们中的任何一个一起使用)。

  1. 需要可再发行的Win32 FileID API Library

一段很好的答案。我之前并不知道打开文件会防止它被unlink或者重命名,即使完全共享。这就意味着当文件描述符或HANDLE仍在指向该路径时,可以安全地获取与句柄相关联的文件名,因为它不会改变。 - Matt Joiner
@alaamh:你看过其他答案了吗?其中有几个讨论了如何在Linux上做同样的事情。 - Jerry Coffin

3
因此,我建议您多了解一下。 dup()和相关功能用于在文件描述符表中创建一个重复值,并指向开放文件表中的同一条目。这旨在具有相同的偏移量。如果调用open(),则会在打开文件表中创建一个新条目。
创建文件描述符的副本并使其在打开文件表中具有不同的偏移量是没有意义的(这似乎与“副本”一词的含义相矛盾)。
实际上,我不确定您的问题是什么。但是,您可以读取:/proc/self/fd/[descriptor]并获得用于打开该文件描述符的字符串; 请记住,这可能会提供一些陷阱,其中一些您在调用open()时已经注意到。
也许您可以进一步解释,我可以尝试更新以提供帮助。

@BobbyShaftoe,你重申了我的问题:P我需要的是打开/proc/self/fd/[descriptor]的行为,但更便携和面向系统调用的方法更可取。 - Matt Joiner
1
@Anacrolix,不是真的。你编辑了它并添加了那一部分。:) 无论如何,恐怕没有这样的东西,肯定不是系统调用,也不是真正的可移植和可靠的。 - BobbyShaftoe
@BobbyShaftoe:你可能会注意到问题已经改为“仅限Windows”,这对于那些在没有这样的限定条件下回答问题的人来说是完全不公平的。 - Jonathan Leffler
@Jonathan Leffler,我明白了。是的,我同意。我一直认为这是投票/编辑系统中奇怪的一个方面。你可以因为之前的编辑很好而得到很多赞,但是后来却因为同样的答案而得到很多踩。 - BobbyShaftoe
我的错,大家请看我上面的注释。所有的功劳归给Bobby,他的Linux回答非常出色。 - Matt Joiner

0

为什么不使用open()或在Windows上使用CreateFile()打开文件的第二个实例呢?这样可以给你不同访问权限和单独偏移量的自由。

当然,这种方法的缺点是你不能独占地打开文件,但它可以非常简单地解决你的问题。


4
我对“重新打开”一个文件句柄而无其他信息感兴趣。在Linux上,原始文件不能保证已移动或删除。在Windows上,经过一些测试,似乎只要有人持有文件句柄,就无法操作文件系统条目。 - Matt Joiner

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