diff options
Diffstat (limited to 'std/os/path.zig')
| -rw-r--r-- | std/os/path.zig | 235 |
1 files changed, 137 insertions, 98 deletions
diff --git a/std/os/path.zig b/std/os/path.zig index 23c217b295..b3cfec1a3a 100644 --- a/std/os/path.zig +++ b/std/os/path.zig @@ -11,11 +11,14 @@ const math = std.math; const posix = os.posix; const windows = os.windows; const cstr = std.cstr; +const windows_util = @import("windows/util.zig"); pub const sep_windows = '\\'; pub const sep_posix = '/'; pub const sep = if (is_windows) sep_windows else sep_posix; +pub const sep_str = [1]u8{sep}; + pub const delimiter_windows = ';'; pub const delimiter_posix = ':'; pub const delimiter = if (is_windows) delimiter_windows else delimiter_posix; @@ -337,7 +340,7 @@ pub fn resolveSlice(allocator: *Allocator, paths: []const []const u8) ![]u8 { pub fn resolveWindows(allocator: *Allocator, paths: []const []const u8) ![]u8 { if (paths.len == 0) { assert(is_windows); // resolveWindows called on non windows can't use getCwd - return os.getCwd(allocator); + return os.getCwdAlloc(allocator); } // determine which disk designator we will result with, if any @@ -432,7 +435,7 @@ pub fn resolveWindows(allocator: *Allocator, paths: []const []const u8) ![]u8 { }, WindowsPath.Kind.None => { assert(is_windows); // resolveWindows called on non windows can't use getCwd - const cwd = try os.getCwd(allocator); + const cwd = try os.getCwdAlloc(allocator); defer allocator.free(cwd); const parsed_cwd = windowsParsePath(cwd); result = try allocator.alloc(u8, max_size + parsed_cwd.disk_designator.len + 1); @@ -448,7 +451,7 @@ pub fn resolveWindows(allocator: *Allocator, paths: []const []const u8) ![]u8 { } else { assert(is_windows); // resolveWindows called on non windows can't use getCwd // TODO call get cwd for the result_disk_designator instead of the global one - const cwd = try os.getCwd(allocator); + const cwd = try os.getCwdAlloc(allocator); defer allocator.free(cwd); result = try allocator.alloc(u8, max_size + cwd.len + 1); @@ -516,7 +519,7 @@ pub fn resolveWindows(allocator: *Allocator, paths: []const []const u8) ![]u8 { pub fn resolvePosix(allocator: *Allocator, paths: []const []const u8) ![]u8 { if (paths.len == 0) { assert(!is_windows); // resolvePosix called on windows can't use getCwd - return os.getCwd(allocator); + return os.getCwdAlloc(allocator); } var first_index: usize = 0; @@ -538,7 +541,7 @@ pub fn resolvePosix(allocator: *Allocator, paths: []const []const u8) ![]u8 { result = try allocator.alloc(u8, max_size); } else { assert(!is_windows); // resolvePosix called on windows can't use getCwd - const cwd = try os.getCwd(allocator); + const cwd = try os.getCwdAlloc(allocator); defer allocator.free(cwd); result = try allocator.alloc(u8, max_size + cwd.len + 1); mem.copy(u8, result, cwd); @@ -573,11 +576,11 @@ pub fn resolvePosix(allocator: *Allocator, paths: []const []const u8) ![]u8 { result_index += 1; } - return result[0..result_index]; + return allocator.shrink(u8, result, result_index); } test "os.path.resolve" { - const cwd = try os.getCwd(debug.global_allocator); + const cwd = try os.getCwdAlloc(debug.global_allocator); if (is_windows) { if (windowsParsePath(cwd).kind == WindowsPath.Kind.Drive) { cwd[0] = asciiUpper(cwd[0]); @@ -591,7 +594,7 @@ test "os.path.resolve" { test "os.path.resolveWindows" { if (is_windows) { - const cwd = try os.getCwd(debug.global_allocator); + const cwd = try os.getCwdAlloc(debug.global_allocator); const parsed_cwd = windowsParsePath(cwd); { const result = testResolveWindows([][]const u8{ "/usr/local", "lib\\zig\\std\\array_list.zig" }); @@ -1073,112 +1076,148 @@ fn testRelativeWindows(from: []const u8, to: []const u8, expected_output: []cons assert(mem.eql(u8, result, expected_output)); } -/// Return the canonicalized absolute pathname. -/// Expands all symbolic links and resolves references to `.`, `..`, and -/// extra `/` characters in ::pathname. -/// Caller must deallocate result. -pub fn real(allocator: *Allocator, pathname: []const u8) ![]u8 { - switch (builtin.os) { - Os.windows => { - const pathname_buf = try allocator.alloc(u8, pathname.len + 1); - defer allocator.free(pathname_buf); - - mem.copy(u8, pathname_buf, pathname); - pathname_buf[pathname.len] = 0; - - const h_file = windows.CreateFileA(pathname_buf.ptr, windows.GENERIC_READ, windows.FILE_SHARE_READ, null, windows.OPEN_EXISTING, windows.FILE_ATTRIBUTE_NORMAL, null); - if (h_file == windows.INVALID_HANDLE_VALUE) { - const err = windows.GetLastError(); - return switch (err) { - windows.ERROR.FILE_NOT_FOUND => error.FileNotFound, - windows.ERROR.ACCESS_DENIED => error.AccessDenied, - windows.ERROR.FILENAME_EXCED_RANGE => error.NameTooLong, - else => os.unexpectedErrorWindows(err), - }; - } - defer os.close(h_file); - var buf = try allocator.alloc(u8, 256); - errdefer allocator.free(buf); - while (true) { - const buf_len = math.cast(windows.DWORD, buf.len) catch return error.NameTooLong; - const result = windows.GetFinalPathNameByHandleA(h_file, buf.ptr, buf_len, windows.VOLUME_NAME_DOS); - - if (result == 0) { - const err = windows.GetLastError(); - return switch (err) { - windows.ERROR.PATH_NOT_FOUND => error.FileNotFound, - windows.ERROR.NOT_ENOUGH_MEMORY => error.OutOfMemory, - windows.ERROR.INVALID_PARAMETER => unreachable, - else => os.unexpectedErrorWindows(err), - }; - } +pub const RealError = error{ + FileNotFound, + AccessDenied, + NameTooLong, + NotSupported, + NotDir, + SymLinkLoop, + InputOutput, + FileTooBig, + IsDir, + ProcessFdQuotaExceeded, + SystemFdQuotaExceeded, + NoDevice, + SystemResources, + NoSpaceLeft, + FileSystem, + BadPathName, + + /// On Windows, file paths must be valid Unicode. + InvalidUtf8, + + /// TODO remove this possibility + PathAlreadyExists, + + /// TODO remove this possibility + Unexpected, +}; - if (result > buf.len) { - buf = try allocator.realloc(u8, buf, result); - continue; - } +/// Call from Windows-specific code if you already have a UTF-16LE encoded, null terminated string. +/// Otherwise use `real` or `realC`. +pub fn realW(out_buffer: *[os.MAX_PATH_BYTES]u8, pathname: [*]const u16) RealError![]u8 { + const h_file = windows.CreateFileW( + pathname, + windows.GENERIC_READ, + windows.FILE_SHARE_READ, + null, + windows.OPEN_EXISTING, + windows.FILE_ATTRIBUTE_NORMAL, + null, + ); + if (h_file == windows.INVALID_HANDLE_VALUE) { + const err = windows.GetLastError(); + switch (err) { + windows.ERROR.FILE_NOT_FOUND => return error.FileNotFound, + windows.ERROR.ACCESS_DENIED => return error.AccessDenied, + windows.ERROR.FILENAME_EXCED_RANGE => return error.NameTooLong, + else => return os.unexpectedErrorWindows(err), + } + } + defer os.close(h_file); + var utf16le_buf: [windows_util.PATH_MAX_WIDE]u16 = undefined; + const casted_len = @intCast(windows.DWORD, utf16le_buf.len); // TODO shouldn't need this cast + const result = windows.GetFinalPathNameByHandleW(h_file, &utf16le_buf, casted_len, windows.VOLUME_NAME_DOS); + assert(result <= utf16le_buf.len); + if (result == 0) { + const err = windows.GetLastError(); + switch (err) { + windows.ERROR.FILE_NOT_FOUND => return error.FileNotFound, + windows.ERROR.PATH_NOT_FOUND => return error.FileNotFound, + windows.ERROR.NOT_ENOUGH_MEMORY => return error.SystemResources, + windows.ERROR.FILENAME_EXCED_RANGE => return error.NameTooLong, + windows.ERROR.INVALID_PARAMETER => unreachable, + else => return os.unexpectedErrorWindows(err), + } + } + const utf16le_slice = utf16le_buf[0..result]; - // windows returns \\?\ prepended to the path - // we strip it because nobody wants \\?\ prepended to their path - const final_len = x: { - if (result > 4 and mem.startsWith(u8, buf, "\\\\?\\")) { - var i: usize = 4; - while (i < result) : (i += 1) { - buf[i - 4] = buf[i]; - } - break :x result - 4; - } else { - break :x result; - } - }; - - return allocator.shrink(u8, buf, final_len); - } + // windows returns \\?\ prepended to the path + // we strip it because nobody wants \\?\ prepended to their path + const prefix = []u16{ '\\', '\\', '?', '\\' }; + const start_index = if (mem.startsWith(u16, utf16le_slice, prefix)) prefix.len else 0; + + // Trust that Windows gives us valid UTF-16LE. + const end_index = std.unicode.utf16leToUtf8(out_buffer, utf16le_slice[start_index..]) catch unreachable; + return out_buffer[0..end_index]; +} + +/// See `real` +/// Use this when you have a null terminated pointer path. +pub fn realC(out_buffer: *[os.MAX_PATH_BYTES]u8, pathname: [*]const u8) RealError![]u8 { + switch (builtin.os) { + Os.windows => { + const pathname_w = try windows_util.cStrToPrefixedFileW(pathname); + return realW(out_buffer, pathname_w); }, Os.macosx, Os.ios => { - // TODO instead of calling the libc function here, port the implementation - // to Zig, and then remove the NameTooLong error possibility. - const pathname_buf = try allocator.alloc(u8, pathname.len + 1); - defer allocator.free(pathname_buf); - - const result_buf = try allocator.alloc(u8, posix.PATH_MAX); - errdefer allocator.free(result_buf); - - mem.copy(u8, pathname_buf, pathname); - pathname_buf[pathname.len] = 0; - - const err = posix.getErrno(posix.realpath(pathname_buf.ptr, result_buf.ptr)); - if (err > 0) { - return switch (err) { - posix.EINVAL => unreachable, - posix.EBADF => unreachable, - posix.EFAULT => unreachable, - posix.EACCES => error.AccessDenied, - posix.ENOENT => error.FileNotFound, - posix.ENOTSUP => error.NotSupported, - posix.ENOTDIR => error.NotDir, - posix.ENAMETOOLONG => error.NameTooLong, - posix.ELOOP => error.SymLinkLoop, - posix.EIO => error.InputOutput, - else => os.unexpectedErrorPosix(err), - }; + // TODO instead of calling the libc function here, port the implementation to Zig + const err = posix.getErrno(posix.realpath(pathname, out_buffer)); + switch (err) { + 0 => return mem.toSlice(u8, out_buffer), + posix.EINVAL => unreachable, + posix.EBADF => unreachable, + posix.EFAULT => unreachable, + posix.EACCES => return error.AccessDenied, + posix.ENOENT => return error.FileNotFound, + posix.ENOTSUP => return error.NotSupported, + posix.ENOTDIR => return error.NotDir, + posix.ENAMETOOLONG => return error.NameTooLong, + posix.ELOOP => return error.SymLinkLoop, + posix.EIO => return error.InputOutput, + else => return os.unexpectedErrorPosix(err), } - return allocator.shrink(u8, result_buf, cstr.len(result_buf.ptr)); }, Os.linux => { - const fd = try os.posixOpen(allocator, pathname, posix.O_PATH | posix.O_NONBLOCK | posix.O_CLOEXEC, 0); + const fd = try os.posixOpenC(pathname, posix.O_PATH | posix.O_NONBLOCK | posix.O_CLOEXEC, 0); defer os.close(fd); var buf: ["/proc/self/fd/-2147483648".len]u8 = undefined; - const proc_path = fmt.bufPrint(buf[0..], "/proc/self/fd/{}", fd) catch unreachable; + const proc_path = fmt.bufPrint(buf[0..], "/proc/self/fd/{}\x00", fd) catch unreachable; - return os.readLink(allocator, proc_path); + return os.readLinkC(out_buffer, proc_path.ptr); }, else => @compileError("TODO implement os.path.real for " ++ @tagName(builtin.os)), } } +/// Return the canonicalized absolute pathname. +/// Expands all symbolic links and resolves references to `.`, `..`, and +/// extra `/` characters in ::pathname. +/// The return value is a slice of out_buffer, and not necessarily from the beginning. +pub fn real(out_buffer: *[os.MAX_PATH_BYTES]u8, pathname: []const u8) RealError![]u8 { + switch (builtin.os) { + Os.windows => { + const pathname_w = try windows_util.sliceToPrefixedFileW(pathname); + return realW(out_buffer, &pathname_w); + }, + Os.macosx, Os.ios, Os.linux => { + const pathname_c = try os.toPosixPath(pathname); + return realC(out_buffer, &pathname_c); + }, + else => @compileError("Unsupported OS"), + } +} + +/// `real`, except caller must free the returned memory. +pub fn realAlloc(allocator: *Allocator, pathname: []const u8) ![]u8 { + var buf: [os.MAX_PATH_BYTES]u8 = undefined; + return mem.dupe(allocator, u8, try real(&buf, pathname)); +} + test "os.path.real" { // at least call it so it gets compiled - _ = real(debug.global_allocator, "some_path"); + var buf: [os.MAX_PATH_BYTES]u8 = undefined; + std.debug.assertError(real(&buf, "definitely_bogus_does_not_exist1234"), error.FileNotFound); } |
