【问题标题】:How can I determine if a file was opened for append on Windows?如何确定文件是否已在 Windows 上打开以进行追加?
【发布时间】:2013-08-30 21:28:12
【问题描述】:

在 UNIX 中,如果我以附加模式打开文件,例如

fd = open("filename", O_APPEND);

然后给定这样一个文件描述符,我们可以很容易地找出使用fcntl打开它的标志:

fcntl(fd, F_GETFL) & O_APPEND

我知道fcntl 在 Windows 上不可用,但我想知道是否有某种方法可以确定这一点。 Windows 确实支持附加模式,例如在使用 CreateFile 创建文件并传入 FILE_APPEND_DATA 标志时。

但是,如果我只有一个已打开文件的句柄,那么我终其一生都无法找到一种方法来确定首次打开文件时请求的访问权限。 This question 提供了检查对特定文件的访问权限的基本方法,但这似乎无济于事。我试过了,即使我以只读模式打开一个文件,它仍然告诉我我有FILE_APPEND_DATA access 到该文件,如果我要请求它。换句话说,这个方法只告诉我进程对特定文件的访问权限(继承自启动进程的用户)。它没有说明打开文件时请求的确切访问权限。

这与 Windows 如何跟踪文件是否应仅附加到无关。正是后一个问题,我在任何地方都找不到答案。我发现的最接近的东西是GetFileInformationByHandleEx,但在梳理完文档后,没有一个文件属性可以通过该 API 返回建议“附加模式”。

更新:为了更好地澄清我的问题,这个问题实际上只适用于 MS VC 运行时库——使用类似 POSIX 的函数 _open 打开并使用 @987654331 写入的文件@ 之类的。似乎本机 win32 文件句柄没有“附加模式”的概念。

【问题讨论】:

    标签: winforms file winapi msvcrt


    【解决方案1】:

    对于 Windows 认为处于“附加”模式的文件,这将起作用:

    使用半文档化的NtQueryInformationFile 系统调用(从ntdll.dll 导出)查询FILE_ACCESS_INFORMATION。这应该会告诉您打开文件时使用的访问掩码,其中应该包含FILE_APPEND_DATA

    对于 C 运行时 以“追加”模式打开的文件,但是,这不起作用,因为 Windows 实际上并没有以追加模式打开它。这样做的方法是以某种方式将文件描述符转换为文件对象,并检查那里的标志——但没有记录的方法可以做到这一点。

    您可以查看 Visual C++ CRT 源代码以了解如何操作...可能有一些方法可以访问描述符索引的数组,但我不确定如何。
    CRT 中的代码似乎是 _pioinfo(fd)->osfile |= FAPPEND,希望对您有所帮助。

    【讨论】:

    • 哇,这太晦涩难懂了。我根本找不到那个。我会试一试,如果它可以告诉我我需要知道的内容,请告诉你。
    • @Iguananaut:是的,微软不喜欢你使用这些功能,这就是不幸的原因。但是使用它们实际上是完全安全的,所以继续吧哈哈。
    • 射击——如此接近,却又如此遥远。无论我以“写入”还是“附加”模式打开文件,FILE_ACCESS_INFORMATION 完全相同(FILE_WRITE_DATAFILE_APPEND_DATA 位都打开,但 FILE_READ_DATA 关闭)。即使当我写这些文件时它们仍然表现出不同的行为......
    • @Iguananaut write 和 append 有什么区别?
    • @user2246674 查看msdn.microsoft.com/en-us/library/z0kc8e3z.aspx 使用_open 打开的文件,使用_O_APPEND 标志在任何写操作之前根据POSIX 搜索到文件末尾。这在 Windows 上绝对有效,但是一旦打开文件,就没有明显的方法来查询这些信息,即它是如何在 Windows 中实际实现的。
    【解决方案2】:

    FileMode.Append 是 .NET Framework 团队的想象。它不是 Windows 支持的模式。他们添加它是为了使本机 winapi CreateFile() 函数 (FileStream) 的包装器更易于使用。 FileMode 是其 dwCreationDisposition 参数的枚举包装器。没有 CREATE_APPEND 选项。

    FileStream.Init() 方法中最相关的代码是:

       bool seekToEnd = (mode==FileMode.Append);
       // Must use a valid Win32 constant here...
       if (mode == FileMode.Append)
           mode = FileMode.OpenOrCreate;
    

    换句话说,FileMode.Append 与有效的 CreateFile() 选项不匹配,必须进行映射。该文件将在存在时打开,如果不存在则创建。它负责一个额外操作,打开文件后,它会寻找文件的末尾。当您附加到现有文件时,您当然会期望它。存在一些额外的错误检查,它还确保您打开文件进行写入。

    因此,这反而会在您检测这个背部的过程中留下一个漏洞。 GetFileInformationByHandleEx() winapi 函数可用于恢复文件状态。 Mehrdad 的未记录本机 api 调用的记录函数。您可以从 FileDispositionInfo 中获取 dwCreationDisposition 参数,以检查它是否为 OpenOrCreate。然而,这不是唯一的,它也可以从客户端代码使用 FileMode.OpenOrCreate 打开的文件开始。罕见,但可能。

    你失去的是它寻找文件末尾的记忆。您可以使用 FileStream.Position 属性,但同时该属性当然会受到任何写入的影响。如果这真的很重要,那么您确实必须保留所使用的 FileMode 值。

    【讨论】:

    • 您可能已经知道这一点,但我还是会提到它...GetFileInformationByHandleEx 已记录但只是部分记录。它没有提到 FileAccessInformation 作为有效选项,但 ZwQueryInformationFile 确实如此。 GetFileInformationByHandleEx 仅适用于 Vista+,而 ZwQueryInformationFile 也适用于 XP。
    • CreateFile 有一个鲜为人知的标志,名为 FILE_APPEND_DATA,在此处的示例代码中使用:msdn.microsoft.com/en-us/library/windows/desktop/…。谁知道 .NET 团队为什么不使用它,但它就在那里。
    【解决方案3】:

    自我回答,感谢@mehrdad 和@HansPassant 为我指明了正确的方向。实际上,MSVCRT 会导出一个名为 ioinfo 的结构的数组数组,其中存储有关进程中每个打开文件句柄的信息。

    结构的确切内容取决于 VC 版本和一些定义,但通常定义它的前两个成员:

    typedef struct {
            intptr_t osfhnd;    /* underlying OS file HANDLE */
            char osfile;        /* attributes of file (e.g., open in text mode?) */
            ....
    } ioinfo;
    

    osfile 成员是一个有趣的成员 - 如果使用 _O_APPEND 打开文件,则在此设置名为 FAPPEND 的标志,定义为 0x20

    我基于 CPython 的 posixmodule 中的类似代码在 Python 中编写了一个小实用函数,可以执行此检查:https://gist.github.com/embray/6444262

    【讨论】:

    • 任何人介意解释为什么它被修改了,即使它为我的问题提供了一个精确的解决方案?
    • 至少在 python 3 中的 Python C API 函数 _PyVerify_fd(没有检查 2.x)也使用 __pioinfo 来验证给定的文件描述符是否有效。
    • @jsalter 已确认,Python 2 在 Modules/posixmodule.c 中也与 __pioinfo 混在一起。我觉得它是那种东西,是的,它不是一个公共 API,但它的实现不会很快改变,至少对于已知版本的 MSCRT。
    【解决方案4】:

    (我知道这是 2013 年以来的一个非常古老的问题,但我想与任何想要直接从 Windows 文件句柄获取文件模式 [READ, WRITE, APPEND] 的人分享我的解决方案)

    NtQueryInformationFile api 实际上可以为您提供足够的信息来确定文件处于什么模式。

    我们用下面的代码来演示一下:

    IO_STATUS_BLOCK statusBlock;
    FILE_ACCESS_INFORMATION accessInfo;
    
    // You have to import this API by yourself using LoadLibary and GetProcAddress
    auto status = NtQueryInformationFile(
       yourFileHandle, &statusBlock, &accessInfo,
       sizeof(FILE_ACCESS_INFORMATION), (FILE_INFORMATION_CLASS)8);
    
    auto flags = accessInfo.AccessFlags;
    
    // true if in read mode, otherwise false
    auto isRead = (FILE_READ_DATA & flags) != 0;
    // true if in write mode, otherwise false
    auto isWrite = (FILE_WRITE_DATA & flags) != 0;
    // true if in write mode or append mode 
    // (I think these 2 modes are mutual exclusive), otherwise false
    auto isAppend = (FILE_APPEND_DATA & flags) != 0;
    

    其实很简单:

    • 如果文件是使用 GENERIC_READ(或 FILE_READ_DATA)打开的,则设置 FILE_READ_DATA 标志,否则不设置;
    • 如果文件是使用 GENERIC_WRITE(或 FILE_WRITE_DATA)打开的,则设置 FILE_WRITE_DATA 标志,否则不设置;
    • 如果文件是使用 GENERIC_WRITE FILE_APPEND_DATA 打开的,则设置 FILE_APPEND_DATA 标志,否则不设置;

    读取模式本身不会开启 FILE_APPEND_DATA 标志,附加模式本身也不会开启 FILE_WRITE_DATA 标志。

    我希望这可以帮助其他人。

    【讨论】:

    • 太棒了,谢谢 :) 回想起来,自从我上次写这个问题以来,我已经了解了更多关于 Cygwin 如何工作的知识,我可能已经能够通过深入研究 Cygwin 资源来找到这一点,但我不确定。反正看起来是合法的。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-08-03
    • 2013-08-15
    • 1970-01-01
    • 2012-12-01
    • 1970-01-01
    相关资源
    最近更新 更多