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) {...@@ -66,15 +66,7 @@ pub const need_async_thread = std.io.is_async and switch (builtin.os.tag) {
6666
67/// TODO remove the allocator requirement from this API67/// TODO remove the allocator requirement from this API
68pub fn atomicSymLink(allocator: *Allocator, existing_path: []const u8, new_path: []const u8) !void {68pub fn atomicSymLink(allocator: *Allocator, existing_path: []const u8, new_path: []const u8) !void {
69 const res = blk: {69 if (cwd().symLink(existing_path, new_path, .{})) {
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) {
78 return;70 return;
79 } else |err| switch (err) {71 } else |err| switch (err) {
80 error.PathAlreadyExists => {},72 error.PathAlreadyExists => {},
...@@ -92,15 +84,7 @@ pub fn atomicSymLink(allocator: *Allocator, existing_path: []const u8, new_path:...@@ -92,15 +84,7 @@ pub fn atomicSymLink(allocator: *Allocator, existing_path: []const u8, new_path:
92 try crypto.randomBytes(rand_buf[0..]);84 try crypto.randomBytes(rand_buf[0..]);
93 base64_encoder.encode(tmp_path[dirname.len + 1 ..], &rand_buf);85 base64_encoder.encode(tmp_path[dirname.len + 1 ..], &rand_buf);
9486
95 const res2 = blk: {87 if (cwd().symLink(existing_path, new_path, .{})) {
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) {
104 return rename(tmp_path, new_path);88 return rename(tmp_path, new_path);
105 } else |err| switch (err) {89 } else |err| switch (err) {
106 error.PathAlreadyExists => continue,90 error.PathAlreadyExists => continue,
...@@ -1239,10 +1223,12 @@ pub const Dir = struct {...@@ -1239,10 +1223,12 @@ pub const Dir = struct {
1239 flags: SymLinkFlags,1223 flags: SymLinkFlags,
1240 ) !void {1224 ) !void {
1241 if (builtin.os.tag == .wasi) {1225 if (builtin.os.tag == .wasi) {
1242 return self.symLinkWasi(target_path, sym_link_path);1226 return self.symLinkWasi(target_path, sym_link_path, flags);
1243 }1227 }
1244 if (builtin.os.tag == .windows) {1228 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);
1246 }1232 }
1247 const target_path_c = try os.toPosixPath(target_path);1233 const target_path_c = try os.toPosixPath(target_path);
1248 const sym_link_path_c = try os.toPosixPath(sym_link_path);1234 const sym_link_path_c = try os.toPosixPath(sym_link_path);
...@@ -1250,15 +1236,41 @@ pub const Dir = struct {...@@ -1250,15 +1236,41 @@ pub const Dir = struct {
1250 }1236 }
12511237
1252 /// WASI-only. Same as `symLink` except targeting WASI.1238 /// 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 {
1254 return os.symlinkatWasi(target_path, self.fd, sym_link_path);1245 return os.symlinkatWasi(target_path, self.fd, sym_link_path);
1255 }1246 }
12561247
1257 /// Same as `symLink`, except the pathname parameters are null-terminated.1248 /// 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 }
1259 return os.symlinkatZ(target_path_c, self.fd, sym_link_path_c);1260 return os.symlinkatZ(target_path_c, self.fd, sym_link_path_c);
1260 }1261 }
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
1262 /// Read value of a symbolic link.1274 /// Read value of a symbolic link.
1263 /// The return value is a slice of `buffer`, from index `0`.1275 /// The return value is a slice of `buffer`, from index `0`.
1264 /// Asserts that the path parameter has no null bytes.1276 /// 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)...@@ -1761,10 +1773,10 @@ pub fn readLinkAbsoluteZ(pathname_c: [*:0]const u8, buffer: *[MAX_PATH_BYTES]u8)
1761pub const readLink = @compileError("deprecated; use Dir.readLink or readLinkAbsolute");1773pub const readLink = @compileError("deprecated; use Dir.readLink or readLinkAbsolute");
1762pub const readLinkC = @compileError("deprecated; use Dir.readLinkZ or readLinkAbsoluteZ");1774pub const readLinkC = @compileError("deprecated; use Dir.readLinkZ or readLinkAbsoluteZ");
17631775
1764/// Use with `symLinkAbsolute` to specify whether the symlink will point to a file1776/// Use with `Dir.symLink` and `symLinkAbsolute` to specify whether the symlink
1765/// or a directory. This value is ignored on all hosts except Windows where1777/// will point to a file or a directory. This value is ignored on all hosts
1766/// creating symlinks to different resource types, requires different flags.1778/// except Windows where creating symlinks to different resource types, requires
1767/// By default, `symLinkAbsolute` is assumed to point to a file.1779/// different flags. By default, `symLinkAbsolute` is assumed to point to a file.
1768pub const SymLinkFlags = struct {1780pub const SymLinkFlags = struct {
1769 is_directory: bool = false,1781 is_directory: bool = false,
1770};1782};
...@@ -1781,7 +1793,7 @@ pub fn symLinkAbsolute(target_path: []const u8, sym_link_path: []const u8, flags...@@ -1781,7 +1793,7 @@ pub fn symLinkAbsolute(target_path: []const u8, sym_link_path: []const u8, flags
1781 assert(path.isAbsolute(target_path));1793 assert(path.isAbsolute(target_path));
1782 assert(path.isAbsolute(sym_link_path));1794 assert(path.isAbsolute(sym_link_path));
1783 if (builtin.os.tag == .windows) {1795 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);
1785 }1797 }
1786 return os.symlink(target_path, sym_link_path);1798 return os.symlink(target_path, sym_link_path);
1787}1799}
...@@ -1790,10 +1802,10 @@ pub fn symLinkAbsolute(target_path: []const u8, sym_link_path: []const u8, flags...@@ -1790,10 +1802,10 @@ pub fn symLinkAbsolute(target_path: []const u8, sym_link_path: []const u8, flags
1790/// Note that this function will by default try creating a symbolic link to a file. If you would1802/// Note that this function will by default try creating a symbolic link to a file. If you would
1791/// like to create a symbolic link to a directory, specify this with `SymLinkFlags{ .is_directory = true }`.1803/// like to create a symbolic link to a directory, specify this with `SymLinkFlags{ .is_directory = true }`.
1792/// See also `symLinkAbsolute`, `symLinkAbsoluteZ`.1804/// 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 {
1794 assert(path.isAbsoluteWindowsW(target_path_w));1806 assert(path.isAbsoluteWindowsW(target_path_w));
1795 assert(path.isAbsoluteWindowsW(sym_link_path_w));1807 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);
1797}1809}
17981810
1799/// Same as `symLinkAbsolute` except the parameters are null-terminated pointers.1811/// 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" {...@@ -27,7 +27,7 @@ test "symlink with relative paths" {
27 try cwd.writeFile("file.txt", "nonsense");27 try cwd.writeFile("file.txt", "nonsense");
2828
29 if (builtin.os.tag == .windows) {29 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);
31 } else {31 } else {
32 try os.symlink("file.txt", "symlinked");32 try os.symlink("file.txt", "symlinked");
33 }33 }
lib/std/os/windows.zig+33-51
...@@ -602,9 +602,34 @@ pub fn GetCurrentDirectory(buffer: []u8) GetCurrentDirectoryError![]u8 {...@@ -602,9 +602,34 @@ pub fn GetCurrentDirectory(buffer: []u8) GetCurrentDirectoryError![]u8 {
602 return buffer[0..end_index];602 return buffer[0..end_index];
603}603}
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 {
608 const SYMLINK_DATA = extern struct {633 const SYMLINK_DATA = extern struct {
609 ReparseTag: ULONG,634 ReparseTag: ULONG,
610 ReparseDataLength: USHORT,635 ReparseDataLength: USHORT,
...@@ -660,7 +685,7 @@ pub fn NtCreateSymbolicLinkW(dir: ?HANDLE, sym_link_path: [:0]const u16, target_...@@ -660,7 +685,7 @@ pub fn NtCreateSymbolicLinkW(dir: ?HANDLE, sym_link_path: [:0]const u16, target_
660 .OBJECT_NAME_INVALID => unreachable,685 .OBJECT_NAME_INVALID => unreachable,
661 .OBJECT_NAME_NOT_FOUND => return error.FileNotFound,686 .OBJECT_NAME_NOT_FOUND => return error.FileNotFound,
662 .OBJECT_PATH_NOT_FOUND => return error.FileNotFound,687 .OBJECT_PATH_NOT_FOUND => return error.FileNotFound,
663 // .NO_MEDIA_IN_DEVICE => return error.NoDevice,688 .NO_MEDIA_IN_DEVICE => return error.NoDevice,
664 .INVALID_PARAMETER => unreachable,689 .INVALID_PARAMETER => unreachable,
665 .ACCESS_DENIED => return error.AccessDenied,690 .ACCESS_DENIED => return error.AccessDenied,
666 .OBJECT_PATH_SYNTAX_BAD => unreachable,691 .OBJECT_PATH_SYNTAX_BAD => unreachable,
...@@ -674,7 +699,11 @@ pub fn NtCreateSymbolicLinkW(dir: ?HANDLE, sym_link_path: [:0]const u16, target_...@@ -674,7 +699,11 @@ pub fn NtCreateSymbolicLinkW(dir: ?HANDLE, sym_link_path: [:0]const u16, target_
674 .creation = FILE_CREATE,699 .creation = FILE_CREATE,
675 .io_mode = .blocking,700 .io_mode = .blocking,
676 }) catch |err| switch (err) {701 }) 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,
678 };707 };
679 }708 }
680 defer CloseHandle(symlink_handle);709 defer CloseHandle(symlink_handle);
...@@ -702,53 +731,6 @@ pub fn NtCreateSymbolicLinkW(dir: ?HANDLE, sym_link_path: [:0]const u16, target_...@@ -702,53 +731,6 @@ pub fn NtCreateSymbolicLinkW(dir: ?HANDLE, sym_link_path: [:0]const u16, target_
702 _ = try DeviceIoControl(symlink_handle, FSCTL_SET_REPARSE_POINT, buffer[0..buf_len], null, null);731 _ = try DeviceIoControl(symlink_handle, FSCTL_SET_REPARSE_POINT, buffer[0..buf_len], null, null);
703}732}
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
752pub const DeleteFileError = error{734pub const DeleteFileError = error{
753 FileNotFound,735 FileNotFound,
754 AccessDenied,736 AccessDenied,