C++17 In Detail

21 January 2019

std:filesystem::file_size Advantages and Differences

Getting File Size in C++

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.

Get my free ebook about C++17!

More than 50 pages about the new Language Standard.

C++17 in detail, by Bartlomiej Filipek

C++17 In Detail
© 2017, Bartlomiej Filipek, Blogger platform
Disclaimer: Any opinions expressed herein are in no way representative of those of my employers. All data and information provided on this site is for informational purposes only. I try to write complete and accurate articles, but the web-site will not be liable for any errors, omissions, or delays in this information or any losses, injuries, or damages arising from its display or use.
This site contains ads or referral links, which provide me with a commission. Thank you for your understanding.