Simple Permission Checks

I'm writing a program where I would like to check the permissions of some files and directories before I go and run a bulk of operations on them. As far as I can tell, with std::fs::Permissions, all I can check for is readonly or, if I use the unix specific trait, the mode of the individual file. However, as far as I understand determining the permissions of a file isn't that simple and is dependent on the current user, parent directory permissions, and even your current working directory. Is there an easy way to check wether you can access a file before actually reading and writing to it? Perhaps a community crate that handles this?

Essentially I want something like is_readable(path) , is_writable(path) and is_executable(path).

Cross-platform would be a nice bonus, but I know 0 about how windows permissions work.

Any solution that operates on paths would be subject to race conditions. More generally, you can never guarantee that a read/write from/to a file will succeed no matter how many checks you do up-front.

The best solution is to just do what you want and handle errors later. If you need to ensure that it either works or doesn't, do your work in a temporary directory/file and then atomically move the temporary directory/file into place.

I get the feeling this may be an X-Y problem, what's the actual issue you're trying to solve?

2 Likes

@stebalien I am writing a dotfiles manager that will handle linking and copying files, and then cleaning up after itself when you no longer decide to use them. Since a partial or failed install could potentially cause a lot of problems I want to run as many checks as I can before I run the actual operations.

I understand this. I am still handling permission failures when I actually go to make real reads and writes. However, I want to avoid littering the file system with files & links to find out half-way that the install is going to fail due to permission issues. Yes, you can recover from it, but that would be much more difficult as it would require backing up overwritten files. Also I am planning on implementing a "doctor-like" command which will tell the user if the currently defined install is likely to succeed without actually going and running the install.

For the dry run mode, you'd use the access function (C, you'd have to use FFI) on unix (POSIX) but I have no idea what you'd use on windows. However, that would simply be an "optimization" of sorts.

Yes, you can recover from it, but that would be much more difficult as it would require backing up overwritten files.

You'll need to be able to recover regardless; with IO, all checking ahead of time will tell you is that an operation isn't guaranteed to fail. If you want to avoid lots of copying when backing up files/directories, you can use hardlinking as long as you make sure to use rename to overwrite.

1 Like

I agree, and it's something I would like to work into the cli eventually, but I don't plan on tackling that issue until at least v2.x.

Thank you for the suggestions, I like the idea of using hard links for the backups and I'll be sure to check out access.

Windows is completely different. It doesn't have the chmod abstraction at all and just uses Access Control Lists. That readonly thing isn't even a permission, it is a simple attribute that can be toggled on and off trivially. Unless you already know what you're doing, or really love working with Windows API, don't even bother with the Windows permissions stuff, it's quite complex. Hopefully someone does eventually write a good library abstracting it. You'll just have to design your code to be robust and attempt the operation and be able to gracefully recover.

1 Like

What about _access_s and _waccess_s?

@stebalien I'm looking at the source code for those functions right now and the only thing they do is check for that readonly attribute and whether you requested write access, and if they're both true then it returns access denied. That's it, no permission checks at all.

//
// waccess.cpp
//
//      Copyright (c) Microsoft Corporation. All rights reserved.
//
// The _waccess() and _waccess_s() functions, which test file accessibility.
//
#include <corecrt_internal.h>
#include <io.h>

// Tests whether the specified file can be accessed with the specified mode.  The
// access_mode must be one of:  0 (exist only), 2 (write), 4 (read), 6 (read and
// write).  Returns zero if the file can be accessed with the given mode; returns
// an error code otherwise or if an error occurs.
extern "C" errno_t __cdecl _waccess_s(wchar_t const* const path, int const access_mode)
{
    _VALIDATE_CLEAR_OSSERR_RETURN_ERRCODE(path != nullptr,           EINVAL);
    _VALIDATE_CLEAR_OSSERR_RETURN_ERRCODE((access_mode & (~6)) == 0, EINVAL);
    
    WIN32_FILE_ATTRIBUTE_DATA attributes;
    if (!GetFileAttributesExW(path, GetFileExInfoStandard, &attributes))
    {
        __acrt_errno_map_os_error(GetLastError());
        return errno;
    }
    
    // All directories have both read and write access:
    if (attributes.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
        return 0;
    
    // If we require write access, make sure the read only flag is not set:
    bool const file_is_read_only = (attributes.dwFileAttributes & FILE_ATTRIBUTE_READONLY) != 0;
    bool const mode_requires_write = (access_mode & 2) != 0;
    
    if (file_is_read_only && mode_requires_write)
    {
        _doserrno = ERROR_ACCESS_DENIED;
        errno = EACCES;
        return errno;
    }
    
    // Otherwise, the file is accessible:
    return 0;
    
}
1 Like