Switch to std::filesystem

Change-Id: I3b8e8adfae9b063f97396d35a5f2296df56a3eb9
diff --git a/.jenkins.d/00-deps.sh b/.jenkins.d/00-deps.sh
index 7a98432..6d8923f 100755
--- a/.jenkins.d/00-deps.sh
+++ b/.jenkins.d/00-deps.sh
@@ -7,7 +7,6 @@
     libboost-chrono-dev
     libboost-date-time-dev
     libboost-dev
-    libboost-filesystem-dev
     libboost-log-dev
     libboost-program-options-dev
     libboost-stacktrace-dev
diff --git a/.waf-tools/default-compiler-flags.py b/.waf-tools/default-compiler-flags.py
index a0bc330..aab0f92 100644
--- a/.waf-tools/default-compiler-flags.py
+++ b/.waf-tools/default-compiler-flags.py
@@ -136,7 +136,7 @@
         return {
             'CXXFLAGS': [],
             'LINKFLAGS': [],
-            'DEFINES': ['BOOST_FILESYSTEM_NO_DEPRECATED'],
+            'DEFINES': [],
         }
 
     def getOptimizedFlags(self, conf):
diff --git a/daemon/face/unix-stream-channel.cpp b/daemon/face/unix-stream-channel.cpp
index 2b09ed4..a3299f6 100644
--- a/daemon/face/unix-stream-channel.cpp
+++ b/daemon/face/unix-stream-channel.cpp
@@ -29,9 +29,8 @@
 #include "unix-stream-transport.hpp"
 #include "common/global.hpp"
 
-#include <boost/filesystem/exception.hpp>
-#include <boost/filesystem/operations.hpp>
-#include <boost/filesystem/path.hpp>
+#include <filesystem>
+#include <system_error>
 
 namespace nfd::face {
 
@@ -51,10 +50,11 @@
 {
   if (isListening()) {
     // use the non-throwing variants during destruction and ignore any errors
-    boost::system::error_code ec;
-    m_acceptor.close(ec);
+    boost::system::error_code ec1;
+    m_acceptor.close(ec1);
     NFD_LOG_CHAN_TRACE("Removing socket file");
-    boost::filesystem::remove(m_endpoint.path(), ec);
+    std::error_code ec2;
+    std::filesystem::remove(m_endpoint.path(), ec2);
   }
 }
 
@@ -68,7 +68,7 @@
     return;
   }
 
-  namespace fs = boost::filesystem;
+  namespace fs = std::filesystem;
 
   fs::path socketPath = m_endpoint.path();
   // ensure parent directory exists
@@ -77,18 +77,18 @@
     NFD_LOG_CHAN_TRACE("Created directory " << parent);
   }
 
-  boost::system::error_code ec;
-  fs::file_type type = fs::symlink_status(socketPath).type();
-  if (type == fs::socket_file) {
+  auto type = fs::symlink_status(socketPath).type();
+  if (type == fs::file_type::socket) {
     // if the socket file already exists, there may be another instance
     // of NFD running on the system: make sure we don't steal its socket
+    boost::system::error_code ec;
     boost::asio::local::stream_protocol::socket socket(getGlobalIoService());
     socket.connect(m_endpoint, ec);
     NFD_LOG_CHAN_TRACE("connect() on existing socket file returned: " << ec.message());
     if (!ec) {
       // someone answered, leave the socket alone
-      ec = boost::system::errc::make_error_code(boost::system::errc::address_in_use);
-      NDN_THROW_NO_STACK(fs::filesystem_error("UnixStreamChannel::listen", socketPath, ec));
+      NDN_THROW_NO_STACK(fs::filesystem_error("UnixStreamChannel::listen", socketPath,
+                                              std::make_error_code(std::errc::address_in_use)));
     }
     else if (ec == boost::asio::error::connection_refused ||
              ec == boost::asio::error::timed_out) {
@@ -97,11 +97,11 @@
       fs::remove(socketPath);
     }
   }
-  else if (type != fs::file_not_found) {
+  else if (type != fs::file_type::not_found) {
     // the file exists but is not a socket: this is a fatal error as we cannot
     // safely overwrite the file without potentially risking data loss
-    ec = boost::system::errc::make_error_code(boost::system::errc::not_a_socket);
-    NDN_THROW_NO_STACK(fs::filesystem_error("UnixStreamChannel::listen", socketPath, ec));
+    NDN_THROW_NO_STACK(fs::filesystem_error("UnixStreamChannel::listen", socketPath,
+                                            std::make_error_code(std::errc::not_a_socket)));
   }
 
   try {
@@ -119,8 +119,8 @@
   // the destructor will still remove the socket file
   m_isListening = true;
 
-  fs::permissions(socketPath, fs::owner_read | fs::group_read | fs::others_read |
-                              fs::owner_write | fs::group_write | fs::others_write);
+  fs::permissions(socketPath, fs::perms::owner_read | fs::perms::group_read | fs::perms::others_read |
+                              fs::perms::owner_write | fs::perms::group_write | fs::perms::others_write);
 
   accept(onFaceCreated, onAcceptFailed);
   NFD_LOG_CHAN_DEBUG("Started listening");
diff --git a/daemon/face/unix-stream-channel.hpp b/daemon/face/unix-stream-channel.hpp
index dbe53d4..d99ea33 100644
--- a/daemon/face/unix-stream-channel.hpp
+++ b/daemon/face/unix-stream-channel.hpp
@@ -80,7 +80,7 @@
    *                       returns an error)
    * \param backlog        The maximum length of the queue of pending incoming
    *                       connections
-   * \throw boost::system::system_error
+   * \throw std::system_error
    */
   void
   listen(const FaceCreatedCallback& onFaceCreated,
diff --git a/daemon/face/unix-stream-factory.cpp b/daemon/face/unix-stream-factory.cpp
index a1cc2fe..e20a8dd 100644
--- a/daemon/face/unix-stream-factory.cpp
+++ b/daemon/face/unix-stream-factory.cpp
@@ -25,7 +25,7 @@
 
 #include "unix-stream-factory.hpp"
 
-#include <boost/filesystem/operations.hpp>
+#include <filesystem>
 
 namespace nfd::face {
 
@@ -86,8 +86,8 @@
 shared_ptr<UnixStreamChannel>
 UnixStreamFactory::createChannel(const std::string& socketPath)
 {
-  auto normalizedPath = boost::filesystem::weakly_canonical(boost::filesystem::absolute(socketPath));
-  unix_stream::Endpoint endpoint(normalizedPath.string());
+  auto normalizedPath = std::filesystem::weakly_canonical(std::filesystem::absolute(socketPath));
+  unix_stream::Endpoint endpoint(normalizedPath);
 
   auto it = m_channels.find(endpoint);
   if (it != m_channels.end())
diff --git a/daemon/main.cpp b/daemon/main.cpp
index cc3c58a..60edb1e 100644
--- a/daemon/main.cpp
+++ b/daemon/main.cpp
@@ -45,6 +45,7 @@
 #include <atomic>
 #include <condition_variable>
 #include <iostream>
+#include <system_error>
 #include <thread>
 
 #include <ndn-cxx/util/logging.hpp>
@@ -331,6 +332,14 @@
     }
     return 1;
   }
+  catch (const std::system_error& e) {
+    NFD_LOG_FATAL(boost::diagnostic_information(e));
+    if (e.code() == std::errc::operation_not_permitted ||
+        e.code() == std::errc::permission_denied) {
+      return 4;
+    }
+    return 1;
+  }
   catch (const std::exception& e) {
     NFD_LOG_FATAL(boost::diagnostic_information(e));
     return 1;
diff --git a/daemon/mgmt/command-authenticator.cpp b/daemon/mgmt/command-authenticator.cpp
index 1f1b036..b07d23d 100644
--- a/daemon/mgmt/command-authenticator.cpp
+++ b/daemon/mgmt/command-authenticator.cpp
@@ -34,8 +34,7 @@
 #include <ndn-cxx/tag.hpp>
 #include <ndn-cxx/util/io.hpp>
 
-#include <boost/filesystem/operations.hpp>
-#include <boost/filesystem/path.hpp>
+#include <filesystem>
 
 namespace security = ndn::security;
 
@@ -158,12 +157,12 @@
                    "SHOULD NOT be used in production environments");
     }
     else {
-      using namespace boost::filesystem;
-      path certfilePath = absolute(certfile, path(filename).parent_path());
-      cert = ndn::io::load<security::Certificate>(certfilePath.string());
+      auto certfilePath = std::filesystem::absolute(filename).parent_path() / certfile;
+      certfilePath = certfilePath.lexically_normal();
+      cert = ndn::io::load<security::Certificate>(certfilePath);
       if (cert == nullptr) {
-        NDN_THROW(ConfigFile::Error("cannot load certfile " + certfilePath.string() +
-                                    " for authorize[" + std::to_string(authSectionIndex) + "]"));
+        NDN_THROW(ConfigFile::Error("cannot load certfile '" + certfilePath.native() +
+                                    "' for authorize[" + std::to_string(authSectionIndex) + "]"));
       }
     }
 
diff --git a/tests/daemon/face/unix-stream-channel.t.cpp b/tests/daemon/face/unix-stream-channel.t.cpp
index 605597d..f149b95 100644
--- a/tests/daemon/face/unix-stream-channel.t.cpp
+++ b/tests/daemon/face/unix-stream-channel.t.cpp
@@ -27,12 +27,16 @@
 
 #include "channel-fixture.hpp"
 
-#include <boost/filesystem.hpp>
+#include <filesystem>
 #include <fstream>
+#include <system_error>
+
+BOOST_TEST_DONT_PRINT_LOG_VALUE(::std::filesystem::file_type)
+BOOST_TEST_DONT_PRINT_LOG_VALUE(::std::filesystem::perms)
 
 namespace nfd::tests {
 
-namespace fs = boost::filesystem;
+namespace fs = std::filesystem;
 namespace local = boost::asio::local;
 using face::UnixStreamChannel;
 
@@ -41,12 +45,12 @@
 protected:
   UnixStreamChannelFixture()
   {
-    listenerEp = unix_stream::Endpoint(socketPath.string());
+    listenerEp = unix_stream::Endpoint(socketPath);
   }
 
   ~UnixStreamChannelFixture() override
   {
-    boost::system::error_code ec;
+    std::error_code ec;
     fs::remove_all(testDir, ec); // ignore error
   }
 
@@ -141,16 +145,17 @@
 BOOST_AUTO_TEST_CASE(CreateAndRemove)
 {
   auto channel = makeChannel();
-  BOOST_CHECK_EQUAL(fs::symlink_status(socketPath).type(), fs::file_not_found);
+  BOOST_TEST(fs::symlink_status(socketPath).type() == fs::file_type::not_found);
 
   channel->listen(nullptr, nullptr);
   auto status = fs::symlink_status(socketPath);
-  BOOST_CHECK_EQUAL(status.type(), fs::socket_file);
-  BOOST_CHECK_EQUAL(status.permissions(), fs::owner_read | fs::group_read | fs::others_read |
-                                          fs::owner_write | fs::group_write | fs::others_write);
+  BOOST_TEST(status.type() == fs::file_type::socket);
+  BOOST_TEST(status.permissions() ==
+             (fs::perms::owner_read | fs::perms::group_read | fs::perms::others_read |
+              fs::perms::owner_write | fs::perms::group_write | fs::perms::others_write));
 
   channel.reset();
-  BOOST_CHECK_EQUAL(fs::symlink_status(socketPath).type(), fs::file_not_found);
+  BOOST_TEST(fs::symlink_status(socketPath).type() == fs::file_type::not_found);
 }
 
 BOOST_AUTO_TEST_CASE(InUse)
@@ -159,10 +164,10 @@
   fs::create_directories(socketPath.parent_path());
 
   local::stream_protocol::acceptor acceptor(g_io, listenerEp);
-  BOOST_CHECK_EQUAL(fs::symlink_status(socketPath).type(), fs::socket_file);
+  BOOST_TEST(fs::symlink_status(socketPath).type() == fs::file_type::socket);
 
   BOOST_CHECK_EXCEPTION(channel->listen(nullptr, nullptr), fs::filesystem_error, [&] (const auto& e) {
-    return e.code() == boost::system::errc::address_in_use &&
+    return e.code() == std::errc::address_in_use &&
            e.path1() == socketPath &&
            std::string_view(e.what()).find("UnixStreamChannel::listen") != std::string_view::npos;
   });
@@ -176,23 +181,23 @@
   local::stream_protocol::acceptor acceptor(g_io, listenerEp);
   acceptor.close();
   // the socket file is not removed when the acceptor is closed
-  BOOST_CHECK_EQUAL(fs::symlink_status(socketPath).type(), fs::socket_file);
+  BOOST_TEST(fs::symlink_status(socketPath).type() == fs::file_type::socket);
 
   // drop write permission from the parent directory
-  fs::permissions(socketPath.parent_path(), fs::owner_all & ~fs::owner_write);
+  fs::permissions(socketPath.parent_path(), fs::perms::owner_all & ~fs::perms::owner_write);
   // removal of the "stale" socket fails due to insufficient permissions
   BOOST_CHECK_EXCEPTION(channel->listen(nullptr, nullptr), fs::filesystem_error, [&] (const auto& e) {
-    return e.code() == boost::system::errc::permission_denied &&
+    return e.code() == std::errc::permission_denied &&
            e.path1() == socketPath &&
            std::string_view(e.what()).find("remove") != std::string_view::npos;
   });
-  BOOST_CHECK_EQUAL(fs::symlink_status(socketPath).type(), fs::socket_file);
+  BOOST_TEST(fs::symlink_status(socketPath).type() == fs::file_type::socket);
 
   // restore all permissions
-  fs::permissions(socketPath.parent_path(), fs::owner_all);
+  fs::permissions(socketPath.parent_path(), fs::perms::owner_all);
   // the socket file should be considered "stale" and overwritten
   channel->listen(nullptr, nullptr);
-  BOOST_CHECK_EQUAL(fs::symlink_status(socketPath).type(), fs::socket_file);
+  BOOST_TEST(fs::symlink_status(socketPath).type() == fs::file_type::socket);
 }
 
 BOOST_AUTO_TEST_CASE(NotASocket)
@@ -200,19 +205,19 @@
   auto channel = makeChannel();
 
   fs::create_directories(socketPath);
-  BOOST_CHECK_EQUAL(fs::symlink_status(socketPath).type(), fs::directory_file);
+  BOOST_TEST(fs::symlink_status(socketPath).type() == fs::file_type::directory);
   BOOST_CHECK_EXCEPTION(channel->listen(nullptr, nullptr), fs::filesystem_error, [&] (const auto& e) {
-    return e.code() == boost::system::errc::not_a_socket &&
+    return e.code() == std::errc::not_a_socket &&
            e.path1() == socketPath &&
            std::string_view(e.what()).find("UnixStreamChannel::listen") != std::string_view::npos;
   });
 
   fs::remove(socketPath);
-  std::ofstream f(socketPath.string());
+  std::ofstream f(socketPath);
   f.close();
-  BOOST_CHECK_EQUAL(fs::symlink_status(socketPath).type(), fs::regular_file);
+  BOOST_TEST(fs::symlink_status(socketPath).type() == fs::file_type::regular);
   BOOST_CHECK_EXCEPTION(channel->listen(nullptr, nullptr), fs::filesystem_error, [&] (const auto& e) {
-    return e.code() == boost::system::errc::not_a_socket &&
+    return e.code() == std::errc::not_a_socket &&
            e.path1() == socketPath &&
            std::string_view(e.what()).find("UnixStreamChannel::listen") != std::string_view::npos;
   });
@@ -224,13 +229,14 @@
   fs::create_directories(testDir);
 
   auto parent = socketPath.parent_path();
-  std::ofstream f(parent.string());
+  std::ofstream f(parent);
   f.close();
-  BOOST_CHECK_EQUAL(fs::symlink_status(parent).type(), fs::regular_file);
+  BOOST_TEST(fs::symlink_status(parent).type() == fs::file_type::regular);
   BOOST_CHECK_EXCEPTION(channel->listen(nullptr, nullptr), fs::filesystem_error, [&] (const auto& e) {
-    return e.code() == boost::system::errc::file_exists &&
+    return (e.code() == std::errc::not_a_directory || // libstdc++
+            e.code() == std::errc::file_exists) &&    // libc++
            e.path1() == parent &&
-           std::string_view(e.what()).find("create_dir") != std::string_view::npos;
+           std::string_view(e.what()).find("create") != std::string_view::npos;
   });
 }
 
@@ -239,38 +245,40 @@
   auto channel = makeChannel();
   fs::create_directories(testDir);
 
-  fs::permissions(testDir, fs::no_perms);
+  fs::permissions(testDir, fs::perms::none);
   BOOST_CHECK_EXCEPTION(channel->listen(nullptr, nullptr), fs::filesystem_error, [&] (const auto& e) {
-    return e.code() == boost::system::errc::permission_denied &&
+    return e.code() == std::errc::permission_denied &&
            e.path1() == socketPath.parent_path() &&
-           std::string_view(e.what()).find("create_dir") != std::string_view::npos;
+           std::string_view(e.what()).find("create") != std::string_view::npos;
   });
 
-  fs::permissions(testDir, fs::owner_read | fs::owner_exe);
+  fs::permissions(testDir, fs::perms::owner_read | fs::perms::owner_exec);
   BOOST_CHECK_EXCEPTION(channel->listen(nullptr, nullptr), fs::filesystem_error, [&] (const auto& e) {
-    return e.code() == boost::system::errc::permission_denied &&
+    return e.code() == std::errc::permission_denied &&
            e.path1() == socketPath.parent_path() &&
-           std::string_view(e.what()).find("create_dir") != std::string_view::npos;
+           std::string_view(e.what()).find("create") != std::string_view::npos;
   });
 
-  fs::permissions(testDir, fs::owner_all);
+  fs::permissions(testDir, fs::perms::owner_all);
   fs::create_directories(socketPath.parent_path());
 
-  fs::permissions(socketPath.parent_path(), fs::no_perms);
+  fs::permissions(socketPath.parent_path(), fs::perms::none);
   BOOST_CHECK_EXCEPTION(channel->listen(nullptr, nullptr), fs::filesystem_error, [&] (const auto& e) {
-    return e.code() == boost::system::errc::permission_denied &&
+    std::string_view what(e.what());
+    return e.code() == std::errc::permission_denied &&
            e.path1() == socketPath &&
-           std::string_view(e.what()).find("status") != std::string_view::npos;
+           (what.find("symlink_status") != std::string_view::npos ||  // libstdc++
+            what.find("posix_stat") != std::string_view::npos);       // libc++
   });
 
-  fs::permissions(socketPath.parent_path(), fs::owner_read | fs::owner_exe);
+  fs::permissions(socketPath.parent_path(), fs::perms::owner_read | fs::perms::owner_exec);
   BOOST_CHECK_EXCEPTION(channel->listen(nullptr, nullptr), fs::filesystem_error, [&] (const auto& e) {
-    return e.code() == boost::system::errc::permission_denied &&
+    return e.code() == std::errc::permission_denied &&
            e.path1() == socketPath &&
            std::string_view(e.what()).find("bind") != std::string_view::npos;
   });
 
-  fs::permissions(socketPath.parent_path(), fs::owner_all);
+  fs::permissions(socketPath.parent_path(), fs::perms::owner_all);
 }
 
 BOOST_AUTO_TEST_SUITE_END() // SocketFile
diff --git a/tests/daemon/face/unix-stream-factory.t.cpp b/tests/daemon/face/unix-stream-factory.t.cpp
index 1f0a62f..3eb43b7 100644
--- a/tests/daemon/face/unix-stream-factory.t.cpp
+++ b/tests/daemon/face/unix-stream-factory.t.cpp
@@ -28,7 +28,7 @@
 #include "face-system-fixture.hpp"
 #include "factory-test-common.hpp"
 
-#include <boost/filesystem.hpp>
+#include <filesystem>
 
 namespace nfd::tests {
 
@@ -59,10 +59,7 @@
 
   const auto& uri = factory.getChannels().front()->getUri();
   BOOST_TEST(uri.getScheme() == "unix");
-  boost::filesystem::path path(uri.getPath());
-  BOOST_TEST(path.filename() == "nfd-test.sock");
-  BOOST_TEST(boost::filesystem::canonical(path) == path); // path should already be in canonical form
-  BOOST_TEST(boost::filesystem::equivalent(path, "/tmp/nfd-test.sock"));
+  BOOST_TEST(uri.getPath() == std::filesystem::canonical("/tmp/nfd-test.sock"));
 }
 
 BOOST_AUTO_TEST_CASE(RelativePath)
@@ -85,10 +82,7 @@
 
   const auto& uri = factory.getChannels().front()->getUri();
   BOOST_TEST(uri.getScheme() == "unix");
-  boost::filesystem::path path(uri.getPath());
-  BOOST_TEST(path.filename() == "nfd-test.sock");
-  BOOST_TEST(boost::filesystem::canonical(path) == path); // path should already be in canonical form
-  BOOST_TEST(boost::filesystem::equivalent(path, "nfd-test.sock"));
+  BOOST_TEST(uri.getPath() == std::filesystem::canonical("nfd-test.sock"));
 }
 
 BOOST_AUTO_TEST_CASE(Omitted)
@@ -140,7 +134,7 @@
 {
   auto channel1 = factory.createChannel("./" + CHANNEL_PATH1); // test path normalization
   auto channel1a = factory.createChannel(CHANNEL_PATH1);
-  auto channel1b = factory.createChannel(boost::filesystem::absolute(CHANNEL_PATH1).string());
+  auto channel1b = factory.createChannel(std::filesystem::absolute(CHANNEL_PATH1));
   auto channel1c = factory.createChannel("foo//../" + CHANNEL_PATH1);
   BOOST_CHECK_EQUAL(channel1, channel1a);
   BOOST_CHECK_EQUAL(channel1, channel1b);
@@ -152,7 +146,7 @@
   BOOST_CHECK_EQUAL(uri.getScheme(), "unix");
   BOOST_CHECK_EQUAL(uri.getHost(), "");
   BOOST_CHECK_EQUAL(uri.getPort(), "");
-  boost::filesystem::path path1(uri.getPath());
+  std::filesystem::path path1(uri.getPath());
   BOOST_TEST(path1.filename() == CHANNEL_PATH1);
   BOOST_TEST(path1.is_absolute()); // path should always be absolute
   BOOST_TEST(path1.lexically_normal() == path1); // path should be in normal form
diff --git a/tests/daemon/face/unix-stream-transport-fixture.hpp b/tests/daemon/face/unix-stream-transport-fixture.hpp
index 8aca3da..ca4077e 100644
--- a/tests/daemon/face/unix-stream-transport-fixture.hpp
+++ b/tests/daemon/face/unix-stream-transport-fixture.hpp
@@ -34,7 +34,7 @@
 #include "tests/daemon/face/dummy-link-service.hpp"
 #include "tests/daemon/face/transport-test-common.hpp"
 
-#include <boost/filesystem/operations.hpp>
+#include <filesystem>
 
 namespace nfd::tests {
 
@@ -65,14 +65,15 @@
 
   ~AcceptorWithCleanup()
   {
-    boost::system::error_code ec;
-    std::string path = local_endpoint(ec).path();
-    if (ec) {
-      return;
+    try {
+      std::string path = local_endpoint().path();
+      boost::system::error_code ec;
+      close(ec);
+      std::filesystem::remove(path);
     }
-
-    close(ec);
-    boost::filesystem::remove(path, ec);
+    catch (...) {
+      // ignore
+    }
   }
 };
 
diff --git a/tests/daemon/mgmt/command-authenticator.t.cpp b/tests/daemon/mgmt/command-authenticator.t.cpp
index 2dd3da6..a431211 100644
--- a/tests/daemon/mgmt/command-authenticator.t.cpp
+++ b/tests/daemon/mgmt/command-authenticator.t.cpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /*
- * Copyright (c) 2014-2023,  Regents of the University of California,
+ * Copyright (c) 2014-2024,  Regents of the University of California,
  *                           Arizona Board of Regents,
  *                           Colorado State University,
  *                           University Pierre & Marie Curie, Sorbonne University,
@@ -27,11 +27,18 @@
 
 #include "manager-common-fixture.hpp"
 
+#include <filesystem>
+
 namespace nfd::tests {
 
 class CommandAuthenticatorFixture : public InterestSignerFixture
 {
 protected:
+  CommandAuthenticatorFixture()
+  {
+    std::filesystem::create_directories(confDir);
+  }
+
   void
   makeModules(std::initializer_list<std::string> modules)
   {
@@ -45,7 +52,7 @@
   {
     ConfigFile cf;
     authenticator->setConfigFile(cf);
-    cf.parse(config, false, "command-authenticator-test.conf");
+    cf.parse(config, false, confDir / "test.conf");
   }
 
   bool
@@ -84,6 +91,8 @@
   }
 
 protected:
+  static inline const std::filesystem::path confDir{UNIT_TESTS_TMPDIR "/command-authenticator"};
+
   shared_ptr<CommandAuthenticator> authenticator = CommandAuthenticator::create();
   std::unordered_map<std::string, ndn::mgmt::Authorization> authorizations;
   std::string lastRequester;
@@ -99,8 +108,8 @@
   Name id1("/localhost/CommandAuthenticator/1");
   Name id2("/localhost/CommandAuthenticator/2");
   BOOST_REQUIRE(m_keyChain.createIdentity(id0));
-  BOOST_REQUIRE(saveIdentityCert(id1, "1.ndncert", true));
-  BOOST_REQUIRE(saveIdentityCert(id2, "2.ndncert", true));
+  BOOST_REQUIRE(saveIdentityCert(id1, confDir / "1.ndncert", true));
+  BOOST_REQUIRE(saveIdentityCert(id2, confDir / "2.ndncert", true));
 
   makeModules({"module0", "module1", "module2", "module3", "module4", "module5", "module6", "module7"});
   const std::string config = R"CONFIG(
@@ -189,7 +198,7 @@
   Name id0("/localhost/CommandAuthenticator/0");
   Name id1("/localhost/CommandAuthenticator/1");
   BOOST_REQUIRE(m_keyChain.createIdentity(id0));
-  BOOST_REQUIRE(saveIdentityCert(id1, "1.ndncert", true));
+  BOOST_REQUIRE(saveIdentityCert(id1, confDir / "1.ndncert", true));
 
   makeModules({"module0", "module1"});
   const std::string config = R"CONFIG(
@@ -232,7 +241,7 @@
 protected:
   IdentityAuthorizedFixture()
   {
-    BOOST_REQUIRE(saveIdentityCert(id1, "1.ndncert", true));
+    BOOST_REQUIRE(saveIdentityCert(id1, confDir / "1.ndncert", true));
 
     makeModules({"module1"});
     const std::string config = R"CONFIG(
diff --git a/tests/global-configuration.cpp b/tests/global-configuration.cpp
index 402dcf8..f56959a 100644
--- a/tests/global-configuration.cpp
+++ b/tests/global-configuration.cpp
@@ -27,11 +27,10 @@
 
 #include <ndn-cxx/util/exception.hpp>
 
-#include <boost/filesystem/operations.hpp>
-#include <boost/filesystem/path.hpp>
-
+#include <filesystem>
 #include <stdexcept>
 #include <stdlib.h>
+#include <system_error>
 
 namespace nfd::tests {
 
@@ -45,10 +44,10 @@
       m_home.assign(envHome);
 
     // in case an earlier test run crashed without a chance to run the destructor
-    boost::filesystem::remove_all(TESTDIR);
+    std::filesystem::remove_all(TESTDIR);
 
     auto testHome = TESTDIR / "test-home";
-    boost::filesystem::create_directories(testHome);
+    std::filesystem::create_directories(testHome);
 
     if (::setenv("HOME", testHome.c_str(), 1) != 0)
       NDN_THROW_NO_STACK(std::runtime_error("setenv() failed"));
@@ -61,12 +60,12 @@
     else
       ::setenv("HOME", m_home.data(), 1);
 
-    boost::system::error_code ec;
-    boost::filesystem::remove_all(TESTDIR, ec); // ignore error
+    std::error_code ec;
+    std::filesystem::remove_all(TESTDIR, ec); // ignore error
   }
 
 private:
-  static inline const boost::filesystem::path TESTDIR{UNIT_TESTS_TMPDIR};
+  static inline const std::filesystem::path TESTDIR{UNIT_TESTS_TMPDIR};
   std::string m_home;
 };
 
diff --git a/tests/key-chain-fixture.cpp b/tests/key-chain-fixture.cpp
index 57bb057..1a9b43b 100644
--- a/tests/key-chain-fixture.cpp
+++ b/tests/key-chain-fixture.cpp
@@ -27,7 +27,8 @@
 
 #include <ndn-cxx/util/io.hpp>
 
-#include <boost/filesystem/operations.hpp>
+#include <filesystem>
+#include <system_error>
 
 namespace nfd::tests {
 
@@ -40,9 +41,9 @@
 
 KeyChainFixture::~KeyChainFixture()
 {
-  boost::system::error_code ec;
+  std::error_code ec;
   for (const auto& certFile : m_certFiles) {
-    boost::filesystem::remove(certFile, ec); // ignore error
+    std::filesystem::remove(certFile, ec); // ignore error
   }
 }
 
diff --git a/wscript b/wscript
index 11c470e..fda954e 100644
--- a/wscript
+++ b/wscript
@@ -114,7 +114,7 @@
 
     conf.check_cxx(header_name='valgrind/valgrind.h', define_name='HAVE_VALGRIND', mandatory=False)
 
-    conf.check_boost(lib='filesystem program_options', mt=True)
+    conf.check_boost(lib='program_options', mt=True)
     if conf.env.BOOST_VERSION_NUMBER < 107100:
         conf.fatal('The minimum supported version of Boost is 1.71.0.\n'
                    'Please upgrade your distribution or manually install a newer version of Boost.\n'