| author | |
| committer | |
| log | 6df35c5877c1a1060dcfd28d5d4092638e6e6754 |
| tree | e3320200a4ee2754ec60255ab22388af79a8cba0 |
| parent | cf15b4c2690ae7884e7bdb9259e6497c18213d35 |
| parent | 4b0ab0c1864677d0c50b2a8e12a708a6640d1734 |
| signature |
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,6 +364,7 @@ pub const ChildProcess = struct { |
| 364 | error.FileTooBig => unreachable, | 364 | error.FileTooBig => unreachable, |
| 365 | error.DeviceBusy => unreachable, | 365 | error.DeviceBusy => unreachable, |
| 366 | error.FileLocksNotSupported => unreachable, | 366 | error.FileLocksNotSupported => unreachable, |
| 367 | error.BadPathName => unreachable, // Windows-only | ||
| 367 | else => |e| return e, | 368 | else => |e| return e, |
| 368 | } | 369 | } |
| 369 | else | 370 | else |
| ... | @@ -480,25 +481,20 @@ pub const ChildProcess = struct { | ... | @@ -480,25 +481,20 @@ pub const ChildProcess = struct { |
| 480 | 481 | ||
| 481 | const any_ignore = (self.stdin_behavior == StdIo.Ignore or self.stdout_behavior == StdIo.Ignore or self.stderr_behavior == StdIo.Ignore); | 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 | const nul_handle = if (any_ignore) | 484 | const nul_handle = if (any_ignore) |
| 485 | windows.CreateFile( | 485 | windows.OpenFile(&[_]u16{ 'N', 'U', 'L' }, .{ |
| 486 | "NUL", | 486 | .dir = std.fs.cwd().fd, |
| 487 | windows.GENERIC_READ, | 487 | .access_mask = windows.GENERIC_READ | windows.SYNCHRONIZE, |
| 488 | windows.FILE_SHARE_READ, | 488 | .share_access = windows.FILE_SHARE_READ, |
| 489 | null, | 489 | .creation = windows.OPEN_EXISTING, |
| 490 | windows.OPEN_EXISTING, | 490 | .io_mode = .blocking, |
| 491 | windows.FILE_ATTRIBUTE_NORMAL, | 491 | }) catch |err| switch (err) { |
| 492 | null, | ||
| 493 | ) catch |err| switch (err) { | ||
| 494 | error.SharingViolation => unreachable, // not possible for "NUL" | ||
| 495 | error.PathAlreadyExists => unreachable, // not possible for "NUL" | 492 | error.PathAlreadyExists => unreachable, // not possible for "NUL" |
| 496 | error.PipeBusy => unreachable, // not possible for "NUL" | 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 | error.FileNotFound => unreachable, // not possible for "NUL" | 494 | error.FileNotFound => unreachable, // not possible for "NUL" |
| 500 | error.AccessDenied => unreachable, // not possible for "NUL" | 495 | error.AccessDenied => unreachable, // not possible for "NUL" |
| 501 | error.NameTooLong => unreachable, // not possible for "NUL" | 496 | error.NameTooLong => unreachable, // not possible for "NUL" |
| 497 | error.WouldBlock => unreachable, // not possible for "NUL" | ||
| 502 | else => |e| return e, | 498 | else => |e| return e, |
| 503 | } | 499 | } |
| 504 | else | 500 | else |
lib/std/fs.zig+36-22| ... | @@ -225,8 +225,7 @@ pub fn makeDirAbsoluteZ(absolute_path_z: [*:0]const u8) !void { | ... | @@ -225,8 +225,7 @@ pub fn makeDirAbsoluteZ(absolute_path_z: [*:0]const u8) !void { |
| 225 | /// Same as `makeDirAbsolute` except the parameter is a null-terminated WTF-16 encoded string. | 225 | /// Same as `makeDirAbsolute` except the parameter is a null-terminated WTF-16 encoded string. |
| 226 | pub fn makeDirAbsoluteW(absolute_path_w: [*:0]const u16) !void { | 226 | pub fn makeDirAbsoluteW(absolute_path_w: [*:0]const u16) !void { |
| 227 | assert(path.isAbsoluteWindowsW(absolute_path_w)); | 227 | assert(path.isAbsoluteWindowsW(absolute_path_w)); |
| 228 | const handle = try os.windows.CreateDirectoryW(null, absolute_path_w, null); | 228 | return os.mkdirW(absolute_path_w, default_new_dir_mode); |
| 229 | os.windows.CloseHandle(handle); | ||
| 230 | } | 229 | } |
| 231 | 230 | ||
| 232 | pub const deleteDir = @compileError("deprecated; use dir.deleteDir or deleteDirAbsolute"); | 231 | pub const deleteDir = @compileError("deprecated; use dir.deleteDir or deleteDirAbsolute"); |
| ... | @@ -881,8 +880,7 @@ pub const Dir = struct { | ... | @@ -881,8 +880,7 @@ pub const Dir = struct { |
| 881 | } | 880 | } |
| 882 | 881 | ||
| 883 | pub fn makeDirW(self: Dir, sub_path: [*:0]const u16) !void { | 882 | pub fn makeDirW(self: Dir, sub_path: [*:0]const u16) !void { |
| 884 | const handle = try os.windows.CreateDirectoryW(self.fd, sub_path, null); | 883 | try os.mkdiratW(self.fd, sub_path, default_new_dir_mode); |
| 885 | os.windows.CloseHandle(handle); | ||
| 886 | } | 884 | } |
| 887 | 885 | ||
| 888 | /// Calls makeDir recursively to make an entire path. Returns success if the path | 886 | /// Calls makeDir recursively to make an entire path. Returns success if the path |
| ... | @@ -1119,7 +1117,7 @@ pub const Dir = struct { | ... | @@ -1119,7 +1117,7 @@ pub const Dir = struct { |
| 1119 | pub fn deleteFile(self: Dir, sub_path: []const u8) DeleteFileError!void { | 1117 | pub fn deleteFile(self: Dir, sub_path: []const u8) DeleteFileError!void { |
| 1120 | if (builtin.os.tag == .windows) { | 1118 | if (builtin.os.tag == .windows) { |
| 1121 | const sub_path_w = try os.windows.sliceToPrefixedFileW(sub_path); | 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 | } else if (builtin.os.tag == .wasi) { | 1121 | } else if (builtin.os.tag == .wasi) { |
| 1124 | os.unlinkatWasi(self.fd, sub_path, 0) catch |err| switch (err) { | 1122 | os.unlinkatWasi(self.fd, sub_path, 0) catch |err| switch (err) { |
| 1125 | error.DirNotEmpty => unreachable, // not passing AT_REMOVEDIR | 1123 | error.DirNotEmpty => unreachable, // not passing AT_REMOVEDIR |
| ... | @@ -1153,7 +1151,7 @@ pub const Dir = struct { | ... | @@ -1153,7 +1151,7 @@ pub const Dir = struct { |
| 1153 | } | 1151 | } |
| 1154 | 1152 | ||
| 1155 | /// Same as `deleteFile` except the parameter is WTF-16 encoded. | 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 | os.unlinkatW(self.fd, sub_path_w, 0) catch |err| switch (err) { | 1155 | os.unlinkatW(self.fd, sub_path_w, 0) catch |err| switch (err) { |
| 1158 | error.DirNotEmpty => unreachable, // not passing AT_REMOVEDIR | 1156 | error.DirNotEmpty => unreachable, // not passing AT_REMOVEDIR |
| 1159 | else => |e| return e, | 1157 | else => |e| return e, |
| ... | @@ -1182,7 +1180,7 @@ pub const Dir = struct { | ... | @@ -1182,7 +1180,7 @@ pub const Dir = struct { |
| 1182 | pub fn deleteDir(self: Dir, sub_path: []const u8) DeleteDirError!void { | 1180 | pub fn deleteDir(self: Dir, sub_path: []const u8) DeleteDirError!void { |
| 1183 | if (builtin.os.tag == .windows) { | 1181 | if (builtin.os.tag == .windows) { |
| 1184 | const sub_path_w = try os.windows.sliceToPrefixedFileW(sub_path); | 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 | } else if (builtin.os.tag == .wasi) { | 1184 | } else if (builtin.os.tag == .wasi) { |
| 1187 | os.unlinkat(self.fd, sub_path, os.AT_REMOVEDIR) catch |err| switch (err) { | 1185 | os.unlinkat(self.fd, sub_path, os.AT_REMOVEDIR) catch |err| switch (err) { |
| 1188 | error.IsDir => unreachable, // not possible since we pass AT_REMOVEDIR | 1186 | error.IsDir => unreachable, // not possible since we pass AT_REMOVEDIR |
| ... | @@ -1204,7 +1202,7 @@ pub const Dir = struct { | ... | @@ -1204,7 +1202,7 @@ pub const Dir = struct { |
| 1204 | 1202 | ||
| 1205 | /// Same as `deleteDir` except the parameter is UTF16LE, NT prefixed. | 1203 | /// Same as `deleteDir` except the parameter is UTF16LE, NT prefixed. |
| 1206 | /// This function is Windows-only. | 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 | os.unlinkatW(self.fd, sub_path_w, os.AT_REMOVEDIR) catch |err| switch (err) { | 1206 | os.unlinkatW(self.fd, sub_path_w, os.AT_REMOVEDIR) catch |err| switch (err) { |
| 1209 | error.IsDir => unreachable, // not possible since we pass AT_REMOVEDIR | 1207 | error.IsDir => unreachable, // not possible since we pass AT_REMOVEDIR |
| 1210 | else => |e| return e, | 1208 | else => |e| return e, |
| ... | @@ -1263,11 +1261,11 @@ pub const Dir = struct { | ... | @@ -1263,11 +1261,11 @@ pub const Dir = struct { |
| 1263 | /// are null-terminated, WTF16 encoded. | 1261 | /// are null-terminated, WTF16 encoded. |
| 1264 | pub fn symLinkW( | 1262 | pub fn symLinkW( |
| 1265 | self: Dir, | 1263 | self: Dir, |
| 1266 | target_path_w: [:0]const u16, | 1264 | target_path_w: []const u16, |
| 1267 | sym_link_path_w: [:0]const u16, | 1265 | sym_link_path_w: []const u16, |
| 1268 | flags: SymLinkFlags, | 1266 | flags: SymLinkFlags, |
| 1269 | ) !void { | 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 | /// Read value of a symbolic link. | 1271 | /// Read value of a symbolic link. |
| ... | @@ -1278,7 +1276,8 @@ pub const Dir = struct { | ... | @@ -1278,7 +1276,8 @@ pub const Dir = struct { |
| 1278 | return self.readLinkWasi(sub_path, buffer); | 1276 | return self.readLinkWasi(sub_path, buffer); |
| 1279 | } | 1277 | } |
| 1280 | if (builtin.os.tag == .windows) { | 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 | const sub_path_c = try os.toPosixPath(sub_path); | 1282 | const sub_path_c = try os.toPosixPath(sub_path); |
| 1284 | return self.readLinkZ(&sub_path_c, buffer); | 1283 | return self.readLinkZ(&sub_path_c, buffer); |
| ... | @@ -1295,15 +1294,15 @@ pub const Dir = struct { | ... | @@ -1295,15 +1294,15 @@ pub const Dir = struct { |
| 1295 | pub fn readLinkZ(self: Dir, sub_path_c: [*:0]const u8, buffer: []u8) ![]u8 { | 1294 | pub fn readLinkZ(self: Dir, sub_path_c: [*:0]const u8, buffer: []u8) ![]u8 { |
| 1296 | if (builtin.os.tag == .windows) { | 1295 | if (builtin.os.tag == .windows) { |
| 1297 | const sub_path_w = try os.windows.cStrToPrefixedFileW(sub_path_c); | 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 | return os.readlinkatZ(self.fd, sub_path_c, buffer); | 1299 | return os.readlinkatZ(self.fd, sub_path_c, buffer); |
| 1301 | } | 1300 | } |
| 1302 | 1301 | ||
| 1303 | /// Windows-only. Same as `readLink` except the pathname parameter | 1302 | /// Windows-only. Same as `readLink` except the pathname parameter |
| 1304 | /// is null-terminated, WTF16 encoded. | 1303 | /// is null-terminated, WTF16 encoded. |
| 1305 | pub fn readLinkW(self: Dir, sub_path_w: [*:0]const u16, buffer: []u8) ![]u8 { | 1304 | pub fn readLinkW(self: Dir, sub_path_w: []const u16, buffer: []u8) ![]u8 { |
| 1306 | return os.windows.ReadLinkW(self.fd, sub_path_w, buffer); | 1305 | return os.windows.ReadLink(self.fd, sub_path_w, buffer); |
| 1307 | } | 1306 | } |
| 1308 | 1307 | ||
| 1309 | /// On success, caller owns returned buffer. | 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,7 +1812,9 @@ pub fn symLinkAbsolute(target_path: []const u8, sym_link_path: []const u8, flags |
| 1813 | assert(path.isAbsolute(target_path)); | 1812 | assert(path.isAbsolute(target_path)); |
| 1814 | assert(path.isAbsolute(sym_link_path)); | 1813 | assert(path.isAbsolute(sym_link_path)); |
| 1815 | if (builtin.os.tag == .windows) { | 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 | return os.symlink(target_path, sym_link_path); | 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,10 +1823,10 @@ pub fn symLinkAbsolute(target_path: []const u8, sym_link_path: []const u8, flags |
| 1822 | /// Note that this function will by default try creating a symbolic link to a file. If you would | 1823 | /// Note that this function will by default try creating a symbolic link to a file. If you would |
| 1823 | /// like to create a symbolic link to a directory, specify this with `SymLinkFlags{ .is_directory = true }`. | 1824 | /// like to create a symbolic link to a directory, specify this with `SymLinkFlags{ .is_directory = true }`. |
| 1824 | /// See also `symLinkAbsolute`, `symLinkAbsoluteZ`. | 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 | pub fn symLinkAbsoluteW(target_path_w: []const u16, sym_link_path_w: []const u16, flags: SymLinkFlags) !void { |
| 1826 | assert(path.isAbsoluteWindowsW(target_path_w)); | 1827 | assert(path.isAbsoluteWindowsWTF16(target_path_w)); |
| 1827 | assert(path.isAbsoluteWindowsW(sym_link_path_w)); | 1828 | assert(path.isAbsoluteWindowsWTF16(sym_link_path_w)); |
| 1828 | return os.windows.CreateSymbolicLinkW(null, sym_link_path_w, target_path_w, flags.is_directory); | 1829 | return os.windows.CreateSymbolicLink(null, sym_link_path_w, target_path_w, flags.is_directory); |
| 1829 | } | 1830 | } |
| 1830 | 1831 | ||
| 1831 | /// Same as `symLinkAbsolute` except the parameters are null-terminated pointers. | 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,7 +1837,7 @@ pub fn symLinkAbsoluteZ(target_path_c: [*:0]const u8, sym_link_path_c: [*:0]cons |
| 1836 | if (builtin.os.tag == .windows) { | 1837 | if (builtin.os.tag == .windows) { |
| 1837 | const target_path_w = try os.windows.cStrToWin32PrefixedFileW(target_path_c); | 1838 | const target_path_w = try os.windows.cStrToWin32PrefixedFileW(target_path_c); |
| 1838 | const sym_link_path_w = try os.windows.cStrToWin32PrefixedFileW(sym_link_path_c); | 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 | return os.symlinkZ(target_path_c, sym_link_path_c); | 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,7 +1939,20 @@ pub fn walkPath(allocator: *Allocator, dir_path: []const u8) !Walker { |
| 1938 | return walker; | 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 | pub fn openSelfExe(flags: File.OpenFlags) OpenSelfExeError!File { | 1957 | pub fn openSelfExe(flags: File.OpenFlags) OpenSelfExeError!File { |
| 1944 | if (builtin.os.tag == .linux) { | 1958 | if (builtin.os.tag == .linux) { |
lib/std/fs/file.zig+14-1| ... | @@ -47,7 +47,20 @@ pub const File = struct { | ... | @@ -47,7 +47,20 @@ pub const File = struct { |
| 47 | else => 0o666, | 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 | pub const Lock = enum { None, Shared, Exclusive }; | 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,15 +374,13 @@ pub fn Watch(comptime V: type) type { |
| 374 | defer if (!basename_utf16le_null_consumed) self.allocator.free(basename_utf16le_null); | 374 | defer if (!basename_utf16le_null_consumed) self.allocator.free(basename_utf16le_null); |
| 375 | const basename_utf16le_no_null = basename_utf16le_null[0 .. basename_utf16le_null.len - 1]; | 375 | const basename_utf16le_no_null = basename_utf16le_null[0 .. basename_utf16le_null.len - 1]; |
| 376 | 376 | ||
| 377 | const dir_handle = try windows.CreateFileW( | 377 | const dir_handle = try windows.OpenFile(dirname_utf16le, .{ |
| 378 | dirname_utf16le.ptr, | 378 | .dir = std.fs.cwd().fd, |
| 379 | windows.FILE_LIST_DIRECTORY, | 379 | .access_mask = windows.FILE_LIST_DIRECTORY, |
| 380 | windows.FILE_SHARE_READ | windows.FILE_SHARE_DELETE | windows.FILE_SHARE_WRITE, | 380 | .creation = windows.FILE_OPEN, |
| 381 | null, | 381 | .io_mode = .blocking, |
| 382 | windows.OPEN_EXISTING, | 382 | .open_dir = true, |
| 383 | windows.FILE_FLAG_BACKUP_SEMANTICS | windows.FILE_FLAG_OVERLAPPED, | 383 | }); |
| 384 | null, | ||
| 385 | ); | ||
| 386 | var dir_handle_consumed = false; | 384 | var dir_handle_consumed = false; |
| 387 | defer if (!dir_handle_consumed) windows.CloseHandle(dir_handle); | 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,6 +1041,9 @@ pub const OpenError = error{ |
| 1041 | 1041 | ||
| 1042 | /// The underlying filesystem does not support file locks | 1042 | /// The underlying filesystem does not support file locks |
| 1043 | FileLocksNotSupported, | 1043 | FileLocksNotSupported, |
| 1044 | |||
| 1045 | BadPathName, | ||
| 1046 | InvalidUtf8, | ||
| 1044 | } || UnexpectedError; | 1047 | } || UnexpectedError; |
| 1045 | 1048 | ||
| 1046 | /// Open and possibly create a file. Keeps trying if it gets interrupted. | 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,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 | /// Windows-only. The path parameter is | 1131 | /// Windows-only. The path parameter is |
| 1096 | /// [WTF-16](https://simonsapin.github.io/wtf-8/#potentially-ill-formed-utf-16) encoded. | 1132 | /// [WTF-16](https://simonsapin.github.io/wtf-8/#potentially-ill-formed-utf-16) encoded. |
| 1097 | /// Translates the POSIX open API call to a Windows API call. | 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 { | 1134 | /// TODO currently, this function does not handle all flag combinations |
| 1099 | @compileError("TODO implement openW for windows"); | 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 | /// Open and possibly create a file. Keeps trying if it gets interrupted. | 1146 | /// Open and possibly create a file. Keeps trying if it gets interrupted. |
| 1103 | /// `file_path` is relative to the open directory handle `dir_fd`. | 1147 | /// `file_path` is relative to the open directory handle `dir_fd`. |
| 1104 | /// See also `openatC`. | 1148 | /// See also `openatC`. |
| 1105 | /// TODO support windows | ||
| 1106 | pub fn openat(dir_fd: fd_t, file_path: []const u8, flags: u32, mode: mode_t) OpenError!fd_t { | 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 | const file_path_c = try toPosixPath(file_path); | 1157 | const file_path_c = try toPosixPath(file_path); |
| 1108 | return openatZ(dir_fd, &file_path_c, flags, mode); | 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,8 +1195,11 @@ pub const openatC = @compileError("deprecated: renamed to openatZ"); |
| 1145 | /// Open and possibly create a file. Keeps trying if it gets interrupted. | 1195 | /// Open and possibly create a file. Keeps trying if it gets interrupted. |
| 1146 | /// `file_path` is relative to the open directory handle `dir_fd`. | 1196 | /// `file_path` is relative to the open directory handle `dir_fd`. |
| 1147 | /// See also `openat`. | 1197 | /// See also `openat`. |
| 1148 | /// TODO support windows | ||
| 1149 | pub fn openatZ(dir_fd: fd_t, file_path: [*:0]const u8, flags: u32, mode: mode_t) OpenError!fd_t { | 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 | while (true) { | 1203 | while (true) { |
| 1151 | const rc = system.openat(dir_fd, file_path, flags, mode); | 1204 | const rc = system.openat(dir_fd, file_path, flags, mode); |
| 1152 | switch (errno(rc)) { | 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,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 | pub fn dup2(old_fd: fd_t, new_fd: fd_t) !void { | 1247 | pub fn dup2(old_fd: fd_t, new_fd: fd_t) !void { |
| 1181 | while (true) { | 1248 | while (true) { |
| 1182 | switch (errno(system.dup2(old_fd, new_fd))) { | 1249 | switch (errno(system.dup2(old_fd, new_fd))) { |
| ... | @@ -1683,7 +1750,7 @@ pub fn unlink(file_path: []const u8) UnlinkError!void { | ... | @@ -1683,7 +1750,7 @@ pub fn unlink(file_path: []const u8) UnlinkError!void { |
| 1683 | @compileError("unlink is not supported in WASI; use unlinkat instead"); | 1750 | @compileError("unlink is not supported in WASI; use unlinkat instead"); |
| 1684 | } else if (builtin.os.tag == .windows) { | 1751 | } else if (builtin.os.tag == .windows) { |
| 1685 | const file_path_w = try windows.sliceToPrefixedFileW(file_path); | 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 | } else { | 1754 | } else { |
| 1688 | const file_path_c = try toPosixPath(file_path); | 1755 | const file_path_c = try toPosixPath(file_path); |
| 1689 | return unlinkZ(&file_path_c); | 1756 | return unlinkZ(&file_path_c); |
| ... | @@ -1696,7 +1763,7 @@ pub const unlinkC = @compileError("deprecated: renamed to unlinkZ"); | ... | @@ -1696,7 +1763,7 @@ pub const unlinkC = @compileError("deprecated: renamed to unlinkZ"); |
| 1696 | pub fn unlinkZ(file_path: [*:0]const u8) UnlinkError!void { | 1763 | pub fn unlinkZ(file_path: [*:0]const u8) UnlinkError!void { |
| 1697 | if (builtin.os.tag == .windows) { | 1764 | if (builtin.os.tag == .windows) { |
| 1698 | const file_path_w = try windows.cStrToPrefixedFileW(file_path); | 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 | switch (errno(system.unlink(file_path))) { | 1768 | switch (errno(system.unlink(file_path))) { |
| 1702 | 0 => return, | 1769 | 0 => return, |
| ... | @@ -1717,6 +1784,11 @@ pub fn unlinkZ(file_path: [*:0]const u8) UnlinkError!void { | ... | @@ -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 | pub const UnlinkatError = UnlinkError || error{ | 1792 | pub const UnlinkatError = UnlinkError || error{ |
| 1721 | /// When passing `AT_REMOVEDIR`, this error occurs when the named directory is not empty. | 1793 | /// When passing `AT_REMOVEDIR`, this error occurs when the named directory is not empty. |
| 1722 | DirNotEmpty, | 1794 | DirNotEmpty, |
| ... | @@ -1727,7 +1799,7 @@ pub const UnlinkatError = UnlinkError || error{ | ... | @@ -1727,7 +1799,7 @@ pub const UnlinkatError = UnlinkError || error{ |
| 1727 | pub fn unlinkat(dirfd: fd_t, file_path: []const u8, flags: u32) UnlinkatError!void { | 1799 | pub fn unlinkat(dirfd: fd_t, file_path: []const u8, flags: u32) UnlinkatError!void { |
| 1728 | if (builtin.os.tag == .windows) { | 1800 | if (builtin.os.tag == .windows) { |
| 1729 | const file_path_w = try windows.sliceToPrefixedFileW(file_path); | 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 | } else if (builtin.os.tag == .wasi) { | 1803 | } else if (builtin.os.tag == .wasi) { |
| 1732 | return unlinkatWasi(dirfd, file_path, flags); | 1804 | return unlinkatWasi(dirfd, file_path, flags); |
| 1733 | } else { | 1805 | } else { |
| ... | @@ -1774,7 +1846,7 @@ pub fn unlinkatWasi(dirfd: fd_t, file_path: []const u8, flags: u32) UnlinkatErro | ... | @@ -1774,7 +1846,7 @@ pub fn unlinkatWasi(dirfd: fd_t, file_path: []const u8, flags: u32) UnlinkatErro |
| 1774 | pub fn unlinkatZ(dirfd: fd_t, file_path_c: [*:0]const u8, flags: u32) UnlinkatError!void { | 1846 | pub fn unlinkatZ(dirfd: fd_t, file_path_c: [*:0]const u8, flags: u32) UnlinkatError!void { |
| 1775 | if (builtin.os.tag == .windows) { | 1847 | if (builtin.os.tag == .windows) { |
| 1776 | const file_path_w = try windows.cStrToPrefixedFileW(file_path_c); | 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 | switch (errno(system.unlinkat(dirfd, file_path_c, flags))) { | 1851 | switch (errno(system.unlinkat(dirfd, file_path_c, flags))) { |
| 1780 | 0 => return, | 1852 | 0 => return, |
| ... | @@ -1800,67 +1872,9 @@ pub fn unlinkatZ(dirfd: fd_t, file_path_c: [*:0]const u8, flags: u32) UnlinkatEr | ... | @@ -1800,67 +1872,9 @@ pub fn unlinkatZ(dirfd: fd_t, file_path_c: [*:0]const u8, flags: u32) UnlinkatEr |
| 1800 | } | 1872 | } |
| 1801 | 1873 | ||
| 1802 | /// Same as `unlinkat` but `sub_path_w` is UTF16LE, NT prefixed. Windows only. | 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 { | 1875 | pub fn unlinkatW(dirfd: fd_t, sub_path_w: []const u16, flags: u32) UnlinkatError!void { |
| 1804 | const w = windows; | 1876 | const remove_dir = (flags & AT_REMOVEDIR) != 0; |
| 1805 | 1877 | return windows.DeleteFile(sub_path_w, .{ .dir = dirfd, .remove_dir = remove_dir }); | |
| 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 | } | ||
| 1864 | } | 1878 | } |
| 1865 | 1879 | ||
| 1866 | const RenameError = error{ | 1880 | const RenameError = error{ |
| ... | @@ -2087,7 +2101,7 @@ pub fn renameatW( | ... | @@ -2087,7 +2101,7 @@ pub fn renameatW( |
| 2087 | pub fn mkdirat(dir_fd: fd_t, sub_dir_path: []const u8, mode: u32) MakeDirError!void { | 2101 | pub fn mkdirat(dir_fd: fd_t, sub_dir_path: []const u8, mode: u32) MakeDirError!void { |
| 2088 | if (builtin.os.tag == .windows) { | 2102 | if (builtin.os.tag == .windows) { |
| 2089 | const sub_dir_path_w = try windows.sliceToPrefixedFileW(sub_dir_path); | 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 | } else if (builtin.os.tag == .wasi) { | 2105 | } else if (builtin.os.tag == .wasi) { |
| 2092 | return mkdiratWasi(dir_fd, sub_dir_path, mode); | 2106 | return mkdiratWasi(dir_fd, sub_dir_path, mode); |
| 2093 | } else { | 2107 | } else { |
| ... | @@ -2145,8 +2159,19 @@ pub fn mkdiratZ(dir_fd: fd_t, sub_dir_path: [*:0]const u8, mode: u32) MakeDirErr | ... | @@ -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 { | 2162 | pub fn mkdiratW(dir_fd: fd_t, sub_path_w: []const u16, mode: u32) MakeDirError!void { |
| 2149 | const sub_dir_handle = try windows.CreateDirectoryW(dir_fd, sub_path_w, null); | 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 | windows.CloseHandle(sub_dir_handle); | 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,9 +2200,8 @@ pub fn mkdir(dir_path: []const u8, mode: u32) MakeDirError!void { |
| 2175 | if (builtin.os.tag == .wasi) { | 2200 | if (builtin.os.tag == .wasi) { |
| 2176 | @compileError("mkdir is not supported in WASI; use mkdirat instead"); | 2201 | @compileError("mkdir is not supported in WASI; use mkdirat instead"); |
| 2177 | } else if (builtin.os.tag == .windows) { | 2202 | } else if (builtin.os.tag == .windows) { |
| 2178 | const sub_dir_handle = try windows.CreateDirectory(null, dir_path, null); | 2203 | const dir_path_w = try windows.sliceToPrefixedFileW(dir_path); |
| 2179 | windows.CloseHandle(sub_dir_handle); | 2204 | return mkdirW(dir_path_w.span(), mode); |
| 2180 | return; | ||
| 2181 | } else { | 2205 | } else { |
| 2182 | const dir_path_c = try toPosixPath(dir_path); | 2206 | const dir_path_c = try toPosixPath(dir_path); |
| 2183 | return mkdirZ(&dir_path_c, mode); | 2207 | return mkdirZ(&dir_path_c, mode); |
| ... | @@ -2188,9 +2212,7 @@ pub fn mkdir(dir_path: []const u8, mode: u32) MakeDirError!void { | ... | @@ -2188,9 +2212,7 @@ pub fn mkdir(dir_path: []const u8, mode: u32) MakeDirError!void { |
| 2188 | pub fn mkdirZ(dir_path: [*:0]const u8, mode: u32) MakeDirError!void { | 2212 | pub fn mkdirZ(dir_path: [*:0]const u8, mode: u32) MakeDirError!void { |
| 2189 | if (builtin.os.tag == .windows) { | 2213 | if (builtin.os.tag == .windows) { |
| 2190 | const dir_path_w = try windows.cStrToPrefixedFileW(dir_path); | 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); | 2215 | return mkdirW(dir_path_w.span(), mode); |
| 2192 | windows.CloseHandle(sub_dir_handle); | ||
| 2193 | return; | ||
| 2194 | } | 2216 | } |
| 2195 | switch (errno(system.mkdir(dir_path, mode))) { | 2217 | switch (errno(system.mkdir(dir_path, mode))) { |
| 2196 | 0 => return, | 2218 | 0 => return, |
| ... | @@ -2211,6 +2233,23 @@ pub fn mkdirZ(dir_path: [*:0]const u8, mode: u32) MakeDirError!void { | ... | @@ -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 | pub const DeleteDirError = error{ | 2253 | pub const DeleteDirError = error{ |
| 2215 | AccessDenied, | 2254 | AccessDenied, |
| 2216 | FileBusy, | 2255 | FileBusy, |
| ... | @@ -2231,7 +2270,7 @@ pub fn rmdir(dir_path: []const u8) DeleteDirError!void { | ... | @@ -2231,7 +2270,7 @@ pub fn rmdir(dir_path: []const u8) DeleteDirError!void { |
| 2231 | @compileError("rmdir is not supported in WASI; use unlinkat instead"); | 2270 | @compileError("rmdir is not supported in WASI; use unlinkat instead"); |
| 2232 | } else if (builtin.os.tag == .windows) { | 2271 | } else if (builtin.os.tag == .windows) { |
| 2233 | const dir_path_w = try windows.sliceToPrefixedFileW(dir_path); | 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 | } else { | 2274 | } else { |
| 2236 | const dir_path_c = try toPosixPath(dir_path); | 2275 | const dir_path_c = try toPosixPath(dir_path); |
| 2237 | return rmdirZ(&dir_path_c); | 2276 | return rmdirZ(&dir_path_c); |
| ... | @@ -2244,7 +2283,7 @@ pub const rmdirC = @compileError("deprecated: renamed to rmdirZ"); | ... | @@ -2244,7 +2283,7 @@ pub const rmdirC = @compileError("deprecated: renamed to rmdirZ"); |
| 2244 | pub fn rmdirZ(dir_path: [*:0]const u8) DeleteDirError!void { | 2283 | pub fn rmdirZ(dir_path: [*:0]const u8) DeleteDirError!void { |
| 2245 | if (builtin.os.tag == .windows) { | 2284 | if (builtin.os.tag == .windows) { |
| 2246 | const dir_path_w = try windows.cStrToPrefixedFileW(dir_path); | 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 | switch (errno(system.rmdir(dir_path))) { | 2288 | switch (errno(system.rmdir(dir_path))) { |
| 2250 | 0 => return, | 2289 | 0 => return, |
| ... | @@ -2265,6 +2304,14 @@ pub fn rmdirZ(dir_path: [*:0]const u8) DeleteDirError!void { | ... | @@ -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 | pub const ChangeCurDirError = error{ | 2315 | pub const ChangeCurDirError = error{ |
| 2269 | AccessDenied, | 2316 | AccessDenied, |
| 2270 | FileSystem, | 2317 | FileSystem, |
| ... | @@ -2354,7 +2401,8 @@ pub fn readlink(file_path: []const u8, out_buffer: []u8) ReadLinkError![]u8 { | ... | @@ -2354,7 +2401,8 @@ pub fn readlink(file_path: []const u8, out_buffer: []u8) ReadLinkError![]u8 { |
| 2354 | if (builtin.os.tag == .wasi) { | 2401 | if (builtin.os.tag == .wasi) { |
| 2355 | @compileError("readlink is not supported in WASI; use readlinkat instead"); | 2402 | @compileError("readlink is not supported in WASI; use readlinkat instead"); |
| 2356 | } else if (builtin.os.tag == .windows) { | 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 | } else { | 2406 | } else { |
| 2359 | const file_path_c = try toPosixPath(file_path); | 2407 | const file_path_c = try toPosixPath(file_path); |
| 2360 | return readlinkZ(&file_path_c, out_buffer); | 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,17 +2411,17 @@ pub fn readlink(file_path: []const u8, out_buffer: []u8) ReadLinkError![]u8 { |
| 2363 | 2411 | ||
| 2364 | pub const readlinkC = @compileError("deprecated: renamed to readlinkZ"); | 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 | /// See also `readlinkZ`. | 2415 | /// See also `readlinkZ`. |
| 2368 | pub fn readlinkW(file_path: [*:0]const u16, out_buffer: []u8) ReadLinkError![]u8 { | 2416 | pub fn readlinkW(file_path: []const u16, out_buffer: []u8) ReadLinkError![]u8 { |
| 2369 | return windows.ReadLinkW(std.fs.cwd().fd, file_path, out_buffer); | 2417 | return windows.ReadLink(std.fs.cwd().fd, file_path, out_buffer); |
| 2370 | } | 2418 | } |
| 2371 | 2419 | ||
| 2372 | /// Same as `readlink` except `file_path` is null-terminated. | 2420 | /// Same as `readlink` except `file_path` is null-terminated. |
| 2373 | pub fn readlinkZ(file_path: [*:0]const u8, out_buffer: []u8) ReadLinkError![]u8 { | 2421 | pub fn readlinkZ(file_path: [*:0]const u8, out_buffer: []u8) ReadLinkError![]u8 { |
| 2374 | if (builtin.os.tag == .windows) { | 2422 | if (builtin.os.tag == .windows) { |
| 2375 | const file_path_w = try windows.cStrToWin32PrefixedFileW(file_path); | 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 | const rc = system.readlink(file_path, out_buffer.ptr, out_buffer.len); | 2426 | const rc = system.readlink(file_path, out_buffer.ptr, out_buffer.len); |
| 2379 | switch (errno(rc)) { | 2427 | switch (errno(rc)) { |
| ... | @@ -2399,7 +2447,8 @@ pub fn readlinkat(dirfd: fd_t, file_path: []const u8, out_buffer: []u8) ReadLink | ... | @@ -2399,7 +2447,8 @@ pub fn readlinkat(dirfd: fd_t, file_path: []const u8, out_buffer: []u8) ReadLink |
| 2399 | return readlinkatWasi(dirfd, file_path, out_buffer); | 2447 | return readlinkatWasi(dirfd, file_path, out_buffer); |
| 2400 | } | 2448 | } |
| 2401 | if (builtin.os.tag == .windows) { | 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 | const file_path_c = try toPosixPath(file_path); | 2453 | const file_path_c = try toPosixPath(file_path); |
| 2405 | return readlinkatZ(dirfd, &file_path_c, out_buffer); | 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,8 +2478,8 @@ pub fn readlinkatWasi(dirfd: fd_t, file_path: []const u8, out_buffer: []u8) Read |
| 2429 | 2478 | ||
| 2430 | /// Windows-only. Same as `readlinkat` except `file_path` is null-terminated, WTF16 encoded. | 2479 | /// Windows-only. Same as `readlinkat` except `file_path` is null-terminated, WTF16 encoded. |
| 2431 | /// See also `readlinkat`. | 2480 | /// See also `readlinkat`. |
| 2432 | pub fn readlinkatW(dirfd: fd_t, file_path: [*:0]const u16, out_buffer: []u8) ReadLinkError![]u8 { | 2481 | pub fn readlinkatW(dirfd: fd_t, file_path: []const u16, out_buffer: []u8) ReadLinkError![]u8 { |
| 2433 | return windows.ReadLinkW(dirfd, file_path, out_buffer); | 2482 | return windows.ReadLink(dirfd, file_path, out_buffer); |
| 2434 | } | 2483 | } |
| 2435 | 2484 | ||
| 2436 | /// Same as `readlinkat` except `file_path` is null-terminated. | 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,7 +2487,7 @@ pub fn readlinkatW(dirfd: fd_t, file_path: [*:0]const u16, out_buffer: []u8) Rea |
| 2438 | pub fn readlinkatZ(dirfd: fd_t, file_path: [*:0]const u8, out_buffer: []u8) ReadLinkError![]u8 { | 2487 | pub fn readlinkatZ(dirfd: fd_t, file_path: [*:0]const u8, out_buffer: []u8) ReadLinkError![]u8 { |
| 2439 | if (builtin.os.tag == .windows) { | 2488 | if (builtin.os.tag == .windows) { |
| 2440 | const file_path_w = try windows.cStrToPrefixedFileW(file_path); | 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 | const rc = system.readlinkat(dirfd, file_path, out_buffer.ptr, out_buffer.len); | 2492 | const rc = system.readlinkat(dirfd, file_path, out_buffer.ptr, out_buffer.len); |
| 2444 | switch (errno(rc)) { | 2493 | switch (errno(rc)) { |
| ... | @@ -3959,7 +4008,7 @@ pub const RealPathError = error{ | ... | @@ -3959,7 +4008,7 @@ pub const RealPathError = error{ |
| 3959 | pub fn realpath(pathname: []const u8, out_buffer: *[MAX_PATH_BYTES]u8) RealPathError![]u8 { | 4008 | pub fn realpath(pathname: []const u8, out_buffer: *[MAX_PATH_BYTES]u8) RealPathError![]u8 { |
| 3960 | if (builtin.os.tag == .windows) { | 4009 | if (builtin.os.tag == .windows) { |
| 3961 | const pathname_w = try windows.sliceToPrefixedFileW(pathname); | 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 | if (builtin.os.tag == .wasi) { | 4013 | if (builtin.os.tag == .wasi) { |
| 3965 | @compileError("Use std.fs.wasi.PreopenList to obtain valid Dir handles instead of using absolute paths"); | 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,7 +4023,7 @@ pub const realpathC = @compileError("deprecated: renamed realpathZ"); |
| 3974 | pub fn realpathZ(pathname: [*:0]const u8, out_buffer: *[MAX_PATH_BYTES]u8) RealPathError![]u8 { | 4023 | pub fn realpathZ(pathname: [*:0]const u8, out_buffer: *[MAX_PATH_BYTES]u8) RealPathError![]u8 { |
| 3975 | if (builtin.os.tag == .windows) { | 4024 | if (builtin.os.tag == .windows) { |
| 3976 | const pathname_w = try windows.cStrToPrefixedFileW(pathname); | 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 | if (builtin.os.tag == .linux and !builtin.link_libc) { | 4028 | if (builtin.os.tag == .linux and !builtin.link_libc) { |
| 3980 | const fd = openZ(pathname, linux.O_PATH | linux.O_NONBLOCK | linux.O_CLOEXEC, 0) catch |err| switch (err) { | 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,22 +4059,43 @@ pub fn realpathZ(pathname: [*:0]const u8, out_buffer: *[MAX_PATH_BYTES]u8) RealP |
| 4010 | return mem.spanZ(result_path); | 4059 | return mem.spanZ(result_path); |
| 4011 | } | 4060 | } |
| 4012 | 4061 | ||
| 4013 | /// Same as `realpath` except `pathname` is null-terminated and UTF16LE-encoded. | 4062 | /// Same as `realpath` except `pathname` is UTF16LE-encoded. |
| 4014 | /// TODO use ntdll for better semantics | 4063 | /// TODO use ntdll to emulate `GetFinalPathNameByHandleW` routine |
| 4015 | pub fn realpathW(pathname: [*:0]const u16, out_buffer: *[MAX_PATH_BYTES]u8) RealPathError![]u8 { | 4064 | pub fn realpathW(pathname: []const u16, out_buffer: *[MAX_PATH_BYTES]u8) RealPathError![]u8 { |
| 4016 | const h_file = try windows.CreateFileW( | 4065 | const w = windows; |
| 4017 | pathname, | 4066 | |
| 4018 | windows.GENERIC_READ, | 4067 | const dir = std.fs.cwd().fd; |
| 4019 | windows.FILE_SHARE_READ, | 4068 | const access_mask = w.GENERIC_READ | w.SYNCHRONIZE; |
| 4020 | null, | 4069 | const share_access = w.FILE_SHARE_READ; |
| 4021 | windows.OPEN_EXISTING, | 4070 | const creation = w.FILE_OPEN; |
| 4022 | windows.FILE_FLAG_BACKUP_SEMANTICS, | 4071 | const h_file = blk: { |
| 4023 | null, | 4072 | const res = w.OpenFile(pathname, .{ |
| 4024 | ); | 4073 | .dir = dir, |
| 4025 | defer windows.CloseHandle(h_file); | 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; | 4097 | var wide_buf: [w.PATH_MAX_WIDE]u16 = undefined; |
| 4028 | const wide_slice = try windows.GetFinalPathNameByHandleW(h_file, &wide_buf, wide_buf.len, windows.VOLUME_NAME_DOS); | 4098 | const wide_slice = try w.GetFinalPathNameByHandleW(h_file, &wide_buf, wide_buf.len, w.VOLUME_NAME_DOS); |
| 4029 | 4099 | ||
| 4030 | // Windows returns \\?\ prepended to the path. | 4100 | // Windows returns \\?\ prepended to the path. |
| 4031 | // We strip it to make this function consistent across platforms. | 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,3 +237,28 @@ pub const IPPROTO_TCP = ws2_32.IPPROTO_TCP; |
| 237 | pub const IPPROTO_UDP = ws2_32.IPPROTO_UDP; | 237 | pub const IPPROTO_UDP = ws2_32.IPPROTO_UDP; |
| 238 | pub const IPPROTO_ICMPV6 = ws2_32.IPPROTO_ICMPV6; | 238 | pub const IPPROTO_ICMPV6 = ws2_32.IPPROTO_ICMPV6; |
| 239 | pub const IPPROTO_RM = ws2_32.IPPROTO_RM; | 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,6 +3,7 @@ const os = std.os; |
| 3 | const testing = std.testing; | 3 | const testing = std.testing; |
| 4 | const expect = testing.expect; | 4 | const expect = testing.expect; |
| 5 | const expectEqual = testing.expectEqual; | 5 | const expectEqual = testing.expectEqual; |
| 6 | const expectError = testing.expectError; | ||
| 6 | const io = std.io; | 7 | const io = std.io; |
| 7 | const fs = std.fs; | 8 | const fs = std.fs; |
| 8 | const mem = std.mem; | 9 | const mem = std.mem; |
| ... | @@ -19,6 +20,95 @@ const tmpDir = std.testing.tmpDir; | ... | @@ -19,6 +20,95 @@ const tmpDir = std.testing.tmpDir; |
| 19 | const Dir = std.fs.Dir; | 20 | const Dir = std.fs.Dir; |
| 20 | const ArenaAllocator = std.heap.ArenaAllocator; | 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 | test "symlink with relative paths" { | 112 | test "symlink with relative paths" { |
| 23 | if (builtin.os.tag == .wasi) return error.SkipZigTest; | 113 | if (builtin.os.tag == .wasi) return error.SkipZigTest; |
| 24 | 114 | ||
| ... | @@ -27,7 +117,7 @@ test "symlink with relative paths" { | ... | @@ -27,7 +117,7 @@ test "symlink with relative paths" { |
| 27 | try cwd.writeFile("file.txt", "nonsense"); | 117 | try cwd.writeFile("file.txt", "nonsense"); |
| 28 | 118 | ||
| 29 | if (builtin.os.tag == .windows) { | 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 | } else { | 121 | } else { |
| 32 | try os.symlink("file.txt", "symlinked"); | 122 | try os.symlink("file.txt", "symlinked"); |
| 33 | } | 123 | } |
| ... | @@ -85,7 +175,7 @@ test "readlinkat" { | ... | @@ -85,7 +175,7 @@ test "readlinkat" { |
| 85 | 175 | ||
| 86 | // create a symbolic link | 176 | // create a symbolic link |
| 87 | if (builtin.os.tag == .windows) { | 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 | } else { | 179 | } else { |
| 90 | try os.symlinkat("file.txt", tmp.dir.fd, "link"); | 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,76 +25,11 @@ pub usingnamespace @import("windows/bits.zig"); |
| 25 | 25 | ||
| 26 | pub const self_process_handle = @intToPtr(HANDLE, maxInt(usize)); | 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 | pub const OpenError = error{ | 28 | pub const OpenError = error{ |
| 94 | IsDir, | 29 | IsDir, |
| 30 | NotDir, | ||
| 95 | FileNotFound, | 31 | FileNotFound, |
| 96 | NoDevice, | 32 | NoDevice, |
| 97 | SharingViolation, | ||
| 98 | AccessDenied, | 33 | AccessDenied, |
| 99 | PipeBusy, | 34 | PipeBusy, |
| 100 | PathAlreadyExists, | 35 | PathAlreadyExists, |
| ... | @@ -111,15 +46,21 @@ pub const OpenFileOptions = struct { | ... | @@ -111,15 +46,21 @@ pub const OpenFileOptions = struct { |
| 111 | share_access_nonblocking: bool = false, | 46 | share_access_nonblocking: bool = false, |
| 112 | creation: ULONG, | 47 | creation: ULONG, |
| 113 | io_mode: std.io.ModeOverride, | 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 | /// TODO when share_access_nonblocking is false, this implementation uses | 57 | /// TODO when share_access_nonblocking is false, this implementation uses |
| 117 | /// untinterruptible sleep() to block. This is not the final iteration of the API. | 58 | /// untinterruptible sleep() to block. This is not the final iteration of the API. |
| 118 | pub fn OpenFile(sub_path_w: []const u16, options: OpenFileOptions) OpenError!HANDLE { | 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 | return error.IsDir; | 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 | return error.IsDir; | 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,11 +83,13 @@ pub fn OpenFile(sub_path_w: []const u16, options: OpenFileOptions) OpenError!HAN |
| 142 | .SecurityQualityOfService = null, | 83 | .SecurityQualityOfService = null, |
| 143 | }; | 84 | }; |
| 144 | var io: IO_STATUS_BLOCK = undefined; | 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 | var delay: usize = 1; | 91 | var delay: usize = 1; |
| 147 | while (true) { | 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 | const rc = ntdll.NtCreateFile( | 93 | const rc = ntdll.NtCreateFile( |
| 151 | &result, | 94 | &result, |
| 152 | options.access_mask, | 95 | options.access_mask, |
| ... | @@ -156,7 +99,7 @@ pub fn OpenFile(sub_path_w: []const u16, options: OpenFileOptions) OpenError!HAN | ... | @@ -156,7 +99,7 @@ pub fn OpenFile(sub_path_w: []const u16, options: OpenFileOptions) OpenError!HAN |
| 156 | FILE_ATTRIBUTE_NORMAL, | 99 | FILE_ATTRIBUTE_NORMAL, |
| 157 | options.share_access, | 100 | options.share_access, |
| 158 | options.creation, | 101 | options.creation, |
| 159 | FILE_NON_DIRECTORY_FILE | blocking_flag, | 102 | flags, |
| 160 | null, | 103 | null, |
| 161 | 0, | 104 | 0, |
| 162 | ); | 105 | ); |
| ... | @@ -184,6 +127,7 @@ pub fn OpenFile(sub_path_w: []const u16, options: OpenFileOptions) OpenError!HAN | ... | @@ -184,6 +127,7 @@ pub fn OpenFile(sub_path_w: []const u16, options: OpenFileOptions) OpenError!HAN |
| 184 | .OBJECT_PATH_SYNTAX_BAD => unreachable, | 127 | .OBJECT_PATH_SYNTAX_BAD => unreachable, |
| 185 | .OBJECT_NAME_COLLISION => return error.PathAlreadyExists, | 128 | .OBJECT_NAME_COLLISION => return error.PathAlreadyExists, |
| 186 | .FILE_IS_A_DIRECTORY => return error.IsDir, | 129 | .FILE_IS_A_DIRECTORY => return error.IsDir, |
| 130 | .NOT_A_DIRECTORY => return error.NotDir, | ||
| 187 | else => return unexpectedStatus(rc), | 131 | else => return unexpectedStatus(rc), |
| 188 | } | 132 | } |
| 189 | } | 133 | } |
| ... | @@ -638,27 +582,14 @@ pub const CreateSymbolicLinkError = error{ | ... | @@ -638,27 +582,14 @@ pub const CreateSymbolicLinkError = error{ |
| 638 | PathAlreadyExists, | 582 | PathAlreadyExists, |
| 639 | FileNotFound, | 583 | FileNotFound, |
| 640 | NameTooLong, | 584 | NameTooLong, |
| 641 | InvalidUtf8, | ||
| 642 | BadPathName, | ||
| 643 | NoDevice, | 585 | NoDevice, |
| 644 | Unexpected, | 586 | Unexpected, |
| 645 | }; | 587 | }; |
| 646 | 588 | ||
| 647 | pub fn CreateSymbolicLink( | 589 | pub fn CreateSymbolicLink( |
| 648 | dir: ?HANDLE, | 590 | dir: ?HANDLE, |
| 649 | sym_link_path: []const u8, | 591 | sym_link_path: []const u16, |
| 650 | target_path: []const u8, | 592 | target_path: []const u16, |
| 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, | ||
| 662 | is_directory: bool, | 593 | is_directory: bool, |
| 663 | ) CreateSymbolicLinkError!void { | 594 | ) CreateSymbolicLinkError!void { |
| 664 | const SYMLINK_DATA = extern struct { | 595 | const SYMLINK_DATA = extern struct { |
| ... | @@ -672,71 +603,19 @@ pub fn CreateSymbolicLinkW( | ... | @@ -672,71 +603,19 @@ pub fn CreateSymbolicLinkW( |
| 672 | Flags: ULONG, | 603 | Flags: ULONG, |
| 673 | }; | 604 | }; |
| 674 | 605 | ||
| 675 | var symlink_handle: HANDLE = undefined; | 606 | const symlink_handle = OpenFile(sym_link_path, .{ |
| 676 | if (is_directory) { | 607 | .access_mask = SYNCHRONIZE | GENERIC_READ | GENERIC_WRITE, |
| 677 | const sym_link_len_bytes = math.cast(u16, sym_link_path.len * 2) catch |err| switch (err) { | 608 | .dir = dir, |
| 678 | error.Overflow => return error.NameTooLong, | 609 | .creation = FILE_CREATE, |
| 679 | }; | 610 | .io_mode = .blocking, |
| 680 | var nt_name = UNICODE_STRING{ | 611 | .open_dir = is_directory, |
| 681 | .Length = sym_link_len_bytes, | 612 | }) catch |err| switch (err) { |
| 682 | .MaximumLength = sym_link_len_bytes, | 613 | error.IsDir => return error.PathAlreadyExists, |
| 683 | .Buffer = @intToPtr([*]u16, @ptrToInt(sym_link_path.ptr)), | 614 | error.NotDir => unreachable, |
| 684 | }; | 615 | error.WouldBlock => unreachable, |
| 685 | 616 | error.PipeBusy => unreachable, | |
| 686 | if (sym_link_path[0] == '.' and sym_link_path[1] == 0) { | 617 | else => |e| return e, |
| 687 | // Windows does not recognize this, but it does work with empty string. | 618 | }; |
| 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 | } | ||
| 740 | defer CloseHandle(symlink_handle); | 619 | defer CloseHandle(symlink_handle); |
| 741 | 620 | ||
| 742 | // prepare reparse data buffer | 621 | // prepare reparse data buffer |
| ... | @@ -767,44 +646,32 @@ pub const ReadLinkError = error{ | ... | @@ -767,44 +646,32 @@ pub const ReadLinkError = error{ |
| 767 | Unexpected, | 646 | Unexpected, |
| 768 | NameTooLong, | 647 | NameTooLong, |
| 769 | UnsupportedReparsePointType, | 648 | UnsupportedReparsePointType, |
| 770 | InvalidUtf8, | ||
| 771 | BadPathName, | ||
| 772 | }; | 649 | }; |
| 773 | 650 | ||
| 774 | pub fn ReadLink( | 651 | pub fn ReadLink(dir: ?HANDLE, sub_path_w: []const u16, out_buffer: []u8) ReadLinkError![]u8 { |
| 775 | dir: ?HANDLE, | 652 | // Here, we use `NtCreateFile` to shave off one syscall if we were to use `OpenFile` wrapper. |
| 776 | sub_path: []const u8, | 653 | // With the latter, we'd need to call `NtCreateFile` twice, once for file symlink, and if that |
| 777 | out_buffer: []u8, | 654 | // failed, again for dir symlink. Omitting any mention of file/dir flags makes it possible |
| 778 | ) ReadLinkError![]u8 { | 655 | // to open the symlink there and then. |
| 779 | const sub_path_w = try sliceToPrefixedFileW(sub_path); | 656 | const path_len_bytes = math.cast(u16, sub_path_w.len * 2) catch |err| switch (err) { |
| 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) { | ||
| 785 | error.Overflow => return error.NameTooLong, | 657 | error.Overflow => return error.NameTooLong, |
| 786 | }; | 658 | }; |
| 787 | var nt_name = UNICODE_STRING{ | 659 | var nt_name = UNICODE_STRING{ |
| 788 | .Length = path_len_bytes, | 660 | .Length = path_len_bytes, |
| 789 | .MaximumLength = path_len_bytes, | 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 | var attr = OBJECT_ATTRIBUTES{ | 664 | var attr = OBJECT_ATTRIBUTES{ |
| 799 | .Length = @sizeOf(OBJECT_ATTRIBUTES), | 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 | .Attributes = 0, // Note we do not use OBJ_CASE_INSENSITIVE here. | 667 | .Attributes = 0, // Note we do not use OBJ_CASE_INSENSITIVE here. |
| 802 | .ObjectName = &nt_name, | 668 | .ObjectName = &nt_name, |
| 803 | .SecurityDescriptor = null, | 669 | .SecurityDescriptor = null, |
| 804 | .SecurityQualityOfService = null, | 670 | .SecurityQualityOfService = null, |
| 805 | }; | 671 | }; |
| 806 | var io: IO_STATUS_BLOCK = undefined; | ||
| 807 | var result_handle: HANDLE = undefined; | 672 | var result_handle: HANDLE = undefined; |
| 673 | var io: IO_STATUS_BLOCK = undefined; | ||
| 674 | |||
| 808 | const rc = ntdll.NtCreateFile( | 675 | const rc = ntdll.NtCreateFile( |
| 809 | &result_handle, | 676 | &result_handle, |
| 810 | FILE_READ_ATTRIBUTES, | 677 | FILE_READ_ATTRIBUTES, |
| ... | @@ -878,135 +745,83 @@ pub const DeleteFileError = error{ | ... | @@ -878,135 +745,83 @@ pub const DeleteFileError = error{ |
| 878 | NameTooLong, | 745 | NameTooLong, |
| 879 | FileBusy, | 746 | FileBusy, |
| 880 | Unexpected, | 747 | Unexpected, |
| 748 | NotDir, | ||
| 749 | IsDir, | ||
| 881 | }; | 750 | }; |
| 882 | 751 | ||
| 883 | pub fn DeleteFile(filename: []const u8) DeleteFileError!void { | 752 | pub const DeleteFileOptions = struct { |
| 884 | const filename_w = try sliceToPrefixedFileW(filename); | 753 | dir: ?HANDLE, |
| 885 | return DeleteFileW(filename_w.span().ptr); | 754 | remove_dir: bool = false, |
| 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, | ||
| 927 | }; | 755 | }; |
| 928 | 756 | ||
| 929 | /// Returns an open directory handle which the caller is responsible for closing with `CloseHandle`. | 757 | pub fn DeleteFile(sub_path_w: []const u16, options: DeleteFileOptions) DeleteFileError!void { |
| 930 | pub fn CreateDirectory(dir: ?HANDLE, pathname: []const u8, sa: ?*SECURITY_ATTRIBUTES) CreateDirectoryError!HANDLE { | 758 | const create_options_flags: ULONG = if (options.remove_dir) |
| 931 | const pathname_w = try sliceToPrefixedFileW(pathname); | 759 | FILE_DELETE_ON_CLOSE | FILE_DIRECTORY_FILE | FILE_OPEN_REPARSE_POINT |
| 932 | return CreateDirectoryW(dir, pathname_w.span().ptr, sa); | 760 | else |
| 933 | } | 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. | 763 | const path_len_bytes = @intCast(u16, sub_path_w.len * 2); |
| 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 | }; | ||
| 944 | var nt_name = UNICODE_STRING{ | 764 | var nt_name = UNICODE_STRING{ |
| 945 | .Length = path_len_bytes, | 765 | .Length = path_len_bytes, |
| 946 | .MaximumLength = path_len_bytes, | 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 | if (sub_path_w[0] == '.' and sub_path_w[1] == 0) { | 771 | if (sub_path_w[0] == '.' and sub_path_w[1] == 0) { |
| 951 | // Windows does not recognize this, but it does work with empty string. | 772 | // Windows does not recognize this, but it does work with empty string. |
| 952 | nt_name.Length = 0; | 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 | var attr = OBJECT_ATTRIBUTES{ | 780 | var attr = OBJECT_ATTRIBUTES{ |
| 956 | .Length = @sizeOf(OBJECT_ATTRIBUTES), | 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 | .Attributes = 0, // Note we do not use OBJ_CASE_INSENSITIVE here. | 783 | .Attributes = 0, // Note we do not use OBJ_CASE_INSENSITIVE here. |
| 959 | .ObjectName = &nt_name, | 784 | .ObjectName = &nt_name, |
| 960 | .SecurityDescriptor = if (sa) |ptr| ptr.lpSecurityDescriptor else null, | 785 | .SecurityDescriptor = null, |
| 961 | .SecurityQualityOfService = null, | 786 | .SecurityQualityOfService = null, |
| 962 | }; | 787 | }; |
| 963 | var io: IO_STATUS_BLOCK = undefined; | 788 | var io: IO_STATUS_BLOCK = undefined; |
| 964 | var result_handle: HANDLE = undefined; | 789 | var tmp_handle: HANDLE = undefined; |
| 965 | const rc = ntdll.NtCreateFile( | 790 | var rc = ntdll.NtCreateFile( |
| 966 | &result_handle, | 791 | &tmp_handle, |
| 967 | GENERIC_READ | SYNCHRONIZE, | 792 | SYNCHRONIZE | DELETE, |
| 968 | &attr, | 793 | &attr, |
| 969 | &io, | 794 | &io, |
| 970 | null, | 795 | null, |
| 971 | FILE_ATTRIBUTE_NORMAL, | 796 | 0, |
| 972 | FILE_SHARE_READ, | 797 | FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, |
| 973 | FILE_CREATE, | 798 | FILE_OPEN, |
| 974 | FILE_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT, | 799 | create_options_flags, |
| 975 | null, | 800 | null, |
| 976 | 0, | 801 | 0, |
| 977 | ); | 802 | ); |
| 978 | switch (rc) { | 803 | switch (rc) { |
| 979 | .SUCCESS => return result_handle, | 804 | .SUCCESS => return CloseHandle(tmp_handle), |
| 980 | .OBJECT_NAME_INVALID => unreachable, | 805 | .OBJECT_NAME_INVALID => unreachable, |
| 981 | .OBJECT_NAME_NOT_FOUND => return error.FileNotFound, | 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 | .INVALID_PARAMETER => unreachable, | 807 | .INVALID_PARAMETER => unreachable, |
| 985 | .ACCESS_DENIED => return error.AccessDenied, | 808 | .FILE_IS_A_DIRECTORY => return error.IsDir, |
| 986 | .OBJECT_PATH_SYNTAX_BAD => unreachable, | 809 | .NOT_A_DIRECTORY => return error.NotDir, |
| 987 | .OBJECT_NAME_COLLISION => return error.PathAlreadyExists, | ||
| 988 | else => return unexpectedStatus(rc), | 810 | else => return unexpectedStatus(rc), |
| 989 | } | 811 | } |
| 990 | } | 812 | } |
| 991 | 813 | ||
| 992 | pub const RemoveDirectoryError = error{ | 814 | pub const MoveFileError = error{Unexpected}; |
| 993 | FileNotFound, | ||
| 994 | DirNotEmpty, | ||
| 995 | Unexpected, | ||
| 996 | NotDir, | ||
| 997 | }; | ||
| 998 | 815 | ||
| 999 | pub fn RemoveDirectory(dir_path: []const u8) RemoveDirectoryError!void { | 816 | pub fn MoveFileEx(old_path: []const u8, new_path: []const u8, flags: DWORD) MoveFileError!void { |
| 1000 | const dir_path_w = try sliceToPrefixedFileW(dir_path); | 817 | const old_path_w = try sliceToPrefixedFileW(old_path); |
| 1001 | return RemoveDirectoryW(dir_path_w.span().ptr); | 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 { | 822 | pub fn MoveFileExW(old_path: [*:0]const u16, new_path: [*:0]const u16, flags: DWORD) MoveFileError!void { |
| 1005 | if (kernel32.RemoveDirectoryW(dir_path_w) == 0) { | 823 | if (kernel32.MoveFileExW(old_path, new_path, flags) == 0) { |
| 1006 | switch (kernel32.GetLastError()) { | 824 | switch (kernel32.GetLastError()) { |
| 1007 | .PATH_NOT_FOUND => return error.FileNotFound, | ||
| 1008 | .DIR_NOT_EMPTY => return error.DirNotEmpty, | ||
| 1009 | .DIRECTORY => return error.NotDir, | ||
| 1010 | else => |err| return unexpectedError(err), | 825 | else => |err| return unexpectedError(err), |
| 1011 | } | 826 | } |
| 1012 | } | 827 | } |
| ... | @@ -1493,8 +1308,7 @@ pub fn cStrToPrefixedFileW(s: [*:0]const u8) !PathSpace { | ... | @@ -1493,8 +1308,7 @@ pub fn cStrToPrefixedFileW(s: [*:0]const u8) !PathSpace { |
| 1493 | } | 1308 | } |
| 1494 | 1309 | ||
| 1495 | /// Converts the path `s` to WTF16, null-terminated. If the path is absolute, | 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 | 1311 | /// it will get NT-style prefix `\??\` prepended automatically. |
| 1497 | /// Win32-style prefix, see `sliceToWin32PrefixedFileW` instead. | ||
| 1498 | pub fn sliceToPrefixedFileW(s: []const u8) !PathSpace { | 1312 | pub fn sliceToPrefixedFileW(s: []const u8) !PathSpace { |
| 1499 | // TODO https://github.com/ziglang/zig/issues/2765 | 1313 | // TODO https://github.com/ziglang/zig/issues/2765 |
| 1500 | var path_space: PathSpace = undefined; | 1314 | var path_space: PathSpace = undefined; |