3

In UNIX if I open a file in append mode like

fd = open("filename", O_APPEND);

then given such a file descriptor one can easily find out what flags it was opened with using fcntl:

fcntl(fd, F_GETFL) & O_APPEND

I know that fcntl is not available on Windows, but I wonder if there is some way to determine this. Windows does clearly support an append mode for example when creating a file with CreateFile and passing in the FILE_APPEND_DATA flag.

But if all I have is a handle to an already opened file I cannot for the life of me find a way to determine what access rights were requested when the file was first opened. This question provides a basic recipe for checking the access rights to a specific file, but that doesn't seem to help. I tried it and even if I open a file with read-only mode it still tells me I have the FILE_APPEND_DATA access to the file, if I were to request it. In other words, this method only tells me what access rights the process has to a particular file (as inherited from the user who started the process). It says nothing about what exact access was requested when the file was opened.

This has nothing to do with how Windows keeps track of whether the file should only be appended to. And it's that latter question that I can't find an answer to anywhere. The closest thing I've found is GetFileInformationByHandleEx but after combing through the docs there's not one file attribute that can be returned through that API that suggests "append mode".

Update: To clarify my question a bit better, this question really just applies to the MS VC runtime library--files opened with the POSIX-like functions _open and written to with fwrite and the like. It appears that the native win32 file handles have no notion of "append mode".

Community
  • 1
  • 1
Iguananaut
  • 21,810
  • 5
  • 50
  • 63

4 Answers4

4

For a file that Windows sees as being in "append" mode, this will work:

Use the semi-documented NtQueryInformationFile system call (exported from ntdll.dll) to query for FILE_ACCESS_INFORMATION. That should tell you the access mask that the file was opened with, which should contain FILE_APPEND_DATA.

For a file that the C runtime opens in "append" mode, however, this won't work, because Windows doesn't actually open it in append mode. The way to do this is to somehow translate the file descriptor into the file object, and check the flag there -- but there's no documented way to do this.

You can look at the Visual C++ CRT source code to figure out how to do it... there might be some way to access the array that the descriptor indexes in, but I'm not sure how.
The code in the CRT seems to be _pioinfo(fd)->osfile |= FAPPEND so hopefully that helps.

user541686
  • 205,094
  • 128
  • 528
  • 886
  • 1
    Wow that's obscure. I could not find that at *all*. I'll give it a try and let you know if it can tell me what I need to know. – Iguananaut Aug 30 '13 at 21:33
  • @Iguananaut: Yeah Microsoft doesn't like you using those functions, that's why it's obscure unfortunately. But it's practically completely safe to use them so go right ahead haha. – user541686 Aug 30 '13 at 21:38
  • Shoot--so close, yet so far. Whether I open a file in 'write' or 'append' mode the `FILE_ACCESS_INFORMATION` is exactly identical (both the `FILE_WRITE_DATA` and `FILE_APPEND_DATA` bits are on, but `FILE_READ_DATA` is off). Even though the files still exhibit different behaviors when I write to them... – Iguananaut Aug 30 '13 at 21:59
  • @Iguananaut What differences are there between write and append? – user2246674 Aug 30 '13 at 22:17
  • @user2246674 See http://msdn.microsoft.com/en-us/library/z0kc8e3z.aspx Files opened with `_open` with the `_O_APPEND` flag seek to the end of the file before any write operation, per POSIX. This definitely *works* on Windows but once the file is opened there's no obvious way to query this information, i.e. how it's actually implemented natively in Windows. – Iguananaut Aug 30 '13 at 22:23
  • @Iguananaut But after the seek itself, I wonder if there is a difference on windows (even internally). If not, it may simply discard or not track the flag. Is there any way a "hint" could be obtained from the current seek position? – user2246674 Aug 30 '13 at 22:25
  • @user2246674 No, append mode means the file pointer is *always* positioned at the end of the file before a write operation. It even performs an atomic seek and write to ensure this--it doesn't just seek to the end of the file upon opening the file (in fact it shouldn't--not until a write). So somewhere in Windows this flag is tracked, though perhaps in a way that does not directly map to a single flag. It's just a question of where. – Iguananaut Aug 30 '13 at 22:30
  • Yeah you're right. It seems like `_O_APPEND` doesn't get translated into `FILE_APPEND_DATA` by the runtime, unfortunately. :( So Windows has no idea this file is in append mode; it's the runtime that's keeping track of this. – user541686 Aug 30 '13 at 22:34
  • I didn't realize the C runtime source code was available, but indeed this feature must be unique to the C runtime and is not a feature of the win32 API. This points me in the right direction. – Iguananaut Sep 04 '13 at 21:17
4

FileMode.Append is a figment of the .NET Framework team's imagination. It is not a mode that Windows supports. They added it to make the wrapper for the native winapi CreateFile() function (FileStream) easier to use. FileMode is the enum wrapper for its dwCreationDisposition argument. Which does not have a CREATE_APPEND option.

The most relevant code from the FileStream.Init() method is:

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

In other words, FileMode.Append does not match a valid CreateFile() option and has to be mapped. The file will be opened if exists, created if it doesn't. And it takes care of an extra operation, after opening the file, it will seek to the end of the file. Which you'd of course expect when you append to an existing file. Some additional error checking exists, it also makes sure you opened the file for writing.

So that rather shoots a hole in your quest to detect this back. The GetFileInformationByHandleEx() winapi function is available to recover the file state. The documented function for Mehrdad's undocumented native api call. You can get the dwCreationDisposition argument back from FileDispositionInfo to check that it was OpenOrCreate. That is however not unique, it could have also started from the file being opened with FileMode.OpenOrCreate from the client code. Rare, but possible.

What you've lost is the memory of it seeking to the end of the file. You could use the FileStream.Position property but that of course will be affected by any writes in the mean time. If this really matters then you do have to preserve the FileMode value that was used.

Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
  • You probably already know this but I'll mention it anyway... `GetFileInformationByHandleEx` is documented but only partially. It doesn't mention `FileAccessInformation` as a valid option, but `ZwQueryInformationFile` does. And `GetFileInformationByHandleEx` is only for Vista+, whereas `ZwQueryInformationFile` works on XP too. – user541686 Aug 30 '13 at 23:55
  • There's a scarcely-documented flag to CreateFile called FILE_APPEND_DATA, used in example code here: https://msdn.microsoft.com/en-us/library/windows/desktop/aa363778(v=vs.85).aspx. Who knows why the .NET team didn't use it, but it's there. – jsalter Jan 21 '15 at 16:42
3

Self-answering, with thanks to @mehrdad and @HansPassant for pointing me in the right direction. Indeed, MSVCRT exports an array of arrays of a structure called ioinfo in which it stores information about each open file handle in the process.

The exact contents of the structure depend on the VC version and some defines, but in general its first two members are defined:

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

The osfile member is the interesting one--if the file is opened with _O_APPEND the flag called FAPPEND defined as 0x20 is set on this.

I wrote a little utility function in Python based on similar code in CPython's posixmodule that can perform this check: https://gist.github.com/embray/6444262

Iguananaut
  • 21,810
  • 5
  • 50
  • 63
  • Anyone mind explaining why this was modded down even though it supplies an exact solution to my problem? – Iguananaut Sep 05 '13 at 15:39
  • 1
    The Python C API function _PyVerify_fd in at least python 3 (didn't check 2.x) also uses __pioinfo to verify that a given file descriptor is valid. – jsalter Jan 22 '15 at 11:35
  • @jsalter Confirmed, Python 2 mucks around with `__pioinfo` as well, in Modules/posixmodule.c. I feel like it's the sort of thing where it's like, yeah, it's not a public API, but it's not like the implementation is going to change any time soon if ever, at least for a known version of the MSCRT. – Iguananaut Jan 22 '15 at 15:36
2

(I know this is a very old question from 2013, but I want to share my solution for anyone who want to get file mode [READ, WRITE, APPEND] directly from Windows File Handle)

The NtQueryInformationFile api actually can give you enough information to determine what mode the file is in.

Let's use the following code for demonstration:

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;

It's actually very simple:

  • If the file was opened using GENERIC_READ (or FILE_READ_DATA), then the FILE_READ_DATA flag is set, otherwise it's not set;
  • If the file was opened using GENERIC_WRITE (or FILE_WRITE_DATA), then the FILE_WRITE_DATA flag is set, otherwise it's not set;
  • If the file was opened using GENERIC_WRITE or FILE_APPEND_DATA, then the FILE_APPEND_DATA flag is set, otherwise it's not set;

Read mode itself will not turn on the FILE_APPEND_DATA flag, and append mode itself will not turn on the FILE_WRITE_DATA flag.

I hope this could help someone else.

Meigyoku Thmn
  • 69
  • 1
  • 7
  • 1
    Awesome, thanks :) In retrospect, since I last wrote this question I've learned a lot more about how Cygwin works, and I might have been able to find this by digging into the Cygwin sources, but I'm not sure. Seems legit anyways. – Iguananaut Aug 22 '20 at 13:03