[zbi] Use std::filesystem::path

Change-Id: I2acc5c686e5ac2e3fc1e4fff0f1324f92159b87f
diff --git a/zircon/public/gn/config/BUILD.gn b/zircon/public/gn/config/BUILD.gn
index 905b751..7420ef4 100644
--- a/zircon/public/gn/config/BUILD.gn
+++ b/zircon/public/gn/config/BUILD.gn
@@ -526,13 +526,16 @@
   ]
 }
 
+if (current_os == "mac") {
+  libcxx_dir = "${toolchain.tool_dir}/../lib"
+}
+
 config("static-libc++") {
   if (current_os == "mac") {
     # The macOS driver doesn't support -static-libstdc++ properly, so pass
     # the libraries directly.  This has to locate the files explicitly in
     # the toolchain, because -lc++ would look for the shared library.
     ldflags = [ "-nostdlib++" ]
-    libcxx_dir = "${toolchain.tool_dir}/../lib"
     libs = [
       "$libcxx_dir/libc++abi.a",
       "$libcxx_dir/libc++.a",
@@ -550,6 +553,14 @@
   }
 }
 
+config("libc++fs") {
+  if (current_os == "mac") {
+    libs = [ "$libcxx_dir/libc++fs.a" ]
+  } else {
+    libs = [ "c++fs" ]
+  }
+}
+
 config("rodso") {
   if (is_gcc) {
     # TODO(mcgrathr): Move the file to this dir?
diff --git a/zircon/system/host/zbi/BUILD.gn b/zircon/system/host/zbi/BUILD.gn
index 5d7a219..8fdaceb 100644
--- a/zircon/system/host/zbi/BUILD.gn
+++ b/zircon/system/host/zbi/BUILD.gn
@@ -6,6 +6,7 @@
   sources = [
     "zbi.cpp",
   ]
+  configs += [ "$zx/public/gn/config:libc++fs" ]
   deps = [
     "$zx/system/ulib/fbl",
     "$zx/third_party/ulib/cksum",
diff --git a/zircon/system/host/zbi/zbi.cpp b/zircon/system/host/zbi/zbi.cpp
index 6b5b83f..1f938a7 100644
--- a/zircon/system/host/zbi/zbi.cpp
+++ b/zircon/system/host/zbi/zbi.cpp
@@ -13,6 +13,7 @@
 #include <deque>
 #include <dirent.h>
 #include <fcntl.h>
+#include <filesystem>
 #include <fnmatch.h>
 #include <forward_list>
 #include <functional>
@@ -241,7 +242,7 @@
 
 class FileWriter {
 public:
-    FileWriter(const char* outfile, std::string prefix)
+    FileWriter(const char* outfile, std::filesystem::path prefix)
         : prefix_(std::move(prefix)), outfile_(outfile) {
     }
 
@@ -260,13 +261,13 @@
                 return CreateFile(outfile_);
             }
         } else {
-            auto file = prefix_ + name;
+            auto file = prefix_ / name;
             return CreateFile(file.c_str());
         }
     }
 
 private:
-    std::string prefix_;
+    std::filesystem::path prefix_;
     const char* outfile_ = nullptr;
     unsigned int files_ = 0;
 
@@ -279,10 +280,18 @@
         fbl::unique_fd fd = openit();
         if (!fd) {
             switch (errno) {
-            case ENOENT:
-                MakeDirs(outfile);
+            case ENOENT: {
+                std::filesystem::path dir(outfile);
+                dir.remove_filename();
+                std::error_code ec;
+                if (!std::filesystem::create_directories(dir, ec) && ec) {
+                    fprintf(stderr, "cannot create directory %s: %s\n",
+                            dir.c_str(), ec.message().c_str());
+                    exit(1);
+                }
                 fd = openit();
                 break;
+            }
 
             case EEXIST:
                 // Remove the file in case it exists.  This makes it safe to do
@@ -296,35 +305,12 @@
             }
         }
         if (!fd) {
-            fprintf(stderr, "cannot create %s: %s\n",
-                    outfile, strerror(errno));
+            fprintf(stderr, "cannot create %s: %s\n", outfile, strerror(errno));
             exit(1);
         }
 
         return OutputStream(std::move(fd));
     }
-
-    static void MakeDirs(const std::string& name) {
-        auto lastslash = name.rfind('/');
-        if (lastslash == std::string::npos) {
-            return;
-        }
-        auto dir = name.substr(0, lastslash);
-        if (mkdir(dir.c_str(), 0777) == 0) {
-            return;
-        }
-        if (errno == ENOENT) {
-            MakeDirs(dir);
-            if (mkdir(dir.c_str(), 0777) == 0) {
-                return;
-            }
-        }
-        if (errno != EEXIST) {
-            fprintf(stderr, "mkdir: %s: %s\n",
-                    dir.c_str(), strerror(errno));
-            exit(1);
-        }
-    }
 };
 
 class NameMatcher {
@@ -611,6 +597,11 @@
 
 #undef LZ4F_CALL
 
+template <typename T>
+std::size_t HashValue(const T& x) {
+    return std::hash<std::decay_t<T>>()(x);
+}
+
 class FileContents {
 public:
     DISALLOW_COPY_AND_ASSIGN_ALLOW_MOVE(FileContents);
@@ -664,7 +655,7 @@
 
     struct Hash {
         std::size_t operator()(const FileContents& file) const {
-            return std::hash<decltype(file.mapped_)>()(file.mapped_);
+            return HashValue(file.mapped_);
         }
     };
 
@@ -761,6 +752,12 @@
     std::unique_ptr<const Directory> dir_;
 };
 
+struct PathHash {
+    std::size_t operator()(const std::filesystem::path& file) const {
+        return HashValue(file.native());
+    }
+};
+
 // This is used for all opening of files and directories for input.
 // It tracks all files opened so a depfile can be written at the end.
 //
@@ -783,7 +780,8 @@
 
     // The returned FileContents is cached and lives forever (for the lifetime
     // of the FileOpener).
-    const FileContents* OpenFile(const char* file) {
+    const FileContents* OpenFile(std::filesystem::path file) {
+        file.make_preferred();
         auto& cache = name_cache_[file];
         if (!cache) {
             struct stat st;
@@ -795,15 +793,16 @@
     }
 
     // Like OpenFile, but also accept a directory.
-    const File* OpenFileOrDir(const char* file) {
+    const File* OpenFileOrDir(std::filesystem::path file) {
+        file.make_preferred();
         auto& cache = name_cache_[file];
         if (!cache) {
             struct stat st;
             auto [cached_file, fd] = Open(file, &st);
             if (S_ISDIR(st.st_mode)) {
-                OpenDirectory(cached_file, std::move(fd), file);
+                OpenDirectory(cached_file, std::move(fd), std::move(file));
             } else {
-                OpenFile(cached_file, std::move(fd), st, file);
+                OpenFile(cached_file, std::move(fd), st, std::move(file));
             }
             cache = cached_file;
         }
@@ -861,7 +860,8 @@
     std::map<FileId, File> file_cache_;
 
     // Cache of contents by file name.  These point into the file_cache_.
-    std::unordered_map<std::string, const File*> name_cache_;
+    std::unordered_map<std::filesystem::path, const File*,
+                       PathHash> name_cache_;
 
     // These are created by Emplace() and kept here both to de-duplicate them
     // and to tie their lifetimes to the FileOpener (to parallel file_cache_).
@@ -871,10 +871,11 @@
     // region of the image).
     std::unordered_set<FileContents, FileContents::Hash> memory_cache_;
 
-    std::pair<File*, fbl::unique_fd> Open(const char* file, struct stat* st) {
-        fbl::unique_fd fd(open(file, O_RDONLY));
+    std::pair<File*, fbl::unique_fd> Open(const std::filesystem::path& file,
+                                          struct stat* st) {
+        fbl::unique_fd fd(open(file.c_str(), O_RDONLY));
         if (!fd) {
-            perror(file);
+            perror(file.c_str());
             exit(1);
         }
         if (fstat(fd.get(), st) < 0) {
@@ -885,30 +886,33 @@
     }
 
     void OpenFile(File* cached,
-                  fbl::unique_fd fd, const struct stat& st, const char* file) {
+                  fbl::unique_fd fd, const struct stat& st,
+                  std::filesystem::path file) {
         if (!S_ISREG(st.st_mode)) {
-            fprintf(stderr, "%s: not a regular file\n", file);
+            fprintf(stderr, "%s: not a regular file\n", file.c_str());
             exit(1);
         }
-        *cached = File(std::make_unique<const FileContents>(
-                           FileContents::Map(std::move(fd), st, file)));
+        *cached =
+            File(std::make_unique<const FileContents>(
+                     FileContents::Map(std::move(fd), st, file.c_str())));
     }
 
-    void OpenDirectory(File* cached, fbl::unique_fd fd, std::string file) {
+    void OpenDirectory(File* cached, fbl::unique_fd fd,
+                       std::filesystem::path file) {
         DIR* dir = fdopendir(fd.release());
         if (!dir) {
             perror("fdopendir");
             exit(1);
         }
-        file += "/";
         auto dirmap = std::make_unique<Directory>();
         const dirent* d;
         while ((d = readdir(dir)) != nullptr) {
             if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, "..")) {
                 continue;
             }
-            std::string path = file + d->d_name;
-            (*dirmap)[d->d_name] = OpenFileOrDir(path.c_str());
+            file /= d->d_name;
+            (*dirmap)[d->d_name] = OpenFileOrDir(file.c_str());
+            file.remove_filename();
         }
         closedir(dir);
         *cached = File(std::move(dirmap));
@@ -919,11 +923,13 @@
 // These both deliver target name -> file contents mappings until they don't.
 struct InputFileGenerator {
     struct value_type {
-        std::string target;
+        std::filesystem::path target;
         const FileContents* file;
     };
     virtual ~InputFileGenerator() = default;
-    virtual bool Next(FileOpener*, const std::string& prefix, value_type*) = 0;
+    virtual bool Next(FileOpener*,
+                      const std::filesystem::path& prefix,
+                      value_type*) = 0;
 };
 
 using InputFileGeneratorList =
@@ -931,7 +937,8 @@
 
 class ManifestInputFileGenerator : public InputFileGenerator {
 public:
-    ManifestInputFileGenerator(const FileContents* file, std::string prefix)
+    ManifestInputFileGenerator(const FileContents* file,
+                               std::filesystem::path prefix)
         : prefix_(std::move(prefix)) {
         read_ptr_ = static_cast<const char*>(
             file->View(0, file->exact_size()).iov_base);
@@ -940,7 +947,7 @@
 
     ~ManifestInputFileGenerator() override = default;
 
-    bool Next(FileOpener* opener, const std::string& prefix,
+    bool Next(FileOpener* opener, const std::filesystem::path& prefix,
               value_type* value) override {
         while (read_ptr_ != eof_) {
             auto eol = static_cast<const char*>(
@@ -958,31 +965,34 @@
                 exit(1);
             }
 
-            std::string target(line, eq - line);
-            std::string source(eq + 1, eol - (eq + 1));
-            *value = value_type{prefix_ + prefix + target,
-                                opener->OpenFile(source.c_str())};
+            value->target = prefix_ / prefix / "";
+            value->target.concat(line, eq);
+            value->target = value->target.lexically_normal();
+
+            value->file = opener->OpenFile({eq + 1, eol});
+
             return true;
         }
         return false;
     }
 
 private:
-    const std::string prefix_;
+    const std::filesystem::path prefix_;
     const char* read_ptr_ = nullptr;
     const char* eof_ = nullptr;
 };
 
 class DirectoryInputFileGenerator : public InputFileGenerator {
 public:
-    DirectoryInputFileGenerator(const Directory* dir, std::string prefix)
+    DirectoryInputFileGenerator(const Directory* dir,
+                                std::filesystem::path prefix)
         : source_prefix_(std::move(prefix)) {
         walk_pos_.emplace_front(dir, 0);
     }
 
     ~DirectoryInputFileGenerator() override = default;
 
-    bool Next(FileOpener* opener, const std::string& prefix,
+    bool Next(FileOpener* opener, const std::filesystem::path& prefix,
               value_type* value) override {
         do {
             auto& it = walk_pos_.front().it;
@@ -990,13 +1000,13 @@
                 Ascend();
                 continue;
             }
-            std::string source = source_prefix_ + walk_prefix_ + it->first;
             if (it->second->IsDir()) {
                 Descend(it->second->AsDir(), it->first);
                 ++it;
             } else {
-                *value = value_type{prefix + walk_prefix_ + it->first,
-                                    it->second->AsContents()};
+                value->target =
+                    (prefix / walk_prefix_ / it->first).lexically_normal();
+                value->file = it->second->AsContents();
                 ++it;
                 return true;
             }
@@ -1057,24 +1067,25 @@
         return sscanf(name, "%x%n", abi_type, &i) == 1 && name[i] == '\0';
     }
 
-    static std::string ExtractedFileName(unsigned int n, uint32_t zbi_type,
-                                         bool raw) {
-        std::string name;
+    static std::filesystem::path ExtractedFileName(
+        unsigned int n, uint32_t zbi_type, bool raw) {
+        std::filesystem::path path;
         char buf[32];
         const auto info = ItemTypeInfo(zbi_type);
         if (info.name) {
             snprintf(buf, sizeof(buf), "%03u.", n);
-            name = buf;
+            std::string name(buf);
             name += info.name;
             for (auto& c : name) {
                 c = static_cast<unsigned char>(std::tolower(c));
             }
+            path = std::move(name);
         } else {
             snprintf(buf, sizeof(buf), "%03u.%08x", n, zbi_type);
-            name = buf;
+            path = buf;
         }
-        name += (raw && info.extension) ? info.extension : ".zbi";
-        return name;
+        return path.replace_extension(
+            (raw && info.extension) ? info.extension : ".zbi");
     }
 
     static void PrintTypeUsage(FILE* out) {
@@ -1302,7 +1313,7 @@
                                 const InputFileGeneratorList& input,
                                 const Filter& include_file,
                                 bool sort,
-                                const std::string& prefix,
+                                const std::filesystem::path& prefix,
                                 bool compress) {
         auto item = MakeItem(NewHeader(ZBI_TYPE_STORAGE_BOOTFS, 0), compress);
 
@@ -1316,13 +1327,12 @@
         for (const auto& generator : input) {
             InputFileGenerator::value_type next;
             while (generator->Next(opener, prefix, &next)) {
-                if (!include_file(next.target.c_str())) {
+                Entry entry{next.target.generic_string()};
+                if (!include_file(entry.name.c_str())) {
                     continue;
                 }
                 // Accumulate the space needed for each zbi_bootfs_dirent_t.
-                dirsize += ZBI_BOOTFS_DIRENT_SIZE(next.target.size() + 1);
-                Entry entry;
-                entry.name.swap(next.target);
+                dirsize += ZBI_BOOTFS_DIRENT_SIZE(entry.name.size() + 1);
                 entry.data_len =
                     static_cast<uint32_t>(next.file->exact_size());
                 if (entry.data_len != next.file->exact_size()) {
@@ -1406,18 +1416,17 @@
     }
 
     void ExtractItem(FileWriter* writer, NameMatcher* matcher) {
-        std::string namestr = ExtractedFileName(writer->NextFileNumber(),
-                                                type(), false);
-        auto name = namestr.c_str();
+        auto path = ExtractedFileName(writer->NextFileNumber(),
+                                         type(), false);
+        auto name = path.c_str();
         if (matcher->Matches(name, true)) {
             WriteZBI(writer, name, (Item* const[]){this});
         }
     }
 
     void ExtractRaw(FileWriter* writer, NameMatcher* matcher) {
-        std::string namestr = ExtractedFileName(writer->NextFileNumber(),
-                                                type(), true);
-        auto name = namestr.c_str();
+        auto path = ExtractedFileName(writer->NextFileNumber(), type(), true);
+        auto name = path.c_str();
         if (matcher->Matches(name, true)) {
             if (type() == ZBI_TYPE_CMDLINE) {
                 // Drop a trailing NUL.
@@ -1719,7 +1728,7 @@
         ~BootFSInputFileGenerator() override = default;
 
         // Copying from an existing BOOTFS ignores the --prefix setting.
-        bool Next(FileOpener* opener, const std::string&,
+        bool Next(FileOpener* opener, const std::filesystem::path&,
                   value_type* value) override {
             if (!dir_) {
                 return false;
@@ -1914,7 +1923,7 @@
     bool verbose = false;
     ItemList items;
     InputFileGeneratorList bootfs_input;
-    std::string prefix;
+    std::filesystem::path prefix;
     int opt;
     while ((opt = getopt_long(argc, argv,
                               kOptString, kLongOpts, nullptr)) != -1) {
@@ -1958,23 +1967,19 @@
             continue;
 
         case 'p':
-            // A nonempty prefix should have no leading slashes and
-            // exactly one trailing slash.
+            // The directory prefix must be a relative path.
             prefix = optarg;
-            while (!prefix.empty() && prefix.front() == '/') {
-                prefix.erase(0, 1);
-            }
-            if (!prefix.empty() && prefix.back() == '/') {
-                prefix.pop_back();
-            }
-            if (prefix.empty() && optarg[0] != '\0') {
-                fprintf(stderr, "\
---prefix cannot be /; use --prefix= (empty) instead\n");
+            if (prefix.is_absolute()) {
+                fprintf(stderr,
+                        "--prefix should be relative (no leading slash)\n");
                 exit(1);
             }
-            if (!prefix.empty()) {
-                prefix.push_back('/');
+            if (prefix.empty()) {
+                // Normalize to a nonempty prefix so /= works right.
+                // We'll normalize the concatenation before using it anyway.
+                prefix = ".";
             }
+            prefix = prefix.lexically_normal();
             continue;
 
         case 't':
@@ -2054,13 +2059,10 @@
         if (input->IsDir()) {
             // Calculate the prefix for opening files within the directory.
             // This won't be part of the BOOTFS file name.
-            std::string dir_prefix(optarg);
-            if (dir_prefix.back() != '/') {
-                dir_prefix.push_back('/');
-            }
+            std::filesystem::path dir_prefix(optarg);
             bootfs_input.emplace_back(
-                new DirectoryInputFileGenerator(input->AsDir(),
-                                                std::move(dir_prefix)));
+                new DirectoryInputFileGenerator(
+                    input->AsDir(), dir_prefix.lexically_normal()));
             continue;
         }
 
@@ -2240,7 +2242,8 @@
                 auto generator = Item::ReadBootFS(std::move(item));
                 InputFileGenerator::value_type next;
                 while (generator->Next(&opener, prefix, &next)) {
-                    if (name_matcher.Matches(next.target.c_str())) {
+                    if (name_matcher.Matches(
+                            next.target.generic_string().c_str())) {
                         writer.RawFile(next.target.c_str())
                             .Write(next.file->View(
                                        0, next.file->exact_size()));