Commit f5b43ada46

Andrew Kelley <superjoe30@gmail.com>
2018-03-05 06:57:02
std/os: getting dir entries works on OS X
1 parent 0b7b319
std/c/darwin.zig
@@ -1,6 +1,7 @@
 extern "c" fn __error() &c_int;
 pub extern "c" fn _NSGetExecutablePath(buf: &u8, bufsize: &u32) c_int;
 
+pub extern "c" fn __getdirentries64(fd: c_int, buf_ptr: &u8, buf_len: usize, basep: &i64) usize;
 
 pub use @import("../os/darwin_errno.zig");
 
@@ -45,3 +46,12 @@ pub const Sigaction = extern struct {
     sa_mask: sigset_t,
     sa_flags: c_int,
 };
+
+pub const dirent = extern struct {
+    d_ino: usize,
+    d_seekoff: usize,
+    d_reclen: u16,
+    d_namlen: u16,
+    d_type: u8,
+    d_name: u8, // field address is address of first byte of name
+};
std/c/index.zig
@@ -44,6 +44,7 @@ pub extern "c" fn sigaction(sig: c_int, noalias act: &const Sigaction, noalias o
 pub extern "c" fn nanosleep(rqtp: &const timespec, rmtp: ?&timespec) c_int;
 pub extern "c" fn setreuid(ruid: c_uint, euid: c_uint) c_int;
 pub extern "c" fn setregid(rgid: c_uint, egid: c_uint) c_int;
+pub extern "c" fn rmdir(path: &const u8) c_int;
 
 pub extern "c" fn aligned_alloc(alignment: usize, size: usize) ?&c_void;
 pub extern "c" fn malloc(usize) ?&c_void;
std/os/linux/x86_64.zig
@@ -488,3 +488,11 @@ pub const timespec = extern struct {
     tv_sec: isize,
     tv_nsec: isize,
 };
+
+pub const dirent = extern struct {
+    d_ino: usize,
+    d_off: usize,
+    d_reclen: u16,
+    d_name: u8, // field address is the address of first byte of name
+};
+
std/os/darwin.zig
@@ -56,10 +56,32 @@ pub const O_SYMLINK  = 0x200000; /// allow open of symlinks
 pub const O_EVTONLY  = 0x8000; /// descriptor requested for event notifications only
 pub const O_CLOEXEC  = 0x1000000; /// mark as close-on-exec
 
+pub const O_ACCMODE = 3;
+pub const O_ALERT = 536870912;
+pub const O_ASYNC = 64;
+pub const O_DIRECTORY = 1048576;
+pub const O_DP_GETRAWENCRYPTED = 1;
+pub const O_DP_GETRAWUNENCRYPTED = 2;
+pub const O_DSYNC = 4194304;
+pub const O_FSYNC = O_SYNC;
+pub const O_NOCTTY = 131072;
+pub const O_POPUP = 2147483648;
+pub const O_SYNC = 128;
+
 pub const SEEK_SET = 0x0;
 pub const SEEK_CUR = 0x1;
 pub const SEEK_END = 0x2;
 
+pub const DT_UNKNOWN = 0;
+pub const DT_FIFO = 1;
+pub const DT_CHR = 2;
+pub const DT_DIR = 4;
+pub const DT_BLK = 6;
+pub const DT_REG = 8;
+pub const DT_LNK = 10;
+pub const DT_SOCK = 12;
+pub const DT_WHT = 14;
+
 pub const SIG_BLOCK   = 1; /// block specified signal set
 pub const SIG_UNBLOCK = 2; /// unblock specified signal set
 pub const SIG_SETMASK = 3; /// set specified signal set
@@ -192,6 +214,11 @@ pub fn pipe(fds: &[2]i32) usize {
     return errnoWrap(c.pipe(@ptrCast(&c_int, fds)));
 }
 
+
+pub fn getdirentries64(fd: i32, buf_ptr: &u8, buf_len: usize, basep: &i64) usize {
+    return errnoWrap(@bitCast(isize, c.__getdirentries64(fd, buf_ptr, buf_len, basep)));
+}
+
 pub fn mkdir(path: &const u8, mode: u32) usize {
     return errnoWrap(c.mkdir(path, mode));
 }
@@ -204,6 +231,10 @@ pub fn rename(old: &const u8, new: &const u8) usize {
     return errnoWrap(c.rename(old, new));
 }
 
+pub fn rmdir(path: &const u8) usize {
+    return errnoWrap(c.rmdir(path));
+}
+
 pub fn chdir(path: &const u8) usize {
     return errnoWrap(c.chdir(path));
 }
@@ -268,6 +299,7 @@ pub const empty_sigset = sigset_t(0);
 
 pub const timespec = c.timespec;
 pub const Stat = c.Stat;
+pub const dirent = c.dirent;
 
 /// Renamed from `sigaction` to `Sigaction` to avoid conflict with the syscall.
 pub const Sigaction = struct {
std/os/index.zig
@@ -1055,10 +1055,11 @@ pub fn deleteTree(allocator: &Allocator, full_path: []const u8) DeleteTreeError!
             return;
         } else |err| switch (err) {
             error.FileNotFound => return,
+
+            error.AccessDenied,
             error.IsDir => {},
 
             error.OutOfMemory,
-            error.AccessDenied,
             error.SymLinkLoop,
             error.NameTooLong,
             error.SystemResources,
@@ -1109,18 +1110,16 @@ pub fn deleteTree(allocator: &Allocator, full_path: []const u8) DeleteTreeError!
 }
 
 pub const Dir = struct {
-    // See man getdents
     fd: i32,
+    darwin_seek: darwin_seek_t,
     allocator: &Allocator,
     buf: []u8,
     index: usize,
     end_index: usize,
 
-    const LinuxEntry = extern struct {
-        d_ino: usize,
-        d_off: usize,
-        d_reclen: u16,
-        d_name: u8, // field address is the address of first byte of name
+    const darwin_seek_t = switch (builtin.os) {
+        Os.macosx, Os.ios => i64,
+        else => void,
     };
 
     pub const Entry = struct {
@@ -1135,15 +1134,26 @@ pub const Dir = struct {
             SymLink,
             File,
             UnixDomainSocket,
+            Wht, // TODO wtf is this
             Unknown,
         };
     };
 
     pub fn open(allocator: &Allocator, dir_path: []const u8) !Dir {
-        const fd = try posixOpen(allocator, dir_path, posix.O_RDONLY|posix.O_DIRECTORY|posix.O_CLOEXEC, 0);
+        const fd = switch (builtin.os) {
+            Os.windows => @compileError("TODO support Dir.open for windows"),
+            Os.linux => try posixOpen(allocator, dir_path, posix.O_RDONLY|posix.O_DIRECTORY|posix.O_CLOEXEC, 0),
+            Os.macosx, Os.ios => try posixOpen(allocator, dir_path, posix.O_RDONLY|posix.O_NONBLOCK|posix.O_DIRECTORY|posix.O_CLOEXEC, 0),
+            else => @compileError("Dir.open is not supported for this platform"),
+        };
+        const darwin_seek_init = switch (builtin.os) {
+            Os.macosx, Os.ios => 0,
+            else => {},
+        };
         return Dir {
             .allocator = allocator,
             .fd = fd,
+            .darwin_seek = darwin_seek_init,
             .index = 0,
             .end_index = 0,
             .buf = []u8{},
@@ -1158,6 +1168,76 @@ pub const Dir = struct {
     /// Memory such as file names referenced in this returned entry becomes invalid
     /// with subsequent calls to next, as well as when this ::Dir is deinitialized.
     pub fn next(self: &Dir) !?Entry {
+        switch (builtin.os) {
+            Os.linux => return self.nextLinux(),
+            Os.macosx, Os.ios => return self.nextDarwin(),
+            Os.windows => return self.nextWindows(),
+            else => @compileError("Dir.next not supported on " ++ @tagName(builtin.os)),
+        }
+    }
+
+    fn nextDarwin(self: &Dir) !?Entry {
+        start_over: while (true) {
+            if (self.index >= self.end_index) {
+                if (self.buf.len == 0) {
+                    self.buf = try self.allocator.alloc(u8, page_size);
+                }
+
+                while (true) {
+                    const result = posix.getdirentries64(self.fd, self.buf.ptr, self.buf.len,
+                        &self.darwin_seek);
+                    const err = posix.getErrno(result);
+                    if (err > 0) {
+                        switch (err) {
+                            posix.EBADF, posix.EFAULT, posix.ENOTDIR => unreachable,
+                            posix.EINVAL => {
+                                self.buf = try self.allocator.realloc(u8, self.buf, self.buf.len * 2);
+                                continue;
+                            },
+                            else => return unexpectedErrorPosix(err),
+                        }
+                    }
+                    if (result == 0)
+                        return null;
+                    self.index = 0;
+                    self.end_index = result;
+                    break;
+                }
+            }
+            const darwin_entry = @ptrCast(& align(1) posix.dirent, &self.buf[self.index]);
+            const next_index = self.index + darwin_entry.d_reclen;
+            self.index = next_index;
+
+            const name = (&darwin_entry.d_name)[0..darwin_entry.d_namlen];
+
+            // skip . and .. entries
+            if (mem.eql(u8, name, ".") or mem.eql(u8, name, "..")) {
+                continue :start_over;
+            }
+
+            const entry_kind = switch (darwin_entry.d_type) {
+                posix.DT_BLK => Entry.Kind.BlockDevice,
+                posix.DT_CHR => Entry.Kind.CharacterDevice,
+                posix.DT_DIR => Entry.Kind.Directory,
+                posix.DT_FIFO => Entry.Kind.NamedPipe,
+                posix.DT_LNK => Entry.Kind.SymLink,
+                posix.DT_REG => Entry.Kind.File,
+                posix.DT_SOCK => Entry.Kind.UnixDomainSocket,
+                posix.DT_WHT => Entry.Kind.Wht,
+                else => Entry.Kind.Unknown,
+            };
+            return Entry {
+                .name = name,
+                .kind = entry_kind,
+            };
+        }
+    }
+
+    fn nextWindows(self: &Dir) !?Entry {
+        @compileError("TODO support Dir.next for windows");
+    }
+
+    fn nextLinux(self: &Dir) !?Entry {
         start_over: while (true) {
             if (self.index >= self.end_index) {
                 if (self.buf.len == 0) {
@@ -1166,7 +1246,7 @@ pub const Dir = struct {
 
                 while (true) {
                     const result = posix.getdents(self.fd, self.buf.ptr, self.buf.len);
-                    const err = linux.getErrno(result);
+                    const err = posix.getErrno(result);
                     if (err > 0) {
                         switch (err) {
                             posix.EBADF, posix.EFAULT, posix.ENOTDIR => unreachable,
@@ -1184,7 +1264,7 @@ pub const Dir = struct {
                     break;
                 }
             }
-            const linux_entry = @ptrCast(& align(1) LinuxEntry, &self.buf[self.index]);
+            const linux_entry = @ptrCast(& align(1) posix.dirent, &self.buf[self.index]);
             const next_index = self.index + linux_entry.d_reclen;
             self.index = next_index;
 
@@ -1683,7 +1763,7 @@ test "std.os" {
 
 
 // TODO make this a build variable that you can set
-const unexpected_error_tracing = false;
+const unexpected_error_tracing = true;
 
 /// Call this when you made a syscall or something that sets errno
 /// and you get an unexpected error.
std/os/test.zig
@@ -0,0 +1,12 @@
+const std = @import("../index.zig");
+const os = std.os;
+const io = std.io;
+
+const a = std.debug.global_allocator;
+
+test "makePath, put some files in it, deleteTree" {
+    try os.makePath(a, "os_test_tmp/b/c");
+    try io.writeFile(a, "os_test_tmp/b/c/file.txt", "nonsense");
+    try io.writeFile(a, "os_test_tmp/b/file2.txt", "blah");
+    try os.deleteTree(a, "os_test_tmp");
+}