master
cpp 430 lines 14.3 KB
Raw
1 /*++
2
3 Copyright (c) Microsoft. All rights reserved.
4
5 Module Name:
6
7 ArgumentValidation.cpp
8
9 Abstract:
10
11 Implementation of the Argument Validation.
12
13 --*/
14
15 #include "precomp.h"
16 #include "Argument.h"
17 #include "ArgMap.h"
18 #include "ArgumentValidation.h"
19 #include "ContainerModel.h"
20 #include "Exceptions.h"
21 #include "ImageService.h"
22 #include "Localization.h"
23 #include "MountSpecParsing.h"
24 #include <algorithm>
25 #include <type_traits>
26 #include <utility>
27 #include <wslc.h>
28
29 using namespace wsl::windows::common;
30 using namespace wsl::shared;
31 using namespace wsl::shared::string;
32
33 namespace wsl::windows::wslc {
34
35 namespace mount = wsl::windows::common::mount;
36
37 namespace argument::details {
38 struct RawArgMapAccess
39 {
40 template <ArgType E>
41 static auto GetAll(const ArgMap& map)
42 {
43 return map.GetAll<E>();
44 }
45 };
46 } // namespace argument::details
47
48 namespace {
49 using argument::details::RawArgMapAccess;
50
51 // Converts each raw value for argument A using the provided converter and caches the result on
52 // the ArgMap. This is the single point where an argument's string input is converted; execution
53 // later reads the cached value via GetValue/GetAllValues.
54 template <ArgType A, typename Converter>
55 void CacheConverted(ArgMap& execArgs, const std::wstring& argName, Converter&& convert)
56 {
57 using value_t = typename wsl::windows::wslc::argument::details::ArgConvertedTypeMapping<A>::value_t;
58 using converted_t = decltype(convert(std::declval<const std::wstring&>(), std::declval<const std::wstring&>()));
59 static_assert(
60 std::is_same_v<converted_t, value_t>,
61 "converter return type must exactly match the argument's declared ConvertedType in ArgumentDefinitions.h");
62
63 for (const auto& value : RawArgMapAccess::GetAll<A>(execArgs))
64 {
65 execArgs.AddValidated<A>(convert(value, argName));
66 }
67
68 // Sanity check: each raw value for this argument must produce exactly one cached value.
69 WI_ASSERT(execArgs.CountValidated(A) == execArgs.Count(A));
70 }
71 } // namespace
72
73 // Common per-argument validation, run both by the up-front pass and on demand from ArgMap's read
74 // path. Arguments with a converted type are converted and cached here; the type is recorded as
75 // validated on success.
76 void Argument::Validate(ArgMap& execArgs) const
77 {
78 if (execArgs.IsValidated(m_argType))
79 {
80 return;
81 }
82
83 switch (m_argType)
84 {
85 case ArgType::BuildLabel:
86 for (const auto& value : RawArgMapAccess::GetAll<ArgType::BuildLabel>(execArgs))
87 {
88 validation::ParseLabel(value);
89 }
90 break;
91
92 case ArgType::BuildOutput:
93 CacheConverted<ArgType::BuildOutput>(
94 execArgs, m_name, [](const std::wstring& value, const std::wstring&) { return validation::ParseOutputSpec(value); });
95 break;
96
97 case ArgType::Format:
98 CacheConverted<ArgType::Format>(execArgs, m_name, validation::GetFormatTypeFromString);
99 break;
100
101 case ArgType::InspectFormat:
102 CacheConverted<ArgType::InspectFormat>(execArgs, m_name, validation::GetInspectJsonIndentFromString);
103 break;
104
105 case ArgType::Pull:
106 CacheConverted<ArgType::Pull>(execArgs, m_name, validation::GetPullPolicyFromString);
107 break;
108
109 case ArgType::Progress:
110 CacheConverted<ArgType::Progress>(execArgs, m_name, validation::GetProgressModeFromString);
111 break;
112
113 case ArgType::Signal:
114 CacheConverted<ArgType::Signal>(execArgs, m_name, validation::GetWSLCSignalFromString);
115 break;
116
117 case ArgType::StopSignal:
118 CacheConverted<ArgType::StopSignal>(execArgs, m_name, validation::GetWSLCSignalFromString);
119 break;
120
121 case ArgType::StopTimeout:
122 CacheConverted<ArgType::StopTimeout>(execArgs, m_name, [](const std::wstring& value, const std::wstring& name) {
123 return validation::GetIntegerFromString<int>(value, name);
124 });
125 break;
126
127 case ArgType::ShmSize:
128 CacheConverted<ArgType::ShmSize>(execArgs, m_name, validation::GetMemorySizeFromString);
129 break;
130
131 case ArgType::HealthInterval:
132 CacheConverted<ArgType::HealthInterval>(execArgs, m_name, validation::GetDurationNanosFromString);
133 break;
134
135 case ArgType::HealthTimeout:
136 CacheConverted<ArgType::HealthTimeout>(execArgs, m_name, validation::GetDurationNanosFromString);
137 break;
138
139 case ArgType::HealthStartPeriod:
140 CacheConverted<ArgType::HealthStartPeriod>(execArgs, m_name, validation::GetDurationNanosFromString);
141 break;
142
143 case ArgType::HealthRetries:
144 CacheConverted<ArgType::HealthRetries>(execArgs, m_name, [](const std::wstring& value, const std::wstring& name) {
145 return validation::GetIntegerFromString<int>(value, name, [](int v) { return v >= 0; });
146 });
147 break;
148
149 case ArgType::NoHealthcheck:
150 if (execArgs.Contains(ArgType::HealthCmd) || execArgs.Contains(ArgType::HealthInterval) || execArgs.Contains(ArgType::HealthTimeout) ||
151 execArgs.Contains(ArgType::HealthStartPeriod) || execArgs.Contains(ArgType::HealthRetries))
152 {
153 std::vector<Argument> conflictingArguments{*this};
154 for (const auto type :
155 {ArgType::HealthCmd, ArgType::HealthInterval, ArgType::HealthTimeout, ArgType::HealthStartPeriod, ArgType::HealthRetries})
156 {
157 if (execArgs.Contains(type))
158 {
159 conflictingArguments.emplace_back(Argument::Create(type));
160 }
161 }
162
163 throw ArgumentException(Localization::WSLCCLI_NoHealthcheckConflictError(), std::move(conflictingArguments));
164 }
165 break;
166
167 case ArgType::Memory:
168 CacheConverted<ArgType::Memory>(execArgs, m_name, validation::GetMemorySizeFromString);
169 break;
170
171 case ArgType::Cpus:
172 CacheConverted<ArgType::Cpus>(execArgs, m_name, validation::GetNanoCpusFromString);
173 break;
174
175 case ArgType::Ulimit:
176 CacheConverted<ArgType::Ulimit>(execArgs, m_name, validation::ParseUlimit);
177 break;
178
179 case ArgType::Tail:
180 CacheConverted<ArgType::Tail>(execArgs, m_name, [](const std::wstring& value, const std::wstring& name) {
181 return validation::GetIntegerFromString<ULONGLONG>(value, name, [](ULONGLONG v) { return v != 0; });
182 });
183 break;
184
185 case ArgType::Time:
186 CacheConverted<ArgType::Time>(execArgs, m_name, [](const std::wstring& value, const std::wstring& name) {
187 return validation::GetIntegerFromString<LONG>(value, name);
188 });
189 break;
190
191 case ArgType::Timeout:
192 CacheConverted<ArgType::Timeout>(execArgs, m_name, [](const std::wstring& value, const std::wstring& name) {
193 return validation::GetIntegerFromString<LONG>(value, name);
194 });
195 break;
196
197 case ArgType::Secret:
198 CacheConverted<ArgType::Secret>(
199 execArgs, m_name, [](const std::wstring& value, const std::wstring&) { return validation::ParseSecretSpec(value); });
200 break;
201
202 case ArgType::Since:
203 CacheConverted<ArgType::Since>(execArgs, m_name, validation::GetTimestampFromString);
204 break;
205
206 case ArgType::Until:
207 CacheConverted<ArgType::Until>(execArgs, m_name, validation::GetTimestampFromString);
208 break;
209
210 case ArgType::Last:
211 CacheConverted<ArgType::Last>(execArgs, m_name, [](const std::wstring& value, const std::wstring& name) {
212 return validation::GetIntegerFromString<int>(value, name);
213 });
214 break;
215
216 case ArgType::Filter:
217 CacheConverted<ArgType::Filter>(
218 execArgs, m_name, [](const std::wstring& value, const std::wstring&) { return validation::ParseFilter(value); });
219 break;
220
221 case ArgType::Label:
222 CacheConverted<ArgType::Label>(
223 execArgs, m_name, [](const std::wstring& value, const std::wstring&) { return validation::ParseLabel(value); });
224 break;
225
226 case ArgType::Options:
227 CacheConverted<ArgType::Options>(
228 execArgs, m_name, [](const std::wstring& value, const std::wstring&) { return validation::ParseDriverOption(value); });
229 break;
230
231 case ArgType::Type:
232 CacheConverted<ArgType::Type>(execArgs, m_name, validation::GetInspectTypeFromString);
233 break;
234
235 case ArgType::Gpus:
236 validation::ValidateGpus(RawArgMapAccess::GetAll<ArgType::Gpus>(execArgs), m_name);
237 break;
238
239 case ArgType::Volume:
240 CacheConverted<ArgType::Volume>(execArgs, m_name, [](const std::wstring& value, const std::wstring&) {
241 try
242 {
243 auto mountSpec = mount::ParseDockerVolumeString(value);
244 mount::ValidateMountSpec(mountSpec);
245 return mountSpec;
246 }
247 catch (const mount::MountException& ex)
248 {
249 throw ArgumentException(ex.Reason());
250 }
251 });
252 break;
253
254 case ArgType::TMPFS:
255 CacheConverted<ArgType::TMPFS>(execArgs, m_name, [](const std::wstring& value, const std::wstring&) {
256 try
257 {
258 auto mountSpec = mount::ParseDockerTmpfsString(value);
259 mount::ValidateMountSpec(mountSpec);
260 return mountSpec;
261 }
262 catch (const mount::MountException& ex)
263 {
264 throw ArgumentException(Localization::WSLCCLI_InvalidTmpfsError(value, ex.Reason()));
265 }
266 });
267 break;
268
269 case ArgType::Mount:
270 CacheConverted<ArgType::Mount>(execArgs, m_name, [](const std::wstring& value, const std::wstring&) {
271 try
272 {
273 auto mountSpec = mount::ParseDockerMountString(value);
274 mount::ValidateMountSpec(mountSpec);
275 return mountSpec;
276 }
277 catch (const mount::MountUnsupportedException& ex)
278 {
279 throw ArgumentException(Localization::WSLCCLI_UnsupportedMountError(value, ex.Reason()));
280 }
281 catch (const mount::MountException& ex)
282 {
283 throw ArgumentException(Localization::WSLCCLI_InvalidMountError(value, ex.Reason()));
284 }
285 });
286 break;
287
288 case ArgType::WorkDir:
289 {
290 for (const auto& value : RawArgMapAccess::GetAll<ArgType::WorkDir>(execArgs))
291 {
292 if (value.empty() ||
293 std::all_of(value.begin(), value.end(), [](wchar_t c) { return std::iswspace(static_cast<wint_t>(c)); }))
294 {
295 throw ArgumentException(Localization::WSLCCLI_WorkingDirEmptyError(m_name));
296 }
297 }
298 break;
299 }
300
301 case ArgType::Network:
302 {
303 CacheConverted<ArgType::Network>(execArgs, m_name, [](const std::wstring& value, const std::wstring& name) {
304 auto parsed = validation::ParseNetworkArgument(value, name);
305 if (IsEqual(parsed.Name, "host", true))
306 {
307 throw ExecutionException(Localization::WSLCCLI_NetworkHostModeNotSupportedError());
308 }
309
310 return parsed;
311 });
312 break;
313 }
314
315 case ArgType::NetworkAlias:
316 {
317 for (const auto& value : RawArgMapAccess::GetAll<ArgType::NetworkAlias>(execArgs))
318 {
319 if (value.empty() ||
320 std::all_of(value.begin(), value.end(), [](wchar_t c) { return std::iswspace(static_cast<wint_t>(c)); }))
321 {
322 throw ArgumentException(Localization::WSLCCLI_NetworkAliasEmptyError(m_name));
323 }
324 }
325 break;
326 }
327
328 default:
329 break;
330 }
331
332 // Mark validated only on success: a throw above (invalid value) skips this, so the next read
333 // re-validates and reports the same error again.
334 execArgs.MarkValidated(m_argType);
335 }
336 } // namespace wsl::windows::wslc
337
338 namespace wsl::windows::wslc::argument {
339
340 // On-demand validation for ArgMap's read path. Clears any stale converted cache first (idempotent),
341 // then Argument::Validate re-checks the raw values, throwing for an invalid one and recording the
342 // type as validated on success.
343 void EnsureArgumentValidated(ArgMap& map, ArgType type)
344 {
345 map.InvalidateValidated(type);
346 Argument::Create(type).Validate(map);
347 }
348
349 } // namespace wsl::windows::wslc::argument
350
351 namespace wsl::windows::wslc::validation {
352
353 void ValidateWSLCSignalFromString(const std::vector<std::wstring>& values, const std::wstring& argName)
354 {
355 for (const auto& value : values)
356 {
357 std::ignore = GetWSLCSignalFromString(value, argName);
358 }
359 }
360
361 // Validates that each --filter argument is in the form "key=value". Rejects entries without an '=';
362 // the runtime validates the key and value for specific objects.
363 void ValidateFilter(const std::vector<std::wstring>& values)
364 {
365 for (const auto& value : values)
366 {
367 std::ignore = ParseFilter(value);
368 }
369 }
370
371 void ValidateTimestamp(const std::vector<std::wstring>& values, const std::wstring& argName)
372 {
373 for (const auto& value : values)
374 {
375 std::ignore = GetTimestampFromString(value, argName);
376 }
377 }
378
379 void ValidateFormatTypeFromString(const std::vector<std::wstring>& values, const std::wstring& argName)
380 {
381 for (const auto& value : values)
382 {
383 std::ignore = GetFormatTypeFromString(value, argName);
384 }
385 }
386
387 void ValidateGpus(const std::vector<std::wstring>& values, const std::wstring& argName)
388 {
389 for (const auto& value : values)
390 {
391 if (!IsEqual(value, L"all"))
392 {
393 throw ArgumentException(Localization::WSLCCLI_GpusInvalidValue(argName, value));
394 }
395 }
396 }
397
398 void ValidateMemorySize(const std::vector<std::wstring>& values, const std::wstring& argName)
399 {
400 for (const auto& value : values)
401 {
402 std::ignore = GetMemorySizeFromString(value, argName);
403 }
404 }
405
406 void ValidateDuration(const std::vector<std::wstring>& values, const std::wstring& argName)
407 {
408 for (const auto& value : values)
409 {
410 std::ignore = GetDurationNanosFromString(value, argName);
411 }
412 }
413
414 void ValidateNanoCpus(const std::vector<std::wstring>& values, const std::wstring& argName)
415 {
416 for (const auto& value : values)
417 {
418 std::ignore = GetNanoCpusFromString(value, argName);
419 }
420 }
421
422 void ValidateUlimit(const std::vector<std::wstring>& values, const std::wstring& argName)
423 {
424 for (const auto& value : values)
425 {
426 std::ignore = ParseUlimit(value, argName);
427 }
428 }
429
430 } // namespace wsl::windows::wslc::validation