authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2019-05-24 18:27:18-04:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2019-05-26 18:32:44-04:00
log17b0166e004e81d9c433576f4c642e743fc527b7
tree0ba5e905ee444e21ffd68c348de240e76dc7dbef
parent2def23063fbabb4128da17aca27745a7e8062ce5
signature Commit is signed but in an unrecognized format.

do Jay's suggestion with posix/os API naming & layout


31 files changed, 7102 insertions(+), 6930 deletions(-)

std/child_process.zig created+825
......@@ -0,0 +1,825 @@
1const std = @import("std.zig");
2const cstr = std.cstr;
3const unicode = std.unicode;
4const io = std.io;
5const os = std.os;
6const posix = os.posix;
7const windows = os.windows;
8const mem = std.mem;
9const debug = std.debug;
10const BufMap = std.BufMap;
11const Buffer = std.Buffer;
12const builtin = @import("builtin");
13const Os = builtin.Os;
14const LinkedList = std.LinkedList;
15const maxInt = std.math.maxInt;
16
17const is_windows = builtin.os == .windows;
18
19pub const ChildProcess = struct {
20 pub pid: if (is_windows) void else i32,
21 pub handle: if (is_windows) windows.HANDLE else void,
22 pub thread_handle: if (is_windows) windows.HANDLE else void,
23
24 pub allocator: *mem.Allocator,
25
26 pub stdin: ?os.File,
27 pub stdout: ?os.File,
28 pub stderr: ?os.File,
29
30 pub term: ?(SpawnError!Term),
31
32 pub argv: []const []const u8,
33
34 /// Leave as null to use the current env map using the supplied allocator.
35 pub env_map: ?*const BufMap,
36
37 pub stdin_behavior: StdIo,
38 pub stdout_behavior: StdIo,
39 pub stderr_behavior: StdIo,
40
41 /// Set to change the user id when spawning the child process.
42 pub uid: if (is_windows) void else ?u32,
43
44 /// Set to change the group id when spawning the child process.
45 pub gid: if (is_windows) void else ?u32,
46
47 /// Set to change the current working directory when spawning the child process.
48 pub cwd: ?[]const u8,
49
50 err_pipe: if (is_windows) void else [2]i32,
51 llnode: if (is_windows) void else LinkedList(*ChildProcess).Node,
52
53 pub const SpawnError = error{
54 ProcessFdQuotaExceeded,
55 Unexpected,
56 NotDir,
57 SystemResources,
58 FileNotFound,
59 NameTooLong,
60 SymLinkLoop,
61 FileSystem,
62 OutOfMemory,
63 AccessDenied,
64 PermissionDenied,
65 InvalidUserId,
66 ResourceLimitReached,
67 InvalidExe,
68 IsDir,
69 FileBusy,
70 };
71
72 pub const Term = union(enum) {
73 Exited: i32,
74 Signal: i32,
75 Stopped: i32,
76 Unknown: i32,
77 };
78
79 pub const StdIo = enum {
80 Inherit,
81 Ignore,
82 Pipe,
83 Close,
84 };
85
86 /// First argument in argv is the executable.
87 /// On success must call deinit.
88 pub fn init(argv: []const []const u8, allocator: *mem.Allocator) !*ChildProcess {
89 const child = try allocator.create(ChildProcess);
90 child.* = ChildProcess{
91 .allocator = allocator,
92 .argv = argv,
93 .pid = undefined,
94 .handle = undefined,
95 .thread_handle = undefined,
96 .err_pipe = undefined,
97 .llnode = undefined,
98 .term = null,
99 .env_map = null,
100 .cwd = null,
101 .uid = if (is_windows) {} else
102 null,
103 .gid = if (is_windows) {} else
104 null,
105 .stdin = null,
106 .stdout = null,
107 .stderr = null,
108 .stdin_behavior = StdIo.Inherit,
109 .stdout_behavior = StdIo.Inherit,
110 .stderr_behavior = StdIo.Inherit,
111 };
112 errdefer allocator.destroy(child);
113 return child;
114 }
115
116 pub fn setUserName(self: *ChildProcess, name: []const u8) !void {
117 const user_info = try os.getUserInfo(name);
118 self.uid = user_info.uid;
119 self.gid = user_info.gid;
120 }
121
122 /// On success must call `kill` or `wait`.
123 pub fn spawn(self: *ChildProcess) !void {
124 if (is_windows) {
125 return self.spawnWindows();
126 } else {
127 return self.spawnPosix();
128 }
129 }
130
131 pub fn spawnAndWait(self: *ChildProcess) !Term {
132 try self.spawn();
133 return self.wait();
134 }
135
136 /// Forcibly terminates child process and then cleans up all resources.
137 pub fn kill(self: *ChildProcess) !Term {
138 if (is_windows) {
139 return self.killWindows(1);
140 } else {
141 return self.killPosix();
142 }
143 }
144
145 pub fn killWindows(self: *ChildProcess, exit_code: windows.UINT) !Term {
146 if (self.term) |term| {
147 self.cleanupStreams();
148 return term;
149 }
150
151 if (!windows.TerminateProcess(self.handle, exit_code)) {
152 const err = windows.GetLastError();
153 return switch (err) {
154 else => os.unexpectedErrorWindows(err),
155 };
156 }
157 try self.waitUnwrappedWindows();
158 return self.term.?;
159 }
160
161 pub fn killPosix(self: *ChildProcess) !Term {
162 if (self.term) |term| {
163 self.cleanupStreams();
164 return term;
165 }
166 const ret = posix.kill(self.pid, posix.SIGTERM);
167 const err = posix.getErrno(ret);
168 if (err > 0) {
169 return switch (err) {
170 posix.EINVAL => unreachable,
171 posix.EPERM => error.PermissionDenied,
172 posix.ESRCH => error.ProcessNotFound,
173 else => os.unexpectedErrorPosix(err),
174 };
175 }
176 self.waitUnwrapped();
177 return self.term.?;
178 }
179
180 /// Blocks until child process terminates and then cleans up all resources.
181 pub fn wait(self: *ChildProcess) !Term {
182 if (is_windows) {
183 return self.waitWindows();
184 } else {
185 return self.waitPosix();
186 }
187 }
188
189 pub const ExecResult = struct {
190 term: os.ChildProcess.Term,
191 stdout: []u8,
192 stderr: []u8,
193 };
194
195 /// Spawns a child process, waits for it, collecting stdout and stderr, and then returns.
196 /// If it succeeds, the caller owns result.stdout and result.stderr memory.
197 pub fn exec(allocator: *mem.Allocator, argv: []const []const u8, cwd: ?[]const u8, env_map: ?*const BufMap, max_output_size: usize) !ExecResult {
198 const child = try ChildProcess.init(argv, allocator);
199 defer child.deinit();
200
201 child.stdin_behavior = ChildProcess.StdIo.Ignore;
202 child.stdout_behavior = ChildProcess.StdIo.Pipe;
203 child.stderr_behavior = ChildProcess.StdIo.Pipe;
204 child.cwd = cwd;
205 child.env_map = env_map;
206
207 try child.spawn();
208
209 var stdout = Buffer.initNull(allocator);
210 var stderr = Buffer.initNull(allocator);
211 defer Buffer.deinit(&stdout);
212 defer Buffer.deinit(&stderr);
213
214 var stdout_file_in_stream = child.stdout.?.inStream();
215 var stderr_file_in_stream = child.stderr.?.inStream();
216
217 try stdout_file_in_stream.stream.readAllBuffer(&stdout, max_output_size);
218 try stderr_file_in_stream.stream.readAllBuffer(&stderr, max_output_size);
219
220 return ExecResult{
221 .term = try child.wait(),
222 .stdout = stdout.toOwnedSlice(),
223 .stderr = stderr.toOwnedSlice(),
224 };
225 }
226
227 fn waitWindows(self: *ChildProcess) !Term {
228 if (self.term) |term| {
229 self.cleanupStreams();
230 return term;
231 }
232
233 try self.waitUnwrappedWindows();
234 return self.term.?;
235 }
236
237 fn waitPosix(self: *ChildProcess) !Term {
238 if (self.term) |term| {
239 self.cleanupStreams();
240 return term;
241 }
242
243 self.waitUnwrapped();
244 return self.term.?;
245 }
246
247 pub fn deinit(self: *ChildProcess) void {
248 self.allocator.destroy(self);
249 }
250
251 fn waitUnwrappedWindows(self: *ChildProcess) !void {
252 const result = os.windowsWaitSingle(self.handle, windows.INFINITE);
253
254 self.term = (SpawnError!Term)(x: {
255 var exit_code: windows.DWORD = undefined;
256 if (windows.GetExitCodeProcess(self.handle, &exit_code) == 0) {
257 break :x Term{ .Unknown = 0 };
258 } else {
259 break :x Term{ .Exited = @bitCast(i32, exit_code) };
260 }
261 });
262
263 os.close(self.handle);
264 os.close(self.thread_handle);
265 self.cleanupStreams();
266 return result;
267 }
268
269 fn waitUnwrapped(self: *ChildProcess) void {
270 var status: i32 = undefined;
271 while (true) {
272 const err = posix.getErrno(posix.waitpid(self.pid, &status, 0));
273 if (err > 0) {
274 switch (err) {
275 posix.EINTR => continue,
276 else => unreachable,
277 }
278 }
279 self.cleanupStreams();
280 self.handleWaitResult(status);
281 return;
282 }
283 }
284
285 fn handleWaitResult(self: *ChildProcess, status: i32) void {
286 self.term = self.cleanupAfterWait(status);
287 }
288
289 fn cleanupStreams(self: *ChildProcess) void {
290 if (self.stdin) |*stdin| {
291 stdin.close();
292 self.stdin = null;
293 }
294 if (self.stdout) |*stdout| {
295 stdout.close();
296 self.stdout = null;
297 }
298 if (self.stderr) |*stderr| {
299 stderr.close();
300 self.stderr = null;
301 }
302 }
303
304 fn cleanupAfterWait(self: *ChildProcess, status: i32) !Term {
305 defer {
306 os.close(self.err_pipe[0]);
307 os.close(self.err_pipe[1]);
308 }
309
310 // Write maxInt(ErrInt) to the write end of the err_pipe. This is after
311 // waitpid, so this write is guaranteed to be after the child
312 // pid potentially wrote an error. This way we can do a blocking
313 // read on the error pipe and either get maxInt(ErrInt) (no error) or
314 // an error code.
315 try writeIntFd(self.err_pipe[1], maxInt(ErrInt));
316 const err_int = try readIntFd(self.err_pipe[0]);
317 // Here we potentially return the fork child's error
318 // from the parent pid.
319 if (err_int != maxInt(ErrInt)) {
320 return @errSetCast(SpawnError, @intToError(err_int));
321 }
322
323 return statusToTerm(status);
324 }
325
326 fn statusToTerm(status: i32) Term {
327 return if (posix.WIFEXITED(status))
328 Term{ .Exited = posix.WEXITSTATUS(status) }
329 else if (posix.WIFSIGNALED(status))
330 Term{ .Signal = posix.WTERMSIG(status) }
331 else if (posix.WIFSTOPPED(status))
332 Term{ .Stopped = posix.WSTOPSIG(status) }
333 else
334 Term{ .Unknown = status };
335 }
336
337 fn spawnPosix(self: *ChildProcess) !void {
338 const stdin_pipe = if (self.stdin_behavior == StdIo.Pipe) try makePipe() else undefined;
339 errdefer if (self.stdin_behavior == StdIo.Pipe) {
340 destroyPipe(stdin_pipe);
341 };
342
343 const stdout_pipe = if (self.stdout_behavior == StdIo.Pipe) try makePipe() else undefined;
344 errdefer if (self.stdout_behavior == StdIo.Pipe) {
345 destroyPipe(stdout_pipe);
346 };
347
348 const stderr_pipe = if (self.stderr_behavior == StdIo.Pipe) try makePipe() else undefined;
349 errdefer if (self.stderr_behavior == StdIo.Pipe) {
350 destroyPipe(stderr_pipe);
351 };
352
353 const any_ignore = (self.stdin_behavior == StdIo.Ignore or self.stdout_behavior == StdIo.Ignore or self.stderr_behavior == StdIo.Ignore);
354 const dev_null_fd = if (any_ignore) try os.posixOpenC(c"/dev/null", posix.O_RDWR, 0) else undefined;
355 defer {
356 if (any_ignore) os.close(dev_null_fd);
357 }
358
359 var env_map_owned: BufMap = undefined;
360 var we_own_env_map: bool = undefined;
361 const env_map = if (self.env_map) |env_map| x: {
362 we_own_env_map = false;
363 break :x env_map;
364 } else x: {
365 we_own_env_map = true;
366 env_map_owned = try os.getEnvMap(self.allocator);
367 break :x &env_map_owned;
368 };
369 defer {
370 if (we_own_env_map) env_map_owned.deinit();
371 }
372
373 // This pipe is used to communicate errors between the time of fork
374 // and execve from the child process to the parent process.
375 const err_pipe = try makePipe();
376 errdefer destroyPipe(err_pipe);
377
378 const pid_result = try posix.fork();
379 if (pid_result == 0) {
380 // we are the child
381 setUpChildIo(self.stdin_behavior, stdin_pipe[0], posix.STDIN_FILENO, dev_null_fd) catch |err| forkChildErrReport(err_pipe[1], err);
382 setUpChildIo(self.stdout_behavior, stdout_pipe[1], posix.STDOUT_FILENO, dev_null_fd) catch |err| forkChildErrReport(err_pipe[1], err);
383 setUpChildIo(self.stderr_behavior, stderr_pipe[1], posix.STDERR_FILENO, dev_null_fd) catch |err| forkChildErrReport(err_pipe[1], err);
384
385 if (self.stdin_behavior == StdIo.Pipe) {
386 os.close(stdin_pipe[0]);
387 os.close(stdin_pipe[1]);
388 }
389 if (self.stdout_behavior == StdIo.Pipe) {
390 os.close(stdout_pipe[0]);
391 os.close(stdout_pipe[1]);
392 }
393 if (self.stderr_behavior == StdIo.Pipe) {
394 os.close(stderr_pipe[0]);
395 os.close(stderr_pipe[1]);
396 }
397
398 if (self.cwd) |cwd| {
399 os.changeCurDir(cwd) catch |err| forkChildErrReport(err_pipe[1], err);
400 }
401
402 if (self.gid) |gid| {
403 os.posix_setregid(gid, gid) catch |err| forkChildErrReport(err_pipe[1], err);
404 }
405
406 if (self.uid) |uid| {
407 os.posix_setreuid(uid, uid) catch |err| forkChildErrReport(err_pipe[1], err);
408 }
409
410 os.posix.execve(self.allocator, self.argv, env_map) catch |err| forkChildErrReport(err_pipe[1], err);
411 }
412
413 // we are the parent
414 const pid = @intCast(i32, pid_result);
415 if (self.stdin_behavior == StdIo.Pipe) {
416 self.stdin = os.File.openHandle(stdin_pipe[1]);
417 } else {
418 self.stdin = null;
419 }
420 if (self.stdout_behavior == StdIo.Pipe) {
421 self.stdout = os.File.openHandle(stdout_pipe[0]);
422 } else {
423 self.stdout = null;
424 }
425 if (self.stderr_behavior == StdIo.Pipe) {
426 self.stderr = os.File.openHandle(stderr_pipe[0]);
427 } else {
428 self.stderr = null;
429 }
430
431 self.pid = pid;
432 self.err_pipe = err_pipe;
433 self.llnode = LinkedList(*ChildProcess).Node.init(self);
434 self.term = null;
435
436 if (self.stdin_behavior == StdIo.Pipe) {
437 os.close(stdin_pipe[0]);
438 }
439 if (self.stdout_behavior == StdIo.Pipe) {
440 os.close(stdout_pipe[1]);
441 }
442 if (self.stderr_behavior == StdIo.Pipe) {
443 os.close(stderr_pipe[1]);
444 }
445 }
446
447 fn spawnWindows(self: *ChildProcess) !void {
448 const saAttr = windows.SECURITY_ATTRIBUTES{
449 .nLength = @sizeOf(windows.SECURITY_ATTRIBUTES),
450 .bInheritHandle = windows.TRUE,
451 .lpSecurityDescriptor = null,
452 };
453
454 const any_ignore = (self.stdin_behavior == StdIo.Ignore or self.stdout_behavior == StdIo.Ignore or self.stderr_behavior == StdIo.Ignore);
455
456 const nul_handle = if (any_ignore) blk: {
457 break :blk try os.windowsOpen("NUL", windows.GENERIC_READ, windows.FILE_SHARE_READ, windows.OPEN_EXISTING, windows.FILE_ATTRIBUTE_NORMAL);
458 } else blk: {
459 break :blk undefined;
460 };
461 defer {
462 if (any_ignore) os.close(nul_handle);
463 }
464 if (any_ignore) {
465 try windowsSetHandleInfo(nul_handle, windows.HANDLE_FLAG_INHERIT, 0);
466 }
467
468 var g_hChildStd_IN_Rd: ?windows.HANDLE = null;
469 var g_hChildStd_IN_Wr: ?windows.HANDLE = null;
470 switch (self.stdin_behavior) {
471 StdIo.Pipe => {
472 try windowsMakePipeIn(&g_hChildStd_IN_Rd, &g_hChildStd_IN_Wr, &saAttr);
473 },
474 StdIo.Ignore => {
475 g_hChildStd_IN_Rd = nul_handle;
476 },
477 StdIo.Inherit => {
478 g_hChildStd_IN_Rd = windows.GetStdHandle(windows.STD_INPUT_HANDLE);
479 },
480 StdIo.Close => {
481 g_hChildStd_IN_Rd = null;
482 },
483 }
484 errdefer if (self.stdin_behavior == StdIo.Pipe) {
485 windowsDestroyPipe(g_hChildStd_IN_Rd, g_hChildStd_IN_Wr);
486 };
487
488 var g_hChildStd_OUT_Rd: ?windows.HANDLE = null;
489 var g_hChildStd_OUT_Wr: ?windows.HANDLE = null;
490 switch (self.stdout_behavior) {
491 StdIo.Pipe => {
492 try windowsMakePipeOut(&g_hChildStd_OUT_Rd, &g_hChildStd_OUT_Wr, &saAttr);
493 },
494 StdIo.Ignore => {
495 g_hChildStd_OUT_Wr = nul_handle;
496 },
497 StdIo.Inherit => {
498 g_hChildStd_OUT_Wr = windows.GetStdHandle(windows.STD_OUTPUT_HANDLE);
499 },
500 StdIo.Close => {
501 g_hChildStd_OUT_Wr = null;
502 },
503 }
504 errdefer if (self.stdin_behavior == StdIo.Pipe) {
505 windowsDestroyPipe(g_hChildStd_OUT_Rd, g_hChildStd_OUT_Wr);
506 };
507
508 var g_hChildStd_ERR_Rd: ?windows.HANDLE = null;
509 var g_hChildStd_ERR_Wr: ?windows.HANDLE = null;
510 switch (self.stderr_behavior) {
511 StdIo.Pipe => {
512 try windowsMakePipeOut(&g_hChildStd_ERR_Rd, &g_hChildStd_ERR_Wr, &saAttr);
513 },
514 StdIo.Ignore => {
515 g_hChildStd_ERR_Wr = nul_handle;
516 },
517 StdIo.Inherit => {
518 g_hChildStd_ERR_Wr = windows.GetStdHandle(windows.STD_ERROR_HANDLE);
519 },
520 StdIo.Close => {
521 g_hChildStd_ERR_Wr = null;
522 },
523 }
524 errdefer if (self.stdin_behavior == StdIo.Pipe) {
525 windowsDestroyPipe(g_hChildStd_ERR_Rd, g_hChildStd_ERR_Wr);
526 };
527
528 const cmd_line = try windowsCreateCommandLine(self.allocator, self.argv);
529 defer self.allocator.free(cmd_line);
530
531 var siStartInfo = windows.STARTUPINFOW{
532 .cb = @sizeOf(windows.STARTUPINFOW),
533 .hStdError = g_hChildStd_ERR_Wr,
534 .hStdOutput = g_hChildStd_OUT_Wr,
535 .hStdInput = g_hChildStd_IN_Rd,
536 .dwFlags = windows.STARTF_USESTDHANDLES,
537
538 .lpReserved = null,
539 .lpDesktop = null,
540 .lpTitle = null,
541 .dwX = 0,
542 .dwY = 0,
543 .dwXSize = 0,
544 .dwYSize = 0,
545 .dwXCountChars = 0,
546 .dwYCountChars = 0,
547 .dwFillAttribute = 0,
548 .wShowWindow = 0,
549 .cbReserved2 = 0,
550 .lpReserved2 = null,
551 };
552 var piProcInfo: windows.PROCESS_INFORMATION = undefined;
553
554 const cwd_slice = if (self.cwd) |cwd| try cstr.addNullByte(self.allocator, cwd) else null;
555 defer if (cwd_slice) |cwd| self.allocator.free(cwd);
556 const cwd_w = if (cwd_slice) |cwd| try unicode.utf8ToUtf16LeWithNull(self.allocator, cwd) else null;
557 defer if (cwd_w) |cwd| self.allocator.free(cwd);
558 const cwd_w_ptr = if (cwd_w) |cwd| cwd.ptr else null;
559
560 const maybe_envp_buf = if (self.env_map) |env_map| try createWindowsEnvBlock(self.allocator, env_map) else null;
561 defer if (maybe_envp_buf) |envp_buf| self.allocator.free(envp_buf);
562 const envp_ptr = if (maybe_envp_buf) |envp_buf| envp_buf.ptr else null;
563
564 // the cwd set in ChildProcess is in effect when choosing the executable path
565 // to match posix semantics
566 const app_name = x: {
567 if (self.cwd) |cwd| {
568 const resolved = try os.path.resolve(self.allocator, [][]const u8{ cwd, self.argv[0] });
569 defer self.allocator.free(resolved);
570 break :x try cstr.addNullByte(self.allocator, resolved);
571 } else {
572 break :x try cstr.addNullByte(self.allocator, self.argv[0]);
573 }
574 };
575 defer self.allocator.free(app_name);
576
577 const app_name_w = try unicode.utf8ToUtf16LeWithNull(self.allocator, app_name);
578 defer self.allocator.free(app_name_w);
579
580 const cmd_line_w = try unicode.utf8ToUtf16LeWithNull(self.allocator, cmd_line);
581 defer self.allocator.free(cmd_line_w);
582
583 windowsCreateProcess(app_name_w.ptr, cmd_line_w.ptr, envp_ptr, cwd_w_ptr, &siStartInfo, &piProcInfo) catch |no_path_err| {
584 if (no_path_err != error.FileNotFound) return no_path_err;
585
586 const PATH = try os.getEnvVarOwned(self.allocator, "PATH");
587 defer self.allocator.free(PATH);
588
589 var it = mem.tokenize(PATH, ";");
590 while (it.next()) |search_path| {
591 const joined_path = try os.path.join(self.allocator, [][]const u8{ search_path, app_name });
592 defer self.allocator.free(joined_path);
593
594 const joined_path_w = try unicode.utf8ToUtf16LeWithNull(self.allocator, joined_path);
595 defer self.allocator.free(joined_path_w);
596
597 if (windowsCreateProcess(joined_path_w.ptr, cmd_line_w.ptr, envp_ptr, cwd_w_ptr, &siStartInfo, &piProcInfo)) |_| {
598 break;
599 } else |err| if (err == error.FileNotFound) {
600 continue;
601 } else {
602 return err;
603 }
604 } else {
605 // Every other error would have been returned earlier.
606 return error.FileNotFound;
607 }
608 };
609
610 if (g_hChildStd_IN_Wr) |h| {
611 self.stdin = os.File.openHandle(h);
612 } else {
613 self.stdin = null;
614 }
615 if (g_hChildStd_OUT_Rd) |h| {
616 self.stdout = os.File.openHandle(h);
617 } else {
618 self.stdout = null;
619 }
620 if (g_hChildStd_ERR_Rd) |h| {
621 self.stderr = os.File.openHandle(h);
622 } else {
623 self.stderr = null;
624 }
625
626 self.handle = piProcInfo.hProcess;
627 self.thread_handle = piProcInfo.hThread;
628 self.term = null;
629
630 if (self.stdin_behavior == StdIo.Pipe) {
631 os.close(g_hChildStd_IN_Rd.?);
632 }
633 if (self.stderr_behavior == StdIo.Pipe) {
634 os.close(g_hChildStd_ERR_Wr.?);
635 }
636 if (self.stdout_behavior == StdIo.Pipe) {
637 os.close(g_hChildStd_OUT_Wr.?);
638 }
639 }
640
641 fn setUpChildIo(stdio: StdIo, pipe_fd: i32, std_fileno: i32, dev_null_fd: i32) !void {
642 switch (stdio) {
643 StdIo.Pipe => try os.posixDup2(pipe_fd, std_fileno),
644 StdIo.Close => os.close(std_fileno),
645 StdIo.Inherit => {},
646 StdIo.Ignore => try os.posixDup2(dev_null_fd, std_fileno),
647 }
648 }
649};
650
651fn windowsCreateProcess(app_name: [*]u16, cmd_line: [*]u16, envp_ptr: ?[*]u16, cwd_ptr: ?[*]u16, lpStartupInfo: *windows.STARTUPINFOW, lpProcessInformation: *windows.PROCESS_INFORMATION) !void {
652 // TODO the docs for environment pointer say:
653 // > A pointer to the environment block for the new process. If this parameter
654 // > is NULL, the new process uses the environment of the calling process.
655 // > ...
656 // > An environment block can contain either Unicode or ANSI characters. If
657 // > the environment block pointed to by lpEnvironment contains Unicode
658 // > characters, be sure that dwCreationFlags includes CREATE_UNICODE_ENVIRONMENT.
659 // > If this parameter is NULL and the environment block of the parent process
660 // > contains Unicode characters, you must also ensure that dwCreationFlags
661 // > includes CREATE_UNICODE_ENVIRONMENT.
662 // This seems to imply that we have to somehow know whether our process parent passed
663 // CREATE_UNICODE_ENVIRONMENT if we want to pass NULL for the environment parameter.
664 // Since we do not know this information that would imply that we must not pass NULL
665 // for the parameter.
666 // However this would imply that programs compiled with -DUNICODE could not pass
667 // environment variables to programs that were not, which seems unlikely.
668 // More investigation is needed.
669 if (windows.CreateProcessW(
670 app_name,
671 cmd_line,
672 null,
673 null,
674 windows.TRUE,
675 windows.CREATE_UNICODE_ENVIRONMENT,
676 @ptrCast(?*c_void, envp_ptr),
677 cwd_ptr,
678 lpStartupInfo,
679 lpProcessInformation,
680 ) == 0) {
681 switch (windows.GetLastError()) {
682 windows.ERROR.FILE_NOT_FOUND => return error.FileNotFound,
683 windows.ERROR.PATH_NOT_FOUND => return error.FileNotFound,
684 windows.ERROR.INVALID_PARAMETER => unreachable,
685 windows.ERROR.INVALID_NAME => return error.InvalidName,
686 else => |err| return windows.unexpectedError(err),
687 }
688 }
689}
690
691/// Caller must dealloc.
692/// Guarantees a null byte at result[result.len].
693fn windowsCreateCommandLine(allocator: *mem.Allocator, argv: []const []const u8) ![]u8 {
694 var buf = try Buffer.initSize(allocator, 0);
695 defer buf.deinit();
696
697 var buf_stream = &io.BufferOutStream.init(&buf).stream;
698
699 for (argv) |arg, arg_i| {
700 if (arg_i != 0) try buf.appendByte(' ');
701 if (mem.indexOfAny(u8, arg, " \t\n\"") == null) {
702 try buf.append(arg);
703 continue;
704 }
705 try buf.appendByte('"');
706 var backslash_count: usize = 0;
707 for (arg) |byte| {
708 switch (byte) {
709 '\\' => backslash_count += 1,
710 '"' => {
711 try buf_stream.writeByteNTimes('\\', backslash_count * 2 + 1);
712 try buf.appendByte('"');
713 backslash_count = 0;
714 },
715 else => {
716 try buf_stream.writeByteNTimes('\\', backslash_count);
717 try buf.appendByte(byte);
718 backslash_count = 0;
719 },
720 }
721 }
722 try buf_stream.writeByteNTimes('\\', backslash_count * 2);
723 try buf.appendByte('"');
724 }
725
726 return buf.toOwnedSlice();
727}
728
729fn windowsDestroyPipe(rd: ?windows.HANDLE, wr: ?windows.HANDLE) void {
730 if (rd) |h| os.close(h);
731 if (wr) |h| os.close(h);
732}
733
734fn windowsMakePipeIn(rd: *?windows.HANDLE, wr: *?windows.HANDLE, sattr: *const SECURITY_ATTRIBUTES) !void {
735 var rd_h: windows.HANDLE = undefined;
736 var wr_h: windows.HANDLE = undefined;
737 try windows.CreatePipe(&rd_h, &wr_h, sattr);
738 errdefer windowsDestroyPipe(rd_h, wr_h);
739 try windowsSetHandleInfo(wr_h, windows.HANDLE_FLAG_INHERIT, 0);
740 rd.* = rd_h;
741 wr.* = wr_h;
742}
743
744fn windowsMakePipeOut(rd: *?windows.HANDLE, wr: *?windows.HANDLE, sattr: *const SECURITY_ATTRIBUTES) !void {
745 var rd_h: windows.HANDLE = undefined;
746 var wr_h: windows.HANDLE = undefined;
747 try windows.CreatePipe(&rd_h, &wr_h, sattr);
748 errdefer windowsDestroyPipe(rd_h, wr_h);
749 try windowsSetHandleInfo(rd_h, windows.HANDLE_FLAG_INHERIT, 0);
750 rd.* = rd_h;
751 wr.* = wr_h;
752}
753
754fn makePipe() ![2]i32 {
755 var fds: [2]i32 = undefined;
756 const err = posix.getErrno(posix.pipe(&fds));
757 if (err > 0) {
758 return switch (err) {
759 posix.EMFILE, posix.ENFILE => error.SystemResources,
760 else => os.unexpectedErrorPosix(err),
761 };
762 }
763 return fds;
764}
765
766fn destroyPipe(pipe: [2]i32) void {
767 os.close(pipe[0]);
768 os.close(pipe[1]);
769}
770
771// Child of fork calls this to report an error to the fork parent.
772// Then the child exits.
773fn forkChildErrReport(fd: i32, err: ChildProcess.SpawnError) noreturn {
774 writeIntFd(fd, ErrInt(@errorToInt(err))) catch {};
775 posix.exit(1);
776}
777
778const ErrInt = @IntType(false, @sizeOf(anyerror) * 8);
779
780fn writeIntFd(fd: i32, value: ErrInt) !void {
781 const stream = &os.File.openHandle(fd).outStream().stream;
782 stream.writeIntNative(ErrInt, value) catch return error.SystemResources;
783}
784
785fn readIntFd(fd: i32) !ErrInt {
786 const stream = &os.File.openHandle(fd).inStream().stream;
787 return stream.readIntNative(ErrInt) catch return error.SystemResources;
788}
789
790/// Caller must free result.
791pub fn createWindowsEnvBlock(allocator: *mem.Allocator, env_map: *const BufMap) ![]u16 {
792 // count bytes needed
793 const max_chars_needed = x: {
794 var max_chars_needed: usize = 4; // 4 for the final 4 null bytes
795 var it = env_map.iterator();
796 while (it.next()) |pair| {
797 // +1 for '='
798 // +1 for null byte
799 max_chars_needed += pair.key.len + pair.value.len + 2;
800 }
801 break :x max_chars_needed;
802 };
803 const result = try allocator.alloc(u16, max_chars_needed);
804 errdefer allocator.free(result);
805
806 var it = env_map.iterator();
807 var i: usize = 0;
808 while (it.next()) |pair| {
809 i += try unicode.utf8ToUtf16Le(result[i..], pair.key);
810 result[i] = '=';
811 i += 1;
812 i += try unicode.utf8ToUtf16Le(result[i..], pair.value);
813 result[i] = 0;
814 i += 1;
815 }
816 result[i] = 0;
817 i += 1;
818 result[i] = 0;
819 i += 1;
820 result[i] = 0;
821 i += 1;
822 result[i] = 0;
823 i += 1;
824 return allocator.shrink(result, i);
825}
std/coff.zig+1-1
......@@ -91,7 +91,7 @@ pub const Coff = struct {
9191 } else
9292 return error.InvalidPEMagic;
9393
94 try self.in_file.seekForward(skip_size);
94 try self.in_file.seekBy(skip_size);
9595
9696 const number_of_rva_and_sizes = try in.readIntLittle(u32);
9797 if (number_of_rva_and_sizes != IMAGE_NUMBEROF_DIRECTORY_ENTRIES)
std/crypto.zig+3
......@@ -32,6 +32,9 @@ pub const chaCha20With64BitNonce = import_chaCha20.chaCha20With64BitNonce;
3232pub const Poly1305 = @import("crypto/poly1305.zig").Poly1305;
3333pub const X25519 = @import("crypto/x25519.zig").X25519;
3434
35const std = @import("std.zig");
36pub const randomBytes = std.posix.getrandom;
37
3538test "crypto" {
3639 _ = @import("crypto/blake2.zig");
3740 _ = @import("crypto/chacha20.zig");
std/debug.zig+4-4
......@@ -893,7 +893,7 @@ fn openSelfDebugInfoWindows(allocator: *mem.Allocator) !DebugInfo {
893893 if (this_record_len % 4 != 0) {
894894 const round_to_next_4 = (this_record_len | 0x3) + 1;
895895 const march_forward_bytes = round_to_next_4 - this_record_len;
896 try dbi.seekForward(march_forward_bytes);
896 try dbi.seekBy(march_forward_bytes);
897897 this_record_len += march_forward_bytes;
898898 }
899899
......@@ -1116,7 +1116,7 @@ fn printLineFromFileAnyOs(out_stream: var, line_info: LineInfo) !void {
11161116 defer f.close();
11171117 // TODO fstat and make sure that the file has the correct size
11181118
1119 var buf: [os.page_size]u8 = undefined;
1119 var buf: [mem.page_size]u8 = undefined;
11201120 var line: usize = 1;
11211121 var column: usize = 1;
11221122 var abs_index: usize = 0;
......@@ -1938,7 +1938,7 @@ fn getLineNumberInfoDwarf(di: *DwarfInfo, compile_unit: CompileUnit, target_addr
19381938 },
19391939 else => {
19401940 const fwd_amt = math.cast(isize, op_size - 1) catch return error.InvalidDebugInfo;
1941 try di.dwarf_seekable_stream.seekForward(fwd_amt);
1941 try di.dwarf_seekable_stream.seekBy(fwd_amt);
19421942 },
19431943 }
19441944 } else if (opcode >= opcode_base) {
......@@ -1990,7 +1990,7 @@ fn getLineNumberInfoDwarf(di: *DwarfInfo, compile_unit: CompileUnit, target_addr
19901990 else => {
19911991 if (opcode - 1 >= standard_opcode_lengths.len) return error.InvalidDebugInfo;
19921992 const len_bytes = standard_opcode_lengths[opcode - 1];
1993 try di.dwarf_seekable_stream.seekForward(len_bytes);
1993 try di.dwarf_seekable_stream.seekBy(len_bytes);
19941994 },
19951995 }
19961996 }
std/dynamic_library.zig+1-1
......@@ -125,7 +125,7 @@ pub const LinuxDynLib = struct {
125125 );
126126 errdefer _ = linux.munmap(addr, size);
127127
128 const bytes = @intToPtr([*]align(std.os.page_size) u8, addr)[0..size];
128 const bytes = @intToPtr([*]align(mem.page_size) u8, addr)[0..size];
129129
130130 return DynLib{
131131 .elf_lib = try ElfLib.init(bytes),
std/elf.zig+2-2
......@@ -410,7 +410,7 @@ pub const Elf = struct {
410410 if (version_byte != 1) return error.InvalidFormat;
411411
412412 // skip over padding
413 try seekable_stream.seekForward(9);
413 try seekable_stream.seekBy(9);
414414
415415 elf.file_type = switch (try in.readInt(u16, elf.endian)) {
416416 1 => FileType.Relocatable,
......@@ -447,7 +447,7 @@ pub const Elf = struct {
447447 }
448448
449449 // skip over flags
450 try seekable_stream.seekForward(4);
450 try seekable_stream.seekBy(4);
451451
452452 const header_size = try in.readInt(u16, elf.endian);
453453 if ((elf.is_64 and header_size != 64) or (!elf.is_64 and header_size != 52)) {
std/event/fs.zig+1-1
......@@ -688,7 +688,7 @@ pub async fn readFile(loop: *Loop, file_path: []const u8, max_size: usize) ![]u8
688688 defer list.deinit();
689689
690690 while (true) {
691 try list.ensureCapacity(list.len + os.page_size);
691 try list.ensureCapacity(list.len + mem.page_size);
692692 const buf = list.items[list.len..];
693693 const buf_array = [][]u8{buf};
694694 const amt = try await (async preadv(loop, fd, buf_array, list.len) catch unreachable);
std/fs.zig created+840
......@@ -0,0 +1,840 @@
1const std = @import("std.zig");
2const os = std.os;
3const mem = std.mem;
4
5pub const path = @import("fs/path.zig");
6pub const File = @import("fs/file.zig").File;
7
8pub const symLink = os.symlink;
9pub const symLinkC = os.symlinkC;
10pub const deleteFile = os.unlink;
11pub const deleteFileC = os.unlinkC;
12pub const rename = os.rename;
13pub const renameC = os.renameC;
14pub const changeCurDir = os.chdir;
15pub const changeCurDirC = os.chdirC;
16pub const realpath = os.realpath;
17pub const realpathC = os.realpathC;
18pub const realpathW = os.realpathW;
19
20pub const getAppDataDir = @import("fs/get_app_data_dir.zig").getAppDataDir;
21pub const GetAppDataDirError = @import("fs/get_app_data_dir.zig").GetAppDataDirError;
22
23/// This represents the maximum size of a UTF-8 encoded file path.
24/// All file system operations which return a path are guaranteed to
25/// fit into a UTF-8 encoded array of this length.
26/// path being too long if it is this 0long
27pub const MAX_PATH_BYTES = switch (builtin.os) {
28 .linux, .macosx, .ios, .freebsd, .netbsd => posix.PATH_MAX,
29 // Each UTF-16LE character may be expanded to 3 UTF-8 bytes.
30 // If it would require 4 UTF-8 bytes, then there would be a surrogate
31 // pair in the UTF-16LE, and we (over)account 3 bytes for it that way.
32 // +1 for the null byte at the end, which can be encoded in 1 byte.
33 .windows => posix.PATH_MAX_WIDE * 3 + 1,
34 else => @compileError("Unsupported OS"),
35};
36
37/// The result is a slice of `out_buffer`, from index `0`.
38pub fn getCwd(out_buffer: *[MAX_PATH_BYTES]u8) ![]u8 {
39 return posix.getcwd(out_buffer);
40}
41
42/// Caller must free the returned memory.
43pub fn getCwdAlloc(allocator: *Allocator) ![]u8 {
44 var buf: [MAX_PATH_BYTES]u8 = undefined;
45 return mem.dupe(allocator, u8, try posix.getcwd(&buf));
46}
47
48test "getCwdAlloc" {
49 // at least call it so it gets compiled
50 var buf: [1000]u8 = undefined;
51 const allocator = &std.heap.FixedBufferAllocator.init(&buf).allocator;
52 _ = getCwdAlloc(allocator) catch {};
53}
54
55// here we replace the standard +/ with -_ so that it can be used in a file name
56const b64_fs_encoder = base64.Base64Encoder.init("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_", base64.standard_pad_char);
57
58/// TODO remove the allocator requirement from this API
59pub fn atomicSymLink(allocator: *Allocator, existing_path: []const u8, new_path: []const u8) !void {
60 if (symLink(existing_path, new_path)) {
61 return;
62 } else |err| switch (err) {
63 error.PathAlreadyExists => {},
64 else => return err, // TODO zig should know this set does not include PathAlreadyExists
65 }
66
67 const dirname = os.path.dirname(new_path) orelse ".";
68
69 var rand_buf: [12]u8 = undefined;
70 const tmp_path = try allocator.alloc(u8, dirname.len + 1 + base64.Base64Encoder.calcSize(rand_buf.len));
71 defer allocator.free(tmp_path);
72 mem.copy(u8, tmp_path[0..], dirname);
73 tmp_path[dirname.len] = os.path.sep;
74 while (true) {
75 try getRandomBytes(rand_buf[0..]);
76 b64_fs_encoder.encode(tmp_path[dirname.len + 1 ..], rand_buf);
77
78 if (symLink(existing_path, tmp_path)) {
79 return rename(tmp_path, new_path);
80 } else |err| switch (err) {
81 error.PathAlreadyExists => continue,
82 else => return err, // TODO zig should know this set does not include PathAlreadyExists
83 }
84 }
85}
86
87/// Guaranteed to be atomic. However until https://patchwork.kernel.org/patch/9636735/ is
88/// merged and readily available,
89/// there is a possibility of power loss or application termination leaving temporary files present
90/// in the same directory as dest_path.
91/// Destination file will have the same mode as the source file.
92pub fn copyFile(source_path: []const u8, dest_path: []const u8) !void {
93 var in_file = try os.File.openRead(source_path);
94 defer in_file.close();
95
96 const mode = try in_file.mode();
97 const in_stream = &in_file.inStream().stream;
98
99 var atomic_file = try AtomicFile.init(dest_path, mode);
100 defer atomic_file.deinit();
101
102 var buf: [mem.page_size]u8 = undefined;
103 while (true) {
104 const amt = try in_stream.readFull(buf[0..]);
105 try atomic_file.file.write(buf[0..amt]);
106 if (amt != buf.len) {
107 return atomic_file.finish();
108 }
109 }
110}
111
112/// Guaranteed to be atomic. However until https://patchwork.kernel.org/patch/9636735/ is
113/// merged and readily available,
114/// there is a possibility of power loss or application termination leaving temporary files present
115pub fn copyFileMode(source_path: []const u8, dest_path: []const u8, mode: File.Mode) !void {
116 var in_file = try os.File.openRead(source_path);
117 defer in_file.close();
118
119 var atomic_file = try AtomicFile.init(dest_path, mode);
120 defer atomic_file.deinit();
121
122 var buf: [mem.page_size]u8 = undefined;
123 while (true) {
124 const amt = try in_file.read(buf[0..]);
125 try atomic_file.file.write(buf[0..amt]);
126 if (amt != buf.len) {
127 return atomic_file.finish();
128 }
129 }
130}
131
132pub const AtomicFile = struct {
133 file: os.File,
134 tmp_path_buf: [MAX_PATH_BYTES]u8,
135 dest_path: []const u8,
136 finished: bool,
137
138 const InitError = os.File.OpenError;
139
140 /// dest_path must remain valid for the lifetime of AtomicFile
141 /// call finish to atomically replace dest_path with contents
142 /// TODO once we have null terminated pointers, use the
143 /// openWriteNoClobberN function
144 pub fn init(dest_path: []const u8, mode: File.Mode) InitError!AtomicFile {
145 const dirname = os.path.dirname(dest_path);
146 var rand_buf: [12]u8 = undefined;
147 const dirname_component_len = if (dirname) |d| d.len + 1 else 0;
148 const encoded_rand_len = comptime base64.Base64Encoder.calcSize(rand_buf.len);
149 const tmp_path_len = dirname_component_len + encoded_rand_len;
150 var tmp_path_buf: [MAX_PATH_BYTES]u8 = undefined;
151 if (tmp_path_len >= tmp_path_buf.len) return error.NameTooLong;
152
153 if (dirname) |dir| {
154 mem.copy(u8, tmp_path_buf[0..], dir);
155 tmp_path_buf[dir.len] = os.path.sep;
156 }
157
158 tmp_path_buf[tmp_path_len] = 0;
159
160 while (true) {
161 try getRandomBytes(rand_buf[0..]);
162 b64_fs_encoder.encode(tmp_path_buf[dirname_component_len..tmp_path_len], rand_buf);
163
164 const file = os.File.openWriteNoClobberC(&tmp_path_buf, mode) catch |err| switch (err) {
165 error.PathAlreadyExists => continue,
166 // TODO zig should figure out that this error set does not include PathAlreadyExists since
167 // it is handled in the above switch
168 else => return err,
169 };
170
171 return AtomicFile{
172 .file = file,
173 .tmp_path_buf = tmp_path_buf,
174 .dest_path = dest_path,
175 .finished = false,
176 };
177 }
178 }
179
180 /// always call deinit, even after successful finish()
181 pub fn deinit(self: *AtomicFile) void {
182 if (!self.finished) {
183 self.file.close();
184 deleteFileC(&self.tmp_path_buf) catch {};
185 self.finished = true;
186 }
187 }
188
189 pub fn finish(self: *AtomicFile) !void {
190 assert(!self.finished);
191 self.file.close();
192 self.finished = true;
193 if (is_posix) {
194 const dest_path_c = try toPosixPath(self.dest_path);
195 return renameC(&self.tmp_path_buf, &dest_path_c);
196 } else if (is_windows) {
197 const dest_path_w = try posix.sliceToPrefixedFileW(self.dest_path);
198 const tmp_path_w = try posix.cStrToPrefixedFileW(&self.tmp_path_buf);
199 return renameW(&tmp_path_w, &dest_path_w);
200 } else {
201 @compileError("Unsupported OS");
202 }
203 }
204};
205
206const default_new_dir_mode = 0o755;
207
208/// Create a new directory.
209pub fn makeDir(dir_path: []const u8) !void {
210 return posix.mkdir(dir_path, default_new_dir_mode);
211}
212
213/// Same as `makeDir` except the parameter is a null-terminated UTF8-encoded string.
214pub fn makeDirC(dir_path: [*]const u8) !void {
215 return posix.mkdirC(dir_path, default_new_dir_mode);
216}
217
218/// Same as `makeDir` except the parameter is a null-terminated UTF16LE-encoded string.
219pub fn makeDirW(dir_path: [*]const u16) !void {
220 return posix.mkdirW(dir_path, default_new_dir_mode);
221}
222
223/// Calls makeDir recursively to make an entire path. Returns success if the path
224/// already exists and is a directory.
225/// This function is not atomic, and if it returns an error, the file system may
226/// have been modified regardless.
227/// TODO determine if we can remove the allocator requirement from this function
228pub fn makePath(allocator: *Allocator, full_path: []const u8) !void {
229 const resolved_path = try path.resolve(allocator, [][]const u8{full_path});
230 defer allocator.free(resolved_path);
231
232 var end_index: usize = resolved_path.len;
233 while (true) {
234 makeDir(resolved_path[0..end_index]) catch |err| switch (err) {
235 error.PathAlreadyExists => {
236 // TODO stat the file and return an error if it's not a directory
237 // this is important because otherwise a dangling symlink
238 // could cause an infinite loop
239 if (end_index == resolved_path.len) return;
240 },
241 error.FileNotFound => {
242 // march end_index backward until next path component
243 while (true) {
244 end_index -= 1;
245 if (os.path.isSep(resolved_path[end_index])) break;
246 }
247 continue;
248 },
249 else => return err,
250 };
251 if (end_index == resolved_path.len) return;
252 // march end_index forward until next path component
253 while (true) {
254 end_index += 1;
255 if (end_index == resolved_path.len or os.path.isSep(resolved_path[end_index])) break;
256 }
257 }
258}
259
260/// Returns `error.DirNotEmpty` if the directory is not empty.
261/// To delete a directory recursively, see `deleteTree`.
262pub fn deleteDir(dir_path: []const u8) DeleteDirError!void {
263 return posix.rmdir(dir_path);
264}
265
266/// Same as `deleteDir` except the parameter is a null-terminated UTF8-encoded string.
267pub fn deleteDirC(dir_path: [*]const u8) DeleteDirError!void {
268 return posix.rmdirC(dir_path);
269}
270
271/// Same as `deleteDir` except the parameter is a null-terminated UTF16LE-encoded string.
272pub fn deleteDirW(dir_path: [*]const u16) DeleteDirError!void {
273 return posix.rmdirW(dir_path);
274}
275
276/// Whether ::full_path describes a symlink, file, or directory, this function
277/// removes it. If it cannot be removed because it is a non-empty directory,
278/// this function recursively removes its entries and then tries again.
279const DeleteTreeError = error{
280 OutOfMemory,
281 AccessDenied,
282 FileTooBig,
283 IsDir,
284 SymLinkLoop,
285 ProcessFdQuotaExceeded,
286 NameTooLong,
287 SystemFdQuotaExceeded,
288 NoDevice,
289 SystemResources,
290 NoSpaceLeft,
291 PathAlreadyExists,
292 ReadOnlyFileSystem,
293 NotDir,
294 FileNotFound,
295 FileSystem,
296 FileBusy,
297 DirNotEmpty,
298 DeviceBusy,
299
300 /// On Windows, file paths must be valid Unicode.
301 InvalidUtf8,
302
303 /// On Windows, file paths cannot contain these characters:
304 /// '/', '*', '?', '"', '<', '>', '|'
305 BadPathName,
306
307 Unexpected,
308};
309
310/// TODO determine if we can remove the allocator requirement
311pub fn deleteTree(allocator: *Allocator, full_path: []const u8) DeleteTreeError!void {
312 start_over: while (true) {
313 var got_access_denied = false;
314 // First, try deleting the item as a file. This way we don't follow sym links.
315 if (deleteFile(full_path)) {
316 return;
317 } else |err| switch (err) {
318 error.FileNotFound => return,
319 error.IsDir => {},
320 error.AccessDenied => got_access_denied = true,
321
322 error.InvalidUtf8,
323 error.SymLinkLoop,
324 error.NameTooLong,
325 error.SystemResources,
326 error.ReadOnlyFileSystem,
327 error.NotDir,
328 error.FileSystem,
329 error.FileBusy,
330 error.BadPathName,
331 error.Unexpected,
332 => return err,
333 }
334 {
335 var dir = Dir.open(allocator, full_path) catch |err| switch (err) {
336 error.NotDir => {
337 if (got_access_denied) {
338 return error.AccessDenied;
339 }
340 continue :start_over;
341 },
342
343 error.OutOfMemory,
344 error.AccessDenied,
345 error.FileTooBig,
346 error.IsDir,
347 error.SymLinkLoop,
348 error.ProcessFdQuotaExceeded,
349 error.NameTooLong,
350 error.SystemFdQuotaExceeded,
351 error.NoDevice,
352 error.FileNotFound,
353 error.SystemResources,
354 error.NoSpaceLeft,
355 error.PathAlreadyExists,
356 error.Unexpected,
357 error.InvalidUtf8,
358 error.BadPathName,
359 error.DeviceBusy,
360 => return err,
361 };
362 defer dir.close();
363
364 var full_entry_buf = ArrayList(u8).init(allocator);
365 defer full_entry_buf.deinit();
366
367 while (try dir.next()) |entry| {
368 try full_entry_buf.resize(full_path.len + entry.name.len + 1);
369 const full_entry_path = full_entry_buf.toSlice();
370 mem.copy(u8, full_entry_path, full_path);
371 full_entry_path[full_path.len] = path.sep;
372 mem.copy(u8, full_entry_path[full_path.len + 1 ..], entry.name);
373
374 try deleteTree(allocator, full_entry_path);
375 }
376 }
377 return deleteDir(full_path);
378 }
379}
380
381pub const Dir = struct {
382 handle: Handle,
383 allocator: *Allocator,
384
385 pub const Handle = switch (builtin.os) {
386 Os.macosx, Os.ios, Os.freebsd, Os.netbsd => struct {
387 fd: i32,
388 seek: i64,
389 buf: []u8,
390 index: usize,
391 end_index: usize,
392 },
393 Os.linux => struct {
394 fd: i32,
395 buf: []u8,
396 index: usize,
397 end_index: usize,
398 },
399 Os.windows => struct {
400 handle: windows.HANDLE,
401 find_file_data: windows.WIN32_FIND_DATAW,
402 first: bool,
403 name_data: [256]u8,
404 },
405 else => @compileError("unimplemented"),
406 };
407
408 pub const Entry = struct {
409 name: []const u8,
410 kind: Kind,
411
412 pub const Kind = enum {
413 BlockDevice,
414 CharacterDevice,
415 Directory,
416 NamedPipe,
417 SymLink,
418 File,
419 UnixDomainSocket,
420 Whiteout,
421 Unknown,
422 };
423 };
424
425 pub const OpenError = error{
426 FileNotFound,
427 NotDir,
428 AccessDenied,
429 FileTooBig,
430 IsDir,
431 SymLinkLoop,
432 ProcessFdQuotaExceeded,
433 NameTooLong,
434 SystemFdQuotaExceeded,
435 NoDevice,
436 SystemResources,
437 NoSpaceLeft,
438 PathAlreadyExists,
439 OutOfMemory,
440 InvalidUtf8,
441 BadPathName,
442 DeviceBusy,
443
444 Unexpected,
445 };
446
447 /// TODO remove the allocator requirement from this API
448 pub fn open(allocator: *Allocator, dir_path: []const u8) OpenError!Dir {
449 return Dir{
450 .allocator = allocator,
451 .handle = switch (builtin.os) {
452 Os.windows => blk: {
453 var find_file_data: windows.WIN32_FIND_DATAW = undefined;
454 const handle = try windows_util.windowsFindFirstFile(dir_path, &find_file_data);
455 break :blk Handle{
456 .handle = handle,
457 .find_file_data = find_file_data, // TODO guaranteed copy elision
458 .first = true,
459 .name_data = undefined,
460 };
461 },
462 Os.macosx, Os.ios, Os.freebsd, Os.netbsd => Handle{
463 .fd = try posixOpen(
464 dir_path,
465 posix.O_RDONLY | posix.O_NONBLOCK | posix.O_DIRECTORY | posix.O_CLOEXEC,
466 0,
467 ),
468 .seek = 0,
469 .index = 0,
470 .end_index = 0,
471 .buf = []u8{},
472 },
473 Os.linux => Handle{
474 .fd = try posixOpen(
475 dir_path,
476 posix.O_RDONLY | posix.O_DIRECTORY | posix.O_CLOEXEC,
477 0,
478 ),
479 .index = 0,
480 .end_index = 0,
481 .buf = []u8{},
482 },
483 else => @compileError("unimplemented"),
484 },
485 };
486 }
487
488 pub fn close(self: *Dir) void {
489 switch (builtin.os) {
490 Os.windows => {
491 _ = windows.FindClose(self.handle.handle);
492 },
493 Os.macosx, Os.ios, Os.linux, Os.freebsd, Os.netbsd => {
494 self.allocator.free(self.handle.buf);
495 os.close(self.handle.fd);
496 },
497 else => @compileError("unimplemented"),
498 }
499 }
500
501 /// Memory such as file names referenced in this returned entry becomes invalid
502 /// with subsequent calls to next, as well as when this `Dir` is deinitialized.
503 pub fn next(self: *Dir) !?Entry {
504 switch (builtin.os) {
505 Os.linux => return self.nextLinux(),
506 Os.macosx, Os.ios => return self.nextDarwin(),
507 Os.windows => return self.nextWindows(),
508 Os.freebsd => return self.nextFreebsd(),
509 Os.netbsd => return self.nextFreebsd(),
510 else => @compileError("unimplemented"),
511 }
512 }
513
514 fn nextDarwin(self: *Dir) !?Entry {
515 start_over: while (true) {
516 if (self.handle.index >= self.handle.end_index) {
517 if (self.handle.buf.len == 0) {
518 self.handle.buf = try self.allocator.alloc(u8, mem.page_size);
519 }
520
521 while (true) {
522 const result = system.__getdirentries64(self.handle.fd, self.handle.buf.ptr, self.handle.buf.len, &self.handle.seek);
523 if (result == 0) return null;
524 if (result < 0) {
525 switch (system.getErrno(result)) {
526 posix.EBADF => unreachable,
527 posix.EFAULT => unreachable,
528 posix.ENOTDIR => unreachable,
529 posix.EINVAL => {
530 self.handle.buf = try self.allocator.realloc(self.handle.buf, self.handle.buf.len * 2);
531 continue;
532 },
533 else => return unexpectedErrorPosix(err),
534 }
535 }
536 self.handle.index = 0;
537 self.handle.end_index = @intCast(usize, result);
538 break;
539 }
540 }
541 const darwin_entry = @ptrCast(*align(1) posix.dirent, &self.handle.buf[self.handle.index]);
542 const next_index = self.handle.index + darwin_entry.d_reclen;
543 self.handle.index = next_index;
544
545 const name = @ptrCast([*]u8, &darwin_entry.d_name)[0..darwin_entry.d_namlen];
546
547 if (mem.eql(u8, name, ".") or mem.eql(u8, name, "..")) {
548 continue :start_over;
549 }
550
551 const entry_kind = switch (darwin_entry.d_type) {
552 posix.DT_BLK => Entry.Kind.BlockDevice,
553 posix.DT_CHR => Entry.Kind.CharacterDevice,
554 posix.DT_DIR => Entry.Kind.Directory,
555 posix.DT_FIFO => Entry.Kind.NamedPipe,
556 posix.DT_LNK => Entry.Kind.SymLink,
557 posix.DT_REG => Entry.Kind.File,
558 posix.DT_SOCK => Entry.Kind.UnixDomainSocket,
559 posix.DT_WHT => Entry.Kind.Whiteout,
560 else => Entry.Kind.Unknown,
561 };
562 return Entry{
563 .name = name,
564 .kind = entry_kind,
565 };
566 }
567 }
568
569 fn nextWindows(self: *Dir) !?Entry {
570 while (true) {
571 if (self.handle.first) {
572 self.handle.first = false;
573 } else {
574 if (!try posix.FindNextFile(self.handle.handle, &self.handle.find_file_data))
575 return null;
576 }
577 const name_utf16le = mem.toSlice(u16, self.handle.find_file_data.cFileName[0..].ptr);
578 if (mem.eql(u16, name_utf16le, []u16{'.'}) or mem.eql(u16, name_utf16le, []u16{ '.', '.' }))
579 continue;
580 // Trust that Windows gives us valid UTF-16LE
581 const name_utf8_len = std.unicode.utf16leToUtf8(self.handle.name_data[0..], name_utf16le) catch unreachable;
582 const name_utf8 = self.handle.name_data[0..name_utf8_len];
583 const kind = blk: {
584 const attrs = self.handle.find_file_data.dwFileAttributes;
585 if (attrs & windows.FILE_ATTRIBUTE_DIRECTORY != 0) break :blk Entry.Kind.Directory;
586 if (attrs & windows.FILE_ATTRIBUTE_REPARSE_POINT != 0) break :blk Entry.Kind.SymLink;
587 if (attrs & windows.FILE_ATTRIBUTE_NORMAL != 0) break :blk Entry.Kind.File;
588 break :blk Entry.Kind.Unknown;
589 };
590 return Entry{
591 .name = name_utf8,
592 .kind = kind,
593 };
594 }
595 }
596
597 fn nextLinux(self: *Dir) !?Entry {
598 start_over: while (true) {
599 if (self.handle.index >= self.handle.end_index) {
600 if (self.handle.buf.len == 0) {
601 self.handle.buf = try self.allocator.alloc(u8, mem.page_size);
602 }
603
604 while (true) {
605 const result = posix.getdents64(self.handle.fd, self.handle.buf.ptr, self.handle.buf.len);
606 const err = posix.getErrno(result);
607 if (err > 0) {
608 switch (err) {
609 posix.EBADF, posix.EFAULT, posix.ENOTDIR => unreachable,
610 posix.EINVAL => {
611 self.handle.buf = try self.allocator.realloc(self.handle.buf, self.handle.buf.len * 2);
612 continue;
613 },
614 else => return unexpectedErrorPosix(err),
615 }
616 }
617 if (result == 0) return null;
618 self.handle.index = 0;
619 self.handle.end_index = result;
620 break;
621 }
622 }
623 const linux_entry = @ptrCast(*align(1) posix.dirent64, &self.handle.buf[self.handle.index]);
624 const next_index = self.handle.index + linux_entry.d_reclen;
625 self.handle.index = next_index;
626
627 const name = cstr.toSlice(@ptrCast([*]u8, &linux_entry.d_name));
628
629 // skip . and .. entries
630 if (mem.eql(u8, name, ".") or mem.eql(u8, name, "..")) {
631 continue :start_over;
632 }
633
634 const entry_kind = switch (linux_entry.d_type) {
635 posix.DT_BLK => Entry.Kind.BlockDevice,
636 posix.DT_CHR => Entry.Kind.CharacterDevice,
637 posix.DT_DIR => Entry.Kind.Directory,
638 posix.DT_FIFO => Entry.Kind.NamedPipe,
639 posix.DT_LNK => Entry.Kind.SymLink,
640 posix.DT_REG => Entry.Kind.File,
641 posix.DT_SOCK => Entry.Kind.UnixDomainSocket,
642 else => Entry.Kind.Unknown,
643 };
644 return Entry{
645 .name = name,
646 .kind = entry_kind,
647 };
648 }
649 }
650
651 fn nextFreebsd(self: *Dir) !?Entry {
652 start_over: while (true) {
653 if (self.handle.index >= self.handle.end_index) {
654 if (self.handle.buf.len == 0) {
655 self.handle.buf = try self.allocator.alloc(u8, mem.page_size);
656 }
657
658 while (true) {
659 const result = posix.getdirentries(self.handle.fd, self.handle.buf.ptr, self.handle.buf.len, &self.handle.seek);
660 const err = posix.getErrno(result);
661 if (err > 0) {
662 switch (err) {
663 posix.EBADF, posix.EFAULT, posix.ENOTDIR => unreachable,
664 posix.EINVAL => {
665 self.handle.buf = try self.allocator.realloc(self.handle.buf, self.handle.buf.len * 2);
666 continue;
667 },
668 else => return unexpectedErrorPosix(err),
669 }
670 }
671 if (result == 0) return null;
672 self.handle.index = 0;
673 self.handle.end_index = result;
674 break;
675 }
676 }
677 const freebsd_entry = @ptrCast(*align(1) posix.dirent, &self.handle.buf[self.handle.index]);
678 const next_index = self.handle.index + freebsd_entry.d_reclen;
679 self.handle.index = next_index;
680
681 const name = @ptrCast([*]u8, &freebsd_entry.d_name)[0..freebsd_entry.d_namlen];
682
683 if (mem.eql(u8, name, ".") or mem.eql(u8, name, "..")) {
684 continue :start_over;
685 }
686
687 const entry_kind = switch (freebsd_entry.d_type) {
688 posix.DT_BLK => Entry.Kind.BlockDevice,
689 posix.DT_CHR => Entry.Kind.CharacterDevice,
690 posix.DT_DIR => Entry.Kind.Directory,
691 posix.DT_FIFO => Entry.Kind.NamedPipe,
692 posix.DT_LNK => Entry.Kind.SymLink,
693 posix.DT_REG => Entry.Kind.File,
694 posix.DT_SOCK => Entry.Kind.UnixDomainSocket,
695 posix.DT_WHT => Entry.Kind.Whiteout,
696 else => Entry.Kind.Unknown,
697 };
698 return Entry{
699 .name = name,
700 .kind = entry_kind,
701 };
702 }
703 }
704};
705
706/// Read value of a symbolic link.
707/// The return value is a slice of buffer, from index `0`.
708pub fn readLink(buffer: *[posix.PATH_MAX]u8, pathname: []const u8) ![]u8 {
709 return posix.readlink(pathname, buffer);
710}
711
712/// Same as `readLink`, except the `pathname` parameter is null-terminated.
713pub fn readLinkC(buffer: *[posix.PATH_MAX]u8, pathname: [*]const u8) ![]u8 {
714 return posix.readlinkC(pathname, buffer);
715}
716
717pub fn openSelfExe() !os.File {
718 switch (builtin.os) {
719 Os.linux => return os.File.openReadC(c"/proc/self/exe"),
720 Os.macosx, Os.ios, Os.freebsd, Os.netbsd => {
721 var buf: [MAX_PATH_BYTES]u8 = undefined;
722 const self_exe_path = try selfExePath(&buf);
723 buf[self_exe_path.len] = 0;
724 return os.File.openReadC(self_exe_path.ptr);
725 },
726 Os.windows => {
727 var buf: [posix.PATH_MAX_WIDE]u16 = undefined;
728 const wide_slice = try selfExePathW(&buf);
729 return os.File.openReadW(wide_slice.ptr);
730 },
731 else => @compileError("Unsupported OS"),
732 }
733}
734
735test "openSelfExe" {
736 switch (builtin.os) {
737 Os.linux, Os.macosx, Os.ios, Os.windows, Os.freebsd => (try openSelfExe()).close(),
738 else => return error.SkipZigTest, // Unsupported OS.
739 }
740}
741
742pub fn selfExePathW(out_buffer: *[posix.PATH_MAX_WIDE]u16) ![]u16 {
743 const casted_len = @intCast(windows.DWORD, out_buffer.len); // TODO shouldn't need this cast
744 const rc = windows.GetModuleFileNameW(null, out_buffer, casted_len);
745 assert(rc <= out_buffer.len);
746 if (rc == 0) {
747 const err = windows.GetLastError();
748 switch (err) {
749 else => return windows.unexpectedError(err),
750 }
751 }
752 return out_buffer[0..rc];
753}
754
755/// Get the path to the current executable.
756/// If you only need the directory, use selfExeDirPath.
757/// If you only want an open file handle, use openSelfExe.
758/// This function may return an error if the current executable
759/// was deleted after spawning.
760/// Returned value is a slice of out_buffer.
761///
762/// On Linux, depends on procfs being mounted. If the currently executing binary has
763/// been deleted, the file path looks something like `/a/b/c/exe (deleted)`.
764/// TODO make the return type of this a null terminated pointer
765pub fn selfExePath(out_buffer: *[MAX_PATH_BYTES]u8) ![]u8 {
766 switch (builtin.os) {
767 Os.linux => return readLink(out_buffer, "/proc/self/exe"),
768 Os.freebsd => {
769 var mib = [4]c_int{ posix.CTL_KERN, posix.KERN_PROC, posix.KERN_PROC_PATHNAME, -1 };
770 var out_len: usize = out_buffer.len;
771 try posix.sysctl(&mib, out_buffer, &out_len, null, 0);
772 // TODO could this slice from 0 to out_len instead?
773 return mem.toSlice(u8, out_buffer);
774 },
775 Os.netbsd => {
776 var mib = [4]c_int{ posix.CTL_KERN, posix.KERN_PROC_ARGS, -1, posix.KERN_PROC_PATHNAME };
777 var out_len: usize = out_buffer.len;
778 try posix.sysctl(&mib, out_buffer, &out_len, null, 0);
779 // TODO could this slice from 0 to out_len instead?
780 return mem.toSlice(u8, out_buffer);
781 },
782 Os.windows => {
783 var utf16le_buf: [posix.PATH_MAX_WIDE]u16 = undefined;
784 const utf16le_slice = try selfExePathW(&utf16le_buf);
785 // Trust that Windows gives us valid UTF-16LE.
786 const end_index = std.unicode.utf16leToUtf8(out_buffer, utf16le_slice) catch unreachable;
787 return out_buffer[0..end_index];
788 },
789 Os.macosx, Os.ios => {
790 var u32_len: u32 = @intCast(u32, out_buffer.len); // TODO shouldn't need this cast
791 const rc = c._NSGetExecutablePath(out_buffer, &u32_len);
792 if (rc != 0) return error.NameTooLong;
793 return mem.toSlice(u8, out_buffer);
794 },
795 else => @compileError("Unsupported OS"),
796 }
797}
798
799/// `selfExeDirPath` except allocates the result on the heap.
800/// Caller owns returned memory.
801pub fn selfExeDirPathAlloc(allocator: *Allocator) ![]u8 {
802 var buf: [MAX_PATH_BYTES]u8 = undefined;
803 return mem.dupe(allocator, u8, try selfExeDirPath(&buf));
804}
805
806/// Get the directory path that contains the current executable.
807/// Returned value is a slice of out_buffer.
808pub fn selfExeDirPath(out_buffer: *[MAX_PATH_BYTES]u8) ![]const u8 {
809 switch (builtin.os) {
810 Os.linux => {
811 // If the currently executing binary has been deleted,
812 // the file path looks something like `/a/b/c/exe (deleted)`
813 // This path cannot be opened, but it's valid for determining the directory
814 // the executable was in when it was run.
815 const full_exe_path = try readLinkC(out_buffer, c"/proc/self/exe");
816 // Assume that /proc/self/exe has an absolute path, and therefore dirname
817 // will not return null.
818 return path.dirname(full_exe_path).?;
819 },
820 Os.windows, Os.macosx, Os.ios, Os.freebsd, Os.netbsd => {
821 const self_exe_path = try selfExePath(out_buffer);
822 // Assume that the OS APIs return absolute paths, and therefore dirname
823 // will not return null.
824 return path.dirname(self_exe_path).?;
825 },
826 else => @compileError("Unsupported OS"),
827 }
828}
829
830/// `realpath`, except caller must free the returned memory.
831pub fn realAlloc(allocator: *Allocator, pathname: []const u8) ![]u8 {
832 var buf: [MAX_PATH_BYTES]u8 = undefined;
833 return mem.dupe(allocator, u8, try realpath(pathname, &buf));
834}
835
836test "" {
837 _ = @import("fs/path.zig");
838 _ = @import("fs/file.zig");
839 _ = @import("fs/get_app_data_dir.zig");
840}
std/fs/file.zig created+334
......@@ -0,0 +1,334 @@
1const std = @import("../std.zig");
2const builtin = @import("builtin");
3const os = std.os;
4const io = std.io;
5const mem = std.mem;
6const math = std.math;
7const assert = std.debug.assert;
8const windows = os.windows;
9const Os = builtin.Os;
10const maxInt = std.math.maxInt;
11
12pub const File = struct {
13 /// The OS-specific file descriptor or file handle.
14 handle: os.fd_t,
15
16 pub const Mode = switch (builtin.os) {
17 Os.windows => void,
18 else => u32,
19 };
20
21 pub const default_mode = switch (builtin.os) {
22 Os.windows => {},
23 else => 0o666,
24 };
25
26 pub const OpenError = windows.CreateFileError || os.OpenError;
27
28 /// Call close to clean up.
29 pub fn openRead(path: []const u8) OpenError!File {
30 if (windows.is_the_target and !builtin.link_libc) {
31 const path_w = try windows.sliceToPrefixedFileW(path);
32 return openReadW(&path_w);
33 }
34 const path_c = try os.toPosixPath(path);
35 return openReadC(&path_c);
36 }
37
38 /// `openRead` except with a null terminated path
39 pub fn openReadC(path: [*]const u8) OpenError!File {
40 if (windows.is_the_target and !builtin.link_libc) {
41 const path_w = try windows.cStrToPrefixedFileW(path);
42 return openReadW(&path_w);
43 }
44 const flags = os.O_LARGEFILE | os.O_RDONLY;
45 const fd = try os.openC(path, flags, 0);
46 return openHandle(fd);
47 }
48
49 /// `openRead` except with a null terminated UTF16LE encoded path
50 pub fn openReadW(path_w: [*]const u16) OpenError!File {
51 const handle = try windows.CreateFileW(
52 path_w,
53 windows.GENERIC_READ,
54 windows.FILE_SHARE_READ,
55 windows.OPEN_EXISTING,
56 windows.FILE_ATTRIBUTE_NORMAL,
57 );
58 return openHandle(handle);
59 }
60
61 /// Calls `openWriteMode` with `default_mode` for the mode.
62 pub fn openWrite(path: []const u8) OpenError!File {
63 return openWriteMode(path, default_mode);
64 }
65
66 /// If the path does not exist it will be created.
67 /// If a file already exists in the destination it will be truncated.
68 /// Call close to clean up.
69 pub fn openWriteMode(path: []const u8, file_mode: Mode) OpenError!File {
70 if (windows.is_the_target and !builtin.link_libc) {
71 const path_w = try windows.sliceToPrefixedFileW(path);
72 return openWriteModeW(&path_w, file_mode);
73 }
74 const path_c = try os.toPosixPath(path);
75 return openWriteModeC(&path_c, file_mode);
76 }
77
78 /// Same as `openWriteMode` except `path` is null-terminated.
79 pub fn openWriteModeC(path: [*]const u8, file_mode: Mode) OpenError!File {
80 if (windows.is_the_target and !builtin.link_libc) {
81 const path_w = try windows.cStrToPrefixedFileW(path);
82 return openWriteModeW(&path_w, file_mode);
83 }
84 const flags = os.O_LARGEFILE | os.O_WRONLY | os.O_CREAT | os.O_CLOEXEC | os.O_TRUNC;
85 const fd = try os.openC(path, flags, file_mode);
86 return openHandle(fd);
87 }
88
89 /// Same as `openWriteMode` except `path` is null-terminated and UTF16LE encoded
90 pub fn openWriteModeW(path_w: [*]const u16, file_mode: Mode) OpenError!File {
91 const handle = try windows.CreateFileW(
92 path_w,
93 windows.GENERIC_WRITE,
94 windows.FILE_SHARE_WRITE | windows.FILE_SHARE_READ | windows.FILE_SHARE_DELETE,
95 windows.CREATE_ALWAYS,
96 windows.FILE_ATTRIBUTE_NORMAL,
97 );
98 return openHandle(handle);
99 }
100
101 /// If the path does not exist it will be created.
102 /// If a file already exists in the destination this returns OpenError.PathAlreadyExists
103 /// Call close to clean up.
104 pub fn openWriteNoClobber(path: []const u8, file_mode: Mode) OpenError!File {
105 if (windows.is_the_target and !builtin.link_libc) {
106 const path_w = try windows.sliceToPrefixedFileW(path);
107 return openWriteNoClobberW(&path_w, file_mode);
108 }
109 const path_c = try os.toPosixPath(path);
110 return openWriteNoClobberC(&path_c, file_mode);
111 }
112
113 pub fn openWriteNoClobberC(path: [*]const u8, file_mode: Mode) OpenError!File {
114 if (windows.is_the_target and !builtin.link_libc) {
115 const path_w = try windows.cStrToPrefixedFileW(path);
116 return openWriteNoClobberW(&path_w, file_mode);
117 }
118 const flags = os.O_LARGEFILE | os.O_WRONLY | os.O_CREAT | os.O_CLOEXEC | os.O_EXCL;
119 const fd = try os.openC(path, flags, file_mode);
120 return openHandle(fd);
121 }
122
123 pub fn openWriteNoClobberW(path_w: [*]const u16, file_mode: Mode) OpenError!File {
124 const handle = try windows.CreateFileW(
125 path_w,
126 windows.GENERIC_WRITE,
127 windows.FILE_SHARE_WRITE | windows.FILE_SHARE_READ | windows.FILE_SHARE_DELETE,
128 windows.CREATE_NEW,
129 windows.FILE_ATTRIBUTE_NORMAL,
130 );
131 return openHandle(handle);
132 }
133
134 pub fn openHandle(handle: os.fd_t) File {
135 return File{ .handle = handle };
136 }
137
138 /// Test for the existence of `path`.
139 /// `path` is UTF8-encoded.
140 pub fn exists(path: []const u8) AccessError!void {
141 return os.access(path, os.F_OK);
142 }
143
144 /// Same as `exists` except the parameter is null-terminated.
145 pub fn existsC(path: [*]const u8) AccessError!void {
146 return os.accessC(path, os.F_OK);
147 }
148
149 /// Same as `exists` except the parameter is null-terminated UTF16LE-encoded.
150 pub fn existsW(path: [*]const u16) AccessError!void {
151 return os.accessW(path, os.F_OK);
152 }
153
154 /// Upon success, the stream is in an uninitialized state. To continue using it,
155 /// you must use the open() function.
156 pub fn close(self: File) void {
157 os.close(self.handle);
158 }
159
160 /// Test whether the file refers to a terminal.
161 /// See also `supportsAnsiEscapeCodes`.
162 pub fn isTty(self: File) bool {
163 return os.isatty(self.handle);
164 }
165
166 /// Test whether ANSI escape codes will be treated as such.
167 pub fn supportsAnsiEscapeCodes(self: File) bool {
168 if (windows.is_the_target) {
169 return os.isCygwinPty(self.handle);
170 }
171 return self.isTty();
172 }
173
174 pub const SeekError = os.SeekError;
175
176 /// Repositions read/write file offset relative to the current offset.
177 pub fn seekBy(self: File, offset: i64) SeekError!void {
178 return os.lseek_CUR(self.handle, offset);
179 }
180
181 /// Repositions read/write file offset relative to the end.
182 pub fn seekFromEnd(self: File, offset: i64) SeekError!void {
183 return os.lseek_END(self.handle, offset);
184 }
185
186 /// Repositions read/write file offset relative to the beginning.
187 pub fn seekTo(self: File, offset: u64) SeekError!void {
188 return os.lseek_SET(self.handle, offset);
189 }
190
191 pub const GetPosError = os.SeekError || os.FStatError;
192
193 pub fn getPos(self: File) GetPosError!u64 {
194 return os.lseek_CUR_get(self.handle);
195 }
196
197 pub fn getEndPos(self: File) GetPosError!u64 {
198 if (windows.is_the_target and !builtin.link_libc) {
199 return windows.GetFileSizeEx(self.handle);
200 }
201 const stat = try os.fstat(self.handle);
202 return @bitCast(u64, stat.size);
203 }
204
205 pub const ModeError = os.FStatError;
206
207 pub fn mode(self: File) ModeError!Mode {
208 if (windows.is_the_target and !builtin.link_libc) {
209 return {};
210 }
211 const stat = try os.fstat(self.handle);
212 // TODO: we should be able to cast u16 to ModeError!u32, making this
213 // explicit cast not necessary
214 return Mode(stat.mode);
215 }
216
217 pub const ReadError = os.ReadError;
218
219 pub fn read(self: File, buffer: []u8) ReadError!usize {
220 return os.read(self.handle, buffer);
221 }
222
223 pub const WriteError = os.WriteError;
224
225 pub fn write(self: File, bytes: []const u8) WriteError!void {
226 return os.write(self.handle, bytes);
227 }
228
229 pub fn inStream(file: File) InStream {
230 return InStream{
231 .file = file,
232 .stream = InStream.Stream{ .readFn = InStream.readFn },
233 };
234 }
235
236 pub fn outStream(file: File) OutStream {
237 return OutStream{
238 .file = file,
239 .stream = OutStream.Stream{ .writeFn = OutStream.writeFn },
240 };
241 }
242
243 pub fn seekableStream(file: File) SeekableStream {
244 return SeekableStream{
245 .file = file,
246 .stream = SeekableStream.Stream{
247 .seekToFn = SeekableStream.seekToFn,
248 .seekByFn = SeekableStream.seekByFn,
249 .getPosFn = SeekableStream.getPosFn,
250 .getEndPosFn = SeekableStream.getEndPosFn,
251 },
252 };
253 }
254
255 /// Implementation of io.InStream trait for File
256 pub const InStream = struct {
257 file: File,
258 stream: Stream,
259
260 pub const Error = ReadError;
261 pub const Stream = io.InStream(Error);
262
263 fn readFn(in_stream: *Stream, buffer: []u8) Error!usize {
264 const self = @fieldParentPtr(InStream, "stream", in_stream);
265 return self.file.read(buffer);
266 }
267 };
268
269 /// Implementation of io.OutStream trait for File
270 pub const OutStream = struct {
271 file: File,
272 stream: Stream,
273
274 pub const Error = WriteError;
275 pub const Stream = io.OutStream(Error);
276
277 fn writeFn(out_stream: *Stream, bytes: []const u8) Error!void {
278 const self = @fieldParentPtr(OutStream, "stream", out_stream);
279 return self.file.write(bytes);
280 }
281 };
282
283 /// Implementation of io.SeekableStream trait for File
284 pub const SeekableStream = struct {
285 file: File,
286 stream: Stream,
287
288 pub const Stream = io.SeekableStream(SeekError, GetPosError);
289
290 pub fn seekToFn(seekable_stream: *Stream, pos: u64) SeekError!void {
291 const self = @fieldParentPtr(SeekableStream, "stream", seekable_stream);
292 return self.file.seekTo(pos);
293 }
294
295 pub fn seekByFn(seekable_stream: *Stream, amt: i64) SeekError!void {
296 const self = @fieldParentPtr(SeekableStream, "stream", seekable_stream);
297 return self.file.seekBy(amt);
298 }
299
300 pub fn getEndPosFn(seekable_stream: *Stream) GetPosError!u64 {
301 const self = @fieldParentPtr(SeekableStream, "stream", seekable_stream);
302 return self.file.getEndPos();
303 }
304
305 pub fn getPosFn(seekable_stream: *Stream) GetPosError!u64 {
306 const self = @fieldParentPtr(SeekableStream, "stream", seekable_stream);
307 return self.file.getPos();
308 }
309 };
310
311 pub fn stdout() !File {
312 if (windows.is_the_target) {
313 const handle = try windows.GetStdHandle(windows.STD_OUTPUT_HANDLE);
314 return openHandle(handle);
315 }
316 return openHandle(os.STDOUT_FILENO);
317 }
318
319 pub fn stderr() !File {
320 if (windows.is_the_target) {
321 const handle = try windows.GetStdHandle(windows.STD_ERROR_HANDLE);
322 return openHandle(handle);
323 }
324 return openHandle(os.STDERR_FILENO);
325 }
326
327 pub fn stdin() !File {
328 if (windows.is_the_target) {
329 const handle = try windows.GetStdHandle(windows.STD_INPUT_HANDLE);
330 return openHandle(handle);
331 }
332 return openHandle(os.STDIN_FILENO);
333 }
334};
std/fs/get_app_data_dir.zig created+69
......@@ -0,0 +1,69 @@
1const std = @import("../std.zig");
2const builtin = @import("builtin");
3const unicode = std.unicode;
4const mem = std.mem;
5const os = std.os;
6
7pub const GetAppDataDirError = error{
8 OutOfMemory,
9 AppDataDirUnavailable,
10};
11
12/// Caller owns returned memory.
13/// TODO determine if we can remove the allocator requirement
14pub fn getAppDataDir(allocator: *mem.Allocator, appname: []const u8) GetAppDataDirError![]u8 {
15 switch (builtin.os) {
16 .windows => {
17 var dir_path_ptr: [*]u16 = undefined;
18 switch (os.windows.SHGetKnownFolderPath(
19 &os.windows.FOLDERID_LocalAppData,
20 os.windows.KF_FLAG_CREATE,
21 null,
22 &dir_path_ptr,
23 )) {
24 os.windows.S_OK => {
25 defer os.windows.CoTaskMemFree(@ptrCast(*c_void, dir_path_ptr));
26 const global_dir = unicode.utf16leToUtf8Alloc(allocator, utf16lePtrSlice(dir_path_ptr)) catch |err| switch (err) {
27 error.UnexpectedSecondSurrogateHalf => return error.AppDataDirUnavailable,
28 error.ExpectedSecondSurrogateHalf => return error.AppDataDirUnavailable,
29 error.DanglingSurrogateHalf => return error.AppDataDirUnavailable,
30 error.OutOfMemory => return error.OutOfMemory,
31 };
32 defer allocator.free(global_dir);
33 return os.path.join(allocator, [][]const u8{ global_dir, appname });
34 },
35 os.windows.E_OUTOFMEMORY => return error.OutOfMemory,
36 else => return error.AppDataDirUnavailable,
37 }
38 },
39 .macosx => {
40 const home_dir = os.getEnvPosix("HOME") orelse {
41 // TODO look in /etc/passwd
42 return error.AppDataDirUnavailable;
43 };
44 return os.path.join(allocator, [][]const u8{ home_dir, "Library", "Application Support", appname });
45 },
46 .linux, .freebsd, .netbsd => {
47 const home_dir = os.getEnvPosix("HOME") orelse {
48 // TODO look in /etc/passwd
49 return error.AppDataDirUnavailable;
50 };
51 return os.path.join(allocator, [][]const u8{ home_dir, ".local", "share", appname });
52 },
53 else => @compileError("Unsupported OS"),
54 }
55}
56
57fn utf16lePtrSlice(ptr: [*]const u16) []const u16 {
58 var index: usize = 0;
59 while (ptr[index] != 0) : (index += 1) {}
60 return ptr[0..index];
61}
62
63test "getAppDataDir" {
64 var buf: [512]u8 = undefined;
65 const allocator = &std.heap.FixedBufferAllocator.init(buf[0..]).allocator;
66
67 // We can't actually validate the result
68 _ = getAppDataDir(allocator, "zig") catch return;
69}
std/fs/path.zig created+1140
......@@ -0,0 +1,1140 @@
1const std = @import("../std.zig");
2const builtin = @import("builtin");
3const Os = builtin.Os;
4const debug = std.debug;
5const assert = debug.assert;
6const testing = std.testing;
7const mem = std.mem;
8const fmt = std.fmt;
9const Allocator = mem.Allocator;
10const os = std.os;
11const math = std.math;
12const posix = os.posix;
13const windows = os.windows;
14const cstr = std.cstr;
15
16pub const sep_windows = '\\';
17pub const sep_posix = '/';
18pub const sep = if (is_windows) sep_windows else sep_posix;
19
20pub const sep_str = [1]u8{sep};
21
22pub const delimiter_windows = ';';
23pub const delimiter_posix = ':';
24pub const delimiter = if (is_windows) delimiter_windows else delimiter_posix;
25
26const is_windows = builtin.os == builtin.Os.windows;
27
28pub fn isSep(byte: u8) bool {
29 if (is_windows) {
30 return byte == '/' or byte == '\\';
31 } else {
32 return byte == '/';
33 }
34}
35
36/// This is different from mem.join in that the separator will not be repeated if
37/// it is found at the end or beginning of a pair of consecutive paths.
38fn joinSep(allocator: *Allocator, separator: u8, paths: []const []const u8) ![]u8 {
39 if (paths.len == 0) return (([*]u8)(undefined))[0..0];
40
41 const total_len = blk: {
42 var sum: usize = paths[0].len;
43 var i: usize = 1;
44 while (i < paths.len) : (i += 1) {
45 const prev_path = paths[i - 1];
46 const this_path = paths[i];
47 const prev_sep = (prev_path.len != 0 and prev_path[prev_path.len - 1] == separator);
48 const this_sep = (this_path.len != 0 and this_path[0] == separator);
49 sum += @boolToInt(!prev_sep and !this_sep);
50 sum += if (prev_sep and this_sep) this_path.len - 1 else this_path.len;
51 }
52 break :blk sum;
53 };
54
55 const buf = try allocator.alloc(u8, total_len);
56 errdefer allocator.free(buf);
57
58 mem.copy(u8, buf, paths[0]);
59 var buf_index: usize = paths[0].len;
60 var i: usize = 1;
61 while (i < paths.len) : (i += 1) {
62 const prev_path = paths[i - 1];
63 const this_path = paths[i];
64 const prev_sep = (prev_path.len != 0 and prev_path[prev_path.len - 1] == separator);
65 const this_sep = (this_path.len != 0 and this_path[0] == separator);
66 if (!prev_sep and !this_sep) {
67 buf[buf_index] = separator;
68 buf_index += 1;
69 }
70 const adjusted_path = if (prev_sep and this_sep) this_path[1..] else this_path;
71 mem.copy(u8, buf[buf_index..], adjusted_path);
72 buf_index += adjusted_path.len;
73 }
74
75 // No need for shrink since buf is exactly the correct size.
76 return buf;
77}
78
79pub const join = if (is_windows) joinWindows else joinPosix;
80
81/// Naively combines a series of paths with the native path seperator.
82/// Allocates memory for the result, which must be freed by the caller.
83pub fn joinWindows(allocator: *Allocator, paths: []const []const u8) ![]u8 {
84 return joinSep(allocator, sep_windows, paths);
85}
86
87/// Naively combines a series of paths with the native path seperator.
88/// Allocates memory for the result, which must be freed by the caller.
89pub fn joinPosix(allocator: *Allocator, paths: []const []const u8) ![]u8 {
90 return joinSep(allocator, sep_posix, paths);
91}
92
93fn testJoinWindows(paths: []const []const u8, expected: []const u8) void {
94 var buf: [1024]u8 = undefined;
95 const a = &std.heap.FixedBufferAllocator.init(&buf).allocator;
96 const actual = joinWindows(a, paths) catch @panic("fail");
97 testing.expectEqualSlices(u8, expected, actual);
98}
99
100fn testJoinPosix(paths: []const []const u8, expected: []const u8) void {
101 var buf: [1024]u8 = undefined;
102 const a = &std.heap.FixedBufferAllocator.init(&buf).allocator;
103 const actual = joinPosix(a, paths) catch @panic("fail");
104 testing.expectEqualSlices(u8, expected, actual);
105}
106
107test "join" {
108 testJoinWindows([][]const u8{ "c:\\a\\b", "c" }, "c:\\a\\b\\c");
109 testJoinWindows([][]const u8{ "c:\\a\\b", "c" }, "c:\\a\\b\\c");
110 testJoinWindows([][]const u8{ "c:\\a\\b\\", "c" }, "c:\\a\\b\\c");
111
112 testJoinWindows([][]const u8{ "c:\\", "a", "b\\", "c" }, "c:\\a\\b\\c");
113 testJoinWindows([][]const u8{ "c:\\a\\", "b\\", "c" }, "c:\\a\\b\\c");
114
115 testJoinWindows(
116 [][]const u8{ "c:\\home\\andy\\dev\\zig\\build\\lib\\zig\\std", "io.zig" },
117 "c:\\home\\andy\\dev\\zig\\build\\lib\\zig\\std\\io.zig",
118 );
119
120 testJoinPosix([][]const u8{ "/a/b", "c" }, "/a/b/c");
121 testJoinPosix([][]const u8{ "/a/b/", "c" }, "/a/b/c");
122
123 testJoinPosix([][]const u8{ "/", "a", "b/", "c" }, "/a/b/c");
124 testJoinPosix([][]const u8{ "/a/", "b/", "c" }, "/a/b/c");
125
126 testJoinPosix(
127 [][]const u8{ "/home/andy/dev/zig/build/lib/zig/std", "io.zig" },
128 "/home/andy/dev/zig/build/lib/zig/std/io.zig",
129 );
130
131 testJoinPosix([][]const u8{ "a", "/c" }, "a/c");
132 testJoinPosix([][]const u8{ "a/", "/c" }, "a/c");
133}
134
135pub fn isAbsolute(path: []const u8) bool {
136 if (is_windows) {
137 return isAbsoluteWindows(path);
138 } else {
139 return isAbsolutePosix(path);
140 }
141}
142
143pub fn isAbsoluteWindows(path: []const u8) bool {
144 if (path[0] == '/')
145 return true;
146
147 if (path[0] == '\\') {
148 return true;
149 }
150 if (path.len < 3) {
151 return false;
152 }
153 if (path[1] == ':') {
154 if (path[2] == '/')
155 return true;
156 if (path[2] == '\\')
157 return true;
158 }
159 return false;
160}
161
162pub fn isAbsolutePosix(path: []const u8) bool {
163 return path[0] == sep_posix;
164}
165
166test "isAbsoluteWindows" {
167 testIsAbsoluteWindows("/", true);
168 testIsAbsoluteWindows("//", true);
169 testIsAbsoluteWindows("//server", true);
170 testIsAbsoluteWindows("//server/file", true);
171 testIsAbsoluteWindows("\\\\server\\file", true);
172 testIsAbsoluteWindows("\\\\server", true);
173 testIsAbsoluteWindows("\\\\", true);
174 testIsAbsoluteWindows("c", false);
175 testIsAbsoluteWindows("c:", false);
176 testIsAbsoluteWindows("c:\\", true);
177 testIsAbsoluteWindows("c:/", true);
178 testIsAbsoluteWindows("c://", true);
179 testIsAbsoluteWindows("C:/Users/", true);
180 testIsAbsoluteWindows("C:\\Users\\", true);
181 testIsAbsoluteWindows("C:cwd/another", false);
182 testIsAbsoluteWindows("C:cwd\\another", false);
183 testIsAbsoluteWindows("directory/directory", false);
184 testIsAbsoluteWindows("directory\\directory", false);
185 testIsAbsoluteWindows("/usr/local", true);
186}
187
188test "isAbsolutePosix" {
189 testIsAbsolutePosix("/home/foo", true);
190 testIsAbsolutePosix("/home/foo/..", true);
191 testIsAbsolutePosix("bar/", false);
192 testIsAbsolutePosix("./baz", false);
193}
194
195fn testIsAbsoluteWindows(path: []const u8, expected_result: bool) void {
196 testing.expectEqual(expected_result, isAbsoluteWindows(path));
197}
198
199fn testIsAbsolutePosix(path: []const u8, expected_result: bool) void {
200 testing.expectEqual(expected_result, isAbsolutePosix(path));
201}
202
203pub const WindowsPath = struct {
204 is_abs: bool,
205 kind: Kind,
206 disk_designator: []const u8,
207
208 pub const Kind = enum {
209 None,
210 Drive,
211 NetworkShare,
212 };
213};
214
215pub fn windowsParsePath(path: []const u8) WindowsPath {
216 if (path.len >= 2 and path[1] == ':') {
217 return WindowsPath{
218 .is_abs = isAbsoluteWindows(path),
219 .kind = WindowsPath.Kind.Drive,
220 .disk_designator = path[0..2],
221 };
222 }
223 if (path.len >= 1 and (path[0] == '/' or path[0] == '\\') and
224 (path.len == 1 or (path[1] != '/' and path[1] != '\\')))
225 {
226 return WindowsPath{
227 .is_abs = true,
228 .kind = WindowsPath.Kind.None,
229 .disk_designator = path[0..0],
230 };
231 }
232 const relative_path = WindowsPath{
233 .kind = WindowsPath.Kind.None,
234 .disk_designator = []u8{},
235 .is_abs = false,
236 };
237 if (path.len < "//a/b".len) {
238 return relative_path;
239 }
240
241 // TODO when I combined these together with `inline for` the compiler crashed
242 {
243 const this_sep = '/';
244 const two_sep = []u8{ this_sep, this_sep };
245 if (mem.startsWith(u8, path, two_sep)) {
246 if (path[2] == this_sep) {
247 return relative_path;
248 }
249
250 var it = mem.tokenize(path, []u8{this_sep});
251 _ = (it.next() orelse return relative_path);
252 _ = (it.next() orelse return relative_path);
253 return WindowsPath{
254 .is_abs = isAbsoluteWindows(path),
255 .kind = WindowsPath.Kind.NetworkShare,
256 .disk_designator = path[0..it.index],
257 };
258 }
259 }
260 {
261 const this_sep = '\\';
262 const two_sep = []u8{ this_sep, this_sep };
263 if (mem.startsWith(u8, path, two_sep)) {
264 if (path[2] == this_sep) {
265 return relative_path;
266 }
267
268 var it = mem.tokenize(path, []u8{this_sep});
269 _ = (it.next() orelse return relative_path);
270 _ = (it.next() orelse return relative_path);
271 return WindowsPath{
272 .is_abs = isAbsoluteWindows(path),
273 .kind = WindowsPath.Kind.NetworkShare,
274 .disk_designator = path[0..it.index],
275 };
276 }
277 }
278 return relative_path;
279}
280
281test "windowsParsePath" {
282 {
283 const parsed = windowsParsePath("//a/b");
284 testing.expect(parsed.is_abs);
285 testing.expect(parsed.kind == WindowsPath.Kind.NetworkShare);
286 testing.expect(mem.eql(u8, parsed.disk_designator, "//a/b"));
287 }
288 {
289 const parsed = windowsParsePath("\\\\a\\b");
290 testing.expect(parsed.is_abs);
291 testing.expect(parsed.kind == WindowsPath.Kind.NetworkShare);
292 testing.expect(mem.eql(u8, parsed.disk_designator, "\\\\a\\b"));
293 }
294 {
295 const parsed = windowsParsePath("\\\\a\\");
296 testing.expect(!parsed.is_abs);
297 testing.expect(parsed.kind == WindowsPath.Kind.None);
298 testing.expect(mem.eql(u8, parsed.disk_designator, ""));
299 }
300 {
301 const parsed = windowsParsePath("/usr/local");
302 testing.expect(parsed.is_abs);
303 testing.expect(parsed.kind == WindowsPath.Kind.None);
304 testing.expect(mem.eql(u8, parsed.disk_designator, ""));
305 }
306 {
307 const parsed = windowsParsePath("c:../");
308 testing.expect(!parsed.is_abs);
309 testing.expect(parsed.kind == WindowsPath.Kind.Drive);
310 testing.expect(mem.eql(u8, parsed.disk_designator, "c:"));
311 }
312}
313
314pub fn diskDesignator(path: []const u8) []const u8 {
315 if (is_windows) {
316 return diskDesignatorWindows(path);
317 } else {
318 return "";
319 }
320}
321
322pub fn diskDesignatorWindows(path: []const u8) []const u8 {
323 return windowsParsePath(path).disk_designator;
324}
325
326fn networkShareServersEql(ns1: []const u8, ns2: []const u8) bool {
327 const sep1 = ns1[0];
328 const sep2 = ns2[0];
329
330 var it1 = mem.tokenize(ns1, []u8{sep1});
331 var it2 = mem.tokenize(ns2, []u8{sep2});
332
333 // TODO ASCII is wrong, we actually need full unicode support to compare paths.
334 return asciiEqlIgnoreCase(it1.next().?, it2.next().?);
335}
336
337fn compareDiskDesignators(kind: WindowsPath.Kind, p1: []const u8, p2: []const u8) bool {
338 switch (kind) {
339 WindowsPath.Kind.None => {
340 assert(p1.len == 0);
341 assert(p2.len == 0);
342 return true;
343 },
344 WindowsPath.Kind.Drive => {
345 return asciiUpper(p1[0]) == asciiUpper(p2[0]);
346 },
347 WindowsPath.Kind.NetworkShare => {
348 const sep1 = p1[0];
349 const sep2 = p2[0];
350
351 var it1 = mem.tokenize(p1, []u8{sep1});
352 var it2 = mem.tokenize(p2, []u8{sep2});
353
354 // TODO ASCII is wrong, we actually need full unicode support to compare paths.
355 return asciiEqlIgnoreCase(it1.next().?, it2.next().?) and asciiEqlIgnoreCase(it1.next().?, it2.next().?);
356 },
357 }
358}
359
360fn asciiUpper(byte: u8) u8 {
361 return switch (byte) {
362 'a'...'z' => 'A' + (byte - 'a'),
363 else => byte,
364 };
365}
366
367fn asciiEqlIgnoreCase(s1: []const u8, s2: []const u8) bool {
368 if (s1.len != s2.len)
369 return false;
370 var i: usize = 0;
371 while (i < s1.len) : (i += 1) {
372 if (asciiUpper(s1[i]) != asciiUpper(s2[i]))
373 return false;
374 }
375 return true;
376}
377
378/// On Windows, this calls `resolveWindows` and on POSIX it calls `resolvePosix`.
379pub fn resolve(allocator: *Allocator, paths: []const []const u8) ![]u8 {
380 if (is_windows) {
381 return resolveWindows(allocator, paths);
382 } else {
383 return resolvePosix(allocator, paths);
384 }
385}
386
387/// This function is like a series of `cd` statements executed one after another.
388/// It resolves "." and "..".
389/// The result does not have a trailing path separator.
390/// If all paths are relative it uses the current working directory as a starting point.
391/// Each drive has its own current working directory.
392/// Path separators are canonicalized to '\\' and drives are canonicalized to capital letters.
393/// Note: all usage of this function should be audited due to the existence of symlinks.
394/// Without performing actual syscalls, resolving `..` could be incorrect.
395pub fn resolveWindows(allocator: *Allocator, paths: []const []const u8) ![]u8 {
396 if (paths.len == 0) {
397 assert(is_windows); // resolveWindows called on non windows can't use getCwd
398 return os.getCwdAlloc(allocator);
399 }
400
401 // determine which disk designator we will result with, if any
402 var result_drive_buf = "_:";
403 var result_disk_designator: []const u8 = "";
404 var have_drive_kind = WindowsPath.Kind.None;
405 var have_abs_path = false;
406 var first_index: usize = 0;
407 var max_size: usize = 0;
408 for (paths) |p, i| {
409 const parsed = windowsParsePath(p);
410 if (parsed.is_abs) {
411 have_abs_path = true;
412 first_index = i;
413 max_size = result_disk_designator.len;
414 }
415 switch (parsed.kind) {
416 WindowsPath.Kind.Drive => {
417 result_drive_buf[0] = asciiUpper(parsed.disk_designator[0]);
418 result_disk_designator = result_drive_buf[0..];
419 have_drive_kind = WindowsPath.Kind.Drive;
420 },
421 WindowsPath.Kind.NetworkShare => {
422 result_disk_designator = parsed.disk_designator;
423 have_drive_kind = WindowsPath.Kind.NetworkShare;
424 },
425 WindowsPath.Kind.None => {},
426 }
427 max_size += p.len + 1;
428 }
429
430 // if we will result with a disk designator, loop again to determine
431 // which is the last time the disk designator is absolutely specified, if any
432 // and count up the max bytes for paths related to this disk designator
433 if (have_drive_kind != WindowsPath.Kind.None) {
434 have_abs_path = false;
435 first_index = 0;
436 max_size = result_disk_designator.len;
437 var correct_disk_designator = false;
438
439 for (paths) |p, i| {
440 const parsed = windowsParsePath(p);
441 if (parsed.kind != WindowsPath.Kind.None) {
442 if (parsed.kind == have_drive_kind) {
443 correct_disk_designator = compareDiskDesignators(have_drive_kind, result_disk_designator, parsed.disk_designator);
444 } else {
445 continue;
446 }
447 }
448 if (!correct_disk_designator) {
449 continue;
450 }
451 if (parsed.is_abs) {
452 first_index = i;
453 max_size = result_disk_designator.len;
454 have_abs_path = true;
455 }
456 max_size += p.len + 1;
457 }
458 }
459
460 // Allocate result and fill in the disk designator, calling getCwd if we have to.
461 var result: []u8 = undefined;
462 var result_index: usize = 0;
463
464 if (have_abs_path) {
465 switch (have_drive_kind) {
466 WindowsPath.Kind.Drive => {
467 result = try allocator.alloc(u8, max_size);
468
469 mem.copy(u8, result, result_disk_designator);
470 result_index += result_disk_designator.len;
471 },
472 WindowsPath.Kind.NetworkShare => {
473 result = try allocator.alloc(u8, max_size);
474 var it = mem.tokenize(paths[first_index], "/\\");
475 const server_name = it.next().?;
476 const other_name = it.next().?;
477
478 result[result_index] = '\\';
479 result_index += 1;
480 result[result_index] = '\\';
481 result_index += 1;
482 mem.copy(u8, result[result_index..], server_name);
483 result_index += server_name.len;
484 result[result_index] = '\\';
485 result_index += 1;
486 mem.copy(u8, result[result_index..], other_name);
487 result_index += other_name.len;
488
489 result_disk_designator = result[0..result_index];
490 },
491 WindowsPath.Kind.None => {
492 assert(is_windows); // resolveWindows called on non windows can't use getCwd
493 const cwd = try os.getCwdAlloc(allocator);
494 defer allocator.free(cwd);
495 const parsed_cwd = windowsParsePath(cwd);
496 result = try allocator.alloc(u8, max_size + parsed_cwd.disk_designator.len + 1);
497 mem.copy(u8, result, parsed_cwd.disk_designator);
498 result_index += parsed_cwd.disk_designator.len;
499 result_disk_designator = result[0..parsed_cwd.disk_designator.len];
500 if (parsed_cwd.kind == WindowsPath.Kind.Drive) {
501 result[0] = asciiUpper(result[0]);
502 }
503 have_drive_kind = parsed_cwd.kind;
504 },
505 }
506 } else {
507 assert(is_windows); // resolveWindows called on non windows can't use getCwd
508 // TODO call get cwd for the result_disk_designator instead of the global one
509 const cwd = try os.getCwdAlloc(allocator);
510 defer allocator.free(cwd);
511
512 result = try allocator.alloc(u8, max_size + cwd.len + 1);
513
514 mem.copy(u8, result, cwd);
515 result_index += cwd.len;
516 const parsed_cwd = windowsParsePath(result[0..result_index]);
517 result_disk_designator = parsed_cwd.disk_designator;
518 if (parsed_cwd.kind == WindowsPath.Kind.Drive) {
519 result[0] = asciiUpper(result[0]);
520 }
521 have_drive_kind = parsed_cwd.kind;
522 }
523 errdefer allocator.free(result);
524
525 // Now we know the disk designator to use, if any, and what kind it is. And our result
526 // is big enough to append all the paths to.
527 var correct_disk_designator = true;
528 for (paths[first_index..]) |p, i| {
529 const parsed = windowsParsePath(p);
530
531 if (parsed.kind != WindowsPath.Kind.None) {
532 if (parsed.kind == have_drive_kind) {
533 correct_disk_designator = compareDiskDesignators(have_drive_kind, result_disk_designator, parsed.disk_designator);
534 } else {
535 continue;
536 }
537 }
538 if (!correct_disk_designator) {
539 continue;
540 }
541 var it = mem.tokenize(p[parsed.disk_designator.len..], "/\\");
542 while (it.next()) |component| {
543 if (mem.eql(u8, component, ".")) {
544 continue;
545 } else if (mem.eql(u8, component, "..")) {
546 while (true) {
547 if (result_index == 0 or result_index == result_disk_designator.len)
548 break;
549 result_index -= 1;
550 if (result[result_index] == '\\' or result[result_index] == '/')
551 break;
552 }
553 } else {
554 result[result_index] = sep_windows;
555 result_index += 1;
556 mem.copy(u8, result[result_index..], component);
557 result_index += component.len;
558 }
559 }
560 }
561
562 if (result_index == result_disk_designator.len) {
563 result[result_index] = '\\';
564 result_index += 1;
565 }
566
567 return allocator.shrink(result, result_index);
568}
569
570/// This function is like a series of `cd` statements executed one after another.
571/// It resolves "." and "..".
572/// The result does not have a trailing path separator.
573/// If all paths are relative it uses the current working directory as a starting point.
574/// Note: all usage of this function should be audited due to the existence of symlinks.
575/// Without performing actual syscalls, resolving `..` could be incorrect.
576pub fn resolvePosix(allocator: *Allocator, paths: []const []const u8) ![]u8 {
577 if (paths.len == 0) {
578 assert(!is_windows); // resolvePosix called on windows can't use getCwd
579 return os.getCwdAlloc(allocator);
580 }
581
582 var first_index: usize = 0;
583 var have_abs = false;
584 var max_size: usize = 0;
585 for (paths) |p, i| {
586 if (isAbsolutePosix(p)) {
587 first_index = i;
588 have_abs = true;
589 max_size = 0;
590 }
591 max_size += p.len + 1;
592 }
593
594 var result: []u8 = undefined;
595 var result_index: usize = 0;
596
597 if (have_abs) {
598 result = try allocator.alloc(u8, max_size);
599 } else {
600 assert(!is_windows); // resolvePosix called on windows can't use getCwd
601 const cwd = try os.getCwdAlloc(allocator);
602 defer allocator.free(cwd);
603 result = try allocator.alloc(u8, max_size + cwd.len + 1);
604 mem.copy(u8, result, cwd);
605 result_index += cwd.len;
606 }
607 errdefer allocator.free(result);
608
609 for (paths[first_index..]) |p, i| {
610 var it = mem.tokenize(p, "/");
611 while (it.next()) |component| {
612 if (mem.eql(u8, component, ".")) {
613 continue;
614 } else if (mem.eql(u8, component, "..")) {
615 while (true) {
616 if (result_index == 0)
617 break;
618 result_index -= 1;
619 if (result[result_index] == '/')
620 break;
621 }
622 } else {
623 result[result_index] = '/';
624 result_index += 1;
625 mem.copy(u8, result[result_index..], component);
626 result_index += component.len;
627 }
628 }
629 }
630
631 if (result_index == 0) {
632 result[0] = '/';
633 result_index += 1;
634 }
635
636 return allocator.shrink(result, result_index);
637}
638
639test "resolve" {
640 const cwd = try os.getCwdAlloc(debug.global_allocator);
641 if (is_windows) {
642 if (windowsParsePath(cwd).kind == WindowsPath.Kind.Drive) {
643 cwd[0] = asciiUpper(cwd[0]);
644 }
645 testing.expect(mem.eql(u8, testResolveWindows([][]const u8{"."}), cwd));
646 } else {
647 testing.expect(mem.eql(u8, testResolvePosix([][]const u8{ "a/b/c/", "../../.." }), cwd));
648 testing.expect(mem.eql(u8, testResolvePosix([][]const u8{"."}), cwd));
649 }
650}
651
652test "resolveWindows" {
653 if (is_windows) {
654 const cwd = try os.getCwdAlloc(debug.global_allocator);
655 const parsed_cwd = windowsParsePath(cwd);
656 {
657 const result = testResolveWindows([][]const u8{ "/usr/local", "lib\\zig\\std\\array_list.zig" });
658 const expected = try join(debug.global_allocator, [][]const u8{
659 parsed_cwd.disk_designator,
660 "usr\\local\\lib\\zig\\std\\array_list.zig",
661 });
662 if (parsed_cwd.kind == WindowsPath.Kind.Drive) {
663 expected[0] = asciiUpper(parsed_cwd.disk_designator[0]);
664 }
665 testing.expect(mem.eql(u8, result, expected));
666 }
667 {
668 const result = testResolveWindows([][]const u8{ "usr/local", "lib\\zig" });
669 const expected = try join(debug.global_allocator, [][]const u8{
670 cwd,
671 "usr\\local\\lib\\zig",
672 });
673 if (parsed_cwd.kind == WindowsPath.Kind.Drive) {
674 expected[0] = asciiUpper(parsed_cwd.disk_designator[0]);
675 }
676 testing.expect(mem.eql(u8, result, expected));
677 }
678 }
679
680 testing.expect(mem.eql(u8, testResolveWindows([][]const u8{ "c:\\a\\b\\c", "/hi", "ok" }), "C:\\hi\\ok"));
681 testing.expect(mem.eql(u8, testResolveWindows([][]const u8{ "c:/blah\\blah", "d:/games", "c:../a" }), "C:\\blah\\a"));
682 testing.expect(mem.eql(u8, testResolveWindows([][]const u8{ "c:/blah\\blah", "d:/games", "C:../a" }), "C:\\blah\\a"));
683 testing.expect(mem.eql(u8, testResolveWindows([][]const u8{ "c:/ignore", "d:\\a/b\\c/d", "\\e.exe" }), "D:\\e.exe"));
684 testing.expect(mem.eql(u8, testResolveWindows([][]const u8{ "c:/ignore", "c:/some/file" }), "C:\\some\\file"));
685 testing.expect(mem.eql(u8, testResolveWindows([][]const u8{ "d:/ignore", "d:some/dir//" }), "D:\\ignore\\some\\dir"));
686 testing.expect(mem.eql(u8, testResolveWindows([][]const u8{ "//server/share", "..", "relative\\" }), "\\\\server\\share\\relative"));
687 testing.expect(mem.eql(u8, testResolveWindows([][]const u8{ "c:/", "//" }), "C:\\"));
688 testing.expect(mem.eql(u8, testResolveWindows([][]const u8{ "c:/", "//dir" }), "C:\\dir"));
689 testing.expect(mem.eql(u8, testResolveWindows([][]const u8{ "c:/", "//server/share" }), "\\\\server\\share\\"));
690 testing.expect(mem.eql(u8, testResolveWindows([][]const u8{ "c:/", "//server//share" }), "\\\\server\\share\\"));
691 testing.expect(mem.eql(u8, testResolveWindows([][]const u8{ "c:/", "///some//dir" }), "C:\\some\\dir"));
692 testing.expect(mem.eql(u8, testResolveWindows([][]const u8{ "C:\\foo\\tmp.3\\", "..\\tmp.3\\cycles\\root.js" }), "C:\\foo\\tmp.3\\cycles\\root.js"));
693}
694
695test "resolvePosix" {
696 testing.expect(mem.eql(u8, testResolvePosix([][]const u8{ "/a/b", "c" }), "/a/b/c"));
697 testing.expect(mem.eql(u8, testResolvePosix([][]const u8{ "/a/b", "c", "//d", "e///" }), "/d/e"));
698 testing.expect(mem.eql(u8, testResolvePosix([][]const u8{ "/a/b/c", "..", "../" }), "/a"));
699 testing.expect(mem.eql(u8, testResolvePosix([][]const u8{ "/", "..", ".." }), "/"));
700 testing.expect(mem.eql(u8, testResolvePosix([][]const u8{"/a/b/c/"}), "/a/b/c"));
701
702 testing.expect(mem.eql(u8, testResolvePosix([][]const u8{ "/var/lib", "../", "file/" }), "/var/file"));
703 testing.expect(mem.eql(u8, testResolvePosix([][]const u8{ "/var/lib", "/../", "file/" }), "/file"));
704 testing.expect(mem.eql(u8, testResolvePosix([][]const u8{ "/some/dir", ".", "/absolute/" }), "/absolute"));
705 testing.expect(mem.eql(u8, testResolvePosix([][]const u8{ "/foo/tmp.3/", "../tmp.3/cycles/root.js" }), "/foo/tmp.3/cycles/root.js"));
706}
707
708fn testResolveWindows(paths: []const []const u8) []u8 {
709 return resolveWindows(debug.global_allocator, paths) catch unreachable;
710}
711
712fn testResolvePosix(paths: []const []const u8) []u8 {
713 return resolvePosix(debug.global_allocator, paths) catch unreachable;
714}
715
716/// If the path is a file in the current directory (no directory component)
717/// then returns null
718pub fn dirname(path: []const u8) ?[]const u8 {
719 if (is_windows) {
720 return dirnameWindows(path);
721 } else {
722 return dirnamePosix(path);
723 }
724}
725
726pub fn dirnameWindows(path: []const u8) ?[]const u8 {
727 if (path.len == 0)
728 return null;
729
730 const root_slice = diskDesignatorWindows(path);
731 if (path.len == root_slice.len)
732 return path;
733
734 const have_root_slash = path.len > root_slice.len and (path[root_slice.len] == '/' or path[root_slice.len] == '\\');
735
736 var end_index: usize = path.len - 1;
737
738 while ((path[end_index] == '/' or path[end_index] == '\\') and end_index > root_slice.len) {
739 if (end_index == 0)
740 return null;
741 end_index -= 1;
742 }
743
744 while (path[end_index] != '/' and path[end_index] != '\\' and end_index > root_slice.len) {
745 if (end_index == 0)
746 return null;
747 end_index -= 1;
748 }
749
750 if (have_root_slash and end_index == root_slice.len) {
751 end_index += 1;
752 }
753
754 if (end_index == 0)
755 return null;
756
757 return path[0..end_index];
758}
759
760pub fn dirnamePosix(path: []const u8) ?[]const u8 {
761 if (path.len == 0)
762 return null;
763
764 var end_index: usize = path.len - 1;
765 while (path[end_index] == '/') {
766 if (end_index == 0)
767 return path[0..1];
768 end_index -= 1;
769 }
770
771 while (path[end_index] != '/') {
772 if (end_index == 0)
773 return null;
774 end_index -= 1;
775 }
776
777 if (end_index == 0 and path[end_index] == '/')
778 return path[0..1];
779
780 if (end_index == 0)
781 return null;
782
783 return path[0..end_index];
784}
785
786test "dirnamePosix" {
787 testDirnamePosix("/a/b/c", "/a/b");
788 testDirnamePosix("/a/b/c///", "/a/b");
789 testDirnamePosix("/a", "/");
790 testDirnamePosix("/", "/");
791 testDirnamePosix("////", "/");
792 testDirnamePosix("", null);
793 testDirnamePosix("a", null);
794 testDirnamePosix("a/", null);
795 testDirnamePosix("a//", null);
796}
797
798test "dirnameWindows" {
799 testDirnameWindows("c:\\", "c:\\");
800 testDirnameWindows("c:\\foo", "c:\\");
801 testDirnameWindows("c:\\foo\\", "c:\\");
802 testDirnameWindows("c:\\foo\\bar", "c:\\foo");
803 testDirnameWindows("c:\\foo\\bar\\", "c:\\foo");
804 testDirnameWindows("c:\\foo\\bar\\baz", "c:\\foo\\bar");
805 testDirnameWindows("\\", "\\");
806 testDirnameWindows("\\foo", "\\");
807 testDirnameWindows("\\foo\\", "\\");
808 testDirnameWindows("\\foo\\bar", "\\foo");
809 testDirnameWindows("\\foo\\bar\\", "\\foo");
810 testDirnameWindows("\\foo\\bar\\baz", "\\foo\\bar");
811 testDirnameWindows("c:", "c:");
812 testDirnameWindows("c:foo", "c:");
813 testDirnameWindows("c:foo\\", "c:");
814 testDirnameWindows("c:foo\\bar", "c:foo");
815 testDirnameWindows("c:foo\\bar\\", "c:foo");
816 testDirnameWindows("c:foo\\bar\\baz", "c:foo\\bar");
817 testDirnameWindows("file:stream", null);
818 testDirnameWindows("dir\\file:stream", "dir");
819 testDirnameWindows("\\\\unc\\share", "\\\\unc\\share");
820 testDirnameWindows("\\\\unc\\share\\foo", "\\\\unc\\share\\");
821 testDirnameWindows("\\\\unc\\share\\foo\\", "\\\\unc\\share\\");
822 testDirnameWindows("\\\\unc\\share\\foo\\bar", "\\\\unc\\share\\foo");
823 testDirnameWindows("\\\\unc\\share\\foo\\bar\\", "\\\\unc\\share\\foo");
824 testDirnameWindows("\\\\unc\\share\\foo\\bar\\baz", "\\\\unc\\share\\foo\\bar");
825 testDirnameWindows("/a/b/", "/a");
826 testDirnameWindows("/a/b", "/a");
827 testDirnameWindows("/a", "/");
828 testDirnameWindows("", null);
829 testDirnameWindows("/", "/");
830 testDirnameWindows("////", "/");
831 testDirnameWindows("foo", null);
832}
833
834fn testDirnamePosix(input: []const u8, expected_output: ?[]const u8) void {
835 if (dirnamePosix(input)) |output| {
836 testing.expect(mem.eql(u8, output, expected_output.?));
837 } else {
838 testing.expect(expected_output == null);
839 }
840}
841
842fn testDirnameWindows(input: []const u8, expected_output: ?[]const u8) void {
843 if (dirnameWindows(input)) |output| {
844 testing.expect(mem.eql(u8, output, expected_output.?));
845 } else {
846 testing.expect(expected_output == null);
847 }
848}
849
850pub fn basename(path: []const u8) []const u8 {
851 if (is_windows) {
852 return basenameWindows(path);
853 } else {
854 return basenamePosix(path);
855 }
856}
857
858pub fn basenamePosix(path: []const u8) []const u8 {
859 if (path.len == 0)
860 return []u8{};
861
862 var end_index: usize = path.len - 1;
863 while (path[end_index] == '/') {
864 if (end_index == 0)
865 return []u8{};
866 end_index -= 1;
867 }
868 var start_index: usize = end_index;
869 end_index += 1;
870 while (path[start_index] != '/') {
871 if (start_index == 0)
872 return path[0..end_index];
873 start_index -= 1;
874 }
875
876 return path[start_index + 1 .. end_index];
877}
878
879pub fn basenameWindows(path: []const u8) []const u8 {
880 if (path.len == 0)
881 return []u8{};
882
883 var end_index: usize = path.len - 1;
884 while (true) {
885 const byte = path[end_index];
886 if (byte == '/' or byte == '\\') {
887 if (end_index == 0)
888 return []u8{};
889 end_index -= 1;
890 continue;
891 }
892 if (byte == ':' and end_index == 1) {
893 return []u8{};
894 }
895 break;
896 }
897
898 var start_index: usize = end_index;
899 end_index += 1;
900 while (path[start_index] != '/' and path[start_index] != '\\' and
901 !(path[start_index] == ':' and start_index == 1))
902 {
903 if (start_index == 0)
904 return path[0..end_index];
905 start_index -= 1;
906 }
907
908 return path[start_index + 1 .. end_index];
909}
910
911test "basename" {
912 testBasename("", "");
913 testBasename("/", "");
914 testBasename("/dir/basename.ext", "basename.ext");
915 testBasename("/basename.ext", "basename.ext");
916 testBasename("basename.ext", "basename.ext");
917 testBasename("basename.ext/", "basename.ext");
918 testBasename("basename.ext//", "basename.ext");
919 testBasename("/aaa/bbb", "bbb");
920 testBasename("/aaa/", "aaa");
921 testBasename("/aaa/b", "b");
922 testBasename("/a/b", "b");
923 testBasename("//a", "a");
924
925 testBasenamePosix("\\dir\\basename.ext", "\\dir\\basename.ext");
926 testBasenamePosix("\\basename.ext", "\\basename.ext");
927 testBasenamePosix("basename.ext", "basename.ext");
928 testBasenamePosix("basename.ext\\", "basename.ext\\");
929 testBasenamePosix("basename.ext\\\\", "basename.ext\\\\");
930 testBasenamePosix("foo", "foo");
931
932 testBasenameWindows("\\dir\\basename.ext", "basename.ext");
933 testBasenameWindows("\\basename.ext", "basename.ext");
934 testBasenameWindows("basename.ext", "basename.ext");
935 testBasenameWindows("basename.ext\\", "basename.ext");
936 testBasenameWindows("basename.ext\\\\", "basename.ext");
937 testBasenameWindows("foo", "foo");
938 testBasenameWindows("C:", "");
939 testBasenameWindows("C:.", ".");
940 testBasenameWindows("C:\\", "");
941 testBasenameWindows("C:\\dir\\base.ext", "base.ext");
942 testBasenameWindows("C:\\basename.ext", "basename.ext");
943 testBasenameWindows("C:basename.ext", "basename.ext");
944 testBasenameWindows("C:basename.ext\\", "basename.ext");
945 testBasenameWindows("C:basename.ext\\\\", "basename.ext");
946 testBasenameWindows("C:foo", "foo");
947 testBasenameWindows("file:stream", "file:stream");
948}
949
950fn testBasename(input: []const u8, expected_output: []const u8) void {
951 testing.expectEqualSlices(u8, expected_output, basename(input));
952}
953
954fn testBasenamePosix(input: []const u8, expected_output: []const u8) void {
955 testing.expectEqualSlices(u8, expected_output, basenamePosix(input));
956}
957
958fn testBasenameWindows(input: []const u8, expected_output: []const u8) void {
959 testing.expectEqualSlices(u8, expected_output, basenameWindows(input));
960}
961
962/// Returns the relative path from `from` to `to`. If `from` and `to` each
963/// resolve to the same path (after calling `resolve` on each), a zero-length
964/// string is returned.
965/// On Windows this canonicalizes the drive to a capital letter and paths to `\\`.
966pub fn relative(allocator: *Allocator, from: []const u8, to: []const u8) ![]u8 {
967 if (is_windows) {
968 return relativeWindows(allocator, from, to);
969 } else {
970 return relativePosix(allocator, from, to);
971 }
972}
973
974pub fn relativeWindows(allocator: *Allocator, from: []const u8, to: []const u8) ![]u8 {
975 const resolved_from = try resolveWindows(allocator, [][]const u8{from});
976 defer allocator.free(resolved_from);
977
978 var clean_up_resolved_to = true;
979 const resolved_to = try resolveWindows(allocator, [][]const u8{to});
980 defer if (clean_up_resolved_to) allocator.free(resolved_to);
981
982 const parsed_from = windowsParsePath(resolved_from);
983 const parsed_to = windowsParsePath(resolved_to);
984 const result_is_to = x: {
985 if (parsed_from.kind != parsed_to.kind) {
986 break :x true;
987 } else switch (parsed_from.kind) {
988 WindowsPath.Kind.NetworkShare => {
989 break :x !networkShareServersEql(parsed_to.disk_designator, parsed_from.disk_designator);
990 },
991 WindowsPath.Kind.Drive => {
992 break :x asciiUpper(parsed_from.disk_designator[0]) != asciiUpper(parsed_to.disk_designator[0]);
993 },
994 else => unreachable,
995 }
996 };
997
998 if (result_is_to) {
999 clean_up_resolved_to = false;
1000 return resolved_to;
1001 }
1002
1003 var from_it = mem.tokenize(resolved_from, "/\\");
1004 var to_it = mem.tokenize(resolved_to, "/\\");
1005 while (true) {
1006 const from_component = from_it.next() orelse return mem.dupe(allocator, u8, to_it.rest());
1007 const to_rest = to_it.rest();
1008 if (to_it.next()) |to_component| {
1009 // TODO ASCII is wrong, we actually need full unicode support to compare paths.
1010 if (asciiEqlIgnoreCase(from_component, to_component))
1011 continue;
1012 }
1013 var up_count: usize = 1;
1014 while (from_it.next()) |_| {
1015 up_count += 1;
1016 }
1017 const up_index_end = up_count * "..\\".len;
1018 const result = try allocator.alloc(u8, up_index_end + to_rest.len);
1019 errdefer allocator.free(result);
1020
1021 var result_index: usize = 0;
1022 while (result_index < up_index_end) {
1023 result[result_index] = '.';
1024 result_index += 1;
1025 result[result_index] = '.';
1026 result_index += 1;
1027 result[result_index] = '\\';
1028 result_index += 1;
1029 }
1030 // shave off the trailing slash
1031 result_index -= 1;
1032
1033 var rest_it = mem.tokenize(to_rest, "/\\");
1034 while (rest_it.next()) |to_component| {
1035 result[result_index] = '\\';
1036 result_index += 1;
1037 mem.copy(u8, result[result_index..], to_component);
1038 result_index += to_component.len;
1039 }
1040
1041 return result[0..result_index];
1042 }
1043
1044 return []u8{};
1045}
1046
1047pub fn relativePosix(allocator: *Allocator, from: []const u8, to: []const u8) ![]u8 {
1048 const resolved_from = try resolvePosix(allocator, [][]const u8{from});
1049 defer allocator.free(resolved_from);
1050
1051 const resolved_to = try resolvePosix(allocator, [][]const u8{to});
1052 defer allocator.free(resolved_to);
1053
1054 var from_it = mem.tokenize(resolved_from, "/");
1055 var to_it = mem.tokenize(resolved_to, "/");
1056 while (true) {
1057 const from_component = from_it.next() orelse return mem.dupe(allocator, u8, to_it.rest());
1058 const to_rest = to_it.rest();
1059 if (to_it.next()) |to_component| {
1060 if (mem.eql(u8, from_component, to_component))
1061 continue;
1062 }
1063 var up_count: usize = 1;
1064 while (from_it.next()) |_| {
1065 up_count += 1;
1066 }
1067 const up_index_end = up_count * "../".len;
1068 const result = try allocator.alloc(u8, up_index_end + to_rest.len);
1069 errdefer allocator.free(result);
1070
1071 var result_index: usize = 0;
1072 while (result_index < up_index_end) {
1073 result[result_index] = '.';
1074 result_index += 1;
1075 result[result_index] = '.';
1076 result_index += 1;
1077 result[result_index] = '/';
1078 result_index += 1;
1079 }
1080 if (to_rest.len == 0) {
1081 // shave off the trailing slash
1082 return result[0 .. result_index - 1];
1083 }
1084
1085 mem.copy(u8, result[result_index..], to_rest);
1086 return result;
1087 }
1088
1089 return []u8{};
1090}
1091
1092test "relative" {
1093 testRelativeWindows("c:/blah\\blah", "d:/games", "D:\\games");
1094 testRelativeWindows("c:/aaaa/bbbb", "c:/aaaa", "..");
1095 testRelativeWindows("c:/aaaa/bbbb", "c:/cccc", "..\\..\\cccc");
1096 testRelativeWindows("c:/aaaa/bbbb", "c:/aaaa/bbbb", "");
1097 testRelativeWindows("c:/aaaa/bbbb", "c:/aaaa/cccc", "..\\cccc");
1098 testRelativeWindows("c:/aaaa/", "c:/aaaa/cccc", "cccc");
1099 testRelativeWindows("c:/", "c:\\aaaa\\bbbb", "aaaa\\bbbb");
1100 testRelativeWindows("c:/aaaa/bbbb", "d:\\", "D:\\");
1101 testRelativeWindows("c:/AaAa/bbbb", "c:/aaaa/bbbb", "");
1102 testRelativeWindows("c:/aaaaa/", "c:/aaaa/cccc", "..\\aaaa\\cccc");
1103 testRelativeWindows("C:\\foo\\bar\\baz\\quux", "C:\\", "..\\..\\..\\..");
1104 testRelativeWindows("C:\\foo\\test", "C:\\foo\\test\\bar\\package.json", "bar\\package.json");
1105 testRelativeWindows("C:\\foo\\bar\\baz-quux", "C:\\foo\\bar\\baz", "..\\baz");
1106 testRelativeWindows("C:\\foo\\bar\\baz", "C:\\foo\\bar\\baz-quux", "..\\baz-quux");
1107 testRelativeWindows("\\\\foo\\bar", "\\\\foo\\bar\\baz", "baz");
1108 testRelativeWindows("\\\\foo\\bar\\baz", "\\\\foo\\bar", "..");
1109 testRelativeWindows("\\\\foo\\bar\\baz-quux", "\\\\foo\\bar\\baz", "..\\baz");
1110 testRelativeWindows("\\\\foo\\bar\\baz", "\\\\foo\\bar\\baz-quux", "..\\baz-quux");
1111 testRelativeWindows("C:\\baz-quux", "C:\\baz", "..\\baz");
1112 testRelativeWindows("C:\\baz", "C:\\baz-quux", "..\\baz-quux");
1113 testRelativeWindows("\\\\foo\\baz-quux", "\\\\foo\\baz", "..\\baz");
1114 testRelativeWindows("\\\\foo\\baz", "\\\\foo\\baz-quux", "..\\baz-quux");
1115 testRelativeWindows("C:\\baz", "\\\\foo\\bar\\baz", "\\\\foo\\bar\\baz");
1116 testRelativeWindows("\\\\foo\\bar\\baz", "C:\\baz", "C:\\baz");
1117
1118 testRelativePosix("/var/lib", "/var", "..");
1119 testRelativePosix("/var/lib", "/bin", "../../bin");
1120 testRelativePosix("/var/lib", "/var/lib", "");
1121 testRelativePosix("/var/lib", "/var/apache", "../apache");
1122 testRelativePosix("/var/", "/var/lib", "lib");
1123 testRelativePosix("/", "/var/lib", "var/lib");
1124 testRelativePosix("/foo/test", "/foo/test/bar/package.json", "bar/package.json");
1125 testRelativePosix("/Users/a/web/b/test/mails", "/Users/a/web/b", "../..");
1126 testRelativePosix("/foo/bar/baz-quux", "/foo/bar/baz", "../baz");
1127 testRelativePosix("/foo/bar/baz", "/foo/bar/baz-quux", "../baz-quux");
1128 testRelativePosix("/baz-quux", "/baz", "../baz");
1129 testRelativePosix("/baz", "/baz-quux", "../baz-quux");
1130}
1131
1132fn testRelativePosix(from: []const u8, to: []const u8, expected_output: []const u8) void {
1133 const result = relativePosix(debug.global_allocator, from, to) catch unreachable;
1134 testing.expectEqualSlices(u8, expected_output, result);
1135}
1136
1137fn testRelativeWindows(from: []const u8, to: []const u8, expected_output: []const u8) void {
1138 const result = relativeWindows(debug.global_allocator, from, to) catch unreachable;
1139 testing.expectEqualSlices(u8, expected_output, result);
1140}
std/io.zig+3-3
......@@ -49,7 +49,7 @@ pub fn InStream(comptime ReadError: type) type {
4949 return;
5050 }
5151
52 const new_buf_size = math.min(max_size, actual_buf_len + os.page_size);
52 const new_buf_size = math.min(max_size, actual_buf_len + mem.page_size);
5353 if (new_buf_size == actual_buf_len) return error.StreamTooLong;
5454 try buffer.resize(new_buf_size);
5555 }
......@@ -284,7 +284,7 @@ pub fn readFileAllocAligned(allocator: *mem.Allocator, path: []const u8, comptim
284284}
285285
286286pub fn BufferedInStream(comptime Error: type) type {
287 return BufferedInStreamCustom(os.page_size, Error);
287 return BufferedInStreamCustom(mem.page_size, Error);
288288}
289289
290290pub fn BufferedInStreamCustom(comptime buffer_size: usize, comptime Error: type) type {
......@@ -757,7 +757,7 @@ test "io.CountingOutStream" {
757757}
758758
759759pub fn BufferedOutStream(comptime Error: type) type {
760 return BufferedOutStreamCustom(os.page_size, Error);
760 return BufferedOutStreamCustom(mem.page_size, Error);
761761}
762762
763763pub fn BufferedOutStreamCustom(comptime buffer_size: usize, comptime OutStreamError: type) type {
std/io/seekable_stream.zig+5-5
......@@ -8,7 +8,7 @@ pub fn SeekableStream(comptime SeekErrorType: type, comptime GetSeekPosErrorType
88 pub const GetSeekPosError = GetSeekPosErrorType;
99
1010 seekToFn: fn (self: *Self, pos: u64) SeekError!void,
11 seekForwardFn: fn (self: *Self, pos: i64) SeekError!void,
11 seekByFn: fn (self: *Self, pos: i64) SeekError!void,
1212
1313 getPosFn: fn (self: *Self) GetSeekPosError!u64,
1414 getEndPosFn: fn (self: *Self) GetSeekPosError!u64,
......@@ -17,8 +17,8 @@ pub fn SeekableStream(comptime SeekErrorType: type, comptime GetSeekPosErrorType
1717 return self.seekToFn(self, pos);
1818 }
1919
20 pub fn seekForward(self: *Self, amt: i64) SeekError!void {
21 return self.seekForwardFn(self, amt);
20 pub fn seekBy(self: *Self, amt: i64) SeekError!void {
21 return self.seekByFn(self, amt);
2222 }
2323
2424 pub fn getEndPos(self: *Self) GetSeekPosError!u64 {
......@@ -52,7 +52,7 @@ pub const SliceSeekableInStream = struct {
5252 .stream = Stream{ .readFn = readFn },
5353 .seekable_stream = SeekableInStream{
5454 .seekToFn = seekToFn,
55 .seekForwardFn = seekForwardFn,
55 .seekByFn = seekByFn,
5656 .getEndPosFn = getEndPosFn,
5757 .getPosFn = getPosFn,
5858 },
......@@ -77,7 +77,7 @@ pub const SliceSeekableInStream = struct {
7777 self.pos = usize_pos;
7878 }
7979
80 fn seekForwardFn(in_stream: *SeekableInStream, amt: i64) SeekError!void {
80 fn seekByFn(in_stream: *SeekableInStream, amt: i64) SeekError!void {
8181 const self = @fieldParentPtr(Self, "seekable_stream", in_stream);
8282
8383 if (amt < 0) {
std/mem.zig+23
......@@ -8,6 +8,11 @@ const meta = std.meta;
88const trait = meta.trait;
99const testing = std.testing;
1010
11pub const page_size = switch (builtin.arch) {
12 .wasm32, .wasm64 => 64 * 1024,
13 else => 4 * 1024,
14};
15
1116pub const Allocator = struct {
1217 pub const Error = error{OutOfMemory};
1318
......@@ -1457,3 +1462,21 @@ test "std.mem.alignForward" {
14571462 testing.expect(alignForward(16, 8) == 16);
14581463 testing.expect(alignForward(17, 8) == 24);
14591464}
1465
1466pub fn getBaseAddress() usize {
1467 switch (builtin.os) {
1468 .linux => {
1469 const base = std.os.posix.getauxval(std.elf.AT_BASE);
1470 if (base != 0) {
1471 return base;
1472 }
1473 const phdr = std.os.posix.getauxval(std.elf.AT_PHDR);
1474 return phdr - @sizeOf(std.elf.Ehdr);
1475 },
1476 .macosx, .freebsd, .netbsd => {
1477 return @ptrToInt(&std.c._mh_execute_header);
1478 },
1479 .windows => return @ptrToInt(windows.GetModuleHandleW(null)),
1480 else => @compileError("Unsupported OS"),
1481 }
1482}
std/os.zig+2096-1554
......@@ -1,544 +1,449 @@
1// This file contains thin wrappers around OS-specific APIs, with these
2// specific goals in mind:
3// * Convert "errno"-style error codes into Zig errors.
4// * When null-terminated byte buffers are required, provide APIs which accept
5// slices as well as APIs which accept null-terminated byte buffers. Same goes
6// for UTF-16LE encoding.
7// * Where operating systems share APIs, e.g. POSIX, these thin wrappers provide
8// cross platform abstracting.
9// * When there exists a corresponding libc function and linking libc, the libc
10// implementation is used. Exceptions are made for known buggy areas of libc.
11// On Linux libc can be side-stepped by using `std.os.linux.sys`.
12// * For Windows, this file represents the API that libc would provide for
13// Windows. For thin wrappers around Windows-specific APIs, see `std.os.windows`.
14// Note: The Zig standard library does not support POSIX thread cancellation, and
15// in general EINTR is handled by trying again.
16
117const std = @import("std.zig");
218const builtin = @import("builtin");
3const Os = builtin.Os;
4const is_windows = builtin.os == Os.windows;
5const os = @This();
19const MAX_PATH_BYTES = std.fs.MAX_PATH_BYTES;
620
721comptime {
822 assert(@import("std") == std); // You have to run the std lib tests with --override-std-dir
923}
1024
11test "std.os" {
12 _ = @import("os/child_process.zig");
13 _ = @import("os/darwin.zig");
14 _ = @import("os/get_user_id.zig");
15 _ = @import("os/linux.zig");
16 _ = @import("os/path.zig");
17 _ = @import("os/test.zig");
18 _ = @import("os/time.zig");
19 _ = @import("os/windows.zig");
20 _ = @import("os/uefi.zig");
21 _ = @import("os/wasi.zig");
22 _ = @import("os/get_app_data_dir.zig");
23}
24
25pub const windows = @import("os/windows.zig");
2625pub const darwin = @import("os/darwin.zig");
27pub const linux = @import("os/linux.zig");
2826pub const freebsd = @import("os/freebsd.zig");
27pub const linux = @import("os/linux.zig");
2928pub const netbsd = @import("os/netbsd.zig");
30pub const zen = @import("os/zen.zig");
3129pub const uefi = @import("os/uefi.zig");
3230pub const wasi = @import("os/wasi.zig");
31pub const windows = @import("os/windows.zig");
32pub const zen = @import("os/zen.zig");
3333
34/// When linking libc, this is the C API. Otherwise, it is the OS-specific system interface.
3435pub const system = if (builtin.link_libc) std.c else switch (builtin.os) {
35 .linux => linux,
3636 .macosx, .ios, .watchos, .tvos => darwin,
3737 .freebsd => freebsd,
38 .linux => linux,
3839 .netbsd => netbsd,
39 .zen => zen,
4040 .wasi => wasi,
4141 .windows => windows,
42 .zen => zen,
4243 else => struct {},
4344};
4445
45pub const net = @import("net.zig");
46
47pub const ChildProcess = @import("os/child_process.zig").ChildProcess;
48pub const path = @import("os/path.zig");
49pub const File = @import("os/file.zig").File;
50pub const time = @import("os/time.zig");
51
52pub const page_size = switch (builtin.arch) {
53 .wasm32, .wasm64 => 64 * 1024,
54 else => 4 * 1024,
55};
56
57pub const unexpected_error_tracing = builtin.mode == .Debug;
58pub const UnexpectedError = error{
59 /// The Operating System returned an undocumented error code.
60 Unexpected,
61};
62
63/// This represents the maximum size of a UTF-8 encoded file path.
64/// All file system operations which return a path are guaranteed to
65/// fit into a UTF-8 encoded array of this length.
66/// path being too long if it is this 0long
67pub const MAX_PATH_BYTES = switch (builtin.os) {
68 .linux, .macosx, .ios, .freebsd, .netbsd => posix.PATH_MAX,
69 // Each UTF-16LE character may be expanded to 3 UTF-8 bytes.
70 // If it would require 4 UTF-8 bytes, then there would be a surrogate
71 // pair in the UTF-16LE, and we (over)account 3 bytes for it that way.
72 // +1 for the null byte at the end, which can be encoded in 1 byte.
73 .windows => posix.PATH_MAX_WIDE * 3 + 1,
74 else => @compileError("Unsupported OS"),
75};
76
77pub const UserInfo = @import("os/get_user_id.zig").UserInfo;
78pub const getUserInfo = @import("os/get_user_id.zig").getUserInfo;
79
80const windows_util = @import("os/windows/util.zig");
81pub const windowsWaitSingle = windows_util.windowsWaitSingle;
82pub const windowsWrite = windows_util.windowsWrite;
83pub const windowsIsCygwinPty = windows_util.windowsIsCygwinPty;
84pub const windowsOpen = windows_util.windowsOpen;
85pub const windowsOpenW = windows_util.windowsOpenW;
86pub const createWindowsEnvBlock = windows_util.createWindowsEnvBlock;
87
88pub const WindowsCreateIoCompletionPortError = windows_util.WindowsCreateIoCompletionPortError;
89pub const windowsCreateIoCompletionPort = windows_util.windowsCreateIoCompletionPort;
90
91pub const WindowsPostQueuedCompletionStatusError = windows_util.WindowsPostQueuedCompletionStatusError;
92pub const windowsPostQueuedCompletionStatus = windows_util.windowsPostQueuedCompletionStatus;
93
94pub const WindowsWaitResult = windows_util.WindowsWaitResult;
95pub const windowsGetQueuedCompletionStatus = windows_util.windowsGetQueuedCompletionStatus;
96
97pub const WindowsWaitError = windows_util.WaitError;
98pub const WindowsOpenError = windows_util.OpenError;
99pub const WindowsWriteError = windows_util.WriteError;
100pub const WindowsReadError = windows_util.ReadError;
101
102pub const getAppDataDir = @import("os/get_app_data_dir.zig").getAppDataDir;
103pub const GetAppDataDirError = @import("os/get_app_data_dir.zig").GetAppDataDirError;
104
105pub const getRandomBytes = posix.getrandom;
106pub const abort = posix.abort;
107pub const exit = posix.exit;
108pub const symLink = posix.symlink;
109pub const symLinkC = posix.symlinkC;
110pub const symLinkW = posix.symlinkW;
111pub const deleteFile = posix.unlink;
112pub const deleteFileC = posix.unlinkC;
113pub const deleteFileW = posix.unlinkW;
114pub const rename = posix.rename;
115pub const renameC = posix.renameC;
116pub const renameW = posix.renameW;
117pub const changeCurDir = posix.chdir;
118pub const changeCurDirC = posix.chdirC;
119pub const changeCurDirW = posix.chdirW;
120
121const debug = std.debug;
122const assert = debug.assert;
123const testing = std.testing;
124
125const c = std.c;
126
127const mem = std.mem;
128const Allocator = mem.Allocator;
129
130const BufMap = std.BufMap;
131const cstr = std.cstr;
132
133const io = std.io;
134const base64 = std.base64;
135const ArrayList = std.ArrayList;
136const Buffer = std.Buffer;
137const math = std.math;
138
139pub fn getBaseAddress() usize {
140 switch (builtin.os) {
141 builtin.Os.linux => {
142 const base = linuxGetAuxVal(std.elf.AT_BASE);
143 if (base != 0) {
144 return base;
145 }
146 const phdr = linuxGetAuxVal(std.elf.AT_PHDR);
147 return phdr - @sizeOf(std.elf.Ehdr);
148 },
149 builtin.Os.macosx, builtin.Os.freebsd, builtin.Os.netbsd => {
150 return @ptrToInt(&std.c._mh_execute_header);
151 },
152 builtin.Os.windows => return @ptrToInt(windows.GetModuleHandleW(null)),
153 else => @compileError("Unsupported OS"),
46/// See also `getenv`.
47pub var environ: [][*]u8 = undefined;
48
49/// To obtain errno, call this function with the return value of the
50/// system function call. For some systems this will obtain the value directly
51/// from the return code; for others it will use a thread-local errno variable.
52/// Therefore, this function only returns a well-defined value when it is called
53/// directly after the system function call which one wants to learn the errno
54/// value of.
55pub const errno = system.getErrno;
56
57/// Closes the file descriptor.
58/// This function is not capable of returning any indication of failure. An
59/// application which wants to ensure writes have succeeded before closing
60/// must call `fsync` before `close`.
61/// Note: The Zig standard library does not support POSIX thread cancellation.
62pub fn close(fd: fd_t) void {
63 if (windows.is_the_target and !builtin.link_libc) {
64 return windows.CloseHandle(fd);
65 }
66 if (wasi.is_the_target) {
67 switch (wasi.fd_close(fd)) {
68 0 => return,
69 else => |err| return unexpectedErrno(err),
70 }
71 }
72 switch (errno(system.close(fd))) {
73 EBADF => unreachable, // Always a race condition.
74 EINTR => return, // This is still a success. See https://github.com/ziglang/zig/issues/2425
75 else => return,
15476 }
15577}
15678
157/// Caller must free result when done.
158/// TODO make this go through libc when we have it
159pub fn getEnvMap(allocator: *Allocator) !BufMap {
160 var result = BufMap.init(allocator);
161 errdefer result.deinit();
162
163 if (is_windows) {
164 const ptr = windows.GetEnvironmentStringsW() orelse return error.OutOfMemory;
165 defer assert(windows.FreeEnvironmentStringsW(ptr) != 0);
79pub const GetRandomError = error{};
16680
167 var i: usize = 0;
81/// Obtain a series of random bytes. These bytes can be used to seed user-space
82/// random number generators or for cryptographic purposes.
83/// When linking against libc, this calls the
84/// appropriate OS-specific library call. Otherwise it uses the zig standard
85/// library implementation.
86pub fn getrandom(buf: []u8) GetRandomError!void {
87 if (windows.is_the_target) {
88 return windows.RtlGenRandom(buf);
89 }
90 if (linux.is_the_target) {
16891 while (true) {
169 if (ptr[i] == 0) return result;
170
171 const key_start = i;
172
173 while (ptr[i] != 0 and ptr[i] != '=') : (i += 1) {}
174 const key_w = ptr[key_start..i];
175 const key = try std.unicode.utf16leToUtf8Alloc(allocator, key_w);
176 errdefer allocator.free(key);
177
178 if (ptr[i] == '=') i += 1;
179
180 const value_start = i;
181 while (ptr[i] != 0) : (i += 1) {}
182 const value_w = ptr[value_start..i];
183 const value = try std.unicode.utf16leToUtf8Alloc(allocator, value_w);
184 errdefer allocator.free(value);
185
186 i += 1; // skip over null byte
187
188 try result.setMove(key, value);
189 }
190 } else if (builtin.os == Os.wasi) {
191 var environ_count: usize = undefined;
192 var environ_buf_size: usize = undefined;
193
194 const environ_sizes_get_ret = std.os.wasi.environ_sizes_get(&environ_count, &environ_buf_size);
195 if (environ_sizes_get_ret != os.wasi.ESUCCESS) {
196 return unexpectedErrorPosix(environ_sizes_get_ret);
197 }
198
199 // TODO: Verify that the documentation is incorrect
200 // https://github.com/WebAssembly/WASI/issues/27
201 var environ = try allocator.alloc(?[*]u8, environ_count + 1);
202 defer allocator.free(environ);
203 var environ_buf = try std.heap.wasm_allocator.alloc(u8, environ_buf_size);
204 defer allocator.free(environ_buf);
205
206 const environ_get_ret = std.os.wasi.environ_get(environ.ptr, environ_buf.ptr);
207 if (environ_get_ret != os.wasi.ESUCCESS) {
208 return unexpectedErrorPosix(environ_get_ret);
209 }
210
211 for (environ) |env| {
212 if (env) |ptr| {
213 const pair = mem.toSlice(u8, ptr);
214 var parts = mem.separate(pair, "=");
215 const key = parts.next().?;
216 const value = parts.next().?;
217 try result.set(key, value);
92 switch (errno(system.getrandom(buf.ptr, buf.len, 0))) {
93 0 => return,
94 EINVAL => unreachable,
95 EFAULT => unreachable,
96 EINTR => continue,
97 ENOSYS => return getRandomBytesDevURandom(buf),
98 else => |err| return unexpectedErrno(err),
21899 }
219100 }
220 return result;
221 } else {
222 for (posix.environ) |ptr| {
223 var line_i: usize = 0;
224 while (ptr[line_i] != 0 and ptr[line_i] != '=') : (line_i += 1) {}
225 const key = ptr[0..line_i];
226
227 var end_i: usize = line_i;
228 while (ptr[end_i] != 0) : (end_i += 1) {}
229 const value = ptr[line_i + 1 .. end_i];
230
231 try result.set(key, value);
101 }
102 if (wasi.is_the_target) {
103 switch (os.wasi.random_get(buf.ptr, buf.len)) {
104 0 => return,
105 else => |err| return unexpectedErrno(err),
232106 }
233 return result;
234107 }
108 return getRandomBytesDevURandom(buf);
235109}
236110
237test "os.getEnvMap" {
238 var env = try getEnvMap(std.debug.global_allocator);
239 defer env.deinit();
240}
241
242pub const GetEnvVarOwnedError = error{
243 OutOfMemory,
244 EnvironmentVariableNotFound,
245
246 /// See https://github.com/ziglang/zig/issues/1774
247 InvalidUtf8,
248};
249
250/// Caller must free returned memory.
251/// TODO make this go through libc when we have it
252pub fn getEnvVarOwned(allocator: *mem.Allocator, key: []const u8) GetEnvVarOwnedError![]u8 {
253 if (is_windows) {
254 const key_with_null = try std.unicode.utf8ToUtf16LeWithNull(allocator, key);
255 defer allocator.free(key_with_null);
256
257 var buf = try allocator.alloc(u16, 256);
258 defer allocator.free(buf);
259
260 while (true) {
261 const windows_buf_len = math.cast(windows.DWORD, buf.len) catch return error.OutOfMemory;
262 const result = windows.GetEnvironmentVariableW(key_with_null.ptr, buf.ptr, windows_buf_len);
263
264 if (result == 0) {
265 const err = windows.GetLastError();
266 return switch (err) {
267 windows.ERROR.ENVVAR_NOT_FOUND => error.EnvironmentVariableNotFound,
268 else => {
269 windows.unexpectedError(err) catch {};
270 return error.EnvironmentVariableNotFound;
271 },
272 };
273 }
111fn getRandomBytesDevURandom(buf: []u8) !void {
112 const fd = try openC(c"/dev/urandom", O_RDONLY | O_CLOEXEC, 0);
113 defer close(fd);
274114
275 if (result > buf.len) {
276 buf = try allocator.realloc(buf, result);
277 continue;
278 }
115 const stream = &os.File.openHandle(fd).inStream().stream;
116 stream.readNoEof(buf) catch return error.Unexpected;
117}
279118
280 return std.unicode.utf16leToUtf8Alloc(allocator, buf) catch |err| switch (err) {
281 error.DanglingSurrogateHalf => return error.InvalidUtf8,
282 error.ExpectedSecondSurrogateHalf => return error.InvalidUtf8,
283 error.UnexpectedSecondSurrogateHalf => return error.InvalidUtf8,
284 error.OutOfMemory => return error.OutOfMemory,
285 };
119/// Causes abnormal process termination.
120/// If linking against libc, this calls the abort() libc function. Otherwise
121/// it raises SIGABRT followed by SIGKILL and finally lo
122pub fn abort() noreturn {
123 @setCold(true);
124 if (builtin.link_libc) {
125 system.abort();
126 }
127 if (windows.is_the_target) {
128 if (builtin.mode == .Debug) {
129 @breakpoint();
286130 }
287 } else {
288 const result = getEnvPosix(key) orelse return error.EnvironmentVariableNotFound;
289 return mem.dupe(allocator, u8, result);
131 windows.kernel32.ExitProcess(3);
132 }
133 if (builtin.os == .uefi) {
134 // TODO there must be a better thing to do here than loop forever
135 while (true) {}
290136 }
291}
292
293test "os.getEnvVarOwned" {
294 var ga = debug.global_allocator;
295 testing.expectError(error.EnvironmentVariableNotFound, getEnvVarOwned(ga, "BADENV"));
296}
297137
298/// The result is a slice of `out_buffer`, from index `0`.
299pub fn getCwd(out_buffer: *[MAX_PATH_BYTES]u8) ![]u8 {
300 return posix.getcwd(out_buffer);
301}
138 raise(SIGABRT);
302139
303/// Caller must free the returned memory.
304pub fn getCwdAlloc(allocator: *Allocator) ![]u8 {
305 var buf: [os.MAX_PATH_BYTES]u8 = undefined;
306 return mem.dupe(allocator, u8, try posix.getcwd(&buf));
307}
140 // TODO the rest of the implementation of abort() from musl libc here
308141
309test "getCwdAlloc" {
310 // at least call it so it gets compiled
311 var buf: [1000]u8 = undefined;
312 const allocator = &std.heap.FixedBufferAllocator.init(&buf).allocator;
313 _ = getCwdAlloc(allocator) catch {};
142 raise(SIGKILL);
143 exit(127);
314144}
315145
316// here we replace the standard +/ with -_ so that it can be used in a file name
317const b64_fs_encoder = base64.Base64Encoder.init("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_", base64.standard_pad_char);
146pub const RaiseError = error{};
318147
319/// TODO remove the allocator requirement from this API
320pub fn atomicSymLink(allocator: *Allocator, existing_path: []const u8, new_path: []const u8) !void {
321 if (symLink(existing_path, new_path)) {
322 return;
323 } else |err| switch (err) {
324 error.PathAlreadyExists => {},
325 else => return err, // TODO zig should know this set does not include PathAlreadyExists
148pub fn raise(sig: u8) RaiseError!void {
149 if (builtin.link_libc) {
150 switch (errno(system.raise(sig))) {
151 0 => return,
152 else => |err| return unexpectedErrno(err),
153 }
326154 }
327155
328 const dirname = os.path.dirname(new_path) orelse ".";
329
330 var rand_buf: [12]u8 = undefined;
331 const tmp_path = try allocator.alloc(u8, dirname.len + 1 + base64.Base64Encoder.calcSize(rand_buf.len));
332 defer allocator.free(tmp_path);
333 mem.copy(u8, tmp_path[0..], dirname);
334 tmp_path[dirname.len] = os.path.sep;
335 while (true) {
336 try getRandomBytes(rand_buf[0..]);
337 b64_fs_encoder.encode(tmp_path[dirname.len + 1 ..], rand_buf);
338
339 if (symLink(existing_path, tmp_path)) {
340 return rename(tmp_path, new_path);
341 } else |err| switch (err) {
342 error.PathAlreadyExists => continue,
343 else => return err, // TODO zig should know this set does not include PathAlreadyExists
156 if (wasi.is_the_target) {
157 switch (wasi.proc_raise(SIGABRT)) {
158 0 => return,
159 else => |err| return unexpectedErrno(err),
344160 }
345161 }
346}
347
348/// Guaranteed to be atomic. However until https://patchwork.kernel.org/patch/9636735/ is
349/// merged and readily available,
350/// there is a possibility of power loss or application termination leaving temporary files present
351/// in the same directory as dest_path.
352/// Destination file will have the same mode as the source file.
353pub fn copyFile(source_path: []const u8, dest_path: []const u8) !void {
354 var in_file = try os.File.openRead(source_path);
355 defer in_file.close();
356
357 const mode = try in_file.mode();
358 const in_stream = &in_file.inStream().stream;
359162
360 var atomic_file = try AtomicFile.init(dest_path, mode);
361 defer atomic_file.deinit();
163 if (windows.is_the_target) {
164 @compileError("TODO implement std.posix.raise for Windows");
165 }
362166
363 var buf: [page_size]u8 = undefined;
364 while (true) {
365 const amt = try in_stream.readFull(buf[0..]);
366 try atomic_file.file.write(buf[0..amt]);
367 if (amt != buf.len) {
368 return atomic_file.finish();
369 }
167 var set: system.sigset_t = undefined;
168 system.blockAppSignals(&set);
169 const tid = system.syscall0(system.SYS_gettid);
170 const rc = system.syscall2(system.SYS_tkill, tid, sig);
171 system.restoreSignals(&set);
172 switch (errno(rc)) {
173 0 => return,
174 else => |err| return unexpectedErrno(err),
370175 }
371176}
372177
373/// Guaranteed to be atomic. However until https://patchwork.kernel.org/patch/9636735/ is
374/// merged and readily available,
375/// there is a possibility of power loss or application termination leaving temporary files present
376pub fn copyFileMode(source_path: []const u8, dest_path: []const u8, mode: File.Mode) !void {
377 var in_file = try os.File.openRead(source_path);
378 defer in_file.close();
379
380 var atomic_file = try AtomicFile.init(dest_path, mode);
381 defer atomic_file.deinit();
382
383 var buf: [page_size]u8 = undefined;
384 while (true) {
385 const amt = try in_file.read(buf[0..]);
386 try atomic_file.file.write(buf[0..amt]);
387 if (amt != buf.len) {
388 return atomic_file.finish();
389 }
178/// Exits the program cleanly with the specified status code.
179pub fn exit(status: u8) noreturn {
180 if (builtin.link_libc) {
181 system.exit(status);
182 }
183 if (windows.is_the_target) {
184 windows.kernel32.ExitProcess(status);
390185 }
186 if (wasi.is_the_target) {
187 wasi.proc_exit(status);
188 }
189 if (linux.is_the_target and !builtin.single_threaded) {
190 linux.exit_group(status);
191 }
192 system.exit(status);
391193}
392194
393pub const AtomicFile = struct {
394 file: os.File,
395 tmp_path_buf: [MAX_PATH_BYTES]u8,
396 dest_path: []const u8,
397 finished: bool,
195pub const ReadError = error{
196 InputOutput,
197 SystemResources,
198 IsDir,
199 OperationAborted,
200 BrokenPipe,
201 Unexpected,
202};
398203
399 const InitError = os.File.OpenError;
204/// Returns the number of bytes that were read, which can be less than
205/// buf.len. If 0 bytes were read, that means EOF.
206/// This function is for blocking file descriptors only. For non-blocking, see
207/// `readAsync`.
208pub fn read(fd: fd_t, buf: []u8) ReadError!usize {
209 if (windows.is_the_target and !builtin.link_libc) {
210 return windows.ReadFile(fd, buf);
211 }
400212
401 /// dest_path must remain valid for the lifetime of AtomicFile
402 /// call finish to atomically replace dest_path with contents
403 /// TODO once we have null terminated pointers, use the
404 /// openWriteNoClobberN function
405 pub fn init(dest_path: []const u8, mode: File.Mode) InitError!AtomicFile {
406 const dirname = os.path.dirname(dest_path);
407 var rand_buf: [12]u8 = undefined;
408 const dirname_component_len = if (dirname) |d| d.len + 1 else 0;
409 const encoded_rand_len = comptime base64.Base64Encoder.calcSize(rand_buf.len);
410 const tmp_path_len = dirname_component_len + encoded_rand_len;
411 var tmp_path_buf: [MAX_PATH_BYTES]u8 = undefined;
412 if (tmp_path_len >= tmp_path_buf.len) return error.NameTooLong;
213 if (wasi.is_the_target and !builtin.link_libc) {
214 const iovs = [1]was.iovec_t{wasi.iovec_t{
215 .buf = buf.ptr,
216 .buf_len = buf.len,
217 }};
413218
414 if (dirname) |dir| {
415 mem.copy(u8, tmp_path_buf[0..], dir);
416 tmp_path_buf[dir.len] = os.path.sep;
219 var nread: usize = undefined;
220 switch (fd_read(fd, &iovs, iovs.len, &nread)) {
221 0 => return nread,
222 else => |err| return unexpectedErrno(err),
417223 }
224 }
418225
419 tmp_path_buf[tmp_path_len] = 0;
420
421 while (true) {
422 try getRandomBytes(rand_buf[0..]);
423 b64_fs_encoder.encode(tmp_path_buf[dirname_component_len..tmp_path_len], rand_buf);
424
425 const file = os.File.openWriteNoClobberC(&tmp_path_buf, mode) catch |err| switch (err) {
426 error.PathAlreadyExists => continue,
427 // TODO zig should figure out that this error set does not include PathAlreadyExists since
428 // it is handled in the above switch
429 else => return err,
430 };
431
432 return AtomicFile{
433 .file = file,
434 .tmp_path_buf = tmp_path_buf,
435 .dest_path = dest_path,
436 .finished = false,
437 };
226 // Linux can return EINVAL when read amount is > 0x7ffff000
227 // See https://github.com/ziglang/zig/pull/743#issuecomment-363158274
228 // TODO audit this. Shawn Landden says that this is not actually true.
229 // if this logic should stay, move it to std.os.linux.sys
230 const max_buf_len = 0x7ffff000;
231
232 var index: usize = 0;
233 while (index < buf.len) {
234 const want_to_read = math.min(buf.len - index, usize(max_buf_len));
235 const rc = system.read(fd, buf.ptr + index, want_to_read);
236 switch (errno(rc)) {
237 0 => {
238 index += rc;
239 if (rc == want_to_read) continue;
240 // Read returned less than buf.len.
241 return index;
242 },
243 EINTR => continue,
244 EINVAL => unreachable,
245 EFAULT => unreachable,
246 EAGAIN => unreachable, // This function is for blocking reads.
247 EBADF => unreachable, // Always a race condition.
248 EIO => return error.InputOutput,
249 EISDIR => return error.IsDir,
250 ENOBUFS => return error.SystemResources,
251 ENOMEM => return error.SystemResources,
252 else => |err| return unexpectedErrno(err),
438253 }
439254 }
255 return index;
256}
440257
441 /// always call deinit, even after successful finish()
442 pub fn deinit(self: *AtomicFile) void {
443 if (!self.finished) {
444 self.file.close();
445 deleteFileC(&self.tmp_path_buf) catch {};
446 self.finished = true;
258/// Number of bytes read is returned. Upon reading end-of-file, zero is returned.
259/// This function is for blocking file descriptors only. For non-blocking, see
260/// `preadvAsync`.
261pub fn preadv(fd: fd_t, iov: [*]const iovec, count: usize, offset: u64) ReadError!usize {
262 if (os.darwin.is_the_target) {
263 // Darwin does not have preadv but it does have pread.
264 var off: usize = 0;
265 var iov_i: usize = 0;
266 var inner_off: usize = 0;
267 while (true) {
268 const v = iov[iov_i];
269 const rc = darwin.pread(fd, v.iov_base + inner_off, v.iov_len - inner_off, offset + off);
270 const err = darwin.getErrno(rc);
271 switch (err) {
272 0 => {
273 off += rc;
274 inner_off += rc;
275 if (inner_off == v.iov_len) {
276 iov_i += 1;
277 inner_off = 0;
278 if (iov_i == count) {
279 return off;
280 }
281 }
282 if (rc == 0) return off; // EOF
283 continue;
284 },
285 EINTR => continue,
286 EINVAL => unreachable,
287 EFAULT => unreachable,
288 ESPIPE => unreachable, // fd is not seekable
289 EAGAIN => unreachable, // This function is for blocking reads.
290 EBADF => unreachable, // always a race condition
291 EIO => return error.InputOutput,
292 EISDIR => return error.IsDir,
293 ENOBUFS => return error.SystemResources,
294 ENOMEM => return error.SystemResources,
295 else => return unexpectedErrno(err),
296 }
447297 }
448298 }
449
450 pub fn finish(self: *AtomicFile) !void {
451 assert(!self.finished);
452 self.file.close();
453 self.finished = true;
454 if (is_posix) {
455 const dest_path_c = try toPosixPath(self.dest_path);
456 return renameC(&self.tmp_path_buf, &dest_path_c);
457 } else if (is_windows) {
458 const dest_path_w = try posix.sliceToPrefixedFileW(self.dest_path);
459 const tmp_path_w = try posix.cStrToPrefixedFileW(&self.tmp_path_buf);
460 return renameW(&tmp_path_w, &dest_path_w);
461 } else {
462 @compileError("Unsupported OS");
299 while (true) {
300 const rc = system.preadv(fd, iov, count, offset);
301 switch (errno(rc)) {
302 0 => return rc,
303 EINTR => continue,
304 EINVAL => unreachable,
305 EFAULT => unreachable,
306 EAGAIN => unreachable, // This function is for blocking reads.
307 EBADF => unreachable, // always a race condition
308 EIO => return error.InputOutput,
309 EISDIR => return error.IsDir,
310 ENOBUFS => return error.SystemResources,
311 ENOMEM => return error.SystemResources,
312 else => |err| return unexpectedErrno(err),
463313 }
464314 }
465};
466
467const default_new_dir_mode = 0o755;
468
469/// Create a new directory.
470pub fn makeDir(dir_path: []const u8) !void {
471 return posix.mkdir(dir_path, default_new_dir_mode);
472315}
473316
474/// Same as `makeDir` except the parameter is a null-terminated UTF8-encoded string.
475pub fn makeDirC(dir_path: [*]const u8) !void {
476 return posix.mkdirC(dir_path, default_new_dir_mode);
477}
317pub const WriteError = error{
318 DiskQuota,
319 FileTooBig,
320 InputOutput,
321 NoSpaceLeft,
322 AccessDenied,
323 BrokenPipe,
324 SystemResources,
325 OperationAborted,
326 Unexpected,
327};
478328
479/// Same as `makeDir` except the parameter is a null-terminated UTF16LE-encoded string.
480pub fn makeDirW(dir_path: [*]const u16) !void {
481 return posix.mkdirW(dir_path, default_new_dir_mode);
482}
329/// Write to a file descriptor. Keeps trying if it gets interrupted.
330/// This function is for blocking file descriptors only. For non-blocking, see
331/// `writeAsync`.
332pub fn write(fd: fd_t, bytes: []const u8) WriteError!void {
333 if (windows.is_the_target and !builtin.link_libc) {
334 return windows.WriteFile(fd, bytes);
335 }
483336
484/// Calls makeDir recursively to make an entire path. Returns success if the path
485/// already exists and is a directory.
486/// This function is not atomic, and if it returns an error, the file system may
487/// have been modified regardless.
488/// TODO determine if we can remove the allocator requirement from this function
489pub fn makePath(allocator: *Allocator, full_path: []const u8) !void {
490 const resolved_path = try path.resolve(allocator, [][]const u8{full_path});
491 defer allocator.free(resolved_path);
337 if (wasi.is_the_target and !builtin.link_libc) {
338 const ciovs = [1]wasi.ciovec_t{wasi.ciovec_t{
339 .buf = bytes.ptr,
340 .buf_len = bytes.len,
341 }};
342 var nwritten: usize = undefined;
343 switch (fd_write(fd, &ciovs, ciovs.len, &nwritten)) {
344 0 => return,
345 else => |err| return unexpectedErrno(err),
346 }
347 }
492348
493 var end_index: usize = resolved_path.len;
494 while (true) {
495 makeDir(resolved_path[0..end_index]) catch |err| switch (err) {
496 error.PathAlreadyExists => {
497 // TODO stat the file and return an error if it's not a directory
498 // this is important because otherwise a dangling symlink
499 // could cause an infinite loop
500 if (end_index == resolved_path.len) return;
501 },
502 error.FileNotFound => {
503 // march end_index backward until next path component
504 while (true) {
505 end_index -= 1;
506 if (os.path.isSep(resolved_path[end_index])) break;
507 }
349 // Linux can return EINVAL when write amount is > 0x7ffff000
350 // See https://github.com/ziglang/zig/pull/743#issuecomment-363165856
351 // TODO audit this. Shawn Landden says that this is not actually true.
352 // if this logic should stay, move it to std.os.linux.sys
353 const max_bytes_len = 0x7ffff000;
354
355 var index: usize = 0;
356 while (index < bytes.len) {
357 const amt_to_write = math.min(bytes.len - index, usize(max_bytes_len));
358 const rc = system.write(fd, bytes.ptr + index, amt_to_write);
359 switch (errno(rc)) {
360 0 => {
361 index += rc;
508362 continue;
509363 },
510 else => return err,
511 };
512 if (end_index == resolved_path.len) return;
513 // march end_index forward until next path component
514 while (true) {
515 end_index += 1;
516 if (end_index == resolved_path.len or os.path.isSep(resolved_path[end_index])) break;
364 EINTR => continue,
365 EINVAL => unreachable,
366 EFAULT => unreachable,
367 EAGAIN => unreachable, // This function is for blocking writes.
368 EBADF => unreachable, // Always a race condition.
369 EDESTADDRREQ => unreachable, // `connect` was never called.
370 EDQUOT => return error.DiskQuota,
371 EFBIG => return error.FileTooBig,
372 EIO => return error.InputOutput,
373 ENOSPC => return error.NoSpaceLeft,
374 EPERM => return error.AccessDenied,
375 EPIPE => return error.BrokenPipe,
376 else => |err| return unexpectedErrno(err),
517377 }
518378 }
519379}
520380
521/// Returns `error.DirNotEmpty` if the directory is not empty.
522/// To delete a directory recursively, see `deleteTree`.
523pub fn deleteDir(dir_path: []const u8) DeleteDirError!void {
524 return posix.rmdir(dir_path);
525}
526
527/// Same as `deleteDir` except the parameter is a null-terminated UTF8-encoded string.
528pub fn deleteDirC(dir_path: [*]const u8) DeleteDirError!void {
529 return posix.rmdirC(dir_path);
530}
381/// Write multiple buffers to a file descriptor. Keeps trying if it gets interrupted.
382/// This function is for blocking file descriptors only. For non-blocking, see
383/// `pwritevAsync`.
384pub fn pwritev(fd: fd_t, iov: [*]const iovec_const, count: usize, offset: u64) WriteError!void {
385 if (darwin.is_the_target) {
386 // Darwin does not have pwritev but it does have pwrite.
387 var off: usize = 0;
388 var iov_i: usize = 0;
389 var inner_off: usize = 0;
390 while (true) {
391 const v = iov[iov_i];
392 const rc = darwin.pwrite(fd, v.iov_base + inner_off, v.iov_len - inner_off, offset + off);
393 const err = darwin.getErrno(rc);
394 switch (err) {
395 0 => {
396 off += rc;
397 inner_off += rc;
398 if (inner_off == v.iov_len) {
399 iov_i += 1;
400 inner_off = 0;
401 if (iov_i == count) {
402 return;
403 }
404 }
405 continue;
406 },
407 EINTR => continue,
408 ESPIPE => unreachable, // `fd` is not seekable.
409 EINVAL => unreachable,
410 EFAULT => unreachable,
411 EAGAIN => unreachable, // This function is for blocking writes.
412 EBADF => unreachable, // Always a race condition.
413 EDESTADDRREQ => unreachable, // `connect` was never called.
414 EDQUOT => return error.DiskQuota,
415 EFBIG => return error.FileTooBig,
416 EIO => return error.InputOutput,
417 ENOSPC => return error.NoSpaceLeft,
418 EPERM => return error.AccessDenied,
419 EPIPE => return error.BrokenPipe,
420 else => return unexpectedErrno(err),
421 }
422 }
423 }
531424
532/// Same as `deleteDir` except the parameter is a null-terminated UTF16LE-encoded string.
533pub fn deleteDirW(dir_path: [*]const u16) DeleteDirError!void {
534 return posix.rmdirW(dir_path);
425 while (true) {
426 const rc = system.pwritev(fd, iov, count, offset);
427 switch (errno(rc)) {
428 0 => return,
429 EINTR => continue,
430 EINVAL => unreachable,
431 EFAULT => unreachable,
432 EAGAIN => unreachable, // This function is for blocking writes.
433 EBADF => unreachable, // Always a race condition.
434 EDESTADDRREQ => unreachable, // `connect` was never called.
435 EDQUOT => return error.DiskQuota,
436 EFBIG => return error.FileTooBig,
437 EIO => return error.InputOutput,
438 ENOSPC => return error.NoSpaceLeft,
439 EPERM => return error.AccessDenied,
440 EPIPE => return error.BrokenPipe,
441 else => |err| return unexpectedErrno(err),
442 }
443 }
535444}
536445
537/// Whether ::full_path describes a symlink, file, or directory, this function
538/// removes it. If it cannot be removed because it is a non-empty directory,
539/// this function recursively removes its entries and then tries again.
540const DeleteTreeError = error{
541 OutOfMemory,
446pub const OpenError = error{
542447 AccessDenied,
543448 FileTooBig,
544449 IsDir,
......@@ -547,1239 +452,1876 @@ const DeleteTreeError = error{
547452 NameTooLong,
548453 SystemFdQuotaExceeded,
549454 NoDevice,
455 FileNotFound,
550456 SystemResources,
551457 NoSpaceLeft,
552 PathAlreadyExists,
553 ReadOnlyFileSystem,
554458 NotDir,
555 FileNotFound,
556 FileSystem,
557 FileBusy,
558 DirNotEmpty,
459 PathAlreadyExists,
559460 DeviceBusy,
560
561 /// On Windows, file paths must be valid Unicode.
562 InvalidUtf8,
563
564 /// On Windows, file paths cannot contain these characters:
565 /// '/', '*', '?', '"', '<', '>', '|'
566 BadPathName,
567
568461 Unexpected,
569462};
570463
571/// TODO determine if we can remove the allocator requirement
572pub fn deleteTree(allocator: *Allocator, full_path: []const u8) DeleteTreeError!void {
573 start_over: while (true) {
574 var got_access_denied = false;
575 // First, try deleting the item as a file. This way we don't follow sym links.
576 if (deleteFile(full_path)) {
577 return;
578 } else |err| switch (err) {
579 error.FileNotFound => return,
580 error.IsDir => {},
581 error.AccessDenied => got_access_denied = true,
582
583 error.InvalidUtf8,
584 error.SymLinkLoop,
585 error.NameTooLong,
586 error.SystemResources,
587 error.ReadOnlyFileSystem,
588 error.NotDir,
589 error.FileSystem,
590 error.FileBusy,
591 error.BadPathName,
592 error.Unexpected,
593 => return err,
594 }
595 {
596 var dir = Dir.open(allocator, full_path) catch |err| switch (err) {
597 error.NotDir => {
598 if (got_access_denied) {
599 return error.AccessDenied;
600 }
601 continue :start_over;
602 },
464/// Open and possibly create a file. Keeps trying if it gets interrupted.
465/// `file_path` needs to be copied in memory to add a null terminating byte.
466/// See also `openC`.
467pub fn open(file_path: []const u8, flags: u32, perm: usize) OpenError!fd_t {
468 const file_path_c = try toPosixPath(file_path);
469 return openC(&file_path_c, flags, perm);
470}
603471
604 error.OutOfMemory,
605 error.AccessDenied,
606 error.FileTooBig,
607 error.IsDir,
608 error.SymLinkLoop,
609 error.ProcessFdQuotaExceeded,
610 error.NameTooLong,
611 error.SystemFdQuotaExceeded,
612 error.NoDevice,
613 error.FileNotFound,
614 error.SystemResources,
615 error.NoSpaceLeft,
616 error.PathAlreadyExists,
617 error.Unexpected,
618 error.InvalidUtf8,
619 error.BadPathName,
620 error.DeviceBusy,
621 => return err,
622 };
623 defer dir.close();
624
625 var full_entry_buf = ArrayList(u8).init(allocator);
626 defer full_entry_buf.deinit();
627
628 while (try dir.next()) |entry| {
629 try full_entry_buf.resize(full_path.len + entry.name.len + 1);
630 const full_entry_path = full_entry_buf.toSlice();
631 mem.copy(u8, full_entry_path, full_path);
632 full_entry_path[full_path.len] = path.sep;
633 mem.copy(u8, full_entry_path[full_path.len + 1 ..], entry.name);
634
635 try deleteTree(allocator, full_entry_path);
636 }
472/// Open and possibly create a file. Keeps trying if it gets interrupted.
473/// See also `open`.
474/// TODO https://github.com/ziglang/zig/issues/265
475pub fn openC(file_path: [*]const u8, flags: u32, perm: usize) OpenError!fd_t {
476 while (true) {
477 const rc = system.open(file_path, flags, perm);
478 switch (errno(rc)) {
479 0 => return @intCast(fd_t, rc),
480 EINTR => continue,
481
482 EFAULT => unreachable,
483 EINVAL => unreachable,
484 EACCES => return error.AccessDenied,
485 EFBIG => return error.FileTooBig,
486 EOVERFLOW => return error.FileTooBig,
487 EISDIR => return error.IsDir,
488 ELOOP => return error.SymLinkLoop,
489 EMFILE => return error.ProcessFdQuotaExceeded,
490 ENAMETOOLONG => return error.NameTooLong,
491 ENFILE => return error.SystemFdQuotaExceeded,
492 ENODEV => return error.NoDevice,
493 ENOENT => return error.FileNotFound,
494 ENOMEM => return error.SystemResources,
495 ENOSPC => return error.NoSpaceLeft,
496 ENOTDIR => return error.NotDir,
497 EPERM => return error.AccessDenied,
498 EEXIST => return error.PathAlreadyExists,
499 EBUSY => return error.DeviceBusy,
500 else => |err| return unexpectedErrno(err),
637501 }
638 return deleteDir(full_path);
639502 }
640503}
641504
642pub const Dir = struct {
643 handle: Handle,
644 allocator: *Allocator,
645
646 pub const Handle = switch (builtin.os) {
647 Os.macosx, Os.ios, Os.freebsd, Os.netbsd => struct {
648 fd: i32,
649 seek: i64,
650 buf: []u8,
651 index: usize,
652 end_index: usize,
653 },
654 Os.linux => struct {
655 fd: i32,
656 buf: []u8,
657 index: usize,
658 end_index: usize,
659 },
660 Os.windows => struct {
661 handle: windows.HANDLE,
662 find_file_data: windows.WIN32_FIND_DATAW,
663 first: bool,
664 name_data: [256]u8,
665 },
666 else => @compileError("unimplemented"),
667 };
668
669 pub const Entry = struct {
670 name: []const u8,
671 kind: Kind,
672
673 pub const Kind = enum {
674 BlockDevice,
675 CharacterDevice,
676 Directory,
677 NamedPipe,
678 SymLink,
679 File,
680 UnixDomainSocket,
681 Whiteout,
682 Unknown,
683 };
684 };
685
686 pub const OpenError = error{
687 FileNotFound,
688 NotDir,
689 AccessDenied,
690 FileTooBig,
691 IsDir,
692 SymLinkLoop,
693 ProcessFdQuotaExceeded,
694 NameTooLong,
695 SystemFdQuotaExceeded,
696 NoDevice,
697 SystemResources,
698 NoSpaceLeft,
699 PathAlreadyExists,
700 OutOfMemory,
701 InvalidUtf8,
702 BadPathName,
703 DeviceBusy,
704
705 Unexpected,
706 };
707
708 /// TODO remove the allocator requirement from this API
709 pub fn open(allocator: *Allocator, dir_path: []const u8) OpenError!Dir {
710 return Dir{
711 .allocator = allocator,
712 .handle = switch (builtin.os) {
713 Os.windows => blk: {
714 var find_file_data: windows.WIN32_FIND_DATAW = undefined;
715 const handle = try windows_util.windowsFindFirstFile(dir_path, &find_file_data);
716 break :blk Handle{
717 .handle = handle,
718 .find_file_data = find_file_data, // TODO guaranteed copy elision
719 .first = true,
720 .name_data = undefined,
721 };
722 },
723 Os.macosx, Os.ios, Os.freebsd, Os.netbsd => Handle{
724 .fd = try posixOpen(
725 dir_path,
726 posix.O_RDONLY | posix.O_NONBLOCK | posix.O_DIRECTORY | posix.O_CLOEXEC,
727 0,
728 ),
729 .seek = 0,
730 .index = 0,
731 .end_index = 0,
732 .buf = []u8{},
733 },
734 Os.linux => Handle{
735 .fd = try posixOpen(
736 dir_path,
737 posix.O_RDONLY | posix.O_DIRECTORY | posix.O_CLOEXEC,
738 0,
739 ),
740 .index = 0,
741 .end_index = 0,
742 .buf = []u8{},
743 },
744 else => @compileError("unimplemented"),
745 },
746 };
747 }
748
749 pub fn close(self: *Dir) void {
750 switch (builtin.os) {
751 Os.windows => {
752 _ = windows.FindClose(self.handle.handle);
753 },
754 Os.macosx, Os.ios, Os.linux, Os.freebsd, Os.netbsd => {
755 self.allocator.free(self.handle.buf);
756 os.close(self.handle.fd);
757 },
758 else => @compileError("unimplemented"),
505pub fn dup2(old_fd: fd_t, new_fd: fd_t) !void {
506 while (true) {
507 switch (errno(system.dup2(old_fd, new_fd))) {
508 0 => return,
509 EBUSY, EINTR => continue,
510 EMFILE => return error.ProcessFdQuotaExceeded,
511 EINVAL => unreachable,
512 else => |err| return unexpectedErrno(err),
759513 }
760514 }
515}
761516
762 /// Memory such as file names referenced in this returned entry becomes invalid
763 /// with subsequent calls to next, as well as when this `Dir` is deinitialized.
764 pub fn next(self: *Dir) !?Entry {
765 switch (builtin.os) {
766 Os.linux => return self.nextLinux(),
767 Os.macosx, Os.ios => return self.nextDarwin(),
768 Os.windows => return self.nextWindows(),
769 Os.freebsd => return self.nextFreebsd(),
770 Os.netbsd => return self.nextFreebsd(),
771 else => @compileError("unimplemented"),
517/// This function must allocate memory to add a null terminating bytes on path and each arg.
518/// It must also convert to KEY=VALUE\0 format for environment variables, and include null
519/// pointers after the args and after the environment variables.
520/// `argv[0]` is the executable path.
521/// This function also uses the PATH environment variable to get the full path to the executable.
522/// TODO provide execveC which does not take an allocator
523pub fn execve(allocator: *Allocator, argv: []const []const u8, env_map: *const BufMap) !void {
524 const argv_buf = try allocator.alloc(?[*]u8, argv.len + 1);
525 mem.set(?[*]u8, argv_buf, null);
526 defer {
527 for (argv_buf) |arg| {
528 const arg_buf = if (arg) |ptr| cstr.toSlice(ptr) else break;
529 allocator.free(arg_buf);
772530 }
531 allocator.free(argv_buf);
773532 }
533 for (argv) |arg, i| {
534 const arg_buf = try allocator.alloc(u8, arg.len + 1);
535 @memcpy(arg_buf.ptr, arg.ptr, arg.len);
536 arg_buf[arg.len] = 0;
774537
775 fn nextDarwin(self: *Dir) !?Entry {
776 start_over: while (true) {
777 if (self.handle.index >= self.handle.end_index) {
778 if (self.handle.buf.len == 0) {
779 self.handle.buf = try self.allocator.alloc(u8, page_size);
780 }
781
782 while (true) {
783 const result = system.__getdirentries64(self.handle.fd, self.handle.buf.ptr, self.handle.buf.len, &self.handle.seek);
784 if (result == 0) return null;
785 if (result < 0) {
786 switch (system.getErrno(result)) {
787 posix.EBADF => unreachable,
788 posix.EFAULT => unreachable,
789 posix.ENOTDIR => unreachable,
790 posix.EINVAL => {
791 self.handle.buf = try self.allocator.realloc(self.handle.buf, self.handle.buf.len * 2);
792 continue;
793 },
794 else => return unexpectedErrorPosix(err),
795 }
796 }
797 self.handle.index = 0;
798 self.handle.end_index = @intCast(usize, result);
799 break;
800 }
801 }
802 const darwin_entry = @ptrCast(*align(1) posix.dirent, &self.handle.buf[self.handle.index]);
803 const next_index = self.handle.index + darwin_entry.d_reclen;
804 self.handle.index = next_index;
805
806 const name = @ptrCast([*]u8, &darwin_entry.d_name)[0..darwin_entry.d_namlen];
538 argv_buf[i] = arg_buf.ptr;
539 }
540 argv_buf[argv.len] = null;
807541
808 if (mem.eql(u8, name, ".") or mem.eql(u8, name, "..")) {
809 continue :start_over;
810 }
542 const envp_buf = try createNullDelimitedEnvMap(allocator, env_map);
543 defer freeNullDelimitedEnvMap(allocator, envp_buf);
811544
812 const entry_kind = switch (darwin_entry.d_type) {
813 posix.DT_BLK => Entry.Kind.BlockDevice,
814 posix.DT_CHR => Entry.Kind.CharacterDevice,
815 posix.DT_DIR => Entry.Kind.Directory,
816 posix.DT_FIFO => Entry.Kind.NamedPipe,
817 posix.DT_LNK => Entry.Kind.SymLink,
818 posix.DT_REG => Entry.Kind.File,
819 posix.DT_SOCK => Entry.Kind.UnixDomainSocket,
820 posix.DT_WHT => Entry.Kind.Whiteout,
821 else => Entry.Kind.Unknown,
822 };
823 return Entry{
824 .name = name,
825 .kind = entry_kind,
826 };
827 }
545 const exe_path = argv[0];
546 if (mem.indexOfScalar(u8, exe_path, '/') != null) {
547 return execveErrnoToErr(errno(system.execve(argv_buf[0].?, argv_buf.ptr, envp_buf.ptr)));
828548 }
829549
830 fn nextWindows(self: *Dir) !?Entry {
831 while (true) {
832 if (self.handle.first) {
833 self.handle.first = false;
834 } else {
835 if (!try posix.FindNextFile(self.handle.handle, &self.handle.find_file_data))
836 return null;
837 }
838 const name_utf16le = mem.toSlice(u16, self.handle.find_file_data.cFileName[0..].ptr);
839 if (mem.eql(u16, name_utf16le, []u16{'.'}) or mem.eql(u16, name_utf16le, []u16{ '.', '.' }))
840 continue;
841 // Trust that Windows gives us valid UTF-16LE
842 const name_utf8_len = std.unicode.utf16leToUtf8(self.handle.name_data[0..], name_utf16le) catch unreachable;
843 const name_utf8 = self.handle.name_data[0..name_utf8_len];
844 const kind = blk: {
845 const attrs = self.handle.find_file_data.dwFileAttributes;
846 if (attrs & windows.FILE_ATTRIBUTE_DIRECTORY != 0) break :blk Entry.Kind.Directory;
847 if (attrs & windows.FILE_ATTRIBUTE_REPARSE_POINT != 0) break :blk Entry.Kind.SymLink;
848 if (attrs & windows.FILE_ATTRIBUTE_NORMAL != 0) break :blk Entry.Kind.File;
849 break :blk Entry.Kind.Unknown;
850 };
851 return Entry{
852 .name = name_utf8,
853 .kind = kind,
854 };
550 const PATH = getenv("PATH") orelse "/usr/local/bin:/bin/:/usr/bin";
551 // PATH.len because it is >= the largest search_path
552 // +1 for the / to join the search path and exe_path
553 // +1 for the null terminating byte
554 const path_buf = try allocator.alloc(u8, PATH.len + exe_path.len + 2);
555 defer allocator.free(path_buf);
556 var it = mem.tokenize(PATH, ":");
557 var seen_eacces = false;
558 var err: usize = undefined;
559 while (it.next()) |search_path| {
560 mem.copy(u8, path_buf, search_path);
561 path_buf[search_path.len] = '/';
562 mem.copy(u8, path_buf[search_path.len + 1 ..], exe_path);
563 path_buf[search_path.len + exe_path.len + 1] = 0;
564 err = errno(system.execve(path_buf.ptr, argv_buf.ptr, envp_buf.ptr));
565 assert(err > 0);
566 if (err == EACCES) {
567 seen_eacces = true;
568 } else if (err != ENOENT) {
569 return execveErrnoToErr(err);
855570 }
856571 }
857
858 fn nextLinux(self: *Dir) !?Entry {
859 start_over: while (true) {
860 if (self.handle.index >= self.handle.end_index) {
861 if (self.handle.buf.len == 0) {
862 self.handle.buf = try self.allocator.alloc(u8, page_size);
863 }
864
865 while (true) {
866 const result = posix.getdents64(self.handle.fd, self.handle.buf.ptr, self.handle.buf.len);
867 const err = posix.getErrno(result);
868 if (err > 0) {
869 switch (err) {
870 posix.EBADF, posix.EFAULT, posix.ENOTDIR => unreachable,
871 posix.EINVAL => {
872 self.handle.buf = try self.allocator.realloc(self.handle.buf, self.handle.buf.len * 2);
873 continue;
874 },
875 else => return unexpectedErrorPosix(err),
876 }
877 }
878 if (result == 0) return null;
879 self.handle.index = 0;
880 self.handle.end_index = result;
881 break;
882 }
883 }
884 const linux_entry = @ptrCast(*align(1) posix.dirent64, &self.handle.buf[self.handle.index]);
885 const next_index = self.handle.index + linux_entry.d_reclen;
886 self.handle.index = next_index;
887
888 const name = cstr.toSlice(@ptrCast([*]u8, &linux_entry.d_name));
889
890 // skip . and .. entries
891 if (mem.eql(u8, name, ".") or mem.eql(u8, name, "..")) {
892 continue :start_over;
893 }
894
895 const entry_kind = switch (linux_entry.d_type) {
896 posix.DT_BLK => Entry.Kind.BlockDevice,
897 posix.DT_CHR => Entry.Kind.CharacterDevice,
898 posix.DT_DIR => Entry.Kind.Directory,
899 posix.DT_FIFO => Entry.Kind.NamedPipe,
900 posix.DT_LNK => Entry.Kind.SymLink,
901 posix.DT_REG => Entry.Kind.File,
902 posix.DT_SOCK => Entry.Kind.UnixDomainSocket,
903 else => Entry.Kind.Unknown,
904 };
905 return Entry{
906 .name = name,
907 .kind = entry_kind,
908 };
909 }
572 if (seen_eacces) {
573 err = EACCES;
910574 }
575 return execveErrnoToErr(err);
576}
911577
912 fn nextFreebsd(self: *Dir) !?Entry {
913 start_over: while (true) {
914 if (self.handle.index >= self.handle.end_index) {
915 if (self.handle.buf.len == 0) {
916 self.handle.buf = try self.allocator.alloc(u8, page_size);
917 }
918
919 while (true) {
920 const result = posix.getdirentries(self.handle.fd, self.handle.buf.ptr, self.handle.buf.len, &self.handle.seek);
921 const err = posix.getErrno(result);
922 if (err > 0) {
923 switch (err) {
924 posix.EBADF, posix.EFAULT, posix.ENOTDIR => unreachable,
925 posix.EINVAL => {
926 self.handle.buf = try self.allocator.realloc(self.handle.buf, self.handle.buf.len * 2);
927 continue;
928 },
929 else => return unexpectedErrorPosix(err),
930 }
931 }
932 if (result == 0) return null;
933 self.handle.index = 0;
934 self.handle.end_index = result;
935 break;
936 }
937 }
938 const freebsd_entry = @ptrCast(*align(1) posix.dirent, &self.handle.buf[self.handle.index]);
939 const next_index = self.handle.index + freebsd_entry.d_reclen;
940 self.handle.index = next_index;
941
942 const name = @ptrCast([*]u8, &freebsd_entry.d_name)[0..freebsd_entry.d_namlen];
943
944 if (mem.eql(u8, name, ".") or mem.eql(u8, name, "..")) {
945 continue :start_over;
946 }
947
948 const entry_kind = switch (freebsd_entry.d_type) {
949 posix.DT_BLK => Entry.Kind.BlockDevice,
950 posix.DT_CHR => Entry.Kind.CharacterDevice,
951 posix.DT_DIR => Entry.Kind.Directory,
952 posix.DT_FIFO => Entry.Kind.NamedPipe,
953 posix.DT_LNK => Entry.Kind.SymLink,
954 posix.DT_REG => Entry.Kind.File,
955 posix.DT_SOCK => Entry.Kind.UnixDomainSocket,
956 posix.DT_WHT => Entry.Kind.Whiteout,
957 else => Entry.Kind.Unknown,
958 };
959 return Entry{
960 .name = name,
961 .kind = entry_kind,
962 };
578pub fn createNullDelimitedEnvMap(allocator: *Allocator, env_map: *const BufMap) ![]?[*]u8 {
579 const envp_count = env_map.count();
580 const envp_buf = try allocator.alloc(?[*]u8, envp_count + 1);
581 mem.set(?[*]u8, envp_buf, null);
582 errdefer freeNullDelimitedEnvMap(allocator, envp_buf);
583 {
584 var it = env_map.iterator();
585 var i: usize = 0;
586 while (it.next()) |pair| : (i += 1) {
587 const env_buf = try allocator.alloc(u8, pair.key.len + pair.value.len + 2);
588 @memcpy(env_buf.ptr, pair.key.ptr, pair.key.len);
589 env_buf[pair.key.len] = '=';
590 @memcpy(env_buf.ptr + pair.key.len + 1, pair.value.ptr, pair.value.len);
591 env_buf[env_buf.len - 1] = 0;
592
593 envp_buf[i] = env_buf.ptr;
963594 }
595 assert(i == envp_count);
964596 }
965};
966
967/// Read value of a symbolic link.
968/// The return value is a slice of buffer, from index `0`.
969pub fn readLink(buffer: *[posix.PATH_MAX]u8, pathname: []const u8) ![]u8 {
970 return posix.readlink(pathname, buffer);
597 assert(envp_buf[envp_count] == null);
598 return envp_buf;
971599}
972600
973/// Same as `readLink`, except the `pathname` parameter is null-terminated.
974pub fn readLinkC(buffer: *[posix.PATH_MAX]u8, pathname: [*]const u8) ![]u8 {
975 return posix.readlinkC(pathname, buffer);
601pub fn freeNullDelimitedEnvMap(allocator: *Allocator, envp_buf: []?[*]u8) void {
602 for (envp_buf) |env| {
603 const env_buf = if (env) |ptr| ptr[0 .. cstr.len(ptr) + 1] else break;
604 allocator.free(env_buf);
605 }
606 allocator.free(envp_buf);
976607}
977608
978pub const ArgIteratorPosix = struct {
979 index: usize,
980 count: usize,
609pub const ExecveError = error{
610 SystemResources,
611 AccessDenied,
612 InvalidExe,
613 FileSystem,
614 IsDir,
615 FileNotFound,
616 NotDir,
617 FileBusy,
981618
982 pub fn init() ArgIteratorPosix {
983 return ArgIteratorPosix{
984 .index = 0,
985 .count = raw.len,
986 };
987 }
619 Unexpected,
620};
988621
989 pub fn next(self: *ArgIteratorPosix) ?[]const u8 {
990 if (self.index == self.count) return null;
622fn execveErrnoToErr(err: usize) ExecveError {
623 assert(err > 0);
624 switch (err) {
625 EFAULT => unreachable,
626 E2BIG => return error.SystemResources,
627 EMFILE => return error.ProcessFdQuotaExceeded,
628 ENAMETOOLONG => return error.NameTooLong,
629 ENFILE => return error.SystemFdQuotaExceeded,
630 ENOMEM => return error.SystemResources,
631 EACCES => return error.AccessDenied,
632 EPERM => return error.AccessDenied,
633 EINVAL => return error.InvalidExe,
634 ENOEXEC => return error.InvalidExe,
635 EIO => return error.FileSystem,
636 ELOOP => return error.FileSystem,
637 EISDIR => return error.IsDir,
638 ENOENT => return error.FileNotFound,
639 ENOTDIR => return error.NotDir,
640 ETXTBSY => return error.FileBusy,
641 else => return unexpectedErrno(err),
642 }
643}
991644
992 const s = raw[self.index];
993 self.index += 1;
994 return cstr.toSlice(s);
645/// Get an environment variable.
646/// See also `getenvC`.
647/// TODO make this go through libc when we have it
648pub fn getenv(key: []const u8) ?[]const u8 {
649 for (environ) |ptr| {
650 var line_i: usize = 0;
651 while (ptr[line_i] != 0 and ptr[line_i] != '=') : (line_i += 1) {}
652 const this_key = ptr[0..line_i];
653 if (!mem.eql(u8, key, this_key)) continue;
654
655 var end_i: usize = line_i;
656 while (ptr[end_i] != 0) : (end_i += 1) {}
657 const this_value = ptr[line_i + 1 .. end_i];
658
659 return this_value;
995660 }
661 return null;
662}
996663
997 pub fn skip(self: *ArgIteratorPosix) bool {
998 if (self.index == self.count) return false;
664/// Get an environment variable with a null-terminated name.
665/// See also `getenv`.
666/// TODO https://github.com/ziglang/zig/issues/265
667pub fn getenvC(key: [*]const u8) ?[]const u8 {
668 if (builtin.link_libc) {
669 const value = system.getenv(key) orelse return null;
670 return mem.toSliceConst(u8, value);
671 }
672 return getenv(mem.toSliceConst(u8, key));
673}
999674
1000 self.index += 1;
1001 return true;
675/// See std.elf for the constants.
676pub fn getauxval(index: usize) usize {
677 if (builtin.link_libc) {
678 return usize(system.getauxval(index));
679 } else if (linux.elf_aux_maybe) |auxv| {
680 var i: usize = 0;
681 while (auxv[i].a_type != std.elf.AT_NULL) : (i += 1) {
682 if (auxv[i].a_type == index)
683 return auxv[i].a_un.a_val;
684 }
1002685 }
686 return 0;
687}
1003688
1004 /// This is marked as public but actually it's only meant to be used
1005 /// internally by zig's startup code.
1006 pub var raw: [][*]u8 = undefined;
689pub const GetCwdError = error{
690 NameTooLong,
691 CurrentWorkingDirectoryUnlinked,
692 Unexpected,
1007693};
1008694
1009pub const ArgIteratorWindows = struct {
1010 index: usize,
1011 cmd_line: [*]const u8,
1012 in_quote: bool,
1013 quote_count: usize,
1014 seen_quote_count: usize,
1015
1016 pub const NextError = error{OutOfMemory};
1017
1018 pub fn init() ArgIteratorWindows {
1019 return initWithCmdLine(windows.GetCommandLineA());
1020 }
1021
1022 pub fn initWithCmdLine(cmd_line: [*]const u8) ArgIteratorWindows {
1023 return ArgIteratorWindows{
1024 .index = 0,
1025 .cmd_line = cmd_line,
1026 .in_quote = false,
1027 .quote_count = countQuotes(cmd_line),
1028 .seen_quote_count = 0,
1029 };
1030 }
1031
1032 /// You must free the returned memory when done.
1033 pub fn next(self: *ArgIteratorWindows, allocator: *Allocator) ?(NextError![]u8) {
1034 // march forward over whitespace
1035 while (true) : (self.index += 1) {
1036 const byte = self.cmd_line[self.index];
1037 switch (byte) {
1038 0 => return null,
1039 ' ', '\t' => continue,
1040 else => break,
1041 }
1042 }
1043
1044 return self.internalNext(allocator);
695/// The result is a slice of out_buffer, indexed from 0.
696pub fn getcwd(out_buffer: []u8) GetCwdError![]u8 {
697 if (windows.is_the_target and !builtin.link_libc) {
698 return windows.GetCurrentDirectory(out_buffer);
1045699 }
1046700
1047 pub fn skip(self: *ArgIteratorWindows) bool {
1048 // march forward over whitespace
1049 while (true) : (self.index += 1) {
1050 const byte = self.cmd_line[self.index];
1051 switch (byte) {
1052 0 => return false,
1053 ' ', '\t' => continue,
1054 else => break,
1055 }
1056 }
701 const err = if (builtin.link_libc) blk: {
702 break :blk if (system.getcwd(out_buffer.ptr, out_buffer.len)) |_| 0 else system._errno().*;
703 } else blk: {
704 break :blk errno(system.getcwd(out_buffer, out_buffer.len));
705 };
706 switch (err) {
707 0 => return mem.toSlice(u8, out_buffer),
708 EFAULT => unreachable,
709 EINVAL => unreachable,
710 ENOENT => return error.CurrentWorkingDirectoryUnlinked,
711 ERANGE => return error.NameTooLong,
712 else => |err| return unexpectedErrno(err),
713 }
714}
1057715
1058 var backslash_count: usize = 0;
1059 while (true) : (self.index += 1) {
1060 const byte = self.cmd_line[self.index];
1061 switch (byte) {
1062 0 => return true,
1063 '"' => {
1064 const quote_is_real = backslash_count % 2 == 0;
1065 if (quote_is_real) {
1066 self.seen_quote_count += 1;
1067 }
1068 },
1069 '\\' => {
1070 backslash_count += 1;
1071 },
1072 ' ', '\t' => {
1073 if (self.seen_quote_count % 2 == 0 or self.seen_quote_count == self.quote_count) {
1074 return true;
1075 }
1076 backslash_count = 0;
1077 },
1078 else => {
1079 backslash_count = 0;
1080 continue;
1081 },
1082 }
716pub const SymLinkError = error{
717 AccessDenied,
718 DiskQuota,
719 PathAlreadyExists,
720 FileSystem,
721 SymLinkLoop,
722 FileNotFound,
723 SystemResources,
724 NoSpaceLeft,
725 ReadOnlyFileSystem,
726 NotDir,
727 NameTooLong,
728 InvalidUtf8,
729 BadPathName,
730 Unexpected,
731};
732
733/// Creates a symbolic link named `sym_link_path` which contains the string `target_path`.
734/// A symbolic link (also known as a soft link) may point to an existing file or to a nonexistent
735/// one; the latter case is known as a dangling link.
736/// If `sym_link_path` exists, it will not be overwritten.
737/// See also `symlinkC` and `symlinkW`.
738pub fn symlink(target_path: []const u8, sym_link_path: []const u8) SymLinkError!void {
739 if (windows.is_the_target and !builtin.link_libc) {
740 const target_path_w = try windows.sliceToPrefixedFileW(target_path);
741 const sym_link_path_w = try windows.sliceToPrefixedFileW(sym_link_path);
742 return windows.CreateSymbolicLinkW(&sym_link_path_w, &target_path_w, 0);
743 } else {
744 const target_path_c = try toPosixPath(target_path);
745 const sym_link_path_c = try toPosixPath(sym_link_path);
746 return symlinkC(&target_path_c, &sym_link_path_c);
747 }
748}
749
750/// This is the same as `symlink` except the parameters are null-terminated pointers.
751/// See also `symlink`.
752pub fn symlinkC(target_path: [*]const u8, sym_link_path: [*]const u8) SymLinkError!void {
753 if (windows.is_the_target and !builtin.link_libc) {
754 const target_path_w = try windows.cStrToPrefixedFileW(target_path);
755 const sym_link_path_w = try windows.cStrToPrefixedFileW(sym_link_path);
756 return windows.CreateSymbolicLinkW(&sym_link_path_w, &target_path_w, 0);
757 }
758 switch (errno(system.symlink(target_path, sym_link_path))) {
759 0 => return,
760 EFAULT => unreachable,
761 EINVAL => unreachable,
762 EACCES => return error.AccessDenied,
763 EPERM => return error.AccessDenied,
764 EDQUOT => return error.DiskQuota,
765 EEXIST => return error.PathAlreadyExists,
766 EIO => return error.FileSystem,
767 ELOOP => return error.SymLinkLoop,
768 ENAMETOOLONG => return error.NameTooLong,
769 ENOENT => return error.FileNotFound,
770 ENOTDIR => return error.NotDir,
771 ENOMEM => return error.SystemResources,
772 ENOSPC => return error.NoSpaceLeft,
773 EROFS => return error.ReadOnlyFileSystem,
774 else => |err| return unexpectedErrno(err),
775 }
776}
777
778pub fn symlinkat(target_path: []const u8, newdirfd: fd_t, sym_link_path: []const u8) SymLinkError!void {
779 const target_path_c = try toPosixPath(target_path);
780 const sym_link_path_c = try toPosixPath(sym_link_path);
781 return symlinkatC(target_path_c, newdirfd, sym_link_path_c);
782}
783
784pub fn symlinkatC(target_path: [*]const u8, newdirfd: fd_t, sym_link_path: [*]const u8) SymLinkError!void {
785 switch (errno(system.symlinkat(target_path, newdirfd, sym_link_path))) {
786 0 => return,
787 EFAULT => unreachable,
788 EINVAL => unreachable,
789 EACCES => return error.AccessDenied,
790 EPERM => return error.AccessDenied,
791 EDQUOT => return error.DiskQuota,
792 EEXIST => return error.PathAlreadyExists,
793 EIO => return error.FileSystem,
794 ELOOP => return error.SymLinkLoop,
795 ENAMETOOLONG => return error.NameTooLong,
796 ENOENT => return error.FileNotFound,
797 ENOTDIR => return error.NotDir,
798 ENOMEM => return error.SystemResources,
799 ENOSPC => return error.NoSpaceLeft,
800 EROFS => return error.ReadOnlyFileSystem,
801 else => |err| return unexpectedErrno(err),
802 }
803}
804
805pub const UnlinkError = error{
806 FileNotFound,
807 AccessDenied,
808 FileBusy,
809 FileSystem,
810 IsDir,
811 SymLinkLoop,
812 NameTooLong,
813 NotDir,
814 SystemResources,
815 ReadOnlyFileSystem,
816 Unexpected,
817
818 /// On Windows, file paths must be valid Unicode.
819 InvalidUtf8,
820
821 /// On Windows, file paths cannot contain these characters:
822 /// '/', '*', '?', '"', '<', '>', '|'
823 BadPathName,
824};
825
826/// Delete a name and possibly the file it refers to.
827/// See also `unlinkC`.
828pub fn unlink(file_path: []const u8) UnlinkError!void {
829 if (windows.is_the_target and !builtin.link_libc) {
830 const file_path_w = try windows.sliceToPrefixedFileW(file_path);
831 return windows.DeleteFileW(&file_path_w);
832 } else {
833 const file_path_c = try toPosixPath(file_path);
834 return unlinkC(&file_path_c);
835 }
836}
837
838/// Same as `unlink` except the parameter is a null terminated UTF8-encoded string.
839pub fn unlinkC(file_path: [*]const u8) UnlinkError!void {
840 if (windows.is_the_target and !builtin.link_libc) {
841 const file_path_w = try windows.cStrToPrefixedFileW(file_path);
842 return windows.DeleteFileW(&file_path_w);
843 }
844 switch (errno(system.unlink(file_path))) {
845 0 => return,
846 EACCES => return error.AccessDenied,
847 EPERM => return error.AccessDenied,
848 EBUSY => return error.FileBusy,
849 EFAULT => unreachable,
850 EINVAL => unreachable,
851 EIO => return error.FileSystem,
852 EISDIR => return error.IsDir,
853 ELOOP => return error.SymLinkLoop,
854 ENAMETOOLONG => return error.NameTooLong,
855 ENOENT => return error.FileNotFound,
856 ENOTDIR => return error.NotDir,
857 ENOMEM => return error.SystemResources,
858 EROFS => return error.ReadOnlyFileSystem,
859 else => |err| return unexpectedErrno(err),
860 }
861}
862
863const RenameError = error{
864 AccessDenied,
865 FileBusy,
866 DiskQuota,
867 IsDir,
868 SymLinkLoop,
869 LinkQuotaExceeded,
870 NameTooLong,
871 FileNotFound,
872 NotDir,
873 SystemResources,
874 NoSpaceLeft,
875 PathAlreadyExists,
876 ReadOnlyFileSystem,
877 RenameAcrossMountPoints,
878 Unexpected,
879};
880
881/// Change the name or location of a file.
882pub fn rename(old_path: []const u8, new_path: []const u8) RenameError!void {
883 if (windows.is_the_target and !builtin.link_libc) {
884 const old_path_w = try windows.sliceToPrefixedFileW(old_path);
885 const new_path_w = try windows.sliceToPrefixedFileW(new_path);
886 const flags = windows.MOVEFILE_REPLACE_EXISTING | windows.MOVEFILE_WRITE_THROUGH;
887 return windows.MoveFileExW(&old_path_w, &new_path_w, flags);
888 } else {
889 const old_path_c = try toPosixPath(old_path);
890 const new_path_c = try toPosixPath(new_path);
891 return renameC(&old_path_c, &new_path_c);
892 }
893}
894
895/// Same as `rename` except the parameters are null-terminated byte arrays.
896pub fn renameC(old_path: [*]const u8, new_path: [*]const u8) RenameError!void {
897 if (windows.is_the_target and !builtin.link_libc) {
898 const old_path_w = try windows.cStrToPrefixedFileW(old_path);
899 const new_path_w = try windows.cStrToPrefixedFileW(new_path);
900 const flags = windows.MOVEFILE_REPLACE_EXISTING | windows.MOVEFILE_WRITE_THROUGH;
901 return windows.MoveFileExW(&old_path_w, &new_path_w, flags);
902 }
903 switch (errno(system.rename(old_path, new_path))) {
904 0 => return,
905 EACCES => return error.AccessDenied,
906 EPERM => return error.AccessDenied,
907 EBUSY => return error.FileBusy,
908 EDQUOT => return error.DiskQuota,
909 EFAULT => unreachable,
910 EINVAL => unreachable,
911 EISDIR => return error.IsDir,
912 ELOOP => return error.SymLinkLoop,
913 EMLINK => return error.LinkQuotaExceeded,
914 ENAMETOOLONG => return error.NameTooLong,
915 ENOENT => return error.FileNotFound,
916 ENOTDIR => return error.NotDir,
917 ENOMEM => return error.SystemResources,
918 ENOSPC => return error.NoSpaceLeft,
919 EEXIST => return error.PathAlreadyExists,
920 ENOTEMPTY => return error.PathAlreadyExists,
921 EROFS => return error.ReadOnlyFileSystem,
922 EXDEV => return error.RenameAcrossMountPoints,
923 else => |err| return unexpectedErrno(err),
924 }
925}
926
927pub const MakeDirError = error{
928 AccessDenied,
929 DiskQuota,
930 PathAlreadyExists,
931 SymLinkLoop,
932 LinkQuotaExceeded,
933 NameTooLong,
934 FileNotFound,
935 SystemResources,
936 NoSpaceLeft,
937 NotDir,
938 ReadOnlyFileSystem,
939 Unexpected,
940};
941
942/// Create a directory.
943/// `mode` is ignored on Windows.
944pub fn mkdir(dir_path: []const u8, mode: u32) MakeDirError!void {
945 if (windows.is_the_target and !builtin.link_libc) {
946 const dir_path_w = try windows.sliceToPrefixedFileW(dir_path);
947 return windows.CreateDirectoryW(&dir_path_w, null);
948 } else {
949 const dir_path_c = try toPosixPath(dir_path);
950 return mkdirC(&dir_path_c, mode);
951 }
952}
953
954/// Same as `mkdir` but the parameter is a null-terminated UTF8-encoded string.
955pub fn mkdirC(dir_path: [*]const u8, mode: u32) MakeDirError!void {
956 if (windows.is_the_target and !builtin.link_libc) {
957 const dir_path_w = try windows.cStrToPrefixedFileW(dir_path);
958 return windows.CreateDirectoryW(&dir_path_w, null);
959 }
960 switch (errno(system.mkdir(dir_path, mode))) {
961 0 => return,
962 EACCES => return error.AccessDenied,
963 EPERM => return error.AccessDenied,
964 EDQUOT => return error.DiskQuota,
965 EEXIST => return error.PathAlreadyExists,
966 EFAULT => unreachable,
967 ELOOP => return error.SymLinkLoop,
968 EMLINK => return error.LinkQuotaExceeded,
969 ENAMETOOLONG => return error.NameTooLong,
970 ENOENT => return error.FileNotFound,
971 ENOMEM => return error.SystemResources,
972 ENOSPC => return error.NoSpaceLeft,
973 ENOTDIR => return error.NotDir,
974 EROFS => return error.ReadOnlyFileSystem,
975 else => |err| return unexpectedErrno(err),
976 }
977}
978
979pub const DeleteDirError = error{
980 AccessDenied,
981 FileBusy,
982 SymLinkLoop,
983 NameTooLong,
984 FileNotFound,
985 SystemResources,
986 NotDir,
987 DirNotEmpty,
988 ReadOnlyFileSystem,
989 InvalidUtf8,
990 BadPathName,
991 Unexpected,
992};
993
994/// Deletes an empty directory.
995pub fn rmdir(dir_path: []const u8) DeleteDirError!void {
996 if (windows.is_the_target and !builtin.link_libc) {
997 const dir_path_w = try windows.sliceToPrefixedFileW(dir_path);
998 return windows.RemoveDirectoryW(&dir_path_w);
999 } else {
1000 const dir_path_c = try toPosixPath(dir_path);
1001 return rmdirC(&dir_path_c);
1002 }
1003}
1004
1005/// Same as `rmdir` except the parameter is null-terminated.
1006pub fn rmdirC(dir_path: [*]const u8) DeleteDirError!void {
1007 if (windows.is_the_target and !builtin.link_libc) {
1008 const dir_path_w = try windows.cStrToPrefixedFileW(dir_path);
1009 return windows.RemoveDirectoryW(&dir_path_w);
1010 }
1011 switch (errno(system.rmdir(dir_path))) {
1012 0 => return,
1013 EACCES => return error.AccessDenied,
1014 EPERM => return error.AccessDenied,
1015 EBUSY => return error.FileBusy,
1016 EFAULT => unreachable,
1017 EINVAL => unreachable,
1018 ELOOP => return error.SymLinkLoop,
1019 ENAMETOOLONG => return error.NameTooLong,
1020 ENOENT => return error.FileNotFound,
1021 ENOMEM => return error.SystemResources,
1022 ENOTDIR => return error.NotDir,
1023 EEXIST => return error.DirNotEmpty,
1024 ENOTEMPTY => return error.DirNotEmpty,
1025 EROFS => return error.ReadOnlyFileSystem,
1026 else => |err| return unexpectedErrno(err),
1027 }
1028}
1029
1030pub const ChangeCurDirError = error{
1031 AccessDenied,
1032 FileSystem,
1033 SymLinkLoop,
1034 NameTooLong,
1035 FileNotFound,
1036 SystemResources,
1037 NotDir,
1038 Unexpected,
1039};
1040
1041/// Changes the current working directory of the calling process.
1042/// `dir_path` is recommended to be a UTF-8 encoded string.
1043pub fn chdir(dir_path: []const u8) ChangeCurDirError!void {
1044 if (windows.is_the_target and !builtin.link_libc) {
1045 const dir_path_w = try windows.sliceToPrefixedFileW(dir_path);
1046 @compileError("TODO implement chdir for Windows");
1047 } else {
1048 const dir_path_c = try toPosixPath(dir_path);
1049 return chdirC(&dir_path_c);
1050 }
1051}
1052
1053/// Same as `chdir` except the parameter is null-terminated.
1054pub fn chdirC(dir_path: [*]const u8) ChangeCurDirError!void {
1055 if (windows.is_the_target and !builtin.link_libc) {
1056 const dir_path_w = try windows.cStrToPrefixedFileW(dir_path);
1057 @compileError("TODO implement chdir for Windows");
1058 }
1059 switch (errno(system.chdir(dir_path))) {
1060 0 => return,
1061 EACCES => return error.AccessDenied,
1062 EFAULT => unreachable,
1063 EIO => return error.FileSystem,
1064 ELOOP => return error.SymLinkLoop,
1065 ENAMETOOLONG => return error.NameTooLong,
1066 ENOENT => return error.FileNotFound,
1067 ENOMEM => return error.SystemResources,
1068 ENOTDIR => return error.NotDir,
1069 else => |err| return unexpectedErrno(err),
1070 }
1071}
1072
1073pub const ReadLinkError = error{
1074 AccessDenied,
1075 FileSystem,
1076 SymLinkLoop,
1077 NameTooLong,
1078 FileNotFound,
1079 SystemResources,
1080 NotDir,
1081 Unexpected,
1082};
1083
1084/// Read value of a symbolic link.
1085/// The return value is a slice of `out_buffer` from index 0.
1086pub fn readlink(file_path: []const u8, out_buffer: []u8) ReadLinkError![]u8 {
1087 if (windows.is_the_target and !builtin.link_libc) {
1088 const file_path_w = try windows.sliceToPrefixedFileW(file_path);
1089 @compileError("TODO implement readlink for Windows");
1090 } else {
1091 const file_path_c = try toPosixPath(file_path);
1092 return readlinkC(&file_path_c, out_buffer);
1093 }
1094}
1095
1096/// Same as `readlink` except `file_path` is null-terminated.
1097pub fn readlinkC(file_path: [*]const u8, out_buffer: []u8) ReadLinkError![]u8 {
1098 if (windows.is_the_target and !builtin.link_libc) {
1099 const file_path_w = try windows.cStrToPrefixedFileW(file_path);
1100 @compileError("TODO implement readlink for Windows");
1101 }
1102 const rc = system.readlink(file_path, out_buffer.ptr, out_buffer.len);
1103 switch (errno(rc)) {
1104 0 => return out_buffer[0..rc],
1105 EACCES => return error.AccessDenied,
1106 EFAULT => unreachable,
1107 EINVAL => unreachable,
1108 EIO => return error.FileSystem,
1109 ELOOP => return error.SymLinkLoop,
1110 ENAMETOOLONG => return error.NameTooLong,
1111 ENOENT => return error.FileNotFound,
1112 ENOMEM => return error.SystemResources,
1113 ENOTDIR => return error.NotDir,
1114 else => |err| return unexpectedErrno(err),
1115 }
1116}
1117
1118pub const SetIdError = error{
1119 ResourceLimitReached,
1120 InvalidUserId,
1121 PermissionDenied,
1122 Unexpected,
1123};
1124
1125pub fn setuid(uid: u32) SetIdError!void {
1126 switch (errno(system.setuid(uid))) {
1127 0 => return,
1128 EAGAIN => return error.ResourceLimitReached,
1129 EINVAL => return error.InvalidUserId,
1130 EPERM => return error.PermissionDenied,
1131 else => |err| return unexpectedErrno(err),
1132 }
1133}
1134
1135pub fn setreuid(ruid: u32, euid: u32) SetIdError!void {
1136 switch (errno(system.setreuid(ruid, euid))) {
1137 0 => return,
1138 EAGAIN => return error.ResourceLimitReached,
1139 EINVAL => return error.InvalidUserId,
1140 EPERM => return error.PermissionDenied,
1141 else => |err| return unexpectedErrno(err),
1142 }
1143}
1144
1145pub fn setgid(gid: u32) SetIdError!void {
1146 switch (errno(system.setgid(gid))) {
1147 0 => return,
1148 EAGAIN => return error.ResourceLimitReached,
1149 EINVAL => return error.InvalidUserId,
1150 EPERM => return error.PermissionDenied,
1151 else => |err| return unexpectedErrno(err),
1152 }
1153}
1154
1155pub fn setregid(rgid: u32, egid: u32) SetIdError!void {
1156 switch (errno(system.setregid(rgid, egid))) {
1157 0 => return,
1158 EAGAIN => return error.ResourceLimitReached,
1159 EINVAL => return error.InvalidUserId,
1160 EPERM => return error.PermissionDenied,
1161 else => |err| return unexpectedErrno(err),
1162 }
1163}
1164
1165/// Test whether a file descriptor refers to a terminal.
1166pub fn isatty(handle: fd_t) bool {
1167 if (builtin.link_libc) {
1168 return system.isatty(handle) != 0;
1169 }
1170 if (windows.is_the_target) {
1171 if (isCygwinPty(handle))
1172 return true;
1173
1174 var out: windows.DWORD = undefined;
1175 return windows.kernel32.GetConsoleMode(handle, &out) != 0;
1176 }
1177 if (wasi.is_the_target) {
1178 @compileError("TODO implement std.os.posix.isatty for WASI");
1179 }
1180 if (linux.is_the_target) {
1181 var wsz: system.winsize = undefined;
1182 return system.syscall3(system.SYS_ioctl, @bitCast(usize, isize(handle)), TIOCGWINSZ, @ptrToInt(&wsz)) == 0;
1183 }
1184 unreachable;
1185}
1186
1187pub fn isCygwinPty(handle: fd_t) bool {
1188 if (!windows.is_the_target) return false;
1189
1190 const size = @sizeOf(windows.FILE_NAME_INFO);
1191 var name_info_bytes align(@alignOf(windows.FILE_NAME_INFO)) = []u8{0} ** (size + windows.MAX_PATH);
1192
1193 if (windows.kernel32.GetFileInformationByHandleEx(
1194 handle,
1195 windows.FileNameInfo,
1196 @ptrCast(*c_void, &name_info_bytes),
1197 name_info_bytes.len,
1198 ) == 0) {
1199 return false;
1200 }
1201
1202 const name_info = @ptrCast(*const windows.FILE_NAME_INFO, &name_info_bytes[0]);
1203 const name_bytes = name_info_bytes[size .. size + usize(name_info.FileNameLength)];
1204 const name_wide = @bytesToSlice(u16, name_bytes);
1205 return mem.indexOf(u16, name_wide, []u16{ 'm', 's', 'y', 's', '-' }) != null or
1206 mem.indexOf(u16, name_wide, []u16{ '-', 'p', 't', 'y' }) != null;
1207}
1208
1209pub const SocketError = error{
1210 /// Permission to create a socket of the specified type and/or
1211 /// pro‐tocol is denied.
1212 PermissionDenied,
1213
1214 /// The implementation does not support the specified address family.
1215 AddressFamilyNotSupported,
1216
1217 /// Unknown protocol, or protocol family not available.
1218 ProtocolFamilyNotAvailable,
1219
1220 /// The per-process limit on the number of open file descriptors has been reached.
1221 ProcessFdQuotaExceeded,
1222
1223 /// The system-wide limit on the total number of open files has been reached.
1224 SystemFdQuotaExceeded,
1225
1226 /// Insufficient memory is available. The socket cannot be created until sufficient
1227 /// resources are freed.
1228 SystemResources,
1229
1230 /// The protocol type or the specified protocol is not supported within this domain.
1231 ProtocolNotSupported,
1232
1233 Unexpected,
1234};
1235
1236pub fn socket(domain: u32, socket_type: u32, protocol: u32) SocketError!i32 {
1237 const rc = system.socket(domain, socket_type, protocol);
1238 switch (errno(rc)) {
1239 0 => return @intCast(i32, rc),
1240 EACCES => return error.PermissionDenied,
1241 EAFNOSUPPORT => return error.AddressFamilyNotSupported,
1242 EINVAL => return error.ProtocolFamilyNotAvailable,
1243 EMFILE => return error.ProcessFdQuotaExceeded,
1244 ENFILE => return error.SystemFdQuotaExceeded,
1245 ENOBUFS, ENOMEM => return error.SystemResources,
1246 EPROTONOSUPPORT => return error.ProtocolNotSupported,
1247 else => |err| return unexpectedErrno(err),
1248 }
1249}
1250
1251pub const BindError = error{
1252 /// The address is protected, and the user is not the superuser.
1253 /// For UNIX domain sockets: Search permission is denied on a component
1254 /// of the path prefix.
1255 AccessDenied,
1256
1257 /// The given address is already in use, or in the case of Internet domain sockets,
1258 /// The port number was specified as zero in the socket
1259 /// address structure, but, upon attempting to bind to an ephemeral port, it was
1260 /// determined that all port numbers in the ephemeral port range are currently in
1261 /// use. See the discussion of /proc/sys/net/ipv4/ip_local_port_range ip(7).
1262 AddressInUse,
1263
1264 /// A nonexistent interface was requested or the requested address was not local.
1265 AddressNotAvailable,
1266
1267 /// Too many symbolic links were encountered in resolving addr.
1268 SymLinkLoop,
1269
1270 /// addr is too long.
1271 NameTooLong,
1272
1273 /// A component in the directory prefix of the socket pathname does not exist.
1274 FileNotFound,
1275
1276 /// Insufficient kernel memory was available.
1277 SystemResources,
1278
1279 /// A component of the path prefix is not a directory.
1280 NotDir,
1281
1282 /// The socket inode would reside on a read-only filesystem.
1283 ReadOnlyFileSystem,
1284
1285 Unexpected,
1286};
1287
1288/// addr is `*const T` where T is one of the sockaddr
1289pub fn bind(fd: i32, addr: *const sockaddr) BindError!void {
1290 const rc = system.bind(fd, system, @sizeOf(sockaddr));
1291 switch (errno(rc)) {
1292 0 => return,
1293 EACCES => return error.AccessDenied,
1294 EADDRINUSE => return error.AddressInUse,
1295 EBADF => unreachable, // always a race condition if this error is returned
1296 EINVAL => unreachable,
1297 ENOTSOCK => unreachable,
1298 EADDRNOTAVAIL => return error.AddressNotAvailable,
1299 EFAULT => unreachable,
1300 ELOOP => return error.SymLinkLoop,
1301 ENAMETOOLONG => return error.NameTooLong,
1302 ENOENT => return error.FileNotFound,
1303 ENOMEM => return error.SystemResources,
1304 ENOTDIR => return error.NotDir,
1305 EROFS => return error.ReadOnlyFileSystem,
1306 else => |err| return unexpectedErrno(err),
1307 }
1308}
1309
1310const ListenError = error{
1311 /// Another socket is already listening on the same port.
1312 /// For Internet domain sockets, the socket referred to by sockfd had not previously
1313 /// been bound to an address and, upon attempting to bind it to an ephemeral port, it
1314 /// was determined that all port numbers in the ephemeral port range are currently in
1315 /// use. See the discussion of /proc/sys/net/ipv4/ip_local_port_range in ip(7).
1316 AddressInUse,
1317
1318 /// The file descriptor sockfd does not refer to a socket.
1319 FileDescriptorNotASocket,
1320
1321 /// The socket is not of a type that supports the listen() operation.
1322 OperationNotSupported,
1323
1324 Unexpected,
1325};
1326
1327pub fn listen(sockfd: i32, backlog: u32) ListenError!void {
1328 const rc = system.listen(sockfd, backlog);
1329 switch (errno(rc)) {
1330 0 => return,
1331 EADDRINUSE => return error.AddressInUse,
1332 EBADF => unreachable,
1333 ENOTSOCK => return error.FileDescriptorNotASocket,
1334 EOPNOTSUPP => return error.OperationNotSupported,
1335 else => |err| return unexpectedErrno(err),
1336 }
1337}
1338
1339pub const AcceptError = error{
1340 ConnectionAborted,
1341
1342 /// The per-process limit on the number of open file descriptors has been reached.
1343 ProcessFdQuotaExceeded,
1344
1345 /// The system-wide limit on the total number of open files has been reached.
1346 SystemFdQuotaExceeded,
1347
1348 /// Not enough free memory. This often means that the memory allocation is limited
1349 /// by the socket buffer limits, not by the system memory.
1350 SystemResources,
1351
1352 /// The file descriptor sockfd does not refer to a socket.
1353 FileDescriptorNotASocket,
1354
1355 /// The referenced socket is not of type SOCK_STREAM.
1356 OperationNotSupported,
1357
1358 ProtocolFailure,
1359
1360 /// Firewall rules forbid connection.
1361 BlockedByFirewall,
1362
1363 Unexpected,
1364};
1365
1366/// Accept a connection on a socket. `fd` must be opened in blocking mode.
1367/// See also `accept4_async`.
1368pub fn accept4(fd: i32, addr: *sockaddr, flags: u32) AcceptError!i32 {
1369 while (true) {
1370 var sockaddr_size = u32(@sizeOf(sockaddr));
1371 const rc = system.accept4(fd, addr, &sockaddr_size, flags);
1372 switch (errno(rc)) {
1373 0 => return @intCast(i32, rc),
1374 EINTR => continue,
1375 else => |err| return unexpectedErrno(err),
1376
1377 EAGAIN => unreachable, // This function is for blocking only.
1378 EBADF => unreachable, // always a race condition
1379 ECONNABORTED => return error.ConnectionAborted,
1380 EFAULT => unreachable,
1381 EINVAL => unreachable,
1382 EMFILE => return error.ProcessFdQuotaExceeded,
1383 ENFILE => return error.SystemFdQuotaExceeded,
1384 ENOBUFS => return error.SystemResources,
1385 ENOMEM => return error.SystemResources,
1386 ENOTSOCK => return error.FileDescriptorNotASocket,
1387 EOPNOTSUPP => return error.OperationNotSupported,
1388 EPROTO => return error.ProtocolFailure,
1389 EPERM => return error.BlockedByFirewall,
1390 }
1391 }
1392}
1393
1394/// This is the same as `accept4` except `fd` is expected to be non-blocking.
1395/// Returns -1 if would block.
1396pub fn accept4_async(fd: i32, addr: *sockaddr, flags: u32) AcceptError!i32 {
1397 while (true) {
1398 var sockaddr_size = u32(@sizeOf(sockaddr));
1399 const rc = system.accept4(fd, addr, &sockaddr_size, flags);
1400 switch (errno(rc)) {
1401 0 => return @intCast(i32, rc),
1402 EINTR => continue,
1403 else => |err| return unexpectedErrno(err),
1404
1405 EAGAIN => return -1,
1406 EBADF => unreachable, // always a race condition
1407 ECONNABORTED => return error.ConnectionAborted,
1408 EFAULT => unreachable,
1409 EINVAL => unreachable,
1410 EMFILE => return error.ProcessFdQuotaExceeded,
1411 ENFILE => return error.SystemFdQuotaExceeded,
1412 ENOBUFS => return error.SystemResources,
1413 ENOMEM => return error.SystemResources,
1414 ENOTSOCK => return error.FileDescriptorNotASocket,
1415 EOPNOTSUPP => return error.OperationNotSupported,
1416 EPROTO => return error.ProtocolFailure,
1417 EPERM => return error.BlockedByFirewall,
10831418 }
10841419 }
1420}
1421
1422pub const EpollCreateError = error{
1423 /// The per-user limit on the number of epoll instances imposed by
1424 /// /proc/sys/fs/epoll/max_user_instances was encountered. See epoll(7) for further
1425 /// details.
1426 /// Or, The per-process limit on the number of open file descriptors has been reached.
1427 ProcessFdQuotaExceeded,
1428
1429 /// The system-wide limit on the total number of open files has been reached.
1430 SystemFdQuotaExceeded,
1431
1432 /// There was insufficient memory to create the kernel object.
1433 SystemResources,
1434
1435 Unexpected,
1436};
1437
1438pub fn epoll_create1(flags: u32) EpollCreateError!i32 {
1439 const rc = system.epoll_create1(flags);
1440 switch (errno(rc)) {
1441 0 => return @intCast(i32, rc),
1442 else => |err| return unexpectedErrno(err),
1443
1444 EINVAL => unreachable,
1445 EMFILE => return error.ProcessFdQuotaExceeded,
1446 ENFILE => return error.SystemFdQuotaExceeded,
1447 ENOMEM => return error.SystemResources,
1448 }
1449}
10851450
1086 fn internalNext(self: *ArgIteratorWindows, allocator: *Allocator) NextError![]u8 {
1087 var buf = try Buffer.initSize(allocator, 0);
1088 defer buf.deinit();
1089
1090 var backslash_count: usize = 0;
1091 while (true) : (self.index += 1) {
1092 const byte = self.cmd_line[self.index];
1093 switch (byte) {
1094 0 => return buf.toOwnedSlice(),
1095 '"' => {
1096 const quote_is_real = backslash_count % 2 == 0;
1097 try self.emitBackslashes(&buf, backslash_count / 2);
1098 backslash_count = 0;
1099
1100 if (quote_is_real) {
1101 self.seen_quote_count += 1;
1102 if (self.seen_quote_count == self.quote_count and self.seen_quote_count % 2 == 1) {
1103 try buf.appendByte('"');
1104 }
1105 } else {
1106 try buf.appendByte('"');
1107 }
1108 },
1109 '\\' => {
1110 backslash_count += 1;
1111 },
1112 ' ', '\t' => {
1113 try self.emitBackslashes(&buf, backslash_count);
1114 backslash_count = 0;
1115 if (self.seen_quote_count % 2 == 1 and self.seen_quote_count != self.quote_count) {
1116 try buf.appendByte(byte);
1117 } else {
1118 return buf.toOwnedSlice();
1119 }
1120 },
1121 else => {
1122 try self.emitBackslashes(&buf, backslash_count);
1123 backslash_count = 0;
1124 try buf.appendByte(byte);
1125 },
1126 }
1127 }
1451pub const EpollCtlError = error{
1452 /// op was EPOLL_CTL_ADD, and the supplied file descriptor fd is already registered
1453 /// with this epoll instance.
1454 FileDescriptorAlreadyPresentInSet,
1455
1456 /// fd refers to an epoll instance and this EPOLL_CTL_ADD operation would result in a
1457 /// circular loop of epoll instances monitoring one another.
1458 OperationCausesCircularLoop,
1459
1460 /// op was EPOLL_CTL_MOD or EPOLL_CTL_DEL, and fd is not registered with this epoll
1461 /// instance.
1462 FileDescriptorNotRegistered,
1463
1464 /// There was insufficient memory to handle the requested op control operation.
1465 SystemResources,
1466
1467 /// The limit imposed by /proc/sys/fs/epoll/max_user_watches was encountered while
1468 /// trying to register (EPOLL_CTL_ADD) a new file descriptor on an epoll instance.
1469 /// See epoll(7) for further details.
1470 UserResourceLimitReached,
1471
1472 /// The target file fd does not support epoll. This error can occur if fd refers to,
1473 /// for example, a regular file or a directory.
1474 FileDescriptorIncompatibleWithEpoll,
1475
1476 Unexpected,
1477};
1478
1479pub fn epoll_ctl(epfd: i32, op: u32, fd: i32, event: *epoll_event) EpollCtlError!void {
1480 const rc = system.epoll_ctl(epfd, op, fd, event);
1481 switch (errno(rc)) {
1482 0 => return,
1483 else => |err| return unexpectedErrno(err),
1484
1485 EBADF => unreachable, // always a race condition if this happens
1486 EEXIST => return error.FileDescriptorAlreadyPresentInSet,
1487 EINVAL => unreachable,
1488 ELOOP => return error.OperationCausesCircularLoop,
1489 ENOENT => return error.FileDescriptorNotRegistered,
1490 ENOMEM => return error.SystemResources,
1491 ENOSPC => return error.UserResourceLimitReached,
1492 EPERM => return error.FileDescriptorIncompatibleWithEpoll,
11281493 }
1494}
11291495
1130 fn emitBackslashes(self: *ArgIteratorWindows, buf: *Buffer, emit_count: usize) !void {
1131 var i: usize = 0;
1132 while (i < emit_count) : (i += 1) {
1133 try buf.appendByte('\\');
1496/// Waits for an I/O event on an epoll file descriptor.
1497/// Returns the number of file descriptors ready for the requested I/O,
1498/// or zero if no file descriptor became ready during the requested timeout milliseconds.
1499pub fn epoll_wait(epfd: i32, events: []epoll_event, timeout: i32) usize {
1500 while (true) {
1501 // TODO get rid of the @intCast
1502 const rc = system.epoll_wait(epfd, events.ptr, @intCast(u32, events.len), timeout);
1503 switch (errno(rc)) {
1504 0 => return rc,
1505 EINTR => continue,
1506 EBADF => unreachable,
1507 EFAULT => unreachable,
1508 EINVAL => unreachable,
1509 else => unreachable,
11341510 }
11351511 }
1512}
11361513
1137 fn countQuotes(cmd_line: [*]const u8) usize {
1138 var result: usize = 0;
1139 var backslash_count: usize = 0;
1140 var index: usize = 0;
1141 while (true) : (index += 1) {
1142 const byte = cmd_line[index];
1143 switch (byte) {
1144 0 => return result,
1145 '\\' => backslash_count += 1,
1146 '"' => {
1147 result += 1 - (backslash_count % 2);
1148 backslash_count = 0;
1149 },
1150 else => {
1151 backslash_count = 0;
1152 },
1153 }
1154 }
1514pub const EventFdError = error{
1515 SystemResources,
1516 ProcessFdQuotaExceeded,
1517 SystemFdQuotaExceeded,
1518 Unexpected,
1519};
1520
1521pub fn eventfd(initval: u32, flags: u32) EventFdError!i32 {
1522 const rc = system.eventfd(initval, flags);
1523 switch (errno(rc)) {
1524 0 => return @intCast(i32, rc),
1525 else => |err| return unexpectedErrno(err),
1526
1527 EINVAL => unreachable, // invalid parameters
1528 EMFILE => return error.ProcessFdQuotaExceeded,
1529 ENFILE => return error.SystemFdQuotaExceeded,
1530 ENODEV => return error.SystemResources,
1531 ENOMEM => return error.SystemResources,
11551532 }
1533}
1534
1535pub const GetSockNameError = error{
1536 /// Insufficient resources were available in the system to perform the operation.
1537 SystemResources,
1538
1539 Unexpected,
11561540};
11571541
1158pub const ArgIterator = struct {
1159 const InnerType = if (builtin.os == Os.windows) ArgIteratorWindows else ArgIteratorPosix;
1542pub fn getsockname(sockfd: i32) GetSockNameError!sockaddr {
1543 var addr: sockaddr = undefined;
1544 var addrlen: socklen_t = @sizeOf(sockaddr);
1545 switch (errno(system.getsockname(sockfd, &addr, &addrlen))) {
1546 0 => return addr,
1547 else => |err| return unexpectedErrno(err),
1548
1549 EBADF => unreachable, // always a race condition
1550 EFAULT => unreachable,
1551 EINVAL => unreachable, // invalid parameters
1552 ENOTSOCK => unreachable,
1553 ENOBUFS => return error.SystemResources,
1554 }
1555}
1556
1557pub const ConnectError = error{
1558 /// For UNIX domain sockets, which are identified by pathname: Write permission is denied on the socket
1559 /// file, or search permission is denied for one of the directories in the path prefix.
1560 /// or
1561 /// The user tried to connect to a broadcast address without having the socket broadcast flag enabled or
1562 /// the connection request failed because of a local firewall rule.
1563 PermissionDenied,
1564
1565 /// Local address is already in use.
1566 AddressInUse,
1567
1568 /// (Internet domain sockets) The socket referred to by sockfd had not previously been bound to an
1569 /// address and, upon attempting to bind it to an ephemeral port, it was determined that all port numbers
1570 /// in the ephemeral port range are currently in use. See the discussion of
1571 /// /proc/sys/net/ipv4/ip_local_port_range in ip(7).
1572 AddressNotAvailable,
1573
1574 /// The passed address didn't have the correct address family in its sa_family field.
1575 AddressFamilyNotSupported,
1576
1577 /// Insufficient entries in the routing cache.
1578 SystemResources,
1579
1580 /// A connect() on a stream socket found no one listening on the remote address.
1581 ConnectionRefused,
1582
1583 /// Network is unreachable.
1584 NetworkUnreachable,
11601585
1161 inner: InnerType,
1586 /// Timeout while attempting connection. The server may be too busy to accept new connections. Note
1587 /// that for IP sockets the timeout may be very long when syncookies are enabled on the server.
1588 ConnectionTimedOut,
1589
1590 Unexpected,
1591};
11621592
1163 pub fn init() ArgIterator {
1164 if (builtin.os == Os.wasi) {
1165 // TODO: Figure out a compatible interface accomodating WASI
1166 @compileError("ArgIterator is not yet supported in WASI. Use argsAlloc and argsFree instead.");
1593/// Initiate a connection on a socket.
1594/// This is for blocking file descriptors only.
1595/// For non-blocking, see `connect_async`.
1596pub fn connect(sockfd: i32, sockaddr: *const sockaddr) ConnectError!void {
1597 while (true) {
1598 switch (errno(system.connect(sockfd, sockaddr, @sizeOf(sockaddr)))) {
1599 0 => return,
1600 else => |err| return unexpectedErrno(err),
1601
1602 EACCES => return error.PermissionDenied,
1603 EPERM => return error.PermissionDenied,
1604 EADDRINUSE => return error.AddressInUse,
1605 EADDRNOTAVAIL => return error.AddressNotAvailable,
1606 EAFNOSUPPORT => return error.AddressFamilyNotSupported,
1607 EAGAIN => return error.SystemResources,
1608 EALREADY => unreachable, // The socket is nonblocking and a previous connection attempt has not yet been completed.
1609 EBADF => unreachable, // sockfd is not a valid open file descriptor.
1610 ECONNREFUSED => return error.ConnectionRefused,
1611 EFAULT => unreachable, // The socket structure address is outside the user's address space.
1612 EINPROGRESS => unreachable, // The socket is nonblocking and the connection cannot be completed immediately.
1613 EINTR => continue,
1614 EISCONN => unreachable, // The socket is already connected.
1615 ENETUNREACH => return error.NetworkUnreachable,
1616 ENOTSOCK => unreachable, // The file descriptor sockfd does not refer to a socket.
1617 EPROTOTYPE => unreachable, // The socket type does not support the requested communications protocol.
1618 ETIMEDOUT => return error.ConnectionTimedOut,
11671619 }
1620 }
1621}
11681622
1169 return ArgIterator{ .inner = InnerType.init() };
1623/// Same as `connect` except it is for blocking socket file descriptors.
1624/// It expects to receive EINPROGRESS`.
1625pub fn connect_async(sockfd: i32, sockaddr: *const c_void, len: u32) ConnectError!void {
1626 while (true) {
1627 switch (errno(system.connect(sockfd, sockaddr, len))) {
1628 0, EINPROGRESS => return,
1629 EINTR => continue,
1630 else => |err| return unexpectedErrno(err),
1631
1632 EACCES => return error.PermissionDenied,
1633 EPERM => return error.PermissionDenied,
1634 EADDRINUSE => return error.AddressInUse,
1635 EADDRNOTAVAIL => return error.AddressNotAvailable,
1636 EAFNOSUPPORT => return error.AddressFamilyNotSupported,
1637 EAGAIN => return error.SystemResources,
1638 EALREADY => unreachable, // The socket is nonblocking and a previous connection attempt has not yet been completed.
1639 EBADF => unreachable, // sockfd is not a valid open file descriptor.
1640 ECONNREFUSED => return error.ConnectionRefused,
1641 EFAULT => unreachable, // The socket structure address is outside the user's address space.
1642 EISCONN => unreachable, // The socket is already connected.
1643 ENETUNREACH => return error.NetworkUnreachable,
1644 ENOTSOCK => unreachable, // The file descriptor sockfd does not refer to a socket.
1645 EPROTOTYPE => unreachable, // The socket type does not support the requested communications protocol.
1646 ETIMEDOUT => return error.ConnectionTimedOut,
1647 }
11701648 }
1649}
11711650
1172 pub const NextError = ArgIteratorWindows.NextError;
1651pub fn getsockoptError(sockfd: i32) ConnectError!void {
1652 var err_code: i32 = undefined;
1653 var size: u32 = @sizeOf(i32);
1654 const rc = system.getsockopt(sockfd, SOL_SOCKET, SO_ERROR, @ptrCast([*]u8, &err_code), &size);
1655 assert(size == 4);
1656 switch (errno(rc)) {
1657 0 => switch (err_code) {
1658 0 => return,
1659 EACCES => return error.PermissionDenied,
1660 EPERM => return error.PermissionDenied,
1661 EADDRINUSE => return error.AddressInUse,
1662 EADDRNOTAVAIL => return error.AddressNotAvailable,
1663 EAFNOSUPPORT => return error.AddressFamilyNotSupported,
1664 EAGAIN => return error.SystemResources,
1665 EALREADY => unreachable, // The socket is nonblocking and a previous connection attempt has not yet been completed.
1666 EBADF => unreachable, // sockfd is not a valid open file descriptor.
1667 ECONNREFUSED => return error.ConnectionRefused,
1668 EFAULT => unreachable, // The socket structure address is outside the user's address space.
1669 EISCONN => unreachable, // The socket is already connected.
1670 ENETUNREACH => return error.NetworkUnreachable,
1671 ENOTSOCK => unreachable, // The file descriptor sockfd does not refer to a socket.
1672 EPROTOTYPE => unreachable, // The socket type does not support the requested communications protocol.
1673 ETIMEDOUT => return error.ConnectionTimedOut,
1674 else => |err| return unexpectedErrno(err),
1675 },
1676 EBADF => unreachable, // The argument sockfd is not a valid file descriptor.
1677 EFAULT => unreachable, // The address pointed to by optval or optlen is not in a valid part of the process address space.
1678 EINVAL => unreachable,
1679 ENOPROTOOPT => unreachable, // The option is unknown at the level indicated.
1680 ENOTSOCK => unreachable, // The file descriptor sockfd does not refer to a socket.
1681 else => |err| return unexpectedErrno(err),
1682 }
1683}
11731684
1174 /// You must free the returned memory when done.
1175 pub fn next(self: *ArgIterator, allocator: *Allocator) ?(NextError![]u8) {
1176 if (builtin.os == Os.windows) {
1177 return self.inner.next(allocator);
1178 } else {
1179 return mem.dupe(allocator, u8, self.inner.next() orelse return null);
1685pub fn waitpid(pid: i32) i32 {
1686 var status: i32 = undefined;
1687 while (true) {
1688 switch (errno(system.waitpid(pid, &status, 0))) {
1689 0 => return status,
1690 EINTR => continue,
1691 ECHILD => unreachable, // The process specified does not exist. It would be a race condition to handle this error.
1692 EINVAL => unreachable, // The options argument was invalid
1693 else => unreachable,
11801694 }
11811695 }
1696}
11821697
1183 /// If you only are targeting posix you can call this and not need an allocator.
1184 pub fn nextPosix(self: *ArgIterator) ?[]const u8 {
1185 return self.inner.next();
1698pub const FStatError = error{
1699 SystemResources,
1700 Unexpected,
1701};
1702
1703pub fn fstat(fd: fd_t) FStatError!Stat {
1704 var stat: Stat = undefined;
1705 if (os.darwin.is_the_target) {
1706 switch (errno(system.@"fstat$INODE64"(fd, buf))) {
1707 0 => return stat,
1708 EBADF => unreachable, // Always a race condition.
1709 ENOMEM => return error.SystemResources,
1710 else => |err| return unexpectedErrno(err),
1711 }
11861712 }
11871713
1188 /// Parse past 1 argument without capturing it.
1189 /// Returns `true` if skipped an arg, `false` if we are at the end.
1190 pub fn skip(self: *ArgIterator) bool {
1191 return self.inner.skip();
1714 switch (errno(system.fstat(fd, &stat))) {
1715 0 => return stat,
1716 EBADF => unreachable, // Always a race condition.
1717 ENOMEM => return error.SystemResources,
1718 else => |err| return unexpectedErrno(err),
11921719 }
1720}
1721
1722pub const KQueueError = error{
1723 /// The per-process limit on the number of open file descriptors has been reached.
1724 ProcessFdQuotaExceeded,
1725
1726 /// The system-wide limit on the total number of open files has been reached.
1727 SystemFdQuotaExceeded,
1728
1729 Unexpected,
11931730};
11941731
1195pub fn args() ArgIterator {
1196 return ArgIterator.init();
1732pub fn kqueue() KQueueError!i32 {
1733 const rc = system.kqueue();
1734 switch (errno(rc)) {
1735 0 => return @intCast(i32, rc),
1736 EMFILE => return error.ProcessFdQuotaExceeded,
1737 ENFILE => return error.SystemFdQuotaExceeded,
1738 else => |err| return unexpectedErrno(err),
1739 }
11971740}
11981741
1199/// Caller must call argsFree on result.
1200pub fn argsAlloc(allocator: *mem.Allocator) ![]const []u8 {
1201 if (builtin.os == Os.wasi) {
1202 var count: usize = undefined;
1203 var buf_size: usize = undefined;
1204
1205 const args_sizes_get_ret = os.wasi.args_sizes_get(&count, &buf_size);
1206 if (args_sizes_get_ret != os.wasi.ESUCCESS) {
1207 return unexpectedErrorPosix(args_sizes_get_ret);
1208 }
1742pub const KEventError = error{
1743 /// The process does not have permission to register a filter.
1744 AccessDenied,
12091745
1210 var argv = try allocator.alloc([*]u8, count);
1211 defer allocator.free(argv);
1746 /// The event could not be found to be modified or deleted.
1747 EventNotFound,
12121748
1213 var argv_buf = try allocator.alloc(u8, buf_size);
1214 const args_get_ret = os.wasi.args_get(argv.ptr, argv_buf.ptr);
1215 if (args_get_ret != os.wasi.ESUCCESS) {
1216 return unexpectedErrorPosix(args_get_ret);
1217 }
1749 /// No memory was available to register the event.
1750 SystemResources,
12181751
1219 var result_slice = try allocator.alloc([]u8, count);
1752 /// The specified process to attach to does not exist.
1753 ProcessNotFound,
1754};
12201755
1221 var i: usize = 0;
1222 while (i < count) : (i += 1) {
1223 result_slice[i] = mem.toSlice(u8, argv[i]);
1756pub fn kevent(
1757 kq: i32,
1758 changelist: []const Kevent,
1759 eventlist: []Kevent,
1760 timeout: ?*const timespec,
1761) KEventError!usize {
1762 while (true) {
1763 const rc = system.kevent(kq, changelist, eventlist, timeout);
1764 switch (errno(rc)) {
1765 0 => return rc,
1766 EACCES => return error.AccessDenied,
1767 EFAULT => unreachable,
1768 EBADF => unreachable, // Always a race condition.
1769 EINTR => continue,
1770 EINVAL => unreachable,
1771 ENOENT => return error.EventNotFound,
1772 ENOMEM => return error.SystemResources,
1773 ESRCH => return error.ProcessNotFound,
1774 else => unreachable,
12241775 }
1776 }
1777}
1778
1779pub const INotifyInitError = error{
1780 ProcessFdQuotaExceeded,
1781 SystemFdQuotaExceeded,
1782 SystemResources,
1783 Unexpected,
1784};
12251785
1226 return result_slice;
1786/// initialize an inotify instance
1787pub fn inotify_init1(flags: u32) INotifyInitError!i32 {
1788 const rc = system.inotify_init1(flags);
1789 switch (errno(rc)) {
1790 0 => return @intCast(i32, rc),
1791 EINVAL => unreachable,
1792 EMFILE => return error.ProcessFdQuotaExceeded,
1793 ENFILE => return error.SystemFdQuotaExceeded,
1794 ENOMEM => return error.SystemResources,
1795 else => |err| return unexpectedErrno(err),
12271796 }
1797}
12281798
1229 // TODO refactor to only make 1 allocation.
1230 var it = args();
1231 var contents = try Buffer.initSize(allocator, 0);
1232 defer contents.deinit();
1799pub const INotifyAddWatchError = error{
1800 AccessDenied,
1801 NameTooLong,
1802 FileNotFound,
1803 SystemResources,
1804 UserResourceLimitReached,
1805 Unexpected,
1806};
12331807
1234 var slice_list = ArrayList(usize).init(allocator);
1235 defer slice_list.deinit();
1808/// add a watch to an initialized inotify instance
1809pub fn inotify_add_watch(inotify_fd: i32, pathname: []const u8, mask: u32) INotifyAddWatchError!i32 {
1810 const pathname_c = try toPosixPath(pathname);
1811 return inotify_add_watchC(inotify_fd, &pathname_c, mask);
1812}
12361813
1237 while (it.next(allocator)) |arg_or_err| {
1238 const arg = try arg_or_err;
1239 defer allocator.free(arg);
1240 try contents.append(arg);
1241 try slice_list.append(arg.len);
1814/// Same as `inotify_add_watch` except pathname is null-terminated.
1815pub fn inotify_add_watchC(inotify_fd: i32, pathname: [*]const u8, mask: u32) INotifyAddWatchError!i32 {
1816 const rc = system.inotify_add_watch(inotify_fd, pathname, mask);
1817 switch (errno(rc)) {
1818 0 => return @intCast(i32, rc),
1819 EACCES => return error.AccessDenied,
1820 EBADF => unreachable,
1821 EFAULT => unreachable,
1822 EINVAL => unreachable,
1823 ENAMETOOLONG => return error.NameTooLong,
1824 ENOENT => return error.FileNotFound,
1825 ENOMEM => return error.SystemResources,
1826 ENOSPC => return error.UserResourceLimitReached,
1827 else => |err| return unexpectedErrno(err),
12421828 }
1829}
12431830
1244 const contents_slice = contents.toSliceConst();
1245 const slice_sizes = slice_list.toSliceConst();
1246 const slice_list_bytes = try math.mul(usize, @sizeOf([]u8), slice_sizes.len);
1247 const total_bytes = try math.add(usize, slice_list_bytes, contents_slice.len);
1248 const buf = try allocator.alignedAlloc(u8, @alignOf([]u8), total_bytes);
1249 errdefer allocator.free(buf);
1831/// remove an existing watch from an inotify instance
1832pub fn inotify_rm_watch(inotify_fd: i32, wd: i32) void {
1833 switch (errno(system.inotify_rm_watch(inotify_fd, wd))) {
1834 0 => return,
1835 EBADF => unreachable,
1836 EINVAL => unreachable,
1837 else => unreachable,
1838 }
1839}
12501840
1251 const result_slice_list = @bytesToSlice([]u8, buf[0..slice_list_bytes]);
1252 const result_contents = buf[slice_list_bytes..];
1253 mem.copy(u8, result_contents, contents_slice);
1841pub const MProtectError = error{
1842 AccessDenied,
1843 OutOfMemory,
1844 Unexpected,
1845};
12541846
1255 var contents_index: usize = 0;
1256 for (slice_sizes) |len, i| {
1257 const new_index = contents_index + len;
1258 result_slice_list[i] = result_contents[contents_index..new_index];
1259 contents_index = new_index;
1847/// address and length must be page-aligned
1848pub fn mprotect(address: usize, length: usize, protection: u32) MProtectError!void {
1849 const negative_page_size = @bitCast(usize, -isize(mem.page_size));
1850 const aligned_address = address & negative_page_size;
1851 const aligned_end = (address + length + mem.page_size - 1) & negative_page_size;
1852 assert(address == aligned_address);
1853 assert(length == aligned_end - aligned_address);
1854 switch (errno(system.mprotect(address, length, protection))) {
1855 0 => return,
1856 EINVAL => unreachable,
1857 EACCES => return error.AccessDenied,
1858 ENOMEM => return error.OutOfMemory,
1859 else => return unexpectedErrno(err),
12601860 }
1861}
1862
1863pub const ForkError = error{
1864 SystemResources,
1865 Unexpected,
1866};
12611867
1262 return result_slice_list;
1868pub fn fork() ForkError!pid_t {
1869 const rc = system.fork();
1870 switch (errno(rc)) {
1871 0 => return rc,
1872 EAGAIN => return error.SystemResources,
1873 ENOMEM => return error.SystemResources,
1874 else => |err| return unexpectedErrno(err),
1875 }
12631876}
12641877
1265pub fn argsFree(allocator: *mem.Allocator, args_alloc: []const []u8) void {
1266 if (builtin.os == Os.wasi) {
1267 const last_item = args_alloc[args_alloc.len - 1];
1268 const last_byte_addr = @ptrToInt(last_item.ptr) + last_item.len + 1; // null terminated
1269 const first_item_ptr = args_alloc[0].ptr;
1270 const len = last_byte_addr - @ptrToInt(first_item_ptr);
1271 allocator.free(first_item_ptr[0..len]);
1878pub const MMapError = error{
1879 AccessDenied,
1880 PermissionDenied,
1881 LockedMemoryLimitExceeded,
1882 SystemFdQuotaExceeded,
1883 MemoryMappingNotSupported,
1884 OutOfMemory,
1885};
12721886
1273 return allocator.free(args_alloc);
1887/// Map files or devices into memory.
1888/// Use of a mapped region can result in these signals:
1889/// * SIGSEGV - Attempted write into a region mapped as read-only.
1890/// * SIGBUS - Attempted access to a portion of the buffer that does not correspond to the file
1891pub fn mmap(address: ?[*]u8, length: usize, prot: u32, flags: u32, fd: fd_t, offset: isize) MMapError!usize {
1892 const err = if (builtin.link_libc) blk: {
1893 const rc = system.mmap(address, length, prot, flags, fd, offset);
1894 if (rc != system.MMAP_FAILED) return rc;
1895 break :blk system._errno().*;
1896 } else blk: {
1897 const rc = system.mmap(address, length, prot, flags, fd, offset);
1898 const err = errno(rc);
1899 if (err == 0) return rc;
1900 break :blk err;
1901 };
1902 switch (err) {
1903 ETXTBSY => return error.AccessDenied,
1904 EACCES => return error.AccessDenied,
1905 EPERM => return error.PermissionDenied,
1906 EAGAIN => return error.LockedMemoryLimitExceeded,
1907 EBADF => unreachable, // Always a race condition.
1908 EOVERFLOW => unreachable, // The number of pages used for length + offset would overflow.
1909 ENFILE => return error.SystemFdQuotaExceeded,
1910 ENODEV => return error.MemoryMappingNotSupported,
1911 EINVAL => unreachable, // Invalid parameters to mmap()
1912 ENOMEM => return error.OutOfMemory,
1913 else => return unexpectedErrno(err),
12741914 }
1915}
12751916
1276 var total_bytes: usize = 0;
1277 for (args_alloc) |arg| {
1278 total_bytes += @sizeOf([]u8) + arg.len;
1917/// Deletes the mappings for the specified address range, causing
1918/// further references to addresses within the range to generate invalid memory references.
1919/// Note that while POSIX allows unmapping a region in the middle of an existing mapping,
1920/// Zig's munmap function does not, for two reasons:
1921/// * It violates the Zig principle that resource deallocation must succeed.
1922/// * The Windows function, VirtualFree, has this restriction.
1923pub fn munmap(address: usize, length: usize) void {
1924 switch (errno(system.munmap(address, length))) {
1925 0 => return,
1926 EINVAL => unreachable, // Invalid parameters.
1927 ENOMEM => unreachable, // Attempted to unmap a region in the middle of an existing mapping.
1928 else => unreachable,
12791929 }
1280 const unaligned_allocated_buf = @ptrCast([*]const u8, args_alloc.ptr)[0..total_bytes];
1281 const aligned_allocated_buf = @alignCast(@alignOf([]u8), unaligned_allocated_buf);
1282 return allocator.free(aligned_allocated_buf);
12831930}
12841931
1285test "windows arg parsing" {
1286 testWindowsCmdLine(c"a b\tc d", [][]const u8{ "a", "b", "c", "d" });
1287 testWindowsCmdLine(c"\"abc\" d e", [][]const u8{ "abc", "d", "e" });
1288 testWindowsCmdLine(c"a\\\\\\b d\"e f\"g h", [][]const u8{ "a\\\\\\b", "de fg", "h" });
1289 testWindowsCmdLine(c"a\\\\\\\"b c d", [][]const u8{ "a\\\"b", "c", "d" });
1290 testWindowsCmdLine(c"a\\\\\\\\\"b c\" d e", [][]const u8{ "a\\\\b c", "d", "e" });
1291 testWindowsCmdLine(c"a b\tc \"d f", [][]const u8{ "a", "b", "c", "\"d", "f" });
1932pub const AccessError = error{
1933 PermissionDenied,
1934 FileNotFound,
1935 NameTooLong,
1936 InputOutput,
1937 SystemResources,
1938 BadPathName,
1939
1940 /// On Windows, file paths must be valid Unicode.
1941 InvalidUtf8,
1942
1943 Unexpected,
1944};
12921945
1293 testWindowsCmdLine(c"\".\\..\\zig-cache\\build\" \"bin\\zig.exe\" \".\\..\" \".\\..\\zig-cache\" \"--help\"", [][]const u8{
1294 ".\\..\\zig-cache\\build",
1295 "bin\\zig.exe",
1296 ".\\..",
1297 ".\\..\\zig-cache",
1298 "--help",
1299 });
1946/// check user's permissions for a file
1947/// TODO currently this assumes `mode` is `F_OK` on Windows.
1948pub fn access(path: []const u8, mode: u32) AccessError!void {
1949 if (windows.is_the_target and !builtin.link_libc) {
1950 const path_w = try windows.sliceToPrefixedFileW(path);
1951 _ = try windows.GetFileAttributesW(&path_w);
1952 return;
1953 }
1954 const path_c = try toPosixPath(path);
1955 return accessC(&path_c, mode);
13001956}
13011957
1302fn testWindowsCmdLine(input_cmd_line: [*]const u8, expected_args: []const []const u8) void {
1303 var it = ArgIteratorWindows.initWithCmdLine(input_cmd_line);
1304 for (expected_args) |expected_arg| {
1305 const arg = it.next(debug.global_allocator).? catch unreachable;
1306 testing.expectEqualSlices(u8, expected_arg, arg);
1958/// Same as `access` except `path` is null-terminated.
1959pub fn accessC(path: [*]const u8, mode: u32) AccessError!void {
1960 if (windows.is_the_target and !builtin.link_libc) {
1961 const path_w = try windows.cStrToPrefixedFileW(path);
1962 _ = try windows.GetFileAttributesW(&path_w);
1963 return;
1964 }
1965 switch (errno(system.access(path, mode))) {
1966 0 => return,
1967 EACCES => return error.PermissionDenied,
1968 EROFS => return error.PermissionDenied,
1969 ELOOP => return error.PermissionDenied,
1970 ETXTBSY => return error.PermissionDenied,
1971 ENOTDIR => return error.FileNotFound,
1972 ENOENT => return error.FileNotFound,
1973
1974 ENAMETOOLONG => return error.NameTooLong,
1975 EINVAL => unreachable,
1976 EFAULT => unreachable,
1977 EIO => return error.InputOutput,
1978 ENOMEM => return error.SystemResources,
1979 else => |err| return unexpectedErrno(err),
13071980 }
1308 testing.expect(it.next(debug.global_allocator) == null);
13091981}
13101982
1311pub fn openSelfExe() !os.File {
1312 switch (builtin.os) {
1313 Os.linux => return os.File.openReadC(c"/proc/self/exe"),
1314 Os.macosx, Os.ios, Os.freebsd, Os.netbsd => {
1315 var buf: [MAX_PATH_BYTES]u8 = undefined;
1316 const self_exe_path = try selfExePath(&buf);
1317 buf[self_exe_path.len] = 0;
1318 return os.File.openReadC(self_exe_path.ptr);
1319 },
1320 Os.windows => {
1321 var buf: [posix.PATH_MAX_WIDE]u16 = undefined;
1322 const wide_slice = try selfExePathW(&buf);
1323 return os.File.openReadW(wide_slice.ptr);
1324 },
1325 else => @compileError("Unsupported OS"),
1983pub const PipeError = error{
1984 SystemFdQuotaExceeded,
1985 ProcessFdQuotaExceeded,
1986};
1987
1988/// Creates a unidirectional data channel that can be used for interprocess communication.
1989pub fn pipe(fds: *[2]fd_t) PipeError!void {
1990 switch (errno(system.pipe(fds))) {
1991 0 => return,
1992 EINVAL => unreachable, // Invalid parameters to pipe()
1993 EFAULT => unreachable, // Invalid fds pointer
1994 ENFILE => return error.SystemFdQuotaExceeded,
1995 EMFILE => return error.ProcessFdQuotaExceeded,
1996 else => |err| return unexpectedErrno(err),
13261997 }
13271998}
13281999
1329test "openSelfExe" {
1330 switch (builtin.os) {
1331 Os.linux, Os.macosx, Os.ios, Os.windows, Os.freebsd => (try openSelfExe()).close(),
1332 else => return error.SkipZigTest, // Unsupported OS.
2000pub fn pipe2(fds: *[2]fd_t, flags: u32) PipeError!void {
2001 switch (errno(system.pipe2(fds, flags))) {
2002 0 => return,
2003 EINVAL => unreachable, // Invalid flags
2004 EFAULT => unreachable, // Invalid fds pointer
2005 ENFILE => return error.SystemFdQuotaExceeded,
2006 EMFILE => return error.ProcessFdQuotaExceeded,
2007 else => |err| return unexpectedErrno(err),
13332008 }
13342009}
13352010
1336pub fn selfExePathW(out_buffer: *[posix.PATH_MAX_WIDE]u16) ![]u16 {
1337 const casted_len = @intCast(windows.DWORD, out_buffer.len); // TODO shouldn't need this cast
1338 const rc = windows.GetModuleFileNameW(null, out_buffer, casted_len);
1339 assert(rc <= out_buffer.len);
1340 if (rc == 0) {
1341 const err = windows.GetLastError();
1342 switch (err) {
1343 else => return windows.unexpectedError(err),
1344 }
2011pub const SysCtlError = error{
2012 PermissionDenied,
2013 SystemResources,
2014 Unexpected,
2015};
2016
2017pub fn sysctl(
2018 name: []const c_int,
2019 oldp: ?*c_void,
2020 oldlenp: ?*usize,
2021 newp: ?*c_void,
2022 newlen: usize,
2023) SysCtlError!void {
2024 switch (errno(system.sysctl(name.ptr, name.len, oldp, oldlenp, newp, newlen))) {
2025 0 => return,
2026 EFAULT => unreachable,
2027 EPERM => return error.PermissionDenied,
2028 ENOMEM => return error.SystemResources,
2029 else => |err| return unexpectedErrno(err),
13452030 }
1346 return out_buffer[0..rc];
1347}
1348
1349/// Get the path to the current executable.
1350/// If you only need the directory, use selfExeDirPath.
1351/// If you only want an open file handle, use openSelfExe.
1352/// This function may return an error if the current executable
1353/// was deleted after spawning.
1354/// Returned value is a slice of out_buffer.
1355///
1356/// On Linux, depends on procfs being mounted. If the currently executing binary has
1357/// been deleted, the file path looks something like `/a/b/c/exe (deleted)`.
1358/// TODO make the return type of this a null terminated pointer
1359pub fn selfExePath(out_buffer: *[MAX_PATH_BYTES]u8) ![]u8 {
1360 switch (builtin.os) {
1361 Os.linux => return readLink(out_buffer, "/proc/self/exe"),
1362 Os.freebsd => {
1363 var mib = [4]c_int{ posix.CTL_KERN, posix.KERN_PROC, posix.KERN_PROC_PATHNAME, -1 };
1364 var out_len: usize = out_buffer.len;
1365 try posix.sysctl(&mib, out_buffer, &out_len, null, 0);
1366 // TODO could this slice from 0 to out_len instead?
1367 return mem.toSlice(u8, out_buffer);
1368 },
1369 Os.netbsd => {
1370 var mib = [4]c_int{ posix.CTL_KERN, posix.KERN_PROC_ARGS, -1, posix.KERN_PROC_PATHNAME };
1371 var out_len: usize = out_buffer.len;
1372 try posix.sysctl(&mib, out_buffer, &out_len, null, 0);
1373 // TODO could this slice from 0 to out_len instead?
1374 return mem.toSlice(u8, out_buffer);
1375 },
1376 Os.windows => {
1377 var utf16le_buf: [posix.PATH_MAX_WIDE]u16 = undefined;
1378 const utf16le_slice = try selfExePathW(&utf16le_buf);
1379 // Trust that Windows gives us valid UTF-16LE.
1380 const end_index = std.unicode.utf16leToUtf8(out_buffer, utf16le_slice) catch unreachable;
1381 return out_buffer[0..end_index];
1382 },
1383 Os.macosx, Os.ios => {
1384 var u32_len: u32 = @intCast(u32, out_buffer.len); // TODO shouldn't need this cast
1385 const rc = c._NSGetExecutablePath(out_buffer, &u32_len);
1386 if (rc != 0) return error.NameTooLong;
1387 return mem.toSlice(u8, out_buffer);
1388 },
1389 else => @compileError("Unsupported OS"),
1390 }
1391}
1392
1393/// `selfExeDirPath` except allocates the result on the heap.
1394/// Caller owns returned memory.
1395pub fn selfExeDirPathAlloc(allocator: *Allocator) ![]u8 {
1396 var buf: [MAX_PATH_BYTES]u8 = undefined;
1397 return mem.dupe(allocator, u8, try selfExeDirPath(&buf));
1398}
1399
1400/// Get the directory path that contains the current executable.
1401/// Returned value is a slice of out_buffer.
1402pub fn selfExeDirPath(out_buffer: *[MAX_PATH_BYTES]u8) ![]const u8 {
1403 switch (builtin.os) {
1404 Os.linux => {
1405 // If the currently executing binary has been deleted,
1406 // the file path looks something like `/a/b/c/exe (deleted)`
1407 // This path cannot be opened, but it's valid for determining the directory
1408 // the executable was in when it was run.
1409 const full_exe_path = try readLinkC(out_buffer, c"/proc/self/exe");
1410 // Assume that /proc/self/exe has an absolute path, and therefore dirname
1411 // will not return null.
1412 return path.dirname(full_exe_path).?;
1413 },
1414 Os.windows, Os.macosx, Os.ios, Os.freebsd, Os.netbsd => {
1415 const self_exe_path = try selfExePath(out_buffer);
1416 // Assume that the OS APIs return absolute paths, and therefore dirname
1417 // will not return null.
1418 return path.dirname(self_exe_path).?;
1419 },
1420 else => @compileError("Unsupported OS"),
2031}
2032
2033pub fn sysctlbynameC(
2034 name: [*]const u8,
2035 oldp: ?*c_void,
2036 oldlenp: ?*usize,
2037 newp: ?*c_void,
2038 newlen: usize,
2039) SysCtlError!void {
2040 switch (errno(system.sysctlbyname(name, oldp, oldlenp, newp, newlen))) {
2041 0 => return,
2042 EFAULT => unreachable,
2043 EPERM => return error.PermissionDenied,
2044 ENOMEM => return error.SystemResources,
2045 else => |err| return unexpectedErrno(err),
14212046 }
14222047}
14232048
1424pub const Thread = struct {
1425 data: Data,
2049pub fn gettimeofday(tv: ?*timeval, tz: ?*timezone) void {
2050 switch (errno(system.gettimeofday(tv, tz))) {
2051 0 => return,
2052 EINVAL => unreachable,
2053 else => unreachable,
2054 }
2055}
2056
2057pub fn nanosleep(req: timespec) void {
2058 var rem = req;
2059 while (true) {
2060 switch (errno(system.nanosleep(&rem, &rem))) {
2061 0 => return,
2062 EINVAL => unreachable, // Invalid parameters.
2063 EFAULT => unreachable,
2064 EINTR => continue,
2065 }
2066 }
2067}
14262068
1427 pub const use_pthreads = is_posix and builtin.link_libc;
2069pub const SeekError = error{
2070 Unseekable,
2071 Unexpected,
2072};
14282073
1429 /// Represents a kernel thread handle.
1430 /// May be an integer or a pointer depending on the platform.
1431 /// On Linux and POSIX, this is the same as Id.
1432 pub const Handle = if (use_pthreads)
1433 c.pthread_t
1434 else switch (builtin.os) {
1435 builtin.Os.linux => i32,
1436 builtin.Os.windows => windows.HANDLE,
1437 else => @compileError("Unsupported OS"),
1438 };
2074/// Repositions read/write file offset relative to the beginning.
2075pub fn lseek_SET(fd: fd_t, offset: u64) SeekError!void {
2076 if (linux.is_the_target and !builtin.link_libc and @sizeOf(usize) == 4) {
2077 switch (errno(system.llseek(fd, offset, null, SEEK_SET))) {
2078 0 => return,
2079 EBADF => unreachable, // always a race condition
2080 EINVAL => return error.Unseekable,
2081 EOVERFLOW => return error.Unseekable,
2082 ESPIPE => return error.Unseekable,
2083 ENXIO => return error.Unseekable,
2084 else => |err| return unexpectedErrno(err),
2085 }
2086 }
2087 if (windows.is_the_target and !builtin.link_libc) {
2088 return windows.SetFilePointerEx_BEGIN(fd, offset);
2089 }
2090 const ipos = @bitCast(i64, offset); // the OS treats this as unsigned
2091 switch (errno(system.lseek(fd, ipos, SEEK_SET))) {
2092 0 => return,
2093 EBADF => unreachable, // always a race condition
2094 EINVAL => return error.Unseekable,
2095 EOVERFLOW => return error.Unseekable,
2096 ESPIPE => return error.Unseekable,
2097 ENXIO => return error.Unseekable,
2098 else => |err| return unexpectedErrno(err),
2099 }
2100}
14392101
1440 /// Represents a unique ID per thread.
1441 /// May be an integer or pointer depending on the platform.
1442 /// On Linux and POSIX, this is the same as Handle.
1443 pub const Id = switch (builtin.os) {
1444 builtin.Os.windows => windows.DWORD,
1445 else => Handle,
1446 };
2102/// Repositions read/write file offset relative to the current offset.
2103pub fn lseek_CUR(fd: fd_t, offset: i64) SeekError!void {
2104 if (linux.is_the_target and !builtin.link_libc and @sizeOf(usize) == 4) {
2105 switch (errno(system.llseek(fd, @bitCast(u64, offset), null, SEEK_CUR))) {
2106 0 => return,
2107 EBADF => unreachable, // always a race condition
2108 EINVAL => return error.Unseekable,
2109 EOVERFLOW => return error.Unseekable,
2110 ESPIPE => return error.Unseekable,
2111 ENXIO => return error.Unseekable,
2112 else => |err| return unexpectedErrno(err),
2113 }
2114 }
2115 if (windows.is_the_target and !builtin.link_libc) {
2116 return windows.SetFilePointerEx_CURRENT(fd, offset);
2117 }
2118 switch (errno(system.lseek(fd, offset, SEEK_CUR))) {
2119 0 => return,
2120 EBADF => unreachable, // always a race condition
2121 EINVAL => return error.Unseekable,
2122 EOVERFLOW => return error.Unseekable,
2123 ESPIPE => return error.Unseekable,
2124 ENXIO => return error.Unseekable,
2125 else => |err| return unexpectedErrno(err),
2126 }
2127}
14472128
1448 pub const Data = if (use_pthreads)
1449 struct {
1450 handle: Thread.Handle,
1451 mmap_addr: usize,
1452 mmap_len: usize,
2129/// Repositions read/write file offset relative to the end.
2130pub fn lseek_END(fd: fd_t, offset: i64) SeekError!void {
2131 if (linux.is_the_target and !builtin.link_libc and @sizeOf(usize) == 4) {
2132 switch (errno(system.llseek(fd, @bitCast(u64, offset), null, SEEK_END))) {
2133 EBADF => unreachable, // always a race condition
2134 EINVAL => return error.Unseekable,
2135 EOVERFLOW => return error.Unseekable,
2136 ESPIPE => return error.Unseekable,
2137 ENXIO => return error.Unseekable,
2138 else => |err| return unexpectedErrno(err),
14532139 }
1454 else switch (builtin.os) {
1455 builtin.Os.linux => struct {
1456 handle: Thread.Handle,
1457 mmap_addr: usize,
1458 mmap_len: usize,
1459 },
1460 builtin.Os.windows => struct {
1461 handle: Thread.Handle,
1462 alloc_start: *c_void,
1463 heap_handle: windows.HANDLE,
1464 },
1465 else => @compileError("Unsupported OS"),
1466 };
2140 }
2141 if (windows.is_the_target and !builtin.link_libc) {
2142 return windows.SetFilePointerEx_END(fd, offset);
2143 }
2144 switch (errno(system.lseek(fd, offset, SEEK_END))) {
2145 0 => return,
2146 EBADF => unreachable, // always a race condition
2147 EINVAL => return error.Unseekable,
2148 EOVERFLOW => return error.Unseekable,
2149 ESPIPE => return error.Unseekable,
2150 ENXIO => return error.Unseekable,
2151 else => |err| return unexpectedErrno(err),
2152 }
2153}
14672154
1468 /// Returns the ID of the calling thread.
1469 /// Makes a syscall every time the function is called.
1470 /// On Linux and POSIX, this Id is the same as a Handle.
1471 pub fn getCurrentId() Id {
1472 if (use_pthreads) {
1473 return c.pthread_self();
1474 } else
1475 return switch (builtin.os) {
1476 builtin.Os.linux => linux.gettid(),
1477 builtin.Os.windows => windows.GetCurrentThreadId(),
1478 else => @compileError("Unsupported OS"),
1479 };
1480 }
1481
1482 /// Returns the handle of this thread.
1483 /// On Linux and POSIX, this is the same as Id.
1484 /// On Linux, it is possible that the thread spawned with `spawnThread`
1485 /// finishes executing entirely before the clone syscall completes. In this
1486 /// case, this function will return 0 rather than the no-longer-existing thread's
1487 /// pid.
1488 pub fn handle(self: Thread) Handle {
1489 return self.data.handle;
1490 }
1491
1492 pub fn wait(self: *const Thread) void {
1493 if (use_pthreads) {
1494 const err = c.pthread_join(self.data.handle, null);
1495 switch (err) {
1496 0 => {},
1497 posix.EINVAL => unreachable,
1498 posix.ESRCH => unreachable,
1499 posix.EDEADLK => unreachable,
1500 else => unreachable,
1501 }
1502 assert(posix.munmap(self.data.mmap_addr, self.data.mmap_len) == 0);
1503 } else switch (builtin.os) {
1504 builtin.Os.linux => {
1505 while (true) {
1506 const pid_value = @atomicLoad(i32, &self.data.handle, .SeqCst);
1507 if (pid_value == 0) break;
1508 const rc = linux.futex_wait(&self.data.handle, linux.FUTEX_WAIT, pid_value, null);
1509 switch (linux.getErrno(rc)) {
1510 0 => continue,
1511 posix.EINTR => continue,
1512 posix.EAGAIN => continue,
1513 else => unreachable,
1514 }
1515 }
1516 assert(posix.munmap(self.data.mmap_addr, self.data.mmap_len) == 0);
1517 },
1518 builtin.Os.windows => {
1519 assert(windows.WaitForSingleObject(self.data.handle, windows.INFINITE) == windows.WAIT_OBJECT_0);
1520 assert(windows.CloseHandle(self.data.handle) != 0);
1521 assert(windows.HeapFree(self.data.heap_handle, 0, self.data.alloc_start) != 0);
1522 },
1523 else => @compileError("Unsupported OS"),
2155/// Returns the read/write file offset relative to the beginning.
2156pub fn lseek_CUR_get(fd: fd_t) SeekError!u64 {
2157 if (linux.is_the_target and !builtin.link_libc and @sizeOf(usize) == 4) {
2158 var result: u64 = undefined;
2159 switch (errno(system.llseek(fd, 0, &result, SEEK_CUR))) {
2160 0 => return result,
2161 EBADF => unreachable, // always a race condition
2162 EINVAL => return error.Unseekable,
2163 EOVERFLOW => return error.Unseekable,
2164 ESPIPE => return error.Unseekable,
2165 ENXIO => return error.Unseekable,
2166 else => |err| return unexpectedErrno(err),
15242167 }
15252168 }
1526};
2169 if (windows.is_the_target and !builtin.link_libc) {
2170 return windows.SetFilePointerEx_CURRENT_get(fd);
2171 }
2172 const rc = system.lseek(fd, 0, SEEK_CUR);
2173 switch (errno(rc)) {
2174 0 => return @bitCast(u64, rc),
2175 EBADF => unreachable, // always a race condition
2176 EINVAL => return error.Unseekable,
2177 EOVERFLOW => return error.Unseekable,
2178 ESPIPE => return error.Unseekable,
2179 ENXIO => return error.Unseekable,
2180 else => |err| return unexpectedErrno(err),
2181 }
2182}
15272183
1528pub const SpawnThreadError = error{
1529 /// A system-imposed limit on the number of threads was encountered.
1530 /// There are a number of limits that may trigger this error:
1531 /// * the RLIMIT_NPROC soft resource limit (set via setrlimit(2)),
1532 /// which limits the number of processes and threads for a real
1533 /// user ID, was reached;
1534 /// * the kernel's system-wide limit on the number of processes and
1535 /// threads, /proc/sys/kernel/threads-max, was reached (see
1536 /// proc(5));
1537 /// * the maximum number of PIDs, /proc/sys/kernel/pid_max, was
1538 /// reached (see proc(5)); or
1539 /// * the PID limit (pids.max) imposed by the cgroup "process num‐
1540 /// ber" (PIDs) controller was reached.
1541 ThreadQuotaExceeded,
1542
1543 /// The kernel cannot allocate sufficient memory to allocate a task structure
1544 /// for the child, or to copy those parts of the caller's context that need to
1545 /// be copied.
2184pub const RealPathError = error{
2185 FileNotFound,
2186 AccessDenied,
2187 NameTooLong,
2188 NotSupported,
2189 NotDir,
2190 SymLinkLoop,
2191 InputOutput,
2192 FileTooBig,
2193 IsDir,
2194 ProcessFdQuotaExceeded,
2195 SystemFdQuotaExceeded,
2196 NoDevice,
15462197 SystemResources,
2198 NoSpaceLeft,
2199 FileSystem,
2200 BadPathName,
2201 DeviceBusy,
15472202
1548 /// Not enough userland memory to spawn the thread.
1549 OutOfMemory,
2203 /// On Windows, file paths must be valid Unicode.
2204 InvalidUtf8,
2205
2206 PathAlreadyExists,
15502207
15512208 Unexpected,
15522209};
15532210
1554/// caller must call wait on the returned thread
1555/// fn startFn(@typeOf(context)) T
1556/// where T is u8, noreturn, void, or !void
1557/// caller must call wait on the returned thread
1558pub fn spawnThread(context: var, comptime startFn: var) SpawnThreadError!*Thread {
1559 if (builtin.single_threaded) @compileError("cannot spawn thread when building in single-threaded mode");
1560 // TODO compile-time call graph analysis to determine stack upper bound
1561 // https://github.com/ziglang/zig/issues/157
1562 const default_stack_size = 8 * 1024 * 1024;
1563
1564 const Context = @typeOf(context);
1565 comptime assert(@ArgType(@typeOf(startFn), 0) == Context);
1566
1567 if (builtin.os == builtin.Os.windows) {
1568 const WinThread = struct {
1569 const OuterContext = struct {
1570 thread: Thread,
1571 inner: Context,
1572 };
1573 extern fn threadMain(raw_arg: windows.LPVOID) windows.DWORD {
1574 const arg = if (@sizeOf(Context) == 0) {} else @ptrCast(*Context, @alignCast(@alignOf(Context), raw_arg)).*;
1575 switch (@typeId(@typeOf(startFn).ReturnType)) {
1576 builtin.TypeId.Int => {
1577 return startFn(arg);
1578 },
1579 builtin.TypeId.Void => {
1580 startFn(arg);
1581 return 0;
1582 },
1583 else => @compileError("expected return type of startFn to be 'u8', 'noreturn', 'void', or '!void'"),
1584 }
1585 }
1586 };
1587
1588 const heap_handle = windows.GetProcessHeap() orelse return SpawnThreadError.OutOfMemory;
1589 const byte_count = @alignOf(WinThread.OuterContext) + @sizeOf(WinThread.OuterContext);
1590 const bytes_ptr = windows.HeapAlloc(heap_handle, 0, byte_count) orelse return SpawnThreadError.OutOfMemory;
1591 errdefer assert(windows.HeapFree(heap_handle, 0, bytes_ptr) != 0);
1592 const bytes = @ptrCast([*]u8, bytes_ptr)[0..byte_count];
1593 const outer_context = std.heap.FixedBufferAllocator.init(bytes).allocator.create(WinThread.OuterContext) catch unreachable;
1594 outer_context.* = WinThread.OuterContext{
1595 .thread = Thread{
1596 .data = Thread.Data{
1597 .heap_handle = heap_handle,
1598 .alloc_start = bytes_ptr,
1599 .handle = undefined,
1600 },
1601 },
1602 .inner = context,
1603 };
2211/// Return the canonicalized absolute pathname.
2212/// Expands all symbolic links and resolves references to `.`, `..`, and
2213/// extra `/` characters in `pathname`.
2214/// The return value is a slice of `out_buffer`, but not necessarily from the beginning.
2215/// See also `realpathC` and `realpathW`.
2216pub fn realpath(pathname: []const u8, out_buffer: *[MAX_PATH_BYTES]u8) RealPathError![]u8 {
2217 if (windows.is_the_target and !builtin.link_libc) {
2218 const pathname_w = try windows.sliceToPrefixedFileW(pathname);
2219 return realpathW(&pathname_w, out_buffer);
2220 }
2221 const pathname_c = try toPosixPath(pathname);
2222 return realpathC(&pathname_c, out_buffer);
2223}
16042224
1605 const parameter = if (@sizeOf(Context) == 0) null else @ptrCast(*c_void, &outer_context.inner);
1606 outer_context.thread.data.handle = windows.CreateThread(null, default_stack_size, WinThread.threadMain, parameter, 0, null) orelse {
1607 switch (windows.GetLastError()) {
1608 else => |err| windows.unexpectedError(err),
1609 }
1610 };
1611 return &outer_context.thread;
2225/// Same as `realpath` except `pathname` is null-terminated.
2226pub fn realpathC(pathname: [*]const u8, out_buffer: *[MAX_PATH_BYTES]u8) RealPathError![]u8 {
2227 if (windows.is_the_target and !builtin.link_libc) {
2228 const pathname_w = try windows.cStrToPrefixedFileW(pathname);
2229 return realpathW(&pathname_w, out_buffer);
16122230 }
2231 if (linux.is_the_target and !builtin.link_libc) {
2232 const fd = try openC(pathname, O_PATH | O_NONBLOCK | O_CLOEXEC, 0);
2233 defer close(fd);
16132234
1614 const MainFuncs = struct {
1615 extern fn linuxThreadMain(ctx_addr: usize) u8 {
1616 const arg = if (@sizeOf(Context) == 0) {} else @intToPtr(*const Context, ctx_addr).*;
2235 var procfs_buf: ["/proc/self/fd/-2147483648\x00".len]u8 = undefined;
2236 const proc_path = std.fmt.bufPrint(procfs_buf[0..], "/proc/self/fd/{}\x00", fd) catch unreachable;
16172237
1618 switch (@typeId(@typeOf(startFn).ReturnType)) {
1619 builtin.TypeId.Int => {
1620 return startFn(arg);
1621 },
1622 builtin.TypeId.Void => {
1623 startFn(arg);
1624 return 0;
1625 },
1626 else => @compileError("expected return type of startFn to be 'u8', 'noreturn', 'void', or '!void'"),
1627 }
1628 }
1629 extern fn posixThreadMain(ctx: ?*c_void) ?*c_void {
1630 if (@sizeOf(Context) == 0) {
1631 _ = startFn({});
1632 return null;
1633 } else {
1634 _ = startFn(@ptrCast(*const Context, @alignCast(@alignOf(Context), ctx)).*);
1635 return null;
1636 }
1637 }
2238 return readlinkC(proc_path.ptr, out_buffer);
2239 }
2240 const result_path = std.c.realpath(pathname, out_buffer) orelse switch (std.c._errno().*) {
2241 EINVAL => unreachable,
2242 EBADF => unreachable,
2243 EFAULT => unreachable,
2244 EACCES => return error.AccessDenied,
2245 ENOENT => return error.FileNotFound,
2246 ENOTSUP => return error.NotSupported,
2247 ENOTDIR => return error.NotDir,
2248 ENAMETOOLONG => return error.NameTooLong,
2249 ELOOP => return error.SymLinkLoop,
2250 EIO => return error.InputOutput,
2251 else => |err| return unexpectedErrno(err),
16382252 };
2253 return mem.toSlice(u8, result_path);
2254}
16392255
1640 const MAP_GROWSDOWN = if (builtin.os == builtin.Os.linux) linux.MAP_GROWSDOWN else 0;
1641
1642 var stack_end_offset: usize = undefined;
1643 var thread_start_offset: usize = undefined;
1644 var context_start_offset: usize = undefined;
1645 var tls_start_offset: usize = undefined;
1646 const mmap_len = blk: {
1647 // First in memory will be the stack, which grows downwards.
1648 var l: usize = mem.alignForward(default_stack_size, os.page_size);
1649 stack_end_offset = l;
1650 // Above the stack, so that it can be in the same mmap call, put the Thread object.
1651 l = mem.alignForward(l, @alignOf(Thread));
1652 thread_start_offset = l;
1653 l += @sizeOf(Thread);
1654 // Next, the Context object.
1655 if (@sizeOf(Context) != 0) {
1656 l = mem.alignForward(l, @alignOf(Context));
1657 context_start_offset = l;
1658 l += @sizeOf(Context);
1659 }
1660 // Finally, the Thread Local Storage, if any.
1661 if (!Thread.use_pthreads) {
1662 if (linux.tls.tls_image) |tls_img| {
1663 l = mem.alignForward(l, @alignOf(usize));
1664 tls_start_offset = l;
1665 l += tls_img.alloc_size;
1666 }
1667 }
1668 break :blk l;
1669 };
1670 const mmap_addr = posix.mmap(null, mmap_len, posix.PROT_READ | posix.PROT_WRITE, posix.MAP_PRIVATE | posix.MAP_ANONYMOUS | MAP_GROWSDOWN, -1, 0);
1671 if (mmap_addr == posix.MAP_FAILED) return error.OutOfMemory;
1672 errdefer assert(posix.munmap(mmap_addr, mmap_len) == 0);
1673
1674 const thread_ptr = @alignCast(@alignOf(Thread), @intToPtr(*Thread, mmap_addr + thread_start_offset));
1675 thread_ptr.data.mmap_addr = mmap_addr;
1676 thread_ptr.data.mmap_len = mmap_len;
1677
1678 var arg: usize = undefined;
1679 if (@sizeOf(Context) != 0) {
1680 arg = mmap_addr + context_start_offset;
1681 const context_ptr = @alignCast(@alignOf(Context), @intToPtr(*Context, arg));
1682 context_ptr.* = context;
1683 }
1684
1685 if (Thread.use_pthreads) {
1686 // use pthreads
1687 var attr: c.pthread_attr_t = undefined;
1688 if (c.pthread_attr_init(&attr) != 0) return SpawnThreadError.SystemResources;
1689 defer assert(c.pthread_attr_destroy(&attr) == 0);
1690
1691 assert(c.pthread_attr_setstack(&attr, @intToPtr(*c_void, mmap_addr), stack_end_offset) == 0);
1692
1693 const err = c.pthread_create(&thread_ptr.data.handle, &attr, MainFuncs.posixThreadMain, @intToPtr(*c_void, arg));
1694 switch (err) {
1695 0 => return thread_ptr,
1696 posix.EAGAIN => return SpawnThreadError.SystemResources,
1697 posix.EPERM => unreachable,
1698 posix.EINVAL => unreachable,
1699 else => return unexpectedErrorPosix(@intCast(usize, err)),
1700 }
1701 } else if (builtin.os == builtin.Os.linux) {
1702 var flags: u32 = posix.CLONE_VM | posix.CLONE_FS | posix.CLONE_FILES | posix.CLONE_SIGHAND |
1703 posix.CLONE_THREAD | posix.CLONE_SYSVSEM | posix.CLONE_PARENT_SETTID | posix.CLONE_CHILD_CLEARTID |
1704 posix.CLONE_DETACHED;
1705 var newtls: usize = undefined;
1706 if (linux.tls.tls_image) |tls_img| {
1707 newtls = linux.tls.copyTLS(mmap_addr + tls_start_offset);
1708 flags |= posix.CLONE_SETTLS;
1709 }
1710 const rc = posix.clone(MainFuncs.linuxThreadMain, mmap_addr + stack_end_offset, flags, arg, &thread_ptr.data.handle, newtls, &thread_ptr.data.handle);
1711 const err = posix.getErrno(rc);
1712 switch (err) {
1713 0 => return thread_ptr,
1714 posix.EAGAIN => return SpawnThreadError.ThreadQuotaExceeded,
1715 posix.EINVAL => unreachable,
1716 posix.ENOMEM => return SpawnThreadError.SystemResources,
1717 posix.ENOSPC => unreachable,
1718 posix.EPERM => unreachable,
1719 posix.EUSERS => unreachable,
1720 else => return unexpectedErrorPosix(err),
1721 }
1722 } else {
1723 @compileError("Unsupported OS");
1724 }
2256/// Same as `realpath` except `pathname` is null-terminated and UTF16LE-encoded.
2257pub fn realpathW(pathname: [*]const u16, out_buffer: *[MAX_PATH_BYTES]u8) RealPathError![]u8 {
2258 const h_file = try windows.CreateFileW(
2259 pathname,
2260 windows.GENERIC_READ,
2261 windows.FILE_SHARE_READ,
2262 windows.OPEN_EXISTING,
2263 windows.FILE_ATTRIBUTE_NORMAL,
2264 );
2265 defer windows.CloseHandle(h_file);
2266
2267 var wide_buf: [windows.PATH_MAX_WIDE]u16 = undefined;
2268 const wide_len = try windows.GetFinalPathNameByHandleW(h_file, &wide_buf, wide_buf.len, windows.VOLUME_NAME_DOS);
2269 assert(wide_len <= wide_buf.len);
2270 const wide_slice = wide_len[0..wide_len];
2271
2272 // Windows returns \\?\ prepended to the path.
2273 // We strip it to make this function consistent across platforms.
2274 const prefix = []u16{ '\\', '\\', '?', '\\' };
2275 const start_index = if (mem.startsWith(u16, wide_slice, prefix)) prefix.len else 0;
2276
2277 // Trust that Windows gives us valid UTF-16LE.
2278 const end_index = std.unicode.utf16leToUtf8(out_buffer, wide_slice[start_index..]) catch unreachable;
2279 return out_buffer[0..end_index];
17252280}
17262281
1727pub const CpuCountError = error{
1728 OutOfMemory,
1729 PermissionDenied,
2282/// Used to convert a slice to a null terminated slice on the stack.
2283/// TODO https://github.com/ziglang/zig/issues/287
2284pub fn toPosixPath(file_path: []const u8) ![PATH_MAX]u8 {
2285 var path_with_null: [PATH_MAX]u8 = undefined;
2286 // >= rather than > to make room for the null byte
2287 if (file_path.len >= PATH_MAX) return error.NameTooLong;
2288 mem.copy(u8, &path_with_null, file_path);
2289 path_with_null[file_path.len] = 0;
2290 return path_with_null;
2291}
2292
2293/// Whether or not error.Unexpected will print its value and a stack trace.
2294/// if this happens the fix is to add the error code to the corresponding
2295/// switch expression, possibly introduce a new error in the error set, and
2296/// send a patch to Zig.
2297pub const unexpected_error_tracing = builtin.mode == .Debug;
17302298
2299pub const UnexpectedError = error{
2300 /// The Operating System returned an undocumented error code.
2301 /// This error is in theory not possible, but it would be better
2302 /// to handle this error than to invoke undefined behavior.
17312303 Unexpected,
17322304};
17332305
1734pub fn cpuCount(fallback_allocator: *mem.Allocator) CpuCountError!usize {
1735 switch (builtin.os) {
1736 .macosx, .freebsd, .netbsd => {
1737 var count: c_int = undefined;
1738 var count_len: usize = @sizeOf(c_int);
1739 const name = switch (builtin.os) {
1740 builtin.Os.macosx => c"hw.logicalcpu",
1741 else => c"hw.ncpu",
1742 };
1743 try posix.sysctlbyname(name, @ptrCast(*c_void, &count), &count_len, null, 0);
1744 return @intCast(usize, count);
1745 },
1746 .linux => {
1747 const usize_count = 16;
1748 const allocator = std.heap.stackFallback(usize_count * @sizeOf(usize), fallback_allocator).get();
1749
1750 var set = try allocator.alloc(usize, usize_count);
1751 defer allocator.free(set);
1752
1753 while (true) {
1754 const rc = posix.sched_getaffinity(0, set);
1755 const err = posix.getErrno(rc);
1756 switch (err) {
1757 0 => {
1758 if (rc < set.len * @sizeOf(usize)) {
1759 const result = set[0 .. rc / @sizeOf(usize)];
1760 var sum: usize = 0;
1761 for (result) |x| {
1762 sum += @popCount(usize, x);
1763 }
1764 return sum;
1765 } else {
1766 set = try allocator.realloc(set, set.len * 2);
1767 continue;
1768 }
1769 },
1770 posix.EFAULT => unreachable,
1771 posix.EINVAL => unreachable,
1772 posix.EPERM => return CpuCountError.PermissionDenied,
1773 posix.ESRCH => unreachable,
1774 else => return os.unexpectedErrorPosix(err),
1775 }
1776 }
1777 },
1778 .windows => {
1779 var system_info: windows.SYSTEM_INFO = undefined;
1780 windows.GetSystemInfo(&system_info);
1781 return @intCast(usize, system_info.dwNumberOfProcessors);
1782 },
1783 else => @compileError("unsupported OS"),
2306/// Call this when you made a syscall or something that sets errno
2307/// and you get an unexpected error.
2308pub fn unexpectedErrno(err: usize) UnexpectedError {
2309 if (unexpected_error_tracing) {
2310 std.debug.warn("unexpected errno: {}\n", err);
2311 std.debug.dumpCurrentStackTrace(null);
17842312 }
2313 return error.Unexpected;
2314}
2315
2316test "" {
2317 _ = @import("os/darwin.zig");
2318 _ = @import("os/freebsd.zig");
2319 _ = @import("os/linux.zig");
2320 _ = @import("os/netbsd.zig");
2321 _ = @import("os/uefi.zig");
2322 _ = @import("os/wasi.zig");
2323 _ = @import("os/windows.zig");
2324 _ = @import("os/zen.zig");
2325
2326 _ = @import("os/test.zig");
17852327}
std/os/child_process.zig deleted-826
......@@ -1,826 +0,0 @@
1const std = @import("../std.zig");
2const cstr = std.cstr;
3const unicode = std.unicode;
4const io = std.io;
5const os = std.os;
6const posix = os.posix;
7const windows = os.windows;
8const mem = std.mem;
9const debug = std.debug;
10const BufMap = std.BufMap;
11const Buffer = std.Buffer;
12const builtin = @import("builtin");
13const Os = builtin.Os;
14const LinkedList = std.LinkedList;
15const windows_util = @import("windows/util.zig");
16const maxInt = std.math.maxInt;
17
18const is_windows = builtin.os == Os.windows;
19
20pub const ChildProcess = struct {
21 pub pid: if (is_windows) void else i32,
22 pub handle: if (is_windows) windows.HANDLE else void,
23 pub thread_handle: if (is_windows) windows.HANDLE else void,
24
25 pub allocator: *mem.Allocator,
26
27 pub stdin: ?os.File,
28 pub stdout: ?os.File,
29 pub stderr: ?os.File,
30
31 pub term: ?(SpawnError!Term),
32
33 pub argv: []const []const u8,
34
35 /// Leave as null to use the current env map using the supplied allocator.
36 pub env_map: ?*const BufMap,
37
38 pub stdin_behavior: StdIo,
39 pub stdout_behavior: StdIo,
40 pub stderr_behavior: StdIo,
41
42 /// Set to change the user id when spawning the child process.
43 pub uid: if (is_windows) void else ?u32,
44
45 /// Set to change the group id when spawning the child process.
46 pub gid: if (is_windows) void else ?u32,
47
48 /// Set to change the current working directory when spawning the child process.
49 pub cwd: ?[]const u8,
50
51 err_pipe: if (is_windows) void else [2]i32,
52 llnode: if (is_windows) void else LinkedList(*ChildProcess).Node,
53
54 pub const SpawnError = error{
55 ProcessFdQuotaExceeded,
56 Unexpected,
57 NotDir,
58 SystemResources,
59 FileNotFound,
60 NameTooLong,
61 SymLinkLoop,
62 FileSystem,
63 OutOfMemory,
64 AccessDenied,
65 PermissionDenied,
66 InvalidUserId,
67 ResourceLimitReached,
68 InvalidExe,
69 IsDir,
70 FileBusy,
71 };
72
73 pub const Term = union(enum) {
74 Exited: i32,
75 Signal: i32,
76 Stopped: i32,
77 Unknown: i32,
78 };
79
80 pub const StdIo = enum {
81 Inherit,
82 Ignore,
83 Pipe,
84 Close,
85 };
86
87 /// First argument in argv is the executable.
88 /// On success must call deinit.
89 pub fn init(argv: []const []const u8, allocator: *mem.Allocator) !*ChildProcess {
90 const child = try allocator.create(ChildProcess);
91 child.* = ChildProcess{
92 .allocator = allocator,
93 .argv = argv,
94 .pid = undefined,
95 .handle = undefined,
96 .thread_handle = undefined,
97 .err_pipe = undefined,
98 .llnode = undefined,
99 .term = null,
100 .env_map = null,
101 .cwd = null,
102 .uid = if (is_windows) {} else
103 null,
104 .gid = if (is_windows) {} else
105 null,
106 .stdin = null,
107 .stdout = null,
108 .stderr = null,
109 .stdin_behavior = StdIo.Inherit,
110 .stdout_behavior = StdIo.Inherit,
111 .stderr_behavior = StdIo.Inherit,
112 };
113 errdefer allocator.destroy(child);
114 return child;
115 }
116
117 pub fn setUserName(self: *ChildProcess, name: []const u8) !void {
118 const user_info = try os.getUserInfo(name);
119 self.uid = user_info.uid;
120 self.gid = user_info.gid;
121 }
122
123 /// On success must call `kill` or `wait`.
124 pub fn spawn(self: *ChildProcess) !void {
125 if (is_windows) {
126 return self.spawnWindows();
127 } else {
128 return self.spawnPosix();
129 }
130 }
131
132 pub fn spawnAndWait(self: *ChildProcess) !Term {
133 try self.spawn();
134 return self.wait();
135 }
136
137 /// Forcibly terminates child process and then cleans up all resources.
138 pub fn kill(self: *ChildProcess) !Term {
139 if (is_windows) {
140 return self.killWindows(1);
141 } else {
142 return self.killPosix();
143 }
144 }
145
146 pub fn killWindows(self: *ChildProcess, exit_code: windows.UINT) !Term {
147 if (self.term) |term| {
148 self.cleanupStreams();
149 return term;
150 }
151
152 if (!windows.TerminateProcess(self.handle, exit_code)) {
153 const err = windows.GetLastError();
154 return switch (err) {
155 else => os.unexpectedErrorWindows(err),
156 };
157 }
158 try self.waitUnwrappedWindows();
159 return self.term.?;
160 }
161
162 pub fn killPosix(self: *ChildProcess) !Term {
163 if (self.term) |term| {
164 self.cleanupStreams();
165 return term;
166 }
167 const ret = posix.kill(self.pid, posix.SIGTERM);
168 const err = posix.getErrno(ret);
169 if (err > 0) {
170 return switch (err) {
171 posix.EINVAL => unreachable,
172 posix.EPERM => error.PermissionDenied,
173 posix.ESRCH => error.ProcessNotFound,
174 else => os.unexpectedErrorPosix(err),
175 };
176 }
177 self.waitUnwrapped();
178 return self.term.?;
179 }
180
181 /// Blocks until child process terminates and then cleans up all resources.
182 pub fn wait(self: *ChildProcess) !Term {
183 if (is_windows) {
184 return self.waitWindows();
185 } else {
186 return self.waitPosix();
187 }
188 }
189
190 pub const ExecResult = struct {
191 term: os.ChildProcess.Term,
192 stdout: []u8,
193 stderr: []u8,
194 };
195
196 /// Spawns a child process, waits for it, collecting stdout and stderr, and then returns.
197 /// If it succeeds, the caller owns result.stdout and result.stderr memory.
198 pub fn exec(allocator: *mem.Allocator, argv: []const []const u8, cwd: ?[]const u8, env_map: ?*const BufMap, max_output_size: usize) !ExecResult {
199 const child = try ChildProcess.init(argv, allocator);
200 defer child.deinit();
201
202 child.stdin_behavior = ChildProcess.StdIo.Ignore;
203 child.stdout_behavior = ChildProcess.StdIo.Pipe;
204 child.stderr_behavior = ChildProcess.StdIo.Pipe;
205 child.cwd = cwd;
206 child.env_map = env_map;
207
208 try child.spawn();
209
210 var stdout = Buffer.initNull(allocator);
211 var stderr = Buffer.initNull(allocator);
212 defer Buffer.deinit(&stdout);
213 defer Buffer.deinit(&stderr);
214
215 var stdout_file_in_stream = child.stdout.?.inStream();
216 var stderr_file_in_stream = child.stderr.?.inStream();
217
218 try stdout_file_in_stream.stream.readAllBuffer(&stdout, max_output_size);
219 try stderr_file_in_stream.stream.readAllBuffer(&stderr, max_output_size);
220
221 return ExecResult{
222 .term = try child.wait(),
223 .stdout = stdout.toOwnedSlice(),
224 .stderr = stderr.toOwnedSlice(),
225 };
226 }
227
228 fn waitWindows(self: *ChildProcess) !Term {
229 if (self.term) |term| {
230 self.cleanupStreams();
231 return term;
232 }
233
234 try self.waitUnwrappedWindows();
235 return self.term.?;
236 }
237
238 fn waitPosix(self: *ChildProcess) !Term {
239 if (self.term) |term| {
240 self.cleanupStreams();
241 return term;
242 }
243
244 self.waitUnwrapped();
245 return self.term.?;
246 }
247
248 pub fn deinit(self: *ChildProcess) void {
249 self.allocator.destroy(self);
250 }
251
252 fn waitUnwrappedWindows(self: *ChildProcess) !void {
253 const result = os.windowsWaitSingle(self.handle, windows.INFINITE);
254
255 self.term = (SpawnError!Term)(x: {
256 var exit_code: windows.DWORD = undefined;
257 if (windows.GetExitCodeProcess(self.handle, &exit_code) == 0) {
258 break :x Term{ .Unknown = 0 };
259 } else {
260 break :x Term{ .Exited = @bitCast(i32, exit_code) };
261 }
262 });
263
264 os.close(self.handle);
265 os.close(self.thread_handle);
266 self.cleanupStreams();
267 return result;
268 }
269
270 fn waitUnwrapped(self: *ChildProcess) void {
271 var status: i32 = undefined;
272 while (true) {
273 const err = posix.getErrno(posix.waitpid(self.pid, &status, 0));
274 if (err > 0) {
275 switch (err) {
276 posix.EINTR => continue,
277 else => unreachable,
278 }
279 }
280 self.cleanupStreams();
281 self.handleWaitResult(status);
282 return;
283 }
284 }
285
286 fn handleWaitResult(self: *ChildProcess, status: i32) void {
287 self.term = self.cleanupAfterWait(status);
288 }
289
290 fn cleanupStreams(self: *ChildProcess) void {
291 if (self.stdin) |*stdin| {
292 stdin.close();
293 self.stdin = null;
294 }
295 if (self.stdout) |*stdout| {
296 stdout.close();
297 self.stdout = null;
298 }
299 if (self.stderr) |*stderr| {
300 stderr.close();
301 self.stderr = null;
302 }
303 }
304
305 fn cleanupAfterWait(self: *ChildProcess, status: i32) !Term {
306 defer {
307 os.close(self.err_pipe[0]);
308 os.close(self.err_pipe[1]);
309 }
310
311 // Write maxInt(ErrInt) to the write end of the err_pipe. This is after
312 // waitpid, so this write is guaranteed to be after the child
313 // pid potentially wrote an error. This way we can do a blocking
314 // read on the error pipe and either get maxInt(ErrInt) (no error) or
315 // an error code.
316 try writeIntFd(self.err_pipe[1], maxInt(ErrInt));
317 const err_int = try readIntFd(self.err_pipe[0]);
318 // Here we potentially return the fork child's error
319 // from the parent pid.
320 if (err_int != maxInt(ErrInt)) {
321 return @errSetCast(SpawnError, @intToError(err_int));
322 }
323
324 return statusToTerm(status);
325 }
326
327 fn statusToTerm(status: i32) Term {
328 return if (posix.WIFEXITED(status))
329 Term{ .Exited = posix.WEXITSTATUS(status) }
330 else if (posix.WIFSIGNALED(status))
331 Term{ .Signal = posix.WTERMSIG(status) }
332 else if (posix.WIFSTOPPED(status))
333 Term{ .Stopped = posix.WSTOPSIG(status) }
334 else
335 Term{ .Unknown = status };
336 }
337
338 fn spawnPosix(self: *ChildProcess) !void {
339 const stdin_pipe = if (self.stdin_behavior == StdIo.Pipe) try makePipe() else undefined;
340 errdefer if (self.stdin_behavior == StdIo.Pipe) {
341 destroyPipe(stdin_pipe);
342 };
343
344 const stdout_pipe = if (self.stdout_behavior == StdIo.Pipe) try makePipe() else undefined;
345 errdefer if (self.stdout_behavior == StdIo.Pipe) {
346 destroyPipe(stdout_pipe);
347 };
348
349 const stderr_pipe = if (self.stderr_behavior == StdIo.Pipe) try makePipe() else undefined;
350 errdefer if (self.stderr_behavior == StdIo.Pipe) {
351 destroyPipe(stderr_pipe);
352 };
353
354 const any_ignore = (self.stdin_behavior == StdIo.Ignore or self.stdout_behavior == StdIo.Ignore or self.stderr_behavior == StdIo.Ignore);
355 const dev_null_fd = if (any_ignore) try os.posixOpenC(c"/dev/null", posix.O_RDWR, 0) else undefined;
356 defer {
357 if (any_ignore) os.close(dev_null_fd);
358 }
359
360 var env_map_owned: BufMap = undefined;
361 var we_own_env_map: bool = undefined;
362 const env_map = if (self.env_map) |env_map| x: {
363 we_own_env_map = false;
364 break :x env_map;
365 } else x: {
366 we_own_env_map = true;
367 env_map_owned = try os.getEnvMap(self.allocator);
368 break :x &env_map_owned;
369 };
370 defer {
371 if (we_own_env_map) env_map_owned.deinit();
372 }
373
374 // This pipe is used to communicate errors between the time of fork
375 // and execve from the child process to the parent process.
376 const err_pipe = try makePipe();
377 errdefer destroyPipe(err_pipe);
378
379 const pid_result = try posix.fork();
380 if (pid_result == 0) {
381 // we are the child
382 setUpChildIo(self.stdin_behavior, stdin_pipe[0], posix.STDIN_FILENO, dev_null_fd) catch |err| forkChildErrReport(err_pipe[1], err);
383 setUpChildIo(self.stdout_behavior, stdout_pipe[1], posix.STDOUT_FILENO, dev_null_fd) catch |err| forkChildErrReport(err_pipe[1], err);
384 setUpChildIo(self.stderr_behavior, stderr_pipe[1], posix.STDERR_FILENO, dev_null_fd) catch |err| forkChildErrReport(err_pipe[1], err);
385
386 if (self.stdin_behavior == StdIo.Pipe) {
387 os.close(stdin_pipe[0]);
388 os.close(stdin_pipe[1]);
389 }
390 if (self.stdout_behavior == StdIo.Pipe) {
391 os.close(stdout_pipe[0]);
392 os.close(stdout_pipe[1]);
393 }
394 if (self.stderr_behavior == StdIo.Pipe) {
395 os.close(stderr_pipe[0]);
396 os.close(stderr_pipe[1]);
397 }
398
399 if (self.cwd) |cwd| {
400 os.changeCurDir(cwd) catch |err| forkChildErrReport(err_pipe[1], err);
401 }
402
403 if (self.gid) |gid| {
404 os.posix_setregid(gid, gid) catch |err| forkChildErrReport(err_pipe[1], err);
405 }
406
407 if (self.uid) |uid| {
408 os.posix_setreuid(uid, uid) catch |err| forkChildErrReport(err_pipe[1], err);
409 }
410
411 os.posix.execve(self.allocator, self.argv, env_map) catch |err| forkChildErrReport(err_pipe[1], err);
412 }
413
414 // we are the parent
415 const pid = @intCast(i32, pid_result);
416 if (self.stdin_behavior == StdIo.Pipe) {
417 self.stdin = os.File.openHandle(stdin_pipe[1]);
418 } else {
419 self.stdin = null;
420 }
421 if (self.stdout_behavior == StdIo.Pipe) {
422 self.stdout = os.File.openHandle(stdout_pipe[0]);
423 } else {
424 self.stdout = null;
425 }
426 if (self.stderr_behavior == StdIo.Pipe) {
427 self.stderr = os.File.openHandle(stderr_pipe[0]);
428 } else {
429 self.stderr = null;
430 }
431
432 self.pid = pid;
433 self.err_pipe = err_pipe;
434 self.llnode = LinkedList(*ChildProcess).Node.init(self);
435 self.term = null;
436
437 if (self.stdin_behavior == StdIo.Pipe) {
438 os.close(stdin_pipe[0]);
439 }
440 if (self.stdout_behavior == StdIo.Pipe) {
441 os.close(stdout_pipe[1]);
442 }
443 if (self.stderr_behavior == StdIo.Pipe) {
444 os.close(stderr_pipe[1]);
445 }
446 }
447
448 fn spawnWindows(self: *ChildProcess) !void {
449 const saAttr = windows.SECURITY_ATTRIBUTES{
450 .nLength = @sizeOf(windows.SECURITY_ATTRIBUTES),
451 .bInheritHandle = windows.TRUE,
452 .lpSecurityDescriptor = null,
453 };
454
455 const any_ignore = (self.stdin_behavior == StdIo.Ignore or self.stdout_behavior == StdIo.Ignore or self.stderr_behavior == StdIo.Ignore);
456
457 const nul_handle = if (any_ignore) blk: {
458 break :blk try os.windowsOpen("NUL", windows.GENERIC_READ, windows.FILE_SHARE_READ, windows.OPEN_EXISTING, windows.FILE_ATTRIBUTE_NORMAL);
459 } else blk: {
460 break :blk undefined;
461 };
462 defer {
463 if (any_ignore) os.close(nul_handle);
464 }
465 if (any_ignore) {
466 try windowsSetHandleInfo(nul_handle, windows.HANDLE_FLAG_INHERIT, 0);
467 }
468
469 var g_hChildStd_IN_Rd: ?windows.HANDLE = null;
470 var g_hChildStd_IN_Wr: ?windows.HANDLE = null;
471 switch (self.stdin_behavior) {
472 StdIo.Pipe => {
473 try windowsMakePipeIn(&g_hChildStd_IN_Rd, &g_hChildStd_IN_Wr, &saAttr);
474 },
475 StdIo.Ignore => {
476 g_hChildStd_IN_Rd = nul_handle;
477 },
478 StdIo.Inherit => {
479 g_hChildStd_IN_Rd = windows.GetStdHandle(windows.STD_INPUT_HANDLE);
480 },
481 StdIo.Close => {
482 g_hChildStd_IN_Rd = null;
483 },
484 }
485 errdefer if (self.stdin_behavior == StdIo.Pipe) {
486 windowsDestroyPipe(g_hChildStd_IN_Rd, g_hChildStd_IN_Wr);
487 };
488
489 var g_hChildStd_OUT_Rd: ?windows.HANDLE = null;
490 var g_hChildStd_OUT_Wr: ?windows.HANDLE = null;
491 switch (self.stdout_behavior) {
492 StdIo.Pipe => {
493 try windowsMakePipeOut(&g_hChildStd_OUT_Rd, &g_hChildStd_OUT_Wr, &saAttr);
494 },
495 StdIo.Ignore => {
496 g_hChildStd_OUT_Wr = nul_handle;
497 },
498 StdIo.Inherit => {
499 g_hChildStd_OUT_Wr = windows.GetStdHandle(windows.STD_OUTPUT_HANDLE);
500 },
501 StdIo.Close => {
502 g_hChildStd_OUT_Wr = null;
503 },
504 }
505 errdefer if (self.stdin_behavior == StdIo.Pipe) {
506 windowsDestroyPipe(g_hChildStd_OUT_Rd, g_hChildStd_OUT_Wr);
507 };
508
509 var g_hChildStd_ERR_Rd: ?windows.HANDLE = null;
510 var g_hChildStd_ERR_Wr: ?windows.HANDLE = null;
511 switch (self.stderr_behavior) {
512 StdIo.Pipe => {
513 try windowsMakePipeOut(&g_hChildStd_ERR_Rd, &g_hChildStd_ERR_Wr, &saAttr);
514 },
515 StdIo.Ignore => {
516 g_hChildStd_ERR_Wr = nul_handle;
517 },
518 StdIo.Inherit => {
519 g_hChildStd_ERR_Wr = windows.GetStdHandle(windows.STD_ERROR_HANDLE);
520 },
521 StdIo.Close => {
522 g_hChildStd_ERR_Wr = null;
523 },
524 }
525 errdefer if (self.stdin_behavior == StdIo.Pipe) {
526 windowsDestroyPipe(g_hChildStd_ERR_Rd, g_hChildStd_ERR_Wr);
527 };
528
529 const cmd_line = try windowsCreateCommandLine(self.allocator, self.argv);
530 defer self.allocator.free(cmd_line);
531
532 var siStartInfo = windows.STARTUPINFOW{
533 .cb = @sizeOf(windows.STARTUPINFOW),
534 .hStdError = g_hChildStd_ERR_Wr,
535 .hStdOutput = g_hChildStd_OUT_Wr,
536 .hStdInput = g_hChildStd_IN_Rd,
537 .dwFlags = windows.STARTF_USESTDHANDLES,
538
539 .lpReserved = null,
540 .lpDesktop = null,
541 .lpTitle = null,
542 .dwX = 0,
543 .dwY = 0,
544 .dwXSize = 0,
545 .dwYSize = 0,
546 .dwXCountChars = 0,
547 .dwYCountChars = 0,
548 .dwFillAttribute = 0,
549 .wShowWindow = 0,
550 .cbReserved2 = 0,
551 .lpReserved2 = null,
552 };
553 var piProcInfo: windows.PROCESS_INFORMATION = undefined;
554
555 const cwd_slice = if (self.cwd) |cwd| try cstr.addNullByte(self.allocator, cwd) else null;
556 defer if (cwd_slice) |cwd| self.allocator.free(cwd);
557 const cwd_w = if (cwd_slice) |cwd| try unicode.utf8ToUtf16LeWithNull(self.allocator, cwd) else null;
558 defer if (cwd_w) |cwd| self.allocator.free(cwd);
559 const cwd_w_ptr = if (cwd_w) |cwd| cwd.ptr else null;
560
561 const maybe_envp_buf = if (self.env_map) |env_map| try createWindowsEnvBlock(self.allocator, env_map) else null;
562 defer if (maybe_envp_buf) |envp_buf| self.allocator.free(envp_buf);
563 const envp_ptr = if (maybe_envp_buf) |envp_buf| envp_buf.ptr else null;
564
565 // the cwd set in ChildProcess is in effect when choosing the executable path
566 // to match posix semantics
567 const app_name = x: {
568 if (self.cwd) |cwd| {
569 const resolved = try os.path.resolve(self.allocator, [][]const u8{ cwd, self.argv[0] });
570 defer self.allocator.free(resolved);
571 break :x try cstr.addNullByte(self.allocator, resolved);
572 } else {
573 break :x try cstr.addNullByte(self.allocator, self.argv[0]);
574 }
575 };
576 defer self.allocator.free(app_name);
577
578 const app_name_w = try unicode.utf8ToUtf16LeWithNull(self.allocator, app_name);
579 defer self.allocator.free(app_name_w);
580
581 const cmd_line_w = try unicode.utf8ToUtf16LeWithNull(self.allocator, cmd_line);
582 defer self.allocator.free(cmd_line_w);
583
584 windowsCreateProcess(app_name_w.ptr, cmd_line_w.ptr, envp_ptr, cwd_w_ptr, &siStartInfo, &piProcInfo) catch |no_path_err| {
585 if (no_path_err != error.FileNotFound) return no_path_err;
586
587 const PATH = try os.getEnvVarOwned(self.allocator, "PATH");
588 defer self.allocator.free(PATH);
589
590 var it = mem.tokenize(PATH, ";");
591 while (it.next()) |search_path| {
592 const joined_path = try os.path.join(self.allocator, [][]const u8{ search_path, app_name });
593 defer self.allocator.free(joined_path);
594
595 const joined_path_w = try unicode.utf8ToUtf16LeWithNull(self.allocator, joined_path);
596 defer self.allocator.free(joined_path_w);
597
598 if (windowsCreateProcess(joined_path_w.ptr, cmd_line_w.ptr, envp_ptr, cwd_w_ptr, &siStartInfo, &piProcInfo)) |_| {
599 break;
600 } else |err| if (err == error.FileNotFound) {
601 continue;
602 } else {
603 return err;
604 }
605 } else {
606 // Every other error would have been returned earlier.
607 return error.FileNotFound;
608 }
609 };
610
611 if (g_hChildStd_IN_Wr) |h| {
612 self.stdin = os.File.openHandle(h);
613 } else {
614 self.stdin = null;
615 }
616 if (g_hChildStd_OUT_Rd) |h| {
617 self.stdout = os.File.openHandle(h);
618 } else {
619 self.stdout = null;
620 }
621 if (g_hChildStd_ERR_Rd) |h| {
622 self.stderr = os.File.openHandle(h);
623 } else {
624 self.stderr = null;
625 }
626
627 self.handle = piProcInfo.hProcess;
628 self.thread_handle = piProcInfo.hThread;
629 self.term = null;
630
631 if (self.stdin_behavior == StdIo.Pipe) {
632 os.close(g_hChildStd_IN_Rd.?);
633 }
634 if (self.stderr_behavior == StdIo.Pipe) {
635 os.close(g_hChildStd_ERR_Wr.?);
636 }
637 if (self.stdout_behavior == StdIo.Pipe) {
638 os.close(g_hChildStd_OUT_Wr.?);
639 }
640 }
641
642 fn setUpChildIo(stdio: StdIo, pipe_fd: i32, std_fileno: i32, dev_null_fd: i32) !void {
643 switch (stdio) {
644 StdIo.Pipe => try os.posixDup2(pipe_fd, std_fileno),
645 StdIo.Close => os.close(std_fileno),
646 StdIo.Inherit => {},
647 StdIo.Ignore => try os.posixDup2(dev_null_fd, std_fileno),
648 }
649 }
650};
651
652fn windowsCreateProcess(app_name: [*]u16, cmd_line: [*]u16, envp_ptr: ?[*]u16, cwd_ptr: ?[*]u16, lpStartupInfo: *windows.STARTUPINFOW, lpProcessInformation: *windows.PROCESS_INFORMATION) !void {
653 // TODO the docs for environment pointer say:
654 // > A pointer to the environment block for the new process. If this parameter
655 // > is NULL, the new process uses the environment of the calling process.
656 // > ...
657 // > An environment block can contain either Unicode or ANSI characters. If
658 // > the environment block pointed to by lpEnvironment contains Unicode
659 // > characters, be sure that dwCreationFlags includes CREATE_UNICODE_ENVIRONMENT.
660 // > If this parameter is NULL and the environment block of the parent process
661 // > contains Unicode characters, you must also ensure that dwCreationFlags
662 // > includes CREATE_UNICODE_ENVIRONMENT.
663 // This seems to imply that we have to somehow know whether our process parent passed
664 // CREATE_UNICODE_ENVIRONMENT if we want to pass NULL for the environment parameter.
665 // Since we do not know this information that would imply that we must not pass NULL
666 // for the parameter.
667 // However this would imply that programs compiled with -DUNICODE could not pass
668 // environment variables to programs that were not, which seems unlikely.
669 // More investigation is needed.
670 if (windows.CreateProcessW(
671 app_name,
672 cmd_line,
673 null,
674 null,
675 windows.TRUE,
676 windows.CREATE_UNICODE_ENVIRONMENT,
677 @ptrCast(?*c_void, envp_ptr),
678 cwd_ptr,
679 lpStartupInfo,
680 lpProcessInformation,
681 ) == 0) {
682 switch (windows.GetLastError()) {
683 windows.ERROR.FILE_NOT_FOUND => return error.FileNotFound,
684 windows.ERROR.PATH_NOT_FOUND => return error.FileNotFound,
685 windows.ERROR.INVALID_PARAMETER => unreachable,
686 windows.ERROR.INVALID_NAME => return error.InvalidName,
687 else => |err| return windows.unexpectedError(err),
688 }
689 }
690}
691
692/// Caller must dealloc.
693/// Guarantees a null byte at result[result.len].
694fn windowsCreateCommandLine(allocator: *mem.Allocator, argv: []const []const u8) ![]u8 {
695 var buf = try Buffer.initSize(allocator, 0);
696 defer buf.deinit();
697
698 var buf_stream = &io.BufferOutStream.init(&buf).stream;
699
700 for (argv) |arg, arg_i| {
701 if (arg_i != 0) try buf.appendByte(' ');
702 if (mem.indexOfAny(u8, arg, " \t\n\"") == null) {
703 try buf.append(arg);
704 continue;
705 }
706 try buf.appendByte('"');
707 var backslash_count: usize = 0;
708 for (arg) |byte| {
709 switch (byte) {
710 '\\' => backslash_count += 1,
711 '"' => {
712 try buf_stream.writeByteNTimes('\\', backslash_count * 2 + 1);
713 try buf.appendByte('"');
714 backslash_count = 0;
715 },
716 else => {
717 try buf_stream.writeByteNTimes('\\', backslash_count);
718 try buf.appendByte(byte);
719 backslash_count = 0;
720 },
721 }
722 }
723 try buf_stream.writeByteNTimes('\\', backslash_count * 2);
724 try buf.appendByte('"');
725 }
726
727 return buf.toOwnedSlice();
728}
729
730fn windowsDestroyPipe(rd: ?windows.HANDLE, wr: ?windows.HANDLE) void {
731 if (rd) |h| os.close(h);
732 if (wr) |h| os.close(h);
733}
734
735fn windowsMakePipeIn(rd: *?windows.HANDLE, wr: *?windows.HANDLE, sattr: *const SECURITY_ATTRIBUTES) !void {
736 var rd_h: windows.HANDLE = undefined;
737 var wr_h: windows.HANDLE = undefined;
738 try windows.CreatePipe(&rd_h, &wr_h, sattr);
739 errdefer windowsDestroyPipe(rd_h, wr_h);
740 try windowsSetHandleInfo(wr_h, windows.HANDLE_FLAG_INHERIT, 0);
741 rd.* = rd_h;
742 wr.* = wr_h;
743}
744
745fn windowsMakePipeOut(rd: *?windows.HANDLE, wr: *?windows.HANDLE, sattr: *const SECURITY_ATTRIBUTES) !void {
746 var rd_h: windows.HANDLE = undefined;
747 var wr_h: windows.HANDLE = undefined;
748 try windows.CreatePipe(&rd_h, &wr_h, sattr);
749 errdefer windowsDestroyPipe(rd_h, wr_h);
750 try windowsSetHandleInfo(rd_h, windows.HANDLE_FLAG_INHERIT, 0);
751 rd.* = rd_h;
752 wr.* = wr_h;
753}
754
755fn makePipe() ![2]i32 {
756 var fds: [2]i32 = undefined;
757 const err = posix.getErrno(posix.pipe(&fds));
758 if (err > 0) {
759 return switch (err) {
760 posix.EMFILE, posix.ENFILE => error.SystemResources,
761 else => os.unexpectedErrorPosix(err),
762 };
763 }
764 return fds;
765}
766
767fn destroyPipe(pipe: [2]i32) void {
768 os.close(pipe[0]);
769 os.close(pipe[1]);
770}
771
772// Child of fork calls this to report an error to the fork parent.
773// Then the child exits.
774fn forkChildErrReport(fd: i32, err: ChildProcess.SpawnError) noreturn {
775 writeIntFd(fd, ErrInt(@errorToInt(err))) catch {};
776 posix.exit(1);
777}
778
779const ErrInt = @IntType(false, @sizeOf(anyerror) * 8);
780
781fn writeIntFd(fd: i32, value: ErrInt) !void {
782 const stream = &os.File.openHandle(fd).outStream().stream;
783 stream.writeIntNative(ErrInt, value) catch return error.SystemResources;
784}
785
786fn readIntFd(fd: i32) !ErrInt {
787 const stream = &os.File.openHandle(fd).inStream().stream;
788 return stream.readIntNative(ErrInt) catch return error.SystemResources;
789}
790
791/// Caller must free result.
792pub fn createWindowsEnvBlock(allocator: *mem.Allocator, env_map: *const BufMap) ![]u16 {
793 // count bytes needed
794 const max_chars_needed = x: {
795 var max_chars_needed: usize = 4; // 4 for the final 4 null bytes
796 var it = env_map.iterator();
797 while (it.next()) |pair| {
798 // +1 for '='
799 // +1 for null byte
800 max_chars_needed += pair.key.len + pair.value.len + 2;
801 }
802 break :x max_chars_needed;
803 };
804 const result = try allocator.alloc(u16, max_chars_needed);
805 errdefer allocator.free(result);
806
807 var it = env_map.iterator();
808 var i: usize = 0;
809 while (it.next()) |pair| {
810 i += try unicode.utf8ToUtf16Le(result[i..], pair.key);
811 result[i] = '=';
812 i += 1;
813 i += try unicode.utf8ToUtf16Le(result[i..], pair.value);
814 result[i] = 0;
815 i += 1;
816 }
817 result[i] = 0;
818 i += 1;
819 result[i] = 0;
820 i += 1;
821 result[i] = 0;
822 i += 1;
823 result[i] = 0;
824 i += 1;
825 return allocator.shrink(result, i);
826}
std/os/file.zig deleted-450
......@@ -1,450 +0,0 @@
1const std = @import("../std.zig");
2const builtin = @import("builtin");
3const os = std.os;
4const io = std.io;
5const mem = std.mem;
6const math = std.math;
7const assert = std.debug.assert;
8const posix = os.posix;
9const windows = os.windows;
10const Os = builtin.Os;
11const windows_util = @import("windows/util.zig");
12const maxInt = std.math.maxInt;
13
14const is_posix = builtin.os != builtin.Os.windows;
15const is_windows = builtin.os == builtin.Os.windows;
16
17pub const File = struct {
18 /// The OS-specific file descriptor or file handle.
19 handle: posix.fd_t,
20
21 pub const Mode = switch (builtin.os) {
22 Os.windows => void,
23 else => u32,
24 };
25
26 pub const default_mode = switch (builtin.os) {
27 Os.windows => {},
28 else => 0o666,
29 };
30
31 pub const OpenError = os.WindowsOpenError || os.PosixOpenError;
32
33 /// `openRead` except with a null terminated path
34 pub fn openReadC(path: [*]const u8) OpenError!File {
35 if (is_posix) {
36 const flags = posix.O_LARGEFILE | posix.O_RDONLY;
37 const fd = try os.posixOpenC(path, flags, 0);
38 return openHandle(fd);
39 }
40 if (is_windows) {
41 return openRead(mem.toSliceConst(u8, path));
42 }
43 @compileError("Unsupported OS");
44 }
45
46 /// Call close to clean up.
47 pub fn openRead(path: []const u8) OpenError!File {
48 if (is_posix) {
49 const path_c = try os.toPosixPath(path);
50 return openReadC(&path_c);
51 }
52 if (is_windows) {
53 const path_w = try windows_util.sliceToPrefixedFileW(path);
54 return openReadW(&path_w);
55 }
56 @compileError("Unsupported OS");
57 }
58
59 pub fn openReadW(path_w: [*]const u16) OpenError!File {
60 const handle = try os.windowsOpenW(
61 path_w,
62 windows.GENERIC_READ,
63 windows.FILE_SHARE_READ,
64 windows.OPEN_EXISTING,
65 windows.FILE_ATTRIBUTE_NORMAL,
66 );
67 return openHandle(handle);
68 }
69
70 /// Calls `openWriteMode` with os.File.default_mode for the mode.
71 pub fn openWrite(path: []const u8) OpenError!File {
72 return openWriteMode(path, os.File.default_mode);
73 }
74
75 /// If the path does not exist it will be created.
76 /// If a file already exists in the destination it will be truncated.
77 /// Call close to clean up.
78 pub fn openWriteMode(path: []const u8, file_mode: Mode) OpenError!File {
79 if (is_posix) {
80 const flags = posix.O_LARGEFILE | posix.O_WRONLY | posix.O_CREAT | posix.O_CLOEXEC | posix.O_TRUNC;
81 const fd = try os.posixOpen(path, flags, file_mode);
82 return openHandle(fd);
83 } else if (is_windows) {
84 const path_w = try windows_util.sliceToPrefixedFileW(path);
85 return openWriteModeW(&path_w, file_mode);
86 } else {
87 @compileError("TODO implement openWriteMode for this OS");
88 }
89 }
90
91 pub fn openWriteModeW(path_w: [*]const u16, file_mode: Mode) OpenError!File {
92 const handle = try os.windowsOpenW(
93 path_w,
94 windows.GENERIC_WRITE,
95 windows.FILE_SHARE_WRITE | windows.FILE_SHARE_READ | windows.FILE_SHARE_DELETE,
96 windows.CREATE_ALWAYS,
97 windows.FILE_ATTRIBUTE_NORMAL,
98 );
99 return openHandle(handle);
100 }
101
102 /// If the path does not exist it will be created.
103 /// If a file already exists in the destination this returns OpenError.PathAlreadyExists
104 /// Call close to clean up.
105 pub fn openWriteNoClobber(path: []const u8, file_mode: Mode) OpenError!File {
106 if (is_posix) {
107 const path_c = try os.toPosixPath(path);
108 return openWriteNoClobberC(&path_c, file_mode);
109 } else if (is_windows) {
110 const path_w = try windows_util.sliceToPrefixedFileW(path);
111 return openWriteNoClobberW(&path_w, file_mode);
112 } else {
113 @compileError("TODO implement openWriteMode for this OS");
114 }
115 }
116
117 pub fn openWriteNoClobberC(path: [*]const u8, file_mode: Mode) OpenError!File {
118 if (is_posix) {
119 const flags = posix.O_LARGEFILE | posix.O_WRONLY | posix.O_CREAT | posix.O_CLOEXEC | posix.O_EXCL;
120 const fd = try os.posixOpenC(path, flags, file_mode);
121 return openHandle(fd);
122 } else if (is_windows) {
123 const path_w = try windows_util.cStrToPrefixedFileW(path);
124 return openWriteNoClobberW(&path_w, file_mode);
125 } else {
126 @compileError("TODO implement openWriteMode for this OS");
127 }
128 }
129
130 pub fn openWriteNoClobberW(path_w: [*]const u16, file_mode: Mode) OpenError!File {
131 const handle = try os.windowsOpenW(
132 path_w,
133 windows.GENERIC_WRITE,
134 windows.FILE_SHARE_WRITE | windows.FILE_SHARE_READ | windows.FILE_SHARE_DELETE,
135 windows.CREATE_NEW,
136 windows.FILE_ATTRIBUTE_NORMAL,
137 );
138 return openHandle(handle);
139 }
140
141 pub fn openHandle(handle: posix.fd_t) File {
142 return File{ .handle = handle };
143 }
144
145 /// Test for the existence of `path`.
146 /// `path` is UTF8-encoded.
147 pub fn exists(path: []const u8) AccessError!void {
148 return posix.access(path, posix.F_OK);
149 }
150
151 /// Same as `exists` except the parameter is null-terminated UTF16LE-encoded.
152 pub fn existsW(path: [*]const u16) AccessError!void {
153 return posix.accessW(path, posix.F_OK);
154 }
155
156 /// Same as `exists` except the parameter is null-terminated.
157 pub fn existsC(path: [*]const u8) AccessError!void {
158 return posix.accessC(path, posix.F_OK);
159 }
160
161 /// Upon success, the stream is in an uninitialized state. To continue using it,
162 /// you must use the open() function.
163 pub fn close(self: File) void {
164 os.close(self.handle);
165 }
166
167 /// Test whether the file refers to a terminal.
168 /// See also `supportsAnsiEscapeCodes`.
169 pub fn isTty(self: File) bool {
170 return posix.isatty(self.handle);
171 }
172
173 /// Test whether ANSI escape codes will be treated as such.
174 pub fn supportsAnsiEscapeCodes(self: File) bool {
175 if (windows.is_the_target) {
176 return posix.isCygwinPty(self.handle);
177 }
178 return self.isTty();
179 }
180
181 pub const SeekError = error{
182 /// TODO make this error impossible to get
183 Overflow,
184 Unseekable,
185 Unexpected,
186 };
187
188 pub fn seekForward(self: File, amount: i64) SeekError!void {
189 switch (builtin.os) {
190 Os.linux, Os.macosx, Os.ios, Os.freebsd, Os.netbsd => {
191 const iamount = try math.cast(isize, amount);
192 const result = posix.lseek(self.handle, iamount, posix.SEEK_CUR);
193 const err = posix.getErrno(result);
194 if (err > 0) {
195 return switch (err) {
196 // We do not make this an error code because if you get EBADF it's always a bug,
197 // since the fd could have been reused.
198 posix.EBADF => unreachable,
199 posix.EINVAL => error.Unseekable,
200 posix.EOVERFLOW => error.Unseekable,
201 posix.ESPIPE => error.Unseekable,
202 posix.ENXIO => error.Unseekable,
203 else => os.unexpectedErrorPosix(err),
204 };
205 }
206 },
207 Os.windows => {
208 if (windows.SetFilePointerEx(self.handle, amount, null, windows.FILE_CURRENT) == 0) {
209 const err = windows.GetLastError();
210 return switch (err) {
211 windows.ERROR.INVALID_PARAMETER => unreachable,
212 else => os.unexpectedErrorWindows(err),
213 };
214 }
215 },
216 else => @compileError("unsupported OS"),
217 }
218 }
219
220 pub fn seekTo(self: File, pos: u64) SeekError!void {
221 switch (builtin.os) {
222 Os.linux, Os.macosx, Os.ios, Os.freebsd, Os.netbsd => {
223 const ipos = try math.cast(isize, pos);
224 const result = posix.lseek(self.handle, ipos, posix.SEEK_SET);
225 const err = posix.getErrno(result);
226 if (err > 0) {
227 return switch (err) {
228 // We do not make this an error code because if you get EBADF it's always a bug,
229 // since the fd could have been reused.
230 posix.EBADF => unreachable,
231 posix.EINVAL => error.Unseekable,
232 posix.EOVERFLOW => error.Unseekable,
233 posix.ESPIPE => error.Unseekable,
234 posix.ENXIO => error.Unseekable,
235 else => os.unexpectedErrorPosix(err),
236 };
237 }
238 },
239 Os.windows => {
240 const ipos = try math.cast(isize, pos);
241 if (windows.SetFilePointerEx(self.handle, ipos, null, windows.FILE_BEGIN) == 0) {
242 const err = windows.GetLastError();
243 return switch (err) {
244 windows.ERROR.INVALID_PARAMETER => unreachable,
245 windows.ERROR.INVALID_HANDLE => unreachable,
246 else => os.unexpectedErrorWindows(err),
247 };
248 }
249 },
250 else => @compileError("unsupported OS: " ++ @tagName(builtin.os)),
251 }
252 }
253
254 pub const GetSeekPosError = error{
255 SystemResources,
256 Unseekable,
257 Unexpected,
258 };
259
260 pub fn getPos(self: File) GetSeekPosError!u64 {
261 switch (builtin.os) {
262 Os.linux, Os.macosx, Os.ios, Os.freebsd, Os.netbsd => {
263 const result = posix.lseek(self.handle, 0, posix.SEEK_CUR);
264 const err = posix.getErrno(result);
265 if (err > 0) {
266 return switch (err) {
267 // We do not make this an error code because if you get EBADF it's always a bug,
268 // since the fd could have been reused.
269 posix.EBADF => unreachable,
270 posix.EINVAL => error.Unseekable,
271 posix.EOVERFLOW => error.Unseekable,
272 posix.ESPIPE => error.Unseekable,
273 posix.ENXIO => error.Unseekable,
274 else => os.unexpectedErrorPosix(err),
275 };
276 }
277 return u64(result);
278 },
279 Os.windows => {
280 var pos: windows.LARGE_INTEGER = undefined;
281 if (windows.SetFilePointerEx(self.handle, 0, &pos, windows.FILE_CURRENT) == 0) {
282 const err = windows.GetLastError();
283 return switch (err) {
284 windows.ERROR.INVALID_PARAMETER => unreachable,
285 else => os.unexpectedErrorWindows(err),
286 };
287 }
288
289 return @intCast(u64, pos);
290 },
291 else => @compileError("unsupported OS"),
292 }
293 }
294
295 pub fn getEndPos(self: File) GetSeekPosError!u64 {
296 if (is_posix) {
297 const stat = try os.posixFStat(self.handle);
298 return @intCast(u64, stat.size);
299 } else if (is_windows) {
300 var file_size: windows.LARGE_INTEGER = undefined;
301 if (windows.GetFileSizeEx(self.handle, &file_size) == 0) {
302 const err = windows.GetLastError();
303 return switch (err) {
304 else => os.unexpectedErrorWindows(err),
305 };
306 }
307 return @intCast(u64, file_size);
308 } else {
309 @compileError("TODO support getEndPos on this OS");
310 }
311 }
312
313 pub const ModeError = error{
314 SystemResources,
315 Unexpected,
316 };
317
318 pub fn mode(self: File) ModeError!Mode {
319 if (is_posix) {
320 var stat: posix.Stat = undefined;
321 const err = posix.getErrno(posix.fstat(self.handle, &stat));
322 if (err > 0) {
323 return switch (err) {
324 // We do not make this an error code because if you get EBADF it's always a bug,
325 // since the fd could have been reused.
326 posix.EBADF => unreachable,
327 posix.ENOMEM => error.SystemResources,
328 else => os.unexpectedErrorPosix(err),
329 };
330 }
331
332 // TODO: we should be able to cast u16 to ModeError!u32, making this
333 // explicit cast not necessary
334 return Mode(stat.mode);
335 } else if (is_windows) {
336 return {};
337 } else {
338 @compileError("TODO support file mode on this OS");
339 }
340 }
341
342 pub const ReadError = posix.ReadError;
343
344 pub fn read(self: File, buffer: []u8) ReadError!usize {
345 return posix.read(self.handle, buffer);
346 }
347
348 pub const WriteError = posix.WriteError;
349
350 pub fn write(self: File, bytes: []const u8) WriteError!void {
351 return posix.write(self.handle, bytes);
352 }
353
354 pub fn inStream(file: File) InStream {
355 return InStream{
356 .file = file,
357 .stream = InStream.Stream{ .readFn = InStream.readFn },
358 };
359 }
360
361 pub fn outStream(file: File) OutStream {
362 return OutStream{
363 .file = file,
364 .stream = OutStream.Stream{ .writeFn = OutStream.writeFn },
365 };
366 }
367
368 pub fn seekableStream(file: File) SeekableStream {
369 return SeekableStream{
370 .file = file,
371 .stream = SeekableStream.Stream{
372 .seekToFn = SeekableStream.seekToFn,
373 .seekForwardFn = SeekableStream.seekForwardFn,
374 .getPosFn = SeekableStream.getPosFn,
375 .getEndPosFn = SeekableStream.getEndPosFn,
376 },
377 };
378 }
379
380 /// Implementation of io.InStream trait for File
381 pub const InStream = struct {
382 file: File,
383 stream: Stream,
384
385 pub const Error = ReadError;
386 pub const Stream = io.InStream(Error);
387
388 fn readFn(in_stream: *Stream, buffer: []u8) Error!usize {
389 const self = @fieldParentPtr(InStream, "stream", in_stream);
390 return self.file.read(buffer);
391 }
392 };
393
394 /// Implementation of io.OutStream trait for File
395 pub const OutStream = struct {
396 file: File,
397 stream: Stream,
398
399 pub const Error = WriteError;
400 pub const Stream = io.OutStream(Error);
401
402 fn writeFn(out_stream: *Stream, bytes: []const u8) Error!void {
403 const self = @fieldParentPtr(OutStream, "stream", out_stream);
404 return self.file.write(bytes);
405 }
406 };
407
408 /// Implementation of io.SeekableStream trait for File
409 pub const SeekableStream = struct {
410 file: File,
411 stream: Stream,
412
413 pub const Stream = io.SeekableStream(SeekError, GetSeekPosError);
414
415 pub fn seekToFn(seekable_stream: *Stream, pos: u64) SeekError!void {
416 const self = @fieldParentPtr(SeekableStream, "stream", seekable_stream);
417 return self.file.seekTo(pos);
418 }
419
420 pub fn seekForwardFn(seekable_stream: *Stream, amt: i64) SeekError!void {
421 const self = @fieldParentPtr(SeekableStream, "stream", seekable_stream);
422 return self.file.seekForward(amt);
423 }
424
425 pub fn getEndPosFn(seekable_stream: *Stream) GetSeekPosError!u64 {
426 const self = @fieldParentPtr(SeekableStream, "stream", seekable_stream);
427 return self.file.getEndPos();
428 }
429
430 pub fn getPosFn(seekable_stream: *Stream) GetSeekPosError!u64 {
431 const self = @fieldParentPtr(SeekableStream, "stream", seekable_stream);
432 return self.file.getPos();
433 }
434 };
435
436 pub fn stdout() !File {
437 const handle = try posix.GetStdHandle(posix.STD_OUTPUT_HANDLE);
438 return openHandle(handle);
439 }
440
441 pub fn stderr() !File {
442 const handle = try posix.GetStdHandle(posix.STD_ERROR_HANDLE);
443 return openHandle(handle);
444 }
445
446 pub fn stdin() !File {
447 const handle = try posix.GetStdHandle(posix.STD_INPUT_HANDLE);
448 return openHandle(handle);
449 }
450};
std/os/get_app_data_dir.zig deleted-69
......@@ -1,69 +0,0 @@
1const std = @import("../std.zig");
2const builtin = @import("builtin");
3const unicode = std.unicode;
4const mem = std.mem;
5const os = std.os;
6
7pub const GetAppDataDirError = error{
8 OutOfMemory,
9 AppDataDirUnavailable,
10};
11
12/// Caller owns returned memory.
13/// TODO determine if we can remove the allocator requirement
14pub fn getAppDataDir(allocator: *mem.Allocator, appname: []const u8) GetAppDataDirError![]u8 {
15 switch (builtin.os) {
16 builtin.Os.windows => {
17 var dir_path_ptr: [*]u16 = undefined;
18 switch (os.windows.SHGetKnownFolderPath(
19 &os.windows.FOLDERID_LocalAppData,
20 os.windows.KF_FLAG_CREATE,
21 null,
22 &dir_path_ptr,
23 )) {
24 os.windows.S_OK => {
25 defer os.windows.CoTaskMemFree(@ptrCast(*c_void, dir_path_ptr));
26 const global_dir = unicode.utf16leToUtf8Alloc(allocator, utf16lePtrSlice(dir_path_ptr)) catch |err| switch (err) {
27 error.UnexpectedSecondSurrogateHalf => return error.AppDataDirUnavailable,
28 error.ExpectedSecondSurrogateHalf => return error.AppDataDirUnavailable,
29 error.DanglingSurrogateHalf => return error.AppDataDirUnavailable,
30 error.OutOfMemory => return error.OutOfMemory,
31 };
32 defer allocator.free(global_dir);
33 return os.path.join(allocator, [][]const u8{ global_dir, appname });
34 },
35 os.windows.E_OUTOFMEMORY => return error.OutOfMemory,
36 else => return error.AppDataDirUnavailable,
37 }
38 },
39 builtin.Os.macosx => {
40 const home_dir = os.getEnvPosix("HOME") orelse {
41 // TODO look in /etc/passwd
42 return error.AppDataDirUnavailable;
43 };
44 return os.path.join(allocator, [][]const u8{ home_dir, "Library", "Application Support", appname });
45 },
46 builtin.Os.linux, builtin.Os.freebsd, builtin.Os.netbsd => {
47 const home_dir = os.getEnvPosix("HOME") orelse {
48 // TODO look in /etc/passwd
49 return error.AppDataDirUnavailable;
50 };
51 return os.path.join(allocator, [][]const u8{ home_dir, ".local", "share", appname });
52 },
53 else => @compileError("Unsupported OS"),
54 }
55}
56
57fn utf16lePtrSlice(ptr: [*]const u16) []const u16 {
58 var index: usize = 0;
59 while (ptr[index] != 0) : (index += 1) {}
60 return ptr[0..index];
61}
62
63test "std.os.getAppDataDir" {
64 var buf: [512]u8 = undefined;
65 const allocator = &std.heap.FixedBufferAllocator.init(buf[0..]).allocator;
66
67 // We can't actually validate the result
68 _ = getAppDataDir(allocator, "zig") catch return;
69}
std/os/get_user_id.zig deleted-104
......@@ -1,104 +0,0 @@
1const builtin = @import("builtin");
2const Os = builtin.Os;
3const os = @import("../os.zig");
4const io = @import("../io.zig");
5
6pub const UserInfo = struct {
7 uid: u32,
8 gid: u32,
9};
10
11/// POSIX function which gets a uid from username.
12pub fn getUserInfo(name: []const u8) !UserInfo {
13 return switch (builtin.os) {
14 Os.linux, Os.macosx, Os.ios, Os.freebsd, Os.netbsd => posixGetUserInfo(name),
15 else => @compileError("Unsupported OS"),
16 };
17}
18
19const State = enum {
20 Start,
21 WaitForNextLine,
22 SkipPassword,
23 ReadUserId,
24 ReadGroupId,
25};
26
27// TODO this reads /etc/passwd. But sometimes the user/id mapping is in something else
28// like NIS, AD, etc. See `man nss` or look at an strace for `id myuser`.
29
30pub fn posixGetUserInfo(name: []const u8) !UserInfo {
31 var in_stream = try io.InStream.open("/etc/passwd", null);
32 defer in_stream.close();
33
34 var buf: [os.page_size]u8 = undefined;
35 var name_index: usize = 0;
36 var state = State.Start;
37 var uid: u32 = 0;
38 var gid: u32 = 0;
39
40 while (true) {
41 const amt_read = try in_stream.read(buf[0..]);
42 for (buf[0..amt_read]) |byte| {
43 switch (state) {
44 State.Start => switch (byte) {
45 ':' => {
46 state = if (name_index == name.len) State.SkipPassword else State.WaitForNextLine;
47 },
48 '\n' => return error.CorruptPasswordFile,
49 else => {
50 if (name_index == name.len or name[name_index] != byte) {
51 state = State.WaitForNextLine;
52 }
53 name_index += 1;
54 },
55 },
56 State.WaitForNextLine => switch (byte) {
57 '\n' => {
58 name_index = 0;
59 state = State.Start;
60 },
61 else => continue,
62 },
63 State.SkipPassword => switch (byte) {
64 '\n' => return error.CorruptPasswordFile,
65 ':' => {
66 state = State.ReadUserId;
67 },
68 else => continue,
69 },
70 State.ReadUserId => switch (byte) {
71 ':' => {
72 state = State.ReadGroupId;
73 },
74 '\n' => return error.CorruptPasswordFile,
75 else => {
76 const digit = switch (byte) {
77 '0'...'9' => byte - '0',
78 else => return error.CorruptPasswordFile,
79 };
80 if (@mulWithOverflow(u32, uid, 10, *uid)) return error.CorruptPasswordFile;
81 if (@addWithOverflow(u32, uid, digit, *uid)) return error.CorruptPasswordFile;
82 },
83 },
84 State.ReadGroupId => switch (byte) {
85 '\n', ':' => {
86 return UserInfo{
87 .uid = uid,
88 .gid = gid,
89 };
90 },
91 else => {
92 const digit = switch (byte) {
93 '0'...'9' => byte - '0',
94 else => return error.CorruptPasswordFile,
95 };
96 if (@mulWithOverflow(u32, gid, 10, *gid)) return error.CorruptPasswordFile;
97 if (@addWithOverflow(u32, gid, digit, *gid)) return error.CorruptPasswordFile;
98 },
99 },
100 }
101 }
102 if (amt_read < buf.len) return error.UserNotFound;
103 }
104}
std/os/linux/sys.zig+15-2
......@@ -320,8 +320,21 @@ pub fn close(fd: i32) usize {
320320 return syscall1(SYS_close, @bitCast(usize, isize(fd)));
321321}
322322
323pub fn lseek(fd: i32, offset: isize, ref_pos: usize) usize {
324 return syscall3(SYS_lseek, @bitCast(usize, isize(fd)), @bitCast(usize, offset), ref_pos);
323/// Can only be called on 32 bit systems. For 64 bit see `lseek`.
324pub fn llseek(fd: i32, offset: u64, result: ?*u64, whence: usize) usize {
325 return syscall5(
326 SYS__llseek,
327 @bitCast(usize, isize(fd)),
328 @truncate(usize, offset >> 32),
329 @truncate(usize, offset),
330 @ptrToInt(result),
331 whence,
332 );
333}
334
335/// Can only be called on 64 bit systems. For 32 bit see `llseek`.
336pub fn lseek(fd: i32, offset: i64, whence: usize) usize {
337 return syscall3(SYS_lseek, @bitCast(usize, isize(fd)), @bitCast(usize, offset), whence);
325338}
326339
327340pub fn exit(status: i32) noreturn {
std/os/path.zig deleted-1286
......@@ -1,1286 +0,0 @@
1const std = @import("../std.zig");
2const builtin = @import("builtin");
3const Os = builtin.Os;
4const debug = std.debug;
5const assert = debug.assert;
6const testing = std.testing;
7const mem = std.mem;
8const fmt = std.fmt;
9const Allocator = mem.Allocator;
10const os = std.os;
11const math = std.math;
12const posix = os.posix;
13const windows = os.windows;
14const cstr = std.cstr;
15const windows_util = @import("windows/util.zig");
16
17pub const sep_windows = '\\';
18pub const sep_posix = '/';
19pub const sep = if (is_windows) sep_windows else sep_posix;
20
21pub const sep_str = [1]u8{sep};
22
23pub const delimiter_windows = ';';
24pub const delimiter_posix = ':';
25pub const delimiter = if (is_windows) delimiter_windows else delimiter_posix;
26
27const is_windows = builtin.os == builtin.Os.windows;
28
29pub fn isSep(byte: u8) bool {
30 if (is_windows) {
31 return byte == '/' or byte == '\\';
32 } else {
33 return byte == '/';
34 }
35}
36
37/// This is different from mem.join in that the separator will not be repeated if
38/// it is found at the end or beginning of a pair of consecutive paths.
39fn joinSep(allocator: *Allocator, separator: u8, paths: []const []const u8) ![]u8 {
40 if (paths.len == 0) return (([*]u8)(undefined))[0..0];
41
42 const total_len = blk: {
43 var sum: usize = paths[0].len;
44 var i: usize = 1;
45 while (i < paths.len) : (i += 1) {
46 const prev_path = paths[i - 1];
47 const this_path = paths[i];
48 const prev_sep = (prev_path.len != 0 and prev_path[prev_path.len - 1] == separator);
49 const this_sep = (this_path.len != 0 and this_path[0] == separator);
50 sum += @boolToInt(!prev_sep and !this_sep);
51 sum += if (prev_sep and this_sep) this_path.len - 1 else this_path.len;
52 }
53 break :blk sum;
54 };
55
56 const buf = try allocator.alloc(u8, total_len);
57 errdefer allocator.free(buf);
58
59 mem.copy(u8, buf, paths[0]);
60 var buf_index: usize = paths[0].len;
61 var i: usize = 1;
62 while (i < paths.len) : (i += 1) {
63 const prev_path = paths[i - 1];
64 const this_path = paths[i];
65 const prev_sep = (prev_path.len != 0 and prev_path[prev_path.len - 1] == separator);
66 const this_sep = (this_path.len != 0 and this_path[0] == separator);
67 if (!prev_sep and !this_sep) {
68 buf[buf_index] = separator;
69 buf_index += 1;
70 }
71 const adjusted_path = if (prev_sep and this_sep) this_path[1..] else this_path;
72 mem.copy(u8, buf[buf_index..], adjusted_path);
73 buf_index += adjusted_path.len;
74 }
75
76 // No need for shrink since buf is exactly the correct size.
77 return buf;
78}
79
80pub const join = if (is_windows) joinWindows else joinPosix;
81
82/// Naively combines a series of paths with the native path seperator.
83/// Allocates memory for the result, which must be freed by the caller.
84pub fn joinWindows(allocator: *Allocator, paths: []const []const u8) ![]u8 {
85 return joinSep(allocator, sep_windows, paths);
86}
87
88/// Naively combines a series of paths with the native path seperator.
89/// Allocates memory for the result, which must be freed by the caller.
90pub fn joinPosix(allocator: *Allocator, paths: []const []const u8) ![]u8 {
91 return joinSep(allocator, sep_posix, paths);
92}
93
94fn testJoinWindows(paths: []const []const u8, expected: []const u8) void {
95 var buf: [1024]u8 = undefined;
96 const a = &std.heap.FixedBufferAllocator.init(&buf).allocator;
97 const actual = joinWindows(a, paths) catch @panic("fail");
98 testing.expectEqualSlices(u8, expected, actual);
99}
100
101fn testJoinPosix(paths: []const []const u8, expected: []const u8) void {
102 var buf: [1024]u8 = undefined;
103 const a = &std.heap.FixedBufferAllocator.init(&buf).allocator;
104 const actual = joinPosix(a, paths) catch @panic("fail");
105 testing.expectEqualSlices(u8, expected, actual);
106}
107
108test "os.path.join" {
109 testJoinWindows([][]const u8{ "c:\\a\\b", "c" }, "c:\\a\\b\\c");
110 testJoinWindows([][]const u8{ "c:\\a\\b", "c" }, "c:\\a\\b\\c");
111 testJoinWindows([][]const u8{ "c:\\a\\b\\", "c" }, "c:\\a\\b\\c");
112
113 testJoinWindows([][]const u8{ "c:\\", "a", "b\\", "c" }, "c:\\a\\b\\c");
114 testJoinWindows([][]const u8{ "c:\\a\\", "b\\", "c" }, "c:\\a\\b\\c");
115
116 testJoinWindows(
117 [][]const u8{ "c:\\home\\andy\\dev\\zig\\build\\lib\\zig\\std", "io.zig" },
118 "c:\\home\\andy\\dev\\zig\\build\\lib\\zig\\std\\io.zig",
119 );
120
121 testJoinPosix([][]const u8{ "/a/b", "c" }, "/a/b/c");
122 testJoinPosix([][]const u8{ "/a/b/", "c" }, "/a/b/c");
123
124 testJoinPosix([][]const u8{ "/", "a", "b/", "c" }, "/a/b/c");
125 testJoinPosix([][]const u8{ "/a/", "b/", "c" }, "/a/b/c");
126
127 testJoinPosix(
128 [][]const u8{ "/home/andy/dev/zig/build/lib/zig/std", "io.zig" },
129 "/home/andy/dev/zig/build/lib/zig/std/io.zig",
130 );
131
132 testJoinPosix([][]const u8{ "a", "/c" }, "a/c");
133 testJoinPosix([][]const u8{ "a/", "/c" }, "a/c");
134}
135
136pub fn isAbsolute(path: []const u8) bool {
137 if (is_windows) {
138 return isAbsoluteWindows(path);
139 } else {
140 return isAbsolutePosix(path);
141 }
142}
143
144pub fn isAbsoluteWindows(path: []const u8) bool {
145 if (path[0] == '/')
146 return true;
147
148 if (path[0] == '\\') {
149 return true;
150 }
151 if (path.len < 3) {
152 return false;
153 }
154 if (path[1] == ':') {
155 if (path[2] == '/')
156 return true;
157 if (path[2] == '\\')
158 return true;
159 }
160 return false;
161}
162
163pub fn isAbsolutePosix(path: []const u8) bool {
164 return path[0] == sep_posix;
165}
166
167test "os.path.isAbsoluteWindows" {
168 testIsAbsoluteWindows("/", true);
169 testIsAbsoluteWindows("//", true);
170 testIsAbsoluteWindows("//server", true);
171 testIsAbsoluteWindows("//server/file", true);
172 testIsAbsoluteWindows("\\\\server\\file", true);
173 testIsAbsoluteWindows("\\\\server", true);
174 testIsAbsoluteWindows("\\\\", true);
175 testIsAbsoluteWindows("c", false);
176 testIsAbsoluteWindows("c:", false);
177 testIsAbsoluteWindows("c:\\", true);
178 testIsAbsoluteWindows("c:/", true);
179 testIsAbsoluteWindows("c://", true);
180 testIsAbsoluteWindows("C:/Users/", true);
181 testIsAbsoluteWindows("C:\\Users\\", true);
182 testIsAbsoluteWindows("C:cwd/another", false);
183 testIsAbsoluteWindows("C:cwd\\another", false);
184 testIsAbsoluteWindows("directory/directory", false);
185 testIsAbsoluteWindows("directory\\directory", false);
186 testIsAbsoluteWindows("/usr/local", true);
187}
188
189test "os.path.isAbsolutePosix" {
190 testIsAbsolutePosix("/home/foo", true);
191 testIsAbsolutePosix("/home/foo/..", true);
192 testIsAbsolutePosix("bar/", false);
193 testIsAbsolutePosix("./baz", false);
194}
195
196fn testIsAbsoluteWindows(path: []const u8, expected_result: bool) void {
197 testing.expectEqual(expected_result, isAbsoluteWindows(path));
198}
199
200fn testIsAbsolutePosix(path: []const u8, expected_result: bool) void {
201 testing.expectEqual(expected_result, isAbsolutePosix(path));
202}
203
204pub const WindowsPath = struct {
205 is_abs: bool,
206 kind: Kind,
207 disk_designator: []const u8,
208
209 pub const Kind = enum {
210 None,
211 Drive,
212 NetworkShare,
213 };
214};
215
216pub fn windowsParsePath(path: []const u8) WindowsPath {
217 if (path.len >= 2 and path[1] == ':') {
218 return WindowsPath{
219 .is_abs = isAbsoluteWindows(path),
220 .kind = WindowsPath.Kind.Drive,
221 .disk_designator = path[0..2],
222 };
223 }
224 if (path.len >= 1 and (path[0] == '/' or path[0] == '\\') and
225 (path.len == 1 or (path[1] != '/' and path[1] != '\\')))
226 {
227 return WindowsPath{
228 .is_abs = true,
229 .kind = WindowsPath.Kind.None,
230 .disk_designator = path[0..0],
231 };
232 }
233 const relative_path = WindowsPath{
234 .kind = WindowsPath.Kind.None,
235 .disk_designator = []u8{},
236 .is_abs = false,
237 };
238 if (path.len < "//a/b".len) {
239 return relative_path;
240 }
241
242 // TODO when I combined these together with `inline for` the compiler crashed
243 {
244 const this_sep = '/';
245 const two_sep = []u8{ this_sep, this_sep };
246 if (mem.startsWith(u8, path, two_sep)) {
247 if (path[2] == this_sep) {
248 return relative_path;
249 }
250
251 var it = mem.tokenize(path, []u8{this_sep});
252 _ = (it.next() orelse return relative_path);
253 _ = (it.next() orelse return relative_path);
254 return WindowsPath{
255 .is_abs = isAbsoluteWindows(path),
256 .kind = WindowsPath.Kind.NetworkShare,
257 .disk_designator = path[0..it.index],
258 };
259 }
260 }
261 {
262 const this_sep = '\\';
263 const two_sep = []u8{ this_sep, this_sep };
264 if (mem.startsWith(u8, path, two_sep)) {
265 if (path[2] == this_sep) {
266 return relative_path;
267 }
268
269 var it = mem.tokenize(path, []u8{this_sep});
270 _ = (it.next() orelse return relative_path);
271 _ = (it.next() orelse return relative_path);
272 return WindowsPath{
273 .is_abs = isAbsoluteWindows(path),
274 .kind = WindowsPath.Kind.NetworkShare,
275 .disk_designator = path[0..it.index],
276 };
277 }
278 }
279 return relative_path;
280}
281
282test "os.path.windowsParsePath" {
283 {
284 const parsed = windowsParsePath("//a/b");
285 testing.expect(parsed.is_abs);
286 testing.expect(parsed.kind == WindowsPath.Kind.NetworkShare);
287 testing.expect(mem.eql(u8, parsed.disk_designator, "//a/b"));
288 }
289 {
290 const parsed = windowsParsePath("\\\\a\\b");
291 testing.expect(parsed.is_abs);
292 testing.expect(parsed.kind == WindowsPath.Kind.NetworkShare);
293 testing.expect(mem.eql(u8, parsed.disk_designator, "\\\\a\\b"));
294 }
295 {
296 const parsed = windowsParsePath("\\\\a\\");
297 testing.expect(!parsed.is_abs);
298 testing.expect(parsed.kind == WindowsPath.Kind.None);
299 testing.expect(mem.eql(u8, parsed.disk_designator, ""));
300 }
301 {
302 const parsed = windowsParsePath("/usr/local");
303 testing.expect(parsed.is_abs);
304 testing.expect(parsed.kind == WindowsPath.Kind.None);
305 testing.expect(mem.eql(u8, parsed.disk_designator, ""));
306 }
307 {
308 const parsed = windowsParsePath("c:../");
309 testing.expect(!parsed.is_abs);
310 testing.expect(parsed.kind == WindowsPath.Kind.Drive);
311 testing.expect(mem.eql(u8, parsed.disk_designator, "c:"));
312 }
313}
314
315pub fn diskDesignator(path: []const u8) []const u8 {
316 if (is_windows) {
317 return diskDesignatorWindows(path);
318 } else {
319 return "";
320 }
321}
322
323pub fn diskDesignatorWindows(path: []const u8) []const u8 {
324 return windowsParsePath(path).disk_designator;
325}
326
327fn networkShareServersEql(ns1: []const u8, ns2: []const u8) bool {
328 const sep1 = ns1[0];
329 const sep2 = ns2[0];
330
331 var it1 = mem.tokenize(ns1, []u8{sep1});
332 var it2 = mem.tokenize(ns2, []u8{sep2});
333
334 // TODO ASCII is wrong, we actually need full unicode support to compare paths.
335 return asciiEqlIgnoreCase(it1.next().?, it2.next().?);
336}
337
338fn compareDiskDesignators(kind: WindowsPath.Kind, p1: []const u8, p2: []const u8) bool {
339 switch (kind) {
340 WindowsPath.Kind.None => {
341 assert(p1.len == 0);
342 assert(p2.len == 0);
343 return true;
344 },
345 WindowsPath.Kind.Drive => {
346 return asciiUpper(p1[0]) == asciiUpper(p2[0]);
347 },
348 WindowsPath.Kind.NetworkShare => {
349 const sep1 = p1[0];
350 const sep2 = p2[0];
351
352 var it1 = mem.tokenize(p1, []u8{sep1});
353 var it2 = mem.tokenize(p2, []u8{sep2});
354
355 // TODO ASCII is wrong, we actually need full unicode support to compare paths.
356 return asciiEqlIgnoreCase(it1.next().?, it2.next().?) and asciiEqlIgnoreCase(it1.next().?, it2.next().?);
357 },
358 }
359}
360
361fn asciiUpper(byte: u8) u8 {
362 return switch (byte) {
363 'a'...'z' => 'A' + (byte - 'a'),
364 else => byte,
365 };
366}
367
368fn asciiEqlIgnoreCase(s1: []const u8, s2: []const u8) bool {
369 if (s1.len != s2.len)
370 return false;
371 var i: usize = 0;
372 while (i < s1.len) : (i += 1) {
373 if (asciiUpper(s1[i]) != asciiUpper(s2[i]))
374 return false;
375 }
376 return true;
377}
378
379/// On Windows, this calls `resolveWindows` and on POSIX it calls `resolvePosix`.
380pub fn resolve(allocator: *Allocator, paths: []const []const u8) ![]u8 {
381 if (is_windows) {
382 return resolveWindows(allocator, paths);
383 } else {
384 return resolvePosix(allocator, paths);
385 }
386}
387
388/// This function is like a series of `cd` statements executed one after another.
389/// It resolves "." and "..".
390/// The result does not have a trailing path separator.
391/// If all paths are relative it uses the current working directory as a starting point.
392/// Each drive has its own current working directory.
393/// Path separators are canonicalized to '\\' and drives are canonicalized to capital letters.
394/// Note: all usage of this function should be audited due to the existence of symlinks.
395/// Without performing actual syscalls, resolving `..` could be incorrect.
396pub fn resolveWindows(allocator: *Allocator, paths: []const []const u8) ![]u8 {
397 if (paths.len == 0) {
398 assert(is_windows); // resolveWindows called on non windows can't use getCwd
399 return os.getCwdAlloc(allocator);
400 }
401
402 // determine which disk designator we will result with, if any
403 var result_drive_buf = "_:";
404 var result_disk_designator: []const u8 = "";
405 var have_drive_kind = WindowsPath.Kind.None;
406 var have_abs_path = false;
407 var first_index: usize = 0;
408 var max_size: usize = 0;
409 for (paths) |p, i| {
410 const parsed = windowsParsePath(p);
411 if (parsed.is_abs) {
412 have_abs_path = true;
413 first_index = i;
414 max_size = result_disk_designator.len;
415 }
416 switch (parsed.kind) {
417 WindowsPath.Kind.Drive => {
418 result_drive_buf[0] = asciiUpper(parsed.disk_designator[0]);
419 result_disk_designator = result_drive_buf[0..];
420 have_drive_kind = WindowsPath.Kind.Drive;
421 },
422 WindowsPath.Kind.NetworkShare => {
423 result_disk_designator = parsed.disk_designator;
424 have_drive_kind = WindowsPath.Kind.NetworkShare;
425 },
426 WindowsPath.Kind.None => {},
427 }
428 max_size += p.len + 1;
429 }
430
431 // if we will result with a disk designator, loop again to determine
432 // which is the last time the disk designator is absolutely specified, if any
433 // and count up the max bytes for paths related to this disk designator
434 if (have_drive_kind != WindowsPath.Kind.None) {
435 have_abs_path = false;
436 first_index = 0;
437 max_size = result_disk_designator.len;
438 var correct_disk_designator = false;
439
440 for (paths) |p, i| {
441 const parsed = windowsParsePath(p);
442 if (parsed.kind != WindowsPath.Kind.None) {
443 if (parsed.kind == have_drive_kind) {
444 correct_disk_designator = compareDiskDesignators(have_drive_kind, result_disk_designator, parsed.disk_designator);
445 } else {
446 continue;
447 }
448 }
449 if (!correct_disk_designator) {
450 continue;
451 }
452 if (parsed.is_abs) {
453 first_index = i;
454 max_size = result_disk_designator.len;
455 have_abs_path = true;
456 }
457 max_size += p.len + 1;
458 }
459 }
460
461 // Allocate result and fill in the disk designator, calling getCwd if we have to.
462 var result: []u8 = undefined;
463 var result_index: usize = 0;
464
465 if (have_abs_path) {
466 switch (have_drive_kind) {
467 WindowsPath.Kind.Drive => {
468 result = try allocator.alloc(u8, max_size);
469
470 mem.copy(u8, result, result_disk_designator);
471 result_index += result_disk_designator.len;
472 },
473 WindowsPath.Kind.NetworkShare => {
474 result = try allocator.alloc(u8, max_size);
475 var it = mem.tokenize(paths[first_index], "/\\");
476 const server_name = it.next().?;
477 const other_name = it.next().?;
478
479 result[result_index] = '\\';
480 result_index += 1;
481 result[result_index] = '\\';
482 result_index += 1;
483 mem.copy(u8, result[result_index..], server_name);
484 result_index += server_name.len;
485 result[result_index] = '\\';
486 result_index += 1;
487 mem.copy(u8, result[result_index..], other_name);
488 result_index += other_name.len;
489
490 result_disk_designator = result[0..result_index];
491 },
492 WindowsPath.Kind.None => {
493 assert(is_windows); // resolveWindows called on non windows can't use getCwd
494 const cwd = try os.getCwdAlloc(allocator);
495 defer allocator.free(cwd);
496 const parsed_cwd = windowsParsePath(cwd);
497 result = try allocator.alloc(u8, max_size + parsed_cwd.disk_designator.len + 1);
498 mem.copy(u8, result, parsed_cwd.disk_designator);
499 result_index += parsed_cwd.disk_designator.len;
500 result_disk_designator = result[0..parsed_cwd.disk_designator.len];
501 if (parsed_cwd.kind == WindowsPath.Kind.Drive) {
502 result[0] = asciiUpper(result[0]);
503 }
504 have_drive_kind = parsed_cwd.kind;
505 },
506 }
507 } else {
508 assert(is_windows); // resolveWindows called on non windows can't use getCwd
509 // TODO call get cwd for the result_disk_designator instead of the global one
510 const cwd = try os.getCwdAlloc(allocator);
511 defer allocator.free(cwd);
512
513 result = try allocator.alloc(u8, max_size + cwd.len + 1);
514
515 mem.copy(u8, result, cwd);
516 result_index += cwd.len;
517 const parsed_cwd = windowsParsePath(result[0..result_index]);
518 result_disk_designator = parsed_cwd.disk_designator;
519 if (parsed_cwd.kind == WindowsPath.Kind.Drive) {
520 result[0] = asciiUpper(result[0]);
521 }
522 have_drive_kind = parsed_cwd.kind;
523 }
524 errdefer allocator.free(result);
525
526 // Now we know the disk designator to use, if any, and what kind it is. And our result
527 // is big enough to append all the paths to.
528 var correct_disk_designator = true;
529 for (paths[first_index..]) |p, i| {
530 const parsed = windowsParsePath(p);
531
532 if (parsed.kind != WindowsPath.Kind.None) {
533 if (parsed.kind == have_drive_kind) {
534 correct_disk_designator = compareDiskDesignators(have_drive_kind, result_disk_designator, parsed.disk_designator);
535 } else {
536 continue;
537 }
538 }
539 if (!correct_disk_designator) {
540 continue;
541 }
542 var it = mem.tokenize(p[parsed.disk_designator.len..], "/\\");
543 while (it.next()) |component| {
544 if (mem.eql(u8, component, ".")) {
545 continue;
546 } else if (mem.eql(u8, component, "..")) {
547 while (true) {
548 if (result_index == 0 or result_index == result_disk_designator.len)
549 break;
550 result_index -= 1;
551 if (result[result_index] == '\\' or result[result_index] == '/')
552 break;
553 }
554 } else {
555 result[result_index] = sep_windows;
556 result_index += 1;
557 mem.copy(u8, result[result_index..], component);
558 result_index += component.len;
559 }
560 }
561 }
562
563 if (result_index == result_disk_designator.len) {
564 result[result_index] = '\\';
565 result_index += 1;
566 }
567
568 return allocator.shrink(result, result_index);
569}
570
571/// This function is like a series of `cd` statements executed one after another.
572/// It resolves "." and "..".
573/// The result does not have a trailing path separator.
574/// If all paths are relative it uses the current working directory as a starting point.
575/// Note: all usage of this function should be audited due to the existence of symlinks.
576/// Without performing actual syscalls, resolving `..` could be incorrect.
577pub fn resolvePosix(allocator: *Allocator, paths: []const []const u8) ![]u8 {
578 if (paths.len == 0) {
579 assert(!is_windows); // resolvePosix called on windows can't use getCwd
580 return os.getCwdAlloc(allocator);
581 }
582
583 var first_index: usize = 0;
584 var have_abs = false;
585 var max_size: usize = 0;
586 for (paths) |p, i| {
587 if (isAbsolutePosix(p)) {
588 first_index = i;
589 have_abs = true;
590 max_size = 0;
591 }
592 max_size += p.len + 1;
593 }
594
595 var result: []u8 = undefined;
596 var result_index: usize = 0;
597
598 if (have_abs) {
599 result = try allocator.alloc(u8, max_size);
600 } else {
601 assert(!is_windows); // resolvePosix called on windows can't use getCwd
602 const cwd = try os.getCwdAlloc(allocator);
603 defer allocator.free(cwd);
604 result = try allocator.alloc(u8, max_size + cwd.len + 1);
605 mem.copy(u8, result, cwd);
606 result_index += cwd.len;
607 }
608 errdefer allocator.free(result);
609
610 for (paths[first_index..]) |p, i| {
611 var it = mem.tokenize(p, "/");
612 while (it.next()) |component| {
613 if (mem.eql(u8, component, ".")) {
614 continue;
615 } else if (mem.eql(u8, component, "..")) {
616 while (true) {
617 if (result_index == 0)
618 break;
619 result_index -= 1;
620 if (result[result_index] == '/')
621 break;
622 }
623 } else {
624 result[result_index] = '/';
625 result_index += 1;
626 mem.copy(u8, result[result_index..], component);
627 result_index += component.len;
628 }
629 }
630 }
631
632 if (result_index == 0) {
633 result[0] = '/';
634 result_index += 1;
635 }
636
637 return allocator.shrink(result, result_index);
638}
639
640test "os.path.resolve" {
641 const cwd = try os.getCwdAlloc(debug.global_allocator);
642 if (is_windows) {
643 if (windowsParsePath(cwd).kind == WindowsPath.Kind.Drive) {
644 cwd[0] = asciiUpper(cwd[0]);
645 }
646 testing.expect(mem.eql(u8, testResolveWindows([][]const u8{"."}), cwd));
647 } else {
648 testing.expect(mem.eql(u8, testResolvePosix([][]const u8{ "a/b/c/", "../../.." }), cwd));
649 testing.expect(mem.eql(u8, testResolvePosix([][]const u8{"."}), cwd));
650 }
651}
652
653test "os.path.resolveWindows" {
654 if (is_windows) {
655 const cwd = try os.getCwdAlloc(debug.global_allocator);
656 const parsed_cwd = windowsParsePath(cwd);
657 {
658 const result = testResolveWindows([][]const u8{ "/usr/local", "lib\\zig\\std\\array_list.zig" });
659 const expected = try join(debug.global_allocator, [][]const u8{
660 parsed_cwd.disk_designator,
661 "usr\\local\\lib\\zig\\std\\array_list.zig",
662 });
663 if (parsed_cwd.kind == WindowsPath.Kind.Drive) {
664 expected[0] = asciiUpper(parsed_cwd.disk_designator[0]);
665 }
666 testing.expect(mem.eql(u8, result, expected));
667 }
668 {
669 const result = testResolveWindows([][]const u8{ "usr/local", "lib\\zig" });
670 const expected = try join(debug.global_allocator, [][]const u8{
671 cwd,
672 "usr\\local\\lib\\zig",
673 });
674 if (parsed_cwd.kind == WindowsPath.Kind.Drive) {
675 expected[0] = asciiUpper(parsed_cwd.disk_designator[0]);
676 }
677 testing.expect(mem.eql(u8, result, expected));
678 }
679 }
680
681 testing.expect(mem.eql(u8, testResolveWindows([][]const u8{ "c:\\a\\b\\c", "/hi", "ok" }), "C:\\hi\\ok"));
682 testing.expect(mem.eql(u8, testResolveWindows([][]const u8{ "c:/blah\\blah", "d:/games", "c:../a" }), "C:\\blah\\a"));
683 testing.expect(mem.eql(u8, testResolveWindows([][]const u8{ "c:/blah\\blah", "d:/games", "C:../a" }), "C:\\blah\\a"));
684 testing.expect(mem.eql(u8, testResolveWindows([][]const u8{ "c:/ignore", "d:\\a/b\\c/d", "\\e.exe" }), "D:\\e.exe"));
685 testing.expect(mem.eql(u8, testResolveWindows([][]const u8{ "c:/ignore", "c:/some/file" }), "C:\\some\\file"));
686 testing.expect(mem.eql(u8, testResolveWindows([][]const u8{ "d:/ignore", "d:some/dir//" }), "D:\\ignore\\some\\dir"));
687 testing.expect(mem.eql(u8, testResolveWindows([][]const u8{ "//server/share", "..", "relative\\" }), "\\\\server\\share\\relative"));
688 testing.expect(mem.eql(u8, testResolveWindows([][]const u8{ "c:/", "//" }), "C:\\"));
689 testing.expect(mem.eql(u8, testResolveWindows([][]const u8{ "c:/", "//dir" }), "C:\\dir"));
690 testing.expect(mem.eql(u8, testResolveWindows([][]const u8{ "c:/", "//server/share" }), "\\\\server\\share\\"));
691 testing.expect(mem.eql(u8, testResolveWindows([][]const u8{ "c:/", "//server//share" }), "\\\\server\\share\\"));
692 testing.expect(mem.eql(u8, testResolveWindows([][]const u8{ "c:/", "///some//dir" }), "C:\\some\\dir"));
693 testing.expect(mem.eql(u8, testResolveWindows([][]const u8{ "C:\\foo\\tmp.3\\", "..\\tmp.3\\cycles\\root.js" }), "C:\\foo\\tmp.3\\cycles\\root.js"));
694}
695
696test "os.path.resolvePosix" {
697 testing.expect(mem.eql(u8, testResolvePosix([][]const u8{ "/a/b", "c" }), "/a/b/c"));
698 testing.expect(mem.eql(u8, testResolvePosix([][]const u8{ "/a/b", "c", "//d", "e///" }), "/d/e"));
699 testing.expect(mem.eql(u8, testResolvePosix([][]const u8{ "/a/b/c", "..", "../" }), "/a"));
700 testing.expect(mem.eql(u8, testResolvePosix([][]const u8{ "/", "..", ".." }), "/"));
701 testing.expect(mem.eql(u8, testResolvePosix([][]const u8{"/a/b/c/"}), "/a/b/c"));
702
703 testing.expect(mem.eql(u8, testResolvePosix([][]const u8{ "/var/lib", "../", "file/" }), "/var/file"));
704 testing.expect(mem.eql(u8, testResolvePosix([][]const u8{ "/var/lib", "/../", "file/" }), "/file"));
705 testing.expect(mem.eql(u8, testResolvePosix([][]const u8{ "/some/dir", ".", "/absolute/" }), "/absolute"));
706 testing.expect(mem.eql(u8, testResolvePosix([][]const u8{ "/foo/tmp.3/", "../tmp.3/cycles/root.js" }), "/foo/tmp.3/cycles/root.js"));
707}
708
709fn testResolveWindows(paths: []const []const u8) []u8 {
710 return resolveWindows(debug.global_allocator, paths) catch unreachable;
711}
712
713fn testResolvePosix(paths: []const []const u8) []u8 {
714 return resolvePosix(debug.global_allocator, paths) catch unreachable;
715}
716
717/// If the path is a file in the current directory (no directory component)
718/// then returns null
719pub fn dirname(path: []const u8) ?[]const u8 {
720 if (is_windows) {
721 return dirnameWindows(path);
722 } else {
723 return dirnamePosix(path);
724 }
725}
726
727pub fn dirnameWindows(path: []const u8) ?[]const u8 {
728 if (path.len == 0)
729 return null;
730
731 const root_slice = diskDesignatorWindows(path);
732 if (path.len == root_slice.len)
733 return path;
734
735 const have_root_slash = path.len > root_slice.len and (path[root_slice.len] == '/' or path[root_slice.len] == '\\');
736
737 var end_index: usize = path.len - 1;
738
739 while ((path[end_index] == '/' or path[end_index] == '\\') and end_index > root_slice.len) {
740 if (end_index == 0)
741 return null;
742 end_index -= 1;
743 }
744
745 while (path[end_index] != '/' and path[end_index] != '\\' and end_index > root_slice.len) {
746 if (end_index == 0)
747 return null;
748 end_index -= 1;
749 }
750
751 if (have_root_slash and end_index == root_slice.len) {
752 end_index += 1;
753 }
754
755 if (end_index == 0)
756 return null;
757
758 return path[0..end_index];
759}
760
761pub fn dirnamePosix(path: []const u8) ?[]const u8 {
762 if (path.len == 0)
763 return null;
764
765 var end_index: usize = path.len - 1;
766 while (path[end_index] == '/') {
767 if (end_index == 0)
768 return path[0..1];
769 end_index -= 1;
770 }
771
772 while (path[end_index] != '/') {
773 if (end_index == 0)
774 return null;
775 end_index -= 1;
776 }
777
778 if (end_index == 0 and path[end_index] == '/')
779 return path[0..1];
780
781 if (end_index == 0)
782 return null;
783
784 return path[0..end_index];
785}
786
787test "os.path.dirnamePosix" {
788 testDirnamePosix("/a/b/c", "/a/b");
789 testDirnamePosix("/a/b/c///", "/a/b");
790 testDirnamePosix("/a", "/");
791 testDirnamePosix("/", "/");
792 testDirnamePosix("////", "/");
793 testDirnamePosix("", null);
794 testDirnamePosix("a", null);
795 testDirnamePosix("a/", null);
796 testDirnamePosix("a//", null);
797}
798
799test "os.path.dirnameWindows" {
800 testDirnameWindows("c:\\", "c:\\");
801 testDirnameWindows("c:\\foo", "c:\\");
802 testDirnameWindows("c:\\foo\\", "c:\\");
803 testDirnameWindows("c:\\foo\\bar", "c:\\foo");
804 testDirnameWindows("c:\\foo\\bar\\", "c:\\foo");
805 testDirnameWindows("c:\\foo\\bar\\baz", "c:\\foo\\bar");
806 testDirnameWindows("\\", "\\");
807 testDirnameWindows("\\foo", "\\");
808 testDirnameWindows("\\foo\\", "\\");
809 testDirnameWindows("\\foo\\bar", "\\foo");
810 testDirnameWindows("\\foo\\bar\\", "\\foo");
811 testDirnameWindows("\\foo\\bar\\baz", "\\foo\\bar");
812 testDirnameWindows("c:", "c:");
813 testDirnameWindows("c:foo", "c:");
814 testDirnameWindows("c:foo\\", "c:");
815 testDirnameWindows("c:foo\\bar", "c:foo");
816 testDirnameWindows("c:foo\\bar\\", "c:foo");
817 testDirnameWindows("c:foo\\bar\\baz", "c:foo\\bar");
818 testDirnameWindows("file:stream", null);
819 testDirnameWindows("dir\\file:stream", "dir");
820 testDirnameWindows("\\\\unc\\share", "\\\\unc\\share");
821 testDirnameWindows("\\\\unc\\share\\foo", "\\\\unc\\share\\");
822 testDirnameWindows("\\\\unc\\share\\foo\\", "\\\\unc\\share\\");
823 testDirnameWindows("\\\\unc\\share\\foo\\bar", "\\\\unc\\share\\foo");
824 testDirnameWindows("\\\\unc\\share\\foo\\bar\\", "\\\\unc\\share\\foo");
825 testDirnameWindows("\\\\unc\\share\\foo\\bar\\baz", "\\\\unc\\share\\foo\\bar");
826 testDirnameWindows("/a/b/", "/a");
827 testDirnameWindows("/a/b", "/a");
828 testDirnameWindows("/a", "/");
829 testDirnameWindows("", null);
830 testDirnameWindows("/", "/");
831 testDirnameWindows("////", "/");
832 testDirnameWindows("foo", null);
833}
834
835fn testDirnamePosix(input: []const u8, expected_output: ?[]const u8) void {
836 if (dirnamePosix(input)) |output| {
837 testing.expect(mem.eql(u8, output, expected_output.?));
838 } else {
839 testing.expect(expected_output == null);
840 }
841}
842
843fn testDirnameWindows(input: []const u8, expected_output: ?[]const u8) void {
844 if (dirnameWindows(input)) |output| {
845 testing.expect(mem.eql(u8, output, expected_output.?));
846 } else {
847 testing.expect(expected_output == null);
848 }
849}
850
851pub fn basename(path: []const u8) []const u8 {
852 if (is_windows) {
853 return basenameWindows(path);
854 } else {
855 return basenamePosix(path);
856 }
857}
858
859pub fn basenamePosix(path: []const u8) []const u8 {
860 if (path.len == 0)
861 return []u8{};
862
863 var end_index: usize = path.len - 1;
864 while (path[end_index] == '/') {
865 if (end_index == 0)
866 return []u8{};
867 end_index -= 1;
868 }
869 var start_index: usize = end_index;
870 end_index += 1;
871 while (path[start_index] != '/') {
872 if (start_index == 0)
873 return path[0..end_index];
874 start_index -= 1;
875 }
876
877 return path[start_index + 1 .. end_index];
878}
879
880pub fn basenameWindows(path: []const u8) []const u8 {
881 if (path.len == 0)
882 return []u8{};
883
884 var end_index: usize = path.len - 1;
885 while (true) {
886 const byte = path[end_index];
887 if (byte == '/' or byte == '\\') {
888 if (end_index == 0)
889 return []u8{};
890 end_index -= 1;
891 continue;
892 }
893 if (byte == ':' and end_index == 1) {
894 return []u8{};
895 }
896 break;
897 }
898
899 var start_index: usize = end_index;
900 end_index += 1;
901 while (path[start_index] != '/' and path[start_index] != '\\' and
902 !(path[start_index] == ':' and start_index == 1))
903 {
904 if (start_index == 0)
905 return path[0..end_index];
906 start_index -= 1;
907 }
908
909 return path[start_index + 1 .. end_index];
910}
911
912test "os.path.basename" {
913 testBasename("", "");
914 testBasename("/", "");
915 testBasename("/dir/basename.ext", "basename.ext");
916 testBasename("/basename.ext", "basename.ext");
917 testBasename("basename.ext", "basename.ext");
918 testBasename("basename.ext/", "basename.ext");
919 testBasename("basename.ext//", "basename.ext");
920 testBasename("/aaa/bbb", "bbb");
921 testBasename("/aaa/", "aaa");
922 testBasename("/aaa/b", "b");
923 testBasename("/a/b", "b");
924 testBasename("//a", "a");
925
926 testBasenamePosix("\\dir\\basename.ext", "\\dir\\basename.ext");
927 testBasenamePosix("\\basename.ext", "\\basename.ext");
928 testBasenamePosix("basename.ext", "basename.ext");
929 testBasenamePosix("basename.ext\\", "basename.ext\\");
930 testBasenamePosix("basename.ext\\\\", "basename.ext\\\\");
931 testBasenamePosix("foo", "foo");
932
933 testBasenameWindows("\\dir\\basename.ext", "basename.ext");
934 testBasenameWindows("\\basename.ext", "basename.ext");
935 testBasenameWindows("basename.ext", "basename.ext");
936 testBasenameWindows("basename.ext\\", "basename.ext");
937 testBasenameWindows("basename.ext\\\\", "basename.ext");
938 testBasenameWindows("foo", "foo");
939 testBasenameWindows("C:", "");
940 testBasenameWindows("C:.", ".");
941 testBasenameWindows("C:\\", "");
942 testBasenameWindows("C:\\dir\\base.ext", "base.ext");
943 testBasenameWindows("C:\\basename.ext", "basename.ext");
944 testBasenameWindows("C:basename.ext", "basename.ext");
945 testBasenameWindows("C:basename.ext\\", "basename.ext");
946 testBasenameWindows("C:basename.ext\\\\", "basename.ext");
947 testBasenameWindows("C:foo", "foo");
948 testBasenameWindows("file:stream", "file:stream");
949}
950
951fn testBasename(input: []const u8, expected_output: []const u8) void {
952 testing.expectEqualSlices(u8, expected_output, basename(input));
953}
954
955fn testBasenamePosix(input: []const u8, expected_output: []const u8) void {
956 testing.expectEqualSlices(u8, expected_output, basenamePosix(input));
957}
958
959fn testBasenameWindows(input: []const u8, expected_output: []const u8) void {
960 testing.expectEqualSlices(u8, expected_output, basenameWindows(input));
961}
962
963/// Returns the relative path from `from` to `to`. If `from` and `to` each
964/// resolve to the same path (after calling `resolve` on each), a zero-length
965/// string is returned.
966/// On Windows this canonicalizes the drive to a capital letter and paths to `\\`.
967pub fn relative(allocator: *Allocator, from: []const u8, to: []const u8) ![]u8 {
968 if (is_windows) {
969 return relativeWindows(allocator, from, to);
970 } else {
971 return relativePosix(allocator, from, to);
972 }
973}
974
975pub fn relativeWindows(allocator: *Allocator, from: []const u8, to: []const u8) ![]u8 {
976 const resolved_from = try resolveWindows(allocator, [][]const u8{from});
977 defer allocator.free(resolved_from);
978
979 var clean_up_resolved_to = true;
980 const resolved_to = try resolveWindows(allocator, [][]const u8{to});
981 defer if (clean_up_resolved_to) allocator.free(resolved_to);
982
983 const parsed_from = windowsParsePath(resolved_from);
984 const parsed_to = windowsParsePath(resolved_to);
985 const result_is_to = x: {
986 if (parsed_from.kind != parsed_to.kind) {
987 break :x true;
988 } else switch (parsed_from.kind) {
989 WindowsPath.Kind.NetworkShare => {
990 break :x !networkShareServersEql(parsed_to.disk_designator, parsed_from.disk_designator);
991 },
992 WindowsPath.Kind.Drive => {
993 break :x asciiUpper(parsed_from.disk_designator[0]) != asciiUpper(parsed_to.disk_designator[0]);
994 },
995 else => unreachable,
996 }
997 };
998
999 if (result_is_to) {
1000 clean_up_resolved_to = false;
1001 return resolved_to;
1002 }
1003
1004 var from_it = mem.tokenize(resolved_from, "/\\");
1005 var to_it = mem.tokenize(resolved_to, "/\\");
1006 while (true) {
1007 const from_component = from_it.next() orelse return mem.dupe(allocator, u8, to_it.rest());
1008 const to_rest = to_it.rest();
1009 if (to_it.next()) |to_component| {
1010 // TODO ASCII is wrong, we actually need full unicode support to compare paths.
1011 if (asciiEqlIgnoreCase(from_component, to_component))
1012 continue;
1013 }
1014 var up_count: usize = 1;
1015 while (from_it.next()) |_| {
1016 up_count += 1;
1017 }
1018 const up_index_end = up_count * "..\\".len;
1019 const result = try allocator.alloc(u8, up_index_end + to_rest.len);
1020 errdefer allocator.free(result);
1021
1022 var result_index: usize = 0;
1023 while (result_index < up_index_end) {
1024 result[result_index] = '.';
1025 result_index += 1;
1026 result[result_index] = '.';
1027 result_index += 1;
1028 result[result_index] = '\\';
1029 result_index += 1;
1030 }
1031 // shave off the trailing slash
1032 result_index -= 1;
1033
1034 var rest_it = mem.tokenize(to_rest, "/\\");
1035 while (rest_it.next()) |to_component| {
1036 result[result_index] = '\\';
1037 result_index += 1;
1038 mem.copy(u8, result[result_index..], to_component);
1039 result_index += to_component.len;
1040 }
1041
1042 return result[0..result_index];
1043 }
1044
1045 return []u8{};
1046}
1047
1048pub fn relativePosix(allocator: *Allocator, from: []const u8, to: []const u8) ![]u8 {
1049 const resolved_from = try resolvePosix(allocator, [][]const u8{from});
1050 defer allocator.free(resolved_from);
1051
1052 const resolved_to = try resolvePosix(allocator, [][]const u8{to});
1053 defer allocator.free(resolved_to);
1054
1055 var from_it = mem.tokenize(resolved_from, "/");
1056 var to_it = mem.tokenize(resolved_to, "/");
1057 while (true) {
1058 const from_component = from_it.next() orelse return mem.dupe(allocator, u8, to_it.rest());
1059 const to_rest = to_it.rest();
1060 if (to_it.next()) |to_component| {
1061 if (mem.eql(u8, from_component, to_component))
1062 continue;
1063 }
1064 var up_count: usize = 1;
1065 while (from_it.next()) |_| {
1066 up_count += 1;
1067 }
1068 const up_index_end = up_count * "../".len;
1069 const result = try allocator.alloc(u8, up_index_end + to_rest.len);
1070 errdefer allocator.free(result);
1071
1072 var result_index: usize = 0;
1073 while (result_index < up_index_end) {
1074 result[result_index] = '.';
1075 result_index += 1;
1076 result[result_index] = '.';
1077 result_index += 1;
1078 result[result_index] = '/';
1079 result_index += 1;
1080 }
1081 if (to_rest.len == 0) {
1082 // shave off the trailing slash
1083 return result[0 .. result_index - 1];
1084 }
1085
1086 mem.copy(u8, result[result_index..], to_rest);
1087 return result;
1088 }
1089
1090 return []u8{};
1091}
1092
1093test "os.path.relative" {
1094 testRelativeWindows("c:/blah\\blah", "d:/games", "D:\\games");
1095 testRelativeWindows("c:/aaaa/bbbb", "c:/aaaa", "..");
1096 testRelativeWindows("c:/aaaa/bbbb", "c:/cccc", "..\\..\\cccc");
1097 testRelativeWindows("c:/aaaa/bbbb", "c:/aaaa/bbbb", "");
1098 testRelativeWindows("c:/aaaa/bbbb", "c:/aaaa/cccc", "..\\cccc");
1099 testRelativeWindows("c:/aaaa/", "c:/aaaa/cccc", "cccc");
1100 testRelativeWindows("c:/", "c:\\aaaa\\bbbb", "aaaa\\bbbb");
1101 testRelativeWindows("c:/aaaa/bbbb", "d:\\", "D:\\");
1102 testRelativeWindows("c:/AaAa/bbbb", "c:/aaaa/bbbb", "");
1103 testRelativeWindows("c:/aaaaa/", "c:/aaaa/cccc", "..\\aaaa\\cccc");
1104 testRelativeWindows("C:\\foo\\bar\\baz\\quux", "C:\\", "..\\..\\..\\..");
1105 testRelativeWindows("C:\\foo\\test", "C:\\foo\\test\\bar\\package.json", "bar\\package.json");
1106 testRelativeWindows("C:\\foo\\bar\\baz-quux", "C:\\foo\\bar\\baz", "..\\baz");
1107 testRelativeWindows("C:\\foo\\bar\\baz", "C:\\foo\\bar\\baz-quux", "..\\baz-quux");
1108 testRelativeWindows("\\\\foo\\bar", "\\\\foo\\bar\\baz", "baz");
1109 testRelativeWindows("\\\\foo\\bar\\baz", "\\\\foo\\bar", "..");
1110 testRelativeWindows("\\\\foo\\bar\\baz-quux", "\\\\foo\\bar\\baz", "..\\baz");
1111 testRelativeWindows("\\\\foo\\bar\\baz", "\\\\foo\\bar\\baz-quux", "..\\baz-quux");
1112 testRelativeWindows("C:\\baz-quux", "C:\\baz", "..\\baz");
1113 testRelativeWindows("C:\\baz", "C:\\baz-quux", "..\\baz-quux");
1114 testRelativeWindows("\\\\foo\\baz-quux", "\\\\foo\\baz", "..\\baz");
1115 testRelativeWindows("\\\\foo\\baz", "\\\\foo\\baz-quux", "..\\baz-quux");
1116 testRelativeWindows("C:\\baz", "\\\\foo\\bar\\baz", "\\\\foo\\bar\\baz");
1117 testRelativeWindows("\\\\foo\\bar\\baz", "C:\\baz", "C:\\baz");
1118
1119 testRelativePosix("/var/lib", "/var", "..");
1120 testRelativePosix("/var/lib", "/bin", "../../bin");
1121 testRelativePosix("/var/lib", "/var/lib", "");
1122 testRelativePosix("/var/lib", "/var/apache", "../apache");
1123 testRelativePosix("/var/", "/var/lib", "lib");
1124 testRelativePosix("/", "/var/lib", "var/lib");
1125 testRelativePosix("/foo/test", "/foo/test/bar/package.json", "bar/package.json");
1126 testRelativePosix("/Users/a/web/b/test/mails", "/Users/a/web/b", "../..");
1127 testRelativePosix("/foo/bar/baz-quux", "/foo/bar/baz", "../baz");
1128 testRelativePosix("/foo/bar/baz", "/foo/bar/baz-quux", "../baz-quux");
1129 testRelativePosix("/baz-quux", "/baz", "../baz");
1130 testRelativePosix("/baz", "/baz-quux", "../baz-quux");
1131}
1132
1133fn testRelativePosix(from: []const u8, to: []const u8, expected_output: []const u8) void {
1134 const result = relativePosix(debug.global_allocator, from, to) catch unreachable;
1135 testing.expectEqualSlices(u8, expected_output, result);
1136}
1137
1138fn testRelativeWindows(from: []const u8, to: []const u8, expected_output: []const u8) void {
1139 const result = relativeWindows(debug.global_allocator, from, to) catch unreachable;
1140 testing.expectEqualSlices(u8, expected_output, result);
1141}
1142
1143pub const RealError = error{
1144 FileNotFound,
1145 AccessDenied,
1146 NameTooLong,
1147 NotSupported,
1148 NotDir,
1149 SymLinkLoop,
1150 InputOutput,
1151 FileTooBig,
1152 IsDir,
1153 ProcessFdQuotaExceeded,
1154 SystemFdQuotaExceeded,
1155 NoDevice,
1156 SystemResources,
1157 NoSpaceLeft,
1158 FileSystem,
1159 BadPathName,
1160 DeviceBusy,
1161
1162 /// On Windows, file paths must be valid Unicode.
1163 InvalidUtf8,
1164
1165 PathAlreadyExists,
1166
1167 Unexpected,
1168};
1169
1170/// Call from Windows-specific code if you already have a UTF-16LE encoded, null terminated string.
1171/// Otherwise use `real` or `realC`.
1172pub fn realW(out_buffer: *[os.MAX_PATH_BYTES]u8, pathname: [*]const u16) RealError![]u8 {
1173 const h_file = windows.CreateFileW(
1174 pathname,
1175 windows.GENERIC_READ,
1176 windows.FILE_SHARE_READ,
1177 null,
1178 windows.OPEN_EXISTING,
1179 windows.FILE_ATTRIBUTE_NORMAL,
1180 null,
1181 );
1182 if (h_file == windows.INVALID_HANDLE_VALUE) {
1183 const err = windows.GetLastError();
1184 switch (err) {
1185 windows.ERROR.FILE_NOT_FOUND => return error.FileNotFound,
1186 windows.ERROR.ACCESS_DENIED => return error.AccessDenied,
1187 windows.ERROR.FILENAME_EXCED_RANGE => return error.NameTooLong,
1188 else => return os.unexpectedErrorWindows(err),
1189 }
1190 }
1191 defer os.close(h_file);
1192 var utf16le_buf: [windows_util.PATH_MAX_WIDE]u16 = undefined;
1193 const casted_len = @intCast(windows.DWORD, utf16le_buf.len); // TODO shouldn't need this cast
1194 const result = windows.GetFinalPathNameByHandleW(h_file, &utf16le_buf, casted_len, windows.VOLUME_NAME_DOS);
1195 assert(result <= utf16le_buf.len);
1196 if (result == 0) {
1197 const err = windows.GetLastError();
1198 switch (err) {
1199 windows.ERROR.FILE_NOT_FOUND => return error.FileNotFound,
1200 windows.ERROR.PATH_NOT_FOUND => return error.FileNotFound,
1201 windows.ERROR.NOT_ENOUGH_MEMORY => return error.SystemResources,
1202 windows.ERROR.FILENAME_EXCED_RANGE => return error.NameTooLong,
1203 windows.ERROR.INVALID_PARAMETER => unreachable,
1204 else => return os.unexpectedErrorWindows(err),
1205 }
1206 }
1207 const utf16le_slice = utf16le_buf[0..result];
1208
1209 // windows returns \\?\ prepended to the path
1210 // we strip it because nobody wants \\?\ prepended to their path
1211 const prefix = []u16{ '\\', '\\', '?', '\\' };
1212 const start_index = if (mem.startsWith(u16, utf16le_slice, prefix)) prefix.len else 0;
1213
1214 // Trust that Windows gives us valid UTF-16LE.
1215 const end_index = std.unicode.utf16leToUtf8(out_buffer, utf16le_slice[start_index..]) catch unreachable;
1216 return out_buffer[0..end_index];
1217}
1218
1219/// See `real`
1220/// Use this when you have a null terminated pointer path.
1221pub fn realC(out_buffer: *[os.MAX_PATH_BYTES]u8, pathname: [*]const u8) RealError![]u8 {
1222 switch (builtin.os) {
1223 Os.windows => {
1224 const pathname_w = try windows_util.cStrToPrefixedFileW(pathname);
1225 return realW(out_buffer, pathname_w);
1226 },
1227 Os.freebsd, Os.netbsd, Os.macosx, Os.ios => {
1228 // TODO instead of calling the libc function here, port the implementation to Zig
1229 const err = posix.getErrno(posix.realpath(pathname, out_buffer));
1230 switch (err) {
1231 0 => return mem.toSlice(u8, out_buffer),
1232 posix.EINVAL => unreachable,
1233 posix.EBADF => unreachable,
1234 posix.EFAULT => unreachable,
1235 posix.EACCES => return error.AccessDenied,
1236 posix.ENOENT => return error.FileNotFound,
1237 posix.ENOTSUP => return error.NotSupported,
1238 posix.ENOTDIR => return error.NotDir,
1239 posix.ENAMETOOLONG => return error.NameTooLong,
1240 posix.ELOOP => return error.SymLinkLoop,
1241 posix.EIO => return error.InputOutput,
1242 else => return os.unexpectedErrorPosix(err),
1243 }
1244 },
1245 Os.linux => {
1246 const fd = try os.posixOpenC(pathname, posix.O_PATH | posix.O_NONBLOCK | posix.O_CLOEXEC, 0);
1247 defer os.close(fd);
1248
1249 var buf: ["/proc/self/fd/-2147483648\x00".len]u8 = undefined;
1250 const proc_path = fmt.bufPrint(buf[0..], "/proc/self/fd/{}\x00", fd) catch unreachable;
1251
1252 return os.readLinkC(out_buffer, proc_path.ptr);
1253 },
1254 else => @compileError("TODO implement os.path.real for " ++ @tagName(builtin.os)),
1255 }
1256}
1257
1258/// Return the canonicalized absolute pathname.
1259/// Expands all symbolic links and resolves references to `.`, `..`, and
1260/// extra `/` characters in ::pathname.
1261/// The return value is a slice of out_buffer, and not necessarily from the beginning.
1262pub fn real(out_buffer: *[os.MAX_PATH_BYTES]u8, pathname: []const u8) RealError![]u8 {
1263 switch (builtin.os) {
1264 Os.windows => {
1265 const pathname_w = try windows_util.sliceToPrefixedFileW(pathname);
1266 return realW(out_buffer, &pathname_w);
1267 },
1268 Os.macosx, Os.ios, Os.linux, Os.freebsd, Os.netbsd => {
1269 const pathname_c = try os.toPosixPath(pathname);
1270 return realC(out_buffer, &pathname_c);
1271 },
1272 else => @compileError("Unsupported OS"),
1273 }
1274}
1275
1276/// `real`, except caller must free the returned memory.
1277pub fn realAlloc(allocator: *Allocator, pathname: []const u8) ![]u8 {
1278 var buf: [os.MAX_PATH_BYTES]u8 = undefined;
1279 return mem.dupe(allocator, u8, try real(&buf, pathname));
1280}
1281
1282test "os.path.real" {
1283 // at least call it so it gets compiled
1284 var buf: [os.MAX_PATH_BYTES]u8 = undefined;
1285 testing.expectError(error.FileNotFound, real(&buf, "definitely_bogus_does_not_exist1234"));
1286}
std/os/posix.zig deleted-2307
......@@ -1,2307 +0,0 @@
1// This is the "Zig-flavored POSIX" API layer.
2// The purpose is not to match POSIX as closely as possible. Instead,
3// the goal is to provide a very specific layer of abstraction:
4// * Implement the POSIX functions, types, and definitions where possible,
5// using lower-level target-specific API.
6// * When null-terminated byte buffers are required, provide APIs which accept
7// slices as well as APIs which accept null-terminated byte buffers. Same goes
8// for UTF-16LE encoding.
9// * Convert "errno"-style error codes into Zig errors.
10// * Implement the OS-specific functions, types, and definitions that the Zig
11// standard library needs, at the same API abstraction layer as outlined above.
12// For example kevent() and getrandom(). Windows-specific functions are separate,
13// in `std.os.windows`.
14// * When there exists a corresponding libc function and linking libc, the libc
15// implementation is used. Exceptions are made for known buggy areas of libc.
16// On Linux libc can be side-stepped by using `std.os.linux.sys`.
17// Note: The Zig standard library does not support POSIX thread cancellation, and
18// in general EINTR is handled by trying again.
19
20const std = @import("../std.zig");
21const builtin = @import("builtin");
22const assert = std.debug.assert;
23const os = @import("../os.zig");
24const system = os.system;
25const mem = std.mem;
26const BufMap = std.BufMap;
27const Allocator = mem.Allocator;
28const windows = os.windows;
29const kernel32 = windows.kernel32;
30const wasi = os.wasi;
31const linux = os.linux;
32const testing = std.testing;
33
34pub use system.posix;
35
36/// See also `getenv`.
37pub var environ: [][*]u8 = undefined;
38
39/// To obtain errno, call this function with the return value of the
40/// system function call. For some systems this will obtain the value directly
41/// from the return code; for others it will use a thread-local errno variable.
42/// Therefore, this function only returns a well-defined value when it is called
43/// directly after the system function call which one wants to learn the errno
44/// value of.
45pub const errno = system.getErrno;
46
47/// Closes the file descriptor.
48/// This function is not capable of returning any indication of failure. An
49/// application which wants to ensure writes have succeeded before closing
50/// must call `fsync` before `close`.
51/// Note: The Zig standard library does not support POSIX thread cancellation.
52pub fn close(fd: fd_t) void {
53 if (windows.is_the_target and !builtin.link_libc) {
54 assert(kernel32.CloseHandle(fd) != 0);
55 return;
56 }
57 if (wasi.is_the_target) {
58 switch (wasi.fd_close(fd)) {
59 0 => return,
60 else => |err| return unexpectedErrno(err),
61 }
62 }
63 switch (errno(system.close(fd))) {
64 EBADF => unreachable, // Always a race condition.
65 EINTR => return, // This is still a success. See https://github.com/ziglang/zig/issues/2425
66 else => return,
67 }
68}
69
70pub const GetRandomError = error{};
71
72/// Obtain a series of random bytes. These bytes can be used to seed user-space
73/// random number generators or for cryptographic purposes.
74/// When linking against libc, this calls the
75/// appropriate OS-specific library call. Otherwise it uses the zig standard
76/// library implementation.
77pub fn getrandom(buf: []u8) GetRandomError!void {
78 if (windows.is_the_target) {
79 // Call RtlGenRandom() instead of CryptGetRandom() on Windows
80 // https://github.com/rust-lang-nursery/rand/issues/111
81 // https://bugzilla.mozilla.org/show_bug.cgi?id=504270
82 if (windows.advapi32.RtlGenRandom(buf.ptr, buf.len) == 0) {
83 switch (kernel32.GetLastError()) {
84 else => |err| return windows.unexpectedError(err),
85 }
86 }
87 return;
88 }
89 if (linux.is_the_target) {
90 while (true) {
91 switch (errno(system.getrandom(buf.ptr, buf.len, 0))) {
92 0 => return,
93 EINVAL => unreachable,
94 EFAULT => unreachable,
95 EINTR => continue,
96 ENOSYS => return getRandomBytesDevURandom(buf),
97 else => |err| return unexpectedErrno(err),
98 }
99 }
100 }
101 if (wasi.is_the_target) {
102 switch (os.wasi.random_get(buf.ptr, buf.len)) {
103 0 => return,
104 else => |err| return unexpectedErrno(err),
105 }
106 }
107 return getRandomBytesDevURandom(buf);
108}
109
110fn getRandomBytesDevURandom(buf: []u8) !void {
111 const fd = try openC(c"/dev/urandom", O_RDONLY | O_CLOEXEC, 0);
112 defer close(fd);
113
114 const stream = &os.File.openHandle(fd).inStream().stream;
115 stream.readNoEof(buf) catch return error.Unexpected;
116}
117
118test "os.getRandomBytes" {
119 var buf_a: [50]u8 = undefined;
120 var buf_b: [50]u8 = undefined;
121 try getRandomBytes(&buf_a);
122 try getRandomBytes(&buf_b);
123 // If this test fails the chance is significantly higher that there is a bug than
124 // that two sets of 50 bytes were equal.
125 testing.expect(!mem.eql(u8, buf_a, buf_b));
126}
127
128/// Causes abnormal process termination.
129/// If linking against libc, this calls the abort() libc function. Otherwise
130/// it raises SIGABRT followed by SIGKILL and finally lo
131pub fn abort() noreturn {
132 @setCold(true);
133 if (builtin.link_libc) {
134 system.abort();
135 }
136 if (windows.is_the_target) {
137 if (builtin.mode == .Debug) {
138 @breakpoint();
139 }
140 windows.ExitProcess(3);
141 }
142 if (builtin.os == .uefi) {
143 // TODO there must be a better thing to do here than loop forever
144 while (true) {}
145 }
146
147 raise(SIGABRT);
148
149 // TODO the rest of the implementation of abort() from musl libc here
150
151 raise(SIGKILL);
152 exit(127);
153}
154
155pub const RaiseError = error{};
156
157pub fn raise(sig: u8) RaiseError!void {
158 if (builtin.link_libc) {
159 switch (errno(system.raise(sig))) {
160 0 => return,
161 else => |err| return unexpectedErrno(err),
162 }
163 }
164
165 if (wasi.is_the_target) {
166 switch (wasi.proc_raise(SIGABRT)) {
167 0 => return,
168 else => |err| return unexpectedErrno(err),
169 }
170 }
171
172 if (windows.is_the_target) {
173 @compileError("TODO implement std.posix.raise for Windows");
174 }
175
176 var set: system.sigset_t = undefined;
177 system.blockAppSignals(&set);
178 const tid = system.syscall0(system.SYS_gettid);
179 const rc = system.syscall2(system.SYS_tkill, tid, sig);
180 system.restoreSignals(&set);
181 switch (errno(rc)) {
182 0 => return,
183 else => |err| return unexpectedErrno(err),
184 }
185}
186
187/// Exits the program cleanly with the specified status code.
188pub fn exit(status: u8) noreturn {
189 if (builtin.link_libc) {
190 system.exit(status);
191 }
192 if (windows.is_the_target) {
193 windows.ExitProcess(status);
194 }
195 if (wasi.is_the_target) {
196 wasi.proc_exit(status);
197 }
198 if (linux.is_the_target and !builtin.single_threaded) {
199 linux.exit_group(status);
200 }
201 system.exit(status);
202}
203
204pub const ReadError = error{
205 InputOutput,
206 SystemResources,
207 IsDir,
208 OperationAborted,
209 BrokenPipe,
210 Unexpected,
211};
212
213/// Returns the number of bytes that were read, which can be less than
214/// buf.len. If 0 bytes were read, that means EOF.
215/// This function is for blocking file descriptors only. For non-blocking, see
216/// `readAsync`.
217pub fn read(fd: fd_t, buf: []u8) ReadError!usize {
218 if (windows.is_the_target and !builtin.link_libc) {
219 var index: usize = 0;
220 while (index < buffer.len) {
221 const want_read_count = @intCast(windows.DWORD, math.min(windows.DWORD(math.maxInt(windows.DWORD)), buffer.len - index));
222 var amt_read: windows.DWORD = undefined;
223 if (windows.ReadFile(fd, buffer.ptr + index, want_read_count, &amt_read, null) == 0) {
224 switch (windows.GetLastError()) {
225 windows.ERROR.OPERATION_ABORTED => continue,
226 windows.ERROR.BROKEN_PIPE => return index,
227 else => |err| return windows.unexpectedError(err),
228 }
229 }
230 if (amt_read == 0) return index;
231 index += amt_read;
232 }
233 return index;
234 }
235
236 if (wasi.is_the_target and !builtin.link_libc) {
237 const iovs = [1]was.iovec_t{wasi.iovec_t{
238 .buf = buf.ptr,
239 .buf_len = buf.len,
240 }};
241
242 var nread: usize = undefined;
243 switch (fd_read(fd, &iovs, iovs.len, &nread)) {
244 0 => return nread,
245 else => |err| return unexpectedErrno(err),
246 }
247 }
248
249 // Linux can return EINVAL when read amount is > 0x7ffff000
250 // See https://github.com/ziglang/zig/pull/743#issuecomment-363158274
251 const max_buf_len = 0x7ffff000;
252
253 var index: usize = 0;
254 while (index < buf.len) {
255 const want_to_read = math.min(buf.len - index, usize(max_buf_len));
256 const rc = system.read(fd, buf.ptr + index, want_to_read);
257 switch (errno(rc)) {
258 0 => {
259 index += rc;
260 if (rc == want_to_read) continue;
261 // Read returned less than buf.len.
262 return index;
263 },
264 EINTR => continue,
265 EINVAL => unreachable,
266 EFAULT => unreachable,
267 EAGAIN => unreachable, // This function is for blocking reads.
268 EBADF => unreachable, // Always a race condition.
269 EIO => return error.InputOutput,
270 EISDIR => return error.IsDir,
271 ENOBUFS => return error.SystemResources,
272 ENOMEM => return error.SystemResources,
273 else => |err| return unexpectedErrno(err),
274 }
275 }
276 return index;
277}
278
279/// Number of bytes read is returned. Upon reading end-of-file, zero is returned.
280/// This function is for blocking file descriptors only. For non-blocking, see
281/// `preadvAsync`.
282pub fn preadv(fd: fd_t, iov: [*]const iovec, count: usize, offset: u64) ReadError!usize {
283 if (os.darwin.is_the_target) {
284 // Darwin does not have preadv but it does have pread.
285 var off: usize = 0;
286 var iov_i: usize = 0;
287 var inner_off: usize = 0;
288 while (true) {
289 const v = iov[iov_i];
290 const rc = darwin.pread(fd, v.iov_base + inner_off, v.iov_len - inner_off, offset + off);
291 const err = darwin.getErrno(rc);
292 switch (err) {
293 0 => {
294 off += rc;
295 inner_off += rc;
296 if (inner_off == v.iov_len) {
297 iov_i += 1;
298 inner_off = 0;
299 if (iov_i == count) {
300 return off;
301 }
302 }
303 if (rc == 0) return off; // EOF
304 continue;
305 },
306 EINTR => continue,
307 EINVAL => unreachable,
308 EFAULT => unreachable,
309 ESPIPE => unreachable, // fd is not seekable
310 EAGAIN => unreachable, // This function is for blocking reads.
311 EBADF => unreachable, // always a race condition
312 EIO => return error.InputOutput,
313 EISDIR => return error.IsDir,
314 ENOBUFS => return error.SystemResources,
315 ENOMEM => return error.SystemResources,
316 else => return unexpectedErrno(err),
317 }
318 }
319 }
320 while (true) {
321 const rc = system.preadv(fd, iov, count, offset);
322 switch (errno(rc)) {
323 0 => return rc,
324 EINTR => continue,
325 EINVAL => unreachable,
326 EFAULT => unreachable,
327 EAGAIN => unreachable, // This function is for blocking reads.
328 EBADF => unreachable, // always a race condition
329 EIO => return error.InputOutput,
330 EISDIR => return error.IsDir,
331 ENOBUFS => return error.SystemResources,
332 ENOMEM => return error.SystemResources,
333 else => |err| return unexpectedErrno(err),
334 }
335 }
336}
337
338pub const WriteError = error{
339 DiskQuota,
340 FileTooBig,
341 InputOutput,
342 NoSpaceLeft,
343 AccessDenied,
344 BrokenPipe,
345 SystemResources,
346 OperationAborted,
347 Unexpected,
348};
349
350/// Write to a file descriptor. Keeps trying if it gets interrupted.
351/// This function is for blocking file descriptors only. For non-blocking, see
352/// `writeAsync`.
353pub fn write(fd: fd_t, bytes: []const u8) WriteError!void {
354 if (windows.is_the_target and !builtin.link_libc) {
355 var bytes_written: windows.DWORD = undefined;
356 // TODO replace this @intCast with a loop that writes all the bytes
357 if (windows.WriteFile(handle, bytes.ptr, @intCast(u32, bytes.len), &bytes_written, null) == 0) {
358 switch (windows.GetLastError()) {
359 windows.ERROR.INVALID_USER_BUFFER => return error.SystemResources,
360 windows.ERROR.NOT_ENOUGH_MEMORY => return error.SystemResources,
361 windows.ERROR.OPERATION_ABORTED => return error.OperationAborted,
362 windows.ERROR.NOT_ENOUGH_QUOTA => return error.SystemResources,
363 windows.ERROR.IO_PENDING => unreachable,
364 windows.ERROR.BROKEN_PIPE => return error.BrokenPipe,
365 else => |err| return windows.unexpectedError(err),
366 }
367 }
368 }
369
370 if (wasi.is_the_target and !builtin.link_libc) {
371 const ciovs = [1]wasi.ciovec_t{wasi.ciovec_t{
372 .buf = bytes.ptr,
373 .buf_len = bytes.len,
374 }};
375 var nwritten: usize = undefined;
376 switch (fd_write(fd, &ciovs, ciovs.len, &nwritten)) {
377 0 => return,
378 else => |err| return unexpectedErrno(err),
379 }
380 }
381
382 // Linux can return EINVAL when write amount is > 0x7ffff000
383 // See https://github.com/ziglang/zig/pull/743#issuecomment-363165856
384 const max_bytes_len = 0x7ffff000;
385
386 var index: usize = 0;
387 while (index < bytes.len) {
388 const amt_to_write = math.min(bytes.len - index, usize(max_bytes_len));
389 const rc = system.write(fd, bytes.ptr + index, amt_to_write);
390 switch (errno(rc)) {
391 0 => {
392 index += rc;
393 continue;
394 },
395 EINTR => continue,
396 EINVAL => unreachable,
397 EFAULT => unreachable,
398 EAGAIN => unreachable, // This function is for blocking writes.
399 EBADF => unreachable, // Always a race condition.
400 EDESTADDRREQ => unreachable, // `connect` was never called.
401 EDQUOT => return error.DiskQuota,
402 EFBIG => return error.FileTooBig,
403 EIO => return error.InputOutput,
404 ENOSPC => return error.NoSpaceLeft,
405 EPERM => return error.AccessDenied,
406 EPIPE => return error.BrokenPipe,
407 else => |err| return unexpectedErrno(err),
408 }
409 }
410}
411
412/// Write multiple buffers to a file descriptor. Keeps trying if it gets interrupted.
413/// This function is for blocking file descriptors only. For non-blocking, see
414/// `pwritevAsync`.
415pub fn pwritev(fd: fd_t, iov: [*]const iovec_const, count: usize, offset: u64) WriteError!void {
416 if (darwin.is_the_target) {
417 // Darwin does not have pwritev but it does have pwrite.
418 var off: usize = 0;
419 var iov_i: usize = 0;
420 var inner_off: usize = 0;
421 while (true) {
422 const v = iov[iov_i];
423 const rc = darwin.pwrite(fd, v.iov_base + inner_off, v.iov_len - inner_off, offset + off);
424 const err = darwin.getErrno(rc);
425 switch (err) {
426 0 => {
427 off += rc;
428 inner_off += rc;
429 if (inner_off == v.iov_len) {
430 iov_i += 1;
431 inner_off = 0;
432 if (iov_i == count) {
433 return;
434 }
435 }
436 continue;
437 },
438 EINTR => continue,
439 ESPIPE => unreachable, // `fd` is not seekable.
440 EINVAL => unreachable,
441 EFAULT => unreachable,
442 EAGAIN => unreachable, // This function is for blocking writes.
443 EBADF => unreachable, // Always a race condition.
444 EDESTADDRREQ => unreachable, // `connect` was never called.
445 EDQUOT => return error.DiskQuota,
446 EFBIG => return error.FileTooBig,
447 EIO => return error.InputOutput,
448 ENOSPC => return error.NoSpaceLeft,
449 EPERM => return error.AccessDenied,
450 EPIPE => return error.BrokenPipe,
451 else => return unexpectedErrno(err),
452 }
453 }
454 }
455
456 while (true) {
457 const rc = system.pwritev(fd, iov, count, offset);
458 switch (errno(rc)) {
459 0 => return,
460 EINTR => continue,
461 EINVAL => unreachable,
462 EFAULT => unreachable,
463 EAGAIN => unreachable, // This function is for blocking writes.
464 EBADF => unreachable, // Always a race condition.
465 EDESTADDRREQ => unreachable, // `connect` was never called.
466 EDQUOT => return error.DiskQuota,
467 EFBIG => return error.FileTooBig,
468 EIO => return error.InputOutput,
469 ENOSPC => return error.NoSpaceLeft,
470 EPERM => return error.AccessDenied,
471 EPIPE => return error.BrokenPipe,
472 else => |err| return unexpectedErrno(err),
473 }
474 }
475}
476
477pub const OpenError = error{
478 AccessDenied,
479 FileTooBig,
480 IsDir,
481 SymLinkLoop,
482 ProcessFdQuotaExceeded,
483 NameTooLong,
484 SystemFdQuotaExceeded,
485 NoDevice,
486 FileNotFound,
487 SystemResources,
488 NoSpaceLeft,
489 NotDir,
490 PathAlreadyExists,
491 DeviceBusy,
492 Unexpected,
493};
494
495/// Open and possibly create a file. Keeps trying if it gets interrupted.
496/// `file_path` needs to be copied in memory to add a null terminating byte.
497/// See also `openC`.
498pub fn open(file_path: []const u8, flags: u32, perm: usize) OpenError!fd_t {
499 const file_path_c = try toPosixPath(file_path);
500 return openC(&file_path_c, flags, perm);
501}
502
503/// Open and possibly create a file. Keeps trying if it gets interrupted.
504/// See also `open`.
505/// TODO https://github.com/ziglang/zig/issues/265
506pub fn openC(file_path: [*]const u8, flags: u32, perm: usize) OpenError!fd_t {
507 while (true) {
508 const rc = system.open(file_path, flags, perm);
509 switch (errno(rc)) {
510 0 => return @intCast(fd_t, rc),
511 EINTR => continue,
512
513 EFAULT => unreachable,
514 EINVAL => unreachable,
515 EACCES => return error.AccessDenied,
516 EFBIG => return error.FileTooBig,
517 EOVERFLOW => return error.FileTooBig,
518 EISDIR => return error.IsDir,
519 ELOOP => return error.SymLinkLoop,
520 EMFILE => return error.ProcessFdQuotaExceeded,
521 ENAMETOOLONG => return error.NameTooLong,
522 ENFILE => return error.SystemFdQuotaExceeded,
523 ENODEV => return error.NoDevice,
524 ENOENT => return error.FileNotFound,
525 ENOMEM => return error.SystemResources,
526 ENOSPC => return error.NoSpaceLeft,
527 ENOTDIR => return error.NotDir,
528 EPERM => return error.AccessDenied,
529 EEXIST => return error.PathAlreadyExists,
530 EBUSY => return error.DeviceBusy,
531 else => |err| return unexpectedErrno(err),
532 }
533 }
534}
535
536pub fn dup2(old_fd: fd_t, new_fd: fd_t) !void {
537 while (true) {
538 switch (errno(system.dup2(old_fd, new_fd))) {
539 0 => return,
540 EBUSY, EINTR => continue,
541 EMFILE => return error.ProcessFdQuotaExceeded,
542 EINVAL => unreachable,
543 else => |err| return unexpectedErrno(err),
544 }
545 }
546}
547
548/// This function must allocate memory to add a null terminating bytes on path and each arg.
549/// It must also convert to KEY=VALUE\0 format for environment variables, and include null
550/// pointers after the args and after the environment variables.
551/// `argv[0]` is the executable path.
552/// This function also uses the PATH environment variable to get the full path to the executable.
553/// TODO provide execveC which does not take an allocator
554pub fn execve(allocator: *Allocator, argv: []const []const u8, env_map: *const BufMap) !void {
555 const argv_buf = try allocator.alloc(?[*]u8, argv.len + 1);
556 mem.set(?[*]u8, argv_buf, null);
557 defer {
558 for (argv_buf) |arg| {
559 const arg_buf = if (arg) |ptr| cstr.toSlice(ptr) else break;
560 allocator.free(arg_buf);
561 }
562 allocator.free(argv_buf);
563 }
564 for (argv) |arg, i| {
565 const arg_buf = try allocator.alloc(u8, arg.len + 1);
566 @memcpy(arg_buf.ptr, arg.ptr, arg.len);
567 arg_buf[arg.len] = 0;
568
569 argv_buf[i] = arg_buf.ptr;
570 }
571 argv_buf[argv.len] = null;
572
573 const envp_buf = try createNullDelimitedEnvMap(allocator, env_map);
574 defer freeNullDelimitedEnvMap(allocator, envp_buf);
575
576 const exe_path = argv[0];
577 if (mem.indexOfScalar(u8, exe_path, '/') != null) {
578 return execveErrnoToErr(errno(system.execve(argv_buf[0].?, argv_buf.ptr, envp_buf.ptr)));
579 }
580
581 const PATH = getenv("PATH") orelse "/usr/local/bin:/bin/:/usr/bin";
582 // PATH.len because it is >= the largest search_path
583 // +1 for the / to join the search path and exe_path
584 // +1 for the null terminating byte
585 const path_buf = try allocator.alloc(u8, PATH.len + exe_path.len + 2);
586 defer allocator.free(path_buf);
587 var it = mem.tokenize(PATH, ":");
588 var seen_eacces = false;
589 var err: usize = undefined;
590 while (it.next()) |search_path| {
591 mem.copy(u8, path_buf, search_path);
592 path_buf[search_path.len] = '/';
593 mem.copy(u8, path_buf[search_path.len + 1 ..], exe_path);
594 path_buf[search_path.len + exe_path.len + 1] = 0;
595 err = errno(system.execve(path_buf.ptr, argv_buf.ptr, envp_buf.ptr));
596 assert(err > 0);
597 if (err == EACCES) {
598 seen_eacces = true;
599 } else if (err != ENOENT) {
600 return execveErrnoToErr(err);
601 }
602 }
603 if (seen_eacces) {
604 err = EACCES;
605 }
606 return execveErrnoToErr(err);
607}
608
609pub fn createNullDelimitedEnvMap(allocator: *Allocator, env_map: *const BufMap) ![]?[*]u8 {
610 const envp_count = env_map.count();
611 const envp_buf = try allocator.alloc(?[*]u8, envp_count + 1);
612 mem.set(?[*]u8, envp_buf, null);
613 errdefer freeNullDelimitedEnvMap(allocator, envp_buf);
614 {
615 var it = env_map.iterator();
616 var i: usize = 0;
617 while (it.next()) |pair| : (i += 1) {
618 const env_buf = try allocator.alloc(u8, pair.key.len + pair.value.len + 2);
619 @memcpy(env_buf.ptr, pair.key.ptr, pair.key.len);
620 env_buf[pair.key.len] = '=';
621 @memcpy(env_buf.ptr + pair.key.len + 1, pair.value.ptr, pair.value.len);
622 env_buf[env_buf.len - 1] = 0;
623
624 envp_buf[i] = env_buf.ptr;
625 }
626 assert(i == envp_count);
627 }
628 assert(envp_buf[envp_count] == null);
629 return envp_buf;
630}
631
632pub fn freeNullDelimitedEnvMap(allocator: *Allocator, envp_buf: []?[*]u8) void {
633 for (envp_buf) |env| {
634 const env_buf = if (env) |ptr| ptr[0 .. cstr.len(ptr) + 1] else break;
635 allocator.free(env_buf);
636 }
637 allocator.free(envp_buf);
638}
639
640pub const ExecveError = error{
641 SystemResources,
642 AccessDenied,
643 InvalidExe,
644 FileSystem,
645 IsDir,
646 FileNotFound,
647 NotDir,
648 FileBusy,
649
650 Unexpected,
651};
652
653fn execveErrnoToErr(err: usize) ExecveError {
654 assert(err > 0);
655 switch (err) {
656 EFAULT => unreachable,
657 E2BIG => return error.SystemResources,
658 EMFILE => return error.ProcessFdQuotaExceeded,
659 ENAMETOOLONG => return error.NameTooLong,
660 ENFILE => return error.SystemFdQuotaExceeded,
661 ENOMEM => return error.SystemResources,
662 EACCES => return error.AccessDenied,
663 EPERM => return error.AccessDenied,
664 EINVAL => return error.InvalidExe,
665 ENOEXEC => return error.InvalidExe,
666 EIO => return error.FileSystem,
667 ELOOP => return error.FileSystem,
668 EISDIR => return error.IsDir,
669 ENOENT => return error.FileNotFound,
670 ENOTDIR => return error.NotDir,
671 ETXTBSY => return error.FileBusy,
672 else => return unexpectedErrno(err),
673 }
674}
675
676/// Get an environment variable.
677/// See also `getenvC`.
678/// TODO make this go through libc when we have it
679pub fn getenv(key: []const u8) ?[]const u8 {
680 for (environ) |ptr| {
681 var line_i: usize = 0;
682 while (ptr[line_i] != 0 and ptr[line_i] != '=') : (line_i += 1) {}
683 const this_key = ptr[0..line_i];
684 if (!mem.eql(u8, key, this_key)) continue;
685
686 var end_i: usize = line_i;
687 while (ptr[end_i] != 0) : (end_i += 1) {}
688 const this_value = ptr[line_i + 1 .. end_i];
689
690 return this_value;
691 }
692 return null;
693}
694
695/// Get an environment variable with a null-terminated name.
696/// See also `getenv`.
697/// TODO https://github.com/ziglang/zig/issues/265
698pub fn getenvC(key: [*]const u8) ?[]const u8 {
699 if (builtin.link_libc) {
700 const value = system.getenv(key) orelse return null;
701 return mem.toSliceConst(u8, value);
702 }
703 return getenv(mem.toSliceConst(u8, key));
704}
705
706/// See std.elf for the constants.
707pub fn getauxval(index: usize) usize {
708 if (builtin.link_libc) {
709 return usize(system.getauxval(index));
710 } else if (linux.elf_aux_maybe) |auxv| {
711 var i: usize = 0;
712 while (auxv[i].a_type != std.elf.AT_NULL) : (i += 1) {
713 if (auxv[i].a_type == index)
714 return auxv[i].a_un.a_val;
715 }
716 }
717 return 0;
718}
719
720pub const GetCwdError = error{
721 NameTooLong,
722 CurrentWorkingDirectoryUnlinked,
723 Unexpected,
724};
725
726/// The result is a slice of out_buffer, indexed from 0.
727pub fn getcwd(out_buffer: []u8) GetCwdError![]u8 {
728 if (windows.is_the_target and !builtin.link_libc) {
729 var utf16le_buf: [windows.PATH_MAX_WIDE]u16 = undefined;
730 const casted_len = @intCast(windows.DWORD, utf16le_buf.len); // TODO shouldn't need this cast
731 const casted_ptr = ([*]u16)(&utf16le_buf); // TODO shouldn't need this cast
732 const result = windows.GetCurrentDirectoryW(casted_len, casted_ptr);
733 if (result == 0) {
734 switch (windows.GetLastError()) {
735 else => |err| return windows.unexpectedError(err),
736 }
737 }
738 assert(result <= utf16le_buf.len);
739 const utf16le_slice = utf16le_buf[0..result];
740 // Trust that Windows gives us valid UTF-16LE.
741 var end_index: usize = 0;
742 var it = std.unicode.Utf16LeIterator.init(utf16le);
743 while (it.nextCodepoint() catch unreachable) |codepoint| {
744 if (end_index + std.unicode.utf8CodepointSequenceLength(codepoint) >= out_buffer.len)
745 return error.NameTooLong;
746 end_index += utf8Encode(codepoint, out_buffer[end_index..]) catch unreachable;
747 }
748 return out_buffer[0..end_index];
749 }
750
751 const err = if (builtin.link_libc) blk: {
752 break :blk if (system.getcwd(out_buffer.ptr, out_buffer.len)) |_| 0 else system._errno().*;
753 } else blk: {
754 break :blk errno(system.getcwd(out_buffer, out_buffer.len));
755 };
756 switch (err) {
757 0 => return mem.toSlice(u8, out_buffer),
758 EFAULT => unreachable,
759 EINVAL => unreachable,
760 ENOENT => return error.CurrentWorkingDirectoryUnlinked,
761 ERANGE => return error.NameTooLong,
762 else => |err| return unexpectedErrno(err),
763 }
764}
765
766test "getcwd" {
767 // at least call it so it gets compiled
768 var buf: [os.MAX_PATH_BYTES]u8 = undefined;
769 _ = getcwd(&buf) catch {};
770}
771
772pub const SymLinkError = error{
773 AccessDenied,
774 DiskQuota,
775 PathAlreadyExists,
776 FileSystem,
777 SymLinkLoop,
778 FileNotFound,
779 SystemResources,
780 NoSpaceLeft,
781 ReadOnlyFileSystem,
782 NotDir,
783 NameTooLong,
784 InvalidUtf8,
785 BadPathName,
786 Unexpected,
787};
788
789/// Creates a symbolic link named `new_path` which contains the string `target_path`.
790/// A symbolic link (also known as a soft link) may point to an existing file or to a nonexistent
791/// one; the latter case is known as a dangling link.
792/// If `new_path` exists, it will not be overwritten.
793/// See also `symlinkC` and `symlinkW`.
794pub fn symlink(target_path: []const u8, new_path: []const u8) SymLinkError!void {
795 if (windows.is_the_target and !builtin.link_libc) {
796 const target_path_w = try cStrToPrefixedFileW(target_path);
797 const new_path_w = try cStrToPrefixedFileW(new_path);
798 return symlinkW(&target_path_w, &new_path_w);
799 } else {
800 const target_path_c = try toPosixPath(target_path);
801 const new_path_c = try toPosixPath(new_path);
802 return symlinkC(&target_path_c, &new_path_c);
803 }
804}
805
806pub fn symlinkat(target_path: []const u8, newdirfd: fd_t, new_path: []const u8) SymLinkError!void {
807 const target_path_c = try toPosixPath(target_path);
808 const new_path_c = try toPosixPath(new_path);
809 return symlinkatC(target_path_c, newdirfd, new_path_c);
810}
811
812pub fn symlinkatC(target_path: [*]const u8, newdirfd: fd_t, new_path: [*]const u8) SymLinkError!void {
813 switch (errno(system.symlinkat(target_path, newdirfd, new_path))) {
814 0 => return,
815 EFAULT => unreachable,
816 EINVAL => unreachable,
817 EACCES => return error.AccessDenied,
818 EPERM => return error.AccessDenied,
819 EDQUOT => return error.DiskQuota,
820 EEXIST => return error.PathAlreadyExists,
821 EIO => return error.FileSystem,
822 ELOOP => return error.SymLinkLoop,
823 ENAMETOOLONG => return error.NameTooLong,
824 ENOENT => return error.FileNotFound,
825 ENOTDIR => return error.NotDir,
826 ENOMEM => return error.SystemResources,
827 ENOSPC => return error.NoSpaceLeft,
828 EROFS => return error.ReadOnlyFileSystem,
829 else => |err| return unexpectedErrno(err),
830 }
831}
832
833/// This is the same as `symlink` except the parameters are null-terminated pointers.
834/// See also `symlink` and `symlinkW`.
835pub fn symlinkC(target_path: [*]const u8, new_path: [*]const u8) SymLinkError!void {
836 if (windows.is_the_target and !builtin.link_libc) {
837 const target_path_w = try cStrToPrefixedFileW(target_path);
838 const new_path_w = try cStrToPrefixedFileW(new_path);
839 return symlinkW(&target_path_w, &new_path_w);
840 }
841 switch (errno(system.symlink(target_path, new_path))) {
842 0 => return,
843 EFAULT => unreachable,
844 EINVAL => unreachable,
845 EACCES => return error.AccessDenied,
846 EPERM => return error.AccessDenied,
847 EDQUOT => return error.DiskQuota,
848 EEXIST => return error.PathAlreadyExists,
849 EIO => return error.FileSystem,
850 ELOOP => return error.SymLinkLoop,
851 ENAMETOOLONG => return error.NameTooLong,
852 ENOENT => return error.FileNotFound,
853 ENOTDIR => return error.NotDir,
854 ENOMEM => return error.SystemResources,
855 ENOSPC => return error.NoSpaceLeft,
856 EROFS => return error.ReadOnlyFileSystem,
857 else => |err| return unexpectedErrno(err),
858 }
859}
860
861/// This is the same as `symlink` except the parameters are null-terminated pointers to
862/// UTF-16LE encoded strings.
863/// See also `symlink` and `symlinkC`.
864/// TODO handle when linking libc
865pub fn symlinkW(target_path_w: [*]const u16, new_path_w: [*]const u16) SymLinkError!void {
866 if (windows.CreateSymbolicLinkW(target_path_w, new_path_w, 0) == 0) {
867 switch (windows.GetLastError()) {
868 else => |err| return windows.unexpectedError(err),
869 }
870 }
871}
872
873pub const UnlinkError = error{
874 FileNotFound,
875 AccessDenied,
876 FileBusy,
877 FileSystem,
878 IsDir,
879 SymLinkLoop,
880 NameTooLong,
881 NotDir,
882 SystemResources,
883 ReadOnlyFileSystem,
884 Unexpected,
885
886 /// On Windows, file paths must be valid Unicode.
887 InvalidUtf8,
888
889 /// On Windows, file paths cannot contain these characters:
890 /// '/', '*', '?', '"', '<', '>', '|'
891 BadPathName,
892};
893
894/// Delete a name and possibly the file it refers to.
895pub fn unlink(file_path: []const u8) UnlinkError!void {
896 if (windows.is_the_target and !builtin.link_libc) {
897 const file_path_w = try sliceToPrefixedFileW(file_path);
898 return unlinkW(&file_path_w);
899 } else {
900 const file_path_c = try toPosixPath(file_path);
901 return unlinkC(&file_path_c);
902 }
903}
904
905/// Same as `unlink` except the parameter is a UTF16LE-encoded string.
906/// TODO handle when linking libc
907pub fn unlinkW(file_path: [*]const u16) UnlinkError!void {
908 if (windows.unlinkW(file_path) == 0) {
909 switch (windows.GetLastError()) {
910 windows.ERROR.FILE_NOT_FOUND => return error.FileNotFound,
911 windows.ERROR.ACCESS_DENIED => return error.AccessDenied,
912 windows.ERROR.FILENAME_EXCED_RANGE => return error.NameTooLong,
913 windows.ERROR.INVALID_PARAMETER => return error.NameTooLong,
914 else => |err| return windows.unexpectedError(err),
915 }
916 }
917}
918
919/// Same as `unlink` except the parameter is a null terminated UTF8-encoded string.
920pub fn unlinkC(file_path: [*]const u8) UnlinkError!void {
921 if (windows.is_the_target and !builtin.link_libc) {
922 const file_path_w = try cStrToPrefixedFileW(file_path);
923 return unlinkW(&file_path_w);
924 }
925 switch (errno(system.unlink(file_path))) {
926 0 => return,
927 EACCES => return error.AccessDenied,
928 EPERM => return error.AccessDenied,
929 EBUSY => return error.FileBusy,
930 EFAULT => unreachable,
931 EINVAL => unreachable,
932 EIO => return error.FileSystem,
933 EISDIR => return error.IsDir,
934 ELOOP => return error.SymLinkLoop,
935 ENAMETOOLONG => return error.NameTooLong,
936 ENOENT => return error.FileNotFound,
937 ENOTDIR => return error.NotDir,
938 ENOMEM => return error.SystemResources,
939 EROFS => return error.ReadOnlyFileSystem,
940 else => |err| return unexpectedErrno(err),
941 }
942}
943
944const RenameError = error{}; // TODO
945
946/// Change the name or location of a file.
947pub fn rename(old_path: []const u8, new_path: []const u8) RenameError!void {
948 if (windows.is_the_target and !builtin.link_libc) {
949 const old_path_w = try sliceToPrefixedFileW(old_path);
950 const new_path_w = try sliceToPrefixedFileW(new_path);
951 return renameW(&old_path_w, &new_path_w);
952 } else {
953 const old_path_c = try toPosixPath(old_path);
954 const new_path_c = try toPosixPath(new_path);
955 return renameC(&old_path_c, &new_path_c);
956 }
957}
958
959/// Same as `rename` except the parameters are null-terminated byte arrays.
960pub fn renameC(old_path: [*]const u8, new_path: [*]const u8) RenameError!void {
961 if (windows.is_the_target and !builtin.link_libc) {
962 const old_path_w = try cStrToPrefixedFileW(old_path);
963 const new_path_w = try cStrToPrefixedFileW(new_path);
964 return renameW(&old_path_w, &new_path_w);
965 }
966 switch (errno(system.rename(old_path, new_path))) {
967 0 => return,
968 EACCES => return error.AccessDenied,
969 EPERM => return error.AccessDenied,
970 EBUSY => return error.FileBusy,
971 EDQUOT => return error.DiskQuota,
972 EFAULT => unreachable,
973 EINVAL => unreachable,
974 EISDIR => return error.IsDir,
975 ELOOP => return error.SymLinkLoop,
976 EMLINK => return error.LinkQuotaExceeded,
977 ENAMETOOLONG => return error.NameTooLong,
978 ENOENT => return error.FileNotFound,
979 ENOTDIR => return error.NotDir,
980 ENOMEM => return error.SystemResources,
981 ENOSPC => return error.NoSpaceLeft,
982 EEXIST => return error.PathAlreadyExists,
983 ENOTEMPTY => return error.PathAlreadyExists,
984 EROFS => return error.ReadOnlyFileSystem,
985 EXDEV => return error.RenameAcrossMountPoints,
986 else => |err| return unexpectedErrno(err),
987 }
988}
989
990/// Same as `rename` except the parameters are null-terminated UTF16LE-encoded strings.
991/// TODO handle when linking libc
992pub fn renameW(old_path: [*]const u16, new_path: [*]const u16) RenameError!void {
993 const flags = windows.MOVEFILE_REPLACE_EXISTING | windows.MOVEFILE_WRITE_THROUGH;
994 if (windows.MoveFileExW(old_path, new_path, flags) == 0) {
995 switch (windows.GetLastError()) {
996 else => |err| return windows.unexpectedError(err),
997 }
998 }
999}
1000
1001pub const MakeDirError = error{};
1002
1003/// Create a directory.
1004/// `mode` is ignored on Windows.
1005pub fn mkdir(dir_path: []const u8, mode: u32) MakeDirError!void {
1006 if (windows.is_the_target and !builtin.link_libc) {
1007 const dir_path_w = try sliceToPrefixedFileW(dir_path);
1008 return mkdirW(&dir_path_w, mode);
1009 } else {
1010 const dir_path_c = try toPosixPath(dir_path);
1011 return mkdirC(&dir_path_c, mode);
1012 }
1013}
1014
1015/// Same as `mkdir` but the parameter is a null-terminated UTF8-encoded string.
1016pub fn mkdirC(dir_path: [*]const u8, mode: u32) MakeDirError!void {
1017 if (windows.is_the_target and !builtin.link_libc) {
1018 const dir_path_w = try cStrToPrefixedFileW(dir_path);
1019 return mkdirW(&dir_path_w, mode);
1020 }
1021 switch (errno(system.mkdir(dir_path, mode))) {
1022 0 => return,
1023 EACCES => return error.AccessDenied,
1024 EPERM => return error.AccessDenied,
1025 EDQUOT => return error.DiskQuota,
1026 EEXIST => return error.PathAlreadyExists,
1027 EFAULT => unreachable,
1028 ELOOP => return error.SymLinkLoop,
1029 EMLINK => return error.LinkQuotaExceeded,
1030 ENAMETOOLONG => return error.NameTooLong,
1031 ENOENT => return error.FileNotFound,
1032 ENOMEM => return error.SystemResources,
1033 ENOSPC => return error.NoSpaceLeft,
1034 ENOTDIR => return error.NotDir,
1035 EROFS => return error.ReadOnlyFileSystem,
1036 else => |err| return unexpectedErrno(err),
1037 }
1038}
1039
1040/// Same as `mkdir` but the parameter is a null-terminated UTF16LE-encoded string.
1041pub fn mkdirW(dir_path: []const u8, mode: u32) MakeDirError!void {
1042 const dir_path_w = try sliceToPrefixedFileW(dir_path);
1043
1044 if (windows.CreateDirectoryW(&dir_path_w, null) == 0) {
1045 switch (windows.GetLastError()) {
1046 windows.ERROR.ALREADY_EXISTS => return error.PathAlreadyExists,
1047 windows.ERROR.PATH_NOT_FOUND => return error.FileNotFound,
1048 else => |err| return windows.unexpectedError(err),
1049 }
1050 }
1051}
1052
1053pub const DeleteDirError = error{
1054 AccessDenied,
1055 FileBusy,
1056 SymLinkLoop,
1057 NameTooLong,
1058 FileNotFound,
1059 SystemResources,
1060 NotDir,
1061 DirNotEmpty,
1062 ReadOnlyFileSystem,
1063 InvalidUtf8,
1064 BadPathName,
1065 Unexpected,
1066};
1067
1068/// Deletes an empty directory.
1069pub fn rmdir(dir_path: []const u8) DeleteDirError!void {
1070 if (windows.is_the_target and !builtin.link_libc) {
1071 const dir_path_w = try sliceToPrefixedFileW(dir_path);
1072 return rmdirW(&dir_path_w);
1073 } else {
1074 const dir_path_c = try toPosixPath(dir_path);
1075 return rmdirC(&dir_path_c);
1076 }
1077}
1078
1079/// Same as `rmdir` except the parameter is a null-terminated UTF8-encoded string.
1080pub fn rmdirC(dir_path: [*]const u8) DeleteDirError!void {
1081 if (windows.is_the_target and !builtin.link_libc) {
1082 const dir_path_w = try cStrToPrefixedFileW(dir_path);
1083 return rmdirW(&dir_path_w);
1084 }
1085 switch (errno(system.rmdir(dir_path))) {
1086 0 => return,
1087 EACCES => return error.AccessDenied,
1088 EPERM => return error.AccessDenied,
1089 EBUSY => return error.FileBusy,
1090 EFAULT => unreachable,
1091 EINVAL => unreachable,
1092 ELOOP => return error.SymLinkLoop,
1093 ENAMETOOLONG => return error.NameTooLong,
1094 ENOENT => return error.FileNotFound,
1095 ENOMEM => return error.SystemResources,
1096 ENOTDIR => return error.NotDir,
1097 EEXIST => return error.DirNotEmpty,
1098 ENOTEMPTY => return error.DirNotEmpty,
1099 EROFS => return error.ReadOnlyFileSystem,
1100 else => |err| return unexpectedErrno(err),
1101 }
1102}
1103
1104/// Same as `rmdir` except the parameter is a null-terminated UTF16LE-encoded string.
1105/// TODO handle linking libc
1106pub fn rmdirW(dir_path_w: [*]const u16) DeleteDirError!void {
1107 if (windows.RemoveDirectoryW(dir_path_w) == 0) {
1108 switch (windows.GetLastError()) {
1109 windows.ERROR.PATH_NOT_FOUND => return error.FileNotFound,
1110 windows.ERROR.DIR_NOT_EMPTY => return error.DirNotEmpty,
1111 else => |err| return windows.unexpectedError(err),
1112 }
1113 }
1114}
1115
1116pub const ChangeCurDirError = error{};
1117
1118/// Changes the current working directory of the calling process.
1119/// `dir_path` is recommended to be a UTF-8 encoded string.
1120pub fn chdir(dir_path: []const u8) ChangeCurDirError!void {
1121 if (windows.is_the_target and !builtin.link_libc) {
1122 const dir_path_w = try sliceToPrefixedFileW(dir_path);
1123 return chdirW(&dir_path_w);
1124 } else {
1125 const dir_path_c = try toPosixPath(dir_path);
1126 return chdirC(&dir_path_c);
1127 }
1128}
1129
1130/// Same as `chdir` except the parameter is null-terminated.
1131pub fn chdirC(dir_path: [*]const u8) ChangeCurDirError!void {
1132 if (windows.is_the_target and !builtin.link_libc) {
1133 const dir_path_w = try cStrToPrefixedFileW(dir_path);
1134 return chdirW(&dir_path_w);
1135 }
1136 switch (errno(system.chdir(dir_path))) {
1137 0 => return,
1138 EACCES => return error.AccessDenied,
1139 EFAULT => unreachable,
1140 EIO => return error.FileSystem,
1141 ELOOP => return error.SymLinkLoop,
1142 ENAMETOOLONG => return error.NameTooLong,
1143 ENOENT => return error.FileNotFound,
1144 ENOMEM => return error.SystemResources,
1145 ENOTDIR => return error.NotDir,
1146 else => |err| return unexpectedErrno(err),
1147 }
1148}
1149
1150/// Same as `chdir` except the parameter is a null-terminated, UTF16LE-encoded string.
1151/// TODO handle linking libc
1152pub fn chdirW(dir_path: [*]const u16) ChangeCurDirError!void {
1153 @compileError("TODO implement chdir for Windows");
1154}
1155
1156pub const ReadLinkError = error{};
1157
1158/// Read value of a symbolic link.
1159/// The return value is a slice of `out_buffer` from index 0.
1160pub fn readlink(file_path: []const u8, out_buffer: []u8) ReadLinkError![]u8 {
1161 if (windows.is_the_target and !builtin.link_libc) {
1162 const file_path_w = try sliceToPrefixedFileW(file_path);
1163 return readlinkW(&file_path_w, out_buffer);
1164 } else {
1165 const file_path_c = try toPosixPath(file_path);
1166 return readlinkC(&file_path_c, out_buffer);
1167 }
1168}
1169
1170/// Same as `readlink` except `file_path` is null-terminated.
1171pub fn readlinkC(file_path: [*]const u8, out_buffer: []u8) ReadLinkError![]u8 {
1172 if (windows.is_the_target and !builtin.link_libc) {
1173 const file_path_w = try cStrToPrefixedFileW(file_path);
1174 return readlinkW(&file_path_w, out_buffer);
1175 }
1176 const rc = system.readlink(file_path, out_buffer.ptr, out_buffer.len);
1177 switch (errno(rc)) {
1178 0 => return out_buffer[0..rc],
1179 EACCES => return error.AccessDenied,
1180 EFAULT => unreachable,
1181 EINVAL => unreachable,
1182 EIO => return error.FileSystem,
1183 ELOOP => return error.SymLinkLoop,
1184 ENAMETOOLONG => return error.NameTooLong,
1185 ENOENT => return error.FileNotFound,
1186 ENOMEM => return error.SystemResources,
1187 ENOTDIR => return error.NotDir,
1188 else => |err| return unexpectedErrno(err),
1189 }
1190}
1191
1192pub const SetIdError = error{
1193 ResourceLimitReached,
1194 InvalidUserId,
1195 PermissionDenied,
1196 Unexpected,
1197};
1198
1199pub fn setuid(uid: u32) SetIdError!void {
1200 switch (errno(system.setuid(uid))) {
1201 0 => return,
1202 EAGAIN => return error.ResourceLimitReached,
1203 EINVAL => return error.InvalidUserId,
1204 EPERM => return error.PermissionDenied,
1205 else => |err| return unexpectedErrno(err),
1206 }
1207}
1208
1209pub fn setreuid(ruid: u32, euid: u32) SetIdError!void {
1210 switch (errno(system.setreuid(ruid, euid))) {
1211 0 => return,
1212 EAGAIN => return error.ResourceLimitReached,
1213 EINVAL => return error.InvalidUserId,
1214 EPERM => return error.PermissionDenied,
1215 else => |err| return unexpectedErrno(err),
1216 }
1217}
1218
1219pub fn setgid(gid: u32) SetIdError!void {
1220 switch (errno(system.setgid(gid))) {
1221 0 => return,
1222 EAGAIN => return error.ResourceLimitReached,
1223 EINVAL => return error.InvalidUserId,
1224 EPERM => return error.PermissionDenied,
1225 else => |err| return unexpectedErrno(err),
1226 }
1227}
1228
1229pub fn setregid(rgid: u32, egid: u32) SetIdError!void {
1230 switch (errno(system.setregid(rgid, egid))) {
1231 0 => return,
1232 EAGAIN => return error.ResourceLimitReached,
1233 EINVAL => return error.InvalidUserId,
1234 EPERM => return error.PermissionDenied,
1235 else => |err| return unexpectedErrno(err),
1236 }
1237}
1238
1239pub const GetStdHandleError = error{
1240 NoStandardHandleAttached,
1241 Unexpected,
1242};
1243
1244pub fn GetStdHandle(handle_id: windows.DWORD) GetStdHandleError!fd_t {
1245 if (windows.is_the_target) {
1246 const handle = windows.GetStdHandle(handle_id) orelse return error.NoStandardHandleAttached;
1247 if (handle == windows.INVALID_HANDLE_VALUE) {
1248 switch (windows.GetLastError()) {
1249 else => |err| windows.unexpectedError(err),
1250 }
1251 }
1252 return handle;
1253 }
1254
1255 switch (handle_id) {
1256 windows.STD_ERROR_HANDLE => return STDERR_FILENO,
1257 windows.STD_OUTPUT_HANDLE => return STDOUT_FILENO,
1258 windows.STD_INPUT_HANDLE => return STDIN_FILENO,
1259 else => unreachable,
1260 }
1261}
1262
1263/// Test whether a file descriptor refers to a terminal.
1264pub fn isatty(handle: fd_t) bool {
1265 if (builtin.link_libc) {
1266 return system.isatty(handle) != 0;
1267 }
1268 if (windows.is_the_target) {
1269 if (isCygwinPty(handle))
1270 return true;
1271
1272 var out: windows.DWORD = undefined;
1273 return windows.GetConsoleMode(handle, &out) != 0;
1274 }
1275 if (wasi.is_the_target) {
1276 @compileError("TODO implement std.os.posix.isatty for WASI");
1277 }
1278
1279 var wsz: system.winsize = undefined;
1280 return system.syscall3(system.SYS_ioctl, @bitCast(usize, isize(handle)), TIOCGWINSZ, @ptrToInt(&wsz)) == 0;
1281}
1282
1283pub fn isCygwinPty(handle: fd_t) bool {
1284 if (!windows.is_the_target) return false;
1285
1286 const size = @sizeOf(windows.FILE_NAME_INFO);
1287 var name_info_bytes align(@alignOf(windows.FILE_NAME_INFO)) = []u8{0} ** (size + windows.MAX_PATH);
1288
1289 if (windows.GetFileInformationByHandleEx(
1290 handle,
1291 windows.FileNameInfo,
1292 @ptrCast(*c_void, &name_info_bytes[0]),
1293 @intCast(u32, name_info_bytes.len),
1294 ) == 0) {
1295 return false;
1296 }
1297
1298 const name_info = @ptrCast(*const windows.FILE_NAME_INFO, &name_info_bytes[0]);
1299 const name_bytes = name_info_bytes[size .. size + usize(name_info.FileNameLength)];
1300 const name_wide = @bytesToSlice(u16, name_bytes);
1301 return mem.indexOf(u16, name_wide, []u16{ 'm', 's', 'y', 's', '-' }) != null or
1302 mem.indexOf(u16, name_wide, []u16{ '-', 'p', 't', 'y' }) != null;
1303}
1304
1305pub const SocketError = error{
1306 /// Permission to create a socket of the specified type and/or
1307 /// pro‐tocol is denied.
1308 PermissionDenied,
1309
1310 /// The implementation does not support the specified address family.
1311 AddressFamilyNotSupported,
1312
1313 /// Unknown protocol, or protocol family not available.
1314 ProtocolFamilyNotAvailable,
1315
1316 /// The per-process limit on the number of open file descriptors has been reached.
1317 ProcessFdQuotaExceeded,
1318
1319 /// The system-wide limit on the total number of open files has been reached.
1320 SystemFdQuotaExceeded,
1321
1322 /// Insufficient memory is available. The socket cannot be created until sufficient
1323 /// resources are freed.
1324 SystemResources,
1325
1326 /// The protocol type or the specified protocol is not supported within this domain.
1327 ProtocolNotSupported,
1328
1329 Unexpected,
1330};
1331
1332pub fn socket(domain: u32, socket_type: u32, protocol: u32) SocketError!i32 {
1333 const rc = system.socket(domain, socket_type, protocol);
1334 switch (errno(rc)) {
1335 0 => return @intCast(i32, rc),
1336 EACCES => return error.PermissionDenied,
1337 EAFNOSUPPORT => return error.AddressFamilyNotSupported,
1338 EINVAL => return error.ProtocolFamilyNotAvailable,
1339 EMFILE => return error.ProcessFdQuotaExceeded,
1340 ENFILE => return error.SystemFdQuotaExceeded,
1341 ENOBUFS, ENOMEM => return error.SystemResources,
1342 EPROTONOSUPPORT => return error.ProtocolNotSupported,
1343 else => |err| return unexpectedErrno(err),
1344 }
1345}
1346
1347pub const BindError = error{
1348 /// The address is protected, and the user is not the superuser.
1349 /// For UNIX domain sockets: Search permission is denied on a component
1350 /// of the path prefix.
1351 AccessDenied,
1352
1353 /// The given address is already in use, or in the case of Internet domain sockets,
1354 /// The port number was specified as zero in the socket
1355 /// address structure, but, upon attempting to bind to an ephemeral port, it was
1356 /// determined that all port numbers in the ephemeral port range are currently in
1357 /// use. See the discussion of /proc/sys/net/ipv4/ip_local_port_range ip(7).
1358 AddressInUse,
1359
1360 /// A nonexistent interface was requested or the requested address was not local.
1361 AddressNotAvailable,
1362
1363 /// Too many symbolic links were encountered in resolving addr.
1364 SymLinkLoop,
1365
1366 /// addr is too long.
1367 NameTooLong,
1368
1369 /// A component in the directory prefix of the socket pathname does not exist.
1370 FileNotFound,
1371
1372 /// Insufficient kernel memory was available.
1373 SystemResources,
1374
1375 /// A component of the path prefix is not a directory.
1376 NotDir,
1377
1378 /// The socket inode would reside on a read-only filesystem.
1379 ReadOnlyFileSystem,
1380
1381 Unexpected,
1382};
1383
1384/// addr is `*const T` where T is one of the sockaddr
1385pub fn bind(fd: i32, addr: *const sockaddr) BindError!void {
1386 const rc = system.bind(fd, system, @sizeOf(sockaddr));
1387 switch (errno(rc)) {
1388 0 => return,
1389 EACCES => return error.AccessDenied,
1390 EADDRINUSE => return error.AddressInUse,
1391 EBADF => unreachable, // always a race condition if this error is returned
1392 EINVAL => unreachable,
1393 ENOTSOCK => unreachable,
1394 EADDRNOTAVAIL => return error.AddressNotAvailable,
1395 EFAULT => unreachable,
1396 ELOOP => return error.SymLinkLoop,
1397 ENAMETOOLONG => return error.NameTooLong,
1398 ENOENT => return error.FileNotFound,
1399 ENOMEM => return error.SystemResources,
1400 ENOTDIR => return error.NotDir,
1401 EROFS => return error.ReadOnlyFileSystem,
1402 else => |err| return unexpectedErrno(err),
1403 }
1404}
1405
1406const ListenError = error{
1407 /// Another socket is already listening on the same port.
1408 /// For Internet domain sockets, the socket referred to by sockfd had not previously
1409 /// been bound to an address and, upon attempting to bind it to an ephemeral port, it
1410 /// was determined that all port numbers in the ephemeral port range are currently in
1411 /// use. See the discussion of /proc/sys/net/ipv4/ip_local_port_range in ip(7).
1412 AddressInUse,
1413
1414 /// The file descriptor sockfd does not refer to a socket.
1415 FileDescriptorNotASocket,
1416
1417 /// The socket is not of a type that supports the listen() operation.
1418 OperationNotSupported,
1419
1420 Unexpected,
1421};
1422
1423pub fn listen(sockfd: i32, backlog: u32) ListenError!void {
1424 const rc = system.listen(sockfd, backlog);
1425 switch (errno(rc)) {
1426 0 => return,
1427 EADDRINUSE => return error.AddressInUse,
1428 EBADF => unreachable,
1429 ENOTSOCK => return error.FileDescriptorNotASocket,
1430 EOPNOTSUPP => return error.OperationNotSupported,
1431 else => |err| return unexpectedErrno(err),
1432 }
1433}
1434
1435pub const AcceptError = error{
1436 ConnectionAborted,
1437
1438 /// The per-process limit on the number of open file descriptors has been reached.
1439 ProcessFdQuotaExceeded,
1440
1441 /// The system-wide limit on the total number of open files has been reached.
1442 SystemFdQuotaExceeded,
1443
1444 /// Not enough free memory. This often means that the memory allocation is limited
1445 /// by the socket buffer limits, not by the system memory.
1446 SystemResources,
1447
1448 /// The file descriptor sockfd does not refer to a socket.
1449 FileDescriptorNotASocket,
1450
1451 /// The referenced socket is not of type SOCK_STREAM.
1452 OperationNotSupported,
1453
1454 ProtocolFailure,
1455
1456 /// Firewall rules forbid connection.
1457 BlockedByFirewall,
1458
1459 Unexpected,
1460};
1461
1462/// Accept a connection on a socket. `fd` must be opened in blocking mode.
1463/// See also `accept4_async`.
1464pub fn accept4(fd: i32, addr: *sockaddr, flags: u32) AcceptError!i32 {
1465 while (true) {
1466 var sockaddr_size = u32(@sizeOf(sockaddr));
1467 const rc = system.accept4(fd, addr, &sockaddr_size, flags);
1468 switch (errno(rc)) {
1469 0 => return @intCast(i32, rc),
1470 EINTR => continue,
1471 else => |err| return unexpectedErrno(err),
1472
1473 EAGAIN => unreachable, // This function is for blocking only.
1474 EBADF => unreachable, // always a race condition
1475 ECONNABORTED => return error.ConnectionAborted,
1476 EFAULT => unreachable,
1477 EINVAL => unreachable,
1478 EMFILE => return error.ProcessFdQuotaExceeded,
1479 ENFILE => return error.SystemFdQuotaExceeded,
1480 ENOBUFS => return error.SystemResources,
1481 ENOMEM => return error.SystemResources,
1482 ENOTSOCK => return error.FileDescriptorNotASocket,
1483 EOPNOTSUPP => return error.OperationNotSupported,
1484 EPROTO => return error.ProtocolFailure,
1485 EPERM => return error.BlockedByFirewall,
1486 }
1487 }
1488}
1489
1490/// This is the same as `accept4` except `fd` is expected to be non-blocking.
1491/// Returns -1 if would block.
1492pub fn accept4_async(fd: i32, addr: *sockaddr, flags: u32) AcceptError!i32 {
1493 while (true) {
1494 var sockaddr_size = u32(@sizeOf(sockaddr));
1495 const rc = system.accept4(fd, addr, &sockaddr_size, flags);
1496 switch (errno(rc)) {
1497 0 => return @intCast(i32, rc),
1498 EINTR => continue,
1499 else => |err| return unexpectedErrno(err),
1500
1501 EAGAIN => return -1,
1502 EBADF => unreachable, // always a race condition
1503 ECONNABORTED => return error.ConnectionAborted,
1504 EFAULT => unreachable,
1505 EINVAL => unreachable,
1506 EMFILE => return error.ProcessFdQuotaExceeded,
1507 ENFILE => return error.SystemFdQuotaExceeded,
1508 ENOBUFS => return error.SystemResources,
1509 ENOMEM => return error.SystemResources,
1510 ENOTSOCK => return error.FileDescriptorNotASocket,
1511 EOPNOTSUPP => return error.OperationNotSupported,
1512 EPROTO => return error.ProtocolFailure,
1513 EPERM => return error.BlockedByFirewall,
1514 }
1515 }
1516}
1517
1518pub const EpollCreateError = error{
1519 /// The per-user limit on the number of epoll instances imposed by
1520 /// /proc/sys/fs/epoll/max_user_instances was encountered. See epoll(7) for further
1521 /// details.
1522 /// Or, The per-process limit on the number of open file descriptors has been reached.
1523 ProcessFdQuotaExceeded,
1524
1525 /// The system-wide limit on the total number of open files has been reached.
1526 SystemFdQuotaExceeded,
1527
1528 /// There was insufficient memory to create the kernel object.
1529 SystemResources,
1530
1531 Unexpected,
1532};
1533
1534pub fn epoll_create1(flags: u32) EpollCreateError!i32 {
1535 const rc = system.epoll_create1(flags);
1536 switch (errno(rc)) {
1537 0 => return @intCast(i32, rc),
1538 else => |err| return unexpectedErrno(err),
1539
1540 EINVAL => unreachable,
1541 EMFILE => return error.ProcessFdQuotaExceeded,
1542 ENFILE => return error.SystemFdQuotaExceeded,
1543 ENOMEM => return error.SystemResources,
1544 }
1545}
1546
1547pub const EpollCtlError = error{
1548 /// op was EPOLL_CTL_ADD, and the supplied file descriptor fd is already registered
1549 /// with this epoll instance.
1550 FileDescriptorAlreadyPresentInSet,
1551
1552 /// fd refers to an epoll instance and this EPOLL_CTL_ADD operation would result in a
1553 /// circular loop of epoll instances monitoring one another.
1554 OperationCausesCircularLoop,
1555
1556 /// op was EPOLL_CTL_MOD or EPOLL_CTL_DEL, and fd is not registered with this epoll
1557 /// instance.
1558 FileDescriptorNotRegistered,
1559
1560 /// There was insufficient memory to handle the requested op control operation.
1561 SystemResources,
1562
1563 /// The limit imposed by /proc/sys/fs/epoll/max_user_watches was encountered while
1564 /// trying to register (EPOLL_CTL_ADD) a new file descriptor on an epoll instance.
1565 /// See epoll(7) for further details.
1566 UserResourceLimitReached,
1567
1568 /// The target file fd does not support epoll. This error can occur if fd refers to,
1569 /// for example, a regular file or a directory.
1570 FileDescriptorIncompatibleWithEpoll,
1571
1572 Unexpected,
1573};
1574
1575pub fn epoll_ctl(epfd: i32, op: u32, fd: i32, event: *epoll_event) EpollCtlError!void {
1576 const rc = system.epoll_ctl(epfd, op, fd, event);
1577 switch (errno(rc)) {
1578 0 => return,
1579 else => |err| return unexpectedErrno(err),
1580
1581 EBADF => unreachable, // always a race condition if this happens
1582 EEXIST => return error.FileDescriptorAlreadyPresentInSet,
1583 EINVAL => unreachable,
1584 ELOOP => return error.OperationCausesCircularLoop,
1585 ENOENT => return error.FileDescriptorNotRegistered,
1586 ENOMEM => return error.SystemResources,
1587 ENOSPC => return error.UserResourceLimitReached,
1588 EPERM => return error.FileDescriptorIncompatibleWithEpoll,
1589 }
1590}
1591
1592/// Waits for an I/O event on an epoll file descriptor.
1593/// Returns the number of file descriptors ready for the requested I/O,
1594/// or zero if no file descriptor became ready during the requested timeout milliseconds.
1595pub fn epoll_wait(epfd: i32, events: []epoll_event, timeout: i32) usize {
1596 while (true) {
1597 // TODO get rid of the @intCast
1598 const rc = system.epoll_wait(epfd, events.ptr, @intCast(u32, events.len), timeout);
1599 switch (errno(rc)) {
1600 0 => return rc,
1601 EINTR => continue,
1602 EBADF => unreachable,
1603 EFAULT => unreachable,
1604 EINVAL => unreachable,
1605 else => unreachable,
1606 }
1607 }
1608}
1609
1610pub const EventFdError = error{
1611 SystemResources,
1612 ProcessFdQuotaExceeded,
1613 SystemFdQuotaExceeded,
1614 Unexpected,
1615};
1616
1617pub fn eventfd(initval: u32, flags: u32) EventFdError!i32 {
1618 const rc = system.eventfd(initval, flags);
1619 switch (errno(rc)) {
1620 0 => return @intCast(i32, rc),
1621 else => |err| return unexpectedErrno(err),
1622
1623 EINVAL => unreachable, // invalid parameters
1624 EMFILE => return error.ProcessFdQuotaExceeded,
1625 ENFILE => return error.SystemFdQuotaExceeded,
1626 ENODEV => return error.SystemResources,
1627 ENOMEM => return error.SystemResources,
1628 }
1629}
1630
1631pub const GetSockNameError = error{
1632 /// Insufficient resources were available in the system to perform the operation.
1633 SystemResources,
1634
1635 Unexpected,
1636};
1637
1638pub fn getsockname(sockfd: i32) GetSockNameError!sockaddr {
1639 var addr: sockaddr = undefined;
1640 var addrlen: socklen_t = @sizeOf(sockaddr);
1641 switch (errno(system.getsockname(sockfd, &addr, &addrlen))) {
1642 0 => return addr,
1643 else => |err| return unexpectedErrno(err),
1644
1645 EBADF => unreachable, // always a race condition
1646 EFAULT => unreachable,
1647 EINVAL => unreachable, // invalid parameters
1648 ENOTSOCK => unreachable,
1649 ENOBUFS => return error.SystemResources,
1650 }
1651}
1652
1653pub const ConnectError = error{
1654 /// For UNIX domain sockets, which are identified by pathname: Write permission is denied on the socket
1655 /// file, or search permission is denied for one of the directories in the path prefix.
1656 /// or
1657 /// The user tried to connect to a broadcast address without having the socket broadcast flag enabled or
1658 /// the connection request failed because of a local firewall rule.
1659 PermissionDenied,
1660
1661 /// Local address is already in use.
1662 AddressInUse,
1663
1664 /// (Internet domain sockets) The socket referred to by sockfd had not previously been bound to an
1665 /// address and, upon attempting to bind it to an ephemeral port, it was determined that all port numbers
1666 /// in the ephemeral port range are currently in use. See the discussion of
1667 /// /proc/sys/net/ipv4/ip_local_port_range in ip(7).
1668 AddressNotAvailable,
1669
1670 /// The passed address didn't have the correct address family in its sa_family field.
1671 AddressFamilyNotSupported,
1672
1673 /// Insufficient entries in the routing cache.
1674 SystemResources,
1675
1676 /// A connect() on a stream socket found no one listening on the remote address.
1677 ConnectionRefused,
1678
1679 /// Network is unreachable.
1680 NetworkUnreachable,
1681
1682 /// Timeout while attempting connection. The server may be too busy to accept new connections. Note
1683 /// that for IP sockets the timeout may be very long when syncookies are enabled on the server.
1684 ConnectionTimedOut,
1685
1686 Unexpected,
1687};
1688
1689/// Initiate a connection on a socket.
1690/// This is for blocking file descriptors only.
1691/// For non-blocking, see `connect_async`.
1692pub fn connect(sockfd: i32, sockaddr: *const sockaddr) ConnectError!void {
1693 while (true) {
1694 switch (errno(system.connect(sockfd, sockaddr, @sizeOf(sockaddr)))) {
1695 0 => return,
1696 else => |err| return unexpectedErrno(err),
1697
1698 EACCES => return error.PermissionDenied,
1699 EPERM => return error.PermissionDenied,
1700 EADDRINUSE => return error.AddressInUse,
1701 EADDRNOTAVAIL => return error.AddressNotAvailable,
1702 EAFNOSUPPORT => return error.AddressFamilyNotSupported,
1703 EAGAIN => return error.SystemResources,
1704 EALREADY => unreachable, // The socket is nonblocking and a previous connection attempt has not yet been completed.
1705 EBADF => unreachable, // sockfd is not a valid open file descriptor.
1706 ECONNREFUSED => return error.ConnectionRefused,
1707 EFAULT => unreachable, // The socket structure address is outside the user's address space.
1708 EINPROGRESS => unreachable, // The socket is nonblocking and the connection cannot be completed immediately.
1709 EINTR => continue,
1710 EISCONN => unreachable, // The socket is already connected.
1711 ENETUNREACH => return error.NetworkUnreachable,
1712 ENOTSOCK => unreachable, // The file descriptor sockfd does not refer to a socket.
1713 EPROTOTYPE => unreachable, // The socket type does not support the requested communications protocol.
1714 ETIMEDOUT => return error.ConnectionTimedOut,
1715 }
1716 }
1717}
1718
1719/// Same as `connect` except it is for blocking socket file descriptors.
1720/// It expects to receive EINPROGRESS`.
1721pub fn connect_async(sockfd: i32, sockaddr: *const c_void, len: u32) ConnectError!void {
1722 while (true) {
1723 switch (errno(system.connect(sockfd, sockaddr, len))) {
1724 0, EINPROGRESS => return,
1725 EINTR => continue,
1726 else => |err| return unexpectedErrno(err),
1727
1728 EACCES => return error.PermissionDenied,
1729 EPERM => return error.PermissionDenied,
1730 EADDRINUSE => return error.AddressInUse,
1731 EADDRNOTAVAIL => return error.AddressNotAvailable,
1732 EAFNOSUPPORT => return error.AddressFamilyNotSupported,
1733 EAGAIN => return error.SystemResources,
1734 EALREADY => unreachable, // The socket is nonblocking and a previous connection attempt has not yet been completed.
1735 EBADF => unreachable, // sockfd is not a valid open file descriptor.
1736 ECONNREFUSED => return error.ConnectionRefused,
1737 EFAULT => unreachable, // The socket structure address is outside the user's address space.
1738 EISCONN => unreachable, // The socket is already connected.
1739 ENETUNREACH => return error.NetworkUnreachable,
1740 ENOTSOCK => unreachable, // The file descriptor sockfd does not refer to a socket.
1741 EPROTOTYPE => unreachable, // The socket type does not support the requested communications protocol.
1742 ETIMEDOUT => return error.ConnectionTimedOut,
1743 }
1744 }
1745}
1746
1747pub fn getsockoptError(sockfd: i32) ConnectError!void {
1748 var err_code: i32 = undefined;
1749 var size: u32 = @sizeOf(i32);
1750 const rc = system.getsockopt(sockfd, SOL_SOCKET, SO_ERROR, @ptrCast([*]u8, &err_code), &size);
1751 assert(size == 4);
1752 switch (errno(rc)) {
1753 0 => switch (err_code) {
1754 0 => return,
1755 EACCES => return error.PermissionDenied,
1756 EPERM => return error.PermissionDenied,
1757 EADDRINUSE => return error.AddressInUse,
1758 EADDRNOTAVAIL => return error.AddressNotAvailable,
1759 EAFNOSUPPORT => return error.AddressFamilyNotSupported,
1760 EAGAIN => return error.SystemResources,
1761 EALREADY => unreachable, // The socket is nonblocking and a previous connection attempt has not yet been completed.
1762 EBADF => unreachable, // sockfd is not a valid open file descriptor.
1763 ECONNREFUSED => return error.ConnectionRefused,
1764 EFAULT => unreachable, // The socket structure address is outside the user's address space.
1765 EISCONN => unreachable, // The socket is already connected.
1766 ENETUNREACH => return error.NetworkUnreachable,
1767 ENOTSOCK => unreachable, // The file descriptor sockfd does not refer to a socket.
1768 EPROTOTYPE => unreachable, // The socket type does not support the requested communications protocol.
1769 ETIMEDOUT => return error.ConnectionTimedOut,
1770 else => |err| return unexpectedErrno(err),
1771 },
1772 EBADF => unreachable, // The argument sockfd is not a valid file descriptor.
1773 EFAULT => unreachable, // The address pointed to by optval or optlen is not in a valid part of the process address space.
1774 EINVAL => unreachable,
1775 ENOPROTOOPT => unreachable, // The option is unknown at the level indicated.
1776 ENOTSOCK => unreachable, // The file descriptor sockfd does not refer to a socket.
1777 else => |err| return unexpectedErrno(err),
1778 }
1779}
1780
1781pub fn waitpid(pid: i32) i32 {
1782 var status: i32 = undefined;
1783 while (true) {
1784 switch (errno(system.waitpid(pid, &status, 0))) {
1785 0 => return status,
1786 EINTR => continue,
1787 ECHILD => unreachable, // The process specified does not exist. It would be a race condition to handle this error.
1788 EINVAL => unreachable, // The options argument was invalid
1789 else => unreachable,
1790 }
1791 }
1792}
1793
1794pub const FStatError = error{
1795 SystemResources,
1796 Unexpected,
1797};
1798
1799pub fn fstat(fd: fd_t) FStatError!Stat {
1800 var stat: Stat = undefined;
1801 if (os.darwin.is_the_target) {
1802 switch (errno(system.@"fstat$INODE64"(fd, buf))) {
1803 0 => return stat,
1804 EBADF => unreachable, // Always a race condition.
1805 ENOMEM => return error.SystemResources,
1806 else => |err| return unexpectedErrno(err),
1807 }
1808 }
1809
1810 switch (errno(system.fstat(fd, &stat))) {
1811 0 => return stat,
1812 EBADF => unreachable, // Always a race condition.
1813 ENOMEM => return error.SystemResources,
1814 else => |err| return unexpectedErrno(err),
1815 }
1816}
1817
1818pub const KQueueError = error{
1819 /// The per-process limit on the number of open file descriptors has been reached.
1820 ProcessFdQuotaExceeded,
1821
1822 /// The system-wide limit on the total number of open files has been reached.
1823 SystemFdQuotaExceeded,
1824
1825 Unexpected,
1826};
1827
1828pub fn kqueue() KQueueError!i32 {
1829 const rc = system.kqueue();
1830 switch (errno(rc)) {
1831 0 => return @intCast(i32, rc),
1832 EMFILE => return error.ProcessFdQuotaExceeded,
1833 ENFILE => return error.SystemFdQuotaExceeded,
1834 else => |err| return unexpectedErrno(err),
1835 }
1836}
1837
1838pub const KEventError = error{
1839 /// The process does not have permission to register a filter.
1840 AccessDenied,
1841
1842 /// The event could not be found to be modified or deleted.
1843 EventNotFound,
1844
1845 /// No memory was available to register the event.
1846 SystemResources,
1847
1848 /// The specified process to attach to does not exist.
1849 ProcessNotFound,
1850};
1851
1852pub fn kevent(
1853 kq: i32,
1854 changelist: []const Kevent,
1855 eventlist: []Kevent,
1856 timeout: ?*const timespec,
1857) KEventError!usize {
1858 while (true) {
1859 const rc = system.kevent(kq, changelist, eventlist, timeout);
1860 switch (errno(rc)) {
1861 0 => return rc,
1862 EACCES => return error.AccessDenied,
1863 EFAULT => unreachable,
1864 EBADF => unreachable, // Always a race condition.
1865 EINTR => continue,
1866 EINVAL => unreachable,
1867 ENOENT => return error.EventNotFound,
1868 ENOMEM => return error.SystemResources,
1869 ESRCH => return error.ProcessNotFound,
1870 else => unreachable,
1871 }
1872 }
1873}
1874
1875pub const INotifyInitError = error{
1876 ProcessFdQuotaExceeded,
1877 SystemFdQuotaExceeded,
1878 SystemResources,
1879 Unexpected,
1880};
1881
1882/// initialize an inotify instance
1883pub fn inotify_init1(flags: u32) INotifyInitError!i32 {
1884 const rc = system.inotify_init1(flags);
1885 switch (errno(rc)) {
1886 0 => return @intCast(i32, rc),
1887 EINVAL => unreachable,
1888 EMFILE => return error.ProcessFdQuotaExceeded,
1889 ENFILE => return error.SystemFdQuotaExceeded,
1890 ENOMEM => return error.SystemResources,
1891 else => |err| return unexpectedErrno(err),
1892 }
1893}
1894
1895pub const INotifyAddWatchError = error{
1896 AccessDenied,
1897 NameTooLong,
1898 FileNotFound,
1899 SystemResources,
1900 UserResourceLimitReached,
1901 Unexpected,
1902};
1903
1904/// add a watch to an initialized inotify instance
1905pub fn inotify_add_watch(inotify_fd: i32, pathname: []const u8, mask: u32) INotifyAddWatchError!i32 {
1906 const pathname_c = try toPosixPath(pathname);
1907 return inotify_add_watchC(inotify_fd, &pathname_c, mask);
1908}
1909
1910/// Same as `inotify_add_watch` except pathname is null-terminated.
1911pub fn inotify_add_watchC(inotify_fd: i32, pathname: [*]const u8, mask: u32) INotifyAddWatchError!i32 {
1912 const rc = system.inotify_add_watch(inotify_fd, pathname, mask);
1913 switch (errno(rc)) {
1914 0 => return @intCast(i32, rc),
1915 EACCES => return error.AccessDenied,
1916 EBADF => unreachable,
1917 EFAULT => unreachable,
1918 EINVAL => unreachable,
1919 ENAMETOOLONG => return error.NameTooLong,
1920 ENOENT => return error.FileNotFound,
1921 ENOMEM => return error.SystemResources,
1922 ENOSPC => return error.UserResourceLimitReached,
1923 else => |err| return unexpectedErrno(err),
1924 }
1925}
1926
1927/// remove an existing watch from an inotify instance
1928pub fn inotify_rm_watch(inotify_fd: i32, wd: i32) void {
1929 switch (errno(system.inotify_rm_watch(inotify_fd, wd))) {
1930 0 => return,
1931 EBADF => unreachable,
1932 EINVAL => unreachable,
1933 else => unreachable,
1934 }
1935}
1936
1937pub const MProtectError = error{
1938 AccessDenied,
1939 OutOfMemory,
1940 Unexpected,
1941};
1942
1943/// address and length must be page-aligned
1944pub fn mprotect(address: usize, length: usize, protection: u32) MProtectError!void {
1945 const negative_page_size = @bitCast(usize, -isize(os.page_size));
1946 const aligned_address = address & negative_page_size;
1947 const aligned_end = (address + length + os.page_size - 1) & negative_page_size;
1948 assert(address == aligned_address);
1949 assert(length == aligned_end - aligned_address);
1950 switch (errno(system.mprotect(address, length, protection))) {
1951 0 => return,
1952 EINVAL => unreachable,
1953 EACCES => return error.AccessDenied,
1954 ENOMEM => return error.OutOfMemory,
1955 else => return unexpectedErrno(err),
1956 }
1957}
1958
1959pub const ForkError = error{
1960 SystemResources,
1961 Unexpected,
1962};
1963
1964pub fn fork() ForkError!pid_t {
1965 const rc = system.fork();
1966 switch (errno(rc)) {
1967 0 => return rc,
1968 EAGAIN => return error.SystemResources,
1969 ENOMEM => return error.SystemResources,
1970 else => |err| return unexpectedErrno(err),
1971 }
1972}
1973
1974pub const MMapError = error{
1975 AccessDenied,
1976 PermissionDenied,
1977 LockedMemoryLimitExceeded,
1978 SystemFdQuotaExceeded,
1979 MemoryMappingNotSupported,
1980 OutOfMemory,
1981};
1982
1983/// Map files or devices into memory.
1984/// Use of a mapped region can result in these signals:
1985/// * SIGSEGV - Attempted write into a region mapped as read-only.
1986/// * SIGBUS - Attempted access to a portion of the buffer that does not correspond to the file
1987pub fn mmap(address: ?[*]u8, length: usize, prot: u32, flags: u32, fd: fd_t, offset: isize) MMapError!usize {
1988 const err = if (builtin.link_libc) blk: {
1989 const rc = system.mmap(address, length, prot, flags, fd, offset);
1990 if (rc != system.MMAP_FAILED) return rc;
1991 break :blk system._errno().*;
1992 } else blk: {
1993 const rc = system.mmap(address, length, prot, flags, fd, offset);
1994 const err = errno(rc);
1995 if (err == 0) return rc;
1996 break :blk err;
1997 };
1998 switch (err) {
1999 ETXTBSY => return error.AccessDenied,
2000 EACCES => return error.AccessDenied,
2001 EPERM => return error.PermissionDenied,
2002 EAGAIN => return error.LockedMemoryLimitExceeded,
2003 EBADF => unreachable, // Always a race condition.
2004 EOVERFLOW => unreachable, // The number of pages used for length + offset would overflow.
2005 ENFILE => return error.SystemFdQuotaExceeded,
2006 ENODEV => return error.MemoryMappingNotSupported,
2007 EINVAL => unreachable, // Invalid parameters to mmap()
2008 ENOMEM => return error.OutOfMemory,
2009 else => return unexpectedErrno(err),
2010 }
2011}
2012
2013/// Deletes the mappings for the specified address range, causing
2014/// further references to addresses within the range to generate invalid memory references.
2015/// Note that while POSIX allows unmapping a region in the middle of an existing mapping,
2016/// Zig's munmap function does not, for two reasons:
2017/// * It violates the Zig principle that resource deallocation must succeed.
2018/// * The Windows function, VirtualFree, has this restriction.
2019pub fn munmap(address: usize, length: usize) void {
2020 switch (errno(system.munmap(address, length))) {
2021 0 => return,
2022 EINVAL => unreachable, // Invalid parameters.
2023 ENOMEM => unreachable, // Attempted to unmap a region in the middle of an existing mapping.
2024 else => unreachable,
2025 }
2026}
2027
2028pub const AccessError = error{
2029 PermissionDenied,
2030 FileNotFound,
2031 NameTooLong,
2032 InputOutput,
2033 SystemResources,
2034 BadPathName,
2035
2036 /// On Windows, file paths must be valid Unicode.
2037 InvalidUtf8,
2038
2039 Unexpected,
2040};
2041
2042/// check user's permissions for a file
2043pub fn access(path: []const u8, mode: u32) AccessError!void {
2044 if (windows.is_the_target and !builtin.link_libc) {
2045 const path_w = try sliceToPrefixedFileW(path);
2046 return accessW(&path_w, mode);
2047 }
2048 const path_c = try toPosixPath(path);
2049 return accessC(&path_c, mode);
2050}
2051
2052/// Call from Windows-specific code if you already have a UTF-16LE encoded, null terminated string.
2053/// Otherwise use `access` or `accessC`.
2054/// TODO currently this ignores `mode`.
2055pub fn accessW(path: [*]const u16, mode: u32) AccessError!void {
2056 if (windows.GetFileAttributesW(path) != windows.INVALID_FILE_ATTRIBUTES) {
2057 return;
2058 }
2059 switch (windows.GetLastError()) {
2060 windows.ERROR.FILE_NOT_FOUND => return error.FileNotFound,
2061 windows.ERROR.PATH_NOT_FOUND => return error.FileNotFound,
2062 windows.ERROR.ACCESS_DENIED => return error.PermissionDenied,
2063 else => |err| return windows.unexpectedError(err),
2064 }
2065}
2066
2067/// Call if you have a UTF-8 encoded, null-terminated string.
2068/// Otherwise use `access` or `accessW`.
2069pub fn accessC(path: [*]const u8, mode: u32) AccessError!void {
2070 if (windows.is_the_target) {
2071 const path_w = try cStrToPrefixedFileW(path);
2072 return accessW(&path_w, mode);
2073 }
2074 switch (errno(system.access(path, mode))) {
2075 0 => return,
2076 EACCES => return error.PermissionDenied,
2077 EROFS => return error.PermissionDenied,
2078 ELOOP => return error.PermissionDenied,
2079 ETXTBSY => return error.PermissionDenied,
2080 ENOTDIR => return error.FileNotFound,
2081 ENOENT => return error.FileNotFound,
2082
2083 ENAMETOOLONG => return error.NameTooLong,
2084 EINVAL => unreachable,
2085 EFAULT => unreachable,
2086 EIO => return error.InputOutput,
2087 ENOMEM => return error.SystemResources,
2088 else => |err| return unexpectedErrno(err),
2089 }
2090}
2091
2092pub const PipeError = error{
2093 SystemFdQuotaExceeded,
2094 ProcessFdQuotaExceeded,
2095};
2096
2097/// Creates a unidirectional data channel that can be used for interprocess communication.
2098pub fn pipe(fds: *[2]fd_t) PipeError!void {
2099 switch (errno(system.pipe(fds))) {
2100 0 => return,
2101 EINVAL => unreachable, // Invalid parameters to pipe()
2102 EFAULT => unreachable, // Invalid fds pointer
2103 ENFILE => return error.SystemFdQuotaExceeded,
2104 EMFILE => return error.ProcessFdQuotaExceeded,
2105 else => |err| return unexpectedErrno(err),
2106 }
2107}
2108
2109pub fn pipe2(fds: *[2]fd_t, flags: u32) PipeError!void {
2110 switch (errno(system.pipe2(fds, flags))) {
2111 0 => return,
2112 EINVAL => unreachable, // Invalid flags
2113 EFAULT => unreachable, // Invalid fds pointer
2114 ENFILE => return error.SystemFdQuotaExceeded,
2115 EMFILE => return error.ProcessFdQuotaExceeded,
2116 else => |err| return unexpectedErrno(err),
2117 }
2118}
2119
2120pub const SysCtlError = error{
2121 PermissionDenied,
2122 SystemResources,
2123 Unexpected,
2124};
2125
2126pub fn sysctl(
2127 name: []const c_int,
2128 oldp: ?*c_void,
2129 oldlenp: ?*usize,
2130 newp: ?*c_void,
2131 newlen: usize,
2132) SysCtlError!void {
2133 switch (errno(system.sysctl(name.ptr, name.len, oldp, oldlenp, newp, newlen))) {
2134 0 => return,
2135 EFAULT => unreachable,
2136 EPERM => return error.PermissionDenied,
2137 ENOMEM => return error.SystemResources,
2138 else => |err| return unexpectedErrno(err),
2139 }
2140}
2141
2142pub fn sysctlbynameC(
2143 name: [*]const u8,
2144 oldp: ?*c_void,
2145 oldlenp: ?*usize,
2146 newp: ?*c_void,
2147 newlen: usize,
2148) SysCtlError!void {
2149 switch (errno(system.sysctlbyname(name, oldp, oldlenp, newp, newlen))) {
2150 0 => return,
2151 EFAULT => unreachable,
2152 EPERM => return error.PermissionDenied,
2153 ENOMEM => return error.SystemResources,
2154 else => |err| return unexpectedErrno(err),
2155 }
2156}
2157
2158pub fn gettimeofday(tv: ?*timeval, tz: ?*timezone) void {
2159 switch (errno(system.gettimeofday(tv, tz))) {
2160 0 => return,
2161 EINVAL => unreachable,
2162 else => unreachable,
2163 }
2164}
2165
2166pub fn nanosleep(req: timespec) void {
2167 var rem = req;
2168 while (true) {
2169 switch (errno(system.nanosleep(&rem, &rem))) {
2170 0 => return,
2171 EINVAL => unreachable, // Invalid parameters.
2172 EFAULT => unreachable,
2173 EINTR => continue,
2174 }
2175 }
2176}
2177
2178pub const realpath = std.os.path.real;
2179pub const realpathC = std.os.path.realC;
2180pub const realpathW = std.os.path.realW;
2181
2182pub const WaitForSingleObjectError = error{
2183 WaitAbandoned,
2184 WaitTimeOut,
2185 Unexpected,
2186};
2187
2188pub fn WaitForSingleObject(handle: windows.HANDLE, milliseconds: windows.DWORD) WaitForSingleObjectError!void {
2189 switch (windows.WaitForSingleObject(handle, milliseconds)) {
2190 windows.WAIT_ABANDONED => return error.WaitAbandoned,
2191 windows.WAIT_OBJECT_0 => return,
2192 windows.WAIT_TIMEOUT => return error.WaitTimeOut,
2193 windows.WAIT_FAILED => {
2194 switch (windows.GetLastError()) {
2195 else => |err| return windows.unexpectedError(err),
2196 }
2197 },
2198 else => return error.Unexpected,
2199 }
2200}
2201
2202pub fn FindFirstFile(
2203 dir_path: []const u8,
2204 find_file_data: *windows.WIN32_FIND_DATAW,
2205) !windows.HANDLE {
2206 const dir_path_w = try sliceToPrefixedSuffixedFileW(dir_path, []u16{ '\\', '*', 0 });
2207 const handle = windows.FindFirstFileW(&dir_path_w, find_file_data);
2208
2209 if (handle == windows.INVALID_HANDLE_VALUE) {
2210 switch (windows.GetLastError()) {
2211 windows.ERROR.FILE_NOT_FOUND => return error.FileNotFound,
2212 windows.ERROR.PATH_NOT_FOUND => return error.FileNotFound,
2213 else => |err| return windows.unexpectedError(err),
2214 }
2215 }
2216
2217 return handle;
2218}
2219
2220/// Returns `true` if there was another file, `false` otherwise.
2221pub fn FindNextFile(handle: windows.HANDLE, find_file_data: *windows.WIN32_FIND_DATAW) !bool {
2222 if (windows.FindNextFileW(handle, find_file_data) == 0) {
2223 switch (windows.GetLastError()) {
2224 windows.ERROR.NO_MORE_FILES => return false,
2225 else => |err| return windows.unexpectedError(err),
2226 }
2227 }
2228 return true;
2229}
2230
2231pub const CreateIoCompletionPortError = error{Unexpected};
2232
2233pub fn CreateIoCompletionPort(
2234 file_handle: windows.HANDLE,
2235 existing_completion_port: ?windows.HANDLE,
2236 completion_key: usize,
2237 concurrent_thread_count: windows.DWORD,
2238) CreateIoCompletionPortError!windows.HANDLE {
2239 const handle = windows.CreateIoCompletionPort(file_handle, existing_completion_port, completion_key, concurrent_thread_count) orelse {
2240 switch (windows.GetLastError()) {
2241 windows.ERROR.INVALID_PARAMETER => unreachable,
2242 else => |err| return windows.unexpectedError(err),
2243 }
2244 };
2245 return handle;
2246}
2247
2248pub const WindowsPostQueuedCompletionStatusError = error{Unexpected};
2249
2250pub fn windowsPostQueuedCompletionStatus(completion_port: windows.HANDLE, bytes_transferred_count: windows.DWORD, completion_key: usize, lpOverlapped: ?*windows.OVERLAPPED) WindowsPostQueuedCompletionStatusError!void {
2251 if (windows.PostQueuedCompletionStatus(completion_port, bytes_transferred_count, completion_key, lpOverlapped) == 0) {
2252 const err = windows.GetLastError();
2253 switch (err) {
2254 else => return windows.unexpectedError(err),
2255 }
2256 }
2257}
2258
2259pub const GetQueuedCompletionStatusResult = enum {
2260 Normal,
2261 Aborted,
2262 Cancelled,
2263 EOF,
2264};
2265
2266pub fn GetQueuedCompletionStatus(
2267 completion_port: windows.HANDLE,
2268 bytes_transferred_count: *windows.DWORD,
2269 lpCompletionKey: *usize,
2270 lpOverlapped: *?*windows.OVERLAPPED,
2271 dwMilliseconds: windows.DWORD,
2272) GetQueuedCompletionStatusResult {
2273 if (windows.GetQueuedCompletionStatus(completion_port, bytes_transferred_count, lpCompletionKey, lpOverlapped, dwMilliseconds) == windows.FALSE) {
2274 switch (windows.GetLastError()) {
2275 windows.ERROR.ABANDONED_WAIT_0 => return GetQueuedCompletionStatusResult.Aborted,
2276 windows.ERROR.OPERATION_ABORTED => return GetQueuedCompletionStatusResult.Cancelled,
2277 windows.ERROR.HANDLE_EOF => return GetQueuedCompletionStatusResult.EOF,
2278 else => |err| {
2279 if (std.debug.runtime_safety) {
2280 std.debug.panic("unexpected error: {}\n", err);
2281 }
2282 },
2283 }
2284 }
2285 return GetQueuedCompletionStatusResult.Normal;
2286}
2287
2288/// Used to convert a slice to a null terminated slice on the stack.
2289/// TODO https://github.com/ziglang/zig/issues/287
2290pub fn toPosixPath(file_path: []const u8) ![PATH_MAX]u8 {
2291 var path_with_null: [PATH_MAX]u8 = undefined;
2292 // >= rather than > to make room for the null byte
2293 if (file_path.len >= PATH_MAX) return error.NameTooLong;
2294 mem.copy(u8, &path_with_null, file_path);
2295 path_with_null[file_path.len] = 0;
2296 return path_with_null;
2297}
2298
2299/// Call this when you made a syscall or something that sets errno
2300/// and you get an unexpected error.
2301pub fn unexpectedErrno(errno: usize) os.UnexpectedError {
2302 if (os.unexpected_error_tracing) {
2303 std.debug.warn("unexpected errno: {}\n", errno);
2304 std.debug.dumpCurrentStackTrace(null);
2305 }
2306 return error.Unexpected;
2307}
std/os/test.zig+22
......@@ -1,5 +1,6 @@
11const std = @import("../std.zig");
22const os = std.os;
3const testing = std.testing;
34const expect = std.testing.expect;
45const io = std.io;
56const mem = std.mem;
......@@ -127,3 +128,24 @@ fn testTls(context: void) void {
127128 x += 1;
128129 if (x != 1235) @panic("bad end value");
129130}
131
132test "getrandom" {
133 var buf_a: [50]u8 = undefined;
134 var buf_b: [50]u8 = undefined;
135 try os.getrandom(&buf_a);
136 try os.getrandom(&buf_b);
137 // If this test fails the chance is significantly higher that there is a bug than
138 // that two sets of 50 bytes were equal.
139 expect(!mem.eql(u8, buf_a, buf_b));
140}
141
142test "getcwd" {
143 // at least call it so it gets compiled
144 var buf: [std.fs.MAX_PATH_BYTES]u8 = undefined;
145 _ = os.getcwd(&buf) catch {};
146}
147
148test "realpath" {
149 var buf: [std.fs.MAX_PATH_BYTES]u8 = undefined;
150 testing.expectError(error.FileNotFound, os.realpath("definitely_bogus_does_not_exist1234", &buf));
151}
std/os/time.zig deleted-307
......@@ -1,307 +0,0 @@
1const std = @import("../std.zig");
2const builtin = @import("builtin");
3const Os = builtin.Os;
4const debug = std.debug;
5const testing = std.testing;
6const math = std.math;
7
8const windows = std.os.windows;
9const linux = std.os.linux;
10const darwin = std.os.darwin;
11const wasi = std.os.wasi;
12const posix = std.os.posix;
13
14pub const epoch = @import("epoch.zig");
15
16/// Spurious wakeups are possible and no precision of timing is guaranteed.
17pub fn sleep(nanoseconds: u64) void {
18 switch (builtin.os) {
19 Os.linux, Os.macosx, Os.ios, Os.freebsd, Os.netbsd => {
20 const s = nanoseconds / ns_per_s;
21 const ns = nanoseconds % ns_per_s;
22 posixSleep(s, ns);
23 },
24 Os.windows => {
25 const ns_per_ms = ns_per_s / ms_per_s;
26 const milliseconds = nanoseconds / ns_per_ms;
27 const ms_that_will_fit = std.math.cast(windows.DWORD, milliseconds) catch std.math.maxInt(windows.DWORD);
28 windows.Sleep(ms_that_will_fit);
29 },
30 else => @compileError("Unsupported OS"),
31 }
32}
33
34/// Spurious wakeups are possible and no precision of timing is guaranteed.
35pub fn posixSleep(seconds: u64, nanoseconds: u64) void {
36 var req = posix.timespec{
37 .tv_sec = std.math.cast(isize, seconds) catch std.math.maxInt(isize),
38 .tv_nsec = std.math.cast(isize, nanoseconds) catch std.math.maxInt(isize),
39 };
40 var rem: posix.timespec = undefined;
41 while (true) {
42 const ret_val = posix.nanosleep(&req, &rem);
43 const err = posix.getErrno(ret_val);
44 switch (err) {
45 posix.EFAULT => unreachable,
46 posix.EINVAL => {
47 // Sometimes Darwin returns EINVAL for no reason.
48 // We treat it as a spurious wakeup.
49 return;
50 },
51 posix.EINTR => {
52 req = rem;
53 continue;
54 },
55 // This prong handles success as well as unexpected errors.
56 else => return,
57 }
58 }
59}
60
61/// Get the posix timestamp, UTC, in seconds
62pub fn timestamp() u64 {
63 return @divFloor(milliTimestamp(), ms_per_s);
64}
65
66/// Get the posix timestamp, UTC, in milliseconds
67pub const milliTimestamp = switch (builtin.os) {
68 Os.windows => milliTimestampWindows,
69 Os.linux, Os.freebsd, Os.netbsd => milliTimestampPosix,
70 Os.macosx, Os.ios => milliTimestampDarwin,
71 Os.wasi => milliTimestampWasi,
72 else => @compileError("Unsupported OS"),
73};
74
75fn milliTimestampWasi() u64 {
76 var ns: wasi.timestamp_t = undefined;
77
78 // TODO: Verify that precision is ignored
79 const err = wasi.clock_time_get(wasi.CLOCK_REALTIME, 1, &ns);
80 debug.assert(err == wasi.ESUCCESS);
81
82 const ns_per_ms = 1000;
83 return @divFloor(ns, ns_per_ms);
84}
85
86fn milliTimestampWindows() u64 {
87 //FileTime has a granularity of 100 nanoseconds
88 // and uses the NTFS/Windows epoch
89 var ft: windows.FILETIME = undefined;
90 windows.GetSystemTimeAsFileTime(&ft);
91 const hns_per_ms = (ns_per_s / 100) / ms_per_s;
92 const epoch_adj = epoch.windows * ms_per_s;
93
94 const ft64 = (u64(ft.dwHighDateTime) << 32) | ft.dwLowDateTime;
95 return @divFloor(ft64, hns_per_ms) - -epoch_adj;
96}
97
98fn milliTimestampDarwin() u64 {
99 var tv: darwin.timeval = undefined;
100 var err = darwin.gettimeofday(&tv, null);
101 debug.assert(err == 0);
102 const sec_ms = tv.tv_sec * ms_per_s;
103 const usec_ms = @divFloor(tv.tv_usec, us_per_s / ms_per_s);
104 return @intCast(u64, sec_ms + usec_ms);
105}
106
107fn milliTimestampPosix() u64 {
108 //From what I can tell there's no reason clock_gettime
109 // should ever fail for us with CLOCK_REALTIME,
110 // seccomp aside.
111 var ts: posix.timespec = undefined;
112 const err = posix.clock_gettime(posix.CLOCK_REALTIME, &ts);
113 debug.assert(err == 0);
114 const sec_ms = @intCast(u64, ts.tv_sec) * ms_per_s;
115 const nsec_ms = @divFloor(@intCast(u64, ts.tv_nsec), ns_per_s / ms_per_s);
116 return sec_ms + nsec_ms;
117}
118
119/// Multiples of a base unit (nanoseconds)
120pub const nanosecond = 1;
121pub const microsecond = 1000 * nanosecond;
122pub const millisecond = 1000 * microsecond;
123pub const second = 1000 * millisecond;
124pub const minute = 60 * second;
125pub const hour = 60 * minute;
126
127/// Divisions of a second
128pub const ns_per_s = 1000000000;
129pub const us_per_s = 1000000;
130pub const ms_per_s = 1000;
131pub const cs_per_s = 100;
132
133/// Common time divisions
134pub const s_per_min = 60;
135pub const s_per_hour = s_per_min * 60;
136pub const s_per_day = s_per_hour * 24;
137pub const s_per_week = s_per_day * 7;
138
139/// A monotonic high-performance timer.
140/// Timer.start() must be called to initialize the struct, which captures
141/// the counter frequency on windows and darwin, records the resolution,
142/// and gives the user an opportunity to check for the existnece of
143/// monotonic clocks without forcing them to check for error on each read.
144/// .resolution is in nanoseconds on all platforms but .start_time's meaning
145/// depends on the OS. On Windows and Darwin it is a hardware counter
146/// value that requires calculation to convert to a meaninful unit.
147pub const Timer = struct {
148
149 //if we used resolution's value when performing the
150 // performance counter calc on windows/darwin, it would
151 // be less precise
152 frequency: switch (builtin.os) {
153 Os.windows => u64,
154 Os.macosx, Os.ios => darwin.mach_timebase_info_data,
155 else => void,
156 },
157 resolution: u64,
158 start_time: u64,
159
160 //At some point we may change our minds on RAW, but for now we're
161 // sticking with posix standard MONOTONIC. For more information, see:
162 // https://github.com/ziglang/zig/pull/933
163 //
164 //const monotonic_clock_id = switch(builtin.os) {
165 // Os.linux => linux.CLOCK_MONOTONIC_RAW,
166 // else => posix.CLOCK_MONOTONIC,
167 //};
168 const monotonic_clock_id = posix.CLOCK_MONOTONIC;
169 /// Initialize the timer structure.
170 //This gives us an opportunity to grab the counter frequency in windows.
171 //On Windows: QueryPerformanceCounter will succeed on anything >= XP/2000.
172 //On Posix: CLOCK_MONOTONIC will only fail if the monotonic counter is not
173 // supported, or if the timespec pointer is out of bounds, which should be
174 // impossible here barring cosmic rays or other such occurrences of
175 // incredibly bad luck.
176 //On Darwin: This cannot fail, as far as I am able to tell.
177 const TimerError = error{
178 TimerUnsupported,
179 Unexpected,
180 };
181 pub fn start() TimerError!Timer {
182 var self: Timer = undefined;
183
184 switch (builtin.os) {
185 Os.windows => {
186 var freq: i64 = undefined;
187 var err = windows.QueryPerformanceFrequency(&freq);
188 if (err == windows.FALSE) return error.TimerUnsupported;
189 self.frequency = @intCast(u64, freq);
190 self.resolution = @divFloor(ns_per_s, self.frequency);
191
192 var start_time: i64 = undefined;
193 err = windows.QueryPerformanceCounter(&start_time);
194 debug.assert(err != windows.FALSE);
195 self.start_time = @intCast(u64, start_time);
196 },
197 Os.linux, Os.freebsd, Os.netbsd => {
198 //On Linux, seccomp can do arbitrary things to our ability to call
199 // syscalls, including return any errno value it wants and
200 // inconsistently throwing errors. Since we can't account for
201 // abuses of seccomp in a reasonable way, we'll assume that if
202 // seccomp is going to block us it will at least do so consistently
203 var ts: posix.timespec = undefined;
204 var result = posix.clock_getres(monotonic_clock_id, &ts);
205 var errno = posix.getErrno(result);
206 switch (errno) {
207 0 => {},
208 posix.EINVAL => return error.TimerUnsupported,
209 else => return std.os.unexpectedErrorPosix(errno),
210 }
211 self.resolution = @intCast(u64, ts.tv_sec) * u64(ns_per_s) + @intCast(u64, ts.tv_nsec);
212
213 result = posix.clock_gettime(monotonic_clock_id, &ts);
214 errno = posix.getErrno(result);
215 if (errno != 0) return std.os.unexpectedErrorPosix(errno);
216 self.start_time = @intCast(u64, ts.tv_sec) * u64(ns_per_s) + @intCast(u64, ts.tv_nsec);
217 },
218 Os.macosx, Os.ios => {
219 darwin.mach_timebase_info(&self.frequency);
220 self.resolution = @divFloor(self.frequency.numer, self.frequency.denom);
221 self.start_time = darwin.mach_absolute_time();
222 },
223 else => @compileError("Unsupported OS"),
224 }
225 return self;
226 }
227
228 /// Reads the timer value since start or the last reset in nanoseconds
229 pub fn read(self: *Timer) u64 {
230 var clock = clockNative() - self.start_time;
231 return switch (builtin.os) {
232 Os.windows => @divFloor(clock * ns_per_s, self.frequency),
233 Os.linux, Os.freebsd, Os.netbsd => clock,
234 Os.macosx, Os.ios => @divFloor(clock * self.frequency.numer, self.frequency.denom),
235 else => @compileError("Unsupported OS"),
236 };
237 }
238
239 /// Resets the timer value to 0/now.
240 pub fn reset(self: *Timer) void {
241 self.start_time = clockNative();
242 }
243
244 /// Returns the current value of the timer in nanoseconds, then resets it
245 pub fn lap(self: *Timer) u64 {
246 var now = clockNative();
247 var lap_time = self.read();
248 self.start_time = now;
249 return lap_time;
250 }
251
252 const clockNative = switch (builtin.os) {
253 Os.windows => clockWindows,
254 Os.linux, Os.freebsd, Os.netbsd => clockLinux,
255 Os.macosx, Os.ios => clockDarwin,
256 else => @compileError("Unsupported OS"),
257 };
258
259 fn clockWindows() u64 {
260 var result: i64 = undefined;
261 var err = windows.QueryPerformanceCounter(&result);
262 debug.assert(err != windows.FALSE);
263 return @intCast(u64, result);
264 }
265
266 fn clockDarwin() u64 {
267 return darwin.mach_absolute_time();
268 }
269
270 fn clockLinux() u64 {
271 var ts: posix.timespec = undefined;
272 var result = posix.clock_gettime(monotonic_clock_id, &ts);
273 debug.assert(posix.getErrno(result) == 0);
274 return @intCast(u64, ts.tv_sec) * u64(ns_per_s) + @intCast(u64, ts.tv_nsec);
275 }
276};
277
278test "os.time.sleep" {
279 sleep(1);
280}
281
282test "os.time.timestamp" {
283 const ns_per_ms = (ns_per_s / ms_per_s);
284 const margin = 50;
285
286 const time_0 = milliTimestamp();
287 sleep(ns_per_ms);
288 const time_1 = milliTimestamp();
289 const interval = time_1 - time_0;
290 testing.expect(interval > 0 and interval < margin);
291}
292
293test "os.time.Timer" {
294 const ns_per_ms = (ns_per_s / ms_per_s);
295 const margin = ns_per_ms * 150;
296
297 var timer = try Timer.start();
298 sleep(10 * ns_per_ms);
299 const time_0 = timer.read();
300 testing.expect(time_0 > 0 and time_0 < margin);
301
302 const time_1 = timer.lap();
303 testing.expect(time_1 >= time_0);
304
305 timer.reset();
306 testing.expect(timer.read() < time_1);
307}
std/os/windows.zig+449-3
......@@ -535,7 +535,7 @@ pub const COINIT = extern enum {
535535/// from https://docs.microsoft.com/en-us/windows/desktop/FileIO/naming-a-file#maximum-path-length-limitation
536536pub const PATH_MAX_WIDE = 32767;
537537
538pub const OpenError = error{
538pub const CreateFileError = error{
539539 SharingViolation,
540540 PathAlreadyExists,
541541
......@@ -565,7 +565,7 @@ pub fn CreateFile(
565565 share_mode: DWORD,
566566 creation_disposition: DWORD,
567567 flags_and_attrs: DWORD,
568) OpenError!fd_t {
568) CreateFileError!fd_t {
569569 const file_path_w = try sliceToPrefixedFileW(file_path);
570570 return CreateFileW(&file_path_w, desired_access, share_mode, creation_disposition, flags_and_attrs);
571571}
......@@ -576,7 +576,7 @@ pub fn CreateFileW(
576576 share_mode: DWORD,
577577 creation_disposition: DWORD,
578578 flags_and_attrs: DWORD,
579) OpenError!HANDLE {
579) CreateFileError!HANDLE {
580580 const result = kernel32.CreateFileW(file_path_w, desired_access, share_mode, null, creation_disposition, flags_and_attrs, null);
581581
582582 if (result == INVALID_HANDLE_VALUE) {
......@@ -588,6 +588,7 @@ pub fn CreateFileW(
588588 ERROR.PATH_NOT_FOUND => return error.FileNotFound,
589589 ERROR.ACCESS_DENIED => return error.AccessDenied,
590590 ERROR.PIPE_BUSY => return error.PipeBusy,
591 ERROR.FILENAME_EXCED_RANGE => return error.NameTooLong,
591592 else => |err| return unexpectedErrorWindows(err),
592593 }
593594 }
......@@ -615,6 +616,451 @@ fn SetHandleInformation(h: HANDLE, mask: DWORD, flags: DWORD) SetHandleInformati
615616 }
616617}
617618
619pub const RtlGenRandomError = error{Unexpected};
620
621/// Call RtlGenRandom() instead of CryptGetRandom() on Windows
622/// https://github.com/rust-lang-nursery/rand/issues/111
623/// https://bugzilla.mozilla.org/show_bug.cgi?id=504270
624pub fn RtlGenRandom(output: []u8) RtlGenRandomError!void {
625 if (advapi32.RtlGenRandom(output.ptr, output.len) == 0) {
626 switch (kernel32.GetLastError()) {
627 else => |err| return unexpectedError(err),
628 }
629 }
630}
631
632pub const WaitForSingleObjectError = error{
633 WaitAbandoned,
634 WaitTimeOut,
635 Unexpected,
636};
637
638pub fn WaitForSingleObject(handle: HANDLE, milliseconds: DWORD) WaitForSingleObjectError!void {
639 switch (kernel32.WaitForSingleObject(handle, milliseconds)) {
640 WAIT_ABANDONED => return error.WaitAbandoned,
641 WAIT_OBJECT_0 => return,
642 WAIT_TIMEOUT => return error.WaitTimeOut,
643 WAIT_FAILED => switch (kernel32.GetLastError()) {
644 else => |err| return unexpectedError(err),
645 },
646 else => return error.Unexpected,
647 }
648}
649
650pub const FindFirstFileError = error{
651 FileNotFound,
652 Unexpected,
653};
654
655pub fn FindFirstFile(dir_path: []const u8, find_file_data: *WIN32_FIND_DATAW) FindFirstFileError!HANDLE {
656 const dir_path_w = try sliceToPrefixedSuffixedFileW(dir_path, []u16{ '\\', '*', 0 });
657 const handle = kernel32.FindFirstFileW(&dir_path_w, find_file_data);
658
659 if (handle == INVALID_HANDLE_VALUE) {
660 switch (kernel32.GetLastError()) {
661 ERROR.FILE_NOT_FOUND => return error.FileNotFound,
662 ERROR.PATH_NOT_FOUND => return error.FileNotFound,
663 else => |err| return unexpectedError(err),
664 }
665 }
666
667 return handle;
668}
669
670pub const FindNextFileError = error{Unexpected};
671
672/// Returns `true` if there was another file, `false` otherwise.
673pub fn FindNextFile(handle: HANDLE, find_file_data: *WIN32_FIND_DATAW) FindNextFileError!bool {
674 if (kernel32.FindNextFileW(handle, find_file_data) == 0) {
675 switch (kernel32.GetLastError()) {
676 ERROR.NO_MORE_FILES => return false,
677 else => |err| return unexpectedError(err),
678 }
679 }
680 return true;
681}
682
683pub const CreateIoCompletionPortError = error{Unexpected};
684
685pub fn CreateIoCompletionPort(
686 file_handle: HANDLE,
687 existing_completion_port: ?HANDLE,
688 completion_key: usize,
689 concurrent_thread_count: DWORD,
690) CreateIoCompletionPortError!HANDLE {
691 const handle = kernel32.CreateIoCompletionPort(file_handle, existing_completion_port, completion_key, concurrent_thread_count) orelse {
692 switch (kernel32.GetLastError()) {
693 ERROR.INVALID_PARAMETER => unreachable,
694 else => |err| return unexpectedError(err),
695 }
696 };
697 return handle;
698}
699
700pub const PostQueuedCompletionStatusError = error{Unexpected};
701
702pub fn PostQueuedCompletionStatus(
703 completion_port: HANDLE,
704 bytes_transferred_count: DWORD,
705 completion_key: usize,
706 lpOverlapped: ?*OVERLAPPED,
707) PostQueuedCompletionStatusError!void {
708 if (kernel32.PostQueuedCompletionStatus(completion_port, bytes_transferred_count, completion_key, lpOverlapped) == 0) {
709 switch (kernel32.GetLastError()) {
710 else => return unexpectedError(err),
711 }
712 }
713}
714
715pub const GetQueuedCompletionStatusResult = enum {
716 Normal,
717 Aborted,
718 Cancelled,
719 EOF,
720};
721
722pub fn GetQueuedCompletionStatus(
723 completion_port: HANDLE,
724 bytes_transferred_count: *DWORD,
725 lpCompletionKey: *usize,
726 lpOverlapped: *?*OVERLAPPED,
727 dwMilliseconds: DWORD,
728) GetQueuedCompletionStatusResult {
729 if (kernel32.GetQueuedCompletionStatus(
730 completion_port,
731 bytes_transferred_count,
732 lpCompletionKey,
733 lpOverlapped,
734 dwMilliseconds,
735 ) == FALSE) {
736 switch (kernel32.GetLastError()) {
737 ERROR.ABANDONED_WAIT_0 => return GetQueuedCompletionStatusResult.Aborted,
738 ERROR.OPERATION_ABORTED => return GetQueuedCompletionStatusResult.Cancelled,
739 ERROR.HANDLE_EOF => return GetQueuedCompletionStatusResult.EOF,
740 else => |err| {
741 if (std.debug.runtime_safety) {
742 std.debug.panic("unexpected error: {}\n", err);
743 }
744 },
745 }
746 }
747 return GetQueuedCompletionStatusResult.Normal;
748}
749
750pub fn CloseHandle(hObject: HANDLE) void {
751 assert(kernel32.CloseHandle(hObject) != 0);
752}
753
754pub const ReadFileError = error{Unexpected};
755
756pub fn ReadFile(in_hFile: HANDLE, buffer: []u8) ReadFileError!usize {
757 var index: usize = 0;
758 while (index < buffer.len) {
759 const want_read_count = @intCast(DWORD, math.min(DWORD(maxInt(DWORD)), buffer.len - index));
760 var amt_read: DWORD = undefined;
761 if (kernel32.ReadFile(fd, buffer.ptr + index, want_read_count, &amt_read, null) == 0) {
762 switch (kernel32.GetLastError()) {
763 ERROR.OPERATION_ABORTED => continue,
764 ERROR.BROKEN_PIPE => return index,
765 else => |err| return unexpectedError(err),
766 }
767 }
768 if (amt_read == 0) return index;
769 index += amt_read;
770 }
771 return index;
772}
773
774pub const WriteFileError = error{
775 SystemResources,
776 OperationAborted,
777 BrokenPipe,
778 Unexpected,
779};
780
781/// This function is for blocking file descriptors only. For non-blocking, see
782/// `WriteFileAsync`.
783pub fn WriteFile(in_hFile: HANDLE, bytes: []const u8) WriteFileError!void {
784 var bytes_written: DWORD = undefined;
785 // TODO replace this @intCast with a loop that writes all the bytes
786 if (kernel32.WriteFile(handle, bytes.ptr, @intCast(u32, bytes.len), &bytes_written, null) == 0) {
787 switch (kernel32.GetLastError()) {
788 ERROR.INVALID_USER_BUFFER => return error.SystemResources,
789 ERROR.NOT_ENOUGH_MEMORY => return error.SystemResources,
790 ERROR.OPERATION_ABORTED => return error.OperationAborted,
791 ERROR.NOT_ENOUGH_QUOTA => return error.SystemResources,
792 ERROR.IO_PENDING => unreachable, // this function is for blocking files only
793 ERROR.BROKEN_PIPE => return error.BrokenPipe,
794 else => |err| return unexpectedError(err),
795 }
796 }
797}
798
799pub const GetCurrentDirectoryError = error{
800 NameTooLong,
801 Unexpected,
802};
803
804/// The result is a slice of out_buffer, indexed from 0.
805pub fn GetCurrentDirectory(buffer: []u8) GetCurrentDirectoryError![]u8 {
806 var utf16le_buf: [PATH_MAX_WIDE]u16 = undefined;
807 const result = kernel32.GetCurrentDirectoryW(utf16le_buf.len, &utf16le_buf);
808 if (result == 0) {
809 switch (kernel32.GetLastError()) {
810 else => |err| return unexpectedError(err),
811 }
812 }
813 assert(result <= utf16le_buf.len);
814 const utf16le_slice = utf16le_buf[0..result];
815 // Trust that Windows gives us valid UTF-16LE.
816 var end_index: usize = 0;
817 var it = std.unicode.Utf16LeIterator.init(utf16le);
818 while (it.nextCodepoint() catch unreachable) |codepoint| {
819 if (end_index + std.unicode.utf8CodepointSequenceLength(codepoint) >= out_buffer.len)
820 return error.NameTooLong;
821 end_index += utf8Encode(codepoint, out_buffer[end_index..]) catch unreachable;
822 }
823 return out_buffer[0..end_index];
824}
825
826pub const CreateSymbolicLinkError = error{Unexpected};
827
828pub fn CreateSymbolicLink(
829 sym_link_path: []const u8,
830 target_path: []const u8,
831 flags: DWORD,
832) CreateSymbolicLinkError!void {
833 const sym_link_path_w = try sliceToPrefixedFileW(sym_link_path);
834 const target_path_w = try sliceToPrefixedFileW(target_path);
835 return CreateSymbolicLinkW(&sym_link_path_w, &target_path_w, flags);
836}
837
838pub fn CreateSymbolicLinkW(
839 sym_link_path: [*]const u16,
840 target_path: [*]const u16,
841 flags: DWORD,
842) CreateSymbolicLinkError!void {
843 if (kernel32.CreateSymbolicLinkW(sym_link_path, target_path, flags) == 0) {
844 switch (kernel32.GetLastError()) {
845 else => |err| return kernel32.unexpectedError(err),
846 }
847 }
848}
849
850pub const DeleteFileError = error{
851 FileNotFound,
852 AccessDenied,
853 NameTooLong,
854 Unexpected,
855};
856
857pub fn DeleteFile(filename: []const u8) DeleteFileError!void {
858 const filename_w = try sliceToPrefixedFileW(filename);
859 return DeleteFileW(&filename_w);
860}
861
862pub fn DeleteFileW(filename: [*]const u16) DeleteFileError!void {
863 if (kernel32.DeleteFileW(file_path) == 0) {
864 switch (kernel32.GetLastError()) {
865 ERROR.FILE_NOT_FOUND => return error.FileNotFound,
866 ERROR.ACCESS_DENIED => return error.AccessDenied,
867 ERROR.FILENAME_EXCED_RANGE => return error.NameTooLong,
868 ERROR.INVALID_PARAMETER => return error.NameTooLong,
869 else => |err| return unexpectedError(err),
870 }
871 }
872}
873
874pub const MoveFileError = error{Unexpected};
875
876pub fn MoveFileEx(old_path: []const u8, new_path: []const u8, flags: DWORD) MoveFileError!void {
877 const old_path_w = try sliceToPrefixedFileW(old_path);
878 const new_path_w = try sliceToPrefixedFileW(new_path);
879 return MoveFileExW(&old_path_w, &new_path_w, flags);
880}
881
882pub fn MoveFileExW(old_path: [*]const u16, new_path: [*]const u16, flags: DWORD) MoveFileError!void {
883 if (kernel32.MoveFileExW(old_path, new_path, flags) == 0) {
884 switch (kernel32.GetLastError()) {
885 else => |err| return unexpectedError(err),
886 }
887 }
888}
889
890pub const CreateDirectoryError = error{
891 PathAlreadyExists,
892 FileNotFound,
893 Unexpected,
894};
895
896pub fn CreateDirectory(pathname: []const u8, attrs: ?*SECURITY_ATTRIBUTES) CreateDirectoryError!void {
897 const pathname_w = try sliceToPrefixedFileW(pathname);
898 return CreateDirectoryW(&pathname_w, attrs);
899}
900
901pub fn CreateDirectoryW(pathname: [*]const u16, attrs: ?*SECURITY_ATTRIBUTES) CreateDirectoryError!void {
902 if (kernel32.CreateDirectoryW(pathname, attrs) == 0) {
903 switch (kernel32.GetLastError()) {
904 ERROR.ALREADY_EXISTS => return error.PathAlreadyExists,
905 ERROR.PATH_NOT_FOUND => return error.FileNotFound,
906 else => |err| return unexpectedError(err),
907 }
908 }
909}
910
911pub const RemoveDirectoryError = error{
912 FileNotFound,
913 DirNotEmpty,
914 Unexpected,
915};
916
917pub fn RemoveDirectory(dir_path: []const u8) RemoveDirectoryError!void {
918 const dir_path_w = try sliceToPrefixedFileW(dir_path);
919 return RemoveDirectoryW(&dir_path_w);
920}
921
922pub fn RemoveDirectoryW(dir_path_w: [*]const u16) RemoveDirectoryError!void {
923 if (kernel32.RemoveDirectoryW(dir_path_w) == 0) {
924 switch (kernel32.GetLastError()) {
925 ERROR.PATH_NOT_FOUND => return error.FileNotFound,
926 ERROR.DIR_NOT_EMPTY => return error.DirNotEmpty,
927 else => |err| return unexpectedError(err),
928 }
929 }
930}
931
932pub const GetStdHandleError = error{
933 NoStandardHandleAttached,
934 Unexpected,
935};
936
937pub fn GetStdHandle(handle_id: DWORD) GetStdHandleError!fd_t {
938 const handle = kernel32.GetStdHandle(handle_id) orelse return error.NoStandardHandleAttached;
939 if (handle == INVALID_HANDLE_VALUE) {
940 switch (kernel32.GetLastError()) {
941 else => |err| return unexpectedError(err),
942 }
943 }
944 return handle;
945}
946
947pub const SetFilePointerError = error{Unexpected};
948
949/// The SetFilePointerEx function with the `dwMoveMethod` parameter set to `FILE_BEGIN`.
950pub fn SetFilePointerEx_BEGIN(handle: HANDLE, offset: u64) SetFilePointerError!void {
951 // "The starting point is zero or the beginning of the file. If [FILE_BEGIN]
952 // is specified, then the liDistanceToMove parameter is interpreted as an unsigned value."
953 // https://docs.microsoft.com/en-us/windows/desktop/api/fileapi/nf-fileapi-setfilepointerex
954 const ipos = @bitCast(LARGE_INTEGER, offset);
955 if (kernel32.SetFilePointerEx(handle, ipos, null, FILE_BEGIN) == 0) {
956 switch (kernel32.GetLastError()) {
957 ERROR.INVALID_PARAMETER => unreachable,
958 ERROR.INVALID_HANDLE => unreachable,
959 else => |err| return unexpectedError(err),
960 }
961 }
962}
963
964/// The SetFilePointerEx function with the `dwMoveMethod` parameter set to `FILE_CURRENT`.
965pub fn SetFilePointerEx_CURRENT(handle: HANDLE, offset: i64) SetFilePointerError!void {
966 if (kernel32.SetFilePointerEx(handle, offset, null, FILE_CURRENT) == 0) {
967 switch (kernel32.GetLastError()) {
968 ERROR.INVALID_PARAMETER => unreachable,
969 ERROR.INVALID_HANDLE => unreachable,
970 else => |err| return unexpectedError(err),
971 }
972 }
973}
974
975/// The SetFilePointerEx function with the `dwMoveMethod` parameter set to `FILE_END`.
976pub fn SetFilePointerEx_END(handle: HANDLE, offset: i64) SetFilePointerError!void {
977 if (kernel32.SetFilePointerEx(handle, offset, null, FILE_END) == 0) {
978 switch (kernel32.GetLastError()) {
979 ERROR.INVALID_PARAMETER => unreachable,
980 ERROR.INVALID_HANDLE => unreachable,
981 else => |err| return unexpectedError(err),
982 }
983 }
984}
985
986/// The SetFilePointerEx function with parameters to get the current offset.
987pub fn SetFilePointerEx_CURRENT_get(handle: HANDLE) SetFilePointerError!u64 {
988 var result: LARGE_INTEGER = undefined;
989 if (kernel32.SetFilePointerEx(handle, 0, &result, FILE_CURRENT) == 0) {
990 switch (kernel32.GetLastError()) {
991 ERROR.INVALID_PARAMETER => unreachable,
992 ERROR.INVALID_HANDLE => unreachable,
993 else => |err| return unexpectedError(err),
994 }
995 }
996 // Based on the docs for FILE_BEGIN, it seems that the returned signed integer
997 // should be interpreted as an unsigned integer.
998 return @bitCast(u64, result);
999}
1000
1001pub const GetFinalPathNameByHandleError = error{
1002 FileNotFound,
1003 SystemResources,
1004 NameTooLong,
1005 Unexpected,
1006};
1007
1008pub fn GetFinalPathNameByHandleW(
1009 hFile: HANDLE,
1010 buf_ptr: [*]u16,
1011 buf_len: DWORD,
1012 flags: DWORD,
1013) GetFinalPathNameByHandleError!DWORD {
1014 const rc = kernel32.GetFinalPathNameByHandleW(h_file, buf_ptr, buf.len, flags);
1015 if (rc == 0) {
1016 switch (kernel32.GetLastError()) {
1017 ERROR.FILE_NOT_FOUND => return error.FileNotFound,
1018 ERROR.PATH_NOT_FOUND => return error.FileNotFound,
1019 ERROR.NOT_ENOUGH_MEMORY => return error.SystemResources,
1020 ERROR.FILENAME_EXCED_RANGE => return error.NameTooLong,
1021 ERROR.INVALID_PARAMETER => unreachable,
1022 else => |err| return unexpectedError(err),
1023 }
1024 }
1025 return rc;
1026}
1027
1028pub const GetFileSizeError = error{Unexpected};
1029
1030pub fn GetFileSizeEx(hFile: HANDLE) GetFileSizeError!u64 {
1031 var file_size: LARGE_INTEGER = undefined;
1032 if (kernel32.GetFileSizeEx(hFile, &file_size) == 0) {
1033 switch (kernel32.GetLastError()) {
1034 else => |err| return unexpectedError(err),
1035 }
1036 }
1037 return @bitCast(u64, file_size);
1038}
1039
1040pub const GetFileAttributesError = error{
1041 FileNotFound,
1042 PermissionDenied,
1043 Unexpected,
1044};
1045
1046pub fn GetFileAttributes(filename: []const u8) GetFileAttributesError!DWORD {
1047 const filename_w = try sliceToPrefixedFileW(filename);
1048 return GetFileAttributesW(&filename_w);
1049}
1050
1051pub fn GetFileAttributesW(lpFileName: [*]const u16) GetFileAttributesError!DWORD {
1052 const rc = kernel32.GetFileAttributesW(path);
1053 if (rc == INVALID_FILE_ATTRIBUTES) {
1054 switch (kernel32.GetLastError()) {
1055 ERROR.FILE_NOT_FOUND => return error.FileNotFound,
1056 ERROR.PATH_NOT_FOUND => return error.FileNotFound,
1057 ERROR.ACCESS_DENIED => return error.PermissionDenied,
1058 else => |err| return unexpectedError(err),
1059 }
1060 }
1061 return rc;
1062}
1063
6181064pub fn cStrToPrefixedFileW(s: [*]const u8) ![PATH_MAX_WIDE + 1]u16 {
6191065 return sliceToPrefixedFileW(mem.toSliceConst(u8, s));
6201066}
std/packed_int_array.zig+3-3
......@@ -619,7 +619,7 @@ test "PackedIntArray at end of available memory" {
619619 const PackedArray = PackedIntArray(u3, 8);
620620
621621 const Padded = struct {
622 _: [std.os.page_size - @sizeOf(PackedArray)]u8,
622 _: [std.mem.page_size - @sizeOf(PackedArray)]u8,
623623 p: PackedArray,
624624 };
625625
......@@ -641,9 +641,9 @@ test "PackedIntSlice at end of available memory" {
641641 var da = std.heap.DirectAllocator.init();
642642 const allocator = &da.allocator;
643643
644 var page = try allocator.alloc(u8, std.os.page_size);
644 var page = try allocator.alloc(u8, std.mem.page_size);
645645 defer allocator.free(page);
646646
647 var p = PackedSlice.init(page[std.os.page_size - 2 ..], 1);
647 var p = PackedSlice.init(page[std.mem.page_size - 2 ..], 1);
648648 p.set(0, std.math.maxInt(u11));
649649}
std/pdb.zig+1-2
......@@ -660,8 +660,7 @@ const MsfStream = struct {
660660 return size;
661661 }
662662
663 // XXX: The `len` parameter should be signed
664 fn seekForward(self: *MsfStream, len: u64) !void {
663 fn seekBy(self: *MsfStream, len: i64) !void {
665664 self.pos += len;
666665 if (self.pos >= self.blocks.len * self.block_size)
667666 return error.EOF;
std/process.zig created+583
......@@ -0,0 +1,583 @@
1const std = @import("std.zig");
2const posix = std.os.posix;
3const BufMap = std.BufMap;
4const mem = std.mem;
5const Allocator = mem.Allocator;
6const assert = std.debug.assert;
7const testing = std.testing;
8
9pub const abort = posix.abort;
10pub const exit = posix.exit;
11
12/// Caller must free result when done.
13/// TODO make this go through libc when we have it
14pub fn getEnvMap(allocator: *Allocator) !BufMap {
15 var result = BufMap.init(allocator);
16 errdefer result.deinit();
17
18 if (is_windows) {
19 const ptr = windows.GetEnvironmentStringsW() orelse return error.OutOfMemory;
20 defer assert(windows.FreeEnvironmentStringsW(ptr) != 0);
21
22 var i: usize = 0;
23 while (true) {
24 if (ptr[i] == 0) return result;
25
26 const key_start = i;
27
28 while (ptr[i] != 0 and ptr[i] != '=') : (i += 1) {}
29 const key_w = ptr[key_start..i];
30 const key = try std.unicode.utf16leToUtf8Alloc(allocator, key_w);
31 errdefer allocator.free(key);
32
33 if (ptr[i] == '=') i += 1;
34
35 const value_start = i;
36 while (ptr[i] != 0) : (i += 1) {}
37 const value_w = ptr[value_start..i];
38 const value = try std.unicode.utf16leToUtf8Alloc(allocator, value_w);
39 errdefer allocator.free(value);
40
41 i += 1; // skip over null byte
42
43 try result.setMove(key, value);
44 }
45 } else if (builtin.os == Os.wasi) {
46 var environ_count: usize = undefined;
47 var environ_buf_size: usize = undefined;
48
49 const environ_sizes_get_ret = std.os.wasi.environ_sizes_get(&environ_count, &environ_buf_size);
50 if (environ_sizes_get_ret != os.wasi.ESUCCESS) {
51 return unexpectedErrorPosix(environ_sizes_get_ret);
52 }
53
54 // TODO: Verify that the documentation is incorrect
55 // https://github.com/WebAssembly/WASI/issues/27
56 var environ = try allocator.alloc(?[*]u8, environ_count + 1);
57 defer allocator.free(environ);
58 var environ_buf = try std.heap.wasm_allocator.alloc(u8, environ_buf_size);
59 defer allocator.free(environ_buf);
60
61 const environ_get_ret = std.os.wasi.environ_get(environ.ptr, environ_buf.ptr);
62 if (environ_get_ret != os.wasi.ESUCCESS) {
63 return unexpectedErrorPosix(environ_get_ret);
64 }
65
66 for (environ) |env| {
67 if (env) |ptr| {
68 const pair = mem.toSlice(u8, ptr);
69 var parts = mem.separate(pair, "=");
70 const key = parts.next().?;
71 const value = parts.next().?;
72 try result.set(key, value);
73 }
74 }
75 return result;
76 } else {
77 for (posix.environ) |ptr| {
78 var line_i: usize = 0;
79 while (ptr[line_i] != 0 and ptr[line_i] != '=') : (line_i += 1) {}
80 const key = ptr[0..line_i];
81
82 var end_i: usize = line_i;
83 while (ptr[end_i] != 0) : (end_i += 1) {}
84 const value = ptr[line_i + 1 .. end_i];
85
86 try result.set(key, value);
87 }
88 return result;
89 }
90}
91
92test "os.getEnvMap" {
93 var env = try getEnvMap(std.debug.global_allocator);
94 defer env.deinit();
95}
96
97pub const GetEnvVarOwnedError = error{
98 OutOfMemory,
99 EnvironmentVariableNotFound,
100
101 /// See https://github.com/ziglang/zig/issues/1774
102 InvalidUtf8,
103};
104
105/// Caller must free returned memory.
106/// TODO make this go through libc when we have it
107pub fn getEnvVarOwned(allocator: *mem.Allocator, key: []const u8) GetEnvVarOwnedError![]u8 {
108 if (is_windows) {
109 const key_with_null = try std.unicode.utf8ToUtf16LeWithNull(allocator, key);
110 defer allocator.free(key_with_null);
111
112 var buf = try allocator.alloc(u16, 256);
113 defer allocator.free(buf);
114
115 while (true) {
116 const windows_buf_len = math.cast(windows.DWORD, buf.len) catch return error.OutOfMemory;
117 const result = windows.GetEnvironmentVariableW(key_with_null.ptr, buf.ptr, windows_buf_len);
118
119 if (result == 0) {
120 const err = windows.GetLastError();
121 return switch (err) {
122 windows.ERROR.ENVVAR_NOT_FOUND => error.EnvironmentVariableNotFound,
123 else => {
124 windows.unexpectedError(err) catch {};
125 return error.EnvironmentVariableNotFound;
126 },
127 };
128 }
129
130 if (result > buf.len) {
131 buf = try allocator.realloc(buf, result);
132 continue;
133 }
134
135 return std.unicode.utf16leToUtf8Alloc(allocator, buf) catch |err| switch (err) {
136 error.DanglingSurrogateHalf => return error.InvalidUtf8,
137 error.ExpectedSecondSurrogateHalf => return error.InvalidUtf8,
138 error.UnexpectedSecondSurrogateHalf => return error.InvalidUtf8,
139 error.OutOfMemory => return error.OutOfMemory,
140 };
141 }
142 } else {
143 const result = getEnvPosix(key) orelse return error.EnvironmentVariableNotFound;
144 return mem.dupe(allocator, u8, result);
145 }
146}
147
148test "os.getEnvVarOwned" {
149 var ga = std.debug.global_allocator;
150 testing.expectError(error.EnvironmentVariableNotFound, getEnvVarOwned(ga, "BADENV"));
151}
152
153pub const ArgIteratorPosix = struct {
154 index: usize,
155 count: usize,
156
157 pub fn init() ArgIteratorPosix {
158 return ArgIteratorPosix{
159 .index = 0,
160 .count = raw.len,
161 };
162 }
163
164 pub fn next(self: *ArgIteratorPosix) ?[]const u8 {
165 if (self.index == self.count) return null;
166
167 const s = raw[self.index];
168 self.index += 1;
169 return cstr.toSlice(s);
170 }
171
172 pub fn skip(self: *ArgIteratorPosix) bool {
173 if (self.index == self.count) return false;
174
175 self.index += 1;
176 return true;
177 }
178
179 /// This is marked as public but actually it's only meant to be used
180 /// internally by zig's startup code.
181 pub var raw: [][*]u8 = undefined;
182};
183
184pub const ArgIteratorWindows = struct {
185 index: usize,
186 cmd_line: [*]const u8,
187 in_quote: bool,
188 quote_count: usize,
189 seen_quote_count: usize,
190
191 pub const NextError = error{OutOfMemory};
192
193 pub fn init() ArgIteratorWindows {
194 return initWithCmdLine(windows.GetCommandLineA());
195 }
196
197 pub fn initWithCmdLine(cmd_line: [*]const u8) ArgIteratorWindows {
198 return ArgIteratorWindows{
199 .index = 0,
200 .cmd_line = cmd_line,
201 .in_quote = false,
202 .quote_count = countQuotes(cmd_line),
203 .seen_quote_count = 0,
204 };
205 }
206
207 /// You must free the returned memory when done.
208 pub fn next(self: *ArgIteratorWindows, allocator: *Allocator) ?(NextError![]u8) {
209 // march forward over whitespace
210 while (true) : (self.index += 1) {
211 const byte = self.cmd_line[self.index];
212 switch (byte) {
213 0 => return null,
214 ' ', '\t' => continue,
215 else => break,
216 }
217 }
218
219 return self.internalNext(allocator);
220 }
221
222 pub fn skip(self: *ArgIteratorWindows) bool {
223 // march forward over whitespace
224 while (true) : (self.index += 1) {
225 const byte = self.cmd_line[self.index];
226 switch (byte) {
227 0 => return false,
228 ' ', '\t' => continue,
229 else => break,
230 }
231 }
232
233 var backslash_count: usize = 0;
234 while (true) : (self.index += 1) {
235 const byte = self.cmd_line[self.index];
236 switch (byte) {
237 0 => return true,
238 '"' => {
239 const quote_is_real = backslash_count % 2 == 0;
240 if (quote_is_real) {
241 self.seen_quote_count += 1;
242 }
243 },
244 '\\' => {
245 backslash_count += 1;
246 },
247 ' ', '\t' => {
248 if (self.seen_quote_count % 2 == 0 or self.seen_quote_count == self.quote_count) {
249 return true;
250 }
251 backslash_count = 0;
252 },
253 else => {
254 backslash_count = 0;
255 continue;
256 },
257 }
258 }
259 }
260
261 fn internalNext(self: *ArgIteratorWindows, allocator: *Allocator) NextError![]u8 {
262 var buf = try Buffer.initSize(allocator, 0);
263 defer buf.deinit();
264
265 var backslash_count: usize = 0;
266 while (true) : (self.index += 1) {
267 const byte = self.cmd_line[self.index];
268 switch (byte) {
269 0 => return buf.toOwnedSlice(),
270 '"' => {
271 const quote_is_real = backslash_count % 2 == 0;
272 try self.emitBackslashes(&buf, backslash_count / 2);
273 backslash_count = 0;
274
275 if (quote_is_real) {
276 self.seen_quote_count += 1;
277 if (self.seen_quote_count == self.quote_count and self.seen_quote_count % 2 == 1) {
278 try buf.appendByte('"');
279 }
280 } else {
281 try buf.appendByte('"');
282 }
283 },
284 '\\' => {
285 backslash_count += 1;
286 },
287 ' ', '\t' => {
288 try self.emitBackslashes(&buf, backslash_count);
289 backslash_count = 0;
290 if (self.seen_quote_count % 2 == 1 and self.seen_quote_count != self.quote_count) {
291 try buf.appendByte(byte);
292 } else {
293 return buf.toOwnedSlice();
294 }
295 },
296 else => {
297 try self.emitBackslashes(&buf, backslash_count);
298 backslash_count = 0;
299 try buf.appendByte(byte);
300 },
301 }
302 }
303 }
304
305 fn emitBackslashes(self: *ArgIteratorWindows, buf: *Buffer, emit_count: usize) !void {
306 var i: usize = 0;
307 while (i < emit_count) : (i += 1) {
308 try buf.appendByte('\\');
309 }
310 }
311
312 fn countQuotes(cmd_line: [*]const u8) usize {
313 var result: usize = 0;
314 var backslash_count: usize = 0;
315 var index: usize = 0;
316 while (true) : (index += 1) {
317 const byte = cmd_line[index];
318 switch (byte) {
319 0 => return result,
320 '\\' => backslash_count += 1,
321 '"' => {
322 result += 1 - (backslash_count % 2);
323 backslash_count = 0;
324 },
325 else => {
326 backslash_count = 0;
327 },
328 }
329 }
330 }
331};
332
333pub const ArgIterator = struct {
334 const InnerType = if (builtin.os == Os.windows) ArgIteratorWindows else ArgIteratorPosix;
335
336 inner: InnerType,
337
338 pub fn init() ArgIterator {
339 if (builtin.os == Os.wasi) {
340 // TODO: Figure out a compatible interface accomodating WASI
341 @compileError("ArgIterator is not yet supported in WASI. Use argsAlloc and argsFree instead.");
342 }
343
344 return ArgIterator{ .inner = InnerType.init() };
345 }
346
347 pub const NextError = ArgIteratorWindows.NextError;
348
349 /// You must free the returned memory when done.
350 pub fn next(self: *ArgIterator, allocator: *Allocator) ?(NextError![]u8) {
351 if (builtin.os == Os.windows) {
352 return self.inner.next(allocator);
353 } else {
354 return mem.dupe(allocator, u8, self.inner.next() orelse return null);
355 }
356 }
357
358 /// If you only are targeting posix you can call this and not need an allocator.
359 pub fn nextPosix(self: *ArgIterator) ?[]const u8 {
360 return self.inner.next();
361 }
362
363 /// Parse past 1 argument without capturing it.
364 /// Returns `true` if skipped an arg, `false` if we are at the end.
365 pub fn skip(self: *ArgIterator) bool {
366 return self.inner.skip();
367 }
368};
369
370pub fn args() ArgIterator {
371 return ArgIterator.init();
372}
373
374/// Caller must call argsFree on result.
375pub fn argsAlloc(allocator: *mem.Allocator) ![]const []u8 {
376 if (builtin.os == Os.wasi) {
377 var count: usize = undefined;
378 var buf_size: usize = undefined;
379
380 const args_sizes_get_ret = os.wasi.args_sizes_get(&count, &buf_size);
381 if (args_sizes_get_ret != os.wasi.ESUCCESS) {
382 return unexpectedErrorPosix(args_sizes_get_ret);
383 }
384
385 var argv = try allocator.alloc([*]u8, count);
386 defer allocator.free(argv);
387
388 var argv_buf = try allocator.alloc(u8, buf_size);
389 const args_get_ret = os.wasi.args_get(argv.ptr, argv_buf.ptr);
390 if (args_get_ret != os.wasi.ESUCCESS) {
391 return unexpectedErrorPosix(args_get_ret);
392 }
393
394 var result_slice = try allocator.alloc([]u8, count);
395
396 var i: usize = 0;
397 while (i < count) : (i += 1) {
398 result_slice[i] = mem.toSlice(u8, argv[i]);
399 }
400
401 return result_slice;
402 }
403
404 // TODO refactor to only make 1 allocation.
405 var it = args();
406 var contents = try Buffer.initSize(allocator, 0);
407 defer contents.deinit();
408
409 var slice_list = ArrayList(usize).init(allocator);
410 defer slice_list.deinit();
411
412 while (it.next(allocator)) |arg_or_err| {
413 const arg = try arg_or_err;
414 defer allocator.free(arg);
415 try contents.append(arg);
416 try slice_list.append(arg.len);
417 }
418
419 const contents_slice = contents.toSliceConst();
420 const slice_sizes = slice_list.toSliceConst();
421 const slice_list_bytes = try math.mul(usize, @sizeOf([]u8), slice_sizes.len);
422 const total_bytes = try math.add(usize, slice_list_bytes, contents_slice.len);
423 const buf = try allocator.alignedAlloc(u8, @alignOf([]u8), total_bytes);
424 errdefer allocator.free(buf);
425
426 const result_slice_list = @bytesToSlice([]u8, buf[0..slice_list_bytes]);
427 const result_contents = buf[slice_list_bytes..];
428 mem.copy(u8, result_contents, contents_slice);
429
430 var contents_index: usize = 0;
431 for (slice_sizes) |len, i| {
432 const new_index = contents_index + len;
433 result_slice_list[i] = result_contents[contents_index..new_index];
434 contents_index = new_index;
435 }
436
437 return result_slice_list;
438}
439
440pub fn argsFree(allocator: *mem.Allocator, args_alloc: []const []u8) void {
441 if (builtin.os == Os.wasi) {
442 const last_item = args_alloc[args_alloc.len - 1];
443 const last_byte_addr = @ptrToInt(last_item.ptr) + last_item.len + 1; // null terminated
444 const first_item_ptr = args_alloc[0].ptr;
445 const len = last_byte_addr - @ptrToInt(first_item_ptr);
446 allocator.free(first_item_ptr[0..len]);
447
448 return allocator.free(args_alloc);
449 }
450
451 var total_bytes: usize = 0;
452 for (args_alloc) |arg| {
453 total_bytes += @sizeOf([]u8) + arg.len;
454 }
455 const unaligned_allocated_buf = @ptrCast([*]const u8, args_alloc.ptr)[0..total_bytes];
456 const aligned_allocated_buf = @alignCast(@alignOf([]u8), unaligned_allocated_buf);
457 return allocator.free(aligned_allocated_buf);
458}
459
460test "windows arg parsing" {
461 testWindowsCmdLine(c"a b\tc d", [][]const u8{ "a", "b", "c", "d" });
462 testWindowsCmdLine(c"\"abc\" d e", [][]const u8{ "abc", "d", "e" });
463 testWindowsCmdLine(c"a\\\\\\b d\"e f\"g h", [][]const u8{ "a\\\\\\b", "de fg", "h" });
464 testWindowsCmdLine(c"a\\\\\\\"b c d", [][]const u8{ "a\\\"b", "c", "d" });
465 testWindowsCmdLine(c"a\\\\\\\\\"b c\" d e", [][]const u8{ "a\\\\b c", "d", "e" });
466 testWindowsCmdLine(c"a b\tc \"d f", [][]const u8{ "a", "b", "c", "\"d", "f" });
467
468 testWindowsCmdLine(c"\".\\..\\zig-cache\\build\" \"bin\\zig.exe\" \".\\..\" \".\\..\\zig-cache\" \"--help\"", [][]const u8{
469 ".\\..\\zig-cache\\build",
470 "bin\\zig.exe",
471 ".\\..",
472 ".\\..\\zig-cache",
473 "--help",
474 });
475}
476
477fn testWindowsCmdLine(input_cmd_line: [*]const u8, expected_args: []const []const u8) void {
478 var it = ArgIteratorWindows.initWithCmdLine(input_cmd_line);
479 for (expected_args) |expected_arg| {
480 const arg = it.next(std.debug.global_allocator).? catch unreachable;
481 testing.expectEqualSlices(u8, expected_arg, arg);
482 }
483 testing.expect(it.next(std.debug.global_allocator) == null);
484}
485
486pub const UserInfo = struct {
487 uid: u32,
488 gid: u32,
489};
490
491/// POSIX function which gets a uid from username.
492pub fn getUserInfo(name: []const u8) !UserInfo {
493 return switch (builtin.os) {
494 .linux, .macosx, .ios, .freebsd, .netbsd => posixGetUserInfo(name),
495 else => @compileError("Unsupported OS"),
496 };
497}
498
499/// TODO this reads /etc/passwd. But sometimes the user/id mapping is in something else
500/// like NIS, AD, etc. See `man nss` or look at an strace for `id myuser`.
501pub fn posixGetUserInfo(name: []const u8) !UserInfo {
502 var in_stream = try io.InStream.open("/etc/passwd", null);
503 defer in_stream.close();
504
505 const State = enum {
506 Start,
507 WaitForNextLine,
508 SkipPassword,
509 ReadUserId,
510 ReadGroupId,
511 };
512
513 var buf: [std.mem.page_size]u8 = undefined;
514 var name_index: usize = 0;
515 var state = State.Start;
516 var uid: u32 = 0;
517 var gid: u32 = 0;
518
519 while (true) {
520 const amt_read = try in_stream.read(buf[0..]);
521 for (buf[0..amt_read]) |byte| {
522 switch (state) {
523 .Start => switch (byte) {
524 ':' => {
525 state = if (name_index == name.len) State.SkipPassword else State.WaitForNextLine;
526 },
527 '\n' => return error.CorruptPasswordFile,
528 else => {
529 if (name_index == name.len or name[name_index] != byte) {
530 state = .WaitForNextLine;
531 }
532 name_index += 1;
533 },
534 },
535 .WaitForNextLine => switch (byte) {
536 '\n' => {
537 name_index = 0;
538 state = .Start;
539 },
540 else => continue,
541 },
542 .SkipPassword => switch (byte) {
543 '\n' => return error.CorruptPasswordFile,
544 ':' => {
545 state = .ReadUserId;
546 },
547 else => continue,
548 },
549 .ReadUserId => switch (byte) {
550 ':' => {
551 state = .ReadGroupId;
552 },
553 '\n' => return error.CorruptPasswordFile,
554 else => {
555 const digit = switch (byte) {
556 '0'...'9' => byte - '0',
557 else => return error.CorruptPasswordFile,
558 };
559 if (@mulWithOverflow(u32, uid, 10, *uid)) return error.CorruptPasswordFile;
560 if (@addWithOverflow(u32, uid, digit, *uid)) return error.CorruptPasswordFile;
561 },
562 },
563 .ReadGroupId => switch (byte) {
564 '\n', ':' => {
565 return UserInfo{
566 .uid = uid,
567 .gid = gid,
568 };
569 },
570 else => {
571 const digit = switch (byte) {
572 '0'...'9' => byte - '0',
573 else => return error.CorruptPasswordFile,
574 };
575 if (@mulWithOverflow(u32, gid, 10, *gid)) return error.CorruptPasswordFile;
576 if (@addWithOverflow(u32, gid, digit, *gid)) return error.CorruptPasswordFile;
577 },
578 },
579 }
580 }
581 if (amt_read < buf.len) return error.UserNotFound;
582 }
583}
std/std.zig+10
......@@ -17,6 +17,8 @@ pub const PriorityQueue = @import("priority_queue.zig").PriorityQueue;
1717pub const StaticallyInitializedMutex = @import("statically_initialized_mutex.zig").StaticallyInitializedMutex;
1818pub const SegmentedList = @import("segmented_list.zig").SegmentedList;
1919pub const SpinLock = @import("spinlock.zig").SpinLock;
20pub const ChildProcess = @import("child_process.zig").ChildProcess;
21pub const Thread = @import("thread.zig").Thread;
2022
2123pub const atomic = @import("atomic.zig");
2224pub const base64 = @import("base64.zig");
......@@ -30,6 +32,7 @@ pub const dwarf = @import("dwarf.zig");
3032pub const elf = @import("elf.zig");
3133pub const event = @import("event.zig");
3234pub const fmt = @import("fmt.zig");
35pub const fs = @import("fs.zig");
3336pub const hash = @import("hash.zig");
3437pub const hash_map = @import("hash_map.zig");
3538pub const heap = @import("heap.zig");
......@@ -43,11 +46,13 @@ pub const meta = @import("meta.zig");
4346pub const net = @import("net.zig");
4447pub const os = @import("os.zig");
4548pub const pdb = @import("pdb.zig");
49pub const process = @import("process.zig");
4650pub const rand = @import("rand.zig");
4751pub const rb = @import("rb.zig");
4852pub const sort = @import("sort.zig");
4953pub const ascii = @import("ascii.zig");
5054pub const testing = @import("testing.zig");
55pub const time = @import("time.zig");
5156pub const unicode = @import("unicode.zig");
5257pub const valgrind = @import("valgrind.zig");
5358pub const zig = @import("zig.zig");
......@@ -65,6 +70,7 @@ test "std" {
6570 _ = @import("statically_initialized_mutex.zig");
6671 _ = @import("segmented_list.zig");
6772 _ = @import("spinlock.zig");
73 _ = @import("child_process.zig");
6874
6975 _ = @import("ascii.zig");
7076 _ = @import("base64.zig");
......@@ -79,6 +85,7 @@ test "std" {
7985 _ = @import("elf.zig");
8086 _ = @import("event.zig");
8187 _ = @import("fmt.zig");
88 _ = @import("fs.zig");
8289 _ = @import("hash.zig");
8390 _ = @import("heap.zig");
8491 _ = @import("io.zig");
......@@ -91,11 +98,14 @@ test "std" {
9198 _ = @import("net.zig");
9299 _ = @import("os.zig");
93100 _ = @import("pdb.zig");
101 _ = @import("process.zig");
94102 _ = @import("packed_int_array.zig");
95103 _ = @import("priority_queue.zig");
96104 _ = @import("rand.zig");
97105 _ = @import("sort.zig");
98106 _ = @import("testing.zig");
107 _ = @import("thread.zig");
108 _ = @import("time.zig");
99109 _ = @import("unicode.zig");
100110 _ = @import("valgrind.zig");
101111 _ = @import("zig.zig");
std/thread.zig created+365
......@@ -0,0 +1,365 @@
1const builtin = @import("builtin");
2const std = @import("std.zig");
3const windows = std.os.windows;
4
5pub const Thread = struct {
6 data: Data,
7
8 pub const use_pthreads = !windows.is_the_target and builtin.link_libc;
9
10 /// Represents a kernel thread handle.
11 /// May be an integer or a pointer depending on the platform.
12 /// On Linux and POSIX, this is the same as Id.
13 pub const Handle = if (use_pthreads)
14 c.pthread_t
15 else switch (builtin.os) {
16 builtin.Os.linux => i32,
17 builtin.Os.windows => windows.HANDLE,
18 else => @compileError("Unsupported OS"),
19 };
20
21 /// Represents a unique ID per thread.
22 /// May be an integer or pointer depending on the platform.
23 /// On Linux and POSIX, this is the same as Handle.
24 pub const Id = switch (builtin.os) {
25 builtin.Os.windows => windows.DWORD,
26 else => Handle,
27 };
28
29 pub const Data = if (use_pthreads)
30 struct {
31 handle: Thread.Handle,
32 mmap_addr: usize,
33 mmap_len: usize,
34 }
35 else switch (builtin.os) {
36 builtin.Os.linux => struct {
37 handle: Thread.Handle,
38 mmap_addr: usize,
39 mmap_len: usize,
40 },
41 builtin.Os.windows => struct {
42 handle: Thread.Handle,
43 alloc_start: *c_void,
44 heap_handle: windows.HANDLE,
45 },
46 else => @compileError("Unsupported OS"),
47 };
48
49 /// Returns the ID of the calling thread.
50 /// Makes a syscall every time the function is called.
51 /// On Linux and POSIX, this Id is the same as a Handle.
52 pub fn getCurrentId() Id {
53 if (use_pthreads) {
54 return c.pthread_self();
55 } else
56 return switch (builtin.os) {
57 builtin.Os.linux => linux.gettid(),
58 builtin.Os.windows => windows.GetCurrentThreadId(),
59 else => @compileError("Unsupported OS"),
60 };
61 }
62
63 /// Returns the handle of this thread.
64 /// On Linux and POSIX, this is the same as Id.
65 /// On Linux, it is possible that the thread spawned with `spawn`
66 /// finishes executing entirely before the clone syscall completes. In this
67 /// case, this function will return 0 rather than the no-longer-existing thread's
68 /// pid.
69 pub fn handle(self: Thread) Handle {
70 return self.data.handle;
71 }
72
73 pub fn wait(self: *const Thread) void {
74 if (use_pthreads) {
75 const err = c.pthread_join(self.data.handle, null);
76 switch (err) {
77 0 => {},
78 posix.EINVAL => unreachable,
79 posix.ESRCH => unreachable,
80 posix.EDEADLK => unreachable,
81 else => unreachable,
82 }
83 assert(posix.munmap(self.data.mmap_addr, self.data.mmap_len) == 0);
84 } else switch (builtin.os) {
85 builtin.Os.linux => {
86 while (true) {
87 const pid_value = @atomicLoad(i32, &self.data.handle, .SeqCst);
88 if (pid_value == 0) break;
89 const rc = linux.futex_wait(&self.data.handle, linux.FUTEX_WAIT, pid_value, null);
90 switch (linux.getErrno(rc)) {
91 0 => continue,
92 posix.EINTR => continue,
93 posix.EAGAIN => continue,
94 else => unreachable,
95 }
96 }
97 assert(posix.munmap(self.data.mmap_addr, self.data.mmap_len) == 0);
98 },
99 builtin.Os.windows => {
100 assert(windows.WaitForSingleObject(self.data.handle, windows.INFINITE) == windows.WAIT_OBJECT_0);
101 assert(windows.CloseHandle(self.data.handle) != 0);
102 assert(windows.HeapFree(self.data.heap_handle, 0, self.data.alloc_start) != 0);
103 },
104 else => @compileError("Unsupported OS"),
105 }
106 }
107
108 pub const SpawnError = error{
109 /// A system-imposed limit on the number of threads was encountered.
110 /// There are a number of limits that may trigger this error:
111 /// * the RLIMIT_NPROC soft resource limit (set via setrlimit(2)),
112 /// which limits the number of processes and threads for a real
113 /// user ID, was reached;
114 /// * the kernel's system-wide limit on the number of processes and
115 /// threads, /proc/sys/kernel/threads-max, was reached (see
116 /// proc(5));
117 /// * the maximum number of PIDs, /proc/sys/kernel/pid_max, was
118 /// reached (see proc(5)); or
119 /// * the PID limit (pids.max) imposed by the cgroup "process num‐
120 /// ber" (PIDs) controller was reached.
121 ThreadQuotaExceeded,
122
123 /// The kernel cannot allocate sufficient memory to allocate a task structure
124 /// for the child, or to copy those parts of the caller's context that need to
125 /// be copied.
126 SystemResources,
127
128 /// Not enough userland memory to spawn the thread.
129 OutOfMemory,
130
131 Unexpected,
132 };
133
134 /// caller must call wait on the returned thread
135 /// fn startFn(@typeOf(context)) T
136 /// where T is u8, noreturn, void, or !void
137 /// caller must call wait on the returned thread
138 pub fn spawn(context: var, comptime startFn: var) SpawnError!*Thread {
139 if (builtin.single_threaded) @compileError("cannot spawn thread when building in single-threaded mode");
140 // TODO compile-time call graph analysis to determine stack upper bound
141 // https://github.com/ziglang/zig/issues/157
142 const default_stack_size = 8 * 1024 * 1024;
143
144 const Context = @typeOf(context);
145 comptime assert(@ArgType(@typeOf(startFn), 0) == Context);
146
147 if (builtin.os == builtin.Os.windows) {
148 const WinThread = struct {
149 const OuterContext = struct {
150 thread: Thread,
151 inner: Context,
152 };
153 extern fn threadMain(raw_arg: windows.LPVOID) windows.DWORD {
154 const arg = if (@sizeOf(Context) == 0) {} else @ptrCast(*Context, @alignCast(@alignOf(Context), raw_arg)).*;
155 switch (@typeId(@typeOf(startFn).ReturnType)) {
156 builtin.TypeId.Int => {
157 return startFn(arg);
158 },
159 builtin.TypeId.Void => {
160 startFn(arg);
161 return 0;
162 },
163 else => @compileError("expected return type of startFn to be 'u8', 'noreturn', 'void', or '!void'"),
164 }
165 }
166 };
167
168 const heap_handle = windows.GetProcessHeap() orelse return error.OutOfMemory;
169 const byte_count = @alignOf(WinThread.OuterContext) + @sizeOf(WinThread.OuterContext);
170 const bytes_ptr = windows.HeapAlloc(heap_handle, 0, byte_count) orelse return error.OutOfMemory;
171 errdefer assert(windows.HeapFree(heap_handle, 0, bytes_ptr) != 0);
172 const bytes = @ptrCast([*]u8, bytes_ptr)[0..byte_count];
173 const outer_context = std.heap.FixedBufferAllocator.init(bytes).allocator.create(WinThread.OuterContext) catch unreachable;
174 outer_context.* = WinThread.OuterContext{
175 .thread = Thread{
176 .data = Thread.Data{
177 .heap_handle = heap_handle,
178 .alloc_start = bytes_ptr,
179 .handle = undefined,
180 },
181 },
182 .inner = context,
183 };
184
185 const parameter = if (@sizeOf(Context) == 0) null else @ptrCast(*c_void, &outer_context.inner);
186 outer_context.thread.data.handle = windows.CreateThread(null, default_stack_size, WinThread.threadMain, parameter, 0, null) orelse {
187 switch (windows.GetLastError()) {
188 else => |err| windows.unexpectedError(err),
189 }
190 };
191 return &outer_context.thread;
192 }
193
194 const MainFuncs = struct {
195 extern fn linuxThreadMain(ctx_addr: usize) u8 {
196 const arg = if (@sizeOf(Context) == 0) {} else @intToPtr(*const Context, ctx_addr).*;
197
198 switch (@typeId(@typeOf(startFn).ReturnType)) {
199 builtin.TypeId.Int => {
200 return startFn(arg);
201 },
202 builtin.TypeId.Void => {
203 startFn(arg);
204 return 0;
205 },
206 else => @compileError("expected return type of startFn to be 'u8', 'noreturn', 'void', or '!void'"),
207 }
208 }
209 extern fn posixThreadMain(ctx: ?*c_void) ?*c_void {
210 if (@sizeOf(Context) == 0) {
211 _ = startFn({});
212 return null;
213 } else {
214 _ = startFn(@ptrCast(*const Context, @alignCast(@alignOf(Context), ctx)).*);
215 return null;
216 }
217 }
218 };
219
220 const MAP_GROWSDOWN = if (builtin.os == builtin.Os.linux) linux.MAP_GROWSDOWN else 0;
221
222 var stack_end_offset: usize = undefined;
223 var thread_start_offset: usize = undefined;
224 var context_start_offset: usize = undefined;
225 var tls_start_offset: usize = undefined;
226 const mmap_len = blk: {
227 // First in memory will be the stack, which grows downwards.
228 var l: usize = mem.alignForward(default_stack_size, os.page_size);
229 stack_end_offset = l;
230 // Above the stack, so that it can be in the same mmap call, put the Thread object.
231 l = mem.alignForward(l, @alignOf(Thread));
232 thread_start_offset = l;
233 l += @sizeOf(Thread);
234 // Next, the Context object.
235 if (@sizeOf(Context) != 0) {
236 l = mem.alignForward(l, @alignOf(Context));
237 context_start_offset = l;
238 l += @sizeOf(Context);
239 }
240 // Finally, the Thread Local Storage, if any.
241 if (!Thread.use_pthreads) {
242 if (linux.tls.tls_image) |tls_img| {
243 l = mem.alignForward(l, @alignOf(usize));
244 tls_start_offset = l;
245 l += tls_img.alloc_size;
246 }
247 }
248 break :blk l;
249 };
250 const mmap_addr = posix.mmap(null, mmap_len, posix.PROT_READ | posix.PROT_WRITE, posix.MAP_PRIVATE | posix.MAP_ANONYMOUS | MAP_GROWSDOWN, -1, 0);
251 if (mmap_addr == posix.MAP_FAILED) return error.OutOfMemory;
252 errdefer assert(posix.munmap(mmap_addr, mmap_len) == 0);
253
254 const thread_ptr = @alignCast(@alignOf(Thread), @intToPtr(*Thread, mmap_addr + thread_start_offset));
255 thread_ptr.data.mmap_addr = mmap_addr;
256 thread_ptr.data.mmap_len = mmap_len;
257
258 var arg: usize = undefined;
259 if (@sizeOf(Context) != 0) {
260 arg = mmap_addr + context_start_offset;
261 const context_ptr = @alignCast(@alignOf(Context), @intToPtr(*Context, arg));
262 context_ptr.* = context;
263 }
264
265 if (Thread.use_pthreads) {
266 // use pthreads
267 var attr: c.pthread_attr_t = undefined;
268 if (c.pthread_attr_init(&attr) != 0) return error.SystemResources;
269 defer assert(c.pthread_attr_destroy(&attr) == 0);
270
271 assert(c.pthread_attr_setstack(&attr, @intToPtr(*c_void, mmap_addr), stack_end_offset) == 0);
272
273 const err = c.pthread_create(&thread_ptr.data.handle, &attr, MainFuncs.posixThreadMain, @intToPtr(*c_void, arg));
274 switch (err) {
275 0 => return thread_ptr,
276 posix.EAGAIN => return error.SystemResources,
277 posix.EPERM => unreachable,
278 posix.EINVAL => unreachable,
279 else => return unexpectedErrorPosix(@intCast(usize, err)),
280 }
281 } else if (builtin.os == builtin.Os.linux) {
282 var flags: u32 = posix.CLONE_VM | posix.CLONE_FS | posix.CLONE_FILES | posix.CLONE_SIGHAND |
283 posix.CLONE_THREAD | posix.CLONE_SYSVSEM | posix.CLONE_PARENT_SETTID | posix.CLONE_CHILD_CLEARTID |
284 posix.CLONE_DETACHED;
285 var newtls: usize = undefined;
286 if (linux.tls.tls_image) |tls_img| {
287 newtls = linux.tls.copyTLS(mmap_addr + tls_start_offset);
288 flags |= posix.CLONE_SETTLS;
289 }
290 const rc = posix.clone(MainFuncs.linuxThreadMain, mmap_addr + stack_end_offset, flags, arg, &thread_ptr.data.handle, newtls, &thread_ptr.data.handle);
291 const err = posix.getErrno(rc);
292 switch (err) {
293 0 => return thread_ptr,
294 posix.EAGAIN => return error.ThreadQuotaExceeded,
295 posix.EINVAL => unreachable,
296 posix.ENOMEM => return error.SystemResources,
297 posix.ENOSPC => unreachable,
298 posix.EPERM => unreachable,
299 posix.EUSERS => unreachable,
300 else => return unexpectedErrorPosix(err),
301 }
302 } else {
303 @compileError("Unsupported OS");
304 }
305 }
306
307 pub const CpuCountError = error{
308 OutOfMemory,
309 PermissionDenied,
310 Unexpected,
311 };
312
313 pub fn cpuCount(fallback_allocator: *mem.Allocator) CpuCountError!usize {
314 switch (builtin.os) {
315 .macosx, .freebsd, .netbsd => {
316 var count: c_int = undefined;
317 var count_len: usize = @sizeOf(c_int);
318 const name = switch (builtin.os) {
319 builtin.Os.macosx => c"hw.logicalcpu",
320 else => c"hw.ncpu",
321 };
322 try posix.sysctlbyname(name, @ptrCast(*c_void, &count), &count_len, null, 0);
323 return @intCast(usize, count);
324 },
325 .linux => {
326 const usize_count = 16;
327 const allocator = std.heap.stackFallback(usize_count * @sizeOf(usize), fallback_allocator).get();
328
329 var set = try allocator.alloc(usize, usize_count);
330 defer allocator.free(set);
331
332 while (true) {
333 const rc = posix.sched_getaffinity(0, set);
334 const err = posix.getErrno(rc);
335 switch (err) {
336 0 => {
337 if (rc < set.len * @sizeOf(usize)) {
338 const result = set[0 .. rc / @sizeOf(usize)];
339 var sum: usize = 0;
340 for (result) |x| {
341 sum += @popCount(usize, x);
342 }
343 return sum;
344 } else {
345 set = try allocator.realloc(set, set.len * 2);
346 continue;
347 }
348 },
349 posix.EFAULT => unreachable,
350 posix.EINVAL => unreachable,
351 posix.EPERM => return CpuCountError.PermissionDenied,
352 posix.ESRCH => unreachable,
353 else => return os.unexpectedErrorPosix(err),
354 }
355 }
356 },
357 .windows => {
358 var system_info: windows.SYSTEM_INFO = undefined;
359 windows.GetSystemInfo(&system_info);
360 return @intCast(usize, system_info.dwNumberOfProcessors);
361 },
362 else => @compileError("unsupported OS"),
363 }
364 }
365};
std/time.zig created+307
......@@ -0,0 +1,307 @@
1const std = @import("../std.zig");
2const builtin = @import("builtin");
3const Os = builtin.Os;
4const debug = std.debug;
5const testing = std.testing;
6const math = std.math;
7
8const windows = std.os.windows;
9const linux = std.os.linux;
10const darwin = std.os.darwin;
11const wasi = std.os.wasi;
12const posix = std.os.posix;
13
14pub const epoch = @import("epoch.zig");
15
16/// Spurious wakeups are possible and no precision of timing is guaranteed.
17pub fn sleep(nanoseconds: u64) void {
18 switch (builtin.os) {
19 Os.linux, Os.macosx, Os.ios, Os.freebsd, Os.netbsd => {
20 const s = nanoseconds / ns_per_s;
21 const ns = nanoseconds % ns_per_s;
22 posixSleep(s, ns);
23 },
24 Os.windows => {
25 const ns_per_ms = ns_per_s / ms_per_s;
26 const milliseconds = nanoseconds / ns_per_ms;
27 const ms_that_will_fit = std.math.cast(windows.DWORD, milliseconds) catch std.math.maxInt(windows.DWORD);
28 windows.Sleep(ms_that_will_fit);
29 },
30 else => @compileError("Unsupported OS"),
31 }
32}
33
34/// Spurious wakeups are possible and no precision of timing is guaranteed.
35pub fn posixSleep(seconds: u64, nanoseconds: u64) void {
36 var req = posix.timespec{
37 .tv_sec = std.math.cast(isize, seconds) catch std.math.maxInt(isize),
38 .tv_nsec = std.math.cast(isize, nanoseconds) catch std.math.maxInt(isize),
39 };
40 var rem: posix.timespec = undefined;
41 while (true) {
42 const ret_val = posix.nanosleep(&req, &rem);
43 const err = posix.getErrno(ret_val);
44 switch (err) {
45 posix.EFAULT => unreachable,
46 posix.EINVAL => {
47 // Sometimes Darwin returns EINVAL for no reason.
48 // We treat it as a spurious wakeup.
49 return;
50 },
51 posix.EINTR => {
52 req = rem;
53 continue;
54 },
55 // This prong handles success as well as unexpected errors.
56 else => return,
57 }
58 }
59}
60
61/// Get the posix timestamp, UTC, in seconds
62pub fn timestamp() u64 {
63 return @divFloor(milliTimestamp(), ms_per_s);
64}
65
66/// Get the posix timestamp, UTC, in milliseconds
67pub const milliTimestamp = switch (builtin.os) {
68 Os.windows => milliTimestampWindows,
69 Os.linux, Os.freebsd, Os.netbsd => milliTimestampPosix,
70 Os.macosx, Os.ios => milliTimestampDarwin,
71 Os.wasi => milliTimestampWasi,
72 else => @compileError("Unsupported OS"),
73};
74
75fn milliTimestampWasi() u64 {
76 var ns: wasi.timestamp_t = undefined;
77
78 // TODO: Verify that precision is ignored
79 const err = wasi.clock_time_get(wasi.CLOCK_REALTIME, 1, &ns);
80 debug.assert(err == wasi.ESUCCESS);
81
82 const ns_per_ms = 1000;
83 return @divFloor(ns, ns_per_ms);
84}
85
86fn milliTimestampWindows() u64 {
87 //FileTime has a granularity of 100 nanoseconds
88 // and uses the NTFS/Windows epoch
89 var ft: windows.FILETIME = undefined;
90 windows.GetSystemTimeAsFileTime(&ft);
91 const hns_per_ms = (ns_per_s / 100) / ms_per_s;
92 const epoch_adj = epoch.windows * ms_per_s;
93
94 const ft64 = (u64(ft.dwHighDateTime) << 32) | ft.dwLowDateTime;
95 return @divFloor(ft64, hns_per_ms) - -epoch_adj;
96}
97
98fn milliTimestampDarwin() u64 {
99 var tv: darwin.timeval = undefined;
100 var err = darwin.gettimeofday(&tv, null);
101 debug.assert(err == 0);
102 const sec_ms = tv.tv_sec * ms_per_s;
103 const usec_ms = @divFloor(tv.tv_usec, us_per_s / ms_per_s);
104 return @intCast(u64, sec_ms + usec_ms);
105}
106
107fn milliTimestampPosix() u64 {
108 //From what I can tell there's no reason clock_gettime
109 // should ever fail for us with CLOCK_REALTIME,
110 // seccomp aside.
111 var ts: posix.timespec = undefined;
112 const err = posix.clock_gettime(posix.CLOCK_REALTIME, &ts);
113 debug.assert(err == 0);
114 const sec_ms = @intCast(u64, ts.tv_sec) * ms_per_s;
115 const nsec_ms = @divFloor(@intCast(u64, ts.tv_nsec), ns_per_s / ms_per_s);
116 return sec_ms + nsec_ms;
117}
118
119/// Multiples of a base unit (nanoseconds)
120pub const nanosecond = 1;
121pub const microsecond = 1000 * nanosecond;
122pub const millisecond = 1000 * microsecond;
123pub const second = 1000 * millisecond;
124pub const minute = 60 * second;
125pub const hour = 60 * minute;
126
127/// Divisions of a second
128pub const ns_per_s = 1000000000;
129pub const us_per_s = 1000000;
130pub const ms_per_s = 1000;
131pub const cs_per_s = 100;
132
133/// Common time divisions
134pub const s_per_min = 60;
135pub const s_per_hour = s_per_min * 60;
136pub const s_per_day = s_per_hour * 24;
137pub const s_per_week = s_per_day * 7;
138
139/// A monotonic high-performance timer.
140/// Timer.start() must be called to initialize the struct, which captures
141/// the counter frequency on windows and darwin, records the resolution,
142/// and gives the user an opportunity to check for the existnece of
143/// monotonic clocks without forcing them to check for error on each read.
144/// .resolution is in nanoseconds on all platforms but .start_time's meaning
145/// depends on the OS. On Windows and Darwin it is a hardware counter
146/// value that requires calculation to convert to a meaninful unit.
147pub const Timer = struct {
148
149 //if we used resolution's value when performing the
150 // performance counter calc on windows/darwin, it would
151 // be less precise
152 frequency: switch (builtin.os) {
153 Os.windows => u64,
154 Os.macosx, Os.ios => darwin.mach_timebase_info_data,
155 else => void,
156 },
157 resolution: u64,
158 start_time: u64,
159
160 //At some point we may change our minds on RAW, but for now we're
161 // sticking with posix standard MONOTONIC. For more information, see:
162 // https://github.com/ziglang/zig/pull/933
163 //
164 //const monotonic_clock_id = switch(builtin.os) {
165 // Os.linux => linux.CLOCK_MONOTONIC_RAW,
166 // else => posix.CLOCK_MONOTONIC,
167 //};
168 const monotonic_clock_id = posix.CLOCK_MONOTONIC;
169 /// Initialize the timer structure.
170 //This gives us an opportunity to grab the counter frequency in windows.
171 //On Windows: QueryPerformanceCounter will succeed on anything >= XP/2000.
172 //On Posix: CLOCK_MONOTONIC will only fail if the monotonic counter is not
173 // supported, or if the timespec pointer is out of bounds, which should be
174 // impossible here barring cosmic rays or other such occurrences of
175 // incredibly bad luck.
176 //On Darwin: This cannot fail, as far as I am able to tell.
177 const TimerError = error{
178 TimerUnsupported,
179 Unexpected,
180 };
181 pub fn start() TimerError!Timer {
182 var self: Timer = undefined;
183
184 switch (builtin.os) {
185 Os.windows => {
186 var freq: i64 = undefined;
187 var err = windows.QueryPerformanceFrequency(&freq);
188 if (err == windows.FALSE) return error.TimerUnsupported;
189 self.frequency = @intCast(u64, freq);
190 self.resolution = @divFloor(ns_per_s, self.frequency);
191
192 var start_time: i64 = undefined;
193 err = windows.QueryPerformanceCounter(&start_time);
194 debug.assert(err != windows.FALSE);
195 self.start_time = @intCast(u64, start_time);
196 },
197 Os.linux, Os.freebsd, Os.netbsd => {
198 //On Linux, seccomp can do arbitrary things to our ability to call
199 // syscalls, including return any errno value it wants and
200 // inconsistently throwing errors. Since we can't account for
201 // abuses of seccomp in a reasonable way, we'll assume that if
202 // seccomp is going to block us it will at least do so consistently
203 var ts: posix.timespec = undefined;
204 var result = posix.clock_getres(monotonic_clock_id, &ts);
205 var errno = posix.getErrno(result);
206 switch (errno) {
207 0 => {},
208 posix.EINVAL => return error.TimerUnsupported,
209 else => return std.os.unexpectedErrorPosix(errno),
210 }
211 self.resolution = @intCast(u64, ts.tv_sec) * u64(ns_per_s) + @intCast(u64, ts.tv_nsec);
212
213 result = posix.clock_gettime(monotonic_clock_id, &ts);
214 errno = posix.getErrno(result);
215 if (errno != 0) return std.os.unexpectedErrorPosix(errno);
216 self.start_time = @intCast(u64, ts.tv_sec) * u64(ns_per_s) + @intCast(u64, ts.tv_nsec);
217 },
218 Os.macosx, Os.ios => {
219 darwin.mach_timebase_info(&self.frequency);
220 self.resolution = @divFloor(self.frequency.numer, self.frequency.denom);
221 self.start_time = darwin.mach_absolute_time();
222 },
223 else => @compileError("Unsupported OS"),
224 }
225 return self;
226 }
227
228 /// Reads the timer value since start or the last reset in nanoseconds
229 pub fn read(self: *Timer) u64 {
230 var clock = clockNative() - self.start_time;
231 return switch (builtin.os) {
232 Os.windows => @divFloor(clock * ns_per_s, self.frequency),
233 Os.linux, Os.freebsd, Os.netbsd => clock,
234 Os.macosx, Os.ios => @divFloor(clock * self.frequency.numer, self.frequency.denom),
235 else => @compileError("Unsupported OS"),
236 };
237 }
238
239 /// Resets the timer value to 0/now.
240 pub fn reset(self: *Timer) void {
241 self.start_time = clockNative();
242 }
243
244 /// Returns the current value of the timer in nanoseconds, then resets it
245 pub fn lap(self: *Timer) u64 {
246 var now = clockNative();
247 var lap_time = self.read();
248 self.start_time = now;
249 return lap_time;
250 }
251
252 const clockNative = switch (builtin.os) {
253 Os.windows => clockWindows,
254 Os.linux, Os.freebsd, Os.netbsd => clockLinux,
255 Os.macosx, Os.ios => clockDarwin,
256 else => @compileError("Unsupported OS"),
257 };
258
259 fn clockWindows() u64 {
260 var result: i64 = undefined;
261 var err = windows.QueryPerformanceCounter(&result);
262 debug.assert(err != windows.FALSE);
263 return @intCast(u64, result);
264 }
265
266 fn clockDarwin() u64 {
267 return darwin.mach_absolute_time();
268 }
269
270 fn clockLinux() u64 {
271 var ts: posix.timespec = undefined;
272 var result = posix.clock_gettime(monotonic_clock_id, &ts);
273 debug.assert(posix.getErrno(result) == 0);
274 return @intCast(u64, ts.tv_sec) * u64(ns_per_s) + @intCast(u64, ts.tv_nsec);
275 }
276};
277
278test "os.time.sleep" {
279 sleep(1);
280}
281
282test "os.time.timestamp" {
283 const ns_per_ms = (ns_per_s / ms_per_s);
284 const margin = 50;
285
286 const time_0 = milliTimestamp();
287 sleep(ns_per_ms);
288 const time_1 = milliTimestamp();
289 const interval = time_1 - time_0;
290 testing.expect(interval > 0 and interval < margin);
291}
292
293test "os.time.Timer" {
294 const ns_per_ms = (ns_per_s / ms_per_s);
295 const margin = ns_per_ms * 150;
296
297 var timer = try Timer.start();
298 sleep(10 * ns_per_ms);
299 const time_0 = timer.read();
300 testing.expect(time_0 > 0 and time_0 < margin);
301
302 const time_1 = timer.lap();
303 testing.expect(time_1 >= time_0);
304
305 timer.reset();
306 testing.expect(timer.read() < time_1);
307}