diff options
Diffstat (limited to 'lib/std')
| -rw-r--r-- | lib/std/build.zig | 4 | ||||
| -rw-r--r-- | lib/std/build/emit_raw.zig | 8 | ||||
| -rw-r--r-- | lib/std/build/write_file.zig | 2 | ||||
| -rw-r--r-- | lib/std/c.zig | 5 | ||||
| -rw-r--r-- | lib/std/c/darwin.zig | 16 | ||||
| -rw-r--r-- | lib/std/c/freebsd.zig | 16 | ||||
| -rw-r--r-- | lib/std/c/linux.zig | 7 | ||||
| -rw-r--r-- | lib/std/event/loop.zig | 22 | ||||
| -rw-r--r-- | lib/std/fs.zig | 204 | ||||
| -rw-r--r-- | lib/std/fs/file.zig | 231 | ||||
| -rw-r--r-- | lib/std/fs/watch.zig | 5 | ||||
| -rw-r--r-- | lib/std/io.zig | 42 | ||||
| -rw-r--r-- | lib/std/io/c_out_stream.zig | 4 | ||||
| -rw-r--r-- | lib/std/io/out_stream.zig | 13 | ||||
| -rw-r--r-- | lib/std/io/test.zig | 6 | ||||
| -rw-r--r-- | lib/std/os.zig | 754 | ||||
| -rw-r--r-- | lib/std/os/bits/darwin.zig | 3 | ||||
| -rw-r--r-- | lib/std/os/bits/linux/arm64.zig | 1 | ||||
| -rw-r--r-- | lib/std/os/linux.zig | 62 | ||||
| -rw-r--r-- | lib/std/os/test.zig | 72 | ||||
| -rw-r--r-- | lib/std/os/windows.zig | 85 | ||||
| -rw-r--r-- | lib/std/special/docs/index.html | 109 | ||||
| -rw-r--r-- | lib/std/special/docs/main.js | 4 | ||||
| -rw-r--r-- | lib/std/target.zig | 7 | ||||
| -rw-r--r-- | lib/std/zig/cross_target.zig | 22 | ||||
| -rw-r--r-- | lib/std/zig/render.zig | 9 | ||||
| -rw-r--r-- | lib/std/zig/system.zig | 1 |
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, |
