| author | |
| committer | |
| log | 6df35c5877c1a1060dcfd28d5d4092638e6e6754 |
| tree | e3320200a4ee2754ec60255ab22388af79a8cba0 |
| parent | cf15b4c2690ae7884e7bdb9259e6497c18213d35 |
| parent | 4b0ab0c1864677d0c50b2a8e12a708a6640d1734 |
| signature | Signed by PGP key 4AEE18F83AFDEB23 |
Refactor out file and dir creation routines in Windows8 files changed, 437 insertions(+), 417 deletions(-)
lib/std/child_process.zig+9-13| ... | ... | @@ -364,6 +364,7 @@ pub const ChildProcess = struct { |
| 364 | 364 | error.FileTooBig => unreachable, |
| 365 | 365 | error.DeviceBusy => unreachable, |
| 366 | 366 | error.FileLocksNotSupported => unreachable, |
| 367 | error.BadPathName => unreachable, // Windows-only | |
| 367 | 368 | else => |e| return e, |
| 368 | 369 | } |
| 369 | 370 | else |
| ... | ... | @@ -480,25 +481,20 @@ pub const ChildProcess = struct { |
| 480 | 481 | |
| 481 | 482 | const any_ignore = (self.stdin_behavior == StdIo.Ignore or self.stdout_behavior == StdIo.Ignore or self.stderr_behavior == StdIo.Ignore); |
| 482 | 483 | |
| 483 | // TODO use CreateFileW here since we are using a string literal for the path | |
| 484 | 484 | const nul_handle = if (any_ignore) |
| 485 | windows.CreateFile( | |
| 486 | "NUL", | |
| 487 | windows.GENERIC_READ, | |
| 488 | windows.FILE_SHARE_READ, | |
| 489 | null, | |
| 490 | windows.OPEN_EXISTING, | |
| 491 | windows.FILE_ATTRIBUTE_NORMAL, | |
| 492 | null, | |
| 493 | ) catch |err| switch (err) { | |
| 494 | error.SharingViolation => unreachable, // not possible for "NUL" | |
| 485 | windows.OpenFile(&[_]u16{ 'N', 'U', 'L' }, .{ | |
| 486 | .dir = std.fs.cwd().fd, | |
| 487 | .access_mask = windows.GENERIC_READ | windows.SYNCHRONIZE, | |
| 488 | .share_access = windows.FILE_SHARE_READ, | |
| 489 | .creation = windows.OPEN_EXISTING, | |
| 490 | .io_mode = .blocking, | |
| 491 | }) catch |err| switch (err) { | |
| 495 | 492 | error.PathAlreadyExists => unreachable, // not possible for "NUL" |
| 496 | 493 | error.PipeBusy => unreachable, // not possible for "NUL" |
| 497 | error.InvalidUtf8 => unreachable, // not possible for "NUL" | |
| 498 | error.BadPathName => unreachable, // not possible for "NUL" | |
| 499 | 494 | error.FileNotFound => unreachable, // not possible for "NUL" |
| 500 | 495 | error.AccessDenied => unreachable, // not possible for "NUL" |
| 501 | 496 | error.NameTooLong => unreachable, // not possible for "NUL" |
| 497 | error.WouldBlock => unreachable, // not possible for "NUL" | |
| 502 | 498 | else => |e| return e, |
| 503 | 499 | } |
| 504 | 500 | else |
lib/std/fs.zig+36-22| ... | ... | @@ -225,8 +225,7 @@ pub fn makeDirAbsoluteZ(absolute_path_z: [*:0]const u8) !void { |
| 225 | 225 | /// Same as `makeDirAbsolute` except the parameter is a null-terminated WTF-16 encoded string. |
| 226 | 226 | pub fn makeDirAbsoluteW(absolute_path_w: [*:0]const u16) !void { |
| 227 | 227 | assert(path.isAbsoluteWindowsW(absolute_path_w)); |
| 228 | const handle = try os.windows.CreateDirectoryW(null, absolute_path_w, null); | |
| 229 | os.windows.CloseHandle(handle); | |
| 228 | return os.mkdirW(absolute_path_w, default_new_dir_mode); | |
| 230 | 229 | } |
| 231 | 230 | |
| 232 | 231 | pub const deleteDir = @compileError("deprecated; use dir.deleteDir or deleteDirAbsolute"); |
| ... | ... | @@ -881,8 +880,7 @@ pub const Dir = struct { |
| 881 | 880 | } |
| 882 | 881 | |
| 883 | 882 | pub fn makeDirW(self: Dir, sub_path: [*:0]const u16) !void { |
| 884 | const handle = try os.windows.CreateDirectoryW(self.fd, sub_path, null); | |
| 885 | os.windows.CloseHandle(handle); | |
| 883 | try os.mkdiratW(self.fd, sub_path, default_new_dir_mode); | |
| 886 | 884 | } |
| 887 | 885 | |
| 888 | 886 | /// Calls makeDir recursively to make an entire path. Returns success if the path |
| ... | ... | @@ -1119,7 +1117,7 @@ pub const Dir = struct { |
| 1119 | 1117 | pub fn deleteFile(self: Dir, sub_path: []const u8) DeleteFileError!void { |
| 1120 | 1118 | if (builtin.os.tag == .windows) { |
| 1121 | 1119 | const sub_path_w = try os.windows.sliceToPrefixedFileW(sub_path); |
| 1122 | return self.deleteFileW(sub_path_w.span().ptr); | |
| 1120 | return self.deleteFileW(sub_path_w.span()); | |
| 1123 | 1121 | } else if (builtin.os.tag == .wasi) { |
| 1124 | 1122 | os.unlinkatWasi(self.fd, sub_path, 0) catch |err| switch (err) { |
| 1125 | 1123 | error.DirNotEmpty => unreachable, // not passing AT_REMOVEDIR |
| ... | ... | @@ -1153,7 +1151,7 @@ pub const Dir = struct { |
| 1153 | 1151 | } |
| 1154 | 1152 | |
| 1155 | 1153 | /// Same as `deleteFile` except the parameter is WTF-16 encoded. |
| 1156 | pub fn deleteFileW(self: Dir, sub_path_w: [*:0]const u16) DeleteFileError!void { | |
| 1154 | pub fn deleteFileW(self: Dir, sub_path_w: []const u16) DeleteFileError!void { | |
| 1157 | 1155 | os.unlinkatW(self.fd, sub_path_w, 0) catch |err| switch (err) { |
| 1158 | 1156 | error.DirNotEmpty => unreachable, // not passing AT_REMOVEDIR |
| 1159 | 1157 | else => |e| return e, |
| ... | ... | @@ -1182,7 +1180,7 @@ pub const Dir = struct { |
| 1182 | 1180 | pub fn deleteDir(self: Dir, sub_path: []const u8) DeleteDirError!void { |
| 1183 | 1181 | if (builtin.os.tag == .windows) { |
| 1184 | 1182 | const sub_path_w = try os.windows.sliceToPrefixedFileW(sub_path); |
| 1185 | return self.deleteDirW(sub_path_w.span().ptr); | |
| 1183 | return self.deleteDirW(sub_path_w.span()); | |
| 1186 | 1184 | } else if (builtin.os.tag == .wasi) { |
| 1187 | 1185 | os.unlinkat(self.fd, sub_path, os.AT_REMOVEDIR) catch |err| switch (err) { |
| 1188 | 1186 | error.IsDir => unreachable, // not possible since we pass AT_REMOVEDIR |
| ... | ... | @@ -1204,7 +1202,7 @@ pub const Dir = struct { |
| 1204 | 1202 | |
| 1205 | 1203 | /// Same as `deleteDir` except the parameter is UTF16LE, NT prefixed. |
| 1206 | 1204 | /// This function is Windows-only. |
| 1207 | pub fn deleteDirW(self: Dir, sub_path_w: [*:0]const u16) DeleteDirError!void { | |
| 1205 | pub fn deleteDirW(self: Dir, sub_path_w: []const u16) DeleteDirError!void { | |
| 1208 | 1206 | os.unlinkatW(self.fd, sub_path_w, os.AT_REMOVEDIR) catch |err| switch (err) { |
| 1209 | 1207 | error.IsDir => unreachable, // not possible since we pass AT_REMOVEDIR |
| 1210 | 1208 | else => |e| return e, |
| ... | ... | @@ -1263,11 +1261,11 @@ pub const Dir = struct { |
| 1263 | 1261 | /// are null-terminated, WTF16 encoded. |
| 1264 | 1262 | pub fn symLinkW( |
| 1265 | 1263 | self: Dir, |
| 1266 | target_path_w: [:0]const u16, | |
| 1267 | sym_link_path_w: [:0]const u16, | |
| 1264 | target_path_w: []const u16, | |
| 1265 | sym_link_path_w: []const u16, | |
| 1268 | 1266 | flags: SymLinkFlags, |
| 1269 | 1267 | ) !void { |
| 1270 | return os.windows.CreateSymbolicLinkW(self.fd, sym_link_path_w, target_path_w, flags.is_directory); | |
| 1268 | return os.windows.CreateSymbolicLink(self.fd, sym_link_path_w, target_path_w, flags.is_directory); | |
| 1271 | 1269 | } |
| 1272 | 1270 | |
| 1273 | 1271 | /// Read value of a symbolic link. |
| ... | ... | @@ -1278,7 +1276,8 @@ pub const Dir = struct { |
| 1278 | 1276 | return self.readLinkWasi(sub_path, buffer); |
| 1279 | 1277 | } |
| 1280 | 1278 | if (builtin.os.tag == .windows) { |
| 1281 | return os.windows.ReadLink(self.fd, sub_path, buffer); | |
| 1279 | const sub_path_w = try os.windows.sliceToPrefixedFileW(sub_path); | |
| 1280 | return self.readLinkW(sub_path_w.span(), buffer); | |
| 1282 | 1281 | } |
| 1283 | 1282 | const sub_path_c = try os.toPosixPath(sub_path); |
| 1284 | 1283 | return self.readLinkZ(&sub_path_c, buffer); |
| ... | ... | @@ -1295,15 +1294,15 @@ pub const Dir = struct { |
| 1295 | 1294 | pub fn readLinkZ(self: Dir, sub_path_c: [*:0]const u8, buffer: []u8) ![]u8 { |
| 1296 | 1295 | if (builtin.os.tag == .windows) { |
| 1297 | 1296 | const sub_path_w = try os.windows.cStrToPrefixedFileW(sub_path_c); |
| 1298 | return self.readLinkW(sub_path_w, buffer); | |
| 1297 | return self.readLinkW(sub_path_w.span(), buffer); | |
| 1299 | 1298 | } |
| 1300 | 1299 | return os.readlinkatZ(self.fd, sub_path_c, buffer); |
| 1301 | 1300 | } |
| 1302 | 1301 | |
| 1303 | 1302 | /// Windows-only. Same as `readLink` except the pathname parameter |
| 1304 | 1303 | /// is null-terminated, WTF16 encoded. |
| 1305 | pub fn readLinkW(self: Dir, sub_path_w: [*:0]const u16, buffer: []u8) ![]u8 { | |
| 1306 | return os.windows.ReadLinkW(self.fd, sub_path_w, buffer); | |
| 1304 | pub fn readLinkW(self: Dir, sub_path_w: []const u16, buffer: []u8) ![]u8 { | |
| 1305 | return os.windows.ReadLink(self.fd, sub_path_w, buffer); | |
| 1307 | 1306 | } |
| 1308 | 1307 | |
| 1309 | 1308 | /// On success, caller owns returned buffer. |
| ... | ... | @@ -1813,7 +1812,9 @@ pub fn symLinkAbsolute(target_path: []const u8, sym_link_path: []const u8, flags |
| 1813 | 1812 | assert(path.isAbsolute(target_path)); |
| 1814 | 1813 | assert(path.isAbsolute(sym_link_path)); |
| 1815 | 1814 | if (builtin.os.tag == .windows) { |
| 1816 | return os.windows.CreateSymbolicLink(null, sym_link_path, target_path, flags.is_directory); | |
| 1815 | const target_path_w = try os.windows.sliceToPrefixedFileW(target_path); | |
| 1816 | const sym_link_path_w = try os.windows.sliceToPrefixedFileW(sym_link_path); | |
| 1817 | return os.windows.CreateSymbolicLink(null, sym_link_path_w.span(), target_path_w.span(), flags.is_directory); | |
| 1817 | 1818 | } |
| 1818 | 1819 | return os.symlink(target_path, sym_link_path); |
| 1819 | 1820 | } |
| ... | ... | @@ -1822,10 +1823,10 @@ pub fn symLinkAbsolute(target_path: []const u8, sym_link_path: []const u8, flags |
| 1822 | 1823 | /// Note that this function will by default try creating a symbolic link to a file. If you would |
| 1823 | 1824 | /// like to create a symbolic link to a directory, specify this with `SymLinkFlags{ .is_directory = true }`. |
| 1824 | 1825 | /// See also `symLinkAbsolute`, `symLinkAbsoluteZ`. |
| 1825 | pub fn symLinkAbsoluteW(target_path_w: [:0]const u16, sym_link_path_w: [:0]const u16, flags: SymLinkFlags) !void { | |
| 1826 | assert(path.isAbsoluteWindowsW(target_path_w)); | |
| 1827 | assert(path.isAbsoluteWindowsW(sym_link_path_w)); | |
| 1828 | return os.windows.CreateSymbolicLinkW(null, sym_link_path_w, target_path_w, flags.is_directory); | |
| 1826 | pub fn symLinkAbsoluteW(target_path_w: []const u16, sym_link_path_w: []const u16, flags: SymLinkFlags) !void { | |
| 1827 | assert(path.isAbsoluteWindowsWTF16(target_path_w)); | |
| 1828 | assert(path.isAbsoluteWindowsWTF16(sym_link_path_w)); | |
| 1829 | return os.windows.CreateSymbolicLink(null, sym_link_path_w, target_path_w, flags.is_directory); | |
| 1829 | 1830 | } |
| 1830 | 1831 | |
| 1831 | 1832 | /// Same as `symLinkAbsolute` except the parameters are null-terminated pointers. |
| ... | ... | @@ -1836,7 +1837,7 @@ pub fn symLinkAbsoluteZ(target_path_c: [*:0]const u8, sym_link_path_c: [*:0]cons |
| 1836 | 1837 | if (builtin.os.tag == .windows) { |
| 1837 | 1838 | const target_path_w = try os.windows.cStrToWin32PrefixedFileW(target_path_c); |
| 1838 | 1839 | const sym_link_path_w = try os.windows.cStrToWin32PrefixedFileW(sym_link_path_c); |
| 1839 | return os.windows.CreateSymbolicLinkW(sym_link_path_w.span().ptr, target_path_w.span().ptr, flags.is_directory); | |
| 1840 | return os.windows.CreateSymbolicLink(sym_link_path_w.span(), target_path_w.span(), flags.is_directory); | |
| 1840 | 1841 | } |
| 1841 | 1842 | return os.symlinkZ(target_path_c, sym_link_path_c); |
| 1842 | 1843 | } |
| ... | ... | @@ -1938,7 +1939,20 @@ pub fn walkPath(allocator: *Allocator, dir_path: []const u8) !Walker { |
| 1938 | 1939 | return walker; |
| 1939 | 1940 | } |
| 1940 | 1941 | |
| 1941 | pub const OpenSelfExeError = os.OpenError || os.windows.CreateFileError || SelfExePathError || os.FlockError; | |
| 1942 | pub const OpenSelfExeError = error{ | |
| 1943 | SharingViolation, | |
| 1944 | PathAlreadyExists, | |
| 1945 | FileNotFound, | |
| 1946 | AccessDenied, | |
| 1947 | PipeBusy, | |
| 1948 | NameTooLong, | |
| 1949 | /// On Windows, file paths must be valid Unicode. | |
| 1950 | InvalidUtf8, | |
| 1951 | /// On Windows, file paths cannot contain these characters: | |
| 1952 | /// '/', '*', '?', '"', '<', '>', '|' | |
| 1953 | BadPathName, | |
| 1954 | Unexpected, | |
| 1955 | } || os.OpenError || SelfExePathError || os.FlockError; | |
| 1942 | 1956 | |
| 1943 | 1957 | pub fn openSelfExe(flags: File.OpenFlags) OpenSelfExeError!File { |
| 1944 | 1958 | if (builtin.os.tag == .linux) { |
lib/std/fs/file.zig+14-1| ... | ... | @@ -47,7 +47,20 @@ pub const File = struct { |
| 47 | 47 | else => 0o666, |
| 48 | 48 | }; |
| 49 | 49 | |
| 50 | pub const OpenError = windows.CreateFileError || os.OpenError || os.FlockError; | |
| 50 | pub const OpenError = error{ | |
| 51 | SharingViolation, | |
| 52 | PathAlreadyExists, | |
| 53 | FileNotFound, | |
| 54 | AccessDenied, | |
| 55 | PipeBusy, | |
| 56 | NameTooLong, | |
| 57 | /// On Windows, file paths must be valid Unicode. | |
| 58 | InvalidUtf8, | |
| 59 | /// On Windows, file paths cannot contain these characters: | |
| 60 | /// '/', '*', '?', '"', '<', '>', '|' | |
| 61 | BadPathName, | |
| 62 | Unexpected, | |
| 63 | } || os.OpenError || os.FlockError; | |
| 51 | 64 | |
| 52 | 65 | pub const Lock = enum { None, Shared, Exclusive }; |
| 53 | 66 |
lib/std/fs/watch.zig+7-9| ... | ... | @@ -374,15 +374,13 @@ pub fn Watch(comptime V: type) type { |
| 374 | 374 | defer if (!basename_utf16le_null_consumed) self.allocator.free(basename_utf16le_null); |
| 375 | 375 | const basename_utf16le_no_null = basename_utf16le_null[0 .. basename_utf16le_null.len - 1]; |
| 376 | 376 | |
| 377 | const dir_handle = try windows.CreateFileW( | |
| 378 | dirname_utf16le.ptr, | |
| 379 | windows.FILE_LIST_DIRECTORY, | |
| 380 | windows.FILE_SHARE_READ | windows.FILE_SHARE_DELETE | windows.FILE_SHARE_WRITE, | |
| 381 | null, | |
| 382 | windows.OPEN_EXISTING, | |
| 383 | windows.FILE_FLAG_BACKUP_SEMANTICS | windows.FILE_FLAG_OVERLAPPED, | |
| 384 | null, | |
| 385 | ); | |
| 377 | const dir_handle = try windows.OpenFile(dirname_utf16le, .{ | |
| 378 | .dir = std.fs.cwd().fd, | |
| 379 | .access_mask = windows.FILE_LIST_DIRECTORY, | |
| 380 | .creation = windows.FILE_OPEN, | |
| 381 | .io_mode = .blocking, | |
| 382 | .open_dir = true, | |
| 383 | }); | |
| 386 | 384 | var dir_handle_consumed = false; |
| 387 | 385 | defer if (!dir_handle_consumed) windows.CloseHandle(dir_handle); |
| 388 | 386 |
lib/std/os.zig+176-106| ... | ... | @@ -1041,6 +1041,9 @@ pub const OpenError = error{ |
| 1041 | 1041 | |
| 1042 | 1042 | /// The underlying filesystem does not support file locks |
| 1043 | 1043 | FileLocksNotSupported, |
| 1044 | ||
| 1045 | BadPathName, | |
| 1046 | InvalidUtf8, | |
| 1044 | 1047 | } || UnexpectedError; |
| 1045 | 1048 | |
| 1046 | 1049 | /// Open and possibly create a file. Keeps trying if it gets interrupted. |
| ... | ... | @@ -1092,18 +1095,65 @@ pub fn openZ(file_path: [*:0]const u8, flags: u32, perm: mode_t) OpenError!fd_t |
| 1092 | 1095 | } |
| 1093 | 1096 | } |
| 1094 | 1097 | |
| 1098 | fn openOptionsFromFlags(flags: u32) windows.OpenFileOptions { | |
| 1099 | const w = windows; | |
| 1100 | ||
| 1101 | var access_mask: w.ULONG = w.READ_CONTROL | w.FILE_WRITE_ATTRIBUTES | w.SYNCHRONIZE; | |
| 1102 | if (flags & O_RDWR != 0) { | |
| 1103 | access_mask |= w.GENERIC_READ | w.GENERIC_WRITE; | |
| 1104 | } else if (flags & O_WRONLY != 0) { | |
| 1105 | access_mask |= w.GENERIC_WRITE; | |
| 1106 | } else { | |
| 1107 | access_mask |= w.GENERIC_READ | w.GENERIC_WRITE; | |
| 1108 | } | |
| 1109 | ||
| 1110 | const open_dir: bool = flags & O_DIRECTORY != 0; | |
| 1111 | const follow_symlinks: bool = flags & O_NOFOLLOW == 0; | |
| 1112 | ||
| 1113 | const creation: w.ULONG = blk: { | |
| 1114 | if (flags & O_CREAT != 0) { | |
| 1115 | if (flags & O_EXCL != 0) { | |
| 1116 | break :blk w.FILE_CREATE; | |
| 1117 | } | |
| 1118 | } | |
| 1119 | break :blk w.FILE_OPEN; | |
| 1120 | }; | |
| 1121 | ||
| 1122 | return .{ | |
| 1123 | .access_mask = access_mask, | |
| 1124 | .io_mode = .blocking, | |
| 1125 | .creation = creation, | |
| 1126 | .open_dir = open_dir, | |
| 1127 | .follow_symlinks = follow_symlinks, | |
| 1128 | }; | |
| 1129 | } | |
| 1130 | ||
| 1095 | 1131 | /// Windows-only. The path parameter is |
| 1096 | 1132 | /// [WTF-16](https://simonsapin.github.io/wtf-8/#potentially-ill-formed-utf-16) encoded. |
| 1097 | 1133 | /// Translates the POSIX open API call to a Windows API call. |
| 1098 | pub fn openW(file_path_w: []const u16, flags: u32, perm: usize) OpenError!fd_t { | |
| 1099 | @compileError("TODO implement openW for windows"); | |
| 1134 | /// TODO currently, this function does not handle all flag combinations | |
| 1135 | /// or makes use of perm argument. | |
| 1136 | pub fn openW(file_path_w: []const u16, flags: u32, perm: mode_t) OpenError!fd_t { | |
| 1137 | var options = openOptionsFromFlags(flags); | |
| 1138 | options.dir = std.fs.cwd().fd; | |
| 1139 | return windows.OpenFile(file_path_w, options) catch |err| switch (err) { | |
| 1140 | error.WouldBlock => unreachable, | |
| 1141 | error.PipeBusy => unreachable, | |
| 1142 | else => |e| return e, | |
| 1143 | }; | |
| 1100 | 1144 | } |
| 1101 | 1145 | |
| 1102 | 1146 | /// Open and possibly create a file. Keeps trying if it gets interrupted. |
| 1103 | 1147 | /// `file_path` is relative to the open directory handle `dir_fd`. |
| 1104 | 1148 | /// See also `openatC`. |
| 1105 | /// TODO support windows | |
| 1106 | 1149 | pub fn openat(dir_fd: fd_t, file_path: []const u8, flags: u32, mode: mode_t) OpenError!fd_t { |
| 1150 | if (builtin.os.tag == .wasi) { | |
| 1151 | @compileError("use openatWasi instead"); | |
| 1152 | } | |
| 1153 | if (builtin.os.tag == .windows) { | |
| 1154 | const file_path_w = try windows.sliceToPrefixedFileW(file_path); | |
| 1155 | return openatW(dir_fd, file_path_w.span(), flags, mode); | |
| 1156 | } | |
| 1107 | 1157 | const file_path_c = try toPosixPath(file_path); |
| 1108 | 1158 | return openatZ(dir_fd, &file_path_c, flags, mode); |
| 1109 | 1159 | } |
| ... | ... | @@ -1145,8 +1195,11 @@ pub const openatC = @compileError("deprecated: renamed to openatZ"); |
| 1145 | 1195 | /// Open and possibly create a file. Keeps trying if it gets interrupted. |
| 1146 | 1196 | /// `file_path` is relative to the open directory handle `dir_fd`. |
| 1147 | 1197 | /// See also `openat`. |
| 1148 | /// TODO support windows | |
| 1149 | 1198 | pub fn openatZ(dir_fd: fd_t, file_path: [*:0]const u8, flags: u32, mode: mode_t) OpenError!fd_t { |
| 1199 | if (builtin.os.tag == .windows) { | |
| 1200 | const file_path_w = try windows.cStrToPrefixedFileW(file_path); | |
| 1201 | return openatW(dir_fd, file_path_w.span(), flags, mode); | |
| 1202 | } | |
| 1150 | 1203 | while (true) { |
| 1151 | 1204 | const rc = system.openat(dir_fd, file_path, flags, mode); |
| 1152 | 1205 | switch (errno(rc)) { |
| ... | ... | @@ -1177,6 +1230,20 @@ pub fn openatZ(dir_fd: fd_t, file_path: [*:0]const u8, flags: u32, mode: mode_t) |
| 1177 | 1230 | } |
| 1178 | 1231 | } |
| 1179 | 1232 | |
| 1233 | /// Windows-only. Similar to `openat` but with pathname argument null-terminated | |
| 1234 | /// WTF16 encoded. | |
| 1235 | /// TODO currently, this function does not handle all flag combinations | |
| 1236 | /// or makes use of perm argument. | |
| 1237 | pub fn openatW(dir_fd: fd_t, file_path_w: []const u16, flags: u32, mode: mode_t) OpenError!fd_t { | |
| 1238 | var options = openOptionsFromFlags(flags); | |
| 1239 | options.dir = dir_fd; | |
| 1240 | return windows.OpenFile(file_path_w, options) catch |err| switch (err) { | |
| 1241 | error.WouldBlock => unreachable, | |
| 1242 | error.PipeBusy => unreachable, | |
| 1243 | else => |e| return e, | |
| 1244 | }; | |
| 1245 | } | |
| 1246 | ||
| 1180 | 1247 | pub fn dup2(old_fd: fd_t, new_fd: fd_t) !void { |
| 1181 | 1248 | while (true) { |
| 1182 | 1249 | switch (errno(system.dup2(old_fd, new_fd))) { |
| ... | ... | @@ -1683,7 +1750,7 @@ pub fn unlink(file_path: []const u8) UnlinkError!void { |
| 1683 | 1750 | @compileError("unlink is not supported in WASI; use unlinkat instead"); |
| 1684 | 1751 | } else if (builtin.os.tag == .windows) { |
| 1685 | 1752 | const file_path_w = try windows.sliceToPrefixedFileW(file_path); |
| 1686 | return windows.DeleteFileW(file_path_w.span().ptr); | |
| 1753 | return unlinkW(file_path_w.span()); | |
| 1687 | 1754 | } else { |
| 1688 | 1755 | const file_path_c = try toPosixPath(file_path); |
| 1689 | 1756 | return unlinkZ(&file_path_c); |
| ... | ... | @@ -1696,7 +1763,7 @@ pub const unlinkC = @compileError("deprecated: renamed to unlinkZ"); |
| 1696 | 1763 | pub fn unlinkZ(file_path: [*:0]const u8) UnlinkError!void { |
| 1697 | 1764 | if (builtin.os.tag == .windows) { |
| 1698 | 1765 | const file_path_w = try windows.cStrToPrefixedFileW(file_path); |
| 1699 | return windows.DeleteFileW(file_path_w.span().ptr); | |
| 1766 | return unlinkW(file_path_w.span()); | |
| 1700 | 1767 | } |
| 1701 | 1768 | switch (errno(system.unlink(file_path))) { |
| 1702 | 1769 | 0 => return, |
| ... | ... | @@ -1717,6 +1784,11 @@ pub fn unlinkZ(file_path: [*:0]const u8) UnlinkError!void { |
| 1717 | 1784 | } |
| 1718 | 1785 | } |
| 1719 | 1786 | |
| 1787 | /// Windows-only. Same as `unlink` except the parameter is null-terminated, WTF16 encoded. | |
| 1788 | pub fn unlinkW(file_path_w: []const u16) UnlinkError!void { | |
| 1789 | return windows.DeleteFile(file_path_w, .{ .dir = std.fs.cwd().fd }); | |
| 1790 | } | |
| 1791 | ||
| 1720 | 1792 | pub const UnlinkatError = UnlinkError || error{ |
| 1721 | 1793 | /// When passing `AT_REMOVEDIR`, this error occurs when the named directory is not empty. |
| 1722 | 1794 | DirNotEmpty, |
| ... | ... | @@ -1727,7 +1799,7 @@ pub const UnlinkatError = UnlinkError || error{ |
| 1727 | 1799 | pub fn unlinkat(dirfd: fd_t, file_path: []const u8, flags: u32) UnlinkatError!void { |
| 1728 | 1800 | if (builtin.os.tag == .windows) { |
| 1729 | 1801 | const file_path_w = try windows.sliceToPrefixedFileW(file_path); |
| 1730 | return unlinkatW(dirfd, file_path_w.span().ptr, flags); | |
| 1802 | return unlinkatW(dirfd, file_path_w.span(), flags); | |
| 1731 | 1803 | } else if (builtin.os.tag == .wasi) { |
| 1732 | 1804 | return unlinkatWasi(dirfd, file_path, flags); |
| 1733 | 1805 | } else { |
| ... | ... | @@ -1774,7 +1846,7 @@ pub fn unlinkatWasi(dirfd: fd_t, file_path: []const u8, flags: u32) UnlinkatErro |
| 1774 | 1846 | pub fn unlinkatZ(dirfd: fd_t, file_path_c: [*:0]const u8, flags: u32) UnlinkatError!void { |
| 1775 | 1847 | if (builtin.os.tag == .windows) { |
| 1776 | 1848 | const file_path_w = try windows.cStrToPrefixedFileW(file_path_c); |
| 1777 | return unlinkatW(dirfd, file_path_w.span().ptr, flags); | |
| 1849 | return unlinkatW(dirfd, file_path_w.span(), flags); | |
| 1778 | 1850 | } |
| 1779 | 1851 | switch (errno(system.unlinkat(dirfd, file_path_c, flags))) { |
| 1780 | 1852 | 0 => return, |
| ... | ... | @@ -1800,67 +1872,9 @@ pub fn unlinkatZ(dirfd: fd_t, file_path_c: [*:0]const u8, flags: u32) UnlinkatEr |
| 1800 | 1872 | } |
| 1801 | 1873 | |
| 1802 | 1874 | /// Same as `unlinkat` but `sub_path_w` is UTF16LE, NT prefixed. Windows only. |
| 1803 | pub fn unlinkatW(dirfd: fd_t, sub_path_w: [*:0]const u16, flags: u32) UnlinkatError!void { | |
| 1804 | const w = windows; | |
| 1805 | ||
| 1806 | const want_rmdir_behavior = (flags & AT_REMOVEDIR) != 0; | |
| 1807 | const create_options_flags = if (want_rmdir_behavior) | |
| 1808 | @as(w.ULONG, w.FILE_DELETE_ON_CLOSE | w.FILE_DIRECTORY_FILE | w.FILE_OPEN_REPARSE_POINT) | |
| 1809 | else | |
| 1810 | @as(w.ULONG, w.FILE_DELETE_ON_CLOSE | w.FILE_NON_DIRECTORY_FILE | w.FILE_OPEN_REPARSE_POINT); // would we ever want to delete the target instead? | |
| 1811 | ||
| 1812 | const path_len_bytes = @intCast(u16, mem.lenZ(sub_path_w) * 2); | |
| 1813 | var nt_name = w.UNICODE_STRING{ | |
| 1814 | .Length = path_len_bytes, | |
| 1815 | .MaximumLength = path_len_bytes, | |
| 1816 | // The Windows API makes this mutable, but it will not mutate here. | |
| 1817 | .Buffer = @intToPtr([*]u16, @ptrToInt(sub_path_w)), | |
| 1818 | }; | |
| 1819 | ||
| 1820 | if (sub_path_w[0] == '.' and sub_path_w[1] == 0) { | |
| 1821 | // Windows does not recognize this, but it does work with empty string. | |
| 1822 | nt_name.Length = 0; | |
| 1823 | } | |
| 1824 | if (sub_path_w[0] == '.' and sub_path_w[1] == '.' and sub_path_w[2] == 0) { | |
| 1825 | // Can't remove the parent directory with an open handle. | |
| 1826 | return error.FileBusy; | |
| 1827 | } | |
| 1828 | ||
| 1829 | var attr = w.OBJECT_ATTRIBUTES{ | |
| 1830 | .Length = @sizeOf(w.OBJECT_ATTRIBUTES), | |
| 1831 | .RootDirectory = if (std.fs.path.isAbsoluteWindowsW(sub_path_w)) null else dirfd, | |
| 1832 | .Attributes = 0, // Note we do not use OBJ_CASE_INSENSITIVE here. | |
| 1833 | .ObjectName = &nt_name, | |
| 1834 | .SecurityDescriptor = null, | |
| 1835 | .SecurityQualityOfService = null, | |
| 1836 | }; | |
| 1837 | var io: w.IO_STATUS_BLOCK = undefined; | |
| 1838 | var tmp_handle: w.HANDLE = undefined; | |
| 1839 | var rc = w.ntdll.NtCreateFile( | |
| 1840 | &tmp_handle, | |
| 1841 | w.SYNCHRONIZE | w.DELETE, | |
| 1842 | &attr, | |
| 1843 | &io, | |
| 1844 | null, | |
| 1845 | 0, | |
| 1846 | w.FILE_SHARE_READ | w.FILE_SHARE_WRITE | w.FILE_SHARE_DELETE, | |
| 1847 | w.FILE_OPEN, | |
| 1848 | create_options_flags, | |
| 1849 | null, | |
| 1850 | 0, | |
| 1851 | ); | |
| 1852 | if (rc == .SUCCESS) { | |
| 1853 | rc = w.ntdll.NtClose(tmp_handle); | |
| 1854 | } | |
| 1855 | switch (rc) { | |
| 1856 | .SUCCESS => return, | |
| 1857 | .OBJECT_NAME_INVALID => unreachable, | |
| 1858 | .OBJECT_NAME_NOT_FOUND => return error.FileNotFound, | |
| 1859 | .INVALID_PARAMETER => unreachable, | |
| 1860 | .FILE_IS_A_DIRECTORY => return error.IsDir, | |
| 1861 | .NOT_A_DIRECTORY => return error.NotDir, | |
| 1862 | else => return w.unexpectedStatus(rc), | |
| 1863 | } | |
| 1875 | pub fn unlinkatW(dirfd: fd_t, sub_path_w: []const u16, flags: u32) UnlinkatError!void { | |
| 1876 | const remove_dir = (flags & AT_REMOVEDIR) != 0; | |
| 1877 | return windows.DeleteFile(sub_path_w, .{ .dir = dirfd, .remove_dir = remove_dir }); | |
| 1864 | 1878 | } |
| 1865 | 1879 | |
| 1866 | 1880 | const RenameError = error{ |
| ... | ... | @@ -2087,7 +2101,7 @@ pub fn renameatW( |
| 2087 | 2101 | pub fn mkdirat(dir_fd: fd_t, sub_dir_path: []const u8, mode: u32) MakeDirError!void { |
| 2088 | 2102 | if (builtin.os.tag == .windows) { |
| 2089 | 2103 | const sub_dir_path_w = try windows.sliceToPrefixedFileW(sub_dir_path); |
| 2090 | return mkdiratW(dir_fd, sub_dir_path_w.span().ptr, mode); | |
| 2104 | return mkdiratW(dir_fd, sub_dir_path_w.span(), mode); | |
| 2091 | 2105 | } else if (builtin.os.tag == .wasi) { |
| 2092 | 2106 | return mkdiratWasi(dir_fd, sub_dir_path, mode); |
| 2093 | 2107 | } else { |
| ... | ... | @@ -2145,8 +2159,19 @@ pub fn mkdiratZ(dir_fd: fd_t, sub_dir_path: [*:0]const u8, mode: u32) MakeDirErr |
| 2145 | 2159 | } |
| 2146 | 2160 | } |
| 2147 | 2161 | |
| 2148 | pub fn mkdiratW(dir_fd: fd_t, sub_path_w: [*:0]const u16, mode: u32) MakeDirError!void { | |
| 2149 | const sub_dir_handle = try windows.CreateDirectoryW(dir_fd, sub_path_w, null); | |
| 2162 | pub fn mkdiratW(dir_fd: fd_t, sub_path_w: []const u16, mode: u32) MakeDirError!void { | |
| 2163 | const sub_dir_handle = windows.OpenFile(sub_path_w, .{ | |
| 2164 | .dir = dir_fd, | |
| 2165 | .access_mask = windows.GENERIC_READ | windows.SYNCHRONIZE, | |
| 2166 | .creation = windows.FILE_CREATE, | |
| 2167 | .io_mode = .blocking, | |
| 2168 | .open_dir = true, | |
| 2169 | }) catch |err| switch (err) { | |
| 2170 | error.IsDir => unreachable, | |
| 2171 | error.PipeBusy => unreachable, | |
| 2172 | error.WouldBlock => unreachable, | |
| 2173 | else => |e| return e, | |
| 2174 | }; | |
| 2150 | 2175 | windows.CloseHandle(sub_dir_handle); |
| 2151 | 2176 | } |
| 2152 | 2177 | |
| ... | ... | @@ -2175,9 +2200,8 @@ pub fn mkdir(dir_path: []const u8, mode: u32) MakeDirError!void { |
| 2175 | 2200 | if (builtin.os.tag == .wasi) { |
| 2176 | 2201 | @compileError("mkdir is not supported in WASI; use mkdirat instead"); |
| 2177 | 2202 | } else if (builtin.os.tag == .windows) { |
| 2178 | const sub_dir_handle = try windows.CreateDirectory(null, dir_path, null); | |
| 2179 | windows.CloseHandle(sub_dir_handle); | |
| 2180 | return; | |
| 2203 | const dir_path_w = try windows.sliceToPrefixedFileW(dir_path); | |
| 2204 | return mkdirW(dir_path_w.span(), mode); | |
| 2181 | 2205 | } else { |
| 2182 | 2206 | const dir_path_c = try toPosixPath(dir_path); |
| 2183 | 2207 | return mkdirZ(&dir_path_c, mode); |
| ... | ... | @@ -2188,9 +2212,7 @@ pub fn mkdir(dir_path: []const u8, mode: u32) MakeDirError!void { |
| 2188 | 2212 | pub fn mkdirZ(dir_path: [*:0]const u8, mode: u32) MakeDirError!void { |
| 2189 | 2213 | if (builtin.os.tag == .windows) { |
| 2190 | 2214 | const dir_path_w = try windows.cStrToPrefixedFileW(dir_path); |
| 2191 | const sub_dir_handle = try windows.CreateDirectoryW(null, dir_path_w.span().ptr, null); | |
| 2192 | windows.CloseHandle(sub_dir_handle); | |
| 2193 | return; | |
| 2215 | return mkdirW(dir_path_w.span(), mode); | |
| 2194 | 2216 | } |
| 2195 | 2217 | switch (errno(system.mkdir(dir_path, mode))) { |
| 2196 | 2218 | 0 => return, |
| ... | ... | @@ -2211,6 +2233,23 @@ pub fn mkdirZ(dir_path: [*:0]const u8, mode: u32) MakeDirError!void { |
| 2211 | 2233 | } |
| 2212 | 2234 | } |
| 2213 | 2235 | |
| 2236 | /// Windows-only. Same as `mkdir` but the parameters is WTF16 encoded. | |
| 2237 | pub fn mkdirW(dir_path_w: []const u16, mode: u32) MakeDirError!void { | |
| 2238 | const sub_dir_handle = windows.OpenFile(dir_path_w, .{ | |
| 2239 | .dir = std.fs.cwd().fd, | |
| 2240 | .access_mask = windows.GENERIC_READ | windows.SYNCHRONIZE, | |
| 2241 | .creation = windows.FILE_CREATE, | |
| 2242 | .io_mode = .blocking, | |
| 2243 | .open_dir = true, | |
| 2244 | }) catch |err| switch (err) { | |
| 2245 | error.IsDir => unreachable, | |
| 2246 | error.PipeBusy => unreachable, | |
| 2247 | error.WouldBlock => unreachable, | |
| 2248 | else => |e| return e, | |
| 2249 | }; | |
| 2250 | windows.CloseHandle(sub_dir_handle); | |
| 2251 | } | |
| 2252 | ||
| 2214 | 2253 | pub const DeleteDirError = error{ |
| 2215 | 2254 | AccessDenied, |
| 2216 | 2255 | FileBusy, |
| ... | ... | @@ -2231,7 +2270,7 @@ pub fn rmdir(dir_path: []const u8) DeleteDirError!void { |
| 2231 | 2270 | @compileError("rmdir is not supported in WASI; use unlinkat instead"); |
| 2232 | 2271 | } else if (builtin.os.tag == .windows) { |
| 2233 | 2272 | const dir_path_w = try windows.sliceToPrefixedFileW(dir_path); |
| 2234 | return windows.RemoveDirectoryW(dir_path_w.span().ptr); | |
| 2273 | return rmdirW(dir_path_w.span()); | |
| 2235 | 2274 | } else { |
| 2236 | 2275 | const dir_path_c = try toPosixPath(dir_path); |
| 2237 | 2276 | return rmdirZ(&dir_path_c); |
| ... | ... | @@ -2244,7 +2283,7 @@ pub const rmdirC = @compileError("deprecated: renamed to rmdirZ"); |
| 2244 | 2283 | pub fn rmdirZ(dir_path: [*:0]const u8) DeleteDirError!void { |
| 2245 | 2284 | if (builtin.os.tag == .windows) { |
| 2246 | 2285 | const dir_path_w = try windows.cStrToPrefixedFileW(dir_path); |
| 2247 | return windows.RemoveDirectoryW(dir_path_w.span().ptr); | |
| 2286 | return rmdirW(dir_path_w.span()); | |
| 2248 | 2287 | } |
| 2249 | 2288 | switch (errno(system.rmdir(dir_path))) { |
| 2250 | 2289 | 0 => return, |
| ... | ... | @@ -2265,6 +2304,14 @@ pub fn rmdirZ(dir_path: [*:0]const u8) DeleteDirError!void { |
| 2265 | 2304 | } |
| 2266 | 2305 | } |
| 2267 | 2306 | |
| 2307 | /// Windows-only. Same as `rmdir` except the parameter is WTF16 encoded. | |
| 2308 | pub fn rmdirW(dir_path_w: []const u16) DeleteDirError!void { | |
| 2309 | return windows.DeleteFile(dir_path_w, .{ .dir = std.fs.cwd().fd, .remove_dir = true }) catch |err| switch (err) { | |
| 2310 | error.IsDir => unreachable, | |
| 2311 | else => |e| return e, | |
| 2312 | }; | |
| 2313 | } | |
| 2314 | ||
| 2268 | 2315 | pub const ChangeCurDirError = error{ |
| 2269 | 2316 | AccessDenied, |
| 2270 | 2317 | FileSystem, |
| ... | ... | @@ -2354,7 +2401,8 @@ pub fn readlink(file_path: []const u8, out_buffer: []u8) ReadLinkError![]u8 { |
| 2354 | 2401 | if (builtin.os.tag == .wasi) { |
| 2355 | 2402 | @compileError("readlink is not supported in WASI; use readlinkat instead"); |
| 2356 | 2403 | } else if (builtin.os.tag == .windows) { |
| 2357 | return windows.ReadLink(std.fs.cwd().fd, file_path, out_buffer); | |
| 2404 | const file_path_w = try windows.sliceToPrefixedFileW(file_path); | |
| 2405 | return readlinkW(file_path_w.span(), out_buffer); | |
| 2358 | 2406 | } else { |
| 2359 | 2407 | const file_path_c = try toPosixPath(file_path); |
| 2360 | 2408 | return readlinkZ(&file_path_c, out_buffer); |
| ... | ... | @@ -2363,17 +2411,17 @@ pub fn readlink(file_path: []const u8, out_buffer: []u8) ReadLinkError![]u8 { |
| 2363 | 2411 | |
| 2364 | 2412 | pub const readlinkC = @compileError("deprecated: renamed to readlinkZ"); |
| 2365 | 2413 | |
| 2366 | /// Windows-only. Same as `readlink` except `file_path` is null-terminated, WTF16 encoded. | |
| 2414 | /// Windows-only. Same as `readlink` except `file_path` is WTF16 encoded. | |
| 2367 | 2415 | /// See also `readlinkZ`. |
| 2368 | pub fn readlinkW(file_path: [*:0]const u16, out_buffer: []u8) ReadLinkError![]u8 { | |
| 2369 | return windows.ReadLinkW(std.fs.cwd().fd, file_path, out_buffer); | |
| 2416 | pub fn readlinkW(file_path: []const u16, out_buffer: []u8) ReadLinkError![]u8 { | |
| 2417 | return windows.ReadLink(std.fs.cwd().fd, file_path, out_buffer); | |
| 2370 | 2418 | } |
| 2371 | 2419 | |
| 2372 | 2420 | /// Same as `readlink` except `file_path` is null-terminated. |
| 2373 | 2421 | pub fn readlinkZ(file_path: [*:0]const u8, out_buffer: []u8) ReadLinkError![]u8 { |
| 2374 | 2422 | if (builtin.os.tag == .windows) { |
| 2375 | 2423 | const file_path_w = try windows.cStrToWin32PrefixedFileW(file_path); |
| 2376 | return readlinkW(file_path_w.span().ptr, out_buffer); | |
| 2424 | return readlinkW(file_path_w.span(), out_buffer); | |
| 2377 | 2425 | } |
| 2378 | 2426 | const rc = system.readlink(file_path, out_buffer.ptr, out_buffer.len); |
| 2379 | 2427 | switch (errno(rc)) { |
| ... | ... | @@ -2399,7 +2447,8 @@ pub fn readlinkat(dirfd: fd_t, file_path: []const u8, out_buffer: []u8) ReadLink |
| 2399 | 2447 | return readlinkatWasi(dirfd, file_path, out_buffer); |
| 2400 | 2448 | } |
| 2401 | 2449 | if (builtin.os.tag == .windows) { |
| 2402 | return windows.ReadLink(dirfd, file_path, out_buffer); | |
| 2450 | const file_path_w = try windows.sliceToPrefixedFileW(file_path); | |
| 2451 | return readlinkatW(dirfd, file_path_w.span(), out_buffer); | |
| 2403 | 2452 | } |
| 2404 | 2453 | const file_path_c = try toPosixPath(file_path); |
| 2405 | 2454 | return readlinkatZ(dirfd, &file_path_c, out_buffer); |
| ... | ... | @@ -2429,8 +2478,8 @@ pub fn readlinkatWasi(dirfd: fd_t, file_path: []const u8, out_buffer: []u8) Read |
| 2429 | 2478 | |
| 2430 | 2479 | /// Windows-only. Same as `readlinkat` except `file_path` is null-terminated, WTF16 encoded. |
| 2431 | 2480 | /// See also `readlinkat`. |
| 2432 | pub fn readlinkatW(dirfd: fd_t, file_path: [*:0]const u16, out_buffer: []u8) ReadLinkError![]u8 { | |
| 2433 | return windows.ReadLinkW(dirfd, file_path, out_buffer); | |
| 2481 | pub fn readlinkatW(dirfd: fd_t, file_path: []const u16, out_buffer: []u8) ReadLinkError![]u8 { | |
| 2482 | return windows.ReadLink(dirfd, file_path, out_buffer); | |
| 2434 | 2483 | } |
| 2435 | 2484 | |
| 2436 | 2485 | /// Same as `readlinkat` except `file_path` is null-terminated. |
| ... | ... | @@ -2438,7 +2487,7 @@ pub fn readlinkatW(dirfd: fd_t, file_path: [*:0]const u16, out_buffer: []u8) Rea |
| 2438 | 2487 | pub fn readlinkatZ(dirfd: fd_t, file_path: [*:0]const u8, out_buffer: []u8) ReadLinkError![]u8 { |
| 2439 | 2488 | if (builtin.os.tag == .windows) { |
| 2440 | 2489 | const file_path_w = try windows.cStrToPrefixedFileW(file_path); |
| 2441 | return readlinkatW(dirfd, file_path_w.span().ptr, out_buffer); | |
| 2490 | return readlinkatW(dirfd, file_path_w.span(), out_buffer); | |
| 2442 | 2491 | } |
| 2443 | 2492 | const rc = system.readlinkat(dirfd, file_path, out_buffer.ptr, out_buffer.len); |
| 2444 | 2493 | switch (errno(rc)) { |
| ... | ... | @@ -3959,7 +4008,7 @@ pub const RealPathError = error{ |
| 3959 | 4008 | pub fn realpath(pathname: []const u8, out_buffer: *[MAX_PATH_BYTES]u8) RealPathError![]u8 { |
| 3960 | 4009 | if (builtin.os.tag == .windows) { |
| 3961 | 4010 | const pathname_w = try windows.sliceToPrefixedFileW(pathname); |
| 3962 | return realpathW(pathname_w.span().ptr, out_buffer); | |
| 4011 | return realpathW(pathname_w.span(), out_buffer); | |
| 3963 | 4012 | } |
| 3964 | 4013 | if (builtin.os.tag == .wasi) { |
| 3965 | 4014 | @compileError("Use std.fs.wasi.PreopenList to obtain valid Dir handles instead of using absolute paths"); |
| ... | ... | @@ -3974,7 +4023,7 @@ pub const realpathC = @compileError("deprecated: renamed realpathZ"); |
| 3974 | 4023 | pub fn realpathZ(pathname: [*:0]const u8, out_buffer: *[MAX_PATH_BYTES]u8) RealPathError![]u8 { |
| 3975 | 4024 | if (builtin.os.tag == .windows) { |
| 3976 | 4025 | const pathname_w = try windows.cStrToPrefixedFileW(pathname); |
| 3977 | return realpathW(pathname_w.span().ptr, out_buffer); | |
| 4026 | return realpathW(pathname_w.span(), out_buffer); | |
| 3978 | 4027 | } |
| 3979 | 4028 | if (builtin.os.tag == .linux and !builtin.link_libc) { |
| 3980 | 4029 | const fd = openZ(pathname, linux.O_PATH | linux.O_NONBLOCK | linux.O_CLOEXEC, 0) catch |err| switch (err) { |
| ... | ... | @@ -4010,22 +4059,43 @@ pub fn realpathZ(pathname: [*:0]const u8, out_buffer: *[MAX_PATH_BYTES]u8) RealP |
| 4010 | 4059 | return mem.spanZ(result_path); |
| 4011 | 4060 | } |
| 4012 | 4061 | |
| 4013 | /// Same as `realpath` except `pathname` is null-terminated and UTF16LE-encoded. | |
| 4014 | /// TODO use ntdll for better semantics | |
| 4015 | pub fn realpathW(pathname: [*:0]const u16, out_buffer: *[MAX_PATH_BYTES]u8) RealPathError![]u8 { | |
| 4016 | const h_file = try windows.CreateFileW( | |
| 4017 | pathname, | |
| 4018 | windows.GENERIC_READ, | |
| 4019 | windows.FILE_SHARE_READ, | |
| 4020 | null, | |
| 4021 | windows.OPEN_EXISTING, | |
| 4022 | windows.FILE_FLAG_BACKUP_SEMANTICS, | |
| 4023 | null, | |
| 4024 | ); | |
| 4025 | defer windows.CloseHandle(h_file); | |
| 4062 | /// Same as `realpath` except `pathname` is UTF16LE-encoded. | |
| 4063 | /// TODO use ntdll to emulate `GetFinalPathNameByHandleW` routine | |
| 4064 | pub fn realpathW(pathname: []const u16, out_buffer: *[MAX_PATH_BYTES]u8) RealPathError![]u8 { | |
| 4065 | const w = windows; | |
| 4066 | ||
| 4067 | const dir = std.fs.cwd().fd; | |
| 4068 | const access_mask = w.GENERIC_READ | w.SYNCHRONIZE; | |
| 4069 | const share_access = w.FILE_SHARE_READ; | |
| 4070 | const creation = w.FILE_OPEN; | |
| 4071 | const h_file = blk: { | |
| 4072 | const res = w.OpenFile(pathname, .{ | |
| 4073 | .dir = dir, | |
| 4074 | .access_mask = access_mask, | |
| 4075 | .share_access = share_access, | |
| 4076 | .creation = creation, | |
| 4077 | .io_mode = .blocking, | |
| 4078 | }) catch |err| switch (err) { | |
| 4079 | error.IsDir => break :blk w.OpenFile(pathname, .{ | |
| 4080 | .dir = dir, | |
| 4081 | .access_mask = access_mask, | |
| 4082 | .share_access = share_access, | |
| 4083 | .creation = creation, | |
| 4084 | .io_mode = .blocking, | |
| 4085 | .open_dir = true, | |
| 4086 | }) catch |er| switch (er) { | |
| 4087 | error.WouldBlock => unreachable, | |
| 4088 | else => |e2| return e2, | |
| 4089 | }, | |
| 4090 | error.WouldBlock => unreachable, | |
| 4091 | else => |e| return e, | |
| 4092 | }; | |
| 4093 | break :blk res; | |
| 4094 | }; | |
| 4095 | defer w.CloseHandle(h_file); | |
| 4026 | 4096 | |
| 4027 | var wide_buf: [windows.PATH_MAX_WIDE]u16 = undefined; | |
| 4028 | const wide_slice = try windows.GetFinalPathNameByHandleW(h_file, &wide_buf, wide_buf.len, windows.VOLUME_NAME_DOS); | |
| 4097 | var wide_buf: [w.PATH_MAX_WIDE]u16 = undefined; | |
| 4098 | const wide_slice = try w.GetFinalPathNameByHandleW(h_file, &wide_buf, wide_buf.len, w.VOLUME_NAME_DOS); | |
| 4029 | 4099 | |
| 4030 | 4100 | // Windows returns \\?\ prepended to the path. |
| 4031 | 4101 | // We strip it to make this function consistent across platforms. |
lib/std/os/bits/windows.zig+25| ... | ... | @@ -237,3 +237,28 @@ pub const IPPROTO_TCP = ws2_32.IPPROTO_TCP; |
| 237 | 237 | pub const IPPROTO_UDP = ws2_32.IPPROTO_UDP; |
| 238 | 238 | pub const IPPROTO_ICMPV6 = ws2_32.IPPROTO_ICMPV6; |
| 239 | 239 | pub const IPPROTO_RM = ws2_32.IPPROTO_RM; |
| 240 | ||
| 241 | pub const O_RDONLY = 0o0; | |
| 242 | pub const O_WRONLY = 0o1; | |
| 243 | pub const O_RDWR = 0o2; | |
| 244 | ||
| 245 | pub const O_CREAT = 0o100; | |
| 246 | pub const O_EXCL = 0o200; | |
| 247 | pub const O_NOCTTY = 0o400; | |
| 248 | pub const O_TRUNC = 0o1000; | |
| 249 | pub const O_APPEND = 0o2000; | |
| 250 | pub const O_NONBLOCK = 0o4000; | |
| 251 | pub const O_DSYNC = 0o10000; | |
| 252 | pub const O_SYNC = 0o4010000; | |
| 253 | pub const O_RSYNC = 0o4010000; | |
| 254 | pub const O_DIRECTORY = 0o200000; | |
| 255 | pub const O_NOFOLLOW = 0o400000; | |
| 256 | pub const O_CLOEXEC = 0o2000000; | |
| 257 | ||
| 258 | pub const O_ASYNC = 0o20000; | |
| 259 | pub const O_DIRECT = 0o40000; | |
| 260 | pub const O_LARGEFILE = 0; | |
| 261 | pub const O_NOATIME = 0o1000000; | |
| 262 | pub const O_PATH = 0o10000000; | |
| 263 | pub const O_TMPFILE = 0o20200000; | |
| 264 | pub const O_NDELAY = O_NONBLOCK; | |
| \ No newline at end of file |
lib/std/os/test.zig+92-2| ... | ... | @@ -3,6 +3,7 @@ const os = std.os; |
| 3 | 3 | const testing = std.testing; |
| 4 | 4 | const expect = testing.expect; |
| 5 | 5 | const expectEqual = testing.expectEqual; |
| 6 | const expectError = testing.expectError; | |
| 6 | 7 | const io = std.io; |
| 7 | 8 | const fs = std.fs; |
| 8 | 9 | const mem = std.mem; |
| ... | ... | @@ -19,6 +20,95 @@ const tmpDir = std.testing.tmpDir; |
| 19 | 20 | const Dir = std.fs.Dir; |
| 20 | 21 | const ArenaAllocator = std.heap.ArenaAllocator; |
| 21 | 22 | |
| 23 | test "open smoke test" { | |
| 24 | if (builtin.os.tag == .wasi) return error.SkipZigTest; | |
| 25 | ||
| 26 | // TODO verify file attributes using `fstat` | |
| 27 | ||
| 28 | var tmp = tmpDir(.{}); | |
| 29 | defer tmp.cleanup(); | |
| 30 | ||
| 31 | // Get base abs path | |
| 32 | var arena = ArenaAllocator.init(testing.allocator); | |
| 33 | defer arena.deinit(); | |
| 34 | ||
| 35 | const base_path = blk: { | |
| 36 | const relative_path = try fs.path.join(&arena.allocator, &[_][]const u8{ "zig-cache", "tmp", tmp.sub_path[0..] }); | |
| 37 | break :blk try fs.realpathAlloc(&arena.allocator, relative_path); | |
| 38 | }; | |
| 39 | ||
| 40 | var file_path: []u8 = undefined; | |
| 41 | var fd: os.fd_t = undefined; | |
| 42 | const mode: os.mode_t = if (builtin.os.tag == .windows) 0 else 0o666; | |
| 43 | ||
| 44 | // Create some file using `open`. | |
| 45 | file_path = try fs.path.join(&arena.allocator, &[_][]const u8{ base_path, "some_file" }); | |
| 46 | fd = try os.open(file_path, os.O_RDWR | os.O_CREAT | os.O_EXCL, mode); | |
| 47 | os.close(fd); | |
| 48 | ||
| 49 | // Try this again with the same flags. This op should fail with error.PathAlreadyExists. | |
| 50 | file_path = try fs.path.join(&arena.allocator, &[_][]const u8{ base_path, "some_file" }); | |
| 51 | expectError(error.PathAlreadyExists, os.open(file_path, os.O_RDWR | os.O_CREAT | os.O_EXCL, mode)); | |
| 52 | ||
| 53 | // Try opening without `O_EXCL` flag. | |
| 54 | file_path = try fs.path.join(&arena.allocator, &[_][]const u8{ base_path, "some_file" }); | |
| 55 | fd = try os.open(file_path, os.O_RDWR | os.O_CREAT, mode); | |
| 56 | os.close(fd); | |
| 57 | ||
| 58 | // Try opening as a directory which should fail. | |
| 59 | file_path = try fs.path.join(&arena.allocator, &[_][]const u8{ base_path, "some_file" }); | |
| 60 | expectError(error.NotDir, os.open(file_path, os.O_RDWR | os.O_DIRECTORY, mode)); | |
| 61 | ||
| 62 | // Create some directory | |
| 63 | file_path = try fs.path.join(&arena.allocator, &[_][]const u8{ base_path, "some_dir" }); | |
| 64 | try os.mkdir(file_path, mode); | |
| 65 | ||
| 66 | // Open dir using `open` | |
| 67 | file_path = try fs.path.join(&arena.allocator, &[_][]const u8{ base_path, "some_dir" }); | |
| 68 | fd = try os.open(file_path, os.O_RDONLY | os.O_DIRECTORY, mode); | |
| 69 | os.close(fd); | |
| 70 | ||
| 71 | // Try opening as file which should fail. | |
| 72 | file_path = try fs.path.join(&arena.allocator, &[_][]const u8{ base_path, "some_dir" }); | |
| 73 | expectError(error.IsDir, os.open(file_path, os.O_RDWR, mode)); | |
| 74 | } | |
| 75 | ||
| 76 | test "openat smoke test" { | |
| 77 | if (builtin.os.tag == .wasi) return error.SkipZigTest; | |
| 78 | ||
| 79 | // TODO verify file attributes using `fstatat` | |
| 80 | ||
| 81 | var tmp = tmpDir(.{}); | |
| 82 | defer tmp.cleanup(); | |
| 83 | ||
| 84 | var fd: os.fd_t = undefined; | |
| 85 | const mode: os.mode_t = if (builtin.os.tag == .windows) 0 else 0o666; | |
| 86 | ||
| 87 | // Create some file using `openat`. | |
| 88 | fd = try os.openat(tmp.dir.fd, "some_file", os.O_RDWR | os.O_CREAT | os.O_EXCL, mode); | |
| 89 | os.close(fd); | |
| 90 | ||
| 91 | // Try this again with the same flags. This op should fail with error.PathAlreadyExists. | |
| 92 | expectError(error.PathAlreadyExists, os.openat(tmp.dir.fd, "some_file", os.O_RDWR | os.O_CREAT | os.O_EXCL, mode)); | |
| 93 | ||
| 94 | // Try opening without `O_EXCL` flag. | |
| 95 | fd = try os.openat(tmp.dir.fd, "some_file", os.O_RDWR | os.O_CREAT, mode); | |
| 96 | os.close(fd); | |
| 97 | ||
| 98 | // Try opening as a directory which should fail. | |
| 99 | expectError(error.NotDir, os.openat(tmp.dir.fd, "some_file", os.O_RDWR | os.O_DIRECTORY, mode)); | |
| 100 | ||
| 101 | // Create some directory | |
| 102 | try os.mkdirat(tmp.dir.fd, "some_dir", mode); | |
| 103 | ||
| 104 | // Open dir using `open` | |
| 105 | fd = try os.openat(tmp.dir.fd, "some_dir", os.O_RDONLY | os.O_DIRECTORY, mode); | |
| 106 | os.close(fd); | |
| 107 | ||
| 108 | // Try opening as file which should fail. | |
| 109 | expectError(error.IsDir, os.openat(tmp.dir.fd, "some_dir", os.O_RDWR, mode)); | |
| 110 | } | |
| 111 | ||
| 22 | 112 | test "symlink with relative paths" { |
| 23 | 113 | if (builtin.os.tag == .wasi) return error.SkipZigTest; |
| 24 | 114 | |
| ... | ... | @@ -27,7 +117,7 @@ test "symlink with relative paths" { |
| 27 | 117 | try cwd.writeFile("file.txt", "nonsense"); |
| 28 | 118 | |
| 29 | 119 | if (builtin.os.tag == .windows) { |
| 30 | try os.windows.CreateSymbolicLink(cwd.fd, "symlinked", "file.txt", false); | |
| 120 | try os.windows.CreateSymbolicLink(cwd.fd, &[_]u16{ 's', 'y', 'm', 'l', 'i', 'n', 'k', 'e', 'd' }, &[_]u16{ 'f', 'i', 'l', 'e', '.', 't', 'x', 't' }, false); | |
| 31 | 121 | } else { |
| 32 | 122 | try os.symlink("file.txt", "symlinked"); |
| 33 | 123 | } |
| ... | ... | @@ -85,7 +175,7 @@ test "readlinkat" { |
| 85 | 175 | |
| 86 | 176 | // create a symbolic link |
| 87 | 177 | if (builtin.os.tag == .windows) { |
| 88 | try os.windows.CreateSymbolicLink(tmp.dir.fd, "link", "file.txt", false); | |
| 178 | try os.windows.CreateSymbolicLink(tmp.dir.fd, &[_]u16{ 'l', 'i', 'n', 'k' }, &[_]u16{ 'f', 'i', 'l', 'e', '.', 't', 'x', 't' }, false); | |
| 89 | 179 | } else { |
| 90 | 180 | try os.symlinkat("file.txt", tmp.dir.fd, "link"); |
| 91 | 181 | } |
lib/std/os/windows.zig+78-264| ... | ... | @@ -25,76 +25,11 @@ pub usingnamespace @import("windows/bits.zig"); |
| 25 | 25 | |
| 26 | 26 | pub const self_process_handle = @intToPtr(HANDLE, maxInt(usize)); |
| 27 | 27 | |
| 28 | pub const CreateFileError = error{ | |
| 29 | SharingViolation, | |
| 30 | PathAlreadyExists, | |
| 31 | ||
| 32 | /// When any of the path components can not be found or the file component can not | |
| 33 | /// be found. Some operating systems distinguish between path components not found and | |
| 34 | /// file components not found, but they are collapsed into FileNotFound to gain | |
| 35 | /// consistency across operating systems. | |
| 36 | FileNotFound, | |
| 37 | ||
| 38 | AccessDenied, | |
| 39 | PipeBusy, | |
| 40 | NameTooLong, | |
| 41 | ||
| 42 | /// On Windows, file paths must be valid Unicode. | |
| 43 | InvalidUtf8, | |
| 44 | ||
| 45 | /// On Windows, file paths cannot contain these characters: | |
| 46 | /// '/', '*', '?', '"', '<', '>', '|' | |
| 47 | BadPathName, | |
| 48 | ||
| 49 | Unexpected, | |
| 50 | }; | |
| 51 | ||
| 52 | pub fn CreateFile( | |
| 53 | file_path: []const u8, | |
| 54 | desired_access: DWORD, | |
| 55 | share_mode: DWORD, | |
| 56 | lpSecurityAttributes: ?LPSECURITY_ATTRIBUTES, | |
| 57 | creation_disposition: DWORD, | |
| 58 | flags_and_attrs: DWORD, | |
| 59 | hTemplateFile: ?HANDLE, | |
| 60 | ) CreateFileError!HANDLE { | |
| 61 | const file_path_w = try sliceToPrefixedFileW(file_path); | |
| 62 | return CreateFileW(file_path_w.span().ptr, desired_access, share_mode, lpSecurityAttributes, creation_disposition, flags_and_attrs, hTemplateFile); | |
| 63 | } | |
| 64 | ||
| 65 | pub fn CreateFileW( | |
| 66 | file_path_w: [*:0]const u16, | |
| 67 | desired_access: DWORD, | |
| 68 | share_mode: DWORD, | |
| 69 | lpSecurityAttributes: ?LPSECURITY_ATTRIBUTES, | |
| 70 | creation_disposition: DWORD, | |
| 71 | flags_and_attrs: DWORD, | |
| 72 | hTemplateFile: ?HANDLE, | |
| 73 | ) CreateFileError!HANDLE { | |
| 74 | const result = kernel32.CreateFileW(file_path_w, desired_access, share_mode, lpSecurityAttributes, creation_disposition, flags_and_attrs, hTemplateFile); | |
| 75 | ||
| 76 | if (result == INVALID_HANDLE_VALUE) { | |
| 77 | switch (kernel32.GetLastError()) { | |
| 78 | .SHARING_VIOLATION => return error.SharingViolation, | |
| 79 | .ALREADY_EXISTS => return error.PathAlreadyExists, | |
| 80 | .FILE_EXISTS => return error.PathAlreadyExists, | |
| 81 | .FILE_NOT_FOUND => return error.FileNotFound, | |
| 82 | .PATH_NOT_FOUND => return error.FileNotFound, | |
| 83 | .ACCESS_DENIED => return error.AccessDenied, | |
| 84 | .PIPE_BUSY => return error.PipeBusy, | |
| 85 | .FILENAME_EXCED_RANGE => return error.NameTooLong, | |
| 86 | else => |err| return unexpectedError(err), | |
| 87 | } | |
| 88 | } | |
| 89 | ||
| 90 | return result; | |
| 91 | } | |
| 92 | ||
| 93 | 28 | pub const OpenError = error{ |
| 94 | 29 | IsDir, |
| 30 | NotDir, | |
| 95 | 31 | FileNotFound, |
| 96 | 32 | NoDevice, |
| 97 | SharingViolation, | |
| 98 | 33 | AccessDenied, |
| 99 | 34 | PipeBusy, |
| 100 | 35 | PathAlreadyExists, |
| ... | ... | @@ -111,15 +46,21 @@ pub const OpenFileOptions = struct { |
| 111 | 46 | share_access_nonblocking: bool = false, |
| 112 | 47 | creation: ULONG, |
| 113 | 48 | io_mode: std.io.ModeOverride, |
| 49 | /// If true, tries to open path as a directory. | |
| 50 | /// Defaults to false. | |
| 51 | open_dir: bool = false, | |
| 52 | /// If false, tries to open path as a reparse point without dereferencing it. | |
| 53 | /// Defaults to true. | |
| 54 | follow_symlinks: bool = true, | |
| 114 | 55 | }; |
| 115 | 56 | |
| 116 | 57 | /// TODO when share_access_nonblocking is false, this implementation uses |
| 117 | 58 | /// untinterruptible sleep() to block. This is not the final iteration of the API. |
| 118 | 59 | pub fn OpenFile(sub_path_w: []const u16, options: OpenFileOptions) OpenError!HANDLE { |
| 119 | if (mem.eql(u16, sub_path_w, &[_]u16{'.'})) { | |
| 60 | if (mem.eql(u16, sub_path_w, &[_]u16{'.'}) and !options.open_dir) { | |
| 120 | 61 | return error.IsDir; |
| 121 | 62 | } |
| 122 | if (mem.eql(u16, sub_path_w, &[_]u16{ '.', '.' })) { | |
| 63 | if (mem.eql(u16, sub_path_w, &[_]u16{ '.', '.' }) and !options.open_dir) { | |
| 123 | 64 | return error.IsDir; |
| 124 | 65 | } |
| 125 | 66 | |
| ... | ... | @@ -142,11 +83,13 @@ pub fn OpenFile(sub_path_w: []const u16, options: OpenFileOptions) OpenError!HAN |
| 142 | 83 | .SecurityQualityOfService = null, |
| 143 | 84 | }; |
| 144 | 85 | var io: IO_STATUS_BLOCK = undefined; |
| 86 | const blocking_flag: ULONG = if (options.io_mode == .blocking) FILE_SYNCHRONOUS_IO_NONALERT else 0; | |
| 87 | const file_or_dir_flag: ULONG = if (options.open_dir) FILE_DIRECTORY_FILE else FILE_NON_DIRECTORY_FILE; | |
| 88 | // If we're not following symlinks, we need to ensure we don't pass in any synchronization flags such as FILE_SYNCHRONOUS_IO_NONALERT. | |
| 89 | const flags: ULONG = if (options.follow_symlinks) file_or_dir_flag | blocking_flag else file_or_dir_flag | FILE_OPEN_REPARSE_POINT; | |
| 145 | 90 | |
| 146 | 91 | var delay: usize = 1; |
| 147 | 92 | while (true) { |
| 148 | var flags: ULONG = undefined; | |
| 149 | const blocking_flag: ULONG = if (options.io_mode == .blocking) FILE_SYNCHRONOUS_IO_NONALERT else 0; | |
| 150 | 93 | const rc = ntdll.NtCreateFile( |
| 151 | 94 | &result, |
| 152 | 95 | options.access_mask, |
| ... | ... | @@ -156,7 +99,7 @@ pub fn OpenFile(sub_path_w: []const u16, options: OpenFileOptions) OpenError!HAN |
| 156 | 99 | FILE_ATTRIBUTE_NORMAL, |
| 157 | 100 | options.share_access, |
| 158 | 101 | options.creation, |
| 159 | FILE_NON_DIRECTORY_FILE | blocking_flag, | |
| 102 | flags, | |
| 160 | 103 | null, |
| 161 | 104 | 0, |
| 162 | 105 | ); |
| ... | ... | @@ -184,6 +127,7 @@ pub fn OpenFile(sub_path_w: []const u16, options: OpenFileOptions) OpenError!HAN |
| 184 | 127 | .OBJECT_PATH_SYNTAX_BAD => unreachable, |
| 185 | 128 | .OBJECT_NAME_COLLISION => return error.PathAlreadyExists, |
| 186 | 129 | .FILE_IS_A_DIRECTORY => return error.IsDir, |
| 130 | .NOT_A_DIRECTORY => return error.NotDir, | |
| 187 | 131 | else => return unexpectedStatus(rc), |
| 188 | 132 | } |
| 189 | 133 | } |
| ... | ... | @@ -638,27 +582,14 @@ pub const CreateSymbolicLinkError = error{ |
| 638 | 582 | PathAlreadyExists, |
| 639 | 583 | FileNotFound, |
| 640 | 584 | NameTooLong, |
| 641 | InvalidUtf8, | |
| 642 | BadPathName, | |
| 643 | 585 | NoDevice, |
| 644 | 586 | Unexpected, |
| 645 | 587 | }; |
| 646 | 588 | |
| 647 | 589 | pub fn CreateSymbolicLink( |
| 648 | 590 | dir: ?HANDLE, |
| 649 | sym_link_path: []const u8, | |
| 650 | target_path: []const u8, | |
| 651 | is_directory: bool, | |
| 652 | ) CreateSymbolicLinkError!void { | |
| 653 | const sym_link_path_w = try sliceToPrefixedFileW(sym_link_path); | |
| 654 | const target_path_w = try sliceToPrefixedFileW(target_path); | |
| 655 | return CreateSymbolicLinkW(dir, sym_link_path_w.span(), target_path_w.span(), is_directory); | |
| 656 | } | |
| 657 | ||
| 658 | pub fn CreateSymbolicLinkW( | |
| 659 | dir: ?HANDLE, | |
| 660 | sym_link_path: [:0]const u16, | |
| 661 | target_path: [:0]const u16, | |
| 591 | sym_link_path: []const u16, | |
| 592 | target_path: []const u16, | |
| 662 | 593 | is_directory: bool, |
| 663 | 594 | ) CreateSymbolicLinkError!void { |
| 664 | 595 | const SYMLINK_DATA = extern struct { |
| ... | ... | @@ -672,71 +603,19 @@ pub fn CreateSymbolicLinkW( |
| 672 | 603 | Flags: ULONG, |
| 673 | 604 | }; |
| 674 | 605 | |
| 675 | var symlink_handle: HANDLE = undefined; | |
| 676 | if (is_directory) { | |
| 677 | const sym_link_len_bytes = math.cast(u16, sym_link_path.len * 2) catch |err| switch (err) { | |
| 678 | error.Overflow => return error.NameTooLong, | |
| 679 | }; | |
| 680 | var nt_name = UNICODE_STRING{ | |
| 681 | .Length = sym_link_len_bytes, | |
| 682 | .MaximumLength = sym_link_len_bytes, | |
| 683 | .Buffer = @intToPtr([*]u16, @ptrToInt(sym_link_path.ptr)), | |
| 684 | }; | |
| 685 | ||
| 686 | if (sym_link_path[0] == '.' and sym_link_path[1] == 0) { | |
| 687 | // Windows does not recognize this, but it does work with empty string. | |
| 688 | nt_name.Length = 0; | |
| 689 | } | |
| 690 | ||
| 691 | var attr = OBJECT_ATTRIBUTES{ | |
| 692 | .Length = @sizeOf(OBJECT_ATTRIBUTES), | |
| 693 | .RootDirectory = if (std.fs.path.isAbsoluteWindowsW(sym_link_path)) null else dir, | |
| 694 | .Attributes = 0, // Note we do not use OBJ_CASE_INSENSITIVE here. | |
| 695 | .ObjectName = &nt_name, | |
| 696 | .SecurityDescriptor = null, | |
| 697 | .SecurityQualityOfService = null, | |
| 698 | }; | |
| 699 | ||
| 700 | var io: IO_STATUS_BLOCK = undefined; | |
| 701 | const rc = ntdll.NtCreateFile( | |
| 702 | &symlink_handle, | |
| 703 | GENERIC_READ | SYNCHRONIZE | FILE_WRITE_ATTRIBUTES, | |
| 704 | &attr, | |
| 705 | &io, | |
| 706 | null, | |
| 707 | FILE_ATTRIBUTE_NORMAL, | |
| 708 | FILE_SHARE_READ, | |
| 709 | FILE_CREATE, | |
| 710 | FILE_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT | FILE_OPEN_FOR_BACKUP_INTENT, | |
| 711 | null, | |
| 712 | 0, | |
| 713 | ); | |
| 714 | switch (rc) { | |
| 715 | .SUCCESS => {}, | |
| 716 | .OBJECT_NAME_INVALID => unreachable, | |
| 717 | .OBJECT_NAME_NOT_FOUND => return error.FileNotFound, | |
| 718 | .OBJECT_PATH_NOT_FOUND => return error.FileNotFound, | |
| 719 | .NO_MEDIA_IN_DEVICE => return error.NoDevice, | |
| 720 | .INVALID_PARAMETER => unreachable, | |
| 721 | .ACCESS_DENIED => return error.AccessDenied, | |
| 722 | .OBJECT_PATH_SYNTAX_BAD => unreachable, | |
| 723 | .OBJECT_NAME_COLLISION => return error.PathAlreadyExists, | |
| 724 | else => return unexpectedStatus(rc), | |
| 725 | } | |
| 726 | } else { | |
| 727 | symlink_handle = OpenFile(sym_link_path, .{ | |
| 728 | .access_mask = SYNCHRONIZE | GENERIC_READ | GENERIC_WRITE, | |
| 729 | .dir = dir, | |
| 730 | .creation = FILE_CREATE, | |
| 731 | .io_mode = .blocking, | |
| 732 | }) catch |err| switch (err) { | |
| 733 | error.WouldBlock => unreachable, | |
| 734 | error.IsDir => return error.PathAlreadyExists, | |
| 735 | error.PipeBusy => unreachable, | |
| 736 | error.SharingViolation => return error.AccessDenied, | |
| 737 | else => |e| return e, | |
| 738 | }; | |
| 739 | } | |
| 606 | const symlink_handle = OpenFile(sym_link_path, .{ | |
| 607 | .access_mask = SYNCHRONIZE | GENERIC_READ | GENERIC_WRITE, | |
| 608 | .dir = dir, | |
| 609 | .creation = FILE_CREATE, | |
| 610 | .io_mode = .blocking, | |
| 611 | .open_dir = is_directory, | |
| 612 | }) catch |err| switch (err) { | |
| 613 | error.IsDir => return error.PathAlreadyExists, | |
| 614 | error.NotDir => unreachable, | |
| 615 | error.WouldBlock => unreachable, | |
| 616 | error.PipeBusy => unreachable, | |
| 617 | else => |e| return e, | |
| 618 | }; | |
| 740 | 619 | defer CloseHandle(symlink_handle); |
| 741 | 620 | |
| 742 | 621 | // prepare reparse data buffer |
| ... | ... | @@ -767,44 +646,32 @@ pub const ReadLinkError = error{ |
| 767 | 646 | Unexpected, |
| 768 | 647 | NameTooLong, |
| 769 | 648 | UnsupportedReparsePointType, |
| 770 | InvalidUtf8, | |
| 771 | BadPathName, | |
| 772 | 649 | }; |
| 773 | 650 | |
| 774 | pub fn ReadLink( | |
| 775 | dir: ?HANDLE, | |
| 776 | sub_path: []const u8, | |
| 777 | out_buffer: []u8, | |
| 778 | ) ReadLinkError![]u8 { | |
| 779 | const sub_path_w = try sliceToPrefixedFileW(sub_path); | |
| 780 | return ReadLinkW(dir, sub_path_w.span().ptr, out_buffer); | |
| 781 | } | |
| 782 | ||
| 783 | pub fn ReadLinkW(dir: ?HANDLE, sub_path_w: [*:0]const u16, out_buffer: []u8) ReadLinkError![]u8 { | |
| 784 | const path_len_bytes = math.cast(u16, mem.lenZ(sub_path_w) * 2) catch |err| switch (err) { | |
| 651 | pub fn ReadLink(dir: ?HANDLE, sub_path_w: []const u16, out_buffer: []u8) ReadLinkError![]u8 { | |
| 652 | // Here, we use `NtCreateFile` to shave off one syscall if we were to use `OpenFile` wrapper. | |
| 653 | // With the latter, we'd need to call `NtCreateFile` twice, once for file symlink, and if that | |
| 654 | // failed, again for dir symlink. Omitting any mention of file/dir flags makes it possible | |
| 655 | // to open the symlink there and then. | |
| 656 | const path_len_bytes = math.cast(u16, sub_path_w.len * 2) catch |err| switch (err) { | |
| 785 | 657 | error.Overflow => return error.NameTooLong, |
| 786 | 658 | }; |
| 787 | 659 | var nt_name = UNICODE_STRING{ |
| 788 | 660 | .Length = path_len_bytes, |
| 789 | 661 | .MaximumLength = path_len_bytes, |
| 790 | .Buffer = @intToPtr([*]u16, @ptrToInt(sub_path_w)), | |
| 662 | .Buffer = @intToPtr([*]u16, @ptrToInt(sub_path_w.ptr)), | |
| 791 | 663 | }; |
| 792 | ||
| 793 | if (sub_path_w[0] == '.' and sub_path_w[1] == 0) { | |
| 794 | // Windows does not recognize this, but it does work with empty string. | |
| 795 | nt_name.Length = 0; | |
| 796 | } | |
| 797 | ||
| 798 | 664 | var attr = OBJECT_ATTRIBUTES{ |
| 799 | 665 | .Length = @sizeOf(OBJECT_ATTRIBUTES), |
| 800 | .RootDirectory = if (std.fs.path.isAbsoluteWindowsW(sub_path_w)) null else dir, | |
| 666 | .RootDirectory = if (std.fs.path.isAbsoluteWindowsWTF16(sub_path_w)) null else dir, | |
| 801 | 667 | .Attributes = 0, // Note we do not use OBJ_CASE_INSENSITIVE here. |
| 802 | 668 | .ObjectName = &nt_name, |
| 803 | 669 | .SecurityDescriptor = null, |
| 804 | 670 | .SecurityQualityOfService = null, |
| 805 | 671 | }; |
| 806 | var io: IO_STATUS_BLOCK = undefined; | |
| 807 | 672 | var result_handle: HANDLE = undefined; |
| 673 | var io: IO_STATUS_BLOCK = undefined; | |
| 674 | ||
| 808 | 675 | const rc = ntdll.NtCreateFile( |
| 809 | 676 | &result_handle, |
| 810 | 677 | FILE_READ_ATTRIBUTES, |
| ... | ... | @@ -878,135 +745,83 @@ pub const DeleteFileError = error{ |
| 878 | 745 | NameTooLong, |
| 879 | 746 | FileBusy, |
| 880 | 747 | Unexpected, |
| 748 | NotDir, | |
| 749 | IsDir, | |
| 881 | 750 | }; |
| 882 | 751 | |
| 883 | pub fn DeleteFile(filename: []const u8) DeleteFileError!void { | |
| 884 | const filename_w = try sliceToPrefixedFileW(filename); | |
| 885 | return DeleteFileW(filename_w.span().ptr); | |
| 886 | } | |
| 887 | ||
| 888 | pub fn DeleteFileW(filename: [*:0]const u16) DeleteFileError!void { | |
| 889 | if (kernel32.DeleteFileW(filename) == 0) { | |
| 890 | switch (kernel32.GetLastError()) { | |
| 891 | .FILE_NOT_FOUND => return error.FileNotFound, | |
| 892 | .PATH_NOT_FOUND => return error.FileNotFound, | |
| 893 | .ACCESS_DENIED => return error.AccessDenied, | |
| 894 | .FILENAME_EXCED_RANGE => return error.NameTooLong, | |
| 895 | .INVALID_PARAMETER => return error.NameTooLong, | |
| 896 | .SHARING_VIOLATION => return error.FileBusy, | |
| 897 | else => |err| return unexpectedError(err), | |
| 898 | } | |
| 899 | } | |
| 900 | } | |
| 901 | ||
| 902 | pub const MoveFileError = error{Unexpected}; | |
| 903 | ||
| 904 | pub fn MoveFileEx(old_path: []const u8, new_path: []const u8, flags: DWORD) MoveFileError!void { | |
| 905 | const old_path_w = try sliceToPrefixedFileW(old_path); | |
| 906 | const new_path_w = try sliceToPrefixedFileW(new_path); | |
| 907 | return MoveFileExW(old_path_w.span().ptr, new_path_w.span().ptr, flags); | |
| 908 | } | |
| 909 | ||
| 910 | pub fn MoveFileExW(old_path: [*:0]const u16, new_path: [*:0]const u16, flags: DWORD) MoveFileError!void { | |
| 911 | if (kernel32.MoveFileExW(old_path, new_path, flags) == 0) { | |
| 912 | switch (kernel32.GetLastError()) { | |
| 913 | else => |err| return unexpectedError(err), | |
| 914 | } | |
| 915 | } | |
| 916 | } | |
| 917 | ||
| 918 | pub const CreateDirectoryError = error{ | |
| 919 | NameTooLong, | |
| 920 | PathAlreadyExists, | |
| 921 | FileNotFound, | |
| 922 | NoDevice, | |
| 923 | AccessDenied, | |
| 924 | InvalidUtf8, | |
| 925 | BadPathName, | |
| 926 | Unexpected, | |
| 752 | pub const DeleteFileOptions = struct { | |
| 753 | dir: ?HANDLE, | |
| 754 | remove_dir: bool = false, | |
| 927 | 755 | }; |
| 928 | 756 | |
| 929 | /// Returns an open directory handle which the caller is responsible for closing with `CloseHandle`. | |
| 930 | pub fn CreateDirectory(dir: ?HANDLE, pathname: []const u8, sa: ?*SECURITY_ATTRIBUTES) CreateDirectoryError!HANDLE { | |
| 931 | const pathname_w = try sliceToPrefixedFileW(pathname); | |
| 932 | return CreateDirectoryW(dir, pathname_w.span().ptr, sa); | |
| 933 | } | |
| 757 | pub fn DeleteFile(sub_path_w: []const u16, options: DeleteFileOptions) DeleteFileError!void { | |
| 758 | const create_options_flags: ULONG = if (options.remove_dir) | |
| 759 | FILE_DELETE_ON_CLOSE | FILE_DIRECTORY_FILE | FILE_OPEN_REPARSE_POINT | |
| 760 | else | |
| 761 | FILE_DELETE_ON_CLOSE | FILE_NON_DIRECTORY_FILE | FILE_OPEN_REPARSE_POINT; // would we ever want to delete the target instead? | |
| 934 | 762 | |
| 935 | /// Same as `CreateDirectory` except takes a WTF-16 encoded path. | |
| 936 | pub fn CreateDirectoryW( | |
| 937 | dir: ?HANDLE, | |
| 938 | sub_path_w: [*:0]const u16, | |
| 939 | sa: ?*SECURITY_ATTRIBUTES, | |
| 940 | ) CreateDirectoryError!HANDLE { | |
| 941 | const path_len_bytes = math.cast(u16, mem.lenZ(sub_path_w) * 2) catch |err| switch (err) { | |
| 942 | error.Overflow => return error.NameTooLong, | |
| 943 | }; | |
| 763 | const path_len_bytes = @intCast(u16, sub_path_w.len * 2); | |
| 944 | 764 | var nt_name = UNICODE_STRING{ |
| 945 | 765 | .Length = path_len_bytes, |
| 946 | 766 | .MaximumLength = path_len_bytes, |
| 947 | .Buffer = @intToPtr([*]u16, @ptrToInt(sub_path_w)), | |
| 767 | // The Windows API makes this mutable, but it will not mutate here. | |
| 768 | .Buffer = @intToPtr([*]u16, @ptrToInt(sub_path_w.ptr)), | |
| 948 | 769 | }; |
| 949 | 770 | |
| 950 | 771 | if (sub_path_w[0] == '.' and sub_path_w[1] == 0) { |
| 951 | 772 | // Windows does not recognize this, but it does work with empty string. |
| 952 | 773 | nt_name.Length = 0; |
| 953 | 774 | } |
| 775 | if (sub_path_w[0] == '.' and sub_path_w[1] == '.' and sub_path_w[2] == 0) { | |
| 776 | // Can't remove the parent directory with an open handle. | |
| 777 | return error.FileBusy; | |
| 778 | } | |
| 954 | 779 | |
| 955 | 780 | var attr = OBJECT_ATTRIBUTES{ |
| 956 | 781 | .Length = @sizeOf(OBJECT_ATTRIBUTES), |
| 957 | .RootDirectory = if (std.fs.path.isAbsoluteWindowsW(sub_path_w)) null else dir, | |
| 782 | .RootDirectory = if (std.fs.path.isAbsoluteWindowsWTF16(sub_path_w)) null else options.dir, | |
| 958 | 783 | .Attributes = 0, // Note we do not use OBJ_CASE_INSENSITIVE here. |
| 959 | 784 | .ObjectName = &nt_name, |
| 960 | .SecurityDescriptor = if (sa) |ptr| ptr.lpSecurityDescriptor else null, | |
| 785 | .SecurityDescriptor = null, | |
| 961 | 786 | .SecurityQualityOfService = null, |
| 962 | 787 | }; |
| 963 | 788 | var io: IO_STATUS_BLOCK = undefined; |
| 964 | var result_handle: HANDLE = undefined; | |
| 965 | const rc = ntdll.NtCreateFile( | |
| 966 | &result_handle, | |
| 967 | GENERIC_READ | SYNCHRONIZE, | |
| 789 | var tmp_handle: HANDLE = undefined; | |
| 790 | var rc = ntdll.NtCreateFile( | |
| 791 | &tmp_handle, | |
| 792 | SYNCHRONIZE | DELETE, | |
| 968 | 793 | &attr, |
| 969 | 794 | &io, |
| 970 | 795 | null, |
| 971 | FILE_ATTRIBUTE_NORMAL, | |
| 972 | FILE_SHARE_READ, | |
| 973 | FILE_CREATE, | |
| 974 | FILE_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT, | |
| 796 | 0, | |
| 797 | FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, | |
| 798 | FILE_OPEN, | |
| 799 | create_options_flags, | |
| 975 | 800 | null, |
| 976 | 801 | 0, |
| 977 | 802 | ); |
| 978 | 803 | switch (rc) { |
| 979 | .SUCCESS => return result_handle, | |
| 804 | .SUCCESS => return CloseHandle(tmp_handle), | |
| 980 | 805 | .OBJECT_NAME_INVALID => unreachable, |
| 981 | 806 | .OBJECT_NAME_NOT_FOUND => return error.FileNotFound, |
| 982 | .OBJECT_PATH_NOT_FOUND => return error.FileNotFound, | |
| 983 | .NO_MEDIA_IN_DEVICE => return error.NoDevice, | |
| 984 | 807 | .INVALID_PARAMETER => unreachable, |
| 985 | .ACCESS_DENIED => return error.AccessDenied, | |
| 986 | .OBJECT_PATH_SYNTAX_BAD => unreachable, | |
| 987 | .OBJECT_NAME_COLLISION => return error.PathAlreadyExists, | |
| 808 | .FILE_IS_A_DIRECTORY => return error.IsDir, | |
| 809 | .NOT_A_DIRECTORY => return error.NotDir, | |
| 988 | 810 | else => return unexpectedStatus(rc), |
| 989 | 811 | } |
| 990 | 812 | } |
| 991 | 813 | |
| 992 | pub const RemoveDirectoryError = error{ | |
| 993 | FileNotFound, | |
| 994 | DirNotEmpty, | |
| 995 | Unexpected, | |
| 996 | NotDir, | |
| 997 | }; | |
| 814 | pub const MoveFileError = error{Unexpected}; | |
| 998 | 815 | |
| 999 | pub fn RemoveDirectory(dir_path: []const u8) RemoveDirectoryError!void { | |
| 1000 | const dir_path_w = try sliceToPrefixedFileW(dir_path); | |
| 1001 | return RemoveDirectoryW(dir_path_w.span().ptr); | |
| 816 | pub fn MoveFileEx(old_path: []const u8, new_path: []const u8, flags: DWORD) MoveFileError!void { | |
| 817 | const old_path_w = try sliceToPrefixedFileW(old_path); | |
| 818 | const new_path_w = try sliceToPrefixedFileW(new_path); | |
| 819 | return MoveFileExW(old_path_w.span().ptr, new_path_w.span().ptr, flags); | |
| 1002 | 820 | } |
| 1003 | 821 | |
| 1004 | pub fn RemoveDirectoryW(dir_path_w: [*:0]const u16) RemoveDirectoryError!void { | |
| 1005 | if (kernel32.RemoveDirectoryW(dir_path_w) == 0) { | |
| 822 | pub fn MoveFileExW(old_path: [*:0]const u16, new_path: [*:0]const u16, flags: DWORD) MoveFileError!void { | |
| 823 | if (kernel32.MoveFileExW(old_path, new_path, flags) == 0) { | |
| 1006 | 824 | switch (kernel32.GetLastError()) { |
| 1007 | .PATH_NOT_FOUND => return error.FileNotFound, | |
| 1008 | .DIR_NOT_EMPTY => return error.DirNotEmpty, | |
| 1009 | .DIRECTORY => return error.NotDir, | |
| 1010 | 825 | else => |err| return unexpectedError(err), |
| 1011 | 826 | } |
| 1012 | 827 | } |
| ... | ... | @@ -1493,8 +1308,7 @@ pub fn cStrToPrefixedFileW(s: [*:0]const u8) !PathSpace { |
| 1493 | 1308 | } |
| 1494 | 1309 | |
| 1495 | 1310 | /// Converts the path `s` to WTF16, null-terminated. If the path is absolute, |
| 1496 | /// it will get NT-style prefix `\??\` prepended automatically. For prepending | |
| 1497 | /// Win32-style prefix, see `sliceToWin32PrefixedFileW` instead. | |
| 1311 | /// it will get NT-style prefix `\??\` prepended automatically. | |
| 1498 | 1312 | pub fn sliceToPrefixedFileW(s: []const u8) !PathSpace { |
| 1499 | 1313 | // TODO https://github.com/ziglang/zig/issues/2765 |
| 1500 | 1314 | var path_space: PathSpace = undefined; |