Table of Contents

Subtitle: Learning std::filesystem through file_size routines.

Last week I wrote a short post that explained how to use std::filesystem::file_size. Today I’d like to continue and show some significant differences that this new functionality has over the “older” techniques (like reading a file and getting its file position).

We’ll also learn something about permissions and how to manage them in std::filesystem.

Recap  

STL before C++17 didn’t contain any direct facilities to work with a filesystem. We could only use third party libraries (like Boost), or system APIs.

With C++17 we have two methods:

  • std::uintmax_t std::filesystem::file_size(const std::filesystem::path& p);
  • std::uintmax_t std::filesystem::directory_entry::file_size() const;

For example, here’s a code that returns the file size:

try 
{
    const auto fsize = std::filesystem::file_size("test.file"); 

    // use fsize...
} catch(fs::filesystem_error& ex) 
{
    std::cout << ex.what() << '\n';
}

What are the advantages (besides shorter code) over the existing C++ methods? Is this method faster?

The Series  

This article is part of my series about C++17 Library Utilities. Here’s the list of the topics in the series:

Resources about C++17 STL:

File Permissions  

The other, popular technique that is available in C++ (apart from using third-party API) is to open a file and then read its file position (with tellg()). The first question we may ask is - how about file permission? What if you cannot open a file? The C++17’s way doesn’t have to open a file, as it reads only file attributes: From cppreference:

For a regular file p, returns the size determined as if by reading the st_size member of the structure obtained by POSIX stat (symlinks are followed)

We can check this with a simple code:

Let’s create a simple file:

std::ofstream sample("hello.txt");
sample << "Hello World!\n";

We can read the current file permissions and show them.

// adapted from https://en.cppreference.com/w/cpp/filesystem/permissions
void outputPerms(fs::perms p, std::string_view title)
{
    if (!title.empty())
        std::cout << title << ": ";

    std::cout << "owner: "
      << ((p & fs::perms::owner_read) != fs::perms::none ? "r" : "-")
      << ((p & fs::perms::owner_write) != fs::perms::none ? "w" : "-")
      << ((p & fs::perms::owner_exec) != fs::perms::none ? "x" : "-");
    std::cout << " group: "
      << ((p & fs::perms::group_read) != fs::perms::none ? "r" : "-")
      << ((p & fs::perms::group_write) != fs::perms::none ? "w" : "-")
      << ((p & fs::perms::group_exec) != fs::perms::none ? "x" : "-");
    std::cout << " others: "
      << ((p & fs::perms::others_read) != fs::perms::none ? "r" : "-")
      << ((p & fs::perms::others_write) != fs::perms::none ? "w" : "-")
      << ((p & fs::perms::others_exec) != fs::perms::none ? "x" : "-")
      << '\n';
}

For our file we can see:

outputPerms(fs::status("hello.txt").permissions());

And we’ll get (On Linux at Coliru):

owner: rw- group: r-- others: r--

We have the right, so tellg() will work as expected:

std::ifstream testFile(std::string("hello.txt"), 
                       std::ios::binary | std::ios::ate);
if (testFile.good())
     std::cout << "tellgSize: " << testFile.tellg() << '\n';
else
    throw std::runtime_error("cannot read file...");

But how about changing permissions so that we cannot open the file for reading, writing or executing?

fs::permissions(sTempName, fs::perms::owner_all,
                fs::perm_options::remove);
outputPerms(fs::status(sTempName).permissions());

it shows:

owner: --- group: r-- others: r--

fs::permissions is a method that allows us to set permissions - we pass a flag that we’d like to change (it’s a bitmask) and then “operation” - remove, replace or add.

In our case, I’m removing all owner permissions from the file.

perms::owner_all is composed of owner_read | owner_write | owner_exec.

Now… let’s try to execute the same code that uses tellg()… and we’ll get:

general exception: cannot read file...

But how about fs::file_size?:

auto fsize = fs::file_size(sTempName);
std::cout << "fsize: " << fsize << '\n';

We’ll get the expected output:

fsize: 13

No matter the permissions of the file (common permissions like read/write/exec), we can read its size.

Demo here @Coliru

Parent Directory Access  

While there’s no need to have read/write/exec rights for the file, we need a parent directory rights to be correct.

I did one more test and I removed all rights from "." directory (the place were the file was created):

fs::permissions(".", fs::perms::owner_all,  
                     fs::perm_options::remove);  

auto fsize = fs::file_size(sTempName);
std::cout << "fsize: " << fsize << '\n';

But I only got:

filesystem error! filesystem error: 
cannot get file size: Permission denied [hello.txt]

You can play with the code here @Coliru

Note For Windows  

Windows is not a POSIX system, so it also doesn’t map POSIX file permissions to its scheme. For std::filesystem it only supports two modes:

  • (read/write) - read, write, and execute - all modes
  • (read-only) - read, execute - all modes

That’s why our demo code won’t work. Disabling read access for a file does not affect.

Performance  

Getting a file size is maybe not the crucial hot-spot in your application… but since we’re C++ programmers, we’d like to know what’s faster… right? :)

Since there’s no need to read the file… then std::filesystem methods should be faster… isn’t it?

What’s more, directory_entry method might be even faster as it should be able to cache the results for a single path - so if you want to access that information many times, it’s wiser to use directory_entry.

Here’s a simple test (thanks to Patrice Roy for the initial test example)

you can play with a demo code here @Coliru.

The test is run N = 10'000 times.

On Coliru (Linux):

filesystem::file_size     : 2543920 in 21 ms.
homemade file_size        : 2543920 in 66 ms.
directory_entry file_size : 2543920 in 13 ms.

On Windows:

PS .\Test.exe
filesystem::file_size     : 1200128 in 81 ms.
homemade file_size        : 1200128 in 395 ms.
directory_entry file_size : 1200128 in 0 ms.

PS .\Test.exe
filesystem::file_size     : 1200128 in 81 ms.
homemade file_size        : 1200128 in 390 ms.
directory_entry file_size : 1200128 in 0 ms.

It’s interesting to see that the directory_entry method is almost no-op in comparison to other techniques. But I haven’t measured the first time access.

Summary  

In this blog post, we’ve shed some light over the file_size function. We covered permissions that are required to get the file status and also compared the performance.

While getting a file size is probably not the crucial part of your application it was an interesting lesson about some bits of std::filesystem. In the next posts, I’ll hope to cover more stuff in that area.