2
\$\begingroup\$

On windows, to find out if I have write access to a directory, I found that it is easiest to just create a file in that directory.

In order to do so, I found GetTempFileName() API function to get an almost(?) unique file name in the desired directory, then I was going to create a file with that name. so if everything goes well, I have the write access and everything is fine.

Then I found two behavior from the mentioned API call:

  1. If I have write access, it creates the temporary file and returns the path through its output parameter, named lpTempFileName
  2. When I don't have write access, it just clear out the buffer, and literally returns an empty string as temp file name (lpTempFileName).

I can't find this bit of information in documents. Is this always valid? If so I can rely on it and just decide based on if the returned string is empty or not, instead of calling CreateFile and Closing the Handle afterwards.

Here is what I'm doing now:

bool is_writable = true;

try
{
    CString tmp_file_name;
    ::GetTempFileName(folder_path, "ttt", 0, tmp_file_name.GetBuffer(MAX_PATH));
    HANDLE file = CreateFile(tmp_file_name, GENERIC_READ | GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
    if ( file == INVALID_HANDLE_VALUE )
    {
        is_writable = false;
    }
    else
    {
        CloseHandle( file );
        DeleteFile( tmp_file_name );
        is_writable = true;
    }
}
catch (...)
{
    is_writable = false;
}

But here is what I can do if the above assumption is correct:

bool is_writable = true;

try
{
    CString tmp_file_name;
    ::GetTempFileName(folder_path, "ttt", 0, tmp_file_name.GetBuffer(MAX_PATH));

    if ( tmp_file_name.IsEmpty() )
    {
        is_writable = false;
    }
    else
    {
        DeleteFile( tmp_file_name );
        is_writable = true;
    }
}
catch (...)
{
    is_writable = false;
}

Certainly, two less API calls.

So is my finding always stands correct? can I rely on the second snippet?

\$\endgroup\$

1 Answer 1

1
\$\begingroup\$

If so I can rely on it and just decide based on if the returned string is empty or not

No, you may use second version but do not check for string content. More about this later.

On windows, to find out if I have write access to a directory,

Depends what you exactly mean with write access. Your method is not bad (especially if you do not need an high performance check) however note that you're checking for permissions to:

  • Read file/directory list for a given directory (FILE_LIST_DIRECTORY).
  • Create a file in a directory (FILE_ADD_FILE).

As you can imagine this is different from write data into a file. Permissions are a complex thing and in a complex secure scenario you may have, for example, these permissions:

  • Granted: create a new file.
  • Granted: full write data into an existing file you own.
  • Granted: append data into an existing file you're not owner of.
  • Denied: discover existing files.
  • Denied: overwrite any existing data.
  • Denied: delete files (it doesn't matter if you're owner or not).

I hope I highlighted what your problem is, your actual check will fail but you still have write permissions to that folder. What you actually have to do is:

  • Obtain information about file security using GetFileSecurity().
  • Obtain DACL information about that file (GetSecurityDescriptorDacl() and GetAclInformation()).
  • Enumerate ACEs of your ACL to check for required permission (using GetAce() to obtain ACCESS_ALLOWED_ACE to read ACCESS_MASK field). Do not forget to interpolate these data with GetSecurityDescriptorOwner() and GetSecurityDescriptorGroup().

Funny enough to do it you need READ_CONTROL permission but they actually coincides.

What to do then? If you do need a fast implementation you may do the above (checking/skipping if insufficient permissions) and then try to write a test file (not just create it). Note that you may not have DELETE permission to remove this test file (and it may be a problem in production environments). Note that directory traverse in advanced scenarios is much more complex than you may imagine (see also FILE_TRAVERSE and BYPASS_TRAVERSE_CHECKING stuff).

Fortunately there are some ATL helper classes to simplify all these things, you may want to read The Windows Access Control Model: Part 2, in particular Do I Have Access? section.


If what you need is a simple check and you do not have a complex security setup then you may go with your own code (also second version if directory list permission is granted). Just few notes about it:

  • After each call to CString.GetBuffer() you must call CString.ReleaseBuffer() before object is usable again. Do it just before you call tmp_file_name.IsEmpty().
  • You actually do not need to check tmp_file_name content (which is actually an implementation detail even subject to change). To answer your answer then yes you may (see later) use second version but check GetTempFileName() return value, if it's zero then it failed (and you may check with GetLastError() if it's an unauthorized access error). Do not rely on output parameter, its content in case of errors is undefined.
  • I do not see any reason your code will throw exceptions, a catch-all statement may hide other (and more subtle) issues than write permission. I'd remove it.
  • You may want to use your first solution also writing something to that file if to check if you have FILE_APPEND_DATA permission. If file creation and writing are separate things (in your scenario) then first solution is the only one working.
\$\endgroup\$
2
  • \$\begingroup\$ I first tried to use the security apis, but they don't work properly always. when the file is in CD-Rom for exmaple, AccessCheck() function just returns true that I have access to write in that address. that was the reason I fall back to creating a file. \$\endgroup\$ Jan 8, 2016 at 1:36
  • \$\begingroup\$ Since my situation isn't a critical security related thing but just I want to be sure that the path isn't in a read-only disk, like CD-Rom. I guess I'm good to use the second solution and check the return value and last error as you described. Thanks for your answer. \$\endgroup\$ Jan 8, 2016 at 1:38

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.