| author | |
| committer | |
| log | 9505bb74cd0f087f4672d878bf7174273db8c3ae |
| tree | 2798e9c9dd82f7c3a68f2ae0906512938af87f73 |
| parent | c6bd8e8c535c4518fc3f1d2496dc7a921fae0b1c |
| parent | aa6fcaf76f00453f160545e760c37d64588f4517 |
| signature |
Implement readlink on Windows7 files changed, 581 insertions(+), 69 deletions(-)
lib/std/fs.zig+160-17| ... | ... | @@ -16,9 +16,6 @@ pub const wasi = @import("fs/wasi.zig"); |
| 16 | 16 | |
| 17 | 17 | // TODO audit these APIs with respect to Dir and absolute paths |
| 18 | 18 | |
| 19 | pub const symLink = os.symlink; | |
| 20 | pub const symLinkZ = os.symlinkZ; | |
| 21 | pub const symLinkC = @compileError("deprecated: renamed to symlinkZ"); | |
| 22 | 19 | pub const rename = os.rename; |
| 23 | 20 | pub const renameZ = os.renameZ; |
| 24 | 21 | pub const renameC = @compileError("deprecated: renamed to renameZ"); |
| ... | ... | @@ -69,7 +66,7 @@ pub const need_async_thread = std.io.is_async and switch (builtin.os.tag) { |
| 69 | 66 | |
| 70 | 67 | /// TODO remove the allocator requirement from this API |
| 71 | 68 | pub fn atomicSymLink(allocator: *Allocator, existing_path: []const u8, new_path: []const u8) !void { |
| 72 | if (symLink(existing_path, new_path)) { | |
| 69 | if (cwd().symLink(existing_path, new_path, .{})) { | |
| 73 | 70 | return; |
| 74 | 71 | } else |err| switch (err) { |
| 75 | 72 | error.PathAlreadyExists => {}, |
| ... | ... | @@ -87,7 +84,7 @@ pub fn atomicSymLink(allocator: *Allocator, existing_path: []const u8, new_path: |
| 87 | 84 | try crypto.randomBytes(rand_buf[0..]); |
| 88 | 85 | base64_encoder.encode(tmp_path[dirname.len + 1 ..], &rand_buf); |
| 89 | 86 | |
| 90 | if (symLink(existing_path, tmp_path)) { | |
| 87 | if (cwd().symLink(existing_path, new_path, .{})) { | |
| 91 | 88 | return rename(tmp_path, new_path); |
| 92 | 89 | } else |err| switch (err) { |
| 93 | 90 | error.PathAlreadyExists => continue, |
| ... | ... | @@ -672,7 +669,7 @@ pub const Dir = struct { |
| 672 | 669 | w.RIGHT_FD_FILESTAT_SET_TIMES | |
| 673 | 670 | w.RIGHT_FD_FILESTAT_SET_SIZE; |
| 674 | 671 | } |
| 675 | const fd = try os.openatWasi(self.fd, sub_path, 0x0, fdflags, base, 0x0); | |
| 672 | const fd = try os.openatWasi(self.fd, sub_path, 0x0, 0x0, fdflags, base, 0x0); | |
| 676 | 673 | return File{ .handle = fd }; |
| 677 | 674 | } |
| 678 | 675 | |
| ... | ... | @@ -792,7 +789,7 @@ pub const Dir = struct { |
| 792 | 789 | if (flags.exclusive) { |
| 793 | 790 | oflags |= w.O_EXCL; |
| 794 | 791 | } |
| 795 | const fd = try os.openatWasi(self.fd, sub_path, oflags, 0x0, base, 0x0); | |
| 792 | const fd = try os.openatWasi(self.fd, sub_path, 0x0, oflags, 0x0, base, 0x0); | |
| 796 | 793 | return File{ .handle = fd }; |
| 797 | 794 | } |
| 798 | 795 | |
| ... | ... | @@ -954,6 +951,9 @@ pub const Dir = struct { |
| 954 | 951 | /// `true` means the opened directory can be scanned for the files and sub-directories |
| 955 | 952 | /// of the result. It means the `iterate` function can be called. |
| 956 | 953 | iterate: bool = false, |
| 954 | ||
| 955 | /// `true` means it won't dereference the symlinks. | |
| 956 | no_follow: bool = false, | |
| 957 | 957 | }; |
| 958 | 958 | |
| 959 | 959 | /// Opens a directory at the given path. The directory is a system resource that remains |
| ... | ... | @@ -997,10 +997,11 @@ pub const Dir = struct { |
| 997 | 997 | w.RIGHT_PATH_REMOVE_DIRECTORY | |
| 998 | 998 | w.RIGHT_PATH_UNLINK_FILE; |
| 999 | 999 | } |
| 1000 | const symlink_flags: w.lookupflags_t = if (args.no_follow) 0x0 else w.LOOKUP_SYMLINK_FOLLOW; | |
| 1000 | 1001 | // TODO do we really need all the rights here? |
| 1001 | 1002 | const inheriting: w.rights_t = w.RIGHT_ALL ^ w.RIGHT_SOCK_SHUTDOWN; |
| 1002 | 1003 | |
| 1003 | const result = os.openatWasi(self.fd, sub_path, w.O_DIRECTORY, 0x0, base, inheriting); | |
| 1004 | const result = os.openatWasi(self.fd, sub_path, symlink_flags, w.O_DIRECTORY, 0x0, base, inheriting); | |
| 1004 | 1005 | const fd = result catch |err| switch (err) { |
| 1005 | 1006 | error.FileTooBig => unreachable, // can't happen for directories |
| 1006 | 1007 | error.IsDir => unreachable, // we're providing O_DIRECTORY |
| ... | ... | @@ -1017,11 +1018,13 @@ pub const Dir = struct { |
| 1017 | 1018 | if (builtin.os.tag == .windows) { |
| 1018 | 1019 | const sub_path_w = try os.windows.cStrToPrefixedFileW(sub_path_c); |
| 1019 | 1020 | return self.openDirW(sub_path_w.span().ptr, args); |
| 1020 | } else if (!args.iterate) { | |
| 1021 | } | |
| 1022 | const symlink_flags: u32 = if (args.no_follow) os.O_NOFOLLOW else 0x0; | |
| 1023 | if (!args.iterate) { | |
| 1021 | 1024 | const O_PATH = if (@hasDecl(os, "O_PATH")) os.O_PATH else 0; |
| 1022 | return self.openDirFlagsZ(sub_path_c, os.O_DIRECTORY | os.O_RDONLY | os.O_CLOEXEC | O_PATH); | |
| 1025 | return self.openDirFlagsZ(sub_path_c, os.O_DIRECTORY | os.O_RDONLY | os.O_CLOEXEC | O_PATH | symlink_flags); | |
| 1023 | 1026 | } else { |
| 1024 | return self.openDirFlagsZ(sub_path_c, os.O_DIRECTORY | os.O_RDONLY | os.O_CLOEXEC); | |
| 1027 | return self.openDirFlagsZ(sub_path_c, os.O_DIRECTORY | os.O_RDONLY | os.O_CLOEXEC | symlink_flags); | |
| 1025 | 1028 | } |
| 1026 | 1029 | } |
| 1027 | 1030 | |
| ... | ... | @@ -1033,7 +1036,7 @@ pub const Dir = struct { |
| 1033 | 1036 | const base_flags = w.STANDARD_RIGHTS_READ | w.FILE_READ_ATTRIBUTES | w.FILE_READ_EA | |
| 1034 | 1037 | w.SYNCHRONIZE | w.FILE_TRAVERSE; |
| 1035 | 1038 | const flags: u32 = if (args.iterate) base_flags | w.FILE_LIST_DIRECTORY else base_flags; |
| 1036 | return self.openDirAccessMaskW(sub_path_w, flags); | |
| 1039 | return self.openDirAccessMaskW(sub_path_w, flags, args.no_follow); | |
| 1037 | 1040 | } |
| 1038 | 1041 | |
| 1039 | 1042 | /// `flags` must contain `os.O_DIRECTORY`. |
| ... | ... | @@ -1053,7 +1056,7 @@ pub const Dir = struct { |
| 1053 | 1056 | return Dir{ .fd = fd }; |
| 1054 | 1057 | } |
| 1055 | 1058 | |
| 1056 | fn openDirAccessMaskW(self: Dir, sub_path_w: [*:0]const u16, access_mask: u32) OpenError!Dir { | |
| 1059 | fn openDirAccessMaskW(self: Dir, sub_path_w: [*:0]const u16, access_mask: u32, no_follow: bool) OpenError!Dir { | |
| 1057 | 1060 | const w = os.windows; |
| 1058 | 1061 | |
| 1059 | 1062 | var result = Dir{ |
| ... | ... | @@ -1083,6 +1086,7 @@ pub const Dir = struct { |
| 1083 | 1086 | // implement this: https://git.midipix.org/ntapi/tree/src/fs/ntapi_tt_open_physical_parent_directory.c |
| 1084 | 1087 | @panic("TODO opening '..' with a relative directory handle is not yet implemented on Windows"); |
| 1085 | 1088 | } |
| 1089 | const open_reparse_point: w.DWORD = if (no_follow) w.FILE_OPEN_REPARSE_POINT else 0x0; | |
| 1086 | 1090 | var io: w.IO_STATUS_BLOCK = undefined; |
| 1087 | 1091 | const rc = w.ntdll.NtCreateFile( |
| 1088 | 1092 | &result.fd, |
| ... | ... | @@ -1093,7 +1097,7 @@ pub const Dir = struct { |
| 1093 | 1097 | 0, |
| 1094 | 1098 | w.FILE_SHARE_READ | w.FILE_SHARE_WRITE, |
| 1095 | 1099 | w.FILE_OPEN, |
| 1096 | w.FILE_DIRECTORY_FILE | w.FILE_SYNCHRONOUS_IO_NONALERT | w.FILE_OPEN_FOR_BACKUP_INTENT, | |
| 1100 | w.FILE_DIRECTORY_FILE | w.FILE_SYNCHRONOUS_IO_NONALERT | w.FILE_OPEN_FOR_BACKUP_INTENT | open_reparse_point, | |
| 1097 | 1101 | null, |
| 1098 | 1102 | 0, |
| 1099 | 1103 | ); |
| ... | ... | @@ -1207,21 +1211,101 @@ pub const Dir = struct { |
| 1207 | 1211 | }; |
| 1208 | 1212 | } |
| 1209 | 1213 | |
| 1214 | /// Creates a symbolic link named `sym_link_path` which contains the string `target_path`. | |
| 1215 | /// A symbolic link (also known as a soft link) may point to an existing file or to a nonexistent | |
| 1216 | /// one; the latter case is known as a dangling link. | |
| 1217 | /// If `sym_link_path` exists, it will not be overwritten. | |
| 1218 | pub fn symLink( | |
| 1219 | self: Dir, | |
| 1220 | target_path: []const u8, | |
| 1221 | sym_link_path: []const u8, | |
| 1222 | flags: SymLinkFlags, | |
| 1223 | ) !void { | |
| 1224 | if (builtin.os.tag == .wasi) { | |
| 1225 | return self.symLinkWasi(target_path, sym_link_path, flags); | |
| 1226 | } | |
| 1227 | if (builtin.os.tag == .windows) { | |
| 1228 | const target_path_w = try os.windows.sliceToPrefixedFileW(target_path); | |
| 1229 | const sym_link_path_w = try os.windows.sliceToPrefixedFileW(sym_link_path); | |
| 1230 | return self.symLinkW(target_path_w.span(), sym_link_path_w.span(), flags); | |
| 1231 | } | |
| 1232 | const target_path_c = try os.toPosixPath(target_path); | |
| 1233 | const sym_link_path_c = try os.toPosixPath(sym_link_path); | |
| 1234 | return self.symLinkZ(&target_path_c, &sym_link_path_c, flags); | |
| 1235 | } | |
| 1236 | ||
| 1237 | /// WASI-only. Same as `symLink` except targeting WASI. | |
| 1238 | pub fn symLinkWasi( | |
| 1239 | self: Dir, | |
| 1240 | target_path: []const u8, | |
| 1241 | sym_link_path: []const u8, | |
| 1242 | flags: SymLinkFlags, | |
| 1243 | ) !void { | |
| 1244 | return os.symlinkatWasi(target_path, self.fd, sym_link_path); | |
| 1245 | } | |
| 1246 | ||
| 1247 | /// Same as `symLink`, except the pathname parameters are null-terminated. | |
| 1248 | pub fn symLinkZ( | |
| 1249 | self: Dir, | |
| 1250 | target_path_c: [*:0]const u8, | |
| 1251 | sym_link_path_c: [*:0]const u8, | |
| 1252 | flags: SymLinkFlags, | |
| 1253 | ) !void { | |
| 1254 | if (builtin.os.tag == .windows) { | |
| 1255 | const target_path_w = try os.windows.cStrToPrefixedFileW(target_path_c); | |
| 1256 | const sym_link_path_w = try os.windows.cStrToPrefixedFileW(sym_link_path_c); | |
| 1257 | return self.symLinkW(target_path_w.span(), sym_link_path_w.span(), flags); | |
| 1258 | } | |
| 1259 | return os.symlinkatZ(target_path_c, self.fd, sym_link_path_c); | |
| 1260 | } | |
| 1261 | ||
| 1262 | /// Windows-only. Same as `symLink` except the pathname parameters | |
| 1263 | /// are null-terminated, WTF16 encoded. | |
| 1264 | pub fn symLinkW( | |
| 1265 | self: Dir, | |
| 1266 | target_path_w: [:0]const u16, | |
| 1267 | sym_link_path_w: [:0]const u16, | |
| 1268 | flags: SymLinkFlags, | |
| 1269 | ) !void { | |
| 1270 | return os.windows.CreateSymbolicLinkW(self.fd, sym_link_path_w, target_path_w, flags.is_directory); | |
| 1271 | } | |
| 1272 | ||
| 1210 | 1273 | /// Read value of a symbolic link. |
| 1211 | 1274 | /// The return value is a slice of `buffer`, from index `0`. |
| 1212 | 1275 | /// Asserts that the path parameter has no null bytes. |
| 1213 | 1276 | pub fn readLink(self: Dir, sub_path: []const u8, buffer: []u8) ![]u8 { |
| 1277 | if (builtin.os.tag == .wasi) { | |
| 1278 | return self.readLinkWasi(sub_path, buffer); | |
| 1279 | } | |
| 1280 | if (builtin.os.tag == .windows) { | |
| 1281 | return os.windows.ReadLink(self.fd, sub_path, buffer); | |
| 1282 | } | |
| 1214 | 1283 | const sub_path_c = try os.toPosixPath(sub_path); |
| 1215 | 1284 | return self.readLinkZ(&sub_path_c, buffer); |
| 1216 | 1285 | } |
| 1217 | 1286 | |
| 1218 | 1287 | pub const readLinkC = @compileError("deprecated: renamed to readLinkZ"); |
| 1219 | 1288 | |
| 1289 | /// WASI-only. Same as `readLink` except targeting WASI. | |
| 1290 | pub fn readLinkWasi(self: Dir, sub_path: []const u8, buffer: []u8) ![]u8 { | |
| 1291 | return os.readlinkatWasi(self.fd, sub_path, buffer); | |
| 1292 | } | |
| 1293 | ||
| 1220 | 1294 | /// Same as `readLink`, except the `pathname` parameter is null-terminated. |
| 1221 | 1295 | pub fn readLinkZ(self: Dir, sub_path_c: [*:0]const u8, buffer: []u8) ![]u8 { |
| 1296 | if (builtin.os.tag == .windows) { | |
| 1297 | const sub_path_w = try os.windows.cStrToPrefixedFileW(sub_path_c); | |
| 1298 | return self.readLinkW(sub_path_w, buffer); | |
| 1299 | } | |
| 1222 | 1300 | return os.readlinkatZ(self.fd, sub_path_c, buffer); |
| 1223 | 1301 | } |
| 1224 | 1302 | |
| 1303 | /// Windows-only. Same as `readLink` except the pathname parameter | |
| 1304 | /// 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); | |
| 1307 | } | |
| 1308 | ||
| 1225 | 1309 | /// On success, caller owns returned buffer. |
| 1226 | 1310 | /// If the file is larger than `max_bytes`, returns `error.FileTooBig`. |
| 1227 | 1311 | pub fn readFileAlloc(self: Dir, allocator: *mem.Allocator, file_path: []const u8, max_bytes: usize) ![]u8 { |
| ... | ... | @@ -1280,6 +1364,7 @@ pub const Dir = struct { |
| 1280 | 1364 | pub fn deleteTree(self: Dir, sub_path: []const u8) DeleteTreeError!void { |
| 1281 | 1365 | start_over: while (true) { |
| 1282 | 1366 | var got_access_denied = false; |
| 1367 | ||
| 1283 | 1368 | // First, try deleting the item as a file. This way we don't follow sym links. |
| 1284 | 1369 | if (self.deleteFile(sub_path)) { |
| 1285 | 1370 | return; |
| ... | ... | @@ -1300,7 +1385,7 @@ pub const Dir = struct { |
| 1300 | 1385 | error.Unexpected, |
| 1301 | 1386 | => |e| return e, |
| 1302 | 1387 | } |
| 1303 | var dir = self.openDir(sub_path, .{ .iterate = true }) catch |err| switch (err) { | |
| 1388 | var dir = self.openDir(sub_path, .{ .iterate = true, .no_follow = true }) catch |err| switch (err) { | |
| 1304 | 1389 | error.NotDir => { |
| 1305 | 1390 | if (got_access_denied) { |
| 1306 | 1391 | return error.AccessDenied; |
| ... | ... | @@ -1367,7 +1452,7 @@ pub const Dir = struct { |
| 1367 | 1452 | => |e| return e, |
| 1368 | 1453 | } |
| 1369 | 1454 | |
| 1370 | const new_dir = dir.openDir(entry.name, .{ .iterate = true }) catch |err| switch (err) { | |
| 1455 | const new_dir = dir.openDir(entry.name, .{ .iterate = true, .no_follow = true }) catch |err| switch (err) { | |
| 1371 | 1456 | error.NotDir => { |
| 1372 | 1457 | if (got_access_denied) { |
| 1373 | 1458 | return error.AccessDenied; |
| ... | ... | @@ -1692,8 +1777,15 @@ pub fn readLinkAbsolute(pathname: []const u8, buffer: *[MAX_PATH_BYTES]u8) ![]u8 |
| 1692 | 1777 | return os.readlink(pathname, buffer); |
| 1693 | 1778 | } |
| 1694 | 1779 | |
| 1780 | /// Windows-only. Same as `readlinkW`, except the path parameter is null-terminated, WTF16 | |
| 1781 | /// encoded. | |
| 1782 | pub fn readlinkAbsoluteW(pathname_w: [*:0]const u16, buffer: *[MAX_PATH_BYTES]u8) ![]u8 { | |
| 1783 | assert(path.isAbsoluteWindowsW(pathname_w)); | |
| 1784 | return os.readlinkW(pathname_w, buffer); | |
| 1785 | } | |
| 1786 | ||
| 1695 | 1787 | /// Same as `readLink`, except the path parameter is null-terminated. |
| 1696 | pub fn readLinkAbsoluteZ(pathname_c: [*]const u8, buffer: *[MAX_PATH_BYTES]u8) ![]u8 { | |
| 1788 | pub fn readLinkAbsoluteZ(pathname_c: [*:0]const u8, buffer: *[MAX_PATH_BYTES]u8) ![]u8 { | |
| 1697 | 1789 | assert(path.isAbsoluteZ(pathname_c)); |
| 1698 | 1790 | return os.readlinkZ(pathname_c, buffer); |
| 1699 | 1791 | } |
| ... | ... | @@ -1701,6 +1793,57 @@ pub fn readLinkAbsoluteZ(pathname_c: [*]const u8, buffer: *[MAX_PATH_BYTES]u8) ! |
| 1701 | 1793 | pub const readLink = @compileError("deprecated; use Dir.readLink or readLinkAbsolute"); |
| 1702 | 1794 | pub const readLinkC = @compileError("deprecated; use Dir.readLinkZ or readLinkAbsoluteZ"); |
| 1703 | 1795 | |
| 1796 | /// Use with `Dir.symLink` and `symLinkAbsolute` to specify whether the symlink | |
| 1797 | /// will point to a file or a directory. This value is ignored on all hosts | |
| 1798 | /// except Windows where creating symlinks to different resource types, requires | |
| 1799 | /// different flags. By default, `symLinkAbsolute` is assumed to point to a file. | |
| 1800 | pub const SymLinkFlags = struct { | |
| 1801 | is_directory: bool = false, | |
| 1802 | }; | |
| 1803 | ||
| 1804 | /// Creates a symbolic link named `sym_link_path` which contains the string `target_path`. | |
| 1805 | /// A symbolic link (also known as a soft link) may point to an existing file or to a nonexistent | |
| 1806 | /// one; the latter case is known as a dangling link. | |
| 1807 | /// If `sym_link_path` exists, it will not be overwritten. | |
| 1808 | /// See also `symLinkAbsoluteZ` and `symLinkAbsoluteW`. | |
| 1809 | pub fn symLinkAbsolute(target_path: []const u8, sym_link_path: []const u8, flags: SymLinkFlags) !void { | |
| 1810 | if (builtin.os.tag == .wasi) { | |
| 1811 | @compileError("symLinkAbsolute is not supported in WASI; use Dir.symLinkWasi instead"); | |
| 1812 | } | |
| 1813 | assert(path.isAbsolute(target_path)); | |
| 1814 | assert(path.isAbsolute(sym_link_path)); | |
| 1815 | if (builtin.os.tag == .windows) { | |
| 1816 | return os.windows.CreateSymbolicLink(null, sym_link_path, target_path, flags.is_directory); | |
| 1817 | } | |
| 1818 | return os.symlink(target_path, sym_link_path); | |
| 1819 | } | |
| 1820 | ||
| 1821 | /// Windows-only. Same as `symLinkAbsolute` except the parameters are null-terminated, WTF16 encoded. | |
| 1822 | /// 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 | /// See also `symLinkAbsolute`, `symLinkAbsoluteZ`. | |
| 1825 | pub fn symLinkAbsoluteW(target_path_w: [:0]const u16, sym_link_path_w: [:0]const u16, flags: SymLinkFlags) !void { | |
| 1826 | 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); | |
| 1829 | } | |
| 1830 | ||
| 1831 | /// Same as `symLinkAbsolute` except the parameters are null-terminated pointers. | |
| 1832 | /// See also `symLinkAbsolute`. | |
| 1833 | pub fn symLinkAbsoluteZ(target_path_c: [*:0]const u8, sym_link_path_c: [*:0]const u8, flags: SymLinkFlags) !void { | |
| 1834 | assert(path.isAbsoluteZ(target_path_c)); | |
| 1835 | assert(path.isAbsoluteZ(sym_link_path_c)); | |
| 1836 | if (builtin.os.tag == .windows) { | |
| 1837 | 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 | return os.windows.CreateSymbolicLinkW(sym_link_path_w.span().ptr, target_path_w.span().ptr, flags.is_directory); | |
| 1840 | } | |
| 1841 | return os.symlinkZ(target_path_c, sym_link_path_c); | |
| 1842 | } | |
| 1843 | ||
| 1844 | pub const symLink = @compileError("deprecated: use Dir.symLink or symLinkAbsolute"); | |
| 1845 | pub const symLinkC = @compileError("deprecated: use Dir.symLinkZ or symLinkAbsoluteZ"); | |
| 1846 | ||
| 1704 | 1847 | pub const Walker = struct { |
| 1705 | 1848 | stack: std.ArrayList(StackItem), |
| 1706 | 1849 | name_buffer: std.ArrayList(u8), |
lib/std/fs/test.zig+70| ... | ... | @@ -10,6 +10,76 @@ const Dir = std.fs.Dir; |
| 10 | 10 | const File = std.fs.File; |
| 11 | 11 | const tmpDir = testing.tmpDir; |
| 12 | 12 | |
| 13 | test "Dir.readLink" { | |
| 14 | var tmp = tmpDir(.{}); | |
| 15 | defer tmp.cleanup(); | |
| 16 | ||
| 17 | // Create some targets | |
| 18 | try tmp.dir.writeFile("file.txt", "nonsense"); | |
| 19 | try tmp.dir.makeDir("subdir"); | |
| 20 | ||
| 21 | { | |
| 22 | // Create symbolic link by path | |
| 23 | try tmp.dir.symLink("file.txt", "symlink1", .{}); | |
| 24 | try testReadLink(tmp.dir, "file.txt", "symlink1"); | |
| 25 | } | |
| 26 | { | |
| 27 | // Create symbolic link by path | |
| 28 | try tmp.dir.symLink("subdir", "symlink2", .{ .is_directory = true }); | |
| 29 | try testReadLink(tmp.dir, "subdir", "symlink2"); | |
| 30 | } | |
| 31 | } | |
| 32 | ||
| 33 | fn testReadLink(dir: Dir, target_path: []const u8, symlink_path: []const u8) !void { | |
| 34 | var buffer: [fs.MAX_PATH_BYTES]u8 = undefined; | |
| 35 | const given = try dir.readLink(symlink_path, buffer[0..]); | |
| 36 | testing.expect(mem.eql(u8, target_path, given)); | |
| 37 | } | |
| 38 | ||
| 39 | test "readLinkAbsolute" { | |
| 40 | if (builtin.os.tag == .wasi) return error.SkipZigTest; | |
| 41 | ||
| 42 | var tmp = tmpDir(.{}); | |
| 43 | defer tmp.cleanup(); | |
| 44 | ||
| 45 | // Create some targets | |
| 46 | try tmp.dir.writeFile("file.txt", "nonsense"); | |
| 47 | try tmp.dir.makeDir("subdir"); | |
| 48 | ||
| 49 | // Get base abs path | |
| 50 | var arena = ArenaAllocator.init(testing.allocator); | |
| 51 | defer arena.deinit(); | |
| 52 | ||
| 53 | const base_path = blk: { | |
| 54 | const relative_path = try fs.path.join(&arena.allocator, &[_][]const u8{ "zig-cache", "tmp", tmp.sub_path[0..] }); | |
| 55 | break :blk try fs.realpathAlloc(&arena.allocator, relative_path); | |
| 56 | }; | |
| 57 | const allocator = &arena.allocator; | |
| 58 | ||
| 59 | { | |
| 60 | const target_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "file.txt" }); | |
| 61 | const symlink_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "symlink1" }); | |
| 62 | ||
| 63 | // Create symbolic link by path | |
| 64 | try fs.symLinkAbsolute(target_path, symlink_path, .{}); | |
| 65 | try testReadLinkAbsolute(target_path, symlink_path); | |
| 66 | } | |
| 67 | { | |
| 68 | const target_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "subdir" }); | |
| 69 | const symlink_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "symlink2" }); | |
| 70 | ||
| 71 | // Create symbolic link by path | |
| 72 | try fs.symLinkAbsolute(target_path, symlink_path, .{ .is_directory = true }); | |
| 73 | try testReadLinkAbsolute(target_path, symlink_path); | |
| 74 | } | |
| 75 | } | |
| 76 | ||
| 77 | fn testReadLinkAbsolute(target_path: []const u8, symlink_path: []const u8) !void { | |
| 78 | var buffer: [fs.MAX_PATH_BYTES]u8 = undefined; | |
| 79 | const given = try fs.readLinkAbsolute(symlink_path, buffer[0..]); | |
| 80 | testing.expect(mem.eql(u8, target_path, given)); | |
| 81 | } | |
| 82 | ||
| 13 | 83 | test "Dir.Iterator" { |
| 14 | 84 | var tmp_dir = tmpDir(.{ .iterate = true }); |
| 15 | 85 | defer tmp_dir.cleanup(); |
lib/std/os.zig+28-33| ... | ... | @@ -1109,10 +1109,10 @@ pub fn openat(dir_fd: fd_t, file_path: []const u8, flags: u32, mode: mode_t) Ope |
| 1109 | 1109 | } |
| 1110 | 1110 | |
| 1111 | 1111 | /// Open and possibly create a file in WASI. |
| 1112 | pub fn openatWasi(dir_fd: fd_t, file_path: []const u8, oflags: oflags_t, fdflags: fdflags_t, base: rights_t, inheriting: rights_t) OpenError!fd_t { | |
| 1112 | pub fn openatWasi(dir_fd: fd_t, file_path: []const u8, lookup_flags: lookupflags_t, oflags: oflags_t, fdflags: fdflags_t, base: rights_t, inheriting: rights_t) OpenError!fd_t { | |
| 1113 | 1113 | while (true) { |
| 1114 | 1114 | var fd: fd_t = undefined; |
| 1115 | switch (wasi.path_open(dir_fd, 0x0, file_path.ptr, file_path.len, oflags, base, inheriting, fdflags, &fd)) { | |
| 1115 | switch (wasi.path_open(dir_fd, lookup_flags, file_path.ptr, file_path.len, oflags, base, inheriting, fdflags, &fd)) { | |
| 1116 | 1116 | wasi.ESUCCESS => return fd, |
| 1117 | 1117 | wasi.EINTR => continue, |
| 1118 | 1118 | |
| ... | ... | @@ -1542,15 +1542,13 @@ pub const SymLinkError = error{ |
| 1542 | 1542 | /// A symbolic link (also known as a soft link) may point to an existing file or to a nonexistent |
| 1543 | 1543 | /// one; the latter case is known as a dangling link. |
| 1544 | 1544 | /// If `sym_link_path` exists, it will not be overwritten. |
| 1545 | /// See also `symlinkC` and `symlinkW`. | |
| 1545 | /// See also `symlinkZ. | |
| 1546 | 1546 | pub fn symlink(target_path: []const u8, sym_link_path: []const u8) SymLinkError!void { |
| 1547 | 1547 | if (builtin.os.tag == .wasi) { |
| 1548 | 1548 | @compileError("symlink is not supported in WASI; use symlinkat instead"); |
| 1549 | 1549 | } |
| 1550 | 1550 | if (builtin.os.tag == .windows) { |
| 1551 | const target_path_w = try windows.sliceToPrefixedFileW(target_path); | |
| 1552 | const sym_link_path_w = try windows.sliceToPrefixedFileW(sym_link_path); | |
| 1553 | return windows.CreateSymbolicLinkW(sym_link_path_w.span().ptr, target_path_w.span().ptr, 0); | |
| 1551 | @compileError("symlink is not supported on Windows; use std.os.windows.CreateSymbolicLink instead"); | |
| 1554 | 1552 | } |
| 1555 | 1553 | const target_path_c = try toPosixPath(target_path); |
| 1556 | 1554 | const sym_link_path_c = try toPosixPath(sym_link_path); |
| ... | ... | @@ -1563,9 +1561,7 @@ pub const symlinkC = @compileError("deprecated: renamed to symlinkZ"); |
| 1563 | 1561 | /// See also `symlink`. |
| 1564 | 1562 | pub fn symlinkZ(target_path: [*:0]const u8, sym_link_path: [*:0]const u8) SymLinkError!void { |
| 1565 | 1563 | if (builtin.os.tag == .windows) { |
| 1566 | const target_path_w = try windows.cStrToPrefixedFileW(target_path); | |
| 1567 | const sym_link_path_w = try windows.cStrToPrefixedFileW(sym_link_path); | |
| 1568 | return windows.CreateSymbolicLinkW(sym_link_path_w.span().ptr, target_path_w.span().ptr, 0); | |
| 1564 | @compileError("symlink is not supported on Windows; use std.os.windows.CreateSymbolicLink instead"); | |
| 1569 | 1565 | } |
| 1570 | 1566 | switch (errno(system.symlink(target_path, sym_link_path))) { |
| 1571 | 1567 | 0 => return, |
| ... | ... | @@ -1598,9 +1594,7 @@ pub fn symlinkat(target_path: []const u8, newdirfd: fd_t, sym_link_path: []const |
| 1598 | 1594 | return symlinkatWasi(target_path, newdirfd, sym_link_path); |
| 1599 | 1595 | } |
| 1600 | 1596 | if (builtin.os.tag == .windows) { |
| 1601 | const target_path_w = try windows.sliceToPrefixedFileW(target_path); | |
| 1602 | const sym_link_path_w = try windows.sliceToPrefixedFileW(sym_link_path); | |
| 1603 | return symlinkatW(target_path_w.span().ptr, newdirfd, sym_link_path_w.span().ptr); | |
| 1597 | @compileError("symlinkat is not supported on Windows; use std.os.windows.CreateSymbolicLink instead"); | |
| 1604 | 1598 | } |
| 1605 | 1599 | const target_path_c = try toPosixPath(target_path); |
| 1606 | 1600 | const sym_link_path_c = try toPosixPath(sym_link_path); |
| ... | ... | @@ -1633,19 +1627,11 @@ pub fn symlinkatWasi(target_path: []const u8, newdirfd: fd_t, sym_link_path: []c |
| 1633 | 1627 | } |
| 1634 | 1628 | } |
| 1635 | 1629 | |
| 1636 | /// Windows-only. The same as `symlinkat` except the paths are null-terminated, WTF-16 encoded. | |
| 1637 | /// See also `symlinkat`. | |
| 1638 | pub fn symlinkatW(target_path: [*:0]const u16, newdirfd: fd_t, sym_link_path: [*:0]const u16) SymLinkError!void { | |
| 1639 | @compileError("TODO implement on Windows"); | |
| 1640 | } | |
| 1641 | ||
| 1642 | 1630 | /// The same as `symlinkat` except the parameters are null-terminated pointers. |
| 1643 | 1631 | /// See also `symlinkat`. |
| 1644 | 1632 | pub fn symlinkatZ(target_path: [*:0]const u8, newdirfd: fd_t, sym_link_path: [*:0]const u8) SymLinkError!void { |
| 1645 | 1633 | if (builtin.os.tag == .windows) { |
| 1646 | const target_path_w = try windows.cStrToPrefixedFileW(target_path); | |
| 1647 | const sym_link_path_w = try windows.cStrToPrefixedFileW(sym_link_path); | |
| 1648 | return symlinkatW(target_path_w.span().ptr, newdirfd, sym_link_path.span().ptr); | |
| 1634 | @compileError("symlinkat is not supported on Windows; use std.os.windows.CreateSymbolicLink instead"); | |
| 1649 | 1635 | } |
| 1650 | 1636 | switch (errno(system.symlinkat(target_path, newdirfd, sym_link_path))) { |
| 1651 | 1637 | 0 => return, |
| ... | ... | @@ -1819,9 +1805,9 @@ pub fn unlinkatW(dirfd: fd_t, sub_path_w: [*:0]const u16, flags: u32) UnlinkatEr |
| 1819 | 1805 | |
| 1820 | 1806 | const want_rmdir_behavior = (flags & AT_REMOVEDIR) != 0; |
| 1821 | 1807 | const create_options_flags = if (want_rmdir_behavior) |
| 1822 | @as(w.ULONG, w.FILE_DELETE_ON_CLOSE | w.FILE_DIRECTORY_FILE) | |
| 1808 | @as(w.ULONG, w.FILE_DELETE_ON_CLOSE | w.FILE_DIRECTORY_FILE | w.FILE_OPEN_REPARSE_POINT) | |
| 1823 | 1809 | else |
| 1824 | @as(w.ULONG, w.FILE_DELETE_ON_CLOSE | w.FILE_NON_DIRECTORY_FILE); | |
| 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? | |
| 1825 | 1811 | |
| 1826 | 1812 | const path_len_bytes = @intCast(u16, mem.lenZ(sub_path_w) * 2); |
| 1827 | 1813 | var nt_name = w.UNICODE_STRING{ |
| ... | ... | @@ -2355,6 +2341,11 @@ pub const ReadLinkError = error{ |
| 2355 | 2341 | FileNotFound, |
| 2356 | 2342 | SystemResources, |
| 2357 | 2343 | NotDir, |
| 2344 | InvalidUtf8, | |
| 2345 | BadPathName, | |
| 2346 | /// Windows-only. This error may occur if the opened reparse point is | |
| 2347 | /// of unsupported type. | |
| 2348 | UnsupportedReparsePointType, | |
| 2358 | 2349 | } || UnexpectedError; |
| 2359 | 2350 | |
| 2360 | 2351 | /// Read value of a symbolic link. |
| ... | ... | @@ -2363,8 +2354,7 @@ pub fn readlink(file_path: []const u8, out_buffer: []u8) ReadLinkError![]u8 { |
| 2363 | 2354 | if (builtin.os.tag == .wasi) { |
| 2364 | 2355 | @compileError("readlink is not supported in WASI; use readlinkat instead"); |
| 2365 | 2356 | } else if (builtin.os.tag == .windows) { |
| 2366 | const file_path_w = try windows.sliceToPrefixedFileW(file_path); | |
| 2367 | return readlinkW(file_path_w.span().ptr, out_buffer); | |
| 2357 | return windows.ReadLink(std.fs.cwd().fd, file_path, out_buffer); | |
| 2368 | 2358 | } else { |
| 2369 | 2359 | const file_path_c = try toPosixPath(file_path); |
| 2370 | 2360 | return readlinkZ(&file_path_c, out_buffer); |
| ... | ... | @@ -2373,16 +2363,16 @@ pub fn readlink(file_path: []const u8, out_buffer: []u8) ReadLinkError![]u8 { |
| 2373 | 2363 | |
| 2374 | 2364 | pub const readlinkC = @compileError("deprecated: renamed to readlinkZ"); |
| 2375 | 2365 | |
| 2376 | /// Windows-only. Same as `readlink` expecte `file_path` is null-terminated, WTF16 encoded. | |
| 2377 | /// Seel also `readlinkZ`. | |
| 2366 | /// Windows-only. Same as `readlink` except `file_path` is null-terminated, WTF16 encoded. | |
| 2367 | /// See also `readlinkZ`. | |
| 2378 | 2368 | pub fn readlinkW(file_path: [*:0]const u16, out_buffer: []u8) ReadLinkError![]u8 { |
| 2379 | @compileError("TODO implement readlink for Windows"); | |
| 2369 | return windows.ReadLinkW(std.fs.cwd().fd, file_path, out_buffer); | |
| 2380 | 2370 | } |
| 2381 | 2371 | |
| 2382 | 2372 | /// Same as `readlink` except `file_path` is null-terminated. |
| 2383 | 2373 | pub fn readlinkZ(file_path: [*:0]const u8, out_buffer: []u8) ReadLinkError![]u8 { |
| 2384 | 2374 | if (builtin.os.tag == .windows) { |
| 2385 | const file_path_w = try windows.cStrToPrefixedFileW(file_path); | |
| 2375 | const file_path_w = try windows.cStrToWin32PrefixedFileW(file_path); | |
| 2386 | 2376 | return readlinkW(file_path_w.span().ptr, out_buffer); |
| 2387 | 2377 | } |
| 2388 | 2378 | const rc = system.readlink(file_path, out_buffer.ptr, out_buffer.len); |
| ... | ... | @@ -2409,8 +2399,7 @@ pub fn readlinkat(dirfd: fd_t, file_path: []const u8, out_buffer: []u8) ReadLink |
| 2409 | 2399 | return readlinkatWasi(dirfd, file_path, out_buffer); |
| 2410 | 2400 | } |
| 2411 | 2401 | if (builtin.os.tag == .windows) { |
| 2412 | const file_path_w = try windows.cStrToPrefixedFileW(file_path); | |
| 2413 | return readlinkatW(dirfd, file_path.span().ptr, out_buffer); | |
| 2402 | return windows.ReadLink(dirfd, file_path, out_buffer); | |
| 2414 | 2403 | } |
| 2415 | 2404 | const file_path_c = try toPosixPath(file_path); |
| 2416 | 2405 | return readlinkatZ(dirfd, &file_path_c, out_buffer); |
| ... | ... | @@ -2441,7 +2430,7 @@ pub fn readlinkatWasi(dirfd: fd_t, file_path: []const u8, out_buffer: []u8) Read |
| 2441 | 2430 | /// Windows-only. Same as `readlinkat` except `file_path` is null-terminated, WTF16 encoded. |
| 2442 | 2431 | /// See also `readlinkat`. |
| 2443 | 2432 | pub fn readlinkatW(dirfd: fd_t, file_path: [*:0]const u16, out_buffer: []u8) ReadLinkError![]u8 { |
| 2444 | @compileError("TODO implement on Windows"); | |
| 2433 | return windows.ReadLinkW(dirfd, file_path, out_buffer); | |
| 2445 | 2434 | } |
| 2446 | 2435 | |
| 2447 | 2436 | /// Same as `readlinkat` except `file_path` is null-terminated. |
| ... | ... | @@ -3997,7 +3986,13 @@ pub fn realpathZ(pathname: [*:0]const u8, out_buffer: *[MAX_PATH_BYTES]u8) RealP |
| 3997 | 3986 | var procfs_buf: ["/proc/self/fd/-2147483648".len:0]u8 = undefined; |
| 3998 | 3987 | const proc_path = std.fmt.bufPrint(procfs_buf[0..], "/proc/self/fd/{}\x00", .{fd}) catch unreachable; |
| 3999 | 3988 | |
| 4000 | return readlinkZ(@ptrCast([*:0]const u8, proc_path.ptr), out_buffer); | |
| 3989 | const target = readlinkZ(@ptrCast([*:0]const u8, proc_path.ptr), out_buffer) catch |err| { | |
| 3990 | switch (err) { | |
| 3991 | error.UnsupportedReparsePointType => unreachable, // Windows only, | |
| 3992 | else => |e| return e, | |
| 3993 | } | |
| 3994 | }; | |
| 3995 | return target; | |
| 4001 | 3996 | } |
| 4002 | 3997 | const result_path = std.c.realpath(pathname, out_buffer) orelse switch (std.c._errno().*) { |
| 4003 | 3998 | EINVAL => unreachable, |
lib/std/os/test.zig+41-4| ... | ... | @@ -17,6 +17,42 @@ const AtomicRmwOp = builtin.AtomicRmwOp; |
| 17 | 17 | const AtomicOrder = builtin.AtomicOrder; |
| 18 | 18 | const tmpDir = std.testing.tmpDir; |
| 19 | 19 | const Dir = std.fs.Dir; |
| 20 | const ArenaAllocator = std.heap.ArenaAllocator; | |
| 21 | ||
| 22 | test "symlink with relative paths" { | |
| 23 | if (builtin.os.tag == .wasi) return error.SkipZigTest; | |
| 24 | ||
| 25 | // First, try relative paths in cwd | |
| 26 | var cwd = fs.cwd(); | |
| 27 | try cwd.writeFile("file.txt", "nonsense"); | |
| 28 | ||
| 29 | if (builtin.os.tag == .windows) { | |
| 30 | try os.windows.CreateSymbolicLink(cwd.fd, "symlinked", "file.txt", false); | |
| 31 | } else { | |
| 32 | try os.symlink("file.txt", "symlinked"); | |
| 33 | } | |
| 34 | ||
| 35 | var buffer: [fs.MAX_PATH_BYTES]u8 = undefined; | |
| 36 | const given = try os.readlink("symlinked", buffer[0..]); | |
| 37 | expect(mem.eql(u8, "file.txt", given)); | |
| 38 | ||
| 39 | try cwd.deleteFile("file.txt"); | |
| 40 | try cwd.deleteFile("symlinked"); | |
| 41 | } | |
| 42 | ||
| 43 | test "readlink on Windows" { | |
| 44 | if (builtin.os.tag != .windows) return error.SkipZigTest; | |
| 45 | ||
| 46 | try testReadlink("C:\\ProgramData", "C:\\Users\\All Users"); | |
| 47 | try testReadlink("C:\\Users\\Default", "C:\\Users\\Default User"); | |
| 48 | try testReadlink("C:\\Users", "C:\\Documents and Settings"); | |
| 49 | } | |
| 50 | ||
| 51 | fn testReadlink(target_path: []const u8, symlink_path: []const u8) !void { | |
| 52 | var buffer: [fs.MAX_PATH_BYTES]u8 = undefined; | |
| 53 | const given = try os.readlink(symlink_path, buffer[0..]); | |
| 54 | expect(mem.eql(u8, target_path, given)); | |
| 55 | } | |
| 20 | 56 | |
| 21 | 57 | test "fstatat" { |
| 22 | 58 | // enable when `fstat` and `fstatat` are implemented on Windows |
| ... | ... | @@ -41,9 +77,6 @@ test "fstatat" { |
| 41 | 77 | } |
| 42 | 78 | |
| 43 | 79 | test "readlinkat" { |
| 44 | // enable when `readlinkat` and `symlinkat` are implemented on Windows | |
| 45 | if (builtin.os.tag == .windows) return error.SkipZigTest; | |
| 46 | ||
| 47 | 80 | var tmp = tmpDir(.{}); |
| 48 | 81 | defer tmp.cleanup(); |
| 49 | 82 | |
| ... | ... | @@ -51,7 +84,11 @@ test "readlinkat" { |
| 51 | 84 | try tmp.dir.writeFile("file.txt", "nonsense"); |
| 52 | 85 | |
| 53 | 86 | // create a symbolic link |
| 54 | try os.symlinkat("file.txt", tmp.dir.fd, "link"); | |
| 87 | if (builtin.os.tag == .windows) { | |
| 88 | try os.windows.CreateSymbolicLink(tmp.dir.fd, "link", "file.txt", false); | |
| 89 | } else { | |
| 90 | try os.symlinkat("file.txt", tmp.dir.fd, "link"); | |
| 91 | } | |
| 55 | 92 | |
| 56 | 93 | // read the link |
| 57 | 94 | var buffer: [fs.MAX_PATH_BYTES]u8 = undefined; |
lib/std/os/windows.zig+244-14| ... | ... | @@ -145,6 +145,7 @@ pub fn OpenFile(sub_path_w: []const u16, options: OpenFileOptions) OpenError!HAN |
| 145 | 145 | |
| 146 | 146 | var delay: usize = 1; |
| 147 | 147 | while (true) { |
| 148 | var flags: ULONG = undefined; | |
| 148 | 149 | const blocking_flag: ULONG = if (options.io_mode == .blocking) FILE_SYNCHRONOUS_IO_NONALERT else 0; |
| 149 | 150 | const rc = ntdll.NtCreateFile( |
| 150 | 151 | &result, |
| ... | ... | @@ -601,28 +602,244 @@ pub fn GetCurrentDirectory(buffer: []u8) GetCurrentDirectoryError![]u8 { |
| 601 | 602 | return buffer[0..end_index]; |
| 602 | 603 | } |
| 603 | 604 | |
| 604 | pub const CreateSymbolicLinkError = error{Unexpected}; | |
| 605 | pub const CreateSymbolicLinkError = error{ | |
| 606 | AccessDenied, | |
| 607 | PathAlreadyExists, | |
| 608 | FileNotFound, | |
| 609 | NameTooLong, | |
| 610 | InvalidUtf8, | |
| 611 | BadPathName, | |
| 612 | NoDevice, | |
| 613 | Unexpected, | |
| 614 | }; | |
| 605 | 615 | |
| 606 | 616 | pub fn CreateSymbolicLink( |
| 617 | dir: ?HANDLE, | |
| 607 | 618 | sym_link_path: []const u8, |
| 608 | 619 | target_path: []const u8, |
| 609 | flags: DWORD, | |
| 620 | is_directory: bool, | |
| 610 | 621 | ) CreateSymbolicLinkError!void { |
| 611 | 622 | const sym_link_path_w = try sliceToPrefixedFileW(sym_link_path); |
| 612 | 623 | const target_path_w = try sliceToPrefixedFileW(target_path); |
| 613 | return CreateSymbolicLinkW(sym_link_path_w.span().ptr, target_path_w.span().ptr, flags); | |
| 624 | return CreateSymbolicLinkW(dir, sym_link_path_w.span(), target_path_w.span(), is_directory); | |
| 614 | 625 | } |
| 615 | 626 | |
| 616 | 627 | pub fn CreateSymbolicLinkW( |
| 617 | sym_link_path: [*:0]const u16, | |
| 618 | target_path: [*:0]const u16, | |
| 619 | flags: DWORD, | |
| 628 | dir: ?HANDLE, | |
| 629 | sym_link_path: [:0]const u16, | |
| 630 | target_path: [:0]const u16, | |
| 631 | is_directory: bool, | |
| 620 | 632 | ) CreateSymbolicLinkError!void { |
| 621 | if (kernel32.CreateSymbolicLinkW(sym_link_path, target_path, flags) == 0) { | |
| 622 | switch (kernel32.GetLastError()) { | |
| 623 | else => |err| return unexpectedError(err), | |
| 633 | const SYMLINK_DATA = extern struct { | |
| 634 | ReparseTag: ULONG, | |
| 635 | ReparseDataLength: USHORT, | |
| 636 | Reserved: USHORT, | |
| 637 | SubstituteNameOffset: USHORT, | |
| 638 | SubstituteNameLength: USHORT, | |
| 639 | PrintNameOffset: USHORT, | |
| 640 | PrintNameLength: USHORT, | |
| 641 | Flags: ULONG, | |
| 642 | }; | |
| 643 | ||
| 644 | var symlink_handle: HANDLE = undefined; | |
| 645 | if (is_directory) { | |
| 646 | const sym_link_len_bytes = math.cast(u16, sym_link_path.len * 2) catch |err| switch (err) { | |
| 647 | error.Overflow => return error.NameTooLong, | |
| 648 | }; | |
| 649 | var nt_name = UNICODE_STRING{ | |
| 650 | .Length = sym_link_len_bytes, | |
| 651 | .MaximumLength = sym_link_len_bytes, | |
| 652 | .Buffer = @intToPtr([*]u16, @ptrToInt(sym_link_path.ptr)), | |
| 653 | }; | |
| 654 | ||
| 655 | if (sym_link_path[0] == '.' and sym_link_path[1] == 0) { | |
| 656 | // Windows does not recognize this, but it does work with empty string. | |
| 657 | nt_name.Length = 0; | |
| 624 | 658 | } |
| 659 | ||
| 660 | var attr = OBJECT_ATTRIBUTES{ | |
| 661 | .Length = @sizeOf(OBJECT_ATTRIBUTES), | |
| 662 | .RootDirectory = if (std.fs.path.isAbsoluteWindowsW(sym_link_path)) null else dir, | |
| 663 | .Attributes = 0, // Note we do not use OBJ_CASE_INSENSITIVE here. | |
| 664 | .ObjectName = &nt_name, | |
| 665 | .SecurityDescriptor = null, | |
| 666 | .SecurityQualityOfService = null, | |
| 667 | }; | |
| 668 | ||
| 669 | var io: IO_STATUS_BLOCK = undefined; | |
| 670 | const rc = ntdll.NtCreateFile( | |
| 671 | &symlink_handle, | |
| 672 | GENERIC_READ | SYNCHRONIZE | FILE_WRITE_ATTRIBUTES, | |
| 673 | &attr, | |
| 674 | &io, | |
| 675 | null, | |
| 676 | FILE_ATTRIBUTE_NORMAL, | |
| 677 | FILE_SHARE_READ, | |
| 678 | FILE_CREATE, | |
| 679 | FILE_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT | FILE_OPEN_FOR_BACKUP_INTENT, | |
| 680 | null, | |
| 681 | 0, | |
| 682 | ); | |
| 683 | switch (rc) { | |
| 684 | .SUCCESS => {}, | |
| 685 | .OBJECT_NAME_INVALID => unreachable, | |
| 686 | .OBJECT_NAME_NOT_FOUND => return error.FileNotFound, | |
| 687 | .OBJECT_PATH_NOT_FOUND => return error.FileNotFound, | |
| 688 | .NO_MEDIA_IN_DEVICE => return error.NoDevice, | |
| 689 | .INVALID_PARAMETER => unreachable, | |
| 690 | .ACCESS_DENIED => return error.AccessDenied, | |
| 691 | .OBJECT_PATH_SYNTAX_BAD => unreachable, | |
| 692 | .OBJECT_NAME_COLLISION => return error.PathAlreadyExists, | |
| 693 | else => return unexpectedStatus(rc), | |
| 694 | } | |
| 695 | } else { | |
| 696 | symlink_handle = OpenFile(sym_link_path, .{ | |
| 697 | .access_mask = SYNCHRONIZE | GENERIC_READ | GENERIC_WRITE, | |
| 698 | .dir = dir, | |
| 699 | .creation = FILE_CREATE, | |
| 700 | .io_mode = .blocking, | |
| 701 | }) catch |err| switch (err) { | |
| 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, | |
| 707 | }; | |
| 625 | 708 | } |
| 709 | defer CloseHandle(symlink_handle); | |
| 710 | ||
| 711 | // prepare reparse data buffer | |
| 712 | var buffer: [MAXIMUM_REPARSE_DATA_BUFFER_SIZE]u8 = undefined; | |
| 713 | const buf_len = @sizeOf(SYMLINK_DATA) + target_path.len * 4; | |
| 714 | const header_len = @sizeOf(ULONG) + @sizeOf(USHORT) * 2; | |
| 715 | const symlink_data = SYMLINK_DATA{ | |
| 716 | .ReparseTag = IO_REPARSE_TAG_SYMLINK, | |
| 717 | .ReparseDataLength = @intCast(u16, buf_len - header_len), | |
| 718 | .Reserved = 0, | |
| 719 | .SubstituteNameOffset = @intCast(u16, target_path.len * 2), | |
| 720 | .SubstituteNameLength = @intCast(u16, target_path.len * 2), | |
| 721 | .PrintNameOffset = 0, | |
| 722 | .PrintNameLength = @intCast(u16, target_path.len * 2), | |
| 723 | .Flags = if (dir) |_| SYMLINK_FLAG_RELATIVE else 0, | |
| 724 | }; | |
| 725 | ||
| 726 | std.mem.copy(u8, buffer[0..], std.mem.asBytes(&symlink_data)); | |
| 727 | @memcpy(buffer[@sizeOf(SYMLINK_DATA)..], @ptrCast([*]const u8, target_path), target_path.len * 2); | |
| 728 | const paths_start = @sizeOf(SYMLINK_DATA) + target_path.len * 2; | |
| 729 | @memcpy(buffer[paths_start..].ptr, @ptrCast([*]const u8, target_path), target_path.len * 2); | |
| 730 | // TODO replace with NtDeviceIoControl | |
| 731 | _ = try DeviceIoControl(symlink_handle, FSCTL_SET_REPARSE_POINT, buffer[0..buf_len], null, null); | |
| 732 | } | |
| 733 | ||
| 734 | pub const ReadLinkError = error{ | |
| 735 | FileNotFound, | |
| 736 | AccessDenied, | |
| 737 | Unexpected, | |
| 738 | NameTooLong, | |
| 739 | UnsupportedReparsePointType, | |
| 740 | InvalidUtf8, | |
| 741 | BadPathName, | |
| 742 | }; | |
| 743 | ||
| 744 | pub fn ReadLink( | |
| 745 | dir: ?HANDLE, | |
| 746 | sub_path: []const u8, | |
| 747 | out_buffer: []u8, | |
| 748 | ) ReadLinkError![]u8 { | |
| 749 | const sub_path_w = try sliceToPrefixedFileW(sub_path); | |
| 750 | return ReadLinkW(dir, sub_path_w.span().ptr, out_buffer); | |
| 751 | } | |
| 752 | ||
| 753 | pub fn ReadLinkW(dir: ?HANDLE, sub_path_w: [*:0]const u16, out_buffer: []u8) ReadLinkError![]u8 { | |
| 754 | const path_len_bytes = math.cast(u16, mem.lenZ(sub_path_w) * 2) catch |err| switch (err) { | |
| 755 | error.Overflow => return error.NameTooLong, | |
| 756 | }; | |
| 757 | var nt_name = UNICODE_STRING{ | |
| 758 | .Length = path_len_bytes, | |
| 759 | .MaximumLength = path_len_bytes, | |
| 760 | .Buffer = @intToPtr([*]u16, @ptrToInt(sub_path_w)), | |
| 761 | }; | |
| 762 | ||
| 763 | if (sub_path_w[0] == '.' and sub_path_w[1] == 0) { | |
| 764 | // Windows does not recognize this, but it does work with empty string. | |
| 765 | nt_name.Length = 0; | |
| 766 | } | |
| 767 | ||
| 768 | var attr = OBJECT_ATTRIBUTES{ | |
| 769 | .Length = @sizeOf(OBJECT_ATTRIBUTES), | |
| 770 | .RootDirectory = if (std.fs.path.isAbsoluteWindowsW(sub_path_w)) null else dir, | |
| 771 | .Attributes = 0, // Note we do not use OBJ_CASE_INSENSITIVE here. | |
| 772 | .ObjectName = &nt_name, | |
| 773 | .SecurityDescriptor = null, | |
| 774 | .SecurityQualityOfService = null, | |
| 775 | }; | |
| 776 | var io: IO_STATUS_BLOCK = undefined; | |
| 777 | var result_handle: HANDLE = undefined; | |
| 778 | const rc = ntdll.NtCreateFile( | |
| 779 | &result_handle, | |
| 780 | FILE_READ_ATTRIBUTES, | |
| 781 | &attr, | |
| 782 | &io, | |
| 783 | null, | |
| 784 | FILE_ATTRIBUTE_NORMAL, | |
| 785 | FILE_SHARE_READ, | |
| 786 | FILE_OPEN, | |
| 787 | FILE_OPEN_REPARSE_POINT, | |
| 788 | null, | |
| 789 | 0, | |
| 790 | ); | |
| 791 | switch (rc) { | |
| 792 | .SUCCESS => {}, | |
| 793 | .OBJECT_NAME_INVALID => unreachable, | |
| 794 | .OBJECT_NAME_NOT_FOUND => return error.FileNotFound, | |
| 795 | .OBJECT_PATH_NOT_FOUND => return error.FileNotFound, | |
| 796 | .NO_MEDIA_IN_DEVICE => return error.FileNotFound, | |
| 797 | .INVALID_PARAMETER => unreachable, | |
| 798 | .SHARING_VIOLATION => return error.AccessDenied, | |
| 799 | .ACCESS_DENIED => return error.AccessDenied, | |
| 800 | .PIPE_BUSY => return error.AccessDenied, | |
| 801 | .OBJECT_PATH_SYNTAX_BAD => unreachable, | |
| 802 | .OBJECT_NAME_COLLISION => unreachable, | |
| 803 | .FILE_IS_A_DIRECTORY => unreachable, | |
| 804 | else => return unexpectedStatus(rc), | |
| 805 | } | |
| 806 | defer CloseHandle(result_handle); | |
| 807 | ||
| 808 | var reparse_buf: [MAXIMUM_REPARSE_DATA_BUFFER_SIZE]u8 = undefined; | |
| 809 | _ = try DeviceIoControl(result_handle, FSCTL_GET_REPARSE_POINT, null, reparse_buf[0..], null); | |
| 810 | ||
| 811 | const reparse_struct = @ptrCast(*const REPARSE_DATA_BUFFER, @alignCast(@alignOf(REPARSE_DATA_BUFFER), &reparse_buf[0])); | |
| 812 | switch (reparse_struct.ReparseTag) { | |
| 813 | IO_REPARSE_TAG_SYMLINK => { | |
| 814 | const buf = @ptrCast(*const SYMBOLIC_LINK_REPARSE_BUFFER, @alignCast(@alignOf(SYMBOLIC_LINK_REPARSE_BUFFER), &reparse_struct.DataBuffer[0])); | |
| 815 | const offset = buf.SubstituteNameOffset >> 1; | |
| 816 | const len = buf.SubstituteNameLength >> 1; | |
| 817 | const path_buf = @as([*]const u16, &buf.PathBuffer); | |
| 818 | const is_relative = buf.Flags & SYMLINK_FLAG_RELATIVE != 0; | |
| 819 | return parseReadlinkPath(path_buf[offset .. offset + len], is_relative, out_buffer); | |
| 820 | }, | |
| 821 | IO_REPARSE_TAG_MOUNT_POINT => { | |
| 822 | const buf = @ptrCast(*const MOUNT_POINT_REPARSE_BUFFER, @alignCast(@alignOf(MOUNT_POINT_REPARSE_BUFFER), &reparse_struct.DataBuffer[0])); | |
| 823 | const offset = buf.SubstituteNameOffset >> 1; | |
| 824 | const len = buf.SubstituteNameLength >> 1; | |
| 825 | const path_buf = @as([*]const u16, &buf.PathBuffer); | |
| 826 | return parseReadlinkPath(path_buf[offset .. offset + len], false, out_buffer); | |
| 827 | }, | |
| 828 | else => |value| { | |
| 829 | std.debug.warn("unsupported symlink type: {}", .{value}); | |
| 830 | return error.UnsupportedReparsePointType; | |
| 831 | }, | |
| 832 | } | |
| 833 | } | |
| 834 | ||
| 835 | fn parseReadlinkPath(path: []const u16, is_relative: bool, out_buffer: []u8) []u8 { | |
| 836 | const prefix = [_]u16{ '\\', '?', '?', '\\' }; | |
| 837 | var start_index: usize = 0; | |
| 838 | if (!is_relative and std.mem.startsWith(u16, path, &prefix)) { | |
| 839 | start_index = prefix.len; | |
| 840 | } | |
| 841 | const out_len = std.unicode.utf16leToUtf8(out_buffer, path[start_index..]) catch unreachable; | |
| 842 | return out_buffer[0..out_len]; | |
| 626 | 843 | } |
| 627 | 844 | |
| 628 | 845 | pub const DeleteFileError = error{ |
| ... | ... | @@ -1239,23 +1456,30 @@ pub const PathSpace = struct { |
| 1239 | 1456 | } |
| 1240 | 1457 | }; |
| 1241 | 1458 | |
| 1459 | /// Same as `sliceToPrefixedFileW` but accepts a pointer | |
| 1460 | /// to a null-terminated path. | |
| 1242 | 1461 | pub fn cStrToPrefixedFileW(s: [*:0]const u8) !PathSpace { |
| 1243 | 1462 | return sliceToPrefixedFileW(mem.spanZ(s)); |
| 1244 | 1463 | } |
| 1245 | 1464 | |
| 1465 | /// Converts the path `s` to WTF16, null-terminated. If the path is absolute, | |
| 1466 | /// it will get NT-style prefix `\??\` prepended automatically. For prepending | |
| 1467 | /// Win32-style prefix, see `sliceToWin32PrefixedFileW` instead. | |
| 1246 | 1468 | pub fn sliceToPrefixedFileW(s: []const u8) !PathSpace { |
| 1247 | 1469 | // TODO https://github.com/ziglang/zig/issues/2765 |
| 1248 | 1470 | var path_space: PathSpace = undefined; |
| 1249 | for (s) |byte| { | |
| 1471 | const prefix = "\\??\\"; | |
| 1472 | const prefix_index: usize = if (mem.startsWith(u8, s, prefix)) prefix.len else 0; | |
| 1473 | for (s[prefix_index..]) |byte| { | |
| 1250 | 1474 | switch (byte) { |
| 1251 | 1475 | '*', '?', '"', '<', '>', '|' => return error.BadPathName, |
| 1252 | 1476 | else => {}, |
| 1253 | 1477 | } |
| 1254 | 1478 | } |
| 1255 | const start_index = if (mem.startsWith(u8, s, "\\?") or !std.fs.path.isAbsolute(s)) 0 else blk: { | |
| 1256 | const prefix = [_]u16{ '\\', '?', '?', '\\' }; | |
| 1257 | mem.copy(u16, path_space.data[0..], &prefix); | |
| 1258 | break :blk prefix.len; | |
| 1479 | const start_index = if (prefix_index > 0 or !std.fs.path.isAbsolute(s)) 0 else blk: { | |
| 1480 | const prefix_u16 = [_]u16{ '\\', '?', '?', '\\' }; | |
| 1481 | mem.copy(u16, path_space.data[0..], prefix_u16[0..]); | |
| 1482 | break :blk prefix_u16.len; | |
| 1259 | 1483 | }; |
| 1260 | 1484 | path_space.len = start_index + try std.unicode.utf8ToUtf16Le(path_space.data[start_index..], s); |
| 1261 | 1485 | if (path_space.len > path_space.data.len) return error.NameTooLong; |
| ... | ... | @@ -1287,6 +1511,12 @@ pub fn wToPrefixedFileW(s: []const u16) !PathSpace { |
| 1287 | 1511 | path_space.len = start_index + s.len; |
| 1288 | 1512 | if (path_space.len > path_space.data.len) return error.NameTooLong; |
| 1289 | 1513 | mem.copy(u16, path_space.data[start_index..], s); |
| 1514 | // > File I/O functions in the Windows API convert "/" to "\" as part of | |
| 1515 | // > converting the name to an NT-style name, except when using the "\\?\" | |
| 1516 | // > prefix as detailed in the following sections. | |
| 1517 | // from https://docs.microsoft.com/en-us/windows/desktop/FileIO/naming-a-file#maximum-path-length-limitation | |
| 1518 | // Because we want the larger maximum path length for absolute paths, we | |
| 1519 | // convert forward slashes to backward slashes here. | |
| 1290 | 1520 | for (path_space.data[0..path_space.len]) |*elem| { |
| 1291 | 1521 | if (elem.* == '/') { |
| 1292 | 1522 | elem.* = '\\'; |
lib/std/os/windows/bits.zig+32-1| ... | ... | @@ -488,7 +488,7 @@ pub const FILE_OPEN_BY_FILE_ID = 0x00002000; |
| 488 | 488 | pub const FILE_OPEN_FOR_BACKUP_INTENT = 0x00004000; |
| 489 | 489 | pub const FILE_NO_COMPRESSION = 0x00008000; |
| 490 | 490 | pub const FILE_RESERVE_OPFILTER = 0x00100000; |
| 491 | pub const FILE_TRANSACTED_MODE = 0x00200000; | |
| 491 | pub const FILE_OPEN_REPARSE_POINT = 0x00200000; | |
| 492 | 492 | pub const FILE_OPEN_OFFLINE_FILE = 0x00400000; |
| 493 | 493 | pub const FILE_OPEN_FOR_FREE_SPACE_QUERY = 0x00800000; |
| 494 | 494 | |
| ... | ... | @@ -1542,3 +1542,34 @@ pub const POSVERSIONINFOW = *OSVERSIONINFOW; |
| 1542 | 1542 | pub const LPOSVERSIONINFOW = *OSVERSIONINFOW; |
| 1543 | 1543 | pub const RTL_OSVERSIONINFOW = OSVERSIONINFOW; |
| 1544 | 1544 | pub const PRTL_OSVERSIONINFOW = *RTL_OSVERSIONINFOW; |
| 1545 | ||
| 1546 | pub const REPARSE_DATA_BUFFER = extern struct { | |
| 1547 | ReparseTag: ULONG, | |
| 1548 | ReparseDataLength: USHORT, | |
| 1549 | Reserved: USHORT, | |
| 1550 | DataBuffer: [1]UCHAR, | |
| 1551 | }; | |
| 1552 | pub const SYMBOLIC_LINK_REPARSE_BUFFER = extern struct { | |
| 1553 | SubstituteNameOffset: USHORT, | |
| 1554 | SubstituteNameLength: USHORT, | |
| 1555 | PrintNameOffset: USHORT, | |
| 1556 | PrintNameLength: USHORT, | |
| 1557 | Flags: ULONG, | |
| 1558 | PathBuffer: [1]WCHAR, | |
| 1559 | }; | |
| 1560 | pub const MOUNT_POINT_REPARSE_BUFFER = extern struct { | |
| 1561 | SubstituteNameOffset: USHORT, | |
| 1562 | SubstituteNameLength: USHORT, | |
| 1563 | PrintNameOffset: USHORT, | |
| 1564 | PrintNameLength: USHORT, | |
| 1565 | PathBuffer: [1]WCHAR, | |
| 1566 | }; | |
| 1567 | pub const MAXIMUM_REPARSE_DATA_BUFFER_SIZE: ULONG = 16 * 1024; | |
| 1568 | pub const FSCTL_SET_REPARSE_POINT: DWORD = 0x900a4; | |
| 1569 | pub const FSCTL_GET_REPARSE_POINT: DWORD = 0x900a8; | |
| 1570 | pub const IO_REPARSE_TAG_SYMLINK: ULONG = 0xa000000c; | |
| 1571 | pub const IO_REPARSE_TAG_MOUNT_POINT: ULONG = 0xa0000003; | |
| 1572 | pub const SYMLINK_FLAG_RELATIVE: ULONG = 0x1; | |
| 1573 | ||
| 1574 | pub const SYMBOLIC_LINK_FLAG_DIRECTORY: DWORD = 0x1; | |
| 1575 | pub const SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE: DWORD = 0x2; |
lib/std/zig/system.zig+6| ... | ... | @@ -552,6 +552,9 @@ pub const NativeTargetInfo = struct { |
| 552 | 552 | error.SystemResources => return error.SystemResources, |
| 553 | 553 | error.NotDir => return error.GnuLibCVersionUnavailable, |
| 554 | 554 | error.Unexpected => return error.GnuLibCVersionUnavailable, |
| 555 | error.InvalidUtf8 => unreachable, // Windows only | |
| 556 | error.BadPathName => unreachable, // Windows only | |
| 557 | error.UnsupportedReparsePointType => unreachable, // Windows only | |
| 555 | 558 | }; |
| 556 | 559 | return glibcVerFromLinkName(link_name); |
| 557 | 560 | } |
| ... | ... | @@ -817,6 +820,9 @@ pub const NativeTargetInfo = struct { |
| 817 | 820 | &link_buf, |
| 818 | 821 | ) catch |err| switch (err) { |
| 819 | 822 | error.NameTooLong => unreachable, |
| 823 | error.InvalidUtf8 => unreachable, // Windows only | |
| 824 | error.BadPathName => unreachable, // Windows only | |
| 825 | error.UnsupportedReparsePointType => unreachable, // Windows only | |
| 820 | 826 | |
| 821 | 827 | error.AccessDenied, |
| 822 | 828 | error.FileNotFound, |