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
parent
1ca5f06762
commit
4a67dd04c9
104
lib/std/fs.zig
104
lib/std/fs.zig
|
@ -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;
|
const actual_mode = mode orelse src_stat.mode;
|
||||||
|
|
||||||
// TODO this logic could be made more efficient by calling makePath, once
|
if (path.dirname(dest_path)) |dirname| {
|
||||||
// that API does not require an allocator
|
try cwd().makePath(dirname);
|
||||||
var atomic_file = make_atomic_file: while (true) {
|
}
|
||||||
const af = AtomicFile.init(dest_path, actual_mode) catch |err| switch (err) {
|
|
||||||
error.FileNotFound => {
|
var atomic_file = try AtomicFile.init(dest_path, actual_mode);
|
||||||
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;
|
|
||||||
defer atomic_file.deinit();
|
defer atomic_file.deinit();
|
||||||
|
|
||||||
const in_stream = &src_file.inStream().stream;
|
try atomic_file.file.writeFileAll(src_file, .{ .in_len = src_stat.size });
|
||||||
|
try atomic_file.file.updateTimes(src_stat.atime, src_stat.mtime);
|
||||||
var buf: [mem.page_size * 6]u8 = undefined;
|
try atomic_file.finish();
|
||||||
while (true) {
|
return PrevStatus.stale;
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Guaranteed to be atomic. However until https://patchwork.kernel.org/patch/9636735/ is
|
/// Guaranteed to be atomic.
|
||||||
/// merged and readily available,
|
/// 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
|
/// there is a possibility of power loss or application termination leaving temporary files present
|
||||||
/// in the same directory as dest_path.
|
/// in the same directory as dest_path.
|
||||||
/// Destination file will have the same mode as the source file.
|
/// 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 {
|
pub const AtomicFile = struct {
|
||||||
file: File,
|
file: File,
|
||||||
tmp_path_buf: [MAX_PATH_BYTES]u8,
|
tmp_path_buf: [MAX_PATH_BYTES]u8,
|
||||||
|
@ -268,33 +245,42 @@ pub const AtomicFile = struct {
|
||||||
|
|
||||||
pub fn finish(self: *AtomicFile) !void {
|
pub fn finish(self: *AtomicFile) !void {
|
||||||
assert(!self.finished);
|
assert(!self.finished);
|
||||||
self.file.close();
|
if (std.Target.current.os.tag == .windows) {
|
||||||
self.finished = true;
|
|
||||||
if (builtin.os.tag == .windows) {
|
|
||||||
const dest_path_w = try os.windows.sliceToPrefixedFileW(self.dest_path);
|
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));
|
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);
|
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;
|
const default_new_dir_mode = 0o755;
|
||||||
|
|
||||||
/// Create a new directory.
|
/// Create a new directory, based on an absolute path.
|
||||||
pub fn makeDir(dir_path: []const u8) !void {
|
/// Asserts that the path is absolute. See `Dir.makeDir` for a function that operates
|
||||||
return os.mkdir(dir_path, default_new_dir_mode);
|
/// 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.
|
/// Same as `makeDirAbsolute` except the parameter is a null-terminated UTF8-encoded string.
|
||||||
pub fn makeDirC(dir_path: [*:0]const u8) !void {
|
pub fn makeDirAbsoluteZ(absolute_path_z: [*:0]const u8) !void {
|
||||||
return os.mkdirC(dir_path, default_new_dir_mode);
|
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.
|
/// Same as `makeDirAbsolute` except the parameter is a null-terminated WTF-16 encoded string.
|
||||||
pub fn makeDirW(dir_path: [*:0]const u16) !void {
|
pub fn makeDirAbsoluteW(absolute_path_w: [*:0]const u16) !void {
|
||||||
return os.mkdirW(dir_path, default_new_dir_mode);
|
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.
|
/// 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);
|
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);
|
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
|
/// Calls makeDir recursively to make an entire path. Returns success if the path
|
||||||
/// already exists and is a directory.
|
/// already exists and is a directory.
|
||||||
/// This function is not atomic, and if it returns an error, the file system may
|
/// 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);
|
try os.fchdir(self.fd);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -271,6 +271,8 @@ pub const File = struct {
|
||||||
/// The `iovecs` parameter is mutable because this function needs to mutate the fields in
|
/// The `iovecs` parameter is mutable because this function needs to mutate the fields in
|
||||||
/// order to handle partial reads from the underlying OS layer.
|
/// order to handle partial reads from the underlying OS layer.
|
||||||
pub fn readvAll(self: File, iovecs: []os.iovec) ReadError!void {
|
pub fn readvAll(self: File, iovecs: []os.iovec) ReadError!void {
|
||||||
|
if (iovecs.len == 0) return;
|
||||||
|
|
||||||
var i: usize = 0;
|
var i: usize = 0;
|
||||||
while (true) {
|
while (true) {
|
||||||
var amt = try self.readv(iovecs[i..]);
|
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
|
/// The `iovecs` parameter is mutable because this function needs to mutate the fields in
|
||||||
/// order to handle partial reads from the underlying OS layer.
|
/// order to handle partial reads from the underlying OS layer.
|
||||||
pub fn preadvAll(self: File, iovecs: []const os.iovec, offset: u64) PReadError!void {
|
pub fn preadvAll(self: File, iovecs: []const os.iovec, offset: u64) PReadError!void {
|
||||||
|
if (iovecs.len == 0) return;
|
||||||
|
|
||||||
var i: usize = 0;
|
var i: usize = 0;
|
||||||
var off: usize = 0;
|
var off: usize = 0;
|
||||||
while (true) {
|
while (true) {
|
||||||
|
@ -354,6 +358,8 @@ pub const File = struct {
|
||||||
/// The `iovecs` parameter is mutable because this function needs to mutate the fields in
|
/// The `iovecs` parameter is mutable because this function needs to mutate the fields in
|
||||||
/// order to handle partial writes from the underlying OS layer.
|
/// order to handle partial writes from the underlying OS layer.
|
||||||
pub fn writevAll(self: File, iovecs: []os.iovec_const) WriteError!void {
|
pub fn writevAll(self: File, iovecs: []os.iovec_const) WriteError!void {
|
||||||
|
if (iovecs.len == 0) return;
|
||||||
|
|
||||||
var i: usize = 0;
|
var i: usize = 0;
|
||||||
while (true) {
|
while (true) {
|
||||||
var amt = try self.writev(iovecs[i..]);
|
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
|
/// The `iovecs` parameter is mutable because this function needs to mutate the fields in
|
||||||
/// order to handle partial writes from the underlying OS layer.
|
/// order to handle partial writes from the underlying OS layer.
|
||||||
pub fn pwritevAll(self: File, iovecs: []os.iovec_const, offset: usize) PWriteError!void {
|
pub fn pwritevAll(self: File, iovecs: []os.iovec_const, offset: usize) PWriteError!void {
|
||||||
|
if (iovecs.len == 0) return;
|
||||||
|
|
||||||
var i: usize = 0;
|
var i: usize = 0;
|
||||||
var off: usize = 0;
|
var off: usize = 0;
|
||||||
while (true) {
|
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 {
|
pub fn inStream(file: File) InStream {
|
||||||
return InStream{
|
return InStream{
|
||||||
.file = file,
|
.file = file,
|
||||||
|
|
|
@ -1539,12 +1539,13 @@ pub const MakeDirError = error{
|
||||||
ReadOnlyFileSystem,
|
ReadOnlyFileSystem,
|
||||||
InvalidUtf8,
|
InvalidUtf8,
|
||||||
BadPathName,
|
BadPathName,
|
||||||
|
NoDevice,
|
||||||
} || UnexpectedError;
|
} || UnexpectedError;
|
||||||
|
|
||||||
pub fn mkdirat(dir_fd: fd_t, sub_dir_path: []const u8, mode: u32) MakeDirError!void {
|
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);
|
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 {
|
} else {
|
||||||
const sub_dir_path_c = try toPosixPath(sub_dir_path);
|
const sub_dir_path_c = try toPosixPath(sub_dir_path);
|
||||||
return mkdiratC(dir_fd, &sub_dir_path_c, mode);
|
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 {
|
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);
|
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))) {
|
switch (errno(system.mkdirat(dir_fd, sub_dir_path, mode))) {
|
||||||
0 => return,
|
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.
|
/// Create a directory.
|
||||||
/// `mode` is ignored on Windows.
|
/// `mode` is ignored on Windows.
|
||||||
pub fn mkdir(dir_path: []const u8, mode: u32) MakeDirError!void {
|
pub fn mkdir(dir_path: []const u8, mode: u32) MakeDirError!void {
|
||||||
if (builtin.os.tag == .windows) {
|
if (builtin.os.tag == .windows) {
|
||||||
const dir_path_w = try windows.sliceToPrefixedFileW(dir_path);
|
const sub_dir_handle = try windows.CreateDirectory(null, dir_path, null);
|
||||||
return windows.CreateDirectoryW(&dir_path_w, null);
|
windows.CloseHandle(sub_dir_handle);
|
||||||
|
return;
|
||||||
} else {
|
} else {
|
||||||
const dir_path_c = try toPosixPath(dir_path);
|
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.
|
/// 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) {
|
if (builtin.os.tag == .windows) {
|
||||||
const dir_path_w = try windows.cStrToPrefixedFileW(dir_path);
|
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))) {
|
switch (errno(system.mkdir(dir_path, mode))) {
|
||||||
0 => return,
|
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) {
|
while (true) {
|
||||||
switch (errno(system.fchdir(dirfd))) {
|
switch (errno(system.fchdir(dirfd))) {
|
||||||
0 => return,
|
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.
|
/// 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
|
/// 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
|
/// 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.
|
/// 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
|
/// `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.
|
/// atomicity guarantees no longer apply.
|
||||||
///
|
///
|
||||||
/// Copying begins reading at `in_offset`. The input file descriptor seek position is ignored and not updated.
|
/// 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.
|
/// `flags` has different meanings per operating system; refer to the respective man pages.
|
||||||
///
|
///
|
||||||
|
@ -3599,7 +3615,7 @@ pub fn sendfile(
|
||||||
out_fd: fd_t,
|
out_fd: fd_t,
|
||||||
in_fd: fd_t,
|
in_fd: fd_t,
|
||||||
in_offset: u64,
|
in_offset: u64,
|
||||||
count: usize,
|
in_len: u64,
|
||||||
headers: []const iovec_const,
|
headers: []const iovec_const,
|
||||||
trailers: []const iovec_const,
|
trailers: []const iovec_const,
|
||||||
flags: u32,
|
flags: u32,
|
||||||
|
@ -3608,9 +3624,15 @@ pub fn sendfile(
|
||||||
var total_written: usize = 0;
|
var total_written: usize = 0;
|
||||||
|
|
||||||
// Prevents EOVERFLOW.
|
// 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) {
|
const max_count = switch (std.Target.current.os.tag) {
|
||||||
.linux => 0x7ffff000,
|
.linux => 0x7ffff000,
|
||||||
else => math.maxInt(isize),
|
else => math.maxInt(size_t),
|
||||||
};
|
};
|
||||||
|
|
||||||
switch (std.Target.current.os.tag) {
|
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.
|
// 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) {
|
while (true) {
|
||||||
var offset: off_t = @bitCast(off_t, in_offset);
|
var offset: off_t = @bitCast(off_t, in_offset);
|
||||||
|
@ -3639,10 +3661,10 @@ pub fn sendfile(
|
||||||
0 => {
|
0 => {
|
||||||
const amt = @bitCast(usize, rc);
|
const amt = @bitCast(usize, rc);
|
||||||
total_written += amt;
|
total_written += amt;
|
||||||
if (count == 0 and amt == 0) {
|
if (in_len == 0 and amt == 0) {
|
||||||
// We have detected EOF from `in_fd`.
|
// We have detected EOF from `in_fd`.
|
||||||
break;
|
break;
|
||||||
} else if (amt < count) {
|
} else if (amt < in_len) {
|
||||||
return total_written;
|
return total_written;
|
||||||
} else {
|
} else {
|
||||||
break;
|
break;
|
||||||
|
@ -3708,7 +3730,7 @@ pub fn sendfile(
|
||||||
hdtr = &hdtr_data;
|
hdtr = &hdtr_data;
|
||||||
}
|
}
|
||||||
|
|
||||||
const adjusted_count = math.min(count, max_count);
|
const adjusted_count = math.min(in_len, max_count);
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
var sbytes: off_t = undefined;
|
var sbytes: off_t = undefined;
|
||||||
|
@ -3786,7 +3808,7 @@ pub fn sendfile(
|
||||||
hdtr = &hdtr_data;
|
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) {
|
while (true) {
|
||||||
var sbytes: off_t = adjusted_count;
|
var sbytes: off_t = adjusted_count;
|
||||||
|
@ -3840,10 +3862,10 @@ pub fn sendfile(
|
||||||
rw: {
|
rw: {
|
||||||
var buf: [8 * 4096]u8 = undefined;
|
var buf: [8 * 4096]u8 = undefined;
|
||||||
// Here we match BSD behavior, making a zero count value send as many bytes as possible.
|
// 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);
|
const amt_read = try pread(in_fd, buf[0..adjusted_count], in_offset);
|
||||||
if (amt_read == 0) {
|
if (amt_read == 0) {
|
||||||
if (count == 0) {
|
if (in_len == 0) {
|
||||||
// We have detected EOF from `in_fd`.
|
// We have detected EOF from `in_fd`.
|
||||||
break :rw;
|
break :rw;
|
||||||
} else {
|
} else {
|
||||||
|
@ -3852,7 +3874,7 @@ pub fn sendfile(
|
||||||
}
|
}
|
||||||
const amt_written = try write(out_fd, buf[0..amt_read]);
|
const amt_written = try write(out_fd, buf[0..amt_read]);
|
||||||
total_written += amt_written;
|
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) {
|
if (trailers.len != 0) {
|
||||||
|
|
|
@ -45,7 +45,7 @@ fn testThreadIdFn(thread_id: *Thread.Id) void {
|
||||||
}
|
}
|
||||||
|
|
||||||
test "sendfile" {
|
test "sendfile" {
|
||||||
try fs.makePath(a, "os_test_tmp");
|
try fs.cwd().makePath("os_test_tmp");
|
||||||
defer fs.deleteTree("os_test_tmp") catch {};
|
defer fs.deleteTree("os_test_tmp") catch {};
|
||||||
|
|
||||||
var dir = try fs.cwd().openDirList("os_test_tmp");
|
var dir = try fs.cwd().openDirList("os_test_tmp");
|
||||||
|
@ -74,7 +74,9 @@ test "sendfile" {
|
||||||
|
|
||||||
const header1 = "header1\n";
|
const header1 = "header1\n";
|
||||||
const header2 = "second header\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_base = header1,
|
||||||
.iov_len = header1.len,
|
.iov_len = header1.len,
|
||||||
|
@ -83,11 +85,6 @@ test "sendfile" {
|
||||||
.iov_base = header2,
|
.iov_base = header2,
|
||||||
.iov_len = header2.len,
|
.iov_len = header2.len,
|
||||||
},
|
},
|
||||||
};
|
|
||||||
|
|
||||||
const trailer1 = "trailer1\n";
|
|
||||||
const trailer2 = "second trailer\n";
|
|
||||||
var trailers = [_]os.iovec_const{
|
|
||||||
.{
|
.{
|
||||||
.iov_base = trailer1,
|
.iov_base = trailer1,
|
||||||
.iov_len = trailer1.len,
|
.iov_len = trailer1.len,
|
||||||
|
@ -99,59 +96,16 @@ test "sendfile" {
|
||||||
};
|
};
|
||||||
|
|
||||||
var written_buf: [header1.len + header2.len + 10 + trailer1.len + trailer2.len]u8 = undefined;
|
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);
|
try dest_file.preadAll(&written_buf, 0);
|
||||||
expect(mem.eql(u8, &written_buf, "header1\nsecond header\nine1\nsecontrailer1\nsecond trailer\n"));
|
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" {
|
test "std.Thread.getCurrentId" {
|
||||||
if (builtin.single_threaded) return error.SkipZigTest;
|
if (builtin.single_threaded) return error.SkipZigTest;
|
||||||
|
|
||||||
|
|
|
@ -337,7 +337,7 @@ pub fn GetQueuedCompletionStatus(
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn CloseHandle(hObject: HANDLE) void {
|
pub fn CloseHandle(hObject: HANDLE) void {
|
||||||
assert(kernel32.CloseHandle(hObject) != 0);
|
assert(ntdll.NtClose(hObject) == .SUCCESS);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn FindClose(hFindFile: HANDLE) void {
|
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{
|
pub const CreateDirectoryError = error{
|
||||||
|
NameTooLong,
|
||||||
PathAlreadyExists,
|
PathAlreadyExists,
|
||||||
FileNotFound,
|
FileNotFound,
|
||||||
|
NoDevice,
|
||||||
|
AccessDenied,
|
||||||
Unexpected,
|
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);
|
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 {
|
/// Same as `CreateDirectory` except takes a WTF-16 encoded path.
|
||||||
if (kernel32.CreateDirectoryW(pathname, attrs) == 0) {
|
pub fn CreateDirectoryW(
|
||||||
switch (kernel32.GetLastError()) {
|
dir: ?HANDLE,
|
||||||
.ALREADY_EXISTS => return error.PathAlreadyExists,
|
sub_path_w: [*:0]const u16,
|
||||||
.PATH_NOT_FOUND => return error.FileNotFound,
|
sa: ?*SECURITY_ATTRIBUTES,
|
||||||
else => |err| return unexpectedError(err),
|
) 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),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue