I have created a small framework that provides a unified API to multiple file systems/APIs (namely Win32, Posix, NFS). Said API is somewhat similar to Posix -- to access a file you need to "open" it providing a hint for intended purpose (r
, w
or rw
). Something like open_file("/abc/log.txt", access::rw)
.
Supporting Win32 API in this framework gives me a headache due to "declarative" nature of Win32 -- you are supposed to know upfront which operations you plan to perform on given handle and pass related dwDesiredAccess
into related (Nt)CreateFile()
call. Unfortunately framework has no idea what operation client is going to perform (i.e. change-owner, write-attributes, etc) besides generic r/w/rw
hint. And I am not willing to let Win32 concepts to leak into my framework (i.e. I don't like adding dwDesiredAccess
equivalent into my open_file()
).
Here is what I've tried:
1. MAXIMUM_ALLOWED
Idea: Open related handles with MAXIMUM_ALLOWED -- I'll get everything I could and if some right is missing, related operation (e.g. set_mime()
) will simply fail with access denied
.
Problems:
- it doesn't work with read-only files or volumes (
(Nt)CreateFile()
fails withaccess denied
) - MSDN warns that if defragmentation is in progress on FAT volume -- trying to open a directory this way will fail
- in general it seems that using
MAXIMUM_ALLOWED
is frowned upon for some reason
2. Reopen object when necessary
Idea: Represent r/w/rw
though GENERIC_READ
and GENERIC_WRITE
and for all operations that require additional access (e.g. delete()
requires DELETE
) reopen the object with required access.
Problems:
- Reopening object is not cheap
- changes made via second object can be silently overwritten, for example:
set_mtime()
reopens the file withFILE_WRITE_ATTRIBUTES|SYNCHRONIZE
- calls
NtSetInformationFile(... FileBasicInformation)
to update metadata and closes the handle - later original handle gets closed, it causes data flush and silently overwrites
ModifiedTime
previously set byset_mtime()
3. Duplicate handle instead of reopening object
Idea: same as in previous section, but instead of reopening object -- duplicate original handle (asking for new access):
HANDLE h;
HANDLE hp = GetCurrentProcess();
CHECK_WIN32( DuplicateHandle(hp, hFile, hp, &h, FILE_WRITE_ATTRIBUTES|SYNCHRONIZE, FALSE, 0) );
Problems:
- Duplicating (and closing) file handle every time I need to perform (a non-plain-read/write) operation seem to be excessive and somewhat expensive
DuplicateHandle()
documentation warns (without giving any details) that asking for additional access may fail. It has been working fine in all use cases I checked it for (typically asking for things likeDELETE
/FILE_WRITE_ATTRIBUTES
on handles opened withGENERIC_READ
), but apparently Win32 API provides no guarantees :-/
... otherwise approach seem to be working.
Bottomline:
I am looking for a way to address MAXIMUM_ALLOWED
issues. (Or suggestions for alternative approach, maybe?)
Edit: Here is another reason why reopening file is not a good idea.
There is no way to use
MAXIMUM_ALLOWED
reliably -- R/O files and volumes cause it to error. Poorly designed feature.Another approach is to get minimum access and "expand" it as required (by re-opening file with new
dwAccessRequired
flag). This does not work:if you open file temporarily some changes made through new handle (e.g.
mtime
modification) will be wiped out later when original handle is closed (and underlying kernel object flushes data to disk)if you try to replace old handle with new one this means expensive flush (on old handle close) + MT synchronization which means I can't efficiently use my
file
object from multiple threads (I know that right now due toFILE_SYNCHRONOUS_IO_NONALERT
all operations are serialized anyway, but it will be fixed in near term)Alas,
DuplicateHandle()
can't grant new access -- so this won't help either.Basically, all I need is a thread-safe
BOOL ExtendAccess(HANDLE h, DWORD dwAdditionalAccess)
function. It looks like you can't have it even via NT API -- possible only in kernel mode.Luckily this framework is always used under privileged account, which means I can enable
SE_BACKUP_NAME
, useFILE_OPEN_FOR_BACKUP_INTENT
, over-request access (with minimal fallback in case of read-only volume) and avoid dealing with restrictive DACLs. Ah, and yes, deal withReadOnly
attribute indelete()
. Still haven't decided what to do if user wants to open read-only file for writing...I ended up with this:
Overall terrible API, a spaghetti of special cases. I wish I had my own SMB client.