aboutsummaryrefslogtreecommitdiff
path: root/lib/std
diff options
context:
space:
mode:
Diffstat (limited to 'lib/std')
-rw-r--r--lib/std/build.zig4
-rw-r--r--lib/std/build/emit_raw.zig8
-rw-r--r--lib/std/build/write_file.zig2
-rw-r--r--lib/std/c.zig5
-rw-r--r--lib/std/c/darwin.zig16
-rw-r--r--lib/std/c/freebsd.zig16
-rw-r--r--lib/std/c/linux.zig7
-rw-r--r--lib/std/event/loop.zig22
-rw-r--r--lib/std/fs.zig204
-rw-r--r--lib/std/fs/file.zig231
-rw-r--r--lib/std/fs/watch.zig5
-rw-r--r--lib/std/io.zig42
-rw-r--r--lib/std/io/c_out_stream.zig4
-rw-r--r--lib/std/io/out_stream.zig13
-rw-r--r--lib/std/io/test.zig6
-rw-r--r--lib/std/os.zig754
-rw-r--r--lib/std/os/bits/darwin.zig3
-rw-r--r--lib/std/os/bits/linux/arm64.zig1
-rw-r--r--lib/std/os/linux.zig62
-rw-r--r--lib/std/os/test.zig72
-rw-r--r--lib/std/os/windows.zig85
-rw-r--r--lib/std/special/docs/index.html109
-rw-r--r--lib/std/special/docs/main.js4
-rw-r--r--lib/std/target.zig7
-rw-r--r--lib/std/zig/cross_target.zig22
-rw-r--r--lib/std/zig/render.zig9
-rw-r--r--lib/std/zig/system.zig1
27 files changed, 1313 insertions, 401 deletions
diff --git a/lib/std/build.zig b/lib/std/build.zig
index ecf3930551..59e6c4e87c 100644
--- 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/emit_raw.zig b/lib/std/build/emit_raw.zig
index 54ceae3263..44e0227b6e 100644
--- a/lib/std/build/emit_raw.zig
+++ b/lib/std/build/emit_raw.zig
@@ -213,11 +213,11 @@ pub const InstallRawStep = struct {
builder: *Builder,
artifact: *LibExeObjStep,
dest_dir: InstallDir,
- dest_filename: [] const u8,
+ dest_filename: []const u8,
const Self = @This();
- pub fn create(builder: *Builder, artifact: *LibExeObjStep, dest_filename: [] const u8) *Self {
+ pub fn create(builder: *Builder, artifact: *LibExeObjStep, dest_filename: []const u8) *Self {
const self = builder.allocator.create(Self) catch unreachable;
self.* = Self{
.step = Step.init(builder.fmt("install raw binary {}", .{artifact.step.name}), builder.allocator, make),
@@ -249,7 +249,7 @@ pub const InstallRawStep = struct {
const full_src_path = self.artifact.getOutputPath();
const full_dest_path = builder.getInstallPath(self.dest_dir, self.dest_filename);
- fs.makePath(builder.allocator, builder.getInstallPath(self.dest_dir, "")) catch unreachable;
+ fs.cwd().makePath(builder.getInstallPath(self.dest_dir, "")) catch unreachable;
try emit_raw(builder.allocator, full_src_path, full_dest_path);
}
-}; \ No newline at end of file
+};
diff --git a/lib/std/build/write_file.zig b/lib/std/build/write_file.zig
index 13d131ac61..60c54336e0 100644
--- 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
index 48a3039f51..7c01908540 100644
--- 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;
@@ -142,7 +145,7 @@ pub extern "c" fn sendto(
buf: *const c_void,
len: usize,
flags: u32,
- dest_addr: *const sockaddr,
+ dest_addr: ?*const sockaddr,
addrlen: socklen_t,
) isize;
diff --git a/lib/std/c/darwin.zig b/lib/std/c/darwin.zig
index 524c82211e..881e52c72c 100644
--- a/lib/std/c/darwin.zig
+++ b/lib/std/c/darwin.zig
@@ -55,6 +55,22 @@ pub extern "c" fn clock_get_time(clock_serv: clock_serv_t, cur_time: *mach_times
pub extern "c" fn host_get_clock_service(host: host_t, clock_id: clock_id_t, clock_serv: ?[*]clock_serv_t) kern_return_t;
pub extern "c" fn mach_port_deallocate(task: ipc_space_t, name: mach_port_name_t) kern_return_t;
+pub const sf_hdtr = extern struct {
+ headers: [*]const iovec_const,
+ hdr_cnt: c_int,
+ trailers: [*]const iovec_const,
+ trl_cnt: c_int,
+};
+
+pub extern "c" fn sendfile(
+ out_fd: fd_t,
+ in_fd: fd_t,
+ offset: off_t,
+ len: *off_t,
+ sf_hdtr: ?*sf_hdtr,
+ flags: u32,
+) c_int;
+
pub fn sigaddset(set: *sigset_t, signo: u5) void {
set.* |= @as(u32, 1) << (signo - 1);
}
diff --git a/lib/std/c/freebsd.zig b/lib/std/c/freebsd.zig
index 4c6614c978..2c4820bbe9 100644
--- a/lib/std/c/freebsd.zig
+++ b/lib/std/c/freebsd.zig
@@ -8,6 +8,22 @@ pub extern "c" fn getdents(fd: c_int, buf_ptr: [*]u8, nbytes: usize) usize;
pub extern "c" fn sigaltstack(ss: ?*stack_t, old_ss: ?*stack_t) c_int;
pub extern "c" fn getrandom(buf_ptr: [*]u8, buf_len: usize, flags: c_uint) isize;
+pub const sf_hdtr = extern struct {
+ headers: [*]const iovec_const,
+ hdr_cnt: c_int,
+ trailers: [*]const iovec_const,
+ trl_cnt: c_int,
+};
+pub extern "c" fn sendfile(
+ out_fd: fd_t,
+ in_fd: fd_t,
+ offset: ?*off_t,
+ nbytes: usize,
+ sf_hdtr: ?*sf_hdtr,
+ sbytes: ?*off_t,
+ flags: u32,
+) c_int;
+
pub const dl_iterate_phdr_callback = extern fn (info: *dl_phdr_info, size: usize, data: ?*c_void) c_int;
pub extern "c" fn dl_iterate_phdr(callback: dl_iterate_phdr_callback, data: ?*c_void) c_int;
diff --git a/lib/std/c/linux.zig b/lib/std/c/linux.zig
index be32536d6f..7ac5ecd3fe 100644
--- a/lib/std/c/linux.zig
+++ b/lib/std/c/linux.zig
@@ -82,6 +82,13 @@ pub extern "c" fn sigaltstack(ss: ?*stack_t, old_ss: ?*stack_t) c_int;
pub extern "c" fn memfd_create(name: [*:0]const u8, flags: c_uint) c_int;
+pub extern "c" fn sendfile(
+ out_fd: fd_t,
+ in_fd: fd_t,
+ offset: ?*off_t,
+ count: usize,
+) isize;
+
pub const pthread_attr_t = extern struct {
__size: [56]u8,
__align: c_long,
diff --git a/lib/std/event/loop.zig b/lib/std/event/loop.zig
index 80ba5a79b5..e62f15d59a 100644
--- a/lib/std/event/loop.zig
+++ b/lib/std/event/loop.zig
@@ -236,7 +236,8 @@ pub const Loop = struct {
var extra_thread_index: usize = 0;
errdefer {
// writing 8 bytes to an eventfd cannot fail
- noasync os.write(self.os_data.final_eventfd, &wakeup_bytes) catch unreachable;
+ const amt = noasync os.write(self.os_data.final_eventfd, &wakeup_bytes) catch unreachable;
+ assert(amt == wakeup_bytes.len);
while (extra_thread_index != 0) {
extra_thread_index -= 1;
self.extra_threads[extra_thread_index].wait();
@@ -682,7 +683,8 @@ pub const Loop = struct {
.linux => {
self.posixFsRequest(&self.os_data.fs_end_request);
// writing 8 bytes to an eventfd cannot fail
- noasync os.write(self.os_data.final_eventfd, &wakeup_bytes) catch unreachable;
+ const amt = noasync os.write(self.os_data.final_eventfd, &wakeup_bytes) catch unreachable;
+ assert(amt == wakeup_bytes.len);
return;
},
.macosx, .freebsd, .netbsd, .dragonfly => {
@@ -831,7 +833,7 @@ pub const Loop = struct {
/// Performs an async `os.write` using a separate thread.
/// `fd` must block and not return EAGAIN.
- pub fn write(self: *Loop, fd: os.fd_t, bytes: []const u8) os.WriteError!void {
+ pub fn write(self: *Loop, fd: os.fd_t, bytes: []const u8) os.WriteError!usize {
var req_node = Request.Node{
.data = .{
.msg = .{
@@ -852,7 +854,7 @@ pub const Loop = struct {
/// Performs an async `os.writev` using a separate thread.
/// `fd` must block and not return EAGAIN.
- pub fn writev(self: *Loop, fd: os.fd_t, iov: []const os.iovec_const) os.WriteError!void {
+ pub fn writev(self: *Loop, fd: os.fd_t, iov: []const os.iovec_const) os.WriteError!usize {
var req_node = Request.Node{
.data = .{
.msg = .{
@@ -873,7 +875,7 @@ pub const Loop = struct {
/// Performs an async `os.pwritev` using a separate thread.
/// `fd` must block and not return EAGAIN.
- pub fn pwritev(self: *Loop, fd: os.fd_t, iov: []const os.iovec_const, offset: u64) os.WriteError!void {
+ pub fn pwritev(self: *Loop, fd: os.fd_t, iov: []const os.iovec_const, offset: u64) os.WriteError!usize {
var req_node = Request.Node{
.data = .{
.msg = .{
@@ -1137,7 +1139,7 @@ pub const Loop = struct {
pub const Write = struct {
fd: os.fd_t,
bytes: []const u8,
- result: Error!void,
+ result: Error!usize,
pub const Error = os.WriteError;
};
@@ -1145,7 +1147,7 @@ pub const Loop = struct {
pub const WriteV = struct {
fd: os.fd_t,
iov: []const os.iovec_const,
- result: Error!void,
+ result: Error!usize,
pub const Error = os.WriteError;
};
@@ -1154,9 +1156,9 @@ pub const Loop = struct {
fd: os.fd_t,
iov: []const os.iovec_const,
offset: usize,
- result: Error!void,
+ result: Error!usize,
- pub const Error = os.WriteError;
+ pub const Error = os.PWriteError;
};
pub const PReadV = struct {
@@ -1165,7 +1167,7 @@ pub const Loop = struct {
offset: usize,
result: Error!usize,
- pub const Error = os.ReadError;
+ pub const Error = os.PReadError;
};
pub const Open = struct {
diff --git a/lib/std/fs.zig b/lib/std/fs.zig
index 5077c52cd9..88806f69bb 100644
--- 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.write(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,17 +658,19 @@ 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);
}
const path_c = try os.toPosixPath(sub_path);
- return self.openFileC(&path_c, flags);
+ return self.openFileZ(&path_c, flags);
}
+ /// Deprecated; use `openFileZ`.
+ pub const openFileC = openFileZ;
+
/// Same as `openFile` but the path parameter is null-terminated.
- pub fn openFileC(self: Dir, sub_path: [*:0]const u8, flags: File.OpenFlags) File.OpenError!File {
+ pub fn openFileZ(self: Dir, sub_path: [*:0]const u8, flags: File.OpenFlags) File.OpenError!File {
if (builtin.os.tag == .windows) {
const path_w = try os.windows.cStrToPrefixedFileW(sub_path);
return self.openFileW(&path_w, flags);
@@ -759,7 +710,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);
@@ -806,9 +756,9 @@ pub const Dir = struct {
return self.openFile(sub_path, .{});
}
- /// Deprecated; call `openFileC` directly.
+ /// Deprecated; call `openFileZ` directly.
pub fn openReadC(self: Dir, sub_path: [*:0]const u8) File.OpenError!File {
- return self.openFileC(sub_path, .{});
+ return self.openFileZ(sub_path, .{});
}
/// Deprecated; call `openFileW` directly.
@@ -882,6 +832,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 +908,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 +925,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 +1088,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 +1117,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);
}
@@ -1329,7 +1333,7 @@ pub const Dir = struct {
pub fn writeFile(self: Dir, sub_path: []const u8, data: []const u8) !void {
var file = try self.createFile(sub_path, .{});
defer file.close();
- try file.write(data);
+ try file.writeAll(data);
}
pub const AccessError = os.AccessError;
@@ -1402,7 +1406,7 @@ pub fn openFileAbsolute(absolute_path: []const u8, flags: File.OpenFlags) File.O
/// Same as `openFileAbsolute` but the path parameter is null-terminated.
pub fn openFileAbsoluteC(absolute_path_c: [*:0]const u8, flags: File.OpenFlags) File.OpenError!File {
assert(path.isAbsoluteC(absolute_path_c));
- return cwd().openFileC(absolute_path_c, flags);
+ return cwd().openFileZ(absolute_path_c, flags);
}
/// Same as `openFileAbsolute` but the path parameter is WTF-16 encoded.
diff --git a/lib/std/fs/file.zig b/lib/std/fs/file.zig
index c243eeb62c..5b51f19f41 100644
--- a/lib/std/fs/file.zig
+++ b/lib/std/fs/file.zig
@@ -228,63 +228,260 @@ pub const File = struct {
}
pub const ReadError = os.ReadError;
+ pub const PReadError = os.PReadError;
pub fn read(self: File, buffer: []u8) ReadError!usize {
if (need_async_thread and self.io_mode == .blocking and !self.async_block_allowed) {
return std.event.Loop.instance.?.read(self.handle, buffer);
+ } else {
+ return os.read(self.handle, buffer);
+ }
+ }
+
+ pub fn readAll(self: File, buffer: []u8) ReadError!void {
+ var index: usize = 0;
+ while (index < buffer.len) {
+ index += try self.read(buffer[index..]);
}
- return os.read(self.handle, buffer);
}
- pub fn pread(self: File, buffer: []u8, offset: u64) ReadError!usize {
+ pub fn pread(self: File, buffer: []u8, offset: u64) PReadError!usize {
if (need_async_thread and self.io_mode == .blocking and !self.async_block_allowed) {
- return std.event.Loop.instance.?.pread(self.handle, buffer);
+ return std.event.Loop.instance.?.pread(self.handle, buffer, offset);
+ } else {
+ return os.pread(self.handle, buffer, offset);
+ }
+ }
+
+ pub fn preadAll(self: File, buffer: []u8, offset: u64) PReadError!void {
+ var index: usize = 0;
+ while (index < buffer.len) {
+ index += try self.pread(buffer[index..], offset + index);
}
- return os.pread(self.handle, buffer, offset);
}
pub fn readv(self: File, iovecs: []const os.iovec) ReadError!usize {
if (need_async_thread and self.io_mode == .blocking and !self.async_block_allowed) {
return std.event.Loop.instance.?.readv(self.handle, iovecs);
+ } else {
+ return os.readv(self.handle, iovecs);
}
- return os.readv(self.handle, iovecs);
}
- pub fn preadv(self: File, iovecs: []const os.iovec, offset: u64) ReadError!usize {
+ /// 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..]);
+ while (amt >= iovecs[i].iov_len) {
+ amt -= iovecs[i].iov_len;
+ i += 1;
+ if (i >= iovecs.len) return;
+ }
+ iovecs[i].iov_base += amt;
+ iovecs[i].iov_len -= amt;
+ }
+ }
+
+ pub fn preadv(self: File, iovecs: []const os.iovec, offset: u64) PReadError!usize {
if (need_async_thread and self.io_mode == .blocking and !self.async_block_allowed) {
return std.event.Loop.instance.?.preadv(self.handle, iovecs, offset);
+ } else {
+ return os.preadv(self.handle, iovecs, offset);
+ }
+ }
+
+ /// 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) {
+ var amt = try self.preadv(iovecs[i..], offset + off);
+ off += amt;
+ while (amt >= iovecs[i].iov_len) {
+ amt -= iovecs[i].iov_len;
+ i += 1;
+ if (i >= iovecs.len) return;
+ }
+ iovecs[i].iov_base += amt;
+ iovecs[i].iov_len -= amt;
}
- return os.preadv(self.handle, iovecs, offset);
}
pub const WriteError = os.WriteError;
+ pub const PWriteError = os.PWriteError;
- pub fn write(self: File, bytes: []const u8) WriteError!void {
+ pub fn write(self: File, bytes: []const u8) WriteError!usize {
if (need_async_thread and self.io_mode == .blocking and !self.async_block_allowed) {
return std.event.Loop.instance.?.write(self.handle, bytes);
+ } else {
+ return os.write(self.handle, bytes);
+ }
+ }
+
+ pub fn writeAll(self: File, bytes: []const u8) WriteError!void {
+ var index: usize = 0;
+ while (index < bytes.len) {
+ index += try self.write(bytes[index..]);
}
- return os.write(self.handle, bytes);
}
- pub fn pwrite(self: File, bytes: []const u8, offset: u64) WriteError!void {
+ pub fn pwrite(self: File, bytes: []const u8, offset: u64) PWriteError!usize {
if (need_async_thread and self.io_mode == .blocking and !self.async_block_allowed) {
return std.event.Loop.instance.?.pwrite(self.handle, bytes, offset);
+ } else {
+ return os.pwrite(self.handle, bytes, offset);
}
- return os.pwrite(self.handle, bytes, offset);
}
- pub fn writev(self: File, iovecs: []const os.iovec_const) WriteError!void {
+ pub fn pwriteAll(self: File, bytes: []const u8, offset: u64) PWriteError!void {
+ var index: usize = 0;
+ while (index < bytes.len) {
+ index += try self.pwrite(bytes[index..], offset + index);
+ }
+ }
+
+ pub fn writev(self: File, iovecs: []const os.iovec_const) WriteError!usize {
if (need_async_thread and self.io_mode == .blocking and !self.async_block_allowed) {
return std.event.Loop.instance.?.writev(self.handle, iovecs);
+ } else {
+ return os.writev(self.handle, iovecs);
+ }
+ }
+
+ /// 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..]);
+ while (amt >= iovecs[i].iov_len) {
+ amt -= iovecs[i].iov_len;
+ i += 1;
+ if (i >= iovecs.len) return;
+ }
+ iovecs[i].iov_base += amt;
+ iovecs[i].iov_len -= amt;
}
- return os.writev(self.handle, iovecs);
}
- pub fn pwritev(self: File, iovecs: []const os.iovec_const, offset: usize) WriteError!void {
+ pub fn pwritev(self: File, iovecs: []os.iovec_const, offset: usize) PWriteError!usize {
if (need_async_thread and self.io_mode == .blocking and !self.async_block_allowed) {
- return std.event.Loop.instance.?.pwritev(self.handle, iovecs);
+ return std.event.Loop.instance.?.pwritev(self.handle, iovecs, offset);
+ } else {
+ return os.pwritev(self.handle, iovecs, offset);
+ }
+ }
+
+ /// 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) {
+ var amt = try self.pwritev(iovecs[i..], offset + off);
+ off += amt;
+ while (amt >= iovecs[i].iov_len) {
+ amt -= iovecs[i].iov_len;
+ i += 1;
+ if (i >= iovecs.len) return;
+ }
+ iovecs[i].iov_base += amt;
+ iovecs[i].iov_len -= amt;
+ }
+ }
+
+ 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..]);
}
- return os.pwritev(self.handle, iovecs);
}
pub fn inStream(file: File) InStream {
@@ -335,7 +532,7 @@ pub const File = struct {
pub const Error = WriteError;
pub const Stream = io.OutStream(Error);
- fn writeFn(out_stream: *Stream, bytes: []const u8) Error!void {
+ fn writeFn(out_stream: *Stream, bytes: []const u8) Error!usize {
const self = @fieldParentPtr(OutStream, "stream", out_stream);
return self.file.write(bytes);
}
diff --git a/lib/std/fs/watch.zig b/lib/std/fs/watch.zig
index 1eb5a97ff1..3180240a72 100644
--- 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/io.zig b/lib/std/io.zig
index d0bf26d548..99e9391f1d 100644
--- a/lib/std/io.zig
+++ b/lib/std/io.zig
@@ -437,10 +437,11 @@ pub fn BitInStream(endian: builtin.Endian, comptime Error: type) type {
};
}
-/// This is a simple OutStream that writes to a fixed buffer, and returns an error
-/// when it runs out of space.
+/// This is a simple OutStream that writes to a fixed buffer. If the returned number
+/// of bytes written is less than requested, the buffer is full.
+/// Returns error.OutOfMemory when no bytes would be written.
pub const SliceOutStream = struct {
- pub const Error = error{OutOfSpace};
+ pub const Error = error{OutOfMemory};
pub const Stream = OutStream(Error);
stream: Stream,
@@ -464,9 +465,11 @@ pub const SliceOutStream = struct {
self.pos = 0;
}
- fn writeFn(out_stream: *Stream, bytes: []const u8) Error!void {
+ fn writeFn(out_stream: *Stream, bytes: []const u8) Error!usize {
const self = @fieldParentPtr(SliceOutStream, "stream", out_stream);
+ if (bytes.len == 0) return 0;
+
assert(self.pos <= self.slice.len);
const n = if (self.pos + bytes.len <= self.slice.len)
@@ -477,9 +480,9 @@ pub const SliceOutStream = struct {
std.mem.copy(u8, self.slice[self.pos .. self.pos + n], bytes[0..n]);
self.pos += n;
- if (n < bytes.len) {
- return Error.OutOfSpace;
- }
+ if (n == 0) return error.OutOfMemory;
+
+ return n;
}
};
@@ -508,7 +511,9 @@ pub const NullOutStream = struct {
};
}
- fn writeFn(out_stream: *Stream, bytes: []const u8) Error!void {}
+ fn writeFn(out_stream: *Stream, bytes: []const u8) Error!usize {
+ return bytes.len;
+ }
};
test "io.NullOutStream" {
@@ -536,10 +541,11 @@ pub fn CountingOutStream(comptime OutStreamError: type) type {
};
}
- fn writeFn(out_stream: *Stream, bytes: []const u8) OutStreamError!void {
+ fn writeFn(out_stream: *Stream, bytes: []const u8) OutStreamError!usize {
const self = @fieldParentPtr(Self, "stream", out_stream);
try self.child_stream.write(bytes);
self.bytes_written += bytes.len;
+ return bytes.len;
}
};
}
@@ -588,13 +594,14 @@ pub fn BufferedOutStreamCustom(comptime buffer_size: usize, comptime OutStreamEr
}
}
- fn writeFn(out_stream: *Stream, bytes: []const u8) Error!void {
+ fn writeFn(out_stream: *Stream, bytes: []const u8) Error!usize {
const self = @fieldParentPtr(Self, "stream", out_stream);
if (bytes.len >= self.fifo.writableLength()) {
try self.flush();
- return self.unbuffered_out_stream.write(bytes);
+ return self.unbuffered_out_stream.writeOnce(bytes);
}
self.fifo.writeAssumeCapacity(bytes);
+ return bytes.len;
}
};
}
@@ -614,9 +621,10 @@ pub const BufferOutStream = struct {
};
}
- fn writeFn(out_stream: *Stream, bytes: []const u8) !void {
+ fn writeFn(out_stream: *Stream, bytes: []const u8) !usize {
const self = @fieldParentPtr(BufferOutStream, "stream", out_stream);
- return self.buffer.append(bytes);
+ try self.buffer.append(bytes);
+ return bytes.len;
}
};
@@ -734,17 +742,17 @@ pub fn BitOutStream(endian: builtin.Endian, comptime Error: type) type {
self.bit_count = 0;
}
- pub fn write(self_stream: *Stream, buffer: []const u8) Error!void {
+ pub fn write(self_stream: *Stream, buffer: []const u8) Error!usize {
var self = @fieldParentPtr(Self, "stream", self_stream);
- //@NOTE: I'm not sure this is a good idea, maybe flushBits should be forced
+ // TODO: I'm not sure this is a good idea, maybe flushBits should be forced
if (self.bit_count > 0) {
for (buffer) |b, i|
try self.writeBits(b, u8_bit_count);
- return;
+ return buffer.len;
}
- return self.out_stream.write(buffer);
+ return self.out_stream.writeOnce(buffer);
}
};
}
diff --git a/lib/std/io/c_out_stream.zig b/lib/std/io/c_out_stream.zig
index 8b341e6937..adaa3fcbaf 100644
--- a/lib/std/io/c_out_stream.zig
+++ b/lib/std/io/c_out_stream.zig
@@ -20,10 +20,10 @@ pub const COutStream = struct {
};
}
- fn writeFn(out_stream: *Stream, bytes: []const u8) Error!void {
+ fn writeFn(out_stream: *Stream, bytes: []const u8) Error!usize {
const self = @fieldParentPtr(COutStream, "stream", out_stream);
const amt_written = std.c.fwrite(bytes.ptr, 1, bytes.len, self.c_file);
- if (amt_written == bytes.len) return;
+ if (amt_written >= 0) return amt_written;
switch (std.c._errno().*) {
0 => unreachable,
os.EINVAL => unreachable,
diff --git a/lib/std/io/out_stream.zig b/lib/std/io/out_stream.zig
index 7f534865f5..cb75b27bf1 100644
--- a/lib/std/io/out_stream.zig
+++ b/lib/std/io/out_stream.zig
@@ -14,13 +14,13 @@ pub fn OutStream(comptime WriteError: type) type {
const Self = @This();
pub const Error = WriteError;
pub const WriteFn = if (std.io.is_async)
- async fn (self: *Self, bytes: []const u8) Error!void
+ async fn (self: *Self, bytes: []const u8) Error!usize
else
- fn (self: *Self, bytes: []const u8) Error!void;
+ fn (self: *Self, bytes: []const u8) Error!usize;
writeFn: WriteFn,
- pub fn write(self: *Self, bytes: []const u8) Error!void {
+ pub fn writeOnce(self: *Self, bytes: []const u8) Error!usize {
if (std.io.is_async) {
// Let's not be writing 0xaa in safe modes for upwards of 4 MiB for every stream write.
@setRuntimeSafety(false);
@@ -31,6 +31,13 @@ pub fn OutStream(comptime WriteError: type) type {
}
}
+ pub fn write(self: *Self, bytes: []const u8) Error!void {
+ var index: usize = 0;
+ while (index != bytes.len) {
+ index += try self.writeOnce(bytes[index..]);
+ }
+ }
+
pub fn print(self: *Self, comptime format: []const u8, args: var) Error!void {
return std.fmt.format(self, Error, write, format, args);
}
diff --git a/lib/std/io/test.zig b/lib/std/io/test.zig
index a5b0913a60..bc194046f6 100644
--- a/lib/std/io/test.zig
+++ b/lib/std/io/test.zig
@@ -134,13 +134,13 @@ test "SliceOutStream" {
try ss.stream.write("world");
expect(mem.eql(u8, ss.getWritten(), "Helloworld"));
- expectError(error.OutOfSpace, ss.stream.write("!"));
+ expectError(error.OutOfMemory, ss.stream.write("!"));
expect(mem.eql(u8, ss.getWritten(), "Helloworld"));
ss.reset();
expect(ss.getWritten().len == 0);
- expectError(error.OutOfSpace, ss.stream.write("Hello world!"));
+ expectError(error.OutOfMemory, ss.stream.write("Hello world!"));
expect(mem.eql(u8, ss.getWritten(), "Hello worl"));
}
@@ -613,7 +613,7 @@ test "File seek ops" {
fs.cwd().deleteFile(tmp_file_name) catch {};
}
- try file.write(&([_]u8{0x55} ** 8192));
+ try file.writeAll(&([_]u8{0x55} ** 8192));
// Seek to the end
try file.seekFromEnd(0);
diff --git a/lib/std/os.zig b/lib/std/os.zig
index fbfef4ac4c..90ad3e03a9 100644
--- a/lib/std/os.zig
+++ b/lib/std/os.zig
@@ -298,6 +298,11 @@ pub const ReadError = error{
/// buf.len. If 0 bytes were read, that means EOF.
/// If the application has a global event loop enabled, EAGAIN is handled
/// via the event loop. Otherwise EAGAIN results in error.WouldBlock.
+///
+/// Linux has a limit on how many bytes may be transferred in one `read` call, which is `0x7ffff000`
+/// on both 64-bit and 32-bit systems. This is due to using a signed C int as the return value, as
+/// well as stuffing the errno codes into the last `4096` values. This is noted on the `read` man page.
+/// For POSIX the limit is `math.maxInt(isize)`.
pub fn read(fd: fd_t, buf: []u8) ReadError!usize {
if (builtin.os.tag == .windows) {
return windows.ReadFile(fd, buf, null);
@@ -316,8 +321,15 @@ pub fn read(fd: fd_t, buf: []u8) ReadError!usize {
}
}
+ // Prevents EINVAL.
+ const max_count = switch (std.Target.current.os.tag) {
+ .linux => 0x7ffff000,
+ else => math.maxInt(isize),
+ };
+ const adjusted_len = math.min(max_count, buf.len);
+
while (true) {
- const rc = system.read(fd, buf.ptr, buf.len);
+ const rc = system.read(fd, buf.ptr, adjusted_len);
switch (errno(rc)) {
0 => return @intCast(usize, rc),
EINTR => continue,
@@ -352,32 +364,18 @@ pub fn read(fd: fd_t, buf: []u8) ReadError!usize {
/// * Windows
/// On these systems, the read races with concurrent writes to the same file descriptor.
pub fn readv(fd: fd_t, iov: []const iovec) ReadError!usize {
- if (builtin.os.tag == .windows) {
- // TODO batch these into parallel requests
- var off: usize = 0;
- var iov_i: usize = 0;
- var inner_off: usize = 0;
- while (true) {
- const v = iov[iov_i];
- const amt_read = try read(fd, v.iov_base[inner_off .. v.iov_len - inner_off]);
- off += amt_read;
- inner_off += amt_read;
- if (inner_off == v.len) {
- iov_i += 1;
- inner_off = 0;
- if (iov_i == iov.len) {
- return off;
- }
- }
- if (amt_read == 0) return off; // EOF
- } else unreachable; // TODO https://github.com/ziglang/zig/issues/707
+ if (std.Target.current.os.tag == .windows) {
+ // TODO does Windows have a way to read an io vector?
+ if (iov.len == 0) return @as(usize, 0);
+ const first = iov[0];
+ return read(fd, first.iov_base[0..first.iov_len]);
}
while (true) {
// TODO handle the case when iov_len is too large and get rid of this @intCast
- const rc = system.readv(fd, iov.ptr, @intCast(u32, iov.len));
+ const rc = system.readv(fd, iov.ptr, iov_count);
switch (errno(rc)) {
- 0 => return @bitCast(usize, rc),
+ 0 => return @intCast(usize, rc),
EINTR => continue,
EINVAL => unreachable,
EFAULT => unreachable,
@@ -397,6 +395,8 @@ pub fn readv(fd: fd_t, iov: []const iovec) ReadError!usize {
}
}
+pub const PReadError = ReadError || error{Unseekable};
+
/// Number of bytes read is returned. Upon reading end-of-file, zero is returned.
///
/// Retries when interrupted by a signal.
@@ -405,7 +405,7 @@ pub fn readv(fd: fd_t, iov: []const iovec) ReadError!usize {
/// via the event loop. Otherwise EAGAIN results in `error.WouldBlock`.
/// On Windows, if the application has a global event loop enabled, I/O Completion Ports are
/// used to perform the I/O. `error.WouldBlock` is not possible on Windows.
-pub fn pread(fd: fd_t, buf: []u8, offset: u64) ReadError!usize {
+pub fn pread(fd: fd_t, buf: []u8, offset: u64) PReadError!usize {
if (builtin.os.tag == .windows) {
return windows.ReadFile(fd, buf, offset);
}
@@ -429,6 +429,9 @@ pub fn pread(fd: fd_t, buf: []u8, offset: u64) ReadError!usize {
ENOBUFS => return error.SystemResources,
ENOMEM => return error.SystemResources,
ECONNRESET => return error.ConnectionResetByPeer,
+ ENXIO => return error.Unseekable,
+ ESPIPE => return error.Unseekable,
+ EOVERFLOW => return error.Unseekable,
else => |err| return unexpectedErrno(err),
}
}
@@ -448,75 +451,23 @@ pub fn pread(fd: fd_t, buf: []u8, offset: u64) ReadError!usize {
/// * Darwin
/// * Windows
/// On these systems, the read races with concurrent writes to the same file descriptor.
-pub fn preadv(fd: fd_t, iov: []const iovec, offset: u64) ReadError!usize {
- if (comptime std.Target.current.isDarwin()) {
- // Darwin does not have preadv but it does have pread.
- var off: usize = 0;
- var iov_i: usize = 0;
- var inner_off: usize = 0;
- while (true) {
- const v = iov[iov_i];
- const rc = darwin.pread(fd, v.iov_base + inner_off, v.iov_len - inner_off, offset + off);
- const err = darwin.getErrno(rc);
- switch (err) {
- 0 => {
- const amt_read = @bitCast(usize, rc);
- off += amt_read;
- inner_off += amt_read;
- if (inner_off == v.iov_len) {
- iov_i += 1;
- inner_off = 0;
- if (iov_i == iov.len) {
- return off;
- }
- }
- if (rc == 0) return off; // EOF
- continue;
- },
- EINTR => continue,
- EINVAL => unreachable,
- EFAULT => unreachable,
- ESPIPE => unreachable, // fd is not seekable
- EAGAIN => if (std.event.Loop.instance) |loop| {
- loop.waitUntilFdReadable(fd);
- continue;
- } else {
- return error.WouldBlock;
- },
- EBADF => unreachable, // always a race condition
- EIO => return error.InputOutput,
- EISDIR => return error.IsDir,
- ENOBUFS => return error.SystemResources,
- ENOMEM => return error.SystemResources,
- else => return unexpectedErrno(err),
- }
- }
+pub fn preadv(fd: fd_t, iov: []const iovec, offset: u64) PReadError!usize {
+ const have_pread_but_not_preadv = switch (std.Target.current.os.tag) {
+ .windows, .macosx, .ios, .watchos, .tvos => true,
+ else => false,
+ };
+ if (have_pread_but_not_preadv) {
+ // We could loop here; but proper usage of `preadv` must handle partial reads anyway.
+ // So we simply read into the first vector only.
+ if (iov.len == 0) return @as(usize, 0);
+ const first = iov[0];
+ return pread(fd, first.iov_base[0..first.iov_len], offset);
}
- if (builtin.os.tag == .windows) {
- // TODO batch these into parallel requests
- var off: usize = 0;
- var iov_i: usize = 0;
- var inner_off: usize = 0;
- while (true) {
- const v = iov[iov_i];
- const amt_read = try pread(fd, v.iov_base[inner_off .. v.iov_len - inner_off], offset + off);
- off += amt_read;
- inner_off += amt_read;
- if (inner_off == v.len) {
- iov_i += 1;
- inner_off = 0;
- if (iov_i == iov.len) {
- return off;
- }
- }
- if (amt_read == 0) return off; // EOF
- } else unreachable; // TODO https://github.com/ziglang/zig/issues/707
- }
+ const iov_count = math.cast(u31, iov.len) catch math.maxInt(u31);
while (true) {
- // TODO handle the case when iov_len is too large and get rid of this @intCast
- const rc = system.preadv(fd, iov.ptr, @intCast(u32, iov.len), offset);
+ const rc = system.preadv(fd, iov.ptr, iov_count, offset);
switch (errno(rc)) {
0 => return @bitCast(usize, rc),
EINTR => continue,
@@ -533,6 +484,9 @@ pub fn preadv(fd: fd_t, iov: []const iovec, offset: u64) ReadError!usize {
EISDIR => return error.IsDir,
ENOBUFS => return error.SystemResources,
ENOMEM => return error.SystemResources,
+ ENXIO => return error.Unseekable,
+ ESPIPE => return error.Unseekable,
+ EOVERFLOW => return error.Unseekable,
else => |err| return unexpectedErrno(err),
}
}
@@ -553,10 +507,28 @@ pub const WriteError = error{
WouldBlock,
} || UnexpectedError;
-/// Write to a file descriptor. Keeps trying if it gets interrupted.
-/// If the application has a global event loop enabled, EAGAIN is handled
-/// via the event loop. Otherwise EAGAIN results in error.WouldBlock.
-pub fn write(fd: fd_t, bytes: []const u8) WriteError!void {
+/// Write to a file descriptor.
+/// Retries when interrupted by a signal.
+/// Returns the number of bytes written. If nonzero bytes were supplied, this will be nonzero.
+///
+/// Note that a successful write() may transfer fewer than count bytes. Such partial writes can
+/// occur for various reasons; for example, because there was insufficient space on the disk
+/// device to write all of the requested bytes, or because a blocked write() to a socket, pipe, or
+/// similar was interrupted by a signal handler after it had transferred some, but before it had
+/// transferred all of the requested bytes. In the event of a partial write, the caller can make
+/// another write() call to transfer the remaining bytes. The subsequent call will either
+/// transfer further bytes or may result in an error (e.g., if the disk is now full).
+///
+/// For POSIX systems, if the application has a global event loop enabled, EAGAIN is handled
+/// via the event loop. Otherwise EAGAIN results in `error.WouldBlock`.
+/// On Windows, if the application has a global event loop enabled, I/O Completion Ports are
+/// used to perform the I/O. `error.WouldBlock` is not possible on Windows.
+///
+/// Linux has a limit on how many bytes may be transferred in one `write` call, which is `0x7ffff000`
+/// on both 64-bit and 32-bit systems. This is due to using a signed C int as the return value, as
+/// well as stuffing the errno codes into the last `4096` values. This is noted on the `write` man page.
+/// The corresponding POSIX limit is `math.maxInt(isize)`.
+pub fn write(fd: fd_t, bytes: []const u8) WriteError!usize {
if (builtin.os.tag == .windows) {
return windows.WriteFile(fd, bytes, null);
}
@@ -568,26 +540,21 @@ pub fn write(fd: fd_t, bytes: []const u8) WriteError!void {
}};
var nwritten: usize = undefined;
switch (wasi.fd_write(fd, &ciovs, ciovs.len, &nwritten)) {
- 0 => return,
+ 0 => return nwritten,
else => |err| return unexpectedErrno(err),
}
}
- // Linux can return EINVAL when write amount is > 0x7ffff000
- // See https://github.com/ziglang/zig/pull/743#issuecomment-363165856
- // TODO audit this. Shawn Landden says that this is not actually true.
- // if this logic should stay, move it to std.os.linux
- const max_bytes_len = 0x7ffff000;
+ const max_count = switch (std.Target.current.os.tag) {
+ .linux => 0x7ffff000,
+ else => math.maxInt(isize),
+ };
+ const adjusted_len = math.min(max_count, bytes.len);
- var index: usize = 0;
- while (index < bytes.len) {
- const amt_to_write = math.min(bytes.len - index, @as(usize, max_bytes_len));
- const rc = system.write(fd, bytes.ptr + index, amt_to_write);
+ while (true) {
+ const rc = system.write(fd, bytes.ptr, adjusted_len);
switch (errno(rc)) {
- 0 => {
- index += @intCast(usize, rc);
- continue;
- },
+ 0 => return @intCast(usize, rc),
EINTR => continue,
EINVAL => unreachable,
EFAULT => unreachable,
@@ -611,14 +578,36 @@ pub fn write(fd: fd_t, bytes: []const u8) WriteError!void {
}
/// Write multiple buffers to a file descriptor.
-/// If the application has a global event loop enabled, EAGAIN is handled
-/// via the event loop. Otherwise EAGAIN results in error.WouldBlock.
-pub fn writev(fd: fd_t, iov: []const iovec_const) WriteError!void {
+/// Retries when interrupted by a signal.
+/// Returns the number of bytes written. If nonzero bytes were supplied, this will be nonzero.
+///
+/// Note that a successful write() may transfer fewer bytes than supplied. Such partial writes can
+/// occur for various reasons; for example, because there was insufficient space on the disk
+/// device to write all of the requested bytes, or because a blocked write() to a socket, pipe, or
+/// similar was interrupted by a signal handler after it had transferred some, but before it had
+/// transferred all of the requested bytes. In the event of a partial write, the caller can make
+/// another write() call to transfer the remaining bytes. The subsequent call will either
+/// transfer further bytes or may result in an error (e.g., if the disk is now full).
+///
+/// For POSIX systems, if the application has a global event loop enabled, EAGAIN is handled
+/// via the event loop. Otherwise EAGAIN results in `error.WouldBlock`.
+/// On Windows, if the application has a global event loop enabled, I/O Completion Ports are
+/// used to perform the I/O. `error.WouldBlock` is not possible on Windows.
+///
+/// If `iov.len` is larger than will fit in a `u31`, a partial write will occur.
+pub fn writev(fd: fd_t, iov: []const iovec_const) WriteError!usize {
+ if (std.Target.current.os.tag == .windows) {
+ // TODO does Windows have a way to write an io vector?
+ if (iov.len == 0) return @as(usize, 0);
+ const first = iov[0];
+ return write(fd, first.iov_base[0..first.iov_len]);
+ }
+
+ const iov_count = math.cast(u31, iov.len) catch math.maxInt(u31);
while (true) {
- // TODO handle the case when iov_len is too large and get rid of this @intCast
- const rc = system.writev(fd, iov.ptr, @intCast(u32, iov.len));
+ const rc = system.writev(fd, iov.ptr, iov_count);
switch (errno(rc)) {
- 0 => return,
+ 0 => return @intCast(usize, rc),
EINTR => continue,
EINVAL => unreachable,
EFAULT => unreachable,
@@ -641,23 +630,45 @@ pub fn writev(fd: fd_t, iov: []const iovec_const) WriteError!void {
}
}
+pub const PWriteError = WriteError || error{Unseekable};
+
/// Write to a file descriptor, with a position offset.
-///
/// Retries when interrupted by a signal.
+/// Returns the number of bytes written. If nonzero bytes were supplied, this will be nonzero.
+///
+/// Note that a successful write() may transfer fewer bytes than supplied. Such partial writes can
+/// occur for various reasons; for example, because there was insufficient space on the disk
+/// device to write all of the requested bytes, or because a blocked write() to a socket, pipe, or
+/// similar was interrupted by a signal handler after it had transferred some, but before it had
+/// transferred all of the requested bytes. In the event of a partial write, the caller can make
+/// another write() call to transfer the remaining bytes. The subsequent call will either
+/// transfer further bytes or may result in an error (e.g., if the disk is now full).
///
/// For POSIX systems, if the application has a global event loop enabled, EAGAIN is handled
/// via the event loop. Otherwise EAGAIN results in `error.WouldBlock`.
/// On Windows, if the application has a global event loop enabled, I/O Completion Ports are
/// used to perform the I/O. `error.WouldBlock` is not possible on Windows.
-pub fn pwrite(fd: fd_t, bytes: []const u8, offset: u64) WriteError!void {
+///
+/// Linux has a limit on how many bytes may be transferred in one `pwrite` call, which is `0x7ffff000`
+/// on both 64-bit and 32-bit systems. This is due to using a signed C int as the return value, as
+/// well as stuffing the errno codes into the last `4096` values. This is noted on the `write` man page.
+/// The corresponding POSIX limit is `math.maxInt(isize)`.
+pub fn pwrite(fd: fd_t, bytes: []const u8, offset: u64) PWriteError!usize {
if (std.Target.current.os.tag == .windows) {
return windows.WriteFile(fd, bytes, offset);
}
+ // Prevent EINVAL.
+ const max_count = switch (std.Target.current.os.tag) {
+ .linux => 0x7ffff000,
+ else => math.maxInt(isize),
+ };
+ const adjusted_len = math.min(max_count, bytes.len);
+
while (true) {
- const rc = system.pwrite(fd, bytes.ptr, bytes.len, offset);
+ const rc = system.pwrite(fd, bytes.ptr, adjusted_len, offset);
switch (errno(rc)) {
- 0 => return,
+ 0 => return @intCast(usize, rc),
EINTR => continue,
EINVAL => unreachable,
EFAULT => unreachable,
@@ -675,84 +686,54 @@ pub fn pwrite(fd: fd_t, bytes: []const u8, offset: u64) WriteError!void {
ENOSPC => return error.NoSpaceLeft,
EPERM => return error.AccessDenied,
EPIPE => return error.BrokenPipe,
+ ENXIO => return error.Unseekable,
+ ESPIPE => return error.Unseekable,
+ EOVERFLOW => return error.Unseekable,
else => |err| return unexpectedErrno(err),
}
}
}
/// Write multiple buffers to a file descriptor, with a position offset.
-///
/// Retries when interrupted by a signal.
+/// Returns the number of bytes written. If nonzero bytes were supplied, this will be nonzero.
+///
+/// Note that a successful write() may transfer fewer than count bytes. Such partial writes can
+/// occur for various reasons; for example, because there was insufficient space on the disk
+/// device to write all of the requested bytes, or because a blocked write() to a socket, pipe, or
+/// similar was interrupted by a signal handler after it had transferred some, but before it had
+/// transferred all of the requested bytes. In the event of a partial write, the caller can make
+/// another write() call to transfer the remaining bytes. The subsequent call will either
+/// transfer further bytes or may result in an error (e.g., if the disk is now full).
///
/// If the application has a global event loop enabled, EAGAIN is handled
/// via the event loop. Otherwise EAGAIN results in `error.WouldBlock`.
///
-/// This operation is non-atomic on the following systems:
+/// The following systems do not have this syscall, and will return partial writes if more than one
+/// vector is provided:
/// * Darwin
/// * Windows
-/// On these systems, the write races with concurrent writes to the same file descriptor, and
-/// the file can be in a partially written state when an error occurs.
-pub fn pwritev(fd: fd_t, iov: []const iovec_const, offset: u64) WriteError!void {
- if (comptime std.Target.current.isDarwin()) {
- // Darwin does not have pwritev but it does have pwrite.
- var off: usize = 0;
- var iov_i: usize = 0;
- var inner_off: usize = 0;
- while (true) {
- const v = iov[iov_i];
- const rc = darwin.pwrite(fd, v.iov_base + inner_off, v.iov_len - inner_off, offset + off);
- const err = darwin.getErrno(rc);
- switch (err) {
- 0 => {
- const amt_written = @bitCast(usize, rc);
- off += amt_written;
- inner_off += amt_written;
- if (inner_off == v.iov_len) {
- iov_i += 1;
- inner_off = 0;
- if (iov_i == iov.len) {
- return;
- }
- }
- continue;
- },
- EINTR => continue,
- ESPIPE => unreachable, // `fd` is not seekable.
- EINVAL => unreachable,
- EFAULT => unreachable,
- EAGAIN => if (std.event.Loop.instance) |loop| {
- loop.waitUntilFdWritable(fd);
- continue;
- } else {
- return error.WouldBlock;
- },
- EBADF => unreachable, // Always a race condition.
- EDESTADDRREQ => unreachable, // `connect` was never called.
- EDQUOT => return error.DiskQuota,
- EFBIG => return error.FileTooBig,
- EIO => return error.InputOutput,
- ENOSPC => return error.NoSpaceLeft,
- EPERM => return error.AccessDenied,
- EPIPE => return error.BrokenPipe,
- else => return unexpectedErrno(err),
- }
- }
- }
+///
+/// If `iov.len` is larger than will fit in a `u31`, a partial write will occur.
+pub fn pwritev(fd: fd_t, iov: []const iovec_const, offset: u64) PWriteError!usize {
+ const have_pwrite_but_not_pwritev = switch (std.Target.current.os.tag) {
+ .windows, .macosx, .ios, .watchos, .tvos => true,
+ else => false,
+ };
- if (std.Target.current.os.tag == .windows) {
- var off = offset;
- for (iov) |item| {
- try pwrite(fd, item.iov_base[0..item.iov_len], off);
- off += buf.len;
- }
- return;
+ if (have_pwrite_but_not_pwritev) {
+ // We could loop here; but proper usage of `pwritev` must handle partial writes anyway.
+ // So we simply write the first vector only.
+ if (iov.len == 0) return @as(usize, 0);
+ const first = iov[0];
+ return pwrite(fd, first.iov_base[0..first.iov_len], offset);
}
+ const iov_count = math.cast(u31, iov.len) catch math.maxInt(u31);
while (true) {
- // TODO handle the case when iov_len is too large and get rid of this @intCast
- const rc = system.pwritev(fd, iov.ptr, @intCast(u32, iov.len), offset);
+ const rc = system.pwritev(fd, iov.ptr, iov_count, offset);
switch (errno(rc)) {
- 0 => return,
+ 0 => return @intCast(usize, rc),
EINTR => continue,
EINVAL => unreachable,
EFAULT => unreachable,
@@ -770,6 +751,9 @@ pub fn pwritev(fd: fd_t, iov: []const iovec_const, offset: u64) WriteError!void
ENOSPC => return error.NoSpaceLeft,
EPERM => return error.AccessDenied,
EPIPE => return error.BrokenPipe,
+ ENXIO => return error.Unseekable,
+ ESPIPE => return error.Unseekable,
+ EOVERFLOW => return error.Unseekable,
else => |err| return unexpectedErrno(err),
}
}
@@ -1371,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);
@@ -1556,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,
@@ -1687,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,
@@ -2338,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,
@@ -3185,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;
@@ -3389,7 +3460,6 @@ pub const SendError = error{
/// The socket type requires that message be sent atomically, and the size of the message
/// to be sent made this impossible. The message is not transmitted.
- ///
MessageTooBig,
/// The output queue for a network interface was full. This generally indicates that the
@@ -3498,6 +3568,324 @@ pub fn send(
return sendto(sockfd, buf, flags, null, 0);
}
+pub const SendFileError = PReadError || WriteError || SendError;
+
+fn count_iovec_bytes(iovs: []const iovec_const) usize {
+ var count: usize = 0;
+ for (iovs) |iov| {
+ count += iov.iov_len;
+ }
+ return count;
+}
+
+/// Transfer data between file descriptors, with optional headers and trailers.
+/// Returns the number of bytes written, which can be zero.
+///
+/// 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 `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
+/// opened for writing. They may be any kind of file descriptor; however, if `in_fd` is not a regular
+/// file system file, it may cause this function to fall back to calling `read` and `write`, in which case
+/// 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. 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.
+///
+/// These systems support atomically sending everything, including headers and trailers:
+/// * macOS
+/// * FreeBSD
+///
+/// These systems support in-kernel data copying, but headers and trailers are not sent atomically:
+/// * Linux
+///
+/// Other systems fall back to calling `read` / `write`.
+///
+/// Linux has a limit on how many bytes may be transferred in one `sendfile` call, which is `0x7ffff000`
+/// on both 64-bit and 32-bit systems. This is due to using a signed C int as the return value, as
+/// well as stuffing the errno codes into the last `4096` values. This is cited on the `sendfile` man page.
+/// The corresponding POSIX limit on this is `math.maxInt(isize)`.
+pub fn sendfile(
+ out_fd: fd_t,
+ in_fd: fd_t,
+ in_offset: u64,
+ in_len: u64,
+ headers: []const iovec_const,
+ trailers: []const iovec_const,
+ flags: u32,
+) SendFileError!usize {
+ var header_done = false;
+ 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(size_t),
+ };
+
+ switch (std.Target.current.os.tag) {
+ .linux => sf: {
+ // sendfile() first appeared in Linux 2.2, glibc 2.1.
+ const call_sf = comptime if (builtin.link_libc)
+ std.c.versionCheck(.{ .major = 2, .minor = 1 }).ok
+ else
+ std.Target.current.os.version_range.linux.range.max.order(.{ .major = 2, .minor = 2 }) != .lt;
+ if (!call_sf) break :sf;
+
+ if (headers.len != 0) {
+ const amt = try writev(out_fd, headers);
+ total_written += amt;
+ if (amt < count_iovec_bytes(headers)) return total_written;
+ header_done = true;
+ }
+
+ // Here we match BSD behavior, making a zero count value send as many bytes as possible.
+ 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);
+ const rc = system.sendfile(out_fd, in_fd, &offset, adjusted_count);
+ switch (errno(rc)) {
+ 0 => {
+ const amt = @bitCast(usize, rc);
+ total_written += amt;
+ if (in_len == 0 and amt == 0) {
+ // We have detected EOF from `in_fd`.
+ break;
+ } else if (amt < in_len) {
+ return total_written;
+ } else {
+ break;
+ }
+ },
+
+ EBADF => unreachable, // Always a race condition.
+ EFAULT => unreachable, // Segmentation fault.
+ EOVERFLOW => unreachable, // We avoid passing too large of a `count`.
+ ENOTCONN => unreachable, // `out_fd` is an unconnected socket.
+
+ EINVAL, ENOSYS => {
+ // EINVAL could be any of the following situations:
+ // * Descriptor is not valid or locked
+ // * an mmap(2)-like operation is not available for in_fd
+ // * count is negative
+ // * out_fd has the O_APPEND flag set
+ // Because of the "mmap(2)-like operation" possibility, we fall back to doing read/write
+ // manually, the same as ENOSYS.
+ break :sf;
+ },
+ EAGAIN => if (std.event.Loop.instance) |loop| {
+ loop.waitUntilFdWritable(out_fd);
+ continue;
+ } else {
+ return error.WouldBlock;
+ },
+ EIO => return error.InputOutput,
+ EPIPE => return error.BrokenPipe,
+ ENOMEM => return error.SystemResources,
+ ENXIO => return error.Unseekable,
+ ESPIPE => return error.Unseekable,
+ else => |err| {
+ const discard = unexpectedErrno(err);
+ break :sf;
+ },
+ }
+ }
+
+ if (trailers.len != 0) {
+ total_written += try writev(out_fd, trailers);
+ }
+
+ return total_written;
+ },
+ .freebsd => sf: {
+ var hdtr_data: std.c.sf_hdtr = undefined;
+ var hdtr: ?*std.c.sf_hdtr = null;
+ if (headers.len != 0 or trailers.len != 0) {
+ // Here we carefully avoid `@intCast` by returning partial writes when
+ // too many io vectors are provided.
+ const hdr_cnt = math.cast(u31, headers.len) catch math.maxInt(u31);
+ if (headers.len > hdr_cnt) return writev(out_fd, headers);
+
+ const trl_cnt = math.cast(u31, trailers.len) catch math.maxInt(u31);
+
+ hdtr_data = std.c.sf_hdtr{
+ .headers = headers.ptr,
+ .hdr_cnt = hdr_cnt,
+ .trailers = trailers.ptr,
+ .trl_cnt = trl_cnt,
+ };
+ hdtr = &hdtr_data;
+ }
+
+ const adjusted_count = math.min(in_len, max_count);
+
+ while (true) {
+ var sbytes: off_t = undefined;
+ const err = errno(system.sendfile(out_fd, in_fd, in_offset, adjusted_count, hdtr, &sbytes, flags));
+ const amt = @bitCast(usize, sbytes);
+ switch (err) {
+ 0 => return amt,
+
+ EBADF => unreachable, // Always a race condition.
+ EFAULT => unreachable, // Segmentation fault.
+ ENOTCONN => unreachable, // `out_fd` is an unconnected socket.
+
+ EINVAL, EOPNOTSUPP, ENOTSOCK, ENOSYS => {
+ // EINVAL could be any of the following situations:
+ // * The fd argument is not a regular file.
+ // * The s argument is not a SOCK_STREAM type socket.
+ // * The offset argument is negative.
+ // Because of some of these possibilities, we fall back to doing read/write
+ // manually, the same as ENOSYS.
+ break :sf;
+ },
+
+ EINTR => if (amt != 0) return amt else continue,
+
+ EAGAIN => if (amt != 0) {
+ return amt;
+ } else if (std.event.Loop.instance) |loop| {
+ loop.waitUntilFdWritable(out_fd);
+ continue;
+ } else {
+ return error.WouldBlock;
+ },
+
+ EBUSY => if (amt != 0) {
+ return amt;
+ } else if (std.event.Loop.instance) |loop| {
+ loop.waitUntilFdReadable(in_fd);
+ continue;
+ } else {
+ return error.WouldBlock;
+ },
+
+ EIO => return error.InputOutput,
+ ENOBUFS => return error.SystemResources,
+ EPIPE => return error.BrokenPipe,
+
+ else => {
+ const discard = unexpectedErrno(err);
+ if (amt != 0) {
+ return amt;
+ } else {
+ break :sf;
+ }
+ },
+ }
+ }
+ },
+ .macosx, .ios, .tvos, .watchos => sf: {
+ var hdtr_data: std.c.sf_hdtr = undefined;
+ var hdtr: ?*std.c.sf_hdtr = null;
+ if (headers.len != 0 or trailers.len != 0) {
+ // Here we carefully avoid `@intCast` by returning partial writes when
+ // too many io vectors are provided.
+ const hdr_cnt = math.cast(u31, headers.len) catch math.maxInt(u31);
+ if (headers.len > hdr_cnt) return writev(out_fd, headers);
+
+ const trl_cnt = math.cast(u31, trailers.len) catch math.maxInt(u31);
+
+ hdtr_data = std.c.sf_hdtr{
+ .headers = headers.ptr,
+ .hdr_cnt = hdr_cnt,
+ .trailers = trailers.ptr,
+ .trl_cnt = trl_cnt,
+ };
+ hdtr = &hdtr_data;
+ }
+
+ const adjusted_count = math.min(in_len, @as(u63, max_count));
+
+ while (true) {
+ var sbytes: off_t = adjusted_count;
+ const signed_offset = @bitCast(i64, in_offset);
+ const err = errno(system.sendfile(out_fd, in_fd, signed_offset, &sbytes, hdtr, flags));
+ const amt = @bitCast(usize, sbytes);
+ switch (err) {
+ 0 => return amt,
+
+ EFAULT => unreachable, // Segmentation fault.
+ EINVAL => unreachable,
+ ENOTCONN => unreachable, // `out_fd` is an unconnected socket.
+
+ // 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,
+
+ EAGAIN => if (amt != 0) {
+ return amt;
+ } else if (std.event.Loop.instance) |loop| {
+ loop.waitUntilFdWritable(out_fd);
+ continue;
+ } else {
+ return error.WouldBlock;
+ },
+
+ EIO => return error.InputOutput,
+ EPIPE => return error.BrokenPipe,
+
+ else => {
+ const discard = unexpectedErrno(err);
+ if (amt != 0) {
+ return amt;
+ } else {
+ break :sf;
+ }
+ },
+ }
+ }
+ },
+ else => {}, // fall back to read/write
+ }
+
+ if (headers.len != 0 and !header_done) {
+ const amt = try writev(out_fd, headers);
+ total_written += amt;
+ if (amt < count_iovec_bytes(headers)) return total_written;
+ }
+
+ 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 (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 (in_len == 0) {
+ // We have detected EOF from `in_fd`.
+ break :rw;
+ } else {
+ return total_written;
+ }
+ }
+ const amt_written = try write(out_fd, buf[0..amt_read]);
+ total_written += amt_written;
+ if (amt_written < in_len or in_len == 0) return total_written;
+ }
+
+ if (trailers.len != 0) {
+ total_written += try writev(out_fd, trailers);
+ }
+
+ return total_written;
+}
+
pub const PollError = error{
/// The kernel had no space to allocate file descriptor tables.
SystemResources,
diff --git a/lib/std/os/bits/darwin.zig b/lib/std/os/bits/darwin.zig
index 22897974c2..fb933c6698 100644
--- a/lib/std/os/bits/darwin.zig
+++ b/lib/std/os/bits/darwin.zig
@@ -926,6 +926,9 @@ pub const ESOCKTNOSUPPORT = 44;
/// Operation not supported
pub const ENOTSUP = 45;
+/// Operation not supported. Alias of `ENOTSUP`.
+pub const EOPNOTSUPP = ENOTSUP;
+
/// Protocol family not supported
pub const EPFNOSUPPORT = 46;
diff --git a/lib/std/os/bits/linux/arm64.zig b/lib/std/os/bits/linux/arm64.zig
index 8dcebc5ddf..386e889873 100644
--- a/lib/std/os/bits/linux/arm64.zig
+++ b/lib/std/os/bits/linux/arm64.zig
@@ -82,6 +82,7 @@ pub const SYS_pread64 = 67;
pub const SYS_pwrite64 = 68;
pub const SYS_preadv = 69;
pub const SYS_pwritev = 70;
+pub const SYS_sendfile = 71;
pub const SYS_pselect6 = 72;
pub const SYS_ppoll = 73;
pub const SYS_signalfd4 = 74;
diff --git a/lib/std/os/linux.zig b/lib/std/os/linux.zig
index 30dba85e51..719e541846 100644
--- a/lib/std/os/linux.zig
+++ b/lib/std/os/linux.zig
@@ -6,7 +6,7 @@
// provide `rename` when only the `renameat` syscall exists.
// * Does not support POSIX thread cancellation.
const std = @import("../std.zig");
-const builtin = @import("builtin");
+const builtin = std.builtin;
const assert = std.debug.assert;
const maxInt = std.math.maxInt;
const elf = std.elf;
@@ -39,6 +39,13 @@ pub fn getauxval(index: usize) usize {
return 0;
}
+// Some architectures require 64bit parameters for some syscalls to be passed in
+// even-aligned register pair
+const require_aligned_register_pair = //
+ std.Target.current.cpu.arch.isMIPS() or
+ std.Target.current.cpu.arch.isARM() or
+ std.Target.current.cpu.arch.isThumb();
+
/// Get the errno from a syscall return value, or 0 for no error.
pub fn getErrno(r: usize) u12 {
const signed_r = @bitCast(isize, r);
@@ -69,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));
}
@@ -316,8 +327,31 @@ pub fn symlinkat(existing: [*:0]const u8, newfd: i32, newpath: [*:0]const u8) us
return syscall3(SYS_symlinkat, @ptrToInt(existing), @bitCast(usize, @as(isize, newfd)), @ptrToInt(newpath));
}
-pub fn pread(fd: i32, buf: [*]u8, count: usize, offset: usize) usize {
- return syscall4(SYS_pread, @bitCast(usize, @as(isize, fd)), @ptrToInt(buf), count, offset);
+pub fn pread(fd: i32, buf: [*]u8, count: usize, offset: u64) usize {
+ if (@hasDecl(@This(), "SYS_pread64")) {
+ if (require_aligned_register_pair) {
+ return syscall6(
+ SYS_pread64,
+ @bitCast(usize, @as(isize, fd)),
+ @ptrToInt(buf),
+ count,
+ 0,
+ @truncate(usize, offset),
+ @truncate(usize, offset >> 32),
+ );
+ } else {
+ return syscall5(
+ SYS_pread64,
+ @bitCast(usize, @as(isize, fd)),
+ @ptrToInt(buf),
+ count,
+ @truncate(usize, offset),
+ @truncate(usize, offset >> 32),
+ );
+ }
+ } else {
+ return syscall4(SYS_pread, @bitCast(usize, @as(isize, fd)), @ptrToInt(buf), count, offset);
+ }
}
pub fn access(path: [*:0]const u8, mode: u32) usize {
@@ -333,7 +367,7 @@ pub fn faccessat(dirfd: i32, path: [*:0]const u8, mode: u32, flags: u32) usize {
}
pub fn pipe(fd: *[2]i32) usize {
- if (builtin.arch == .mipsel) {
+ if (comptime builtin.arch.isMIPS()) {
return syscall_pipe(fd);
} else if (@hasDecl(@This(), "SYS_pipe")) {
return syscall1(SYS_pipe, @ptrToInt(fd));
@@ -846,6 +880,26 @@ pub fn sendto(fd: i32, buf: [*]const u8, len: usize, flags: u32, addr: ?*const s
return syscall6(SYS_sendto, @bitCast(usize, @as(isize, fd)), @ptrToInt(buf), len, flags, @ptrToInt(addr), @intCast(usize, alen));
}
+pub fn sendfile(outfd: i32, infd: i32, offset: ?*i64, count: usize) usize {
+ if (@hasDecl(@This(), "SYS_sendfile64")) {
+ return syscall4(
+ SYS_sendfile64,
+ @bitCast(usize, @as(isize, outfd)),
+ @bitCast(usize, @as(isize, infd)),
+ @ptrToInt(offset),
+ count,
+ );
+ } else {
+ return syscall4(
+ SYS_sendfile,
+ @bitCast(usize, @as(isize, outfd)),
+ @bitCast(usize, @as(isize, infd)),
+ @ptrToInt(offset),
+ count,
+ );
+ }
+}
+
pub fn socketpair(domain: i32, socket_type: i32, protocol: i32, fd: [2]i32) usize {
if (builtin.arch == .i386) {
return socketcall(SC_socketpair, &[4]usize{ @intCast(usize, domain), @intCast(usize, socket_type), @intCast(usize, protocol), @ptrToInt(&fd[0]) });
diff --git a/lib/std/os/test.zig b/lib/std/os/test.zig
index 197edd82c1..5f97597537 100644
--- 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| {
@@ -44,6 +44,68 @@ fn testThreadIdFn(thread_id: *Thread.Id) void {
thread_id.* = Thread.getCurrentId();
}
+test "sendfile" {
+ try fs.cwd().makePath("os_test_tmp");
+ defer fs.deleteTree("os_test_tmp") catch {};
+
+ var dir = try fs.cwd().openDirList("os_test_tmp");
+ defer dir.close();
+
+ const line1 = "line1\n";
+ const line2 = "second line\n";
+ var vecs = [_]os.iovec_const{
+ .{
+ .iov_base = line1,
+ .iov_len = line1.len,
+ },
+ .{
+ .iov_base = line2,
+ .iov_len = line2.len,
+ },
+ };
+
+ var src_file = try dir.createFileC("sendfile1.txt", .{ .read = true });
+ defer src_file.close();
+
+ try src_file.writevAll(&vecs);
+
+ var dest_file = try dir.createFileC("sendfile2.txt", .{ .read = true });
+ defer dest_file.close();
+
+ const header1 = "header1\n";
+ const header2 = "second header\n";
+ const trailer1 = "trailer1\n";
+ const trailer2 = "second trailer\n";
+ var hdtr = [_]os.iovec_const{
+ .{
+ .iov_base = header1,
+ .iov_len = header1.len,
+ },
+ .{
+ .iov_base = header2,
+ .iov_len = header2.len,
+ },
+ .{
+ .iov_base = trailer1,
+ .iov_len = trailer1.len,
+ },
+ .{
+ .iov_base = trailer2,
+ .iov_len = trailer2.len,
+ },
+ };
+
+ var written_buf: [header1.len + header2.len + 10 + trailer1.len + trailer2.len]u8 = undefined;
+ 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"));
+}
+
test "std.Thread.getCurrentId" {
if (builtin.single_threaded) return error.SkipZigTest;
@@ -103,7 +165,7 @@ test "AtomicFile" {
{
var af = try fs.AtomicFile.init(test_out_file, File.default_mode);
defer af.deinit();
- try af.file.write(test_content);
+ try af.file.writeAll(test_content);
try af.finish();
}
const content = try io.readFileAlloc(testing.allocator, test_out_file);
@@ -226,7 +288,7 @@ test "pipe" {
return error.SkipZigTest;
var fds = try os.pipe();
- try os.write(fds[1], "hello");
+ expect((try os.write(fds[1], "hello")) == 5);
var buf: [16]u8 = undefined;
expect((try os.read(fds[0], buf[0..])) == 5);
testing.expectEqualSlices(u8, buf[0..5], "hello");
@@ -248,7 +310,7 @@ test "memfd_create" {
else => |e| return e,
};
defer std.os.close(fd);
- try std.os.write(fd, "test");
+ expect((try std.os.write(fd, "test")) == 4);
try std.os.lseek_SET(fd, 0);
var buf: [10]u8 = undefined;
diff --git a/lib/std/os/windows.zig b/lib/std/os/windows.zig
index cc0d446b12..b8e14a220d 100644
--- 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 {
@@ -424,7 +424,7 @@ pub const WriteFileError = error{
Unexpected,
};
-pub fn WriteFile(handle: HANDLE, bytes: []const u8, offset: ?u64) WriteFileError!void {
+pub fn WriteFile(handle: HANDLE, bytes: []const u8, offset: ?u64) WriteFileError!usize {
if (std.event.Loop.instance) |loop| {
// TODO support async WriteFile with no offset
const off = offset.?;
@@ -445,8 +445,8 @@ pub fn WriteFile(handle: HANDLE, bytes: []const u8, offset: ?u64) WriteFileError
_ = CreateIoCompletionPort(fd, loop.os_data.io_port, undefined, undefined);
loop.beginOneEvent();
suspend {
- // TODO replace this @intCast with a loop that writes all the bytes
- _ = kernel32.WriteFile(fd, bytes.ptr, @intCast(windows.DWORD, bytes.len), null, &resume_node.base.overlapped);
+ const adjusted_len = math.cast(windows.DWORD, bytes.len) catch maxInt(windows.DWORD);
+ _ = kernel32.WriteFile(fd, bytes.ptr, adjusted_len, null, &resume_node.base.overlapped);
}
var bytes_transferred: windows.DWORD = undefined;
if (kernel32.GetOverlappedResult(fd, &resume_node.base.overlapped, &bytes_transferred, FALSE) == 0) {
@@ -460,6 +460,7 @@ pub fn WriteFile(handle: HANDLE, bytes: []const u8, offset: ?u64) WriteFileError
else => |err| return windows.unexpectedError(err),
}
}
+ return bytes_transferred;
} else {
var bytes_written: DWORD = undefined;
var overlapped_data: OVERLAPPED = undefined;
@@ -473,18 +474,19 @@ pub fn WriteFile(handle: HANDLE, bytes: []const u8, offset: ?u64) WriteFileError
};
break :blk &overlapped_data;
} else null;
- // TODO replace this @intCast with a loop that writes all the bytes
- if (kernel32.WriteFile(handle, bytes.ptr, @intCast(u32, bytes.len), &bytes_written, overlapped) == 0) {
+ const adjusted_len = math.cast(u32, bytes.len) catch maxInt(u32);
+ if (kernel32.WriteFile(handle, bytes.ptr, adjusted_len, &bytes_written, overlapped) == 0) {
switch (kernel32.GetLastError()) {
.INVALID_USER_BUFFER => return error.SystemResources,
.NOT_ENOUGH_MEMORY => return error.SystemResources,
.OPERATION_ABORTED => return error.OperationAborted,
.NOT_ENOUGH_QUOTA => return error.SystemResources,
- .IO_PENDING => unreachable, // this function is for blocking files only
+ .IO_PENDING => unreachable,
.BROKEN_PIPE => return error.BrokenPipe,
else => |err| return unexpectedError(err),
}
}
+ return bytes_written;
}
}
@@ -584,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/lib/std/special/docs/index.html b/lib/std/special/docs/index.html
index d1eeb89c23..2683418aef 100644
--- a/lib/std/special/docs/index.html
+++ b/lib/std/special/docs/index.html
@@ -2,6 +2,7 @@
<html lang="en">
<head>
<meta charset="utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1">
<title>Documentation - Zig</title>
<link rel="icon" href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAgklEQVR4AWMYWuD7EllJIM4G4g4g5oIJ/odhOJ8wToOxSTXgNxDHoeiBMfA4+wGShjyYOCkG/IGqWQziEzYAoUAeiF9D5U+DxEg14DRU7jWIT5IBIOdCxf+A+CQZAAoopEB7QJwBCBwHiip8UYmRdrAlDpIMgApwQZNnNii5Dq0MBgCxxycBnwEd+wAAAABJRU5ErkJggg==">
<style>
@@ -25,6 +26,8 @@
--search-sh-color: rgba(0, 0, 0, 0.18);
--help-sh-color: rgba(0, 0, 0, 0.75);
}
+
+ html, body { margin: 0; padding:0; height: 100%; }
a {
text-decoration: none;
@@ -69,6 +72,7 @@
max-width: 15vw;
min-width: 9.5rem;
overflow: auto;
+ -webkit-overflow-scrolling: touch;
overflow-wrap: break-word;
flex-shrink: 0;
flex-grow: 0;
@@ -79,6 +83,7 @@
.flex-right {
display: flex;
overflow: auto;
+ -webkit-overflow-scrolling: touch;
flex-grow: 1;
flex-shrink: 1;
@@ -175,6 +180,8 @@
border-bottom-color: var(--tx-color);
outline: none;
transition: border-bottom-color 0.35s, background 0.35s, box-shadow 0.35s;
+ border-radius: 0;
+ -webkit-appearance: none;
}
.docs .search:focus {
@@ -258,12 +265,12 @@
font-weight: bold;
}
- #listFnExamples {
+ .examples {
list-style-type: none;
margin: 0;
padding: 0;
}
- #listFnExamples li {
+ .examples li {
padding: 0.5em 0;
white-space: nowrap;
overflow-x: auto;
@@ -435,6 +442,76 @@
}
}
+
+ @media only screen and (max-width: 750px) {
+ .canvas {
+ overflow: auto;
+ }
+ .flex-main {
+ flex-direction: column;
+ display: block;
+ }
+ .sidebar {
+ min-width: calc(100vw - 2.8rem);
+ padding-left: 1.4rem;
+ padding-right: 1.4rem;
+ }
+ .logo {
+ max-width: 6.5rem;
+ }
+ .flex-main > .flex-filler {
+ display: none;
+ }
+ .flex-main > .flex-right > .flex-filler {
+ display: none;
+ }
+ .flex-main > .flex-right > .wrap {
+ max-width: 100vw;
+ }
+ .flex-main > .flex-right > .wrap > .docs {
+ padding-right: 1.4rem;
+ background: transparent;
+ }
+ .packages {
+ display: flex;
+ flex-wrap: wrap;
+ }
+ .table-container table {
+ display: flex;
+ flex-direction: column;
+ }
+ .table-container tr {
+ display: flex;
+ flex-direction: column;
+ }
+ .examples {
+ overflow-x: scroll;
+ -webkit-overflow-scrolling: touch;
+ max-width: 100vw;
+ margin-left: -1.4rem;
+ margin-right: -1.4rem;
+ }
+ .examples li {
+ width: max-content;
+ padding-left: 1.4rem;
+ padding-right: 1.4rem;
+ }
+ .mobile-scroll-container {
+ overflow-x: scroll;
+ -webkit-overflow-scrolling: touch;
+ margin-left: -1.4rem;
+ margin-right: -1.4rem;
+ max-width: 100vw;
+ }
+ .mobile-scroll-container > .scroll-item {
+ margin-left: 1.4rem;
+ margin-right: 1.4rem;
+ box-sizing: border-box;
+ width: max-content;
+ display: inline-block;
+ min-width: calc(100% - 2.8rem);
+ }
+ }
</style>
</head>
<body class="canvas">
@@ -471,7 +548,7 @@
<p id="status">Loading...</p>
<div id="sectNav" class="hidden"><ul id="listNav"></ul></div>
<div id="fnProto" class="hidden">
- <pre id="fnProtoCode"></pre>
+ <div class="mobile-scroll-container"><pre id="fnProtoCode" class="scroll-item"></pre></div>
</div>
<h1 id="hdrName" class="hidden"></h1>
<div id="fnNoExamples" class="hidden">
@@ -518,21 +595,27 @@
</div>
<div id="sectGlobalVars" class="hidden">
<h2>Global Variables</h2>
- <table>
- <tbody id="listGlobalVars"></tbody>
- </table>
+ <div class="table-container">
+ <table>
+ <tbody id="listGlobalVars"></tbody>
+ </table>
+ </div>
</div>
<div id="sectFns" class="hidden">
<h2>Functions</h2>
- <table>
- <tbody id="listFns"></tbody>
- </table>
+ <div class="table-container">
+ <table>
+ <tbody id="listFns"></tbody>
+ </table>
+ </div>
</div>
<div id="sectValues" class="hidden">
<h2>Values</h2>
- <table>
- <tbody id="listValues"></tbody>
- </table>
+ <div class="table-container">
+ <table>
+ <tbody id="listValues"></tbody>
+ </table>
+ </div>
</div>
<div id="sectErrSets" class="hidden">
<h2>Error Sets</h2>
@@ -540,7 +623,7 @@
</div>
<div id="fnExamples" class="hidden">
<h2>Examples</h2>
- <ul id="listFnExamples"></ul>
+ <ul id="listFnExamples" class="examples"></ul>
</div>
</section>
</div>
diff --git a/lib/std/special/docs/main.js b/lib/std/special/docs/main.js
index 138f79887d..b11e10caa7 100644
--- a/lib/std/special/docs/main.js
+++ b/lib/std/special/docs/main.js
@@ -1086,7 +1086,7 @@
var fieldNode = zigAnalysis.astNodes[containerNode.fields[i]];
var divDom = domListFields.children[i];
- var html = '<pre>' + escapeHtml(fieldNode.name);
+ var html = '<div class="mobile-scroll-container"><pre class="scroll-item">' + escapeHtml(fieldNode.name);
if (container.kind === typeKinds.Enum) {
html += ' = <span class="tok-number">' + field + '</span>';
@@ -1099,7 +1099,7 @@
}
}
- html += ',</pre>';
+ html += ',</pre></div>';
var docs = fieldNode.docs;
if (docs != null) {
diff --git a/lib/std/target.zig b/lib/std/target.zig
index 778a93b90d..74c5362a00 100644
--- a/lib/std/target.zig
+++ b/lib/std/target.zig
@@ -146,7 +146,6 @@ pub const Target = struct {
.freestanding,
.ananas,
.cloudabi,
- .dragonfly,
.fuchsia,
.kfreebsd,
.lv2,
@@ -215,6 +214,12 @@ pub const Target = struct {
.max = .{ .major = 6, .minor = 6 },
},
},
+ .dragonfly => return .{
+ .semver = .{
+ .min = .{ .major = 5, .minor = 8 },
+ .max = .{ .major = 5, .minor = 8 },
+ },
+ },
.linux => return .{
.linux = .{
diff --git a/lib/std/zig/cross_target.zig b/lib/std/zig/cross_target.zig
index 38ce6549f3..cec3ea05e5 100644
--- a/lib/std/zig/cross_target.zig
+++ b/lib/std/zig/cross_target.zig
@@ -102,7 +102,6 @@ pub const CrossTarget = struct {
.freestanding,
.ananas,
.cloudabi,
- .dragonfly,
.fuchsia,
.kfreebsd,
.lv2,
@@ -135,10 +134,11 @@ pub const CrossTarget = struct {
.freebsd,
.macosx,
.ios,
- .netbsd,
- .openbsd,
.tvos,
.watchos,
+ .netbsd,
+ .openbsd,
+ .dragonfly,
=> {
self.os_version_min = .{ .semver = os.version_range.semver.min };
self.os_version_max = .{ .semver = os.version_range.semver.max };
@@ -355,8 +355,10 @@ pub const CrossTarget = struct {
}
pub fn getCpuModel(self: CrossTarget) *const Target.Cpu.Model {
- if (self.cpu_model) |cpu_model| return cpu_model;
- return self.getCpu().model;
+ return switch (self.cpu_model) {
+ .explicit => |cpu_model| cpu_model,
+ else => self.getCpu().model,
+ };
}
pub fn getCpuFeatures(self: CrossTarget) Target.Cpu.Feature.Set {
@@ -645,7 +647,7 @@ pub const CrossTarget = struct {
self.glibc_version = SemVer{ .major = major, .minor = minor, .patch = patch };
}
- pub fn getObjectFormat(self: CrossTarget) ObjectFormat {
+ pub fn getObjectFormat(self: CrossTarget) Target.ObjectFormat {
return Target.getObjectFormatSimple(self.getOsTag(), self.getCpuArch());
}
@@ -675,9 +677,7 @@ pub const CrossTarget = struct {
.freestanding,
.ananas,
.cloudabi,
- .dragonfly,
.fuchsia,
- .ios,
.kfreebsd,
.lv2,
.solaris,
@@ -692,8 +692,6 @@ pub const CrossTarget = struct {
.amdhsa,
.ps4,
.elfiamcu,
- .tvos,
- .watchos,
.mesa3d,
.contiki,
.amdpal,
@@ -707,9 +705,13 @@ pub const CrossTarget = struct {
.freebsd,
.macosx,
+ .ios,
+ .tvos,
+ .watchos,
.netbsd,
.openbsd,
.linux,
+ .dragonfly,
=> {
var range_it = mem.separate(version_text, "...");
diff --git a/lib/std/zig/render.zig b/lib/std/zig/render.zig
index ee6ee37d5d..625aef3131 100644
--- a/lib/std/zig/render.zig
+++ b/lib/std/zig/render.zig
@@ -29,7 +29,7 @@ pub fn render(allocator: *mem.Allocator, stream: var, tree: *ast.Tree) (@TypeOf(
source_index: usize,
source: []const u8,
- fn write(iface_stream: *Stream, bytes: []const u8) StreamError!void {
+ fn write(iface_stream: *Stream, bytes: []const u8) StreamError!usize {
const self = @fieldParentPtr(MyStream, "stream", iface_stream);
if (!self.anything_changed_ptr.*) {
@@ -45,7 +45,7 @@ pub fn render(allocator: *mem.Allocator, stream: var, tree: *ast.Tree) (@TypeOf(
}
}
- try self.child_stream.write(bytes);
+ return self.child_stream.writeOnce(bytes);
}
};
var my_stream = MyStream{
@@ -2443,14 +2443,15 @@ const FindByteOutStream = struct {
};
}
- fn writeFn(out_stream: *Stream, bytes: []const u8) Error!void {
+ fn writeFn(out_stream: *Stream, bytes: []const u8) Error!usize {
const self = @fieldParentPtr(Self, "stream", out_stream);
- if (self.byte_found) return;
+ if (self.byte_found) return bytes.len;
self.byte_found = blk: {
for (bytes) |b|
if (b == self.byte) break :blk true;
break :blk false;
};
+ return bytes.len;
}
};
diff --git a/lib/std/zig/system.zig b/lib/std/zig/system.zig
index 38a90f4c60..7f0609c112 100644
--- a/lib/std/zig/system.zig
+++ b/lib/std/zig/system.zig
@@ -796,6 +796,7 @@ pub const NativeTargetInfo = struct {
error.SystemResources => return error.SystemResources,
error.IsDir => return error.UnableToReadElfFile,
error.BrokenPipe => return error.UnableToReadElfFile,
+ error.Unseekable => return error.UnableToReadElfFile,
error.ConnectionResetByPeer => return error.UnableToReadElfFile,
error.Unexpected => return error.Unexpected,
error.InputOutput => return error.FileSystem,