| author | |
| committer | |
| log | 17b0166e004e81d9c433576f4c642e743fc527b7 |
| tree | 0ba5e905ee444e21ffd68c348de240e76dc7dbef |
| parent | 2def23063fbabb4128da17aca27745a7e8062ce5 |
| signature | Commit is signed but in an unrecognized format. |
31 files changed, 7102 insertions(+), 6930 deletions(-)
std/child_process.zig created+825| ... | ... | @@ -0,0 +1,825 @@ |
| 1 | const std = @import("std.zig"); | |
| 2 | const cstr = std.cstr; | |
| 3 | const unicode = std.unicode; | |
| 4 | const io = std.io; | |
| 5 | const os = std.os; | |
| 6 | const posix = os.posix; | |
| 7 | const windows = os.windows; | |
| 8 | const mem = std.mem; | |
| 9 | const debug = std.debug; | |
| 10 | const BufMap = std.BufMap; | |
| 11 | const Buffer = std.Buffer; | |
| 12 | const builtin = @import("builtin"); | |
| 13 | const Os = builtin.Os; | |
| 14 | const LinkedList = std.LinkedList; | |
| 15 | const maxInt = std.math.maxInt; | |
| 16 | ||
| 17 | const is_windows = builtin.os == .windows; | |
| 18 | ||
| 19 | pub 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 | ||
| 651 | fn 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]. | |
| 693 | fn 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 | ||
| 729 | fn windowsDestroyPipe(rd: ?windows.HANDLE, wr: ?windows.HANDLE) void { | |
| 730 | if (rd) |h| os.close(h); | |
| 731 | if (wr) |h| os.close(h); | |
| 732 | } | |
| 733 | ||
| 734 | fn 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 | ||
| 744 | fn 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 | ||
| 754 | fn 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 | ||
| 766 | fn 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. | |
| 773 | fn forkChildErrReport(fd: i32, err: ChildProcess.SpawnError) noreturn { | |
| 774 | writeIntFd(fd, ErrInt(@errorToInt(err))) catch {}; | |
| 775 | posix.exit(1); | |
| 776 | } | |
| 777 | ||
| 778 | const ErrInt = @IntType(false, @sizeOf(anyerror) * 8); | |
| 779 | ||
| 780 | fn 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 | ||
| 785 | fn 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. | |
| 791 | pub 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 { |
| 91 | 91 | } else |
| 92 | 92 | return error.InvalidPEMagic; |
| 93 | 93 | |
| 94 | try self.in_file.seekForward(skip_size); | |
| 94 | try self.in_file.seekBy(skip_size); | |
| 95 | 95 | |
| 96 | 96 | const number_of_rva_and_sizes = try in.readIntLittle(u32); |
| 97 | 97 | if (number_of_rva_and_sizes != IMAGE_NUMBEROF_DIRECTORY_ENTRIES) |
std/crypto.zig+3| ... | ... | @@ -32,6 +32,9 @@ pub const chaCha20With64BitNonce = import_chaCha20.chaCha20With64BitNonce; |
| 32 | 32 | pub const Poly1305 = @import("crypto/poly1305.zig").Poly1305; |
| 33 | 33 | pub const X25519 = @import("crypto/x25519.zig").X25519; |
| 34 | 34 | |
| 35 | const std = @import("std.zig"); | |
| 36 | pub const randomBytes = std.posix.getrandom; | |
| 37 | ||
| 35 | 38 | test "crypto" { |
| 36 | 39 | _ = @import("crypto/blake2.zig"); |
| 37 | 40 | _ = @import("crypto/chacha20.zig"); |
std/debug.zig+4-4| ... | ... | @@ -893,7 +893,7 @@ fn openSelfDebugInfoWindows(allocator: *mem.Allocator) !DebugInfo { |
| 893 | 893 | if (this_record_len % 4 != 0) { |
| 894 | 894 | const round_to_next_4 = (this_record_len | 0x3) + 1; |
| 895 | 895 | 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); | |
| 897 | 897 | this_record_len += march_forward_bytes; |
| 898 | 898 | } |
| 899 | 899 | |
| ... | ... | @@ -1116,7 +1116,7 @@ fn printLineFromFileAnyOs(out_stream: var, line_info: LineInfo) !void { |
| 1116 | 1116 | defer f.close(); |
| 1117 | 1117 | // TODO fstat and make sure that the file has the correct size |
| 1118 | 1118 | |
| 1119 | var buf: [os.page_size]u8 = undefined; | |
| 1119 | var buf: [mem.page_size]u8 = undefined; | |
| 1120 | 1120 | var line: usize = 1; |
| 1121 | 1121 | var column: usize = 1; |
| 1122 | 1122 | var abs_index: usize = 0; |
| ... | ... | @@ -1938,7 +1938,7 @@ fn getLineNumberInfoDwarf(di: *DwarfInfo, compile_unit: CompileUnit, target_addr |
| 1938 | 1938 | }, |
| 1939 | 1939 | else => { |
| 1940 | 1940 | 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); | |
| 1942 | 1942 | }, |
| 1943 | 1943 | } |
| 1944 | 1944 | } else if (opcode >= opcode_base) { |
| ... | ... | @@ -1990,7 +1990,7 @@ fn getLineNumberInfoDwarf(di: *DwarfInfo, compile_unit: CompileUnit, target_addr |
| 1990 | 1990 | else => { |
| 1991 | 1991 | if (opcode - 1 >= standard_opcode_lengths.len) return error.InvalidDebugInfo; |
| 1992 | 1992 | 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); | |
| 1994 | 1994 | }, |
| 1995 | 1995 | } |
| 1996 | 1996 | } |
std/dynamic_library.zig+1-1| ... | ... | @@ -125,7 +125,7 @@ pub const LinuxDynLib = struct { |
| 125 | 125 | ); |
| 126 | 126 | errdefer _ = linux.munmap(addr, size); |
| 127 | 127 | |
| 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]; | |
| 129 | 129 | |
| 130 | 130 | return DynLib{ |
| 131 | 131 | .elf_lib = try ElfLib.init(bytes), |
std/elf.zig+2-2| ... | ... | @@ -410,7 +410,7 @@ pub const Elf = struct { |
| 410 | 410 | if (version_byte != 1) return error.InvalidFormat; |
| 411 | 411 | |
| 412 | 412 | // skip over padding |
| 413 | try seekable_stream.seekForward(9); | |
| 413 | try seekable_stream.seekBy(9); | |
| 414 | 414 | |
| 415 | 415 | elf.file_type = switch (try in.readInt(u16, elf.endian)) { |
| 416 | 416 | 1 => FileType.Relocatable, |
| ... | ... | @@ -447,7 +447,7 @@ pub const Elf = struct { |
| 447 | 447 | } |
| 448 | 448 | |
| 449 | 449 | // skip over flags |
| 450 | try seekable_stream.seekForward(4); | |
| 450 | try seekable_stream.seekBy(4); | |
| 451 | 451 | |
| 452 | 452 | const header_size = try in.readInt(u16, elf.endian); |
| 453 | 453 | 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 |
| 688 | 688 | defer list.deinit(); |
| 689 | 689 | |
| 690 | 690 | while (true) { |
| 691 | try list.ensureCapacity(list.len + os.page_size); | |
| 691 | try list.ensureCapacity(list.len + mem.page_size); | |
| 692 | 692 | const buf = list.items[list.len..]; |
| 693 | 693 | const buf_array = [][]u8{buf}; |
| 694 | 694 | const amt = try await (async preadv(loop, fd, buf_array, list.len) catch unreachable); |
std/fs.zig created+840| ... | ... | @@ -0,0 +1,840 @@ |
| 1 | const std = @import("std.zig"); | |
| 2 | const os = std.os; | |
| 3 | const mem = std.mem; | |
| 4 | ||
| 5 | pub const path = @import("fs/path.zig"); | |
| 6 | pub const File = @import("fs/file.zig").File; | |
| 7 | ||
| 8 | pub const symLink = os.symlink; | |
| 9 | pub const symLinkC = os.symlinkC; | |
| 10 | pub const deleteFile = os.unlink; | |
| 11 | pub const deleteFileC = os.unlinkC; | |
| 12 | pub const rename = os.rename; | |
| 13 | pub const renameC = os.renameC; | |
| 14 | pub const changeCurDir = os.chdir; | |
| 15 | pub const changeCurDirC = os.chdirC; | |
| 16 | pub const realpath = os.realpath; | |
| 17 | pub const realpathC = os.realpathC; | |
| 18 | pub const realpathW = os.realpathW; | |
| 19 | ||
| 20 | pub const getAppDataDir = @import("fs/get_app_data_dir.zig").getAppDataDir; | |
| 21 | pub 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 | |
| 27 | pub 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`. | |
| 38 | pub fn getCwd(out_buffer: *[MAX_PATH_BYTES]u8) ![]u8 { | |
| 39 | return posix.getcwd(out_buffer); | |
| 40 | } | |
| 41 | ||
| 42 | /// Caller must free the returned memory. | |
| 43 | pub 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 | ||
| 48 | test "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 | |
| 56 | const b64_fs_encoder = base64.Base64Encoder.init("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_", base64.standard_pad_char); | |
| 57 | ||
| 58 | /// TODO remove the allocator requirement from this API | |
| 59 | pub 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. | |
| 92 | pub 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 | |
| 115 | pub 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 | ||
| 132 | pub 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 | ||
| 206 | const default_new_dir_mode = 0o755; | |
| 207 | ||
| 208 | /// Create a new directory. | |
| 209 | pub 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. | |
| 214 | pub 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. | |
| 219 | pub 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 | |
| 228 | pub 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`. | |
| 262 | pub 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. | |
| 267 | pub 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. | |
| 272 | pub 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. | |
| 279 | const 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 | |
| 311 | pub 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 | ||
| 381 | pub 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`. | |
| 708 | pub 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. | |
| 713 | pub fn readLinkC(buffer: *[posix.PATH_MAX]u8, pathname: [*]const u8) ![]u8 { | |
| 714 | return posix.readlinkC(pathname, buffer); | |
| 715 | } | |
| 716 | ||
| 717 | pub 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 | ||
| 735 | test "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 | ||
| 742 | pub 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 | |
| 765 | pub 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. | |
| 801 | pub 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. | |
| 808 | pub 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. | |
| 831 | pub 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 | ||
| 836 | test "" { | |
| 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 @@ |
| 1 | const std = @import("../std.zig"); | |
| 2 | const builtin = @import("builtin"); | |
| 3 | const os = std.os; | |
| 4 | const io = std.io; | |
| 5 | const mem = std.mem; | |
| 6 | const math = std.math; | |
| 7 | const assert = std.debug.assert; | |
| 8 | const windows = os.windows; | |
| 9 | const Os = builtin.Os; | |
| 10 | const maxInt = std.math.maxInt; | |
| 11 | ||
| 12 | pub 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 @@ |
| 1 | const std = @import("../std.zig"); | |
| 2 | const builtin = @import("builtin"); | |
| 3 | const unicode = std.unicode; | |
| 4 | const mem = std.mem; | |
| 5 | const os = std.os; | |
| 6 | ||
| 7 | pub const GetAppDataDirError = error{ | |
| 8 | OutOfMemory, | |
| 9 | AppDataDirUnavailable, | |
| 10 | }; | |
| 11 | ||
| 12 | /// Caller owns returned memory. | |
| 13 | /// TODO determine if we can remove the allocator requirement | |
| 14 | pub 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 | ||
| 57 | fn 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 | ||
| 63 | test "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 @@ |
| 1 | const std = @import("../std.zig"); | |
| 2 | const builtin = @import("builtin"); | |
| 3 | const Os = builtin.Os; | |
| 4 | const debug = std.debug; | |
| 5 | const assert = debug.assert; | |
| 6 | const testing = std.testing; | |
| 7 | const mem = std.mem; | |
| 8 | const fmt = std.fmt; | |
| 9 | const Allocator = mem.Allocator; | |
| 10 | const os = std.os; | |
| 11 | const math = std.math; | |
| 12 | const posix = os.posix; | |
| 13 | const windows = os.windows; | |
| 14 | const cstr = std.cstr; | |
| 15 | ||
| 16 | pub const sep_windows = '\\'; | |
| 17 | pub const sep_posix = '/'; | |
| 18 | pub const sep = if (is_windows) sep_windows else sep_posix; | |
| 19 | ||
| 20 | pub const sep_str = [1]u8{sep}; | |
| 21 | ||
| 22 | pub const delimiter_windows = ';'; | |
| 23 | pub const delimiter_posix = ':'; | |
| 24 | pub const delimiter = if (is_windows) delimiter_windows else delimiter_posix; | |
| 25 | ||
| 26 | const is_windows = builtin.os == builtin.Os.windows; | |
| 27 | ||
| 28 | pub 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. | |
| 38 | fn 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 | ||
| 79 | pub 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. | |
| 83 | pub 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. | |
| 89 | pub fn joinPosix(allocator: *Allocator, paths: []const []const u8) ![]u8 { | |
| 90 | return joinSep(allocator, sep_posix, paths); | |
| 91 | } | |
| 92 | ||
| 93 | fn 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 | ||
| 100 | fn 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 | ||
| 107 | test "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 | ||
| 135 | pub fn isAbsolute(path: []const u8) bool { | |
| 136 | if (is_windows) { | |
| 137 | return isAbsoluteWindows(path); | |
| 138 | } else { | |
| 139 | return isAbsolutePosix(path); | |
| 140 | } | |
| 141 | } | |
| 142 | ||
| 143 | pub 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 | ||
| 162 | pub fn isAbsolutePosix(path: []const u8) bool { | |
| 163 | return path[0] == sep_posix; | |
| 164 | } | |
| 165 | ||
| 166 | test "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 | ||
| 188 | test "isAbsolutePosix" { | |
| 189 | testIsAbsolutePosix("/home/foo", true); | |
| 190 | testIsAbsolutePosix("/home/foo/..", true); | |
| 191 | testIsAbsolutePosix("bar/", false); | |
| 192 | testIsAbsolutePosix("./baz", false); | |
| 193 | } | |
| 194 | ||
| 195 | fn testIsAbsoluteWindows(path: []const u8, expected_result: bool) void { | |
| 196 | testing.expectEqual(expected_result, isAbsoluteWindows(path)); | |
| 197 | } | |
| 198 | ||
| 199 | fn testIsAbsolutePosix(path: []const u8, expected_result: bool) void { | |
| 200 | testing.expectEqual(expected_result, isAbsolutePosix(path)); | |
| 201 | } | |
| 202 | ||
| 203 | pub 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 | ||
| 215 | pub 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 | ||
| 281 | test "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 | ||
| 314 | pub fn diskDesignator(path: []const u8) []const u8 { | |
| 315 | if (is_windows) { | |
| 316 | return diskDesignatorWindows(path); | |
| 317 | } else { | |
| 318 | return ""; | |
| 319 | } | |
| 320 | } | |
| 321 | ||
| 322 | pub fn diskDesignatorWindows(path: []const u8) []const u8 { | |
| 323 | return windowsParsePath(path).disk_designator; | |
| 324 | } | |
| 325 | ||
| 326 | fn 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 | ||
| 337 | fn 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 | ||
| 360 | fn asciiUpper(byte: u8) u8 { | |
| 361 | return switch (byte) { | |
| 362 | 'a'...'z' => 'A' + (byte - 'a'), | |
| 363 | else => byte, | |
| 364 | }; | |
| 365 | } | |
| 366 | ||
| 367 | fn 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`. | |
| 379 | pub 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. | |
| 395 | pub 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. | |
| 576 | pub 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 | ||
| 639 | test "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 | ||
| 652 | test "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 | ||
| 695 | test "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 | ||
| 708 | fn testResolveWindows(paths: []const []const u8) []u8 { | |
| 709 | return resolveWindows(debug.global_allocator, paths) catch unreachable; | |
| 710 | } | |
| 711 | ||
| 712 | fn 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 | |
| 718 | pub fn dirname(path: []const u8) ?[]const u8 { | |
| 719 | if (is_windows) { | |
| 720 | return dirnameWindows(path); | |
| 721 | } else { | |
| 722 | return dirnamePosix(path); | |
| 723 | } | |
| 724 | } | |
| 725 | ||
| 726 | pub 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 | ||
| 760 | pub 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 | ||
| 786 | test "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 | ||
| 798 | test "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 | ||
| 834 | fn 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 | ||
| 842 | fn 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 | ||
| 850 | pub fn basename(path: []const u8) []const u8 { | |
| 851 | if (is_windows) { | |
| 852 | return basenameWindows(path); | |
| 853 | } else { | |
| 854 | return basenamePosix(path); | |
| 855 | } | |
| 856 | } | |
| 857 | ||
| 858 | pub 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 | ||
| 879 | pub 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 | ||
| 911 | test "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 | ||
| 950 | fn testBasename(input: []const u8, expected_output: []const u8) void { | |
| 951 | testing.expectEqualSlices(u8, expected_output, basename(input)); | |
| 952 | } | |
| 953 | ||
| 954 | fn testBasenamePosix(input: []const u8, expected_output: []const u8) void { | |
| 955 | testing.expectEqualSlices(u8, expected_output, basenamePosix(input)); | |
| 956 | } | |
| 957 | ||
| 958 | fn 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 `\\`. | |
| 966 | pub 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 | ||
| 974 | pub 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 | ||
| 1047 | pub 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 | ||
| 1092 | test "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 | ||
| 1132 | fn 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 | ||
| 1137 | fn 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 { |
| 49 | 49 | return; |
| 50 | 50 | } |
| 51 | 51 | |
| 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); | |
| 53 | 53 | if (new_buf_size == actual_buf_len) return error.StreamTooLong; |
| 54 | 54 | try buffer.resize(new_buf_size); |
| 55 | 55 | } |
| ... | ... | @@ -284,7 +284,7 @@ pub fn readFileAllocAligned(allocator: *mem.Allocator, path: []const u8, comptim |
| 284 | 284 | } |
| 285 | 285 | |
| 286 | 286 | pub fn BufferedInStream(comptime Error: type) type { |
| 287 | return BufferedInStreamCustom(os.page_size, Error); | |
| 287 | return BufferedInStreamCustom(mem.page_size, Error); | |
| 288 | 288 | } |
| 289 | 289 | |
| 290 | 290 | pub fn BufferedInStreamCustom(comptime buffer_size: usize, comptime Error: type) type { |
| ... | ... | @@ -757,7 +757,7 @@ test "io.CountingOutStream" { |
| 757 | 757 | } |
| 758 | 758 | |
| 759 | 759 | pub fn BufferedOutStream(comptime Error: type) type { |
| 760 | return BufferedOutStreamCustom(os.page_size, Error); | |
| 760 | return BufferedOutStreamCustom(mem.page_size, Error); | |
| 761 | 761 | } |
| 762 | 762 | |
| 763 | 763 | pub 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 |
| 8 | 8 | pub const GetSeekPosError = GetSeekPosErrorType; |
| 9 | 9 | |
| 10 | 10 | 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, | |
| 12 | 12 | |
| 13 | 13 | getPosFn: fn (self: *Self) GetSeekPosError!u64, |
| 14 | 14 | getEndPosFn: fn (self: *Self) GetSeekPosError!u64, |
| ... | ... | @@ -17,8 +17,8 @@ pub fn SeekableStream(comptime SeekErrorType: type, comptime GetSeekPosErrorType |
| 17 | 17 | return self.seekToFn(self, pos); |
| 18 | 18 | } |
| 19 | 19 | |
| 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); | |
| 22 | 22 | } |
| 23 | 23 | |
| 24 | 24 | pub fn getEndPos(self: *Self) GetSeekPosError!u64 { |
| ... | ... | @@ -52,7 +52,7 @@ pub const SliceSeekableInStream = struct { |
| 52 | 52 | .stream = Stream{ .readFn = readFn }, |
| 53 | 53 | .seekable_stream = SeekableInStream{ |
| 54 | 54 | .seekToFn = seekToFn, |
| 55 | .seekForwardFn = seekForwardFn, | |
| 55 | .seekByFn = seekByFn, | |
| 56 | 56 | .getEndPosFn = getEndPosFn, |
| 57 | 57 | .getPosFn = getPosFn, |
| 58 | 58 | }, |
| ... | ... | @@ -77,7 +77,7 @@ pub const SliceSeekableInStream = struct { |
| 77 | 77 | self.pos = usize_pos; |
| 78 | 78 | } |
| 79 | 79 | |
| 80 | fn seekForwardFn(in_stream: *SeekableInStream, amt: i64) SeekError!void { | |
| 80 | fn seekByFn(in_stream: *SeekableInStream, amt: i64) SeekError!void { | |
| 81 | 81 | const self = @fieldParentPtr(Self, "seekable_stream", in_stream); |
| 82 | 82 | |
| 83 | 83 | if (amt < 0) { |
std/mem.zig+23| ... | ... | @@ -8,6 +8,11 @@ const meta = std.meta; |
| 8 | 8 | const trait = meta.trait; |
| 9 | 9 | const testing = std.testing; |
| 10 | 10 | |
| 11 | pub const page_size = switch (builtin.arch) { | |
| 12 | .wasm32, .wasm64 => 64 * 1024, | |
| 13 | else => 4 * 1024, | |
| 14 | }; | |
| 15 | ||
| 11 | 16 | pub const Allocator = struct { |
| 12 | 17 | pub const Error = error{OutOfMemory}; |
| 13 | 18 | |
| ... | ... | @@ -1457,3 +1462,21 @@ test "std.mem.alignForward" { |
| 1457 | 1462 | testing.expect(alignForward(16, 8) == 16); |
| 1458 | 1463 | testing.expect(alignForward(17, 8) == 24); |
| 1459 | 1464 | } |
| 1465 | ||
| 1466 | pub 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 | ||
| 1 | 17 | const std = @import("std.zig"); |
| 2 | 18 | const builtin = @import("builtin"); |
| 3 | const Os = builtin.Os; | |
| 4 | const is_windows = builtin.os == Os.windows; | |
| 5 | const os = @This(); | |
| 19 | const MAX_PATH_BYTES = std.fs.MAX_PATH_BYTES; | |
| 6 | 20 | |
| 7 | 21 | comptime { |
| 8 | 22 | assert(@import("std") == std); // You have to run the std lib tests with --override-std-dir |
| 9 | 23 | } |
| 10 | 24 | |
| 11 | test "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 | ||
| 25 | pub const windows = @import("os/windows.zig"); | |
| 26 | 25 | pub const darwin = @import("os/darwin.zig"); |
| 27 | pub const linux = @import("os/linux.zig"); | |
| 28 | 26 | pub const freebsd = @import("os/freebsd.zig"); |
| 27 | pub const linux = @import("os/linux.zig"); | |
| 29 | 28 | pub const netbsd = @import("os/netbsd.zig"); |
| 30 | pub const zen = @import("os/zen.zig"); | |
| 31 | 29 | pub const uefi = @import("os/uefi.zig"); |
| 32 | 30 | pub const wasi = @import("os/wasi.zig"); |
| 31 | pub const windows = @import("os/windows.zig"); | |
| 32 | pub const zen = @import("os/zen.zig"); | |
| 33 | 33 | |
| 34 | /// When linking libc, this is the C API. Otherwise, it is the OS-specific system interface. | |
| 34 | 35 | pub const system = if (builtin.link_libc) std.c else switch (builtin.os) { |
| 35 | .linux => linux, | |
| 36 | 36 | .macosx, .ios, .watchos, .tvos => darwin, |
| 37 | 37 | .freebsd => freebsd, |
| 38 | .linux => linux, | |
| 38 | 39 | .netbsd => netbsd, |
| 39 | .zen => zen, | |
| 40 | 40 | .wasi => wasi, |
| 41 | 41 | .windows => windows, |
| 42 | .zen => zen, | |
| 42 | 43 | else => struct {}, |
| 43 | 44 | }; |
| 44 | 45 | |
| 45 | pub const net = @import("net.zig"); | |
| 46 | ||
| 47 | pub const ChildProcess = @import("os/child_process.zig").ChildProcess; | |
| 48 | pub const path = @import("os/path.zig"); | |
| 49 | pub const File = @import("os/file.zig").File; | |
| 50 | pub const time = @import("os/time.zig"); | |
| 51 | ||
| 52 | pub const page_size = switch (builtin.arch) { | |
| 53 | .wasm32, .wasm64 => 64 * 1024, | |
| 54 | else => 4 * 1024, | |
| 55 | }; | |
| 56 | ||
| 57 | pub const unexpected_error_tracing = builtin.mode == .Debug; | |
| 58 | pub 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 | |
| 67 | pub 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 | ||
| 77 | pub const UserInfo = @import("os/get_user_id.zig").UserInfo; | |
| 78 | pub const getUserInfo = @import("os/get_user_id.zig").getUserInfo; | |
| 79 | ||
| 80 | const windows_util = @import("os/windows/util.zig"); | |
| 81 | pub const windowsWaitSingle = windows_util.windowsWaitSingle; | |
| 82 | pub const windowsWrite = windows_util.windowsWrite; | |
| 83 | pub const windowsIsCygwinPty = windows_util.windowsIsCygwinPty; | |
| 84 | pub const windowsOpen = windows_util.windowsOpen; | |
| 85 | pub const windowsOpenW = windows_util.windowsOpenW; | |
| 86 | pub const createWindowsEnvBlock = windows_util.createWindowsEnvBlock; | |
| 87 | ||
| 88 | pub const WindowsCreateIoCompletionPortError = windows_util.WindowsCreateIoCompletionPortError; | |
| 89 | pub const windowsCreateIoCompletionPort = windows_util.windowsCreateIoCompletionPort; | |
| 90 | ||
| 91 | pub const WindowsPostQueuedCompletionStatusError = windows_util.WindowsPostQueuedCompletionStatusError; | |
| 92 | pub const windowsPostQueuedCompletionStatus = windows_util.windowsPostQueuedCompletionStatus; | |
| 93 | ||
| 94 | pub const WindowsWaitResult = windows_util.WindowsWaitResult; | |
| 95 | pub const windowsGetQueuedCompletionStatus = windows_util.windowsGetQueuedCompletionStatus; | |
| 96 | ||
| 97 | pub const WindowsWaitError = windows_util.WaitError; | |
| 98 | pub const WindowsOpenError = windows_util.OpenError; | |
| 99 | pub const WindowsWriteError = windows_util.WriteError; | |
| 100 | pub const WindowsReadError = windows_util.ReadError; | |
| 101 | ||
| 102 | pub const getAppDataDir = @import("os/get_app_data_dir.zig").getAppDataDir; | |
| 103 | pub const GetAppDataDirError = @import("os/get_app_data_dir.zig").GetAppDataDirError; | |
| 104 | ||
| 105 | pub const getRandomBytes = posix.getrandom; | |
| 106 | pub const abort = posix.abort; | |
| 107 | pub const exit = posix.exit; | |
| 108 | pub const symLink = posix.symlink; | |
| 109 | pub const symLinkC = posix.symlinkC; | |
| 110 | pub const symLinkW = posix.symlinkW; | |
| 111 | pub const deleteFile = posix.unlink; | |
| 112 | pub const deleteFileC = posix.unlinkC; | |
| 113 | pub const deleteFileW = posix.unlinkW; | |
| 114 | pub const rename = posix.rename; | |
| 115 | pub const renameC = posix.renameC; | |
| 116 | pub const renameW = posix.renameW; | |
| 117 | pub const changeCurDir = posix.chdir; | |
| 118 | pub const changeCurDirC = posix.chdirC; | |
| 119 | pub const changeCurDirW = posix.chdirW; | |
| 120 | ||
| 121 | const debug = std.debug; | |
| 122 | const assert = debug.assert; | |
| 123 | const testing = std.testing; | |
| 124 | ||
| 125 | const c = std.c; | |
| 126 | ||
| 127 | const mem = std.mem; | |
| 128 | const Allocator = mem.Allocator; | |
| 129 | ||
| 130 | const BufMap = std.BufMap; | |
| 131 | const cstr = std.cstr; | |
| 132 | ||
| 133 | const io = std.io; | |
| 134 | const base64 = std.base64; | |
| 135 | const ArrayList = std.ArrayList; | |
| 136 | const Buffer = std.Buffer; | |
| 137 | const math = std.math; | |
| 138 | ||
| 139 | pub 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`. | |
| 47 | pub 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. | |
| 55 | pub 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. | |
| 62 | pub 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, | |
| 154 | 76 | } |
| 155 | 77 | } |
| 156 | 78 | |
| 157 | /// Caller must free result when done. | |
| 158 | /// TODO make this go through libc when we have it | |
| 159 | pub 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); | |
| 79 | pub const GetRandomError = error{}; | |
| 166 | 80 | |
| 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. | |
| 86 | pub fn getrandom(buf: []u8) GetRandomError!void { | |
| 87 | if (windows.is_the_target) { | |
| 88 | return windows.RtlGenRandom(buf); | |
| 89 | } | |
| 90 | if (linux.is_the_target) { | |
| 168 | 91 | 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), | |
| 218 | 99 | } |
| 219 | 100 | } |
| 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), | |
| 232 | 106 | } |
| 233 | return result; | |
| 234 | 107 | } |
| 108 | return getRandomBytesDevURandom(buf); | |
| 235 | 109 | } |
| 236 | 110 | |
| 237 | test "os.getEnvMap" { | |
| 238 | var env = try getEnvMap(std.debug.global_allocator); | |
| 239 | defer env.deinit(); | |
| 240 | } | |
| 241 | ||
| 242 | pub 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 | |
| 252 | pub 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 | } | |
| 111 | fn getRandomBytesDevURandom(buf: []u8) !void { | |
| 112 | const fd = try openC(c"/dev/urandom", O_RDONLY | O_CLOEXEC, 0); | |
| 113 | defer close(fd); | |
| 274 | 114 | |
| 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 | } | |
| 279 | 118 | |
| 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 | |
| 122 | pub 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(); | |
| 286 | 130 | } |
| 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) {} | |
| 290 | 136 | } |
| 291 | } | |
| 292 | ||
| 293 | test "os.getEnvVarOwned" { | |
| 294 | var ga = debug.global_allocator; | |
| 295 | testing.expectError(error.EnvironmentVariableNotFound, getEnvVarOwned(ga, "BADENV")); | |
| 296 | } | |
| 297 | 137 | |
| 298 | /// The result is a slice of `out_buffer`, from index `0`. | |
| 299 | pub fn getCwd(out_buffer: *[MAX_PATH_BYTES]u8) ![]u8 { | |
| 300 | return posix.getcwd(out_buffer); | |
| 301 | } | |
| 138 | raise(SIGABRT); | |
| 302 | 139 | |
| 303 | /// Caller must free the returned memory. | |
| 304 | pub 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 | |
| 308 | 141 | |
| 309 | test "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); | |
| 314 | 144 | } |
| 315 | 145 | |
| 316 | // here we replace the standard +/ with -_ so that it can be used in a file name | |
| 317 | const b64_fs_encoder = base64.Base64Encoder.init("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_", base64.standard_pad_char); | |
| 146 | pub const RaiseError = error{}; | |
| 318 | 147 | |
| 319 | /// TODO remove the allocator requirement from this API | |
| 320 | pub 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 | |
| 148 | pub 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 | } | |
| 326 | 154 | } |
| 327 | 155 | |
| 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), | |
| 344 | 160 | } |
| 345 | 161 | } |
| 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. | |
| 353 | pub 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; | |
| 359 | 162 | |
| 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 | } | |
| 362 | 166 | |
| 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), | |
| 370 | 175 | } |
| 371 | 176 | } |
| 372 | 177 | |
| 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 | |
| 376 | pub 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. | |
| 179 | pub 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); | |
| 390 | 185 | } |
| 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); | |
| 391 | 193 | } |
| 392 | 194 | |
| 393 | pub const AtomicFile = struct { | |
| 394 | file: os.File, | |
| 395 | tmp_path_buf: [MAX_PATH_BYTES]u8, | |
| 396 | dest_path: []const u8, | |
| 397 | finished: bool, | |
| 195 | pub const ReadError = error{ | |
| 196 | InputOutput, | |
| 197 | SystemResources, | |
| 198 | IsDir, | |
| 199 | OperationAborted, | |
| 200 | BrokenPipe, | |
| 201 | Unexpected, | |
| 202 | }; | |
| 398 | 203 | |
| 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`. | |
| 208 | pub 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 | } | |
| 400 | 212 | |
| 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 | }}; | |
| 413 | 218 | |
| 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), | |
| 417 | 223 | } |
| 224 | } | |
| 418 | 225 | |
| 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), | |
| 438 | 253 | } |
| 439 | 254 | } |
| 255 | return index; | |
| 256 | } | |
| 440 | 257 | |
| 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`. | |
| 261 | pub 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 | } | |
| 447 | 297 | } |
| 448 | 298 | } |
| 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), | |
| 463 | 313 | } |
| 464 | 314 | } |
| 465 | }; | |
| 466 | ||
| 467 | const default_new_dir_mode = 0o755; | |
| 468 | ||
| 469 | /// Create a new directory. | |
| 470 | pub fn makeDir(dir_path: []const u8) !void { | |
| 471 | return posix.mkdir(dir_path, default_new_dir_mode); | |
| 472 | 315 | } |
| 473 | 316 | |
| 474 | /// Same as `makeDir` except the parameter is a null-terminated UTF8-encoded string. | |
| 475 | pub fn makeDirC(dir_path: [*]const u8) !void { | |
| 476 | return posix.mkdirC(dir_path, default_new_dir_mode); | |
| 477 | } | |
| 317 | pub const WriteError = error{ | |
| 318 | DiskQuota, | |
| 319 | FileTooBig, | |
| 320 | InputOutput, | |
| 321 | NoSpaceLeft, | |
| 322 | AccessDenied, | |
| 323 | BrokenPipe, | |
| 324 | SystemResources, | |
| 325 | OperationAborted, | |
| 326 | Unexpected, | |
| 327 | }; | |
| 478 | 328 | |
| 479 | /// Same as `makeDir` except the parameter is a null-terminated UTF16LE-encoded string. | |
| 480 | pub 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`. | |
| 332 | pub 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 | } | |
| 483 | 336 | |
| 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 | |
| 489 | pub 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 | } | |
| 492 | 348 | |
| 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; | |
| 508 | 362 | continue; |
| 509 | 363 | }, |
| 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), | |
| 517 | 377 | } |
| 518 | 378 | } |
| 519 | 379 | } |
| 520 | 380 | |
| 521 | /// Returns `error.DirNotEmpty` if the directory is not empty. | |
| 522 | /// To delete a directory recursively, see `deleteTree`. | |
| 523 | pub 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. | |
| 528 | pub 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`. | |
| 384 | pub 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 | } | |
| 531 | 424 | |
| 532 | /// Same as `deleteDir` except the parameter is a null-terminated UTF16LE-encoded string. | |
| 533 | pub 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 | } | |
| 535 | 444 | } |
| 536 | 445 | |
| 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. | |
| 540 | const DeleteTreeError = error{ | |
| 541 | OutOfMemory, | |
| 446 | pub const OpenError = error{ | |
| 542 | 447 | AccessDenied, |
| 543 | 448 | FileTooBig, |
| 544 | 449 | IsDir, |
| ... | ... | @@ -547,1239 +452,1876 @@ const DeleteTreeError = error{ |
| 547 | 452 | NameTooLong, |
| 548 | 453 | SystemFdQuotaExceeded, |
| 549 | 454 | NoDevice, |
| 455 | FileNotFound, | |
| 550 | 456 | SystemResources, |
| 551 | 457 | NoSpaceLeft, |
| 552 | PathAlreadyExists, | |
| 553 | ReadOnlyFileSystem, | |
| 554 | 458 | NotDir, |
| 555 | FileNotFound, | |
| 556 | FileSystem, | |
| 557 | FileBusy, | |
| 558 | DirNotEmpty, | |
| 459 | PathAlreadyExists, | |
| 559 | 460 | 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 | ||
| 568 | 461 | Unexpected, |
| 569 | 462 | }; |
| 570 | 463 | |
| 571 | /// TODO determine if we can remove the allocator requirement | |
| 572 | pub 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`. | |
| 467 | pub 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 | } | |
| 603 | 471 | |
| 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 | |
| 475 | pub 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), | |
| 637 | 501 | } |
| 638 | return deleteDir(full_path); | |
| 639 | 502 | } |
| 640 | 503 | } |
| 641 | 504 | |
| 642 | pub 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"), | |
| 505 | pub 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), | |
| 759 | 513 | } |
| 760 | 514 | } |
| 515 | } | |
| 761 | 516 | |
| 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 | |
| 523 | pub 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); | |
| 772 | 530 | } |
| 531 | allocator.free(argv_buf); | |
| 773 | 532 | } |
| 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; | |
| 774 | 537 | |
| 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; | |
| 807 | 541 | |
| 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); | |
| 811 | 544 | |
| 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))); | |
| 828 | 548 | } |
| 829 | 549 | |
| 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); | |
| 855 | 570 | } |
| 856 | 571 | } |
| 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; | |
| 910 | 574 | } |
| 575 | return execveErrnoToErr(err); | |
| 576 | } | |
| 911 | 577 | |
| 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 | }; | |
| 578 | pub 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; | |
| 963 | 594 | } |
| 595 | assert(i == envp_count); | |
| 964 | 596 | } |
| 965 | }; | |
| 966 | ||
| 967 | /// Read value of a symbolic link. | |
| 968 | /// The return value is a slice of buffer, from index `0`. | |
| 969 | pub 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; | |
| 971 | 599 | } |
| 972 | 600 | |
| 973 | /// Same as `readLink`, except the `pathname` parameter is null-terminated. | |
| 974 | pub fn readLinkC(buffer: *[posix.PATH_MAX]u8, pathname: [*]const u8) ![]u8 { | |
| 975 | return posix.readlinkC(pathname, buffer); | |
| 601 | pub 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); | |
| 976 | 607 | } |
| 977 | 608 | |
| 978 | pub const ArgIteratorPosix = struct { | |
| 979 | index: usize, | |
| 980 | count: usize, | |
| 609 | pub const ExecveError = error{ | |
| 610 | SystemResources, | |
| 611 | AccessDenied, | |
| 612 | InvalidExe, | |
| 613 | FileSystem, | |
| 614 | IsDir, | |
| 615 | FileNotFound, | |
| 616 | NotDir, | |
| 617 | FileBusy, | |
| 981 | 618 | |
| 982 | pub fn init() ArgIteratorPosix { | |
| 983 | return ArgIteratorPosix{ | |
| 984 | .index = 0, | |
| 985 | .count = raw.len, | |
| 986 | }; | |
| 987 | } | |
| 619 | Unexpected, | |
| 620 | }; | |
| 988 | 621 | |
| 989 | pub fn next(self: *ArgIteratorPosix) ?[]const u8 { | |
| 990 | if (self.index == self.count) return null; | |
| 622 | fn 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 | } | |
| 991 | 644 | |
| 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 | |
| 648 | pub 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; | |
| 995 | 660 | } |
| 661 | return null; | |
| 662 | } | |
| 996 | 663 | |
| 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 | |
| 667 | pub 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 | } | |
| 999 | 674 | |
| 1000 | self.index += 1; | |
| 1001 | return true; | |
| 675 | /// See std.elf for the constants. | |
| 676 | pub 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 | } | |
| 1002 | 685 | } |
| 686 | return 0; | |
| 687 | } | |
| 1003 | 688 | |
| 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; | |
| 689 | pub const GetCwdError = error{ | |
| 690 | NameTooLong, | |
| 691 | CurrentWorkingDirectoryUnlinked, | |
| 692 | Unexpected, | |
| 1007 | 693 | }; |
| 1008 | 694 | |
| 1009 | pub 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. | |
| 696 | pub fn getcwd(out_buffer: []u8) GetCwdError![]u8 { | |
| 697 | if (windows.is_the_target and !builtin.link_libc) { | |
| 698 | return windows.GetCurrentDirectory(out_buffer); | |
| 1045 | 699 | } |
| 1046 | 700 | |
| 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 | } | |
| 1057 | 715 | |
| 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 | } | |
| 716 | pub 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`. | |
| 738 | pub 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`. | |
| 752 | pub 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 | ||
| 778 | pub 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 | ||
| 784 | pub 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 | ||
| 805 | pub 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`. | |
| 828 | pub 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. | |
| 839 | pub 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 | ||
| 863 | const 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. | |
| 882 | pub 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. | |
| 896 | pub 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 | ||
| 927 | pub 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. | |
| 944 | pub 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. | |
| 955 | pub 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 | ||
| 979 | pub 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. | |
| 995 | pub 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. | |
| 1006 | pub 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 | ||
| 1030 | pub 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. | |
| 1043 | pub 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. | |
| 1054 | pub 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 | ||
| 1073 | pub 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. | |
| 1086 | pub 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. | |
| 1097 | pub 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 | ||
| 1118 | pub const SetIdError = error{ | |
| 1119 | ResourceLimitReached, | |
| 1120 | InvalidUserId, | |
| 1121 | PermissionDenied, | |
| 1122 | Unexpected, | |
| 1123 | }; | |
| 1124 | ||
| 1125 | pub 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 | ||
| 1135 | pub 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 | ||
| 1145 | pub 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 | ||
| 1155 | pub 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. | |
| 1166 | pub 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 | ||
| 1187 | pub 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 | ||
| 1209 | pub 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 | ||
| 1236 | pub 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 | ||
| 1251 | pub 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 | |
| 1289 | pub 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 | ||
| 1310 | const 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 | ||
| 1327 | pub 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 | ||
| 1339 | pub 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`. | |
| 1368 | pub 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. | |
| 1396 | pub 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, | |
| 1083 | 1418 | } |
| 1084 | 1419 | } |
| 1420 | } | |
| 1421 | ||
| 1422 | pub 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 | ||
| 1438 | pub 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 | } | |
| 1085 | 1450 | |
| 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 | } | |
| 1451 | pub 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 | ||
| 1479 | pub 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, | |
| 1128 | 1493 | } |
| 1494 | } | |
| 1129 | 1495 | |
| 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. | |
| 1499 | pub 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, | |
| 1134 | 1510 | } |
| 1135 | 1511 | } |
| 1512 | } | |
| 1136 | 1513 | |
| 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 | } | |
| 1514 | pub const EventFdError = error{ | |
| 1515 | SystemResources, | |
| 1516 | ProcessFdQuotaExceeded, | |
| 1517 | SystemFdQuotaExceeded, | |
| 1518 | Unexpected, | |
| 1519 | }; | |
| 1520 | ||
| 1521 | pub 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, | |
| 1155 | 1532 | } |
| 1533 | } | |
| 1534 | ||
| 1535 | pub const GetSockNameError = error{ | |
| 1536 | /// Insufficient resources were available in the system to perform the operation. | |
| 1537 | SystemResources, | |
| 1538 | ||
| 1539 | Unexpected, | |
| 1156 | 1540 | }; |
| 1157 | 1541 | |
| 1158 | pub const ArgIterator = struct { | |
| 1159 | const InnerType = if (builtin.os == Os.windows) ArgIteratorWindows else ArgIteratorPosix; | |
| 1542 | pub 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 | ||
| 1557 | pub 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, | |
| 1160 | 1585 | |
| 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 | }; | |
| 1162 | 1592 | |
| 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`. | |
| 1596 | pub 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, | |
| 1167 | 1619 | } |
| 1620 | } | |
| 1621 | } | |
| 1168 | 1622 | |
| 1169 | return ArgIterator{ .inner = InnerType.init() }; | |
| 1623 | /// Same as `connect` except it is for blocking socket file descriptors. | |
| 1624 | /// It expects to receive EINPROGRESS`. | |
| 1625 | pub 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 | } | |
| 1170 | 1648 | } |
| 1649 | } | |
| 1171 | 1650 | |
| 1172 | pub const NextError = ArgIteratorWindows.NextError; | |
| 1651 | pub 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 | } | |
| 1173 | 1684 | |
| 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); | |
| 1685 | pub 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, | |
| 1180 | 1694 | } |
| 1181 | 1695 | } |
| 1696 | } | |
| 1182 | 1697 | |
| 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(); | |
| 1698 | pub const FStatError = error{ | |
| 1699 | SystemResources, | |
| 1700 | Unexpected, | |
| 1701 | }; | |
| 1702 | ||
| 1703 | pub 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 | } | |
| 1186 | 1712 | } |
| 1187 | 1713 | |
| 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), | |
| 1192 | 1719 | } |
| 1720 | } | |
| 1721 | ||
| 1722 | pub 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, | |
| 1193 | 1730 | }; |
| 1194 | 1731 | |
| 1195 | pub fn args() ArgIterator { | |
| 1196 | return ArgIterator.init(); | |
| 1732 | pub 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 | } | |
| 1197 | 1740 | } |
| 1198 | 1741 | |
| 1199 | /// Caller must call argsFree on result. | |
| 1200 | pub 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 | } | |
| 1742 | pub const KEventError = error{ | |
| 1743 | /// The process does not have permission to register a filter. | |
| 1744 | AccessDenied, | |
| 1209 | 1745 | |
| 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, | |
| 1212 | 1748 | |
| 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, | |
| 1218 | 1751 | |
| 1219 | var result_slice = try allocator.alloc([]u8, count); | |
| 1752 | /// The specified process to attach to does not exist. | |
| 1753 | ProcessNotFound, | |
| 1754 | }; | |
| 1220 | 1755 | |
| 1221 | var i: usize = 0; | |
| 1222 | while (i < count) : (i += 1) { | |
| 1223 | result_slice[i] = mem.toSlice(u8, argv[i]); | |
| 1756 | pub 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, | |
| 1224 | 1775 | } |
| 1776 | } | |
| 1777 | } | |
| 1778 | ||
| 1779 | pub const INotifyInitError = error{ | |
| 1780 | ProcessFdQuotaExceeded, | |
| 1781 | SystemFdQuotaExceeded, | |
| 1782 | SystemResources, | |
| 1783 | Unexpected, | |
| 1784 | }; | |
| 1225 | 1785 | |
| 1226 | return result_slice; | |
| 1786 | /// initialize an inotify instance | |
| 1787 | pub 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), | |
| 1227 | 1796 | } |
| 1797 | } | |
| 1228 | 1798 | |
| 1229 | // TODO refactor to only make 1 allocation. | |
| 1230 | var it = args(); | |
| 1231 | var contents = try Buffer.initSize(allocator, 0); | |
| 1232 | defer contents.deinit(); | |
| 1799 | pub const INotifyAddWatchError = error{ | |
| 1800 | AccessDenied, | |
| 1801 | NameTooLong, | |
| 1802 | FileNotFound, | |
| 1803 | SystemResources, | |
| 1804 | UserResourceLimitReached, | |
| 1805 | Unexpected, | |
| 1806 | }; | |
| 1233 | 1807 | |
| 1234 | var slice_list = ArrayList(usize).init(allocator); | |
| 1235 | defer slice_list.deinit(); | |
| 1808 | /// add a watch to an initialized inotify instance | |
| 1809 | pub 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 | } | |
| 1236 | 1813 | |
| 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. | |
| 1815 | pub 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), | |
| 1242 | 1828 | } |
| 1829 | } | |
| 1243 | 1830 | |
| 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 | |
| 1832 | pub 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 | } | |
| 1250 | 1840 | |
| 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); | |
| 1841 | pub const MProtectError = error{ | |
| 1842 | AccessDenied, | |
| 1843 | OutOfMemory, | |
| 1844 | Unexpected, | |
| 1845 | }; | |
| 1254 | 1846 | |
| 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 | |
| 1848 | pub 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), | |
| 1260 | 1860 | } |
| 1861 | } | |
| 1862 | ||
| 1863 | pub const ForkError = error{ | |
| 1864 | SystemResources, | |
| 1865 | Unexpected, | |
| 1866 | }; | |
| 1261 | 1867 | |
| 1262 | return result_slice_list; | |
| 1868 | pub 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 | } | |
| 1263 | 1876 | } |
| 1264 | 1877 | |
| 1265 | pub 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]); | |
| 1878 | pub const MMapError = error{ | |
| 1879 | AccessDenied, | |
| 1880 | PermissionDenied, | |
| 1881 | LockedMemoryLimitExceeded, | |
| 1882 | SystemFdQuotaExceeded, | |
| 1883 | MemoryMappingNotSupported, | |
| 1884 | OutOfMemory, | |
| 1885 | }; | |
| 1272 | 1886 | |
| 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 | |
| 1891 | pub 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), | |
| 1274 | 1914 | } |
| 1915 | } | |
| 1275 | 1916 | |
| 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. | |
| 1923 | pub 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, | |
| 1279 | 1929 | } |
| 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); | |
| 1283 | 1930 | } |
| 1284 | 1931 | |
| 1285 | test "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" }); | |
| 1932 | pub 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 | }; | |
| 1292 | 1945 | |
| 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. | |
| 1948 | pub 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); | |
| 1300 | 1956 | } |
| 1301 | 1957 | |
| 1302 | fn 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. | |
| 1959 | pub 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), | |
| 1307 | 1980 | } |
| 1308 | testing.expect(it.next(debug.global_allocator) == null); | |
| 1309 | 1981 | } |
| 1310 | 1982 | |
| 1311 | pub 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"), | |
| 1983 | pub const PipeError = error{ | |
| 1984 | SystemFdQuotaExceeded, | |
| 1985 | ProcessFdQuotaExceeded, | |
| 1986 | }; | |
| 1987 | ||
| 1988 | /// Creates a unidirectional data channel that can be used for interprocess communication. | |
| 1989 | pub 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), | |
| 1326 | 1997 | } |
| 1327 | 1998 | } |
| 1328 | 1999 | |
| 1329 | test "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. | |
| 2000 | pub 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), | |
| 1333 | 2008 | } |
| 1334 | 2009 | } |
| 1335 | 2010 | |
| 1336 | pub 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 | } | |
| 2011 | pub const SysCtlError = error{ | |
| 2012 | PermissionDenied, | |
| 2013 | SystemResources, | |
| 2014 | Unexpected, | |
| 2015 | }; | |
| 2016 | ||
| 2017 | pub 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), | |
| 1345 | 2030 | } |
| 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 | |
| 1359 | pub 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. | |
| 1395 | pub 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. | |
| 1402 | pub 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 | ||
| 2033 | pub 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), | |
| 1421 | 2046 | } |
| 1422 | 2047 | } |
| 1423 | 2048 | |
| 1424 | pub const Thread = struct { | |
| 1425 | data: Data, | |
| 2049 | pub 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 | ||
| 2057 | pub 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 | } | |
| 1426 | 2068 | |
| 1427 | pub const use_pthreads = is_posix and builtin.link_libc; | |
| 2069 | pub const SeekError = error{ | |
| 2070 | Unseekable, | |
| 2071 | Unexpected, | |
| 2072 | }; | |
| 1428 | 2073 | |
| 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. | |
| 2075 | pub 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 | } | |
| 1439 | 2101 | |
| 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. | |
| 2103 | pub 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 | } | |
| 1447 | 2128 | |
| 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. | |
| 2130 | pub 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), | |
| 1453 | 2139 | } |
| 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 | } | |
| 1467 | 2154 | |
| 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. | |
| 2156 | pub 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), | |
| 1524 | 2167 | } |
| 1525 | 2168 | } |
| 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 | } | |
| 1527 | 2183 | |
| 1528 | pub 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. | |
| 2184 | pub 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, | |
| 1546 | 2197 | SystemResources, |
| 2198 | NoSpaceLeft, | |
| 2199 | FileSystem, | |
| 2200 | BadPathName, | |
| 2201 | DeviceBusy, | |
| 1547 | 2202 | |
| 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, | |
| 1550 | 2207 | |
| 1551 | 2208 | Unexpected, |
| 1552 | 2209 | }; |
| 1553 | 2210 | |
| 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 | |
| 1558 | pub 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`. | |
| 2216 | pub 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 | } | |
| 1604 | 2224 | |
| 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. | |
| 2226 | pub 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); | |
| 1612 | 2230 | } |
| 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); | |
| 1613 | 2234 | |
| 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; | |
| 1617 | 2237 | |
| 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), | |
| 1638 | 2252 | }; |
| 2253 | return mem.toSlice(u8, result_path); | |
| 2254 | } | |
| 1639 | 2255 | |
| 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. | |
| 2257 | pub 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]; | |
| 1725 | 2280 | } |
| 1726 | 2281 | |
| 1727 | pub 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 | |
| 2284 | pub 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. | |
| 2297 | pub const unexpected_error_tracing = builtin.mode == .Debug; | |
| 1730 | 2298 | |
| 2299 | pub 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. | |
| 1731 | 2303 | Unexpected, |
| 1732 | 2304 | }; |
| 1733 | 2305 | |
| 1734 | pub 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. | |
| 2308 | pub fn unexpectedErrno(err: usize) UnexpectedError { | |
| 2309 | if (unexpected_error_tracing) { | |
| 2310 | std.debug.warn("unexpected errno: {}\n", err); | |
| 2311 | std.debug.dumpCurrentStackTrace(null); | |
| 1784 | 2312 | } |
| 2313 | return error.Unexpected; | |
| 2314 | } | |
| 2315 | ||
| 2316 | test "" { | |
| 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"); | |
| 1785 | 2327 | } |
std/os/child_process.zig deleted-826| ... | ... | @@ -1,826 +0,0 @@ |
| 1 | const std = @import("../std.zig"); | |
| 2 | const cstr = std.cstr; | |
| 3 | const unicode = std.unicode; | |
| 4 | const io = std.io; | |
| 5 | const os = std.os; | |
| 6 | const posix = os.posix; | |
| 7 | const windows = os.windows; | |
| 8 | const mem = std.mem; | |
| 9 | const debug = std.debug; | |
| 10 | const BufMap = std.BufMap; | |
| 11 | const Buffer = std.Buffer; | |
| 12 | const builtin = @import("builtin"); | |
| 13 | const Os = builtin.Os; | |
| 14 | const LinkedList = std.LinkedList; | |
| 15 | const windows_util = @import("windows/util.zig"); | |
| 16 | const maxInt = std.math.maxInt; | |
| 17 | ||
| 18 | const is_windows = builtin.os == Os.windows; | |
| 19 | ||
| 20 | pub 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 | ||
| 652 | fn 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]. | |
| 694 | fn 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 | ||
| 730 | fn windowsDestroyPipe(rd: ?windows.HANDLE, wr: ?windows.HANDLE) void { | |
| 731 | if (rd) |h| os.close(h); | |
| 732 | if (wr) |h| os.close(h); | |
| 733 | } | |
| 734 | ||
| 735 | fn 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 | ||
| 745 | fn 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 | ||
| 755 | fn 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 | ||
| 767 | fn 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. | |
| 774 | fn forkChildErrReport(fd: i32, err: ChildProcess.SpawnError) noreturn { | |
| 775 | writeIntFd(fd, ErrInt(@errorToInt(err))) catch {}; | |
| 776 | posix.exit(1); | |
| 777 | } | |
| 778 | ||
| 779 | const ErrInt = @IntType(false, @sizeOf(anyerror) * 8); | |
| 780 | ||
| 781 | fn 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 | ||
| 786 | fn 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. | |
| 792 | pub 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 @@ |
| 1 | const std = @import("../std.zig"); | |
| 2 | const builtin = @import("builtin"); | |
| 3 | const os = std.os; | |
| 4 | const io = std.io; | |
| 5 | const mem = std.mem; | |
| 6 | const math = std.math; | |
| 7 | const assert = std.debug.assert; | |
| 8 | const posix = os.posix; | |
| 9 | const windows = os.windows; | |
| 10 | const Os = builtin.Os; | |
| 11 | const windows_util = @import("windows/util.zig"); | |
| 12 | const maxInt = std.math.maxInt; | |
| 13 | ||
| 14 | const is_posix = builtin.os != builtin.Os.windows; | |
| 15 | const is_windows = builtin.os == builtin.Os.windows; | |
| 16 | ||
| 17 | pub 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 @@ |
| 1 | const std = @import("../std.zig"); | |
| 2 | const builtin = @import("builtin"); | |
| 3 | const unicode = std.unicode; | |
| 4 | const mem = std.mem; | |
| 5 | const os = std.os; | |
| 6 | ||
| 7 | pub const GetAppDataDirError = error{ | |
| 8 | OutOfMemory, | |
| 9 | AppDataDirUnavailable, | |
| 10 | }; | |
| 11 | ||
| 12 | /// Caller owns returned memory. | |
| 13 | /// TODO determine if we can remove the allocator requirement | |
| 14 | pub 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 | ||
| 57 | fn 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 | ||
| 63 | test "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 @@ |
| 1 | const builtin = @import("builtin"); | |
| 2 | const Os = builtin.Os; | |
| 3 | const os = @import("../os.zig"); | |
| 4 | const io = @import("../io.zig"); | |
| 5 | ||
| 6 | pub const UserInfo = struct { | |
| 7 | uid: u32, | |
| 8 | gid: u32, | |
| 9 | }; | |
| 10 | ||
| 11 | /// POSIX function which gets a uid from username. | |
| 12 | pub 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 | ||
| 19 | const 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 | ||
| 30 | pub 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 { |
| 320 | 320 | return syscall1(SYS_close, @bitCast(usize, isize(fd))); |
| 321 | 321 | } |
| 322 | 322 | |
| 323 | pub 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`. | |
| 324 | pub 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`. | |
| 336 | pub fn lseek(fd: i32, offset: i64, whence: usize) usize { | |
| 337 | return syscall3(SYS_lseek, @bitCast(usize, isize(fd)), @bitCast(usize, offset), whence); | |
| 325 | 338 | } |
| 326 | 339 | |
| 327 | 340 | pub fn exit(status: i32) noreturn { |
std/os/path.zig deleted-1286| ... | ... | @@ -1,1286 +0,0 @@ |
| 1 | const std = @import("../std.zig"); | |
| 2 | const builtin = @import("builtin"); | |
| 3 | const Os = builtin.Os; | |
| 4 | const debug = std.debug; | |
| 5 | const assert = debug.assert; | |
| 6 | const testing = std.testing; | |
| 7 | const mem = std.mem; | |
| 8 | const fmt = std.fmt; | |
| 9 | const Allocator = mem.Allocator; | |
| 10 | const os = std.os; | |
| 11 | const math = std.math; | |
| 12 | const posix = os.posix; | |
| 13 | const windows = os.windows; | |
| 14 | const cstr = std.cstr; | |
| 15 | const windows_util = @import("windows/util.zig"); | |
| 16 | ||
| 17 | pub const sep_windows = '\\'; | |
| 18 | pub const sep_posix = '/'; | |
| 19 | pub const sep = if (is_windows) sep_windows else sep_posix; | |
| 20 | ||
| 21 | pub const sep_str = [1]u8{sep}; | |
| 22 | ||
| 23 | pub const delimiter_windows = ';'; | |
| 24 | pub const delimiter_posix = ':'; | |
| 25 | pub const delimiter = if (is_windows) delimiter_windows else delimiter_posix; | |
| 26 | ||
| 27 | const is_windows = builtin.os == builtin.Os.windows; | |
| 28 | ||
| 29 | pub 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. | |
| 39 | fn 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 | ||
| 80 | pub 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. | |
| 84 | pub 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. | |
| 90 | pub fn joinPosix(allocator: *Allocator, paths: []const []const u8) ![]u8 { | |
| 91 | return joinSep(allocator, sep_posix, paths); | |
| 92 | } | |
| 93 | ||
| 94 | fn 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 | ||
| 101 | fn 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 | ||
| 108 | test "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 | ||
| 136 | pub fn isAbsolute(path: []const u8) bool { | |
| 137 | if (is_windows) { | |
| 138 | return isAbsoluteWindows(path); | |
| 139 | } else { | |
| 140 | return isAbsolutePosix(path); | |
| 141 | } | |
| 142 | } | |
| 143 | ||
| 144 | pub 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 | ||
| 163 | pub fn isAbsolutePosix(path: []const u8) bool { | |
| 164 | return path[0] == sep_posix; | |
| 165 | } | |
| 166 | ||
| 167 | test "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 | ||
| 189 | test "os.path.isAbsolutePosix" { | |
| 190 | testIsAbsolutePosix("/home/foo", true); | |
| 191 | testIsAbsolutePosix("/home/foo/..", true); | |
| 192 | testIsAbsolutePosix("bar/", false); | |
| 193 | testIsAbsolutePosix("./baz", false); | |
| 194 | } | |
| 195 | ||
| 196 | fn testIsAbsoluteWindows(path: []const u8, expected_result: bool) void { | |
| 197 | testing.expectEqual(expected_result, isAbsoluteWindows(path)); | |
| 198 | } | |
| 199 | ||
| 200 | fn testIsAbsolutePosix(path: []const u8, expected_result: bool) void { | |
| 201 | testing.expectEqual(expected_result, isAbsolutePosix(path)); | |
| 202 | } | |
| 203 | ||
| 204 | pub 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 | ||
| 216 | pub 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 | ||
| 282 | test "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 | ||
| 315 | pub fn diskDesignator(path: []const u8) []const u8 { | |
| 316 | if (is_windows) { | |
| 317 | return diskDesignatorWindows(path); | |
| 318 | } else { | |
| 319 | return ""; | |
| 320 | } | |
| 321 | } | |
| 322 | ||
| 323 | pub fn diskDesignatorWindows(path: []const u8) []const u8 { | |
| 324 | return windowsParsePath(path).disk_designator; | |
| 325 | } | |
| 326 | ||
| 327 | fn 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 | ||
| 338 | fn 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 | ||
| 361 | fn asciiUpper(byte: u8) u8 { | |
| 362 | return switch (byte) { | |
| 363 | 'a'...'z' => 'A' + (byte - 'a'), | |
| 364 | else => byte, | |
| 365 | }; | |
| 366 | } | |
| 367 | ||
| 368 | fn 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`. | |
| 380 | pub 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. | |
| 396 | pub 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. | |
| 577 | pub 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 | ||
| 640 | test "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 | ||
| 653 | test "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 | ||
| 696 | test "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 | ||
| 709 | fn testResolveWindows(paths: []const []const u8) []u8 { | |
| 710 | return resolveWindows(debug.global_allocator, paths) catch unreachable; | |
| 711 | } | |
| 712 | ||
| 713 | fn 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 | |
| 719 | pub fn dirname(path: []const u8) ?[]const u8 { | |
| 720 | if (is_windows) { | |
| 721 | return dirnameWindows(path); | |
| 722 | } else { | |
| 723 | return dirnamePosix(path); | |
| 724 | } | |
| 725 | } | |
| 726 | ||
| 727 | pub 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 | ||
| 761 | pub 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 | ||
| 787 | test "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 | ||
| 799 | test "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 | ||
| 835 | fn 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 | ||
| 843 | fn 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 | ||
| 851 | pub fn basename(path: []const u8) []const u8 { | |
| 852 | if (is_windows) { | |
| 853 | return basenameWindows(path); | |
| 854 | } else { | |
| 855 | return basenamePosix(path); | |
| 856 | } | |
| 857 | } | |
| 858 | ||
| 859 | pub 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 | ||
| 880 | pub 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 | ||
| 912 | test "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 | ||
| 951 | fn testBasename(input: []const u8, expected_output: []const u8) void { | |
| 952 | testing.expectEqualSlices(u8, expected_output, basename(input)); | |
| 953 | } | |
| 954 | ||
| 955 | fn testBasenamePosix(input: []const u8, expected_output: []const u8) void { | |
| 956 | testing.expectEqualSlices(u8, expected_output, basenamePosix(input)); | |
| 957 | } | |
| 958 | ||
| 959 | fn 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 `\\`. | |
| 967 | pub 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 | ||
| 975 | pub 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 | ||
| 1048 | pub 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 | ||
| 1093 | test "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 | ||
| 1133 | fn 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 | ||
| 1138 | fn 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 | ||
| 1143 | pub 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`. | |
| 1172 | pub 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. | |
| 1221 | pub 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. | |
| 1262 | pub 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. | |
| 1277 | pub 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 | ||
| 1282 | test "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 | ||
| 20 | const std = @import("../std.zig"); | |
| 21 | const builtin = @import("builtin"); | |
| 22 | const assert = std.debug.assert; | |
| 23 | const os = @import("../os.zig"); | |
| 24 | const system = os.system; | |
| 25 | const mem = std.mem; | |
| 26 | const BufMap = std.BufMap; | |
| 27 | const Allocator = mem.Allocator; | |
| 28 | const windows = os.windows; | |
| 29 | const kernel32 = windows.kernel32; | |
| 30 | const wasi = os.wasi; | |
| 31 | const linux = os.linux; | |
| 32 | const testing = std.testing; | |
| 33 | ||
| 34 | pub use system.posix; | |
| 35 | ||
| 36 | /// See also `getenv`. | |
| 37 | pub 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. | |
| 45 | pub 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. | |
| 52 | pub 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 | ||
| 70 | pub 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. | |
| 77 | pub 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 | ||
| 110 | fn 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 | ||
| 118 | test "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 | |
| 131 | pub 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 | ||
| 155 | pub const RaiseError = error{}; | |
| 156 | ||
| 157 | pub 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. | |
| 188 | pub 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 | ||
| 204 | pub 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`. | |
| 217 | pub 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`. | |
| 282 | pub 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 | ||
| 338 | pub 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`. | |
| 353 | pub 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`. | |
| 415 | pub 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 | ||
| 477 | pub 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`. | |
| 498 | pub 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 | |
| 506 | pub 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 | ||
| 536 | pub 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 | |
| 554 | pub 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 | ||
| 609 | pub 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 | ||
| 632 | pub 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 | ||
| 640 | pub const ExecveError = error{ | |
| 641 | SystemResources, | |
| 642 | AccessDenied, | |
| 643 | InvalidExe, | |
| 644 | FileSystem, | |
| 645 | IsDir, | |
| 646 | FileNotFound, | |
| 647 | NotDir, | |
| 648 | FileBusy, | |
| 649 | ||
| 650 | Unexpected, | |
| 651 | }; | |
| 652 | ||
| 653 | fn 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 | |
| 679 | pub 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 | |
| 698 | pub 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. | |
| 707 | pub 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 | ||
| 720 | pub const GetCwdError = error{ | |
| 721 | NameTooLong, | |
| 722 | CurrentWorkingDirectoryUnlinked, | |
| 723 | Unexpected, | |
| 724 | }; | |
| 725 | ||
| 726 | /// The result is a slice of out_buffer, indexed from 0. | |
| 727 | pub 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 | ||
| 766 | test "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 | ||
| 772 | pub 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`. | |
| 794 | pub 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 | ||
| 806 | pub 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 | ||
| 812 | pub 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`. | |
| 835 | pub 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 | |
| 865 | pub 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 | ||
| 873 | pub 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. | |
| 895 | pub 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 | |
| 907 | pub 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. | |
| 920 | pub 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 | ||
| 944 | const RenameError = error{}; // TODO | |
| 945 | ||
| 946 | /// Change the name or location of a file. | |
| 947 | pub 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. | |
| 960 | pub 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 | |
| 992 | pub 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 | ||
| 1001 | pub const MakeDirError = error{}; | |
| 1002 | ||
| 1003 | /// Create a directory. | |
| 1004 | /// `mode` is ignored on Windows. | |
| 1005 | pub 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. | |
| 1016 | pub 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. | |
| 1041 | pub 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 | ||
| 1053 | pub 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. | |
| 1069 | pub 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. | |
| 1080 | pub 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 | |
| 1106 | pub 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 | ||
| 1116 | pub 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. | |
| 1120 | pub 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. | |
| 1131 | pub 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 | |
| 1152 | pub fn chdirW(dir_path: [*]const u16) ChangeCurDirError!void { | |
| 1153 | @compileError("TODO implement chdir for Windows"); | |
| 1154 | } | |
| 1155 | ||
| 1156 | pub const ReadLinkError = error{}; | |
| 1157 | ||
| 1158 | /// Read value of a symbolic link. | |
| 1159 | /// The return value is a slice of `out_buffer` from index 0. | |
| 1160 | pub 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. | |
| 1171 | pub 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 | ||
| 1192 | pub const SetIdError = error{ | |
| 1193 | ResourceLimitReached, | |
| 1194 | InvalidUserId, | |
| 1195 | PermissionDenied, | |
| 1196 | Unexpected, | |
| 1197 | }; | |
| 1198 | ||
| 1199 | pub 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 | ||
| 1209 | pub 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 | ||
| 1219 | pub 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 | ||
| 1229 | pub 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 | ||
| 1239 | pub const GetStdHandleError = error{ | |
| 1240 | NoStandardHandleAttached, | |
| 1241 | Unexpected, | |
| 1242 | }; | |
| 1243 | ||
| 1244 | pub 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. | |
| 1264 | pub 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 | ||
| 1283 | pub 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 | ||
| 1305 | pub 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 | ||
| 1332 | pub 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 | ||
| 1347 | pub 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 | |
| 1385 | pub 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 | ||
| 1406 | const 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 | ||
| 1423 | pub 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 | ||
| 1435 | pub 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`. | |
| 1464 | pub 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. | |
| 1492 | pub 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 | ||
| 1518 | pub 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 | ||
| 1534 | pub 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 | ||
| 1547 | pub 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 | ||
| 1575 | pub 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. | |
| 1595 | pub 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 | ||
| 1610 | pub const EventFdError = error{ | |
| 1611 | SystemResources, | |
| 1612 | ProcessFdQuotaExceeded, | |
| 1613 | SystemFdQuotaExceeded, | |
| 1614 | Unexpected, | |
| 1615 | }; | |
| 1616 | ||
| 1617 | pub 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 | ||
| 1631 | pub const GetSockNameError = error{ | |
| 1632 | /// Insufficient resources were available in the system to perform the operation. | |
| 1633 | SystemResources, | |
| 1634 | ||
| 1635 | Unexpected, | |
| 1636 | }; | |
| 1637 | ||
| 1638 | pub 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 | ||
| 1653 | pub 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`. | |
| 1692 | pub 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`. | |
| 1721 | pub 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 | ||
| 1747 | pub 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 | ||
| 1781 | pub 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 | ||
| 1794 | pub const FStatError = error{ | |
| 1795 | SystemResources, | |
| 1796 | Unexpected, | |
| 1797 | }; | |
| 1798 | ||
| 1799 | pub 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 | ||
| 1818 | pub 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 | ||
| 1828 | pub 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 | ||
| 1838 | pub 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 | ||
| 1852 | pub 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 | ||
| 1875 | pub const INotifyInitError = error{ | |
| 1876 | ProcessFdQuotaExceeded, | |
| 1877 | SystemFdQuotaExceeded, | |
| 1878 | SystemResources, | |
| 1879 | Unexpected, | |
| 1880 | }; | |
| 1881 | ||
| 1882 | /// initialize an inotify instance | |
| 1883 | pub 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 | ||
| 1895 | pub 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 | |
| 1905 | pub 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. | |
| 1911 | pub 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 | |
| 1928 | pub 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 | ||
| 1937 | pub const MProtectError = error{ | |
| 1938 | AccessDenied, | |
| 1939 | OutOfMemory, | |
| 1940 | Unexpected, | |
| 1941 | }; | |
| 1942 | ||
| 1943 | /// address and length must be page-aligned | |
| 1944 | pub 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 | ||
| 1959 | pub const ForkError = error{ | |
| 1960 | SystemResources, | |
| 1961 | Unexpected, | |
| 1962 | }; | |
| 1963 | ||
| 1964 | pub 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 | ||
| 1974 | pub 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 | |
| 1987 | pub 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. | |
| 2019 | pub 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 | ||
| 2028 | pub 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 | |
| 2043 | pub 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`. | |
| 2055 | pub 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`. | |
| 2069 | pub 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 | ||
| 2092 | pub const PipeError = error{ | |
| 2093 | SystemFdQuotaExceeded, | |
| 2094 | ProcessFdQuotaExceeded, | |
| 2095 | }; | |
| 2096 | ||
| 2097 | /// Creates a unidirectional data channel that can be used for interprocess communication. | |
| 2098 | pub 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 | ||
| 2109 | pub 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 | ||
| 2120 | pub const SysCtlError = error{ | |
| 2121 | PermissionDenied, | |
| 2122 | SystemResources, | |
| 2123 | Unexpected, | |
| 2124 | }; | |
| 2125 | ||
| 2126 | pub 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 | ||
| 2142 | pub 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 | ||
| 2158 | pub 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 | ||
| 2166 | pub 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 | ||
| 2178 | pub const realpath = std.os.path.real; | |
| 2179 | pub const realpathC = std.os.path.realC; | |
| 2180 | pub const realpathW = std.os.path.realW; | |
| 2181 | ||
| 2182 | pub const WaitForSingleObjectError = error{ | |
| 2183 | WaitAbandoned, | |
| 2184 | WaitTimeOut, | |
| 2185 | Unexpected, | |
| 2186 | }; | |
| 2187 | ||
| 2188 | pub 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 | ||
| 2202 | pub 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. | |
| 2221 | pub 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 | ||
| 2231 | pub const CreateIoCompletionPortError = error{Unexpected}; | |
| 2232 | ||
| 2233 | pub 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 | ||
| 2248 | pub const WindowsPostQueuedCompletionStatusError = error{Unexpected}; | |
| 2249 | ||
| 2250 | pub 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 | ||
| 2259 | pub const GetQueuedCompletionStatusResult = enum { | |
| 2260 | Normal, | |
| 2261 | Aborted, | |
| 2262 | Cancelled, | |
| 2263 | EOF, | |
| 2264 | }; | |
| 2265 | ||
| 2266 | pub 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 | |
| 2290 | pub 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. | |
| 2301 | pub 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 @@ |
| 1 | 1 | const std = @import("../std.zig"); |
| 2 | 2 | const os = std.os; |
| 3 | const testing = std.testing; | |
| 3 | 4 | const expect = std.testing.expect; |
| 4 | 5 | const io = std.io; |
| 5 | 6 | const mem = std.mem; |
| ... | ... | @@ -127,3 +128,24 @@ fn testTls(context: void) void { |
| 127 | 128 | x += 1; |
| 128 | 129 | if (x != 1235) @panic("bad end value"); |
| 129 | 130 | } |
| 131 | ||
| 132 | test "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 | ||
| 142 | test "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 | ||
| 148 | test "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 @@ |
| 1 | const std = @import("../std.zig"); | |
| 2 | const builtin = @import("builtin"); | |
| 3 | const Os = builtin.Os; | |
| 4 | const debug = std.debug; | |
| 5 | const testing = std.testing; | |
| 6 | const math = std.math; | |
| 7 | ||
| 8 | const windows = std.os.windows; | |
| 9 | const linux = std.os.linux; | |
| 10 | const darwin = std.os.darwin; | |
| 11 | const wasi = std.os.wasi; | |
| 12 | const posix = std.os.posix; | |
| 13 | ||
| 14 | pub const epoch = @import("epoch.zig"); | |
| 15 | ||
| 16 | /// Spurious wakeups are possible and no precision of timing is guaranteed. | |
| 17 | pub 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. | |
| 35 | pub 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 | |
| 62 | pub fn timestamp() u64 { | |
| 63 | return @divFloor(milliTimestamp(), ms_per_s); | |
| 64 | } | |
| 65 | ||
| 66 | /// Get the posix timestamp, UTC, in milliseconds | |
| 67 | pub 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 | ||
| 75 | fn 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 | ||
| 86 | fn 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 | ||
| 98 | fn 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 | ||
| 107 | fn 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) | |
| 120 | pub const nanosecond = 1; | |
| 121 | pub const microsecond = 1000 * nanosecond; | |
| 122 | pub const millisecond = 1000 * microsecond; | |
| 123 | pub const second = 1000 * millisecond; | |
| 124 | pub const minute = 60 * second; | |
| 125 | pub const hour = 60 * minute; | |
| 126 | ||
| 127 | /// Divisions of a second | |
| 128 | pub const ns_per_s = 1000000000; | |
| 129 | pub const us_per_s = 1000000; | |
| 130 | pub const ms_per_s = 1000; | |
| 131 | pub const cs_per_s = 100; | |
| 132 | ||
| 133 | /// Common time divisions | |
| 134 | pub const s_per_min = 60; | |
| 135 | pub const s_per_hour = s_per_min * 60; | |
| 136 | pub const s_per_day = s_per_hour * 24; | |
| 137 | pub 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. | |
| 147 | pub 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 | ||
| 278 | test "os.time.sleep" { | |
| 279 | sleep(1); | |
| 280 | } | |
| 281 | ||
| 282 | test "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 | ||
| 293 | test "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 { |
| 535 | 535 | /// from https://docs.microsoft.com/en-us/windows/desktop/FileIO/naming-a-file#maximum-path-length-limitation |
| 536 | 536 | pub const PATH_MAX_WIDE = 32767; |
| 537 | 537 | |
| 538 | pub const OpenError = error{ | |
| 538 | pub const CreateFileError = error{ | |
| 539 | 539 | SharingViolation, |
| 540 | 540 | PathAlreadyExists, |
| 541 | 541 | |
| ... | ... | @@ -565,7 +565,7 @@ pub fn CreateFile( |
| 565 | 565 | share_mode: DWORD, |
| 566 | 566 | creation_disposition: DWORD, |
| 567 | 567 | flags_and_attrs: DWORD, |
| 568 | ) OpenError!fd_t { | |
| 568 | ) CreateFileError!fd_t { | |
| 569 | 569 | const file_path_w = try sliceToPrefixedFileW(file_path); |
| 570 | 570 | return CreateFileW(&file_path_w, desired_access, share_mode, creation_disposition, flags_and_attrs); |
| 571 | 571 | } |
| ... | ... | @@ -576,7 +576,7 @@ pub fn CreateFileW( |
| 576 | 576 | share_mode: DWORD, |
| 577 | 577 | creation_disposition: DWORD, |
| 578 | 578 | flags_and_attrs: DWORD, |
| 579 | ) OpenError!HANDLE { | |
| 579 | ) CreateFileError!HANDLE { | |
| 580 | 580 | const result = kernel32.CreateFileW(file_path_w, desired_access, share_mode, null, creation_disposition, flags_and_attrs, null); |
| 581 | 581 | |
| 582 | 582 | if (result == INVALID_HANDLE_VALUE) { |
| ... | ... | @@ -588,6 +588,7 @@ pub fn CreateFileW( |
| 588 | 588 | ERROR.PATH_NOT_FOUND => return error.FileNotFound, |
| 589 | 589 | ERROR.ACCESS_DENIED => return error.AccessDenied, |
| 590 | 590 | ERROR.PIPE_BUSY => return error.PipeBusy, |
| 591 | ERROR.FILENAME_EXCED_RANGE => return error.NameTooLong, | |
| 591 | 592 | else => |err| return unexpectedErrorWindows(err), |
| 592 | 593 | } |
| 593 | 594 | } |
| ... | ... | @@ -615,6 +616,451 @@ fn SetHandleInformation(h: HANDLE, mask: DWORD, flags: DWORD) SetHandleInformati |
| 615 | 616 | } |
| 616 | 617 | } |
| 617 | 618 | |
| 619 | pub 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 | |
| 624 | pub 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 | ||
| 632 | pub const WaitForSingleObjectError = error{ | |
| 633 | WaitAbandoned, | |
| 634 | WaitTimeOut, | |
| 635 | Unexpected, | |
| 636 | }; | |
| 637 | ||
| 638 | pub 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 | ||
| 650 | pub const FindFirstFileError = error{ | |
| 651 | FileNotFound, | |
| 652 | Unexpected, | |
| 653 | }; | |
| 654 | ||
| 655 | pub 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 | ||
| 670 | pub const FindNextFileError = error{Unexpected}; | |
| 671 | ||
| 672 | /// Returns `true` if there was another file, `false` otherwise. | |
| 673 | pub 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 | ||
| 683 | pub const CreateIoCompletionPortError = error{Unexpected}; | |
| 684 | ||
| 685 | pub 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 | ||
| 700 | pub const PostQueuedCompletionStatusError = error{Unexpected}; | |
| 701 | ||
| 702 | pub 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 | ||
| 715 | pub const GetQueuedCompletionStatusResult = enum { | |
| 716 | Normal, | |
| 717 | Aborted, | |
| 718 | Cancelled, | |
| 719 | EOF, | |
| 720 | }; | |
| 721 | ||
| 722 | pub 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 | ||
| 750 | pub fn CloseHandle(hObject: HANDLE) void { | |
| 751 | assert(kernel32.CloseHandle(hObject) != 0); | |
| 752 | } | |
| 753 | ||
| 754 | pub const ReadFileError = error{Unexpected}; | |
| 755 | ||
| 756 | pub 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 | ||
| 774 | pub 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`. | |
| 783 | pub 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 | ||
| 799 | pub const GetCurrentDirectoryError = error{ | |
| 800 | NameTooLong, | |
| 801 | Unexpected, | |
| 802 | }; | |
| 803 | ||
| 804 | /// The result is a slice of out_buffer, indexed from 0. | |
| 805 | pub 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 | ||
| 826 | pub const CreateSymbolicLinkError = error{Unexpected}; | |
| 827 | ||
| 828 | pub 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 | ||
| 838 | pub 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 | ||
| 850 | pub const DeleteFileError = error{ | |
| 851 | FileNotFound, | |
| 852 | AccessDenied, | |
| 853 | NameTooLong, | |
| 854 | Unexpected, | |
| 855 | }; | |
| 856 | ||
| 857 | pub fn DeleteFile(filename: []const u8) DeleteFileError!void { | |
| 858 | const filename_w = try sliceToPrefixedFileW(filename); | |
| 859 | return DeleteFileW(&filename_w); | |
| 860 | } | |
| 861 | ||
| 862 | pub 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 | ||
| 874 | pub const MoveFileError = error{Unexpected}; | |
| 875 | ||
| 876 | pub 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 | ||
| 882 | pub 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 | ||
| 890 | pub const CreateDirectoryError = error{ | |
| 891 | PathAlreadyExists, | |
| 892 | FileNotFound, | |
| 893 | Unexpected, | |
| 894 | }; | |
| 895 | ||
| 896 | pub 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 | ||
| 901 | pub 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 | ||
| 911 | pub const RemoveDirectoryError = error{ | |
| 912 | FileNotFound, | |
| 913 | DirNotEmpty, | |
| 914 | Unexpected, | |
| 915 | }; | |
| 916 | ||
| 917 | pub 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 | ||
| 922 | pub 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 | ||
| 932 | pub const GetStdHandleError = error{ | |
| 933 | NoStandardHandleAttached, | |
| 934 | Unexpected, | |
| 935 | }; | |
| 936 | ||
| 937 | pub 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 | ||
| 947 | pub const SetFilePointerError = error{Unexpected}; | |
| 948 | ||
| 949 | /// The SetFilePointerEx function with the `dwMoveMethod` parameter set to `FILE_BEGIN`. | |
| 950 | pub 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`. | |
| 965 | pub 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`. | |
| 976 | pub 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. | |
| 987 | pub 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 | ||
| 1001 | pub const GetFinalPathNameByHandleError = error{ | |
| 1002 | FileNotFound, | |
| 1003 | SystemResources, | |
| 1004 | NameTooLong, | |
| 1005 | Unexpected, | |
| 1006 | }; | |
| 1007 | ||
| 1008 | pub 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 | ||
| 1028 | pub const GetFileSizeError = error{Unexpected}; | |
| 1029 | ||
| 1030 | pub 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 | ||
| 1040 | pub const GetFileAttributesError = error{ | |
| 1041 | FileNotFound, | |
| 1042 | PermissionDenied, | |
| 1043 | Unexpected, | |
| 1044 | }; | |
| 1045 | ||
| 1046 | pub fn GetFileAttributes(filename: []const u8) GetFileAttributesError!DWORD { | |
| 1047 | const filename_w = try sliceToPrefixedFileW(filename); | |
| 1048 | return GetFileAttributesW(&filename_w); | |
| 1049 | } | |
| 1050 | ||
| 1051 | pub 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 | ||
| 618 | 1064 | pub fn cStrToPrefixedFileW(s: [*]const u8) ![PATH_MAX_WIDE + 1]u16 { |
| 619 | 1065 | return sliceToPrefixedFileW(mem.toSliceConst(u8, s)); |
| 620 | 1066 | } |
std/packed_int_array.zig+3-3| ... | ... | @@ -619,7 +619,7 @@ test "PackedIntArray at end of available memory" { |
| 619 | 619 | const PackedArray = PackedIntArray(u3, 8); |
| 620 | 620 | |
| 621 | 621 | const Padded = struct { |
| 622 | _: [std.os.page_size - @sizeOf(PackedArray)]u8, | |
| 622 | _: [std.mem.page_size - @sizeOf(PackedArray)]u8, | |
| 623 | 623 | p: PackedArray, |
| 624 | 624 | }; |
| 625 | 625 | |
| ... | ... | @@ -641,9 +641,9 @@ test "PackedIntSlice at end of available memory" { |
| 641 | 641 | var da = std.heap.DirectAllocator.init(); |
| 642 | 642 | const allocator = &da.allocator; |
| 643 | 643 | |
| 644 | var page = try allocator.alloc(u8, std.os.page_size); | |
| 644 | var page = try allocator.alloc(u8, std.mem.page_size); | |
| 645 | 645 | defer allocator.free(page); |
| 646 | 646 | |
| 647 | var p = PackedSlice.init(page[std.os.page_size - 2 ..], 1); | |
| 647 | var p = PackedSlice.init(page[std.mem.page_size - 2 ..], 1); | |
| 648 | 648 | p.set(0, std.math.maxInt(u11)); |
| 649 | 649 | } |
std/pdb.zig+1-2| ... | ... | @@ -660,8 +660,7 @@ const MsfStream = struct { |
| 660 | 660 | return size; |
| 661 | 661 | } |
| 662 | 662 | |
| 663 | // XXX: The `len` parameter should be signed | |
| 664 | fn seekForward(self: *MsfStream, len: u64) !void { | |
| 663 | fn seekBy(self: *MsfStream, len: i64) !void { | |
| 665 | 664 | self.pos += len; |
| 666 | 665 | if (self.pos >= self.blocks.len * self.block_size) |
| 667 | 666 | return error.EOF; |
std/process.zig created+583| ... | ... | @@ -0,0 +1,583 @@ |
| 1 | const std = @import("std.zig"); | |
| 2 | const posix = std.os.posix; | |
| 3 | const BufMap = std.BufMap; | |
| 4 | const mem = std.mem; | |
| 5 | const Allocator = mem.Allocator; | |
| 6 | const assert = std.debug.assert; | |
| 7 | const testing = std.testing; | |
| 8 | ||
| 9 | pub const abort = posix.abort; | |
| 10 | pub const exit = posix.exit; | |
| 11 | ||
| 12 | /// Caller must free result when done. | |
| 13 | /// TODO make this go through libc when we have it | |
| 14 | pub 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 | ||
| 92 | test "os.getEnvMap" { | |
| 93 | var env = try getEnvMap(std.debug.global_allocator); | |
| 94 | defer env.deinit(); | |
| 95 | } | |
| 96 | ||
| 97 | pub 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 | |
| 107 | pub 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 | ||
| 148 | test "os.getEnvVarOwned" { | |
| 149 | var ga = std.debug.global_allocator; | |
| 150 | testing.expectError(error.EnvironmentVariableNotFound, getEnvVarOwned(ga, "BADENV")); | |
| 151 | } | |
| 152 | ||
| 153 | pub 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 | ||
| 184 | pub 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 | ||
| 333 | pub 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 | ||
| 370 | pub fn args() ArgIterator { | |
| 371 | return ArgIterator.init(); | |
| 372 | } | |
| 373 | ||
| 374 | /// Caller must call argsFree on result. | |
| 375 | pub 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 | ||
| 440 | pub 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 | ||
| 460 | test "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 | ||
| 477 | fn 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 | ||
| 486 | pub const UserInfo = struct { | |
| 487 | uid: u32, | |
| 488 | gid: u32, | |
| 489 | }; | |
| 490 | ||
| 491 | /// POSIX function which gets a uid from username. | |
| 492 | pub 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`. | |
| 501 | pub 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; |
| 17 | 17 | pub const StaticallyInitializedMutex = @import("statically_initialized_mutex.zig").StaticallyInitializedMutex; |
| 18 | 18 | pub const SegmentedList = @import("segmented_list.zig").SegmentedList; |
| 19 | 19 | pub const SpinLock = @import("spinlock.zig").SpinLock; |
| 20 | pub const ChildProcess = @import("child_process.zig").ChildProcess; | |
| 21 | pub const Thread = @import("thread.zig").Thread; | |
| 20 | 22 | |
| 21 | 23 | pub const atomic = @import("atomic.zig"); |
| 22 | 24 | pub const base64 = @import("base64.zig"); |
| ... | ... | @@ -30,6 +32,7 @@ pub const dwarf = @import("dwarf.zig"); |
| 30 | 32 | pub const elf = @import("elf.zig"); |
| 31 | 33 | pub const event = @import("event.zig"); |
| 32 | 34 | pub const fmt = @import("fmt.zig"); |
| 35 | pub const fs = @import("fs.zig"); | |
| 33 | 36 | pub const hash = @import("hash.zig"); |
| 34 | 37 | pub const hash_map = @import("hash_map.zig"); |
| 35 | 38 | pub const heap = @import("heap.zig"); |
| ... | ... | @@ -43,11 +46,13 @@ pub const meta = @import("meta.zig"); |
| 43 | 46 | pub const net = @import("net.zig"); |
| 44 | 47 | pub const os = @import("os.zig"); |
| 45 | 48 | pub const pdb = @import("pdb.zig"); |
| 49 | pub const process = @import("process.zig"); | |
| 46 | 50 | pub const rand = @import("rand.zig"); |
| 47 | 51 | pub const rb = @import("rb.zig"); |
| 48 | 52 | pub const sort = @import("sort.zig"); |
| 49 | 53 | pub const ascii = @import("ascii.zig"); |
| 50 | 54 | pub const testing = @import("testing.zig"); |
| 55 | pub const time = @import("time.zig"); | |
| 51 | 56 | pub const unicode = @import("unicode.zig"); |
| 52 | 57 | pub const valgrind = @import("valgrind.zig"); |
| 53 | 58 | pub const zig = @import("zig.zig"); |
| ... | ... | @@ -65,6 +70,7 @@ test "std" { |
| 65 | 70 | _ = @import("statically_initialized_mutex.zig"); |
| 66 | 71 | _ = @import("segmented_list.zig"); |
| 67 | 72 | _ = @import("spinlock.zig"); |
| 73 | _ = @import("child_process.zig"); | |
| 68 | 74 | |
| 69 | 75 | _ = @import("ascii.zig"); |
| 70 | 76 | _ = @import("base64.zig"); |
| ... | ... | @@ -79,6 +85,7 @@ test "std" { |
| 79 | 85 | _ = @import("elf.zig"); |
| 80 | 86 | _ = @import("event.zig"); |
| 81 | 87 | _ = @import("fmt.zig"); |
| 88 | _ = @import("fs.zig"); | |
| 82 | 89 | _ = @import("hash.zig"); |
| 83 | 90 | _ = @import("heap.zig"); |
| 84 | 91 | _ = @import("io.zig"); |
| ... | ... | @@ -91,11 +98,14 @@ test "std" { |
| 91 | 98 | _ = @import("net.zig"); |
| 92 | 99 | _ = @import("os.zig"); |
| 93 | 100 | _ = @import("pdb.zig"); |
| 101 | _ = @import("process.zig"); | |
| 94 | 102 | _ = @import("packed_int_array.zig"); |
| 95 | 103 | _ = @import("priority_queue.zig"); |
| 96 | 104 | _ = @import("rand.zig"); |
| 97 | 105 | _ = @import("sort.zig"); |
| 98 | 106 | _ = @import("testing.zig"); |
| 107 | _ = @import("thread.zig"); | |
| 108 | _ = @import("time.zig"); | |
| 99 | 109 | _ = @import("unicode.zig"); |
| 100 | 110 | _ = @import("valgrind.zig"); |
| 101 | 111 | _ = @import("zig.zig"); |
std/thread.zig created+365| ... | ... | @@ -0,0 +1,365 @@ |
| 1 | const builtin = @import("builtin"); | |
| 2 | const std = @import("std.zig"); | |
| 3 | const windows = std.os.windows; | |
| 4 | ||
| 5 | pub 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 @@ |
| 1 | const std = @import("../std.zig"); | |
| 2 | const builtin = @import("builtin"); | |
| 3 | const Os = builtin.Os; | |
| 4 | const debug = std.debug; | |
| 5 | const testing = std.testing; | |
| 6 | const math = std.math; | |
| 7 | ||
| 8 | const windows = std.os.windows; | |
| 9 | const linux = std.os.linux; | |
| 10 | const darwin = std.os.darwin; | |
| 11 | const wasi = std.os.wasi; | |
| 12 | const posix = std.os.posix; | |
| 13 | ||
| 14 | pub const epoch = @import("epoch.zig"); | |
| 15 | ||
| 16 | /// Spurious wakeups are possible and no precision of timing is guaranteed. | |
| 17 | pub 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. | |
| 35 | pub 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 | |
| 62 | pub fn timestamp() u64 { | |
| 63 | return @divFloor(milliTimestamp(), ms_per_s); | |
| 64 | } | |
| 65 | ||
| 66 | /// Get the posix timestamp, UTC, in milliseconds | |
| 67 | pub 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 | ||
| 75 | fn 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 | ||
| 86 | fn 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 | ||
| 98 | fn 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 | ||
| 107 | fn 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) | |
| 120 | pub const nanosecond = 1; | |
| 121 | pub const microsecond = 1000 * nanosecond; | |
| 122 | pub const millisecond = 1000 * microsecond; | |
| 123 | pub const second = 1000 * millisecond; | |
| 124 | pub const minute = 60 * second; | |
| 125 | pub const hour = 60 * minute; | |
| 126 | ||
| 127 | /// Divisions of a second | |
| 128 | pub const ns_per_s = 1000000000; | |
| 129 | pub const us_per_s = 1000000; | |
| 130 | pub const ms_per_s = 1000; | |
| 131 | pub const cs_per_s = 100; | |
| 132 | ||
| 133 | /// Common time divisions | |
| 134 | pub const s_per_min = 60; | |
| 135 | pub const s_per_hour = s_per_min * 60; | |
| 136 | pub const s_per_day = s_per_hour * 24; | |
| 137 | pub 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. | |
| 147 | pub 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 | ||
| 278 | test "os.time.sleep" { | |
| 279 | sleep(1); | |
| 280 | } | |
| 281 | ||
| 282 | test "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 | ||
| 293 | test "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 | } |