mirror of
https://github.com/ziglang/zig.git
synced 2026-02-13 04:48:20 +00:00
Merge pull request #5960 from kubkon/windows-dir-refactor
Refactor out file and dir creation routines in Windows
This commit is contained in:
commit
6df35c5877
@ -364,6 +364,7 @@ pub const ChildProcess = struct {
|
||||
error.FileTooBig => unreachable,
|
||||
error.DeviceBusy => unreachable,
|
||||
error.FileLocksNotSupported => unreachable,
|
||||
error.BadPathName => unreachable, // Windows-only
|
||||
else => |e| return e,
|
||||
}
|
||||
else
|
||||
@ -480,25 +481,20 @@ pub const ChildProcess = struct {
|
||||
|
||||
const any_ignore = (self.stdin_behavior == StdIo.Ignore or self.stdout_behavior == StdIo.Ignore or self.stderr_behavior == StdIo.Ignore);
|
||||
|
||||
// TODO use CreateFileW here since we are using a string literal for the path
|
||||
const nul_handle = if (any_ignore)
|
||||
windows.CreateFile(
|
||||
"NUL",
|
||||
windows.GENERIC_READ,
|
||||
windows.FILE_SHARE_READ,
|
||||
null,
|
||||
windows.OPEN_EXISTING,
|
||||
windows.FILE_ATTRIBUTE_NORMAL,
|
||||
null,
|
||||
) catch |err| switch (err) {
|
||||
error.SharingViolation => unreachable, // not possible for "NUL"
|
||||
windows.OpenFile(&[_]u16{ 'N', 'U', 'L' }, .{
|
||||
.dir = std.fs.cwd().fd,
|
||||
.access_mask = windows.GENERIC_READ | windows.SYNCHRONIZE,
|
||||
.share_access = windows.FILE_SHARE_READ,
|
||||
.creation = windows.OPEN_EXISTING,
|
||||
.io_mode = .blocking,
|
||||
}) catch |err| switch (err) {
|
||||
error.PathAlreadyExists => unreachable, // not possible for "NUL"
|
||||
error.PipeBusy => unreachable, // not possible for "NUL"
|
||||
error.InvalidUtf8 => unreachable, // not possible for "NUL"
|
||||
error.BadPathName => unreachable, // not possible for "NUL"
|
||||
error.FileNotFound => unreachable, // not possible for "NUL"
|
||||
error.AccessDenied => unreachable, // not possible for "NUL"
|
||||
error.NameTooLong => unreachable, // not possible for "NUL"
|
||||
error.WouldBlock => unreachable, // not possible for "NUL"
|
||||
else => |e| return e,
|
||||
}
|
||||
else
|
||||
|
||||
@ -225,8 +225,7 @@ pub fn makeDirAbsoluteZ(absolute_path_z: [*:0]const u8) !void {
|
||||
/// Same as `makeDirAbsolute` except the parameter is a null-terminated WTF-16 encoded string.
|
||||
pub fn makeDirAbsoluteW(absolute_path_w: [*:0]const u16) !void {
|
||||
assert(path.isAbsoluteWindowsW(absolute_path_w));
|
||||
const handle = try os.windows.CreateDirectoryW(null, absolute_path_w, null);
|
||||
os.windows.CloseHandle(handle);
|
||||
return os.mkdirW(absolute_path_w, default_new_dir_mode);
|
||||
}
|
||||
|
||||
pub const deleteDir = @compileError("deprecated; use dir.deleteDir or deleteDirAbsolute");
|
||||
@ -881,8 +880,7 @@ pub const Dir = struct {
|
||||
}
|
||||
|
||||
pub fn makeDirW(self: Dir, sub_path: [*:0]const u16) !void {
|
||||
const handle = try os.windows.CreateDirectoryW(self.fd, sub_path, null);
|
||||
os.windows.CloseHandle(handle);
|
||||
try os.mkdiratW(self.fd, sub_path, default_new_dir_mode);
|
||||
}
|
||||
|
||||
/// Calls makeDir recursively to make an entire path. Returns success if the path
|
||||
@ -1119,7 +1117,7 @@ pub const Dir = struct {
|
||||
pub fn deleteFile(self: Dir, sub_path: []const u8) DeleteFileError!void {
|
||||
if (builtin.os.tag == .windows) {
|
||||
const sub_path_w = try os.windows.sliceToPrefixedFileW(sub_path);
|
||||
return self.deleteFileW(sub_path_w.span().ptr);
|
||||
return self.deleteFileW(sub_path_w.span());
|
||||
} else if (builtin.os.tag == .wasi) {
|
||||
os.unlinkatWasi(self.fd, sub_path, 0) catch |err| switch (err) {
|
||||
error.DirNotEmpty => unreachable, // not passing AT_REMOVEDIR
|
||||
@ -1153,7 +1151,7 @@ pub const Dir = struct {
|
||||
}
|
||||
|
||||
/// Same as `deleteFile` except the parameter is WTF-16 encoded.
|
||||
pub fn deleteFileW(self: Dir, sub_path_w: [*:0]const u16) DeleteFileError!void {
|
||||
pub fn deleteFileW(self: Dir, sub_path_w: []const u16) DeleteFileError!void {
|
||||
os.unlinkatW(self.fd, sub_path_w, 0) catch |err| switch (err) {
|
||||
error.DirNotEmpty => unreachable, // not passing AT_REMOVEDIR
|
||||
else => |e| return e,
|
||||
@ -1182,7 +1180,7 @@ pub const Dir = struct {
|
||||
pub fn deleteDir(self: Dir, sub_path: []const u8) DeleteDirError!void {
|
||||
if (builtin.os.tag == .windows) {
|
||||
const sub_path_w = try os.windows.sliceToPrefixedFileW(sub_path);
|
||||
return self.deleteDirW(sub_path_w.span().ptr);
|
||||
return self.deleteDirW(sub_path_w.span());
|
||||
} else if (builtin.os.tag == .wasi) {
|
||||
os.unlinkat(self.fd, sub_path, os.AT_REMOVEDIR) catch |err| switch (err) {
|
||||
error.IsDir => unreachable, // not possible since we pass AT_REMOVEDIR
|
||||
@ -1204,7 +1202,7 @@ pub const Dir = struct {
|
||||
|
||||
/// Same as `deleteDir` except the parameter is UTF16LE, NT prefixed.
|
||||
/// This function is Windows-only.
|
||||
pub fn deleteDirW(self: Dir, sub_path_w: [*:0]const u16) DeleteDirError!void {
|
||||
pub fn deleteDirW(self: Dir, sub_path_w: []const u16) DeleteDirError!void {
|
||||
os.unlinkatW(self.fd, sub_path_w, os.AT_REMOVEDIR) catch |err| switch (err) {
|
||||
error.IsDir => unreachable, // not possible since we pass AT_REMOVEDIR
|
||||
else => |e| return e,
|
||||
@ -1263,11 +1261,11 @@ pub const Dir = struct {
|
||||
/// are null-terminated, WTF16 encoded.
|
||||
pub fn symLinkW(
|
||||
self: Dir,
|
||||
target_path_w: [:0]const u16,
|
||||
sym_link_path_w: [:0]const u16,
|
||||
target_path_w: []const u16,
|
||||
sym_link_path_w: []const u16,
|
||||
flags: SymLinkFlags,
|
||||
) !void {
|
||||
return os.windows.CreateSymbolicLinkW(self.fd, sym_link_path_w, target_path_w, flags.is_directory);
|
||||
return os.windows.CreateSymbolicLink(self.fd, sym_link_path_w, target_path_w, flags.is_directory);
|
||||
}
|
||||
|
||||
/// Read value of a symbolic link.
|
||||
@ -1278,7 +1276,8 @@ pub const Dir = struct {
|
||||
return self.readLinkWasi(sub_path, buffer);
|
||||
}
|
||||
if (builtin.os.tag == .windows) {
|
||||
return os.windows.ReadLink(self.fd, sub_path, buffer);
|
||||
const sub_path_w = try os.windows.sliceToPrefixedFileW(sub_path);
|
||||
return self.readLinkW(sub_path_w.span(), buffer);
|
||||
}
|
||||
const sub_path_c = try os.toPosixPath(sub_path);
|
||||
return self.readLinkZ(&sub_path_c, buffer);
|
||||
@ -1295,15 +1294,15 @@ pub const Dir = struct {
|
||||
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 self.readLinkW(sub_path_w.span(), 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);
|
||||
pub fn readLinkW(self: Dir, sub_path_w: []const u16, buffer: []u8) ![]u8 {
|
||||
return os.windows.ReadLink(self.fd, sub_path_w, buffer);
|
||||
}
|
||||
|
||||
/// On success, caller owns returned buffer.
|
||||
@ -1813,7 +1812,9 @@ pub fn symLinkAbsolute(target_path: []const u8, sym_link_path: []const u8, flags
|
||||
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);
|
||||
const target_path_w = try os.windows.sliceToPrefixedFileW(target_path);
|
||||
const sym_link_path_w = try os.windows.sliceToPrefixedFileW(sym_link_path);
|
||||
return os.windows.CreateSymbolicLink(null, sym_link_path_w.span(), target_path_w.span(), flags.is_directory);
|
||||
}
|
||||
return os.symlink(target_path, sym_link_path);
|
||||
}
|
||||
@ -1822,10 +1823,10 @@ pub fn symLinkAbsolute(target_path: []const u8, sym_link_path: []const u8, flags
|
||||
/// 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);
|
||||
pub fn symLinkAbsoluteW(target_path_w: []const u16, sym_link_path_w: []const u16, flags: SymLinkFlags) !void {
|
||||
assert(path.isAbsoluteWindowsWTF16(target_path_w));
|
||||
assert(path.isAbsoluteWindowsWTF16(sym_link_path_w));
|
||||
return os.windows.CreateSymbolicLink(null, sym_link_path_w, target_path_w, flags.is_directory);
|
||||
}
|
||||
|
||||
/// Same as `symLinkAbsolute` except the parameters are null-terminated pointers.
|
||||
@ -1836,7 +1837,7 @@ pub fn symLinkAbsoluteZ(target_path_c: [*:0]const u8, sym_link_path_c: [*:0]cons
|
||||
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.windows.CreateSymbolicLink(sym_link_path_w.span(), target_path_w.span(), flags.is_directory);
|
||||
}
|
||||
return os.symlinkZ(target_path_c, sym_link_path_c);
|
||||
}
|
||||
@ -1938,7 +1939,20 @@ pub fn walkPath(allocator: *Allocator, dir_path: []const u8) !Walker {
|
||||
return walker;
|
||||
}
|
||||
|
||||
pub const OpenSelfExeError = os.OpenError || os.windows.CreateFileError || SelfExePathError || os.FlockError;
|
||||
pub const OpenSelfExeError = error{
|
||||
SharingViolation,
|
||||
PathAlreadyExists,
|
||||
FileNotFound,
|
||||
AccessDenied,
|
||||
PipeBusy,
|
||||
NameTooLong,
|
||||
/// On Windows, file paths must be valid Unicode.
|
||||
InvalidUtf8,
|
||||
/// On Windows, file paths cannot contain these characters:
|
||||
/// '/', '*', '?', '"', '<', '>', '|'
|
||||
BadPathName,
|
||||
Unexpected,
|
||||
} || os.OpenError || SelfExePathError || os.FlockError;
|
||||
|
||||
pub fn openSelfExe(flags: File.OpenFlags) OpenSelfExeError!File {
|
||||
if (builtin.os.tag == .linux) {
|
||||
|
||||
@ -47,7 +47,20 @@ pub const File = struct {
|
||||
else => 0o666,
|
||||
};
|
||||
|
||||
pub const OpenError = windows.CreateFileError || os.OpenError || os.FlockError;
|
||||
pub const OpenError = error{
|
||||
SharingViolation,
|
||||
PathAlreadyExists,
|
||||
FileNotFound,
|
||||
AccessDenied,
|
||||
PipeBusy,
|
||||
NameTooLong,
|
||||
/// On Windows, file paths must be valid Unicode.
|
||||
InvalidUtf8,
|
||||
/// On Windows, file paths cannot contain these characters:
|
||||
/// '/', '*', '?', '"', '<', '>', '|'
|
||||
BadPathName,
|
||||
Unexpected,
|
||||
} || os.OpenError || os.FlockError;
|
||||
|
||||
pub const Lock = enum { None, Shared, Exclusive };
|
||||
|
||||
|
||||
@ -374,15 +374,13 @@ pub fn Watch(comptime V: type) type {
|
||||
defer if (!basename_utf16le_null_consumed) self.allocator.free(basename_utf16le_null);
|
||||
const basename_utf16le_no_null = basename_utf16le_null[0 .. basename_utf16le_null.len - 1];
|
||||
|
||||
const dir_handle = try windows.CreateFileW(
|
||||
dirname_utf16le.ptr,
|
||||
windows.FILE_LIST_DIRECTORY,
|
||||
windows.FILE_SHARE_READ | windows.FILE_SHARE_DELETE | windows.FILE_SHARE_WRITE,
|
||||
null,
|
||||
windows.OPEN_EXISTING,
|
||||
windows.FILE_FLAG_BACKUP_SEMANTICS | windows.FILE_FLAG_OVERLAPPED,
|
||||
null,
|
||||
);
|
||||
const dir_handle = try windows.OpenFile(dirname_utf16le, .{
|
||||
.dir = std.fs.cwd().fd,
|
||||
.access_mask = windows.FILE_LIST_DIRECTORY,
|
||||
.creation = windows.FILE_OPEN,
|
||||
.io_mode = .blocking,
|
||||
.open_dir = true,
|
||||
});
|
||||
var dir_handle_consumed = false;
|
||||
defer if (!dir_handle_consumed) windows.CloseHandle(dir_handle);
|
||||
|
||||
|
||||
282
lib/std/os.zig
282
lib/std/os.zig
@ -1041,6 +1041,9 @@ pub const OpenError = error{
|
||||
|
||||
/// The underlying filesystem does not support file locks
|
||||
FileLocksNotSupported,
|
||||
|
||||
BadPathName,
|
||||
InvalidUtf8,
|
||||
} || UnexpectedError;
|
||||
|
||||
/// Open and possibly create a file. Keeps trying if it gets interrupted.
|
||||
@ -1092,18 +1095,65 @@ pub fn openZ(file_path: [*:0]const u8, flags: u32, perm: mode_t) OpenError!fd_t
|
||||
}
|
||||
}
|
||||
|
||||
fn openOptionsFromFlags(flags: u32) windows.OpenFileOptions {
|
||||
const w = windows;
|
||||
|
||||
var access_mask: w.ULONG = w.READ_CONTROL | w.FILE_WRITE_ATTRIBUTES | w.SYNCHRONIZE;
|
||||
if (flags & O_RDWR != 0) {
|
||||
access_mask |= w.GENERIC_READ | w.GENERIC_WRITE;
|
||||
} else if (flags & O_WRONLY != 0) {
|
||||
access_mask |= w.GENERIC_WRITE;
|
||||
} else {
|
||||
access_mask |= w.GENERIC_READ | w.GENERIC_WRITE;
|
||||
}
|
||||
|
||||
const open_dir: bool = flags & O_DIRECTORY != 0;
|
||||
const follow_symlinks: bool = flags & O_NOFOLLOW == 0;
|
||||
|
||||
const creation: w.ULONG = blk: {
|
||||
if (flags & O_CREAT != 0) {
|
||||
if (flags & O_EXCL != 0) {
|
||||
break :blk w.FILE_CREATE;
|
||||
}
|
||||
}
|
||||
break :blk w.FILE_OPEN;
|
||||
};
|
||||
|
||||
return .{
|
||||
.access_mask = access_mask,
|
||||
.io_mode = .blocking,
|
||||
.creation = creation,
|
||||
.open_dir = open_dir,
|
||||
.follow_symlinks = follow_symlinks,
|
||||
};
|
||||
}
|
||||
|
||||
/// Windows-only. The path parameter is
|
||||
/// [WTF-16](https://simonsapin.github.io/wtf-8/#potentially-ill-formed-utf-16) encoded.
|
||||
/// Translates the POSIX open API call to a Windows API call.
|
||||
pub fn openW(file_path_w: []const u16, flags: u32, perm: usize) OpenError!fd_t {
|
||||
@compileError("TODO implement openW for windows");
|
||||
/// TODO currently, this function does not handle all flag combinations
|
||||
/// or makes use of perm argument.
|
||||
pub fn openW(file_path_w: []const u16, flags: u32, perm: mode_t) OpenError!fd_t {
|
||||
var options = openOptionsFromFlags(flags);
|
||||
options.dir = std.fs.cwd().fd;
|
||||
return windows.OpenFile(file_path_w, options) catch |err| switch (err) {
|
||||
error.WouldBlock => unreachable,
|
||||
error.PipeBusy => unreachable,
|
||||
else => |e| return e,
|
||||
};
|
||||
}
|
||||
|
||||
/// Open and possibly create a file. Keeps trying if it gets interrupted.
|
||||
/// `file_path` is relative to the open directory handle `dir_fd`.
|
||||
/// See also `openatC`.
|
||||
/// TODO support windows
|
||||
pub fn openat(dir_fd: fd_t, file_path: []const u8, flags: u32, mode: mode_t) OpenError!fd_t {
|
||||
if (builtin.os.tag == .wasi) {
|
||||
@compileError("use openatWasi instead");
|
||||
}
|
||||
if (builtin.os.tag == .windows) {
|
||||
const file_path_w = try windows.sliceToPrefixedFileW(file_path);
|
||||
return openatW(dir_fd, file_path_w.span(), flags, mode);
|
||||
}
|
||||
const file_path_c = try toPosixPath(file_path);
|
||||
return openatZ(dir_fd, &file_path_c, flags, mode);
|
||||
}
|
||||
@ -1145,8 +1195,11 @@ pub const openatC = @compileError("deprecated: renamed to openatZ");
|
||||
/// Open and possibly create a file. Keeps trying if it gets interrupted.
|
||||
/// `file_path` is relative to the open directory handle `dir_fd`.
|
||||
/// See also `openat`.
|
||||
/// TODO support windows
|
||||
pub fn openatZ(dir_fd: fd_t, file_path: [*:0]const u8, flags: u32, mode: mode_t) OpenError!fd_t {
|
||||
if (builtin.os.tag == .windows) {
|
||||
const file_path_w = try windows.cStrToPrefixedFileW(file_path);
|
||||
return openatW(dir_fd, file_path_w.span(), flags, mode);
|
||||
}
|
||||
while (true) {
|
||||
const rc = system.openat(dir_fd, file_path, flags, mode);
|
||||
switch (errno(rc)) {
|
||||
@ -1177,6 +1230,20 @@ pub fn openatZ(dir_fd: fd_t, file_path: [*:0]const u8, flags: u32, mode: mode_t)
|
||||
}
|
||||
}
|
||||
|
||||
/// Windows-only. Similar to `openat` but with pathname argument null-terminated
|
||||
/// WTF16 encoded.
|
||||
/// TODO currently, this function does not handle all flag combinations
|
||||
/// or makes use of perm argument.
|
||||
pub fn openatW(dir_fd: fd_t, file_path_w: []const u16, flags: u32, mode: mode_t) OpenError!fd_t {
|
||||
var options = openOptionsFromFlags(flags);
|
||||
options.dir = dir_fd;
|
||||
return windows.OpenFile(file_path_w, options) catch |err| switch (err) {
|
||||
error.WouldBlock => unreachable,
|
||||
error.PipeBusy => unreachable,
|
||||
else => |e| return e,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn dup2(old_fd: fd_t, new_fd: fd_t) !void {
|
||||
while (true) {
|
||||
switch (errno(system.dup2(old_fd, new_fd))) {
|
||||
@ -1683,7 +1750,7 @@ pub fn unlink(file_path: []const u8) UnlinkError!void {
|
||||
@compileError("unlink is not supported in WASI; use unlinkat instead");
|
||||
} else if (builtin.os.tag == .windows) {
|
||||
const file_path_w = try windows.sliceToPrefixedFileW(file_path);
|
||||
return windows.DeleteFileW(file_path_w.span().ptr);
|
||||
return unlinkW(file_path_w.span());
|
||||
} else {
|
||||
const file_path_c = try toPosixPath(file_path);
|
||||
return unlinkZ(&file_path_c);
|
||||
@ -1696,7 +1763,7 @@ pub const unlinkC = @compileError("deprecated: renamed to unlinkZ");
|
||||
pub fn unlinkZ(file_path: [*:0]const u8) UnlinkError!void {
|
||||
if (builtin.os.tag == .windows) {
|
||||
const file_path_w = try windows.cStrToPrefixedFileW(file_path);
|
||||
return windows.DeleteFileW(file_path_w.span().ptr);
|
||||
return unlinkW(file_path_w.span());
|
||||
}
|
||||
switch (errno(system.unlink(file_path))) {
|
||||
0 => return,
|
||||
@ -1717,6 +1784,11 @@ pub fn unlinkZ(file_path: [*:0]const u8) UnlinkError!void {
|
||||
}
|
||||
}
|
||||
|
||||
/// Windows-only. Same as `unlink` except the parameter is null-terminated, WTF16 encoded.
|
||||
pub fn unlinkW(file_path_w: []const u16) UnlinkError!void {
|
||||
return windows.DeleteFile(file_path_w, .{ .dir = std.fs.cwd().fd });
|
||||
}
|
||||
|
||||
pub const UnlinkatError = UnlinkError || error{
|
||||
/// When passing `AT_REMOVEDIR`, this error occurs when the named directory is not empty.
|
||||
DirNotEmpty,
|
||||
@ -1727,7 +1799,7 @@ pub const UnlinkatError = UnlinkError || error{
|
||||
pub fn unlinkat(dirfd: fd_t, file_path: []const u8, flags: u32) UnlinkatError!void {
|
||||
if (builtin.os.tag == .windows) {
|
||||
const file_path_w = try windows.sliceToPrefixedFileW(file_path);
|
||||
return unlinkatW(dirfd, file_path_w.span().ptr, flags);
|
||||
return unlinkatW(dirfd, file_path_w.span(), flags);
|
||||
} else if (builtin.os.tag == .wasi) {
|
||||
return unlinkatWasi(dirfd, file_path, flags);
|
||||
} else {
|
||||
@ -1774,7 +1846,7 @@ pub fn unlinkatWasi(dirfd: fd_t, file_path: []const u8, flags: u32) UnlinkatErro
|
||||
pub fn unlinkatZ(dirfd: fd_t, file_path_c: [*:0]const u8, flags: u32) UnlinkatError!void {
|
||||
if (builtin.os.tag == .windows) {
|
||||
const file_path_w = try windows.cStrToPrefixedFileW(file_path_c);
|
||||
return unlinkatW(dirfd, file_path_w.span().ptr, flags);
|
||||
return unlinkatW(dirfd, file_path_w.span(), flags);
|
||||
}
|
||||
switch (errno(system.unlinkat(dirfd, file_path_c, flags))) {
|
||||
0 => return,
|
||||
@ -1800,67 +1872,9 @@ pub fn unlinkatZ(dirfd: fd_t, file_path_c: [*:0]const u8, flags: u32) UnlinkatEr
|
||||
}
|
||||
|
||||
/// Same as `unlinkat` but `sub_path_w` is UTF16LE, NT prefixed. Windows only.
|
||||
pub fn unlinkatW(dirfd: fd_t, sub_path_w: [*:0]const u16, flags: u32) UnlinkatError!void {
|
||||
const w = windows;
|
||||
|
||||
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 | 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?
|
||||
|
||||
const path_len_bytes = @intCast(u16, mem.lenZ(sub_path_w) * 2);
|
||||
var nt_name = w.UNICODE_STRING{
|
||||
.Length = path_len_bytes,
|
||||
.MaximumLength = path_len_bytes,
|
||||
// The Windows API makes this mutable, but it will not mutate here.
|
||||
.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;
|
||||
}
|
||||
if (sub_path_w[0] == '.' and sub_path_w[1] == '.' and sub_path_w[2] == 0) {
|
||||
// Can't remove the parent directory with an open handle.
|
||||
return error.FileBusy;
|
||||
}
|
||||
|
||||
var attr = w.OBJECT_ATTRIBUTES{
|
||||
.Length = @sizeOf(w.OBJECT_ATTRIBUTES),
|
||||
.RootDirectory = if (std.fs.path.isAbsoluteWindowsW(sub_path_w)) null else dirfd,
|
||||
.Attributes = 0, // Note we do not use OBJ_CASE_INSENSITIVE here.
|
||||
.ObjectName = &nt_name,
|
||||
.SecurityDescriptor = null,
|
||||
.SecurityQualityOfService = null,
|
||||
};
|
||||
var io: w.IO_STATUS_BLOCK = undefined;
|
||||
var tmp_handle: w.HANDLE = undefined;
|
||||
var rc = w.ntdll.NtCreateFile(
|
||||
&tmp_handle,
|
||||
w.SYNCHRONIZE | w.DELETE,
|
||||
&attr,
|
||||
&io,
|
||||
null,
|
||||
0,
|
||||
w.FILE_SHARE_READ | w.FILE_SHARE_WRITE | w.FILE_SHARE_DELETE,
|
||||
w.FILE_OPEN,
|
||||
create_options_flags,
|
||||
null,
|
||||
0,
|
||||
);
|
||||
if (rc == .SUCCESS) {
|
||||
rc = w.ntdll.NtClose(tmp_handle);
|
||||
}
|
||||
switch (rc) {
|
||||
.SUCCESS => return,
|
||||
.OBJECT_NAME_INVALID => unreachable,
|
||||
.OBJECT_NAME_NOT_FOUND => return error.FileNotFound,
|
||||
.INVALID_PARAMETER => unreachable,
|
||||
.FILE_IS_A_DIRECTORY => return error.IsDir,
|
||||
.NOT_A_DIRECTORY => return error.NotDir,
|
||||
else => return w.unexpectedStatus(rc),
|
||||
}
|
||||
pub fn unlinkatW(dirfd: fd_t, sub_path_w: []const u16, flags: u32) UnlinkatError!void {
|
||||
const remove_dir = (flags & AT_REMOVEDIR) != 0;
|
||||
return windows.DeleteFile(sub_path_w, .{ .dir = dirfd, .remove_dir = remove_dir });
|
||||
}
|
||||
|
||||
const RenameError = error{
|
||||
@ -2087,7 +2101,7 @@ pub fn renameatW(
|
||||
pub fn mkdirat(dir_fd: fd_t, sub_dir_path: []const u8, mode: u32) MakeDirError!void {
|
||||
if (builtin.os.tag == .windows) {
|
||||
const sub_dir_path_w = try windows.sliceToPrefixedFileW(sub_dir_path);
|
||||
return mkdiratW(dir_fd, sub_dir_path_w.span().ptr, mode);
|
||||
return mkdiratW(dir_fd, sub_dir_path_w.span(), mode);
|
||||
} else if (builtin.os.tag == .wasi) {
|
||||
return mkdiratWasi(dir_fd, sub_dir_path, mode);
|
||||
} else {
|
||||
@ -2145,8 +2159,19 @@ pub fn mkdiratZ(dir_fd: fd_t, sub_dir_path: [*:0]const u8, mode: u32) MakeDirErr
|
||||
}
|
||||
}
|
||||
|
||||
pub fn mkdiratW(dir_fd: fd_t, sub_path_w: [*:0]const u16, mode: u32) MakeDirError!void {
|
||||
const sub_dir_handle = try windows.CreateDirectoryW(dir_fd, sub_path_w, null);
|
||||
pub fn mkdiratW(dir_fd: fd_t, sub_path_w: []const u16, mode: u32) MakeDirError!void {
|
||||
const sub_dir_handle = windows.OpenFile(sub_path_w, .{
|
||||
.dir = dir_fd,
|
||||
.access_mask = windows.GENERIC_READ | windows.SYNCHRONIZE,
|
||||
.creation = windows.FILE_CREATE,
|
||||
.io_mode = .blocking,
|
||||
.open_dir = true,
|
||||
}) catch |err| switch (err) {
|
||||
error.IsDir => unreachable,
|
||||
error.PipeBusy => unreachable,
|
||||
error.WouldBlock => unreachable,
|
||||
else => |e| return e,
|
||||
};
|
||||
windows.CloseHandle(sub_dir_handle);
|
||||
}
|
||||
|
||||
@ -2175,9 +2200,8 @@ pub fn mkdir(dir_path: []const u8, mode: u32) MakeDirError!void {
|
||||
if (builtin.os.tag == .wasi) {
|
||||
@compileError("mkdir is not supported in WASI; use mkdirat instead");
|
||||
} else if (builtin.os.tag == .windows) {
|
||||
const sub_dir_handle = try windows.CreateDirectory(null, dir_path, null);
|
||||
windows.CloseHandle(sub_dir_handle);
|
||||
return;
|
||||
const dir_path_w = try windows.sliceToPrefixedFileW(dir_path);
|
||||
return mkdirW(dir_path_w.span(), mode);
|
||||
} else {
|
||||
const dir_path_c = try toPosixPath(dir_path);
|
||||
return mkdirZ(&dir_path_c, mode);
|
||||
@ -2188,9 +2212,7 @@ pub fn mkdir(dir_path: []const u8, mode: u32) MakeDirError!void {
|
||||
pub fn mkdirZ(dir_path: [*:0]const u8, mode: u32) MakeDirError!void {
|
||||
if (builtin.os.tag == .windows) {
|
||||
const dir_path_w = try windows.cStrToPrefixedFileW(dir_path);
|
||||
const sub_dir_handle = try windows.CreateDirectoryW(null, dir_path_w.span().ptr, null);
|
||||
windows.CloseHandle(sub_dir_handle);
|
||||
return;
|
||||
return mkdirW(dir_path_w.span(), mode);
|
||||
}
|
||||
switch (errno(system.mkdir(dir_path, mode))) {
|
||||
0 => return,
|
||||
@ -2211,6 +2233,23 @@ pub fn mkdirZ(dir_path: [*:0]const u8, mode: u32) MakeDirError!void {
|
||||
}
|
||||
}
|
||||
|
||||
/// Windows-only. Same as `mkdir` but the parameters is WTF16 encoded.
|
||||
pub fn mkdirW(dir_path_w: []const u16, mode: u32) MakeDirError!void {
|
||||
const sub_dir_handle = windows.OpenFile(dir_path_w, .{
|
||||
.dir = std.fs.cwd().fd,
|
||||
.access_mask = windows.GENERIC_READ | windows.SYNCHRONIZE,
|
||||
.creation = windows.FILE_CREATE,
|
||||
.io_mode = .blocking,
|
||||
.open_dir = true,
|
||||
}) catch |err| switch (err) {
|
||||
error.IsDir => unreachable,
|
||||
error.PipeBusy => unreachable,
|
||||
error.WouldBlock => unreachable,
|
||||
else => |e| return e,
|
||||
};
|
||||
windows.CloseHandle(sub_dir_handle);
|
||||
}
|
||||
|
||||
pub const DeleteDirError = error{
|
||||
AccessDenied,
|
||||
FileBusy,
|
||||
@ -2231,7 +2270,7 @@ pub fn rmdir(dir_path: []const u8) DeleteDirError!void {
|
||||
@compileError("rmdir is not supported in WASI; use unlinkat instead");
|
||||
} else if (builtin.os.tag == .windows) {
|
||||
const dir_path_w = try windows.sliceToPrefixedFileW(dir_path);
|
||||
return windows.RemoveDirectoryW(dir_path_w.span().ptr);
|
||||
return rmdirW(dir_path_w.span());
|
||||
} else {
|
||||
const dir_path_c = try toPosixPath(dir_path);
|
||||
return rmdirZ(&dir_path_c);
|
||||
@ -2244,7 +2283,7 @@ pub const rmdirC = @compileError("deprecated: renamed to rmdirZ");
|
||||
pub fn rmdirZ(dir_path: [*:0]const u8) DeleteDirError!void {
|
||||
if (builtin.os.tag == .windows) {
|
||||
const dir_path_w = try windows.cStrToPrefixedFileW(dir_path);
|
||||
return windows.RemoveDirectoryW(dir_path_w.span().ptr);
|
||||
return rmdirW(dir_path_w.span());
|
||||
}
|
||||
switch (errno(system.rmdir(dir_path))) {
|
||||
0 => return,
|
||||
@ -2265,6 +2304,14 @@ pub fn rmdirZ(dir_path: [*:0]const u8) DeleteDirError!void {
|
||||
}
|
||||
}
|
||||
|
||||
/// Windows-only. Same as `rmdir` except the parameter is WTF16 encoded.
|
||||
pub fn rmdirW(dir_path_w: []const u16) DeleteDirError!void {
|
||||
return windows.DeleteFile(dir_path_w, .{ .dir = std.fs.cwd().fd, .remove_dir = true }) catch |err| switch (err) {
|
||||
error.IsDir => unreachable,
|
||||
else => |e| return e,
|
||||
};
|
||||
}
|
||||
|
||||
pub const ChangeCurDirError = error{
|
||||
AccessDenied,
|
||||
FileSystem,
|
||||
@ -2354,7 +2401,8 @@ 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) {
|
||||
return windows.ReadLink(std.fs.cwd().fd, file_path, out_buffer);
|
||||
const file_path_w = try windows.sliceToPrefixedFileW(file_path);
|
||||
return readlinkW(file_path_w.span(), out_buffer);
|
||||
} else {
|
||||
const file_path_c = try toPosixPath(file_path);
|
||||
return readlinkZ(&file_path_c, out_buffer);
|
||||
@ -2363,17 +2411,17 @@ 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` except `file_path` is null-terminated, WTF16 encoded.
|
||||
/// Windows-only. Same as `readlink` except `file_path` is WTF16 encoded.
|
||||
/// See also `readlinkZ`.
|
||||
pub fn readlinkW(file_path: [*:0]const u16, out_buffer: []u8) ReadLinkError![]u8 {
|
||||
return windows.ReadLinkW(std.fs.cwd().fd, file_path, out_buffer);
|
||||
pub fn readlinkW(file_path: []const u16, out_buffer: []u8) ReadLinkError![]u8 {
|
||||
return windows.ReadLink(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.cStrToWin32PrefixedFileW(file_path);
|
||||
return readlinkW(file_path_w.span().ptr, out_buffer);
|
||||
return readlinkW(file_path_w.span(), out_buffer);
|
||||
}
|
||||
const rc = system.readlink(file_path, out_buffer.ptr, out_buffer.len);
|
||||
switch (errno(rc)) {
|
||||
@ -2399,7 +2447,8 @@ 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) {
|
||||
return windows.ReadLink(dirfd, file_path, out_buffer);
|
||||
const file_path_w = try windows.sliceToPrefixedFileW(file_path);
|
||||
return readlinkatW(dirfd, file_path_w.span(), out_buffer);
|
||||
}
|
||||
const file_path_c = try toPosixPath(file_path);
|
||||
return readlinkatZ(dirfd, &file_path_c, out_buffer);
|
||||
@ -2429,8 +2478,8 @@ 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 {
|
||||
return windows.ReadLinkW(dirfd, file_path, out_buffer);
|
||||
pub fn readlinkatW(dirfd: fd_t, file_path: []const u16, out_buffer: []u8) ReadLinkError![]u8 {
|
||||
return windows.ReadLink(dirfd, file_path, out_buffer);
|
||||
}
|
||||
|
||||
/// Same as `readlinkat` except `file_path` is null-terminated.
|
||||
@ -2438,7 +2487,7 @@ pub fn readlinkatW(dirfd: fd_t, file_path: [*:0]const u16, out_buffer: []u8) Rea
|
||||
pub fn readlinkatZ(dirfd: fd_t, file_path: [*:0]const u8, out_buffer: []u8) ReadLinkError![]u8 {
|
||||
if (builtin.os.tag == .windows) {
|
||||
const file_path_w = try windows.cStrToPrefixedFileW(file_path);
|
||||
return readlinkatW(dirfd, file_path_w.span().ptr, out_buffer);
|
||||
return readlinkatW(dirfd, file_path_w.span(), out_buffer);
|
||||
}
|
||||
const rc = system.readlinkat(dirfd, file_path, out_buffer.ptr, out_buffer.len);
|
||||
switch (errno(rc)) {
|
||||
@ -3959,7 +4008,7 @@ pub const RealPathError = error{
|
||||
pub fn realpath(pathname: []const u8, out_buffer: *[MAX_PATH_BYTES]u8) RealPathError![]u8 {
|
||||
if (builtin.os.tag == .windows) {
|
||||
const pathname_w = try windows.sliceToPrefixedFileW(pathname);
|
||||
return realpathW(pathname_w.span().ptr, out_buffer);
|
||||
return realpathW(pathname_w.span(), out_buffer);
|
||||
}
|
||||
if (builtin.os.tag == .wasi) {
|
||||
@compileError("Use std.fs.wasi.PreopenList to obtain valid Dir handles instead of using absolute paths");
|
||||
@ -3974,7 +4023,7 @@ pub const realpathC = @compileError("deprecated: renamed realpathZ");
|
||||
pub fn realpathZ(pathname: [*:0]const u8, out_buffer: *[MAX_PATH_BYTES]u8) RealPathError![]u8 {
|
||||
if (builtin.os.tag == .windows) {
|
||||
const pathname_w = try windows.cStrToPrefixedFileW(pathname);
|
||||
return realpathW(pathname_w.span().ptr, out_buffer);
|
||||
return realpathW(pathname_w.span(), out_buffer);
|
||||
}
|
||||
if (builtin.os.tag == .linux and !builtin.link_libc) {
|
||||
const fd = openZ(pathname, linux.O_PATH | linux.O_NONBLOCK | linux.O_CLOEXEC, 0) catch |err| switch (err) {
|
||||
@ -4010,22 +4059,43 @@ pub fn realpathZ(pathname: [*:0]const u8, out_buffer: *[MAX_PATH_BYTES]u8) RealP
|
||||
return mem.spanZ(result_path);
|
||||
}
|
||||
|
||||
/// Same as `realpath` except `pathname` is null-terminated and UTF16LE-encoded.
|
||||
/// TODO use ntdll for better semantics
|
||||
pub fn realpathW(pathname: [*:0]const u16, out_buffer: *[MAX_PATH_BYTES]u8) RealPathError![]u8 {
|
||||
const h_file = try windows.CreateFileW(
|
||||
pathname,
|
||||
windows.GENERIC_READ,
|
||||
windows.FILE_SHARE_READ,
|
||||
null,
|
||||
windows.OPEN_EXISTING,
|
||||
windows.FILE_FLAG_BACKUP_SEMANTICS,
|
||||
null,
|
||||
);
|
||||
defer windows.CloseHandle(h_file);
|
||||
/// Same as `realpath` except `pathname` is UTF16LE-encoded.
|
||||
/// TODO use ntdll to emulate `GetFinalPathNameByHandleW` routine
|
||||
pub fn realpathW(pathname: []const u16, out_buffer: *[MAX_PATH_BYTES]u8) RealPathError![]u8 {
|
||||
const w = windows;
|
||||
|
||||
var wide_buf: [windows.PATH_MAX_WIDE]u16 = undefined;
|
||||
const wide_slice = try windows.GetFinalPathNameByHandleW(h_file, &wide_buf, wide_buf.len, windows.VOLUME_NAME_DOS);
|
||||
const dir = std.fs.cwd().fd;
|
||||
const access_mask = w.GENERIC_READ | w.SYNCHRONIZE;
|
||||
const share_access = w.FILE_SHARE_READ;
|
||||
const creation = w.FILE_OPEN;
|
||||
const h_file = blk: {
|
||||
const res = w.OpenFile(pathname, .{
|
||||
.dir = dir,
|
||||
.access_mask = access_mask,
|
||||
.share_access = share_access,
|
||||
.creation = creation,
|
||||
.io_mode = .blocking,
|
||||
}) catch |err| switch (err) {
|
||||
error.IsDir => break :blk w.OpenFile(pathname, .{
|
||||
.dir = dir,
|
||||
.access_mask = access_mask,
|
||||
.share_access = share_access,
|
||||
.creation = creation,
|
||||
.io_mode = .blocking,
|
||||
.open_dir = true,
|
||||
}) catch |er| switch (er) {
|
||||
error.WouldBlock => unreachable,
|
||||
else => |e2| return e2,
|
||||
},
|
||||
error.WouldBlock => unreachable,
|
||||
else => |e| return e,
|
||||
};
|
||||
break :blk res;
|
||||
};
|
||||
defer w.CloseHandle(h_file);
|
||||
|
||||
var wide_buf: [w.PATH_MAX_WIDE]u16 = undefined;
|
||||
const wide_slice = try w.GetFinalPathNameByHandleW(h_file, &wide_buf, wide_buf.len, w.VOLUME_NAME_DOS);
|
||||
|
||||
// Windows returns \\?\ prepended to the path.
|
||||
// We strip it to make this function consistent across platforms.
|
||||
|
||||
@ -237,3 +237,28 @@ pub const IPPROTO_TCP = ws2_32.IPPROTO_TCP;
|
||||
pub const IPPROTO_UDP = ws2_32.IPPROTO_UDP;
|
||||
pub const IPPROTO_ICMPV6 = ws2_32.IPPROTO_ICMPV6;
|
||||
pub const IPPROTO_RM = ws2_32.IPPROTO_RM;
|
||||
|
||||
pub const O_RDONLY = 0o0;
|
||||
pub const O_WRONLY = 0o1;
|
||||
pub const O_RDWR = 0o2;
|
||||
|
||||
pub const O_CREAT = 0o100;
|
||||
pub const O_EXCL = 0o200;
|
||||
pub const O_NOCTTY = 0o400;
|
||||
pub const O_TRUNC = 0o1000;
|
||||
pub const O_APPEND = 0o2000;
|
||||
pub const O_NONBLOCK = 0o4000;
|
||||
pub const O_DSYNC = 0o10000;
|
||||
pub const O_SYNC = 0o4010000;
|
||||
pub const O_RSYNC = 0o4010000;
|
||||
pub const O_DIRECTORY = 0o200000;
|
||||
pub const O_NOFOLLOW = 0o400000;
|
||||
pub const O_CLOEXEC = 0o2000000;
|
||||
|
||||
pub const O_ASYNC = 0o20000;
|
||||
pub const O_DIRECT = 0o40000;
|
||||
pub const O_LARGEFILE = 0;
|
||||
pub const O_NOATIME = 0o1000000;
|
||||
pub const O_PATH = 0o10000000;
|
||||
pub const O_TMPFILE = 0o20200000;
|
||||
pub const O_NDELAY = O_NONBLOCK;
|
||||
@ -3,6 +3,7 @@ const os = std.os;
|
||||
const testing = std.testing;
|
||||
const expect = testing.expect;
|
||||
const expectEqual = testing.expectEqual;
|
||||
const expectError = testing.expectError;
|
||||
const io = std.io;
|
||||
const fs = std.fs;
|
||||
const mem = std.mem;
|
||||
@ -19,6 +20,95 @@ const tmpDir = std.testing.tmpDir;
|
||||
const Dir = std.fs.Dir;
|
||||
const ArenaAllocator = std.heap.ArenaAllocator;
|
||||
|
||||
test "open smoke test" {
|
||||
if (builtin.os.tag == .wasi) return error.SkipZigTest;
|
||||
|
||||
// TODO verify file attributes using `fstat`
|
||||
|
||||
var tmp = tmpDir(.{});
|
||||
defer tmp.cleanup();
|
||||
|
||||
// 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);
|
||||
};
|
||||
|
||||
var file_path: []u8 = undefined;
|
||||
var fd: os.fd_t = undefined;
|
||||
const mode: os.mode_t = if (builtin.os.tag == .windows) 0 else 0o666;
|
||||
|
||||
// Create some file using `open`.
|
||||
file_path = try fs.path.join(&arena.allocator, &[_][]const u8{ base_path, "some_file" });
|
||||
fd = try os.open(file_path, os.O_RDWR | os.O_CREAT | os.O_EXCL, mode);
|
||||
os.close(fd);
|
||||
|
||||
// Try this again with the same flags. This op should fail with error.PathAlreadyExists.
|
||||
file_path = try fs.path.join(&arena.allocator, &[_][]const u8{ base_path, "some_file" });
|
||||
expectError(error.PathAlreadyExists, os.open(file_path, os.O_RDWR | os.O_CREAT | os.O_EXCL, mode));
|
||||
|
||||
// Try opening without `O_EXCL` flag.
|
||||
file_path = try fs.path.join(&arena.allocator, &[_][]const u8{ base_path, "some_file" });
|
||||
fd = try os.open(file_path, os.O_RDWR | os.O_CREAT, mode);
|
||||
os.close(fd);
|
||||
|
||||
// Try opening as a directory which should fail.
|
||||
file_path = try fs.path.join(&arena.allocator, &[_][]const u8{ base_path, "some_file" });
|
||||
expectError(error.NotDir, os.open(file_path, os.O_RDWR | os.O_DIRECTORY, mode));
|
||||
|
||||
// Create some directory
|
||||
file_path = try fs.path.join(&arena.allocator, &[_][]const u8{ base_path, "some_dir" });
|
||||
try os.mkdir(file_path, mode);
|
||||
|
||||
// Open dir using `open`
|
||||
file_path = try fs.path.join(&arena.allocator, &[_][]const u8{ base_path, "some_dir" });
|
||||
fd = try os.open(file_path, os.O_RDONLY | os.O_DIRECTORY, mode);
|
||||
os.close(fd);
|
||||
|
||||
// Try opening as file which should fail.
|
||||
file_path = try fs.path.join(&arena.allocator, &[_][]const u8{ base_path, "some_dir" });
|
||||
expectError(error.IsDir, os.open(file_path, os.O_RDWR, mode));
|
||||
}
|
||||
|
||||
test "openat smoke test" {
|
||||
if (builtin.os.tag == .wasi) return error.SkipZigTest;
|
||||
|
||||
// TODO verify file attributes using `fstatat`
|
||||
|
||||
var tmp = tmpDir(.{});
|
||||
defer tmp.cleanup();
|
||||
|
||||
var fd: os.fd_t = undefined;
|
||||
const mode: os.mode_t = if (builtin.os.tag == .windows) 0 else 0o666;
|
||||
|
||||
// Create some file using `openat`.
|
||||
fd = try os.openat(tmp.dir.fd, "some_file", os.O_RDWR | os.O_CREAT | os.O_EXCL, mode);
|
||||
os.close(fd);
|
||||
|
||||
// Try this again with the same flags. This op should fail with error.PathAlreadyExists.
|
||||
expectError(error.PathAlreadyExists, os.openat(tmp.dir.fd, "some_file", os.O_RDWR | os.O_CREAT | os.O_EXCL, mode));
|
||||
|
||||
// Try opening without `O_EXCL` flag.
|
||||
fd = try os.openat(tmp.dir.fd, "some_file", os.O_RDWR | os.O_CREAT, mode);
|
||||
os.close(fd);
|
||||
|
||||
// Try opening as a directory which should fail.
|
||||
expectError(error.NotDir, os.openat(tmp.dir.fd, "some_file", os.O_RDWR | os.O_DIRECTORY, mode));
|
||||
|
||||
// Create some directory
|
||||
try os.mkdirat(tmp.dir.fd, "some_dir", mode);
|
||||
|
||||
// Open dir using `open`
|
||||
fd = try os.openat(tmp.dir.fd, "some_dir", os.O_RDONLY | os.O_DIRECTORY, mode);
|
||||
os.close(fd);
|
||||
|
||||
// Try opening as file which should fail.
|
||||
expectError(error.IsDir, os.openat(tmp.dir.fd, "some_dir", os.O_RDWR, mode));
|
||||
}
|
||||
|
||||
test "symlink with relative paths" {
|
||||
if (builtin.os.tag == .wasi) return error.SkipZigTest;
|
||||
|
||||
@ -27,7 +117,7 @@ test "symlink with relative paths" {
|
||||
try cwd.writeFile("file.txt", "nonsense");
|
||||
|
||||
if (builtin.os.tag == .windows) {
|
||||
try os.windows.CreateSymbolicLink(cwd.fd, "symlinked", "file.txt", false);
|
||||
try os.windows.CreateSymbolicLink(cwd.fd, &[_]u16{ 's', 'y', 'm', 'l', 'i', 'n', 'k', 'e', 'd' }, &[_]u16{ 'f', 'i', 'l', 'e', '.', 't', 'x', 't' }, false);
|
||||
} else {
|
||||
try os.symlink("file.txt", "symlinked");
|
||||
}
|
||||
@ -85,7 +175,7 @@ test "readlinkat" {
|
||||
|
||||
// create a symbolic link
|
||||
if (builtin.os.tag == .windows) {
|
||||
try os.windows.CreateSymbolicLink(tmp.dir.fd, "link", "file.txt", false);
|
||||
try os.windows.CreateSymbolicLink(tmp.dir.fd, &[_]u16{ 'l', 'i', 'n', 'k' }, &[_]u16{ 'f', 'i', 'l', 'e', '.', 't', 'x', 't' }, false);
|
||||
} else {
|
||||
try os.symlinkat("file.txt", tmp.dir.fd, "link");
|
||||
}
|
||||
|
||||
@ -25,76 +25,11 @@ pub usingnamespace @import("windows/bits.zig");
|
||||
|
||||
pub const self_process_handle = @intToPtr(HANDLE, maxInt(usize));
|
||||
|
||||
pub const CreateFileError = error{
|
||||
SharingViolation,
|
||||
PathAlreadyExists,
|
||||
|
||||
/// When any of the path components can not be found or the file component can not
|
||||
/// be found. Some operating systems distinguish between path components not found and
|
||||
/// file components not found, but they are collapsed into FileNotFound to gain
|
||||
/// consistency across operating systems.
|
||||
FileNotFound,
|
||||
|
||||
AccessDenied,
|
||||
PipeBusy,
|
||||
NameTooLong,
|
||||
|
||||
/// On Windows, file paths must be valid Unicode.
|
||||
InvalidUtf8,
|
||||
|
||||
/// On Windows, file paths cannot contain these characters:
|
||||
/// '/', '*', '?', '"', '<', '>', '|'
|
||||
BadPathName,
|
||||
|
||||
Unexpected,
|
||||
};
|
||||
|
||||
pub fn CreateFile(
|
||||
file_path: []const u8,
|
||||
desired_access: DWORD,
|
||||
share_mode: DWORD,
|
||||
lpSecurityAttributes: ?LPSECURITY_ATTRIBUTES,
|
||||
creation_disposition: DWORD,
|
||||
flags_and_attrs: DWORD,
|
||||
hTemplateFile: ?HANDLE,
|
||||
) CreateFileError!HANDLE {
|
||||
const file_path_w = try sliceToPrefixedFileW(file_path);
|
||||
return CreateFileW(file_path_w.span().ptr, desired_access, share_mode, lpSecurityAttributes, creation_disposition, flags_and_attrs, hTemplateFile);
|
||||
}
|
||||
|
||||
pub fn CreateFileW(
|
||||
file_path_w: [*:0]const u16,
|
||||
desired_access: DWORD,
|
||||
share_mode: DWORD,
|
||||
lpSecurityAttributes: ?LPSECURITY_ATTRIBUTES,
|
||||
creation_disposition: DWORD,
|
||||
flags_and_attrs: DWORD,
|
||||
hTemplateFile: ?HANDLE,
|
||||
) CreateFileError!HANDLE {
|
||||
const result = kernel32.CreateFileW(file_path_w, desired_access, share_mode, lpSecurityAttributes, creation_disposition, flags_and_attrs, hTemplateFile);
|
||||
|
||||
if (result == INVALID_HANDLE_VALUE) {
|
||||
switch (kernel32.GetLastError()) {
|
||||
.SHARING_VIOLATION => return error.SharingViolation,
|
||||
.ALREADY_EXISTS => return error.PathAlreadyExists,
|
||||
.FILE_EXISTS => return error.PathAlreadyExists,
|
||||
.FILE_NOT_FOUND => return error.FileNotFound,
|
||||
.PATH_NOT_FOUND => return error.FileNotFound,
|
||||
.ACCESS_DENIED => return error.AccessDenied,
|
||||
.PIPE_BUSY => return error.PipeBusy,
|
||||
.FILENAME_EXCED_RANGE => return error.NameTooLong,
|
||||
else => |err| return unexpectedError(err),
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
pub const OpenError = error{
|
||||
IsDir,
|
||||
NotDir,
|
||||
FileNotFound,
|
||||
NoDevice,
|
||||
SharingViolation,
|
||||
AccessDenied,
|
||||
PipeBusy,
|
||||
PathAlreadyExists,
|
||||
@ -111,15 +46,21 @@ pub const OpenFileOptions = struct {
|
||||
share_access_nonblocking: bool = false,
|
||||
creation: ULONG,
|
||||
io_mode: std.io.ModeOverride,
|
||||
/// If true, tries to open path as a directory.
|
||||
/// Defaults to false.
|
||||
open_dir: bool = false,
|
||||
/// If false, tries to open path as a reparse point without dereferencing it.
|
||||
/// Defaults to true.
|
||||
follow_symlinks: bool = true,
|
||||
};
|
||||
|
||||
/// TODO when share_access_nonblocking is false, this implementation uses
|
||||
/// untinterruptible sleep() to block. This is not the final iteration of the API.
|
||||
pub fn OpenFile(sub_path_w: []const u16, options: OpenFileOptions) OpenError!HANDLE {
|
||||
if (mem.eql(u16, sub_path_w, &[_]u16{'.'})) {
|
||||
if (mem.eql(u16, sub_path_w, &[_]u16{'.'}) and !options.open_dir) {
|
||||
return error.IsDir;
|
||||
}
|
||||
if (mem.eql(u16, sub_path_w, &[_]u16{ '.', '.' })) {
|
||||
if (mem.eql(u16, sub_path_w, &[_]u16{ '.', '.' }) and !options.open_dir) {
|
||||
return error.IsDir;
|
||||
}
|
||||
|
||||
@ -142,11 +83,13 @@ pub fn OpenFile(sub_path_w: []const u16, options: OpenFileOptions) OpenError!HAN
|
||||
.SecurityQualityOfService = null,
|
||||
};
|
||||
var io: IO_STATUS_BLOCK = undefined;
|
||||
const blocking_flag: ULONG = if (options.io_mode == .blocking) FILE_SYNCHRONOUS_IO_NONALERT else 0;
|
||||
const file_or_dir_flag: ULONG = if (options.open_dir) FILE_DIRECTORY_FILE else FILE_NON_DIRECTORY_FILE;
|
||||
// If we're not following symlinks, we need to ensure we don't pass in any synchronization flags such as FILE_SYNCHRONOUS_IO_NONALERT.
|
||||
const flags: ULONG = if (options.follow_symlinks) file_or_dir_flag | blocking_flag else file_or_dir_flag | FILE_OPEN_REPARSE_POINT;
|
||||
|
||||
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,
|
||||
options.access_mask,
|
||||
@ -156,7 +99,7 @@ pub fn OpenFile(sub_path_w: []const u16, options: OpenFileOptions) OpenError!HAN
|
||||
FILE_ATTRIBUTE_NORMAL,
|
||||
options.share_access,
|
||||
options.creation,
|
||||
FILE_NON_DIRECTORY_FILE | blocking_flag,
|
||||
flags,
|
||||
null,
|
||||
0,
|
||||
);
|
||||
@ -184,6 +127,7 @@ pub fn OpenFile(sub_path_w: []const u16, options: OpenFileOptions) OpenError!HAN
|
||||
.OBJECT_PATH_SYNTAX_BAD => unreachable,
|
||||
.OBJECT_NAME_COLLISION => return error.PathAlreadyExists,
|
||||
.FILE_IS_A_DIRECTORY => return error.IsDir,
|
||||
.NOT_A_DIRECTORY => return error.NotDir,
|
||||
else => return unexpectedStatus(rc),
|
||||
}
|
||||
}
|
||||
@ -638,27 +582,14 @@ pub const CreateSymbolicLinkError = error{
|
||||
PathAlreadyExists,
|
||||
FileNotFound,
|
||||
NameTooLong,
|
||||
InvalidUtf8,
|
||||
BadPathName,
|
||||
NoDevice,
|
||||
Unexpected,
|
||||
};
|
||||
|
||||
pub fn CreateSymbolicLink(
|
||||
dir: ?HANDLE,
|
||||
sym_link_path: []const u8,
|
||||
target_path: []const u8,
|
||||
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(dir, sym_link_path_w.span(), target_path_w.span(), is_directory);
|
||||
}
|
||||
|
||||
pub fn CreateSymbolicLinkW(
|
||||
dir: ?HANDLE,
|
||||
sym_link_path: [:0]const u16,
|
||||
target_path: [:0]const u16,
|
||||
sym_link_path: []const u16,
|
||||
target_path: []const u16,
|
||||
is_directory: bool,
|
||||
) CreateSymbolicLinkError!void {
|
||||
const SYMLINK_DATA = extern struct {
|
||||
@ -672,71 +603,19 @@ pub fn CreateSymbolicLinkW(
|
||||
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,
|
||||
};
|
||||
}
|
||||
const symlink_handle = OpenFile(sym_link_path, .{
|
||||
.access_mask = SYNCHRONIZE | GENERIC_READ | GENERIC_WRITE,
|
||||
.dir = dir,
|
||||
.creation = FILE_CREATE,
|
||||
.io_mode = .blocking,
|
||||
.open_dir = is_directory,
|
||||
}) catch |err| switch (err) {
|
||||
error.IsDir => return error.PathAlreadyExists,
|
||||
error.NotDir => unreachable,
|
||||
error.WouldBlock => unreachable,
|
||||
error.PipeBusy => unreachable,
|
||||
else => |e| return e,
|
||||
};
|
||||
defer CloseHandle(symlink_handle);
|
||||
|
||||
// prepare reparse data buffer
|
||||
@ -767,44 +646,32 @@ pub const ReadLinkError = error{
|
||||
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) {
|
||||
pub fn ReadLink(dir: ?HANDLE, sub_path_w: []const u16, out_buffer: []u8) ReadLinkError![]u8 {
|
||||
// Here, we use `NtCreateFile` to shave off one syscall if we were to use `OpenFile` wrapper.
|
||||
// With the latter, we'd need to call `NtCreateFile` twice, once for file symlink, and if that
|
||||
// failed, again for dir symlink. Omitting any mention of file/dir flags makes it possible
|
||||
// to open the symlink there and then.
|
||||
const path_len_bytes = math.cast(u16, sub_path_w.len * 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)),
|
||||
.Buffer = @intToPtr([*]u16, @ptrToInt(sub_path_w.ptr)),
|
||||
};
|
||||
|
||||
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,
|
||||
.RootDirectory = if (std.fs.path.isAbsoluteWindowsWTF16(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;
|
||||
var io: IO_STATUS_BLOCK = undefined;
|
||||
|
||||
const rc = ntdll.NtCreateFile(
|
||||
&result_handle,
|
||||
FILE_READ_ATTRIBUTES,
|
||||
@ -878,24 +745,69 @@ pub const DeleteFileError = error{
|
||||
NameTooLong,
|
||||
FileBusy,
|
||||
Unexpected,
|
||||
NotDir,
|
||||
IsDir,
|
||||
};
|
||||
|
||||
pub fn DeleteFile(filename: []const u8) DeleteFileError!void {
|
||||
const filename_w = try sliceToPrefixedFileW(filename);
|
||||
return DeleteFileW(filename_w.span().ptr);
|
||||
}
|
||||
pub const DeleteFileOptions = struct {
|
||||
dir: ?HANDLE,
|
||||
remove_dir: bool = false,
|
||||
};
|
||||
|
||||
pub fn DeleteFileW(filename: [*:0]const u16) DeleteFileError!void {
|
||||
if (kernel32.DeleteFileW(filename) == 0) {
|
||||
switch (kernel32.GetLastError()) {
|
||||
.FILE_NOT_FOUND => return error.FileNotFound,
|
||||
.PATH_NOT_FOUND => return error.FileNotFound,
|
||||
.ACCESS_DENIED => return error.AccessDenied,
|
||||
.FILENAME_EXCED_RANGE => return error.NameTooLong,
|
||||
.INVALID_PARAMETER => return error.NameTooLong,
|
||||
.SHARING_VIOLATION => return error.FileBusy,
|
||||
else => |err| return unexpectedError(err),
|
||||
}
|
||||
pub fn DeleteFile(sub_path_w: []const u16, options: DeleteFileOptions) DeleteFileError!void {
|
||||
const create_options_flags: ULONG = if (options.remove_dir)
|
||||
FILE_DELETE_ON_CLOSE | FILE_DIRECTORY_FILE | FILE_OPEN_REPARSE_POINT
|
||||
else
|
||||
FILE_DELETE_ON_CLOSE | FILE_NON_DIRECTORY_FILE | FILE_OPEN_REPARSE_POINT; // would we ever want to delete the target instead?
|
||||
|
||||
const path_len_bytes = @intCast(u16, sub_path_w.len * 2);
|
||||
var nt_name = UNICODE_STRING{
|
||||
.Length = path_len_bytes,
|
||||
.MaximumLength = path_len_bytes,
|
||||
// The Windows API makes this mutable, but it will not mutate here.
|
||||
.Buffer = @intToPtr([*]u16, @ptrToInt(sub_path_w.ptr)),
|
||||
};
|
||||
|
||||
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;
|
||||
}
|
||||
if (sub_path_w[0] == '.' and sub_path_w[1] == '.' and sub_path_w[2] == 0) {
|
||||
// Can't remove the parent directory with an open handle.
|
||||
return error.FileBusy;
|
||||
}
|
||||
|
||||
var attr = OBJECT_ATTRIBUTES{
|
||||
.Length = @sizeOf(OBJECT_ATTRIBUTES),
|
||||
.RootDirectory = if (std.fs.path.isAbsoluteWindowsWTF16(sub_path_w)) null else options.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 tmp_handle: HANDLE = undefined;
|
||||
var rc = ntdll.NtCreateFile(
|
||||
&tmp_handle,
|
||||
SYNCHRONIZE | DELETE,
|
||||
&attr,
|
||||
&io,
|
||||
null,
|
||||
0,
|
||||
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
|
||||
FILE_OPEN,
|
||||
create_options_flags,
|
||||
null,
|
||||
0,
|
||||
);
|
||||
switch (rc) {
|
||||
.SUCCESS => return CloseHandle(tmp_handle),
|
||||
.OBJECT_NAME_INVALID => unreachable,
|
||||
.OBJECT_NAME_NOT_FOUND => return error.FileNotFound,
|
||||
.INVALID_PARAMETER => unreachable,
|
||||
.FILE_IS_A_DIRECTORY => return error.IsDir,
|
||||
.NOT_A_DIRECTORY => return error.NotDir,
|
||||
else => return unexpectedStatus(rc),
|
||||
}
|
||||
}
|
||||
|
||||
@ -915,103 +827,6 @@ pub fn MoveFileExW(old_path: [*:0]const u16, new_path: [*:0]const u16, flags: DW
|
||||
}
|
||||
}
|
||||
|
||||
pub const CreateDirectoryError = error{
|
||||
NameTooLong,
|
||||
PathAlreadyExists,
|
||||
FileNotFound,
|
||||
NoDevice,
|
||||
AccessDenied,
|
||||
InvalidUtf8,
|
||||
BadPathName,
|
||||
Unexpected,
|
||||
};
|
||||
|
||||
/// Returns an open directory handle which the caller is responsible for closing with `CloseHandle`.
|
||||
pub fn CreateDirectory(dir: ?HANDLE, pathname: []const u8, sa: ?*SECURITY_ATTRIBUTES) CreateDirectoryError!HANDLE {
|
||||
const pathname_w = try sliceToPrefixedFileW(pathname);
|
||||
return CreateDirectoryW(dir, pathname_w.span().ptr, sa);
|
||||
}
|
||||
|
||||
/// Same as `CreateDirectory` except takes a WTF-16 encoded path.
|
||||
pub fn CreateDirectoryW(
|
||||
dir: ?HANDLE,
|
||||
sub_path_w: [*:0]const u16,
|
||||
sa: ?*SECURITY_ATTRIBUTES,
|
||||
) CreateDirectoryError!HANDLE {
|
||||
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 = if (sa) |ptr| ptr.lpSecurityDescriptor else null,
|
||||
.SecurityQualityOfService = null,
|
||||
};
|
||||
var io: IO_STATUS_BLOCK = undefined;
|
||||
var result_handle: HANDLE = undefined;
|
||||
const rc = ntdll.NtCreateFile(
|
||||
&result_handle,
|
||||
GENERIC_READ | SYNCHRONIZE,
|
||||
&attr,
|
||||
&io,
|
||||
null,
|
||||
FILE_ATTRIBUTE_NORMAL,
|
||||
FILE_SHARE_READ,
|
||||
FILE_CREATE,
|
||||
FILE_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT,
|
||||
null,
|
||||
0,
|
||||
);
|
||||
switch (rc) {
|
||||
.SUCCESS => return result_handle,
|
||||
.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),
|
||||
}
|
||||
}
|
||||
|
||||
pub const RemoveDirectoryError = error{
|
||||
FileNotFound,
|
||||
DirNotEmpty,
|
||||
Unexpected,
|
||||
NotDir,
|
||||
};
|
||||
|
||||
pub fn RemoveDirectory(dir_path: []const u8) RemoveDirectoryError!void {
|
||||
const dir_path_w = try sliceToPrefixedFileW(dir_path);
|
||||
return RemoveDirectoryW(dir_path_w.span().ptr);
|
||||
}
|
||||
|
||||
pub fn RemoveDirectoryW(dir_path_w: [*:0]const u16) RemoveDirectoryError!void {
|
||||
if (kernel32.RemoveDirectoryW(dir_path_w) == 0) {
|
||||
switch (kernel32.GetLastError()) {
|
||||
.PATH_NOT_FOUND => return error.FileNotFound,
|
||||
.DIR_NOT_EMPTY => return error.DirNotEmpty,
|
||||
.DIRECTORY => return error.NotDir,
|
||||
else => |err| return unexpectedError(err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub const GetStdHandleError = error{
|
||||
NoStandardHandleAttached,
|
||||
Unexpected,
|
||||
@ -1493,8 +1308,7 @@ pub fn cStrToPrefixedFileW(s: [*:0]const u8) !PathSpace {
|
||||
}
|
||||
|
||||
/// 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.
|
||||
/// it will get NT-style prefix `\??\` prepended automatically.
|
||||
pub fn sliceToPrefixedFileW(s: []const u8) !PathSpace {
|
||||
// TODO https://github.com/ziglang/zig/issues/2765
|
||||
var path_space: PathSpace = undefined;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user