commit f6f0b019bee7910702e113be6d33865c592afa2a (tree)
parent 582db68a157520a0cf7777807f466d1f6a2e31e7
Author: Andrew Kelley <andrew@ziglang.org>
Date: Tue, 3 Mar 2020 17:05:14 -0500
Merge pull request #4618 from ziglang/daurnimator-paths
improvements to std.fs, std.os
Diffstat:
14 files changed, 395 insertions(+), 196 deletions(-)
diff --git a/doc/docgen.zig b/doc/docgen.zig
@@ -50,7 +50,7 @@ pub fn main() !void {
var tokenizer = Tokenizer.init(in_file_name, input_file_bytes);
var toc = try genToc(allocator, &tokenizer);
- try fs.makePath(allocator, tmp_dir_name);
+ try fs.cwd().makePath(tmp_dir_name);
defer fs.deleteTree(tmp_dir_name) catch {};
try genHtml(allocator, &tokenizer, &toc, &buffered_out_stream.stream, zig_exe);
diff --git a/lib/std/build.zig b/lib/std/build.zig
@@ -763,7 +763,7 @@ pub const Builder = struct {
}
pub fn makePath(self: *Builder, path: []const u8) !void {
- fs.makePath(self.allocator, self.pathFromRoot(path)) catch |err| {
+ fs.cwd().makePath(self.pathFromRoot(path)) catch |err| {
warn("Unable to create path {}: {}\n", .{ path, @errorName(err) });
return err;
};
@@ -2311,7 +2311,7 @@ pub const InstallDirStep = struct {
const rel_path = entry.path[full_src_dir.len + 1 ..];
const dest_path = try fs.path.join(self.builder.allocator, &[_][]const u8{ dest_prefix, rel_path });
switch (entry.kind) {
- .Directory => try fs.makePath(self.builder.allocator, dest_path),
+ .Directory => try fs.cwd().makePath(dest_path),
.File => try self.builder.updateFile(entry.path, dest_path),
else => continue,
}
diff --git a/lib/std/build/write_file.zig b/lib/std/build/write_file.zig
@@ -74,7 +74,7 @@ pub const WriteFileStep = struct {
&hash_basename,
});
// TODO replace with something like fs.makePathAndOpenDir
- fs.makePath(self.builder.allocator, self.output_dir) catch |err| {
+ fs.cwd().makePath(self.output_dir) catch |err| {
warn("unable to make path {}: {}\n", .{ self.output_dir, @errorName(err) });
return err;
};
diff --git a/lib/std/c.zig b/lib/std/c.zig
@@ -75,6 +75,7 @@ pub extern "c" fn isatty(fd: fd_t) c_int;
pub extern "c" fn close(fd: fd_t) c_int;
pub extern "c" fn fstat(fd: fd_t, buf: *Stat) c_int;
pub extern "c" fn @"fstat$INODE64"(fd: fd_t, buf: *Stat) c_int;
+pub extern "c" fn fstatat(dirfd: fd_t, path: [*:0]const u8, stat_buf: *Stat, flags: u32) c_int;
pub extern "c" fn lseek(fd: fd_t, offset: off_t, whence: c_int) off_t;
pub extern "c" fn open(path: [*:0]const u8, oflag: c_uint, ...) c_int;
pub extern "c" fn openat(fd: c_int, path: [*:0]const u8, oflag: c_uint, ...) c_int;
@@ -101,9 +102,11 @@ pub extern "c" fn faccessat(dirfd: fd_t, path: [*:0]const u8, mode: c_uint, flag
pub extern "c" fn pipe(fds: *[2]fd_t) c_int;
pub extern "c" fn pipe2(fds: *[2]fd_t, flags: u32) c_int;
pub extern "c" fn mkdir(path: [*:0]const u8, mode: c_uint) c_int;
+pub extern "c" fn mkdirat(dirfd: fd_t, path: [*:0]const u8, mode: u32) c_int;
pub extern "c" fn symlink(existing: [*:0]const u8, new: [*:0]const u8) c_int;
pub extern "c" fn rename(old: [*:0]const u8, new: [*:0]const u8) c_int;
pub extern "c" fn chdir(path: [*:0]const u8) c_int;
+pub extern "c" fn fchdir(fd: fd_t) c_int;
pub extern "c" fn execve(path: [*:0]const u8, argv: [*:null]const ?[*:0]const u8, envp: [*:null]const ?[*:0]const u8) c_int;
pub extern "c" fn dup(fd: fd_t) c_int;
pub extern "c" fn dup2(old_fd: fd_t, new_fd: fd_t) c_int;
diff --git a/lib/std/fs.zig b/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;
- // 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;
- defer atomic_file.deinit();
+ if (path.dirname(dest_path)) |dirname| {
+ try cwd().makePath(dirname);
+ }
- const in_stream = &src_file.inStream().stream;
+ var atomic_file = try AtomicFile.init(dest_path, actual_mode);
+ defer atomic_file.deinit();
- 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,70 +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);
-}
-
-/// 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
-/// have been modified regardless.
-/// TODO determine if we can remove the allocator requirement from this function
-pub fn makePath(allocator: *Allocator, full_path: []const u8) !void {
- const resolved_path = try path.resolve(allocator, &[_][]const u8{full_path});
- defer allocator.free(resolved_path);
-
- var end_index: usize = resolved_path.len;
- while (true) {
- makeDir(resolved_path[0..end_index]) catch |err| switch (err) {
- error.PathAlreadyExists => {
- // TODO stat the file and return an error if it's not a directory
- // this is important because otherwise a dangling symlink
- // could cause an infinite loop
- if (end_index == resolved_path.len) return;
- },
- error.FileNotFound => {
- // march end_index backward until next path component
- while (true) {
- end_index -= 1;
- if (path.isSep(resolved_path[end_index])) break;
- }
- continue;
- },
- else => return err,
- };
- if (end_index == resolved_path.len) return;
- // march end_index forward until next path component
- while (true) {
- end_index += 1;
- if (end_index == resolved_path.len or path.isSep(resolved_path[end_index])) break;
- }
- }
+/// 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.
@@ -709,7 +658,6 @@ pub const Dir = struct {
/// Call `File.close` to release the resource.
/// Asserts that the path parameter has no null bytes.
pub fn openFile(self: Dir, sub_path: []const u8, flags: File.OpenFlags) File.OpenError!File {
- if (std.debug.runtime_safety) for (sub_path) |byte| assert(byte != 0);
if (builtin.os.tag == .windows) {
const path_w = try os.windows.sliceToPrefixedFileW(sub_path);
return self.openFileW(&path_w, flags);
@@ -759,7 +707,6 @@ pub const Dir = struct {
/// Call `File.close` on the result when done.
/// Asserts that the path parameter has no null bytes.
pub fn createFile(self: Dir, sub_path: []const u8, flags: File.CreateFlags) File.OpenError!File {
- if (std.debug.runtime_safety) for (sub_path) |byte| assert(byte != 0);
if (builtin.os.tag == .windows) {
const path_w = try os.windows.sliceToPrefixedFileW(sub_path);
return self.createFileW(&path_w, flags);
@@ -882,6 +829,64 @@ pub const Dir = struct {
}
}
+ pub fn makeDir(self: Dir, sub_path: []const u8) !void {
+ try os.mkdirat(self.fd, sub_path, default_new_dir_mode);
+ }
+
+ 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
+ /// have been modified regardless.
+ pub fn makePath(self: Dir, sub_path: []const u8) !void {
+ var end_index: usize = sub_path.len;
+ while (true) {
+ self.makeDir(sub_path[0..end_index]) catch |err| switch (err) {
+ error.PathAlreadyExists => {
+ // TODO stat the file and return an error if it's not a directory
+ // this is important because otherwise a dangling symlink
+ // could cause an infinite loop
+ if (end_index == sub_path.len) return;
+ },
+ error.FileNotFound => {
+ if (end_index == 0) return err;
+ // march end_index backward until next path component
+ while (true) {
+ end_index -= 1;
+ if (path.isSep(sub_path[end_index])) break;
+ }
+ continue;
+ },
+ else => return err,
+ };
+ if (end_index == sub_path.len) return;
+ // march end_index forward until next path component
+ while (true) {
+ end_index += 1;
+ if (end_index == sub_path.len or path.isSep(sub_path[end_index])) break;
+ }
+ }
+ }
+
+ /// 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);
+ }
+
/// Deprecated; call `openDirList` directly.
pub fn openDir(self: Dir, sub_path: []const u8) OpenError!Dir {
return self.openDirList(sub_path);
@@ -900,7 +905,6 @@ pub const Dir = struct {
///
/// Asserts that the path parameter has no null bytes.
pub fn openDirTraverse(self: Dir, sub_path: []const u8) OpenError!Dir {
- if (std.debug.runtime_safety) for (sub_path) |byte| assert(byte != 0);
if (builtin.os.tag == .windows) {
const sub_path_w = try os.windows.sliceToPrefixedFileW(sub_path);
return self.openDirTraverseW(&sub_path_w);
@@ -918,7 +922,6 @@ pub const Dir = struct {
///
/// Asserts that the path parameter has no null bytes.
pub fn openDirList(self: Dir, sub_path: []const u8) OpenError!Dir {
- if (std.debug.runtime_safety) for (sub_path) |byte| assert(byte != 0);
if (builtin.os.tag == .windows) {
const sub_path_w = try os.windows.sliceToPrefixedFileW(sub_path);
return self.openDirListW(&sub_path_w);
@@ -1082,7 +1085,6 @@ pub const Dir = struct {
/// To delete a directory recursively, see `deleteTree`.
/// Asserts that the path parameter has no null bytes.
pub fn deleteDir(self: Dir, sub_path: []const u8) DeleteDirError!void {
- if (std.debug.runtime_safety) for (sub_path) |byte| assert(byte != 0);
if (builtin.os.tag == .windows) {
const sub_path_w = try os.windows.sliceToPrefixedFileW(sub_path);
return self.deleteDirW(&sub_path_w);
@@ -1112,7 +1114,6 @@ pub const Dir = struct {
/// The return value is a slice of `buffer`, from index `0`.
/// Asserts that the path parameter has no null bytes.
pub fn readLink(self: Dir, sub_path: []const u8, buffer: *[MAX_PATH_BYTES]u8) ![]u8 {
- if (std.debug.runtime_safety) for (sub_path) |byte| assert(byte != 0);
const sub_path_c = try os.toPosixPath(sub_path);
return self.readLinkC(&sub_path_c, buffer);
}
diff --git a/lib/std/fs/file.zig b/lib/std/fs/file.zig
@@ -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,
diff --git a/lib/std/fs/watch.zig b/lib/std/fs/watch.zig
@@ -618,11 +618,10 @@ test "write a file, watch it, write it again" {
// TODO re-enable this test
if (true) return error.SkipZigTest;
- const allocator = std.heap.page_allocator;
-
- try os.makePath(allocator, test_tmp_dir);
+ try fs.cwd().makePath(test_tmp_dir);
defer os.deleteTree(test_tmp_dir) catch {};
+ const allocator = std.heap.page_allocator;
return testFsWatch(&allocator);
}
diff --git a/lib/std/os.zig b/lib/std/os.zig
@@ -1355,7 +1355,6 @@ pub const UnlinkatError = UnlinkError || error{
/// Delete a file name and possibly the file it refers to, based on an open directory handle.
/// Asserts that the path parameter has no null bytes.
pub fn unlinkat(dirfd: fd_t, file_path: []const u8, flags: u32) UnlinkatError!void {
- if (std.debug.runtime_safety) for (file_path) |byte| assert(byte != 0);
if (builtin.os.tag == .windows) {
const file_path_w = try windows.sliceToPrefixedFileW(file_path);
return unlinkatW(dirfd, &file_path_w, flags);
@@ -1540,25 +1539,69 @@ 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.tag == .windows) {
+ const sub_dir_path_w = try windows.sliceToPrefixedFileW(sub_dir_path);
+ 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);
+ }
+}
+
+pub fn mkdiratC(dir_fd: fd_t, sub_dir_path: [*:0]const u8, mode: u32) MakeDirError!void {
+ if (builtin.os.tag == .windows) {
+ const sub_dir_path_w = try windows.cStrToPrefixedFileW(sub_dir_path);
+ return mkdiratW(dir_fd, &sub_dir_path_w, mode);
+ }
+ switch (errno(system.mkdirat(dir_fd, sub_dir_path, mode))) {
+ 0 => return,
+ EACCES => return error.AccessDenied,
+ EBADF => unreachable,
+ EPERM => return error.AccessDenied,
+ EDQUOT => return error.DiskQuota,
+ EEXIST => return error.PathAlreadyExists,
+ EFAULT => unreachable,
+ ELOOP => return error.SymLinkLoop,
+ EMLINK => return error.LinkQuotaExceeded,
+ ENAMETOOLONG => return error.NameTooLong,
+ ENOENT => return error.FileNotFound,
+ ENOMEM => return error.SystemResources,
+ ENOSPC => return error.NoSpaceLeft,
+ ENOTDIR => return error.NotDir,
+ EROFS => return error.ReadOnlyFileSystem,
+ else => |err| return unexpectedErrno(err),
+ }
+}
+
+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,
@@ -1671,6 +1714,26 @@ pub fn chdirC(dir_path: [*:0]const u8) 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,
+ EACCES => return error.AccessDenied,
+ EBADF => unreachable,
+ ENOTDIR => return error.NotDir,
+ EINTR => continue,
+ EIO => return error.FileSystem,
+ else => |err| return unexpectedErrno(err),
+ }
+ }
+}
+
pub const ReadLinkError = error{
AccessDenied,
FileSystem,
@@ -2322,6 +2385,29 @@ pub fn fstat(fd: fd_t) FStatError!Stat {
}
}
+const FStatAtError = FStatError || error{NameTooLong};
+
+pub fn fstatat(dirfd: fd_t, pathname: []const u8, flags: u32) FStatAtError![]Stat {
+ const pathname_c = try toPosixPath(pathname);
+ return fstatatC(dirfd, &pathname_c, flags);
+}
+
+pub fn fstatatC(dirfd: fd_t, pathname: [*:0]const u8, flags: u32) FStatAtError!Stat {
+ var stat: Stat = undefined;
+ switch (errno(system.fstatat(dirfd, pathname, &stat, flags))) {
+ 0 => return stat,
+ EINVAL => unreachable,
+ EBADF => unreachable, // Always a race condition.
+ ENOMEM => return error.SystemResources,
+ EACCES => return error.AccessDenied,
+ EFAULT => unreachable,
+ ENAMETOOLONG => return error.NameTooLong,
+ ENOENT => return error.FileNotFound,
+ ENOTDIR => return error.FileNotFound,
+ else => |err| return unexpectedErrno(err),
+ }
+}
+
pub const KQueueError = error{
/// The per-process limit on the number of open file descriptors has been reached.
ProcessFdQuotaExceeded,
@@ -3169,6 +3255,7 @@ pub fn sched_getaffinity(pid: pid_t) SchedGetAffinityError!cpu_set_t {
/// Used to convert a slice to a null terminated slice on the stack.
/// TODO https://github.com/ziglang/zig/issues/287
pub fn toPosixPath(file_path: []const u8) ![PATH_MAX - 1:0]u8 {
+ if (std.debug.runtime_safety) assert(std.mem.indexOfScalar(u8, file_path, 0) == null);
var path_with_null: [PATH_MAX - 1:0]u8 = undefined;
// >= rather than > to make room for the null byte
if (file_path.len >= PATH_MAX) return error.NameTooLong;
@@ -3492,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
@@ -3506,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.
///
@@ -3527,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,
@@ -3536,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) {
@@ -3558,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);
@@ -3567,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;
@@ -3636,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;
@@ -3714,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;
@@ -3724,12 +3818,14 @@ pub fn sendfile(
switch (err) {
0 => return amt,
- EBADF => unreachable, // Always a race condition.
EFAULT => unreachable, // Segmentation fault.
EINVAL => unreachable,
ENOTCONN => unreachable, // `out_fd` is an unconnected socket.
- ENOTSUP, ENOTSOCK, ENOSYS => break :sf,
+ // On macOS version 10.14.6, I observed Darwin return EBADF when
+ // using sendfile on a valid open file descriptor of a file
+ // system file.
+ ENOTSUP, ENOTSOCK, ENOSYS, EBADF => break :sf,
EINTR => if (amt != 0) return amt else continue,
@@ -3768,10 +3864,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 {
@@ -3780,7 +3876,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) {
diff --git a/lib/std/os/linux.zig b/lib/std/os/linux.zig
@@ -76,6 +76,10 @@ pub fn chdir(path: [*:0]const u8) usize {
return syscall1(SYS_chdir, @ptrToInt(path));
}
+pub fn fchdir(fd: fd_t) usize {
+ return syscall1(SYS_fchdir, @bitCast(usize, @as(isize, fd)));
+}
+
pub fn chroot(path: [*:0]const u8) usize {
return syscall1(SYS_chroot, @ptrToInt(path));
}
diff --git a/lib/std/os/test.zig b/lib/std/os/test.zig
@@ -16,7 +16,7 @@ const AtomicRmwOp = builtin.AtomicRmwOp;
const AtomicOrder = builtin.AtomicOrder;
test "makePath, put some files in it, deleteTree" {
- try fs.makePath(a, "os_test_tmp" ++ fs.path.sep_str ++ "b" ++ fs.path.sep_str ++ "c");
+ try fs.cwd().makePath("os_test_tmp" ++ fs.path.sep_str ++ "b" ++ fs.path.sep_str ++ "c");
try io.writeFile("os_test_tmp" ++ fs.path.sep_str ++ "b" ++ fs.path.sep_str ++ "c" ++ fs.path.sep_str ++ "file.txt", "nonsense");
try io.writeFile("os_test_tmp" ++ fs.path.sep_str ++ "b" ++ fs.path.sep_str ++ "file2.txt", "blah");
try fs.deleteTree("os_test_tmp");
@@ -28,7 +28,7 @@ test "makePath, put some files in it, deleteTree" {
}
test "access file" {
- try fs.makePath(a, "os_test_tmp");
+ try fs.cwd().makePath("os_test_tmp");
if (fs.cwd().access("os_test_tmp" ++ fs.path.sep_str ++ "file.txt", .{})) |ok| {
@panic("expected error");
} else |err| {
@@ -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;
diff --git a/lib/std/os/windows.zig b/lib/std/os/windows.zig
@@ -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),
}
}
diff --git a/src-self-hosted/compilation.zig b/src-self-hosted/compilation.zig
@@ -1179,7 +1179,7 @@ pub const Compilation = struct {
defer self.gpa().free(zig_dir_path);
const tmp_dir = try fs.path.join(self.arena(), &[_][]const u8{ zig_dir_path, comp_dir_name[0..] });
- try fs.makePath(self.gpa(), tmp_dir);
+ try fs.cwd().makePath(tmp_dir);
return tmp_dir;
}
diff --git a/src-self-hosted/test.zig b/src-self-hosted/test.zig
@@ -56,7 +56,7 @@ pub const TestContext = struct {
self.zig_lib_dir = try introspect.resolveZigLibDir(allocator);
errdefer allocator.free(self.zig_lib_dir);
- try std.fs.makePath(allocator, tmp_dir_name);
+ try std.fs.cwd().makePath(tmp_dir_name);
errdefer std.fs.deleteTree(tmp_dir_name) catch {};
}
@@ -85,7 +85,7 @@ pub const TestContext = struct {
const file1_path = try std.fs.path.join(allocator, [_][]const u8{ tmp_dir_name, file_index, file1 });
if (std.fs.path.dirname(file1_path)) |dirname| {
- try std.fs.makePath(allocator, dirname);
+ try std.fs.cwd().makePath(dirname);
}
// TODO async I/O
@@ -119,7 +119,7 @@ pub const TestContext = struct {
const output_file = try std.fmt.allocPrint(allocator, "{}-out{}", .{ file1_path, (Target{ .Native = {} }).exeFileExt() });
if (std.fs.path.dirname(file1_path)) |dirname| {
- try std.fs.makePath(allocator, dirname);
+ try std.fs.cwd().makePath(dirname);
}
// TODO async I/O
diff --git a/test/cli.zig b/test/cli.zig
@@ -37,7 +37,7 @@ pub fn main() !void {
};
for (test_fns) |testFn| {
try fs.deleteTree(dir_path);
- try fs.makeDir(dir_path);
+ try fs.cwd().makeDir(dir_path);
try testFn(zig_exe, dir_path);
}
}