std::filesystem
¶
Note
This topic is usually live hacked.
Overview¶
File system related stuff (obviously)
Path manipulation
Directory creation and iteration ⟶ even recursive!
Stats like size, access time
Permissions
Support routines like copy, remove, …
Since C++ 17
Continually improved since then
Fixture¶
(Not central to this topic, just for completeness)
This topic is mainly livehacked, along the test cases that follow. Each test case is executed withing a test fixture; that fixture works as follows:
Every test case has its own temporary directory, by a
std::filesystem::path
type variable,dirname
The test case run inside that directory; i.e. the process’s current working directory is set to
dirname
before the test runs, and reset to whatever it was before.dirname
is removed after each test run ⟶ fixture
#pragma once
#include <fixture-tmpdir-cwd.h>
struct filesystem_suite : public cd_to_tmpdir_fixture {};
Paths: Composition, Comparison¶
Paths are OS dependent
⟶ directory separators, for example (
/
on Unixen,\
on Doze)
std::filesystem::path
does not access filesystemUsed as input to higher level routines
Main operators:
/=
,/
Lexical comparison
Largely compatible with
std::string
#include "suite.h"
#include <gtest/gtest.h>
#include <filesystem>
TEST_F(filesystem_suite, path_compose_compare)
{
std::filesystem::path fn = "/";
fn /= "etc";
fn /= "passwd";
ASSERT_EQ(fn, "/etc/passwd");
}
#include "suite.h"
#include <gtest/gtest.h>
#include <filesystem>
TEST_F(filesystem_suite, path_compose_compare_2)
{
std::filesystem::path fn = "/etc";
std::filesystem::path passwd = fn / "passwd";
ASSERT_EQ(passwd, "/etc/passwd");
}
Absolute and Relative Paths¶
Method |
Description |
---|---|
|
Is |
|
Is |
#include "suite.h"
#include <gtest/gtest.h>
#include <filesystem>
TEST_F(filesystem_suite, path_abs_rel)
{
std::filesystem::path abspath = "/etc/passwd";
ASSERT_TRUE(abspath.is_absolute());
ASSERT_FALSE(abspath.is_relative());
std::filesystem::path relpath = "etc/passwd";
ASSERT_FALSE(relpath.is_absolute());
ASSERT_TRUE(relpath.is_relative());
}
Path Component Access¶
Method |
Description |
---|---|
|
extract filename part |
|
extract parent directory |
|
remove filename part, leaving parent directory path in place |
|
replace filename part |
|
replace extension |
#include "suite.h"
#include <gtest/gtest.h>
#include <filesystem>
#include <string>
TEST_F(filesystem_suite, path_component_access)
{
std::filesystem::path path = "/a/b/c/blah.txt";
std::string filename_part = path.filename();
ASSERT_EQ(filename_part, "blah.txt");
std::string dir_part = path.parent_path();
ASSERT_EQ(dir_part, "/a/b/c");
path.replace_extension("TXT");
ASSERT_EQ(path, "/a/b/c/blah.TXT");
}
Iterating Over Path Components¶
A
path
is basically a list of stringsSeparated by OS specific directory separators
Absolute paths start with a separator
#include "suite.h"
#include <gtest/gtest.h>
#include <filesystem>
#include <string>
TEST_F(filesystem_suite, path_iteration)
{
std::filesystem::path path = "/etc/passwd";
std::string components[3];
size_t i = 0;
for (const auto& component: path)
components[i++] = component;
ASSERT_EQ(components[0], "/");
ASSERT_EQ(components[1], "etc");
ASSERT_EQ(components[2], "passwd");
}
Current Working Directory¶
Current working directory: process attribute
Process can change CWD
… and get it of course
#include "suite.h"
TEST_F(filesystem_suite, cwd_chdir)
{
std::filesystem::current_path(dirname);
std::filesystem::path cwd = std::filesystem::current_path();
ASSERT_EQ(cwd, dirname);
}
Directory Creation: std::filesystem::create_directory()
¶
Os wise, creating a directory fail if the parent directory does not exist (or has no permissions)
⟶ E.g.
mkdir -p
allows you to create paths of multiple levels
#include "suite.h"
#include <gtest/gtest.h>
#include <filesystem>
TEST_F(filesystem_suite, create_directory_error)
{
try {
std::filesystem::create_directory(dirname / "parent/child");
ASSERT_FALSE(true);
}
catch (const std::filesystem::filesystem_error&) {}
// this would be more to the point ...
// ASSERT_THROW(std::filesystem::create_directory(dirname / "parent/child"),
// std::filesystem::filesystem_error);
// ... but (using -O3) ...
// In file included from /usr/include/c++/12/ios:40,
// from /usr/include/c++/12/ostream:38,
// from /usr/include/c++/12/bits/unique_ptr.h:41,
// from /usr/include/c++/12/memory:76,
// from /home/jfasch/work/jfasch-home/googletest/googletest/include/gtest/gtest.h:55,
// from /home/jfasch/work/jfasch-home/trainings/material/soup/cxx-code/fixtures/./fixture-tmpdir.h:3,
// from /home/jfasch/work/jfasch-home/trainings/material/soup/cxx-code/fixtures/./fixture-tmpdir-cwd.h:3,
// from /home/jfasch/work/jfasch-home/trainings/material/soup/cxx11/filesystem/code/suite.h:3,
// from /home/jfasch/work/jfasch-home/trainings/material/soup/cxx11/filesystem/code/create_directory_error.cpp:1:
// In static member function ‘static constexpr std::char_traits<char>::char_type* std::char_traits<char>::copy(char_type*, const char_type*, std::size_t)’,
// inlined from ‘static constexpr void std::__cxx11::basic_string<_CharT, _Traits, _Alloc>::_S_copy(_CharT*, const _CharT*, size_type) [with _CharT = char; _Traits = std::char_traits<char>; _Alloc = std::allocator<char>]’ at /usr/include/c++/12/bits/basic_string.h:423:21,
// inlined from ‘constexpr std::__cxx11::basic_string<_CharT, _Traits, _Allocator>& std::__cxx11::basic_string<_CharT, _Traits, _Alloc>::_M_replace(size_type, size_type, const _CharT*, size_type) [with _CharT = char; _Traits = std::char_traits<char>; _Alloc = std::allocator<char>]’ at /usr/include/c++/12/bits/basic_string.tcc:532:22,
// inlined from ‘constexpr std::__cxx11::basic_string<_CharT, _Traits, _Alloc>& std::__cxx11::basic_string<_CharT, _Traits, _Alloc>::replace(size_type, size_type, const _CharT*, size_type) [with _CharT = char; _Traits = std::char_traits<char>; _Alloc = std::allocator<char>]’ at /usr/include/c++/12/bits/basic_string.h:2171:19,
// inlined from ‘constexpr std::__cxx11::basic_string<_CharT, _Traits, _Alloc>& std::__cxx11::basic_string<_CharT, _Traits, _Alloc>::replace(size_type, size_type, const _CharT*) [with _CharT = char; _Traits = std::char_traits<char>; _Alloc = std::allocator<char>]’ at /usr/include/c++/12/bits/basic_string.h:2196:22,
// inlined from ‘std::string testing::internal::CanonicalizeForStdLibVersioning(std::string)’ at /home/jfasch/work/jfasch-home/googletest/googletest/include/gtest/internal/gtest-type-util.h:83:14:
// /usr/include/c++/12/bits/char_traits.h:431:56: error: ‘void* __builtin_memcpy(void*, const void*, long unsigned int)’ accessing 9223372036854775810 or more bytes at offsets [2, 9223372036854775807] and 1 may overlap up to 9223372036854775813 bytes at offset -3 [-Werror=restrict]
// 431 | return static_cast<char_type*>(__builtin_memcpy(__s1, __s2, __n));
// | ~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~
}
Create directories one at a time
#include "suite.h"
#include <gtest/gtest.h>
#include <filesystem>
#include <stdexcept>
#include <fstream>
TEST_F(filesystem_suite, create_directory_single)
{
std::filesystem::create_directory(dirname / "parent");
std::filesystem::create_directory(dirname / "parent/child");
std::ofstream(dirname / "parent/child/file.txt");
ASSERT_TRUE(std::filesystem::is_regular_file(dirname / "parent/child/file.txt"));
}
Directory Creation: std::filesystem::create_directories()
¶
Creating multiple level of directories in one shot
Like
mkdir -p ...
#include "suite.h"
#include <gtest/gtest.h>
#include <filesystem>
#include <fstream>
TEST_F(filesystem_suite, create_directory_multiple)
{
std::filesystem::create_directories(dirname / "parent/child");
std::ofstream(dirname / "parent/child/file.txt");
ASSERT_TRUE(std::filesystem::is_regular_file(dirname / "parent/child/file.txt"));
}
Directory Entry Classification: std::filesystem::is_xxx()
¶
Directories may contain files and directories
Symbolic links, named pipes, character devices, block devices, …
⟶ classification routines
#include "suite.h"
#include <gtest/gtest.h>
#include <filesystem>
#include <fstream>
TEST_F(filesystem_suite, is_xxx)
{
std::filesystem::create_directory(dirname / "subdir");
ASSERT_TRUE(std::filesystem::is_directory(dirname / "subdir"));
std::ofstream(dirname / "file");
ASSERT_TRUE(std::filesystem::is_regular_file(dirname / "file"));
}
Function |
Description |
---|---|
|
checks whether the given path refers to block device |
|
checks whether the given path refers to a character device |
|
checks whether the given path refers to a directory |
|
checks whether the given path refers to an empty file or directory |
|
checks whether the given path refers to a named pipe |
|
checks whether the argument refers to an other file |
|
checks whether the argument refers to a regular file |
|
checks whether the argument refers to a named IPC socket |
|
checks whether the argument refers to a symbolic link |
Directory Entries: Basics¶
Represents a thing that can be contained in a directory
OS dependent (as everything in
std::filesystem
)Contains
File type (regular file, directory, symlink, character/block device, pipe, …)
File status (permissions, etc.)
Tries to implement the greatest common divisor across different OSen
… and fails (in my opinion)
Unix
ls -l
$ ls -l /etc/passwd
-rw-r--r--. 1 root root 2691 Nov 15 15:45 /etc/passwd
More information: Unix
stat
$ stat /etc/passwd
File: /etc/passwd
Size: 2691 Blocks: 8 IO Block: 4096 regular file
Device: 0,36 Inode: 919153 Links: 1
Access: (0644/-rw-r--r--) Uid: ( 0/ root) Gid: ( 0/ root)
Context: system_u:object_r:passwd_file_t:s0
Access: 2022-12-06 15:00:15.111282886 +0100
Modify: 2022-11-15 15:45:06.737858445 +0100
Change: 2022-11-15 15:45:06.743858359 +0100
Birth: 2022-11-15 15:45:06.737858445 +0100
Directory Entries: std::filesystem::directory_entry
¶
Commonly used when iterating over directories (see below)
Carries information about what’s being iterated over
Method |
Description |
---|---|
|
returns the path the entry refers to |
|
checks whether directory entry refers to existing file system object |
|
checks whether the directory entry refers to block device |
|
checks whether the directory entry refers to a character device |
|
checks whether the directory entry refers to a directory |
|
checks whether the directory entry refers to a named pipe |
|
checks whether the directory entry refers to an other file |
|
checks whether the directory entry refers to a regular file |
|
checks whether the directory entry refers to a named IPC socket |
|
checks whether the directory entry refers to a symbolic link |
|
returns the size of the file to which the directory entry refers |
|
returns the number of hard links referring to the file to which the directory entry refers |
|
gets or sets the time of the last data modification of the file to which the directory entry refers |
|
status of the file designated by this directory entry |
#include "suite.h"
#include <gtest/gtest.h>
#include <filesystem>
#include <fstream>
TEST_F(filesystem_suite, directory_entry)
{
std::filesystem::create_directory(dirname / "subdir");
std::ofstream(dirname / "file").write("Hallo", 5);
std::filesystem::directory_entry subdir(dirname / "subdir");
ASSERT_TRUE(subdir.is_directory());
ASSERT_EQ(subdir.path(), dirname / "subdir");
std::filesystem::directory_entry file(dirname / "file");
ASSERT_TRUE(file.is_regular_file());
ASSERT_EQ(file.path(), dirname / "file");
ASSERT_EQ(file.file_size(), 5);
}
Iterating Over Directory Entries¶
Directories contain entries
⟶ want to iterate over those
OS wise, directories are not read in any particular order
File system specific (e.h.
ext4
might be different frombtrfs
fromntfs
)
#include "suite.h"
#include <gtest/gtest.h>
#include <filesystem>
#include <fstream>
#include <iostream>
TEST_F(filesystem_suite, directory_iterator)
{
std::filesystem::create_directory(dirname / "subdir1");
std::filesystem::create_directory(dirname / "subdir2");
std::ofstream(dirname / "subdir1/file");
std::ofstream(dirname / "subdir2/file");
std::ofstream(dirname / "file1");
std::ofstream(dirname / "file2");
std::set<std::filesystem::path> entries; // <--- iteration order is not (necessarily) creation order
auto diriter = std::filesystem::directory_iterator(dirname);
for (const std::filesystem::directory_entry& entry: diriter)
entries.insert(entry);
ASSERT_EQ(entries.size(), 4); // <--- not 6! (no recursion)
ASSERT_TRUE(entries.contains(dirname / "subdir1"));
ASSERT_TRUE(entries.contains(dirname / "subdir2"));
ASSERT_TRUE(entries.contains(dirname / "file1"));
ASSERT_TRUE(entries.contains(dirname / "file2"));
}
Recursive Directory Iteration¶
#include "suite.h"
#include <gtest/gtest.h>
#include <filesystem>
#include <fstream>
TEST_F(filesystem_suite, recursive_directory_iterator)
{
std::filesystem::create_directory(dirname / "subdir1");
std::filesystem::create_directory(dirname / "subdir2");
std::ofstream(dirname / "subdir1/file");
std::ofstream(dirname / "subdir2/file");
std::ofstream(dirname / "file1");
std::ofstream(dirname / "file2");
auto diriter = std::filesystem::recursive_directory_iterator(dirname);
std::set<std::filesystem::path> entries;
for (const auto& entry: diriter)
entries.insert(entry);
ASSERT_EQ(entries.size(), 6); // <--- *recursive*: hitting entries in subdirs too
ASSERT_TRUE(entries.contains(dirname / "subdir1"));
ASSERT_TRUE(entries.contains(dirname / "subdir2"));
ASSERT_TRUE(entries.contains(dirname / "subdir1/file"));
ASSERT_TRUE(entries.contains(dirname / "subdir2/file"));
ASSERT_TRUE(entries.contains(dirname / "file1"));
ASSERT_TRUE(entries.contains(dirname / "file2"));
}