aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorStefan Boberg <[email protected]>2026-03-13 09:24:59 +0100
committerGitHub Enterprise <[email protected]>2026-03-13 09:24:59 +0100
commitef586f5930ac761f8e8e18cde2ebd5248efeaa4a (patch)
treebe6c7d3e11f9261c1d03d646bc579ac0d27452d7 /src
parentSwitch httpclient default back-end over to libcurl (#832) (diff)
downloadzen-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')
-rw-r--r--src/zen/cmds/admin_cmd.cpp28
-rw-r--r--src/zen/cmds/cache_cmd.cpp24
-rw-r--r--src/zen/cmds/exec_cmd.cpp2
-rw-r--r--src/zen/cmds/info_cmd.cpp4
-rw-r--r--src/zen/cmds/projectstore_cmd.cpp50
-rw-r--r--src/zen/cmds/rpcreplay_cmd.cpp14
-rw-r--r--src/zen/cmds/serve_cmd.cpp4
-rw-r--r--src/zen/cmds/status_cmd.cpp20
-rw-r--r--src/zen/cmds/top_cmd.cpp37
-rw-r--r--src/zen/cmds/trace_cmd.cpp4
-rw-r--r--src/zen/cmds/ui_cmd.cpp7
-rw-r--r--src/zen/cmds/version_cmd.cpp4
-rw-r--r--src/zen/cmds/vfs_cmd.cpp4
-rw-r--r--src/zen/cmds/workspaces_cmd.cpp24
-rw-r--r--src/zen/zen.cpp46
-rw-r--r--src/zen/zen.h6
-rw-r--r--src/zenhttp/clients/httpclientcpr.cpp3
-rw-r--r--src/zenhttp/clients/httpclientcurl.cpp4
-rw-r--r--src/zenhttp/clients/httpwsclient.cpp4
-rw-r--r--src/zenhttp/httpserver.cpp4
-rw-r--r--src/zenhttp/include/zenhttp/httpclient.h12
-rw-r--r--src/zenhttp/include/zenhttp/httpserver.h3
-rw-r--r--src/zenhttp/include/zenhttp/httpwsclient.h2
-rw-r--r--src/zenserver/config/config.cpp8
-rw-r--r--src/zenserver/zenserver.cpp17
-rw-r--r--src/zenserver/zenserver.h5
-rw-r--r--src/zenutil/include/zenutil/zenserverprocess.h56
-rw-r--r--src/zenutil/zenserverprocess.cpp247
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;