authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2018-02-10 20:55:13-05:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2018-02-10 21:02:24-05:00
log46aa416c48c283849059292267ac25a6d0db76d6
treece260c8f27835122f09c010387e7c59aabe3a59d
parent8c31eaf2a87d39fe2f9ed8f5af2a059048bfffb3

std.os and std.io API update

* move std.io.File to std.os.File * add `zig fmt` to self hosted compiler * introduce std.io.BufferedAtomicFile API * introduce std.os.AtomicFile API * add `std.os.default_file_mode` * change FileMode on posix from being a usize to a u32 * add std.os.File.mode to return mode of an open file * std.os.copyFile copies the mode from the source file instead of using the default file mode for the dest file * move `std.os.line_sep` to `std.cstr.line_sep`

15 files changed, 543 insertions(+), 351 deletions(-)

CMakeLists.txt+2-1
......@@ -437,11 +437,12 @@ set(ZIG_STD_FILES
437437 "os/child_process.zig"
438438 "os/darwin.zig"
439439 "os/darwin_errno.zig"
440 "os/file.zig"
440441 "os/get_user_id.zig"
441442 "os/index.zig"
442 "os/linux/index.zig"
443443 "os/linux/errno.zig"
444444 "os/linux/i386.zig"
445 "os/linux/index.zig"
445446 "os/linux/x86_64.zig"
446447 "os/path.zig"
447448 "os/windows/error.zig"
doc/docgen.zig+2-2
......@@ -31,10 +31,10 @@ pub fn main() !void {
3131 const out_file_name = try (args_it.next(allocator) ?? @panic("expected output arg"));
3232 defer allocator.free(out_file_name);
3333
34 var in_file = try io.File.openRead(allocator, in_file_name);
34 var in_file = try os.File.openRead(allocator, in_file_name);
3535 defer in_file.close();
3636
37 var out_file = try io.File.openWrite(allocator, out_file_name);
37 var out_file = try os.File.openWrite(allocator, out_file_name);
3838 defer out_file.close();
3939
4040 var file_in_stream = io.FileInStream.init(&in_file);
example/cat/main.zig+2-2
......@@ -20,7 +20,7 @@ pub fn main() !void {
2020 } else if (arg[0] == '-') {
2121 return usage(exe);
2222 } else {
23 var file = io.File.openRead(allocator, arg) catch |err| {
23 var file = os.File.openRead(allocator, arg) catch |err| {
2424 warn("Unable to open file: {}\n", @errorName(err));
2525 return err;
2626 };
......@@ -41,7 +41,7 @@ fn usage(exe: []const u8) !void {
4141 return error.Invalid;
4242}
4343
44fn cat_file(stdout: &io.File, file: &io.File) !void {
44fn cat_file(stdout: &os.File, file: &os.File) !void {
4545 var buf: [1024 * 4]u8 = undefined;
4646
4747 while (true) {
src-self-hosted/main.zig+10-3
......@@ -562,7 +562,7 @@ fn printZen() !void {
562562
563563fn fmtMain(allocator: &mem.Allocator, file_paths: []const []const u8) !void {
564564 for (file_paths) |file_path| {
565 var file = try io.File.openRead(allocator, file_path);
565 var file = try os.File.openRead(allocator, file_path);
566566 defer file.close();
567567
568568 const source_code = io.readFileAlloc(allocator, file_path) catch |err| {
......@@ -574,7 +574,14 @@ fn fmtMain(allocator: &mem.Allocator, file_paths: []const []const u8) !void {
574574 var tokenizer = std.zig.Tokenizer.init(source_code);
575575 var parser = std.zig.Parser.init(&tokenizer, allocator, file_path);
576576 defer parser.deinit();
577 warn("opened {} (todo tokenize and parse and render)\n", file_path);
577
578 const tree = try parser.parse();
579 defer tree.deinit();
580
581 const baf = try io.BufferedAtomicFile.create(allocator, file_path);
582 defer baf.destroy();
583
584 try parser.renderSource(baf.stream(), tree.root_node);
578585 }
579586}
580587
......@@ -602,7 +609,7 @@ fn testZigInstallPrefix(allocator: &mem.Allocator, test_path: []const u8) ![]u8
602609 const test_index_file = try os.path.join(allocator, test_zig_dir, "std", "index.zig");
603610 defer allocator.free(test_index_file);
604611
605 var file = try io.File.openRead(allocator, test_index_file);
612 var file = try os.File.openRead(allocator, test_index_file);
606613 file.close();
607614
608615 return test_zig_dir;
std/build.zig+9-6
......@@ -624,10 +624,10 @@ pub const Builder = struct {
624624 }
625625
626626 fn copyFile(self: &Builder, source_path: []const u8, dest_path: []const u8) !void {
627 return self.copyFileMode(source_path, dest_path, 0o666);
627 return self.copyFileMode(source_path, dest_path, os.default_file_mode);
628628 }
629629
630 fn copyFileMode(self: &Builder, source_path: []const u8, dest_path: []const u8, mode: usize) !void {
630 fn copyFileMode(self: &Builder, source_path: []const u8, dest_path: []const u8, mode: os.FileMode) !void {
631631 if (self.verbose) {
632632 warn("cp {} {}\n", source_path, dest_path);
633633 }
......@@ -1833,10 +1833,13 @@ const InstallArtifactStep = struct {
18331833 const self = @fieldParentPtr(Self, "step", step);
18341834 const builder = self.builder;
18351835
1836 const mode = switch (self.artifact.kind) {
1837 LibExeObjStep.Kind.Obj => unreachable,
1838 LibExeObjStep.Kind.Exe => usize(0o755),
1839 LibExeObjStep.Kind.Lib => if (self.artifact.static) usize(0o666) else usize(0o755),
1836 const mode = switch (builtin.os) {
1837 builtin.Os.windows => {},
1838 else => switch (self.artifact.kind) {
1839 LibExeObjStep.Kind.Obj => unreachable,
1840 LibExeObjStep.Kind.Exe => u32(0o755),
1841 LibExeObjStep.Kind.Lib => if (self.artifact.static) u32(0o666) else u32(0o755),
1842 },
18401843 };
18411844 try builder.copyFileMode(self.artifact.getOutputPath(), self.dest_file, mode);
18421845 if (self.artifact.kind == LibExeObjStep.Kind.Lib and !self.artifact.static) {
std/cstr.zig+7
......@@ -1,8 +1,15 @@
11const std = @import("index.zig");
2const builtin = @import("builtin");
23const debug = std.debug;
34const mem = std.mem;
45const assert = debug.assert;
56
7pub const line_sep = switch (builtin.os) {
8 builtin.Os.windows => "\r\n",
9 else => "\n",
10};
11
12
613pub fn len(ptr: &const u8) usize {
714 var count: usize = 0;
815 while (ptr[count] != 0) : (count += 1) {}
std/debug/index.zig+3-3
......@@ -13,7 +13,7 @@ pub const FailingAllocator = @import("failing_allocator.zig").FailingAllocator;
1313/// Tries to write to stderr, unbuffered, and ignores any error returned.
1414/// Does not append a newline.
1515/// TODO atomic/multithread support
16var stderr_file: io.File = undefined;
16var stderr_file: os.File = undefined;
1717var stderr_file_out_stream: io.FileOutStream = undefined;
1818var stderr_stream: ?&io.OutStream(io.FileOutStream.Error) = null;
1919pub fn warn(comptime fmt: []const u8, args: ...) void {
......@@ -265,7 +265,7 @@ pub fn openSelfDebugInfo(allocator: &mem.Allocator) !&ElfStackTrace {
265265}
266266
267267fn printLineFromFile(allocator: &mem.Allocator, out_stream: var, line_info: &const LineInfo) !void {
268 var f = try io.File.openRead(allocator, line_info.file_name);
268 var f = try os.File.openRead(allocator, line_info.file_name);
269269 defer f.close();
270270 // TODO fstat and make sure that the file has the correct size
271271
......@@ -298,7 +298,7 @@ fn printLineFromFile(allocator: &mem.Allocator, out_stream: var, line_info: &con
298298}
299299
300300pub const ElfStackTrace = struct {
301 self_exe_file: io.File,
301 self_exe_file: os.File,
302302 elf: elf.Elf,
303303 debug_info: &elf.SectionHeader,
304304 debug_abbrev: &elf.SectionHeader,
std/elf.zig+4-3
......@@ -1,6 +1,7 @@
11const builtin = @import("builtin");
22const std = @import("index.zig");
33const io = std.io;
4const os = std.os;
45const math = std.math;
56const mem = std.mem;
67const debug = std.debug;
......@@ -63,7 +64,7 @@ pub const SectionHeader = struct {
6364};
6465
6566pub const Elf = struct {
66 in_file: &io.File,
67 in_file: &os.File,
6768 auto_close_stream: bool,
6869 is_64: bool,
6970 endian: builtin.Endian,
......@@ -76,7 +77,7 @@ pub const Elf = struct {
7677 string_section: &SectionHeader,
7778 section_headers: []SectionHeader,
7879 allocator: &mem.Allocator,
79 prealloc_file: io.File,
80 prealloc_file: os.File,
8081
8182 /// Call close when done.
8283 pub fn openPath(elf: &Elf, allocator: &mem.Allocator, path: []const u8) !void {
......@@ -86,7 +87,7 @@ pub const Elf = struct {
8687 }
8788
8889 /// Call close when done.
89 pub fn openFile(elf: &Elf, allocator: &mem.Allocator, file: &io.File) !void {
90 pub fn openFile(elf: &Elf, allocator: &mem.Allocator, file: &os.File) !void {
9091 elf.allocator = allocator;
9192 elf.in_file = file;
9293 elf.auto_close_stream = false;
std/io.zig+62-276
......@@ -1,12 +1,6 @@
11const std = @import("index.zig");
22const builtin = @import("builtin");
33const Os = builtin.Os;
4const system = switch(builtin.os) {
5 Os.linux => @import("os/linux/index.zig"),
6 Os.macosx, Os.ios => @import("os/darwin.zig"),
7 Os.windows => @import("os/windows/index.zig"),
8 else => @compileError("Unsupported OS"),
9};
104const c = std.c;
115
126const math = std.math;
......@@ -16,23 +10,18 @@ const os = std.os;
1610const mem = std.mem;
1711const Buffer = std.Buffer;
1812const fmt = std.fmt;
13const File = std.os.File;
1914
2015const is_posix = builtin.os != builtin.Os.windows;
2116const is_windows = builtin.os == builtin.Os.windows;
2217
23test "import io tests" {
24 comptime {
25 _ = @import("io_test.zig");
26 }
27}
28
2918const GetStdIoErrs = os.WindowsGetStdHandleErrs;
3019
3120pub fn getStdErr() GetStdIoErrs!File {
3221 const handle = if (is_windows)
33 try os.windowsGetStdHandle(system.STD_ERROR_HANDLE)
22 try os.windowsGetStdHandle(os.windows.STD_ERROR_HANDLE)
3423 else if (is_posix)
35 system.STDERR_FILENO
24 os.posix.STDERR_FILENO
3625 else
3726 unreachable;
3827 return File.openHandle(handle);
......@@ -40,9 +29,9 @@ pub fn getStdErr() GetStdIoErrs!File {
4029
4130pub fn getStdOut() GetStdIoErrs!File {
4231 const handle = if (is_windows)
43 try os.windowsGetStdHandle(system.STD_OUTPUT_HANDLE)
32 try os.windowsGetStdHandle(os.windows.STD_OUTPUT_HANDLE)
4433 else if (is_posix)
45 system.STDOUT_FILENO
34 os.posix.STDOUT_FILENO
4635 else
4736 unreachable;
4837 return File.openHandle(handle);
......@@ -50,9 +39,9 @@ pub fn getStdOut() GetStdIoErrs!File {
5039
5140pub fn getStdIn() GetStdIoErrs!File {
5241 const handle = if (is_windows)
53 try os.windowsGetStdHandle(system.STD_INPUT_HANDLE)
42 try os.windowsGetStdHandle(os.windows.STD_INPUT_HANDLE)
5443 else if (is_posix)
55 system.STDIN_FILENO
44 os.posix.STDIN_FILENO
5645 else
5746 unreachable;
5847 return File.openHandle(handle);
......@@ -104,260 +93,10 @@ pub const FileOutStream = struct {
10493 }
10594};
10695
107pub const File = struct {
108 /// The OS-specific file descriptor or file handle.
109 handle: os.FileHandle,
110
111 const OpenError = os.WindowsOpenError || os.PosixOpenError;
112
113 /// `path` needs to be copied in memory to add a null terminating byte, hence the allocator.
114 /// Call close to clean up.
115 pub fn openRead(allocator: &mem.Allocator, path: []const u8) OpenError!File {
116 if (is_posix) {
117 const flags = system.O_LARGEFILE|system.O_RDONLY;
118 const fd = try os.posixOpen(allocator, path, flags, 0);
119 return openHandle(fd);
120 } else if (is_windows) {
121 const handle = try os.windowsOpen(allocator, path, system.GENERIC_READ, system.FILE_SHARE_READ,
122 system.OPEN_EXISTING, system.FILE_ATTRIBUTE_NORMAL);
123 return openHandle(handle);
124 } else {
125 unreachable;
126 }
127 }
128
129 /// Calls `openWriteMode` with 0o666 for the mode.
130 pub fn openWrite(allocator: &mem.Allocator, path: []const u8) !File {
131 return openWriteMode(allocator, path, 0o666);
132
133 }
134
135 /// `path` needs to be copied in memory to add a null terminating byte, hence the allocator.
136 /// Call close to clean up.
137 pub fn openWriteMode(allocator: &mem.Allocator, path: []const u8, mode: usize) !File {
138 if (is_posix) {
139 const flags = system.O_LARGEFILE|system.O_WRONLY|system.O_CREAT|system.O_CLOEXEC|system.O_TRUNC;
140 const fd = try os.posixOpen(allocator, path, flags, mode);
141 return openHandle(fd);
142 } else if (is_windows) {
143 const handle = try os.windowsOpen(allocator, path, system.GENERIC_WRITE,
144 system.FILE_SHARE_WRITE|system.FILE_SHARE_READ|system.FILE_SHARE_DELETE,
145 system.CREATE_ALWAYS, system.FILE_ATTRIBUTE_NORMAL);
146 return openHandle(handle);
147 } else {
148 unreachable;
149 }
150
151 }
152
153 pub fn openHandle(handle: os.FileHandle) File {
154 return File {
155 .handle = handle,
156 };
157 }
158
159
160 /// Upon success, the stream is in an uninitialized state. To continue using it,
161 /// you must use the open() function.
162 pub fn close(self: &File) void {
163 os.close(self.handle);
164 self.handle = undefined;
165 }
166
167 /// Calls `os.isTty` on `self.handle`.
168 pub fn isTty(self: &File) bool {
169 return os.isTty(self.handle);
170 }
171
172 pub fn seekForward(self: &File, amount: isize) !void {
173 switch (builtin.os) {
174 Os.linux, Os.macosx, Os.ios => {
175 const result = system.lseek(self.handle, amount, system.SEEK_CUR);
176 const err = system.getErrno(result);
177 if (err > 0) {
178 return switch (err) {
179 system.EBADF => error.BadFd,
180 system.EINVAL => error.Unseekable,
181 system.EOVERFLOW => error.Unseekable,
182 system.ESPIPE => error.Unseekable,
183 system.ENXIO => error.Unseekable,
184 else => os.unexpectedErrorPosix(err),
185 };
186 }
187 },
188 Os.windows => {
189 if (system.SetFilePointerEx(self.handle, amount, null, system.FILE_CURRENT) == 0) {
190 const err = system.GetLastError();
191 return switch (err) {
192 system.ERROR.INVALID_PARAMETER => error.BadFd,
193 else => os.unexpectedErrorWindows(err),
194 };
195 }
196 },
197 else => @compileError("unsupported OS"),
198 }
199 }
200
201 pub fn seekTo(self: &File, pos: usize) !void {
202 switch (builtin.os) {
203 Os.linux, Os.macosx, Os.ios => {
204 const ipos = try math.cast(isize, pos);
205 const result = system.lseek(self.handle, ipos, system.SEEK_SET);
206 const err = system.getErrno(result);
207 if (err > 0) {
208 return switch (err) {
209 system.EBADF => error.BadFd,
210 system.EINVAL => error.Unseekable,
211 system.EOVERFLOW => error.Unseekable,
212 system.ESPIPE => error.Unseekable,
213 system.ENXIO => error.Unseekable,
214 else => os.unexpectedErrorPosix(err),
215 };
216 }
217 },
218 Os.windows => {
219 const ipos = try math.cast(isize, pos);
220 if (system.SetFilePointerEx(self.handle, ipos, null, system.FILE_BEGIN) == 0) {
221 const err = system.GetLastError();
222 return switch (err) {
223 system.ERROR.INVALID_PARAMETER => error.BadFd,
224 else => os.unexpectedErrorWindows(err),
225 };
226 }
227 },
228 else => @compileError("unsupported OS: " ++ @tagName(builtin.os)),
229 }
230 }
231
232 pub fn getPos(self: &File) !usize {
233 switch (builtin.os) {
234 Os.linux, Os.macosx, Os.ios => {
235 const result = system.lseek(self.handle, 0, system.SEEK_CUR);
236 const err = system.getErrno(result);
237 if (err > 0) {
238 return switch (err) {
239 system.EBADF => error.BadFd,
240 system.EINVAL => error.Unseekable,
241 system.EOVERFLOW => error.Unseekable,
242 system.ESPIPE => error.Unseekable,
243 system.ENXIO => error.Unseekable,
244 else => os.unexpectedErrorPosix(err),
245 };
246 }
247 return result;
248 },
249 Os.windows => {
250 var pos : system.LARGE_INTEGER = undefined;
251 if (system.SetFilePointerEx(self.handle, 0, &pos, system.FILE_CURRENT) == 0) {
252 const err = system.GetLastError();
253 return switch (err) {
254 system.ERROR.INVALID_PARAMETER => error.BadFd,
255 else => os.unexpectedErrorWindows(err),
256 };
257 }
258
259 assert(pos >= 0);
260 if (@sizeOf(@typeOf(pos)) > @sizeOf(usize)) {
261 if (pos > @maxValue(usize)) {
262 return error.FilePosLargerThanPointerRange;
263 }
264 }
265
266 return usize(pos);
267 },
268 else => @compileError("unsupported OS"),
269 }
270 }
271
272 pub fn getEndPos(self: &File) !usize {
273 if (is_posix) {
274 var stat: system.Stat = undefined;
275 const err = system.getErrno(system.fstat(self.handle, &stat));
276 if (err > 0) {
277 return switch (err) {
278 system.EBADF => error.BadFd,
279 system.ENOMEM => error.SystemResources,
280 else => os.unexpectedErrorPosix(err),
281 };
282 }
283
284 return usize(stat.size);
285 } else if (is_windows) {
286 var file_size: system.LARGE_INTEGER = undefined;
287 if (system.GetFileSizeEx(self.handle, &file_size) == 0) {
288 const err = system.GetLastError();
289 return switch (err) {
290 else => os.unexpectedErrorWindows(err),
291 };
292 }
293 if (file_size < 0)
294 return error.Overflow;
295 return math.cast(usize, u64(file_size));
296 } else {
297 unreachable;
298 }
299 }
300
301 pub const ReadError = error {};
302
303 pub fn read(self: &File, buffer: []u8) !usize {
304 if (is_posix) {
305 var index: usize = 0;
306 while (index < buffer.len) {
307 const amt_read = system.read(self.handle, &buffer[index], buffer.len - index);
308 const read_err = system.getErrno(amt_read);
309 if (read_err > 0) {
310 switch (read_err) {
311 system.EINTR => continue,
312 system.EINVAL => unreachable,
313 system.EFAULT => unreachable,
314 system.EBADF => return error.BadFd,
315 system.EIO => return error.Io,
316 else => return os.unexpectedErrorPosix(read_err),
317 }
318 }
319 if (amt_read == 0) return index;
320 index += amt_read;
321 }
322 return index;
323 } else if (is_windows) {
324 var index: usize = 0;
325 while (index < buffer.len) {
326 const want_read_count = system.DWORD(math.min(system.DWORD(@maxValue(system.DWORD)), buffer.len - index));
327 var amt_read: system.DWORD = undefined;
328 if (system.ReadFile(self.handle, @ptrCast(&c_void, &buffer[index]), want_read_count, &amt_read, null) == 0) {
329 const err = system.GetLastError();
330 return switch (err) {
331 system.ERROR.OPERATION_ABORTED => continue,
332 system.ERROR.BROKEN_PIPE => return index,
333 else => os.unexpectedErrorWindows(err),
334 };
335 }
336 if (amt_read == 0) return index;
337 index += amt_read;
338 }
339 return index;
340 } else {
341 unreachable;
342 }
343 }
344
345 pub const WriteError = os.WindowsWriteError || os.PosixWriteError;
346
347 fn write(self: &File, bytes: []const u8) WriteError!void {
348 if (is_posix) {
349 try os.posixWrite(self.handle, bytes);
350 } else if (is_windows) {
351 try os.windowsWrite(self.handle, bytes);
352 } else {
353 @compileError("Unsupported OS");
354 }
355 }
356};
357
358pub fn InStream(comptime Error: type) type {
96pub fn InStream(comptime ReadError: type) type {
35997 return struct {
36098 const Self = this;
99 pub const Error = ReadError;
361100
362101 /// Return the number of bytes read. If the number read is smaller than buf.len, it
363102 /// means the stream reached the end. Reaching the end of a stream is not an error
......@@ -486,9 +225,10 @@ pub fn InStream(comptime Error: type) type {
486225 };
487226}
488227
489pub fn OutStream(comptime Error: type) type {
228pub fn OutStream(comptime WriteError: type) type {
490229 return struct {
491230 const Self = this;
231 pub const Error = WriteError;
492232
493233 writeFn: fn(self: &Self, bytes: []const u8) Error!void,
494234
......@@ -614,10 +354,11 @@ pub fn BufferedOutStream(comptime Error: type) type {
614354 return BufferedOutStreamCustom(os.page_size, Error);
615355}
616356
617pub fn BufferedOutStreamCustom(comptime buffer_size: usize, comptime Error: type) type {
357pub fn BufferedOutStreamCustom(comptime buffer_size: usize, comptime OutStreamError: type) type {
618358 return struct {
619359 const Self = this;
620 const Stream = OutStream(Error);
360 pub const Stream = OutStream(Error);
361 pub const Error = OutStreamError;
621362
622363 pub stream: Stream,
623364
......@@ -638,9 +379,6 @@ pub fn BufferedOutStreamCustom(comptime buffer_size: usize, comptime Error: type
638379 }
639380
640381 pub fn flush(self: &Self) !void {
641 if (self.index == 0)
642 return;
643
644382 try self.unbuffered_out_stream.write(self.buffer[0..self.index]);
645383 self.index = 0;
646384 }
......@@ -692,3 +430,51 @@ pub const BufferOutStream = struct {
692430 }
693431};
694432
433
434pub const BufferedAtomicFile = struct {
435 atomic_file: os.AtomicFile,
436 file_stream: FileOutStream,
437 buffered_stream: BufferedOutStream(FileOutStream.Error),
438
439 pub fn create(allocator: &mem.Allocator, dest_path: []const u8) !&BufferedAtomicFile {
440 // TODO with well defined copy elision we don't need this allocation
441 var self = try allocator.create(BufferedAtomicFile);
442 errdefer allocator.destroy(self);
443
444 *self = BufferedAtomicFile {
445 .atomic_file = undefined,
446 .file_stream = undefined,
447 .buffered_stream = undefined,
448 };
449
450 self.atomic_file = try os.AtomicFile.init(allocator, dest_path, os.default_file_mode);
451 errdefer self.atomic_file.deinit();
452
453 self.file_stream = FileOutStream.init(&self.atomic_file.file);
454 self.buffered_stream = BufferedOutStream(FileOutStream.Error).init(&self.file_stream.stream);
455 return self;
456 }
457
458 /// always call destroy, even after successful finish()
459 pub fn destroy(self: &BufferedAtomicFile) void {
460 const allocator = self.atomic_file.allocator;
461 self.atomic_file.deinit();
462 allocator.destroy(self);
463 }
464
465 pub fn finish(self: &BufferedAtomicFile) !void {
466 try self.buffered_stream.flush();
467 try self.atomic_file.finish();
468 }
469
470 pub fn stream(self: &BufferedAtomicFile) &OutStream(FileOutStream.Error) {
471 return &self.buffered_stream.stream;
472 }
473};
474
475test "import io tests" {
476 comptime {
477 _ = @import("io_test.zig");
478 }
479}
480
std/io_test.zig+2-2
......@@ -13,7 +13,7 @@ test "write a file, read it, then delete it" {
1313 rng.fillBytes(data[0..]);
1414 const tmp_file_name = "temp_test_file.txt";
1515 {
16 var file = try io.File.openWrite(allocator, tmp_file_name);
16 var file = try os.File.openWrite(allocator, tmp_file_name);
1717 defer file.close();
1818
1919 var file_out_stream = io.FileOutStream.init(&file);
......@@ -25,7 +25,7 @@ test "write a file, read it, then delete it" {
2525 try buf_stream.flush();
2626 }
2727 {
28 var file = try io.File.openRead(allocator, tmp_file_name);
28 var file = try os.File.openRead(allocator, tmp_file_name);
2929 defer file.close();
3030
3131 const file_size = try file.getEndPos();
std/os/child_process.zig+9-9
......@@ -24,9 +24,9 @@ pub const ChildProcess = struct {
2424
2525 pub allocator: &mem.Allocator,
2626
27 pub stdin: ?io.File,
28 pub stdout: ?io.File,
29 pub stderr: ?io.File,
27 pub stdin: ?os.File,
28 pub stdout: ?os.File,
29 pub stderr: ?os.File,
3030
3131 pub term: ?(SpawnError!Term),
3232
......@@ -428,17 +428,17 @@ pub const ChildProcess = struct {
428428 // we are the parent
429429 const pid = i32(pid_result);
430430 if (self.stdin_behavior == StdIo.Pipe) {
431 self.stdin = io.File.openHandle(stdin_pipe[1]);
431 self.stdin = os.File.openHandle(stdin_pipe[1]);
432432 } else {
433433 self.stdin = null;
434434 }
435435 if (self.stdout_behavior == StdIo.Pipe) {
436 self.stdout = io.File.openHandle(stdout_pipe[0]);
436 self.stdout = os.File.openHandle(stdout_pipe[0]);
437437 } else {
438438 self.stdout = null;
439439 }
440440 if (self.stderr_behavior == StdIo.Pipe) {
441 self.stderr = io.File.openHandle(stderr_pipe[0]);
441 self.stderr = os.File.openHandle(stderr_pipe[0]);
442442 } else {
443443 self.stderr = null;
444444 }
......@@ -620,17 +620,17 @@ pub const ChildProcess = struct {
620620 };
621621
622622 if (g_hChildStd_IN_Wr) |h| {
623 self.stdin = io.File.openHandle(h);
623 self.stdin = os.File.openHandle(h);
624624 } else {
625625 self.stdin = null;
626626 }
627627 if (g_hChildStd_OUT_Rd) |h| {
628 self.stdout = io.File.openHandle(h);
628 self.stdout = os.File.openHandle(h);
629629 } else {
630630 self.stdout = null;
631631 }
632632 if (g_hChildStd_ERR_Rd) |h| {
633 self.stderr = io.File.openHandle(h);
633 self.stderr = os.File.openHandle(h);
634634 } else {
635635 self.stderr = null;
636636 }
std/os/file.zig created+311
......@@ -0,0 +1,311 @@
1const std = @import("../index.zig");
2const builtin = @import("builtin");
3const os = std.os;
4const mem = std.mem;
5const math = std.math;
6const assert = std.debug.assert;
7const posix = os.posix;
8const windows = os.windows;
9const Os = builtin.Os;
10
11const is_posix = builtin.os != builtin.Os.windows;
12const is_windows = builtin.os == builtin.Os.windows;
13
14pub const File = struct {
15 /// The OS-specific file descriptor or file handle.
16 handle: os.FileHandle,
17
18 const OpenError = os.WindowsOpenError || os.PosixOpenError;
19
20 /// `path` needs to be copied in memory to add a null terminating byte, hence the allocator.
21 /// Call close to clean up.
22 pub fn openRead(allocator: &mem.Allocator, path: []const u8) OpenError!File {
23 if (is_posix) {
24 const flags = posix.O_LARGEFILE|posix.O_RDONLY;
25 const fd = try os.posixOpen(allocator, path, flags, 0);
26 return openHandle(fd);
27 } else if (is_windows) {
28 const handle = try os.windowsOpen(allocator, path, windows.GENERIC_READ, windows.FILE_SHARE_READ,
29 windows.OPEN_EXISTING, windows.FILE_ATTRIBUTE_NORMAL);
30 return openHandle(handle);
31 } else {
32 @compileError("TODO implement openRead for this OS");
33 }
34 }
35
36 /// Calls `openWriteMode` with os.default_file_mode for the mode.
37 pub fn openWrite(allocator: &mem.Allocator, path: []const u8) OpenError!File {
38 return openWriteMode(allocator, path, os.default_file_mode);
39
40 }
41
42 /// If the path does not exist it will be created.
43 /// If a file already exists in the destination it will be truncated.
44 /// `path` needs to be copied in memory to add a null terminating byte, hence the allocator.
45 /// Call close to clean up.
46 pub fn openWriteMode(allocator: &mem.Allocator, path: []const u8, file_mode: os.FileMode) OpenError!File {
47 if (is_posix) {
48 const flags = posix.O_LARGEFILE|posix.O_WRONLY|posix.O_CREAT|posix.O_CLOEXEC|posix.O_TRUNC;
49 const fd = try os.posixOpen(allocator, path, flags, file_mode);
50 return openHandle(fd);
51 } else if (is_windows) {
52 const handle = try os.windowsOpen(allocator, path, windows.GENERIC_WRITE,
53 windows.FILE_SHARE_WRITE|windows.FILE_SHARE_READ|windows.FILE_SHARE_DELETE,
54 windows.CREATE_ALWAYS, windows.FILE_ATTRIBUTE_NORMAL);
55 return openHandle(handle);
56 } else {
57 @compileError("TODO implement openWriteMode for this OS");
58 }
59
60 }
61
62 /// If the path does not exist it will be created.
63 /// If a file already exists in the destination this returns OpenError.PathAlreadyExists
64 /// `path` needs to be copied in memory to add a null terminating byte, hence the allocator.
65 /// Call close to clean up.
66 pub fn openWriteNoClobber(allocator: &mem.Allocator, path: []const u8, file_mode: os.FileMode) OpenError!File {
67 if (is_posix) {
68 const flags = posix.O_LARGEFILE|posix.O_WRONLY|posix.O_CREAT|posix.O_CLOEXEC|posix.O_EXCL;
69 const fd = try os.posixOpen(allocator, path, flags, file_mode);
70 return openHandle(fd);
71 } else if (is_windows) {
72 const handle = try os.windowsOpen(allocator, path, windows.GENERIC_WRITE,
73 windows.FILE_SHARE_WRITE|windows.FILE_SHARE_READ|windows.FILE_SHARE_DELETE,
74 windows.CREATE_NEW, windows.FILE_ATTRIBUTE_NORMAL);
75 return openHandle(handle);
76 } else {
77 @compileError("TODO implement openWriteMode for this OS");
78 }
79
80 }
81
82 pub fn openHandle(handle: os.FileHandle) File {
83 return File {
84 .handle = handle,
85 };
86 }
87
88
89 /// Upon success, the stream is in an uninitialized state. To continue using it,
90 /// you must use the open() function.
91 pub fn close(self: &File) void {
92 os.close(self.handle);
93 self.handle = undefined;
94 }
95
96 /// Calls `os.isTty` on `self.handle`.
97 pub fn isTty(self: &File) bool {
98 return os.isTty(self.handle);
99 }
100
101 pub fn seekForward(self: &File, amount: isize) !void {
102 switch (builtin.os) {
103 Os.linux, Os.macosx, Os.ios => {
104 const result = posix.lseek(self.handle, amount, posix.SEEK_CUR);
105 const err = posix.getErrno(result);
106 if (err > 0) {
107 return switch (err) {
108 posix.EBADF => error.BadFd,
109 posix.EINVAL => error.Unseekable,
110 posix.EOVERFLOW => error.Unseekable,
111 posix.ESPIPE => error.Unseekable,
112 posix.ENXIO => error.Unseekable,
113 else => os.unexpectedErrorPosix(err),
114 };
115 }
116 },
117 Os.windows => {
118 if (windows.SetFilePointerEx(self.handle, amount, null, windows.FILE_CURRENT) == 0) {
119 const err = windows.GetLastError();
120 return switch (err) {
121 windows.ERROR.INVALID_PARAMETER => error.BadFd,
122 else => os.unexpectedErrorWindows(err),
123 };
124 }
125 },
126 else => @compileError("unsupported OS"),
127 }
128 }
129
130 pub fn seekTo(self: &File, pos: usize) !void {
131 switch (builtin.os) {
132 Os.linux, Os.macosx, Os.ios => {
133 const ipos = try math.cast(isize, pos);
134 const result = posix.lseek(self.handle, ipos, posix.SEEK_SET);
135 const err = posix.getErrno(result);
136 if (err > 0) {
137 return switch (err) {
138 posix.EBADF => error.BadFd,
139 posix.EINVAL => error.Unseekable,
140 posix.EOVERFLOW => error.Unseekable,
141 posix.ESPIPE => error.Unseekable,
142 posix.ENXIO => error.Unseekable,
143 else => os.unexpectedErrorPosix(err),
144 };
145 }
146 },
147 Os.windows => {
148 const ipos = try math.cast(isize, pos);
149 if (windows.SetFilePointerEx(self.handle, ipos, null, windows.FILE_BEGIN) == 0) {
150 const err = windows.GetLastError();
151 return switch (err) {
152 windows.ERROR.INVALID_PARAMETER => error.BadFd,
153 else => os.unexpectedErrorWindows(err),
154 };
155 }
156 },
157 else => @compileError("unsupported OS: " ++ @tagName(builtin.os)),
158 }
159 }
160
161 pub fn getPos(self: &File) !usize {
162 switch (builtin.os) {
163 Os.linux, Os.macosx, Os.ios => {
164 const result = posix.lseek(self.handle, 0, posix.SEEK_CUR);
165 const err = posix.getErrno(result);
166 if (err > 0) {
167 return switch (err) {
168 posix.EBADF => error.BadFd,
169 posix.EINVAL => error.Unseekable,
170 posix.EOVERFLOW => error.Unseekable,
171 posix.ESPIPE => error.Unseekable,
172 posix.ENXIO => error.Unseekable,
173 else => os.unexpectedErrorPosix(err),
174 };
175 }
176 return result;
177 },
178 Os.windows => {
179 var pos : windows.LARGE_INTEGER = undefined;
180 if (windows.SetFilePointerEx(self.handle, 0, &pos, windows.FILE_CURRENT) == 0) {
181 const err = windows.GetLastError();
182 return switch (err) {
183 windows.ERROR.INVALID_PARAMETER => error.BadFd,
184 else => os.unexpectedErrorWindows(err),
185 };
186 }
187
188 assert(pos >= 0);
189 if (@sizeOf(@typeOf(pos)) > @sizeOf(usize)) {
190 if (pos > @maxValue(usize)) {
191 return error.FilePosLargerThanPointerRange;
192 }
193 }
194
195 return usize(pos);
196 },
197 else => @compileError("unsupported OS"),
198 }
199 }
200
201 pub fn getEndPos(self: &File) !usize {
202 if (is_posix) {
203 var stat: posix.Stat = undefined;
204 const err = posix.getErrno(posix.fstat(self.handle, &stat));
205 if (err > 0) {
206 return switch (err) {
207 posix.EBADF => error.BadFd,
208 posix.ENOMEM => error.SystemResources,
209 else => os.unexpectedErrorPosix(err),
210 };
211 }
212
213 return usize(stat.size);
214 } else if (is_windows) {
215 var file_size: windows.LARGE_INTEGER = undefined;
216 if (windows.GetFileSizeEx(self.handle, &file_size) == 0) {
217 const err = windows.GetLastError();
218 return switch (err) {
219 else => os.unexpectedErrorWindows(err),
220 };
221 }
222 if (file_size < 0)
223 return error.Overflow;
224 return math.cast(usize, u64(file_size));
225 } else {
226 @compileError("TODO support getEndPos on this OS");
227 }
228 }
229
230 pub const ModeError = error {
231 BadFd,
232 SystemResources,
233 Unexpected,
234 };
235
236 fn mode(self: &File) ModeError!FileMode {
237 if (is_posix) {
238 var stat: posix.Stat = undefined;
239 const err = posix.getErrno(posix.fstat(self.handle, &stat));
240 if (err > 0) {
241 return switch (err) {
242 posix.EBADF => error.BadFd,
243 posix.ENOMEM => error.SystemResources,
244 else => os.unexpectedErrorPosix(err),
245 };
246 }
247
248 return stat.mode;
249 } else if (is_windows) {
250 return {};
251 } else {
252 @compileError("TODO support file mode on this OS");
253 }
254 }
255
256 pub const ReadError = error {};
257
258 pub fn read(self: &File, buffer: []u8) !usize {
259 if (is_posix) {
260 var index: usize = 0;
261 while (index < buffer.len) {
262 const amt_read = posix.read(self.handle, &buffer[index], buffer.len - index);
263 const read_err = posix.getErrno(amt_read);
264 if (read_err > 0) {
265 switch (read_err) {
266 posix.EINTR => continue,
267 posix.EINVAL => unreachable,
268 posix.EFAULT => unreachable,
269 posix.EBADF => return error.BadFd,
270 posix.EIO => return error.Io,
271 else => return os.unexpectedErrorPosix(read_err),
272 }
273 }
274 if (amt_read == 0) return index;
275 index += amt_read;
276 }
277 return index;
278 } else if (is_windows) {
279 var index: usize = 0;
280 while (index < buffer.len) {
281 const want_read_count = windows.DWORD(math.min(windows.DWORD(@maxValue(windows.DWORD)), buffer.len - index));
282 var amt_read: windows.DWORD = undefined;
283 if (windows.ReadFile(self.handle, @ptrCast(&c_void, &buffer[index]), want_read_count, &amt_read, null) == 0) {
284 const err = windows.GetLastError();
285 return switch (err) {
286 windows.ERROR.OPERATION_ABORTED => continue,
287 windows.ERROR.BROKEN_PIPE => return index,
288 else => os.unexpectedErrorWindows(err),
289 };
290 }
291 if (amt_read == 0) return index;
292 index += amt_read;
293 }
294 return index;
295 } else {
296 unreachable;
297 }
298 }
299
300 pub const WriteError = os.WindowsWriteError || os.PosixWriteError;
301
302 fn write(self: &File, bytes: []const u8) WriteError!void {
303 if (is_posix) {
304 try os.posixWrite(self.handle, bytes);
305 } else if (is_windows) {
306 try os.windowsWrite(self.handle, bytes);
307 } else {
308 @compileError("Unsupported OS");
309 }
310 }
311};
std/os/index.zig+115-38
......@@ -17,10 +17,16 @@ pub const posix = switch(builtin.os) {
1717
1818pub const ChildProcess = @import("child_process.zig").ChildProcess;
1919pub const path = @import("path.zig");
20pub const File = @import("file.zig").File;
2021
21pub const line_sep = switch (builtin.os) {
22 Os.windows => "\r\n",
23 else => "\n",
22pub const FileMode = switch (builtin.os) {
23 Os.windows => void,
24 else => u32,
25};
26
27pub const default_file_mode = switch (builtin.os) {
28 Os.windows => {},
29 else => 0o666,
2430};
2531
2632pub const page_size = 4 * 1024;
......@@ -672,27 +678,27 @@ const b64_fs_encoder = base64.Base64Encoder.init(
672678pub fn atomicSymLink(allocator: &Allocator, existing_path: []const u8, new_path: []const u8) !void {
673679 if (symLink(allocator, existing_path, new_path)) {
674680 return;
675 } else |err| {
676 if (err != error.PathAlreadyExists) {
677 return err;
678 }
681 } else |err| switch (err) {
682 error.PathAlreadyExists => {},
683 else => return err, // TODO zig should know this set does not include PathAlreadyExists
679684 }
680685
686 const dirname = os.path.dirname(new_path);
687
681688 var rand_buf: [12]u8 = undefined;
682 const tmp_path = try allocator.alloc(u8, new_path.len + base64.Base64Encoder.calcSize(rand_buf.len));
689 const tmp_path = try allocator.alloc(u8, dirname.len + 1 + base64.Base64Encoder.calcSize(rand_buf.len));
683690 defer allocator.free(tmp_path);
684 mem.copy(u8, tmp_path[0..], new_path);
691 mem.copy(u8, tmp_path[0..], dirname);
692 tmp_path[dirname.len] = os.path.sep;
685693 while (true) {
686694 try getRandomBytes(rand_buf[0..]);
687 b64_fs_encoder.encode(tmp_path[new_path.len..], rand_buf);
695 b64_fs_encoder.encode(tmp_path[dirname.len + 1 ..], rand_buf);
696
688697 if (symLink(allocator, existing_path, tmp_path)) {
689698 return rename(allocator, tmp_path, new_path);
690 } else |err| {
691 if (err == error.PathAlreadyExists) {
692 continue;
693 } else {
694 return err;
695 }
699 } else |err| switch (err) {
700 error.PathAlreadyExists => continue,
701 else => return err, // TODO zig should know this set does not include PathAlreadyExists
696702 }
697703 }
698704
......@@ -750,37 +756,108 @@ pub fn deleteFilePosix(allocator: &Allocator, file_path: []const u8) !void {
750756 }
751757}
752758
753/// Calls ::copyFileMode with 0o666 for the mode.
759/// Guaranteed to be atomic. However until https://patchwork.kernel.org/patch/9636735/ is
760/// merged and readily available,
761/// there is a possibility of power loss or application termination leaving temporary files present
762/// in the same directory as dest_path.
763/// Destination file will have the same mode as the source file.
754764pub fn copyFile(allocator: &Allocator, source_path: []const u8, dest_path: []const u8) !void {
755 return copyFileMode(allocator, source_path, dest_path, 0o666);
756}
765 var in_file = try os.File.openRead(allocator, source_path);
766 defer in_file.close();
757767
758// TODO instead of accepting a mode argument, use the mode from fstat'ing the source path once open
759/// Guaranteed to be atomic.
760pub fn copyFileMode(allocator: &Allocator, source_path: []const u8, dest_path: []const u8, mode: usize) !void {
761 var rand_buf: [12]u8 = undefined;
762 const tmp_path = try allocator.alloc(u8, dest_path.len + base64.Base64Encoder.calcSize(rand_buf.len));
763 defer allocator.free(tmp_path);
764 mem.copy(u8, tmp_path[0..], dest_path);
765 try getRandomBytes(rand_buf[0..]);
766 b64_fs_encoder.encode(tmp_path[dest_path.len..], rand_buf);
768 const mode = try in_file.mode();
767769
768 var out_file = try io.File.openWriteMode(allocator, tmp_path, mode);
769 defer out_file.close();
770 errdefer _ = deleteFile(allocator, tmp_path);
770 var atomic_file = try AtomicFile.init(allocator, dest_path, mode);
771 defer atomic_file.deinit();
771772
772 var in_file = try io.File.openRead(allocator, source_path);
773 var buf: [page_size]u8 = undefined;
774 while (true) {
775 const amt = try in_file.read(buf[0..]);
776 try atomic_file.file.write(buf[0..amt]);
777 if (amt != buf.len) {
778 return atomic_file.finish();
779 }
780 }
781}
782
783/// Guaranteed to be atomic. However until https://patchwork.kernel.org/patch/9636735/ is
784/// merged and readily available,
785/// there is a possibility of power loss or application termination leaving temporary files present
786pub fn copyFileMode(allocator: &Allocator, source_path: []const u8, dest_path: []const u8, mode: FileMode) !void {
787 var in_file = try os.File.openRead(allocator, source_path);
773788 defer in_file.close();
774789
790 var atomic_file = try AtomicFile.init(allocator, dest_path, mode);
791 defer atomic_file.deinit();
792
775793 var buf: [page_size]u8 = undefined;
776794 while (true) {
777795 const amt = try in_file.read(buf[0..]);
778 try out_file.write(buf[0..amt]);
779 if (amt != buf.len)
780 return rename(allocator, tmp_path, dest_path);
796 try atomic_file.file.write(buf[0..amt]);
797 if (amt != buf.len) {
798 return atomic_file.finish();
799 }
781800 }
782801}
783802
803pub const AtomicFile = struct {
804 allocator: &Allocator,
805 file: os.File,
806 tmp_path: []u8,
807 dest_path: []const u8,
808 finished: bool,
809
810 /// dest_path must remain valid for the lifetime of AtomicFile
811 /// call finish to atomically replace dest_path with contents
812 pub fn init(allocator: &Allocator, dest_path: []const u8, mode: FileMode) !AtomicFile {
813 const dirname = os.path.dirname(dest_path);
814
815 var rand_buf: [12]u8 = undefined;
816 const tmp_path = try allocator.alloc(u8, dirname.len + 1 + base64.Base64Encoder.calcSize(rand_buf.len));
817 errdefer allocator.free(tmp_path);
818 mem.copy(u8, tmp_path[0..], dirname);
819 tmp_path[dirname.len] = os.path.sep;
820
821 while (true) {
822 try getRandomBytes(rand_buf[0..]);
823 b64_fs_encoder.encode(tmp_path[dirname.len + 1 ..], rand_buf);
824
825 const file = os.File.openWriteNoClobber(allocator, tmp_path, mode) catch |err| switch (err) {
826 error.PathAlreadyExists => continue,
827 // TODO zig should figure out that this error set does not include PathAlreadyExists since
828 // it is handled in the above switch
829 else => return err,
830 };
831
832 return AtomicFile {
833 .allocator = allocator,
834 .file = file,
835 .tmp_path = tmp_path,
836 .dest_path = dest_path,
837 .finished = false,
838 };
839 }
840 }
841
842 /// always call deinit, even after successful finish()
843 pub fn deinit(self: &AtomicFile) void {
844 if (!self.finished) {
845 self.file.close();
846 deleteFile(self.allocator, self.tmp_path) catch {};
847 self.allocator.free(self.tmp_path);
848 self.finished = true;
849 }
850 }
851
852 pub fn finish(self: &AtomicFile) !void {
853 assert(!self.finished);
854 self.file.close();
855 try rename(self.allocator, self.tmp_path, self.dest_path);
856 self.allocator.free(self.tmp_path);
857 self.finished = true;
858 }
859};
860
784861pub fn rename(allocator: &Allocator, old_path: []const u8, new_path: []const u8) !void {
785862 const full_buf = try allocator.alloc(u8, old_path.len + new_path.len + 2);
786863 defer allocator.free(full_buf);
......@@ -1620,19 +1697,19 @@ pub fn unexpectedErrorWindows(err: windows.DWORD) (error{Unexpected}) {
16201697 return error.Unexpected;
16211698}
16221699
1623pub fn openSelfExe() !io.File {
1700pub fn openSelfExe() !os.File {
16241701 switch (builtin.os) {
16251702 Os.linux => {
16261703 const proc_file_path = "/proc/self/exe";
16271704 var fixed_buffer_mem: [proc_file_path.len + 1]u8 = undefined;
16281705 var fixed_allocator = mem.FixedBufferAllocator.init(fixed_buffer_mem[0..]);
1629 return io.File.openRead(&fixed_allocator.allocator, proc_file_path);
1706 return os.File.openRead(&fixed_allocator.allocator, proc_file_path);
16301707 },
16311708 Os.macosx, Os.ios => {
16321709 var fixed_buffer_mem: [darwin.PATH_MAX * 2]u8 = undefined;
16331710 var fixed_allocator = mem.FixedBufferAllocator.init(fixed_buffer_mem[0..]);
16341711 const self_exe_path = try selfExePath(&fixed_allocator.allocator);
1635 return io.File.openRead(&fixed_allocator.allocator, self_exe_path);
1712 return os.File.openRead(&fixed_allocator.allocator, self_exe_path);
16361713 },
16371714 else => @compileError("Unsupported OS"),
16381715 }
std/zig/parser.zig+1-4
......@@ -1038,11 +1038,8 @@ var fixed_buffer_mem: [100 * 1024]u8 = undefined;
10381038fn testParse(source: []const u8, allocator: &mem.Allocator) ![]u8 {
10391039 var padded_source: [0x100]u8 = undefined;
10401040 std.mem.copy(u8, padded_source[0..source.len], source);
1041 padded_source[source.len + 0] = '\n';
1042 padded_source[source.len + 1] = '\n';
1043 padded_source[source.len + 2] = '\n';
10441041
1045 var tokenizer = Tokenizer.init(padded_source[0..source.len + 3]);
1042 var tokenizer = Tokenizer.init(padded_source[0..source.len]);
10461043 var parser = Parser.init(&tokenizer, allocator, "(memory buffer)");
10471044 defer parser.deinit();
10481045
test/compare_output.zig+4-2
......@@ -1,4 +1,6 @@
1const os = @import("std").os;
1const builtin = @import("builtin");
2const std = @import("std");
3const os = std.os;
24const tests = @import("tests.zig");
35
46pub fn addCases(cases: &tests.CompareOutputContext) void {
......@@ -8,7 +10,7 @@ pub fn addCases(cases: &tests.CompareOutputContext) void {
810 \\ _ = c.puts(c"Hello, world!");
911 \\ return 0;
1012 \\}
11 , "Hello, world!" ++ os.line_sep);
13 , "Hello, world!" ++ std.cstr.line_sep);
1214
1315 cases.addCase(x: {
1416 var tc = cases.create("multiple files with private function",