authorgravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2025-09-13 12:54:31+01:00
committergravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2025-09-30 13:44:54+01:00
logf5c8d80e08fac9dcf2eb88941d68b44640a4c9b4
tree8ea4aea6321454af813cea9929265cb582601772
parentd28966785652822b7b763044ce4cb2421247a8f4
signaturelock-open Commit is signed but in an unrecognized format.

windows_bat_args: fix path handling

The input path could be cwd-relative, in which case it must be modified before it is written into the batch script. Also, remove usage of deprecated `GeneralPurposeAllocator` alias, rename `allocator` to `gpa`, use unmanaged `ArrayList`.

2 files changed, 117 insertions(+), 107 deletions(-)

test/standalone/windows_bat_args/fuzz.zig+40-35
...@@ -3,14 +3,14 @@ const builtin = @import("builtin");...@@ -3,14 +3,14 @@ const builtin = @import("builtin");
3const Allocator = std.mem.Allocator;3const Allocator = std.mem.Allocator;
44
5pub fn main() anyerror!void {5pub fn main() anyerror!void {
6 var gpa: std.heap.GeneralPurposeAllocator(.{}) = .init;6 var debug_alloc_inst: std.heap.DebugAllocator(.{}) = .init;
7 defer if (gpa.deinit() == .leak) @panic("found memory leaks");7 defer std.debug.assert(debug_alloc_inst.deinit() == .ok);
8 const allocator = gpa.allocator();8 const gpa = debug_alloc_inst.allocator();
99
10 var it = try std.process.argsWithAllocator(allocator);10 var it = try std.process.argsWithAllocator(gpa);
11 defer it.deinit();11 defer it.deinit();
12 _ = it.next() orelse unreachable; // skip binary name12 _ = it.next() orelse unreachable; // skip binary name
13 const child_exe_path = it.next() orelse unreachable;13 const child_exe_path_orig = it.next() orelse unreachable;
1414
15 const iterations: u64 = iterations: {15 const iterations: u64 = iterations: {
16 const arg = it.next() orelse "0";16 const arg = it.next() orelse "0";
...@@ -42,58 +42,63 @@ pub fn main() anyerror!void {...@@ -42,58 +42,63 @@ pub fn main() anyerror!void {
42 try tmp.dir.setAsCwd();42 try tmp.dir.setAsCwd();
43 defer tmp.parent_dir.setAsCwd() catch {};43 defer tmp.parent_dir.setAsCwd() catch {};
4444
45 var buf = try std.array_list.Managed(u8).initCapacity(allocator, 128);45 // `child_exe_path_orig` might be relative; make it relative to our new cwd.
46 defer buf.deinit();46 const child_exe_path = try std.fs.path.resolve(gpa, &.{ "..\\..\\..", child_exe_path_orig });
47 try buf.appendSlice("@echo off\n");47 defer gpa.free(child_exe_path);
48 try buf.append('"');48
49 try buf.appendSlice(child_exe_path);49 var buf: std.ArrayList(u8) = .empty;
50 try buf.append('"');50 defer buf.deinit(gpa);
51 try buf.print(gpa,
52 \\@echo off
53 \\"{s}"
54 , .{child_exe_path});
55 // Trailing newline intentionally omitted above so we can add args.
51 const preamble_len = buf.items.len;56 const preamble_len = buf.items.len;
5257
53 try buf.appendSlice(" %*");58 try buf.appendSlice(gpa, " %*");
54 try tmp.dir.writeFile(.{ .sub_path = "args1.bat", .data = buf.items });59 try tmp.dir.writeFile(.{ .sub_path = "args1.bat", .data = buf.items });
55 buf.shrinkRetainingCapacity(preamble_len);60 buf.shrinkRetainingCapacity(preamble_len);
5661
57 try buf.appendSlice(" %1 %2 %3 %4 %5 %6 %7 %8 %9");62 try buf.appendSlice(gpa, " %1 %2 %3 %4 %5 %6 %7 %8 %9");
58 try tmp.dir.writeFile(.{ .sub_path = "args2.bat", .data = buf.items });63 try tmp.dir.writeFile(.{ .sub_path = "args2.bat", .data = buf.items });
59 buf.shrinkRetainingCapacity(preamble_len);64 buf.shrinkRetainingCapacity(preamble_len);
6065
61 try buf.appendSlice(" \"%~1\" \"%~2\" \"%~3\" \"%~4\" \"%~5\" \"%~6\" \"%~7\" \"%~8\" \"%~9\"");66 try buf.appendSlice(gpa, " \"%~1\" \"%~2\" \"%~3\" \"%~4\" \"%~5\" \"%~6\" \"%~7\" \"%~8\" \"%~9\"");
62 try tmp.dir.writeFile(.{ .sub_path = "args3.bat", .data = buf.items });67 try tmp.dir.writeFile(.{ .sub_path = "args3.bat", .data = buf.items });
63 buf.shrinkRetainingCapacity(preamble_len);68 buf.shrinkRetainingCapacity(preamble_len);
6469
65 var i: u64 = 0;70 var i: u64 = 0;
66 while (iterations == 0 or i < iterations) {71 while (iterations == 0 or i < iterations) {
67 const rand_arg = try randomArg(allocator, rand);72 const rand_arg = try randomArg(gpa, rand);
68 defer allocator.free(rand_arg);73 defer gpa.free(rand_arg);
6974
70 try testExec(allocator, &.{rand_arg}, null);75 try testExec(gpa, &.{rand_arg}, null);
7176
72 i += 1;77 i += 1;
73 }78 }
74}79}
7580
76fn testExec(allocator: std.mem.Allocator, args: []const []const u8, env: ?*std.process.EnvMap) !void {81fn testExec(gpa: std.mem.Allocator, args: []const []const u8, env: ?*std.process.EnvMap) !void {
77 try testExecBat(allocator, "args1.bat", args, env);82 try testExecBat(gpa, "args1.bat", args, env);
78 try testExecBat(allocator, "args2.bat", args, env);83 try testExecBat(gpa, "args2.bat", args, env);
79 try testExecBat(allocator, "args3.bat", args, env);84 try testExecBat(gpa, "args3.bat", args, env);
80}85}
8186
82fn testExecBat(allocator: std.mem.Allocator, bat: []const u8, args: []const []const u8, env: ?*std.process.EnvMap) !void {87fn testExecBat(gpa: std.mem.Allocator, bat: []const u8, args: []const []const u8, env: ?*std.process.EnvMap) !void {
83 var argv = try std.array_list.Managed([]const u8).initCapacity(allocator, 1 + args.len);88 const argv = try gpa.alloc([]const u8, 1 + args.len);
84 defer argv.deinit();89 defer gpa.free(argv);
85 argv.appendAssumeCapacity(bat);90 argv[0] = bat;
86 argv.appendSliceAssumeCapacity(args);91 @memcpy(argv[1..], args);
8792
88 const can_have_trailing_empty_args = std.mem.eql(u8, bat, "args3.bat");93 const can_have_trailing_empty_args = std.mem.eql(u8, bat, "args3.bat");
8994
90 const result = try std.process.Child.run(.{95 const result = try std.process.Child.run(.{
91 .allocator = allocator,96 .allocator = gpa,
92 .env_map = env,97 .env_map = env,
93 .argv = argv.items,98 .argv = argv,
94 });99 });
95 defer allocator.free(result.stdout);100 defer gpa.free(result.stdout);
96 defer allocator.free(result.stderr);101 defer gpa.free(result.stderr);
97102
98 try std.testing.expectEqualStrings("", result.stderr);103 try std.testing.expectEqualStrings("", result.stderr);
99 var it = std.mem.splitScalar(u8, result.stdout, '\x00');104 var it = std.mem.splitScalar(u8, result.stdout, '\x00');
...@@ -109,7 +114,7 @@ fn testExecBat(allocator: std.mem.Allocator, bat: []const u8, args: []const []co...@@ -109,7 +114,7 @@ fn testExecBat(allocator: std.mem.Allocator, bat: []const u8, args: []const []co
109 }114 }
110}115}
111116
112fn randomArg(allocator: Allocator, rand: std.Random) ![]const u8 {117fn randomArg(gpa: Allocator, rand: std.Random) ![]const u8 {
113 const Choice = enum {118 const Choice = enum {
114 backslash,119 backslash,
115 quote,120 quote,
...@@ -121,8 +126,8 @@ fn randomArg(allocator: Allocator, rand: std.Random) ![]const u8 {...@@ -121,8 +126,8 @@ fn randomArg(allocator: Allocator, rand: std.Random) ![]const u8 {
121 };126 };
122127
123 const choices = rand.uintAtMostBiased(u16, 256);128 const choices = rand.uintAtMostBiased(u16, 256);
124 var buf = try std.array_list.Managed(u8).initCapacity(allocator, choices);129 var buf: std.ArrayList(u8) = try .initCapacity(gpa, choices);
125 errdefer buf.deinit();130 errdefer buf.deinit(gpa);
126131
127 var last_codepoint: u21 = 0;132 var last_codepoint: u21 = 0;
128 for (0..choices) |_| {133 for (0..choices) |_| {
...@@ -149,12 +154,12 @@ fn randomArg(allocator: Allocator, rand: std.Random) ![]const u8 {...@@ -149,12 +154,12 @@ fn randomArg(allocator: Allocator, rand: std.Random) ![]const u8 {
149 continue;154 continue;
150 }155 }
151 }156 }
152 try buf.ensureUnusedCapacity(4);157 try buf.ensureUnusedCapacity(gpa, 4);
153 const unused_slice = buf.unusedCapacitySlice();158 const unused_slice = buf.unusedCapacitySlice();
154 const len = std.unicode.wtf8Encode(codepoint, unused_slice) catch unreachable;159 const len = std.unicode.wtf8Encode(codepoint, unused_slice) catch unreachable;
155 buf.items.len += len;160 buf.items.len += len;
156 last_codepoint = codepoint;161 last_codepoint = codepoint;
157 }162 }
158163
159 return buf.toOwnedSlice();164 return buf.toOwnedSlice(gpa);
160}165}
test/standalone/windows_bat_args/test.zig+77-72
...@@ -1,14 +1,14 @@...@@ -1,14 +1,14 @@
1const std = @import("std");1const std = @import("std");
22
3pub fn main() anyerror!void {3pub fn main() anyerror!void {
4 var gpa: std.heap.GeneralPurposeAllocator(.{}) = .init;4 var debug_alloc_inst: std.heap.DebugAllocator(.{}) = .init;
5 defer if (gpa.deinit() == .leak) @panic("found memory leaks");5 defer std.debug.assert(debug_alloc_inst.deinit() == .ok);
6 const allocator = gpa.allocator();6 const gpa = debug_alloc_inst.allocator();
77
8 var it = try std.process.argsWithAllocator(allocator);8 var it = try std.process.argsWithAllocator(gpa);
9 defer it.deinit();9 defer it.deinit();
10 _ = it.next() orelse unreachable; // skip binary name10 _ = it.next() orelse unreachable; // skip binary name
11 const child_exe_path = it.next() orelse unreachable;11 const child_exe_path_orig = it.next() orelse unreachable;
1212
13 var tmp = std.testing.tmpDir(.{});13 var tmp = std.testing.tmpDir(.{});
14 defer tmp.cleanup();14 defer tmp.cleanup();
...@@ -16,62 +16,67 @@ pub fn main() anyerror!void {...@@ -16,62 +16,67 @@ pub fn main() anyerror!void {
16 try tmp.dir.setAsCwd();16 try tmp.dir.setAsCwd();
17 defer tmp.parent_dir.setAsCwd() catch {};17 defer tmp.parent_dir.setAsCwd() catch {};
1818
19 var buf = try std.array_list.Managed(u8).initCapacity(allocator, 128);19 // `child_exe_path_orig` might be relative; make it relative to our new cwd.
20 defer buf.deinit();20 const child_exe_path = try std.fs.path.resolve(gpa, &.{ "..\\..\\..", child_exe_path_orig });
21 try buf.appendSlice("@echo off\n");21 defer gpa.free(child_exe_path);
22 try buf.append('"');22
23 try buf.appendSlice(child_exe_path);23 var buf: std.ArrayList(u8) = .empty;
24 try buf.append('"');24 defer buf.deinit(gpa);
25 try buf.print(gpa,
26 \\@echo off
27 \\"{s}"
28 , .{child_exe_path});
29 // Trailing newline intentionally omitted above so we can add args.
25 const preamble_len = buf.items.len;30 const preamble_len = buf.items.len;
2631
27 try buf.appendSlice(" %*");32 try buf.appendSlice(gpa, " %*");
28 try tmp.dir.writeFile(.{ .sub_path = "args1.bat", .data = buf.items });33 try tmp.dir.writeFile(.{ .sub_path = "args1.bat", .data = buf.items });
29 buf.shrinkRetainingCapacity(preamble_len);34 buf.shrinkRetainingCapacity(preamble_len);
3035
31 try buf.appendSlice(" %1 %2 %3 %4 %5 %6 %7 %8 %9");36 try buf.appendSlice(gpa, " %1 %2 %3 %4 %5 %6 %7 %8 %9");
32 try tmp.dir.writeFile(.{ .sub_path = "args2.bat", .data = buf.items });37 try tmp.dir.writeFile(.{ .sub_path = "args2.bat", .data = buf.items });
33 buf.shrinkRetainingCapacity(preamble_len);38 buf.shrinkRetainingCapacity(preamble_len);
3439
35 try buf.appendSlice(" \"%~1\" \"%~2\" \"%~3\" \"%~4\" \"%~5\" \"%~6\" \"%~7\" \"%~8\" \"%~9\"");40 try buf.appendSlice(gpa, " \"%~1\" \"%~2\" \"%~3\" \"%~4\" \"%~5\" \"%~6\" \"%~7\" \"%~8\" \"%~9\"");
36 try tmp.dir.writeFile(.{ .sub_path = "args3.bat", .data = buf.items });41 try tmp.dir.writeFile(.{ .sub_path = "args3.bat", .data = buf.items });
37 buf.shrinkRetainingCapacity(preamble_len);42 buf.shrinkRetainingCapacity(preamble_len);
3843
39 // Test cases are from https://github.com/rust-lang/rust/blob/master/tests/ui/std/windows-bat-args.rs44 // Test cases are from https://github.com/rust-lang/rust/blob/master/tests/ui/std/windows-bat-args.rs
40 try testExecError(error.InvalidBatchScriptArg, allocator, &.{"\x00"});45 try testExecError(error.InvalidBatchScriptArg, gpa, &.{"\x00"});
41 try testExecError(error.InvalidBatchScriptArg, allocator, &.{"\n"});46 try testExecError(error.InvalidBatchScriptArg, gpa, &.{"\n"});
42 try testExecError(error.InvalidBatchScriptArg, allocator, &.{"\r"});47 try testExecError(error.InvalidBatchScriptArg, gpa, &.{"\r"});
43 try testExec(allocator, &.{ "a", "b" }, null);48 try testExec(gpa, &.{ "a", "b" }, null);
44 try testExec(allocator, &.{ "c is for cat", "d is for dog" }, null);49 try testExec(gpa, &.{ "c is for cat", "d is for dog" }, null);
45 try testExec(allocator, &.{ "\"", " \"" }, null);50 try testExec(gpa, &.{ "\"", " \"" }, null);
46 try testExec(allocator, &.{ "\\", "\\" }, null);51 try testExec(gpa, &.{ "\\", "\\" }, null);
47 try testExec(allocator, &.{">file.txt"}, null);52 try testExec(gpa, &.{">file.txt"}, null);
48 try testExec(allocator, &.{"whoami.exe"}, null);53 try testExec(gpa, &.{"whoami.exe"}, null);
49 try testExec(allocator, &.{"&a.exe"}, null);54 try testExec(gpa, &.{"&a.exe"}, null);
50 try testExec(allocator, &.{"&echo hello "}, null);55 try testExec(gpa, &.{"&echo hello "}, null);
51 try testExec(allocator, &.{ "&echo hello", "&whoami", ">file.txt" }, null);56 try testExec(gpa, &.{ "&echo hello", "&whoami", ">file.txt" }, null);
52 try testExec(allocator, &.{"!TMP!"}, null);57 try testExec(gpa, &.{"!TMP!"}, null);
53 try testExec(allocator, &.{"key=value"}, null);58 try testExec(gpa, &.{"key=value"}, null);
54 try testExec(allocator, &.{"\"key=value\""}, null);59 try testExec(gpa, &.{"\"key=value\""}, null);
55 try testExec(allocator, &.{"key = value"}, null);60 try testExec(gpa, &.{"key = value"}, null);
56 try testExec(allocator, &.{"key=[\"value\"]"}, null);61 try testExec(gpa, &.{"key=[\"value\"]"}, null);
57 try testExec(allocator, &.{ "", "a=b" }, null);62 try testExec(gpa, &.{ "", "a=b" }, null);
58 try testExec(allocator, &.{"key=\"foo bar\""}, null);63 try testExec(gpa, &.{"key=\"foo bar\""}, null);
59 try testExec(allocator, &.{"key=[\"my_value]"}, null);64 try testExec(gpa, &.{"key=[\"my_value]"}, null);
60 try testExec(allocator, &.{"key=[\"my_value\",\"other-value\"]"}, null);65 try testExec(gpa, &.{"key=[\"my_value\",\"other-value\"]"}, null);
61 try testExec(allocator, &.{"key\\=value"}, null);66 try testExec(gpa, &.{"key\\=value"}, null);
62 try testExec(allocator, &.{"key=\"&whoami\""}, null);67 try testExec(gpa, &.{"key=\"&whoami\""}, null);
63 try testExec(allocator, &.{"key=\"value\"=5"}, null);68 try testExec(gpa, &.{"key=\"value\"=5"}, null);
64 try testExec(allocator, &.{"key=[\">file.txt\"]"}, null);69 try testExec(gpa, &.{"key=[\">file.txt\"]"}, null);
65 try testExec(allocator, &.{"%hello"}, null);70 try testExec(gpa, &.{"%hello"}, null);
66 try testExec(allocator, &.{"%PATH%"}, null);71 try testExec(gpa, &.{"%PATH%"}, null);
67 try testExec(allocator, &.{"%%cd:~,%"}, null);72 try testExec(gpa, &.{"%%cd:~,%"}, null);
68 try testExec(allocator, &.{"%PATH%PATH%"}, null);73 try testExec(gpa, &.{"%PATH%PATH%"}, null);
69 try testExec(allocator, &.{"\">file.txt"}, null);74 try testExec(gpa, &.{"\">file.txt"}, null);
70 try testExec(allocator, &.{"abc\"&echo hello"}, null);75 try testExec(gpa, &.{"abc\"&echo hello"}, null);
71 try testExec(allocator, &.{"123\">file.txt"}, null);76 try testExec(gpa, &.{"123\">file.txt"}, null);
72 try testExec(allocator, &.{"\"&echo hello&whoami.exe"}, null);77 try testExec(gpa, &.{"\"&echo hello&whoami.exe"}, null);
73 try testExec(allocator, &.{ "\"hello^\"world\"", "hello &echo oh no >file.txt" }, null);78 try testExec(gpa, &.{ "\"hello^\"world\"", "hello &echo oh no >file.txt" }, null);
74 try testExec(allocator, &.{"&whoami.exe"}, null);79 try testExec(gpa, &.{"&whoami.exe"}, null);
7580
76 // Ensure that trailing space and . characters can't lead to unexpected bat/cmd script execution.81 // Ensure that trailing space and . characters can't lead to unexpected bat/cmd script execution.
77 // In many Windows APIs (including CreateProcess), trailing space and . characters are stripped82 // In many Windows APIs (including CreateProcess), trailing space and . characters are stripped
...@@ -89,17 +94,17 @@ pub fn main() anyerror!void {...@@ -89,17 +94,17 @@ pub fn main() anyerror!void {
89 // > "args1.bat .. "94 // > "args1.bat .. "
90 // '"args1.bat .. "' is not recognized as an internal or external command,95 // '"args1.bat .. "' is not recognized as an internal or external command,
91 // operable program or batch file.96 // operable program or batch file.
92 try std.testing.expectError(error.FileNotFound, testExecBat(allocator, "args1.bat .. ", &.{"abc"}, null));97 try std.testing.expectError(error.FileNotFound, testExecBat(gpa, "args1.bat .. ", &.{"abc"}, null));
93 const absolute_with_trailing = blk: {98 const absolute_with_trailing = blk: {
94 const absolute_path = try std.fs.realpathAlloc(allocator, "args1.bat");99 const absolute_path = try std.fs.realpathAlloc(gpa, "args1.bat");
95 defer allocator.free(absolute_path);100 defer gpa.free(absolute_path);
96 break :blk try std.mem.concat(allocator, u8, &.{ absolute_path, " .. " });101 break :blk try std.mem.concat(gpa, u8, &.{ absolute_path, " .. " });
97 };102 };
98 defer allocator.free(absolute_with_trailing);103 defer gpa.free(absolute_with_trailing);
99 try std.testing.expectError(error.FileNotFound, testExecBat(allocator, absolute_with_trailing, &.{"abc"}, null));104 try std.testing.expectError(error.FileNotFound, testExecBat(gpa, absolute_with_trailing, &.{"abc"}, null));
100105
101 var env = env: {106 var env = env: {
102 var env = try std.process.getEnvMap(allocator);107 var env = try std.process.getEnvMap(gpa);
103 errdefer env.deinit();108 errdefer env.deinit();
104 // No escaping109 // No escaping
105 try env.put("FOO", "123");110 try env.put("FOO", "123");
...@@ -110,37 +115,37 @@ pub fn main() anyerror!void {...@@ -110,37 +115,37 @@ pub fn main() anyerror!void {
110 break :env env;115 break :env env;
111 };116 };
112 defer env.deinit();117 defer env.deinit();
113 try testExec(allocator, &.{"%FOO%"}, &env);118 try testExec(gpa, &.{"%FOO%"}, &env);
114119
115 // Ensure that none of the `>file.txt`s have caused file.txt to be created120 // Ensure that none of the `>file.txt`s have caused file.txt to be created
116 try std.testing.expectError(error.FileNotFound, tmp.dir.access("file.txt", .{}));121 try std.testing.expectError(error.FileNotFound, tmp.dir.access("file.txt", .{}));
117}122}
118123
119fn testExecError(err: anyerror, allocator: std.mem.Allocator, args: []const []const u8) !void {124fn testExecError(err: anyerror, gpa: std.mem.Allocator, args: []const []const u8) !void {
120 return std.testing.expectError(err, testExec(allocator, args, null));125 return std.testing.expectError(err, testExec(gpa, args, null));
121}126}
122127
123fn testExec(allocator: std.mem.Allocator, args: []const []const u8, env: ?*std.process.EnvMap) !void {128fn testExec(gpa: std.mem.Allocator, args: []const []const u8, env: ?*std.process.EnvMap) !void {
124 try testExecBat(allocator, "args1.bat", args, env);129 try testExecBat(gpa, "args1.bat", args, env);
125 try testExecBat(allocator, "args2.bat", args, env);130 try testExecBat(gpa, "args2.bat", args, env);
126 try testExecBat(allocator, "args3.bat", args, env);131 try testExecBat(gpa, "args3.bat", args, env);
127}132}
128133
129fn testExecBat(allocator: std.mem.Allocator, bat: []const u8, args: []const []const u8, env: ?*std.process.EnvMap) !void {134fn testExecBat(gpa: std.mem.Allocator, bat: []const u8, args: []const []const u8, env: ?*std.process.EnvMap) !void {
130 var argv = try std.array_list.Managed([]const u8).initCapacity(allocator, 1 + args.len);135 const argv = try gpa.alloc([]const u8, 1 + args.len);
131 defer argv.deinit();136 defer gpa.free(argv);
132 argv.appendAssumeCapacity(bat);137 argv[0] = bat;
133 argv.appendSliceAssumeCapacity(args);138 @memcpy(argv[1..], args);
134139
135 const can_have_trailing_empty_args = std.mem.eql(u8, bat, "args3.bat");140 const can_have_trailing_empty_args = std.mem.eql(u8, bat, "args3.bat");
136141
137 const result = try std.process.Child.run(.{142 const result = try std.process.Child.run(.{
138 .allocator = allocator,143 .allocator = gpa,
139 .env_map = env,144 .env_map = env,
140 .argv = argv.items,145 .argv = argv,
141 });146 });
142 defer allocator.free(result.stdout);147 defer gpa.free(result.stdout);
143 defer allocator.free(result.stderr);148 defer gpa.free(result.stderr);
144149
145 try std.testing.expectEqualStrings("", result.stderr);150 try std.testing.expectEqualStrings("", result.stderr);
146 var it = std.mem.splitScalar(u8, result.stdout, '\x00');151 var it = std.mem.splitScalar(u8, result.stdout, '\x00');