| author | |
| committer | |
| log | 8654bc18104d64c7a7f9f80bdba75ed4e0c005fa |
| tree | 398f7b9d18988b61ad12db21aa72e21da3aec4d4 |
| parent | 1ff73a8e69a09b3bc993cffb755cc2ca98c7040b |
* add std.os.deleteTree
* add std.os.deleteDir
* add std.os.page_size
* add std.os API for iterating over directories
* refactor duplication in build.zig
* update documentation on how to run tests9 files changed, 281 insertions(+), 50 deletions(-)
README.md+3-3| ... | @@ -45,8 +45,8 @@ compromises backward compatibility. | ... | @@ -45,8 +45,8 @@ compromises backward compatibility. |
| 45 | * Release mode produces heavily optimized code. What other projects call | 45 | * Release mode produces heavily optimized code. What other projects call |
| 46 | "Link Time Optimization" Zig does automatically. | 46 | "Link Time Optimization" Zig does automatically. |
| 47 | * Mark functions as tests and automatically run them with `zig test`. | 47 | * Mark functions as tests and automatically run them with `zig test`. |
| 48 | * Currently supported architectures: `x86_64`, `i386` | 48 | * Currently supported architectures: `x86_64` |
| 49 | * Currently supported operating systems: linux, macosx | 49 | * Currently supported operating systems: linux |
| 50 | * Friendly toward package maintainers. Reproducible build, bootstrapping | 50 | * Friendly toward package maintainers. Reproducible build, bootstrapping |
| 51 | process carefully documented. Issues filed by package maintainers are | 51 | process carefully documented. Issues filed by package maintainers are |
| 52 | considered especially important. | 52 | considered especially important. |
| ... | @@ -103,7 +103,7 @@ cd build | ... | @@ -103,7 +103,7 @@ cd build |
| 103 | cmake .. -DCMAKE_INSTALL_PREFIX=$(pwd) -DZIG_LIBC_LIB_DIR=$(dirname $(cc -print-file-name=crt1.o)) -DZIG_LIBC_INCLUDE_DIR=$(echo -n | cc -E -x c - -v 2>&1 | grep -B1 "End of search list." | head -n1 | cut -c 2- | sed "s/ .*//") -DZIG_LIBC_STATIC_LIB_DIR=$(dirname $(cc -print-file-name=crtbegin.o)) | 103 | cmake .. -DCMAKE_INSTALL_PREFIX=$(pwd) -DZIG_LIBC_LIB_DIR=$(dirname $(cc -print-file-name=crt1.o)) -DZIG_LIBC_INCLUDE_DIR=$(echo -n | cc -E -x c - -v 2>&1 | grep -B1 "End of search list." | head -n1 | cut -c 2- | sed "s/ .*//") -DZIG_LIBC_STATIC_LIB_DIR=$(dirname $(cc -print-file-name=crtbegin.o)) |
| 104 | make | 104 | make |
| 105 | make install | 105 | make install |
| 106 | ./run_tests | 106 | ./zig build --build-file ../build.zig test |
| 107 | ``` | 107 | ``` |
| 108 | 108 | ||
| 109 | ### Release / Install Build | 109 | ### Release / Install Build |
build.zig+13-38| ... | @@ -5,44 +5,19 @@ pub fn build(b: &Builder) { | ... | @@ -5,44 +5,19 @@ pub fn build(b: &Builder) { |
| 5 | const test_filter = b.option([]const u8, "test-filter", "Skip tests that do not match filter"); | 5 | const test_filter = b.option([]const u8, "test-filter", "Skip tests that do not match filter"); |
| 6 | const test_step = b.step("test", "Run all the tests"); | 6 | const test_step = b.step("test", "Run all the tests"); |
| 7 | 7 | ||
| 8 | const behavior_tests = b.step("test-behavior", "Run the behavior tests"); | 8 | const cleanup = b.addRemoveDirTree("test_artifacts"); |
| 9 | test_step.dependOn(behavior_tests); | 9 | test_step.dependOn(&cleanup.step); |
| 10 | for ([]bool{false, true}) |release| { | ||
| 11 | for ([]bool{false, true}) |link_libc| { | ||
| 12 | const these_tests = b.addTest("test/behavior.zig"); | ||
| 13 | these_tests.setNamePrefix(b.fmt("behavior-{}-{} ", | ||
| 14 | if (release) "release" else "debug", | ||
| 15 | if (link_libc) "c" else "bare")); | ||
| 16 | these_tests.setFilter(test_filter); | ||
| 17 | these_tests.setRelease(release); | ||
| 18 | if (link_libc) { | ||
| 19 | these_tests.linkLibrary("c"); | ||
| 20 | } | ||
| 21 | behavior_tests.dependOn(&these_tests.step); | ||
| 22 | } | ||
| 23 | } | ||
| 24 | 10 | ||
| 25 | const std_lib_tests = b.step("test-std", "Run the standard library tests"); | 11 | cleanup.step.dependOn(tests.addPkgTests(b, test_filter, |
| 26 | test_step.dependOn(std_lib_tests); | 12 | "test/behavior.zig", "behavior", "Run the behavior tests")); |
| 27 | for ([]bool{false, true}) |release| { | ||
| 28 | for ([]bool{false, true}) |link_libc| { | ||
| 29 | const these_tests = b.addTest("std/index.zig"); | ||
| 30 | these_tests.setNamePrefix(b.fmt("std-{}-{} ", | ||
| 31 | if (release) "release" else "debug", | ||
| 32 | if (link_libc) "c" else "bare")); | ||
| 33 | these_tests.setFilter(test_filter); | ||
| 34 | these_tests.setRelease(release); | ||
| 35 | if (link_libc) { | ||
| 36 | these_tests.linkLibrary("c"); | ||
| 37 | } | ||
| 38 | std_lib_tests.dependOn(&these_tests.step); | ||
| 39 | } | ||
| 40 | } | ||
| 41 | 13 | ||
| 42 | test_step.dependOn(tests.addCompareOutputTests(b, test_filter)); | 14 | cleanup.step.dependOn(tests.addPkgTests(b, test_filter, |
| 43 | test_step.dependOn(tests.addBuildExampleTests(b, test_filter)); | 15 | "std/index.zig", "std", "Run the standard library tests")); |
| 44 | test_step.dependOn(tests.addCompileErrorTests(b, test_filter)); | 16 | |
| 45 | test_step.dependOn(tests.addAssembleAndLinkTests(b, test_filter)); | 17 | cleanup.step.dependOn(tests.addCompareOutputTests(b, test_filter)); |
| 46 | test_step.dependOn(tests.addDebugSafetyTests(b, test_filter)); | 18 | cleanup.step.dependOn(tests.addBuildExampleTests(b, test_filter)); |
| 47 | test_step.dependOn(tests.addParseHTests(b, test_filter)); | 19 | cleanup.step.dependOn(tests.addCompileErrorTests(b, test_filter)); |
| 20 | cleanup.step.dependOn(tests.addAssembleAndLinkTests(b, test_filter)); | ||
| 21 | cleanup.step.dependOn(tests.addDebugSafetyTests(b, test_filter)); | ||
| 22 | cleanup.step.dependOn(tests.addParseHTests(b, test_filter)); | ||
| 48 | } | 23 | } |
std/build.zig+34-2| ... | @@ -195,6 +195,12 @@ pub const Builder = struct { | ... | @@ -195,6 +195,12 @@ pub const Builder = struct { |
| 195 | return log_step; | 195 | return log_step; |
| 196 | } | 196 | } |
| 197 | 197 | ||
| 198 | pub fn addRemoveDirTree(self: &Builder, dir_path: []const u8) -> &RemoveDirStep { | ||
| 199 | const remove_dir_step = %%self.allocator.create(RemoveDirStep); | ||
| 200 | *remove_dir_step = RemoveDirStep.init(self, dir_path); | ||
| 201 | return remove_dir_step; | ||
| 202 | } | ||
| 203 | |||
| 198 | pub fn version(self: &const Builder, major: u32, minor: u32, patch: u32) -> Version { | 204 | pub fn version(self: &const Builder, major: u32, minor: u32, patch: u32) -> Version { |
| 199 | Version { | 205 | Version { |
| 200 | .major = major, | 206 | .major = major, |
| ... | @@ -1548,10 +1554,12 @@ pub const WriteFileStep = struct { | ... | @@ -1548,10 +1554,12 @@ pub const WriteFileStep = struct { |
| 1548 | const full_path = self.builder.pathFromRoot(self.file_path); | 1554 | const full_path = self.builder.pathFromRoot(self.file_path); |
| 1549 | const full_path_dir = %%os.path.dirname(self.builder.allocator, full_path); | 1555 | const full_path_dir = %%os.path.dirname(self.builder.allocator, full_path); |
| 1550 | os.makePath(self.builder.allocator, full_path_dir) %% |err| { | 1556 | os.makePath(self.builder.allocator, full_path_dir) %% |err| { |
| 1551 | debug.panic("unable to make path {}: {}\n", full_path_dir, @errorName(err)); | 1557 | %%io.stderr.printf("unable to make path {}: {}\n", full_path_dir, @errorName(err)); |
| 1558 | return err; | ||
| 1552 | }; | 1559 | }; |
| 1553 | io.writeFile(full_path, self.data, self.builder.allocator) %% |err| { | 1560 | io.writeFile(full_path, self.data, self.builder.allocator) %% |err| { |
| 1554 | debug.panic("unable to write {}: {}\n", full_path, @errorName(err)); | 1561 | %%io.stderr.printf("unable to write {}: {}\n", full_path, @errorName(err)); |
| 1562 | return err; | ||
| 1555 | }; | 1563 | }; |
| 1556 | } | 1564 | } |
| 1557 | }; | 1565 | }; |
| ... | @@ -1576,6 +1584,30 @@ pub const LogStep = struct { | ... | @@ -1576,6 +1584,30 @@ pub const LogStep = struct { |
| 1576 | } | 1584 | } |
| 1577 | }; | 1585 | }; |
| 1578 | 1586 | ||
| 1587 | pub const RemoveDirStep = struct { | ||
| 1588 | step: Step, | ||
| 1589 | builder: &Builder, | ||
| 1590 | dir_path: []const u8, | ||
| 1591 | |||
| 1592 | pub fn init(builder: &Builder, dir_path: []const u8) -> RemoveDirStep { | ||
| 1593 | return RemoveDirStep { | ||
| 1594 | .builder = builder, | ||
| 1595 | .step = Step.init(builder.fmt("RemoveDir {}", dir_path), builder.allocator, make), | ||
| 1596 | .dir_path = dir_path, | ||
| 1597 | }; | ||
| 1598 | } | ||
| 1599 | |||
| 1600 | fn make(step: &Step) -> %void { | ||
| 1601 | const self = @fieldParentPtr(RemoveDirStep, "step", step); | ||
| 1602 | |||
| 1603 | const full_path = self.builder.pathFromRoot(self.dir_path); | ||
| 1604 | os.deleteTree(self.builder.allocator, full_path) %% |err| { | ||
| 1605 | %%io.stderr.printf("Unable to remove {}: {}\n", full_path, @errorName(err)); | ||
| 1606 | return err; | ||
| 1607 | }; | ||
| 1608 | } | ||
| 1609 | }; | ||
| 1610 | |||
| 1579 | pub const Step = struct { | 1611 | pub const Step = struct { |
| 1580 | name: []const u8, | 1612 | name: []const u8, |
| 1581 | makeFn: fn(self: &Step) -> %void, | 1613 | makeFn: fn(self: &Step) -> %void, |
std/io.zig+4-6| ... | @@ -57,8 +57,6 @@ error NoMem; | ... | @@ -57,8 +57,6 @@ error NoMem; |
| 57 | error Unseekable; | 57 | error Unseekable; |
| 58 | error Eof; | 58 | error Eof; |
| 59 | 59 | ||
| 60 | const buffer_size = 4 * 1024; | ||
| 61 | |||
| 62 | pub const OpenRead = 0b0001; | 60 | pub const OpenRead = 0b0001; |
| 63 | pub const OpenWrite = 0b0010; | 61 | pub const OpenWrite = 0b0010; |
| 64 | pub const OpenCreate = 0b0100; | 62 | pub const OpenCreate = 0b0100; |
| ... | @@ -66,7 +64,7 @@ pub const OpenTruncate = 0b1000; | ... | @@ -66,7 +64,7 @@ pub const OpenTruncate = 0b1000; |
| 66 | 64 | ||
| 67 | pub const OutStream = struct { | 65 | pub const OutStream = struct { |
| 68 | fd: i32, | 66 | fd: i32, |
| 69 | buffer: [buffer_size]u8, | 67 | buffer: [os.page_size]u8, |
| 70 | index: usize, | 68 | index: usize, |
| 71 | 69 | ||
| 72 | /// `path` may need to be copied in memory to add a null terminating byte. In this case | 70 | /// `path` may need to be copied in memory to add a null terminating byte. In this case |
| ... | @@ -97,7 +95,7 @@ pub const OutStream = struct { | ... | @@ -97,7 +95,7 @@ pub const OutStream = struct { |
| 97 | } | 95 | } |
| 98 | 96 | ||
| 99 | pub fn write(self: &OutStream, bytes: []const u8) -> %void { | 97 | pub fn write(self: &OutStream, bytes: []const u8) -> %void { |
| 100 | if (bytes.len >= buffer_size) { | 98 | if (bytes.len >= self.buffer.len) { |
| 101 | %return self.flush(); | 99 | %return self.flush(); |
| 102 | return os.posixWrite(self.fd, bytes); | 100 | return os.posixWrite(self.fd, bytes); |
| 103 | } | 101 | } |
| ... | @@ -329,7 +327,7 @@ pub const InStream = struct { | ... | @@ -329,7 +327,7 @@ pub const InStream = struct { |
| 329 | } | 327 | } |
| 330 | 328 | ||
| 331 | pub fn readAll(is: &InStream, buf: &Buffer0) -> %void { | 329 | pub fn readAll(is: &InStream, buf: &Buffer0) -> %void { |
| 332 | %return buf.resize(buffer_size); | 330 | %return buf.resize(os.page_size); |
| 333 | 331 | ||
| 334 | var actual_buf_len: usize = 0; | 332 | var actual_buf_len: usize = 0; |
| 335 | while (true) { | 333 | while (true) { |
| ... | @@ -341,7 +339,7 @@ pub const InStream = struct { | ... | @@ -341,7 +339,7 @@ pub const InStream = struct { |
| 341 | return buf.resize(actual_buf_len); | 339 | return buf.resize(actual_buf_len); |
| 342 | } | 340 | } |
| 343 | 341 | ||
| 344 | %return buf.resize(actual_buf_len + buffer_size); | 342 | %return buf.resize(actual_buf_len + os.page_size); |
| 345 | } | 343 | } |
| 346 | } | 344 | } |
| 347 | }; | 345 | }; |
std/list.zig+9-1| ... | @@ -61,10 +61,15 @@ pub fn List(comptime T: type) -> type{ | ... | @@ -61,10 +61,15 @@ pub fn List(comptime T: type) -> type{ |
| 61 | l.len = new_length; | 61 | l.len = new_length; |
| 62 | return result; | 62 | return result; |
| 63 | } | 63 | } |
| 64 | |||
| 65 | pub fn pop(self: &Self) -> T { | ||
| 66 | self.len -= 1; | ||
| 67 | return self.items[self.len]; | ||
| 68 | } | ||
| 64 | } | 69 | } |
| 65 | } | 70 | } |
| 66 | 71 | ||
| 67 | test "basicListTest" { | 72 | test "basic list test" { |
| 68 | var list = List(i32).init(&debug.global_allocator); | 73 | var list = List(i32).init(&debug.global_allocator); |
| 69 | defer list.deinit(); | 74 | defer list.deinit(); |
| 70 | 75 | ||
| ... | @@ -75,4 +80,7 @@ test "basicListTest" { | ... | @@ -75,4 +80,7 @@ test "basicListTest" { |
| 75 | {var i: usize = 0; while (i < 10; i += 1) { | 80 | {var i: usize = 0; while (i < 10; i += 1) { |
| 76 | assert(list.items[i] == i32(i + 1)); | 81 | assert(list.items[i] == i32(i + 1)); |
| 77 | }} | 82 | }} |
| 83 | |||
| 84 | assert(list.pop() == 10); | ||
| 85 | assert(list.len == 9); | ||
| 78 | } | 86 | } |
std/mem.zig+2| ... | @@ -9,6 +9,8 @@ error NoMem; | ... | @@ -9,6 +9,8 @@ error NoMem; |
| 9 | 9 | ||
| 10 | pub const Allocator = struct { | 10 | pub const Allocator = struct { |
| 11 | allocFn: fn (self: &Allocator, n: usize) -> %[]u8, | 11 | allocFn: fn (self: &Allocator, n: usize) -> %[]u8, |
| 12 | /// Note that old_mem may be a slice of length 0, in which case reallocFn | ||
| 13 | /// should simply call allocFn | ||
| 12 | reallocFn: fn (self: &Allocator, old_mem: []u8, new_size: usize) -> %[]u8, | 14 | reallocFn: fn (self: &Allocator, old_mem: []u8, new_size: usize) -> %[]u8, |
| 13 | freeFn: fn (self: &Allocator, mem: []u8), | 15 | freeFn: fn (self: &Allocator, mem: []u8), |
| 14 | 16 |
std/os/index.zig+178| ... | @@ -17,6 +17,8 @@ pub const line_sep = switch (@compileVar("os")) { | ... | @@ -17,6 +17,8 @@ pub const line_sep = switch (@compileVar("os")) { |
| 17 | else => "\n", | 17 | else => "\n", |
| 18 | }; | 18 | }; |
| 19 | 19 | ||
| 20 | pub const page_size = 4 * 1024; | ||
| 21 | |||
| 20 | const debug = @import("../debug.zig"); | 22 | const debug = @import("../debug.zig"); |
| 21 | const assert = debug.assert; | 23 | const assert = debug.assert; |
| 22 | 24 | ||
| ... | @@ -32,6 +34,7 @@ const cstr = @import("../cstr.zig"); | ... | @@ -32,6 +34,7 @@ const cstr = @import("../cstr.zig"); |
| 32 | 34 | ||
| 33 | const io = @import("../io.zig"); | 35 | const io = @import("../io.zig"); |
| 34 | const base64 = @import("../base64.zig"); | 36 | const base64 = @import("../base64.zig"); |
| 37 | const List = @import("../list.zig").List; | ||
| 35 | 38 | ||
| 36 | error Unexpected; | 39 | error Unexpected; |
| 37 | error SystemResources; | 40 | error SystemResources; |
| ... | @@ -46,6 +49,7 @@ error SymLinkLoop; | ... | @@ -46,6 +49,7 @@ error SymLinkLoop; |
| 46 | error ReadOnlyFileSystem; | 49 | error ReadOnlyFileSystem; |
| 47 | error LinkQuotaExceeded; | 50 | error LinkQuotaExceeded; |
| 48 | error RenameAcrossMountPoints; | 51 | error RenameAcrossMountPoints; |
| 52 | error DirNotEmpty; | ||
| 49 | 53 | ||
| 50 | /// Fills `buf` with random bytes. If linking against libc, this calls the | 54 | /// Fills `buf` with random bytes. If linking against libc, this calls the |
| 51 | /// appropriate OS-specific library call. Otherwise it uses the zig standard | 55 | /// appropriate OS-specific library call. Otherwise it uses the zig standard |
| ... | @@ -585,3 +589,177 @@ pub fn makePath(allocator: &Allocator, full_path: []const u8) -> %void { | ... | @@ -585,3 +589,177 @@ pub fn makePath(allocator: &Allocator, full_path: []const u8) -> %void { |
| 585 | // TODO stat the file and return an error if it's not a directory | 589 | // TODO stat the file and return an error if it's not a directory |
| 586 | }; | 590 | }; |
| 587 | } | 591 | } |
| 592 | |||
| 593 | /// Returns ::error.DirNotEmpty if the directory is not empty. | ||
| 594 | /// To delete a directory recursively, see ::deleteTree | ||
| 595 | pub fn deleteDir(allocator: &Allocator, dir_path: []const u8) -> %void { | ||
| 596 | const path_buf = %return allocator.alloc(u8, dir_path.len + 1); | ||
| 597 | defer allocator.free(path_buf); | ||
| 598 | |||
| 599 | mem.copy(u8, path_buf, dir_path); | ||
| 600 | path_buf[dir_path.len] = 0; | ||
| 601 | |||
| 602 | const err = posix.getErrno(posix.rmdir(path_buf.ptr)); | ||
| 603 | if (err > 0) { | ||
| 604 | return switch (err) { | ||
| 605 | errno.EACCES, errno.EPERM => error.AccessDenied, | ||
| 606 | errno.EBUSY => error.FileBusy, | ||
| 607 | errno.EFAULT, errno.EINVAL => unreachable, | ||
| 608 | errno.ELOOP => error.SymLinkLoop, | ||
| 609 | errno.ENAMETOOLONG => error.NameTooLong, | ||
| 610 | errno.ENOENT => error.FileNotFound, | ||
| 611 | errno.ENOMEM => error.SystemResources, | ||
| 612 | errno.ENOTDIR => error.NotDir, | ||
| 613 | errno.EEXIST, errno.ENOTEMPTY => error.DirNotEmpty, | ||
| 614 | errno.EROFS => error.ReadOnlyFileSystem, | ||
| 615 | else => error.Unexpected, | ||
| 616 | }; | ||
| 617 | } | ||
| 618 | } | ||
| 619 | |||
| 620 | /// Whether ::full_path describes a symlink, file, or directory, this function | ||
| 621 | /// removes it. If it cannot be removed because it is a non-empty directory, | ||
| 622 | /// this function recursively removes its entries and then tries again. | ||
| 623 | // TODO non-recursive implementation | ||
| 624 | pub fn deleteTree(allocator: &Allocator, full_path: []const u8) -> %void { | ||
| 625 | start_over: | ||
| 626 | // First, try deleting the item as a file. This way we don't follow sym links. | ||
| 627 | try (deleteFile(allocator, full_path)) { | ||
| 628 | return; | ||
| 629 | } else |err| { | ||
| 630 | if (err == error.FileNotFound) | ||
| 631 | return; | ||
| 632 | if (err != error.IsDir) | ||
| 633 | return err; | ||
| 634 | } | ||
| 635 | { | ||
| 636 | var dir = Dir.open(allocator, full_path) %% |err| { | ||
| 637 | if (err == error.FileNotFound) | ||
| 638 | return; | ||
| 639 | if (err == error.NotDir) | ||
| 640 | goto start_over; | ||
| 641 | return err; | ||
| 642 | }; | ||
| 643 | defer dir.close(); | ||
| 644 | |||
| 645 | var full_entry_buf = List(u8).init(allocator); | ||
| 646 | defer full_entry_buf.deinit(); | ||
| 647 | |||
| 648 | while (true) { | ||
| 649 | const entry = (%return dir.next()) ?? break; | ||
| 650 | |||
| 651 | %return full_entry_buf.resize(full_path.len + entry.name.len + 1); | ||
| 652 | const full_entry_path = full_entry_buf.toSlice(); | ||
| 653 | mem.copy(u8, full_entry_path, full_path); | ||
| 654 | full_entry_path[full_path.len] = '/'; | ||
| 655 | mem.copy(u8, full_entry_path[full_path.len + 1...], entry.name); | ||
| 656 | |||
| 657 | %return deleteTree(allocator, full_entry_path); | ||
| 658 | } | ||
| 659 | } | ||
| 660 | return deleteDir(allocator, full_path); | ||
| 661 | } | ||
| 662 | |||
| 663 | pub const Dir = struct { | ||
| 664 | fd: i32, | ||
| 665 | allocator: &Allocator, | ||
| 666 | buf: []u8, | ||
| 667 | index: usize, | ||
| 668 | end_index: usize, | ||
| 669 | |||
| 670 | const LinuxEntry = extern struct { | ||
| 671 | d_ino: usize, | ||
| 672 | d_off: usize, | ||
| 673 | d_reclen: u16, | ||
| 674 | d_name: u8, // field address is the address of first byte of name | ||
| 675 | }; | ||
| 676 | |||
| 677 | pub const Entry = struct { | ||
| 678 | name: []const u8, | ||
| 679 | kind: Kind, | ||
| 680 | |||
| 681 | pub const Kind = enum { | ||
| 682 | BlockDevice, | ||
| 683 | CharacterDevice, | ||
| 684 | Directory, | ||
| 685 | NamedPipe, | ||
| 686 | SymLink, | ||
| 687 | File, | ||
| 688 | UnixDomainSocket, | ||
| 689 | Unknown, | ||
| 690 | }; | ||
| 691 | }; | ||
| 692 | |||
| 693 | pub fn open(allocator: &Allocator, dir_path: []const u8) -> %Dir { | ||
| 694 | const fd = %return posixOpen(dir_path, posix.O_RDONLY|posix.O_DIRECTORY|posix.O_CLOEXEC, 0, allocator); | ||
| 695 | return Dir { | ||
| 696 | .allocator = allocator, | ||
| 697 | .fd = fd, | ||
| 698 | .index = 0, | ||
| 699 | .end_index = 0, | ||
| 700 | .buf = []u8{}, | ||
| 701 | }; | ||
| 702 | } | ||
| 703 | |||
| 704 | pub fn close(self: &Dir) { | ||
| 705 | self.allocator.free(self.buf); | ||
| 706 | posixClose(self.fd); | ||
| 707 | } | ||
| 708 | |||
| 709 | /// Memory such as file names referenced in this returned entry becomes invalid | ||
| 710 | /// with subsequent calls to next, as well as when this ::Dir is deinitialized. | ||
| 711 | pub fn next(self: &Dir) -> %?Entry { | ||
| 712 | start_over: | ||
| 713 | if (self.index >= self.end_index) { | ||
| 714 | if (self.buf.len == 0) { | ||
| 715 | self.buf = %return self.allocator.alloc(u8, 2); //page_size); | ||
| 716 | } | ||
| 717 | |||
| 718 | while (true) { | ||
| 719 | const result = posix.getdents(self.fd, self.buf.ptr, self.buf.len); | ||
| 720 | const err = linux.getErrno(result); | ||
| 721 | if (err > 0) { | ||
| 722 | switch (err) { | ||
| 723 | errno.EBADF, errno.EFAULT, errno.ENOTDIR => unreachable, | ||
| 724 | errno.EINVAL => { | ||
| 725 | self.buf = %return self.allocator.realloc(u8, self.buf, self.buf.len * 2); | ||
| 726 | continue; | ||
| 727 | }, | ||
| 728 | else => return error.Unexpected, | ||
| 729 | }; | ||
| 730 | } | ||
| 731 | if (result == 0) | ||
| 732 | return null; | ||
| 733 | self.index = 0; | ||
| 734 | self.end_index = result; | ||
| 735 | break; | ||
| 736 | } | ||
| 737 | } | ||
| 738 | const linux_entry = @ptrcast(&LinuxEntry, &self.buf[self.index]); | ||
| 739 | const next_index = self.index + linux_entry.d_reclen; | ||
| 740 | self.index = next_index; | ||
| 741 | |||
| 742 | const name = (&linux_entry.d_name)[0...cstr.len(&linux_entry.d_name)]; | ||
| 743 | |||
| 744 | // skip . and .. entries | ||
| 745 | if (mem.eql(u8, name, ".") or mem.eql(u8, name, "..")) { | ||
| 746 | goto start_over; | ||
| 747 | } | ||
| 748 | |||
| 749 | const type_char = self.buf[next_index - 1]; | ||
| 750 | const entry_kind = switch (type_char) { | ||
| 751 | posix.DT_BLK => Entry.Kind.BlockDevice, | ||
| 752 | posix.DT_CHR => Entry.Kind.CharacterDevice, | ||
| 753 | posix.DT_DIR => Entry.Kind.Directory, | ||
| 754 | posix.DT_FIFO => Entry.Kind.NamedPipe, | ||
| 755 | posix.DT_LNK => Entry.Kind.SymLink, | ||
| 756 | posix.DT_REG => Entry.Kind.File, | ||
| 757 | posix.DT_SOCK => Entry.Kind.UnixDomainSocket, | ||
| 758 | else => Entry.Kind.Unknown, | ||
| 759 | }; | ||
| 760 | return Entry { | ||
| 761 | .name = name, | ||
| 762 | .kind = entry_kind, | ||
| 763 | }; | ||
| 764 | } | ||
| 765 | }; |
std/os/linux.zig+17| ... | @@ -241,6 +241,15 @@ pub const AF_NFC = PF_NFC; | ... | @@ -241,6 +241,15 @@ pub const AF_NFC = PF_NFC; |
| 241 | pub const AF_VSOCK = PF_VSOCK; | 241 | pub const AF_VSOCK = PF_VSOCK; |
| 242 | pub const AF_MAX = PF_MAX; | 242 | pub const AF_MAX = PF_MAX; |
| 243 | 243 | ||
| 244 | pub const DT_UNKNOWN = 0; | ||
| 245 | pub const DT_FIFO = 1; | ||
| 246 | pub const DT_CHR = 2; | ||
| 247 | pub const DT_DIR = 4; | ||
| 248 | pub const DT_BLK = 6; | ||
| 249 | pub const DT_REG = 8; | ||
| 250 | pub const DT_LNK = 10; | ||
| 251 | pub const DT_SOCK = 12; | ||
| 252 | pub const DT_WHT = 14; | ||
| 244 | 253 | ||
| 245 | fn unsigned(s: i32) -> u32 { *@ptrcast(&u32, &s) } | 254 | fn unsigned(s: i32) -> u32 { *@ptrcast(&u32, &s) } |
| 246 | fn signed(s: u32) -> i32 { *@ptrcast(&i32, &s) } | 255 | fn signed(s: u32) -> i32 { *@ptrcast(&i32, &s) } |
| ... | @@ -273,6 +282,10 @@ pub fn getcwd(buf: &u8, size: usize) -> usize { | ... | @@ -273,6 +282,10 @@ pub fn getcwd(buf: &u8, size: usize) -> usize { |
| 273 | arch.syscall2(arch.SYS_getcwd, usize(buf), size) | 282 | arch.syscall2(arch.SYS_getcwd, usize(buf), size) |
| 274 | } | 283 | } |
| 275 | 284 | ||
| 285 | pub fn getdents(fd: i32, dirp: &u8, count: usize) -> usize { | ||
| 286 | arch.syscall3(arch.SYS_getdents, usize(fd), usize(dirp), usize(count)) | ||
| 287 | } | ||
| 288 | |||
| 276 | pub fn mkdir(path: &const u8, mode: usize) -> usize { | 289 | pub fn mkdir(path: &const u8, mode: usize) -> usize { |
| 277 | arch.syscall2(arch.SYS_mkdir, usize(path), mode) | 290 | arch.syscall2(arch.SYS_mkdir, usize(path), mode) |
| 278 | } | 291 | } |
| ... | @@ -291,6 +304,10 @@ pub fn read(fd: i32, buf: &u8, count: usize) -> usize { | ... | @@ -291,6 +304,10 @@ pub fn read(fd: i32, buf: &u8, count: usize) -> usize { |
| 291 | arch.syscall3(arch.SYS_read, usize(fd), usize(buf), count) | 304 | arch.syscall3(arch.SYS_read, usize(fd), usize(buf), count) |
| 292 | } | 305 | } |
| 293 | 306 | ||
| 307 | pub fn rmdir(path: &const u8) -> usize { | ||
| 308 | arch.syscall1(arch.SYS_rmdir, usize(path)) | ||
| 309 | } | ||
| 310 | |||
| 294 | pub fn symlink(existing: &const u8, new: &const u8) -> usize { | 311 | pub fn symlink(existing: &const u8, new: &const u8) -> usize { |
| 295 | arch.syscall2(arch.SYS_symlink, usize(existing), usize(new)) | 312 | arch.syscall2(arch.SYS_symlink, usize(existing), usize(new)) |
| 296 | } | 313 | } |
test/tests.zig+21| ... | @@ -103,6 +103,27 @@ pub fn addParseHTests(b: &build.Builder, test_filter: ?[]const u8) -> &build.Ste | ... | @@ -103,6 +103,27 @@ pub fn addParseHTests(b: &build.Builder, test_filter: ?[]const u8) -> &build.Ste |
| 103 | return cases.step; | 103 | return cases.step; |
| 104 | } | 104 | } |
| 105 | 105 | ||
| 106 | pub fn addPkgTests(b: &build.Builder, test_filter: ?[]const u8, root_src: []const u8, | ||
| 107 | name:[] const u8, desc: []const u8) -> &build.Step | ||
| 108 | { | ||
| 109 | const step = b.step(b.fmt("test-{}", name), desc); | ||
| 110 | for ([]bool{false, true}) |release| { | ||
| 111 | for ([]bool{false, true}) |link_libc| { | ||
| 112 | const these_tests = b.addTest(root_src); | ||
| 113 | these_tests.setNamePrefix(b.fmt("{}-{}-{} ", name, | ||
| 114 | if (release) "release" else "debug", | ||
| 115 | if (link_libc) "c" else "bare")); | ||
| 116 | these_tests.setFilter(test_filter); | ||
| 117 | these_tests.setRelease(release); | ||
| 118 | if (link_libc) { | ||
| 119 | these_tests.linkLibrary("c"); | ||
| 120 | } | ||
| 121 | step.dependOn(&these_tests.step); | ||
| 122 | } | ||
| 123 | } | ||
| 124 | return step; | ||
| 125 | } | ||
| 126 | |||
| 106 | pub const CompareOutputContext = struct { | 127 | pub const CompareOutputContext = struct { |
| 107 | b: &build.Builder, | 128 | b: &build.Builder, |
| 108 | step: &build.Step, | 129 | step: &build.Step, |