authorgravatar for kubkon@jakubkonka.comJakub Konka <kubkon@jakubkonka.com> 2020-07-21 09:26:01+02:00
committergravatar for kubkon@jakubkonka.comJakub Konka <kubkon@jakubkonka.com> 2020-07-22 08:51:23+02:00
log4887350bf420db3193569e8adb30937e6379db0c
treedf624623555409115648af7a7c348b0cb01a99f4
parent99f0e64fa0037ae0b8894ec326f854531ef3bfe6

Finish drafting CreateSymolicLink using NT calls


3 files changed, 75 insertions(+), 81 deletions(-)

lib/std/fs.zig+41-29
......@@ -66,15 +66,7 @@ pub const need_async_thread = std.io.is_async and switch (builtin.os.tag) {
6666
6767/// TODO remove the allocator requirement from this API
6868pub fn atomicSymLink(allocator: *Allocator, existing_path: []const u8, new_path: []const u8) !void {
69 const res = blk: {
70 // TODO this is just a temporary until Dir.symLink is implemented on Windows
71 if (builtin.os.tag == .windows) {
72 break :blk os.windows.CreateSymbolicLink(new_path, existing_path, false);
73 } else {
74 break :blk cwd().symLink(existing_path, new_path, .{});
75 }
76 };
77 if (res) {
69 if (cwd().symLink(existing_path, new_path, .{})) {
7870 return;
7971 } else |err| switch (err) {
8072 error.PathAlreadyExists => {},
......@@ -92,15 +84,7 @@ pub fn atomicSymLink(allocator: *Allocator, existing_path: []const u8, new_path:
9284 try crypto.randomBytes(rand_buf[0..]);
9385 base64_encoder.encode(tmp_path[dirname.len + 1 ..], &rand_buf);
9486
95 const res2 = blk: {
96 // TODO this is just a temporary until Dir.symLink is implemented on Windows
97 if (builtin.os.tag == .windows) {
98 break :blk os.windows.CreateSymbolicLink(tmp_path, existing_path, false);
99 } else {
100 break :blk cwd().symLink(existing_path, new_path, .{});
101 }
102 };
103 if (res2) {
87 if (cwd().symLink(existing_path, new_path, .{})) {
10488 return rename(tmp_path, new_path);
10589 } else |err| switch (err) {
10690 error.PathAlreadyExists => continue,
......@@ -1239,10 +1223,12 @@ pub const Dir = struct {
12391223 flags: SymLinkFlags,
12401224 ) !void {
12411225 if (builtin.os.tag == .wasi) {
1242 return self.symLinkWasi(target_path, sym_link_path);
1226 return self.symLinkWasi(target_path, sym_link_path, flags);
12431227 }
12441228 if (builtin.os.tag == .windows) {
1245 @compileError("TODO implement Dir.symLink on Windows");
1229 const target_path_w = try os.windows.sliceToPrefixedFileW(target_path);
1230 const sym_link_path_w = try os.windows.sliceToPrefixedFileW(sym_link_path);
1231 return self.symLinkW(target_path_w.span(), sym_link_path_w.span(), flags);
12461232 }
12471233 const target_path_c = try os.toPosixPath(target_path);
12481234 const sym_link_path_c = try os.toPosixPath(sym_link_path);
......@@ -1250,15 +1236,41 @@ pub const Dir = struct {
12501236 }
12511237
12521238 /// WASI-only. Same as `symLink` except targeting WASI.
1253 pub fn symLinkWasi(self: Dir, target_path: []const u8, sym_link_path: []const u8, flags: SymLinkFlags) !void {
1239 pub fn symLinkWasi(
1240 self: Dir,
1241 target_path: []const u8,
1242 sym_link_path: []const u8,
1243 flags: SymLinkFlags,
1244 ) !void {
12541245 return os.symlinkatWasi(target_path, self.fd, sym_link_path);
12551246 }
12561247
12571248 /// Same as `symLink`, except the pathname parameters are null-terminated.
1258 pub fn symLinkZ(self: Dir, target_path_c: [*:0]const u8, sym_link_path_c: [*:0]const u8, flags: SymLinkFlags) !void {
1249 pub fn symLinkZ(
1250 self: Dir,
1251 target_path_c: [*:0]const u8,
1252 sym_link_path_c: [*:0]const u8,
1253 flags: SymLinkFlags,
1254 ) !void {
1255 if (builtin.os.tag == .windows) {
1256 const target_path_w = try os.windows.cStrToPrefixedFileW(target_path_c);
1257 const sym_link_path_w = try os.windows.cStrToPrefixedFileW(sym_link_path_c);
1258 return self.symLinkW(target_path_w.span(), sym_link_path_w.span(), flags);
1259 }
12591260 return os.symlinkatZ(target_path_c, self.fd, sym_link_path_c);
12601261 }
12611262
1263 /// Windows-only. Same as `symLink` except the pathname parameters
1264 /// are null-terminated, WTF16 encoded.
1265 pub fn symLinkW(
1266 self: Dir,
1267 target_path_w: [:0]const u16,
1268 sym_link_path_w: [:0]const u16,
1269 flags: SymLinkFlags,
1270 ) !void {
1271 return os.windows.CreateSymbolicLinkW(self.fd, sym_link_path_w, target_path_w, flags.is_directory);
1272 }
1273
12621274 /// Read value of a symbolic link.
12631275 /// The return value is a slice of `buffer`, from index `0`.
12641276 /// Asserts that the path parameter has no null bytes.
......@@ -1761,10 +1773,10 @@ pub fn readLinkAbsoluteZ(pathname_c: [*:0]const u8, buffer: *[MAX_PATH_BYTES]u8)
17611773pub const readLink = @compileError("deprecated; use Dir.readLink or readLinkAbsolute");
17621774pub const readLinkC = @compileError("deprecated; use Dir.readLinkZ or readLinkAbsoluteZ");
17631775
1764/// Use with `symLinkAbsolute` to specify whether the symlink will point to a file
1765/// or a directory. This value is ignored on all hosts except Windows where
1766/// creating symlinks to different resource types, requires different flags.
1767/// By default, `symLinkAbsolute` is assumed to point to a file.
1776/// Use with `Dir.symLink` and `symLinkAbsolute` to specify whether the symlink
1777/// will point to a file or a directory. This value is ignored on all hosts
1778/// except Windows where creating symlinks to different resource types, requires
1779/// different flags. By default, `symLinkAbsolute` is assumed to point to a file.
17681780pub const SymLinkFlags = struct {
17691781 is_directory: bool = false,
17701782};
......@@ -1781,7 +1793,7 @@ pub fn symLinkAbsolute(target_path: []const u8, sym_link_path: []const u8, flags
17811793 assert(path.isAbsolute(target_path));
17821794 assert(path.isAbsolute(sym_link_path));
17831795 if (builtin.os.tag == .windows) {
1784 return os.windows.CreateSymbolicLink(sym_link_path, target_path, flags.is_directory);
1796 return os.windows.CreateSymbolicLink(null, sym_link_path, target_path, flags.is_directory);
17851797 }
17861798 return os.symlink(target_path, sym_link_path);
17871799}
......@@ -1790,10 +1802,10 @@ pub fn symLinkAbsolute(target_path: []const u8, sym_link_path: []const u8, flags
17901802/// Note that this function will by default try creating a symbolic link to a file. If you would
17911803/// like to create a symbolic link to a directory, specify this with `SymLinkFlags{ .is_directory = true }`.
17921804/// See also `symLinkAbsolute`, `symLinkAbsoluteZ`.
1793pub fn symLinkAbsoluteW(target_path_w: [*:0]const u16, sym_link_path_w: [*:0]const u16, flags: SymLinkFlags) !void {
1805pub fn symLinkAbsoluteW(target_path_w: [:0]const u16, sym_link_path_w: [:0]const u16, flags: SymLinkFlags) !void {
17941806 assert(path.isAbsoluteWindowsW(target_path_w));
17951807 assert(path.isAbsoluteWindowsW(sym_link_path_w));
1796 return os.windows.CreateSymbolicLinkW(sym_link_path_w, target_path_w, flags.is_directory);
1808 return os.windows.CreateSymbolicLinkW(null, sym_link_path_w, target_path_w, flags.is_directory);
17971809}
17981810
17991811/// Same as `symLinkAbsolute` except the parameters are null-terminated pointers.
lib/std/os/test.zig+1-1
......@@ -27,7 +27,7 @@ test "symlink with relative paths" {
2727 try cwd.writeFile("file.txt", "nonsense");
2828
2929 if (builtin.os.tag == .windows) {
30 try os.windows.CreateSymbolicLink("symlinked", "file.txt", false);
30 try os.windows.CreateSymbolicLink(cwd.fd, "symlinked", "file.txt", false);
3131 } else {
3232 try os.symlink("file.txt", "symlinked");
3333 }
lib/std/os/windows.zig+33-51
......@@ -602,9 +602,34 @@ pub fn GetCurrentDirectory(buffer: []u8) GetCurrentDirectoryError![]u8 {
602602 return buffer[0..end_index];
603603}
604604
605pub const CreateSymbolicLinkError = error{ AccessDenied, PathAlreadyExists, FileNotFound, NameTooLong, InvalidUtf8, BadPathName, Unexpected };
605pub const CreateSymbolicLinkError = error{
606 AccessDenied,
607 PathAlreadyExists,
608 FileNotFound,
609 NameTooLong,
610 InvalidUtf8,
611 BadPathName,
612 NoDevice,
613 Unexpected,
614};
615
616pub fn CreateSymbolicLink(
617 dir: ?HANDLE,
618 sym_link_path: []const u8,
619 target_path: []const u8,
620 is_directory: bool,
621) CreateSymbolicLinkError!void {
622 const sym_link_path_w = try sliceToPrefixedFileW(sym_link_path);
623 const target_path_w = try sliceToPrefixedFileW(target_path);
624 return CreateSymbolicLinkW(dir, sym_link_path_w.span(), target_path_w.span(), is_directory);
625}
606626
607pub fn NtCreateSymbolicLinkW(dir: ?HANDLE, sym_link_path: [:0]const u16, target_path: [:0]const u16, is_directory: bool) CreateSymbolicLinkError!void {
627pub fn CreateSymbolicLinkW(
628 dir: ?HANDLE,
629 sym_link_path: [:0]const u16,
630 target_path: [:0]const u16,
631 is_directory: bool,
632) CreateSymbolicLinkError!void {
608633 const SYMLINK_DATA = extern struct {
609634 ReparseTag: ULONG,
610635 ReparseDataLength: USHORT,
......@@ -660,7 +685,7 @@ pub fn NtCreateSymbolicLinkW(dir: ?HANDLE, sym_link_path: [:0]const u16, target_
660685 .OBJECT_NAME_INVALID => unreachable,
661686 .OBJECT_NAME_NOT_FOUND => return error.FileNotFound,
662687 .OBJECT_PATH_NOT_FOUND => return error.FileNotFound,
663 // .NO_MEDIA_IN_DEVICE => return error.NoDevice,
688 .NO_MEDIA_IN_DEVICE => return error.NoDevice,
664689 .INVALID_PARAMETER => unreachable,
665690 .ACCESS_DENIED => return error.AccessDenied,
666691 .OBJECT_PATH_SYNTAX_BAD => unreachable,
......@@ -674,7 +699,11 @@ pub fn NtCreateSymbolicLinkW(dir: ?HANDLE, sym_link_path: [:0]const u16, target_
674699 .creation = FILE_CREATE,
675700 .io_mode = .blocking,
676701 }) catch |err| switch (err) {
677 else => |e| unreachable,
702 error.WouldBlock => unreachable,
703 error.IsDir => return error.PathAlreadyExists,
704 error.PipeBusy => unreachable,
705 error.SharingViolation => return error.AccessDenied,
706 else => |e| return e,
678707 };
679708 }
680709 defer CloseHandle(symlink_handle);
......@@ -702,53 +731,6 @@ pub fn NtCreateSymbolicLinkW(dir: ?HANDLE, sym_link_path: [:0]const u16, target_
702731 _ = try DeviceIoControl(symlink_handle, FSCTL_SET_REPARSE_POINT, buffer[0..buf_len], null, null);
703732}
704733
705pub fn CreateSymbolicLink(
706 sym_link_path: []const u8,
707 target_path: []const u8,
708 is_directory: bool,
709) CreateSymbolicLinkError!void {
710 const sym_link_path_w = try sliceToWin32PrefixedFileW(sym_link_path);
711 const target_path_w = try sliceToWin32PrefixedFileW(target_path);
712 return CreateSymbolicLinkW(sym_link_path_w.span().ptr, target_path_w.span().ptr, is_directory);
713}
714
715pub fn CreateSymbolicLinkW(
716 sym_link_path: [*:0]const u16,
717 target_path: [*:0]const u16,
718 is_directory: bool,
719) CreateSymbolicLinkError!void {
720 // Previously, until Win 10 Creators Update, creating symbolic links required
721 // SeCreateSymbolicLink privilege. Currently, this is no longer required if the
722 // OS is in Developer Mode; however, SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE
723 // must be added to the input flags.
724 const flags = if (is_directory) SYMBOLIC_LINK_FLAG_DIRECTORY else 0;
725 if (kernel32.CreateSymbolicLinkW(sym_link_path, target_path, flags | SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE) == 0) {
726 switch (kernel32.GetLastError()) {
727 .INVALID_PARAMETER => {
728 // If we're on Windows pre Creators Update, SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE
729 // flag is an invalid parameter, in which case repeat without the flag.
730 if (kernel32.CreateSymbolicLinkW(sym_link_path, target_path, flags) == 0) {
731 switch (kernel32.GetLastError()) {
732 .PRIVILEGE_NOT_HELD => return error.AccessDenied,
733 .FILE_NOT_FOUND => return error.FileNotFound,
734 .PATH_NOT_FOUND => return error.FileNotFound,
735 .ACCESS_DENIED => return error.AccessDenied,
736 .ALREADY_EXISTS => return error.PathAlreadyExists,
737 else => |err| return unexpectedError(err),
738 }
739 }
740 return;
741 },
742 .PRIVILEGE_NOT_HELD => return error.AccessDenied,
743 .FILE_NOT_FOUND => return error.FileNotFound,
744 .PATH_NOT_FOUND => return error.FileNotFound,
745 .ACCESS_DENIED => return error.AccessDenied,
746 .ALREADY_EXISTS => return error.PathAlreadyExists,
747 else => |err| return unexpectedError(err),
748 }
749 }
750}
751
752734pub const DeleteFileError = error{
753735 FileNotFound,
754736 AccessDenied,