blob: 96ee91f1867308ff1eb272b05e3bf48e946bf613 [file]
/*
* Copyright (C) 2026 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "src/trace_processor/rpc/session_paths.h"
#include <cctype>
#include <cstdint>
#include <cstdlib>
#include <optional>
#include <string>
#include "perfetto/base/build_config.h"
#include "perfetto/base/proc_utils.h"
#include "perfetto/base/time.h"
#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
#include <direct.h> // _getcwd()
#else
#include <unistd.h> // getcwd()
#endif
#include "perfetto/ext/base/file_utils.h"
#include "perfetto/ext/base/murmur_hash.h"
#include "perfetto/ext/base/status_macros.h"
#include "perfetto/ext/base/string_utils.h"
#include "perfetto/ext/base/temp_file.h"
#include "perfetto/ext/base/utils.h"
namespace perfetto::trace_processor::session {
namespace {
#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
constexpr char kPathSep = '\\';
#else
constexpr char kPathSep = '/';
#endif
// Conservative AF_UNIX sun_path limit (108 on Linux, 104 on macOS/BSD). We use
// the smallest so a path that validates here binds on every platform.
constexpr size_t kMaxAfUnixPath = 104;
// Curated, lowercase word banks for auto-generated names. Three banks of 16
// give 4096 combinations; live collisions are resolved by the caller's bind
// check, so this only needs to make them rare and memorable.
constexpr const char* kAdjectives[] = {
"calm", "brave", "lucky", "happy", "swift", "quiet", "bold", "eager",
"merry", "kind", "neat", "proud", "witty", "warm", "shiny", "gentle"};
constexpr const char* kColors[] = {
"amber", "azure", "blue", "coral", "green", "ivory", "jade", "lilac",
"olive", "peach", "plum", "ruby", "rust", "teal", "gold", "violet"};
constexpr const char* kAnimals[] = {
"otter", "falcon", "lynx", "panda", "tapir", "heron", "ibex", "koala",
"marten", "quokka", "raven", "shrew", "stork", "viper", "wren", "yak"};
bool IsAbsolutePath(const std::string& p) {
if (p.empty())
return false;
if (p[0] == '/' || p[0] == '\\')
return true;
// Windows drive-letter path, e.g. "C:\foo" or "C:/foo".
return p.size() >= 3 && std::isalpha(static_cast<unsigned char>(p[0])) &&
p[1] == ':' && (p[2] == '\\' || p[2] == '/');
}
// Returns true if the substring after the last ':' is non-empty and all digits
// (i.e. a "host:port" address).
bool HasTrailingPort(const std::string& addr) {
size_t colon = addr.rfind(':');
if (colon == std::string::npos || colon + 1 == addr.size())
return false;
for (size_t i = colon + 1; i < addr.size(); ++i) {
if (!std::isdigit(static_cast<unsigned char>(addr[i])))
return false;
}
return true;
}
} // namespace
bool IsValidSessionName(const std::string& name) {
if (name.empty() || name.size() > kMaxSessionNameLen)
return false;
if (!std::isalnum(static_cast<unsigned char>(name[0])))
return false;
for (char c : name) {
if (!std::isalnum(static_cast<unsigned char>(c)) && c != '_' && c != '-')
return false;
}
return true;
}
std::string GenerateSessionName() {
uint64_t seed = static_cast<uint64_t>(base::GetWallTimeNs().count()) ^
(static_cast<uint64_t>(base::GetProcessId()) << 32);
const char* adj = kAdjectives[base::MurmurHashCombine(seed, 0) %
base::ArraySize(kAdjectives)];
const char* col =
kColors[base::MurmurHashCombine(seed, 1) % base::ArraySize(kColors)];
const char* ani =
kAnimals[base::MurmurHashCombine(seed, 2) % base::ArraySize(kAnimals)];
return std::string(adj) + "-" + col + "-" + ani;
}
base::StatusOr<std::string> EnsureSessionDir() {
std::string base_dir;
#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
if (const char* local = getenv("LOCALAPPDATA"); local && *local) {
base_dir = local;
} else {
base_dir = base::GetSysTempDir();
}
#elif PERFETTO_BUILDFLAG(PERFETTO_OS_APPLE)
base_dir = base::GetSysTempDir(); // $TMPDIR (per-user) or /tmp.
#else
if (const char* xdg = getenv("XDG_RUNTIME_DIR"); xdg && *xdg) {
base_dir = xdg;
} else {
base_dir = base::GetSysTempDir();
}
#endif
if (base_dir.empty())
return base::ErrStatus("Could not determine a base directory for sessions");
std::string dir = base_dir + kPathSep + "perfetto";
// 0700: the socket exposes the loaded trace, so keep the directory private
// (mode is ignored on Windows).
if (!base::Mkdir(dir, 0700) && !base::FileExists(dir))
return base::ErrStatus("Failed to create session directory %s",
dir.c_str());
return dir;
}
base::StatusOr<std::string> SessionSocketPath(const std::string& name) {
if (!IsValidSessionName(name)) {
return base::ErrStatus(
"Invalid session name '%s' (must match [A-Za-z0-9][A-Za-z0-9_-]* and "
"be "
"at most %zu chars)",
name.c_str(), kMaxSessionNameLen);
}
ASSIGN_OR_RETURN(std::string dir, EnsureSessionDir());
std::string path = dir + kPathSep + name + ".sock";
RETURN_IF_ERROR(ValidateAfUnixPathLength(path));
return path;
}
base::Status ValidateAfUnixPathLength(const std::string& path) {
if (path.size() + 1 > kMaxAfUnixPath) {
return base::ErrStatus(
"Socket path is too long (%zu bytes, limit %zu): %s. Use --path with a "
"shorter directory.",
path.size(), kMaxAfUnixPath - 1, path.c_str());
}
return base::OkStatus();
}
std::string MakeAbsolutePath(const std::string& path) {
if (path.empty() || IsAbsolutePath(path))
return path;
char cwd[4096];
#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
if (!_getcwd(cwd, sizeof(cwd)))
return path;
#else
if (!getcwd(cwd, sizeof(cwd)))
return path;
#endif
return std::string(cwd) + kPathSep + path;
}
base::StatusOr<uint32_t> ParseDurationMs(const std::string& s) {
if (s.empty())
return base::ErrStatus("Empty duration");
if (s == "never" || s == "off" || s == "0")
return 0u;
uint64_t mult_ms = 1000; // Default unit is seconds.
std::string num = s;
char suffix = s.back();
if (suffix == 's' || suffix == 'm' || suffix == 'h') {
num = s.substr(0, s.size() - 1);
mult_ms = suffix == 's' ? 1000 : (suffix == 'm' ? 60 * 1000 : 3600 * 1000);
}
std::optional<int64_t> value = base::StringToInt64(num);
if (!value.has_value() || *value < 0) {
return base::ErrStatus(
"Invalid duration '%s' (expected e.g. 30s, 5m, 2h, or 'never')",
s.c_str());
}
return static_cast<uint32_t>(static_cast<uint64_t>(*value) * mult_ms);
}
RemoteAddrKind ClassifyRemoteAddr(const std::string& addr) {
if (base::EndsWith(addr, ".sock") || IsAbsolutePath(addr))
return RemoteAddrKind::kUnixPath;
if (addr.find("://") != std::string::npos || HasTrailingPort(addr))
return RemoteAddrKind::kHttp;
if (IsValidSessionName(addr))
return RemoteAddrKind::kSessionName;
return RemoteAddrKind::kHttp;
}
} // namespace perfetto::trace_processor::session