face: change default Unix socket path

Refs: #5304
Change-Id: I5c7415874131b156e54a655960d9de47c1eae91d
diff --git a/daemon/face/unix-stream-channel.cpp b/daemon/face/unix-stream-channel.cpp
index 906d23e..150fad4 100644
--- a/daemon/face/unix-stream-channel.cpp
+++ b/daemon/face/unix-stream-channel.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,
@@ -49,11 +49,10 @@
 UnixStreamChannel::~UnixStreamChannel()
 {
   if (isListening()) {
-    // use the non-throwing variants during destruction
-    // and ignore any errors
+    // use the non-throwing variants during destruction and ignore any errors
     boost::system::error_code error;
     m_acceptor.close(error);
-    NFD_LOG_CHAN_DEBUG("Removing socket file");
+    NFD_LOG_CHAN_TRACE("Removing socket file");
     boost::filesystem::remove(m_endpoint.path(), error);
   }
 }
@@ -70,7 +69,7 @@
 
   namespace fs = boost::filesystem;
 
-  fs::path socketPath(m_endpoint.path());
+  fs::path socketPath = m_endpoint.path();
   fs::file_type type = fs::symlink_status(socketPath).type();
 
   if (type == fs::socket_file) {
@@ -80,7 +79,7 @@
     NFD_LOG_CHAN_TRACE("connect() on existing socket file returned: " << error.message());
     if (!error) {
       // someone answered, leave the socket alone
-      NDN_THROW(Error("Socket file at " + m_endpoint.path() + " belongs to another NFD process"));
+      NDN_THROW(Error("Socket file at " + m_endpoint.path() + " belongs to another process"));
     }
     else if (error == boost::asio::error::connection_refused ||
              error == boost::asio::error::timed_out) {
@@ -94,6 +93,12 @@
     NDN_THROW(Error(m_endpoint.path() + " already exists and is not a socket file"));
   }
 
+  // ensure parent directory exists before creating socket
+  fs::path parent = socketPath.parent_path();
+  if (!parent.empty() && fs::create_directories(parent)) {
+    NFD_LOG_CHAN_TRACE("Created directory " << parent);
+  }
+
   m_acceptor.open();
   m_acceptor.bind(m_endpoint);
   m_acceptor.listen(backlog);
diff --git a/daemon/face/unix-stream-factory.cpp b/daemon/face/unix-stream-factory.cpp
index a275fde..27719eb 100644
--- a/daemon/face/unix-stream-factory.cpp
+++ b/daemon/face/unix-stream-factory.cpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /*
- * Copyright (c) 2014-2022,  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,
@@ -45,8 +45,8 @@
 {
   // unix
   // {
-  //   path /run/nfd.sock        ; on Linux
-  //   path /var/run/nfd.sock    ; on other platforms
+  //   path /run/nfd/nfd.sock       ; on Linux
+  //   path /var/run/nfd/nfd.sock   ; on other platforms
   // }
 
   m_wantCongestionMarking = context.generalConfig.wantCongestionMarking;
@@ -59,15 +59,12 @@
   }
 
 #ifdef __linux__
-  std::string path = "/run/nfd.sock";
+  std::string path = "/run/nfd/nfd.sock";
 #else
-  std::string path = "/var/run/nfd.sock";
+  std::string path = "/var/run/nfd/nfd.sock";
 #endif // __linux__
 
-  for (const auto& pair : *configSection) {
-    const std::string& key = pair.first;
-    const ConfigSection& value = pair.second;
-
+  for (const auto& [key, value] : *configSection) {
     if (key == "path") {
       path = value.get_value<std::string>();
     }
@@ -87,11 +84,10 @@
 }
 
 shared_ptr<UnixStreamChannel>
-UnixStreamFactory::createChannel(const std::string& unixSocketPath)
+UnixStreamFactory::createChannel(const std::string& socketPath)
 {
-  boost::filesystem::path p(unixSocketPath);
-  p = boost::filesystem::canonical(p.parent_path()) / p.filename();
-  unix_stream::Endpoint endpoint(p.string());
+  auto normalizedPath = boost::filesystem::weakly_canonical(boost::filesystem::absolute(socketPath));
+  unix_stream::Endpoint endpoint(normalizedPath.string());
 
   auto it = m_channels.find(endpoint);
   if (it != m_channels.end())
diff --git a/daemon/face/unix-stream-factory.hpp b/daemon/face/unix-stream-factory.hpp
index 76bbfc9..51bc51d 100644
--- a/daemon/face/unix-stream-factory.hpp
+++ b/daemon/face/unix-stream-factory.hpp
@@ -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,
@@ -53,7 +53,7 @@
    *          an exception will be thrown if the channel cannot be created.
    */
   shared_ptr<UnixStreamChannel>
-  createChannel(const std::string& unixSocketPath);
+  createChannel(const std::string& socketPath);
 
 private:
   void
diff --git a/daemon/rib/service.cpp b/daemon/rib/service.cpp
index 1634ef9..814feb0 100644
--- a/daemon/rib/service.cpp
+++ b/daemon/rib/service.cpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /*
- * Copyright (c) 2014-2022,  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,
@@ -68,9 +68,9 @@
   if (config.get_child_optional("face_system.unix")) {
     // default socket path should be the same as in UnixStreamFactory::processConfig
 #ifdef __linux__
-    auto path = config.get<std::string>("face_system.unix.path", "/run/nfd.sock");
+    auto path = config.get<std::string>("face_system.unix.path", "/run/nfd/nfd.sock");
 #else
-    auto path = config.get<std::string>("face_system.unix.path", "/var/run/nfd.sock");
+    auto path = config.get<std::string>("face_system.unix.path", "/var/run/nfd/nfd.sock");
 #endif // __linux__
     return make_shared<ndn::UnixTransport>(path);
   }
diff --git a/docs/conf.py b/docs/conf.py
index 4ab82aa..83b4c52 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -10,7 +10,7 @@
 # https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
 
 project = 'Named Data Networking Forwarding Daemon (NFD)'
-copyright = 'Copyright © 2014-2023 Named Data Networking Project.'
+copyright = 'Copyright © 2014-2024 Named Data Networking Project.'
 author = 'Named Data Networking Project'
 
 # The short X.Y version.
diff --git a/docs/manpages/nfdc-face.rst b/docs/manpages/nfdc-face.rst
index c09191b..410b814 100644
--- a/docs/manpages/nfdc-face.rst
+++ b/docs/manpages/nfdc-face.rst
@@ -66,7 +66,7 @@
     - tcp4://192.0.2.1:6363
     - tcp6://[2001:db8::1]:6363
     - tcp://example.net
-    - unix:///var/run/nfd.sock
+    - unix:///run/nfd/nfd.sock
     - fd://6
     - ether://[08:00:27:01:01:01]
     - dev://eth0
diff --git a/nfd.conf.sample.in b/nfd.conf.sample.in
index 660beb2..bf7d9ea 100644
--- a/nfd.conf.sample.in
+++ b/nfd.conf.sample.in
@@ -101,10 +101,9 @@
   ; Unix stream faces and channels.
   unix
   {
-    ; The default transport is unix:///run/nfd.sock (on Linux) or unix:///var/run/nfd.sock (on
-    ; other platforms). This should match the "transport" field in client.conf for ndn-cxx. If you
-    ; wish to use TCP instead of Unix sockets with ndn-cxx, change "transport" to an appropriate
-    ; TCP FaceUri.
+    ; The default transport is 'unix:///run/nfd/nfd.sock' (on Linux) or 'unix:///var/run/nfd/nfd.sock' (on
+    ; other platforms). This should match the 'transport' field in client.conf for ndn-cxx. If you wish
+    ; to use TCP instead of Unix sockets with ndn-cxx, change 'transport' to an appropriate TCP FaceUri.
     path @UNIX_SOCKET_PATH@ ; Unix stream listener path
   }
 
@@ -303,12 +302,12 @@
     ;      sudo mkdir -p @SYSCONFDIR@/ndn/keys
     ;      sudo mv default.ndncert @SYSCONFDIR@/ndn/keys/default.ndncert
     ;
-    ; The "certfile" field below specifies the default key directory for
+    ; The 'certfile' field below specifies the default key directory for
     ; your machine. You may move your newly created key to the location it
     ; specifies or path.
 
     ; certfile keys/default.ndncert ; NDN identity certificate file
-    certfile any ; "any" authorizes command interests signed under any certificate,
+    certfile any ; 'any' authorizes command interests signed under any certificate,
                  ; i.e., no actual validation.
     privileges ; set of privileges granted to this identity
     {
diff --git a/tests/daemon/face/unix-stream-channel.t.cpp b/tests/daemon/face/unix-stream-channel.t.cpp
index e4ce0cf..d583625 100644
--- a/tests/daemon/face/unix-stream-channel.t.cpp
+++ b/tests/daemon/face/unix-stream-channel.t.cpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /*
- * Copyright (c) 2014-2022,  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,
@@ -41,7 +41,8 @@
 protected:
   UnixStreamChannelFixture()
   {
-    listenerEp = unix_stream::Endpoint("nfd-test-unix-stream-channel.sock");
+    listenerEp = unix_stream::Endpoint(socketPath.string());
+    fs::remove_all(socketPath.parent_path());
   }
 
   shared_ptr<UnixStreamChannel>
@@ -68,11 +69,14 @@
   clientConnect(local::stream_protocol::socket& client)
   {
     client.async_connect(listenerEp,
-      [this] (const boost::system::error_code& error) {
+      [this] (const auto& error) {
         BOOST_REQUIRE_EQUAL(error, boost::system::errc::success);
         limitedIo.afterOp();
       });
   }
+
+protected:
+  fs::path socketPath = fs::path(UNIT_TESTS_TMPDIR) / "unix-stream-channel" / "test" / "foo.sock";
 };
 
 BOOST_AUTO_TEST_SUITE(Face)
@@ -128,9 +132,6 @@
 
 BOOST_AUTO_TEST_CASE(SocketFile)
 {
-  fs::path socketPath(listenerEp.path());
-  fs::remove(socketPath);
-
   auto channel = makeChannel();
   BOOST_CHECK_EQUAL(fs::symlink_status(socketPath).type(), fs::file_not_found);
 
@@ -144,32 +145,33 @@
   BOOST_CHECK_EQUAL(fs::symlink_status(socketPath).type(), fs::file_not_found);
 }
 
-BOOST_AUTO_TEST_CASE(ExistingStaleSocketFile)
+BOOST_AUTO_TEST_CASE(ExistingSocketFile)
 {
-  fs::path socketPath(listenerEp.path());
-  fs::remove(socketPath);
-
+  fs::create_directories(socketPath.parent_path());
   local::stream_protocol::acceptor acceptor(g_io, listenerEp);
-  acceptor.close();
   BOOST_CHECK_EQUAL(fs::symlink_status(socketPath).type(), fs::socket_file);
 
   auto channel = makeChannel();
+  BOOST_CHECK_THROW(channel->listen(nullptr, nullptr), UnixStreamChannel::Error);
+
+  acceptor.close();
+  BOOST_CHECK_EQUAL(fs::symlink_status(socketPath).type(), fs::socket_file);
+
   BOOST_CHECK_NO_THROW(channel->listen(nullptr, nullptr));
   BOOST_CHECK_EQUAL(fs::symlink_status(socketPath).type(), fs::socket_file);
 }
 
 BOOST_AUTO_TEST_CASE(ExistingRegularFile)
 {
-  fs::path socketPath(listenerEp.path());
-  fs::remove(socketPath);
+  auto guard = ndn::make_scope_exit([=] { fs::remove(socketPath); });
 
-  std::ofstream f(listenerEp.path());
+  fs::create_directories(socketPath.parent_path());
+  std::ofstream f(socketPath.string());
   f.close();
+  BOOST_CHECK_EQUAL(fs::symlink_status(socketPath).type(), fs::regular_file);
 
   auto channel = makeChannel();
   BOOST_CHECK_THROW(channel->listen(nullptr, nullptr), UnixStreamChannel::Error);
-
-  fs::remove(socketPath);
 }
 
 BOOST_AUTO_TEST_SUITE_END() // TestUnixStreamChannel
diff --git a/tests/daemon/face/unix-stream-factory.t.cpp b/tests/daemon/face/unix-stream-factory.t.cpp
index 53a8687..1f0a62f 100644
--- a/tests/daemon/face/unix-stream-factory.t.cpp
+++ b/tests/daemon/face/unix-stream-factory.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,
@@ -28,21 +28,18 @@
 #include "face-system-fixture.hpp"
 #include "factory-test-common.hpp"
 
+#include <boost/filesystem.hpp>
+
 namespace nfd::tests {
 
-using face::UnixStreamFactory;
-
-using UnixStreamFactoryFixture = FaceSystemFactoryFixture<UnixStreamFactory>;
+using UnixStreamFactoryFixture = FaceSystemFactoryFixture<face::UnixStreamFactory>;
 
 BOOST_AUTO_TEST_SUITE(Face)
 BOOST_FIXTURE_TEST_SUITE(TestUnixStreamFactory, UnixStreamFactoryFixture)
 
-const std::string CHANNEL_PATH1("unix-stream-test.1.sock");
-const std::string CHANNEL_PATH2("unix-stream-test.2.sock");
-
 BOOST_AUTO_TEST_SUITE(ProcessConfig)
 
-BOOST_AUTO_TEST_CASE(Normal)
+BOOST_AUTO_TEST_CASE(AbsolutePath)
 {
   const std::string CONFIG = R"CONFIG(
     face_system
@@ -58,9 +55,40 @@
   parseConfig(CONFIG, false);
 
   BOOST_REQUIRE_EQUAL(factory.getChannels().size(), 1);
+  BOOST_TEST(factory.getChannels().front()->isListening());
+
   const auto& uri = factory.getChannels().front()->getUri();
-  BOOST_CHECK_EQUAL(uri.getScheme(), "unix");
-  BOOST_CHECK_NE(uri.getPath().find("nfd-test.sock"), std::string::npos);
+  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_AUTO_TEST_CASE(RelativePath)
+{
+  const std::string CONFIG = R"CONFIG(
+    face_system
+    {
+      unix
+      {
+        path nfd-test.sock
+      }
+    }
+  )CONFIG";
+
+  parseConfig(CONFIG, true);
+  parseConfig(CONFIG, false);
+
+  BOOST_REQUIRE_EQUAL(factory.getChannels().size(), 1);
+  BOOST_TEST(factory.getChannels().front()->isListening());
+
+  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_AUTO_TEST_CASE(Omitted)
@@ -95,6 +123,9 @@
 
 BOOST_AUTO_TEST_SUITE_END() // ProcessConfig
 
+const std::string CHANNEL_PATH1("unix-stream-test.1.sock");
+const std::string CHANNEL_PATH2("unix-stream-test.2.sock");
+
 BOOST_AUTO_TEST_CASE(GetChannels)
 {
   BOOST_CHECK_EQUAL(factory.getChannels().empty(), true);
@@ -107,16 +138,24 @@
 
 BOOST_AUTO_TEST_CASE(CreateChannel)
 {
-  auto channel1 = factory.createChannel(CHANNEL_PATH1);
+  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 channel1c = factory.createChannel("foo//../" + CHANNEL_PATH1);
   BOOST_CHECK_EQUAL(channel1, channel1a);
+  BOOST_CHECK_EQUAL(channel1, channel1b);
+  BOOST_CHECK_EQUAL(channel1, channel1c);
   BOOST_CHECK_EQUAL(factory.getChannels().size(), 1);
 
   const auto& uri = channel1->getUri();
+  BOOST_TEST_INFO_SCOPE(uri);
   BOOST_CHECK_EQUAL(uri.getScheme(), "unix");
   BOOST_CHECK_EQUAL(uri.getHost(), "");
   BOOST_CHECK_EQUAL(uri.getPort(), "");
-  BOOST_CHECK_EQUAL(uri.getPath().rfind(CHANNEL_PATH1), uri.getPath().size() - CHANNEL_PATH1.size());
+  boost::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
 
   auto channel2 = factory.createChannel(CHANNEL_PATH2);
   BOOST_CHECK_NE(channel1, channel2);
diff --git a/wscript b/wscript
index 11a1353..63b8a8a 100644
--- a/wscript
+++ b/wscript
@@ -1,6 +1,6 @@
 # -*- Mode: python; py-indent-offset: 4; indent-tabs-mode: nil; coding: utf-8; -*-
 """
-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,
@@ -209,7 +209,7 @@
         install_path='${SYSCONFDIR}/ndn',
         IF_HAVE_LIBPCAP='' if bld.env.HAVE_LIBPCAP else '; ',
         IF_HAVE_WEBSOCKET='' if bld.env.HAVE_WEBSOCKET else '; ',
-        UNIX_SOCKET_PATH='/run/nfd.sock' if Utils.unversioned_sys_platform() == 'linux' else '/var/run/nfd.sock')
+        UNIX_SOCKET_PATH='/run/nfd/nfd.sock' if Utils.unversioned_sys_platform() == 'linux' else '/var/run/nfd/nfd.sock')
     bld.install_files('${SYSCONFDIR}/ndn', 'autoconfig.conf.sample')
 
     if bld.env.HAVE_SYSTEMD: