@samitouri / QOSAMI-WSL / commits / f9c5ebb6

wslc: align docker_schema with bundled dockerd v25.0.3 (API v1.44) (#40552)

* Align docker_schema with bundled dockerd v25.0.3 (API v1.44) WSLC talks to a bundled dockerd v25.0.3 (Docker API v1.44). Several entries in docker_schema.h drifted toward documented-but-not-shipped types or were defensive in the wrong way. None of these are observable user-facing bugs on a stock daemon today, but each is a latent crash or misbehavior waiting on the right wire payload from a third-party driver, future daemon, or TTY exec. Authoritative source: https://github.com/moby/moby/tree/v25.0.3/api Schema fixes: * ContainerState enum: prepend {Unknown, nullptr} so an unrecognized state string from the daemon falls back to Unknown rather than silently decoding as the first map entry (Created). * HostConfig.ShmSize: change std::optional<ULONGLONG> -> std::int64_t. Docker's wire type is signed int64 and 0 already means "use daemon default", so the optional indirection added nothing. * Volume.Status: change optional<map<string,string>> -> optional<map<string, nlohmann::json>>. Docker's wire schema is map[string]any; third-party volume drivers may publish numbers, bools, or nested objects which would currently throw type_error during deserialize. * Image.Size, InspectImage.Size: change uint64_t -> int64_t to match the daemon's int64 wire type. Cast at consumer sites that feed ULONGLONG ABI fields. * CreateExec / StartExec ConsoleSize: replace NLOHMANN_DEFINE_TYPE_INTRUSIVE_* with explicit to_json that omits the field when empty. Docker treats an empty array as an explicit 0x0 console for TTY execs; we were unconditionally serializing []. * CreatedContainer.Name: removed (Docker's POST /containers/create response only contains Id and Warnings; Name is supplied as a query parameter, not echoed back). Switched to WITH_DEFAULT for parity with surrounding types. Doc: * Updated two API doc URL references from v1.52 to v1.44 with a note about the bundled dockerd version. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * wslc: align Size/ShmSize types with docker int64 end-to-end Per PR review: replace boundary static_casts with consistent signed 64-bit types across IDL, SDK, service models, and CLI schema. Mirrors the existing MemoryBytes/NanoCpus precedent. - IDL: WSLCImageInformation.Size, WSLCContainerOptions.ShmSize -> LONGLONG - SDK: WslcImageInfo.sizeBytes -> int64_t - Models: ImageInformation::Size, ContainerOptions::ShmSize -> int64_t - Schema: InspectImage::Size -> int64_t - Launcher: m_shmSize/SetShmSize -> int64_t - Validation: GetMemorySizeFromString returns int64_t Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Ben Hillis <benhill@ntdev.microsoft.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Ben Hillis committed May 18, 2026 at 12:43 UTC f9c5ebb630b22794a921501242c496eff574f6c0
11 files changed +64 -30
src/windows/WslcSDK/wslcsdk.h
+1 -1
@@ -477,7 +477,7 @@ typedef struct WslcImageInfo
477 // we should expose this
478 CHAR name[WSLC_IMAGE_NAME_LENGTH];
479 uint8_t sha256[32];
480 - uint64_t sizeBytes;
480 + int64_t sizeBytes;
481 uint64_t createdUnixTime;
482 } WslcImageInfo;
483
src/windows/common/WSLCContainerLauncher.cpp
+1 -1
@@ -129,7 +129,7 @@ void WSLCContainerLauncher::SetDefaultStopSignal(WSLCSignal Signal)
129 m_stopSignal = Signal;
130 }
131
132 -void WSLCContainerLauncher::SetShmSize(ULONGLONG ShmSize)
132 +void WSLCContainerLauncher::SetShmSize(int64_t ShmSize)
133 {
134 m_shmSize = ShmSize;
135 }
src/windows/common/WSLCContainerLauncher.h
+2 -2
@@ -73,7 +73,7 @@ public:
73 void SetName(std::string&& Name);
74 void SetEntrypoint(std::vector<std::string>&& entrypoint);
75 void SetDefaultStopSignal(WSLCSignal Signal);
76 - void SetShmSize(ULONGLONG ShmSize);
76 + void SetShmSize(int64_t ShmSize);
77 void SetContainerFlags(WSLCContainerFlags Flags);
78 void SetContainerNetworkName(std::string&& Name);
79 void SetHostname(std::string&& Hostname);
@@ -102,7 +102,7 @@ private:
102 std::string m_containerNetworkName;
103 std::vector<std::string> m_entrypoint;
104 WSLCSignal m_stopSignal = WSLCSignalNone;
105 - ULONGLONG m_shmSize = 0;
105 + int64_t m_shmSize = 0;
106 WSLCContainerFlags m_containerFlags = WSLCContainerFlagsNone;
107 std::string m_hostname;
108 std::string m_domainname;
src/windows/inc/docker_schema.h
+51 -14
@@ -9,7 +9,8 @@ Module Name:
9 Abstract:
10
11 JSON schema for the docker API.
12 - The documentation for the API can be found at: https://docs.docker.com/reference/api/engine/version/v1.52/#tag/Container
12 + Targets the daemon API version bundled with WSLC's dockerd (currently v25.0.3, API v1.44).
13 + The documentation for the API can be found at: https://docs.docker.com/reference/api/engine/version/v1.44/#tag/Container
14
15 --*/
16
@@ -22,10 +23,9 @@ namespace wsl::windows::common::docker_schema {
23 struct CreatedContainer
24 {
25 std::string Id;
25 - std::string Name;
26 std::vector<std::string> Warnings;
27
28 - NLOHMANN_DEFINE_TYPE_INTRUSIVE(CreatedContainer, Id, Warnings);
28 + NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(CreatedContainer, Id, Warnings);
29 };
30
31 struct ErrorResponse
@@ -83,7 +83,10 @@ struct Volume
83 std::string CreatedAt;
84 std::optional<std::map<std::string, std::string>> Options;
85 std::optional<std::map<std::string, std::string>> Labels;
86 - std::optional<std::map<std::string, std::string>> Status;
86 + // Docker's wire schema for Status is map[string]any: third-party volume
87 + // drivers may set arbitrary JSON values (numbers, bools, objects), not
88 + // just strings. Use nlohmann::json so deserialization never throws.
89 + std::optional<std::map<std::string, nlohmann::json>> Status;
90 std::optional<VolumeUsageData> UsageData;
91
92 NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(Volume, Name, Driver, Mountpoint, CreatedAt, Options, Labels, Status, UsageData);
@@ -223,7 +226,9 @@ struct HostConfig
226 std::optional<std::vector<std::string>> DnsOptions;
227 std::optional<std::vector<std::string>> Binds;
228 std::map<std::string, std::string> Tmpfs;
226 - std::optional<ULONGLONG> ShmSize;
229 + // Docker wire type is int64. 0 means "use daemon default" — same as omitting
230 + // the field — so we don't bother with std::optional here.
231 + std::int64_t ShmSize{};
232 std::optional<std::vector<DeviceMapping>> Devices;
233
234 // Per-container resource limits. 0 means "no limit" (Docker default).
@@ -358,7 +363,7 @@ struct Image
363 std::string Id;
364 std::vector<std::string> RepoTags;
365 std::vector<std::string> RepoDigests;
361 - uint64_t Size{};
366 + int64_t Size{};
367 int64_t Created{};
368 std::string ParentId;
369
@@ -434,7 +439,7 @@ struct InspectImage
439 std::string Variant;
440 std::string Os;
441 std::string OsVersion;
437 - uint64_t Size{};
442 + int64_t Size{};
443 std::optional<GraphDriverData> GraphDriver;
444 std::optional<RootFS> RootFS;
445 std::optional<std::map<std::string, std::string>> Metadata;
@@ -457,41 +462,73 @@ struct CreateExec
462 bool AttachStdout{};
463 bool AttachStderr{};
464 bool Tty{};
465 + // Docker wire type is *[2]uint64. Sending an empty array on a TTY exec yields
466 + // a 0x0 console; the field must be omitted entirely when the caller didn't set it.
467 std::vector<ULONG> ConsoleSize;
468 std::vector<std::string> Cmd;
469 std::vector<std::string> Env;
470 std::optional<std::string> User;
471 std::string WorkingDir;
472 std::optional<std::string> DetachKeys;
466 -
467 - NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(CreateExec, AttachStdin, AttachStdout, AttachStderr, Tty, ConsoleSize, Cmd, Env, WorkingDir, User, DetachKeys);
473 };
474
475 +inline void to_json(nlohmann::json& j, const CreateExec& v)
476 +{
477 + j = nlohmann::json{
478 + {"AttachStdin", v.AttachStdin},
479 + {"AttachStdout", v.AttachStdout},
480 + {"AttachStderr", v.AttachStderr},
481 + {"Tty", v.Tty},
482 + {"Cmd", v.Cmd},
483 + {"Env", v.Env},
484 + {"WorkingDir", v.WorkingDir},
485 + {"User", v.User},
486 + {"DetachKeys", v.DetachKeys},
487 + };
488 +
489 + if (!v.ConsoleSize.empty())
490 + {
491 + j["ConsoleSize"] = v.ConsoleSize;
492 + }
493 +}
494 +
495 struct StartExec
496 {
497 using TResponse = void;
498 bool Tty{};
499 bool Detach{};
500 + // See CreateExec::ConsoleSize.
501 std::vector<ULONG> ConsoleSize;
476 -
477 - NLOHMANN_DEFINE_TYPE_INTRUSIVE_ONLY_SERIALIZE(StartExec, Tty, Detach, ConsoleSize);
502 };
503
504 +inline void to_json(nlohmann::json& j, const StartExec& v)
505 +{
506 + j = nlohmann::json{{"Tty", v.Tty}, {"Detach", v.Detach}};
507 +
508 + if (!v.ConsoleSize.empty())
509 + {
510 + j["ConsoleSize"] = v.ConsoleSize;
511 + }
512 +}
513 +
514 enum class ContainerState
515 {
516 + Unknown,
517 Created,
518 Running,
519 Paused,
520 Restarting,
521 Exited,
522 Removing,
488 - Dead,
489 - Unknown
523 + Dead
524 };
525
526 NLOHMANN_JSON_SERIALIZE_ENUM(
527 ContainerState,
528 {
529 + // Unknown is first so unrecognized strings (or missing field) deserialize to Unknown,
530 + // not to whatever the next entry happens to be.
531 + {ContainerState::Unknown, nullptr},
532 {ContainerState::Created, "created"},
533 {ContainerState::Running, "running"},
534 {ContainerState::Paused, "paused"},
@@ -582,7 +619,7 @@ struct CreateImageProgress
619 };
620
621 // Container stats (GET /containers/{id}/stats?stream=false)
585 -// See: https://docs.docker.com/reference/api/engine/version/v1.52/#tag/Container/operation/ContainerStats
622 +// See: https://docs.docker.com/reference/api/engine/version/v1.44/#tag/Container/operation/ContainerStats
623
624 struct ContainerStatsCpuUsage
625 {
src/windows/inc/wslc_schema.h
+1 -1
@@ -136,7 +136,7 @@ struct InspectImage
136 std::string Author;
137 std::string Architecture;
138 std::string Os;
139 - uint64_t Size{};
139 + int64_t Size{};
140 std::optional<std::map<std::string, std::string>> Metadata;
141 std::optional<ImageConfig> Config;
142
src/windows/service/inc/wslc.idl
+2 -2
@@ -143,7 +143,7 @@ typedef struct _WSLCImageInformation
143 char Image[WSLC_MAX_IMAGE_NAME_LENGTH + 1];
144 char Hash[256];
145 char Digest[256];
146 - ULONGLONG Size;
146 + LONGLONG Size; // Matches Docker's int64 image size
147 LONGLONG Created; // Unix timestamp
148 char ParentId[256];
149 } WSLCImageInformation;
@@ -322,7 +322,7 @@ typedef struct _WSLCContainerOptions
322 WSLCStringArray DnsSearchDomains;
323 WSLCStringArray DnsOptions;
324
325 - ULONGLONG ShmSize;
325 + LONGLONG ShmSize; // Matches Docker's int64 ShmSize; consistent with MemoryBytes/NanoCpus
326 WSLCContainerNetwork ContainerNetwork;
327 [unique, size_is(TmpfsCount)] const WSLCTmpfsMount* Tmpfs;
328 ULONG TmpfsCount;
src/windows/wslc/arguments/ArgumentValidation.cpp
+2 -2
@@ -248,7 +248,7 @@ void ValidateMemorySize(const std::vector<std::wstring>& values, const std::wstr
248 }
249 }
250
251 -ULONGLONG GetMemorySizeFromString(const std::wstring& input, const std::wstring& argName)
251 +int64_t GetMemorySizeFromString(const std::wstring& input, const std::wstring& argName)
252 {
253 auto parsed = wsl::shared::string::ParseMemorySize(input.c_str());
254 if (!parsed.has_value())
@@ -256,7 +256,7 @@ ULONGLONG GetMemorySizeFromString(const std::wstring& input, const std::wstring&
256 throw ArgumentException(Localization::WSLCCLI_InvalidMemorySizeError(argName, input));
257 }
258
259 - return parsed.value();
259 + return static_cast<int64_t>(parsed.value());
260 }
261
262 } // namespace wsl::windows::wslc::validation
src/windows/wslc/arguments/ArgumentValidation.h
+1 -1
@@ -63,7 +63,7 @@ void ValidateWSLCSignalFromString(const std::vector<std::wstring>& values, const
63 WSLCSignal GetWSLCSignalFromString(const std::wstring& input, const std::wstring& argName = {});
64
65 void ValidateMemorySize(const std::vector<std::wstring>& values, const std::wstring& argName);
66 -ULONGLONG GetMemorySizeFromString(const std::wstring& input, const std::wstring& argName = {});
66 +int64_t GetMemorySizeFromString(const std::wstring& input, const std::wstring& argName = {});
67
68 void ValidateFormatTypeFromString(const std::vector<std::wstring>& values, const std::wstring& argName);
69 FormatType GetFormatTypeFromString(const std::wstring& input, const std::wstring& argName = {});
src/windows/wslc/services/ContainerModel.h
+1 -1
@@ -38,7 +38,7 @@ struct ContainerOptions
38 bool TTY = false;
39 bool PublishAll = false;
40 WSLCSignal StopSignal = WSLCSignalNone;
41 - std::optional<ULONGLONG> ShmSize{};
41 + std::optional<int64_t> ShmSize{};
42 bool Gpu = false;
43 std::vector<std::string> Ports;
44 std::vector<std::wstring> Volumes;
src/windows/wslc/services/ImageModel.h
+1 -1
@@ -23,7 +23,7 @@ struct ImageInformation
23 std::optional<std::string> Tag;
24 std::string Id;
25 LONGLONG Created{};
26 - ULONGLONG Size{};
26 + int64_t Size{};
27
28 NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(ImageInformation, Repository, Tag, Id, Created, Size);
29 };
src/windows/wslcsession/WSLCContainer.cpp
+1 -4
@@ -1501,10 +1501,7 @@ std::unique_ptr<WSLCContainerImpl> WSLCContainerImpl::Create(
1501 request.HostConfig.Ulimits = std::move(ulimits);
1502 }
1503
1504 - if (containerOptions.ShmSize > 0)
1505 - {
1506 - request.HostConfig.ShmSize = containerOptions.ShmSize;
1507 - }
1504 + request.HostConfig.ShmSize = containerOptions.ShmSize;
1505
1506 if (containerOptions.VolumesCount > 0)
1507 {