diff options
| author | Stefan Boberg <[email protected]> | 2026-03-13 09:24:59 +0100 |
|---|---|---|
| committer | GitHub Enterprise <[email protected]> | 2026-03-13 09:24:59 +0100 |
| commit | ef586f5930ac761f8e8e18cde2ebd5248efeaa4a (patch) | |
| tree | be6c7d3e11f9261c1d03d646bc579ac0d27452d7 /src | |
| parent | Switch httpclient default back-end over to libcurl (#832) (diff) | |
| download | zen-ef586f5930ac761f8e8e18cde2ebd5248efeaa4a.tar.xz zen-ef586f5930ac761f8e8e18cde2ebd5248efeaa4a.zip | |
Unix Domain Socket auto discovery (#833)
This PR adds end-to-end Unix domain socket (UDS) support, allowing zen CLI to discover and connect to UDS-only servers automatically.
- **`unix://` URI scheme in zen CLI**: The `-u` / `--hosturl` option now accepts `unix:///path/to/socket` to connect to a zenserver via a Unix domain socket instead of TCP.
- **Per-instance shared memory for extended server info**: Each zenserver instance now publishes a small shared memory section (keyed by SessionId) containing per-instance data that doesn't fit in the fixed-size ZenServerEntry -- starting with the UDS socket path. This is a 4KB pagefile-backed section on Windows (`Global\ZenInstance_{sessionid}`) and a POSIX shared memory object on Linux/Mac (`/UnrealEngineZen_{sessionid}`).
- **Client-side auto-discovery of UDS servers**: `zen info`, `zen status`, etc. now automatically discover and prefer UDS connections when a server publishes a socket path. Servers running with `--no-network` (UDS-only) are no longer invisible to the CLI.
- **`kNoNetwork` flag in ZenServerEntry**: Servers started with `--no-network` advertise this in their shared state entry. Clients skip TCP fallback for these servers, and display commands (`ps`, `status`, `top`) show `-` instead of a port number to indicate TCP is not available.
Diffstat (limited to 'src')
28 files changed, 538 insertions, 105 deletions
diff --git a/src/zen/cmds/admin_cmd.cpp b/src/zen/cmds/admin_cmd.cpp index 15e854796..034d430fd 100644 --- a/src/zen/cmds/admin_cmd.cpp +++ b/src/zen/cmds/admin_cmd.cpp @@ -17,7 +17,7 @@ namespace zen { ScrubCommand::ScrubCommand() { m_Options.add_options()("h,help", "Print help"); - m_Options.add_option("", "u", "hosturl", "Host URL", cxxopts::value(m_HostName)->default_value(""), "<hosturl>"); + m_Options.add_option("", "u", "hosturl", kHostUrlHelp, cxxopts::value(m_HostName)->default_value(""), "<hosturl>"); m_Options.add_option("", "n", "dry", "Dry run (do not delete any data)", cxxopts::value(m_DryRun), "<bool>"); m_Options.add_option("", "", "no-gc", "Do not perform GC after scrub pass", cxxopts::value(m_NoGc), "<bool>"); m_Options.add_option("", "", "no-cas", "Do not scrub CAS stores", cxxopts::value(m_NoCas), "<bool>"); @@ -48,7 +48,7 @@ ScrubCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) throw OptionParseException("Unable to resolve server specification", m_Options.help()); } - HttpClient Http(m_HostName); + HttpClient Http = CreateHttpClient(m_HostName); HttpClient::KeyValueMap Params{{"skipdelete", ToString(m_DryRun)}, {"skipgc", ToString(m_NoGc)}, @@ -70,7 +70,7 @@ ScrubCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) GcCommand::GcCommand() { m_Options.add_options()("h,help", "Print help"); - m_Options.add_option("", "u", "hosturl", "Host URL", cxxopts::value(m_HostName)->default_value(""), "<hosturl>"); + m_Options.add_option("", "u", "hosturl", kHostUrlHelp, cxxopts::value(m_HostName)->default_value(""), "<hosturl>"); m_Options.add_option("", "s", "smallobjects", @@ -258,7 +258,7 @@ GcCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) } Params.Entries.insert({"enablevalidation", m_EnableValidation ? "true" : "false"}); - HttpClient Http(m_HostName); + HttpClient Http = CreateHttpClient(m_HostName); if (HttpClient::Response Response = Http.Post("/admin/gc"sv, HttpClient::Accept(HttpContentType::kJSON), Params)) { ZEN_CONSOLE("OK: {}", Response.ToText()); @@ -272,7 +272,7 @@ GcCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) GcStatusCommand::GcStatusCommand() { m_Options.add_options()("h,help", "Print help"); - m_Options.add_option("", "u", "hosturl", "Host URL", cxxopts::value(m_HostName)->default_value(""), "<hosturl>"); + m_Options.add_option("", "u", "hosturl", kHostUrlHelp, cxxopts::value(m_HostName)->default_value(""), "<hosturl>"); m_Options.add_option("", "d", "details", "Show detailed GC report", cxxopts::value(m_Details)->default_value("false"), "<details>"); } @@ -297,7 +297,7 @@ GcStatusCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) throw OptionParseException("Unable to resolve server specification", m_Options.help()); } - HttpClient Http(m_HostName); + HttpClient Http = CreateHttpClient(m_HostName); if (HttpClient::Response Response = Http.Get("/admin/gc"sv, HttpClient::Accept(HttpContentType::kJSON))) { ZEN_CONSOLE("OK: {}", Response.ToText()); @@ -311,7 +311,7 @@ GcStatusCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) GcStopCommand::GcStopCommand() { m_Options.add_options()("h,help", "Print help"); - m_Options.add_option("", "u", "hosturl", "Host URL", cxxopts::value(m_HostName)->default_value(""), "<hosturl>"); + m_Options.add_option("", "u", "hosturl", kHostUrlHelp, cxxopts::value(m_HostName)->default_value(""), "<hosturl>"); } GcStopCommand::~GcStopCommand() @@ -335,7 +335,7 @@ GcStopCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) throw OptionParseException("Unable to resolve server specification", m_Options.help()); } - HttpClient Http(m_HostName); + HttpClient Http = CreateHttpClient(m_HostName); if (HttpClient::Response Response = Http.Post("/admin/gc-stop"sv, HttpClient::Accept(HttpContentType::kJSON))) { if (Response.StatusCode == HttpResponseCode::Accepted) @@ -358,7 +358,7 @@ GcStopCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) JobCommand::JobCommand() { m_Options.add_options()("h,help", "Print help"); - m_Options.add_option("", "u", "hosturl", "Host URL", cxxopts::value(m_HostName)->default_value(""), "<hosturl>"); + m_Options.add_option("", "u", "hosturl", kHostUrlHelp, cxxopts::value(m_HostName)->default_value(""), "<hosturl>"); m_Options.add_option("", "j", "jobid", "Job id", cxxopts::value(m_JobId), "<jobid>"); m_Options.add_option("", "c", "cancel", "Cancel job id", cxxopts::value(m_Cancel), "<cancel>"); } @@ -384,7 +384,7 @@ JobCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) throw OptionParseException("Unable to resolve server specification", m_Options.help()); } - HttpClient Http(m_HostName); + HttpClient Http = CreateHttpClient(m_HostName); if (m_Cancel) { @@ -421,7 +421,7 @@ JobCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) LoggingCommand::LoggingCommand() { m_Options.add_options()("h,help", "Print help"); - m_Options.add_option("", "u", "hosturl", "Host URL", cxxopts::value(m_HostName)->default_value(""), "<hosturl>"); + m_Options.add_option("", "u", "hosturl", kHostUrlHelp, cxxopts::value(m_HostName)->default_value(""), "<hosturl>"); m_Options.add_option("", "", "cache-write-log", "Enable cache write logging", cxxopts::value(m_CacheWriteLog), "<enable/disable>"); m_Options.add_option("", "", "cache-access-log", "Enable cache access logging", cxxopts::value(m_CacheAccessLog), "<enable/disable>"); m_Options @@ -467,7 +467,7 @@ LoggingCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) throw OptionParseException("Unable to resolve server specification", m_Options.help()); } - HttpClient Http(m_HostName); + HttpClient Http = CreateHttpClient(m_HostName); HttpClient::KeyValueMap Parameters; @@ -564,7 +564,7 @@ LoggingCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) FlushCommand::FlushCommand() { m_Options.add_options()("h,help", "Print help"); - m_Options.add_option("", "u", "hosturl", "Host URL", cxxopts::value(m_HostName)->default_value(""), "<hosturl>"); + m_Options.add_option("", "u", "hosturl", kHostUrlHelp, cxxopts::value(m_HostName)->default_value(""), "<hosturl>"); } FlushCommand::~FlushCommand() = default; @@ -586,7 +586,7 @@ FlushCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) throw OptionParseException("Unable to resolve server specification", m_Options.help()); } - zen::HttpClient Http(m_HostName); + zen::HttpClient Http = CreateHttpClient(m_HostName); if (zen::HttpClient::Response Response = Http.Post("/admin/flush"sv)) { diff --git a/src/zen/cmds/cache_cmd.cpp b/src/zen/cmds/cache_cmd.cpp index 2aa142973..a8c15f119 100644 --- a/src/zen/cmds/cache_cmd.cpp +++ b/src/zen/cmds/cache_cmd.cpp @@ -59,7 +59,7 @@ namespace { DropCommand::DropCommand() { m_Options.add_options()("h,help", "Print help"); - m_Options.add_option("", "u", "hosturl", "Host URL", cxxopts::value(m_HostName)->default_value(""), "<hosturl>"); + m_Options.add_option("", "u", "hosturl", kHostUrlHelp, cxxopts::value(m_HostName)->default_value(""), "<hosturl>"); m_Options.add_option("", "n", "namespace", "Namespace name", cxxopts::value(m_NamespaceName), "<namespacename>"); m_Options.add_option("", "b", "bucket", "Bucket name", cxxopts::value(m_BucketName), "<bucketname>"); m_Options.parse_positional({"namespace", "bucket"}); @@ -105,7 +105,7 @@ DropCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) ZEN_CONSOLE("Dropping {}", DropDescription); - HttpClient Http(m_HostName); + HttpClient Http = CreateHttpClient(m_HostName); if (HttpClient::Response Response = Http.Delete(Url)) { ZEN_CONSOLE("{}", Response.ToText()); @@ -119,7 +119,7 @@ DropCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) CacheInfoCommand::CacheInfoCommand() { m_Options.add_options()("h,help", "Print help"); - m_Options.add_option("", "u", "hosturl", "Host URL", cxxopts::value(m_HostName)->default_value(""), "<hosturl>"); + m_Options.add_option("", "u", "hosturl", kHostUrlHelp, cxxopts::value(m_HostName)->default_value(""), "<hosturl>"); m_Options.add_option("", "n", "namespace", "Namespace name", cxxopts::value(m_NamespaceName), "<namespacename>"); m_Options.add_option("", "", @@ -196,7 +196,7 @@ CacheInfoCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) Parameters.Entries.insert({"bucketsize", "true"}); } - HttpClient Http(m_HostName); + HttpClient Http = CreateHttpClient(m_HostName); if (HttpClient::Response Response = Http.Get(Url, HttpClient::Accept(ZenContentType::kJSON), Parameters)) { ZEN_CONSOLE("{}", Response.ToText()); @@ -210,7 +210,7 @@ CacheInfoCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) CacheStatsCommand::CacheStatsCommand() { m_Options.add_options()("h,help", "Print help"); - m_Options.add_option("", "u", "hosturl", "Host URL", cxxopts::value(m_HostName)->default_value(""), "<hosturl>"); + m_Options.add_option("", "u", "hosturl", kHostUrlHelp, cxxopts::value(m_HostName)->default_value(""), "<hosturl>"); } CacheStatsCommand::~CacheStatsCommand() = default; @@ -232,7 +232,7 @@ CacheStatsCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv throw OptionParseException("Unable to resolve server specification", m_Options.help()); } - HttpClient Http(m_HostName); + HttpClient Http = CreateHttpClient(m_HostName); if (HttpClient::Response Response = Http.Get("/stats/z$", HttpClient::Accept(ZenContentType::kJSON))) { ZEN_CONSOLE("{}", Response.ToText()); @@ -246,7 +246,7 @@ CacheStatsCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv CacheDetailsCommand::CacheDetailsCommand() { m_Options.add_options()("h,help", "Print help"); - m_Options.add_option("", "u", "hosturl", "Host URL", cxxopts::value(m_HostName)->default_value(""), "<hosturl>"); + m_Options.add_option("", "u", "hosturl", kHostUrlHelp, cxxopts::value(m_HostName)->default_value(""), "<hosturl>"); m_Options.add_option("", "c", "csv", "Info on csv format", cxxopts::value(m_CSV), "<csv>"); m_Options.add_option("", "d", "details", "Get detailed information about records", cxxopts::value(m_Details), "<details>"); m_Options.add_option("", @@ -329,7 +329,7 @@ CacheDetailsCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** ar Url = "/z$/details$"; } - HttpClient Http(m_HostName); + HttpClient Http = CreateHttpClient(m_HostName); if (HttpClient::Response Response = Http.Get(Url, Headers, Parameters)) { ZEN_CONSOLE("{}", Response.ToText()); @@ -343,7 +343,7 @@ CacheDetailsCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** ar CacheGenerateCommand::CacheGenerateCommand() { m_Options.add_options()("h,help", "Print help"); - m_Options.add_option("", "u", "hosturl", "Host URL", cxxopts::value(m_HostName)->default_value(""), "<hosturl>"); + m_Options.add_option("", "u", "hosturl", kHostUrlHelp, cxxopts::value(m_HostName)->default_value(""), "<hosturl>"); m_Options .add_option("", "n", "namespace", "Namespace to generate cache values/records for", cxxopts::value(m_Namespace), "<namespace>"); m_Options.add_option("", "b", "bucket", "Bucket name to generate cache values/records for", cxxopts::value(m_Bucket), "<bucket>"); @@ -431,7 +431,7 @@ CacheGenerateCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** a std::uniform_int_distribution<uint64_t> KeyDistribution; - HttpClient Http(m_HostName); + HttpClient Http = CreateHttpClient(m_HostName); auto GeneratePutCacheValueRequest( [this, &KeyDistribution, &Generator](std::span<std::uint64_t> BatchSizes, uint64_t RequestIndex) -> CbPackage { @@ -586,7 +586,7 @@ CacheGenerateCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** a CacheGetCommand::CacheGetCommand() { m_Options.add_options()("h,help", "Print help"); - m_Options.add_option("", "u", "hosturl", "Host URL", cxxopts::value(m_HostName)->default_value(""), "<hosturl>"); + m_Options.add_option("", "u", "hosturl", kHostUrlHelp, cxxopts::value(m_HostName)->default_value(""), "<hosturl>"); m_Options .add_option("", "n", "namespace", "Namespace to generate cache values/records for", cxxopts::value(m_Namespace), "<namespace>"); m_Options.add_option("", "b", "bucket", "Bucket name to generate cache values/records for", cxxopts::value(m_Bucket), "<bucket>"); @@ -656,7 +656,7 @@ CacheGetCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) } } - HttpClient Http(m_HostName); + HttpClient Http = CreateHttpClient(m_HostName); if (!m_OutputPath.empty()) { diff --git a/src/zen/cmds/exec_cmd.cpp b/src/zen/cmds/exec_cmd.cpp index 42c7119e7..cbc153e07 100644 --- a/src/zen/cmds/exec_cmd.cpp +++ b/src/zen/cmds/exec_cmd.cpp @@ -44,7 +44,7 @@ namespace zen { ExecCommand::ExecCommand() { m_Options.add_options()("h,help", "Print help"); - m_Options.add_option("", "u", "hosturl", "Host URL", cxxopts::value(m_HostName), "<hosturl>"); + m_Options.add_option("", "u", "hosturl", kHostUrlHelp, cxxopts::value(m_HostName), "<hosturl>"); m_Options.add_option("", "", "log", "Action log directory", cxxopts::value(m_RecordingLogPath), "<path>"); m_Options.add_option("", "p", "path", "Recording path (directory or .actionlog file)", cxxopts::value(m_RecordingPath), "<path>"); m_Options.add_option("", "", "offset", "Recording replay start offset", cxxopts::value(m_Offset), "<offset>"); diff --git a/src/zen/cmds/info_cmd.cpp b/src/zen/cmds/info_cmd.cpp index 49ad022cf..9faad5691 100644 --- a/src/zen/cmds/info_cmd.cpp +++ b/src/zen/cmds/info_cmd.cpp @@ -14,7 +14,7 @@ namespace zen { InfoCommand::InfoCommand() { m_Options.add_options()("h,help", "Print help"); - m_Options.add_option("", "u", "hosturl", "Host URL", cxxopts::value(m_HostName)->default_value(""), "<hosturl>"); + m_Options.add_option("", "u", "hosturl", kHostUrlHelp, cxxopts::value(m_HostName)->default_value(""), "<hosturl>"); } InfoCommand::~InfoCommand() @@ -38,7 +38,7 @@ InfoCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) throw OptionParseException("Unable to resolve server specification", m_Options.help()); } - HttpClient Http(m_HostName); + HttpClient Http = CreateHttpClient(m_HostName); if (HttpClient::Response Result = Http.Get("/admin/info", HttpClient::Accept(ZenContentType::kJSON))) { diff --git a/src/zen/cmds/projectstore_cmd.cpp b/src/zen/cmds/projectstore_cmd.cpp index c0780c7e8..d31c34fd0 100644 --- a/src/zen/cmds/projectstore_cmd.cpp +++ b/src/zen/cmds/projectstore_cmd.cpp @@ -507,7 +507,7 @@ namespace projectstore_impl { DropProjectCommand::DropProjectCommand() { m_Options.add_options()("h,help", "Print help"); - m_Options.add_option("", "u", "hosturl", "Host URL", cxxopts::value(m_HostName)->default_value(""), "<hosturl>"); + m_Options.add_option("", "u", "hosturl", kHostUrlHelp, cxxopts::value(m_HostName)->default_value(""), "<hosturl>"); m_Options.add_option("", "p", "project", "Project name", cxxopts::value(m_ProjectName), "<projectid>"); m_Options.add_option("", "o", "oplog", "Oplog name", cxxopts::value(m_OplogName), "<oplogid>"); m_Options.add_option("", "", "dryrun", "Dry run - resolve arguments but do not drop", cxxopts::value(m_DryRun), "<dryrun>"); @@ -537,7 +537,7 @@ DropProjectCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** arg throw OptionParseException("Unable to resolve server specification", m_Options.help()); } - HttpClient Http(m_HostName); + HttpClient Http = CreateHttpClient(m_HostName); m_ProjectName = ResolveProject(Http, m_ProjectName); if (m_ProjectName.empty()) @@ -598,7 +598,7 @@ DropProjectCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** arg ProjectInfoCommand::ProjectInfoCommand() { m_Options.add_options()("h,help", "Print help"); - m_Options.add_option("", "u", "hosturl", "Host URL", cxxopts::value(m_HostName)->default_value(""), "<hosturl>"); + m_Options.add_option("", "u", "hosturl", kHostUrlHelp, cxxopts::value(m_HostName)->default_value(""), "<hosturl>"); m_Options.add_option("", "p", "project", "Project name", cxxopts::value(m_ProjectName), "<projectid>"); m_Options.add_option("", "o", "oplog", "Oplog name", cxxopts::value(m_OplogName), "<oplogid>"); m_Options.parse_positional({"project", "oplog"}); @@ -632,7 +632,7 @@ ProjectInfoCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** arg throw OptionParseException("'--project' is required", m_Options.help()); } - HttpClient Http(m_HostName); + HttpClient Http = CreateHttpClient(m_HostName); std::string Url; if (m_ProjectName.empty()) @@ -684,7 +684,7 @@ ProjectInfoCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** arg CreateProjectCommand::CreateProjectCommand() { m_Options.add_options()("h,help", "Print help"); - m_Options.add_option("", "u", "hosturl", "Host URL", cxxopts::value(m_HostName)->default_value(""), "<hosturl>"); + m_Options.add_option("", "u", "hosturl", kHostUrlHelp, cxxopts::value(m_HostName)->default_value(""), "<hosturl>"); m_Options.add_option("", "p", "project", "Project name", cxxopts::value(m_ProjectId), "<projectid>"); m_Options.add_option("", "", "rootdir", "Absolute path to root directory", cxxopts::value(m_RootDir), "<root>"); m_Options.add_option("", "", "enginedir", "Absolute path to engine root directory", cxxopts::value(m_EngineRootDir), "<engineroot>"); @@ -721,7 +721,7 @@ CreateProjectCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** a throw OptionParseException("'--project' is required", m_Options.help()); } - HttpClient Http(m_HostName); + HttpClient Http = CreateHttpClient(m_HostName); std::string Url = fmt::format("/prj/{}", m_ProjectId); @@ -756,7 +756,7 @@ CreateProjectCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** a CreateOplogCommand::CreateOplogCommand() { m_Options.add_options()("h,help", "Print help"); - m_Options.add_option("", "u", "hosturl", "Host URL", cxxopts::value(m_HostName)->default_value(""), "<hosturl>"); + m_Options.add_option("", "u", "hosturl", kHostUrlHelp, cxxopts::value(m_HostName)->default_value(""), "<hosturl>"); m_Options.add_option("", "p", "project", "Project name", cxxopts::value(m_ProjectId), "<projectid>"); m_Options.add_option("", "o", "oplog", "Oplog name", cxxopts::value(m_OplogId), "<oplogid>"); m_Options.add_option("", "", "gcpath", "Absolute path to oplog lifetime marker file", cxxopts::value(m_GcPath), "<path>"); @@ -791,8 +791,8 @@ CreateOplogCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** arg throw OptionParseException("'--project' is required", m_Options.help()); } - HttpClient Http(m_HostName); - m_ProjectId = ResolveProject(Http, m_ProjectId); + HttpClient Http = CreateHttpClient(m_HostName); + m_ProjectId = ResolveProject(Http, m_ProjectId); if (m_ProjectId.empty()) { throw std::runtime_error("Project can not be found"); @@ -835,7 +835,7 @@ CreateOplogCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** arg ExportOplogCommand::ExportOplogCommand() { m_Options.add_options()("h,help", "Print help"); - m_Options.add_option("", "u", "hosturl", "Host URL", cxxopts::value(m_HostName)->default_value(""), "<hosturl>"); + m_Options.add_option("", "u", "hosturl", kHostUrlHelp, cxxopts::value(m_HostName)->default_value(""), "<hosturl>"); m_Options.add_option("", "p", "project", "Project name", cxxopts::value(m_ProjectName), "<projectid>"); m_Options.add_option("", "o", "oplog", "Oplog name", cxxopts::value(m_OplogName), "<oplogid>"); m_Options.add_option("", "", "maxblocksize", "Max size for bundled attachments", cxxopts::value(m_MaxBlockSize), "<blocksize>"); @@ -1022,8 +1022,8 @@ ExportOplogCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** arg throw OptionParseException("'--project' is required", m_Options.help()); } - HttpClient Http(m_HostName); - m_ProjectName = ResolveProject(Http, m_ProjectName); + HttpClient Http = CreateHttpClient(m_HostName); + m_ProjectName = ResolveProject(Http, m_ProjectName); if (m_ProjectName.empty()) { throw std::runtime_error("Project can not be found"); @@ -1368,7 +1368,7 @@ ExportOplogCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** arg ImportOplogCommand::ImportOplogCommand() { m_Options.add_options()("h,help", "Print help"); - m_Options.add_option("", "u", "hosturl", "Host URL", cxxopts::value(m_HostName)->default_value(""), "<hosturl>"); + m_Options.add_option("", "u", "hosturl", kHostUrlHelp, cxxopts::value(m_HostName)->default_value(""), "<hosturl>"); m_Options.add_option("", "p", "project", "Project name", cxxopts::value(m_ProjectName), "<projectid>"); m_Options.add_option("", "o", "oplog", "Oplog name", cxxopts::value(m_OplogName), "<oplogid>"); m_Options.add_option("", @@ -1541,8 +1541,8 @@ ImportOplogCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** arg m_Options.help()); } - HttpClient Http(m_HostName); - m_ProjectName = ResolveProject(Http, m_ProjectName); + HttpClient Http = CreateHttpClient(m_HostName); + m_ProjectName = ResolveProject(Http, m_ProjectName); if (m_ProjectName.empty()) { throw std::runtime_error("Project can not be found"); @@ -1782,7 +1782,7 @@ ImportOplogCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** arg SnapshotOplogCommand::SnapshotOplogCommand() { m_Options.add_options()("h,help", "Print help"); - m_Options.add_option("", "u", "hosturl", "Host URL", cxxopts::value(m_HostName)->default_value(""), "<hosturl>"); + m_Options.add_option("", "u", "hosturl", kHostUrlHelp, cxxopts::value(m_HostName)->default_value(""), "<hosturl>"); m_Options.add_option("", "p", "project", "Project name", cxxopts::value(m_ProjectName), "<projectid>"); m_Options.add_option("", "o", "oplog", "Oplog name", cxxopts::value(m_OplogName), "<oplogid>"); @@ -1813,7 +1813,7 @@ SnapshotOplogCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** a throw OptionParseException("Unable to resolve server specification", m_Options.help()); } - HttpClient Http(m_HostName); + HttpClient Http = CreateHttpClient(m_HostName); if (m_ProjectName.empty()) { @@ -1851,7 +1851,7 @@ SnapshotOplogCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** a ProjectStatsCommand::ProjectStatsCommand() { m_Options.add_options()("h,help", "Print help"); - m_Options.add_option("", "u", "hosturl", "Host URL", cxxopts::value(m_HostName)->default_value(""), "<hosturl>"); + m_Options.add_option("", "u", "hosturl", kHostUrlHelp, cxxopts::value(m_HostName)->default_value(""), "<hosturl>"); } ProjectStatsCommand::~ProjectStatsCommand() @@ -1876,7 +1876,7 @@ ProjectStatsCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** ar throw OptionParseException("Unable to resolve server specification", m_Options.help()); } - HttpClient Http(m_HostName); + HttpClient Http = CreateHttpClient(m_HostName); if (HttpClient::Response Result = Http.Get("/stats/prj", HttpClient::Accept(ZenContentType::kJSON))) { ZEN_CONSOLE("{}", Result.ToText()); @@ -1892,7 +1892,7 @@ ProjectStatsCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** ar ProjectOpDetailsCommand::ProjectOpDetailsCommand() { m_Options.add_options()("h,help", "Print help"); - m_Options.add_option("", "u", "hosturl", "Host URL", cxxopts::value(m_HostName)->default_value(""), "<hosturl>"); + m_Options.add_option("", "u", "hosturl", kHostUrlHelp, cxxopts::value(m_HostName)->default_value(""), "<hosturl>"); m_Options.add_option("", "c", "csv", "Output in CSV format (default is JSon)", cxxopts::value(m_CSV), "<csv>"); m_Options.add_option("", "d", "details", "Detailed info on oplog", cxxopts::value(m_Details), "<details>"); m_Options.add_option("", "o", "opdetails", "Details info on oplog body", cxxopts::value(m_OpDetails), "<opdetails>"); @@ -1929,7 +1929,7 @@ ProjectOpDetailsCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char* throw OptionParseException("Unable to resolve server specification", m_Options.help()); } - HttpClient Http(m_HostName); + HttpClient Http = CreateHttpClient(m_HostName); m_ProjectName = ResolveProject(Http, m_ProjectName); if (m_ProjectName.empty()) @@ -1982,7 +1982,7 @@ ProjectOpDetailsCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char* OplogMirrorCommand::OplogMirrorCommand() { m_Options.add_options()("h,help", "Print help"); - m_Options.add_option("", "u", "hosturl", "Host URL", cxxopts::value(m_HostName)->default_value(""), "<hosturl>"); + m_Options.add_option("", "u", "hosturl", kHostUrlHelp, cxxopts::value(m_HostName)->default_value(""), "<hosturl>"); m_Options.add_option("", "p", "project", "Project name to get info from", cxxopts::value(m_ProjectName), "<projectid>"); m_Options.add_option("", "l", "oplog", "Oplog name to get info from", cxxopts::value(m_OplogName), "<oplogid>"); m_Options.add_option("", "t", "target", "Target directory for mirror", cxxopts::value(m_MirrorRootPath), "<path>"); @@ -2045,7 +2045,7 @@ OplogMirrorCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** arg throw OptionParseException("Unable to resolve server specification", m_Options.help()); } - HttpClient Http(m_HostName); + HttpClient Http = CreateHttpClient(m_HostName); m_ProjectName = ResolveProject(Http, m_ProjectName); if (m_ProjectName.empty()) @@ -2283,7 +2283,7 @@ OplogMirrorCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** arg OplogValidateCommand::OplogValidateCommand() { m_Options.add_options()("h,help", "Print help"); - m_Options.add_option("", "u", "hosturl", "Host URL", cxxopts::value(m_HostName)->default_value(""), "<hosturl>"); + m_Options.add_option("", "u", "hosturl", kHostUrlHelp, cxxopts::value(m_HostName)->default_value(""), "<hosturl>"); m_Options.add_option("", "p", "project", "Project name to get info from", cxxopts::value(m_ProjectName), "<projectid>"); m_Options.add_option("", "l", "oplog", "Oplog name to get info from", cxxopts::value(m_OplogName), "<oplogid>"); @@ -2313,7 +2313,7 @@ OplogValidateCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** a throw OptionParseException("Unable to resolve server specification", m_Options.help()); } - HttpClient Http(m_HostName); + HttpClient Http = CreateHttpClient(m_HostName); m_ProjectName = ResolveProject(Http, m_ProjectName); if (m_ProjectName.empty()) diff --git a/src/zen/cmds/rpcreplay_cmd.cpp b/src/zen/cmds/rpcreplay_cmd.cpp index 70e9e5300..3bf81a9df 100644 --- a/src/zen/cmds/rpcreplay_cmd.cpp +++ b/src/zen/cmds/rpcreplay_cmd.cpp @@ -32,7 +32,7 @@ using namespace std::literals; RpcStartRecordingCommand::RpcStartRecordingCommand() { m_Options.add_options()("h,help", "Print help"); - m_Options.add_option("", "u", "hosturl", "Host URL", cxxopts::value(m_HostName)->default_value(""), "<hosturl>"); + m_Options.add_option("", "u", "hosturl", kHostUrlHelp, cxxopts::value(m_HostName)->default_value(""), "<hosturl>"); m_Options.add_option("", "p", "path", "Recording file path", cxxopts::value(m_RecordingPath), "<path>"); m_Options.parse_positional("path"); @@ -61,7 +61,7 @@ RpcStartRecordingCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char throw OptionParseException("'--path' is required", m_Options.help()); } - HttpClient Http(m_HostName); + HttpClient Http = CreateHttpClient(m_HostName); if (HttpClient::Response Response = Http.Post("/z$/exec$/start-recording"sv, HttpClient::KeyValueMap{}, HttpClient::KeyValueMap({{"path", m_RecordingPath}}))) { @@ -78,7 +78,7 @@ RpcStartRecordingCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char RpcStopRecordingCommand::RpcStopRecordingCommand() { m_Options.add_options()("h,help", "Print help"); - m_Options.add_option("", "u", "hosturl", "Host URL", cxxopts::value(m_HostName)->default_value(""), "<hosturl>"); + m_Options.add_option("", "u", "hosturl", kHostUrlHelp, cxxopts::value(m_HostName)->default_value(""), "<hosturl>"); } RpcStopRecordingCommand::~RpcStopRecordingCommand() = default; @@ -100,7 +100,7 @@ RpcStopRecordingCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char* throw OptionParseException("Unable to resolve server specification", m_Options.help()); } - HttpClient Http(m_HostName); + HttpClient Http = CreateHttpClient(m_HostName); if (HttpClient::Response Response = Http.Post("/z$/exec$/stop-recording"sv)) { ZEN_CONSOLE("{}", Response.ToText()); @@ -116,7 +116,7 @@ RpcStopRecordingCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char* RpcReplayCommand::RpcReplayCommand() { m_Options.add_options()("h,help", "Print help"); - m_Options.add_option("", "u", "hosturl", "Host URL", cxxopts::value(m_HostName)->default_value(""), "<hosturl>"); + m_Options.add_option("", "u", "hosturl", kHostUrlHelp, cxxopts::value(m_HostName)->default_value(""), "<hosturl>"); m_Options.add_option("", "p", "path", "Recording file path", cxxopts::value(m_RecordingPath), "<path>"); m_Options.add_option("", "", "dry", "Do a dry run", cxxopts::value(m_DryRun), "<enable>"); m_Options.add_option("", @@ -223,7 +223,7 @@ RpcReplayCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) if (m_OnHost) { - HttpClient Http(m_HostName); + HttpClient Http = CreateHttpClient(m_HostName); if (HttpClient::Response Response = Http.Post("/z$/exec$/replay-recording"sv, HttpClient::KeyValueMap{}, @@ -302,7 +302,7 @@ RpcReplayCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) } }); - HttpClient Http{m_HostName}; + HttpClient Http = CreateHttpClient(m_HostName); uint64_t EntryIndex = EntryOffset.fetch_add(m_Stride); while (EntryIndex < EntryCount) diff --git a/src/zen/cmds/serve_cmd.cpp b/src/zen/cmds/serve_cmd.cpp index 49389bcdf..03007a86c 100644 --- a/src/zen/cmds/serve_cmd.cpp +++ b/src/zen/cmds/serve_cmd.cpp @@ -21,7 +21,7 @@ using namespace std::literals; ServeCommand::ServeCommand() { m_Options.add_options()("h,help", "Print help"); - m_Options.add_option("", "u", "hosturl", "Host URL", cxxopts::value(m_HostName)->default_value(""), "<hosturl>"); + m_Options.add_option("", "u", "hosturl", kHostUrlHelp, cxxopts::value(m_HostName)->default_value(""), "<hosturl>"); m_Options.add_option("", "p", "project", "Project name", cxxopts::value(m_ProjectName), "<projectid>"); m_Options.add_option("", "o", "oplog", "Oplog name", cxxopts::value(m_OplogName), "<oplogid>"); m_Options.add_option("", "", "path", "Root path to directory", cxxopts::value(m_RootPath), "<rootpath>"); @@ -183,7 +183,7 @@ ServeCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) const std::string ProjectUri = fmt::format("/prj/{}", m_ProjectName); const std::string ProjectOplogUri = fmt::format("/prj/{}/oplog/{}", m_ProjectName, m_OplogName); - HttpClient Client(m_HostName); + HttpClient Client = CreateHttpClient(m_HostName); // Ensure project exists diff --git a/src/zen/cmds/status_cmd.cpp b/src/zen/cmds/status_cmd.cpp index c43f85052..6ed3c42e1 100644 --- a/src/zen/cmds/status_cmd.cpp +++ b/src/zen/cmds/status_cmd.cpp @@ -4,6 +4,7 @@ #include <zencore/compactbinary.h> #include <zencore/compactbinaryutil.h> +#include <zencore/filesystem.h> #include <zencore/fmtutils.h> #include <zencore/logging.h> #include <zencore/string.h> @@ -64,7 +65,7 @@ StatusCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) return; } - ZEN_CONSOLE("{:>5} {:>6} {:>24}", "port", "pid", "session"); + ZEN_CONSOLE("{:>5} {:>6} {:>24} {}", "port", "pid", "session", "socket"); State.Snapshot([&](const ZenServerState::ZenServerEntry& Entry) { bool MatchesAnyPort = (m_Port == 0) && (EffectivePort == 0); bool MatchesEffectivePort = (EffectivePort != 0) && (Entry.EffectiveListenPort.load() == EffectivePort); @@ -73,7 +74,22 @@ StatusCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) { StringBuilder<25> SessionStringBuilder; Entry.GetSessionId().ToString(SessionStringBuilder); - ZEN_CONSOLE("{:>5} {:>6} {:>24}", Entry.EffectiveListenPort.load(), Entry.Pid.load(), SessionStringBuilder); + + std::string SocketPath; + if (Entry.HasInstanceInfo()) + { + ZenServerInstanceInfo Info; + if (Info.OpenReadOnly(Entry.GetSessionId())) + { + InstanceInfoData Data = Info.Read(); + if (!Data.UnixSocketPath.empty()) + { + SocketPath = PathToUtf8(Data.UnixSocketPath); + } + } + } + std::string PortStr = Entry.IsNoNetwork() ? std::string("-") : fmt::to_string(Entry.EffectiveListenPort.load()); + ZEN_CONSOLE("{:>5} {:>6} {:>24} {}", PortStr, Entry.Pid.load(), SessionStringBuilder, SocketPath); } }); } diff --git a/src/zen/cmds/top_cmd.cpp b/src/zen/cmds/top_cmd.cpp index f674db6cd..cf2896f0f 100644 --- a/src/zen/cmds/top_cmd.cpp +++ b/src/zen/cmds/top_cmd.cpp @@ -2,6 +2,7 @@ #include "top_cmd.h" +#include <zencore/filesystem.h> #include <zencore/fmtutils.h> #include <zencore/logging.h> #include <zencore/system.h> @@ -81,13 +82,29 @@ TopCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) { if ((n++ % HeaderPeriod) == 0) { - ZEN_CONSOLE("{:>5} {:>6} {:>24}", "port", "pid", "session"); + ZEN_CONSOLE("{:>5} {:>6} {:>24} {}", "port", "pid", "session", "socket"); } State.Snapshot([&](const ZenServerState::ZenServerEntry& Entry) { StringBuilder<25> SessionStringBuilder; Entry.GetSessionId().ToString(SessionStringBuilder); - ZEN_CONSOLE("{:>5} {:>6} {:>24}", Entry.EffectiveListenPort.load(), Entry.Pid.load(), SessionStringBuilder); + + std::string SocketPath; + if (Entry.HasInstanceInfo()) + { + ZenServerInstanceInfo Info; + if (Info.OpenReadOnly(Entry.GetSessionId())) + { + InstanceInfoData Data = Info.Read(); + if (!Data.UnixSocketPath.empty()) + { + SocketPath = PathToUtf8(Data.UnixSocketPath); + } + } + } + + std::string PortStr = Entry.IsNoNetwork() ? std::string("-") : fmt::to_string(Entry.EffectiveListenPort.load()); + ZEN_CONSOLE("{:>5} {:>6} {:>24} {}", PortStr, Entry.Pid.load(), SessionStringBuilder, SocketPath); }); zen::Sleep(1000); @@ -121,7 +138,21 @@ PsCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) } State.Snapshot([&](const ZenServerState::ZenServerEntry& Entry) { - ZEN_CONSOLE("Port {} : pid {}", Entry.EffectiveListenPort.load(), Entry.Pid.load()); + std::string Extra; + if (Entry.HasInstanceInfo()) + { + ZenServerInstanceInfo Info; + if (Info.OpenReadOnly(Entry.GetSessionId())) + { + InstanceInfoData Data = Info.Read(); + if (!Data.UnixSocketPath.empty()) + { + Extra = fmt::format(" (unix: {})", Data.UnixSocketPath); + } + } + } + std::string PortStr = Entry.IsNoNetwork() ? std::string("-") : fmt::to_string(Entry.EffectiveListenPort.load()); + ZEN_CONSOLE("Port {} : pid {}{}", PortStr, Entry.Pid.load(), Extra); }); } diff --git a/src/zen/cmds/trace_cmd.cpp b/src/zen/cmds/trace_cmd.cpp index 41a30068c..54c0f080d 100644 --- a/src/zen/cmds/trace_cmd.cpp +++ b/src/zen/cmds/trace_cmd.cpp @@ -12,7 +12,7 @@ namespace zen { TraceCommand::TraceCommand() { m_Options.add_options()("h,help", "Print help"); - m_Options.add_option("", "u", "hosturl", "Host URL", cxxopts::value(m_HostName)->default_value(""), "<hosturl>"); + m_Options.add_option("", "u", "hosturl", kHostUrlHelp, cxxopts::value(m_HostName)->default_value(""), "<hosturl>"); m_Options.add_option("", "s", "stop", "Stop tracing", cxxopts::value(m_Stop)->default_value("false"), "<stop>"); m_Options.add_option("", "", "host", "Start tracing to host", cxxopts::value(m_TraceHost), "<hostip>"); m_Options.add_option("", "", "file", "Start tracing to file", cxxopts::value(m_TraceFile), "<filepath>"); @@ -37,7 +37,7 @@ TraceCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) throw OptionParseException("Unable to resolve server specification", m_Options.help()); } - zen::HttpClient Http(m_HostName); + zen::HttpClient Http = CreateHttpClient(m_HostName); if (m_Stop) { diff --git a/src/zen/cmds/ui_cmd.cpp b/src/zen/cmds/ui_cmd.cpp index da06ce305..4846b4d18 100644 --- a/src/zen/cmds/ui_cmd.cpp +++ b/src/zen/cmds/ui_cmd.cpp @@ -50,7 +50,7 @@ UiCommand::UiCommand() { m_Options.add_options()("h,help", "Print help"); m_Options.add_options()("a,all", "Open dashboard for all running instances", cxxopts::value(m_All)->default_value("false")); - m_Options.add_option("", "u", "hosturl", "Host URL", cxxopts::value(m_HostName)->default_value(""), "<hosturl>"); + m_Options.add_option("", "u", "hosturl", kHostUrlHelp, cxxopts::value(m_HostName)->default_value(""), "<hosturl>"); m_Options.add_option("", "p", "path", @@ -230,6 +230,11 @@ UiCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) throw OptionParseException("Unable to resolve server specification", m_Options.help()); } + if (IsUnixSocketSpec(m_HostName)) + { + throw std::runtime_error("Cannot open browser for a Unix domain socket connection"); + } + OpenBrowser(m_HostName); } diff --git a/src/zen/cmds/version_cmd.cpp b/src/zen/cmds/version_cmd.cpp index ed34d7011..0948de1bb 100644 --- a/src/zen/cmds/version_cmd.cpp +++ b/src/zen/cmds/version_cmd.cpp @@ -20,7 +20,7 @@ using namespace std::literals; VersionCommand::VersionCommand() { m_Options.add_options()("h,help", "Print help"); - m_Options.add_option("", "u", "hosturl", "Host URL", cxxopts::value(m_HostName), "[hosturl]"); + m_Options.add_option("", "u", "hosturl", kHostUrlHelp, cxxopts::value(m_HostName), "[hosturl]"); m_Options.add_option("", "d", "detailed", "Detailed Version", cxxopts::value(m_DetailedVersion), "[detailedversion]"); m_Options.add_option("", "o", "output-path", "Path for output", cxxopts::value(m_OutputPath), "[outputpath]"); m_Options.parse_positional({"hosturl"}); @@ -57,7 +57,7 @@ VersionCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) ZEN_CONSOLE("Querying host {}", m_HostName); } - HttpClient Client(m_HostName, HttpClientSettings{.Timeout = std::chrono::milliseconds(5000)}); + HttpClient Client = CreateHttpClient(m_HostName, {.Timeout = std::chrono::milliseconds(5000)}); HttpClient::KeyValueMap Parameters; if (m_DetailedVersion) diff --git a/src/zen/cmds/vfs_cmd.cpp b/src/zen/cmds/vfs_cmd.cpp index 79ec20ce2..29ad8dc7c 100644 --- a/src/zen/cmds/vfs_cmd.cpp +++ b/src/zen/cmds/vfs_cmd.cpp @@ -18,7 +18,7 @@ using namespace std::literals; VfsCommand::VfsCommand() { m_Options.add_option("", "", "verb", "VFS management verb (mount, unmount, info)", cxxopts::value(m_Verb), "<verb>"); - m_Options.add_option("", "u", "hosturl", "Host URL", cxxopts::value(m_HostName)->default_value(""), "<url>"); + m_Options.add_option("", "u", "hosturl", kHostUrlHelp, cxxopts::value(m_HostName)->default_value(""), "<url>"); m_Options.add_option("", "", "vfs-path", "Specify VFS mount point path", cxxopts::value(m_MountPath), "<path>"); m_Options.parse_positional({"verb", "vfs-path"}); @@ -45,7 +45,7 @@ VfsCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) if (m_HostName.empty()) throw OptionParseException("Unable to resolve server specification", m_Options.help()); - HttpClient Http(m_HostName); + HttpClient Http = CreateHttpClient(m_HostName); if (m_Verb == "mount"sv) { diff --git a/src/zen/cmds/workspaces_cmd.cpp b/src/zen/cmds/workspaces_cmd.cpp index af265d898..220ef6a9e 100644 --- a/src/zen/cmds/workspaces_cmd.cpp +++ b/src/zen/cmds/workspaces_cmd.cpp @@ -86,7 +86,7 @@ namespace { WorkspaceCommand::WorkspaceCommand() { m_Options.add_options()("h,help", "Print help"); - m_Options.add_option("", "u", "hosturl", "Host URL", cxxopts::value(m_HostName)->default_value(""), "<hosturl>"); + m_Options.add_option("", "u", "hosturl", kHostUrlHelp, cxxopts::value(m_HostName)->default_value(""), "<hosturl>"); m_Options.add_options()("system-dir", "Specify system root", cxxopts::value(m_SystemRootDir)); m_Options.add_option("", "v", "verb", "Verb for workspace - create, remove, info", cxxopts::value(m_Verb), "<verb>"); m_Options.parse_positional({"verb"}); @@ -182,7 +182,7 @@ WorkspaceCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) { if (!m_HostName.empty()) { - HttpClient Http(m_HostName); + HttpClient Http = CreateHttpClient(m_HostName); if (HttpClient::Response Result = Http.Get("/ws/refresh"); !Result) { ZEN_CONSOLE_WARN("Failed to refresh workspaces for host {}. Reason: '{}'", m_HostName, Result.ErrorMessage(""sv)); @@ -254,7 +254,7 @@ WorkspaceCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) { if (!m_HostName.empty()) { - HttpClient Http(m_HostName); + HttpClient Http = CreateHttpClient(m_HostName); if (HttpClient::Response Result = Http.Get("/ws/refresh"); !Result) { ZEN_CONSOLE_WARN("Failed to refresh workspaces for host {}. Reason: '{}'", m_HostName, Result.ErrorMessage(""sv)); @@ -275,7 +275,7 @@ WorkspaceCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) WorkspaceShareCommand::WorkspaceShareCommand() { m_Options.add_options()("h,help", "Print help"); - m_Options.add_option("", "u", "hosturl", "Host URL", cxxopts::value(m_HostName)->default_value(""), "<hosturl>"); + m_Options.add_option("", "u", "hosturl", kHostUrlHelp, cxxopts::value(m_HostName)->default_value(""), "<hosturl>"); m_Options.add_options()("system-dir", "Specify system root", cxxopts::value(m_SystemRootDir)); m_Options.add_option("", "v", "verb", "Verb for workspace - create, remove, info", cxxopts::value(m_Verb), "<verb>"); m_Options.parse_positional({"verb"}); @@ -475,7 +475,7 @@ WorkspaceShareCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** { if (!m_HostName.empty()) { - HttpClient Http(m_HostName); + HttpClient Http = CreateHttpClient(m_HostName); if (HttpClient::Response Result = Http.Get("/ws/refresh"); !Result) { ZEN_CONSOLE_WARN("Failed to refresh workspaces for host {}. Reason: '{}'", m_HostName, Result.ErrorMessage(""sv)); @@ -592,7 +592,7 @@ WorkspaceShareCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** { if (!m_HostName.empty()) { - HttpClient Http(m_HostName); + HttpClient Http = CreateHttpClient(m_HostName); if (HttpClient::Response Result = Http.Get("/ws/refresh"); !Result) { ZEN_CONSOLE_WARN("Failed to refresh workspaces for host {}. Reason: '{}'", m_HostName, Result.ErrorMessage(""sv)); @@ -645,7 +645,7 @@ WorkspaceShareCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** throw OptionParseException("Unable to resolve server specification", SubOption->help()); } - HttpClient Http(m_HostName); + HttpClient Http = CreateHttpClient(m_HostName); if (HttpClient::Response Result = Http.Get(fmt::format("/ws/{}/files", GetShareIdentityUrl(m_FilesOptions)), {}, Params)) { ZEN_CONSOLE("{}: {}", Result, Result.ToText()); @@ -678,7 +678,7 @@ WorkspaceShareCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** throw OptionParseException("Unable to resolve server specification", SubOption->help()); } - HttpClient Http(m_HostName); + HttpClient Http = CreateHttpClient(m_HostName); if (HttpClient::Response Result = Http.Get(fmt::format("/ws/{}/entries", GetShareIdentityUrl(m_EntriesOptions)), {}, Params)) { ZEN_CONSOLE("{}: {}", Result, Result.ToText()); @@ -753,8 +753,8 @@ WorkspaceShareCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** throw OptionParseException("'--chunk' is required", SubOption->help()); } - HttpClient Http(m_HostName); - m_ChunkId = ChunksToOidStrings(Http, m_WorkspaceId, m_ShareId, std::vector<std::string>{m_ChunkId})[0]; + HttpClient Http = CreateHttpClient(m_HostName); + m_ChunkId = ChunksToOidStrings(Http, m_WorkspaceId, m_ShareId, std::vector<std::string>{m_ChunkId})[0]; HttpClient::KeyValueMap Params; if (m_Offset != 0) @@ -794,8 +794,8 @@ WorkspaceShareCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** throw OptionParseException("'--chunks' is required", SubOption->help()); } - HttpClient Http(m_HostName); - m_ChunkIds = ChunksToOidStrings(Http, m_WorkspaceId, m_ShareId, m_ChunkIds); + HttpClient Http = CreateHttpClient(m_HostName); + m_ChunkIds = ChunksToOidStrings(Http, m_WorkspaceId, m_ShareId, m_ChunkIds); std::vector<RequestChunkEntry> ChunkRequests; ChunkRequests.resize(m_ChunkIds.size()); diff --git a/src/zen/zen.cpp b/src/zen/zen.cpp index 10e3a85fe..86154c291 100644 --- a/src/zen/zen.cpp +++ b/src/zen/zen.cpp @@ -369,9 +369,31 @@ GetReturnCodeFromHttpResult(const HttpClientError& Ex) } } +bool +ZenCmdBase::IsUnixSocketSpec(std::string_view Spec) +{ + return Spec.starts_with("unix://"); +} + +HttpClient +ZenCmdBase::CreateHttpClient(const std::string& HostSpec, HttpClientSettings Settings) +{ + if (IsUnixSocketSpec(HostSpec)) + { + Settings.UnixSocketPath = HostSpec.substr(7); // strip "unix://" + return HttpClient("http://localhost", Settings); + } + return HttpClient(HostSpec, Settings); +} + std::string ZenCmdBase::ResolveTargetHostSpec(const std::string& InHostSpec, uint16_t& OutEffectivePort) { + if (IsUnixSocketSpec(InHostSpec)) + { + return InHostSpec; // pass through as-is; parsed later in CreateHttpClient + } + if (InHostSpec.empty()) { // If no host is specified then look to see if we have an instance @@ -386,8 +408,30 @@ ZenCmdBase::ResolveTargetHostSpec(const std::string& InHostSpec, uint16_t& OutEf Servers.Snapshot([&](const zen::ZenServerState::ZenServerEntry& Entry) { if (ResolvedSpec.empty()) { - ResolvedSpec = fmt::format("http://localhost:{}", Entry.EffectiveListenPort.load()); OutEffectivePort = Entry.EffectiveListenPort; + + // Check for per-instance info (e.g. UDS path) + if (Entry.HasInstanceInfo()) + { + ZenServerInstanceInfo Info; + if (Info.OpenReadOnly(Entry.GetSessionId())) + { + InstanceInfoData Data = Info.Read(); + if (!Data.UnixSocketPath.empty()) + { + ResolvedSpec = "unix://" + PathToUtf8(Data.UnixSocketPath); + return; + } + } + } + + // Skip servers with --no-network since TCP is not reachable + if (Entry.IsNoNetwork()) + { + return; + } + + ResolvedSpec = fmt::format("http://localhost:{}", Entry.EffectiveListenPort.load()); } }); diff --git a/src/zen/zen.h b/src/zen/zen.h index 3cc06eea6..05ce32d0a 100644 --- a/src/zen/zen.h +++ b/src/zen/zen.h @@ -5,6 +5,7 @@ #include <zencore/except.h> #include <zencore/timer.h> #include <zencore/zencore.h> +#include <zenhttp/httpclient.h> #include <zenutil/config/commandlineoptions.h> #include <zenutil/config/loggingconfig.h> @@ -68,6 +69,11 @@ public: static std::string ResolveTargetHostSpec(const std::string& InHostSpec); static std::string ResolveTargetHostSpec(const std::string& InHostSpec, uint16_t& OutEffectivePort); + static bool IsUnixSocketSpec(std::string_view Spec); + static HttpClient CreateHttpClient(const std::string& HostSpec, HttpClientSettings Settings = {}); + + static constexpr const char* kHostUrlHelp = "Host URL or unix:///path/to/socket"; + static void LogExecutableVersionAndPid(); }; diff --git a/src/zenhttp/clients/httpclientcpr.cpp b/src/zenhttp/clients/httpclientcpr.cpp index a0f5cc38f..a52b8f74b 100644 --- a/src/zenhttp/clients/httpclientcpr.cpp +++ b/src/zenhttp/clients/httpclientcpr.cpp @@ -7,6 +7,7 @@ #include <zencore/compactbinarypackage.h> #include <zencore/compactbinaryutil.h> #include <zencore/compress.h> +#include <zencore/filesystem.h> #include <zencore/iobuffer.h> #include <zencore/iohash.h> #include <zencore/session.h> @@ -513,7 +514,7 @@ CprHttpClient::AllocSession(const std::string_view BaseUrl, if (!ConnectionSettings.UnixSocketPath.empty()) { - CprSession->SetUnixSocket(cpr::UnixSocket(ConnectionSettings.UnixSocketPath)); + CprSession->SetUnixSocket(cpr::UnixSocket(PathToUtf8(ConnectionSettings.UnixSocketPath))); } if (ConnectionSettings.InsecureSsl || !ConnectionSettings.CaBundlePath.empty()) diff --git a/src/zenhttp/clients/httpclientcurl.cpp b/src/zenhttp/clients/httpclientcurl.cpp index 0dbd5b975..ec9b7bac6 100644 --- a/src/zenhttp/clients/httpclientcurl.cpp +++ b/src/zenhttp/clients/httpclientcurl.cpp @@ -8,6 +8,7 @@ #include <zencore/compactbinaryutil.h> #include <zencore/compress.h> #include <zencore/except.h> +#include <zencore/filesystem.h> #include <zencore/iobuffer.h> #include <zencore/iohash.h> #include <zencore/session.h> @@ -808,7 +809,8 @@ CurlHttpClient::AllocSession(std::string_view ResourcePath, const KeyValueMap& P // Unix domain socket if (!m_ConnectionSettings.UnixSocketPath.empty()) { - curl_easy_setopt(Handle, CURLOPT_UNIX_SOCKET_PATH, m_ConnectionSettings.UnixSocketPath.c_str()); + std::string SocketPathUtf8 = PathToUtf8(m_ConnectionSettings.UnixSocketPath); + curl_easy_setopt(Handle, CURLOPT_UNIX_SOCKET_PATH, SocketPathUtf8.c_str()); } // Build URL with parameters diff --git a/src/zenhttp/clients/httpwsclient.cpp b/src/zenhttp/clients/httpwsclient.cpp index 2d566ae86..fbae9f5fe 100644 --- a/src/zenhttp/clients/httpwsclient.cpp +++ b/src/zenhttp/clients/httpwsclient.cpp @@ -5,6 +5,8 @@ #include "../servers/wsframecodec.h" #include <zencore/base64.h> +#include <zencore/filesystem.h> +#include <zencore/fmtutils.h> #include <zencore/logging.h> #include <zencore/string.h> @@ -155,7 +157,7 @@ struct HttpWsClient::Impl } }); - asio::local::stream_protocol::endpoint Endpoint(m_Settings.UnixSocketPath); + asio::local::stream_protocol::endpoint Endpoint(PathToUtf8(m_Settings.UnixSocketPath)); m_UnixSocket->async_connect(Endpoint, [this](const asio::error_code& Ec) { if (Ec) { diff --git a/src/zenhttp/httpserver.cpp b/src/zenhttp/httpserver.cpp index 6ba0ca563..672467f56 100644 --- a/src/zenhttp/httpserver.cpp +++ b/src/zenhttp/httpserver.cpp @@ -2,6 +2,8 @@ #include <zenhttp/httpserver.h> +#include <zencore/filesystem.h> + #include "servers/httpasio.h" #include "servers/httpmulti.h" #include "servers/httpnull.h" @@ -1157,7 +1159,7 @@ CreateHttpServerClass(const std::string_view ServerClass, const HttpServerConfig ZEN_INFO("using asio HTTP server implementation") return CreateHttpAsioServer(AsioConfig { .ThreadCount = Config.ThreadCount, .ForceLoopback = Config.ForceLoopback, .IsDedicatedServer = Config.IsDedicatedServer, - .NoNetwork = Config.NoNetwork, .UnixSocketPath = Config.UnixSocketPath, + .NoNetwork = Config.NoNetwork, .UnixSocketPath = PathToUtf8(Config.UnixSocketPath), #if ZEN_USE_OPENSSL .HttpsPort = Config.HttpsPort, .CertFile = Config.CertFile, .KeyFile = Config.KeyFile, #endif diff --git a/src/zenhttp/include/zenhttp/httpclient.h b/src/zenhttp/include/zenhttp/httpclient.h index 03c98af7e..e95d78772 100644 --- a/src/zenhttp/include/zenhttp/httpclient.h +++ b/src/zenhttp/include/zenhttp/httpclient.h @@ -10,6 +10,7 @@ #include <zencore/uid.h> #include <zenhttp/httpcommon.h> +#include <filesystem> #include <functional> #include <optional> #include <unordered_map> @@ -91,7 +92,7 @@ struct HttpClientSettings /// Unix domain socket path. When non-empty, the client connects via this /// socket instead of TCP. BaseUri is still used for the Host header and URL. - std::string UnixSocketPath; + std::filesystem::path UnixSocketPath; /// Disable HTTP keep-alive by closing the connection after each request. /// Useful for testing per-connection overhead. @@ -174,11 +175,14 @@ class HttpClientBase; class HttpClient { public: - HttpClient(std::string_view BaseUri, - const HttpClientSettings& Connectionsettings = {}, - std::function<bool()>&& CheckIfAbortFunction = {}); + explicit HttpClient(std::string_view BaseUri, + const HttpClientSettings& Connectionsettings = {}, + std::function<bool()>&& CheckIfAbortFunction = {}); ~HttpClient(); + HttpClient(const HttpClient&) = delete; + HttpClient& operator=(const HttpClient&) = delete; + struct ErrorContext { HttpClientErrorCode ErrorCode; diff --git a/src/zenhttp/include/zenhttp/httpserver.h b/src/zenhttp/include/zenhttp/httpserver.h index 627e7921f..77feb6568 100644 --- a/src/zenhttp/include/zenhttp/httpserver.h +++ b/src/zenhttp/include/zenhttp/httpserver.h @@ -15,6 +15,7 @@ #include <zentelemetry/stats.h> +#include <filesystem> #include <functional> #include <gsl/gsl-lite.hpp> #include <list> @@ -329,7 +330,7 @@ struct HttpServerConfig std::vector<HttpServerPluginConfig> PluginConfigs; bool ForceLoopback = false; unsigned int ThreadCount = 0; - std::string UnixSocketPath; // Unix domain socket path (empty = disabled, non-Windows only) + std::filesystem::path UnixSocketPath; // Unix domain socket path (empty = disabled) bool NoNetwork = false; // Disable TCP/HTTPS listeners; only accept connections via UnixSocketPath int HttpsPort = 0; // HTTPS listen port (0 = disabled, ASIO backend) std::string CertFile; // PEM certificate chain file path diff --git a/src/zenhttp/include/zenhttp/httpwsclient.h b/src/zenhttp/include/zenhttp/httpwsclient.h index 34d338b1d..2ca9b7ab1 100644 --- a/src/zenhttp/include/zenhttp/httpwsclient.h +++ b/src/zenhttp/include/zenhttp/httpwsclient.h @@ -46,7 +46,7 @@ struct HttpWsClientSettings /// Unix domain socket path. When non-empty, connects via this socket /// instead of TCP. The URL host is still used for the Host header. - std::string UnixSocketPath; + std::filesystem::path UnixSocketPath; }; /** diff --git a/src/zenserver/config/config.cpp b/src/zenserver/config/config.cpp index 8118cb424..60ae93853 100644 --- a/src/zenserver/config/config.cpp +++ b/src/zenserver/config/config.cpp @@ -201,6 +201,7 @@ struct ZenServerCmdLineOptions std::string DataDir; std::string BaseSnapshotDir; std::string SecurityConfigPath; + std::string UnixSocketPath; std::string PortStr; ZenLoggingCmdLineOptions LoggingOptions; @@ -320,7 +321,7 @@ ZenServerCmdLineOptions::AddCliOptions(cxxopts::Options& options, ZenServerConfi "", "unix-socket", "Unix domain socket path to listen on (in addition to TCP)", - cxxopts::value<std::string>(ServerOptions.HttpConfig.UnixSocketPath), + cxxopts::value<std::string>(UnixSocketPath), "<path>"); options.add_option("network", @@ -480,6 +481,11 @@ ZenServerCmdLineOptions::ApplyOptions(cxxopts::Options& options, ZenServerConfig ServerOptions.BaseSnapshotDir = MakeSafeAbsolutePath(BaseSnapshotDir); ServerOptions.SecurityConfigPath = MakeSafeAbsolutePath(SecurityConfigPath); + if (!UnixSocketPath.empty()) + { + ServerOptions.HttpConfig.UnixSocketPath = MakeSafeAbsolutePath(UnixSocketPath); + } + if (PortStr != "auto") { int Port = 0; diff --git a/src/zenserver/zenserver.cpp b/src/zenserver/zenserver.cpp index 8283f0cbe..519176ffe 100644 --- a/src/zenserver/zenserver.cpp +++ b/src/zenserver/zenserver.cpp @@ -726,6 +726,20 @@ ZenServerMain::Run() Entry = ServerState.Register(m_ServerOptions.BasePort); + // Publish per-instance extended info (e.g. UDS path) via a small shared memory + // section keyed by SessionId so clients can discover it during Snapshot() enumeration. + { + InstanceInfoData InstanceData; + InstanceData.UnixSocketPath = m_ServerOptions.HttpConfig.UnixSocketPath; + m_InstanceInfo.Create(GetSessionId(), InstanceData); + Entry->SignalHasInstanceInfo(); + } + + if (m_ServerOptions.HttpConfig.NoNetwork) + { + Entry->SignalNoNetwork(); + } + if (m_ServerOptions.OwnerPid) { // We are adding a sponsor process to our own entry, can't wait for pick since the code is not run until later @@ -786,7 +800,8 @@ ZenServerMain::MakeLockData(bool IsReady) .EffectiveListenPort = gsl::narrow<uint16_t>(m_ServerOptions.BasePort), .Ready = IsReady, .DataDir = m_ServerOptions.DataDir, - .ExecutablePath = GetRunningExecutablePath()}); + .ExecutablePath = GetRunningExecutablePath(), + .UnixSocketPath = m_ServerOptions.HttpConfig.UnixSocketPath}); }; } // namespace zen diff --git a/src/zenserver/zenserver.h b/src/zenserver/zenserver.h index 374184aa9..830f36e54 100644 --- a/src/zenserver/zenserver.h +++ b/src/zenserver/zenserver.h @@ -148,8 +148,9 @@ public: ZenServerMain& operator=(const ZenServerMain&) = delete; protected: - ZenServerConfig& m_ServerOptions; - LockFile m_LockFile; + ZenServerConfig& m_ServerOptions; + LockFile m_LockFile; + ZenServerInstanceInfo m_InstanceInfo; virtual void InitializeLogging(); virtual void DoRun(ZenServerState::ZenServerEntry* Entry) = 0; diff --git a/src/zenutil/include/zenutil/zenserverprocess.h b/src/zenutil/include/zenutil/zenserverprocess.h index 1b8750628..2f76f0d6c 100644 --- a/src/zenutil/include/zenutil/zenserverprocess.h +++ b/src/zenutil/include/zenutil/zenserverprocess.h @@ -224,8 +224,10 @@ public: enum class FlagsEnum : uint16_t { - kShutdownPlease = 1 << 0, - kIsReady = 1 << 1, + kShutdownPlease = 1 << 0, + kIsReady = 1 << 1, + kHasInstanceInfo = 1 << 2, + kNoNetwork = 1 << 3, }; FRIEND_ENUM_CLASS_FLAGS(FlagsEnum); @@ -236,6 +238,10 @@ public: bool IsShutdownRequested() const; void SignalReady(); bool IsReady() const; + void SignalHasInstanceInfo(); + bool HasInstanceInfo() const; + void SignalNoNetwork(); + bool IsNoNetwork() const; bool AddSponsorProcess(uint32_t Pid, uint64_t Timeout = 0); }; @@ -258,6 +264,51 @@ private: bool m_IsReadOnly = true; }; +/** Per-instance extended data published via a small shared memory section keyed by SessionId. + + Servers create a writable section; clients open it read-only during Snapshot() + enumeration to discover fields that don't fit in the fixed-size ZenServerEntry + (e.g. Unix domain socket path). + + SessionId is preferred over PID for naming because it is unique per server + instance lifetime, avoiding issues with PID reuse on crash/restart. + */ + +struct InstanceInfoData +{ + std::filesystem::path UnixSocketPath; + // Extensible: add more per-instance fields here in the future +}; + +class ZenServerInstanceInfo +{ +public: + ZenServerInstanceInfo(); + ~ZenServerInstanceInfo(); + + ZenServerInstanceInfo(const ZenServerInstanceInfo&) = delete; + ZenServerInstanceInfo& operator=(const ZenServerInstanceInfo&) = delete; + + /// Server-side: create read-write, populate with data + void Create(const Oid& SessionId, const InstanceInfoData& Data); + + /// Client-side: open read-only by SessionId, returns false if not found + [[nodiscard]] bool OpenReadOnly(const Oid& SessionId); + + /// Read the data (valid after Create or successful OpenReadOnly) + [[nodiscard]] InstanceInfoData Read() const; + + bool IsValid() const { return m_Data != nullptr; } + +private: + static std::string MakeName(const Oid& SessionId); + + void* m_hMapFile = nullptr; + uint8_t* m_Data = nullptr; + bool m_IsOwner = false; + Oid m_SessionId; // for POSIX cleanup (shm_unlink) +}; + struct LockFileInfo { int32_t Pid; @@ -266,6 +317,7 @@ struct LockFileInfo bool Ready; std::filesystem::path DataDir; std::filesystem::path ExecutablePath; + std::filesystem::path UnixSocketPath; }; CbObject MakeLockFilePayload(const LockFileInfo& Info); diff --git a/src/zenutil/zenserverprocess.cpp b/src/zenutil/zenserverprocess.cpp index b9c50be4f..ac614f779 100644 --- a/src/zenutil/zenserverprocess.cpp +++ b/src/zenutil/zenserverprocess.cpp @@ -449,6 +449,30 @@ ZenServerState::ZenServerEntry::IsReady() const return (Flags.load() & static_cast<uint16_t>(FlagsEnum::kIsReady)) != 0; } +void +ZenServerState::ZenServerEntry::SignalHasInstanceInfo() +{ + Flags |= uint16_t(FlagsEnum::kHasInstanceInfo); +} + +bool +ZenServerState::ZenServerEntry::HasInstanceInfo() const +{ + return (Flags.load() & static_cast<uint16_t>(FlagsEnum::kHasInstanceInfo)) != 0; +} + +void +ZenServerState::ZenServerEntry::SignalNoNetwork() +{ + Flags |= uint16_t(FlagsEnum::kNoNetwork); +} + +bool +ZenServerState::ZenServerEntry::IsNoNetwork() const +{ + return (Flags.load() & static_cast<uint16_t>(FlagsEnum::kNoNetwork)) != 0; +} + bool ZenServerState::ZenServerEntry::AddSponsorProcess(uint32_t PidToAdd, uint64_t Timeout) { @@ -492,6 +516,222 @@ ZenServerState::ZenServerEntry::AddSponsorProcess(uint32_t PidToAdd, uint64_t Ti } ////////////////////////////////////////////////////////////////////////// +// ZenServerInstanceInfo +////////////////////////////////////////////////////////////////////////// + +static constexpr size_t kInstanceInfoSize = 4096; + +ZenServerInstanceInfo::ZenServerInstanceInfo() = default; + +ZenServerInstanceInfo::~ZenServerInstanceInfo() +{ +#if ZEN_PLATFORM_WINDOWS + if (m_Data) + { + UnmapViewOfFile(m_Data); + } + if (m_hMapFile) + { + CloseHandle(m_hMapFile); + } +#else + if (m_Data != nullptr) + { + munmap(m_Data, kInstanceInfoSize); + } + if (m_hMapFile != nullptr) + { + int Fd = int(intptr_t(m_hMapFile)); + close(Fd); + } + if (m_IsOwner) + { + std::string Name = MakeName(m_SessionId); + shm_unlink(Name.c_str()); + } +#endif + m_Data = nullptr; +} + +std::string +ZenServerInstanceInfo::MakeName(const Oid& SessionId) +{ +#if ZEN_PLATFORM_WINDOWS + return fmt::format("Global\\ZenInstance_{}", SessionId); +#else + // macOS limits shm_open names to ~31 chars (PSHMNAMLEN), so keep this short. + // "/ZenI_" (6) + 24 hex = 30 chars, within the limit. + return fmt::format("/ZenI_{}", SessionId); +#endif +} + +void +ZenServerInstanceInfo::Create(const Oid& SessionId, const InstanceInfoData& Data) +{ + m_SessionId = SessionId; + m_IsOwner = true; + + // Serialize the data to compact binary + CbObjectWriter Cbo; + if (!Data.UnixSocketPath.empty()) + { + Cbo << "unix_socket" << PathToUtf8(Data.UnixSocketPath); + } + CbObject Payload = Cbo.Save(); + + MemoryView PayloadView = Payload.GetView(); + uint32_t PayloadSize = gsl::narrow<uint32_t>(PayloadView.GetSize()); + + std::string Name = MakeName(SessionId); + +#if ZEN_PLATFORM_WINDOWS + zenutil::AnyUserSecurityAttributes Attrs; + + std::wstring WideName(Name.begin(), Name.end()); + + HANDLE hMap = + CreateFileMappingW(INVALID_HANDLE_VALUE, Attrs.Attributes(), PAGE_READWRITE, 0, DWORD(kInstanceInfoSize), WideName.c_str()); + + if (hMap == NULL) + { + // Fall back to Local namespace + std::string LocalName = fmt::format("Local\\ZenInstance_{}", SessionId); + std::wstring WideLocalName(LocalName.begin(), LocalName.end()); + hMap = CreateFileMappingW(INVALID_HANDLE_VALUE, + Attrs.Attributes(), + PAGE_READWRITE, + 0, + DWORD(kInstanceInfoSize), + WideLocalName.c_str()); + } + + if (hMap == NULL) + { + ThrowLastError("Could not create instance info shared memory"); + } + + void* pBuf = MapViewOfFile(hMap, FILE_MAP_ALL_ACCESS, 0, 0, DWORD(kInstanceInfoSize)); + if (pBuf == NULL) + { + CloseHandle(hMap); + ThrowLastError("Could not map instance info shared memory"); + } +#else + int Fd = shm_open(Name.c_str(), O_RDWR | O_CREAT | O_TRUNC | O_CLOEXEC, 0666); + if (Fd < 0) + { + ThrowLastError("Could not create instance info shared memory"); + } + fchmod(Fd, 0666); + + if (ftruncate(Fd, kInstanceInfoSize) < 0) + { + close(Fd); + shm_unlink(Name.c_str()); + ThrowLastError("Could not resize instance info shared memory"); + } + + void* pBuf = mmap(nullptr, kInstanceInfoSize, PROT_READ | PROT_WRITE, MAP_SHARED, Fd, 0); + if (pBuf == MAP_FAILED) + { + close(Fd); + shm_unlink(Name.c_str()); + ThrowLastError("Could not map instance info shared memory"); + } + + void* hMap = reinterpret_cast<void*>(intptr_t(Fd)); +#endif + + m_hMapFile = hMap; + m_Data = reinterpret_cast<uint8_t*>(pBuf); + + // Write payload: [uint32_t size][compact binary bytes] + memcpy(m_Data, &PayloadSize, sizeof PayloadSize); + if (PayloadSize > 0) + { + memcpy(m_Data + sizeof(uint32_t), PayloadView.GetData(), PayloadSize); + } +} + +bool +ZenServerInstanceInfo::OpenReadOnly(const Oid& SessionId) +{ + m_SessionId = SessionId; + + std::string Name = MakeName(SessionId); + +#if ZEN_PLATFORM_WINDOWS + std::wstring WideName(Name.begin(), Name.end()); + + HANDLE hMap = OpenFileMappingW(FILE_MAP_READ, FALSE, WideName.c_str()); + if (hMap == NULL) + { + // Fall back to Local namespace + std::string LocalName = fmt::format("Local\\ZenInstance_{}", SessionId); + std::wstring WideLocalName(LocalName.begin(), LocalName.end()); + hMap = OpenFileMappingW(FILE_MAP_READ, FALSE, WideLocalName.c_str()); + } + + if (hMap == NULL) + { + return false; + } + + void* pBuf = MapViewOfFile(hMap, FILE_MAP_READ, 0, 0, DWORD(kInstanceInfoSize)); + if (pBuf == NULL) + { + CloseHandle(hMap); + return false; + } +#else + int Fd = shm_open(Name.c_str(), O_RDONLY | O_CLOEXEC, 0666); + if (Fd < 0) + { + return false; + } + + void* pBuf = mmap(nullptr, kInstanceInfoSize, PROT_READ, MAP_SHARED, Fd, 0); + if (pBuf == MAP_FAILED) + { + close(Fd); + return false; + } + + void* hMap = reinterpret_cast<void*>(intptr_t(Fd)); +#endif + + m_hMapFile = hMap; + m_Data = reinterpret_cast<uint8_t*>(pBuf); + m_IsOwner = false; + + return true; +} + +InstanceInfoData +ZenServerInstanceInfo::Read() const +{ + InstanceInfoData Result; + + if (m_Data == nullptr) + { + return Result; + } + + uint32_t PayloadSize = 0; + memcpy(&PayloadSize, m_Data, sizeof PayloadSize); + + if (PayloadSize == 0 || PayloadSize > kInstanceInfoSize - sizeof(uint32_t)) + { + return Result; + } + + CbObject Payload = CbObject::Clone(m_Data + sizeof(uint32_t)); + Result.UnixSocketPath = Payload["unix_socket"].AsU8String(); + + return Result; +} + +////////////////////////////////////////////////////////////////////////// std::atomic<int> ZenServerTestCounter{0}; @@ -1234,6 +1474,10 @@ MakeLockFilePayload(const LockFileInfo& Info) CbObjectWriter Cbo; Cbo << "pid" << Info.Pid << "data" << PathToUtf8(Info.DataDir) << "port" << Info.EffectiveListenPort << "session_id" << Info.SessionId << "ready" << Info.Ready << "executable" << PathToUtf8(Info.ExecutablePath); + if (!Info.UnixSocketPath.empty()) + { + Cbo << "unix_socket" << PathToUtf8(Info.UnixSocketPath); + } return Cbo.Save(); } LockFileInfo @@ -1246,6 +1490,7 @@ ReadLockFilePayload(const CbObject& Payload) Info.Ready = Payload["ready"].AsBool(); Info.DataDir = Payload["data"].AsU8String(); Info.ExecutablePath = Payload["executable"].AsU8String(); + Info.UnixSocketPath = Payload["unix_socket"].AsU8String(); return Info; } @@ -1275,7 +1520,7 @@ ValidateLockFileInfo(const LockFileInfo& Info, std::string& OutReason) OutReason = fmt::format("session id ({}) is not valid", Info.SessionId); return false; } - if (Info.EffectiveListenPort == 0) + if (Info.EffectiveListenPort == 0 && Info.UnixSocketPath.empty()) { OutReason = fmt::format("listen port ({}) is not valid", Info.EffectiveListenPort); return false; |