Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
google
GitHub Repository: google/crosvm
Path: blob/main/crosvm_cli/src/sys/windows/exit.rs
5394 views
1
// Copyright 2022 The ChromiumOS Authors
2
// Use of this source code is governed by a BSD-style license that can be
3
// found in the LICENSE file.
4
5
//! Enum and Anyhow helpers to set the process exit code.
6
7
use std::fmt;
8
use std::fmt::Display;
9
use std::fmt::Formatter;
10
11
use anyhow::Context;
12
use win_util::ProcessType;
13
14
pub type ExitCode = i32;
15
16
#[derive(Debug)]
17
pub struct ExitCodeWrapper(pub ExitCode);
18
19
impl Display for ExitCodeWrapper {
20
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
21
write!(f, "exit code: {} = 0x{:08x}", self.0, self.0)
22
}
23
}
24
25
/// Trait for attaching context with process exit codes to a std::result::Result.
26
pub trait ExitContext<T, E> {
27
fn exit_code<X>(self, exit_code: X) -> anyhow::Result<T>
28
where
29
X: Into<ExitCode>;
30
31
fn exit_context<X, C>(self, exit_code: X, context: C) -> anyhow::Result<T>
32
where
33
X: Into<ExitCode>,
34
C: Display + Send + Sync + 'static;
35
36
fn with_exit_context<X, C, F>(self, exit_code: X, f: F) -> anyhow::Result<T>
37
where
38
X: Into<ExitCode>,
39
C: Display + Send + Sync + 'static,
40
F: FnOnce() -> C;
41
}
42
43
impl<T, E> ExitContext<T, E> for std::result::Result<T, E>
44
where
45
E: std::error::Error + Send + Sync + 'static,
46
{
47
fn exit_code<X>(self, exit_code: X) -> anyhow::Result<T>
48
where
49
X: Into<ExitCode>,
50
{
51
self.context(ExitCodeWrapper(exit_code.into()))
52
}
53
54
fn exit_context<X, C>(self, exit_code: X, context: C) -> anyhow::Result<T>
55
where
56
X: Into<ExitCode>,
57
C: Display + Send + Sync + 'static,
58
{
59
self.context(ExitCodeWrapper(exit_code.into()))
60
.context(context)
61
}
62
63
fn with_exit_context<X, C, F>(self, exit_code: X, f: F) -> anyhow::Result<T>
64
where
65
X: Into<ExitCode>,
66
C: Display + Send + Sync + 'static,
67
F: FnOnce() -> C,
68
{
69
self.context(ExitCodeWrapper(exit_code.into()))
70
.with_context(f)
71
}
72
}
73
74
/// Trait for attaching context with process exit codes to an anyhow::Result.
75
pub trait ExitContextAnyhow<T> {
76
fn exit_code<X>(self, exit_code: X) -> anyhow::Result<T>
77
where
78
X: Into<ExitCode>;
79
80
fn exit_context<X, C>(self, exit_code: X, context: C) -> anyhow::Result<T>
81
where
82
X: Into<ExitCode>,
83
C: Display + Send + Sync + 'static;
84
85
fn with_exit_context<X, C, F>(self, exit_code: X, f: F) -> anyhow::Result<T>
86
where
87
X: Into<ExitCode>,
88
C: Display + Send + Sync + 'static,
89
F: FnOnce() -> C;
90
91
fn to_exit_code(&self) -> Option<ExitCode>;
92
}
93
94
impl<T> ExitContextAnyhow<T> for anyhow::Result<T> {
95
fn exit_code<X>(self, exit_code: X) -> anyhow::Result<T>
96
where
97
X: Into<ExitCode>,
98
{
99
self.context(ExitCodeWrapper(exit_code.into()))
100
}
101
102
fn exit_context<X, C>(self, exit_code: X, context: C) -> anyhow::Result<T>
103
where
104
X: Into<ExitCode>,
105
C: Display + Send + Sync + 'static,
106
{
107
self.context(ExitCodeWrapper(exit_code.into()))
108
.context(context)
109
}
110
111
fn with_exit_context<X, C, F>(self, exit_code: X, f: F) -> anyhow::Result<T>
112
where
113
X: Into<ExitCode>,
114
C: Display + Send + Sync + 'static,
115
F: FnOnce() -> C,
116
{
117
self.context(ExitCodeWrapper(exit_code.into()))
118
.with_context(f)
119
}
120
121
fn to_exit_code(&self) -> Option<ExitCode> {
122
self.as_ref()
123
.err()
124
.and_then(|e| e.downcast_ref::<ExitCodeWrapper>())
125
.map(|w| w.0)
126
}
127
}
128
129
/// Trait for attaching context with process exit codes to an Option.
130
pub trait ExitContextOption<T> {
131
fn exit_code<X>(self, exit_code: X) -> anyhow::Result<T>
132
where
133
X: Into<ExitCode>;
134
135
fn exit_context<X, C>(self, exit_code: X, context: C) -> anyhow::Result<T>
136
where
137
X: Into<ExitCode>,
138
C: Display + Send + Sync + 'static;
139
140
fn with_exit_context<X, C, F>(self, exit_code: X, f: F) -> anyhow::Result<T>
141
where
142
X: Into<ExitCode>,
143
C: Display + Send + Sync + 'static,
144
F: FnOnce() -> C;
145
}
146
147
impl<T> ExitContextOption<T> for std::option::Option<T> {
148
fn exit_code<X>(self, exit_code: X) -> anyhow::Result<T>
149
where
150
X: Into<ExitCode>,
151
{
152
self.context(ExitCodeWrapper(exit_code.into()))
153
}
154
155
fn exit_context<X, C>(self, exit_code: X, context: C) -> anyhow::Result<T>
156
where
157
X: Into<ExitCode>,
158
C: Display + Send + Sync + 'static,
159
{
160
self.context(ExitCodeWrapper(exit_code.into()))
161
.context(context)
162
}
163
164
fn with_exit_context<X, C, F>(self, exit_code: X, f: F) -> anyhow::Result<T>
165
where
166
X: Into<ExitCode>,
167
C: Display + Send + Sync + 'static,
168
F: FnOnce() -> C,
169
{
170
self.context(ExitCodeWrapper(exit_code.into()))
171
.with_context(f)
172
}
173
}
174
175
#[macro_export]
176
macro_rules! bail_exit_code {
177
($exit_code:literal, $msg:literal $(,)?) => {
178
return Err(anyhow!($msg)).exit_code($exit_code)
179
};
180
($exit_code:literal, $err:expr $(,)?) => {
181
return Err(anyhow!($err)).exit_code($exit_code)
182
};
183
($exit_code:literal, $fmt:expr, $($arg:tt)*) => {
184
return Err(anyhow!($fmt, $($arg)*)).exit_code($exit_code)
185
};
186
($exit_code:expr, $msg:literal $(,)?) => {
187
return Err(anyhow!($msg)).exit_code($exit_code)
188
};
189
($exit_code:expr, $err:expr $(,)?) => {
190
return Err(anyhow!($err)).exit_code($exit_code)
191
};
192
($exit_code:expr, $fmt:expr, $($arg:tt)*) => {
193
return Err(anyhow!($fmt, $($arg)*)).exit_code($exit_code)
194
};
195
}
196
197
#[macro_export]
198
macro_rules! ensure_exit_code {
199
($cond:expr, $exit_code:literal $(,)?) => {
200
if !$cond {
201
bail_exit_code!($exit_code, concat!("Condition failed: `", stringify!($cond), "`"));
202
}
203
};
204
($cond:expr, $exit_code:literal, $msg:literal $(,)?) => {
205
if !$cond {
206
bail_exit_code!($exit_code, $msg);
207
}
208
};
209
($cond:expr, $exit_code:literal, $err:expr $(,)?) => {
210
if !$cond {
211
bail_exit_code!($exit_code, $err);
212
}
213
};
214
($cond:expr, $exit_code:literal, $fmt:expr, $($arg:tt)*) => {
215
if !$cond {
216
bail_exit_code!($exit_code, $fmt, $($arg)*);
217
}
218
};
219
($cond:expr, $exit_code:expr $(,)?) => {
220
if !$cond {
221
bail_exit_code!($exit_code, concat!("Condition failed: `", stringify!($cond), "`"));
222
}
223
};
224
($cond:expr, $exit_code:expr, $msg:literal $(,)?) => {
225
if !$cond {
226
bail_exit_code!($exit_code, $msg);
227
}
228
};
229
($cond:expr, $exit_code:expr, $err:expr $(,)?) => {
230
if !$cond {
231
bail_exit_code!($exit_code, $err);
232
}
233
};
234
($cond:expr, $exit_code:expr, $fmt:expr, $($arg:tt)*) => {
235
if !$cond {
236
bail_exit_code!($exit_code, $fmt, $($arg)*);
237
}
238
};
239
}
240
241
#[allow(clippy::enum_clike_unportable_variant)]
242
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
243
pub enum Exit {
244
// Windows process exit codes triggered by the kernel tend to be NTSTATUS, so we treat
245
// our error codes as NTSTATUS to avoid clashing. This means we set the vendor bit. We also
246
// set the severity to error. As these all set in the MSB, we can write this as a prefix of
247
// 0xE0.
248
//
249
// Because of how these error codes are used in CommandType, we can only use the lower two
250
// bytes of the u32 for our error codes; in other words, the legal range is
251
// [0xE0000000, 0xE000FFFF].
252
AddGpuDeviceMemory = 0xE0000001,
253
AddIrqChipVcpu = 0xE0000002,
254
AddPmemDeviceMemory = 0xE0000003,
255
AllocateGpuDeviceAddress = 0xE0000004,
256
AllocatePmemDeviceAddress = 0xE0000005,
257
BlockDeviceNew = 0xE0000006,
258
BuildVm = 0xE0000007,
259
ChownTpmStorage = 0xE0000008,
260
CloneEvent = 0xE000000A,
261
CloneVcpu = 0xE000000B,
262
ConfigureVcpu = 0xE000000C,
263
CreateConsole = 0xE000000E,
264
CreateDisk = 0xE000000F,
265
CreateEvent = 0xE0000010,
266
CreateGralloc = 0xE0000011,
267
CreateGvm = 0xE0000012,
268
CreateSocket = 0xE0000013,
269
CreateTapDevice = 0xE0000014,
270
CreateTimer = 0xE0000015,
271
CreateTpmStorage = 0xE0000016,
272
CreateVcpu = 0xE0000017,
273
CreateWaitContext = 0xE0000018,
274
Disk = 0xE0000019,
275
DiskImageLock = 0xE000001A,
276
DropCapabilities = 0xE000001B,
277
EventDeviceSetup = 0xE000001C,
278
EnableHighResTimer = 0xE000001D,
279
HandleCreateQcowError = 0xE000001E,
280
HandleVmRequestError = 0xE0000020,
281
InitSysLogError = 0xE0000021,
282
InputDeviceNew = 0xE0000022,
283
InputEventsOpen = 0xE0000023,
284
InvalidRunArgs = 0xE0000025,
285
InvalidSubCommand = 0xE0000026,
286
InvalidSubCommandArgs = 0xE0000027,
287
InvalidWaylandPath = 0xE0000028,
288
LoadKernel = 0xE0000029,
289
MissingCommandArg = 0xE0000030,
290
ModifyBatteryError = 0xE0000031,
291
NetDeviceNew = 0xE0000032,
292
OpenAcpiTable = 0xE0000033,
293
OpenAndroidFstab = 0xE0000034,
294
OpenBios = 0xE0000035,
295
OpenInitrd = 0xE0000036,
296
OpenKernel = 0xE0000037,
297
OpenVinput = 0xE0000038,
298
PivotRootDoesntExist = 0xE0000039,
299
PmemDeviceImageTooBig = 0xE000003A,
300
PmemDeviceNew = 0xE000003B,
301
ReadMemAvailable = 0xE000003C,
302
RegisterBalloon = 0xE000003D,
303
RegisterBlock = 0xE000003E,
304
RegisterGpu = 0xE000003F,
305
RegisterNet = 0xE0000040,
306
RegisterP9 = 0xE0000041,
307
RegisterRng = 0xE0000042,
308
RegisterWayland = 0xE0000043,
309
ReserveGpuMemory = 0xE0000044,
310
ReserveMemory = 0xE0000045,
311
ReservePmemMemory = 0xE0000046,
312
ResetTimer = 0xE0000047,
313
RngDeviceNew = 0xE0000048,
314
RunnableVcpu = 0xE0000049,
315
SettingSignalMask = 0xE000004B,
316
SpawnVcpu = 0xE000004D,
317
SysUtil = 0xE000004E,
318
Timer = 0xE000004F,
319
ValidateRawDescriptor = 0xE0000050,
320
VirtioPciDev = 0xE0000051,
321
WaitContextAdd = 0xE0000052,
322
WaitContextDelete = 0xE0000053,
323
WhpxSetupError = 0xE0000054,
324
VcpuFailEntry = 0xE0000055,
325
VcpuRunError = 0xE0000056,
326
VcpuShutdown = 0xE0000057,
327
VcpuSystemEvent = 0xE0000058,
328
WaitUntilRunnable = 0xE0000059,
329
CreateControlServer = 0xE000005A,
330
CreateTube = 0xE000005B,
331
UsbError = 0xE000005E,
332
GuestMemoryLayout = 0xE000005F,
333
CreateVm = 0xE0000060,
334
CreateGuestMemory = 0xE0000061,
335
CreateIrqChip = 0xE0000062,
336
SpawnIrqThread = 0xE0000063,
337
ConnectTube = 0xE0000064,
338
BalloonDeviceNew = 0xE0000065,
339
BalloonStats = 0xE0000066,
340
OpenCompositeFooterFile = 0xE0000068,
341
OpenCompositeHeaderFile = 0xE0000069,
342
OpenCompositeImageFile = 0xE0000070,
343
CreateCompositeDisk = 0xE0000071,
344
MissingControlTube = 0xE0000072,
345
TubeTransporterInit = 0xE0000073,
346
TubeFailure = 0xE0000074,
347
ProcessSpawnFailed = 0xE0000075,
348
LogFile = 0xE0000076,
349
CreateZeroFiller = 0xE0000077,
350
GenerateAcpi = 0xE0000078,
351
WaitContextWait = 0xE0000079,
352
SetSigintHandler = 0xE000007A,
353
KilledBySignal = 0xE000007B,
354
BrokerDeviceExitedTimeout = 0xE000007C,
355
BrokerMainExitedTimeout = 0xE000007D,
356
MemoryTooLarge = 0xE000007E,
357
BrokerMetricsExitedTimeout = 0xE000007F,
358
MetricsController = 0xE0000080,
359
SwiotlbTooLarge = 0xE0000081,
360
UserspaceVsockDeviceNew = 0xE0000082,
361
VhostUserBlockDeviceNew = 0xE0000083,
362
CrashReportingInit = 0xE0000084,
363
StartBackendDevice = 0xE0000085,
364
ConfigureHotPlugDevice = 0xE0000086,
365
InvalidHotPlugKey = 0xE0000087,
366
InvalidVfioPath = 0xE0000088,
367
NoHotPlugBus = 0xE0000089,
368
SandboxError = 0xE000008A,
369
Pstore = 0xE000008B,
370
ProcessInvariantsInit = 0xE000008C,
371
VirtioVhostUserDeviceNew = 0xE000008D,
372
CloneTube = 0xE000008E,
373
VhostUserGpuDeviceNew = 0xE000008F,
374
CreateAsyncDisk = 0xE0000090,
375
CreateDiskCheckAsyncOkError = 0xE0000091,
376
VhostUserNetDeviceNew = 0xE0000092,
377
BrokerSigtermTimeout = 0xE0000093,
378
SpawnVcpuMonitor = 0xE0000094,
379
NoDefaultHypervisor = 0xE0000095,
380
TscCalibrationFailed = 0xE0000096,
381
UnknownError = 0xE0000097,
382
CommonChildSetupError = 0xE0000098,
383
CreateImeThread = 0xE0000099,
384
OpenDiskImage = 0xE000009A,
385
VirtioSoundDeviceNew = 0xE000009B,
386
StartSpu = 0xE000009C,
387
SandboxCreateProcessAccessDenied = 0xE000009D,
388
SandboxCreateProcessElevationRequired = 0xE000009E,
389
BalloonSizeInvalid = 0xE000009F,
390
VhostUserSndDeviceNew = 0xE00000A0,
391
FailedToCreateControlServer = 0xE00000A1,
392
}
393
394
impl From<Exit> for ExitCode {
395
fn from(exit: Exit) -> Self {
396
exit as ExitCode
397
}
398
}
399
400
// Bitfield masks for NTSTATUS & our extension of the format. See to_process_type_error for details.
401
mod bitmasks {
402
pub const FACILITY_FIELD_LOWER_MASK: u32 = u32::from_be_bytes([0x00, 0x3F, 0x00, 0x00]);
403
pub const EXTRA_DATA_FIELD_MASK: u32 = u32::from_be_bytes([0x0F, 0xC0, 0x00, 0x00]);
404
#[cfg(test)]
405
pub const EXTRA_DATA_FIELD_COMMAND_TYPE_MASK: u32 =
406
u32::from_be_bytes([0x07, 0xC0, 0x00, 0x00]);
407
pub const EXTRA_DATA_FIELD_OVERFLOW_BIT_MASK: u32 =
408
u32::from_be_bytes([0x08, 0x00, 0x00, 0x00]);
409
pub const VENDOR_FIELD_MASK: u32 = u32::from_be_bytes([0x20, 0x00, 0x00, 0x00]);
410
pub const RESERVED_BIT_MASK: u32 = u32::from_be_bytes([0x10, 0x00, 0x00, 0x00]);
411
pub const COMMAND_TYPE_MASK: u32 = u32::from_be_bytes([0x00, 0x00, 0x00, 0x1F]);
412
}
413
use bitmasks::*;
414
415
/// If you are looking for a fun interview question, you have come to the right place. To
416
/// understand the details of NTSTATUS, which you'll want to do before reading further, visit
417
/// <https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-erref/87fba13e-bf06-450e-83b1-9241dc81e781>.
418
///
419
/// This function is unfortunately what happens when you only have six bits to store auxiliary
420
/// information, and have to fit in with an existing bitfield's schema.
421
///
422
/// For reference, the format of the NTSTATUS field is as follows:
423
///
424
/// | [31, 30] | [29] | [28] | [27, 16] | [15, 0] |
425
/// | Severity | Customer/vendor | N (reserved) | Facility | Code |
426
///
427
/// This function packs bits in NTSTATUS results (generally what a Windows exit code should be).
428
/// There are three primary cases it deals with:
429
/// 1. Vendor specific exits. These are error codes we generate explicitly in crosvm. We will pack
430
/// these codes with the lower 6 "facility" bits ([21, 16]) set so they can't collide with the
431
/// other cases (this makes our facility value > FACILITY_MAXIMUM_VALUE). The top 6 bits of the
432
/// facility field ([27, 22]) will be clear at this point.
433
///
434
/// 2. Non vendor NTSTATUS exits. These are error codes which come from Windows. We flip the
435
/// vendor bit on these because we're going to pack the facility field, and leaving it unset
436
/// would cause us to violate the rule that if the vendor bit is unset, we shouldn't exceed
437
/// FACILITY_MAXIMUM_VALUE in that field. The top six bits of the facility field ([27, 22])
438
/// will be clear in this scenario because Windows won't exceed FACILITY_MAXIMUM_VALUE;
439
/// however, if for some reason we see a non vendor code with any of those bits set, we will
440
/// fall through to case #3.
441
///
442
/// 3. Non NTSTATUS errors. We detect these with two heuristics: a) Reserved field is set. b) The
443
/// facility field has exceeded the bottom six bits ([21, 16]).
444
///
445
/// For such cases, we pack as much of the error as we can into the lower 6 bits of the
446
/// facility field, and code field (2 bytes). In this case, the most significant bit of the
447
/// facility field is set.
448
///
449
/// For all of the cases above, we pack the 5 bits following the most significant bit of the
450
/// facility field (e.g. [26, 22]) with information about what command type generated this error.
451
pub fn to_process_type_error(error_code: u32, cmd_type: ProcessType) -> u32 {
452
let is_vendor = error_code & VENDOR_FIELD_MASK != 0;
453
454
// The reserved bit is always clear on a NTSTATUS code.
455
let is_reserved_bit_clear = error_code & RESERVED_BIT_MASK == 0;
456
457
// The six most significant bits of the facility field are where we'll be storing our
458
// command type and whether we have a valid NTSTATUS error. If bits are already set there,
459
// it means this isn't a valid NTSTATUS code.
460
let is_extra_data_field_clear = error_code & EXTRA_DATA_FIELD_MASK == 0;
461
462
let is_ntstatus = is_reserved_bit_clear && is_extra_data_field_clear;
463
464
// We use the top bit of the facility field to store whether we ran out of space to pack
465
// the error. The next five bits are where we store the command type, so we'll shift them
466
// into the appropriate position here.
467
let command_type = (cmd_type as u32 & COMMAND_TYPE_MASK) << 22;
468
469
match (is_ntstatus, is_vendor) {
470
// Valid vendor code
471
(true, true) => {
472
// Set all the lower facility bits, and attach the command type.
473
error_code | FACILITY_FIELD_LOWER_MASK | command_type
474
}
475
476
// Valid non-vendor code
477
(true, false) => {
478
// Set the vendor bit and attach the command type.
479
error_code | VENDOR_FIELD_MASK | command_type
480
}
481
482
// Not a valid NTSTATUS code.
483
_ => {
484
// Clear the extra data field, and set the the top bit of the facility field to
485
// signal that we didn't have enough space for the full error codes.
486
error_code & !EXTRA_DATA_FIELD_MASK | command_type | EXTRA_DATA_FIELD_OVERFLOW_BIT_MASK
487
}
488
}
489
}
490
491
#[cfg(test)]
492
mod tests {
493
use winapi::shared::ntstatus::STATUS_BAD_INITIAL_PC;
494
495
use super::*;
496
497
#[test]
498
fn test_to_process_type_error_ntstatus_vendor() {
499
let e = to_process_type_error(Exit::InvalidRunArgs as u32, ProcessType::Main);
500
assert_eq!(
501
e & EXTRA_DATA_FIELD_COMMAND_TYPE_MASK,
502
(ProcessType::Main as u32) << 22
503
);
504
assert_eq!(e & EXTRA_DATA_FIELD_OVERFLOW_BIT_MASK, 0);
505
506
// This is a valid NTSTATUS error.
507
assert_eq!(e & RESERVED_BIT_MASK, 0);
508
509
// Check the actual crosvm error code contained in the NTSTATUS. We don't mutate the
510
// severity field, so we don't mask it off. We mask off the facility field entirely because
511
// that's where we stored the command type & NTSTATUS validity bit.
512
assert_eq!(e & 0xF000FFFF_u32, Exit::InvalidRunArgs as u32);
513
}
514
515
#[test]
516
fn test_to_process_type_error_ntstatus_non_vendor() {
517
let e = to_process_type_error(STATUS_BAD_INITIAL_PC as u32, ProcessType::Main);
518
assert_eq!(
519
e & EXTRA_DATA_FIELD_COMMAND_TYPE_MASK,
520
(ProcessType::Main as u32) << 22
521
);
522
assert_eq!(e & EXTRA_DATA_FIELD_OVERFLOW_BIT_MASK, 0);
523
524
// This is a valid NTSTATUS error.
525
assert_eq!(e & RESERVED_BIT_MASK, 0);
526
527
// Check the actual error code contained in the NTSTATUS. We mask off all our extra data
528
// fields and switch off the vendor bit to confirm the actual code was left alone.
529
assert_eq!(
530
e & !EXTRA_DATA_FIELD_MASK & !VENDOR_FIELD_MASK,
531
STATUS_BAD_INITIAL_PC as u32
532
);
533
}
534
535
#[test]
536
fn test_to_process_type_error_wontfit_ntstatus() {
537
let e = to_process_type_error(0xFFFFFFFF, ProcessType::Main);
538
assert_eq!(
539
e & EXTRA_DATA_FIELD_COMMAND_TYPE_MASK,
540
(ProcessType::Main as u32) << 22
541
);
542
543
// -1 is not a valid NTSTATUS error.
544
assert_ne!(e & RESERVED_BIT_MASK, 0);
545
546
// Overflow did occur.
547
assert_ne!(e & EXTRA_DATA_FIELD_OVERFLOW_BIT_MASK, 0);
548
549
// Check that we left the rest of the bits (except for our command type field & overflow
550
// bit) in the exit code untouched.
551
assert_eq!(e & 0xF03FFFFF_u32, 0xF03FFFFF_u32);
552
}
553
}
554
555