Detect whether a fs::File object is writable?

Given an arbitrary std::fs::File object, is there a preferred way to detect whether you could write to that file, without actually invoking any of the Write methods?

("Without actually invoking" because I don't want to make any changes whatsoever to the file if it turns out to be writable. A zero-length write might still change its last modification time. Also, it's technically allowed for a zero-length write at the OS level to do nothing and return successfully even if applied to a file that isn't writable.)

It seems that Permissions::readonly() is cross-platform.

That reports the permissions of the file itself rather than the permissions of the opened file descriptor.

It looks like on Linux you'd use fnctl(fd, F_GETFL): fcntl(2) - Linux manual page, but I don't think that's exposed in the standard library. You may be able to do it with the nix crate?

3 Likes

Sounds IMO like a good target for extension of the std::fs APIs, assuming that it can be implemented in a cross-platform way, at least in terms of fn/method signatures.
One never really knows in advance with OS-level APIs, regardless of the seemingly convergent evolution of OSes, their capabilities and their APIs.

Just to clarify, yes, I was looking for something analogous to fcntl(fd, F_GETFL) and not something analogous to Permissions::readonly(). But, ideally, cross-platform and in std, just so I don't introduce another speedbump for whoever eventually decides to port this program to Windows. :slight_smile:

In an ideal world, it seems to me that the O_RDONLY / O_WRONLY / O_RDWR distinction (which IIRC does also exist on Windows) would be reflected in Rust's type system, giving us, say, ReadOnlyFile that implements Read but not Write, etc. (But it's not as simple as just inventing ReadOnlyFile, WriteOnlyFile, and ReadWriteFile, because some APIs are going to want to say "I can take anything readable/writable (but it needs to be backed by an OS file descriptor, so impl Read is not specific enough)" and others are going to want to say "I want exclusively things that are read-only".

With the type system we have, I do think a method that tells you how the file was opened (at least the above plus whether O_APPEND is in use) would be nice. Bonus points if it works even if the File came from from_raw_fd :wink:

1 Like

This can indeed be done with the libc crate (I didn't look at nix, since my program already uses libc directly):

fn is_writable(f: &File) -> io::Result<bool> {
    use std::os::fd::AsRawFd;
    let fl = unsafe { libc::fcntl(file.as_raw_fd(), libc::F_GETFL) };
    if fl == -1 {
        Err(std::io::Error::last_os_error())
    } else {
        Ok((fl & libc::O_ACCMODE) != libc::O_RDONLY)
    }
}

O_RDONLY, O_WRONLY, and O_RDWR are not bit flags, so you cannot just write fl & O_WRONLY.

While this can be done on Windows, it's quite low level and generally it's expected that you'll either know how you opened a file or else your methods expect the caller to know how they opened their file. So yes, encoding it in the type system would be one way of doing it. Writing to or reading from a random unknown handle is considered to be a bit weird, tbh.

(aside: Permissions::readonly is bad for a lot reasons unrelated to this question. E.g. it may have nothing to do with permissions and a "readonly" file might be writeable)

1 Like

Also, it's a TOCTOU. The writability may very well change between cehcking it and attempting the write. The correct way of handling fallible APIs is to attempt using them and handle any eventual errors. Trying to guard against all preconditions and then assuming they will succeed is basically always wrong.

4 Likes

My intent is to use this as a safety check on an API that takes an existing File object and wraps it. For reasons which probably deserve their own thread, this API is at least arguably unsafe, in the exact sense of the language keyword unsafe, if it accepts Files that are writable at the OS level.

I assume "it" refers to Permissions::readonly here? On Unix it is not possible AFAIK to change the writability of a file descriptor after it is opened. (I am not up for digging through MSDN today to try to figure out if this is possible for file handles on Windows.)

Permissions::readonly also doesn't necessarily answer the question you want to ask, depending: my library arguably also ought to be checking whether anyone could potentially write to the file it's being asked to wrap, but the scrupulously accurate answer to that question is "yes with probability 1" because of root and because the file owner can change the access control bits at any time. Some OSes have an "immutable" bit that even root cannot clear, and the entire filesystem could be read-only at the hardware level (CD-ROMs and the like), but restricting safe use of the library to those cases would make it unusable in practice.

A more reasonable level of safety netting for my library would be "could any process that doesn't have the ability to bypass discretionary access control open this file for writing right now", but Permissions::readonly doesn't answer that question either, because it doesn't look at ACLs or the read-only-ness of the file system. ¯\_(ツ)_/¯

Hm, if it's a safety feature then don't you need a file lock for this?

I'll be doing that also, but locks on Unix are advisory only so it's not enough.

Sure, you can make file inaccessible (chattr +i filename) then you can no longer write into it.

What about these processes which already have it opened? File may have no write permissions at all, but if some process already have open descriptor to that file then it can be modified.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.