authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2019-10-21 23:54:29-04:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2019-10-21 23:54:29-04:00
loge839250c5156d438f76e7b08e7053e9087fae77c
tree53550b0fc05c78c8594ed4c11c0f606cfc9b2437
parenta5cc758036720babeb13ec8cdec68b720c6af1eb
parent064377be9aaf409bf483f512354ef22f656bf213
signaturelock-open Commit is signed but in an unrecognized format.

Merge branch 'stratact-no-dir-allocators'

closes #2885 closes #2886 closes #2888 closes #3249

25 files changed, 1089 insertions(+), 411 deletions(-)

CMakeLists.txt+3
...@@ -630,5 +630,8 @@ set_target_properties(zig PROPERTIES...@@ -630,5 +630,8 @@ set_target_properties(zig PROPERTIES
630 LINK_FLAGS ${EXE_LDFLAGS}630 LINK_FLAGS ${EXE_LDFLAGS}
631)631)
632target_link_libraries(zig compiler "${LIBUSERLAND}")632target_link_libraries(zig compiler "${LIBUSERLAND}")
633if(MSVC)
634 target_link_libraries(zig ntdll.lib)
635endif()
633add_dependencies(zig zig_build_libuserland)636add_dependencies(zig zig_build_libuserland)
634install(TARGETS zig DESTINATION bin)637install(TARGETS zig DESTINATION bin)
doc/docgen.zig+1-1
...@@ -51,7 +51,7 @@ pub fn main() !void {...@@ -51,7 +51,7 @@ pub fn main() !void {
51 var toc = try genToc(allocator, &tokenizer);51 var toc = try genToc(allocator, &tokenizer);
5252
53 try fs.makePath(allocator, tmp_dir_name);53 try fs.makePath(allocator, tmp_dir_name);
54 defer fs.deleteTree(allocator, tmp_dir_name) catch {};54 defer fs.deleteTree(tmp_dir_name) catch {};
5555
56 try genHtml(allocator, &tokenizer, &toc, &buffered_out_stream.stream, zig_exe);56 try genHtml(allocator, &tokenizer, &toc, &buffered_out_stream.stream, zig_exe);
57 try buffered_out_stream.flush();57 try buffered_out_stream.flush();
lib/std/build.zig+2-2
...@@ -331,7 +331,7 @@ pub const Builder = struct {...@@ -331,7 +331,7 @@ pub const Builder = struct {
331 if (self.verbose) {331 if (self.verbose) {
332 warn("rm {}\n", full_path);332 warn("rm {}\n", full_path);
333 }333 }
334 fs.deleteTree(self.allocator, full_path) catch {};334 fs.deleteTree(full_path) catch {};
335 }335 }
336336
337 // TODO remove empty directories337 // TODO remove empty directories
...@@ -2687,7 +2687,7 @@ pub const RemoveDirStep = struct {...@@ -2687,7 +2687,7 @@ pub const RemoveDirStep = struct {
2687 const self = @fieldParentPtr(RemoveDirStep, "step", step);2687 const self = @fieldParentPtr(RemoveDirStep, "step", step);
26882688
2689 const full_path = self.builder.pathFromRoot(self.dir_path);2689 const full_path = self.builder.pathFromRoot(self.dir_path);
2690 fs.deleteTree(self.builder.allocator, full_path) catch |err| {2690 fs.deleteTree(full_path) catch |err| {
2691 warn("Unable to remove {}: {}\n", full_path, @errorName(err));2691 warn("Unable to remove {}: {}\n", full_path, @errorName(err));
2692 return err;2692 return err;
2693 };2693 };
lib/std/c.zig+1
...@@ -80,6 +80,7 @@ pub extern "c" fn mmap(addr: ?*align(page_size) c_void, len: usize, prot: c_uint...@@ -80,6 +80,7 @@ pub extern "c" fn mmap(addr: ?*align(page_size) c_void, len: usize, prot: c_uint
80pub extern "c" fn munmap(addr: *align(page_size) c_void, len: usize) c_int;80pub extern "c" fn munmap(addr: *align(page_size) c_void, len: usize) c_int;
81pub extern "c" fn mprotect(addr: *align(page_size) c_void, len: usize, prot: c_uint) c_int;81pub extern "c" fn mprotect(addr: *align(page_size) c_void, len: usize, prot: c_uint) c_int;
82pub extern "c" fn unlink(path: [*]const u8) c_int;82pub extern "c" fn unlink(path: [*]const u8) c_int;
83pub extern "c" fn unlinkat(dirfd: fd_t, path: [*]const u8, flags: c_uint) c_int;
83pub extern "c" fn getcwd(buf: [*]u8, size: usize) ?[*]u8;84pub extern "c" fn getcwd(buf: [*]u8, size: usize) ?[*]u8;
84pub extern "c" fn waitpid(pid: c_int, stat_loc: *c_uint, options: c_uint) c_int;85pub extern "c" fn waitpid(pid: c_int, stat_loc: *c_uint, options: c_uint) c_int;
85pub extern "c" fn fork() c_int;86pub extern "c" fn fork() c_int;
lib/std/event/fs.zig+1-1
...@@ -1312,7 +1312,7 @@ const test_tmp_dir = "std_event_fs_test";...@@ -1312,7 +1312,7 @@ const test_tmp_dir = "std_event_fs_test";
1312//1312//
1313// // TODO move this into event loop too1313// // TODO move this into event loop too
1314// try os.makePath(allocator, test_tmp_dir);1314// try os.makePath(allocator, test_tmp_dir);
1315// defer os.deleteTree(allocator, test_tmp_dir) catch {};1315// defer os.deleteTree(test_tmp_dir) catch {};
1316//1316//
1317// var loop: Loop = undefined;1317// var loop: Loop = undefined;
1318// try loop.initMultiThreaded(allocator);1318// try loop.initMultiThreaded(allocator);
lib/std/fs.zig+653-383
...@@ -335,444 +335,708 @@ pub fn deleteDirW(dir_path: [*]const u16) !void {...@@ -335,444 +335,708 @@ pub fn deleteDirW(dir_path: [*]const u16) !void {
335 return os.rmdirW(dir_path);335 return os.rmdirW(dir_path);
336}336}
337337
338const DeleteTreeError = error{338/// Removes a symlink, file, or directory.
339 OutOfMemory,339/// If `full_path` is relative, this is equivalent to `Dir.deleteTree` with the
340 AccessDenied,340/// current working directory as the open directory handle.
341 FileTooBig,341/// If `full_path` is absolute, this is equivalent to `Dir.deleteTree` with the
342 IsDir,342/// base directory.
343 SymLinkLoop,343pub fn deleteTree(full_path: []const u8) !void {
344 ProcessFdQuotaExceeded,344 if (path.isAbsolute(full_path)) {
345 NameTooLong,345 const dirname = path.dirname(full_path) orelse return error{
346 SystemFdQuotaExceeded,346 /// Attempt to remove the root file system path.
347 NoDevice,347 /// This error is unreachable if `full_path` is relative.
348 SystemResources,348 CannotDeleteRootDirectory,
349 NoSpaceLeft,349 }.CannotDeleteRootDirectory;
350 PathAlreadyExists,350
351 ReadOnlyFileSystem,351 var dir = try Dir.open(dirname);
352 NotDir,352 defer dir.close();
353 FileNotFound,353
354 FileSystem,354 return dir.deleteTree(path.basename(full_path));
355 FileBusy,355 } else {
356 DirNotEmpty,356 return Dir.cwd().deleteTree(full_path);
357 DeviceBusy,
358
359 /// On Windows, file paths must be valid Unicode.
360 InvalidUtf8,
361
362 /// On Windows, file paths cannot contain these characters:
363 /// '/', '*', '?', '"', '<', '>', '|'
364 BadPathName,
365
366 Unexpected,
367};
368
369/// Whether `full_path` describes a symlink, file, or directory, this function
370/// removes it. If it cannot be removed because it is a non-empty directory,
371/// this function recursively removes its entries and then tries again.
372/// TODO determine if we can remove the allocator requirement
373/// https://github.com/ziglang/zig/issues/2886
374pub fn deleteTree(allocator: *Allocator, full_path: []const u8) DeleteTreeError!void {
375 start_over: while (true) {
376 var got_access_denied = false;
377 // First, try deleting the item as a file. This way we don't follow sym links.
378 if (deleteFile(full_path)) {
379 return;
380 } else |err| switch (err) {
381 error.FileNotFound => return,
382 error.IsDir => {},
383 error.AccessDenied => got_access_denied = true,
384
385 error.InvalidUtf8,
386 error.SymLinkLoop,
387 error.NameTooLong,
388 error.SystemResources,
389 error.ReadOnlyFileSystem,
390 error.NotDir,
391 error.FileSystem,
392 error.FileBusy,
393 error.BadPathName,
394 error.Unexpected,
395 => return err,
396 }
397 {
398 var dir = Dir.open(allocator, full_path) catch |err| switch (err) {
399 error.NotDir => {
400 if (got_access_denied) {
401 return error.AccessDenied;
402 }
403 continue :start_over;
404 },
405
406 error.OutOfMemory,
407 error.AccessDenied,
408 error.FileTooBig,
409 error.IsDir,
410 error.SymLinkLoop,
411 error.ProcessFdQuotaExceeded,
412 error.NameTooLong,
413 error.SystemFdQuotaExceeded,
414 error.NoDevice,
415 error.FileNotFound,
416 error.SystemResources,
417 error.NoSpaceLeft,
418 error.PathAlreadyExists,
419 error.Unexpected,
420 error.InvalidUtf8,
421 error.BadPathName,
422 error.DeviceBusy,
423 => return err,
424 };
425 defer dir.close();
426
427 var full_entry_buf = std.ArrayList(u8).init(allocator);
428 defer full_entry_buf.deinit();
429
430 while (try dir.next()) |entry| {
431 try full_entry_buf.resize(full_path.len + entry.name.len + 1);
432 const full_entry_path = full_entry_buf.toSlice();
433 mem.copy(u8, full_entry_path, full_path);
434 full_entry_path[full_path.len] = path.sep;
435 mem.copy(u8, full_entry_path[full_path.len + 1 ..], entry.name);
436
437 try deleteTree(allocator, full_entry_path);
438 }
439 }
440 return deleteDir(full_path);
441 }357 }
442}358}
443359
444/// TODO: separate this API into the one that opens directory handles to then subsequently open
445/// files, and into the one that reads files from an open directory handle.
446pub const Dir = struct {360pub const Dir = struct {
447 handle: Handle,361 fd: os.fd_t,
448 allocator: *Allocator,362
363 pub const Entry = struct {
364 name: []const u8,
365 kind: Kind,
449366
450 pub const Handle = switch (builtin.os) {367 pub const Kind = enum {
368 BlockDevice,
369 CharacterDevice,
370 Directory,
371 NamedPipe,
372 SymLink,
373 File,
374 UnixDomainSocket,
375 Whiteout,
376 Unknown,
377 };
378 };
379
380 const IteratorError = error{AccessDenied} || os.UnexpectedError;
381
382 pub const Iterator = switch (builtin.os) {
451 .macosx, .ios, .freebsd, .netbsd => struct {383 .macosx, .ios, .freebsd, .netbsd => struct {
452 fd: i32,384 dir: Dir,
453 seek: i64,385 seek: i64,
454 buf: []u8,386 buf: [8192]u8, // TODO align(@alignOf(os.dirent)),
455 index: usize,387 index: usize,
456 end_index: usize,388 end_index: usize,
389
390 const Self = @This();
391
392 pub const Error = IteratorError;
393
394 /// Memory such as file names referenced in this returned entry becomes invalid
395 /// with subsequent calls to `next`, as well as when this `Dir` is deinitialized.
396 pub fn next(self: *Self) Error!?Entry {
397 switch (builtin.os) {
398 .macosx, .ios => return self.nextDarwin(),
399 .freebsd, .netbsd => return self.nextBsd(),
400 else => @compileError("unimplemented"),
401 }
402 }
403
404 fn nextDarwin(self: *Self) !?Entry {
405 start_over: while (true) {
406 if (self.index >= self.end_index) {
407 const rc = os.system.__getdirentries64(
408 self.dir.fd,
409 &self.buf,
410 self.buf.len,
411 &self.seek,
412 );
413 if (rc == 0) return null;
414 if (rc < 0) {
415 switch (os.errno(rc)) {
416 os.EBADF => unreachable,
417 os.EFAULT => unreachable,
418 os.ENOTDIR => unreachable,
419 os.EINVAL => unreachable,
420 else => |err| return os.unexpectedErrno(err),
421 }
422 }
423 self.index = 0;
424 self.end_index = @intCast(usize, rc);
425 }
426 const darwin_entry = @ptrCast(*align(1) os.dirent, &self.buf[self.index]);
427 const next_index = self.index + darwin_entry.d_reclen;
428 self.index = next_index;
429
430 const name = @ptrCast([*]u8, &darwin_entry.d_name)[0..darwin_entry.d_namlen];
431
432 if (mem.eql(u8, name, ".") or mem.eql(u8, name, "..")) {
433 continue :start_over;
434 }
435
436 const entry_kind = switch (darwin_entry.d_type) {
437 os.DT_BLK => Entry.Kind.BlockDevice,
438 os.DT_CHR => Entry.Kind.CharacterDevice,
439 os.DT_DIR => Entry.Kind.Directory,
440 os.DT_FIFO => Entry.Kind.NamedPipe,
441 os.DT_LNK => Entry.Kind.SymLink,
442 os.DT_REG => Entry.Kind.File,
443 os.DT_SOCK => Entry.Kind.UnixDomainSocket,
444 os.DT_WHT => Entry.Kind.Whiteout,
445 else => Entry.Kind.Unknown,
446 };
447 return Entry{
448 .name = name,
449 .kind = entry_kind,
450 };
451 }
452 }
453
454 fn nextBsd(self: *Self) !?Entry {
455 start_over: while (true) {
456 if (self.index >= self.end_index) {
457 const rc = os.system.getdirentries(
458 self.dir.fd,
459 self.buf[0..].ptr,
460 self.buf.len,
461 &self.seek,
462 );
463 switch (os.errno(rc)) {
464 0 => {},
465 os.EBADF => unreachable,
466 os.EFAULT => unreachable,
467 os.ENOTDIR => unreachable,
468 os.EINVAL => unreachable,
469 else => |err| return os.unexpectedErrno(err),
470 }
471 if (rc == 0) return null;
472 self.index = 0;
473 self.end_index = @intCast(usize, rc);
474 }
475 const freebsd_entry = @ptrCast(*align(1) os.dirent, &self.buf[self.index]);
476 const next_index = self.index + freebsd_entry.d_reclen;
477 self.index = next_index;
478
479 const name = @ptrCast([*]u8, &freebsd_entry.d_name)[0..freebsd_entry.d_namlen];
480
481 if (mem.eql(u8, name, ".") or mem.eql(u8, name, "..")) {
482 continue :start_over;
483 }
484
485 const entry_kind = switch (freebsd_entry.d_type) {
486 os.DT_BLK => Entry.Kind.BlockDevice,
487 os.DT_CHR => Entry.Kind.CharacterDevice,
488 os.DT_DIR => Entry.Kind.Directory,
489 os.DT_FIFO => Entry.Kind.NamedPipe,
490 os.DT_LNK => Entry.Kind.SymLink,
491 os.DT_REG => Entry.Kind.File,
492 os.DT_SOCK => Entry.Kind.UnixDomainSocket,
493 os.DT_WHT => Entry.Kind.Whiteout,
494 else => Entry.Kind.Unknown,
495 };
496 return Entry{
497 .name = name,
498 .kind = entry_kind,
499 };
500 }
501 }
457 },502 },
458 .linux => struct {503 .linux => struct {
459 fd: i32,504 dir: Dir,
460 buf: []u8,505 buf: [8192]u8, // TODO align(@alignOf(os.dirent64)),
461 index: usize,506 index: usize,
462 end_index: usize,507 end_index: usize,
508
509 const Self = @This();
510
511 pub const Error = IteratorError;
512
513 /// Memory such as file names referenced in this returned entry becomes invalid
514 /// with subsequent calls to `next`, as well as when this `Dir` is deinitialized.
515 pub fn next(self: *Self) Error!?Entry {
516 start_over: while (true) {
517 if (self.index >= self.end_index) {
518 const rc = os.linux.getdents64(self.dir.fd, &self.buf, self.buf.len);
519 switch (os.linux.getErrno(rc)) {
520 0 => {},
521 os.EBADF => unreachable,
522 os.EFAULT => unreachable,
523 os.ENOTDIR => unreachable,
524 os.EINVAL => unreachable,
525 else => |err| return os.unexpectedErrno(err),
526 }
527 if (rc == 0) return null;
528 self.index = 0;
529 self.end_index = rc;
530 }
531 const linux_entry = @ptrCast(*align(1) os.dirent64, &self.buf[self.index]);
532 const next_index = self.index + linux_entry.d_reclen;
533 self.index = next_index;
534
535 const name = mem.toSlice(u8, @ptrCast([*]u8, &linux_entry.d_name));
536
537 // skip . and .. entries
538 if (mem.eql(u8, name, ".") or mem.eql(u8, name, "..")) {
539 continue :start_over;
540 }
541
542 const entry_kind = switch (linux_entry.d_type) {
543 os.DT_BLK => Entry.Kind.BlockDevice,
544 os.DT_CHR => Entry.Kind.CharacterDevice,
545 os.DT_DIR => Entry.Kind.Directory,
546 os.DT_FIFO => Entry.Kind.NamedPipe,
547 os.DT_LNK => Entry.Kind.SymLink,
548 os.DT_REG => Entry.Kind.File,
549 os.DT_SOCK => Entry.Kind.UnixDomainSocket,
550 else => Entry.Kind.Unknown,
551 };
552 return Entry{
553 .name = name,
554 .kind = entry_kind,
555 };
556 }
557 }
463 },558 },
464 .windows => struct {559 .windows => struct {
465 handle: os.windows.HANDLE,560 dir: Dir,
466 find_file_data: os.windows.WIN32_FIND_DATAW,561 buf: [8192]u8 align(@alignOf(os.windows.FILE_BOTH_DIR_INFORMATION)),
562 index: usize,
563 end_index: usize,
467 first: bool,564 first: bool,
468 name_data: [256]u8,565 name_data: [256]u8,
566
567 const Self = @This();
568
569 pub const Error = IteratorError;
570
571 pub fn next(self: *Self) Error!?Entry {
572 start_over: while (true) {
573 const w = os.windows;
574 if (self.index >= self.end_index) {
575 var io: w.IO_STATUS_BLOCK = undefined;
576 const rc = w.ntdll.NtQueryDirectoryFile(
577 self.dir.fd,
578 null,
579 null,
580 null,
581 &io,
582 &self.buf,
583 self.buf.len,
584 .FileBothDirectoryInformation,
585 w.FALSE,
586 null,
587 if (self.first) w.BOOLEAN(w.TRUE) else w.BOOLEAN(w.FALSE),
588 );
589 self.first = false;
590 if (io.Information == 0) return null;
591 self.index = 0;
592 self.end_index = io.Information;
593 switch (rc) {
594 w.STATUS.SUCCESS => {},
595 w.STATUS.ACCESS_DENIED => return error.AccessDenied,
596 else => return w.unexpectedStatus(rc),
597 }
598 }
599
600 const aligned_ptr = @alignCast(@alignOf(w.FILE_BOTH_DIR_INFORMATION), &self.buf[self.index]);
601 const dir_info = @ptrCast(*w.FILE_BOTH_DIR_INFORMATION, aligned_ptr);
602 if (dir_info.NextEntryOffset != 0) {
603 self.index += dir_info.NextEntryOffset;
604 } else {
605 self.index = self.buf.len;
606 }
607
608 const name_utf16le = @ptrCast([*]u16, &dir_info.FileName)[0 .. dir_info.FileNameLength / 2];
609
610 if (mem.eql(u16, name_utf16le, [_]u16{'.'}) or mem.eql(u16, name_utf16le, [_]u16{ '.', '.' }))
611 continue;
612 // Trust that Windows gives us valid UTF-16LE
613 const name_utf8_len = std.unicode.utf16leToUtf8(self.name_data[0..], name_utf16le) catch unreachable;
614 const name_utf8 = self.name_data[0..name_utf8_len];
615 const kind = blk: {
616 const attrs = dir_info.FileAttributes;
617 if (attrs & w.FILE_ATTRIBUTE_DIRECTORY != 0) break :blk Entry.Kind.Directory;
618 if (attrs & w.FILE_ATTRIBUTE_REPARSE_POINT != 0) break :blk Entry.Kind.SymLink;
619 break :blk Entry.Kind.File;
620 };
621 return Entry{
622 .name = name_utf8,
623 .kind = kind,
624 };
625 }
626 }
469 },627 },
470 else => @compileError("unimplemented"),628 else => @compileError("unimplemented"),
471 };629 };
472630
473 pub const Entry = struct {631 pub fn iterate(self: Dir) Iterator {
474 name: []const u8,632 switch (builtin.os) {
475 kind: Kind,633 .macosx, .ios, .freebsd, .netbsd => return Iterator{
634 .dir = self,
635 .seek = 0,
636 .index = 0,
637 .end_index = 0,
638 .buf = undefined,
639 },
640 .linux => return Iterator{
641 .dir = self,
642 .index = 0,
643 .end_index = 0,
644 .buf = undefined,
645 },
646 .windows => return Iterator{
647 .dir = self,
648 .index = 0,
649 .end_index = 0,
650 .first = true,
651 .buf = undefined,
652 .name_data = undefined,
653 },
654 else => @compileError("unimplemented"),
655 }
656 }
476657
477 pub const Kind = enum {658 /// Returns an open handle to the current working directory.
478 BlockDevice,659 /// Closing the returned `Dir` is checked illegal behavior.
479 CharacterDevice,660 /// On POSIX targets, this function is comptime-callable.
480 Directory,661 pub fn cwd() Dir {
481 NamedPipe,662 if (os.windows.is_the_target) {
482 SymLink,663 return Dir{ .fd = os.windows.peb().ProcessParameters.CurrentDirectory.Handle };
483 File,664 } else {
484 UnixDomainSocket,665 return Dir{ .fd = os.AT_FDCWD };
485 Whiteout,666 }
486 Unknown,667 }
487 };
488 };
489668
490 pub const OpenError = error{669 pub const OpenError = error{
491 FileNotFound,670 FileNotFound,
492 NotDir,671 NotDir,
493 AccessDenied,672 AccessDenied,
494 FileTooBig,
495 IsDir,
496 SymLinkLoop,673 SymLinkLoop,
497 ProcessFdQuotaExceeded,674 ProcessFdQuotaExceeded,
498 NameTooLong,675 NameTooLong,
499 SystemFdQuotaExceeded,676 SystemFdQuotaExceeded,
500 NoDevice,677 NoDevice,
501 SystemResources,678 SystemResources,
502 NoSpaceLeft,
503 PathAlreadyExists,
504 OutOfMemory,
505 InvalidUtf8,679 InvalidUtf8,
506 BadPathName,680 BadPathName,
507 DeviceBusy,681 DeviceBusy,
682 } || os.UnexpectedError;
508683
509 Unexpected,684 /// Call `close` to free the directory handle.
510 };685 pub fn open(dir_path: []const u8) OpenError!Dir {
511686 return cwd().openDir(dir_path);
512 /// Call close when done.
513 /// TODO remove the allocator requirement from this API
514 /// https://github.com/ziglang/zig/issues/2885
515 pub fn open(allocator: *Allocator, dir_path: []const u8) OpenError!Dir {
516 return Dir{
517 .allocator = allocator,
518 .handle = switch (builtin.os) {
519 .windows => blk: {
520 var find_file_data: os.windows.WIN32_FIND_DATAW = undefined;
521 const handle = try os.windows.FindFirstFile(dir_path, &find_file_data);
522 break :blk Handle{
523 .handle = handle,
524 .find_file_data = find_file_data, // TODO guaranteed copy elision
525 .first = true,
526 .name_data = undefined,
527 };
528 },
529 .macosx, .ios, .freebsd, .netbsd => Handle{
530 .fd = try os.open(dir_path, os.O_RDONLY | os.O_NONBLOCK | os.O_DIRECTORY | os.O_CLOEXEC, 0),
531 .seek = 0,
532 .index = 0,
533 .end_index = 0,
534 .buf = [_]u8{},
535 },
536 .linux => Handle{
537 .fd = try os.open(dir_path, os.O_RDONLY | os.O_DIRECTORY | os.O_CLOEXEC, 0),
538 .index = 0,
539 .end_index = 0,
540 .buf = [_]u8{},
541 },
542 else => @compileError("unimplemented"),
543 },
544 };
545 }687 }
546688
547 pub fn close(self: *Dir) void {689 /// Same as `open` except the parameter is null-terminated.
548 if (os.windows.is_the_target) {690 pub fn openC(dir_path_c: [*]const u8) OpenError!Dir {
549 return os.windows.FindClose(self.handle.handle);691 return cwd().openDirC(dir_path_c);
550 }
551 self.allocator.free(self.handle.buf);
552 os.close(self.handle.fd);
553 }692 }
554693
555 /// Memory such as file names referenced in this returned entry becomes invalid694 pub fn close(self: *Dir) void {
556 /// with subsequent calls to next, as well as when this `Dir` is deinitialized.695 os.close(self.fd);
557 pub fn next(self: *Dir) !?Entry {696 self.* = undefined;
558 switch (builtin.os) {
559 .linux => return self.nextLinux(),
560 .macosx, .ios => return self.nextDarwin(),
561 .windows => return self.nextWindows(),
562 .freebsd => return self.nextBsd(),
563 .netbsd => return self.nextBsd(),
564 else => @compileError("unimplemented"),
565 }
566 }697 }
567698
568 pub fn openRead(self: Dir, file_path: []const u8) os.OpenError!File {699 /// Call `File.close` on the result when done.
569 const path_c = try os.toPosixPath(file_path);700 pub fn openRead(self: Dir, sub_path: []const u8) File.OpenError!File {
701 const path_c = try os.toPosixPath(sub_path);
570 return self.openReadC(&path_c);702 return self.openReadC(&path_c);
571 }703 }
572704
573 pub fn openReadC(self: Dir, file_path: [*]const u8) OpenError!File {705 /// Call `File.close` on the result when done.
706 pub fn openReadC(self: Dir, sub_path: [*]const u8) File.OpenError!File {
574 const flags = os.O_LARGEFILE | os.O_RDONLY;707 const flags = os.O_LARGEFILE | os.O_RDONLY;
575 const fd = try os.openatC(self.handle.fd, file_path, flags, 0);708 const fd = try os.openatC(self.fd, sub_path, flags, 0);
576 return File.openHandle(fd);709 return File.openHandle(fd);
577 }710 }
578711
579 fn nextDarwin(self: *Dir) !?Entry {712 /// Call `close` on the result when done.
580 start_over: while (true) {713 pub fn openDir(self: Dir, sub_path: []const u8) OpenError!Dir {
581 if (self.handle.index >= self.handle.end_index) {714 if (os.windows.is_the_target) {
582 if (self.handle.buf.len == 0) {715 const sub_path_w = try os.windows.sliceToPrefixedFileW(sub_path);
583 self.handle.buf = try self.allocator.alloc(u8, mem.page_size);716 return self.openDirW(&sub_path_w);
584 }717 }
585
586 while (true) {
587 const rc = os.system.__getdirentries64(
588 self.handle.fd,
589 self.handle.buf.ptr,
590 self.handle.buf.len,
591 &self.handle.seek,
592 );
593 if (rc == 0) return null;
594 if (rc < 0) {
595 switch (os.errno(rc)) {
596 os.EBADF => unreachable,
597 os.EFAULT => unreachable,
598 os.ENOTDIR => unreachable,
599 os.EINVAL => {
600 self.handle.buf = try self.allocator.realloc(self.handle.buf, self.handle.buf.len * 2);
601 continue;
602 },
603 else => |err| return os.unexpectedErrno(err),
604 }
605 }
606 self.handle.index = 0;
607 self.handle.end_index = @intCast(usize, rc);
608 break;
609 }
610 }
611 const darwin_entry = @ptrCast(*align(1) os.dirent, &self.handle.buf[self.handle.index]);
612 const next_index = self.handle.index + darwin_entry.d_reclen;
613 self.handle.index = next_index;
614
615 const name = @ptrCast([*]u8, &darwin_entry.d_name)[0..darwin_entry.d_namlen];
616718
617 if (mem.eql(u8, name, ".") or mem.eql(u8, name, "..")) {719 const sub_path_c = try os.toPosixPath(sub_path);
618 continue :start_over;720 return self.openDirC(&sub_path_c);
619 }721 }
620722
621 const entry_kind = switch (darwin_entry.d_type) {723 /// Same as `openDir` except the parameter is null-terminated.
622 os.DT_BLK => Entry.Kind.BlockDevice,724 pub fn openDirC(self: Dir, sub_path_c: [*]const u8) OpenError!Dir {
623 os.DT_CHR => Entry.Kind.CharacterDevice,725 if (os.windows.is_the_target) {
624 os.DT_DIR => Entry.Kind.Directory,726 const sub_path_w = try os.windows.cStrToPrefixedFileW(sub_path_c);
625 os.DT_FIFO => Entry.Kind.NamedPipe,727 return self.openDirW(&sub_path_w);
626 os.DT_LNK => Entry.Kind.SymLink,
627 os.DT_REG => Entry.Kind.File,
628 os.DT_SOCK => Entry.Kind.UnixDomainSocket,
629 os.DT_WHT => Entry.Kind.Whiteout,
630 else => Entry.Kind.Unknown,
631 };
632 return Entry{
633 .name = name,
634 .kind = entry_kind,
635 };
636 }728 }
729
730 const flags = os.O_RDONLY | os.O_DIRECTORY | os.O_CLOEXEC;
731 const fd = os.openatC(self.fd, sub_path_c, flags, 0) catch |err| switch (err) {
732 error.FileTooBig => unreachable, // can't happen for directories
733 error.IsDir => unreachable, // we're providing O_DIRECTORY
734 error.NoSpaceLeft => unreachable, // not providing O_CREAT
735 error.PathAlreadyExists => unreachable, // not providing O_CREAT
736 else => |e| return e,
737 };
738 return Dir{ .fd = fd };
637 }739 }
638740
639 fn nextWindows(self: *Dir) !?Entry {741 /// Same as `openDir` except the path parameter is UTF16LE, NT-prefixed.
640 while (true) {742 /// This function is Windows-only.
641 if (self.handle.first) {743 pub fn openDirW(self: Dir, sub_path_w: [*]const u16) OpenError!Dir {
642 self.handle.first = false;744 const w = os.windows;
643 } else {745
644 if (!try os.windows.FindNextFile(self.handle.handle, &self.handle.find_file_data))746 var result = Dir{
645 return null;747 .fd = undefined,
646 }748 };
647 const name_utf16le = mem.toSlice(u16, self.handle.find_file_data.cFileName[0..].ptr);749
648 if (mem.eql(u16, name_utf16le, [_]u16{'.'}) or mem.eql(u16, name_utf16le, [_]u16{ '.', '.' }))750 const path_len_bytes = @intCast(u16, mem.toSliceConst(u16, sub_path_w).len * 2);
649 continue;751 var nt_name = w.UNICODE_STRING{
650 // Trust that Windows gives us valid UTF-16LE752 .Length = path_len_bytes,
651 const name_utf8_len = std.unicode.utf16leToUtf8(self.handle.name_data[0..], name_utf16le) catch unreachable;753 .MaximumLength = path_len_bytes,
652 const name_utf8 = self.handle.name_data[0..name_utf8_len];754 .Buffer = @intToPtr([*]u16, @ptrToInt(sub_path_w)),
653 const kind = blk: {755 };
654 const attrs = self.handle.find_file_data.dwFileAttributes;756 var attr = w.OBJECT_ATTRIBUTES{
655 if (attrs & os.windows.FILE_ATTRIBUTE_DIRECTORY != 0) break :blk Entry.Kind.Directory;757 .Length = @sizeOf(w.OBJECT_ATTRIBUTES),
656 if (attrs & os.windows.FILE_ATTRIBUTE_REPARSE_POINT != 0) break :blk Entry.Kind.SymLink;758 .RootDirectory = if (path.isAbsoluteW(sub_path_w)) null else self.fd,
657 break :blk Entry.Kind.File;759 .Attributes = 0, // Note we do not use OBJ_CASE_INSENSITIVE here.
658 };760 .ObjectName = &nt_name,
659 return Entry{761 .SecurityDescriptor = null,
660 .name = name_utf8,762 .SecurityQualityOfService = null,
661 .kind = kind,763 };
662 };764 if (sub_path_w[0] == '.' and sub_path_w[1] == 0) {
765 // Windows does not recognize this, but it does work with empty string.
766 nt_name.Length = 0;
767 }
768 if (sub_path_w[0] == '.' and sub_path_w[1] == '.' and sub_path_w[2] == 0) {
769 // If you're looking to contribute to zig and fix this, see here for an example of how to
770 // implement this: https://git.midipix.org/ntapi/tree/src/fs/ntapi_tt_open_physical_parent_directory.c
771 @panic("TODO opening '..' with a relative directory handle is not yet implemented on Windows");
772 }
773 var io: w.IO_STATUS_BLOCK = undefined;
774 const rc = w.ntdll.NtCreateFile(
775 &result.fd,
776 w.GENERIC_READ | w.SYNCHRONIZE,
777 &attr,
778 &io,
779 null,
780 0,
781 w.FILE_SHARE_READ | w.FILE_SHARE_WRITE,
782 w.FILE_OPEN,
783 w.FILE_DIRECTORY_FILE | w.FILE_SYNCHRONOUS_IO_NONALERT | w.FILE_OPEN_FOR_BACKUP_INTENT,
784 null,
785 0,
786 );
787 switch (rc) {
788 w.STATUS.SUCCESS => return result,
789 w.STATUS.OBJECT_NAME_INVALID => unreachable,
790 w.STATUS.OBJECT_NAME_NOT_FOUND => return error.FileNotFound,
791 w.STATUS.OBJECT_PATH_NOT_FOUND => return error.FileNotFound,
792 w.STATUS.INVALID_PARAMETER => unreachable,
793 else => return w.unexpectedStatus(rc),
663 }794 }
664 }795 }
665796
666 fn nextLinux(self: *Dir) !?Entry {797 pub const DeleteFileError = os.UnlinkError;
667 start_over: while (true) {
668 if (self.handle.index >= self.handle.end_index) {
669 if (self.handle.buf.len == 0) {
670 self.handle.buf = try self.allocator.alloc(u8, mem.page_size);
671 }
672798
673 while (true) {799 /// Delete a file name and possibly the file it refers to, based on an open directory handle.
674 const rc = os.linux.getdents64(self.handle.fd, self.handle.buf.ptr, self.handle.buf.len);800 pub fn deleteFile(self: Dir, sub_path: []const u8) DeleteFileError!void {
675 switch (os.linux.getErrno(rc)) {801 const sub_path_c = try os.toPosixPath(sub_path);
676 0 => {},802 return self.deleteFileC(&sub_path_c);
677 os.EBADF => unreachable,803 }
678 os.EFAULT => unreachable,
679 os.ENOTDIR => unreachable,
680 os.EINVAL => {
681 self.handle.buf = try self.allocator.realloc(self.handle.buf, self.handle.buf.len * 2);
682 continue;
683 },
684 else => |err| return os.unexpectedErrno(err),
685 }
686 if (rc == 0) return null;
687 self.handle.index = 0;
688 self.handle.end_index = rc;
689 break;
690 }
691 }
692 const linux_entry = @ptrCast(*align(1) os.dirent64, &self.handle.buf[self.handle.index]);
693 const next_index = self.handle.index + linux_entry.d_reclen;
694 self.handle.index = next_index;
695804
696 const name = mem.toSlice(u8, @ptrCast([*]u8, &linux_entry.d_name));805 /// Same as `deleteFile` except the parameter is null-terminated.
806 pub fn deleteFileC(self: Dir, sub_path_c: [*]const u8) DeleteFileError!void {
807 os.unlinkatC(self.fd, sub_path_c, 0) catch |err| switch (err) {
808 error.DirNotEmpty => unreachable, // not passing AT_REMOVEDIR
809 else => |e| return e,
810 };
811 }
697812
698 // skip . and .. entries813 pub const DeleteDirError = error{
699 if (mem.eql(u8, name, ".") or mem.eql(u8, name, "..")) {814 DirNotEmpty,
700 continue :start_over;815 FileNotFound,
701 }816 AccessDenied,
817 FileBusy,
818 FileSystem,
819 SymLinkLoop,
820 NameTooLong,
821 NotDir,
822 SystemResources,
823 ReadOnlyFileSystem,
824 InvalidUtf8,
825 BadPathName,
826 Unexpected,
827 };
702828
703 const entry_kind = switch (linux_entry.d_type) {829 /// Returns `error.DirNotEmpty` if the directory is not empty.
704 os.DT_BLK => Entry.Kind.BlockDevice,830 /// To delete a directory recursively, see `deleteTree`.
705 os.DT_CHR => Entry.Kind.CharacterDevice,831 pub fn deleteDir(self: Dir, sub_path: []const u8) DeleteDirError!void {
706 os.DT_DIR => Entry.Kind.Directory,832 if (os.windows.is_the_target) {
707 os.DT_FIFO => Entry.Kind.NamedPipe,833 const sub_path_w = try os.windows.sliceToPrefixedFileW(sub_path);
708 os.DT_LNK => Entry.Kind.SymLink,834 return self.deleteDirW(&sub_path_w);
709 os.DT_REG => Entry.Kind.File,
710 os.DT_SOCK => Entry.Kind.UnixDomainSocket,
711 else => Entry.Kind.Unknown,
712 };
713 return Entry{
714 .name = name,
715 .kind = entry_kind,
716 };
717 }835 }
836 const sub_path_c = try os.toPosixPath(sub_path);
837 return self.deleteDirC(&sub_path_c);
718 }838 }
719839
720 fn nextBsd(self: *Dir) !?Entry {840 /// Same as `deleteDir` except the parameter is null-terminated.
721 start_over: while (true) {841 pub fn deleteDirC(self: Dir, sub_path_c: [*]const u8) DeleteDirError!void {
722 if (self.handle.index >= self.handle.end_index) {842 os.unlinkatC(self.fd, sub_path_c, os.AT_REMOVEDIR) catch |err| switch (err) {
723 if (self.handle.buf.len == 0) {843 error.IsDir => unreachable, // not possible since we pass AT_REMOVEDIR
724 self.handle.buf = try self.allocator.alloc(u8, mem.page_size);844 else => |e| return e,
725 }845 };
846 }
726847
727 while (true) {848 /// Same as `deleteDir` except the parameter is UTF16LE, NT prefixed.
728 const rc = os.system.getdirentries(849 /// This function is Windows-only.
729 self.handle.fd,850 pub fn deleteDirW(self: Dir, sub_path_w: [*]const u16) DeleteDirError!void {
730 self.handle.buf.ptr,851 os.unlinkatW(self.fd, sub_path_w, os.AT_REMOVEDIR) catch |err| switch (err) {
731 self.handle.buf.len,852 error.IsDir => unreachable, // not possible since we pass AT_REMOVEDIR
732 &self.handle.seek,853 else => |e| return e,
733 );854 };
734 switch (os.errno(rc)) {855 }
735 0 => {},
736 os.EBADF => unreachable,
737 os.EFAULT => unreachable,
738 os.ENOTDIR => unreachable,
739 os.EINVAL => {
740 self.handle.buf = try self.allocator.realloc(self.handle.buf, self.handle.buf.len * 2);
741 continue;
742 },
743 else => |err| return os.unexpectedErrno(err),
744 }
745 if (rc == 0) return null;
746 self.handle.index = 0;
747 self.handle.end_index = @intCast(usize, rc);
748 break;
749 }
750 }
751 const freebsd_entry = @ptrCast(*align(1) os.dirent, &self.handle.buf[self.handle.index]);
752 const next_index = self.handle.index + freebsd_entry.d_reclen;
753 self.handle.index = next_index;
754856
755 const name = @ptrCast([*]u8, &freebsd_entry.d_name)[0..freebsd_entry.d_namlen];857 /// Read value of a symbolic link.
858 /// The return value is a slice of `buffer`, from index `0`.
859 pub fn readLink(self: Dir, sub_path: []const u8, buffer: *[MAX_PATH_BYTES]u8) ![]u8 {
860 const sub_path_c = try os.toPosixPath(sub_path);
861 return self.readLinkC(&sub_path_c, buffer);
862 }
756863
757 if (mem.eql(u8, name, ".") or mem.eql(u8, name, "..")) {864 /// Same as `readLink`, except the `pathname` parameter is null-terminated.
758 continue :start_over;865 pub fn readLinkC(self: Dir, sub_path_c: [*]const u8, buffer: *[MAX_PATH_BYTES]u8) ![]u8 {
866 return os.readlinkatC(self.fd, sub_path_c, buffer);
867 }
868
869 pub const DeleteTreeError = error{
870 AccessDenied,
871 FileTooBig,
872 SymLinkLoop,
873 ProcessFdQuotaExceeded,
874 NameTooLong,
875 SystemFdQuotaExceeded,
876 NoDevice,
877 SystemResources,
878 ReadOnlyFileSystem,
879 FileSystem,
880 FileBusy,
881 DeviceBusy,
882
883 /// One of the path components was not a directory.
884 /// This error is unreachable if `sub_path` does not contain a path separator.
885 NotDir,
886
887 /// On Windows, file paths must be valid Unicode.
888 InvalidUtf8,
889
890 /// On Windows, file paths cannot contain these characters:
891 /// '/', '*', '?', '"', '<', '>', '|'
892 BadPathName,
893 } || os.UnexpectedError;
894
895 /// Whether `full_path` describes a symlink, file, or directory, this function
896 /// removes it. If it cannot be removed because it is a non-empty directory,
897 /// this function recursively removes its entries and then tries again.
898 /// This operation is not atomic on most file systems.
899 pub fn deleteTree(self: Dir, sub_path: []const u8) DeleteTreeError!void {
900 start_over: while (true) {
901 var got_access_denied = false;
902 // First, try deleting the item as a file. This way we don't follow sym links.
903 if (self.deleteFile(sub_path)) {
904 return;
905 } else |err| switch (err) {
906 error.FileNotFound => return,
907 error.IsDir => {},
908 error.AccessDenied => got_access_denied = true,
909
910 error.InvalidUtf8,
911 error.SymLinkLoop,
912 error.NameTooLong,
913 error.SystemResources,
914 error.ReadOnlyFileSystem,
915 error.NotDir,
916 error.FileSystem,
917 error.FileBusy,
918 error.BadPathName,
919 error.Unexpected,
920 => |e| return e,
759 }921 }
922 var dir = self.openDir(sub_path) catch |err| switch (err) {
923 error.NotDir => {
924 if (got_access_denied) {
925 return error.AccessDenied;
926 }
927 continue :start_over;
928 },
929 error.FileNotFound => {
930 // That's fine, we were trying to remove this directory anyway.
931 continue :start_over;
932 },
760933
761 const entry_kind = switch (freebsd_entry.d_type) {934 error.AccessDenied,
762 os.DT_BLK => Entry.Kind.BlockDevice,935 error.SymLinkLoop,
763 os.DT_CHR => Entry.Kind.CharacterDevice,936 error.ProcessFdQuotaExceeded,
764 os.DT_DIR => Entry.Kind.Directory,937 error.NameTooLong,
765 os.DT_FIFO => Entry.Kind.NamedPipe,938 error.SystemFdQuotaExceeded,
766 os.DT_LNK => Entry.Kind.SymLink,939 error.NoDevice,
767 os.DT_REG => Entry.Kind.File,940 error.SystemResources,
768 os.DT_SOCK => Entry.Kind.UnixDomainSocket,941 error.Unexpected,
769 os.DT_WHT => Entry.Kind.Whiteout,942 error.InvalidUtf8,
770 else => Entry.Kind.Unknown,943 error.BadPathName,
771 };944 error.DeviceBusy,
772 return Entry{945 => |e| return e,
773 .name = name,
774 .kind = entry_kind,
775 };946 };
947 var cleanup_dir_parent: ?Dir = null;
948 defer if (cleanup_dir_parent) |*d| d.close();
949
950 var cleanup_dir = true;
951 defer if (cleanup_dir) dir.close();
952
953 var dir_name_buf: [MAX_PATH_BYTES]u8 = undefined;
954 var dir_name: []const u8 = sub_path;
955 var parent_dir = self;
956
957 // Here we must avoid recursion, in order to provide O(1) memory guarantee of this function.
958 // Go through each entry and if it is not a directory, delete it. If it is a directory,
959 // open it, and close the original directory. Repeat. Then start the entire operation over.
960
961 scan_dir: while (true) {
962 var dir_it = dir.iterate();
963 while (try dir_it.next()) |entry| {
964 if (dir.deleteFile(entry.name)) {
965 continue;
966 } else |err| switch (err) {
967 error.FileNotFound => continue,
968
969 // Impossible because we do not pass any path separators.
970 error.NotDir => unreachable,
971
972 error.IsDir => {},
973 error.AccessDenied => got_access_denied = true,
974
975 error.InvalidUtf8,
976 error.SymLinkLoop,
977 error.NameTooLong,
978 error.SystemResources,
979 error.ReadOnlyFileSystem,
980 error.FileSystem,
981 error.FileBusy,
982 error.BadPathName,
983 error.Unexpected,
984 => |e| return e,
985 }
986
987 const new_dir = dir.openDir(entry.name) catch |err| switch (err) {
988 error.NotDir => {
989 if (got_access_denied) {
990 return error.AccessDenied;
991 }
992 continue :scan_dir;
993 },
994 error.FileNotFound => {
995 // That's fine, we were trying to remove this directory anyway.
996 continue :scan_dir;
997 },
998
999 error.AccessDenied,
1000 error.SymLinkLoop,
1001 error.ProcessFdQuotaExceeded,
1002 error.NameTooLong,
1003 error.SystemFdQuotaExceeded,
1004 error.NoDevice,
1005 error.SystemResources,
1006 error.Unexpected,
1007 error.InvalidUtf8,
1008 error.BadPathName,
1009 error.DeviceBusy,
1010 => |e| return e,
1011 };
1012 if (cleanup_dir_parent) |*d| d.close();
1013 cleanup_dir_parent = dir;
1014 dir = new_dir;
1015 mem.copy(u8, &dir_name_buf, entry.name);
1016 dir_name = dir_name_buf[0..entry.name.len];
1017 continue :scan_dir;
1018 }
1019 // Reached the end of the directory entries, which means we successfully deleted all of them.
1020 // Now to remove the directory itself.
1021 dir.close();
1022 cleanup_dir = false;
1023
1024 if (cleanup_dir_parent) |d| {
1025 d.deleteDir(dir_name) catch |err| switch (err) {
1026 // These two things can happen due to file system race conditions.
1027 error.FileNotFound, error.DirNotEmpty => continue :start_over,
1028 else => |e| return e,
1029 };
1030 continue :start_over;
1031 } else {
1032 self.deleteDir(sub_path) catch |err| switch (err) {
1033 error.FileNotFound => return,
1034 error.DirNotEmpty => continue :start_over,
1035 else => |e| return e,
1036 };
1037 return;
1038 }
1039 }
776 }1040 }
777 }1041 }
778};1042};
...@@ -782,13 +1046,18 @@ pub const Walker = struct {...@@ -782,13 +1046,18 @@ pub const Walker = struct {
782 name_buffer: std.Buffer,1046 name_buffer: std.Buffer,
7831047
784 pub const Entry = struct {1048 pub const Entry = struct {
785 path: []const u8,1049 /// The containing directory. This can be used to operate directly on `basename`
1050 /// rather than `path`, avoiding `error.NameTooLong` for deeply nested paths.
1051 /// The directory remains open until `next` or `deinit` is called.
1052 dir: Dir,
786 basename: []const u8,1053 basename: []const u8,
1054
1055 path: []const u8,
787 kind: Dir.Entry.Kind,1056 kind: Dir.Entry.Kind,
788 };1057 };
7891058
790 const StackItem = struct {1059 const StackItem = struct {
791 dir_it: Dir,1060 dir_it: Dir.Iterator,
792 dirname_len: usize,1061 dirname_len: usize,
793 };1062 };
7941063
...@@ -806,23 +1075,26 @@ pub const Walker = struct {...@@ -806,23 +1075,26 @@ pub const Walker = struct {
806 try self.name_buffer.appendByte(path.sep);1075 try self.name_buffer.appendByte(path.sep);
807 try self.name_buffer.append(base.name);1076 try self.name_buffer.append(base.name);
808 if (base.kind == .Directory) {1077 if (base.kind == .Directory) {
809 // TODO https://github.com/ziglang/zig/issues/28881078 var new_dir = top.dir_it.dir.openDir(base.name) catch |err| switch (err) {
810 var new_dir = try Dir.open(self.stack.allocator, self.name_buffer.toSliceConst());1079 error.NameTooLong => unreachable, // no path sep in base.name
1080 else => |e| return e,
1081 };
811 {1082 {
812 errdefer new_dir.close();1083 errdefer new_dir.close();
813 try self.stack.append(StackItem{1084 try self.stack.append(StackItem{
814 .dir_it = new_dir,1085 .dir_it = new_dir.iterate(),
815 .dirname_len = self.name_buffer.len(),1086 .dirname_len = self.name_buffer.len(),
816 });1087 });
817 }1088 }
818 }1089 }
819 return Entry{1090 return Entry{
1091 .dir = top.dir_it.dir,
820 .basename = self.name_buffer.toSliceConst()[dirname_len + 1 ..],1092 .basename = self.name_buffer.toSliceConst()[dirname_len + 1 ..],
821 .path = self.name_buffer.toSliceConst(),1093 .path = self.name_buffer.toSliceConst(),
822 .kind = base.kind,1094 .kind = base.kind,
823 };1095 };
824 } else {1096 } else {
825 self.stack.pop().dir_it.close();1097 self.stack.pop().dir_it.dir.close();
826 }1098 }
827 }1099 }
828 }1100 }
...@@ -837,12 +1109,12 @@ pub const Walker = struct {...@@ -837,12 +1109,12 @@ pub const Walker = struct {
837/// Recursively iterates over a directory.1109/// Recursively iterates over a directory.
838/// Must call `Walker.deinit` when done.1110/// Must call `Walker.deinit` when done.
839/// `dir_path` must not end in a path separator.1111/// `dir_path` must not end in a path separator.
840/// TODO: https://github.com/ziglang/zig/issues/28881112/// The order of returned file system entries is undefined.
841pub fn walkPath(allocator: *Allocator, dir_path: []const u8) !Walker {1113pub fn walkPath(allocator: *Allocator, dir_path: []const u8) !Walker {
842 assert(!mem.endsWith(u8, dir_path, path.sep_str));1114 assert(!mem.endsWith(u8, dir_path, path.sep_str));
8431115
844 var dir_it = try Dir.open(allocator, dir_path);1116 var dir = try Dir.open(dir_path);
845 errdefer dir_it.close();1117 errdefer dir.close();
8461118
847 var name_buffer = try std.Buffer.init(allocator, dir_path);1119 var name_buffer = try std.Buffer.init(allocator, dir_path);
848 errdefer name_buffer.deinit();1120 errdefer name_buffer.deinit();
...@@ -853,7 +1125,7 @@ pub fn walkPath(allocator: *Allocator, dir_path: []const u8) !Walker {...@@ -853,7 +1125,7 @@ pub fn walkPath(allocator: *Allocator, dir_path: []const u8) !Walker {
853 };1125 };
8541126
855 try walker.stack.append(Walker.StackItem{1127 try walker.stack.append(Walker.StackItem{
856 .dir_it = dir_it,1128 .dir_it = dir.iterate(),
857 .dirname_len = dir_path.len,1129 .dirname_len = dir_path.len,
858 });1130 });
8591131
...@@ -862,15 +1134,13 @@ pub fn walkPath(allocator: *Allocator, dir_path: []const u8) !Walker {...@@ -862,15 +1134,13 @@ pub fn walkPath(allocator: *Allocator, dir_path: []const u8) !Walker {
8621134
863/// Read value of a symbolic link.1135/// Read value of a symbolic link.
864/// The return value is a slice of buffer, from index `0`.1136/// The return value is a slice of buffer, from index `0`.
865/// TODO https://github.com/ziglang/zig/issues/28881137pub fn readLink(pathname: []const u8, buffer: *[MAX_PATH_BYTES]u8) ![]u8 {
866pub fn readLink(pathname: []const u8, buffer: *[os.PATH_MAX]u8) ![]u8 {
867 return os.readlink(pathname, buffer);1138 return os.readlink(pathname, buffer);
868}1139}
8691140
870/// Same as `readLink`, except the `pathname` parameter is null-terminated.1141/// Same as `readLink`, except the parameter is null-terminated.
871/// TODO https://github.com/ziglang/zig/issues/28881142pub fn readLinkC(pathname_c: [*]const u8, buffer: *[MAX_PATH_BYTES]u8) ![]u8 {
872pub fn readLinkC(pathname: [*]const u8, buffer: *[os.PATH_MAX]u8) ![]u8 {1143 return os.readlinkC(pathname_c, buffer);
873 return os.readlinkC(pathname, buffer);
874}1144}
8751145
876pub const OpenSelfExeError = os.OpenError || os.windows.CreateFileError || SelfExePathError;1146pub const OpenSelfExeError = os.OpenError || os.windows.CreateFileError || SelfExePathError;
lib/std/fs/file.zig+1
...@@ -243,6 +243,7 @@ pub const File = struct {...@@ -243,6 +243,7 @@ pub const File = struct {
243 switch (rc) {243 switch (rc) {
244 windows.STATUS.SUCCESS => {},244 windows.STATUS.SUCCESS => {},
245 windows.STATUS.BUFFER_OVERFLOW => {},245 windows.STATUS.BUFFER_OVERFLOW => {},
246 windows.STATUS.INVALID_PARAMETER => unreachable,
246 else => return windows.unexpectedStatus(rc),247 else => return windows.unexpectedStatus(rc),
247 }248 }
248 return Stat{249 return Stat{
lib/std/fs/path.zig+19
...@@ -136,6 +136,25 @@ pub fn isAbsolute(path: []const u8) bool {...@@ -136,6 +136,25 @@ pub fn isAbsolute(path: []const u8) bool {
136 }136 }
137}137}
138138
139pub fn isAbsoluteW(path_w: [*]const u16) bool {
140 if (path_w[0] == '/')
141 return true;
142
143 if (path_w[0] == '\\') {
144 return true;
145 }
146 if (path_w[0] == 0 or path_w[1] == 0 or path_w[2] == 0) {
147 return false;
148 }
149 if (path_w[1] == ':') {
150 if (path_w[2] == '/')
151 return true;
152 if (path_w[2] == '\\')
153 return true;
154 }
155 return false;
156}
157
139pub fn isAbsoluteWindows(path: []const u8) bool {158pub fn isAbsoluteWindows(path: []const u8) bool {
140 if (path[0] == '/')159 if (path[0] == '/')
141 return true;160 return true;
lib/std/io.zig+3
...@@ -127,6 +127,7 @@ pub fn OutStream(comptime WriteError: type) type {...@@ -127,6 +127,7 @@ pub fn OutStream(comptime WriteError: type) type {
127 };127 };
128}128}
129129
130/// TODO move this to `std.fs` and add a version to `std.fs.Dir`.
130pub fn writeFile(path: []const u8, data: []const u8) !void {131pub fn writeFile(path: []const u8, data: []const u8) !void {
131 var file = try File.openWrite(path);132 var file = try File.openWrite(path);
132 defer file.close();133 defer file.close();
...@@ -134,11 +135,13 @@ pub fn writeFile(path: []const u8, data: []const u8) !void {...@@ -134,11 +135,13 @@ pub fn writeFile(path: []const u8, data: []const u8) !void {
134}135}
135136
136/// On success, caller owns returned buffer.137/// On success, caller owns returned buffer.
138/// TODO move this to `std.fs` and add a version to `std.fs.Dir`.
137pub fn readFileAlloc(allocator: *mem.Allocator, path: []const u8) ![]u8 {139pub fn readFileAlloc(allocator: *mem.Allocator, path: []const u8) ![]u8 {
138 return readFileAllocAligned(allocator, path, @alignOf(u8));140 return readFileAllocAligned(allocator, path, @alignOf(u8));
139}141}
140142
141/// On success, caller owns returned buffer.143/// On success, caller owns returned buffer.
144/// TODO move this to `std.fs` and add a version to `std.fs.Dir`.
142pub fn readFileAllocAligned(allocator: *mem.Allocator, path: []const u8, comptime A: u29) ![]align(A) u8 {145pub fn readFileAllocAligned(allocator: *mem.Allocator, path: []const u8, comptime A: u29) ![]align(A) u8 {
143 var file = try File.openRead(path);146 var file = try File.openRead(path);
144 defer file.close();147 defer file.close();
lib/std/os.zig+146-3
...@@ -529,22 +529,36 @@ pub fn pwritev(fd: fd_t, iov: []const iovec_const, offset: u64) WriteError!void...@@ -529,22 +529,36 @@ pub fn pwritev(fd: fd_t, iov: []const iovec_const, offset: u64) WriteError!void
529529
530pub const OpenError = error{530pub const OpenError = error{
531 AccessDenied,531 AccessDenied,
532 FileTooBig,
533 IsDir,
534 SymLinkLoop,532 SymLinkLoop,
535 ProcessFdQuotaExceeded,533 ProcessFdQuotaExceeded,
536 NameTooLong,
537 SystemFdQuotaExceeded,534 SystemFdQuotaExceeded,
538 NoDevice,535 NoDevice,
539 FileNotFound,536 FileNotFound,
540537
538 /// The path exceeded `MAX_PATH_BYTES` bytes.
539 NameTooLong,
540
541 /// Insufficient kernel memory was available, or541 /// Insufficient kernel memory was available, or
542 /// the named file is a FIFO and per-user hard limit on542 /// the named file is a FIFO and per-user hard limit on
543 /// memory allocation for pipes has been reached.543 /// memory allocation for pipes has been reached.
544 SystemResources,544 SystemResources,
545545
546 /// The file is too large to be opened. This error is unreachable
547 /// for 64-bit targets, as well as when opening directories.
548 FileTooBig,
549
550 /// The path refers to directory but the `O_DIRECTORY` flag was not provided.
551 IsDir,
552
553 /// A new path cannot be created because the device has no room for the new file.
554 /// This error is only reachable when the `O_CREAT` flag is provided.
546 NoSpaceLeft,555 NoSpaceLeft,
556
557 /// A component used as a directory in the path was not, in fact, a directory, or
558 /// `O_DIRECTORY` was specified and the path was not a directory.
547 NotDir,559 NotDir,
560
561 /// The path already exists and the `O_CREAT` and `O_EXCL` flags were provided.
548 PathAlreadyExists,562 PathAlreadyExists,
549 DeviceBusy,563 DeviceBusy,
550} || UnexpectedError;564} || UnexpectedError;
...@@ -978,6 +992,114 @@ pub fn unlinkC(file_path: [*]const u8) UnlinkError!void {...@@ -978,6 +992,114 @@ pub fn unlinkC(file_path: [*]const u8) UnlinkError!void {
978 }992 }
979}993}
980994
995pub const UnlinkatError = UnlinkError || error{
996 /// When passing `AT_REMOVEDIR`, this error occurs when the named directory is not empty.
997 DirNotEmpty,
998};
999
1000/// Delete a file name and possibly the file it refers to, based on an open directory handle.
1001pub fn unlinkat(dirfd: fd_t, file_path: []const u8, flags: u32) UnlinkatError!void {
1002 if (windows.is_the_target) {
1003 const file_path_w = try windows.sliceToPrefixedFileW(file_path);
1004 return unlinkatW(dirfd, &file_path_w, flags);
1005 }
1006 const file_path_c = try toPosixPath(file_path);
1007 return unlinkatC(dirfd, &file_path_c, flags);
1008}
1009
1010/// Same as `unlinkat` but `file_path` is a null-terminated string.
1011pub fn unlinkatC(dirfd: fd_t, file_path_c: [*]const u8, flags: u32) UnlinkatError!void {
1012 if (windows.is_the_target) {
1013 const file_path_w = try windows.cStrToPrefixedFileW(file_path_c);
1014 return unlinkatW(dirfd, &file_path_w, flags);
1015 }
1016 switch (errno(system.unlinkat(dirfd, file_path_c, flags))) {
1017 0 => return,
1018 EACCES => return error.AccessDenied,
1019 EPERM => return error.AccessDenied,
1020 EBUSY => return error.FileBusy,
1021 EFAULT => unreachable,
1022 EIO => return error.FileSystem,
1023 EISDIR => return error.IsDir,
1024 ELOOP => return error.SymLinkLoop,
1025 ENAMETOOLONG => return error.NameTooLong,
1026 ENOENT => return error.FileNotFound,
1027 ENOTDIR => return error.NotDir,
1028 ENOMEM => return error.SystemResources,
1029 EROFS => return error.ReadOnlyFileSystem,
1030 ENOTEMPTY => return error.DirNotEmpty,
1031
1032 EINVAL => unreachable, // invalid flags, or pathname has . as last component
1033 EBADF => unreachable, // always a race condition
1034
1035 else => |err| return unexpectedErrno(err),
1036 }
1037}
1038
1039/// Same as `unlinkat` but `sub_path_w` is UTF16LE, NT prefixed. Windows only.
1040pub fn unlinkatW(dirfd: fd_t, sub_path_w: [*]const u16, flags: u32) UnlinkatError!void {
1041 const w = windows;
1042
1043 const want_rmdir_behavior = (flags & AT_REMOVEDIR) != 0;
1044 const create_options_flags = if (want_rmdir_behavior)
1045 w.ULONG(w.FILE_DELETE_ON_CLOSE)
1046 else
1047 w.ULONG(w.FILE_DELETE_ON_CLOSE | w.FILE_NON_DIRECTORY_FILE);
1048
1049 const path_len_bytes = @intCast(u16, mem.toSliceConst(u16, sub_path_w).len * 2);
1050 var nt_name = w.UNICODE_STRING{
1051 .Length = path_len_bytes,
1052 .MaximumLength = path_len_bytes,
1053 // The Windows API makes this mutable, but it will not mutate here.
1054 .Buffer = @intToPtr([*]u16, @ptrToInt(sub_path_w)),
1055 };
1056
1057 if (sub_path_w[0] == '.' and sub_path_w[1] == 0) {
1058 // Windows does not recognize this, but it does work with empty string.
1059 nt_name.Length = 0;
1060 }
1061 if (sub_path_w[0] == '.' and sub_path_w[1] == '.' and sub_path_w[2] == 0) {
1062 // Can't remove the parent directory with an open handle.
1063 return error.FileBusy;
1064 }
1065
1066
1067 var attr = w.OBJECT_ATTRIBUTES{
1068 .Length = @sizeOf(w.OBJECT_ATTRIBUTES),
1069 .RootDirectory = dirfd,
1070 .Attributes = 0, // Note we do not use OBJ_CASE_INSENSITIVE here.
1071 .ObjectName = &nt_name,
1072 .SecurityDescriptor = null,
1073 .SecurityQualityOfService = null,
1074 };
1075 var io: w.IO_STATUS_BLOCK = undefined;
1076 var tmp_handle: w.HANDLE = undefined;
1077 var rc = w.ntdll.NtCreateFile(
1078 &tmp_handle,
1079 w.SYNCHRONIZE | w.DELETE,
1080 &attr,
1081 &io,
1082 null,
1083 0,
1084 w.FILE_SHARE_READ | w.FILE_SHARE_WRITE | w.FILE_SHARE_DELETE,
1085 w.FILE_OPEN,
1086 create_options_flags,
1087 null,
1088 0,
1089 );
1090 if (rc == w.STATUS.SUCCESS) {
1091 rc = w.ntdll.NtClose(tmp_handle);
1092 }
1093 switch (rc) {
1094 w.STATUS.SUCCESS => return,
1095 w.STATUS.OBJECT_NAME_INVALID => unreachable,
1096 w.STATUS.OBJECT_NAME_NOT_FOUND => return error.FileNotFound,
1097 w.STATUS.INVALID_PARAMETER => unreachable,
1098 w.STATUS.FILE_IS_A_DIRECTORY => return error.IsDir,
1099 else => return w.unexpectedStatus(rc),
1100 }
1101}
1102
981const RenameError = error{1103const RenameError = error{
982 AccessDenied,1104 AccessDenied,
983 FileBusy,1105 FileBusy,
...@@ -1237,6 +1359,27 @@ pub fn readlinkC(file_path: [*]const u8, out_buffer: []u8) ReadLinkError![]u8 {...@@ -1237,6 +1359,27 @@ pub fn readlinkC(file_path: [*]const u8, out_buffer: []u8) ReadLinkError![]u8 {
1237 }1359 }
1238}1360}
12391361
1362pub fn readlinkatC(dirfd: fd_t, file_path: [*]const u8, out_buffer: []u8) ReadLinkError![]u8 {
1363 if (windows.is_the_target) {
1364 const file_path_w = try windows.cStrToPrefixedFileW(file_path);
1365 @compileError("TODO implement readlink for Windows");
1366 }
1367 const rc = system.readlinkat(dirfd, file_path, out_buffer.ptr, out_buffer.len);
1368 switch (errno(rc)) {
1369 0 => return out_buffer[0..@bitCast(usize, rc)],
1370 EACCES => return error.AccessDenied,
1371 EFAULT => unreachable,
1372 EINVAL => unreachable,
1373 EIO => return error.FileSystem,
1374 ELOOP => return error.SymLinkLoop,
1375 ENAMETOOLONG => return error.NameTooLong,
1376 ENOENT => return error.FileNotFound,
1377 ENOMEM => return error.SystemResources,
1378 ENOTDIR => return error.NotDir,
1379 else => |err| return unexpectedErrno(err),
1380 }
1381}
1382
1240pub const SetIdError = error{1383pub const SetIdError = error{
1241 ResourceLimitReached,1384 ResourceLimitReached,
1242 InvalidUserId,1385 InvalidUserId,
lib/std/os/bits/darwin.zig+14
...@@ -1178,3 +1178,17 @@ pub fn S_IWHT(m: u32) bool {...@@ -1178,3 +1178,17 @@ pub fn S_IWHT(m: u32) bool {
1178 return m & S_IFMT == S_IFWHT;1178 return m & S_IFMT == S_IFWHT;
1179}1179}
1180pub const HOST_NAME_MAX = 72;1180pub const HOST_NAME_MAX = 72;
1181
1182pub const AT_FDCWD = -2;
1183
1184/// Use effective ids in access check
1185pub const AT_EACCESS = 0x0010;
1186
1187/// Act on the symlink itself not the target
1188pub const AT_SYMLINK_NOFOLLOW = 0x0020;
1189
1190/// Act on target of symlink
1191pub const AT_SYMLINK_FOLLOW = 0x0040;
1192
1193/// Path refers to directory
1194pub const AT_REMOVEDIR = 0x0080;
lib/std/os/bits/freebsd.zig+20
...@@ -939,3 +939,23 @@ pub fn S_IWHT(m: u32) bool {...@@ -939,3 +939,23 @@ pub fn S_IWHT(m: u32) bool {
939}939}
940940
941pub const HOST_NAME_MAX = 255;941pub const HOST_NAME_MAX = 255;
942
943/// Magic value that specify the use of the current working directory
944/// to determine the target of relative file paths in the openat() and
945/// similar syscalls.
946pub const AT_FDCWD = -100;
947
948/// Check access using effective user and group ID
949pub const AT_EACCESS = 0x0100;
950
951/// Do not follow symbolic links
952pub const AT_SYMLINK_NOFOLLOW = 0x0200;
953
954/// Follow symbolic link
955pub const AT_SYMLINK_FOLLOW = 0x0400;
956
957/// Remove directory instead of file
958pub const AT_REMOVEDIR = 0x0800;
959
960/// Fail if not under dirfd
961pub const AT_BENEATH = 0x1000;
lib/std/os/bits/windows.zig+3
...@@ -158,3 +158,6 @@ pub const EWOULDBLOCK = 140;...@@ -158,3 +158,6 @@ pub const EWOULDBLOCK = 140;
158pub const EDQUOT = 10069;158pub const EDQUOT = 10069;
159159
160pub const F_OK = 0;160pub const F_OK = 0;
161
162/// Remove directory instead of unlinking file
163pub const AT_REMOVEDIR = 0x200;
lib/std/os/test.zig+3-3
...@@ -19,8 +19,8 @@ test "makePath, put some files in it, deleteTree" {...@@ -19,8 +19,8 @@ test "makePath, put some files in it, deleteTree" {
19 try fs.makePath(a, "os_test_tmp" ++ fs.path.sep_str ++ "b" ++ fs.path.sep_str ++ "c");19 try fs.makePath(a, "os_test_tmp" ++ fs.path.sep_str ++ "b" ++ fs.path.sep_str ++ "c");
20 try io.writeFile("os_test_tmp" ++ fs.path.sep_str ++ "b" ++ fs.path.sep_str ++ "c" ++ fs.path.sep_str ++ "file.txt", "nonsense");20 try io.writeFile("os_test_tmp" ++ fs.path.sep_str ++ "b" ++ fs.path.sep_str ++ "c" ++ fs.path.sep_str ++ "file.txt", "nonsense");
21 try io.writeFile("os_test_tmp" ++ fs.path.sep_str ++ "b" ++ fs.path.sep_str ++ "file2.txt", "blah");21 try io.writeFile("os_test_tmp" ++ fs.path.sep_str ++ "b" ++ fs.path.sep_str ++ "file2.txt", "blah");
22 try fs.deleteTree(a, "os_test_tmp");22 try fs.deleteTree("os_test_tmp");
23 if (fs.Dir.open(a, "os_test_tmp")) |dir| {23 if (fs.Dir.open("os_test_tmp")) |dir| {
24 @panic("expected error");24 @panic("expected error");
25 } else |err| {25 } else |err| {
26 expect(err == error.FileNotFound);26 expect(err == error.FileNotFound);
...@@ -37,7 +37,7 @@ test "access file" {...@@ -37,7 +37,7 @@ test "access file" {
3737
38 try io.writeFile("os_test_tmp" ++ fs.path.sep_str ++ "file.txt", "");38 try io.writeFile("os_test_tmp" ++ fs.path.sep_str ++ "file.txt", "");
39 try os.access("os_test_tmp" ++ fs.path.sep_str ++ "file.txt", os.F_OK);39 try os.access("os_test_tmp" ++ fs.path.sep_str ++ "file.txt", os.F_OK);
40 try fs.deleteTree(a, "os_test_tmp");40 try fs.deleteTree("os_test_tmp");
41}41}
4242
43fn testThreadIdFn(thread_id: *Thread.Id) void {43fn testThreadIdFn(thread_id: *Thread.Id) void {
lib/std/os/windows.zig+24-3
...@@ -20,6 +20,8 @@ pub const shell32 = @import("windows/shell32.zig");...@@ -20,6 +20,8 @@ pub const shell32 = @import("windows/shell32.zig");
2020
21pub usingnamespace @import("windows/bits.zig");21pub usingnamespace @import("windows/bits.zig");
2222
23pub const self_process_handle = @intToPtr(HANDLE, maxInt(usize));
24
23/// `builtin` is missing `subsystem` when the subsystem is automatically detected,25/// `builtin` is missing `subsystem` when the subsystem is automatically detected,
24/// so Zig standard library has the subsystem detection logic here. This should generally be26/// so Zig standard library has the subsystem detection logic here. This should generally be
25/// used rather than `builtin.subsystem`.27/// used rather than `builtin.subsystem`.
...@@ -791,6 +793,25 @@ pub fn SetFileTime(...@@ -791,6 +793,25 @@ pub fn SetFileTime(
791 }793 }
792}794}
793795
796pub fn peb() *PEB {
797 switch (builtin.arch) {
798 .i386 => {
799 return asm (
800 \\ mov %%fs:0x18, %[ptr]
801 \\ mov %%ds:0x30(%[ptr]), %[ptr]
802 : [ptr] "=r" (-> *PEB)
803 );
804 },
805 .x86_64 => {
806 return asm (
807 \\ mov %%gs:0x60, %[ptr]
808 : [ptr] "=r" (-> *PEB)
809 );
810 },
811 else => @compileError("unsupported architecture"),
812 }
813}
814
794/// A file time is a 64-bit value that represents the number of 100-nanosecond815/// A file time is a 64-bit value that represents the number of 100-nanosecond
795/// intervals that have elapsed since 12:00 A.M. January 1, 1601 Coordinated816/// intervals that have elapsed since 12:00 A.M. January 1, 1601 Coordinated
796/// Universal Time (UTC).817/// Universal Time (UTC).
...@@ -843,8 +864,8 @@ pub fn sliceToPrefixedSuffixedFileW(s: []const u8, comptime suffix: []const u16)...@@ -843,8 +864,8 @@ pub fn sliceToPrefixedSuffixedFileW(s: []const u8, comptime suffix: []const u16)
843 else => {},864 else => {},
844 }865 }
845 }866 }
846 const start_index = if (mem.startsWith(u8, s, "\\\\") or !std.fs.path.isAbsolute(s)) 0 else blk: {867 const start_index = if (mem.startsWith(u8, s, "\\?") or !std.fs.path.isAbsolute(s)) 0 else blk: {
847 const prefix = [_]u16{ '\\', '\\', '?', '\\' };868 const prefix = [_]u16{ '\\', '?', '?', '\\' };
848 mem.copy(u16, result[0..], prefix);869 mem.copy(u16, result[0..], prefix);
849 break :blk prefix.len;870 break :blk prefix.len;
850 };871 };
...@@ -878,7 +899,7 @@ pub fn unexpectedError(err: DWORD) std.os.UnexpectedError {...@@ -878,7 +899,7 @@ pub fn unexpectedError(err: DWORD) std.os.UnexpectedError {
878/// and you get an unexpected status.899/// and you get an unexpected status.
879pub fn unexpectedStatus(status: NTSTATUS) std.os.UnexpectedError {900pub fn unexpectedStatus(status: NTSTATUS) std.os.UnexpectedError {
880 if (std.os.unexpected_error_tracing) {901 if (std.os.unexpected_error_tracing) {
881 std.debug.warn("error.Unexpected NTSTATUS={}\n", status);902 std.debug.warn("error.Unexpected NTSTATUS=0x{x}\n", status);
882 std.debug.dumpCurrentStackTrace(null);903 std.debug.dumpCurrentStackTrace(null);
883 }904 }
884 return error.Unexpected;905 return error.Unexpected;
lib/std/os/windows/bits.zig+145-3
...@@ -300,6 +300,44 @@ pub const FILE_SHARE_DELETE = 0x00000004;...@@ -300,6 +300,44 @@ pub const FILE_SHARE_DELETE = 0x00000004;
300pub const FILE_SHARE_READ = 0x00000001;300pub const FILE_SHARE_READ = 0x00000001;
301pub const FILE_SHARE_WRITE = 0x00000002;301pub const FILE_SHARE_WRITE = 0x00000002;
302302
303pub const DELETE = 0x00010000;
304pub const READ_CONTROL = 0x00020000;
305pub const WRITE_DAC = 0x00040000;
306pub const WRITE_OWNER = 0x00080000;
307pub const SYNCHRONIZE = 0x00100000;
308pub const STANDARD_RIGHTS_REQUIRED = 0x000f0000;
309
310// disposition for NtCreateFile
311pub const FILE_SUPERSEDE = 0;
312pub const FILE_OPEN = 1;
313pub const FILE_CREATE = 2;
314pub const FILE_OPEN_IF = 3;
315pub const FILE_OVERWRITE = 4;
316pub const FILE_OVERWRITE_IF = 5;
317pub const FILE_MAXIMUM_DISPOSITION = 5;
318
319// flags for NtCreateFile and NtOpenFile
320pub const FILE_DIRECTORY_FILE = 0x00000001;
321pub const FILE_WRITE_THROUGH = 0x00000002;
322pub const FILE_SEQUENTIAL_ONLY = 0x00000004;
323pub const FILE_NO_INTERMEDIATE_BUFFERING = 0x00000008;
324pub const FILE_SYNCHRONOUS_IO_ALERT = 0x00000010;
325pub const FILE_SYNCHRONOUS_IO_NONALERT = 0x00000020;
326pub const FILE_NON_DIRECTORY_FILE = 0x00000040;
327pub const FILE_CREATE_TREE_CONNECTION = 0x00000080;
328pub const FILE_COMPLETE_IF_OPLOCKED = 0x00000100;
329pub const FILE_NO_EA_KNOWLEDGE = 0x00000200;
330pub const FILE_OPEN_FOR_RECOVERY = 0x00000400;
331pub const FILE_RANDOM_ACCESS = 0x00000800;
332pub const FILE_DELETE_ON_CLOSE = 0x00001000;
333pub const FILE_OPEN_BY_FILE_ID = 0x00002000;
334pub const FILE_OPEN_FOR_BACKUP_INTENT = 0x00004000;
335pub const FILE_NO_COMPRESSION = 0x00008000;
336pub const FILE_RESERVE_OPFILTER = 0x00100000;
337pub const FILE_TRANSACTED_MODE = 0x00200000;
338pub const FILE_OPEN_OFFLINE_FILE = 0x00400000;
339pub const FILE_OPEN_FOR_FREE_SPACE_QUERY = 0x00800000;
340
303pub const CREATE_ALWAYS = 2;341pub const CREATE_ALWAYS = 2;
304pub const CREATE_NEW = 1;342pub const CREATE_NEW = 1;
305pub const OPEN_ALWAYS = 4;343pub const OPEN_ALWAYS = 4;
...@@ -720,15 +758,119 @@ pub const VECTORED_EXCEPTION_HANDLER = stdcallcc fn (ExceptionInfo: *EXCEPTION_P...@@ -720,15 +758,119 @@ pub const VECTORED_EXCEPTION_HANDLER = stdcallcc fn (ExceptionInfo: *EXCEPTION_P
720758
721pub const OBJECT_ATTRIBUTES = extern struct {759pub const OBJECT_ATTRIBUTES = extern struct {
722 Length: ULONG,760 Length: ULONG,
723 RootDirectory: HANDLE,761 RootDirectory: ?HANDLE,
724 ObjectName: *UNICODE_STRING,762 ObjectName: *UNICODE_STRING,
725 Attributes: ULONG,763 Attributes: ULONG,
726 SecurityDescriptor: ?*c_void,764 SecurityDescriptor: ?*c_void,
727 SecurityQualityOfService: ?*c_void,765 SecurityQualityOfService: ?*c_void,
728};766};
729767
768pub const OBJ_INHERIT = 0x00000002;
769pub const OBJ_PERMANENT = 0x00000010;
770pub const OBJ_EXCLUSIVE = 0x00000020;
771pub const OBJ_CASE_INSENSITIVE = 0x00000040;
772pub const OBJ_OPENIF = 0x00000080;
773pub const OBJ_OPENLINK = 0x00000100;
774pub const OBJ_KERNEL_HANDLE = 0x00000200;
775pub const OBJ_VALID_ATTRIBUTES = 0x000003F2;
776
730pub const UNICODE_STRING = extern struct {777pub const UNICODE_STRING = extern struct {
731 Length: USHORT,778 Length: c_ushort,
732 MaximumLength: USHORT,779 MaximumLength: c_ushort,
733 Buffer: [*]WCHAR,780 Buffer: [*]WCHAR,
734};781};
782
783pub const PEB = extern struct {
784 Reserved1: [2]BYTE,
785 BeingDebugged: BYTE,
786 Reserved2: [1]BYTE,
787 Reserved3: [2]PVOID,
788 Ldr: *PEB_LDR_DATA,
789 ProcessParameters: *RTL_USER_PROCESS_PARAMETERS,
790 Reserved4: [3]PVOID,
791 AtlThunkSListPtr: PVOID,
792 Reserved5: PVOID,
793 Reserved6: ULONG,
794 Reserved7: PVOID,
795 Reserved8: ULONG,
796 AtlThunkSListPtr32: ULONG,
797 Reserved9: [45]PVOID,
798 Reserved10: [96]BYTE,
799 PostProcessInitRoutine: PPS_POST_PROCESS_INIT_ROUTINE,
800 Reserved11: [128]BYTE,
801 Reserved12: [1]PVOID,
802 SessionId: ULONG,
803};
804
805pub const PEB_LDR_DATA = extern struct {
806 Reserved1: [8]BYTE,
807 Reserved2: [3]PVOID,
808 InMemoryOrderModuleList: LIST_ENTRY,
809};
810
811pub const RTL_USER_PROCESS_PARAMETERS = extern struct {
812 AllocationSize: ULONG,
813 Size: ULONG,
814 Flags: ULONG,
815 DebugFlags: ULONG,
816 ConsoleHandle: HANDLE,
817 ConsoleFlags: ULONG,
818 hStdInput: HANDLE,
819 hStdOutput: HANDLE,
820 hStdError: HANDLE,
821 CurrentDirectory: CURDIR,
822 DllPath: UNICODE_STRING,
823 ImagePathName: UNICODE_STRING,
824 CommandLine: UNICODE_STRING,
825 Environment: [*]WCHAR,
826 dwX: ULONG,
827 dwY: ULONG,
828 dwXSize: ULONG,
829 dwYSize: ULONG,
830 dwXCountChars: ULONG,
831 dwYCountChars: ULONG,
832 dwFillAttribute: ULONG,
833 dwFlags: ULONG,
834 dwShowWindow: ULONG,
835 WindowTitle: UNICODE_STRING,
836 Desktop: UNICODE_STRING,
837 ShellInfo: UNICODE_STRING,
838 RuntimeInfo: UNICODE_STRING,
839 DLCurrentDirectory: [0x20]RTL_DRIVE_LETTER_CURDIR,
840};
841
842pub const RTL_DRIVE_LETTER_CURDIR = extern struct {
843 Flags: c_ushort,
844 Length: c_ushort,
845 TimeStamp: ULONG,
846 DosPath: UNICODE_STRING,
847};
848
849pub const PPS_POST_PROCESS_INIT_ROUTINE = ?extern fn () void;
850
851pub const FILE_BOTH_DIR_INFORMATION = extern struct {
852 NextEntryOffset: ULONG,
853 FileIndex: ULONG,
854 CreationTime: LARGE_INTEGER,
855 LastAccessTime: LARGE_INTEGER,
856 LastWriteTime: LARGE_INTEGER,
857 ChangeTime: LARGE_INTEGER,
858 EndOfFile: LARGE_INTEGER,
859 AllocationSize: LARGE_INTEGER,
860 FileAttributes: ULONG,
861 FileNameLength: ULONG,
862 EaSize: ULONG,
863 ShortNameLength: CHAR,
864 ShortName: [12]WCHAR,
865 FileName: [1]WCHAR,
866};
867pub const FILE_BOTH_DIRECTORY_INFORMATION = FILE_BOTH_DIR_INFORMATION;
868
869pub const IO_APC_ROUTINE = extern fn (PVOID, *IO_STATUS_BLOCK, ULONG) void;
870
871pub const CURDIR = extern struct {
872 DosPath: UNICODE_STRING,
873 Handle: HANDLE,
874};
875
876pub const DUPLICATE_SAME_ACCESS = 2;
\ No newline at end of file
lib/std/os/windows/kernel32.zig+2
...@@ -47,6 +47,8 @@ pub extern "kernel32" stdcallcc fn CreateThread(lpThreadAttributes: ?LPSECURITY_...@@ -47,6 +47,8 @@ pub extern "kernel32" stdcallcc fn CreateThread(lpThreadAttributes: ?LPSECURITY_
4747
48pub extern "kernel32" stdcallcc fn DeleteFileW(lpFileName: [*]const u16) BOOL;48pub extern "kernel32" stdcallcc fn DeleteFileW(lpFileName: [*]const u16) BOOL;
4949
50pub extern "kernel32" stdcallcc fn DuplicateHandle(hSourceProcessHandle: HANDLE, hSourceHandle: HANDLE, hTargetProcessHandle: HANDLE, lpTargetHandle: *HANDLE, dwDesiredAccess: DWORD, bInheritHandle: BOOL, dwOptions: DWORD) BOOL;
51
50pub extern "kernel32" stdcallcc fn ExitProcess(exit_code: UINT) noreturn;52pub extern "kernel32" stdcallcc fn ExitProcess(exit_code: UINT) noreturn;
5153
52pub extern "kernel32" stdcallcc fn FindFirstFileW(lpFileName: [*]const u16, lpFindFileData: *WIN32_FIND_DATAW) HANDLE;54pub extern "kernel32" stdcallcc fn FindFirstFileW(lpFileName: [*]const u16, lpFindFileData: *WIN32_FIND_DATAW) HANDLE;
lib/std/os/windows/ntdll.zig+23-2
...@@ -13,12 +13,33 @@ pub extern "NtDll" stdcallcc fn NtCreateFile(...@@ -13,12 +13,33 @@ pub extern "NtDll" stdcallcc fn NtCreateFile(
13 DesiredAccess: ACCESS_MASK,13 DesiredAccess: ACCESS_MASK,
14 ObjectAttributes: *OBJECT_ATTRIBUTES,14 ObjectAttributes: *OBJECT_ATTRIBUTES,
15 IoStatusBlock: *IO_STATUS_BLOCK,15 IoStatusBlock: *IO_STATUS_BLOCK,
16 AllocationSize: *LARGE_INTEGER,16 AllocationSize: ?*LARGE_INTEGER,
17 FileAttributes: ULONG,17 FileAttributes: ULONG,
18 ShareAccess: ULONG,18 ShareAccess: ULONG,
19 CreateDisposition: ULONG,19 CreateDisposition: ULONG,
20 CreateOptions: ULONG,20 CreateOptions: ULONG,
21 EaBuffer: *c_void,21 EaBuffer: ?*c_void,
22 EaLength: ULONG,22 EaLength: ULONG,
23) NTSTATUS;23) NTSTATUS;
24pub extern "NtDll" stdcallcc fn NtClose(Handle: HANDLE) NTSTATUS;24pub extern "NtDll" stdcallcc fn NtClose(Handle: HANDLE) NTSTATUS;
25pub extern "NtDll" stdcallcc fn RtlDosPathNameToNtPathName_U(
26 DosPathName: [*]const u16,
27 NtPathName: *UNICODE_STRING,
28 NtFileNamePart: ?*?[*]const u16,
29 DirectoryInfo: ?*CURDIR,
30) BOOL;
31pub extern "NtDll" stdcallcc fn RtlFreeUnicodeString(UnicodeString: *UNICODE_STRING) void;
32
33pub extern "NtDll" stdcallcc fn NtQueryDirectoryFile(
34 FileHandle: HANDLE,
35 Event: ?HANDLE,
36 ApcRoutine: ?IO_APC_ROUTINE,
37 ApcContext: ?*c_void,
38 IoStatusBlock: *IO_STATUS_BLOCK,
39 FileInformation: *c_void,
40 Length: ULONG,
41 FileInformationClass: FILE_INFORMATION_CLASS,
42 ReturnSingleEntry: BOOLEAN,
43 FileName: ?*UNICODE_STRING,
44 RestartScan: BOOLEAN,
45) NTSTATUS;
lib/std/os/windows/status.zig+8
...@@ -3,3 +3,11 @@ pub const SUCCESS = 0x00000000;...@@ -3,3 +3,11 @@ pub const SUCCESS = 0x00000000;
33
4/// The data was too large to fit into the specified buffer.4/// The data was too large to fit into the specified buffer.
5pub const BUFFER_OVERFLOW = 0x80000005;5pub const BUFFER_OVERFLOW = 0x80000005;
6
7pub const INVALID_PARAMETER = 0xC000000D;
8pub const ACCESS_DENIED = 0xC0000022;
9pub const OBJECT_NAME_INVALID = 0xC0000033;
10pub const OBJECT_NAME_NOT_FOUND = 0xC0000034;
11pub const OBJECT_PATH_NOT_FOUND = 0xC000003A;
12pub const OBJECT_PATH_SYNTAX_BAD = 0xC000003B;
13pub const FILE_IS_A_DIRECTORY = 0xC00000BA;
lib/std/special/test_runner.zig+6-1
...@@ -16,14 +16,17 @@ pub fn main() anyerror!void {...@@ -16,14 +16,17 @@ pub fn main() anyerror!void {
16 var test_node = root_node.start(test_fn.name, null);16 var test_node = root_node.start(test_fn.name, null);
17 test_node.activate();17 test_node.activate();
18 progress.refresh();18 progress.refresh();
19 if (progress.terminal == null) std.debug.warn("{}/{} {}...", i + 1, test_fn_list.len, test_fn.name);
19 if (test_fn.func()) |_| {20 if (test_fn.func()) |_| {
20 ok_count += 1;21 ok_count += 1;
21 test_node.end();22 test_node.end();
23 if (progress.terminal == null) std.debug.warn("OK\n");
22 } else |err| switch (err) {24 } else |err| switch (err) {
23 error.SkipZigTest => {25 error.SkipZigTest => {
24 skip_count += 1;26 skip_count += 1;
25 test_node.end();27 test_node.end();
26 progress.log("{}...SKIP\n", test_fn.name);28 progress.log("{}...SKIP\n", test_fn.name);
29 if (progress.terminal == null) std.debug.warn("SKIP\n");
27 },30 },
28 else => {31 else => {
29 progress.log("");32 progress.log("");
...@@ -32,7 +35,9 @@ pub fn main() anyerror!void {...@@ -32,7 +35,9 @@ pub fn main() anyerror!void {
32 }35 }
33 }36 }
34 root_node.end();37 root_node.end();
35 if (ok_count != test_fn_list.len) {38 if (ok_count == test_fn_list.len) {
39 std.debug.warn("All tests passed.\n");
40 } else {
36 std.debug.warn("{} passed; {} skipped.\n", ok_count, skip_count);41 std.debug.warn("{} passed; {} skipped.\n", ok_count, skip_count);
37 }42 }
38}43}
src-self-hosted/main.zig+1-1
...@@ -747,7 +747,7 @@ async fn fmtPath(fmt: *Fmt, file_path_ref: []const u8, check_mode: bool) FmtErro...@@ -747,7 +747,7 @@ async fn fmtPath(fmt: *Fmt, file_path_ref: []const u8, check_mode: bool) FmtErro
747 )) catch |err| switch (err) {747 )) catch |err| switch (err) {
748 error.IsDir, error.AccessDenied => {748 error.IsDir, error.AccessDenied => {
749 // TODO make event based (and dir.next())749 // TODO make event based (and dir.next())
750 var dir = try fs.Dir.open(fmt.loop.allocator, file_path);750 var dir = try fs.Dir.open(file_path);
751 defer dir.close();751 defer dir.close();
752752
753 var group = event.Group(FmtError!void).init(fmt.loop);753 var group = event.Group(FmtError!void).init(fmt.loop);
src-self-hosted/stage1.zig+5-3
...@@ -283,11 +283,13 @@ fn fmtPath(fmt: *Fmt, file_path_ref: []const u8, check_mode: bool) FmtError!void...@@ -283,11 +283,13 @@ fn fmtPath(fmt: *Fmt, file_path_ref: []const u8, check_mode: bool) FmtError!void
283 const source_code = io.readFileAlloc(fmt.allocator, file_path) catch |err| switch (err) {283 const source_code = io.readFileAlloc(fmt.allocator, file_path) catch |err| switch (err) {
284 error.IsDir, error.AccessDenied => {284 error.IsDir, error.AccessDenied => {
285 // TODO make event based (and dir.next())285 // TODO make event based (and dir.next())
286 var dir = try fs.Dir.open(fmt.allocator, file_path);286 var dir = try fs.Dir.open(file_path);
287 defer dir.close();287 defer dir.close();
288288
289 while (try dir.next()) |entry| {289 var dir_it = dir.iterate();
290 if (entry.kind == fs.Dir.Entry.Kind.Directory or mem.endsWith(u8, entry.name, ".zig")) {290
291 while (try dir_it.next()) |entry| {
292 if (entry.kind == .Directory or mem.endsWith(u8, entry.name, ".zig")) {
291 const full_path = try fs.path.join(fmt.allocator, [_][]const u8{ file_path, entry.name });293 const full_path = try fs.path.join(fmt.allocator, [_][]const u8{ file_path, entry.name });
292 try fmtPath(fmt, full_path, check_mode);294 try fmtPath(fmt, full_path, check_mode);
293 }295 }
src-self-hosted/test.zig+2-2
...@@ -56,11 +56,11 @@ pub const TestContext = struct {...@@ -56,11 +56,11 @@ pub const TestContext = struct {
56 errdefer allocator.free(self.zig_lib_dir);56 errdefer allocator.free(self.zig_lib_dir);
5757
58 try std.fs.makePath(allocator, tmp_dir_name);58 try std.fs.makePath(allocator, tmp_dir_name);
59 errdefer std.fs.deleteTree(allocator, tmp_dir_name) catch {};59 errdefer std.fs.deleteTree(tmp_dir_name) catch {};
60 }60 }
6161
62 fn deinit(self: *TestContext) void {62 fn deinit(self: *TestContext) void {
63 std.fs.deleteTree(allocator, tmp_dir_name) catch {};63 std.fs.deleteTree(tmp_dir_name) catch {};
64 allocator.free(self.zig_lib_dir);64 allocator.free(self.zig_lib_dir);
65 self.zig_compiler.deinit();65 self.zig_compiler.deinit();
66 self.loop.deinit();66 self.loop.deinit();
test/cli.zig+2-2
...@@ -37,7 +37,7 @@ pub fn main() !void {...@@ -37,7 +37,7 @@ pub fn main() !void {
37 testMissingOutputPath,37 testMissingOutputPath,
38 };38 };
39 for (test_fns) |testFn| {39 for (test_fns) |testFn| {
40 try fs.deleteTree(a, dir_path);40 try fs.deleteTree(dir_path);
41 try fs.makeDir(dir_path);41 try fs.makeDir(dir_path);
42 try testFn(zig_exe, dir_path);42 try testFn(zig_exe, dir_path);
43 }43 }
...@@ -87,7 +87,7 @@ fn exec(cwd: []const u8, argv: []const []const u8) !ChildProcess.ExecResult {...@@ -87,7 +87,7 @@ fn exec(cwd: []const u8, argv: []const []const u8) !ChildProcess.ExecResult {
87fn testZigInitLib(zig_exe: []const u8, dir_path: []const u8) !void {87fn testZigInitLib(zig_exe: []const u8, dir_path: []const u8) !void {
88 _ = try exec(dir_path, [_][]const u8{ zig_exe, "init-lib" });88 _ = try exec(dir_path, [_][]const u8{ zig_exe, "init-lib" });
89 const test_result = try exec(dir_path, [_][]const u8{ zig_exe, "build", "test" });89 const test_result = try exec(dir_path, [_][]const u8{ zig_exe, "build", "test" });
90 testing.expect(std.mem.eql(u8, test_result.stderr, ""));90 testing.expect(std.mem.endsWith(u8, test_result.stderr, "All tests passed.\n"));
91}91}
9292
93fn testZigInitExe(zig_exe: []const u8, dir_path: []const u8) !void {93fn testZigInitExe(zig_exe: []const u8, dir_path: []const u8) !void {
tools/process_headers.zig+1-1
...@@ -340,7 +340,7 @@ pub fn main() !void {...@@ -340,7 +340,7 @@ pub fn main() !void {
340 try dir_stack.append(target_include_dir);340 try dir_stack.append(target_include_dir);
341341
342 while (dir_stack.popOrNull()) |full_dir_name| {342 while (dir_stack.popOrNull()) |full_dir_name| {
343 var dir = std.fs.Dir.open(allocator, full_dir_name) catch |err| switch (err) {343 var dir = std.fs.Dir.open(full_dir_name) catch |err| switch (err) {
344 error.FileNotFound => continue :search,344 error.FileNotFound => continue :search,
345 error.AccessDenied => continue :search,345 error.AccessDenied => continue :search,
346 else => return err,346 else => return err,