DoxigAlpha

sendFile

Function parameters

Parameters

#
io_w:*std.Io.Writer
file_reader:*Reader
limit:std.Io.Limit

Type definitions in this namespace

Types

#
Permissions
Cross-platform representation of permissions on a file.
Reader
Memoizes key information about a file handle such as:

Functions in this namespace

Functions

#
close
Upon success, the stream is in an uninitialized state.
sync
Blocks until all pending file contents and metadata modifications
isTty
Test whether the file refers to a terminal.
getOrEnableAnsiEscapeSupport
Returns whether or not ANSI escape codes will be treated as such,
supportsAnsiEscapeCodes
Test whether ANSI escape codes will be treated as such without
setEndPos
Shrinks or expands the file.
seekBy
Repositions read/write file offset relative to the current offset.
seekFromEnd
Repositions read/write file offset relative to the end.
seekTo
Repositions read/write file offset relative to the beginning.
getPos
TODO: integrate with async I/O
getEndPos
TODO: integrate with async I/O
mode
TODO: integrate with async I/O
stat
Returns `Stat` containing basic information about the `File`.
chmod
Changes the mode of the file.
chown
Changes the owner and group of the file.
setPermissions
Sets permissions according to the provided `Permissions` struct.
updateTimes
The underlying file system may have a different granularity than nanoseconds,
readToEndAlloc
Deprecated in favor of `Reader`.
readToEndAllocOptions
Deprecated in favor of `Reader`.
readAll
Deprecated in favor of `Reader`.
pread
On Windows, this function currently does alter the file pointer.
preadAll
Deprecated in favor of `Reader`.
readv
See https://github.com/ziglang/zig/issues/7699
readvAll
Deprecated in favor of `Reader`.
preadv
See https://github.com/ziglang/zig/issues/7699
preadvAll
Deprecated in favor of `Reader`.
writeAll
Deprecated in favor of `Writer`.
pwrite
On Windows, this function currently does alter the file pointer.
pwriteAll
Deprecated in favor of `Writer`.
writev
See https://github.com/ziglang/zig/issues/7699
writevAll
Deprecated in favor of `Writer`.
pwritev
See https://github.com/ziglang/zig/issues/7699
pwritevAll
Deprecated in favor of `Writer`.
copyRange
Deprecated in favor of `Writer`.
copyRangeAll
Deprecated in favor of `Writer`.
deprecatedReader
Deprecated in favor of `Reader`.
deprecatedWriter
Deprecated in favor of `Writer`.
reader
Defaults to positional reading; falls back to streaming.
readerStreaming
Positional is more threadsafe, since the global seek position is not
writer
Defaults to positional reading; falls back to streaming.
writerStreaming
Positional is more threadsafe, since the global seek position is not
lock
Blocks when an incompatible lock is held by another process.
unlock
Assumes the file is locked.
tryLock
Attempts to obtain a lock, returning `true` if the lock is
downgradeLock
Assumes the file is already locked in exclusive mode.

Error sets in this namespace

Error Sets

#

= posix.fd_t

Values

#
Handle
= posix.fd_t
Mode
= posix.mode_t
INode
= posix.ino_t
Uid
= posix.uid_t
Gid
= posix.gid_t
default_mode
This is the default mode given to POSIX operating systems for creating

Source

Implementation

#
pub fn sendFile(
    io_w: *std.Io.Writer,
    file_reader: *Reader,
    limit: std.Io.Limit,
) std.Io.Writer.FileError!usize {
    const reader_buffered = file_reader.interface.buffered();
    if (reader_buffered.len >= @intFromEnum(limit))
        return sendFileBuffered(io_w, file_reader, limit.slice(reader_buffered));
    const writer_buffered = io_w.buffered();
    const file_limit = @intFromEnum(limit) - reader_buffered.len;
    const w: *Writer = @alignCast(@fieldParentPtr("interface", io_w));
    const out_fd = w.file.handle;
    const in_fd = file_reader.file.handle;

    if (file_reader.size) |size| {
        if (size - file_reader.pos == 0) {
            if (reader_buffered.len != 0) {
                return sendFileBuffered(io_w, file_reader, reader_buffered);
            } else {
                return error.EndOfStream;
            }
        }
    }

    if (native_os == .freebsd and w.mode == .streaming) sf: {
        // Try using sendfile on FreeBSD.
        if (w.sendfile_err != null) break :sf;
        const offset = std.math.cast(std.c.off_t, file_reader.pos) orelse break :sf;
        var hdtr_data: std.c.sf_hdtr = undefined;
        var headers: [2]posix.iovec_const = undefined;
        var headers_i: u8 = 0;
        if (writer_buffered.len != 0) {
            headers[headers_i] = .{ .base = writer_buffered.ptr, .len = writer_buffered.len };
            headers_i += 1;
        }
        if (reader_buffered.len != 0) {
            headers[headers_i] = .{ .base = reader_buffered.ptr, .len = reader_buffered.len };
            headers_i += 1;
        }
        const hdtr: ?*std.c.sf_hdtr = if (headers_i == 0) null else b: {
            hdtr_data = .{
                .headers = &headers,
                .hdr_cnt = headers_i,
                .trailers = null,
                .trl_cnt = 0,
            };
            break :b &hdtr_data;
        };
        var sbytes: std.c.off_t = undefined;
        const nbytes: usize = @min(file_limit, maxInt(usize));
        const flags = 0;
        switch (posix.errno(std.c.sendfile(in_fd, out_fd, offset, nbytes, hdtr, &sbytes, flags))) {
            .SUCCESS, .INTR => {},
            .INVAL, .OPNOTSUPP, .NOTSOCK, .NOSYS => w.sendfile_err = error.UnsupportedOperation,
            .BADF => if (builtin.mode == .Debug) @panic("race condition") else {
                w.sendfile_err = error.Unexpected;
            },
            .FAULT => if (builtin.mode == .Debug) @panic("segmentation fault") else {
                w.sendfile_err = error.Unexpected;
            },
            .NOTCONN => w.sendfile_err = error.BrokenPipe,
            .AGAIN, .BUSY => if (sbytes == 0) {
                w.sendfile_err = error.WouldBlock;
            },
            .IO => w.sendfile_err = error.InputOutput,
            .PIPE => w.sendfile_err = error.BrokenPipe,
            .NOBUFS => w.sendfile_err = error.SystemResources,
            else => |err| w.sendfile_err = posix.unexpectedErrno(err),
        }
        if (w.sendfile_err != null) {
            // Give calling code chance to observe the error before trying
            // something else.
            return 0;
        }
        if (sbytes == 0) {
            file_reader.size = file_reader.pos;
            return error.EndOfStream;
        }
        const consumed = io_w.consume(@intCast(sbytes));
        file_reader.seekBy(@intCast(consumed)) catch return error.ReadFailed;
        return consumed;
    }

    if (native_os.isDarwin() and w.mode == .streaming) sf: {
        // Try using sendfile on macOS.
        if (w.sendfile_err != null) break :sf;
        const offset = std.math.cast(std.c.off_t, file_reader.pos) orelse break :sf;
        var hdtr_data: std.c.sf_hdtr = undefined;
        var headers: [2]posix.iovec_const = undefined;
        var headers_i: u8 = 0;
        if (writer_buffered.len != 0) {
            headers[headers_i] = .{ .base = writer_buffered.ptr, .len = writer_buffered.len };
            headers_i += 1;
        }
        if (reader_buffered.len != 0) {
            headers[headers_i] = .{ .base = reader_buffered.ptr, .len = reader_buffered.len };
            headers_i += 1;
        }
        const hdtr: ?*std.c.sf_hdtr = if (headers_i == 0) null else b: {
            hdtr_data = .{
                .headers = &headers,
                .hdr_cnt = headers_i,
                .trailers = null,
                .trl_cnt = 0,
            };
            break :b &hdtr_data;
        };
        const max_count = maxInt(i32); // Avoid EINVAL.
        var len: std.c.off_t = @min(file_limit, max_count);
        const flags = 0;
        switch (posix.errno(std.c.sendfile(in_fd, out_fd, offset, &len, hdtr, flags))) {
            .SUCCESS, .INTR => {},
            .OPNOTSUPP, .NOTSOCK, .NOSYS => w.sendfile_err = error.UnsupportedOperation,
            .BADF => if (builtin.mode == .Debug) @panic("race condition") else {
                w.sendfile_err = error.Unexpected;
            },
            .FAULT => if (builtin.mode == .Debug) @panic("segmentation fault") else {
                w.sendfile_err = error.Unexpected;
            },
            .INVAL => if (builtin.mode == .Debug) @panic("invalid API usage") else {
                w.sendfile_err = error.Unexpected;
            },
            .NOTCONN => w.sendfile_err = error.BrokenPipe,
            .AGAIN => if (len == 0) {
                w.sendfile_err = error.WouldBlock;
            },
            .IO => w.sendfile_err = error.InputOutput,
            .PIPE => w.sendfile_err = error.BrokenPipe,
            else => |err| w.sendfile_err = posix.unexpectedErrno(err),
        }
        if (w.sendfile_err != null) {
            // Give calling code chance to observe the error before trying
            // something else.
            return 0;
        }
        if (len == 0) {
            file_reader.size = file_reader.pos;
            return error.EndOfStream;
        }
        const consumed = io_w.consume(@bitCast(len));
        file_reader.seekBy(@intCast(consumed)) catch return error.ReadFailed;
        return consumed;
    }

    if (native_os == .linux and w.mode == .streaming) sf: {
        // Try using sendfile on Linux.
        if (w.sendfile_err != null) break :sf;
        // Linux sendfile does not support headers.
        if (writer_buffered.len != 0 or reader_buffered.len != 0)
            return sendFileBuffered(io_w, file_reader, reader_buffered);
        const max_count = 0x7ffff000; // Avoid EINVAL.
        var off: std.os.linux.off_t = undefined;
        const off_ptr: ?*std.os.linux.off_t, const count: usize = switch (file_reader.mode) {
            .positional => o: {
                const size = file_reader.getSize() catch return 0;
                off = std.math.cast(std.os.linux.off_t, file_reader.pos) orelse return error.ReadFailed;
                break :o .{ &off, @min(@intFromEnum(limit), size - file_reader.pos, max_count) };
            },
            .streaming => .{ null, limit.minInt(max_count) },
            .streaming_reading, .positional_reading => break :sf,
            .failure => return error.ReadFailed,
        };
        const n = std.os.linux.wrapped.sendfile(out_fd, in_fd, off_ptr, count) catch |err| switch (err) {
            error.Unseekable => {
                file_reader.mode = file_reader.mode.toStreaming();
                const pos = file_reader.pos;
                if (pos != 0) {
                    file_reader.pos = 0;
                    file_reader.seekBy(@intCast(pos)) catch {
                        file_reader.mode = .failure;
                        return error.ReadFailed;
                    };
                }
                return 0;
            },
            else => |e| {
                w.sendfile_err = e;
                return 0;
            },
        };
        if (n == 0) {
            file_reader.size = file_reader.pos;
            return error.EndOfStream;
        }
        file_reader.pos += n;
        w.pos += n;
        return n;
    }

    const copy_file_range = switch (native_os) {
        .freebsd => std.os.freebsd.copy_file_range,
        .linux => std.os.linux.wrapped.copy_file_range,
        else => {},
    };
    if (@TypeOf(copy_file_range) != void) cfr: {
        if (w.copy_file_range_err != null) break :cfr;
        if (writer_buffered.len != 0 or reader_buffered.len != 0)
            return sendFileBuffered(io_w, file_reader, reader_buffered);
        var off_in: i64 = undefined;
        var off_out: i64 = undefined;
        const off_in_ptr: ?*i64 = switch (file_reader.mode) {
            .positional_reading, .streaming_reading => return error.Unimplemented,
            .positional => p: {
                off_in = @intCast(file_reader.pos);
                break :p &off_in;
            },
            .streaming => null,
            .failure => return error.WriteFailed,
        };
        const off_out_ptr: ?*i64 = switch (w.mode) {
            .positional_reading, .streaming_reading => return error.Unimplemented,
            .positional => p: {
                off_out = @intCast(w.pos);
                break :p &off_out;
            },
            .streaming => null,
            .failure => return error.WriteFailed,
        };
        const n = copy_file_range(in_fd, off_in_ptr, out_fd, off_out_ptr, @intFromEnum(limit), 0) catch |err| {
            w.copy_file_range_err = err;
            return 0;
        };
        if (n == 0) {
            file_reader.size = file_reader.pos;
            return error.EndOfStream;
        }
        file_reader.pos += n;
        w.pos += n;
        return n;
    }

    if (builtin.os.tag.isDarwin()) fcf: {
        if (w.fcopyfile_err != null) break :fcf;
        if (file_reader.pos != 0) break :fcf;
        if (w.pos != 0) break :fcf;
        if (limit != .unlimited) break :fcf;
        const size = file_reader.getSize() catch break :fcf;
        if (writer_buffered.len != 0 or reader_buffered.len != 0)
            return sendFileBuffered(io_w, file_reader, reader_buffered);
        const rc = std.c.fcopyfile(in_fd, out_fd, null, .{ .DATA = true });
        switch (posix.errno(rc)) {
            .SUCCESS => {},
            .INVAL => if (builtin.mode == .Debug) @panic("invalid API usage") else {
                w.fcopyfile_err = error.Unexpected;
                return 0;
            },
            .NOMEM => {
                w.fcopyfile_err = error.OutOfMemory;
                return 0;
            },
            .OPNOTSUPP => {
                w.fcopyfile_err = error.OperationNotSupported;
                return 0;
            },
            else => |err| {
                w.fcopyfile_err = posix.unexpectedErrno(err);
                return 0;
            },
        }
        file_reader.pos = size;
        w.pos = size;
        return size;
    }

    return error.Unimplemented;
}