authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2020-08-02 17:41:47+00:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2020-08-02 17:41:47+00:00
log6df35c5877c1a1060dcfd28d5d4092638e6e6754
treee3320200a4ee2754ec60255ab22388af79a8cba0
parentcf15b4c2690ae7884e7bdb9259e6497c18213d35
parent4b0ab0c1864677d0c50b2a8e12a708a6640d1734
signaturebadge-question-mark Signed by PGP key 4AEE18F83AFDEB23

Merge pull request #5960 from kubkon/windows-dir-refactor

Refactor out file and dir creation routines in Windows

8 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 else370 else
...@@ -480,25 +481,20 @@ pub const ChildProcess = struct {...@@ -480,25 +481,20 @@ pub const ChildProcess = struct {
480481
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);
482483
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 else500 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.
226pub fn makeDirAbsoluteW(absolute_path_w: [*:0]const u16) !void {226pub 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}
231230
232pub const deleteDir = @compileError("deprecated; use dir.deleteDir or deleteDirAbsolute");231pub 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 }
882881
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 }
887885
888 /// Calls makeDir recursively to make an entire path. Returns success if the path886 /// 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_REMOVEDIR1123 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 }
11541152
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_REMOVEDIR1156 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_REMOVEDIR1186 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 {
12041202
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_REMOVEDIR1207 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 }
12721270
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 }
13021301
1303 /// Windows-only. Same as `readLink` except the pathname parameter1302 /// 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 }
13081307
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 would1823/// 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`.
1825pub fn symLinkAbsoluteW(target_path_w: [:0]const u16, sym_link_path_w: [:0]const u16, flags: SymLinkFlags) !void {1826pub 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}
18301831
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}
19401941
1941pub const OpenSelfExeError = os.OpenError || os.windows.CreateFileError || SelfExePathError || os.FlockError;1942pub 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;
19421956
1943pub fn openSelfExe(flags: File.OpenFlags) OpenSelfExeError!File {1957pub 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 };
4949
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;
5164
52 pub const Lock = enum { None, Shared, Exclusive };65 pub const Lock = enum { None, Shared, Exclusive };
5366
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];
376376
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);
388386
lib/std/os.zig+176-106
...@@ -1041,6 +1041,9 @@ pub const OpenError = error{...@@ -1041,6 +1041,9 @@ pub const OpenError = error{
10411041
1042 /// The underlying filesystem does not support file locks1042 /// The underlying filesystem does not support file locks
1043 FileLocksNotSupported,1043 FileLocksNotSupported,
1044
1045 BadPathName,
1046 InvalidUtf8,
1044} || UnexpectedError;1047} || UnexpectedError;
10451048
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}
10941097
1098fn 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 is1131/// 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.
1098pub 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.
1136pub 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}
11011145
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
1106pub fn openat(dir_fd: fd_t, file_path: []const u8, flags: u32, mode: mode_t) OpenError!fd_t {1149pub 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
1149pub fn openatZ(dir_fd: fd_t, file_path: [*:0]const u8, flags: u32, mode: mode_t) OpenError!fd_t {1198pub 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}
11791232
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.
1237pub 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
1180pub fn dup2(old_fd: fd_t, new_fd: fd_t) !void {1247pub 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");
1696pub fn unlinkZ(file_path: [*:0]const u8) UnlinkError!void {1763pub 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}
17191786
1787/// Windows-only. Same as `unlink` except the parameter is null-terminated, WTF16 encoded.
1788pub fn unlinkW(file_path_w: []const u16) UnlinkError!void {
1789 return windows.DeleteFile(file_path_w, .{ .dir = std.fs.cwd().fd });
1790}
1791
1720pub const UnlinkatError = UnlinkError || error{1792pub 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{
1727pub fn unlinkat(dirfd: fd_t, file_path: []const u8, flags: u32) UnlinkatError!void {1799pub 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
1774pub fn unlinkatZ(dirfd: fd_t, file_path_c: [*:0]const u8, flags: u32) UnlinkatError!void {1846pub 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}
18011873
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.
1803pub fn unlinkatW(dirfd: fd_t, sub_path_w: [*:0]const u16, flags: u32) UnlinkatError!void {1875pub 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;
18051877 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}
18651879
1866const RenameError = error{1880const RenameError = error{
...@@ -2087,7 +2101,7 @@ pub fn renameatW(...@@ -2087,7 +2101,7 @@ pub fn renameatW(
2087pub fn mkdirat(dir_fd: fd_t, sub_dir_path: []const u8, mode: u32) MakeDirError!void {2101pub 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}
21472161
2148pub fn mkdiratW(dir_fd: fd_t, sub_path_w: [*:0]const u16, mode: u32) MakeDirError!void {2162pub 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}
21522177
...@@ -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 {
2188pub fn mkdirZ(dir_path: [*:0]const u8, mode: u32) MakeDirError!void {2212pub 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}
22132235
2236/// Windows-only. Same as `mkdir` but the parameters is WTF16 encoded.
2237pub 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
2214pub const DeleteDirError = error{2253pub 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");
2244pub fn rmdirZ(dir_path: [*:0]const u8) DeleteDirError!void {2283pub 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}
22672306
2307/// Windows-only. Same as `rmdir` except the parameter is WTF16 encoded.
2308pub 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
2268pub const ChangeCurDirError = error{2315pub 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 {
23632411
2364pub const readlinkC = @compileError("deprecated: renamed to readlinkZ");2412pub const readlinkC = @compileError("deprecated: renamed to readlinkZ");
23652413
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`.
2368pub fn readlinkW(file_path: [*:0]const u16, out_buffer: []u8) ReadLinkError![]u8 {2416pub 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}
23712419
2372/// Same as `readlink` except `file_path` is null-terminated.2420/// Same as `readlink` except `file_path` is null-terminated.
2373pub fn readlinkZ(file_path: [*:0]const u8, out_buffer: []u8) ReadLinkError![]u8 {2421pub 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
24292478
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`.
2432pub fn readlinkatW(dirfd: fd_t, file_path: [*:0]const u16, out_buffer: []u8) ReadLinkError![]u8 {2481pub 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}
24352484
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
2438pub fn readlinkatZ(dirfd: fd_t, file_path: [*:0]const u8, out_buffer: []u8) ReadLinkError![]u8 {2487pub 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{
3959pub fn realpath(pathname: []const u8, out_buffer: *[MAX_PATH_BYTES]u8) RealPathError![]u8 {4008pub 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");
3974pub fn realpathZ(pathname: [*:0]const u8, out_buffer: *[MAX_PATH_BYTES]u8) RealPathError![]u8 {4023pub 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}
40124061
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 semantics4063/// TODO use ntdll to emulate `GetFinalPathNameByHandleW` routine
4015pub fn realpathW(pathname: [*:0]const u16, out_buffer: *[MAX_PATH_BYTES]u8) RealPathError![]u8 {4064pub 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);
40264096
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);
40294099
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;
237pub const IPPROTO_UDP = ws2_32.IPPROTO_UDP;237pub const IPPROTO_UDP = ws2_32.IPPROTO_UDP;
238pub const IPPROTO_ICMPV6 = ws2_32.IPPROTO_ICMPV6;238pub const IPPROTO_ICMPV6 = ws2_32.IPPROTO_ICMPV6;
239pub const IPPROTO_RM = ws2_32.IPPROTO_RM;239pub const IPPROTO_RM = ws2_32.IPPROTO_RM;
240
241pub const O_RDONLY = 0o0;
242pub const O_WRONLY = 0o1;
243pub const O_RDWR = 0o2;
244
245pub const O_CREAT = 0o100;
246pub const O_EXCL = 0o200;
247pub const O_NOCTTY = 0o400;
248pub const O_TRUNC = 0o1000;
249pub const O_APPEND = 0o2000;
250pub const O_NONBLOCK = 0o4000;
251pub const O_DSYNC = 0o10000;
252pub const O_SYNC = 0o4010000;
253pub const O_RSYNC = 0o4010000;
254pub const O_DIRECTORY = 0o200000;
255pub const O_NOFOLLOW = 0o400000;
256pub const O_CLOEXEC = 0o2000000;
257
258pub const O_ASYNC = 0o20000;
259pub const O_DIRECT = 0o40000;
260pub const O_LARGEFILE = 0;
261pub const O_NOATIME = 0o1000000;
262pub const O_PATH = 0o10000000;
263pub const O_TMPFILE = 0o20200000;
264pub 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;
3const testing = std.testing;3const testing = std.testing;
4const expect = testing.expect;4const expect = testing.expect;
5const expectEqual = testing.expectEqual;5const expectEqual = testing.expectEqual;
6const expectError = testing.expectError;
6const io = std.io;7const io = std.io;
7const fs = std.fs;8const fs = std.fs;
8const mem = std.mem;9const mem = std.mem;
...@@ -19,6 +20,95 @@ const tmpDir = std.testing.tmpDir;...@@ -19,6 +20,95 @@ const tmpDir = std.testing.tmpDir;
19const Dir = std.fs.Dir;20const Dir = std.fs.Dir;
20const ArenaAllocator = std.heap.ArenaAllocator;21const ArenaAllocator = std.heap.ArenaAllocator;
2122
23test "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
76test "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
22test "symlink with relative paths" {112test "symlink with relative paths" {
23 if (builtin.os.tag == .wasi) return error.SkipZigTest;113 if (builtin.os.tag == .wasi) return error.SkipZigTest;
24114
...@@ -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");
28118
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" {
85175
86 // create a symbolic link176 // 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");
2525
26pub const self_process_handle = @intToPtr(HANDLE, maxInt(usize));26pub const self_process_handle = @intToPtr(HANDLE, maxInt(usize));
2727
28pub 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
52pub 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
65pub 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
93pub const OpenError = error{28pub 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};
11556
116/// TODO when share_access_nonblocking is false, this implementation uses57/// 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.
118pub fn OpenFile(sub_path_w: []const u16, options: OpenFileOptions) OpenError!HANDLE {59pub 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 }
12566
...@@ -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;
14590
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};
646588
647pub fn CreateSymbolicLink(589pub 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
658pub 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 };
674605
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,
685616 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);
741620
742 // prepare reparse data buffer621 // 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};
773650
774pub fn ReadLink(651pub 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
783pub 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};
882751
883pub fn DeleteFile(filename: []const u8) DeleteFileError!void {752pub 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
888pub 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
902pub const MoveFileError = error{Unexpected};
903
904pub 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
910pub 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
918pub const CreateDirectoryError = error{
919 NameTooLong,
920 PathAlreadyExists,
921 FileNotFound,
922 NoDevice,
923 AccessDenied,
924 InvalidUtf8,
925 BadPathName,
926 Unexpected,
927};755};
928756
929/// Returns an open directory handle which the caller is responsible for closing with `CloseHandle`.757pub fn DeleteFile(sub_path_w: []const u16, options: DeleteFileOptions) DeleteFileError!void {
930pub 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?
934762
935/// Same as `CreateDirectory` except takes a WTF-16 encoded path.763 const path_len_bytes = @intCast(u16, sub_path_w.len * 2);
936pub 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 };
949770
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 }
954779
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}
991813
992pub const RemoveDirectoryError = error{814pub const MoveFileError = error{Unexpected};
993 FileNotFound,
994 DirNotEmpty,
995 Unexpected,
996 NotDir,
997};
998815
999pub fn RemoveDirectory(dir_path: []const u8) RemoveDirectoryError!void {816pub 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}
1003821
1004pub fn RemoveDirectoryW(dir_path_w: [*:0]const u16) RemoveDirectoryError!void {822pub 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}
14941309
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 prepending1311/// it will get NT-style prefix `\??\` prepended automatically.
1497/// Win32-style prefix, see `sliceToWin32PrefixedFileW` instead.
1498pub fn sliceToPrefixedFileW(s: []const u8) !PathSpace {1312pub fn sliceToPrefixedFileW(s: []const u8) !PathSpace {
1499 // TODO https://github.com/ziglang/zig/issues/27651313 // TODO https://github.com/ziglang/zig/issues/2765
1500 var path_space: PathSpace = undefined;1314 var path_space: PathSpace = undefined;