blob: f38960da4525f6eb45ae46b001ed8cef64791ce0 [file] [edit]
// Copyright 2018 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/developer/debug/zxdb/common/file_util.h"
#include <lib/syslog/cpp/macros.h>
#include <pwd.h>
#include <sys/types.h>
#include <unistd.h>
#include <chrono>
#include <cstdint>
#include <cstdlib>
#include <filesystem>
#include <vector>
#include "src/developer/debug/shared/logging/logging.h"
#include "src/developer/debug/shared/string_util.h"
namespace zxdb {
std::string_view ExtractLastFileComponent(std::string_view path) {
size_t last_slash = path.rfind('/');
if (last_slash == std::string::npos)
return path;
return path.substr(last_slash + 1);
}
bool IsPathAbsolute(const std::string& path) { return !path.empty() && path[0] == '/'; }
bool PathEndsWith(std::string_view path, std::string_view right_query) {
return debug::StringEndsWith(path, right_query) &&
(path.size() == right_query.size() || path[path.size() - right_query.size() - 1] == '/');
}
std::string CatPathComponents(const std::string& first, const std::string& second) {
// Second component shouldn't begin with a slash.
FX_DCHECK(second.empty() || second[0] != '/');
std::string result;
result.reserve(first.size() + second.size() + 1);
result.append(first);
if (!first.empty() && !second.empty() && first.back() != '/')
result.push_back('/');
result.append(second);
return result;
}
std::string NormalizePath(const std::string& path) {
return std::filesystem::path(path).lexically_normal();
}
std::time_t GetFileModificationTime(const std::string& path) {
std::error_code ec;
std::filesystem::file_time_type last_write = std::filesystem::last_write_time(path, ec);
if (ec)
return 0;
return std::chrono::duration_cast<std::chrono::seconds>(last_write.time_since_epoch()).count();
}
bool PathStartsWith(const std::filesystem::path& path, const std::filesystem::path& base) {
if (path.is_absolute() != base.is_absolute())
return false;
if (path.empty())
return base.empty();
auto path_it = path.begin();
for (const auto& ancestor : base) {
if (ancestor.empty() || ancestor == ".")
continue;
while (path_it->empty() || *path_it == ".")
path_it++;
if (path_it == path.end())
return false;
if (ancestor != *path_it)
return false;
path_it++;
}
return true;
}
std::filesystem::path PathRelativeTo(const std::filesystem::path& path,
const std::filesystem::path& base) {
FX_CHECK(path.is_absolute() == base.is_absolute());
auto base_it = base.begin();
auto path_it = path.begin();
while (base_it != base.end() && path_it != path.end() && *base_it == *path_it) {
base_it++;
path_it++;
}
std::filesystem::path res;
while (base_it != base.end()) {
if (*base_it != ".") {
res.append("..");
}
base_it++;
}
while (path_it != path.end()) {
res.append(path_it->string());
path_it++;
}
return res;
}
std::optional<std::string> ExpandAndNormalizePath(const std::string& path) {
// Remove all the ".." and "." components from the path.
std::string normalized_path = NormalizePath(path);
std::filesystem::path p(normalized_path);
if (p.is_absolute() || p.empty()) {
return normalized_path;
}
std::filesystem::path expand;
// Support "~", "~/", $HOME, and "$HOME/".
if (PathStartsWith(p, "~") || PathStartsWith(p, "$HOME")) {
const char* home = getenv("HOME");
if (home) {
expand = std::filesystem::path(home);
// Collect the rest of the path.
auto it = p.begin();
for (++it; it != p.end(); ++it) {
expand /= *it;
}
} else {
LOGS(Warn) << "HOME environment variable is not set.";
return std::nullopt;
}
} else if (normalized_path.starts_with('~')) {
LOGS(Warn) << "~username is not supported yet.";
return std::nullopt;
} else { // Start of a relative path.
expand = std::filesystem::absolute(normalized_path);
}
return expand.string();
}
} // namespace zxdb