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 {
364364 error.FileTooBig => unreachable,
365365 error.DeviceBusy => unreachable,
366366 error.FileLocksNotSupported => unreachable,
367 error.BadPathName => unreachable, // Windows-only
367368 else => |e| return e,
368369 }
369370 else
......@@ -480,25 +481,20 @@ pub const ChildProcess = struct {
480481
481482 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
484484 const nul_handle = if (any_ignore)
485 windows.CreateFile(
486 "NUL",
487 windows.GENERIC_READ,
488 windows.FILE_SHARE_READ,
489 null,
490 windows.OPEN_EXISTING,
491 windows.FILE_ATTRIBUTE_NORMAL,
492 null,
493 ) catch |err| switch (err) {
494 error.SharingViolation => unreachable, // not possible for "NUL"
485 windows.OpenFile(&[_]u16{ 'N', 'U', 'L' }, .{
486 .dir = std.fs.cwd().fd,
487 .access_mask = windows.GENERIC_READ | windows.SYNCHRONIZE,
488 .share_access = windows.FILE_SHARE_READ,
489 .creation = windows.OPEN_EXISTING,
490 .io_mode = .blocking,
491 }) catch |err| switch (err) {
495492 error.PathAlreadyExists => unreachable, // not possible for "NUL"
496493 error.PipeBusy => unreachable, // not possible for "NUL"
497 error.InvalidUtf8 => unreachable, // not possible for "NUL"
498 error.BadPathName => unreachable, // not possible for "NUL"
499494 error.FileNotFound => unreachable, // not possible for "NUL"
500495 error.AccessDenied => unreachable, // not possible for "NUL"
501496 error.NameTooLong => unreachable, // not possible for "NUL"
497 error.WouldBlock => unreachable, // not possible for "NUL"
502498 else => |e| return e,
503499 }
504500 else
lib/std/fs.zig+36-22
......@@ -225,8 +225,7 @@ pub fn makeDirAbsoluteZ(absolute_path_z: [*:0]const u8) !void {
225225/// Same as `makeDirAbsolute` except the parameter is a null-terminated WTF-16 encoded string.
226226pub fn makeDirAbsoluteW(absolute_path_w: [*:0]const u16) !void {
227227 assert(path.isAbsoluteWindowsW(absolute_path_w));
228 const handle = try os.windows.CreateDirectoryW(null, absolute_path_w, null);
229 os.windows.CloseHandle(handle);
228 return os.mkdirW(absolute_path_w, default_new_dir_mode);
230229}
231230
232231pub const deleteDir = @compileError("deprecated; use dir.deleteDir or deleteDirAbsolute");
......@@ -881,8 +880,7 @@ pub const Dir = struct {
881880 }
882881
883882 pub fn makeDirW(self: Dir, sub_path: [*:0]const u16) !void {
884 const handle = try os.windows.CreateDirectoryW(self.fd, sub_path, null);
885 os.windows.CloseHandle(handle);
883 try os.mkdiratW(self.fd, sub_path, default_new_dir_mode);
886884 }
887885
888886 /// Calls makeDir recursively to make an entire path. Returns success if the path
......@@ -1119,7 +1117,7 @@ pub const Dir = struct {
11191117 pub fn deleteFile(self: Dir, sub_path: []const u8) DeleteFileError!void {
11201118 if (builtin.os.tag == .windows) {
11211119 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());
11231121 } else if (builtin.os.tag == .wasi) {
11241122 os.unlinkatWasi(self.fd, sub_path, 0) catch |err| switch (err) {
11251123 error.DirNotEmpty => unreachable, // not passing AT_REMOVEDIR
......@@ -1153,7 +1151,7 @@ pub const Dir = struct {
11531151 }
11541152
11551153 /// 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 {
11571155 os.unlinkatW(self.fd, sub_path_w, 0) catch |err| switch (err) {
11581156 error.DirNotEmpty => unreachable, // not passing AT_REMOVEDIR
11591157 else => |e| return e,
......@@ -1182,7 +1180,7 @@ pub const Dir = struct {
11821180 pub fn deleteDir(self: Dir, sub_path: []const u8) DeleteDirError!void {
11831181 if (builtin.os.tag == .windows) {
11841182 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());
11861184 } else if (builtin.os.tag == .wasi) {
11871185 os.unlinkat(self.fd, sub_path, os.AT_REMOVEDIR) catch |err| switch (err) {
11881186 error.IsDir => unreachable, // not possible since we pass AT_REMOVEDIR
......@@ -1204,7 +1202,7 @@ pub const Dir = struct {
12041202
12051203 /// Same as `deleteDir` except the parameter is UTF16LE, NT prefixed.
12061204 /// 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 {
12081206 os.unlinkatW(self.fd, sub_path_w, os.AT_REMOVEDIR) catch |err| switch (err) {
12091207 error.IsDir => unreachable, // not possible since we pass AT_REMOVEDIR
12101208 else => |e| return e,
......@@ -1263,11 +1261,11 @@ pub const Dir = struct {
12631261 /// are null-terminated, WTF16 encoded.
12641262 pub fn symLinkW(
12651263 self: Dir,
1266 target_path_w: [:0]const u16,
1267 sym_link_path_w: [:0]const u16,
1264 target_path_w: []const u16,
1265 sym_link_path_w: []const u16,
12681266 flags: SymLinkFlags,
12691267 ) !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);
12711269 }
12721270
12731271 /// Read value of a symbolic link.
......@@ -1278,7 +1276,8 @@ pub const Dir = struct {
12781276 return self.readLinkWasi(sub_path, buffer);
12791277 }
12801278 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);
12821281 }
12831282 const sub_path_c = try os.toPosixPath(sub_path);
12841283 return self.readLinkZ(&sub_path_c, buffer);
......@@ -1295,15 +1294,15 @@ pub const Dir = struct {
12951294 pub fn readLinkZ(self: Dir, sub_path_c: [*:0]const u8, buffer: []u8) ![]u8 {
12961295 if (builtin.os.tag == .windows) {
12971296 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);
12991298 }
13001299 return os.readlinkatZ(self.fd, sub_path_c, buffer);
13011300 }
13021301
13031302 /// Windows-only. Same as `readLink` except the pathname parameter
13041303 /// is null-terminated, WTF16 encoded.
1305 pub fn readLinkW(self: Dir, sub_path_w: [*:0]const u16, buffer: []u8) ![]u8 {
1306 return os.windows.ReadLinkW(self.fd, sub_path_w, buffer);
1304 pub fn readLinkW(self: Dir, sub_path_w: []const u16, buffer: []u8) ![]u8 {
1305 return os.windows.ReadLink(self.fd, sub_path_w, buffer);
13071306 }
13081307
13091308 /// On success, caller owns returned buffer.
......@@ -1813,7 +1812,9 @@ pub fn symLinkAbsolute(target_path: []const u8, sym_link_path: []const u8, flags
18131812 assert(path.isAbsolute(target_path));
18141813 assert(path.isAbsolute(sym_link_path));
18151814 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);
18171818 }
18181819 return os.symlink(target_path, sym_link_path);
18191820}
......@@ -1822,10 +1823,10 @@ pub fn symLinkAbsolute(target_path: []const u8, sym_link_path: []const u8, flags
18221823/// Note that this function will by default try creating a symbolic link to a file. If you would
18231824/// like to create a symbolic link to a directory, specify this with `SymLinkFlags{ .is_directory = true }`.
18241825/// See also `symLinkAbsolute`, `symLinkAbsoluteZ`.
1825pub fn symLinkAbsoluteW(target_path_w: [:0]const u16, sym_link_path_w: [:0]const u16, flags: SymLinkFlags) !void {
1826 assert(path.isAbsoluteWindowsW(target_path_w));
1827 assert(path.isAbsoluteWindowsW(sym_link_path_w));
1828 return os.windows.CreateSymbolicLinkW(null, sym_link_path_w, target_path_w, flags.is_directory);
1826pub fn symLinkAbsoluteW(target_path_w: []const u16, sym_link_path_w: []const u16, flags: SymLinkFlags) !void {
1827 assert(path.isAbsoluteWindowsWTF16(target_path_w));
1828 assert(path.isAbsoluteWindowsWTF16(sym_link_path_w));
1829 return os.windows.CreateSymbolicLink(null, sym_link_path_w, target_path_w, flags.is_directory);
18291830}
18301831
18311832/// 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
18361837 if (builtin.os.tag == .windows) {
18371838 const target_path_w = try os.windows.cStrToWin32PrefixedFileW(target_path_c);
18381839 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);
18401841 }
18411842 return os.symlinkZ(target_path_c, sym_link_path_c);
18421843}
......@@ -1938,7 +1939,20 @@ pub fn walkPath(allocator: *Allocator, dir_path: []const u8) !Walker {
19381939 return walker;
19391940}
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
19431957pub fn openSelfExe(flags: File.OpenFlags) OpenSelfExeError!File {
19441958 if (builtin.os.tag == .linux) {
lib/std/fs/file.zig+14-1
......@@ -47,7 +47,20 @@ pub const File = struct {
4747 else => 0o666,
4848 };
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
5265 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 {
374374 defer if (!basename_utf16le_null_consumed) self.allocator.free(basename_utf16le_null);
375375 const basename_utf16le_no_null = basename_utf16le_null[0 .. basename_utf16le_null.len - 1];
376376
377 const dir_handle = try windows.CreateFileW(
378 dirname_utf16le.ptr,
379 windows.FILE_LIST_DIRECTORY,
380 windows.FILE_SHARE_READ | windows.FILE_SHARE_DELETE | windows.FILE_SHARE_WRITE,
381 null,
382 windows.OPEN_EXISTING,
383 windows.FILE_FLAG_BACKUP_SEMANTICS | windows.FILE_FLAG_OVERLAPPED,
384 null,
385 );
377 const dir_handle = try windows.OpenFile(dirname_utf16le, .{
378 .dir = std.fs.cwd().fd,
379 .access_mask = windows.FILE_LIST_DIRECTORY,
380 .creation = windows.FILE_OPEN,
381 .io_mode = .blocking,
382 .open_dir = true,
383 });
386384 var dir_handle_consumed = false;
387385 defer if (!dir_handle_consumed) windows.CloseHandle(dir_handle);
388386
lib/std/os.zig+176-106
......@@ -1041,6 +1041,9 @@ pub const OpenError = error{
10411041
10421042 /// The underlying filesystem does not support file locks
10431043 FileLocksNotSupported,
1044
1045 BadPathName,
1046 InvalidUtf8,
10441047} || UnexpectedError;
10451048
10461049/// 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
10921095 }
10931096}
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
10951131/// Windows-only. The path parameter is
10961132/// [WTF-16](https://simonsapin.github.io/wtf-8/#potentially-ill-formed-utf-16) encoded.
10971133/// 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 {
1099 @compileError("TODO implement openW for windows");
1134/// TODO currently, this function does not handle all flag combinations
1135/// or makes use of perm argument.
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 };
11001144}
11011145
11021146/// Open and possibly create a file. Keeps trying if it gets interrupted.
11031147/// `file_path` is relative to the open directory handle `dir_fd`.
11041148/// See also `openatC`.
1105/// TODO support windows
11061149pub 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 }
11071157 const file_path_c = try toPosixPath(file_path);
11081158 return openatZ(dir_fd, &file_path_c, flags, mode);
11091159}
......@@ -1145,8 +1195,11 @@ pub const openatC = @compileError("deprecated: renamed to openatZ");
11451195/// Open and possibly create a file. Keeps trying if it gets interrupted.
11461196/// `file_path` is relative to the open directory handle `dir_fd`.
11471197/// See also `openat`.
1148/// TODO support windows
11491198pub 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 }
11501203 while (true) {
11511204 const rc = system.openat(dir_fd, file_path, flags, mode);
11521205 switch (errno(rc)) {
......@@ -1177,6 +1230,20 @@ pub fn openatZ(dir_fd: fd_t, file_path: [*:0]const u8, flags: u32, mode: mode_t)
11771230 }
11781231}
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
11801247pub fn dup2(old_fd: fd_t, new_fd: fd_t) !void {
11811248 while (true) {
11821249 switch (errno(system.dup2(old_fd, new_fd))) {
......@@ -1683,7 +1750,7 @@ pub fn unlink(file_path: []const u8) UnlinkError!void {
16831750 @compileError("unlink is not supported in WASI; use unlinkat instead");
16841751 } else if (builtin.os.tag == .windows) {
16851752 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());
16871754 } else {
16881755 const file_path_c = try toPosixPath(file_path);
16891756 return unlinkZ(&file_path_c);
......@@ -1696,7 +1763,7 @@ pub const unlinkC = @compileError("deprecated: renamed to unlinkZ");
16961763pub fn unlinkZ(file_path: [*:0]const u8) UnlinkError!void {
16971764 if (builtin.os.tag == .windows) {
16981765 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());
17001767 }
17011768 switch (errno(system.unlink(file_path))) {
17021769 0 => return,
......@@ -1717,6 +1784,11 @@ pub fn unlinkZ(file_path: [*:0]const u8) UnlinkError!void {
17171784 }
17181785}
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
17201792pub const UnlinkatError = UnlinkError || error{
17211793 /// When passing `AT_REMOVEDIR`, this error occurs when the named directory is not empty.
17221794 DirNotEmpty,
......@@ -1727,7 +1799,7 @@ pub const UnlinkatError = UnlinkError || error{
17271799pub fn unlinkat(dirfd: fd_t, file_path: []const u8, flags: u32) UnlinkatError!void {
17281800 if (builtin.os.tag == .windows) {
17291801 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);
17311803 } else if (builtin.os.tag == .wasi) {
17321804 return unlinkatWasi(dirfd, file_path, flags);
17331805 } else {
......@@ -1774,7 +1846,7 @@ pub fn unlinkatWasi(dirfd: fd_t, file_path: []const u8, flags: u32) UnlinkatErro
17741846pub fn unlinkatZ(dirfd: fd_t, file_path_c: [*:0]const u8, flags: u32) UnlinkatError!void {
17751847 if (builtin.os.tag == .windows) {
17761848 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);
17781850 }
17791851 switch (errno(system.unlinkat(dirfd, file_path_c, flags))) {
17801852 0 => return,
......@@ -1800,67 +1872,9 @@ pub fn unlinkatZ(dirfd: fd_t, file_path_c: [*:0]const u8, flags: u32) UnlinkatEr
18001872}
18011873
18021874/// 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 {
1804 const w = windows;
1805
1806 const want_rmdir_behavior = (flags & AT_REMOVEDIR) != 0;
1807 const create_options_flags = if (want_rmdir_behavior)
1808 @as(w.ULONG, w.FILE_DELETE_ON_CLOSE | w.FILE_DIRECTORY_FILE | w.FILE_OPEN_REPARSE_POINT)
1809 else
1810 @as(w.ULONG, w.FILE_DELETE_ON_CLOSE | w.FILE_NON_DIRECTORY_FILE | w.FILE_OPEN_REPARSE_POINT); // would we ever want to delete the target instead?
1811
1812 const path_len_bytes = @intCast(u16, mem.lenZ(sub_path_w) * 2);
1813 var nt_name = w.UNICODE_STRING{
1814 .Length = path_len_bytes,
1815 .MaximumLength = path_len_bytes,
1816 // The Windows API makes this mutable, but it will not mutate here.
1817 .Buffer = @intToPtr([*]u16, @ptrToInt(sub_path_w)),
1818 };
1819
1820 if (sub_path_w[0] == '.' and sub_path_w[1] == 0) {
1821 // Windows does not recognize this, but it does work with empty string.
1822 nt_name.Length = 0;
1823 }
1824 if (sub_path_w[0] == '.' and sub_path_w[1] == '.' and sub_path_w[2] == 0) {
1825 // Can't remove the parent directory with an open handle.
1826 return error.FileBusy;
1827 }
1828
1829 var attr = w.OBJECT_ATTRIBUTES{
1830 .Length = @sizeOf(w.OBJECT_ATTRIBUTES),
1831 .RootDirectory = if (std.fs.path.isAbsoluteWindowsW(sub_path_w)) null else dirfd,
1832 .Attributes = 0, // Note we do not use OBJ_CASE_INSENSITIVE here.
1833 .ObjectName = &nt_name,
1834 .SecurityDescriptor = null,
1835 .SecurityQualityOfService = null,
1836 };
1837 var io: w.IO_STATUS_BLOCK = undefined;
1838 var tmp_handle: w.HANDLE = undefined;
1839 var rc = w.ntdll.NtCreateFile(
1840 &tmp_handle,
1841 w.SYNCHRONIZE | w.DELETE,
1842 &attr,
1843 &io,
1844 null,
1845 0,
1846 w.FILE_SHARE_READ | w.FILE_SHARE_WRITE | w.FILE_SHARE_DELETE,
1847 w.FILE_OPEN,
1848 create_options_flags,
1849 null,
1850 0,
1851 );
1852 if (rc == .SUCCESS) {
1853 rc = w.ntdll.NtClose(tmp_handle);
1854 }
1855 switch (rc) {
1856 .SUCCESS => return,
1857 .OBJECT_NAME_INVALID => unreachable,
1858 .OBJECT_NAME_NOT_FOUND => return error.FileNotFound,
1859 .INVALID_PARAMETER => unreachable,
1860 .FILE_IS_A_DIRECTORY => return error.IsDir,
1861 .NOT_A_DIRECTORY => return error.NotDir,
1862 else => return w.unexpectedStatus(rc),
1863 }
1875pub fn unlinkatW(dirfd: fd_t, sub_path_w: []const u16, flags: u32) UnlinkatError!void {
1876 const remove_dir = (flags & AT_REMOVEDIR) != 0;
1877 return windows.DeleteFile(sub_path_w, .{ .dir = dirfd, .remove_dir = remove_dir });
18641878}
18651879
18661880const RenameError = error{
......@@ -2087,7 +2101,7 @@ pub fn renameatW(
20872101pub fn mkdirat(dir_fd: fd_t, sub_dir_path: []const u8, mode: u32) MakeDirError!void {
20882102 if (builtin.os.tag == .windows) {
20892103 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);
20912105 } else if (builtin.os.tag == .wasi) {
20922106 return mkdiratWasi(dir_fd, sub_dir_path, mode);
20932107 } else {
......@@ -2145,8 +2159,19 @@ pub fn mkdiratZ(dir_fd: fd_t, sub_dir_path: [*:0]const u8, mode: u32) MakeDirErr
21452159 }
21462160}
21472161
2148pub fn mkdiratW(dir_fd: fd_t, sub_path_w: [*:0]const u16, mode: u32) MakeDirError!void {
2149 const sub_dir_handle = try windows.CreateDirectoryW(dir_fd, sub_path_w, null);
2162pub fn mkdiratW(dir_fd: fd_t, sub_path_w: []const u16, mode: u32) MakeDirError!void {
2163 const sub_dir_handle = windows.OpenFile(sub_path_w, .{
2164 .dir = dir_fd,
2165 .access_mask = windows.GENERIC_READ | windows.SYNCHRONIZE,
2166 .creation = windows.FILE_CREATE,
2167 .io_mode = .blocking,
2168 .open_dir = true,
2169 }) catch |err| switch (err) {
2170 error.IsDir => unreachable,
2171 error.PipeBusy => unreachable,
2172 error.WouldBlock => unreachable,
2173 else => |e| return e,
2174 };
21502175 windows.CloseHandle(sub_dir_handle);
21512176}
21522177
......@@ -2175,9 +2200,8 @@ pub fn mkdir(dir_path: []const u8, mode: u32) MakeDirError!void {
21752200 if (builtin.os.tag == .wasi) {
21762201 @compileError("mkdir is not supported in WASI; use mkdirat instead");
21772202 } else if (builtin.os.tag == .windows) {
2178 const sub_dir_handle = try windows.CreateDirectory(null, dir_path, null);
2179 windows.CloseHandle(sub_dir_handle);
2180 return;
2203 const dir_path_w = try windows.sliceToPrefixedFileW(dir_path);
2204 return mkdirW(dir_path_w.span(), mode);
21812205 } else {
21822206 const dir_path_c = try toPosixPath(dir_path);
21832207 return mkdirZ(&dir_path_c, mode);
......@@ -2188,9 +2212,7 @@ pub fn mkdir(dir_path: []const u8, mode: u32) MakeDirError!void {
21882212pub fn mkdirZ(dir_path: [*:0]const u8, mode: u32) MakeDirError!void {
21892213 if (builtin.os.tag == .windows) {
21902214 const dir_path_w = try windows.cStrToPrefixedFileW(dir_path);
2191 const sub_dir_handle = try windows.CreateDirectoryW(null, dir_path_w.span().ptr, null);
2192 windows.CloseHandle(sub_dir_handle);
2193 return;
2215 return mkdirW(dir_path_w.span(), mode);
21942216 }
21952217 switch (errno(system.mkdir(dir_path, mode))) {
21962218 0 => return,
......@@ -2211,6 +2233,23 @@ pub fn mkdirZ(dir_path: [*:0]const u8, mode: u32) MakeDirError!void {
22112233 }
22122234}
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
22142253pub const DeleteDirError = error{
22152254 AccessDenied,
22162255 FileBusy,
......@@ -2231,7 +2270,7 @@ pub fn rmdir(dir_path: []const u8) DeleteDirError!void {
22312270 @compileError("rmdir is not supported in WASI; use unlinkat instead");
22322271 } else if (builtin.os.tag == .windows) {
22332272 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());
22352274 } else {
22362275 const dir_path_c = try toPosixPath(dir_path);
22372276 return rmdirZ(&dir_path_c);
......@@ -2244,7 +2283,7 @@ pub const rmdirC = @compileError("deprecated: renamed to rmdirZ");
22442283pub fn rmdirZ(dir_path: [*:0]const u8) DeleteDirError!void {
22452284 if (builtin.os.tag == .windows) {
22462285 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());
22482287 }
22492288 switch (errno(system.rmdir(dir_path))) {
22502289 0 => return,
......@@ -2265,6 +2304,14 @@ pub fn rmdirZ(dir_path: [*:0]const u8) DeleteDirError!void {
22652304 }
22662305}
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
22682315pub const ChangeCurDirError = error{
22692316 AccessDenied,
22702317 FileSystem,
......@@ -2354,7 +2401,8 @@ pub fn readlink(file_path: []const u8, out_buffer: []u8) ReadLinkError![]u8 {
23542401 if (builtin.os.tag == .wasi) {
23552402 @compileError("readlink is not supported in WASI; use readlinkat instead");
23562403 } 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);
23582406 } else {
23592407 const file_path_c = try toPosixPath(file_path);
23602408 return readlinkZ(&file_path_c, out_buffer);
......@@ -2363,17 +2411,17 @@ pub fn readlink(file_path: []const u8, out_buffer: []u8) ReadLinkError![]u8 {
23632411
23642412pub 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.
23672415/// See also `readlinkZ`.
2368pub fn readlinkW(file_path: [*:0]const u16, out_buffer: []u8) ReadLinkError![]u8 {
2369 return windows.ReadLinkW(std.fs.cwd().fd, file_path, out_buffer);
2416pub fn readlinkW(file_path: []const u16, out_buffer: []u8) ReadLinkError![]u8 {
2417 return windows.ReadLink(std.fs.cwd().fd, file_path, out_buffer);
23702418}
23712419
23722420/// Same as `readlink` except `file_path` is null-terminated.
23732421pub fn readlinkZ(file_path: [*:0]const u8, out_buffer: []u8) ReadLinkError![]u8 {
23742422 if (builtin.os.tag == .windows) {
23752423 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);
23772425 }
23782426 const rc = system.readlink(file_path, out_buffer.ptr, out_buffer.len);
23792427 switch (errno(rc)) {
......@@ -2399,7 +2447,8 @@ pub fn readlinkat(dirfd: fd_t, file_path: []const u8, out_buffer: []u8) ReadLink
23992447 return readlinkatWasi(dirfd, file_path, out_buffer);
24002448 }
24012449 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);
24032452 }
24042453 const file_path_c = try toPosixPath(file_path);
24052454 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
24292478
24302479/// Windows-only. Same as `readlinkat` except `file_path` is null-terminated, WTF16 encoded.
24312480/// See also `readlinkat`.
2432pub fn readlinkatW(dirfd: fd_t, file_path: [*:0]const u16, out_buffer: []u8) ReadLinkError![]u8 {
2433 return windows.ReadLinkW(dirfd, file_path, out_buffer);
2481pub fn readlinkatW(dirfd: fd_t, file_path: []const u16, out_buffer: []u8) ReadLinkError![]u8 {
2482 return windows.ReadLink(dirfd, file_path, out_buffer);
24342483}
24352484
24362485/// 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
24382487pub fn readlinkatZ(dirfd: fd_t, file_path: [*:0]const u8, out_buffer: []u8) ReadLinkError![]u8 {
24392488 if (builtin.os.tag == .windows) {
24402489 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);
24422491 }
24432492 const rc = system.readlinkat(dirfd, file_path, out_buffer.ptr, out_buffer.len);
24442493 switch (errno(rc)) {
......@@ -3959,7 +4008,7 @@ pub const RealPathError = error{
39594008pub fn realpath(pathname: []const u8, out_buffer: *[MAX_PATH_BYTES]u8) RealPathError![]u8 {
39604009 if (builtin.os.tag == .windows) {
39614010 const pathname_w = try windows.sliceToPrefixedFileW(pathname);
3962 return realpathW(pathname_w.span().ptr, out_buffer);
4011 return realpathW(pathname_w.span(), out_buffer);
39634012 }
39644013 if (builtin.os.tag == .wasi) {
39654014 @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");
39744023pub fn realpathZ(pathname: [*:0]const u8, out_buffer: *[MAX_PATH_BYTES]u8) RealPathError![]u8 {
39754024 if (builtin.os.tag == .windows) {
39764025 const pathname_w = try windows.cStrToPrefixedFileW(pathname);
3977 return realpathW(pathname_w.span().ptr, out_buffer);
4026 return realpathW(pathname_w.span(), out_buffer);
39784027 }
39794028 if (builtin.os.tag == .linux and !builtin.link_libc) {
39804029 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
40104059 return mem.spanZ(result_path);
40114060}
40124061
4013/// Same as `realpath` except `pathname` is null-terminated and UTF16LE-encoded.
4014/// TODO use ntdll for better semantics
4015pub fn realpathW(pathname: [*:0]const u16, out_buffer: *[MAX_PATH_BYTES]u8) RealPathError![]u8 {
4016 const h_file = try windows.CreateFileW(
4017 pathname,
4018 windows.GENERIC_READ,
4019 windows.FILE_SHARE_READ,
4020 null,
4021 windows.OPEN_EXISTING,
4022 windows.FILE_FLAG_BACKUP_SEMANTICS,
4023 null,
4024 );
4025 defer windows.CloseHandle(h_file);
4062/// Same as `realpath` except `pathname` is UTF16LE-encoded.
4063/// TODO use ntdll to emulate `GetFinalPathNameByHandleW` routine
4064pub fn realpathW(pathname: []const u16, out_buffer: *[MAX_PATH_BYTES]u8) RealPathError![]u8 {
4065 const w = windows;
4066
4067 const dir = std.fs.cwd().fd;
4068 const access_mask = w.GENERIC_READ | w.SYNCHRONIZE;
4069 const share_access = w.FILE_SHARE_READ;
4070 const creation = w.FILE_OPEN;
4071 const h_file = blk: {
4072 const res = w.OpenFile(pathname, .{
4073 .dir = dir,
4074 .access_mask = access_mask,
4075 .share_access = share_access,
4076 .creation = creation,
4077 .io_mode = .blocking,
4078 }) catch |err| switch (err) {
4079 error.IsDir => break :blk w.OpenFile(pathname, .{
4080 .dir = dir,
4081 .access_mask = access_mask,
4082 .share_access = share_access,
4083 .creation = creation,
4084 .io_mode = .blocking,
4085 .open_dir = true,
4086 }) catch |er| switch (er) {
4087 error.WouldBlock => unreachable,
4088 else => |e2| return e2,
4089 },
4090 error.WouldBlock => unreachable,
4091 else => |e| return e,
4092 };
4093 break :blk res;
4094 };
4095 defer w.CloseHandle(h_file);
40264096
4027 var wide_buf: [windows.PATH_MAX_WIDE]u16 = undefined;
4028 const wide_slice = try windows.GetFinalPathNameByHandleW(h_file, &wide_buf, wide_buf.len, windows.VOLUME_NAME_DOS);
4097 var wide_buf: [w.PATH_MAX_WIDE]u16 = undefined;
4098 const wide_slice = try w.GetFinalPathNameByHandleW(h_file, &wide_buf, wide_buf.len, w.VOLUME_NAME_DOS);
40294099
40304100 // Windows returns \\?\ prepended to the path.
40314101 // 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;
237237pub const IPPROTO_UDP = ws2_32.IPPROTO_UDP;
238238pub const IPPROTO_ICMPV6 = ws2_32.IPPROTO_ICMPV6;
239239pub 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;
33const testing = std.testing;
44const expect = testing.expect;
55const expectEqual = testing.expectEqual;
6const expectError = testing.expectError;
67const io = std.io;
78const fs = std.fs;
89const mem = std.mem;
......@@ -19,6 +20,95 @@ const tmpDir = std.testing.tmpDir;
1920const Dir = std.fs.Dir;
2021const 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
22112test "symlink with relative paths" {
23113 if (builtin.os.tag == .wasi) return error.SkipZigTest;
24114
......@@ -27,7 +117,7 @@ test "symlink with relative paths" {
27117 try cwd.writeFile("file.txt", "nonsense");
28118
29119 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);
31121 } else {
32122 try os.symlink("file.txt", "symlinked");
33123 }
......@@ -85,7 +175,7 @@ test "readlinkat" {
85175
86176 // create a symbolic link
87177 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);
89179 } else {
90180 try os.symlinkat("file.txt", tmp.dir.fd, "link");
91181 }
lib/std/os/windows.zig+78-264
......@@ -25,76 +25,11 @@ pub usingnamespace @import("windows/bits.zig");
2525
2626pub 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
9328pub const OpenError = error{
9429 IsDir,
30 NotDir,
9531 FileNotFound,
9632 NoDevice,
97 SharingViolation,
9833 AccessDenied,
9934 PipeBusy,
10035 PathAlreadyExists,
......@@ -111,15 +46,21 @@ pub const OpenFileOptions = struct {
11146 share_access_nonblocking: bool = false,
11247 creation: ULONG,
11348 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,
11455};
11556
11657/// TODO when share_access_nonblocking is false, this implementation uses
11758/// untinterruptible sleep() to block. This is not the final iteration of the API.
11859pub 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) {
12061 return error.IsDir;
12162 }
122 if (mem.eql(u16, sub_path_w, &[_]u16{ '.', '.' })) {
63 if (mem.eql(u16, sub_path_w, &[_]u16{ '.', '.' }) and !options.open_dir) {
12364 return error.IsDir;
12465 }
12566
......@@ -142,11 +83,13 @@ pub fn OpenFile(sub_path_w: []const u16, options: OpenFileOptions) OpenError!HAN
14283 .SecurityQualityOfService = null,
14384 };
14485 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
14691 var delay: usize = 1;
14792 while (true) {
148 var flags: ULONG = undefined;
149 const blocking_flag: ULONG = if (options.io_mode == .blocking) FILE_SYNCHRONOUS_IO_NONALERT else 0;
15093 const rc = ntdll.NtCreateFile(
15194 &result,
15295 options.access_mask,
......@@ -156,7 +99,7 @@ pub fn OpenFile(sub_path_w: []const u16, options: OpenFileOptions) OpenError!HAN
15699 FILE_ATTRIBUTE_NORMAL,
157100 options.share_access,
158101 options.creation,
159 FILE_NON_DIRECTORY_FILE | blocking_flag,
102 flags,
160103 null,
161104 0,
162105 );
......@@ -184,6 +127,7 @@ pub fn OpenFile(sub_path_w: []const u16, options: OpenFileOptions) OpenError!HAN
184127 .OBJECT_PATH_SYNTAX_BAD => unreachable,
185128 .OBJECT_NAME_COLLISION => return error.PathAlreadyExists,
186129 .FILE_IS_A_DIRECTORY => return error.IsDir,
130 .NOT_A_DIRECTORY => return error.NotDir,
187131 else => return unexpectedStatus(rc),
188132 }
189133 }
......@@ -638,27 +582,14 @@ pub const CreateSymbolicLinkError = error{
638582 PathAlreadyExists,
639583 FileNotFound,
640584 NameTooLong,
641 InvalidUtf8,
642 BadPathName,
643585 NoDevice,
644586 Unexpected,
645587};
646588
647589pub fn CreateSymbolicLink(
648590 dir: ?HANDLE,
649 sym_link_path: []const u8,
650 target_path: []const u8,
651 is_directory: bool,
652) CreateSymbolicLinkError!void {
653 const sym_link_path_w = try sliceToPrefixedFileW(sym_link_path);
654 const target_path_w = try sliceToPrefixedFileW(target_path);
655 return CreateSymbolicLinkW(dir, sym_link_path_w.span(), target_path_w.span(), is_directory);
656}
657
658pub fn CreateSymbolicLinkW(
659 dir: ?HANDLE,
660 sym_link_path: [:0]const u16,
661 target_path: [:0]const u16,
591 sym_link_path: []const u16,
592 target_path: []const u16,
662593 is_directory: bool,
663594) CreateSymbolicLinkError!void {
664595 const SYMLINK_DATA = extern struct {
......@@ -672,71 +603,19 @@ pub fn CreateSymbolicLinkW(
672603 Flags: ULONG,
673604 };
674605
675 var symlink_handle: HANDLE = undefined;
676 if (is_directory) {
677 const sym_link_len_bytes = math.cast(u16, sym_link_path.len * 2) catch |err| switch (err) {
678 error.Overflow => return error.NameTooLong,
679 };
680 var nt_name = UNICODE_STRING{
681 .Length = sym_link_len_bytes,
682 .MaximumLength = sym_link_len_bytes,
683 .Buffer = @intToPtr([*]u16, @ptrToInt(sym_link_path.ptr)),
684 };
685
686 if (sym_link_path[0] == '.' and sym_link_path[1] == 0) {
687 // Windows does not recognize this, but it does work with empty string.
688 nt_name.Length = 0;
689 }
690
691 var attr = OBJECT_ATTRIBUTES{
692 .Length = @sizeOf(OBJECT_ATTRIBUTES),
693 .RootDirectory = if (std.fs.path.isAbsoluteWindowsW(sym_link_path)) null else dir,
694 .Attributes = 0, // Note we do not use OBJ_CASE_INSENSITIVE here.
695 .ObjectName = &nt_name,
696 .SecurityDescriptor = null,
697 .SecurityQualityOfService = null,
698 };
699
700 var io: IO_STATUS_BLOCK = undefined;
701 const rc = ntdll.NtCreateFile(
702 &symlink_handle,
703 GENERIC_READ | SYNCHRONIZE | FILE_WRITE_ATTRIBUTES,
704 &attr,
705 &io,
706 null,
707 FILE_ATTRIBUTE_NORMAL,
708 FILE_SHARE_READ,
709 FILE_CREATE,
710 FILE_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT | FILE_OPEN_FOR_BACKUP_INTENT,
711 null,
712 0,
713 );
714 switch (rc) {
715 .SUCCESS => {},
716 .OBJECT_NAME_INVALID => unreachable,
717 .OBJECT_NAME_NOT_FOUND => return error.FileNotFound,
718 .OBJECT_PATH_NOT_FOUND => return error.FileNotFound,
719 .NO_MEDIA_IN_DEVICE => return error.NoDevice,
720 .INVALID_PARAMETER => unreachable,
721 .ACCESS_DENIED => return error.AccessDenied,
722 .OBJECT_PATH_SYNTAX_BAD => unreachable,
723 .OBJECT_NAME_COLLISION => return error.PathAlreadyExists,
724 else => return unexpectedStatus(rc),
725 }
726 } else {
727 symlink_handle = OpenFile(sym_link_path, .{
728 .access_mask = SYNCHRONIZE | GENERIC_READ | GENERIC_WRITE,
729 .dir = dir,
730 .creation = FILE_CREATE,
731 .io_mode = .blocking,
732 }) catch |err| switch (err) {
733 error.WouldBlock => unreachable,
734 error.IsDir => return error.PathAlreadyExists,
735 error.PipeBusy => unreachable,
736 error.SharingViolation => return error.AccessDenied,
737 else => |e| return e,
738 };
739 }
606 const symlink_handle = OpenFile(sym_link_path, .{
607 .access_mask = SYNCHRONIZE | GENERIC_READ | GENERIC_WRITE,
608 .dir = dir,
609 .creation = FILE_CREATE,
610 .io_mode = .blocking,
611 .open_dir = is_directory,
612 }) catch |err| switch (err) {
613 error.IsDir => return error.PathAlreadyExists,
614 error.NotDir => unreachable,
615 error.WouldBlock => unreachable,
616 error.PipeBusy => unreachable,
617 else => |e| return e,
618 };
740619 defer CloseHandle(symlink_handle);
741620
742621 // prepare reparse data buffer
......@@ -767,44 +646,32 @@ pub const ReadLinkError = error{
767646 Unexpected,
768647 NameTooLong,
769648 UnsupportedReparsePointType,
770 InvalidUtf8,
771 BadPathName,
772649};
773650
774pub fn ReadLink(
775 dir: ?HANDLE,
776 sub_path: []const u8,
777 out_buffer: []u8,
778) ReadLinkError![]u8 {
779 const sub_path_w = try sliceToPrefixedFileW(sub_path);
780 return ReadLinkW(dir, sub_path_w.span().ptr, out_buffer);
781}
782
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) {
651pub fn ReadLink(dir: ?HANDLE, sub_path_w: []const u16, out_buffer: []u8) ReadLinkError![]u8 {
652 // Here, we use `NtCreateFile` to shave off one syscall if we were to use `OpenFile` wrapper.
653 // With the latter, we'd need to call `NtCreateFile` twice, once for file symlink, and if that
654 // failed, again for dir symlink. Omitting any mention of file/dir flags makes it possible
655 // to open the symlink there and then.
656 const path_len_bytes = math.cast(u16, sub_path_w.len * 2) catch |err| switch (err) {
785657 error.Overflow => return error.NameTooLong,
786658 };
787659 var nt_name = UNICODE_STRING{
788660 .Length = path_len_bytes,
789661 .MaximumLength = path_len_bytes,
790 .Buffer = @intToPtr([*]u16, @ptrToInt(sub_path_w)),
662 .Buffer = @intToPtr([*]u16, @ptrToInt(sub_path_w.ptr)),
791663 };
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
798664 var attr = OBJECT_ATTRIBUTES{
799665 .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,
801667 .Attributes = 0, // Note we do not use OBJ_CASE_INSENSITIVE here.
802668 .ObjectName = &nt_name,
803669 .SecurityDescriptor = null,
804670 .SecurityQualityOfService = null,
805671 };
806 var io: IO_STATUS_BLOCK = undefined;
807672 var result_handle: HANDLE = undefined;
673 var io: IO_STATUS_BLOCK = undefined;
674
808675 const rc = ntdll.NtCreateFile(
809676 &result_handle,
810677 FILE_READ_ATTRIBUTES,
......@@ -878,135 +745,83 @@ pub const DeleteFileError = error{
878745 NameTooLong,
879746 FileBusy,
880747 Unexpected,
748 NotDir,
749 IsDir,
881750};
882751
883pub fn DeleteFile(filename: []const u8) DeleteFileError!void {
884 const filename_w = try sliceToPrefixedFileW(filename);
885 return DeleteFileW(filename_w.span().ptr);
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,
752pub const DeleteFileOptions = struct {
753 dir: ?HANDLE,
754 remove_dir: bool = false,
927755};
928756
929/// Returns an open directory handle which the caller is responsible for closing with `CloseHandle`.
930pub fn CreateDirectory(dir: ?HANDLE, pathname: []const u8, sa: ?*SECURITY_ATTRIBUTES) CreateDirectoryError!HANDLE {
931 const pathname_w = try sliceToPrefixedFileW(pathname);
932 return CreateDirectoryW(dir, pathname_w.span().ptr, sa);
933}
757pub fn DeleteFile(sub_path_w: []const u16, options: DeleteFileOptions) DeleteFileError!void {
758 const create_options_flags: ULONG = if (options.remove_dir)
759 FILE_DELETE_ON_CLOSE | FILE_DIRECTORY_FILE | FILE_OPEN_REPARSE_POINT
760 else
761 FILE_DELETE_ON_CLOSE | FILE_NON_DIRECTORY_FILE | FILE_OPEN_REPARSE_POINT; // would we ever want to delete the target instead?
934762
935/// Same as `CreateDirectory` except takes a WTF-16 encoded path.
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 };
763 const path_len_bytes = @intCast(u16, sub_path_w.len * 2);
944764 var nt_name = UNICODE_STRING{
945765 .Length = path_len_bytes,
946766 .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)),
948769 };
949770
950771 if (sub_path_w[0] == '.' and sub_path_w[1] == 0) {
951772 // Windows does not recognize this, but it does work with empty string.
952773 nt_name.Length = 0;
953774 }
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
955780 var attr = OBJECT_ATTRIBUTES{
956781 .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,
958783 .Attributes = 0, // Note we do not use OBJ_CASE_INSENSITIVE here.
959784 .ObjectName = &nt_name,
960 .SecurityDescriptor = if (sa) |ptr| ptr.lpSecurityDescriptor else null,
785 .SecurityDescriptor = null,
961786 .SecurityQualityOfService = null,
962787 };
963788 var io: IO_STATUS_BLOCK = undefined;
964 var result_handle: HANDLE = undefined;
965 const rc = ntdll.NtCreateFile(
966 &result_handle,
967 GENERIC_READ | SYNCHRONIZE,
789 var tmp_handle: HANDLE = undefined;
790 var rc = ntdll.NtCreateFile(
791 &tmp_handle,
792 SYNCHRONIZE | DELETE,
968793 &attr,
969794 &io,
970795 null,
971 FILE_ATTRIBUTE_NORMAL,
972 FILE_SHARE_READ,
973 FILE_CREATE,
974 FILE_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT,
796 0,
797 FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
798 FILE_OPEN,
799 create_options_flags,
975800 null,
976801 0,
977802 );
978803 switch (rc) {
979 .SUCCESS => return result_handle,
804 .SUCCESS => return CloseHandle(tmp_handle),
980805 .OBJECT_NAME_INVALID => unreachable,
981806 .OBJECT_NAME_NOT_FOUND => return error.FileNotFound,
982 .OBJECT_PATH_NOT_FOUND => return error.FileNotFound,
983 .NO_MEDIA_IN_DEVICE => return error.NoDevice,
984807 .INVALID_PARAMETER => unreachable,
985 .ACCESS_DENIED => return error.AccessDenied,
986 .OBJECT_PATH_SYNTAX_BAD => unreachable,
987 .OBJECT_NAME_COLLISION => return error.PathAlreadyExists,
808 .FILE_IS_A_DIRECTORY => return error.IsDir,
809 .NOT_A_DIRECTORY => return error.NotDir,
988810 else => return unexpectedStatus(rc),
989811 }
990812}
991813
992pub const RemoveDirectoryError = error{
993 FileNotFound,
994 DirNotEmpty,
995 Unexpected,
996 NotDir,
997};
814pub const MoveFileError = error{Unexpected};
998815
999pub fn RemoveDirectory(dir_path: []const u8) RemoveDirectoryError!void {
1000 const dir_path_w = try sliceToPrefixedFileW(dir_path);
1001 return RemoveDirectoryW(dir_path_w.span().ptr);
816pub fn MoveFileEx(old_path: []const u8, new_path: []const u8, flags: DWORD) MoveFileError!void {
817 const old_path_w = try sliceToPrefixedFileW(old_path);
818 const new_path_w = try sliceToPrefixedFileW(new_path);
819 return MoveFileExW(old_path_w.span().ptr, new_path_w.span().ptr, flags);
1002820}
1003821
1004pub fn RemoveDirectoryW(dir_path_w: [*:0]const u16) RemoveDirectoryError!void {
1005 if (kernel32.RemoveDirectoryW(dir_path_w) == 0) {
822pub fn MoveFileExW(old_path: [*:0]const u16, new_path: [*:0]const u16, flags: DWORD) MoveFileError!void {
823 if (kernel32.MoveFileExW(old_path, new_path, flags) == 0) {
1006824 switch (kernel32.GetLastError()) {
1007 .PATH_NOT_FOUND => return error.FileNotFound,
1008 .DIR_NOT_EMPTY => return error.DirNotEmpty,
1009 .DIRECTORY => return error.NotDir,
1010825 else => |err| return unexpectedError(err),
1011826 }
1012827 }
......@@ -1493,8 +1308,7 @@ pub fn cStrToPrefixedFileW(s: [*:0]const u8) !PathSpace {
14931308}
14941309
14951310/// Converts the path `s` to WTF16, null-terminated. If the path is absolute,
1496/// it will get NT-style prefix `\??\` prepended automatically. For prepending
1497/// Win32-style prefix, see `sliceToWin32PrefixedFileW` instead.
1311/// it will get NT-style prefix `\??\` prepended automatically.
14981312pub fn sliceToPrefixedFileW(s: []const u8) !PathSpace {
14991313 // TODO https://github.com/ziglang/zig/issues/2765
15001314 var path_space: PathSpace = undefined;