aboutsummaryrefslogtreecommitdiff
path: root/lib/std
diff options
context:
space:
mode:
authorJakub Konka <kubkon@jakubkonka.com>2020-07-19 22:25:00 +0200
committerJakub Konka <kubkon@jakubkonka.com>2020-07-22 08:51:22 +0200
commite0b77a6b77614538d18b9f0b9e3e7e434ccee4ff (patch)
tree04b40728d9b672ef160431fe550e389863ce666d /lib/std
parent3c8ceb674eb00ac0a70d6a0742234ce520eccf06 (diff)
downloadzig-e0b77a6b77614538d18b9f0b9e3e7e434ccee4ff.tar.gz
zig-e0b77a6b77614538d18b9f0b9e3e7e434ccee4ff.zip
Ensure Dir.deleteTree does not dereference symlinks
Otherwise, the behaviour can lead to unexpected results, resulting in removing an entire tree that's not necessarily under the root. Furthermore, this change is needed if are to properly handle dir symlinks on Windows. Without explicitly requiring that a directory or file is opened with `FILE_OPEN_REPARSE_POINT`, Windows automatically dereferences all symlinks along the way. This commit adds another option to `OpenDirOptions`, namely `.no_follow`, which defaults to `false` and can be used to specifically open a directory symlink on Windows or call `openat` with `O_NOFOLLOW` flag in POSIX.
Diffstat (limited to 'lib/std')
-rw-r--r--lib/std/fs.zig27
-rw-r--r--lib/std/fs/test.zig6
-rw-r--r--lib/std/os.zig4
-rw-r--r--lib/std/os/windows.zig10
4 files changed, 25 insertions, 22 deletions
diff --git a/lib/std/fs.zig b/lib/std/fs.zig
index 3cdb163f38..a22b15b0fb 100644
--- a/lib/std/fs.zig
+++ b/lib/std/fs.zig
@@ -951,6 +951,9 @@ pub const Dir = struct {
/// `true` means the opened directory can be scanned for the files and sub-directories
/// of the result. It means the `iterate` function can be called.
iterate: bool = false,
+
+ /// `true` means it won't dereference the symlink.
+ no_follow: bool = false,
};
/// Opens a directory at the given path. The directory is a system resource that remains
@@ -994,10 +997,11 @@ pub const Dir = struct {
w.RIGHT_PATH_REMOVE_DIRECTORY |
w.RIGHT_PATH_UNLINK_FILE;
}
+ const symlink_flags: w.lookupflags_t = if (args.no_follow) 0x0 else w.LOOKUP_SYMLINK_FOLLOW;
// TODO do we really need all the rights here?
const inheriting: w.rights_t = w.RIGHT_ALL ^ w.RIGHT_SOCK_SHUTDOWN;
- const result = os.openatWasi(self.fd, sub_path, w.O_DIRECTORY, 0x0, base, inheriting);
+ const result = os.openatWasi(self.fd, sub_path, w.O_DIRECTORY, symlink_flags, base, inheriting);
const fd = result catch |err| switch (err) {
error.FileTooBig => unreachable, // can't happen for directories
error.IsDir => unreachable, // we're providing O_DIRECTORY
@@ -1014,11 +1018,13 @@ pub const Dir = struct {
if (builtin.os.tag == .windows) {
const sub_path_w = try os.windows.cStrToPrefixedFileW(sub_path_c);
return self.openDirW(sub_path_w.span().ptr, args);
- } else if (!args.iterate) {
+ }
+ const symlink_flags: u32 = if (args.no_follow) os.O_NOFOLLOW else 0x0;
+ if (!args.iterate) {
const O_PATH = if (@hasDecl(os, "O_PATH")) os.O_PATH else 0;
- return self.openDirFlagsZ(sub_path_c, os.O_DIRECTORY | os.O_RDONLY | os.O_CLOEXEC | O_PATH);
+ return self.openDirFlagsZ(sub_path_c, os.O_DIRECTORY | os.O_RDONLY | os.O_CLOEXEC | O_PATH | symlink_flags);
} else {
- return self.openDirFlagsZ(sub_path_c, os.O_DIRECTORY | os.O_RDONLY | os.O_CLOEXEC);
+ return self.openDirFlagsZ(sub_path_c, os.O_DIRECTORY | os.O_RDONLY | os.O_CLOEXEC | symlink_flags);
}
}
@@ -1030,7 +1036,7 @@ pub const Dir = struct {
const base_flags = w.STANDARD_RIGHTS_READ | w.FILE_READ_ATTRIBUTES | w.FILE_READ_EA |
w.SYNCHRONIZE | w.FILE_TRAVERSE;
const flags: u32 = if (args.iterate) base_flags | w.FILE_LIST_DIRECTORY else base_flags;
- return self.openDirAccessMaskW(sub_path_w, flags);
+ return self.openDirAccessMaskW(sub_path_w, flags, args.no_follow);
}
/// `flags` must contain `os.O_DIRECTORY`.
@@ -1050,7 +1056,7 @@ pub const Dir = struct {
return Dir{ .fd = fd };
}
- fn openDirAccessMaskW(self: Dir, sub_path_w: [*:0]const u16, access_mask: u32) OpenError!Dir {
+ fn openDirAccessMaskW(self: Dir, sub_path_w: [*:0]const u16, access_mask: u32, no_follow: bool) OpenError!Dir {
const w = os.windows;
var result = Dir{
@@ -1080,6 +1086,7 @@ pub const Dir = struct {
// implement this: https://git.midipix.org/ntapi/tree/src/fs/ntapi_tt_open_physical_parent_directory.c
@panic("TODO opening '..' with a relative directory handle is not yet implemented on Windows");
}
+ const open_reparse_point: w.DWORD = if (no_follow) w.FILE_OPEN_REPARSE_POINT else 0x0;
var io: w.IO_STATUS_BLOCK = undefined;
const rc = w.ntdll.NtCreateFile(
&result.fd,
@@ -1090,7 +1097,7 @@ pub const Dir = struct {
0,
w.FILE_SHARE_READ | w.FILE_SHARE_WRITE,
w.FILE_OPEN,
- w.FILE_DIRECTORY_FILE | w.FILE_SYNCHRONOUS_IO_NONALERT | w.FILE_OPEN_FOR_BACKUP_INTENT,
+ w.FILE_DIRECTORY_FILE | w.FILE_SYNCHRONOUS_IO_NONALERT | w.FILE_OPEN_FOR_BACKUP_INTENT | open_reparse_point,
null,
0,
);
@@ -1277,6 +1284,7 @@ pub const Dir = struct {
pub fn deleteTree(self: Dir, sub_path: []const u8) DeleteTreeError!void {
start_over: while (true) {
var got_access_denied = false;
+
// First, try deleting the item as a file. This way we don't follow sym links.
if (self.deleteFile(sub_path)) {
return;
@@ -1297,7 +1305,8 @@ pub const Dir = struct {
error.Unexpected,
=> |e| return e,
}
- var dir = self.openDir(sub_path, .{ .iterate = true }) catch |err| switch (err) {
+
+ var dir = self.openDir(sub_path, .{ .iterate = true, .no_follow = true }) catch |err| switch (err) {
error.NotDir => {
if (got_access_denied) {
return error.AccessDenied;
@@ -1364,7 +1373,7 @@ pub const Dir = struct {
=> |e| return e,
}
- const new_dir = dir.openDir(entry.name, .{ .iterate = true }) catch |err| switch (err) {
+ const new_dir = dir.openDir(entry.name, .{ .iterate = true, .no_follow = true }) catch |err| switch (err) {
error.NotDir => {
if (got_access_denied) {
return error.AccessDenied;
diff --git a/lib/std/fs/test.zig b/lib/std/fs/test.zig
index c20cb2e62f..4fecf3c9c5 100644
--- a/lib/std/fs/test.zig
+++ b/lib/std/fs/test.zig
@@ -36,7 +36,7 @@ test "readLinkAbsolute" {
// Create symbolic link by path
try fs.symLinkAbsolute(target_path, symlink_path, .{});
- try testReadlinkAbsolute(target_path, symlink_path);
+ try testReadLinkAbsolute(target_path, symlink_path);
}
{
const target_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "subdir" });
@@ -44,11 +44,11 @@ test "readLinkAbsolute" {
// Create symbolic link by path
try fs.symLinkAbsolute(target_path, symlink_path, .{ .is_directory = true });
- try testReadlinkAbsolute(target_path, symlink_path);
+ try testReadLinkAbsolute(target_path, symlink_path);
}
}
-fn testReadlinkAbsolute(target_path: []const u8, symlink_path: []const u8) !void {
+fn testReadLinkAbsolute(target_path: []const u8, symlink_path: []const u8) !void {
var buffer: [fs.MAX_PATH_BYTES]u8 = undefined;
const given = try fs.readLinkAbsolute(symlink_path, buffer[0..]);
testing.expect(mem.eql(u8, target_path, given));
diff --git a/lib/std/os.zig b/lib/std/os.zig
index ebe457fe4d..86a5a7ad08 100644
--- a/lib/std/os.zig
+++ b/lib/std/os.zig
@@ -1815,7 +1815,7 @@ pub fn unlinkatW(dirfd: fd_t, sub_path_w: [*:0]const u16, flags: u32) UnlinkatEr
const want_rmdir_behavior = (flags & AT_REMOVEDIR) != 0;
const create_options_flags = if (want_rmdir_behavior)
- @as(w.ULONG, w.FILE_DELETE_ON_CLOSE | w.FILE_DIRECTORY_FILE)
+ @as(w.ULONG, w.FILE_DELETE_ON_CLOSE | w.FILE_DIRECTORY_FILE | w.FILE_OPEN_REPARSE_POINT)
else
@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?
@@ -2390,9 +2390,11 @@ pub fn readlinkW(file_path: [*:0]const u16, out_buffer: []u8) ReadLinkError![]u8
else => |e| return e,
}
};
+ defer w.CloseHandle(handle);
var reparse_buf: [w.MAXIMUM_REPARSE_DATA_BUFFER_SIZE]u8 = undefined;
_ = try w.DeviceIoControl(handle, w.FSCTL_GET_REPARSE_POINT, null, reparse_buf[0..], null);
+
const reparse_struct = @ptrCast(*const w.REPARSE_DATA_BUFFER, @alignCast(@alignOf(w.REPARSE_DATA_BUFFER), &reparse_buf[0]));
switch (reparse_struct.ReparseTag) {
w.IO_REPARSE_TAG_SYMLINK => {
diff --git a/lib/std/os/windows.zig b/lib/std/os/windows.zig
index 7835742e39..3895e5d80a 100644
--- a/lib/std/os/windows.zig
+++ b/lib/std/os/windows.zig
@@ -602,15 +602,7 @@ pub fn GetCurrentDirectory(buffer: []u8) GetCurrentDirectoryError![]u8 {
return buffer[0..end_index];
}
-pub const CreateSymbolicLinkError = error{
- AccessDenied,
- PathAlreadyExists,
- FileNotFound,
- NameTooLong,
- InvalidUtf8,
- BadPathName,
- Unexpected
-};
+pub const CreateSymbolicLinkError = error{ AccessDenied, PathAlreadyExists, FileNotFound, NameTooLong, InvalidUtf8, BadPathName, Unexpected };
pub fn CreateSymbolicLink(
sym_link_path: []const u8,