mirror of
https://github.com/ziglang/zig.git
synced 2026-02-13 04:48:20 +00:00
Merge pull request #5879 from kubkon/readlink-win
Implement readlink on Windows
This commit is contained in:
commit
9505bb74cd
177
lib/std/fs.zig
177
lib/std/fs.zig
@ -16,9 +16,6 @@ pub const wasi = @import("fs/wasi.zig");
|
||||
|
||||
// TODO audit these APIs with respect to Dir and absolute paths
|
||||
|
||||
pub const symLink = os.symlink;
|
||||
pub const symLinkZ = os.symlinkZ;
|
||||
pub const symLinkC = @compileError("deprecated: renamed to symlinkZ");
|
||||
pub const rename = os.rename;
|
||||
pub const renameZ = os.renameZ;
|
||||
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) {
|
||||
|
||||
/// TODO remove the allocator requirement from this API
|
||||
pub fn atomicSymLink(allocator: *Allocator, existing_path: []const u8, new_path: []const u8) !void {
|
||||
if (symLink(existing_path, new_path)) {
|
||||
if (cwd().symLink(existing_path, new_path, .{})) {
|
||||
return;
|
||||
} else |err| switch (err) {
|
||||
error.PathAlreadyExists => {},
|
||||
@ -87,7 +84,7 @@ pub fn atomicSymLink(allocator: *Allocator, existing_path: []const u8, new_path:
|
||||
try crypto.randomBytes(rand_buf[0..]);
|
||||
base64_encoder.encode(tmp_path[dirname.len + 1 ..], &rand_buf);
|
||||
|
||||
if (symLink(existing_path, tmp_path)) {
|
||||
if (cwd().symLink(existing_path, new_path, .{})) {
|
||||
return rename(tmp_path, new_path);
|
||||
} else |err| switch (err) {
|
||||
error.PathAlreadyExists => continue,
|
||||
@ -672,7 +669,7 @@ pub const Dir = struct {
|
||||
w.RIGHT_FD_FILESTAT_SET_TIMES |
|
||||
w.RIGHT_FD_FILESTAT_SET_SIZE;
|
||||
}
|
||||
const fd = try os.openatWasi(self.fd, sub_path, 0x0, fdflags, base, 0x0);
|
||||
const fd = try os.openatWasi(self.fd, sub_path, 0x0, 0x0, fdflags, base, 0x0);
|
||||
return File{ .handle = fd };
|
||||
}
|
||||
|
||||
@ -792,7 +789,7 @@ pub const Dir = struct {
|
||||
if (flags.exclusive) {
|
||||
oflags |= w.O_EXCL;
|
||||
}
|
||||
const fd = try os.openatWasi(self.fd, sub_path, oflags, 0x0, base, 0x0);
|
||||
const fd = try os.openatWasi(self.fd, sub_path, 0x0, oflags, 0x0, base, 0x0);
|
||||
return File{ .handle = fd };
|
||||
}
|
||||
|
||||
@ -954,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 symlinks.
|
||||
no_follow: bool = false,
|
||||
};
|
||||
|
||||
/// Opens a directory at the given path. The directory is a system resource that remains
|
||||
@ -997,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, symlink_flags, w.O_DIRECTORY, 0x0, 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
|
||||
@ -1017,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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1033,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`.
|
||||
@ -1053,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{
|
||||
@ -1083,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,
|
||||
@ -1093,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,
|
||||
);
|
||||
@ -1207,21 +1211,101 @@ pub const Dir = struct {
|
||||
};
|
||||
}
|
||||
|
||||
/// Creates a symbolic link named `sym_link_path` which contains the string `target_path`.
|
||||
/// A symbolic link (also known as a soft link) may point to an existing file or to a nonexistent
|
||||
/// one; the latter case is known as a dangling link.
|
||||
/// If `sym_link_path` exists, it will not be overwritten.
|
||||
pub fn symLink(
|
||||
self: Dir,
|
||||
target_path: []const u8,
|
||||
sym_link_path: []const u8,
|
||||
flags: SymLinkFlags,
|
||||
) !void {
|
||||
if (builtin.os.tag == .wasi) {
|
||||
return self.symLinkWasi(target_path, sym_link_path, flags);
|
||||
}
|
||||
if (builtin.os.tag == .windows) {
|
||||
const target_path_w = try os.windows.sliceToPrefixedFileW(target_path);
|
||||
const sym_link_path_w = try os.windows.sliceToPrefixedFileW(sym_link_path);
|
||||
return self.symLinkW(target_path_w.span(), sym_link_path_w.span(), flags);
|
||||
}
|
||||
const target_path_c = try os.toPosixPath(target_path);
|
||||
const sym_link_path_c = try os.toPosixPath(sym_link_path);
|
||||
return self.symLinkZ(&target_path_c, &sym_link_path_c, flags);
|
||||
}
|
||||
|
||||
/// WASI-only. Same as `symLink` except targeting WASI.
|
||||
pub fn symLinkWasi(
|
||||
self: Dir,
|
||||
target_path: []const u8,
|
||||
sym_link_path: []const u8,
|
||||
flags: SymLinkFlags,
|
||||
) !void {
|
||||
return os.symlinkatWasi(target_path, self.fd, sym_link_path);
|
||||
}
|
||||
|
||||
/// Same as `symLink`, except the pathname parameters are null-terminated.
|
||||
pub fn symLinkZ(
|
||||
self: Dir,
|
||||
target_path_c: [*:0]const u8,
|
||||
sym_link_path_c: [*:0]const u8,
|
||||
flags: SymLinkFlags,
|
||||
) !void {
|
||||
if (builtin.os.tag == .windows) {
|
||||
const target_path_w = try os.windows.cStrToPrefixedFileW(target_path_c);
|
||||
const sym_link_path_w = try os.windows.cStrToPrefixedFileW(sym_link_path_c);
|
||||
return self.symLinkW(target_path_w.span(), sym_link_path_w.span(), flags);
|
||||
}
|
||||
return os.symlinkatZ(target_path_c, self.fd, sym_link_path_c);
|
||||
}
|
||||
|
||||
/// Windows-only. Same as `symLink` except the pathname parameters
|
||||
/// are null-terminated, WTF16 encoded.
|
||||
pub fn symLinkW(
|
||||
self: Dir,
|
||||
target_path_w: [:0]const u16,
|
||||
sym_link_path_w: [:0]const u16,
|
||||
flags: SymLinkFlags,
|
||||
) !void {
|
||||
return os.windows.CreateSymbolicLinkW(self.fd, sym_link_path_w, target_path_w, flags.is_directory);
|
||||
}
|
||||
|
||||
/// Read value of a symbolic link.
|
||||
/// The return value is a slice of `buffer`, from index `0`.
|
||||
/// Asserts that the path parameter has no null bytes.
|
||||
pub fn readLink(self: Dir, sub_path: []const u8, buffer: []u8) ![]u8 {
|
||||
if (builtin.os.tag == .wasi) {
|
||||
return self.readLinkWasi(sub_path, buffer);
|
||||
}
|
||||
if (builtin.os.tag == .windows) {
|
||||
return os.windows.ReadLink(self.fd, sub_path, buffer);
|
||||
}
|
||||
const sub_path_c = try os.toPosixPath(sub_path);
|
||||
return self.readLinkZ(&sub_path_c, buffer);
|
||||
}
|
||||
|
||||
pub const readLinkC = @compileError("deprecated: renamed to readLinkZ");
|
||||
|
||||
/// WASI-only. Same as `readLink` except targeting WASI.
|
||||
pub fn readLinkWasi(self: Dir, sub_path: []const u8, buffer: []u8) ![]u8 {
|
||||
return os.readlinkatWasi(self.fd, sub_path, buffer);
|
||||
}
|
||||
|
||||
/// Same as `readLink`, except the `pathname` parameter is null-terminated.
|
||||
pub fn readLinkZ(self: Dir, sub_path_c: [*:0]const u8, buffer: []u8) ![]u8 {
|
||||
if (builtin.os.tag == .windows) {
|
||||
const sub_path_w = try os.windows.cStrToPrefixedFileW(sub_path_c);
|
||||
return self.readLinkW(sub_path_w, buffer);
|
||||
}
|
||||
return os.readlinkatZ(self.fd, sub_path_c, buffer);
|
||||
}
|
||||
|
||||
/// Windows-only. Same as `readLink` except the pathname parameter
|
||||
/// is null-terminated, WTF16 encoded.
|
||||
pub fn readLinkW(self: Dir, sub_path_w: [*:0]const u16, buffer: []u8) ![]u8 {
|
||||
return os.windows.ReadLinkW(self.fd, sub_path_w, buffer);
|
||||
}
|
||||
|
||||
/// On success, caller owns returned buffer.
|
||||
/// If the file is larger than `max_bytes`, returns `error.FileTooBig`.
|
||||
pub fn readFileAlloc(self: Dir, allocator: *mem.Allocator, file_path: []const u8, max_bytes: usize) ![]u8 {
|
||||
@ -1280,6 +1364,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;
|
||||
@ -1300,7 +1385,7 @@ 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;
|
||||
@ -1367,7 +1452,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;
|
||||
@ -1692,8 +1777,15 @@ pub fn readLinkAbsolute(pathname: []const u8, buffer: *[MAX_PATH_BYTES]u8) ![]u8
|
||||
return os.readlink(pathname, buffer);
|
||||
}
|
||||
|
||||
/// Windows-only. Same as `readlinkW`, except the path parameter is null-terminated, WTF16
|
||||
/// encoded.
|
||||
pub fn readlinkAbsoluteW(pathname_w: [*:0]const u16, buffer: *[MAX_PATH_BYTES]u8) ![]u8 {
|
||||
assert(path.isAbsoluteWindowsW(pathname_w));
|
||||
return os.readlinkW(pathname_w, buffer);
|
||||
}
|
||||
|
||||
/// Same as `readLink`, except the path parameter is null-terminated.
|
||||
pub fn readLinkAbsoluteZ(pathname_c: [*]const u8, buffer: *[MAX_PATH_BYTES]u8) ![]u8 {
|
||||
pub fn readLinkAbsoluteZ(pathname_c: [*:0]const u8, buffer: *[MAX_PATH_BYTES]u8) ![]u8 {
|
||||
assert(path.isAbsoluteZ(pathname_c));
|
||||
return os.readlinkZ(pathname_c, buffer);
|
||||
}
|
||||
@ -1701,6 +1793,57 @@ pub fn readLinkAbsoluteZ(pathname_c: [*]const u8, buffer: *[MAX_PATH_BYTES]u8) !
|
||||
pub const readLink = @compileError("deprecated; use Dir.readLink or readLinkAbsolute");
|
||||
pub const readLinkC = @compileError("deprecated; use Dir.readLinkZ or readLinkAbsoluteZ");
|
||||
|
||||
/// Use with `Dir.symLink` and `symLinkAbsolute` to specify whether the symlink
|
||||
/// will point to a file or a directory. This value is ignored on all hosts
|
||||
/// except Windows where creating symlinks to different resource types, requires
|
||||
/// different flags. By default, `symLinkAbsolute` is assumed to point to a file.
|
||||
pub const SymLinkFlags = struct {
|
||||
is_directory: bool = false,
|
||||
};
|
||||
|
||||
/// Creates a symbolic link named `sym_link_path` which contains the string `target_path`.
|
||||
/// A symbolic link (also known as a soft link) may point to an existing file or to a nonexistent
|
||||
/// one; the latter case is known as a dangling link.
|
||||
/// If `sym_link_path` exists, it will not be overwritten.
|
||||
/// See also `symLinkAbsoluteZ` and `symLinkAbsoluteW`.
|
||||
pub fn symLinkAbsolute(target_path: []const u8, sym_link_path: []const u8, flags: SymLinkFlags) !void {
|
||||
if (builtin.os.tag == .wasi) {
|
||||
@compileError("symLinkAbsolute is not supported in WASI; use Dir.symLinkWasi instead");
|
||||
}
|
||||
assert(path.isAbsolute(target_path));
|
||||
assert(path.isAbsolute(sym_link_path));
|
||||
if (builtin.os.tag == .windows) {
|
||||
return os.windows.CreateSymbolicLink(null, sym_link_path, target_path, flags.is_directory);
|
||||
}
|
||||
return os.symlink(target_path, sym_link_path);
|
||||
}
|
||||
|
||||
/// Windows-only. Same as `symLinkAbsolute` except the parameters are null-terminated, WTF16 encoded.
|
||||
/// Note that this function will by default try creating a symbolic link to a file. If you would
|
||||
/// like to create a symbolic link to a directory, specify this with `SymLinkFlags{ .is_directory = true }`.
|
||||
/// See also `symLinkAbsolute`, `symLinkAbsoluteZ`.
|
||||
pub fn symLinkAbsoluteW(target_path_w: [:0]const u16, sym_link_path_w: [:0]const u16, flags: SymLinkFlags) !void {
|
||||
assert(path.isAbsoluteWindowsW(target_path_w));
|
||||
assert(path.isAbsoluteWindowsW(sym_link_path_w));
|
||||
return os.windows.CreateSymbolicLinkW(null, sym_link_path_w, target_path_w, flags.is_directory);
|
||||
}
|
||||
|
||||
/// Same as `symLinkAbsolute` except the parameters are null-terminated pointers.
|
||||
/// See also `symLinkAbsolute`.
|
||||
pub fn symLinkAbsoluteZ(target_path_c: [*:0]const u8, sym_link_path_c: [*:0]const u8, flags: SymLinkFlags) !void {
|
||||
assert(path.isAbsoluteZ(target_path_c));
|
||||
assert(path.isAbsoluteZ(sym_link_path_c));
|
||||
if (builtin.os.tag == .windows) {
|
||||
const target_path_w = try os.windows.cStrToWin32PrefixedFileW(target_path_c);
|
||||
const sym_link_path_w = try os.windows.cStrToWin32PrefixedFileW(sym_link_path_c);
|
||||
return os.windows.CreateSymbolicLinkW(sym_link_path_w.span().ptr, target_path_w.span().ptr, flags.is_directory);
|
||||
}
|
||||
return os.symlinkZ(target_path_c, sym_link_path_c);
|
||||
}
|
||||
|
||||
pub const symLink = @compileError("deprecated: use Dir.symLink or symLinkAbsolute");
|
||||
pub const symLinkC = @compileError("deprecated: use Dir.symLinkZ or symLinkAbsoluteZ");
|
||||
|
||||
pub const Walker = struct {
|
||||
stack: std.ArrayList(StackItem),
|
||||
name_buffer: std.ArrayList(u8),
|
||||
|
||||
@ -10,6 +10,76 @@ const Dir = std.fs.Dir;
|
||||
const File = std.fs.File;
|
||||
const tmpDir = testing.tmpDir;
|
||||
|
||||
test "Dir.readLink" {
|
||||
var tmp = tmpDir(.{});
|
||||
defer tmp.cleanup();
|
||||
|
||||
// Create some targets
|
||||
try tmp.dir.writeFile("file.txt", "nonsense");
|
||||
try tmp.dir.makeDir("subdir");
|
||||
|
||||
{
|
||||
// Create symbolic link by path
|
||||
try tmp.dir.symLink("file.txt", "symlink1", .{});
|
||||
try testReadLink(tmp.dir, "file.txt", "symlink1");
|
||||
}
|
||||
{
|
||||
// Create symbolic link by path
|
||||
try tmp.dir.symLink("subdir", "symlink2", .{ .is_directory = true });
|
||||
try testReadLink(tmp.dir, "subdir", "symlink2");
|
||||
}
|
||||
}
|
||||
|
||||
fn testReadLink(dir: Dir, target_path: []const u8, symlink_path: []const u8) !void {
|
||||
var buffer: [fs.MAX_PATH_BYTES]u8 = undefined;
|
||||
const given = try dir.readLink(symlink_path, buffer[0..]);
|
||||
testing.expect(mem.eql(u8, target_path, given));
|
||||
}
|
||||
|
||||
test "readLinkAbsolute" {
|
||||
if (builtin.os.tag == .wasi) return error.SkipZigTest;
|
||||
|
||||
var tmp = tmpDir(.{});
|
||||
defer tmp.cleanup();
|
||||
|
||||
// Create some targets
|
||||
try tmp.dir.writeFile("file.txt", "nonsense");
|
||||
try tmp.dir.makeDir("subdir");
|
||||
|
||||
// Get base abs path
|
||||
var arena = ArenaAllocator.init(testing.allocator);
|
||||
defer arena.deinit();
|
||||
|
||||
const base_path = blk: {
|
||||
const relative_path = try fs.path.join(&arena.allocator, &[_][]const u8{ "zig-cache", "tmp", tmp.sub_path[0..] });
|
||||
break :blk try fs.realpathAlloc(&arena.allocator, relative_path);
|
||||
};
|
||||
const allocator = &arena.allocator;
|
||||
|
||||
{
|
||||
const target_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "file.txt" });
|
||||
const symlink_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "symlink1" });
|
||||
|
||||
// Create symbolic link by path
|
||||
try fs.symLinkAbsolute(target_path, symlink_path, .{});
|
||||
try testReadLinkAbsolute(target_path, symlink_path);
|
||||
}
|
||||
{
|
||||
const target_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "subdir" });
|
||||
const symlink_path = try fs.path.join(allocator, &[_][]const u8{ base_path, "symlink2" });
|
||||
|
||||
// Create symbolic link by path
|
||||
try fs.symLinkAbsolute(target_path, symlink_path, .{ .is_directory = true });
|
||||
try testReadLinkAbsolute(target_path, symlink_path);
|
||||
}
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
test "Dir.Iterator" {
|
||||
var tmp_dir = tmpDir(.{ .iterate = true });
|
||||
defer tmp_dir.cleanup();
|
||||
|
||||
@ -1109,10 +1109,10 @@ pub fn openat(dir_fd: fd_t, file_path: []const u8, flags: u32, mode: mode_t) Ope
|
||||
}
|
||||
|
||||
/// Open and possibly create a file in WASI.
|
||||
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 {
|
||||
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 {
|
||||
while (true) {
|
||||
var fd: fd_t = undefined;
|
||||
switch (wasi.path_open(dir_fd, 0x0, file_path.ptr, file_path.len, oflags, base, inheriting, fdflags, &fd)) {
|
||||
switch (wasi.path_open(dir_fd, lookup_flags, file_path.ptr, file_path.len, oflags, base, inheriting, fdflags, &fd)) {
|
||||
wasi.ESUCCESS => return fd,
|
||||
wasi.EINTR => continue,
|
||||
|
||||
@ -1542,15 +1542,13 @@ pub const SymLinkError = error{
|
||||
/// A symbolic link (also known as a soft link) may point to an existing file or to a nonexistent
|
||||
/// one; the latter case is known as a dangling link.
|
||||
/// If `sym_link_path` exists, it will not be overwritten.
|
||||
/// See also `symlinkC` and `symlinkW`.
|
||||
/// See also `symlinkZ.
|
||||
pub fn symlink(target_path: []const u8, sym_link_path: []const u8) SymLinkError!void {
|
||||
if (builtin.os.tag == .wasi) {
|
||||
@compileError("symlink is not supported in WASI; use symlinkat instead");
|
||||
}
|
||||
if (builtin.os.tag == .windows) {
|
||||
const target_path_w = try windows.sliceToPrefixedFileW(target_path);
|
||||
const sym_link_path_w = try windows.sliceToPrefixedFileW(sym_link_path);
|
||||
return windows.CreateSymbolicLinkW(sym_link_path_w.span().ptr, target_path_w.span().ptr, 0);
|
||||
@compileError("symlink is not supported on Windows; use std.os.windows.CreateSymbolicLink instead");
|
||||
}
|
||||
const target_path_c = try toPosixPath(target_path);
|
||||
const sym_link_path_c = try toPosixPath(sym_link_path);
|
||||
@ -1563,9 +1561,7 @@ pub const symlinkC = @compileError("deprecated: renamed to symlinkZ");
|
||||
/// See also `symlink`.
|
||||
pub fn symlinkZ(target_path: [*:0]const u8, sym_link_path: [*:0]const u8) SymLinkError!void {
|
||||
if (builtin.os.tag == .windows) {
|
||||
const target_path_w = try windows.cStrToPrefixedFileW(target_path);
|
||||
const sym_link_path_w = try windows.cStrToPrefixedFileW(sym_link_path);
|
||||
return windows.CreateSymbolicLinkW(sym_link_path_w.span().ptr, target_path_w.span().ptr, 0);
|
||||
@compileError("symlink is not supported on Windows; use std.os.windows.CreateSymbolicLink instead");
|
||||
}
|
||||
switch (errno(system.symlink(target_path, sym_link_path))) {
|
||||
0 => return,
|
||||
@ -1598,9 +1594,7 @@ pub fn symlinkat(target_path: []const u8, newdirfd: fd_t, sym_link_path: []const
|
||||
return symlinkatWasi(target_path, newdirfd, sym_link_path);
|
||||
}
|
||||
if (builtin.os.tag == .windows) {
|
||||
const target_path_w = try windows.sliceToPrefixedFileW(target_path);
|
||||
const sym_link_path_w = try windows.sliceToPrefixedFileW(sym_link_path);
|
||||
return symlinkatW(target_path_w.span().ptr, newdirfd, sym_link_path_w.span().ptr);
|
||||
@compileError("symlinkat is not supported on Windows; use std.os.windows.CreateSymbolicLink instead");
|
||||
}
|
||||
const target_path_c = try toPosixPath(target_path);
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
/// Windows-only. The same as `symlinkat` except the paths are null-terminated, WTF-16 encoded.
|
||||
/// See also `symlinkat`.
|
||||
pub fn symlinkatW(target_path: [*:0]const u16, newdirfd: fd_t, sym_link_path: [*:0]const u16) SymLinkError!void {
|
||||
@compileError("TODO implement on Windows");
|
||||
}
|
||||
|
||||
/// The same as `symlinkat` except the parameters are null-terminated pointers.
|
||||
/// See also `symlinkat`.
|
||||
pub fn symlinkatZ(target_path: [*:0]const u8, newdirfd: fd_t, sym_link_path: [*:0]const u8) SymLinkError!void {
|
||||
if (builtin.os.tag == .windows) {
|
||||
const target_path_w = try windows.cStrToPrefixedFileW(target_path);
|
||||
const sym_link_path_w = try windows.cStrToPrefixedFileW(sym_link_path);
|
||||
return symlinkatW(target_path_w.span().ptr, newdirfd, sym_link_path.span().ptr);
|
||||
@compileError("symlinkat is not supported on Windows; use std.os.windows.CreateSymbolicLink instead");
|
||||
}
|
||||
switch (errno(system.symlinkat(target_path, newdirfd, sym_link_path))) {
|
||||
0 => return,
|
||||
@ -1819,9 +1805,9 @@ 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);
|
||||
@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?
|
||||
|
||||
const path_len_bytes = @intCast(u16, mem.lenZ(sub_path_w) * 2);
|
||||
var nt_name = w.UNICODE_STRING{
|
||||
@ -2355,6 +2341,11 @@ pub const ReadLinkError = error{
|
||||
FileNotFound,
|
||||
SystemResources,
|
||||
NotDir,
|
||||
InvalidUtf8,
|
||||
BadPathName,
|
||||
/// Windows-only. This error may occur if the opened reparse point is
|
||||
/// of unsupported type.
|
||||
UnsupportedReparsePointType,
|
||||
} || UnexpectedError;
|
||||
|
||||
/// Read value of a symbolic link.
|
||||
@ -2363,8 +2354,7 @@ pub fn readlink(file_path: []const u8, out_buffer: []u8) ReadLinkError![]u8 {
|
||||
if (builtin.os.tag == .wasi) {
|
||||
@compileError("readlink is not supported in WASI; use readlinkat instead");
|
||||
} else if (builtin.os.tag == .windows) {
|
||||
const file_path_w = try windows.sliceToPrefixedFileW(file_path);
|
||||
return readlinkW(file_path_w.span().ptr, out_buffer);
|
||||
return windows.ReadLink(std.fs.cwd().fd, file_path, out_buffer);
|
||||
} else {
|
||||
const file_path_c = try toPosixPath(file_path);
|
||||
return readlinkZ(&file_path_c, out_buffer);
|
||||
@ -2373,16 +2363,16 @@ pub fn readlink(file_path: []const u8, out_buffer: []u8) ReadLinkError![]u8 {
|
||||
|
||||
pub const readlinkC = @compileError("deprecated: renamed to readlinkZ");
|
||||
|
||||
/// Windows-only. Same as `readlink` expecte `file_path` is null-terminated, WTF16 encoded.
|
||||
/// Seel also `readlinkZ`.
|
||||
/// Windows-only. Same as `readlink` except `file_path` is null-terminated, WTF16 encoded.
|
||||
/// See also `readlinkZ`.
|
||||
pub fn readlinkW(file_path: [*:0]const u16, out_buffer: []u8) ReadLinkError![]u8 {
|
||||
@compileError("TODO implement readlink for Windows");
|
||||
return windows.ReadLinkW(std.fs.cwd().fd, file_path, out_buffer);
|
||||
}
|
||||
|
||||
/// Same as `readlink` except `file_path` is null-terminated.
|
||||
pub fn readlinkZ(file_path: [*:0]const u8, out_buffer: []u8) ReadLinkError![]u8 {
|
||||
if (builtin.os.tag == .windows) {
|
||||
const file_path_w = try windows.cStrToPrefixedFileW(file_path);
|
||||
const file_path_w = try windows.cStrToWin32PrefixedFileW(file_path);
|
||||
return readlinkW(file_path_w.span().ptr, out_buffer);
|
||||
}
|
||||
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
|
||||
return readlinkatWasi(dirfd, file_path, out_buffer);
|
||||
}
|
||||
if (builtin.os.tag == .windows) {
|
||||
const file_path_w = try windows.cStrToPrefixedFileW(file_path);
|
||||
return readlinkatW(dirfd, file_path.span().ptr, out_buffer);
|
||||
return windows.ReadLink(dirfd, file_path, out_buffer);
|
||||
}
|
||||
const file_path_c = try toPosixPath(file_path);
|
||||
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
|
||||
/// Windows-only. Same as `readlinkat` except `file_path` is null-terminated, WTF16 encoded.
|
||||
/// See also `readlinkat`.
|
||||
pub fn readlinkatW(dirfd: fd_t, file_path: [*:0]const u16, out_buffer: []u8) ReadLinkError![]u8 {
|
||||
@compileError("TODO implement on Windows");
|
||||
return windows.ReadLinkW(dirfd, file_path, out_buffer);
|
||||
}
|
||||
|
||||
/// 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
|
||||
var procfs_buf: ["/proc/self/fd/-2147483648".len:0]u8 = undefined;
|
||||
const proc_path = std.fmt.bufPrint(procfs_buf[0..], "/proc/self/fd/{}\x00", .{fd}) catch unreachable;
|
||||
|
||||
return readlinkZ(@ptrCast([*:0]const u8, proc_path.ptr), out_buffer);
|
||||
const target = readlinkZ(@ptrCast([*:0]const u8, proc_path.ptr), out_buffer) catch |err| {
|
||||
switch (err) {
|
||||
error.UnsupportedReparsePointType => unreachable, // Windows only,
|
||||
else => |e| return e,
|
||||
}
|
||||
};
|
||||
return target;
|
||||
}
|
||||
const result_path = std.c.realpath(pathname, out_buffer) orelse switch (std.c._errno().*) {
|
||||
EINVAL => unreachable,
|
||||
|
||||
@ -17,6 +17,42 @@ const AtomicRmwOp = builtin.AtomicRmwOp;
|
||||
const AtomicOrder = builtin.AtomicOrder;
|
||||
const tmpDir = std.testing.tmpDir;
|
||||
const Dir = std.fs.Dir;
|
||||
const ArenaAllocator = std.heap.ArenaAllocator;
|
||||
|
||||
test "symlink with relative paths" {
|
||||
if (builtin.os.tag == .wasi) return error.SkipZigTest;
|
||||
|
||||
// First, try relative paths in cwd
|
||||
var cwd = fs.cwd();
|
||||
try cwd.writeFile("file.txt", "nonsense");
|
||||
|
||||
if (builtin.os.tag == .windows) {
|
||||
try os.windows.CreateSymbolicLink(cwd.fd, "symlinked", "file.txt", false);
|
||||
} else {
|
||||
try os.symlink("file.txt", "symlinked");
|
||||
}
|
||||
|
||||
var buffer: [fs.MAX_PATH_BYTES]u8 = undefined;
|
||||
const given = try os.readlink("symlinked", buffer[0..]);
|
||||
expect(mem.eql(u8, "file.txt", given));
|
||||
|
||||
try cwd.deleteFile("file.txt");
|
||||
try cwd.deleteFile("symlinked");
|
||||
}
|
||||
|
||||
test "readlink on Windows" {
|
||||
if (builtin.os.tag != .windows) return error.SkipZigTest;
|
||||
|
||||
try testReadlink("C:\\ProgramData", "C:\\Users\\All Users");
|
||||
try testReadlink("C:\\Users\\Default", "C:\\Users\\Default User");
|
||||
try testReadlink("C:\\Users", "C:\\Documents and Settings");
|
||||
}
|
||||
|
||||
fn testReadlink(target_path: []const u8, symlink_path: []const u8) !void {
|
||||
var buffer: [fs.MAX_PATH_BYTES]u8 = undefined;
|
||||
const given = try os.readlink(symlink_path, buffer[0..]);
|
||||
expect(mem.eql(u8, target_path, given));
|
||||
}
|
||||
|
||||
test "fstatat" {
|
||||
// enable when `fstat` and `fstatat` are implemented on Windows
|
||||
@ -41,9 +77,6 @@ test "fstatat" {
|
||||
}
|
||||
|
||||
test "readlinkat" {
|
||||
// enable when `readlinkat` and `symlinkat` are implemented on Windows
|
||||
if (builtin.os.tag == .windows) return error.SkipZigTest;
|
||||
|
||||
var tmp = tmpDir(.{});
|
||||
defer tmp.cleanup();
|
||||
|
||||
@ -51,7 +84,11 @@ test "readlinkat" {
|
||||
try tmp.dir.writeFile("file.txt", "nonsense");
|
||||
|
||||
// create a symbolic link
|
||||
try os.symlinkat("file.txt", tmp.dir.fd, "link");
|
||||
if (builtin.os.tag == .windows) {
|
||||
try os.windows.CreateSymbolicLink(tmp.dir.fd, "link", "file.txt", false);
|
||||
} else {
|
||||
try os.symlinkat("file.txt", tmp.dir.fd, "link");
|
||||
}
|
||||
|
||||
// read the link
|
||||
var buffer: [fs.MAX_PATH_BYTES]u8 = undefined;
|
||||
|
||||
@ -145,6 +145,7 @@ pub fn OpenFile(sub_path_w: []const u16, options: OpenFileOptions) OpenError!HAN
|
||||
|
||||
var delay: usize = 1;
|
||||
while (true) {
|
||||
var flags: ULONG = undefined;
|
||||
const blocking_flag: ULONG = if (options.io_mode == .blocking) FILE_SYNCHRONOUS_IO_NONALERT else 0;
|
||||
const rc = ntdll.NtCreateFile(
|
||||
&result,
|
||||
@ -601,28 +602,244 @@ pub fn GetCurrentDirectory(buffer: []u8) GetCurrentDirectoryError![]u8 {
|
||||
return buffer[0..end_index];
|
||||
}
|
||||
|
||||
pub const CreateSymbolicLinkError = error{Unexpected};
|
||||
pub const CreateSymbolicLinkError = error{
|
||||
AccessDenied,
|
||||
PathAlreadyExists,
|
||||
FileNotFound,
|
||||
NameTooLong,
|
||||
InvalidUtf8,
|
||||
BadPathName,
|
||||
NoDevice,
|
||||
Unexpected,
|
||||
};
|
||||
|
||||
pub fn CreateSymbolicLink(
|
||||
dir: ?HANDLE,
|
||||
sym_link_path: []const u8,
|
||||
target_path: []const u8,
|
||||
flags: DWORD,
|
||||
is_directory: bool,
|
||||
) CreateSymbolicLinkError!void {
|
||||
const sym_link_path_w = try sliceToPrefixedFileW(sym_link_path);
|
||||
const target_path_w = try sliceToPrefixedFileW(target_path);
|
||||
return CreateSymbolicLinkW(sym_link_path_w.span().ptr, target_path_w.span().ptr, flags);
|
||||
return CreateSymbolicLinkW(dir, sym_link_path_w.span(), target_path_w.span(), is_directory);
|
||||
}
|
||||
|
||||
pub fn CreateSymbolicLinkW(
|
||||
sym_link_path: [*:0]const u16,
|
||||
target_path: [*:0]const u16,
|
||||
flags: DWORD,
|
||||
dir: ?HANDLE,
|
||||
sym_link_path: [:0]const u16,
|
||||
target_path: [:0]const u16,
|
||||
is_directory: bool,
|
||||
) CreateSymbolicLinkError!void {
|
||||
if (kernel32.CreateSymbolicLinkW(sym_link_path, target_path, flags) == 0) {
|
||||
switch (kernel32.GetLastError()) {
|
||||
else => |err| return unexpectedError(err),
|
||||
const SYMLINK_DATA = extern struct {
|
||||
ReparseTag: ULONG,
|
||||
ReparseDataLength: USHORT,
|
||||
Reserved: USHORT,
|
||||
SubstituteNameOffset: USHORT,
|
||||
SubstituteNameLength: USHORT,
|
||||
PrintNameOffset: USHORT,
|
||||
PrintNameLength: USHORT,
|
||||
Flags: ULONG,
|
||||
};
|
||||
|
||||
var symlink_handle: HANDLE = undefined;
|
||||
if (is_directory) {
|
||||
const sym_link_len_bytes = math.cast(u16, sym_link_path.len * 2) catch |err| switch (err) {
|
||||
error.Overflow => return error.NameTooLong,
|
||||
};
|
||||
var nt_name = UNICODE_STRING{
|
||||
.Length = sym_link_len_bytes,
|
||||
.MaximumLength = sym_link_len_bytes,
|
||||
.Buffer = @intToPtr([*]u16, @ptrToInt(sym_link_path.ptr)),
|
||||
};
|
||||
|
||||
if (sym_link_path[0] == '.' and sym_link_path[1] == 0) {
|
||||
// Windows does not recognize this, but it does work with empty string.
|
||||
nt_name.Length = 0;
|
||||
}
|
||||
|
||||
var attr = OBJECT_ATTRIBUTES{
|
||||
.Length = @sizeOf(OBJECT_ATTRIBUTES),
|
||||
.RootDirectory = if (std.fs.path.isAbsoluteWindowsW(sym_link_path)) null else dir,
|
||||
.Attributes = 0, // Note we do not use OBJ_CASE_INSENSITIVE here.
|
||||
.ObjectName = &nt_name,
|
||||
.SecurityDescriptor = null,
|
||||
.SecurityQualityOfService = null,
|
||||
};
|
||||
|
||||
var io: IO_STATUS_BLOCK = undefined;
|
||||
const rc = ntdll.NtCreateFile(
|
||||
&symlink_handle,
|
||||
GENERIC_READ | SYNCHRONIZE | FILE_WRITE_ATTRIBUTES,
|
||||
&attr,
|
||||
&io,
|
||||
null,
|
||||
FILE_ATTRIBUTE_NORMAL,
|
||||
FILE_SHARE_READ,
|
||||
FILE_CREATE,
|
||||
FILE_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT | FILE_OPEN_FOR_BACKUP_INTENT,
|
||||
null,
|
||||
0,
|
||||
);
|
||||
switch (rc) {
|
||||
.SUCCESS => {},
|
||||
.OBJECT_NAME_INVALID => unreachable,
|
||||
.OBJECT_NAME_NOT_FOUND => return error.FileNotFound,
|
||||
.OBJECT_PATH_NOT_FOUND => return error.FileNotFound,
|
||||
.NO_MEDIA_IN_DEVICE => return error.NoDevice,
|
||||
.INVALID_PARAMETER => unreachable,
|
||||
.ACCESS_DENIED => return error.AccessDenied,
|
||||
.OBJECT_PATH_SYNTAX_BAD => unreachable,
|
||||
.OBJECT_NAME_COLLISION => return error.PathAlreadyExists,
|
||||
else => return unexpectedStatus(rc),
|
||||
}
|
||||
} else {
|
||||
symlink_handle = OpenFile(sym_link_path, .{
|
||||
.access_mask = SYNCHRONIZE | GENERIC_READ | GENERIC_WRITE,
|
||||
.dir = dir,
|
||||
.creation = FILE_CREATE,
|
||||
.io_mode = .blocking,
|
||||
}) catch |err| switch (err) {
|
||||
error.WouldBlock => unreachable,
|
||||
error.IsDir => return error.PathAlreadyExists,
|
||||
error.PipeBusy => unreachable,
|
||||
error.SharingViolation => return error.AccessDenied,
|
||||
else => |e| return e,
|
||||
};
|
||||
}
|
||||
defer CloseHandle(symlink_handle);
|
||||
|
||||
// prepare reparse data buffer
|
||||
var buffer: [MAXIMUM_REPARSE_DATA_BUFFER_SIZE]u8 = undefined;
|
||||
const buf_len = @sizeOf(SYMLINK_DATA) + target_path.len * 4;
|
||||
const header_len = @sizeOf(ULONG) + @sizeOf(USHORT) * 2;
|
||||
const symlink_data = SYMLINK_DATA{
|
||||
.ReparseTag = IO_REPARSE_TAG_SYMLINK,
|
||||
.ReparseDataLength = @intCast(u16, buf_len - header_len),
|
||||
.Reserved = 0,
|
||||
.SubstituteNameOffset = @intCast(u16, target_path.len * 2),
|
||||
.SubstituteNameLength = @intCast(u16, target_path.len * 2),
|
||||
.PrintNameOffset = 0,
|
||||
.PrintNameLength = @intCast(u16, target_path.len * 2),
|
||||
.Flags = if (dir) |_| SYMLINK_FLAG_RELATIVE else 0,
|
||||
};
|
||||
|
||||
std.mem.copy(u8, buffer[0..], std.mem.asBytes(&symlink_data));
|
||||
@memcpy(buffer[@sizeOf(SYMLINK_DATA)..], @ptrCast([*]const u8, target_path), target_path.len * 2);
|
||||
const paths_start = @sizeOf(SYMLINK_DATA) + target_path.len * 2;
|
||||
@memcpy(buffer[paths_start..].ptr, @ptrCast([*]const u8, target_path), target_path.len * 2);
|
||||
// TODO replace with NtDeviceIoControl
|
||||
_ = try DeviceIoControl(symlink_handle, FSCTL_SET_REPARSE_POINT, buffer[0..buf_len], null, null);
|
||||
}
|
||||
|
||||
pub const ReadLinkError = error{
|
||||
FileNotFound,
|
||||
AccessDenied,
|
||||
Unexpected,
|
||||
NameTooLong,
|
||||
UnsupportedReparsePointType,
|
||||
InvalidUtf8,
|
||||
BadPathName,
|
||||
};
|
||||
|
||||
pub fn ReadLink(
|
||||
dir: ?HANDLE,
|
||||
sub_path: []const u8,
|
||||
out_buffer: []u8,
|
||||
) ReadLinkError![]u8 {
|
||||
const sub_path_w = try sliceToPrefixedFileW(sub_path);
|
||||
return ReadLinkW(dir, sub_path_w.span().ptr, out_buffer);
|
||||
}
|
||||
|
||||
pub fn ReadLinkW(dir: ?HANDLE, sub_path_w: [*:0]const u16, out_buffer: []u8) ReadLinkError![]u8 {
|
||||
const path_len_bytes = math.cast(u16, mem.lenZ(sub_path_w) * 2) catch |err| switch (err) {
|
||||
error.Overflow => return error.NameTooLong,
|
||||
};
|
||||
var nt_name = UNICODE_STRING{
|
||||
.Length = path_len_bytes,
|
||||
.MaximumLength = path_len_bytes,
|
||||
.Buffer = @intToPtr([*]u16, @ptrToInt(sub_path_w)),
|
||||
};
|
||||
|
||||
if (sub_path_w[0] == '.' and sub_path_w[1] == 0) {
|
||||
// Windows does not recognize this, but it does work with empty string.
|
||||
nt_name.Length = 0;
|
||||
}
|
||||
|
||||
var attr = OBJECT_ATTRIBUTES{
|
||||
.Length = @sizeOf(OBJECT_ATTRIBUTES),
|
||||
.RootDirectory = if (std.fs.path.isAbsoluteWindowsW(sub_path_w)) null else dir,
|
||||
.Attributes = 0, // Note we do not use OBJ_CASE_INSENSITIVE here.
|
||||
.ObjectName = &nt_name,
|
||||
.SecurityDescriptor = null,
|
||||
.SecurityQualityOfService = null,
|
||||
};
|
||||
var io: IO_STATUS_BLOCK = undefined;
|
||||
var result_handle: HANDLE = undefined;
|
||||
const rc = ntdll.NtCreateFile(
|
||||
&result_handle,
|
||||
FILE_READ_ATTRIBUTES,
|
||||
&attr,
|
||||
&io,
|
||||
null,
|
||||
FILE_ATTRIBUTE_NORMAL,
|
||||
FILE_SHARE_READ,
|
||||
FILE_OPEN,
|
||||
FILE_OPEN_REPARSE_POINT,
|
||||
null,
|
||||
0,
|
||||
);
|
||||
switch (rc) {
|
||||
.SUCCESS => {},
|
||||
.OBJECT_NAME_INVALID => unreachable,
|
||||
.OBJECT_NAME_NOT_FOUND => return error.FileNotFound,
|
||||
.OBJECT_PATH_NOT_FOUND => return error.FileNotFound,
|
||||
.NO_MEDIA_IN_DEVICE => return error.FileNotFound,
|
||||
.INVALID_PARAMETER => unreachable,
|
||||
.SHARING_VIOLATION => return error.AccessDenied,
|
||||
.ACCESS_DENIED => return error.AccessDenied,
|
||||
.PIPE_BUSY => return error.AccessDenied,
|
||||
.OBJECT_PATH_SYNTAX_BAD => unreachable,
|
||||
.OBJECT_NAME_COLLISION => unreachable,
|
||||
.FILE_IS_A_DIRECTORY => unreachable,
|
||||
else => return unexpectedStatus(rc),
|
||||
}
|
||||
defer CloseHandle(result_handle);
|
||||
|
||||
var reparse_buf: [MAXIMUM_REPARSE_DATA_BUFFER_SIZE]u8 = undefined;
|
||||
_ = try DeviceIoControl(result_handle, FSCTL_GET_REPARSE_POINT, null, reparse_buf[0..], null);
|
||||
|
||||
const reparse_struct = @ptrCast(*const REPARSE_DATA_BUFFER, @alignCast(@alignOf(REPARSE_DATA_BUFFER), &reparse_buf[0]));
|
||||
switch (reparse_struct.ReparseTag) {
|
||||
IO_REPARSE_TAG_SYMLINK => {
|
||||
const buf = @ptrCast(*const SYMBOLIC_LINK_REPARSE_BUFFER, @alignCast(@alignOf(SYMBOLIC_LINK_REPARSE_BUFFER), &reparse_struct.DataBuffer[0]));
|
||||
const offset = buf.SubstituteNameOffset >> 1;
|
||||
const len = buf.SubstituteNameLength >> 1;
|
||||
const path_buf = @as([*]const u16, &buf.PathBuffer);
|
||||
const is_relative = buf.Flags & SYMLINK_FLAG_RELATIVE != 0;
|
||||
return parseReadlinkPath(path_buf[offset .. offset + len], is_relative, out_buffer);
|
||||
},
|
||||
IO_REPARSE_TAG_MOUNT_POINT => {
|
||||
const buf = @ptrCast(*const MOUNT_POINT_REPARSE_BUFFER, @alignCast(@alignOf(MOUNT_POINT_REPARSE_BUFFER), &reparse_struct.DataBuffer[0]));
|
||||
const offset = buf.SubstituteNameOffset >> 1;
|
||||
const len = buf.SubstituteNameLength >> 1;
|
||||
const path_buf = @as([*]const u16, &buf.PathBuffer);
|
||||
return parseReadlinkPath(path_buf[offset .. offset + len], false, out_buffer);
|
||||
},
|
||||
else => |value| {
|
||||
std.debug.warn("unsupported symlink type: {}", .{value});
|
||||
return error.UnsupportedReparsePointType;
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn parseReadlinkPath(path: []const u16, is_relative: bool, out_buffer: []u8) []u8 {
|
||||
const prefix = [_]u16{ '\\', '?', '?', '\\' };
|
||||
var start_index: usize = 0;
|
||||
if (!is_relative and std.mem.startsWith(u16, path, &prefix)) {
|
||||
start_index = prefix.len;
|
||||
}
|
||||
const out_len = std.unicode.utf16leToUtf8(out_buffer, path[start_index..]) catch unreachable;
|
||||
return out_buffer[0..out_len];
|
||||
}
|
||||
|
||||
pub const DeleteFileError = error{
|
||||
@ -1239,23 +1456,30 @@ pub const PathSpace = struct {
|
||||
}
|
||||
};
|
||||
|
||||
/// Same as `sliceToPrefixedFileW` but accepts a pointer
|
||||
/// to a null-terminated path.
|
||||
pub fn cStrToPrefixedFileW(s: [*:0]const u8) !PathSpace {
|
||||
return sliceToPrefixedFileW(mem.spanZ(s));
|
||||
}
|
||||
|
||||
/// Converts the path `s` to WTF16, null-terminated. If the path is absolute,
|
||||
/// it will get NT-style prefix `\??\` prepended automatically. For prepending
|
||||
/// Win32-style prefix, see `sliceToWin32PrefixedFileW` instead.
|
||||
pub fn sliceToPrefixedFileW(s: []const u8) !PathSpace {
|
||||
// TODO https://github.com/ziglang/zig/issues/2765
|
||||
var path_space: PathSpace = undefined;
|
||||
for (s) |byte| {
|
||||
const prefix = "\\??\\";
|
||||
const prefix_index: usize = if (mem.startsWith(u8, s, prefix)) prefix.len else 0;
|
||||
for (s[prefix_index..]) |byte| {
|
||||
switch (byte) {
|
||||
'*', '?', '"', '<', '>', '|' => return error.BadPathName,
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
const start_index = if (mem.startsWith(u8, s, "\\?") or !std.fs.path.isAbsolute(s)) 0 else blk: {
|
||||
const prefix = [_]u16{ '\\', '?', '?', '\\' };
|
||||
mem.copy(u16, path_space.data[0..], &prefix);
|
||||
break :blk prefix.len;
|
||||
const start_index = if (prefix_index > 0 or !std.fs.path.isAbsolute(s)) 0 else blk: {
|
||||
const prefix_u16 = [_]u16{ '\\', '?', '?', '\\' };
|
||||
mem.copy(u16, path_space.data[0..], prefix_u16[0..]);
|
||||
break :blk prefix_u16.len;
|
||||
};
|
||||
path_space.len = start_index + try std.unicode.utf8ToUtf16Le(path_space.data[start_index..], s);
|
||||
if (path_space.len > path_space.data.len) return error.NameTooLong;
|
||||
@ -1287,6 +1511,12 @@ pub fn wToPrefixedFileW(s: []const u16) !PathSpace {
|
||||
path_space.len = start_index + s.len;
|
||||
if (path_space.len > path_space.data.len) return error.NameTooLong;
|
||||
mem.copy(u16, path_space.data[start_index..], s);
|
||||
// > File I/O functions in the Windows API convert "/" to "\" as part of
|
||||
// > converting the name to an NT-style name, except when using the "\\?\"
|
||||
// > prefix as detailed in the following sections.
|
||||
// from https://docs.microsoft.com/en-us/windows/desktop/FileIO/naming-a-file#maximum-path-length-limitation
|
||||
// Because we want the larger maximum path length for absolute paths, we
|
||||
// convert forward slashes to backward slashes here.
|
||||
for (path_space.data[0..path_space.len]) |*elem| {
|
||||
if (elem.* == '/') {
|
||||
elem.* = '\\';
|
||||
|
||||
@ -488,7 +488,7 @@ pub const FILE_OPEN_BY_FILE_ID = 0x00002000;
|
||||
pub const FILE_OPEN_FOR_BACKUP_INTENT = 0x00004000;
|
||||
pub const FILE_NO_COMPRESSION = 0x00008000;
|
||||
pub const FILE_RESERVE_OPFILTER = 0x00100000;
|
||||
pub const FILE_TRANSACTED_MODE = 0x00200000;
|
||||
pub const FILE_OPEN_REPARSE_POINT = 0x00200000;
|
||||
pub const FILE_OPEN_OFFLINE_FILE = 0x00400000;
|
||||
pub const FILE_OPEN_FOR_FREE_SPACE_QUERY = 0x00800000;
|
||||
|
||||
@ -1542,3 +1542,34 @@ pub const POSVERSIONINFOW = *OSVERSIONINFOW;
|
||||
pub const LPOSVERSIONINFOW = *OSVERSIONINFOW;
|
||||
pub const RTL_OSVERSIONINFOW = OSVERSIONINFOW;
|
||||
pub const PRTL_OSVERSIONINFOW = *RTL_OSVERSIONINFOW;
|
||||
|
||||
pub const REPARSE_DATA_BUFFER = extern struct {
|
||||
ReparseTag: ULONG,
|
||||
ReparseDataLength: USHORT,
|
||||
Reserved: USHORT,
|
||||
DataBuffer: [1]UCHAR,
|
||||
};
|
||||
pub const SYMBOLIC_LINK_REPARSE_BUFFER = extern struct {
|
||||
SubstituteNameOffset: USHORT,
|
||||
SubstituteNameLength: USHORT,
|
||||
PrintNameOffset: USHORT,
|
||||
PrintNameLength: USHORT,
|
||||
Flags: ULONG,
|
||||
PathBuffer: [1]WCHAR,
|
||||
};
|
||||
pub const MOUNT_POINT_REPARSE_BUFFER = extern struct {
|
||||
SubstituteNameOffset: USHORT,
|
||||
SubstituteNameLength: USHORT,
|
||||
PrintNameOffset: USHORT,
|
||||
PrintNameLength: USHORT,
|
||||
PathBuffer: [1]WCHAR,
|
||||
};
|
||||
pub const MAXIMUM_REPARSE_DATA_BUFFER_SIZE: ULONG = 16 * 1024;
|
||||
pub const FSCTL_SET_REPARSE_POINT: DWORD = 0x900a4;
|
||||
pub const FSCTL_GET_REPARSE_POINT: DWORD = 0x900a8;
|
||||
pub const IO_REPARSE_TAG_SYMLINK: ULONG = 0xa000000c;
|
||||
pub const IO_REPARSE_TAG_MOUNT_POINT: ULONG = 0xa0000003;
|
||||
pub const SYMLINK_FLAG_RELATIVE: ULONG = 0x1;
|
||||
|
||||
pub const SYMBOLIC_LINK_FLAG_DIRECTORY: DWORD = 0x1;
|
||||
pub const SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE: DWORD = 0x2;
|
||||
|
||||
@ -552,6 +552,9 @@ pub const NativeTargetInfo = struct {
|
||||
error.SystemResources => return error.SystemResources,
|
||||
error.NotDir => return error.GnuLibCVersionUnavailable,
|
||||
error.Unexpected => return error.GnuLibCVersionUnavailable,
|
||||
error.InvalidUtf8 => unreachable, // Windows only
|
||||
error.BadPathName => unreachable, // Windows only
|
||||
error.UnsupportedReparsePointType => unreachable, // Windows only
|
||||
};
|
||||
return glibcVerFromLinkName(link_name);
|
||||
}
|
||||
@ -817,6 +820,9 @@ pub const NativeTargetInfo = struct {
|
||||
&link_buf,
|
||||
) catch |err| switch (err) {
|
||||
error.NameTooLong => unreachable,
|
||||
error.InvalidUtf8 => unreachable, // Windows only
|
||||
error.BadPathName => unreachable, // Windows only
|
||||
error.UnsupportedReparsePointType => unreachable, // Windows only
|
||||
|
||||
error.AccessDenied,
|
||||
error.FileNotFound,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user