From 8d49a3ee37f2803ea78f13ed8418705d10befd6f Mon Sep 17 00:00:00 2001 From: Jarred Sumner Date: Fri, 12 Apr 2024 22:19:29 -0700 Subject: [PATCH] Better way to check if a directory exists (#10235) * Better way to check if a directory exists * Update sys.zig * Fix windows build * Add missing file --- src/bun.js/node/types.zig | 8 +++-- src/install/install.zig | 5 +-- src/sys.zig | 67 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 74 insertions(+), 6 deletions(-) diff --git a/src/bun.js/node/types.zig b/src/bun.js/node/types.zig index 008a9a8c09afeb..894e99a5ff4674 100644 --- a/src/bun.js/node/types.zig +++ b/src/bun.js/node/types.zig @@ -248,7 +248,9 @@ pub fn Maybe(comptime ReturnTypeT: type, comptime ErrorTypeT: type) type { pub inline fn errnoSysFd(rc: anytype, syscall: Syscall.Tag, fd: bun.FileDescriptor) ?@This() { if (comptime Environment.isWindows) { - if (rc != 0) return null; + if (comptime @TypeOf(rc) == std.os.windows.NTSTATUS) {} else { + if (rc != 0) return null; + } } return switch (Syscall.getErrno(rc)) { .SUCCESS => null, @@ -268,7 +270,9 @@ pub fn Maybe(comptime ReturnTypeT: type, comptime ErrorTypeT: type) type { @compileError("Do not pass WString path to errnoSysP, it needs the path encoded as utf8"); } if (comptime Environment.isWindows) { - if (rc != 0) return null; + if (comptime @TypeOf(rc) == std.os.windows.NTSTATUS) {} else { + if (rc != 0) return null; + } } return switch (Syscall.getErrno(rc)) { .SUCCESS => null, diff --git a/src/install/install.zig b/src/install/install.zig index c1443a31703410..aec380fac5d1eb 100644 --- a/src/install/install.zig +++ b/src/install/install.zig @@ -3338,10 +3338,7 @@ pub const PackageManager = struct { } pub fn isFolderInCache(this: *PackageManager, folder_path: stringZ) bool { - // TODO: is this slow? - var dir = this.getCacheDirectory().openDirZ(folder_path, .{}) catch return false; - dir.close(); - return true; + return bun.sys.directoryExistsAt(this.getCacheDirectory(), folder_path).unwrap() catch false; } pub fn pathForCachedNPMPath( diff --git a/src/sys.zig b/src/sys.zig index 33c34857552a73..270369fb97cc24 100644 --- a/src/sys.zig +++ b/src/sys.zig @@ -2118,6 +2118,73 @@ pub fn exists(path: []const u8) bool { @compileError("TODO: existsOSPath"); } +pub fn directoryExistsAt(dir_: anytype, subpath: anytype) JSC.Maybe(bool) { + const has_sentinel = std.meta.sentinel(@TypeOf(subpath)) != null; + const dir_fd = bun.toFD(dir_); + if (comptime Environment.isWindows) { + var wbuf: bun.WPathBuffer = undefined; + const path = bun.strings.toNTPath(&wbuf, subpath); + const path_len_bytes: u16 = @truncate(path.len * 2); + var nt_name = w.UNICODE_STRING{ + .Length = path_len_bytes, + .MaximumLength = path_len_bytes, + .Buffer = @constCast(path.ptr), + }; + var attr = w.OBJECT_ATTRIBUTES{ + .Length = @sizeOf(w.OBJECT_ATTRIBUTES), + .RootDirectory = if (std.fs.path.isAbsoluteWindowsWTF16(path)) + null + else if (dir_fd == bun.invalid_fd) + std.fs.cwd().fd + else + dir_fd.cast(), + .Attributes = 0, // Note we do not use OBJ_CASE_INSENSITIVE here. + .ObjectName = &nt_name, + .SecurityDescriptor = null, + .SecurityQualityOfService = null, + }; + var basic_info: w.FILE_BASIC_INFORMATION = undefined; + const rc = kernel32.NtQueryAttributesFile(&attr, &basic_info); + if (JSC.Maybe(bool).errnoSysP(rc, .access, subpath)) |err| { + syslog("NtQueryAttributesFile({}, {}, O_DIRECTORY | O_RDONLY, 0) = {}", .{ dir_fd, bun.fmt.fmtOSPath(path, .{}), err }); + return err; + } + + const is_dir = basic_info.FileAttributes != kernel32.INVALID_FILE_ATTRIBUTES and + basic_info.FileAttributes & kernel32.FILE_ATTRIBUTE_DIRECTORY != 0; + syslog("NtQueryAttributesFile({}, {}, O_DIRECTORY | O_RDONLY, 0) = {d}", .{ dir_fd, bun.fmt.fmtOSPath(path, .{}), @intFromBool(is_dir) }); + + return .{ + .result = is_dir, + }; + } + + if (comptime !has_sentinel) { + const path = std.os.toPosixPath(subpath) catch return JSC.Maybe(bool){ .err = Error.oom }; + return directoryExistsAt(dir_fd, path); + } + + if (comptime Environment.isLinux) { + // avoid loading the libc symbol for this to reduce chances of GLIBC minimum version requirements + const rc = linux.faccessat(dir_fd.cast(), subpath, linux.O.DIRECTORY | linux.O.RDONLY, 0); + syslog("faccessat({}, {}, O_DIRECTORY | O_RDONLY, 0) = {d}", .{ dir_fd, bun.fmt.fmtOSPath(subpath, .{}), rc }); + if (rc == 0) { + return JSC.Maybe(bool){ .result = true }; + } + + return JSC.Maybe(bool){ .result = false }; + } + + // on toher platforms use faccessat from libc + const rc = std.c.faccessat(dir_fd.cast(), subpath, std.os.O.DIRECTORY | std.os.O.RDONLY, 0); + syslog("faccessat({}, {}, O_DIRECTORY | O_RDONLY, 0) = {d}", .{ dir_fd, bun.fmt.fmtOSPath(subpath, .{}), rc }); + if (rc == 0) { + return JSC.Maybe(bool){ .result = true }; + } + + return JSC.Maybe(bool){ .result = false }; +} + pub fn existsAt(fd: bun.FileDescriptor, subpath: []const u8) bool { if (comptime Environment.isPosix) { return system.faccessat(bun.toFD(fd), &(std.os.toPosixPath(subpath) catch return false), 0, 0) == 0;