1const std = @import("std");
2const builtin = @import("builtin");
3const nio = @import("nio");
4const time = @import("time");
5
6const nfs = @import("./nfs.zig");
7const Dir = nfs.Dir;
8const File = @This();
9
10const os = builtin.target.os.tag;
11
12const sys = switch (os) {
13 .linux => @import("sys-linux"),
14 .macos => @import("sys-darwin"),
15 .freebsd => @import("sys-freebsd"),
16 .netbsd => @import("sys-netbsd"),
17 .openbsd => @import("sys-openbsd"),
18 else => unreachable,
19};
20
21pub const stdin = nfs.stdin;
22pub const stdout = nfs.stdout;
23pub const stderr = nfs.stderr;
24
25fd: nfs.Handle,
26
27// Resource allocation may fail; resource deallocation must succeed.
28pub fn close(self: File) void {
29 sys.close(@intFromEnum(self.fd)) catch if (builtin.mode == .Debug) unreachable;
30}
31
32const R = nio.Readable(@This(), ._bare);
33pub const readAll = R.readAll;
34pub const readAtLeast = R.readAtLeast;
35pub const readNoEof = R.readNoEof;
36pub const readAllAlloc = R.readAllAlloc;
37pub const readArray = R.readArray;
38pub const readByte = R.readByte;
39pub const readUntilDelimiterArrayList = R.readUntilDelimiterArrayList;
40pub const readUntilDelimiterAlloc = R.readUntilDelimiterAlloc;
41pub const readUntilDelimiterOrEofAlloc = R.readUntilDelimiterOrEofAlloc;
42pub const readUntilDelimitersBuf = R.readUntilDelimitersBuf;
43pub const readUntilDelimitersArrayList = R.readUntilDelimitersArrayList;
44pub const readAlloc = R.readAlloc;
45pub const readInt = R.readInt;
46pub const readUntilDelimitersAlloc = R.readUntilDelimitersAlloc;
47pub const readUntilDelimiter = R.readUntilDelimiter;
48pub const readUntilDelimiterOrEof = R.readUntilDelimiterOrEof;
49pub const readExpected = R.readExpected;
50pub const readType = R.readType;
51pub const skipBytes = R.skipBytes;
52pub const skipUntilDelimiterOrEof = R.skipUntilDelimiterOrEof;
53pub const pipeTo = R.pipeTo;
54
55pub const ReadError = sys.errno.Error;
56pub fn read(self: File, buffer: []u8) ReadError!usize {
57 return sys.read(@intFromEnum(self.fd), buffer);
58}
59
60pub fn anyReadable(self: File) nio.AnyReadable {
61 const S = struct {
62 fn read(s: *allowzero anyopaque, buffer: []u8) anyerror!usize {
63 const fd: nfs.Handle = @enumFromInt(@intFromPtr(s));
64 const f: File = .{ .fd = fd };
65 return f.read(buffer);
66 }
67 };
68 return .{
69 .vtable = &.{ .read = S.read },
70 .state = @ptrFromInt(@as(usize, @bitCast(@as(isize, @intCast(@intFromEnum(self.fd)))))),
71 };
72}
73
74pub fn stat(self: File) !Stat {
75 return .fromPosix(try sys.fstat(@intFromEnum(self.fd)));
76}
77
78pub fn getEndPos(self: File) !u64 {
79 return (try self.stat()).size;
80}
81
82pub fn readToEndAlloc(self: File, allocator: std.mem.Allocator, max_bytes: usize, size_hint: ?usize) ![:0]u8 {
83 var array_list = try std.array_list.Managed(u8).initCapacity(allocator, @min(size_hint orelse 1023, max_bytes) + 1);
84 defer array_list.deinit();
85 self.readAllArrayList(&array_list, max_bytes) catch |err| switch (err) {
86 error.StreamTooLong => return error.FileTooBig,
87 else => |e| return e,
88 };
89 return try array_list.toOwnedSliceSentinel(0);
90}
91
92pub fn readAllArrayList(self: File, array_list: *std.array_list.Managed(u8), max_append_size: usize) anyerror!void {
93 try array_list.ensureTotalCapacity(@min(max_append_size, 4096));
94 const original_len = array_list.items.len;
95 var start_index: usize = original_len;
96 while (true) {
97 array_list.expandToCapacity();
98 const dest_slice = array_list.items[start_index..];
99 const bytes_read = try self.readAll(dest_slice);
100 start_index += bytes_read;
101
102 if (start_index - original_len > max_append_size) {
103 array_list.shrinkAndFree(original_len + max_append_size);
104 return error.StreamTooLong;
105 }
106 if (bytes_read != dest_slice.len) {
107 array_list.shrinkAndFree(start_index);
108 return;
109 }
110 // This will trigger ArrayList to expand superlinearly at whatever its growth rate is.
111 try array_list.ensureTotalCapacity(start_index + 1);
112 }
113}
114
115const W = nio.Writable(@This(), ._bare);
116pub const writeAll = W.writeAll;
117pub const writevAll = W.writevAll;
118pub const writeByteNTimes = W.writeByteNTimes;
119pub const writeNTimes = W.writeNTimes;
120pub const writeInt = W.writeInt;
121pub const writeStruct = W.writeStruct;
122pub const writeIntPretty = W.writeIntPretty;
123pub const print = W.print;
124
125pub const WriteError = sys.errno.Error;
126pub fn write(self: File, buffer: []const u8) WriteError!usize {
127 return sys.write(@intFromEnum(self.fd), buffer);
128}
129pub fn writev(self: File, iovec: []const sys.struct_iovec) WriteError!usize {
130 return sys.writev(@intFromEnum(self.fd), iovec);
131}
132
133pub const Stat = struct {
134 inode: INode,
135 size: u64,
136 mode: Mode,
137 /// Last access time in nanoseconds, relative to UTC 1970-01-01.
138 atime: i128,
139 /// Last modification time in nanoseconds, relative to UTC 1970-01-01.
140 mtime: i128,
141 /// Last status/metadata change time in nanoseconds, relative to UTC 1970-01-01.
142 ctime: i128,
143
144 pub fn fromPosix(st: sys.struct_stat) Stat {
145 if (os == .macos) {
146 return .{
147 .inode = st.ino,
148 .size = @bitCast(st.size),
149 .mode = st.mode,
150 .atime = @as(i128, st.atimespec.sec) * time.ns_per_s + st.atimespec.nsec,
151 .mtime = @as(i128, st.mtimespec.sec) * time.ns_per_s + st.mtimespec.nsec,
152 .ctime = @as(i128, st.ctimespec.sec) * time.ns_per_s + st.ctimespec.nsec,
153 };
154 }
155 return .{
156 .inode = st.ino,
157 .size = @bitCast(st.size),
158 .mode = st.mode,
159 .atime = @as(i128, st.atim.sec) * time.ns_per_s + st.atim.nsec,
160 .mtime = @as(i128, st.mtim.sec) * time.ns_per_s + st.mtim.nsec,
161 .ctime = @as(i128, st.ctim.sec) * time.ns_per_s + st.ctim.nsec,
162 };
163 }
164
165 pub fn kind(self: Stat) sys.DT {
166 const m = self.mode & sys.S.IFMT;
167 switch (m) {
168 sys.S.IFBLK => return .BLK,
169 sys.S.IFCHR => return .CHR,
170 sys.S.IFIFO => return .FIFO,
171 sys.S.IFREG => return .REG,
172 sys.S.IFDIR => return .DIR,
173 sys.S.IFLNK => return .LNK,
174 sys.S.IFSOCK => return .SOCK,
175 sys.S.IFWHT => return .WHT,
176 else => {},
177 }
178 return .UNKNOWN;
179 }
180};
181
182pub const INode = sys.ino_t;
183
184pub const Mode = sys.mode_t;
185
186pub const Kind = sys.DT;
187
188/// Maps entire file content into memory with a single syscall.
189/// Release with `nfs.munmap`.
190pub fn mmap(self: File) ![]const u8 {
191 return mmapRegion(self, 0, (try self.stat()).size);
192}
193
194/// Maps file content into memory with a single syscall.
195/// Release with `nfs.munmap`.
196pub fn mmapRegion(self: File, offset: sys.off_t, len: usize) ![]const u8 {
197 return sys.mmap(
198 null,
199 len,
200 sys.PROT.READ,
201 sys.MAP.PRIVATE,
202 @intFromEnum(self.fd),
203 offset,
204 );
205}
206
207pub fn utime(self: File, times: [2]sys.struct_timespec) !void {
208 return sys.futimens(@intFromEnum(self.fd), times);
209}
210
211pub fn isatty(self: File) bool {
212 return sys.libc.isatty(@intFromEnum(self.fd)) == 1;
213}
214
215pub fn seekTo(self: File, pos: u64) !void {
216 return sys.lseek(@intFromEnum(self.fd), @bitCast(pos), sys.SEEK.SET);
217}
218
219pub fn chmod(self: File, mode: Mode) !void {
220 return sys.fchmod(@intFromEnum(self.fd), mode);
221}
222
223pub fn realpath(self: File, buf: *[sys.PATH_MAX]u8) ![:0]u8 {
224 return nfs.realdpath(self.fd, buf);
225}
226
227pub fn realpathAlloc(self: File, allocator: std.mem.Allocator) ![:0]u8 {
228 var buf: [sys.PATH_MAX]u8 = undefined;
229 const actual = try self.realpath(&buf);
230 return allocator.dupeZ(u8, actual);
231}
232
233pub fn dup(self: File) !File {
234 return .{ .fd = @enumFromInt(try sys.dup(@intFromEnum(self.fd))) };
235}