Merge pull request #4618 from ziglang/daurnimator-paths

improvements to std.fs, std.os
This commit is contained in:
Andrew Kelley 2020-03-03 17:05:14 -05:00 committed by GitHub
commit f6f0b019be
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 395 additions and 196 deletions

View File

@ -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);

View File

@ -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,
}

View File

@ -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;
};

View File

@ -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;

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,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);
}

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

@ -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);
}

View File

@ -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) {

View File

@ -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));
}

View File

@ -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;

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),
}
}

View File

@ -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;
}

View File

@ -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

View File

@ -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);
}
}