authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2020-07-22 17:59:40+00:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2020-07-22 17:59:40+00:00
log9505bb74cd0f087f4672d878bf7174273db8c3ae
tree2798e9c9dd82f7c3a68f2ae0906512938af87f73
parentc6bd8e8c535c4518fc3f1d2496dc7a921fae0b1c
parentaa6fcaf76f00453f160545e760c37d64588f4517
signaturebadge-question-mark Signed by PGP key 4AEE18F83AFDEB23

Merge pull request #5879 from kubkon/readlink-win

Implement readlink on Windows

7 files changed, 581 insertions(+), 69 deletions(-)

lib/std/fs.zig+160-17
...@@ -16,9 +16,6 @@ pub const wasi = @import("fs/wasi.zig");...@@ -16,9 +16,6 @@ pub const wasi = @import("fs/wasi.zig");
1616
17// TODO audit these APIs with respect to Dir and absolute paths17// TODO audit these APIs with respect to Dir and absolute paths
1818
19pub const symLink = os.symlink;
20pub const symLinkZ = os.symlinkZ;
21pub const symLinkC = @compileError("deprecated: renamed to symlinkZ");
22pub const rename = os.rename;19pub const rename = os.rename;
23pub const renameZ = os.renameZ;20pub const renameZ = os.renameZ;
24pub const renameC = @compileError("deprecated: renamed to renameZ");21pub 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,7 +66,7 @@ pub const need_async_thread = std.io.is_async and switch (builtin.os.tag) {
6966
70/// TODO remove the allocator requirement from this API67/// TODO remove the allocator requirement from this API
71pub 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 {
72 if (symLink(existing_path, new_path)) {69 if (cwd().symLink(existing_path, new_path, .{})) {
73 return;70 return;
74 } else |err| switch (err) {71 } else |err| switch (err) {
75 error.PathAlreadyExists => {},72 error.PathAlreadyExists => {},
...@@ -87,7 +84,7 @@ pub fn atomicSymLink(allocator: *Allocator, existing_path: []const u8, new_path:...@@ -87,7 +84,7 @@ pub fn atomicSymLink(allocator: *Allocator, existing_path: []const u8, new_path:
87 try crypto.randomBytes(rand_buf[0..]);84 try crypto.randomBytes(rand_buf[0..]);
88 base64_encoder.encode(tmp_path[dirname.len + 1 ..], &rand_buf);85 base64_encoder.encode(tmp_path[dirname.len + 1 ..], &rand_buf);
8986
90 if (symLink(existing_path, tmp_path)) {87 if (cwd().symLink(existing_path, new_path, .{})) {
91 return rename(tmp_path, new_path);88 return rename(tmp_path, new_path);
92 } else |err| switch (err) {89 } else |err| switch (err) {
93 error.PathAlreadyExists => continue,90 error.PathAlreadyExists => continue,
...@@ -672,7 +669,7 @@ pub const Dir = struct {...@@ -672,7 +669,7 @@ pub const Dir = struct {
672 w.RIGHT_FD_FILESTAT_SET_TIMES |669 w.RIGHT_FD_FILESTAT_SET_TIMES |
673 w.RIGHT_FD_FILESTAT_SET_SIZE;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 return File{ .handle = fd };673 return File{ .handle = fd };
677 }674 }
678675
...@@ -792,7 +789,7 @@ pub const Dir = struct {...@@ -792,7 +789,7 @@ pub const Dir = struct {
792 if (flags.exclusive) {789 if (flags.exclusive) {
793 oflags |= w.O_EXCL;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 return File{ .handle = fd };793 return File{ .handle = fd };
797 }794 }
798795
...@@ -954,6 +951,9 @@ pub const Dir = struct {...@@ -954,6 +951,9 @@ pub const Dir = struct {
954 /// `true` means the opened directory can be scanned for the files and sub-directories951 /// `true` means the opened directory can be scanned for the files and sub-directories
955 /// of the result. It means the `iterate` function can be called.952 /// of the result. It means the `iterate` function can be called.
956 iterate: bool = false,953 iterate: bool = false,
954
955 /// `true` means it won't dereference the symlinks.
956 no_follow: bool = false,
957 };957 };
958958
959 /// Opens a directory at the given path. The directory is a system resource that remains959 /// Opens a directory at the given path. The directory is a system resource that remains
...@@ -997,10 +997,11 @@ pub const Dir = struct {...@@ -997,10 +997,11 @@ pub const Dir = struct {
997 w.RIGHT_PATH_REMOVE_DIRECTORY |997 w.RIGHT_PATH_REMOVE_DIRECTORY |
998 w.RIGHT_PATH_UNLINK_FILE;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 // TODO do we really need all the rights here?1001 // TODO do we really need all the rights here?
1001 const inheriting: w.rights_t = w.RIGHT_ALL ^ w.RIGHT_SOCK_SHUTDOWN;1002 const inheriting: w.rights_t = w.RIGHT_ALL ^ w.RIGHT_SOCK_SHUTDOWN;
10021003
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 const fd = result catch |err| switch (err) {1005 const fd = result catch |err| switch (err) {
1005 error.FileTooBig => unreachable, // can't happen for directories1006 error.FileTooBig => unreachable, // can't happen for directories
1006 error.IsDir => unreachable, // we're providing O_DIRECTORY1007 error.IsDir => unreachable, // we're providing O_DIRECTORY
...@@ -1017,11 +1018,13 @@ pub const Dir = struct {...@@ -1017,11 +1018,13 @@ pub const Dir = struct {
1017 if (builtin.os.tag == .windows) {1018 if (builtin.os.tag == .windows) {
1018 const sub_path_w = try os.windows.cStrToPrefixedFileW(sub_path_c);1019 const sub_path_w = try os.windows.cStrToPrefixedFileW(sub_path_c);
1019 return self.openDirW(sub_path_w.span().ptr, args);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 const O_PATH = if (@hasDecl(os, "O_PATH")) os.O_PATH else 0;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 } else {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 }
10271030
...@@ -1033,7 +1036,7 @@ pub const Dir = struct {...@@ -1033,7 +1036,7 @@ pub const Dir = struct {
1033 const base_flags = w.STANDARD_RIGHTS_READ | w.FILE_READ_ATTRIBUTES | w.FILE_READ_EA |1036 const base_flags = w.STANDARD_RIGHTS_READ | w.FILE_READ_ATTRIBUTES | w.FILE_READ_EA |
1034 w.SYNCHRONIZE | w.FILE_TRAVERSE;1037 w.SYNCHRONIZE | w.FILE_TRAVERSE;
1035 const flags: u32 = if (args.iterate) base_flags | w.FILE_LIST_DIRECTORY else base_flags;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 }
10381041
1039 /// `flags` must contain `os.O_DIRECTORY`.1042 /// `flags` must contain `os.O_DIRECTORY`.
...@@ -1053,7 +1056,7 @@ pub const Dir = struct {...@@ -1053,7 +1056,7 @@ pub const Dir = struct {
1053 return Dir{ .fd = fd };1056 return Dir{ .fd = fd };
1054 }1057 }
10551058
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 const w = os.windows;1060 const w = os.windows;
10581061
1059 var result = Dir{1062 var result = Dir{
...@@ -1083,6 +1086,7 @@ pub const Dir = struct {...@@ -1083,6 +1086,7 @@ pub const Dir = struct {
1083 // implement this: https://git.midipix.org/ntapi/tree/src/fs/ntapi_tt_open_physical_parent_directory.c1086 // implement this: https://git.midipix.org/ntapi/tree/src/fs/ntapi_tt_open_physical_parent_directory.c
1084 @panic("TODO opening '..' with a relative directory handle is not yet implemented on Windows");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 var io: w.IO_STATUS_BLOCK = undefined;1090 var io: w.IO_STATUS_BLOCK = undefined;
1087 const rc = w.ntdll.NtCreateFile(1091 const rc = w.ntdll.NtCreateFile(
1088 &result.fd,1092 &result.fd,
...@@ -1093,7 +1097,7 @@ pub const Dir = struct {...@@ -1093,7 +1097,7 @@ pub const Dir = struct {
1093 0,1097 0,
1094 w.FILE_SHARE_READ | w.FILE_SHARE_WRITE,1098 w.FILE_SHARE_READ | w.FILE_SHARE_WRITE,
1095 w.FILE_OPEN,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 null,1101 null,
1098 0,1102 0,
1099 );1103 );
...@@ -1207,21 +1211,101 @@ pub const Dir = struct {...@@ -1207,21 +1211,101 @@ pub const Dir = struct {
1207 };1211 };
1208 }1212 }
12091213
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 /// Read value of a symbolic link.1273 /// Read value of a symbolic link.
1211 /// The return value is a slice of `buffer`, from index `0`.1274 /// The return value is a slice of `buffer`, from index `0`.
1212 /// Asserts that the path parameter has no null bytes.1275 /// Asserts that the path parameter has no null bytes.
1213 pub fn readLink(self: Dir, sub_path: []const u8, buffer: []u8) ![]u8 {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 const sub_path_c = try os.toPosixPath(sub_path);1283 const sub_path_c = try os.toPosixPath(sub_path);
1215 return self.readLinkZ(&sub_path_c, buffer);1284 return self.readLinkZ(&sub_path_c, buffer);
1216 }1285 }
12171286
1218 pub const readLinkC = @compileError("deprecated: renamed to readLinkZ");1287 pub const readLinkC = @compileError("deprecated: renamed to readLinkZ");
12191288
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 /// Same as `readLink`, except the `pathname` parameter is null-terminated.1294 /// Same as `readLink`, except the `pathname` parameter is null-terminated.
1221 pub fn readLinkZ(self: Dir, sub_path_c: [*:0]const u8, buffer: []u8) ![]u8 {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 return os.readlinkatZ(self.fd, sub_path_c, buffer);1300 return os.readlinkatZ(self.fd, sub_path_c, buffer);
1223 }1301 }
12241302
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 /// On success, caller owns returned buffer.1309 /// On success, caller owns returned buffer.
1226 /// If the file is larger than `max_bytes`, returns `error.FileTooBig`.1310 /// If the file is larger than `max_bytes`, returns `error.FileTooBig`.
1227 pub fn readFileAlloc(self: Dir, allocator: *mem.Allocator, file_path: []const u8, max_bytes: usize) ![]u8 {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,6 +1364,7 @@ pub const Dir = struct {
1280 pub fn deleteTree(self: Dir, sub_path: []const u8) DeleteTreeError!void {1364 pub fn deleteTree(self: Dir, sub_path: []const u8) DeleteTreeError!void {
1281 start_over: while (true) {1365 start_over: while (true) {
1282 var got_access_denied = false;1366 var got_access_denied = false;
1367
1283 // First, try deleting the item as a file. This way we don't follow sym links.1368 // First, try deleting the item as a file. This way we don't follow sym links.
1284 if (self.deleteFile(sub_path)) {1369 if (self.deleteFile(sub_path)) {
1285 return;1370 return;
...@@ -1300,7 +1385,7 @@ pub const Dir = struct {...@@ -1300,7 +1385,7 @@ pub const Dir = struct {
1300 error.Unexpected,1385 error.Unexpected,
1301 => |e| return e,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 error.NotDir => {1389 error.NotDir => {
1305 if (got_access_denied) {1390 if (got_access_denied) {
1306 return error.AccessDenied;1391 return error.AccessDenied;
...@@ -1367,7 +1452,7 @@ pub const Dir = struct {...@@ -1367,7 +1452,7 @@ pub const Dir = struct {
1367 => |e| return e,1452 => |e| return e,
1368 }1453 }
13691454
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 error.NotDir => {1456 error.NotDir => {
1372 if (got_access_denied) {1457 if (got_access_denied) {
1373 return error.AccessDenied;1458 return error.AccessDenied;
...@@ -1692,8 +1777,15 @@ pub fn readLinkAbsolute(pathname: []const u8, buffer: *[MAX_PATH_BYTES]u8) ![]u8...@@ -1692,8 +1777,15 @@ pub fn readLinkAbsolute(pathname: []const u8, buffer: *[MAX_PATH_BYTES]u8) ![]u8
1692 return os.readlink(pathname, buffer);1777 return os.readlink(pathname, buffer);
1693}1778}
16941779
1780/// Windows-only. Same as `readlinkW`, except the path parameter is null-terminated, WTF16
1781/// encoded.
1782pub 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/// Same as `readLink`, except the path parameter is null-terminated.1787/// Same as `readLink`, except the path parameter is null-terminated.
1696pub fn readLinkAbsoluteZ(pathname_c: [*]const u8, buffer: *[MAX_PATH_BYTES]u8) ![]u8 {1788pub fn readLinkAbsoluteZ(pathname_c: [*:0]const u8, buffer: *[MAX_PATH_BYTES]u8) ![]u8 {
1697 assert(path.isAbsoluteZ(pathname_c));1789 assert(path.isAbsoluteZ(pathname_c));
1698 return os.readlinkZ(pathname_c, buffer);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,6 +1793,57 @@ pub fn readLinkAbsoluteZ(pathname_c: [*]const u8, buffer: *[MAX_PATH_BYTES]u8) !
1701pub const readLink = @compileError("deprecated; use Dir.readLink or readLinkAbsolute");1793pub const readLink = @compileError("deprecated; use Dir.readLink or readLinkAbsolute");
1702pub const readLinkC = @compileError("deprecated; use Dir.readLinkZ or readLinkAbsoluteZ");1794pub const readLinkC = @compileError("deprecated; use Dir.readLinkZ or readLinkAbsoluteZ");
17031795
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.
1800pub 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`.
1809pub 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`.
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);
1829}
1830
1831/// Same as `symLinkAbsolute` except the parameters are null-terminated pointers.
1832/// See also `symLinkAbsolute`.
1833pub 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
1844pub const symLink = @compileError("deprecated: use Dir.symLink or symLinkAbsolute");
1845pub const symLinkC = @compileError("deprecated: use Dir.symLinkZ or symLinkAbsoluteZ");
1846
1704pub const Walker = struct {1847pub const Walker = struct {
1705 stack: std.ArrayList(StackItem),1848 stack: std.ArrayList(StackItem),
1706 name_buffer: std.ArrayList(u8),1849 name_buffer: std.ArrayList(u8),
lib/std/fs/test.zig+70
...@@ -10,6 +10,76 @@ const Dir = std.fs.Dir;...@@ -10,6 +10,76 @@ const Dir = std.fs.Dir;
10const File = std.fs.File;10const File = std.fs.File;
11const tmpDir = testing.tmpDir;11const tmpDir = testing.tmpDir;
1212
13test "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
33fn 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
39test "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
77fn 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
13test "Dir.Iterator" {83test "Dir.Iterator" {
14 var tmp_dir = tmpDir(.{ .iterate = true });84 var tmp_dir = tmpDir(.{ .iterate = true });
15 defer tmp_dir.cleanup();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,10 +1109,10 @@ pub fn openat(dir_fd: fd_t, file_path: []const u8, flags: u32, mode: mode_t) Ope
1109}1109}
11101110
1111/// Open and possibly create a file in WASI.1111/// Open and possibly create a file in WASI.
1112pub 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 {1112pub 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 while (true) {1113 while (true) {
1114 var fd: fd_t = undefined;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 wasi.ESUCCESS => return fd,1116 wasi.ESUCCESS => return fd,
1117 wasi.EINTR => continue,1117 wasi.EINTR => continue,
11181118
...@@ -1542,15 +1542,13 @@ pub const SymLinkError = error{...@@ -1542,15 +1542,13 @@ pub const SymLinkError = error{
1542/// A symbolic link (also known as a soft link) may point to an existing file or to a nonexistent1542/// A symbolic link (also known as a soft link) may point to an existing file or to a nonexistent
1543/// one; the latter case is known as a dangling link.1543/// one; the latter case is known as a dangling link.
1544/// If `sym_link_path` exists, it will not be overwritten.1544/// If `sym_link_path` exists, it will not be overwritten.
1545/// See also `symlinkC` and `symlinkW`.1545/// See also `symlinkZ.
1546pub fn symlink(target_path: []const u8, sym_link_path: []const u8) SymLinkError!void {1546pub fn symlink(target_path: []const u8, sym_link_path: []const u8) SymLinkError!void {
1547 if (builtin.os.tag == .wasi) {1547 if (builtin.os.tag == .wasi) {
1548 @compileError("symlink is not supported in WASI; use symlinkat instead");1548 @compileError("symlink is not supported in WASI; use symlinkat instead");
1549 }1549 }
1550 if (builtin.os.tag == .windows) {1550 if (builtin.os.tag == .windows) {
1551 const target_path_w = try windows.sliceToPrefixedFileW(target_path);1551 @compileError("symlink is not supported on Windows; use std.os.windows.CreateSymbolicLink instead");
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);
1554 }1552 }
1555 const target_path_c = try toPosixPath(target_path);1553 const target_path_c = try toPosixPath(target_path);
1556 const sym_link_path_c = try toPosixPath(sym_link_path);1554 const sym_link_path_c = try toPosixPath(sym_link_path);
...@@ -1563,9 +1561,7 @@ pub const symlinkC = @compileError("deprecated: renamed to symlinkZ");...@@ -1563,9 +1561,7 @@ pub const symlinkC = @compileError("deprecated: renamed to symlinkZ");
1563/// See also `symlink`.1561/// See also `symlink`.
1564pub fn symlinkZ(target_path: [*:0]const u8, sym_link_path: [*:0]const u8) SymLinkError!void {1562pub fn symlinkZ(target_path: [*:0]const u8, sym_link_path: [*:0]const u8) SymLinkError!void {
1565 if (builtin.os.tag == .windows) {1563 if (builtin.os.tag == .windows) {
1566 const target_path_w = try windows.cStrToPrefixedFileW(target_path);1564 @compileError("symlink is not supported on Windows; use std.os.windows.CreateSymbolicLink instead");
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);
1569 }1565 }
1570 switch (errno(system.symlink(target_path, sym_link_path))) {1566 switch (errno(system.symlink(target_path, sym_link_path))) {
1571 0 => return,1567 0 => return,
...@@ -1598,9 +1594,7 @@ pub fn symlinkat(target_path: []const u8, newdirfd: fd_t, sym_link_path: []const...@@ -1598,9 +1594,7 @@ pub fn symlinkat(target_path: []const u8, newdirfd: fd_t, sym_link_path: []const
1598 return symlinkatWasi(target_path, newdirfd, sym_link_path);1594 return symlinkatWasi(target_path, newdirfd, sym_link_path);
1599 }1595 }
1600 if (builtin.os.tag == .windows) {1596 if (builtin.os.tag == .windows) {
1601 const target_path_w = try windows.sliceToPrefixedFileW(target_path);1597 @compileError("symlinkat is not supported on Windows; use std.os.windows.CreateSymbolicLink instead");
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);
1604 }1598 }
1605 const target_path_c = try toPosixPath(target_path);1599 const target_path_c = try toPosixPath(target_path);
1606 const sym_link_path_c = try toPosixPath(sym_link_path);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,19 +1627,11 @@ pub fn symlinkatWasi(target_path: []const u8, newdirfd: fd_t, sym_link_path: []c
1633 }1627 }
1634}1628}
16351629
1636/// Windows-only. The same as `symlinkat` except the paths are null-terminated, WTF-16 encoded.
1637/// See also `symlinkat`.
1638pub 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/// The same as `symlinkat` except the parameters are null-terminated pointers.1630/// The same as `symlinkat` except the parameters are null-terminated pointers.
1643/// See also `symlinkat`.1631/// See also `symlinkat`.
1644pub fn symlinkatZ(target_path: [*:0]const u8, newdirfd: fd_t, sym_link_path: [*:0]const u8) SymLinkError!void {1632pub fn symlinkatZ(target_path: [*:0]const u8, newdirfd: fd_t, sym_link_path: [*:0]const u8) SymLinkError!void {
1645 if (builtin.os.tag == .windows) {1633 if (builtin.os.tag == .windows) {
1646 const target_path_w = try windows.cStrToPrefixedFileW(target_path);1634 @compileError("symlinkat is not supported on Windows; use std.os.windows.CreateSymbolicLink instead");
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);
1649 }1635 }
1650 switch (errno(system.symlinkat(target_path, newdirfd, sym_link_path))) {1636 switch (errno(system.symlinkat(target_path, newdirfd, sym_link_path))) {
1651 0 => return,1637 0 => return,
...@@ -1819,9 +1805,9 @@ pub fn unlinkatW(dirfd: fd_t, sub_path_w: [*:0]const u16, flags: u32) UnlinkatEr...@@ -1819,9 +1805,9 @@ pub fn unlinkatW(dirfd: fd_t, sub_path_w: [*:0]const u16, flags: u32) UnlinkatEr
18191805
1820 const want_rmdir_behavior = (flags & AT_REMOVEDIR) != 0;1806 const want_rmdir_behavior = (flags & AT_REMOVEDIR) != 0;
1821 const create_options_flags = if (want_rmdir_behavior)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 else1809 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?
18251811
1826 const path_len_bytes = @intCast(u16, mem.lenZ(sub_path_w) * 2);1812 const path_len_bytes = @intCast(u16, mem.lenZ(sub_path_w) * 2);
1827 var nt_name = w.UNICODE_STRING{1813 var nt_name = w.UNICODE_STRING{
...@@ -2355,6 +2341,11 @@ pub const ReadLinkError = error{...@@ -2355,6 +2341,11 @@ pub const ReadLinkError = error{
2355 FileNotFound,2341 FileNotFound,
2356 SystemResources,2342 SystemResources,
2357 NotDir,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} || UnexpectedError;2349} || UnexpectedError;
23592350
2360/// Read value of a symbolic link.2351/// Read value of a symbolic link.
...@@ -2363,8 +2354,7 @@ pub fn readlink(file_path: []const u8, out_buffer: []u8) ReadLinkError![]u8 {...@@ -2363,8 +2354,7 @@ pub fn readlink(file_path: []const u8, out_buffer: []u8) ReadLinkError![]u8 {
2363 if (builtin.os.tag == .wasi) {2354 if (builtin.os.tag == .wasi) {
2364 @compileError("readlink is not supported in WASI; use readlinkat instead");2355 @compileError("readlink is not supported in WASI; use readlinkat instead");
2365 } else if (builtin.os.tag == .windows) {2356 } else if (builtin.os.tag == .windows) {
2366 const file_path_w = try windows.sliceToPrefixedFileW(file_path);2357 return windows.ReadLink(std.fs.cwd().fd, file_path, out_buffer);
2367 return readlinkW(file_path_w.span().ptr, out_buffer);
2368 } else {2358 } else {
2369 const file_path_c = try toPosixPath(file_path);2359 const file_path_c = try toPosixPath(file_path);
2370 return readlinkZ(&file_path_c, out_buffer);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,16 +2363,16 @@ pub fn readlink(file_path: []const u8, out_buffer: []u8) ReadLinkError![]u8 {
23732363
2374pub const readlinkC = @compileError("deprecated: renamed to readlinkZ");2364pub const readlinkC = @compileError("deprecated: renamed to readlinkZ");
23752365
2376/// Windows-only. Same as `readlink` expecte `file_path` is null-terminated, WTF16 encoded.2366/// Windows-only. Same as `readlink` except `file_path` is null-terminated, WTF16 encoded.
2377/// Seel also `readlinkZ`.2367/// See also `readlinkZ`.
2378pub fn readlinkW(file_path: [*:0]const u16, out_buffer: []u8) ReadLinkError![]u8 {2368pub 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}
23812371
2382/// Same as `readlink` except `file_path` is null-terminated.2372/// Same as `readlink` except `file_path` is null-terminated.
2383pub fn readlinkZ(file_path: [*:0]const u8, out_buffer: []u8) ReadLinkError![]u8 {2373pub fn readlinkZ(file_path: [*:0]const u8, out_buffer: []u8) ReadLinkError![]u8 {
2384 if (builtin.os.tag == .windows) {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 return readlinkW(file_path_w.span().ptr, out_buffer);2376 return readlinkW(file_path_w.span().ptr, out_buffer);
2387 }2377 }
2388 const rc = system.readlink(file_path, out_buffer.ptr, out_buffer.len);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,8 +2399,7 @@ pub fn readlinkat(dirfd: fd_t, file_path: []const u8, out_buffer: []u8) ReadLink
2409 return readlinkatWasi(dirfd, file_path, out_buffer);2399 return readlinkatWasi(dirfd, file_path, out_buffer);
2410 }2400 }
2411 if (builtin.os.tag == .windows) {2401 if (builtin.os.tag == .windows) {
2412 const file_path_w = try windows.cStrToPrefixedFileW(file_path);2402 return windows.ReadLink(dirfd, file_path, out_buffer);
2413 return readlinkatW(dirfd, file_path.span().ptr, out_buffer);
2414 }2403 }
2415 const file_path_c = try toPosixPath(file_path);2404 const file_path_c = try toPosixPath(file_path);
2416 return readlinkatZ(dirfd, &file_path_c, out_buffer);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,7 +2430,7 @@ pub fn readlinkatWasi(dirfd: fd_t, file_path: []const u8, out_buffer: []u8) Read
2441/// Windows-only. Same as `readlinkat` except `file_path` is null-terminated, WTF16 encoded.2430/// Windows-only. Same as `readlinkat` except `file_path` is null-terminated, WTF16 encoded.
2442/// See also `readlinkat`.2431/// See also `readlinkat`.
2443pub fn readlinkatW(dirfd: fd_t, file_path: [*:0]const u16, out_buffer: []u8) ReadLinkError![]u8 {2432pub 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}
24462435
2447/// Same as `readlinkat` except `file_path` is null-terminated.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,7 +3986,13 @@ pub fn realpathZ(pathname: [*:0]const u8, out_buffer: *[MAX_PATH_BYTES]u8) RealP
3997 var procfs_buf: ["/proc/self/fd/-2147483648".len:0]u8 = undefined;3986 var procfs_buf: ["/proc/self/fd/-2147483648".len:0]u8 = undefined;
3998 const proc_path = std.fmt.bufPrint(procfs_buf[0..], "/proc/self/fd/{}\x00", .{fd}) catch unreachable;3987 const proc_path = std.fmt.bufPrint(procfs_buf[0..], "/proc/self/fd/{}\x00", .{fd}) catch unreachable;
39993988
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 const result_path = std.c.realpath(pathname, out_buffer) orelse switch (std.c._errno().*) {3997 const result_path = std.c.realpath(pathname, out_buffer) orelse switch (std.c._errno().*) {
4003 EINVAL => unreachable,3998 EINVAL => unreachable,
lib/std/os/test.zig+41-4
...@@ -17,6 +17,42 @@ const AtomicRmwOp = builtin.AtomicRmwOp;...@@ -17,6 +17,42 @@ const AtomicRmwOp = builtin.AtomicRmwOp;
17const AtomicOrder = builtin.AtomicOrder;17const AtomicOrder = builtin.AtomicOrder;
18const tmpDir = std.testing.tmpDir;18const tmpDir = std.testing.tmpDir;
19const Dir = std.fs.Dir;19const Dir = std.fs.Dir;
20const ArenaAllocator = std.heap.ArenaAllocator;
21
22test "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
43test "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
51fn 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}
2056
21test "fstatat" {57test "fstatat" {
22 // enable when `fstat` and `fstatat` are implemented on Windows58 // enable when `fstat` and `fstatat` are implemented on Windows
...@@ -41,9 +77,6 @@ test "fstatat" {...@@ -41,9 +77,6 @@ test "fstatat" {
41}77}
4278
43test "readlinkat" {79test "readlinkat" {
44 // enable when `readlinkat` and `symlinkat` are implemented on Windows
45 if (builtin.os.tag == .windows) return error.SkipZigTest;
46
47 var tmp = tmpDir(.{});80 var tmp = tmpDir(.{});
48 defer tmp.cleanup();81 defer tmp.cleanup();
4982
...@@ -51,7 +84,11 @@ test "readlinkat" {...@@ -51,7 +84,11 @@ test "readlinkat" {
51 try tmp.dir.writeFile("file.txt", "nonsense");84 try tmp.dir.writeFile("file.txt", "nonsense");
5285
53 // create a symbolic link86 // 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 }
5592
56 // read the link93 // read the link
57 var buffer: [fs.MAX_PATH_BYTES]u8 = undefined;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,6 +145,7 @@ pub fn OpenFile(sub_path_w: []const u16, options: OpenFileOptions) OpenError!HAN
145145
146 var delay: usize = 1;146 var delay: usize = 1;
147 while (true) {147 while (true) {
148 var flags: ULONG = undefined;
148 const blocking_flag: ULONG = if (options.io_mode == .blocking) FILE_SYNCHRONOUS_IO_NONALERT else 0;149 const blocking_flag: ULONG = if (options.io_mode == .blocking) FILE_SYNCHRONOUS_IO_NONALERT else 0;
149 const rc = ntdll.NtCreateFile(150 const rc = ntdll.NtCreateFile(
150 &result,151 &result,
...@@ -601,28 +602,244 @@ pub fn GetCurrentDirectory(buffer: []u8) GetCurrentDirectoryError![]u8 {...@@ -601,28 +602,244 @@ pub fn GetCurrentDirectory(buffer: []u8) GetCurrentDirectoryError![]u8 {
601 return buffer[0..end_index];602 return buffer[0..end_index];
602}603}
603604
604pub const CreateSymbolicLinkError = error{Unexpected};605pub const CreateSymbolicLinkError = error{
606 AccessDenied,
607 PathAlreadyExists,
608 FileNotFound,
609 NameTooLong,
610 InvalidUtf8,
611 BadPathName,
612 NoDevice,
613 Unexpected,
614};
605615
606pub fn CreateSymbolicLink(616pub fn CreateSymbolicLink(
617 dir: ?HANDLE,
607 sym_link_path: []const u8,618 sym_link_path: []const u8,
608 target_path: []const u8,619 target_path: []const u8,
609 flags: DWORD,620 is_directory: bool,
610) CreateSymbolicLinkError!void {621) CreateSymbolicLinkError!void {
611 const sym_link_path_w = try sliceToPrefixedFileW(sym_link_path);622 const sym_link_path_w = try sliceToPrefixedFileW(sym_link_path);
612 const target_path_w = try sliceToPrefixedFileW(target_path);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}
615626
616pub fn CreateSymbolicLinkW(627pub fn CreateSymbolicLinkW(
617 sym_link_path: [*:0]const u16,628 dir: ?HANDLE,
618 target_path: [*:0]const u16,629 sym_link_path: [:0]const u16,
619 flags: DWORD,630 target_path: [:0]const u16,
631 is_directory: bool,
620) CreateSymbolicLinkError!void {632) CreateSymbolicLinkError!void {
621 if (kernel32.CreateSymbolicLinkW(sym_link_path, target_path, flags) == 0) {633 const SYMLINK_DATA = extern struct {
622 switch (kernel32.GetLastError()) {634 ReparseTag: ULONG,
623 else => |err| return unexpectedError(err),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
734pub const ReadLinkError = error{
735 FileNotFound,
736 AccessDenied,
737 Unexpected,
738 NameTooLong,
739 UnsupportedReparsePointType,
740 InvalidUtf8,
741 BadPathName,
742};
743
744pub 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
753pub 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
835fn 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}
627844
628pub const DeleteFileError = error{845pub const DeleteFileError = error{
...@@ -1239,23 +1456,30 @@ pub const PathSpace = struct {...@@ -1239,23 +1456,30 @@ pub const PathSpace = struct {
1239 }1456 }
1240};1457};
12411458
1459/// Same as `sliceToPrefixedFileW` but accepts a pointer
1460/// to a null-terminated path.
1242pub fn cStrToPrefixedFileW(s: [*:0]const u8) !PathSpace {1461pub fn cStrToPrefixedFileW(s: [*:0]const u8) !PathSpace {
1243 return sliceToPrefixedFileW(mem.spanZ(s));1462 return sliceToPrefixedFileW(mem.spanZ(s));
1244}1463}
12451464
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.
1246pub fn sliceToPrefixedFileW(s: []const u8) !PathSpace {1468pub fn sliceToPrefixedFileW(s: []const u8) !PathSpace {
1247 // TODO https://github.com/ziglang/zig/issues/27651469 // TODO https://github.com/ziglang/zig/issues/2765
1248 var path_space: PathSpace = undefined;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 switch (byte) {1474 switch (byte) {
1251 '*', '?', '"', '<', '>', '|' => return error.BadPathName,1475 '*', '?', '"', '<', '>', '|' => return error.BadPathName,
1252 else => {},1476 else => {},
1253 }1477 }
1254 }1478 }
1255 const start_index = if (mem.startsWith(u8, s, "\\?") or !std.fs.path.isAbsolute(s)) 0 else blk: {1479 const start_index = if (prefix_index > 0 or !std.fs.path.isAbsolute(s)) 0 else blk: {
1256 const prefix = [_]u16{ '\\', '?', '?', '\\' };1480 const prefix_u16 = [_]u16{ '\\', '?', '?', '\\' };
1257 mem.copy(u16, path_space.data[0..], &prefix);1481 mem.copy(u16, path_space.data[0..], prefix_u16[0..]);
1258 break :blk prefix.len;1482 break :blk prefix_u16.len;
1259 };1483 };
1260 path_space.len = start_index + try std.unicode.utf8ToUtf16Le(path_space.data[start_index..], s);1484 path_space.len = start_index + try std.unicode.utf8ToUtf16Le(path_space.data[start_index..], s);
1261 if (path_space.len > path_space.data.len) return error.NameTooLong;1485 if (path_space.len > path_space.data.len) return error.NameTooLong;
...@@ -1287,6 +1511,12 @@ pub fn wToPrefixedFileW(s: []const u16) !PathSpace {...@@ -1287,6 +1511,12 @@ pub fn wToPrefixedFileW(s: []const u16) !PathSpace {
1287 path_space.len = start_index + s.len;1511 path_space.len = start_index + s.len;
1288 if (path_space.len > path_space.data.len) return error.NameTooLong;1512 if (path_space.len > path_space.data.len) return error.NameTooLong;
1289 mem.copy(u16, path_space.data[start_index..], s);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 for (path_space.data[0..path_space.len]) |*elem| {1520 for (path_space.data[0..path_space.len]) |*elem| {
1291 if (elem.* == '/') {1521 if (elem.* == '/') {
1292 elem.* = '\\';1522 elem.* = '\\';
lib/std/os/windows/bits.zig+32-1
...@@ -488,7 +488,7 @@ pub const FILE_OPEN_BY_FILE_ID = 0x00002000;...@@ -488,7 +488,7 @@ pub const FILE_OPEN_BY_FILE_ID = 0x00002000;
488pub const FILE_OPEN_FOR_BACKUP_INTENT = 0x00004000;488pub const FILE_OPEN_FOR_BACKUP_INTENT = 0x00004000;
489pub const FILE_NO_COMPRESSION = 0x00008000;489pub const FILE_NO_COMPRESSION = 0x00008000;
490pub const FILE_RESERVE_OPFILTER = 0x00100000;490pub const FILE_RESERVE_OPFILTER = 0x00100000;
491pub const FILE_TRANSACTED_MODE = 0x00200000;491pub const FILE_OPEN_REPARSE_POINT = 0x00200000;
492pub const FILE_OPEN_OFFLINE_FILE = 0x00400000;492pub const FILE_OPEN_OFFLINE_FILE = 0x00400000;
493pub const FILE_OPEN_FOR_FREE_SPACE_QUERY = 0x00800000;493pub const FILE_OPEN_FOR_FREE_SPACE_QUERY = 0x00800000;
494494
...@@ -1542,3 +1542,34 @@ pub const POSVERSIONINFOW = *OSVERSIONINFOW;...@@ -1542,3 +1542,34 @@ pub const POSVERSIONINFOW = *OSVERSIONINFOW;
1542pub const LPOSVERSIONINFOW = *OSVERSIONINFOW;1542pub const LPOSVERSIONINFOW = *OSVERSIONINFOW;
1543pub const RTL_OSVERSIONINFOW = OSVERSIONINFOW;1543pub const RTL_OSVERSIONINFOW = OSVERSIONINFOW;
1544pub const PRTL_OSVERSIONINFOW = *RTL_OSVERSIONINFOW;1544pub const PRTL_OSVERSIONINFOW = *RTL_OSVERSIONINFOW;
1545
1546pub const REPARSE_DATA_BUFFER = extern struct {
1547 ReparseTag: ULONG,
1548 ReparseDataLength: USHORT,
1549 Reserved: USHORT,
1550 DataBuffer: [1]UCHAR,
1551};
1552pub 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};
1560pub const MOUNT_POINT_REPARSE_BUFFER = extern struct {
1561 SubstituteNameOffset: USHORT,
1562 SubstituteNameLength: USHORT,
1563 PrintNameOffset: USHORT,
1564 PrintNameLength: USHORT,
1565 PathBuffer: [1]WCHAR,
1566};
1567pub const MAXIMUM_REPARSE_DATA_BUFFER_SIZE: ULONG = 16 * 1024;
1568pub const FSCTL_SET_REPARSE_POINT: DWORD = 0x900a4;
1569pub const FSCTL_GET_REPARSE_POINT: DWORD = 0x900a8;
1570pub const IO_REPARSE_TAG_SYMLINK: ULONG = 0xa000000c;
1571pub const IO_REPARSE_TAG_MOUNT_POINT: ULONG = 0xa0000003;
1572pub const SYMLINK_FLAG_RELATIVE: ULONG = 0x1;
1573
1574pub const SYMBOLIC_LINK_FLAG_DIRECTORY: DWORD = 0x1;
1575pub const SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE: DWORD = 0x2;
lib/std/zig/system.zig+6
...@@ -552,6 +552,9 @@ pub const NativeTargetInfo = struct {...@@ -552,6 +552,9 @@ pub const NativeTargetInfo = struct {
552 error.SystemResources => return error.SystemResources,552 error.SystemResources => return error.SystemResources,
553 error.NotDir => return error.GnuLibCVersionUnavailable,553 error.NotDir => return error.GnuLibCVersionUnavailable,
554 error.Unexpected => return error.GnuLibCVersionUnavailable,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 return glibcVerFromLinkName(link_name);559 return glibcVerFromLinkName(link_name);
557 }560 }
...@@ -817,6 +820,9 @@ pub const NativeTargetInfo = struct {...@@ -817,6 +820,9 @@ pub const NativeTargetInfo = struct {
817 &link_buf,820 &link_buf,
818 ) catch |err| switch (err) {821 ) catch |err| switch (err) {
819 error.NameTooLong => unreachable,822 error.NameTooLong => unreachable,
823 error.InvalidUtf8 => unreachable, // Windows only
824 error.BadPathName => unreachable, // Windows only
825 error.UnsupportedReparsePointType => unreachable, // Windows only
820826
821 error.AccessDenied,827 error.AccessDenied,
822 error.FileNotFound,828 error.FileNotFound,