An Annoyance With std::filesystem::last_write_time

When writing new C++ code, I mostly try to use std::filesystem rather than the C POSIX functions directly. I prefer std::filestem not necessarily for portability reasons, but because code using std::filesystem tends to be easier to be shorter and easier to read than code that uses the C POSIX API directly.

Still, there are a lot of tricky Linux or POSIX-specific things you can do with the C APIs, so in situations where I'm writing tricky or non-portable code I don't mind the fact that I have to use the C APIs. As an extremely simple example, POSIX provides both stat(2) and fstat(2). The difference between the two is that with stat(2) you specify the file path as a const char* string, whereas fstat(2) takes an open file descriptor and reads the status of that fd. The main advantage of fstat(2) that using it correctly can prevent TOCTTOU races (it can also be ever so slightly more efficient in the case where you need to open a handle to the file anyway, but TOCTTOU is its raison d'ĂȘtre). While it's a little annoying that std::filesystem doesn't provide an analog to fstat(2) (e.g. perhaps a function that takes a std::fstream as a parameter), I consider this a somewhat fringe use case, and it might be difficult to implement in the STL for portability reasons anyway, so I'm willing to give it a pass.

However, there are some other deficiencies in std::filsystem that there are really no excuses for. One such example is getting the last-modified time (or "mtime") of a file. Normally on Unix-like operating systems you would do this by calling one of the stat family of functions, and then reading the st_mtim field from the stat struct. This struct also contains a lot of other useful information, such as the file permissions, the file owner, etc. This means with the most basic POSIX file status function, stat(2), you can make a single system call and then read all of these attributes. Doing this with a single stat(2) call is important not just for efficiency reasons, but also ensures that you have an atomic view of the file status.

For some baffling reason C++17 provides a stat(2) analog called std::filesystem::file_status() which lets you access various file status information including the file type and permissions, but the returned file status object doesn't let you access other basic fields such as the last-modified time. There's another function named std::filesystem::last_write_time() that will give the last-modified time, but this function only accepts a std::filesystem::path, meaning that using this method will re-stat the file. This means that if you want to use std::filesystem instead of the POSIX APIs directly you'll likely end up calling stat(2) multiple times on the same file path. Besides the TOCTTOU problems, this is just inefficient.

This is just one of many complaints I have with std::filesystem, and is a simple example of why std::filesystem is currently only good for toy use cases. The good news is that these are problems that can be addressed in future C++ standards revisions, so hopefully this is just a temporary problem we have to live with.