breaking changes to std.fs, std.os

* improve `std.fs.AtomicFile` to use sendfile()
   - also fix AtomicFile cleanup not destroying tmp files under some
     error conditions
 * improve `std.fs.updateFile` to take advantage of the new `makePath`
   which no longer needs an Allocator.
 * rename std.fs.makeDir to std.fs.makeDirAbsolute
 * rename std.fs.Dir.makeDirC to std.fs.Dir.makeDirZ
 * add std.fs.Dir.makeDirW and provide Windows implementation of
   std.os.mkdirat. std.os.windows.CreateDirectory is now implemented
   by calling ntdll, supports an optional root directory handle,
   and returns an open directory handle. Its error set has a few more
   errors in it.
 * rename std.fs.Dir.changeTo to std.fs.Dir.setAsCwd
 * fix std.fs.File.writevAll and related functions when len 0 iovecs
   supplied.
 * introduce `std.fs.File.writeFileAll`, exposing a convenient
   cross-platform API on top of sendfile().
 * `NoDevice` added to std.os.MakeDirError error set.
 * std.os.fchdir gets a smaller error set.
 * std.os.windows.CloseHandle is implemented with ntdll call rather than
   kernel32.
master
Andrew Kelley 2020-03-03 15:01:08 -05:00
parent 1ca5f06762
commit 4a67dd04c9
No known key found for this signature in database
GPG Key ID: 7C5F548F728501A9
5 changed files with 259 additions and 143 deletions

View File

@ -123,47 +123,21 @@ pub fn updateFileMode(source_path: []const u8, dest_path: []const u8, mode: ?Fil
}
const actual_mode = mode orelse src_stat.mode;
// TODO this logic could be made more efficient by calling makePath, once
// that API does not require an allocator
var atomic_file = make_atomic_file: while (true) {
const af = AtomicFile.init(dest_path, actual_mode) catch |err| switch (err) {
error.FileNotFound => {
var p = dest_path;
while (path.dirname(p)) |dirname| {
makeDir(dirname) catch |e| switch (e) {
error.FileNotFound => {
p = dirname;
continue;
},
else => return e,
};
continue :make_atomic_file;
} else {
return err;
}
},
else => |e| return e,
};
break af;
} else unreachable;
if (path.dirname(dest_path)) |dirname| {
try cwd().makePath(dirname);
}
var atomic_file = try AtomicFile.init(dest_path, actual_mode);
defer atomic_file.deinit();
const in_stream = &src_file.inStream().stream;
var buf: [mem.page_size * 6]u8 = undefined;
while (true) {
const amt = try in_stream.readFull(buf[0..]);
try atomic_file.file.writeAll(buf[0..amt]);
if (amt != buf.len) {
try atomic_file.file.updateTimes(src_stat.atime, src_stat.mtime);
try atomic_file.finish();
return PrevStatus.stale;
}
}
try atomic_file.file.writeFileAll(src_file, .{ .in_len = src_stat.size });
try atomic_file.file.updateTimes(src_stat.atime, src_stat.mtime);
try atomic_file.finish();
return PrevStatus.stale;
}
/// Guaranteed to be atomic. However until https://patchwork.kernel.org/patch/9636735/ is
/// merged and readily available,
/// Guaranteed to be atomic.
/// On Linux, until https://patchwork.kernel.org/patch/9636735/ is merged and readily available,
/// there is a possibility of power loss or application termination leaving temporary files present
/// in the same directory as dest_path.
/// Destination file will have the same mode as the source file.
@ -207,6 +181,9 @@ pub fn copyFileMode(source_path: []const u8, dest_path: []const u8, mode: File.M
}
}
/// TODO update this API to avoid a getrandom syscall for every operation. It
/// should accept a random interface.
/// TODO rework this to integrate with Dir
pub const AtomicFile = struct {
file: File,
tmp_path_buf: [MAX_PATH_BYTES]u8,
@ -268,33 +245,42 @@ pub const AtomicFile = struct {
pub fn finish(self: *AtomicFile) !void {
assert(!self.finished);
self.file.close();
self.finished = true;
if (builtin.os.tag == .windows) {
if (std.Target.current.os.tag == .windows) {
const dest_path_w = try os.windows.sliceToPrefixedFileW(self.dest_path);
const tmp_path_w = try os.windows.cStrToPrefixedFileW(@ptrCast([*:0]u8, &self.tmp_path_buf));
self.file.close();
self.finished = true;
return os.renameW(&tmp_path_w, &dest_path_w);
} else {
const dest_path_c = try os.toPosixPath(self.dest_path);
self.file.close();
self.finished = true;
return os.renameC(@ptrCast([*:0]u8, &self.tmp_path_buf), &dest_path_c);
}
const dest_path_c = try os.toPosixPath(self.dest_path);
return os.renameC(@ptrCast([*:0]u8, &self.tmp_path_buf), &dest_path_c);
}
};
const default_new_dir_mode = 0o755;
/// Create a new directory.
pub fn makeDir(dir_path: []const u8) !void {
return os.mkdir(dir_path, default_new_dir_mode);
/// Create a new directory, based on an absolute path.
/// Asserts that the path is absolute. See `Dir.makeDir` for a function that operates
/// on both absolute and relative paths.
pub fn makeDirAbsolute(absolute_path: []const u8) !void {
assert(path.isAbsoluteC(absolute_path));
return os.mkdir(absolute_path, default_new_dir_mode);
}
/// Same as `makeDir` except the parameter is a null-terminated UTF8-encoded string.
pub fn makeDirC(dir_path: [*:0]const u8) !void {
return os.mkdirC(dir_path, default_new_dir_mode);
/// Same as `makeDirAbsolute` except the parameter is a null-terminated UTF8-encoded string.
pub fn makeDirAbsoluteZ(absolute_path_z: [*:0]const u8) !void {
assert(path.isAbsoluteC(absolute_path_z));
return os.mkdirZ(absolute_path_z, default_new_dir_mode);
}
/// Same as `makeDir` except the parameter is a null-terminated UTF16LE-encoded string.
pub fn makeDirW(dir_path: [*:0]const u16) !void {
return os.mkdirW(dir_path, default_new_dir_mode);
/// 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);
}
/// Returns `error.DirNotEmpty` if the directory is not empty.
@ -847,10 +833,15 @@ pub const Dir = struct {
try os.mkdirat(self.fd, sub_path, default_new_dir_mode);
}
pub fn makeDirC(self: Dir, sub_path: [*:0]const u8) !void {
pub fn makeDirZ(self: Dir, sub_path: [*:0]const u8) !void {
try os.mkdiratC(self.fd, sub_path, default_new_dir_mode);
}
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);
}
/// Calls makeDir recursively to make an entire path. Returns success if the path
/// already exists and is a directory.
/// This function is not atomic, and if it returns an error, the file system may
@ -885,7 +876,14 @@ pub const Dir = struct {
}
}
pub fn changeTo(self: Dir) !void {
/// Changes the current working directory to the open directory handle.
/// This modifies global state and can have surprising effects in multi-
/// threaded applications. Most applications and especially libraries should
/// not call this function as a general rule, however it can have use cases
/// in, for example, implementing a shell, or child process execution.
/// Not all targets support this. For example, WASI does not have the concept
/// of a current working directory.
pub fn setAsCwd(self: Dir) !void {
try os.fchdir(self.fd);
}

View File

@ -271,6 +271,8 @@ pub const File = struct {
/// The `iovecs` parameter is mutable because this function needs to mutate the fields in
/// order to handle partial reads from the underlying OS layer.
pub fn readvAll(self: File, iovecs: []os.iovec) ReadError!void {
if (iovecs.len == 0) return;
var i: usize = 0;
while (true) {
var amt = try self.readv(iovecs[i..]);
@ -295,6 +297,8 @@ pub const File = struct {
/// The `iovecs` parameter is mutable because this function needs to mutate the fields in
/// order to handle partial reads from the underlying OS layer.
pub fn preadvAll(self: File, iovecs: []const os.iovec, offset: u64) PReadError!void {
if (iovecs.len == 0) return;
var i: usize = 0;
var off: usize = 0;
while (true) {
@ -354,6 +358,8 @@ pub const File = struct {
/// The `iovecs` parameter is mutable because this function needs to mutate the fields in
/// order to handle partial writes from the underlying OS layer.
pub fn writevAll(self: File, iovecs: []os.iovec_const) WriteError!void {
if (iovecs.len == 0) return;
var i: usize = 0;
while (true) {
var amt = try self.writev(iovecs[i..]);
@ -378,6 +384,8 @@ pub const File = struct {
/// The `iovecs` parameter is mutable because this function needs to mutate the fields in
/// order to handle partial writes from the underlying OS layer.
pub fn pwritevAll(self: File, iovecs: []os.iovec_const, offset: usize) PWriteError!void {
if (iovecs.len == 0) return;
var i: usize = 0;
var off: usize = 0;
while (true) {
@ -393,6 +401,89 @@ pub const File = struct {
}
}
pub const WriteFileOptions = struct {
in_offset: u64 = 0,
/// `null` means the entire file. `0` means no bytes from the file.
/// When this is `null`, trailers must be sent in a separate writev() call
/// due to a flaw in the BSD sendfile API. Other operating systems, such as
/// Linux, already do this anyway due to API limitations.
/// If the size of the source file is known, passing the size here will save one syscall.
in_len: ?u64 = null,
headers_and_trailers: []os.iovec_const = &[0]os.iovec_const{},
/// The trailer count is inferred from `headers_and_trailers.len - header_count`
header_count: usize = 0,
};
pub const WriteFileError = os.SendFileError;
/// TODO integrate with async I/O
pub fn writeFileAll(self: File, in_file: File, args: WriteFileOptions) WriteFileError!void {
const count = blk: {
if (args.in_len) |l| {
if (l == 0) {
return self.writevAll(args.headers_and_trailers);
} else {
break :blk l;
}
} else {
break :blk 0;
}
};
const headers = args.headers_and_trailers[0..args.header_count];
const trailers = args.headers_and_trailers[args.header_count..];
const zero_iovec = &[0]os.iovec_const{};
// When reading the whole file, we cannot put the trailers in the sendfile() syscall,
// because we have no way to determine whether a partial write is past the end of the file or not.
const trls = if (count == 0) zero_iovec else trailers;
const offset = args.in_offset;
const out_fd = self.handle;
const in_fd = in_file.handle;
const flags = 0;
var amt: usize = 0;
hdrs: {
var i: usize = 0;
while (i < headers.len) {
amt = try os.sendfile(out_fd, in_fd, offset, count, headers[i..], trls, flags);
while (amt >= headers[i].iov_len) {
amt -= headers[i].iov_len;
i += 1;
if (i >= headers.len) break :hdrs;
}
headers[i].iov_base += amt;
headers[i].iov_len -= amt;
}
}
if (count == 0) {
var off: u64 = amt;
while (true) {
amt = try os.sendfile(out_fd, in_fd, offset + off, 0, zero_iovec, zero_iovec, flags);
if (amt == 0) break;
off += amt;
}
} else {
var off: u64 = amt;
while (off < count) {
amt = try os.sendfile(out_fd, in_fd, offset + off, count - off, zero_iovec, trailers, flags);
off += amt;
}
amt = @intCast(usize, off - count);
}
var i: usize = 0;
while (i < trailers.len) {
while (amt >= headers[i].iov_len) {
amt -= trailers[i].iov_len;
i += 1;
if (i >= trailers.len) return;
}
trailers[i].iov_base += amt;
trailers[i].iov_len -= amt;
amt = try os.writev(self.handle, trailers[i..]);
}
}
pub fn inStream(file: File) InStream {
return InStream{
.file = file,

View File

@ -1539,12 +1539,13 @@ pub const MakeDirError = error{
ReadOnlyFileSystem,
InvalidUtf8,
BadPathName,
NoDevice,
} || UnexpectedError;
pub fn mkdirat(dir_fd: fd_t, sub_dir_path: []const u8, mode: u32) MakeDirError!void {
if (builtin.os == .windows) {
if (builtin.os.tag == .windows) {
const sub_dir_path_w = try windows.sliceToPrefixedFileW(sub_dir_path);
@compileError("TODO implement mkdirat for Windows");
return mkdiratW(dir_fd, &sub_dir_path_w, mode);
} else {
const sub_dir_path_c = try toPosixPath(sub_dir_path);
return mkdiratC(dir_fd, &sub_dir_path_c, mode);
@ -1552,9 +1553,9 @@ pub fn mkdirat(dir_fd: fd_t, sub_dir_path: []const u8, mode: u32) MakeDirError!v
}
pub fn mkdiratC(dir_fd: fd_t, sub_dir_path: [*:0]const u8, mode: u32) MakeDirError!void {
if (builtin.os == .windows) {
if (builtin.os.tag == .windows) {
const sub_dir_path_w = try windows.cStrToPrefixedFileW(sub_dir_path);
@compileError("TODO implement mkdiratC for Windows");
return mkdiratW(dir_fd, &sub_dir_path_w, mode);
}
switch (errno(system.mkdirat(dir_fd, sub_dir_path, mode))) {
0 => return,
@ -1576,23 +1577,31 @@ pub fn mkdiratC(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);
windows.CloseHandle(sub_dir_handle);
}
/// Create a directory.
/// `mode` is ignored on Windows.
pub fn mkdir(dir_path: []const u8, mode: u32) MakeDirError!void {
if (builtin.os.tag == .windows) {
const dir_path_w = try windows.sliceToPrefixedFileW(dir_path);
return windows.CreateDirectoryW(&dir_path_w, null);
const sub_dir_handle = try windows.CreateDirectory(null, dir_path, null);
windows.CloseHandle(sub_dir_handle);
return;
} else {
const dir_path_c = try toPosixPath(dir_path);
return mkdirC(&dir_path_c, mode);
return mkdirZ(&dir_path_c, mode);
}
}
/// Same as `mkdir` but the parameter is a null-terminated UTF8-encoded string.
pub fn mkdirC(dir_path: [*:0]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);
return windows.CreateDirectoryW(&dir_path_w, null);
const sub_dir_handle = try windows.CreateDirectoryW(null, &dir_path_w, null);
windows.CloseHandle(sub_dir_handle);
return;
}
switch (errno(system.mkdir(dir_path, mode))) {
0 => return,
@ -1705,7 +1714,13 @@ pub fn chdirC(dir_path: [*:0]const u8) ChangeCurDirError!void {
}
}
pub fn fchdir(dirfd: fd_t) ChangeCurDirError!void {
pub const FchdirError = error{
AccessDenied,
NotDir,
FileSystem,
} || UnexpectedError;
pub fn fchdir(dirfd: fd_t) FchdirError!void {
while (true) {
switch (errno(system.fchdir(dirfd))) {
0 => return,
@ -3564,12 +3579,12 @@ fn count_iovec_bytes(iovs: []const iovec_const) usize {
}
/// Transfer data between file descriptors, with optional headers and trailers.
/// Returns the number of bytes written. This will be zero if `in_offset` falls beyond the end of the file.
/// Returns the number of bytes written, which can be zero.
///
/// The `sendfile` call copies `count` bytes from one file descriptor to another. When possible,
/// The `sendfile` call copies `in_len` bytes from one file descriptor to another. When possible,
/// this is done within the operating system kernel, which can provide better performance
/// characteristics than transferring data from kernel to user space and back, such as with
/// `read` and `write` calls. When `count` is `0`, it means to copy until the end of the input file has been
/// `read` and `write` calls. When `in_len` is `0`, it means to copy until the end of the input file has been
/// reached. Note, however, that partial writes are still possible in this case.
///
/// `in_fd` must be a file descriptor opened for reading, and `out_fd` must be a file descriptor
@ -3578,7 +3593,8 @@ fn count_iovec_bytes(iovs: []const iovec_const) usize {
/// atomicity guarantees no longer apply.
///
/// Copying begins reading at `in_offset`. The input file descriptor seek position is ignored and not updated.
/// If the output file descriptor has a seek position, it is updated as bytes are written.
/// If the output file descriptor has a seek position, it is updated as bytes are written. When
/// `in_offset` is past the end of the input file, it successfully reads 0 bytes.
///
/// `flags` has different meanings per operating system; refer to the respective man pages.
///
@ -3599,7 +3615,7 @@ pub fn sendfile(
out_fd: fd_t,
in_fd: fd_t,
in_offset: u64,
count: usize,
in_len: u64,
headers: []const iovec_const,
trailers: []const iovec_const,
flags: u32,
@ -3608,9 +3624,15 @@ pub fn sendfile(
var total_written: usize = 0;
// Prevents EOVERFLOW.
const size_t = @Type(std.builtin.TypeInfo{
.Int = .{
.is_signed = false,
.bits = @typeInfo(usize).Int.bits - 1,
},
});
const max_count = switch (std.Target.current.os.tag) {
.linux => 0x7ffff000,
else => math.maxInt(isize),
else => math.maxInt(size_t),
};
switch (std.Target.current.os.tag) {
@ -3630,7 +3652,7 @@ pub fn sendfile(
}
// Here we match BSD behavior, making a zero count value send as many bytes as possible.
const adjusted_count = if (count == 0) max_count else math.min(count, max_count);
const adjusted_count = if (in_len == 0) max_count else math.min(in_len, @as(size_t, max_count));
while (true) {
var offset: off_t = @bitCast(off_t, in_offset);
@ -3639,10 +3661,10 @@ pub fn sendfile(
0 => {
const amt = @bitCast(usize, rc);
total_written += amt;
if (count == 0 and amt == 0) {
if (in_len == 0 and amt == 0) {
// We have detected EOF from `in_fd`.
break;
} else if (amt < count) {
} else if (amt < in_len) {
return total_written;
} else {
break;
@ -3708,7 +3730,7 @@ pub fn sendfile(
hdtr = &hdtr_data;
}
const adjusted_count = math.min(count, max_count);
const adjusted_count = math.min(in_len, max_count);
while (true) {
var sbytes: off_t = undefined;
@ -3786,7 +3808,7 @@ pub fn sendfile(
hdtr = &hdtr_data;
}
const adjusted_count = math.min(count, @as(u63, max_count));
const adjusted_count = math.min(in_len, @as(u63, max_count));
while (true) {
var sbytes: off_t = adjusted_count;
@ -3840,10 +3862,10 @@ pub fn sendfile(
rw: {
var buf: [8 * 4096]u8 = undefined;
// Here we match BSD behavior, making a zero count value send as many bytes as possible.
const adjusted_count = if (count == 0) buf.len else math.min(buf.len, count);
const adjusted_count = if (in_len == 0) buf.len else math.min(buf.len, in_len);
const amt_read = try pread(in_fd, buf[0..adjusted_count], in_offset);
if (amt_read == 0) {
if (count == 0) {
if (in_len == 0) {
// We have detected EOF from `in_fd`.
break :rw;
} else {
@ -3852,7 +3874,7 @@ pub fn sendfile(
}
const amt_written = try write(out_fd, buf[0..amt_read]);
total_written += amt_written;
if (amt_written < count or count == 0) return total_written;
if (amt_written < in_len or in_len == 0) return total_written;
}
if (trailers.len != 0) {

View File

@ -45,7 +45,7 @@ fn testThreadIdFn(thread_id: *Thread.Id) void {
}
test "sendfile" {
try fs.makePath(a, "os_test_tmp");
try fs.cwd().makePath("os_test_tmp");
defer fs.deleteTree("os_test_tmp") catch {};
var dir = try fs.cwd().openDirList("os_test_tmp");
@ -74,7 +74,9 @@ test "sendfile" {
const header1 = "header1\n";
const header2 = "second header\n";
var headers = [_]os.iovec_const{
const trailer1 = "trailer1\n";
const trailer2 = "second trailer\n";
var hdtr = [_]os.iovec_const{
.{
.iov_base = header1,
.iov_len = header1.len,
@ -83,11 +85,6 @@ test "sendfile" {
.iov_base = header2,
.iov_len = header2.len,
},
};
const trailer1 = "trailer1\n";
const trailer2 = "second trailer\n";
var trailers = [_]os.iovec_const{
.{
.iov_base = trailer1,
.iov_len = trailer1.len,
@ -99,59 +96,16 @@ test "sendfile" {
};
var written_buf: [header1.len + header2.len + 10 + trailer1.len + trailer2.len]u8 = undefined;
try sendfileAll(dest_file.handle, src_file.handle, 1, 10, &headers, &trailers, 0);
try dest_file.writeFileAll(src_file, .{
.in_offset = 1,
.in_len = 10,
.headers_and_trailers = &hdtr,
.header_count = 2,
});
try dest_file.preadAll(&written_buf, 0);
expect(mem.eql(u8, &written_buf, "header1\nsecond header\nine1\nsecontrailer1\nsecond trailer\n"));
}
fn sendfileAll(
out_fd: os.fd_t,
in_fd: os.fd_t,
offset: u64,
count: usize,
headers: []os.iovec_const,
trailers: []os.iovec_const,
flags: u32,
) os.SendFileError!void {
var amt: usize = undefined;
hdrs: {
var i: usize = 0;
while (i < headers.len) {
amt = try os.sendfile(out_fd, in_fd, offset, count, headers[i..], trailers, flags);
while (amt >= headers[i].iov_len) {
amt -= headers[i].iov_len;
i += 1;
if (i >= headers.len) break :hdrs;
}
headers[i].iov_base += amt;
headers[i].iov_len -= amt;
}
}
var off = amt;
while (off < count) {
amt = try os.sendfile(out_fd, in_fd, offset + off, count - off, &[0]os.iovec_const{}, trailers, flags);
off += amt;
}
amt = off - count;
var i: usize = 0;
while (i < trailers.len) {
while (amt >= headers[i].iov_len) {
amt -= trailers[i].iov_len;
i += 1;
if (i >= trailers.len) return;
}
trailers[i].iov_base += amt;
trailers[i].iov_len -= amt;
if (std.Target.current.os.tag == .windows) {
amt = try os.writev(out_fd, trailers[i..]);
} else {
// Here we must use send because it's the only way to give the flags.
amt = try os.send(out_fd, trailers[i].iov_base[0..trailers[i].iov_len], flags);
}
}
}
test "std.Thread.getCurrentId" {
if (builtin.single_threaded) return error.SkipZigTest;

View File

@ -337,7 +337,7 @@ pub fn GetQueuedCompletionStatus(
}
pub fn CloseHandle(hObject: HANDLE) void {
assert(kernel32.CloseHandle(hObject) != 0);
assert(ntdll.NtClose(hObject) == .SUCCESS);
}
pub fn FindClose(hFindFile: HANDLE) void {
@ -586,23 +586,74 @@ pub fn MoveFileExW(old_path: [*:0]const u16, new_path: [*:0]const u16, flags: DW
}
pub const CreateDirectoryError = error{
NameTooLong,
PathAlreadyExists,
FileNotFound,
NoDevice,
AccessDenied,
Unexpected,
};
pub fn CreateDirectory(pathname: []const u8, attrs: ?*SECURITY_ATTRIBUTES) CreateDirectoryError!void {
/// 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(&pathname_w, attrs);
return CreateDirectoryW(dir, &pathname_w, sa);
}
pub fn CreateDirectoryW(pathname: [*:0]const u16, attrs: ?*SECURITY_ATTRIBUTES) CreateDirectoryError!void {
if (kernel32.CreateDirectoryW(pathname, attrs) == 0) {
switch (kernel32.GetLastError()) {
.ALREADY_EXISTS => return error.PathAlreadyExists,
.PATH_NOT_FOUND => return error.FileNotFound,
else => |err| return unexpectedError(err),
}
/// 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.toSliceConst(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)),
};
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),
}
}