authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2023-11-22 14:12:53-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2023-11-22 15:24:57-07:00
log0a536a7c9020c891001be053a4c0b354961cc46f
tree6303c74109fd4c1bd219bc65608ab83fa10946fa
parente00e9c0fbf0913154f0f0de95e8d28a6d0ace95a

std.fs.File: flatten struct


6 files changed, 1639 insertions(+), 1633 deletions(-)

CMakeLists.txt+1-1
......@@ -249,7 +249,7 @@ set(ZIG_STAGE2_SOURCES
249249 "${CMAKE_SOURCE_DIR}/lib/std/fs.zig"
250250 "${CMAKE_SOURCE_DIR}/lib/std/fs/AtomicFile.zig"
251251 "${CMAKE_SOURCE_DIR}/lib/std/fs/Dir.zig"
252 "${CMAKE_SOURCE_DIR}/lib/std/fs/file.zig"
252 "${CMAKE_SOURCE_DIR}/lib/std/fs/File.zig"
253253 "${CMAKE_SOURCE_DIR}/lib/std/fs/get_app_data_dir.zig"
254254 "${CMAKE_SOURCE_DIR}/lib/std/fs/path.zig"
255255 "${CMAKE_SOURCE_DIR}/lib/std/hash.zig"
lib/std/fs.zig+7-5
......@@ -10,16 +10,16 @@ const assert = std.debug.assert;
1010
1111const is_darwin = builtin.os.tag.isDarwin();
1212
13pub const Dir = @import("fs/Dir.zig");
1413pub const AtomicFile = @import("fs/AtomicFile.zig");
14pub const Dir = @import("fs/Dir.zig");
15pub const File = @import("fs/File.zig");
16pub const path = @import("fs/path.zig");
1517
1618pub const has_executable_bit = switch (builtin.os.tag) {
1719 .windows, .wasi => false,
1820 else => true,
1921};
2022
21pub const path = @import("fs/path.zig");
22pub const File = @import("fs/file.zig").File;
2323pub const wasi = @import("fs/wasi.zig");
2424
2525// TODO audit these APIs with respect to Dir and absolute paths
......@@ -94,6 +94,7 @@ pub const need_async_thread = std.io.is_async and switch (builtin.os.tag) {
9494};
9595
9696/// TODO remove the allocator requirement from this API
97/// TODO move to Dir
9798pub fn atomicSymLink(allocator: Allocator, existing_path: []const u8, new_path: []const u8) !void {
9899 if (cwd().symLink(existing_path, new_path, .{})) {
99100 return;
......@@ -104,7 +105,7 @@ pub fn atomicSymLink(allocator: Allocator, existing_path: []const u8, new_path:
104105
105106 const dirname = path.dirname(new_path) orelse ".";
106107
107 var rand_buf: [AtomicFile.RANDOM_BYTES]u8 = undefined;
108 var rand_buf: [AtomicFile.random_bytes_len]u8 = undefined;
108109 const tmp_path = try allocator.alloc(u8, dirname.len + 1 + base64_encoder.calcSize(rand_buf.len));
109110 defer allocator.free(tmp_path);
110111 @memcpy(tmp_path[0..dirname.len], dirname);
......@@ -634,8 +635,9 @@ test {
634635 _ = &copyFileAbsolute;
635636 _ = &updateFileAbsolute;
636637 }
637 _ = &File;
638 _ = &AtomicFile;
638639 _ = &Dir;
640 _ = &File;
639641 _ = &path;
640642 _ = @import("fs/test.zig");
641643 _ = @import("fs/get_app_data_dir.zig");
lib/std/fs/AtomicFile.zig+6-5
......@@ -1,6 +1,6 @@
11file: File,
22// TODO either replace this with rand_buf or use []u16 on Windows
3tmp_path_buf: [TMP_PATH_LEN:0]u8,
3tmp_path_buf: [tmp_path_len:0]u8,
44dest_basename: []const u8,
55file_open: bool,
66file_exists: bool,
......@@ -9,8 +9,8 @@ dir: Dir,
99
1010pub const InitError = File.OpenError;
1111
12const RANDOM_BYTES = 12;
13const TMP_PATH_LEN = fs.base64_encoder.calcSize(RANDOM_BYTES);
12pub const random_bytes_len = 12;
13const tmp_path_len = fs.base64_encoder.calcSize(random_bytes_len);
1414
1515/// Note that the `Dir.atomicFile` API may be more handy than this lower-level function.
1616pub fn init(
......@@ -19,8 +19,8 @@ pub fn init(
1919 dir: Dir,
2020 close_dir_on_deinit: bool,
2121) InitError!AtomicFile {
22 var rand_buf: [RANDOM_BYTES]u8 = undefined;
23 var tmp_path_buf: [TMP_PATH_LEN:0]u8 = undefined;
22 var rand_buf: [random_bytes_len]u8 = undefined;
23 var tmp_path_buf: [tmp_path_len:0]u8 = undefined;
2424
2525 while (true) {
2626 std.crypto.random.bytes(rand_buf[0..]);
......@@ -81,4 +81,5 @@ const File = std.fs.File;
8181const Dir = std.fs.Dir;
8282const fs = std.fs;
8383const assert = std.debug.assert;
84// https://github.com/ziglang/zig/issues/5019
8485const posix = std.os;
lib/std/fs/Dir.zig+1
......@@ -2527,6 +2527,7 @@ const builtin = @import("builtin");
25272527const std = @import("../std.zig");
25282528const File = std.fs.File;
25292529const AtomicFile = std.fs.AtomicFile;
2530// https://github.com/ziglang/zig/issues/5019
25302531const posix = std.os;
25312532const mem = std.mem;
25322533const fs = std.fs;
lib/std/fs/File.zig created+1624
......@@ -0,0 +1,1624 @@
1/// The OS-specific file descriptor or file handle.
2handle: Handle,
3
4/// On some systems, such as Linux, file system file descriptors are incapable
5/// of non-blocking I/O. This forces us to perform asynchronous I/O on a dedicated thread,
6/// to achieve non-blocking file-system I/O. To do this, `File` must be aware of whether
7/// it is a file system file descriptor, or, more specifically, whether the I/O is always
8/// blocking.
9capable_io_mode: io.ModeOverride = io.default_mode,
10
11/// Furthermore, even when `std.options.io_mode` is async, it is still sometimes desirable
12/// to perform blocking I/O, although not by default. For example, when printing a
13/// stack trace to stderr. This field tracks both by acting as an overriding I/O mode.
14/// When not building in async I/O mode, the type only has the `.blocking` tag, making
15/// it a zero-bit type.
16intended_io_mode: io.ModeOverride = io.default_mode,
17
18pub const Handle = posix.fd_t;
19pub const Mode = posix.mode_t;
20pub const INode = posix.ino_t;
21pub const Uid = posix.uid_t;
22pub const Gid = posix.gid_t;
23
24pub const Kind = enum {
25 block_device,
26 character_device,
27 directory,
28 named_pipe,
29 sym_link,
30 file,
31 unix_domain_socket,
32 whiteout,
33 door,
34 event_port,
35 unknown,
36};
37
38/// This is the default mode given to POSIX operating systems for creating
39/// files. `0o666` is "-rw-rw-rw-" which is counter-intuitive at first,
40/// since most people would expect "-rw-r--r--", for example, when using
41/// the `touch` command, which would correspond to `0o644`. However, POSIX
42/// libc implementations use `0o666` inside `fopen` and then rely on the
43/// process-scoped "umask" setting to adjust this number for file creation.
44pub const default_mode = switch (builtin.os.tag) {
45 .windows => 0,
46 .wasi => 0,
47 else => 0o666,
48};
49
50pub const OpenError = error{
51 SharingViolation,
52 PathAlreadyExists,
53 FileNotFound,
54 AccessDenied,
55 PipeBusy,
56 NameTooLong,
57 /// On Windows, file paths must be valid Unicode.
58 InvalidUtf8,
59 /// On Windows, file paths cannot contain these characters:
60 /// '/', '*', '?', '"', '<', '>', '|'
61 BadPathName,
62 Unexpected,
63 /// On Windows, `\\server` or `\\server\share` was not found.
64 NetworkNotFound,
65} || posix.OpenError || posix.FlockError;
66
67pub const OpenMode = enum {
68 read_only,
69 write_only,
70 read_write,
71};
72
73pub const Lock = enum {
74 none,
75 shared,
76 exclusive,
77};
78
79pub const OpenFlags = struct {
80 mode: OpenMode = .read_only,
81
82 /// Open the file with an advisory lock to coordinate with other processes
83 /// accessing it at the same time. An exclusive lock will prevent other
84 /// processes from acquiring a lock. A shared lock will prevent other
85 /// processes from acquiring a exclusive lock, but does not prevent
86 /// other process from getting their own shared locks.
87 ///
88 /// The lock is advisory, except on Linux in very specific circumstances[1].
89 /// This means that a process that does not respect the locking API can still get access
90 /// to the file, despite the lock.
91 ///
92 /// On these operating systems, the lock is acquired atomically with
93 /// opening the file:
94 /// * Darwin
95 /// * DragonFlyBSD
96 /// * FreeBSD
97 /// * Haiku
98 /// * NetBSD
99 /// * OpenBSD
100 /// On these operating systems, the lock is acquired via a separate syscall
101 /// after opening the file:
102 /// * Linux
103 /// * Windows
104 ///
105 /// [1]: https://www.kernel.org/doc/Documentation/filesystems/mandatory-locking.txt
106 lock: Lock = .none,
107
108 /// Sets whether or not to wait until the file is locked to return. If set to true,
109 /// `error.WouldBlock` will be returned. Otherwise, the file will wait until the file
110 /// is available to proceed.
111 /// In async I/O mode, non-blocking at the OS level is
112 /// determined by `intended_io_mode`, and `true` means `error.WouldBlock` is returned,
113 /// and `false` means `error.WouldBlock` is handled by the event loop.
114 lock_nonblocking: bool = false,
115
116 /// Setting this to `.blocking` prevents `O.NONBLOCK` from being passed even
117 /// if `std.io.is_async`. It allows the use of `nosuspend` when calling functions
118 /// related to opening the file, reading, writing, and locking.
119 intended_io_mode: io.ModeOverride = io.default_mode,
120
121 /// Set this to allow the opened file to automatically become the
122 /// controlling TTY for the current process.
123 allow_ctty: bool = false,
124
125 pub fn isRead(self: OpenFlags) bool {
126 return self.mode != .write_only;
127 }
128
129 pub fn isWrite(self: OpenFlags) bool {
130 return self.mode != .read_only;
131 }
132};
133
134pub const CreateFlags = struct {
135 /// Whether the file will be created with read access.
136 read: bool = false,
137
138 /// If the file already exists, and is a regular file, and the access
139 /// mode allows writing, it will be truncated to length 0.
140 truncate: bool = true,
141
142 /// Ensures that this open call creates the file, otherwise causes
143 /// `error.PathAlreadyExists` to be returned.
144 exclusive: bool = false,
145
146 /// Open the file with an advisory lock to coordinate with other processes
147 /// accessing it at the same time. An exclusive lock will prevent other
148 /// processes from acquiring a lock. A shared lock will prevent other
149 /// processes from acquiring a exclusive lock, but does not prevent
150 /// other process from getting their own shared locks.
151 ///
152 /// The lock is advisory, except on Linux in very specific circumstances[1].
153 /// This means that a process that does not respect the locking API can still get access
154 /// to the file, despite the lock.
155 ///
156 /// On these operating systems, the lock is acquired atomically with
157 /// opening the file:
158 /// * Darwin
159 /// * DragonFlyBSD
160 /// * FreeBSD
161 /// * Haiku
162 /// * NetBSD
163 /// * OpenBSD
164 /// On these operating systems, the lock is acquired via a separate syscall
165 /// after opening the file:
166 /// * Linux
167 /// * Windows
168 ///
169 /// [1]: https://www.kernel.org/doc/Documentation/filesystems/mandatory-locking.txt
170 lock: Lock = .none,
171
172 /// Sets whether or not to wait until the file is locked to return. If set to true,
173 /// `error.WouldBlock` will be returned. Otherwise, the file will wait until the file
174 /// is available to proceed.
175 /// In async I/O mode, non-blocking at the OS level is
176 /// determined by `intended_io_mode`, and `true` means `error.WouldBlock` is returned,
177 /// and `false` means `error.WouldBlock` is handled by the event loop.
178 lock_nonblocking: bool = false,
179
180 /// For POSIX systems this is the file system mode the file will
181 /// be created with. On other systems this is always 0.
182 mode: Mode = default_mode,
183
184 /// Setting this to `.blocking` prevents `O.NONBLOCK` from being passed even
185 /// if `std.io.is_async`. It allows the use of `nosuspend` when calling functions
186 /// related to opening the file, reading, writing, and locking.
187 intended_io_mode: io.ModeOverride = io.default_mode,
188};
189
190/// Upon success, the stream is in an uninitialized state. To continue using it,
191/// you must use the open() function.
192pub fn close(self: File) void {
193 if (is_windows) {
194 windows.CloseHandle(self.handle);
195 } else if (self.capable_io_mode != self.intended_io_mode) {
196 std.event.Loop.instance.?.close(self.handle);
197 } else {
198 posix.close(self.handle);
199 }
200}
201
202pub const SyncError = posix.SyncError;
203
204/// Blocks until all pending file contents and metadata modifications
205/// for the file have been synchronized with the underlying filesystem.
206///
207/// Note that this does not ensure that metadata for the
208/// directory containing the file has also reached disk.
209pub fn sync(self: File) SyncError!void {
210 return posix.fsync(self.handle);
211}
212
213/// Test whether the file refers to a terminal.
214/// See also `supportsAnsiEscapeCodes`.
215pub fn isTty(self: File) bool {
216 return posix.isatty(self.handle);
217}
218
219/// Test whether ANSI escape codes will be treated as such.
220pub fn supportsAnsiEscapeCodes(self: File) bool {
221 if (builtin.os.tag == .windows) {
222 var console_mode: windows.DWORD = 0;
223 if (windows.kernel32.GetConsoleMode(self.handle, &console_mode) != 0) {
224 if (console_mode & windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING != 0) return true;
225 }
226
227 return posix.isCygwinPty(self.handle);
228 }
229 if (builtin.os.tag == .wasi) {
230 // WASI sanitizes stdout when fd is a tty so ANSI escape codes
231 // will not be interpreted as actual cursor commands, and
232 // stderr is always sanitized.
233 return false;
234 }
235 if (self.isTty()) {
236 if (self.handle == posix.STDOUT_FILENO or self.handle == posix.STDERR_FILENO) {
237 if (posix.getenvZ("TERM")) |term| {
238 if (std.mem.eql(u8, term, "dumb"))
239 return false;
240 }
241 }
242 return true;
243 }
244 return false;
245}
246
247pub const SetEndPosError = posix.TruncateError;
248
249/// Shrinks or expands the file.
250/// The file offset after this call is left unchanged.
251pub fn setEndPos(self: File, length: u64) SetEndPosError!void {
252 try posix.ftruncate(self.handle, length);
253}
254
255pub const SeekError = posix.SeekError;
256
257/// Repositions read/write file offset relative to the current offset.
258/// TODO: integrate with async I/O
259pub fn seekBy(self: File, offset: i64) SeekError!void {
260 return posix.lseek_CUR(self.handle, offset);
261}
262
263/// Repositions read/write file offset relative to the end.
264/// TODO: integrate with async I/O
265pub fn seekFromEnd(self: File, offset: i64) SeekError!void {
266 return posix.lseek_END(self.handle, offset);
267}
268
269/// Repositions read/write file offset relative to the beginning.
270/// TODO: integrate with async I/O
271pub fn seekTo(self: File, offset: u64) SeekError!void {
272 return posix.lseek_SET(self.handle, offset);
273}
274
275pub const GetSeekPosError = posix.SeekError || posix.FStatError;
276
277/// TODO: integrate with async I/O
278pub fn getPos(self: File) GetSeekPosError!u64 {
279 return posix.lseek_CUR_get(self.handle);
280}
281
282/// TODO: integrate with async I/O
283pub fn getEndPos(self: File) GetSeekPosError!u64 {
284 if (builtin.os.tag == .windows) {
285 return windows.GetFileSizeEx(self.handle);
286 }
287 return (try self.stat()).size;
288}
289
290pub const ModeError = posix.FStatError;
291
292/// TODO: integrate with async I/O
293pub fn mode(self: File) ModeError!Mode {
294 if (builtin.os.tag == .windows) {
295 return 0;
296 }
297 return (try self.stat()).mode;
298}
299
300pub const Stat = struct {
301 /// A number that the system uses to point to the file metadata. This
302 /// number is not guaranteed to be unique across time, as some file
303 /// systems may reuse an inode after its file has been deleted. Some
304 /// systems may change the inode of a file over time.
305 ///
306 /// On Linux, the inode is a structure that stores the metadata, and
307 /// the inode _number_ is what you see here: the index number of the
308 /// inode.
309 ///
310 /// The FileIndex on Windows is similar. It is a number for a file that
311 /// is unique to each filesystem.
312 inode: INode,
313 size: u64,
314 /// This is available on POSIX systems and is always 0 otherwise.
315 mode: Mode,
316 kind: Kind,
317
318 /// Access time in nanoseconds, relative to UTC 1970-01-01.
319 atime: i128,
320 /// Last modification time in nanoseconds, relative to UTC 1970-01-01.
321 mtime: i128,
322 /// Creation time in nanoseconds, relative to UTC 1970-01-01.
323 ctime: i128,
324
325 pub fn fromSystem(st: posix.system.Stat) Stat {
326 const atime = st.atime();
327 const mtime = st.mtime();
328 const ctime = st.ctime();
329 const kind: Kind = if (builtin.os.tag == .wasi and !builtin.link_libc) switch (st.filetype) {
330 .BLOCK_DEVICE => .block_device,
331 .CHARACTER_DEVICE => .character_device,
332 .DIRECTORY => .directory,
333 .SYMBOLIC_LINK => .sym_link,
334 .REGULAR_FILE => .file,
335 .SOCKET_STREAM, .SOCKET_DGRAM => .unix_domain_socket,
336 else => .unknown,
337 } else blk: {
338 const m = st.mode & posix.S.IFMT;
339 switch (m) {
340 posix.S.IFBLK => break :blk .block_device,
341 posix.S.IFCHR => break :blk .character_device,
342 posix.S.IFDIR => break :blk .directory,
343 posix.S.IFIFO => break :blk .named_pipe,
344 posix.S.IFLNK => break :blk .sym_link,
345 posix.S.IFREG => break :blk .file,
346 posix.S.IFSOCK => break :blk .unix_domain_socket,
347 else => {},
348 }
349 if (builtin.os.tag.isSolarish()) switch (m) {
350 posix.S.IFDOOR => break :blk .door,
351 posix.S.IFPORT => break :blk .event_port,
352 else => {},
353 };
354
355 break :blk .unknown;
356 };
357
358 return Stat{
359 .inode = st.ino,
360 .size = @as(u64, @bitCast(st.size)),
361 .mode = st.mode,
362 .kind = kind,
363 .atime = @as(i128, atime.tv_sec) * std.time.ns_per_s + atime.tv_nsec,
364 .mtime = @as(i128, mtime.tv_sec) * std.time.ns_per_s + mtime.tv_nsec,
365 .ctime = @as(i128, ctime.tv_sec) * std.time.ns_per_s + ctime.tv_nsec,
366 };
367 }
368};
369
370pub const StatError = posix.FStatError;
371
372/// TODO: integrate with async I/O
373pub fn stat(self: File) StatError!Stat {
374 if (builtin.os.tag == .windows) {
375 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
376 var info: windows.FILE_ALL_INFORMATION = undefined;
377 const rc = windows.ntdll.NtQueryInformationFile(self.handle, &io_status_block, &info, @sizeOf(windows.FILE_ALL_INFORMATION), .FileAllInformation);
378 switch (rc) {
379 .SUCCESS => {},
380 // Buffer overflow here indicates that there is more information available than was able to be stored in the buffer
381 // size provided. This is treated as success because the type of variable-length information that this would be relevant for
382 // (name, volume name, etc) we don't care about.
383 .BUFFER_OVERFLOW => {},
384 .INVALID_PARAMETER => unreachable,
385 .ACCESS_DENIED => return error.AccessDenied,
386 else => return windows.unexpectedStatus(rc),
387 }
388 return Stat{
389 .inode = info.InternalInformation.IndexNumber,
390 .size = @as(u64, @bitCast(info.StandardInformation.EndOfFile)),
391 .mode = 0,
392 .kind = if (info.StandardInformation.Directory == 0) .file else .directory,
393 .atime = windows.fromSysTime(info.BasicInformation.LastAccessTime),
394 .mtime = windows.fromSysTime(info.BasicInformation.LastWriteTime),
395 .ctime = windows.fromSysTime(info.BasicInformation.CreationTime),
396 };
397 }
398
399 const st = try posix.fstat(self.handle);
400 return Stat.fromSystem(st);
401}
402
403pub const ChmodError = posix.FChmodError;
404
405/// Changes the mode of the file.
406/// The process must have the correct privileges in order to do this
407/// successfully, or must have the effective user ID matching the owner
408/// of the file.
409pub fn chmod(self: File, new_mode: Mode) ChmodError!void {
410 try posix.fchmod(self.handle, new_mode);
411}
412
413pub const ChownError = posix.FChownError;
414
415/// Changes the owner and group of the file.
416/// The process must have the correct privileges in order to do this
417/// successfully. The group may be changed by the owner of the file to
418/// any group of which the owner is a member. If the owner or group is
419/// specified as `null`, the ID is not changed.
420pub fn chown(self: File, owner: ?Uid, group: ?Gid) ChownError!void {
421 try posix.fchown(self.handle, owner, group);
422}
423
424/// Cross-platform representation of permissions on a file.
425/// The `readonly` and `setReadonly` are the only methods available across all platforms.
426/// Platform-specific functionality is available through the `inner` field.
427pub const Permissions = struct {
428 /// You may use the `inner` field to use platform-specific functionality
429 inner: switch (builtin.os.tag) {
430 .windows => PermissionsWindows,
431 else => PermissionsUnix,
432 },
433
434 const Self = @This();
435
436 /// Returns `true` if permissions represent an unwritable file.
437 /// On Unix, `true` is returned only if no class has write permissions.
438 pub fn readOnly(self: Self) bool {
439 return self.inner.readOnly();
440 }
441
442 /// Sets whether write permissions are provided.
443 /// On Unix, this affects *all* classes. If this is undesired, use `unixSet`.
444 /// This method *DOES NOT* set permissions on the filesystem: use `File.setPermissions(permissions)`
445 pub fn setReadOnly(self: *Self, read_only: bool) void {
446 self.inner.setReadOnly(read_only);
447 }
448};
449
450pub const PermissionsWindows = struct {
451 attributes: windows.DWORD,
452
453 const Self = @This();
454
455 /// Returns `true` if permissions represent an unwritable file.
456 pub fn readOnly(self: Self) bool {
457 return self.attributes & windows.FILE_ATTRIBUTE_READONLY != 0;
458 }
459
460 /// Sets whether write permissions are provided.
461 /// This method *DOES NOT* set permissions on the filesystem: use `File.setPermissions(permissions)`
462 pub fn setReadOnly(self: *Self, read_only: bool) void {
463 if (read_only) {
464 self.attributes |= windows.FILE_ATTRIBUTE_READONLY;
465 } else {
466 self.attributes &= ~@as(windows.DWORD, windows.FILE_ATTRIBUTE_READONLY);
467 }
468 }
469};
470
471pub const PermissionsUnix = struct {
472 mode: Mode,
473
474 const Self = @This();
475
476 /// Returns `true` if permissions represent an unwritable file.
477 /// `true` is returned only if no class has write permissions.
478 pub fn readOnly(self: Self) bool {
479 return self.mode & 0o222 == 0;
480 }
481
482 /// Sets whether write permissions are provided.
483 /// This affects *all* classes. If this is undesired, use `unixSet`.
484 /// This method *DOES NOT* set permissions on the filesystem: use `File.setPermissions(permissions)`
485 pub fn setReadOnly(self: *Self, read_only: bool) void {
486 if (read_only) {
487 self.mode &= ~@as(Mode, 0o222);
488 } else {
489 self.mode |= @as(Mode, 0o222);
490 }
491 }
492
493 pub const Class = enum(u2) {
494 user = 2,
495 group = 1,
496 other = 0,
497 };
498
499 pub const Permission = enum(u3) {
500 read = 0o4,
501 write = 0o2,
502 execute = 0o1,
503 };
504
505 /// Returns `true` if the chosen class has the selected permission.
506 /// This method is only available on Unix platforms.
507 pub fn unixHas(self: Self, class: Class, permission: Permission) bool {
508 const mask = @as(Mode, @intFromEnum(permission)) << @as(u3, @intFromEnum(class)) * 3;
509 return self.mode & mask != 0;
510 }
511
512 /// Sets the permissions for the chosen class. Any permissions set to `null` are left unchanged.
513 /// This method *DOES NOT* set permissions on the filesystem: use `File.setPermissions(permissions)`
514 pub fn unixSet(self: *Self, class: Class, permissions: struct {
515 read: ?bool = null,
516 write: ?bool = null,
517 execute: ?bool = null,
518 }) void {
519 const shift = @as(u3, @intFromEnum(class)) * 3;
520 if (permissions.read) |r| {
521 if (r) {
522 self.mode |= @as(Mode, 0o4) << shift;
523 } else {
524 self.mode &= ~(@as(Mode, 0o4) << shift);
525 }
526 }
527 if (permissions.write) |w| {
528 if (w) {
529 self.mode |= @as(Mode, 0o2) << shift;
530 } else {
531 self.mode &= ~(@as(Mode, 0o2) << shift);
532 }
533 }
534 if (permissions.execute) |x| {
535 if (x) {
536 self.mode |= @as(Mode, 0o1) << shift;
537 } else {
538 self.mode &= ~(@as(Mode, 0o1) << shift);
539 }
540 }
541 }
542
543 /// Returns a `Permissions` struct representing the permissions from the passed mode.
544 pub fn unixNew(new_mode: Mode) Self {
545 return Self{
546 .mode = new_mode,
547 };
548 }
549};
550
551pub const SetPermissionsError = ChmodError;
552
553/// Sets permissions according to the provided `Permissions` struct.
554/// This method is *NOT* available on WASI
555pub fn setPermissions(self: File, permissions: Permissions) SetPermissionsError!void {
556 switch (builtin.os.tag) {
557 .windows => {
558 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
559 var info = windows.FILE_BASIC_INFORMATION{
560 .CreationTime = 0,
561 .LastAccessTime = 0,
562 .LastWriteTime = 0,
563 .ChangeTime = 0,
564 .FileAttributes = permissions.inner.attributes,
565 };
566 const rc = windows.ntdll.NtSetInformationFile(
567 self.handle,
568 &io_status_block,
569 &info,
570 @sizeOf(windows.FILE_BASIC_INFORMATION),
571 .FileBasicInformation,
572 );
573 switch (rc) {
574 .SUCCESS => return,
575 .INVALID_HANDLE => unreachable,
576 .ACCESS_DENIED => return error.AccessDenied,
577 else => return windows.unexpectedStatus(rc),
578 }
579 },
580 .wasi => @compileError("Unsupported OS"), // Wasi filesystem does not *yet* support chmod
581 else => {
582 try self.chmod(permissions.inner.mode);
583 },
584 }
585}
586
587/// Cross-platform representation of file metadata.
588/// Platform-specific functionality is available through the `inner` field.
589pub const Metadata = struct {
590 /// You may use the `inner` field to use platform-specific functionality
591 inner: switch (builtin.os.tag) {
592 .windows => MetadataWindows,
593 .linux => MetadataLinux,
594 else => MetadataUnix,
595 },
596
597 const Self = @This();
598
599 /// Returns the size of the file
600 pub fn size(self: Self) u64 {
601 return self.inner.size();
602 }
603
604 /// Returns a `Permissions` struct, representing the permissions on the file
605 pub fn permissions(self: Self) Permissions {
606 return self.inner.permissions();
607 }
608
609 /// Returns the `Kind` of file.
610 /// On Windows, can only return: `.file`, `.directory`, `.sym_link` or `.unknown`
611 pub fn kind(self: Self) Kind {
612 return self.inner.kind();
613 }
614
615 /// Returns the last time the file was accessed in nanoseconds since UTC 1970-01-01
616 pub fn accessed(self: Self) i128 {
617 return self.inner.accessed();
618 }
619
620 /// Returns the time the file was modified in nanoseconds since UTC 1970-01-01
621 pub fn modified(self: Self) i128 {
622 return self.inner.modified();
623 }
624
625 /// Returns the time the file was created in nanoseconds since UTC 1970-01-01
626 /// On Windows, this cannot return null
627 /// On Linux, this returns null if the filesystem does not support creation times, or if the kernel is older than 4.11
628 /// On Unices, this returns null if the filesystem or OS does not support creation times
629 /// On MacOS, this returns the ctime if the filesystem does not support creation times; this is insanity, and yet another reason to hate on Apple
630 pub fn created(self: Self) ?i128 {
631 return self.inner.created();
632 }
633};
634
635pub const MetadataUnix = struct {
636 stat: posix.Stat,
637
638 const Self = @This();
639
640 /// Returns the size of the file
641 pub fn size(self: Self) u64 {
642 return @as(u64, @intCast(self.stat.size));
643 }
644
645 /// Returns a `Permissions` struct, representing the permissions on the file
646 pub fn permissions(self: Self) Permissions {
647 return Permissions{ .inner = PermissionsUnix{ .mode = self.stat.mode } };
648 }
649
650 /// Returns the `Kind` of the file
651 pub fn kind(self: Self) Kind {
652 if (builtin.os.tag == .wasi and !builtin.link_libc) return switch (self.stat.filetype) {
653 .BLOCK_DEVICE => .block_device,
654 .CHARACTER_DEVICE => .character_device,
655 .DIRECTORY => .directory,
656 .SYMBOLIC_LINK => .sym_link,
657 .REGULAR_FILE => .file,
658 .SOCKET_STREAM, .SOCKET_DGRAM => .unix_domain_socket,
659 else => .unknown,
660 };
661
662 const m = self.stat.mode & posix.S.IFMT;
663
664 switch (m) {
665 posix.S.IFBLK => return .block_device,
666 posix.S.IFCHR => return .character_device,
667 posix.S.IFDIR => return .directory,
668 posix.S.IFIFO => return .named_pipe,
669 posix.S.IFLNK => return .sym_link,
670 posix.S.IFREG => return .file,
671 posix.S.IFSOCK => return .unix_domain_socket,
672 else => {},
673 }
674
675 if (builtin.os.tag.isSolarish()) switch (m) {
676 posix.S.IFDOOR => return .door,
677 posix.S.IFPORT => return .event_port,
678 else => {},
679 };
680
681 return .unknown;
682 }
683
684 /// Returns the last time the file was accessed in nanoseconds since UTC 1970-01-01
685 pub fn accessed(self: Self) i128 {
686 const atime = self.stat.atime();
687 return @as(i128, atime.tv_sec) * std.time.ns_per_s + atime.tv_nsec;
688 }
689
690 /// Returns the last time the file was modified in nanoseconds since UTC 1970-01-01
691 pub fn modified(self: Self) i128 {
692 const mtime = self.stat.mtime();
693 return @as(i128, mtime.tv_sec) * std.time.ns_per_s + mtime.tv_nsec;
694 }
695
696 /// Returns the time the file was created in nanoseconds since UTC 1970-01-01.
697 /// Returns null if this is not supported by the OS or filesystem
698 pub fn created(self: Self) ?i128 {
699 if (!@hasDecl(@TypeOf(self.stat), "birthtime")) return null;
700 const birthtime = self.stat.birthtime();
701
702 // If the filesystem doesn't support this the value *should* be:
703 // On FreeBSD: tv_nsec = 0, tv_sec = -1
704 // On NetBSD and OpenBSD: tv_nsec = 0, tv_sec = 0
705 // On MacOS, it is set to ctime -- we cannot detect this!!
706 switch (builtin.os.tag) {
707 .freebsd => if (birthtime.tv_sec == -1 and birthtime.tv_nsec == 0) return null,
708 .netbsd, .openbsd => if (birthtime.tv_sec == 0 and birthtime.tv_nsec == 0) return null,
709 .macos => {},
710 else => @compileError("Creation time detection not implemented for OS"),
711 }
712
713 return @as(i128, birthtime.tv_sec) * std.time.ns_per_s + birthtime.tv_nsec;
714 }
715};
716
717/// `MetadataUnix`, but using Linux's `statx` syscall.
718/// On Linux versions below 4.11, `statx` will be filled with data from stat.
719pub const MetadataLinux = struct {
720 statx: std.os.linux.Statx,
721
722 const Self = @This();
723
724 /// Returns the size of the file
725 pub fn size(self: Self) u64 {
726 return self.statx.size;
727 }
728
729 /// Returns a `Permissions` struct, representing the permissions on the file
730 pub fn permissions(self: Self) Permissions {
731 return Permissions{ .inner = PermissionsUnix{ .mode = self.statx.mode } };
732 }
733
734 /// Returns the `Kind` of the file
735 pub fn kind(self: Self) Kind {
736 const m = self.statx.mode & posix.S.IFMT;
737
738 switch (m) {
739 posix.S.IFBLK => return .block_device,
740 posix.S.IFCHR => return .character_device,
741 posix.S.IFDIR => return .directory,
742 posix.S.IFIFO => return .named_pipe,
743 posix.S.IFLNK => return .sym_link,
744 posix.S.IFREG => return .file,
745 posix.S.IFSOCK => return .unix_domain_socket,
746 else => {},
747 }
748
749 return .unknown;
750 }
751
752 /// Returns the last time the file was accessed in nanoseconds since UTC 1970-01-01
753 pub fn accessed(self: Self) i128 {
754 return @as(i128, self.statx.atime.tv_sec) * std.time.ns_per_s + self.statx.atime.tv_nsec;
755 }
756
757 /// Returns the last time the file was modified in nanoseconds since UTC 1970-01-01
758 pub fn modified(self: Self) i128 {
759 return @as(i128, self.statx.mtime.tv_sec) * std.time.ns_per_s + self.statx.mtime.tv_nsec;
760 }
761
762 /// Returns the time the file was created in nanoseconds since UTC 1970-01-01.
763 /// Returns null if this is not supported by the filesystem, or on kernels before than version 4.11
764 pub fn created(self: Self) ?i128 {
765 if (self.statx.mask & std.os.linux.STATX_BTIME == 0) return null;
766 return @as(i128, self.statx.btime.tv_sec) * std.time.ns_per_s + self.statx.btime.tv_nsec;
767 }
768};
769
770pub const MetadataWindows = struct {
771 attributes: windows.DWORD,
772 reparse_tag: windows.DWORD,
773 _size: u64,
774 access_time: i128,
775 modified_time: i128,
776 creation_time: i128,
777
778 const Self = @This();
779
780 /// Returns the size of the file
781 pub fn size(self: Self) u64 {
782 return self._size;
783 }
784
785 /// Returns a `Permissions` struct, representing the permissions on the file
786 pub fn permissions(self: Self) Permissions {
787 return Permissions{ .inner = PermissionsWindows{ .attributes = self.attributes } };
788 }
789
790 /// Returns the `Kind` of the file.
791 /// Can only return: `.file`, `.directory`, `.sym_link` or `.unknown`
792 pub fn kind(self: Self) Kind {
793 if (self.attributes & windows.FILE_ATTRIBUTE_REPARSE_POINT != 0) {
794 if (self.reparse_tag & 0x20000000 != 0) {
795 return .sym_link;
796 }
797 } else if (self.attributes & windows.FILE_ATTRIBUTE_DIRECTORY != 0) {
798 return .directory;
799 } else {
800 return .file;
801 }
802 return .unknown;
803 }
804
805 /// Returns the last time the file was accessed in nanoseconds since UTC 1970-01-01
806 pub fn accessed(self: Self) i128 {
807 return self.access_time;
808 }
809
810 /// Returns the time the file was modified in nanoseconds since UTC 1970-01-01
811 pub fn modified(self: Self) i128 {
812 return self.modified_time;
813 }
814
815 /// Returns the time the file was created in nanoseconds since UTC 1970-01-01.
816 /// This never returns null, only returning an optional for compatibility with other OSes
817 pub fn created(self: Self) ?i128 {
818 return self.creation_time;
819 }
820};
821
822pub const MetadataError = posix.FStatError;
823
824pub fn metadata(self: File) MetadataError!Metadata {
825 return Metadata{
826 .inner = switch (builtin.os.tag) {
827 .windows => blk: {
828 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
829 var info: windows.FILE_ALL_INFORMATION = undefined;
830
831 const rc = windows.ntdll.NtQueryInformationFile(self.handle, &io_status_block, &info, @sizeOf(windows.FILE_ALL_INFORMATION), .FileAllInformation);
832 switch (rc) {
833 .SUCCESS => {},
834 // Buffer overflow here indicates that there is more information available than was able to be stored in the buffer
835 // size provided. This is treated as success because the type of variable-length information that this would be relevant for
836 // (name, volume name, etc) we don't care about.
837 .BUFFER_OVERFLOW => {},
838 .INVALID_PARAMETER => unreachable,
839 .ACCESS_DENIED => return error.AccessDenied,
840 else => return windows.unexpectedStatus(rc),
841 }
842
843 const reparse_tag: windows.DWORD = reparse_blk: {
844 if (info.BasicInformation.FileAttributes & windows.FILE_ATTRIBUTE_REPARSE_POINT != 0) {
845 var reparse_buf: [windows.MAXIMUM_REPARSE_DATA_BUFFER_SIZE]u8 = undefined;
846 try windows.DeviceIoControl(self.handle, windows.FSCTL_GET_REPARSE_POINT, null, reparse_buf[0..]);
847 const reparse_struct: *const windows.REPARSE_DATA_BUFFER = @ptrCast(@alignCast(&reparse_buf[0]));
848 break :reparse_blk reparse_struct.ReparseTag;
849 }
850 break :reparse_blk 0;
851 };
852
853 break :blk MetadataWindows{
854 .attributes = info.BasicInformation.FileAttributes,
855 .reparse_tag = reparse_tag,
856 ._size = @as(u64, @bitCast(info.StandardInformation.EndOfFile)),
857 .access_time = windows.fromSysTime(info.BasicInformation.LastAccessTime),
858 .modified_time = windows.fromSysTime(info.BasicInformation.LastWriteTime),
859 .creation_time = windows.fromSysTime(info.BasicInformation.CreationTime),
860 };
861 },
862 .linux => blk: {
863 const l = std.os.linux;
864 var stx = std.mem.zeroes(l.Statx);
865 const rcx = l.statx(self.handle, "\x00", l.AT.EMPTY_PATH, l.STATX_TYPE |
866 l.STATX_MODE | l.STATX_ATIME | l.STATX_MTIME | l.STATX_BTIME, &stx);
867
868 switch (posix.errno(rcx)) {
869 .SUCCESS => {},
870 // NOSYS happens when `statx` is unsupported, which is the case on kernel versions before 4.11
871 // Here, we call `fstat` and fill `stx` with the data we need
872 .NOSYS => {
873 const st = try posix.fstat(self.handle);
874
875 stx.mode = @as(u16, @intCast(st.mode));
876
877 // Hacky conversion from timespec to statx_timestamp
878 stx.atime = std.mem.zeroes(l.statx_timestamp);
879 stx.atime.tv_sec = st.atim.tv_sec;
880 stx.atime.tv_nsec = @as(u32, @intCast(st.atim.tv_nsec)); // Guaranteed to succeed (tv_nsec is always below 10^9)
881
882 stx.mtime = std.mem.zeroes(l.statx_timestamp);
883 stx.mtime.tv_sec = st.mtim.tv_sec;
884 stx.mtime.tv_nsec = @as(u32, @intCast(st.mtim.tv_nsec));
885
886 stx.mask = l.STATX_BASIC_STATS | l.STATX_MTIME;
887 },
888 .BADF => unreachable,
889 .FAULT => unreachable,
890 .NOMEM => return error.SystemResources,
891 else => |err| return posix.unexpectedErrno(err),
892 }
893
894 break :blk MetadataLinux{
895 .statx = stx,
896 };
897 },
898 else => blk: {
899 const st = try posix.fstat(self.handle);
900 break :blk MetadataUnix{
901 .stat = st,
902 };
903 },
904 },
905 };
906}
907
908pub const UpdateTimesError = posix.FutimensError || windows.SetFileTimeError;
909
910/// The underlying file system may have a different granularity than nanoseconds,
911/// and therefore this function cannot guarantee any precision will be stored.
912/// Further, the maximum value is limited by the system ABI. When a value is provided
913/// that exceeds this range, the value is clamped to the maximum.
914/// TODO: integrate with async I/O
915pub fn updateTimes(
916 self: File,
917 /// access timestamp in nanoseconds
918 atime: i128,
919 /// last modification timestamp in nanoseconds
920 mtime: i128,
921) UpdateTimesError!void {
922 if (builtin.os.tag == .windows) {
923 const atime_ft = windows.nanoSecondsToFileTime(atime);
924 const mtime_ft = windows.nanoSecondsToFileTime(mtime);
925 return windows.SetFileTime(self.handle, null, &atime_ft, &mtime_ft);
926 }
927 const times = [2]posix.timespec{
928 posix.timespec{
929 .tv_sec = math.cast(isize, @divFloor(atime, std.time.ns_per_s)) orelse maxInt(isize),
930 .tv_nsec = math.cast(isize, @mod(atime, std.time.ns_per_s)) orelse maxInt(isize),
931 },
932 posix.timespec{
933 .tv_sec = math.cast(isize, @divFloor(mtime, std.time.ns_per_s)) orelse maxInt(isize),
934 .tv_nsec = math.cast(isize, @mod(mtime, std.time.ns_per_s)) orelse maxInt(isize),
935 },
936 };
937 try posix.futimens(self.handle, &times);
938}
939
940/// Reads all the bytes from the current position to the end of the file.
941/// On success, caller owns returned buffer.
942/// If the file is larger than `max_bytes`, returns `error.FileTooBig`.
943pub fn readToEndAlloc(self: File, allocator: Allocator, max_bytes: usize) ![]u8 {
944 return self.readToEndAllocOptions(allocator, max_bytes, null, @alignOf(u8), null);
945}
946
947/// Reads all the bytes from the current position to the end of the file.
948/// On success, caller owns returned buffer.
949/// If the file is larger than `max_bytes`, returns `error.FileTooBig`.
950/// If `size_hint` is specified the initial buffer size is calculated using
951/// that value, otherwise an arbitrary value is used instead.
952/// Allows specifying alignment and a sentinel value.
953pub fn readToEndAllocOptions(
954 self: File,
955 allocator: Allocator,
956 max_bytes: usize,
957 size_hint: ?usize,
958 comptime alignment: u29,
959 comptime optional_sentinel: ?u8,
960) !(if (optional_sentinel) |s| [:s]align(alignment) u8 else []align(alignment) u8) {
961 // If no size hint is provided fall back to the size=0 code path
962 const size = size_hint orelse 0;
963
964 // The file size returned by stat is used as hint to set the buffer
965 // size. If the reported size is zero, as it happens on Linux for files
966 // in /proc, a small buffer is allocated instead.
967 const initial_cap = (if (size > 0) size else 1024) + @intFromBool(optional_sentinel != null);
968 var array_list = try std.ArrayListAligned(u8, alignment).initCapacity(allocator, initial_cap);
969 defer array_list.deinit();
970
971 self.reader().readAllArrayListAligned(alignment, &array_list, max_bytes) catch |err| switch (err) {
972 error.StreamTooLong => return error.FileTooBig,
973 else => |e| return e,
974 };
975
976 if (optional_sentinel) |sentinel| {
977 return try array_list.toOwnedSliceSentinel(sentinel);
978 } else {
979 return try array_list.toOwnedSlice();
980 }
981}
982
983pub const ReadError = posix.ReadError;
984pub const PReadError = posix.PReadError;
985
986pub fn read(self: File, buffer: []u8) ReadError!usize {
987 if (is_windows) {
988 return windows.ReadFile(self.handle, buffer, null, self.intended_io_mode);
989 }
990
991 if (self.intended_io_mode == .blocking) {
992 return posix.read(self.handle, buffer);
993 } else {
994 return std.event.Loop.instance.?.read(self.handle, buffer, self.capable_io_mode != self.intended_io_mode);
995 }
996}
997
998/// Returns the number of bytes read. If the number read is smaller than `buffer.len`, it
999/// means the file reached the end. Reaching the end of a file is not an error condition.
1000pub fn readAll(self: File, buffer: []u8) ReadError!usize {
1001 var index: usize = 0;
1002 while (index != buffer.len) {
1003 const amt = try self.read(buffer[index..]);
1004 if (amt == 0) break;
1005 index += amt;
1006 }
1007 return index;
1008}
1009
1010/// On Windows, this function currently does alter the file pointer.
1011/// https://github.com/ziglang/zig/issues/12783
1012pub fn pread(self: File, buffer: []u8, offset: u64) PReadError!usize {
1013 if (is_windows) {
1014 return windows.ReadFile(self.handle, buffer, offset, self.intended_io_mode);
1015 }
1016
1017 if (self.intended_io_mode == .blocking) {
1018 return posix.pread(self.handle, buffer, offset);
1019 } else {
1020 return std.event.Loop.instance.?.pread(self.handle, buffer, offset, self.capable_io_mode != self.intended_io_mode);
1021 }
1022}
1023
1024/// Returns the number of bytes read. If the number read is smaller than `buffer.len`, it
1025/// means the file reached the end. Reaching the end of a file is not an error condition.
1026/// On Windows, this function currently does alter the file pointer.
1027/// https://github.com/ziglang/zig/issues/12783
1028pub fn preadAll(self: File, buffer: []u8, offset: u64) PReadError!usize {
1029 var index: usize = 0;
1030 while (index != buffer.len) {
1031 const amt = try self.pread(buffer[index..], offset + index);
1032 if (amt == 0) break;
1033 index += amt;
1034 }
1035 return index;
1036}
1037
1038/// See https://github.com/ziglang/zig/issues/7699
1039pub fn readv(self: File, iovecs: []const posix.iovec) ReadError!usize {
1040 if (is_windows) {
1041 // TODO improve this to use ReadFileScatter
1042 if (iovecs.len == 0) return @as(usize, 0);
1043 const first = iovecs[0];
1044 return windows.ReadFile(self.handle, first.iov_base[0..first.iov_len], null, self.intended_io_mode);
1045 }
1046
1047 if (self.intended_io_mode == .blocking) {
1048 return posix.readv(self.handle, iovecs);
1049 } else {
1050 return std.event.Loop.instance.?.readv(self.handle, iovecs, self.capable_io_mode != self.intended_io_mode);
1051 }
1052}
1053
1054/// Returns the number of bytes read. If the number read is smaller than the total bytes
1055/// from all the buffers, it means the file reached the end. Reaching the end of a file
1056/// is not an error condition.
1057///
1058/// The `iovecs` parameter is mutable because:
1059/// * This function needs to mutate the fields in order to handle partial
1060/// reads from the underlying OS layer.
1061/// * The OS layer expects pointer addresses to be inside the application's address space
1062/// even if the length is zero. Meanwhile, in Zig, slices may have undefined pointer
1063/// addresses when the length is zero. So this function modifies the iov_base fields
1064/// when the length is zero.
1065///
1066/// Related open issue: https://github.com/ziglang/zig/issues/7699
1067pub fn readvAll(self: File, iovecs: []posix.iovec) ReadError!usize {
1068 if (iovecs.len == 0) return 0;
1069
1070 // We use the address of this local variable for all zero-length
1071 // vectors so that the OS does not complain that we are giving it
1072 // addresses outside the application's address space.
1073 var garbage: [1]u8 = undefined;
1074 for (iovecs) |*v| {
1075 if (v.iov_len == 0) v.iov_base = &garbage;
1076 }
1077
1078 var i: usize = 0;
1079 var off: usize = 0;
1080 while (true) {
1081 var amt = try self.readv(iovecs[i..]);
1082 var eof = amt == 0;
1083 off += amt;
1084 while (amt >= iovecs[i].iov_len) {
1085 amt -= iovecs[i].iov_len;
1086 i += 1;
1087 if (i >= iovecs.len) return off;
1088 eof = false;
1089 }
1090 if (eof) return off;
1091 iovecs[i].iov_base += amt;
1092 iovecs[i].iov_len -= amt;
1093 }
1094}
1095
1096/// See https://github.com/ziglang/zig/issues/7699
1097/// On Windows, this function currently does alter the file pointer.
1098/// https://github.com/ziglang/zig/issues/12783
1099pub fn preadv(self: File, iovecs: []const posix.iovec, offset: u64) PReadError!usize {
1100 if (is_windows) {
1101 // TODO improve this to use ReadFileScatter
1102 if (iovecs.len == 0) return @as(usize, 0);
1103 const first = iovecs[0];
1104 return windows.ReadFile(self.handle, first.iov_base[0..first.iov_len], offset, self.intended_io_mode);
1105 }
1106
1107 if (self.intended_io_mode == .blocking) {
1108 return posix.preadv(self.handle, iovecs, offset);
1109 } else {
1110 return std.event.Loop.instance.?.preadv(self.handle, iovecs, offset, self.capable_io_mode != self.intended_io_mode);
1111 }
1112}
1113
1114/// Returns the number of bytes read. If the number read is smaller than the total bytes
1115/// from all the buffers, it means the file reached the end. Reaching the end of a file
1116/// is not an error condition.
1117/// The `iovecs` parameter is mutable because this function needs to mutate the fields in
1118/// order to handle partial reads from the underlying OS layer.
1119/// See https://github.com/ziglang/zig/issues/7699
1120/// On Windows, this function currently does alter the file pointer.
1121/// https://github.com/ziglang/zig/issues/12783
1122pub fn preadvAll(self: File, iovecs: []posix.iovec, offset: u64) PReadError!usize {
1123 if (iovecs.len == 0) return 0;
1124
1125 var i: usize = 0;
1126 var off: usize = 0;
1127 while (true) {
1128 var amt = try self.preadv(iovecs[i..], offset + off);
1129 var eof = amt == 0;
1130 off += amt;
1131 while (amt >= iovecs[i].iov_len) {
1132 amt -= iovecs[i].iov_len;
1133 i += 1;
1134 if (i >= iovecs.len) return off;
1135 eof = false;
1136 }
1137 if (eof) return off;
1138 iovecs[i].iov_base += amt;
1139 iovecs[i].iov_len -= amt;
1140 }
1141}
1142
1143pub const WriteError = posix.WriteError;
1144pub const PWriteError = posix.PWriteError;
1145
1146pub fn write(self: File, bytes: []const u8) WriteError!usize {
1147 if (is_windows) {
1148 return windows.WriteFile(self.handle, bytes, null, self.intended_io_mode);
1149 }
1150
1151 if (self.intended_io_mode == .blocking) {
1152 return posix.write(self.handle, bytes);
1153 } else {
1154 return std.event.Loop.instance.?.write(self.handle, bytes, self.capable_io_mode != self.intended_io_mode);
1155 }
1156}
1157
1158pub fn writeAll(self: File, bytes: []const u8) WriteError!void {
1159 var index: usize = 0;
1160 while (index < bytes.len) {
1161 index += try self.write(bytes[index..]);
1162 }
1163}
1164
1165/// On Windows, this function currently does alter the file pointer.
1166/// https://github.com/ziglang/zig/issues/12783
1167pub fn pwrite(self: File, bytes: []const u8, offset: u64) PWriteError!usize {
1168 if (is_windows) {
1169 return windows.WriteFile(self.handle, bytes, offset, self.intended_io_mode);
1170 }
1171
1172 if (self.intended_io_mode == .blocking) {
1173 return posix.pwrite(self.handle, bytes, offset);
1174 } else {
1175 return std.event.Loop.instance.?.pwrite(self.handle, bytes, offset, self.capable_io_mode != self.intended_io_mode);
1176 }
1177}
1178
1179/// On Windows, this function currently does alter the file pointer.
1180/// https://github.com/ziglang/zig/issues/12783
1181pub fn pwriteAll(self: File, bytes: []const u8, offset: u64) PWriteError!void {
1182 var index: usize = 0;
1183 while (index < bytes.len) {
1184 index += try self.pwrite(bytes[index..], offset + index);
1185 }
1186}
1187
1188/// See https://github.com/ziglang/zig/issues/7699
1189/// See equivalent function: `std.net.Stream.writev`.
1190pub fn writev(self: File, iovecs: []const posix.iovec_const) WriteError!usize {
1191 if (is_windows) {
1192 // TODO improve this to use WriteFileScatter
1193 if (iovecs.len == 0) return @as(usize, 0);
1194 const first = iovecs[0];
1195 return windows.WriteFile(self.handle, first.iov_base[0..first.iov_len], null, self.intended_io_mode);
1196 }
1197
1198 if (self.intended_io_mode == .blocking) {
1199 return posix.writev(self.handle, iovecs);
1200 } else {
1201 return std.event.Loop.instance.?.writev(self.handle, iovecs, self.capable_io_mode != self.intended_io_mode);
1202 }
1203}
1204
1205/// The `iovecs` parameter is mutable because:
1206/// * This function needs to mutate the fields in order to handle partial
1207/// writes from the underlying OS layer.
1208/// * The OS layer expects pointer addresses to be inside the application's address space
1209/// even if the length is zero. Meanwhile, in Zig, slices may have undefined pointer
1210/// addresses when the length is zero. So this function modifies the iov_base fields
1211/// when the length is zero.
1212/// See https://github.com/ziglang/zig/issues/7699
1213/// See equivalent function: `std.net.Stream.writevAll`.
1214pub fn writevAll(self: File, iovecs: []posix.iovec_const) WriteError!void {
1215 if (iovecs.len == 0) return;
1216
1217 // We use the address of this local variable for all zero-length
1218 // vectors so that the OS does not complain that we are giving it
1219 // addresses outside the application's address space.
1220 var garbage: [1]u8 = undefined;
1221 for (iovecs) |*v| {
1222 if (v.iov_len == 0) v.iov_base = &garbage;
1223 }
1224
1225 var i: usize = 0;
1226 while (true) {
1227 var amt = try self.writev(iovecs[i..]);
1228 while (amt >= iovecs[i].iov_len) {
1229 amt -= iovecs[i].iov_len;
1230 i += 1;
1231 if (i >= iovecs.len) return;
1232 }
1233 iovecs[i].iov_base += amt;
1234 iovecs[i].iov_len -= amt;
1235 }
1236}
1237
1238/// See https://github.com/ziglang/zig/issues/7699
1239/// On Windows, this function currently does alter the file pointer.
1240/// https://github.com/ziglang/zig/issues/12783
1241pub fn pwritev(self: File, iovecs: []posix.iovec_const, offset: u64) PWriteError!usize {
1242 if (is_windows) {
1243 // TODO improve this to use WriteFileScatter
1244 if (iovecs.len == 0) return @as(usize, 0);
1245 const first = iovecs[0];
1246 return windows.WriteFile(self.handle, first.iov_base[0..first.iov_len], offset, self.intended_io_mode);
1247 }
1248
1249 if (self.intended_io_mode == .blocking) {
1250 return posix.pwritev(self.handle, iovecs, offset);
1251 } else {
1252 return std.event.Loop.instance.?.pwritev(self.handle, iovecs, offset, self.capable_io_mode != self.intended_io_mode);
1253 }
1254}
1255
1256/// The `iovecs` parameter is mutable because this function needs to mutate the fields in
1257/// order to handle partial writes from the underlying OS layer.
1258/// See https://github.com/ziglang/zig/issues/7699
1259/// On Windows, this function currently does alter the file pointer.
1260/// https://github.com/ziglang/zig/issues/12783
1261pub fn pwritevAll(self: File, iovecs: []posix.iovec_const, offset: u64) PWriteError!void {
1262 if (iovecs.len == 0) return;
1263
1264 var i: usize = 0;
1265 var off: u64 = 0;
1266 while (true) {
1267 var amt = try self.pwritev(iovecs[i..], offset + off);
1268 off += amt;
1269 while (amt >= iovecs[i].iov_len) {
1270 amt -= iovecs[i].iov_len;
1271 i += 1;
1272 if (i >= iovecs.len) return;
1273 }
1274 iovecs[i].iov_base += amt;
1275 iovecs[i].iov_len -= amt;
1276 }
1277}
1278
1279pub const CopyRangeError = posix.CopyFileRangeError;
1280
1281pub fn copyRange(in: File, in_offset: u64, out: File, out_offset: u64, len: u64) CopyRangeError!u64 {
1282 const adjusted_len = math.cast(usize, len) orelse maxInt(usize);
1283 const result = try posix.copy_file_range(in.handle, in_offset, out.handle, out_offset, adjusted_len, 0);
1284 return result;
1285}
1286
1287/// Returns the number of bytes copied. If the number read is smaller than `buffer.len`, it
1288/// means the in file reached the end. Reaching the end of a file is not an error condition.
1289pub fn copyRangeAll(in: File, in_offset: u64, out: File, out_offset: u64, len: u64) CopyRangeError!u64 {
1290 var total_bytes_copied: u64 = 0;
1291 var in_off = in_offset;
1292 var out_off = out_offset;
1293 while (total_bytes_copied < len) {
1294 const amt_copied = try copyRange(in, in_off, out, out_off, len - total_bytes_copied);
1295 if (amt_copied == 0) return total_bytes_copied;
1296 total_bytes_copied += amt_copied;
1297 in_off += amt_copied;
1298 out_off += amt_copied;
1299 }
1300 return total_bytes_copied;
1301}
1302
1303pub const WriteFileOptions = struct {
1304 in_offset: u64 = 0,
1305
1306 /// `null` means the entire file. `0` means no bytes from the file.
1307 /// When this is `null`, trailers must be sent in a separate writev() call
1308 /// due to a flaw in the BSD sendfile API. Other operating systems, such as
1309 /// Linux, already do this anyway due to API limitations.
1310 /// If the size of the source file is known, passing the size here will save one syscall.
1311 in_len: ?u64 = null,
1312
1313 headers_and_trailers: []posix.iovec_const = &[0]posix.iovec_const{},
1314
1315 /// The trailer count is inferred from `headers_and_trailers.len - header_count`
1316 header_count: usize = 0,
1317};
1318
1319pub const WriteFileError = ReadError || error{EndOfStream} || WriteError;
1320
1321pub fn writeFileAll(self: File, in_file: File, args: WriteFileOptions) WriteFileError!void {
1322 return self.writeFileAllSendfile(in_file, args) catch |err| switch (err) {
1323 error.Unseekable,
1324 error.FastOpenAlreadyInProgress,
1325 error.MessageTooBig,
1326 error.FileDescriptorNotASocket,
1327 error.NetworkUnreachable,
1328 error.NetworkSubsystemFailed,
1329 => return self.writeFileAllUnseekable(in_file, args),
1330
1331 else => |e| return e,
1332 };
1333}
1334
1335/// Does not try seeking in either of the File parameters.
1336/// See `writeFileAll` as an alternative to calling this.
1337pub fn writeFileAllUnseekable(self: File, in_file: File, args: WriteFileOptions) WriteFileError!void {
1338 const headers = args.headers_and_trailers[0..args.header_count];
1339 const trailers = args.headers_and_trailers[args.header_count..];
1340
1341 try self.writevAll(headers);
1342
1343 try in_file.reader().skipBytes(args.in_offset, .{ .buf_size = 4096 });
1344
1345 var fifo = std.fifo.LinearFifo(u8, .{ .Static = 4096 }).init();
1346 if (args.in_len) |len| {
1347 var stream = std.io.limitedReader(in_file.reader(), len);
1348 try fifo.pump(stream.reader(), self.writer());
1349 } else {
1350 try fifo.pump(in_file.reader(), self.writer());
1351 }
1352
1353 try self.writevAll(trailers);
1354}
1355
1356/// Low level function which can fail for OS-specific reasons.
1357/// See `writeFileAll` as an alternative to calling this.
1358/// TODO integrate with async I/O
1359fn writeFileAllSendfile(self: File, in_file: File, args: WriteFileOptions) posix.SendFileError!void {
1360 const count = blk: {
1361 if (args.in_len) |l| {
1362 if (l == 0) {
1363 return self.writevAll(args.headers_and_trailers);
1364 } else {
1365 break :blk l;
1366 }
1367 } else {
1368 break :blk 0;
1369 }
1370 };
1371 const headers = args.headers_and_trailers[0..args.header_count];
1372 const trailers = args.headers_and_trailers[args.header_count..];
1373 const zero_iovec = &[0]posix.iovec_const{};
1374 // When reading the whole file, we cannot put the trailers in the sendfile() syscall,
1375 // because we have no way to determine whether a partial write is past the end of the file or not.
1376 const trls = if (count == 0) zero_iovec else trailers;
1377 const offset = args.in_offset;
1378 const out_fd = self.handle;
1379 const in_fd = in_file.handle;
1380 const flags = 0;
1381 var amt: usize = 0;
1382 hdrs: {
1383 var i: usize = 0;
1384 while (i < headers.len) {
1385 amt = try posix.sendfile(out_fd, in_fd, offset, count, headers[i..], trls, flags);
1386 while (amt >= headers[i].iov_len) {
1387 amt -= headers[i].iov_len;
1388 i += 1;
1389 if (i >= headers.len) break :hdrs;
1390 }
1391 headers[i].iov_base += amt;
1392 headers[i].iov_len -= amt;
1393 }
1394 }
1395 if (count == 0) {
1396 var off: u64 = amt;
1397 while (true) {
1398 amt = try posix.sendfile(out_fd, in_fd, offset + off, 0, zero_iovec, zero_iovec, flags);
1399 if (amt == 0) break;
1400 off += amt;
1401 }
1402 } else {
1403 var off: u64 = amt;
1404 while (off < count) {
1405 amt = try posix.sendfile(out_fd, in_fd, offset + off, count - off, zero_iovec, trailers, flags);
1406 off += amt;
1407 }
1408 amt = @as(usize, @intCast(off - count));
1409 }
1410 var i: usize = 0;
1411 while (i < trailers.len) {
1412 while (amt >= trailers[i].iov_len) {
1413 amt -= trailers[i].iov_len;
1414 i += 1;
1415 if (i >= trailers.len) return;
1416 }
1417 trailers[i].iov_base += amt;
1418 trailers[i].iov_len -= amt;
1419 amt = try posix.writev(self.handle, trailers[i..]);
1420 }
1421}
1422
1423pub const Reader = io.Reader(File, ReadError, read);
1424
1425pub fn reader(file: File) Reader {
1426 return .{ .context = file };
1427}
1428
1429pub const Writer = io.Writer(File, WriteError, write);
1430
1431pub fn writer(file: File) Writer {
1432 return .{ .context = file };
1433}
1434
1435pub const SeekableStream = io.SeekableStream(
1436 File,
1437 SeekError,
1438 GetSeekPosError,
1439 seekTo,
1440 seekBy,
1441 getPos,
1442 getEndPos,
1443);
1444
1445pub fn seekableStream(file: File) SeekableStream {
1446 return .{ .context = file };
1447}
1448
1449const range_off: windows.LARGE_INTEGER = 0;
1450const range_len: windows.LARGE_INTEGER = 1;
1451
1452pub const LockError = error{
1453 SystemResources,
1454 FileLocksNotSupported,
1455} || posix.UnexpectedError;
1456
1457/// Blocks when an incompatible lock is held by another process.
1458/// A process may hold only one type of lock (shared or exclusive) on
1459/// a file. When a process terminates in any way, the lock is released.
1460///
1461/// Assumes the file is unlocked.
1462///
1463/// TODO: integrate with async I/O
1464pub fn lock(file: File, l: Lock) LockError!void {
1465 if (is_windows) {
1466 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
1467 const exclusive = switch (l) {
1468 .none => return,
1469 .shared => false,
1470 .exclusive => true,
1471 };
1472 return windows.LockFile(
1473 file.handle,
1474 null,
1475 null,
1476 null,
1477 &io_status_block,
1478 &range_off,
1479 &range_len,
1480 null,
1481 windows.FALSE, // non-blocking=false
1482 @intFromBool(exclusive),
1483 ) catch |err| switch (err) {
1484 error.WouldBlock => unreachable, // non-blocking=false
1485 else => |e| return e,
1486 };
1487 } else {
1488 return posix.flock(file.handle, switch (l) {
1489 .none => posix.LOCK.UN,
1490 .shared => posix.LOCK.SH,
1491 .exclusive => posix.LOCK.EX,
1492 }) catch |err| switch (err) {
1493 error.WouldBlock => unreachable, // non-blocking=false
1494 else => |e| return e,
1495 };
1496 }
1497}
1498
1499/// Assumes the file is locked.
1500pub fn unlock(file: File) void {
1501 if (is_windows) {
1502 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
1503 return windows.UnlockFile(
1504 file.handle,
1505 &io_status_block,
1506 &range_off,
1507 &range_len,
1508 null,
1509 ) catch |err| switch (err) {
1510 error.RangeNotLocked => unreachable, // Function assumes unlocked.
1511 error.Unexpected => unreachable, // Resource deallocation must succeed.
1512 };
1513 } else {
1514 return posix.flock(file.handle, posix.LOCK.UN) catch |err| switch (err) {
1515 error.WouldBlock => unreachable, // unlocking can't block
1516 error.SystemResources => unreachable, // We are deallocating resources.
1517 error.FileLocksNotSupported => unreachable, // We already got the lock.
1518 error.Unexpected => unreachable, // Resource deallocation must succeed.
1519 };
1520 }
1521}
1522
1523/// Attempts to obtain a lock, returning `true` if the lock is
1524/// obtained, and `false` if there was an existing incompatible lock held.
1525/// A process may hold only one type of lock (shared or exclusive) on
1526/// a file. When a process terminates in any way, the lock is released.
1527///
1528/// Assumes the file is unlocked.
1529///
1530/// TODO: integrate with async I/O
1531pub fn tryLock(file: File, l: Lock) LockError!bool {
1532 if (is_windows) {
1533 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
1534 const exclusive = switch (l) {
1535 .none => return,
1536 .shared => false,
1537 .exclusive => true,
1538 };
1539 windows.LockFile(
1540 file.handle,
1541 null,
1542 null,
1543 null,
1544 &io_status_block,
1545 &range_off,
1546 &range_len,
1547 null,
1548 windows.TRUE, // non-blocking=true
1549 @intFromBool(exclusive),
1550 ) catch |err| switch (err) {
1551 error.WouldBlock => return false,
1552 else => |e| return e,
1553 };
1554 } else {
1555 posix.flock(file.handle, switch (l) {
1556 .none => posix.LOCK.UN,
1557 .shared => posix.LOCK.SH | posix.LOCK.NB,
1558 .exclusive => posix.LOCK.EX | posix.LOCK.NB,
1559 }) catch |err| switch (err) {
1560 error.WouldBlock => return false,
1561 else => |e| return e,
1562 };
1563 }
1564 return true;
1565}
1566
1567/// Assumes the file is already locked in exclusive mode.
1568/// Atomically modifies the lock to be in shared mode, without releasing it.
1569///
1570/// TODO: integrate with async I/O
1571pub fn downgradeLock(file: File) LockError!void {
1572 if (is_windows) {
1573 // On Windows it works like a semaphore + exclusivity flag. To implement this
1574 // function, we first obtain another lock in shared mode. This changes the
1575 // exclusivity flag, but increments the semaphore to 2. So we follow up with
1576 // an NtUnlockFile which decrements the semaphore but does not modify the
1577 // exclusivity flag.
1578 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
1579 windows.LockFile(
1580 file.handle,
1581 null,
1582 null,
1583 null,
1584 &io_status_block,
1585 &range_off,
1586 &range_len,
1587 null,
1588 windows.TRUE, // non-blocking=true
1589 windows.FALSE, // exclusive=false
1590 ) catch |err| switch (err) {
1591 error.WouldBlock => unreachable, // File was not locked in exclusive mode.
1592 else => |e| return e,
1593 };
1594 return windows.UnlockFile(
1595 file.handle,
1596 &io_status_block,
1597 &range_off,
1598 &range_len,
1599 null,
1600 ) catch |err| switch (err) {
1601 error.RangeNotLocked => unreachable, // File was not locked.
1602 error.Unexpected => unreachable, // Resource deallocation must succeed.
1603 };
1604 } else {
1605 return posix.flock(file.handle, posix.LOCK.SH | posix.LOCK.NB) catch |err| switch (err) {
1606 error.WouldBlock => unreachable, // File was not locked in exclusive mode.
1607 else => |e| return e,
1608 };
1609 }
1610}
1611
1612const File = @This();
1613const std = @import("../std.zig");
1614const builtin = @import("builtin");
1615const Allocator = std.mem.Allocator;
1616// https://github.com/ziglang/zig/issues/5019
1617const posix = std.os;
1618const io = std.io;
1619const math = std.math;
1620const assert = std.debug.assert;
1621const windows = std.os.windows;
1622const Os = std.builtin.Os;
1623const maxInt = std.math.maxInt;
1624const is_windows = builtin.os.tag == .windows;
lib/std/fs/file.zig deleted-1622
......@@ -1,1622 +0,0 @@
1const std = @import("../std.zig");
2const builtin = @import("builtin");
3const os = std.os;
4const io = std.io;
5const mem = std.mem;
6const math = std.math;
7const assert = std.debug.assert;
8const windows = os.windows;
9const Os = std.builtin.Os;
10const maxInt = std.math.maxInt;
11const is_windows = builtin.os.tag == .windows;
12
13pub const File = struct {
14 /// The OS-specific file descriptor or file handle.
15 handle: Handle,
16
17 /// On some systems, such as Linux, file system file descriptors are incapable
18 /// of non-blocking I/O. This forces us to perform asynchronous I/O on a dedicated thread,
19 /// to achieve non-blocking file-system I/O. To do this, `File` must be aware of whether
20 /// it is a file system file descriptor, or, more specifically, whether the I/O is always
21 /// blocking.
22 capable_io_mode: io.ModeOverride = io.default_mode,
23
24 /// Furthermore, even when `std.options.io_mode` is async, it is still sometimes desirable
25 /// to perform blocking I/O, although not by default. For example, when printing a
26 /// stack trace to stderr. This field tracks both by acting as an overriding I/O mode.
27 /// When not building in async I/O mode, the type only has the `.blocking` tag, making
28 /// it a zero-bit type.
29 intended_io_mode: io.ModeOverride = io.default_mode,
30
31 pub const Handle = os.fd_t;
32 pub const Mode = os.mode_t;
33 pub const INode = os.ino_t;
34 pub const Uid = os.uid_t;
35 pub const Gid = os.gid_t;
36
37 pub const Kind = enum {
38 block_device,
39 character_device,
40 directory,
41 named_pipe,
42 sym_link,
43 file,
44 unix_domain_socket,
45 whiteout,
46 door,
47 event_port,
48 unknown,
49 };
50
51 /// This is the default mode given to POSIX operating systems for creating
52 /// files. `0o666` is "-rw-rw-rw-" which is counter-intuitive at first,
53 /// since most people would expect "-rw-r--r--", for example, when using
54 /// the `touch` command, which would correspond to `0o644`. However, POSIX
55 /// libc implementations use `0o666` inside `fopen` and then rely on the
56 /// process-scoped "umask" setting to adjust this number for file creation.
57 pub const default_mode = switch (builtin.os.tag) {
58 .windows => 0,
59 .wasi => 0,
60 else => 0o666,
61 };
62
63 pub const OpenError = error{
64 SharingViolation,
65 PathAlreadyExists,
66 FileNotFound,
67 AccessDenied,
68 PipeBusy,
69 NameTooLong,
70 /// On Windows, file paths must be valid Unicode.
71 InvalidUtf8,
72 /// On Windows, file paths cannot contain these characters:
73 /// '/', '*', '?', '"', '<', '>', '|'
74 BadPathName,
75 Unexpected,
76 /// On Windows, `\\server` or `\\server\share` was not found.
77 NetworkNotFound,
78 } || os.OpenError || os.FlockError;
79
80 pub const OpenMode = enum {
81 read_only,
82 write_only,
83 read_write,
84 };
85
86 pub const Lock = enum {
87 none,
88 shared,
89 exclusive,
90 };
91
92 pub const OpenFlags = struct {
93 mode: OpenMode = .read_only,
94
95 /// Open the file with an advisory lock to coordinate with other processes
96 /// accessing it at the same time. An exclusive lock will prevent other
97 /// processes from acquiring a lock. A shared lock will prevent other
98 /// processes from acquiring a exclusive lock, but does not prevent
99 /// other process from getting their own shared locks.
100 ///
101 /// The lock is advisory, except on Linux in very specific circumstances[1].
102 /// This means that a process that does not respect the locking API can still get access
103 /// to the file, despite the lock.
104 ///
105 /// On these operating systems, the lock is acquired atomically with
106 /// opening the file:
107 /// * Darwin
108 /// * DragonFlyBSD
109 /// * FreeBSD
110 /// * Haiku
111 /// * NetBSD
112 /// * OpenBSD
113 /// On these operating systems, the lock is acquired via a separate syscall
114 /// after opening the file:
115 /// * Linux
116 /// * Windows
117 ///
118 /// [1]: https://www.kernel.org/doc/Documentation/filesystems/mandatory-locking.txt
119 lock: Lock = .none,
120
121 /// Sets whether or not to wait until the file is locked to return. If set to true,
122 /// `error.WouldBlock` will be returned. Otherwise, the file will wait until the file
123 /// is available to proceed.
124 /// In async I/O mode, non-blocking at the OS level is
125 /// determined by `intended_io_mode`, and `true` means `error.WouldBlock` is returned,
126 /// and `false` means `error.WouldBlock` is handled by the event loop.
127 lock_nonblocking: bool = false,
128
129 /// Setting this to `.blocking` prevents `O.NONBLOCK` from being passed even
130 /// if `std.io.is_async`. It allows the use of `nosuspend` when calling functions
131 /// related to opening the file, reading, writing, and locking.
132 intended_io_mode: io.ModeOverride = io.default_mode,
133
134 /// Set this to allow the opened file to automatically become the
135 /// controlling TTY for the current process.
136 allow_ctty: bool = false,
137
138 pub fn isRead(self: OpenFlags) bool {
139 return self.mode != .write_only;
140 }
141
142 pub fn isWrite(self: OpenFlags) bool {
143 return self.mode != .read_only;
144 }
145 };
146
147 pub const CreateFlags = struct {
148 /// Whether the file will be created with read access.
149 read: bool = false,
150
151 /// If the file already exists, and is a regular file, and the access
152 /// mode allows writing, it will be truncated to length 0.
153 truncate: bool = true,
154
155 /// Ensures that this open call creates the file, otherwise causes
156 /// `error.PathAlreadyExists` to be returned.
157 exclusive: bool = false,
158
159 /// Open the file with an advisory lock to coordinate with other processes
160 /// accessing it at the same time. An exclusive lock will prevent other
161 /// processes from acquiring a lock. A shared lock will prevent other
162 /// processes from acquiring a exclusive lock, but does not prevent
163 /// other process from getting their own shared locks.
164 ///
165 /// The lock is advisory, except on Linux in very specific circumstances[1].
166 /// This means that a process that does not respect the locking API can still get access
167 /// to the file, despite the lock.
168 ///
169 /// On these operating systems, the lock is acquired atomically with
170 /// opening the file:
171 /// * Darwin
172 /// * DragonFlyBSD
173 /// * FreeBSD
174 /// * Haiku
175 /// * NetBSD
176 /// * OpenBSD
177 /// On these operating systems, the lock is acquired via a separate syscall
178 /// after opening the file:
179 /// * Linux
180 /// * Windows
181 ///
182 /// [1]: https://www.kernel.org/doc/Documentation/filesystems/mandatory-locking.txt
183 lock: Lock = .none,
184
185 /// Sets whether or not to wait until the file is locked to return. If set to true,
186 /// `error.WouldBlock` will be returned. Otherwise, the file will wait until the file
187 /// is available to proceed.
188 /// In async I/O mode, non-blocking at the OS level is
189 /// determined by `intended_io_mode`, and `true` means `error.WouldBlock` is returned,
190 /// and `false` means `error.WouldBlock` is handled by the event loop.
191 lock_nonblocking: bool = false,
192
193 /// For POSIX systems this is the file system mode the file will
194 /// be created with. On other systems this is always 0.
195 mode: Mode = default_mode,
196
197 /// Setting this to `.blocking` prevents `O.NONBLOCK` from being passed even
198 /// if `std.io.is_async`. It allows the use of `nosuspend` when calling functions
199 /// related to opening the file, reading, writing, and locking.
200 intended_io_mode: io.ModeOverride = io.default_mode,
201 };
202
203 /// Upon success, the stream is in an uninitialized state. To continue using it,
204 /// you must use the open() function.
205 pub fn close(self: File) void {
206 if (is_windows) {
207 windows.CloseHandle(self.handle);
208 } else if (self.capable_io_mode != self.intended_io_mode) {
209 std.event.Loop.instance.?.close(self.handle);
210 } else {
211 os.close(self.handle);
212 }
213 }
214
215 pub const SyncError = os.SyncError;
216
217 /// Blocks until all pending file contents and metadata modifications
218 /// for the file have been synchronized with the underlying filesystem.
219 ///
220 /// Note that this does not ensure that metadata for the
221 /// directory containing the file has also reached disk.
222 pub fn sync(self: File) SyncError!void {
223 return os.fsync(self.handle);
224 }
225
226 /// Test whether the file refers to a terminal.
227 /// See also `supportsAnsiEscapeCodes`.
228 pub fn isTty(self: File) bool {
229 return os.isatty(self.handle);
230 }
231
232 /// Test whether ANSI escape codes will be treated as such.
233 pub fn supportsAnsiEscapeCodes(self: File) bool {
234 if (builtin.os.tag == .windows) {
235 var console_mode: os.windows.DWORD = 0;
236 if (os.windows.kernel32.GetConsoleMode(self.handle, &console_mode) != 0) {
237 if (console_mode & os.windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING != 0) return true;
238 }
239
240 return os.isCygwinPty(self.handle);
241 }
242 if (builtin.os.tag == .wasi) {
243 // WASI sanitizes stdout when fd is a tty so ANSI escape codes
244 // will not be interpreted as actual cursor commands, and
245 // stderr is always sanitized.
246 return false;
247 }
248 if (self.isTty()) {
249 if (self.handle == os.STDOUT_FILENO or self.handle == os.STDERR_FILENO) {
250 if (os.getenvZ("TERM")) |term| {
251 if (std.mem.eql(u8, term, "dumb"))
252 return false;
253 }
254 }
255 return true;
256 }
257 return false;
258 }
259
260 pub const SetEndPosError = os.TruncateError;
261
262 /// Shrinks or expands the file.
263 /// The file offset after this call is left unchanged.
264 pub fn setEndPos(self: File, length: u64) SetEndPosError!void {
265 try os.ftruncate(self.handle, length);
266 }
267
268 pub const SeekError = os.SeekError;
269
270 /// Repositions read/write file offset relative to the current offset.
271 /// TODO: integrate with async I/O
272 pub fn seekBy(self: File, offset: i64) SeekError!void {
273 return os.lseek_CUR(self.handle, offset);
274 }
275
276 /// Repositions read/write file offset relative to the end.
277 /// TODO: integrate with async I/O
278 pub fn seekFromEnd(self: File, offset: i64) SeekError!void {
279 return os.lseek_END(self.handle, offset);
280 }
281
282 /// Repositions read/write file offset relative to the beginning.
283 /// TODO: integrate with async I/O
284 pub fn seekTo(self: File, offset: u64) SeekError!void {
285 return os.lseek_SET(self.handle, offset);
286 }
287
288 pub const GetSeekPosError = os.SeekError || os.FStatError;
289
290 /// TODO: integrate with async I/O
291 pub fn getPos(self: File) GetSeekPosError!u64 {
292 return os.lseek_CUR_get(self.handle);
293 }
294
295 /// TODO: integrate with async I/O
296 pub fn getEndPos(self: File) GetSeekPosError!u64 {
297 if (builtin.os.tag == .windows) {
298 return windows.GetFileSizeEx(self.handle);
299 }
300 return (try self.stat()).size;
301 }
302
303 pub const ModeError = os.FStatError;
304
305 /// TODO: integrate with async I/O
306 pub fn mode(self: File) ModeError!Mode {
307 if (builtin.os.tag == .windows) {
308 return 0;
309 }
310 return (try self.stat()).mode;
311 }
312
313 pub const Stat = struct {
314 /// A number that the system uses to point to the file metadata. This
315 /// number is not guaranteed to be unique across time, as some file
316 /// systems may reuse an inode after its file has been deleted. Some
317 /// systems may change the inode of a file over time.
318 ///
319 /// On Linux, the inode is a structure that stores the metadata, and
320 /// the inode _number_ is what you see here: the index number of the
321 /// inode.
322 ///
323 /// The FileIndex on Windows is similar. It is a number for a file that
324 /// is unique to each filesystem.
325 inode: INode,
326 size: u64,
327 /// This is available on POSIX systems and is always 0 otherwise.
328 mode: Mode,
329 kind: Kind,
330
331 /// Access time in nanoseconds, relative to UTC 1970-01-01.
332 atime: i128,
333 /// Last modification time in nanoseconds, relative to UTC 1970-01-01.
334 mtime: i128,
335 /// Creation time in nanoseconds, relative to UTC 1970-01-01.
336 ctime: i128,
337
338 pub fn fromSystem(st: os.system.Stat) Stat {
339 const atime = st.atime();
340 const mtime = st.mtime();
341 const ctime = st.ctime();
342 const kind: Kind = if (builtin.os.tag == .wasi and !builtin.link_libc) switch (st.filetype) {
343 .BLOCK_DEVICE => .block_device,
344 .CHARACTER_DEVICE => .character_device,
345 .DIRECTORY => .directory,
346 .SYMBOLIC_LINK => .sym_link,
347 .REGULAR_FILE => .file,
348 .SOCKET_STREAM, .SOCKET_DGRAM => .unix_domain_socket,
349 else => .unknown,
350 } else blk: {
351 const m = st.mode & os.S.IFMT;
352 switch (m) {
353 os.S.IFBLK => break :blk .block_device,
354 os.S.IFCHR => break :blk .character_device,
355 os.S.IFDIR => break :blk .directory,
356 os.S.IFIFO => break :blk .named_pipe,
357 os.S.IFLNK => break :blk .sym_link,
358 os.S.IFREG => break :blk .file,
359 os.S.IFSOCK => break :blk .unix_domain_socket,
360 else => {},
361 }
362 if (builtin.os.tag.isSolarish()) switch (m) {
363 os.S.IFDOOR => break :blk .door,
364 os.S.IFPORT => break :blk .event_port,
365 else => {},
366 };
367
368 break :blk .unknown;
369 };
370
371 return Stat{
372 .inode = st.ino,
373 .size = @as(u64, @bitCast(st.size)),
374 .mode = st.mode,
375 .kind = kind,
376 .atime = @as(i128, atime.tv_sec) * std.time.ns_per_s + atime.tv_nsec,
377 .mtime = @as(i128, mtime.tv_sec) * std.time.ns_per_s + mtime.tv_nsec,
378 .ctime = @as(i128, ctime.tv_sec) * std.time.ns_per_s + ctime.tv_nsec,
379 };
380 }
381 };
382
383 pub const StatError = os.FStatError;
384
385 /// TODO: integrate with async I/O
386 pub fn stat(self: File) StatError!Stat {
387 if (builtin.os.tag == .windows) {
388 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
389 var info: windows.FILE_ALL_INFORMATION = undefined;
390 const rc = windows.ntdll.NtQueryInformationFile(self.handle, &io_status_block, &info, @sizeOf(windows.FILE_ALL_INFORMATION), .FileAllInformation);
391 switch (rc) {
392 .SUCCESS => {},
393 // Buffer overflow here indicates that there is more information available than was able to be stored in the buffer
394 // size provided. This is treated as success because the type of variable-length information that this would be relevant for
395 // (name, volume name, etc) we don't care about.
396 .BUFFER_OVERFLOW => {},
397 .INVALID_PARAMETER => unreachable,
398 .ACCESS_DENIED => return error.AccessDenied,
399 else => return windows.unexpectedStatus(rc),
400 }
401 return Stat{
402 .inode = info.InternalInformation.IndexNumber,
403 .size = @as(u64, @bitCast(info.StandardInformation.EndOfFile)),
404 .mode = 0,
405 .kind = if (info.StandardInformation.Directory == 0) .file else .directory,
406 .atime = windows.fromSysTime(info.BasicInformation.LastAccessTime),
407 .mtime = windows.fromSysTime(info.BasicInformation.LastWriteTime),
408 .ctime = windows.fromSysTime(info.BasicInformation.CreationTime),
409 };
410 }
411
412 const st = try os.fstat(self.handle);
413 return Stat.fromSystem(st);
414 }
415
416 pub const ChmodError = std.os.FChmodError;
417
418 /// Changes the mode of the file.
419 /// The process must have the correct privileges in order to do this
420 /// successfully, or must have the effective user ID matching the owner
421 /// of the file.
422 pub fn chmod(self: File, new_mode: Mode) ChmodError!void {
423 try os.fchmod(self.handle, new_mode);
424 }
425
426 pub const ChownError = std.os.FChownError;
427
428 /// Changes the owner and group of the file.
429 /// The process must have the correct privileges in order to do this
430 /// successfully. The group may be changed by the owner of the file to
431 /// any group of which the owner is a member. If the owner or group is
432 /// specified as `null`, the ID is not changed.
433 pub fn chown(self: File, owner: ?Uid, group: ?Gid) ChownError!void {
434 try os.fchown(self.handle, owner, group);
435 }
436
437 /// Cross-platform representation of permissions on a file.
438 /// The `readonly` and `setReadonly` are the only methods available across all platforms.
439 /// Platform-specific functionality is available through the `inner` field.
440 pub const Permissions = struct {
441 /// You may use the `inner` field to use platform-specific functionality
442 inner: switch (builtin.os.tag) {
443 .windows => PermissionsWindows,
444 else => PermissionsUnix,
445 },
446
447 const Self = @This();
448
449 /// Returns `true` if permissions represent an unwritable file.
450 /// On Unix, `true` is returned only if no class has write permissions.
451 pub fn readOnly(self: Self) bool {
452 return self.inner.readOnly();
453 }
454
455 /// Sets whether write permissions are provided.
456 /// On Unix, this affects *all* classes. If this is undesired, use `unixSet`.
457 /// This method *DOES NOT* set permissions on the filesystem: use `File.setPermissions(permissions)`
458 pub fn setReadOnly(self: *Self, read_only: bool) void {
459 self.inner.setReadOnly(read_only);
460 }
461 };
462
463 pub const PermissionsWindows = struct {
464 attributes: os.windows.DWORD,
465
466 const Self = @This();
467
468 /// Returns `true` if permissions represent an unwritable file.
469 pub fn readOnly(self: Self) bool {
470 return self.attributes & os.windows.FILE_ATTRIBUTE_READONLY != 0;
471 }
472
473 /// Sets whether write permissions are provided.
474 /// This method *DOES NOT* set permissions on the filesystem: use `File.setPermissions(permissions)`
475 pub fn setReadOnly(self: *Self, read_only: bool) void {
476 if (read_only) {
477 self.attributes |= os.windows.FILE_ATTRIBUTE_READONLY;
478 } else {
479 self.attributes &= ~@as(os.windows.DWORD, os.windows.FILE_ATTRIBUTE_READONLY);
480 }
481 }
482 };
483
484 pub const PermissionsUnix = struct {
485 mode: Mode,
486
487 const Self = @This();
488
489 /// Returns `true` if permissions represent an unwritable file.
490 /// `true` is returned only if no class has write permissions.
491 pub fn readOnly(self: Self) bool {
492 return self.mode & 0o222 == 0;
493 }
494
495 /// Sets whether write permissions are provided.
496 /// This affects *all* classes. If this is undesired, use `unixSet`.
497 /// This method *DOES NOT* set permissions on the filesystem: use `File.setPermissions(permissions)`
498 pub fn setReadOnly(self: *Self, read_only: bool) void {
499 if (read_only) {
500 self.mode &= ~@as(Mode, 0o222);
501 } else {
502 self.mode |= @as(Mode, 0o222);
503 }
504 }
505
506 pub const Class = enum(u2) {
507 user = 2,
508 group = 1,
509 other = 0,
510 };
511
512 pub const Permission = enum(u3) {
513 read = 0o4,
514 write = 0o2,
515 execute = 0o1,
516 };
517
518 /// Returns `true` if the chosen class has the selected permission.
519 /// This method is only available on Unix platforms.
520 pub fn unixHas(self: Self, class: Class, permission: Permission) bool {
521 const mask = @as(Mode, @intFromEnum(permission)) << @as(u3, @intFromEnum(class)) * 3;
522 return self.mode & mask != 0;
523 }
524
525 /// Sets the permissions for the chosen class. Any permissions set to `null` are left unchanged.
526 /// This method *DOES NOT* set permissions on the filesystem: use `File.setPermissions(permissions)`
527 pub fn unixSet(self: *Self, class: Class, permissions: struct {
528 read: ?bool = null,
529 write: ?bool = null,
530 execute: ?bool = null,
531 }) void {
532 const shift = @as(u3, @intFromEnum(class)) * 3;
533 if (permissions.read) |r| {
534 if (r) {
535 self.mode |= @as(Mode, 0o4) << shift;
536 } else {
537 self.mode &= ~(@as(Mode, 0o4) << shift);
538 }
539 }
540 if (permissions.write) |w| {
541 if (w) {
542 self.mode |= @as(Mode, 0o2) << shift;
543 } else {
544 self.mode &= ~(@as(Mode, 0o2) << shift);
545 }
546 }
547 if (permissions.execute) |x| {
548 if (x) {
549 self.mode |= @as(Mode, 0o1) << shift;
550 } else {
551 self.mode &= ~(@as(Mode, 0o1) << shift);
552 }
553 }
554 }
555
556 /// Returns a `Permissions` struct representing the permissions from the passed mode.
557 pub fn unixNew(new_mode: Mode) Self {
558 return Self{
559 .mode = new_mode,
560 };
561 }
562 };
563
564 pub const SetPermissionsError = ChmodError;
565
566 /// Sets permissions according to the provided `Permissions` struct.
567 /// This method is *NOT* available on WASI
568 pub fn setPermissions(self: File, permissions: Permissions) SetPermissionsError!void {
569 switch (builtin.os.tag) {
570 .windows => {
571 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
572 var info = windows.FILE_BASIC_INFORMATION{
573 .CreationTime = 0,
574 .LastAccessTime = 0,
575 .LastWriteTime = 0,
576 .ChangeTime = 0,
577 .FileAttributes = permissions.inner.attributes,
578 };
579 const rc = windows.ntdll.NtSetInformationFile(
580 self.handle,
581 &io_status_block,
582 &info,
583 @sizeOf(windows.FILE_BASIC_INFORMATION),
584 .FileBasicInformation,
585 );
586 switch (rc) {
587 .SUCCESS => return,
588 .INVALID_HANDLE => unreachable,
589 .ACCESS_DENIED => return error.AccessDenied,
590 else => return windows.unexpectedStatus(rc),
591 }
592 },
593 .wasi => @compileError("Unsupported OS"), // Wasi filesystem does not *yet* support chmod
594 else => {
595 try self.chmod(permissions.inner.mode);
596 },
597 }
598 }
599
600 /// Cross-platform representation of file metadata.
601 /// Platform-specific functionality is available through the `inner` field.
602 pub const Metadata = struct {
603 /// You may use the `inner` field to use platform-specific functionality
604 inner: switch (builtin.os.tag) {
605 .windows => MetadataWindows,
606 .linux => MetadataLinux,
607 else => MetadataUnix,
608 },
609
610 const Self = @This();
611
612 /// Returns the size of the file
613 pub fn size(self: Self) u64 {
614 return self.inner.size();
615 }
616
617 /// Returns a `Permissions` struct, representing the permissions on the file
618 pub fn permissions(self: Self) Permissions {
619 return self.inner.permissions();
620 }
621
622 /// Returns the `Kind` of file.
623 /// On Windows, can only return: `.file`, `.directory`, `.sym_link` or `.unknown`
624 pub fn kind(self: Self) Kind {
625 return self.inner.kind();
626 }
627
628 /// Returns the last time the file was accessed in nanoseconds since UTC 1970-01-01
629 pub fn accessed(self: Self) i128 {
630 return self.inner.accessed();
631 }
632
633 /// Returns the time the file was modified in nanoseconds since UTC 1970-01-01
634 pub fn modified(self: Self) i128 {
635 return self.inner.modified();
636 }
637
638 /// Returns the time the file was created in nanoseconds since UTC 1970-01-01
639 /// On Windows, this cannot return null
640 /// On Linux, this returns null if the filesystem does not support creation times, or if the kernel is older than 4.11
641 /// On Unices, this returns null if the filesystem or OS does not support creation times
642 /// On MacOS, this returns the ctime if the filesystem does not support creation times; this is insanity, and yet another reason to hate on Apple
643 pub fn created(self: Self) ?i128 {
644 return self.inner.created();
645 }
646 };
647
648 pub const MetadataUnix = struct {
649 stat: os.Stat,
650
651 const Self = @This();
652
653 /// Returns the size of the file
654 pub fn size(self: Self) u64 {
655 return @as(u64, @intCast(self.stat.size));
656 }
657
658 /// Returns a `Permissions` struct, representing the permissions on the file
659 pub fn permissions(self: Self) Permissions {
660 return Permissions{ .inner = PermissionsUnix{ .mode = self.stat.mode } };
661 }
662
663 /// Returns the `Kind` of the file
664 pub fn kind(self: Self) Kind {
665 if (builtin.os.tag == .wasi and !builtin.link_libc) return switch (self.stat.filetype) {
666 .BLOCK_DEVICE => .block_device,
667 .CHARACTER_DEVICE => .character_device,
668 .DIRECTORY => .directory,
669 .SYMBOLIC_LINK => .sym_link,
670 .REGULAR_FILE => .file,
671 .SOCKET_STREAM, .SOCKET_DGRAM => .unix_domain_socket,
672 else => .unknown,
673 };
674
675 const m = self.stat.mode & os.S.IFMT;
676
677 switch (m) {
678 os.S.IFBLK => return .block_device,
679 os.S.IFCHR => return .character_device,
680 os.S.IFDIR => return .directory,
681 os.S.IFIFO => return .named_pipe,
682 os.S.IFLNK => return .sym_link,
683 os.S.IFREG => return .file,
684 os.S.IFSOCK => return .unix_domain_socket,
685 else => {},
686 }
687
688 if (builtin.os.tag.isSolarish()) switch (m) {
689 os.S.IFDOOR => return .door,
690 os.S.IFPORT => return .event_port,
691 else => {},
692 };
693
694 return .unknown;
695 }
696
697 /// Returns the last time the file was accessed in nanoseconds since UTC 1970-01-01
698 pub fn accessed(self: Self) i128 {
699 const atime = self.stat.atime();
700 return @as(i128, atime.tv_sec) * std.time.ns_per_s + atime.tv_nsec;
701 }
702
703 /// Returns the last time the file was modified in nanoseconds since UTC 1970-01-01
704 pub fn modified(self: Self) i128 {
705 const mtime = self.stat.mtime();
706 return @as(i128, mtime.tv_sec) * std.time.ns_per_s + mtime.tv_nsec;
707 }
708
709 /// Returns the time the file was created in nanoseconds since UTC 1970-01-01.
710 /// Returns null if this is not supported by the OS or filesystem
711 pub fn created(self: Self) ?i128 {
712 if (!@hasDecl(@TypeOf(self.stat), "birthtime")) return null;
713 const birthtime = self.stat.birthtime();
714
715 // If the filesystem doesn't support this the value *should* be:
716 // On FreeBSD: tv_nsec = 0, tv_sec = -1
717 // On NetBSD and OpenBSD: tv_nsec = 0, tv_sec = 0
718 // On MacOS, it is set to ctime -- we cannot detect this!!
719 switch (builtin.os.tag) {
720 .freebsd => if (birthtime.tv_sec == -1 and birthtime.tv_nsec == 0) return null,
721 .netbsd, .openbsd => if (birthtime.tv_sec == 0 and birthtime.tv_nsec == 0) return null,
722 .macos => {},
723 else => @compileError("Creation time detection not implemented for OS"),
724 }
725
726 return @as(i128, birthtime.tv_sec) * std.time.ns_per_s + birthtime.tv_nsec;
727 }
728 };
729
730 /// `MetadataUnix`, but using Linux's `statx` syscall.
731 /// On Linux versions below 4.11, `statx` will be filled with data from stat.
732 pub const MetadataLinux = struct {
733 statx: os.linux.Statx,
734
735 const Self = @This();
736
737 /// Returns the size of the file
738 pub fn size(self: Self) u64 {
739 return self.statx.size;
740 }
741
742 /// Returns a `Permissions` struct, representing the permissions on the file
743 pub fn permissions(self: Self) Permissions {
744 return Permissions{ .inner = PermissionsUnix{ .mode = self.statx.mode } };
745 }
746
747 /// Returns the `Kind` of the file
748 pub fn kind(self: Self) Kind {
749 const m = self.statx.mode & os.S.IFMT;
750
751 switch (m) {
752 os.S.IFBLK => return .block_device,
753 os.S.IFCHR => return .character_device,
754 os.S.IFDIR => return .directory,
755 os.S.IFIFO => return .named_pipe,
756 os.S.IFLNK => return .sym_link,
757 os.S.IFREG => return .file,
758 os.S.IFSOCK => return .unix_domain_socket,
759 else => {},
760 }
761
762 return .unknown;
763 }
764
765 /// Returns the last time the file was accessed in nanoseconds since UTC 1970-01-01
766 pub fn accessed(self: Self) i128 {
767 return @as(i128, self.statx.atime.tv_sec) * std.time.ns_per_s + self.statx.atime.tv_nsec;
768 }
769
770 /// Returns the last time the file was modified in nanoseconds since UTC 1970-01-01
771 pub fn modified(self: Self) i128 {
772 return @as(i128, self.statx.mtime.tv_sec) * std.time.ns_per_s + self.statx.mtime.tv_nsec;
773 }
774
775 /// Returns the time the file was created in nanoseconds since UTC 1970-01-01.
776 /// Returns null if this is not supported by the filesystem, or on kernels before than version 4.11
777 pub fn created(self: Self) ?i128 {
778 if (self.statx.mask & os.linux.STATX_BTIME == 0) return null;
779 return @as(i128, self.statx.btime.tv_sec) * std.time.ns_per_s + self.statx.btime.tv_nsec;
780 }
781 };
782
783 pub const MetadataWindows = struct {
784 attributes: windows.DWORD,
785 reparse_tag: windows.DWORD,
786 _size: u64,
787 access_time: i128,
788 modified_time: i128,
789 creation_time: i128,
790
791 const Self = @This();
792
793 /// Returns the size of the file
794 pub fn size(self: Self) u64 {
795 return self._size;
796 }
797
798 /// Returns a `Permissions` struct, representing the permissions on the file
799 pub fn permissions(self: Self) Permissions {
800 return Permissions{ .inner = PermissionsWindows{ .attributes = self.attributes } };
801 }
802
803 /// Returns the `Kind` of the file.
804 /// Can only return: `.file`, `.directory`, `.sym_link` or `.unknown`
805 pub fn kind(self: Self) Kind {
806 if (self.attributes & windows.FILE_ATTRIBUTE_REPARSE_POINT != 0) {
807 if (self.reparse_tag & 0x20000000 != 0) {
808 return .sym_link;
809 }
810 } else if (self.attributes & windows.FILE_ATTRIBUTE_DIRECTORY != 0) {
811 return .directory;
812 } else {
813 return .file;
814 }
815 return .unknown;
816 }
817
818 /// Returns the last time the file was accessed in nanoseconds since UTC 1970-01-01
819 pub fn accessed(self: Self) i128 {
820 return self.access_time;
821 }
822
823 /// Returns the time the file was modified in nanoseconds since UTC 1970-01-01
824 pub fn modified(self: Self) i128 {
825 return self.modified_time;
826 }
827
828 /// Returns the time the file was created in nanoseconds since UTC 1970-01-01.
829 /// This never returns null, only returning an optional for compatibility with other OSes
830 pub fn created(self: Self) ?i128 {
831 return self.creation_time;
832 }
833 };
834
835 pub const MetadataError = os.FStatError;
836
837 pub fn metadata(self: File) MetadataError!Metadata {
838 return Metadata{
839 .inner = switch (builtin.os.tag) {
840 .windows => blk: {
841 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
842 var info: windows.FILE_ALL_INFORMATION = undefined;
843
844 const rc = windows.ntdll.NtQueryInformationFile(self.handle, &io_status_block, &info, @sizeOf(windows.FILE_ALL_INFORMATION), .FileAllInformation);
845 switch (rc) {
846 .SUCCESS => {},
847 // Buffer overflow here indicates that there is more information available than was able to be stored in the buffer
848 // size provided. This is treated as success because the type of variable-length information that this would be relevant for
849 // (name, volume name, etc) we don't care about.
850 .BUFFER_OVERFLOW => {},
851 .INVALID_PARAMETER => unreachable,
852 .ACCESS_DENIED => return error.AccessDenied,
853 else => return windows.unexpectedStatus(rc),
854 }
855
856 const reparse_tag: windows.DWORD = reparse_blk: {
857 if (info.BasicInformation.FileAttributes & windows.FILE_ATTRIBUTE_REPARSE_POINT != 0) {
858 var reparse_buf: [windows.MAXIMUM_REPARSE_DATA_BUFFER_SIZE]u8 = undefined;
859 try windows.DeviceIoControl(self.handle, windows.FSCTL_GET_REPARSE_POINT, null, reparse_buf[0..]);
860 const reparse_struct: *const windows.REPARSE_DATA_BUFFER = @ptrCast(@alignCast(&reparse_buf[0]));
861 break :reparse_blk reparse_struct.ReparseTag;
862 }
863 break :reparse_blk 0;
864 };
865
866 break :blk MetadataWindows{
867 .attributes = info.BasicInformation.FileAttributes,
868 .reparse_tag = reparse_tag,
869 ._size = @as(u64, @bitCast(info.StandardInformation.EndOfFile)),
870 .access_time = windows.fromSysTime(info.BasicInformation.LastAccessTime),
871 .modified_time = windows.fromSysTime(info.BasicInformation.LastWriteTime),
872 .creation_time = windows.fromSysTime(info.BasicInformation.CreationTime),
873 };
874 },
875 .linux => blk: {
876 var stx = mem.zeroes(os.linux.Statx);
877 const rcx = os.linux.statx(self.handle, "\x00", os.linux.AT.EMPTY_PATH, os.linux.STATX_TYPE | os.linux.STATX_MODE | os.linux.STATX_ATIME | os.linux.STATX_MTIME | os.linux.STATX_BTIME, &stx);
878
879 switch (os.errno(rcx)) {
880 .SUCCESS => {},
881 // NOSYS happens when `statx` is unsupported, which is the case on kernel versions before 4.11
882 // Here, we call `fstat` and fill `stx` with the data we need
883 .NOSYS => {
884 const st = try os.fstat(self.handle);
885
886 stx.mode = @as(u16, @intCast(st.mode));
887
888 // Hacky conversion from timespec to statx_timestamp
889 stx.atime = std.mem.zeroes(os.linux.statx_timestamp);
890 stx.atime.tv_sec = st.atim.tv_sec;
891 stx.atime.tv_nsec = @as(u32, @intCast(st.atim.tv_nsec)); // Guaranteed to succeed (tv_nsec is always below 10^9)
892
893 stx.mtime = std.mem.zeroes(os.linux.statx_timestamp);
894 stx.mtime.tv_sec = st.mtim.tv_sec;
895 stx.mtime.tv_nsec = @as(u32, @intCast(st.mtim.tv_nsec));
896
897 stx.mask = os.linux.STATX_BASIC_STATS | os.linux.STATX_MTIME;
898 },
899 .BADF => unreachable,
900 .FAULT => unreachable,
901 .NOMEM => return error.SystemResources,
902 else => |err| return os.unexpectedErrno(err),
903 }
904
905 break :blk MetadataLinux{
906 .statx = stx,
907 };
908 },
909 else => blk: {
910 const st = try os.fstat(self.handle);
911 break :blk MetadataUnix{
912 .stat = st,
913 };
914 },
915 },
916 };
917 }
918
919 pub const UpdateTimesError = os.FutimensError || windows.SetFileTimeError;
920
921 /// The underlying file system may have a different granularity than nanoseconds,
922 /// and therefore this function cannot guarantee any precision will be stored.
923 /// Further, the maximum value is limited by the system ABI. When a value is provided
924 /// that exceeds this range, the value is clamped to the maximum.
925 /// TODO: integrate with async I/O
926 pub fn updateTimes(
927 self: File,
928 /// access timestamp in nanoseconds
929 atime: i128,
930 /// last modification timestamp in nanoseconds
931 mtime: i128,
932 ) UpdateTimesError!void {
933 if (builtin.os.tag == .windows) {
934 const atime_ft = windows.nanoSecondsToFileTime(atime);
935 const mtime_ft = windows.nanoSecondsToFileTime(mtime);
936 return windows.SetFileTime(self.handle, null, &atime_ft, &mtime_ft);
937 }
938 const times = [2]os.timespec{
939 os.timespec{
940 .tv_sec = math.cast(isize, @divFloor(atime, std.time.ns_per_s)) orelse maxInt(isize),
941 .tv_nsec = math.cast(isize, @mod(atime, std.time.ns_per_s)) orelse maxInt(isize),
942 },
943 os.timespec{
944 .tv_sec = math.cast(isize, @divFloor(mtime, std.time.ns_per_s)) orelse maxInt(isize),
945 .tv_nsec = math.cast(isize, @mod(mtime, std.time.ns_per_s)) orelse maxInt(isize),
946 },
947 };
948 try os.futimens(self.handle, &times);
949 }
950
951 /// Reads all the bytes from the current position to the end of the file.
952 /// On success, caller owns returned buffer.
953 /// If the file is larger than `max_bytes`, returns `error.FileTooBig`.
954 pub fn readToEndAlloc(self: File, allocator: mem.Allocator, max_bytes: usize) ![]u8 {
955 return self.readToEndAllocOptions(allocator, max_bytes, null, @alignOf(u8), null);
956 }
957
958 /// Reads all the bytes from the current position to the end of the file.
959 /// On success, caller owns returned buffer.
960 /// If the file is larger than `max_bytes`, returns `error.FileTooBig`.
961 /// If `size_hint` is specified the initial buffer size is calculated using
962 /// that value, otherwise an arbitrary value is used instead.
963 /// Allows specifying alignment and a sentinel value.
964 pub fn readToEndAllocOptions(
965 self: File,
966 allocator: mem.Allocator,
967 max_bytes: usize,
968 size_hint: ?usize,
969 comptime alignment: u29,
970 comptime optional_sentinel: ?u8,
971 ) !(if (optional_sentinel) |s| [:s]align(alignment) u8 else []align(alignment) u8) {
972 // If no size hint is provided fall back to the size=0 code path
973 const size = size_hint orelse 0;
974
975 // The file size returned by stat is used as hint to set the buffer
976 // size. If the reported size is zero, as it happens on Linux for files
977 // in /proc, a small buffer is allocated instead.
978 const initial_cap = (if (size > 0) size else 1024) + @intFromBool(optional_sentinel != null);
979 var array_list = try std.ArrayListAligned(u8, alignment).initCapacity(allocator, initial_cap);
980 defer array_list.deinit();
981
982 self.reader().readAllArrayListAligned(alignment, &array_list, max_bytes) catch |err| switch (err) {
983 error.StreamTooLong => return error.FileTooBig,
984 else => |e| return e,
985 };
986
987 if (optional_sentinel) |sentinel| {
988 return try array_list.toOwnedSliceSentinel(sentinel);
989 } else {
990 return try array_list.toOwnedSlice();
991 }
992 }
993
994 pub const ReadError = os.ReadError;
995 pub const PReadError = os.PReadError;
996
997 pub fn read(self: File, buffer: []u8) ReadError!usize {
998 if (is_windows) {
999 return windows.ReadFile(self.handle, buffer, null, self.intended_io_mode);
1000 }
1001
1002 if (self.intended_io_mode == .blocking) {
1003 return os.read(self.handle, buffer);
1004 } else {
1005 return std.event.Loop.instance.?.read(self.handle, buffer, self.capable_io_mode != self.intended_io_mode);
1006 }
1007 }
1008
1009 /// Returns the number of bytes read. If the number read is smaller than `buffer.len`, it
1010 /// means the file reached the end. Reaching the end of a file is not an error condition.
1011 pub fn readAll(self: File, buffer: []u8) ReadError!usize {
1012 var index: usize = 0;
1013 while (index != buffer.len) {
1014 const amt = try self.read(buffer[index..]);
1015 if (amt == 0) break;
1016 index += amt;
1017 }
1018 return index;
1019 }
1020
1021 /// On Windows, this function currently does alter the file pointer.
1022 /// https://github.com/ziglang/zig/issues/12783
1023 pub fn pread(self: File, buffer: []u8, offset: u64) PReadError!usize {
1024 if (is_windows) {
1025 return windows.ReadFile(self.handle, buffer, offset, self.intended_io_mode);
1026 }
1027
1028 if (self.intended_io_mode == .blocking) {
1029 return os.pread(self.handle, buffer, offset);
1030 } else {
1031 return std.event.Loop.instance.?.pread(self.handle, buffer, offset, self.capable_io_mode != self.intended_io_mode);
1032 }
1033 }
1034
1035 /// Returns the number of bytes read. If the number read is smaller than `buffer.len`, it
1036 /// means the file reached the end. Reaching the end of a file is not an error condition.
1037 /// On Windows, this function currently does alter the file pointer.
1038 /// https://github.com/ziglang/zig/issues/12783
1039 pub fn preadAll(self: File, buffer: []u8, offset: u64) PReadError!usize {
1040 var index: usize = 0;
1041 while (index != buffer.len) {
1042 const amt = try self.pread(buffer[index..], offset + index);
1043 if (amt == 0) break;
1044 index += amt;
1045 }
1046 return index;
1047 }
1048
1049 /// See https://github.com/ziglang/zig/issues/7699
1050 pub fn readv(self: File, iovecs: []const os.iovec) ReadError!usize {
1051 if (is_windows) {
1052 // TODO improve this to use ReadFileScatter
1053 if (iovecs.len == 0) return @as(usize, 0);
1054 const first = iovecs[0];
1055 return windows.ReadFile(self.handle, first.iov_base[0..first.iov_len], null, self.intended_io_mode);
1056 }
1057
1058 if (self.intended_io_mode == .blocking) {
1059 return os.readv(self.handle, iovecs);
1060 } else {
1061 return std.event.Loop.instance.?.readv(self.handle, iovecs, self.capable_io_mode != self.intended_io_mode);
1062 }
1063 }
1064
1065 /// Returns the number of bytes read. If the number read is smaller than the total bytes
1066 /// from all the buffers, it means the file reached the end. Reaching the end of a file
1067 /// is not an error condition.
1068 ///
1069 /// The `iovecs` parameter is mutable because:
1070 /// * This function needs to mutate the fields in order to handle partial
1071 /// reads from the underlying OS layer.
1072 /// * The OS layer expects pointer addresses to be inside the application's address space
1073 /// even if the length is zero. Meanwhile, in Zig, slices may have undefined pointer
1074 /// addresses when the length is zero. So this function modifies the iov_base fields
1075 /// when the length is zero.
1076 ///
1077 /// Related open issue: https://github.com/ziglang/zig/issues/7699
1078 pub fn readvAll(self: File, iovecs: []os.iovec) ReadError!usize {
1079 if (iovecs.len == 0) return 0;
1080
1081 // We use the address of this local variable for all zero-length
1082 // vectors so that the OS does not complain that we are giving it
1083 // addresses outside the application's address space.
1084 var garbage: [1]u8 = undefined;
1085 for (iovecs) |*v| {
1086 if (v.iov_len == 0) v.iov_base = &garbage;
1087 }
1088
1089 var i: usize = 0;
1090 var off: usize = 0;
1091 while (true) {
1092 var amt = try self.readv(iovecs[i..]);
1093 var eof = amt == 0;
1094 off += amt;
1095 while (amt >= iovecs[i].iov_len) {
1096 amt -= iovecs[i].iov_len;
1097 i += 1;
1098 if (i >= iovecs.len) return off;
1099 eof = false;
1100 }
1101 if (eof) return off;
1102 iovecs[i].iov_base += amt;
1103 iovecs[i].iov_len -= amt;
1104 }
1105 }
1106
1107 /// See https://github.com/ziglang/zig/issues/7699
1108 /// On Windows, this function currently does alter the file pointer.
1109 /// https://github.com/ziglang/zig/issues/12783
1110 pub fn preadv(self: File, iovecs: []const os.iovec, offset: u64) PReadError!usize {
1111 if (is_windows) {
1112 // TODO improve this to use ReadFileScatter
1113 if (iovecs.len == 0) return @as(usize, 0);
1114 const first = iovecs[0];
1115 return windows.ReadFile(self.handle, first.iov_base[0..first.iov_len], offset, self.intended_io_mode);
1116 }
1117
1118 if (self.intended_io_mode == .blocking) {
1119 return os.preadv(self.handle, iovecs, offset);
1120 } else {
1121 return std.event.Loop.instance.?.preadv(self.handle, iovecs, offset, self.capable_io_mode != self.intended_io_mode);
1122 }
1123 }
1124
1125 /// Returns the number of bytes read. If the number read is smaller than the total bytes
1126 /// from all the buffers, it means the file reached the end. Reaching the end of a file
1127 /// is not an error condition.
1128 /// The `iovecs` parameter is mutable because this function needs to mutate the fields in
1129 /// order to handle partial reads from the underlying OS layer.
1130 /// See https://github.com/ziglang/zig/issues/7699
1131 /// On Windows, this function currently does alter the file pointer.
1132 /// https://github.com/ziglang/zig/issues/12783
1133 pub fn preadvAll(self: File, iovecs: []os.iovec, offset: u64) PReadError!usize {
1134 if (iovecs.len == 0) return 0;
1135
1136 var i: usize = 0;
1137 var off: usize = 0;
1138 while (true) {
1139 var amt = try self.preadv(iovecs[i..], offset + off);
1140 var eof = amt == 0;
1141 off += amt;
1142 while (amt >= iovecs[i].iov_len) {
1143 amt -= iovecs[i].iov_len;
1144 i += 1;
1145 if (i >= iovecs.len) return off;
1146 eof = false;
1147 }
1148 if (eof) return off;
1149 iovecs[i].iov_base += amt;
1150 iovecs[i].iov_len -= amt;
1151 }
1152 }
1153
1154 pub const WriteError = os.WriteError;
1155 pub const PWriteError = os.PWriteError;
1156
1157 pub fn write(self: File, bytes: []const u8) WriteError!usize {
1158 if (is_windows) {
1159 return windows.WriteFile(self.handle, bytes, null, self.intended_io_mode);
1160 }
1161
1162 if (self.intended_io_mode == .blocking) {
1163 return os.write(self.handle, bytes);
1164 } else {
1165 return std.event.Loop.instance.?.write(self.handle, bytes, self.capable_io_mode != self.intended_io_mode);
1166 }
1167 }
1168
1169 pub fn writeAll(self: File, bytes: []const u8) WriteError!void {
1170 var index: usize = 0;
1171 while (index < bytes.len) {
1172 index += try self.write(bytes[index..]);
1173 }
1174 }
1175
1176 /// On Windows, this function currently does alter the file pointer.
1177 /// https://github.com/ziglang/zig/issues/12783
1178 pub fn pwrite(self: File, bytes: []const u8, offset: u64) PWriteError!usize {
1179 if (is_windows) {
1180 return windows.WriteFile(self.handle, bytes, offset, self.intended_io_mode);
1181 }
1182
1183 if (self.intended_io_mode == .blocking) {
1184 return os.pwrite(self.handle, bytes, offset);
1185 } else {
1186 return std.event.Loop.instance.?.pwrite(self.handle, bytes, offset, self.capable_io_mode != self.intended_io_mode);
1187 }
1188 }
1189
1190 /// On Windows, this function currently does alter the file pointer.
1191 /// https://github.com/ziglang/zig/issues/12783
1192 pub fn pwriteAll(self: File, bytes: []const u8, offset: u64) PWriteError!void {
1193 var index: usize = 0;
1194 while (index < bytes.len) {
1195 index += try self.pwrite(bytes[index..], offset + index);
1196 }
1197 }
1198
1199 /// See https://github.com/ziglang/zig/issues/7699
1200 /// See equivalent function: `std.net.Stream.writev`.
1201 pub fn writev(self: File, iovecs: []const os.iovec_const) WriteError!usize {
1202 if (is_windows) {
1203 // TODO improve this to use WriteFileScatter
1204 if (iovecs.len == 0) return @as(usize, 0);
1205 const first = iovecs[0];
1206 return windows.WriteFile(self.handle, first.iov_base[0..first.iov_len], null, self.intended_io_mode);
1207 }
1208
1209 if (self.intended_io_mode == .blocking) {
1210 return os.writev(self.handle, iovecs);
1211 } else {
1212 return std.event.Loop.instance.?.writev(self.handle, iovecs, self.capable_io_mode != self.intended_io_mode);
1213 }
1214 }
1215
1216 /// The `iovecs` parameter is mutable because:
1217 /// * This function needs to mutate the fields in order to handle partial
1218 /// writes from the underlying OS layer.
1219 /// * The OS layer expects pointer addresses to be inside the application's address space
1220 /// even if the length is zero. Meanwhile, in Zig, slices may have undefined pointer
1221 /// addresses when the length is zero. So this function modifies the iov_base fields
1222 /// when the length is zero.
1223 /// See https://github.com/ziglang/zig/issues/7699
1224 /// See equivalent function: `std.net.Stream.writevAll`.
1225 pub fn writevAll(self: File, iovecs: []os.iovec_const) WriteError!void {
1226 if (iovecs.len == 0) return;
1227
1228 // We use the address of this local variable for all zero-length
1229 // vectors so that the OS does not complain that we are giving it
1230 // addresses outside the application's address space.
1231 var garbage: [1]u8 = undefined;
1232 for (iovecs) |*v| {
1233 if (v.iov_len == 0) v.iov_base = &garbage;
1234 }
1235
1236 var i: usize = 0;
1237 while (true) {
1238 var amt = try self.writev(iovecs[i..]);
1239 while (amt >= iovecs[i].iov_len) {
1240 amt -= iovecs[i].iov_len;
1241 i += 1;
1242 if (i >= iovecs.len) return;
1243 }
1244 iovecs[i].iov_base += amt;
1245 iovecs[i].iov_len -= amt;
1246 }
1247 }
1248
1249 /// See https://github.com/ziglang/zig/issues/7699
1250 /// On Windows, this function currently does alter the file pointer.
1251 /// https://github.com/ziglang/zig/issues/12783
1252 pub fn pwritev(self: File, iovecs: []os.iovec_const, offset: u64) PWriteError!usize {
1253 if (is_windows) {
1254 // TODO improve this to use WriteFileScatter
1255 if (iovecs.len == 0) return @as(usize, 0);
1256 const first = iovecs[0];
1257 return windows.WriteFile(self.handle, first.iov_base[0..first.iov_len], offset, self.intended_io_mode);
1258 }
1259
1260 if (self.intended_io_mode == .blocking) {
1261 return os.pwritev(self.handle, iovecs, offset);
1262 } else {
1263 return std.event.Loop.instance.?.pwritev(self.handle, iovecs, offset, self.capable_io_mode != self.intended_io_mode);
1264 }
1265 }
1266
1267 /// The `iovecs` parameter is mutable because this function needs to mutate the fields in
1268 /// order to handle partial writes from the underlying OS layer.
1269 /// See https://github.com/ziglang/zig/issues/7699
1270 /// On Windows, this function currently does alter the file pointer.
1271 /// https://github.com/ziglang/zig/issues/12783
1272 pub fn pwritevAll(self: File, iovecs: []os.iovec_const, offset: u64) PWriteError!void {
1273 if (iovecs.len == 0) return;
1274
1275 var i: usize = 0;
1276 var off: u64 = 0;
1277 while (true) {
1278 var amt = try self.pwritev(iovecs[i..], offset + off);
1279 off += amt;
1280 while (amt >= iovecs[i].iov_len) {
1281 amt -= iovecs[i].iov_len;
1282 i += 1;
1283 if (i >= iovecs.len) return;
1284 }
1285 iovecs[i].iov_base += amt;
1286 iovecs[i].iov_len -= amt;
1287 }
1288 }
1289
1290 pub const CopyRangeError = os.CopyFileRangeError;
1291
1292 pub fn copyRange(in: File, in_offset: u64, out: File, out_offset: u64, len: u64) CopyRangeError!u64 {
1293 const adjusted_len = math.cast(usize, len) orelse math.maxInt(usize);
1294 const result = try os.copy_file_range(in.handle, in_offset, out.handle, out_offset, adjusted_len, 0);
1295 return result;
1296 }
1297
1298 /// Returns the number of bytes copied. If the number read is smaller than `buffer.len`, it
1299 /// means the in file reached the end. Reaching the end of a file is not an error condition.
1300 pub fn copyRangeAll(in: File, in_offset: u64, out: File, out_offset: u64, len: u64) CopyRangeError!u64 {
1301 var total_bytes_copied: u64 = 0;
1302 var in_off = in_offset;
1303 var out_off = out_offset;
1304 while (total_bytes_copied < len) {
1305 const amt_copied = try copyRange(in, in_off, out, out_off, len - total_bytes_copied);
1306 if (amt_copied == 0) return total_bytes_copied;
1307 total_bytes_copied += amt_copied;
1308 in_off += amt_copied;
1309 out_off += amt_copied;
1310 }
1311 return total_bytes_copied;
1312 }
1313
1314 pub const WriteFileOptions = struct {
1315 in_offset: u64 = 0,
1316
1317 /// `null` means the entire file. `0` means no bytes from the file.
1318 /// When this is `null`, trailers must be sent in a separate writev() call
1319 /// due to a flaw in the BSD sendfile API. Other operating systems, such as
1320 /// Linux, already do this anyway due to API limitations.
1321 /// If the size of the source file is known, passing the size here will save one syscall.
1322 in_len: ?u64 = null,
1323
1324 headers_and_trailers: []os.iovec_const = &[0]os.iovec_const{},
1325
1326 /// The trailer count is inferred from `headers_and_trailers.len - header_count`
1327 header_count: usize = 0,
1328 };
1329
1330 pub const WriteFileError = ReadError || error{EndOfStream} || WriteError;
1331
1332 pub fn writeFileAll(self: File, in_file: File, args: WriteFileOptions) WriteFileError!void {
1333 return self.writeFileAllSendfile(in_file, args) catch |err| switch (err) {
1334 error.Unseekable,
1335 error.FastOpenAlreadyInProgress,
1336 error.MessageTooBig,
1337 error.FileDescriptorNotASocket,
1338 error.NetworkUnreachable,
1339 error.NetworkSubsystemFailed,
1340 => return self.writeFileAllUnseekable(in_file, args),
1341
1342 else => |e| return e,
1343 };
1344 }
1345
1346 /// Does not try seeking in either of the File parameters.
1347 /// See `writeFileAll` as an alternative to calling this.
1348 pub fn writeFileAllUnseekable(self: File, in_file: File, args: WriteFileOptions) WriteFileError!void {
1349 const headers = args.headers_and_trailers[0..args.header_count];
1350 const trailers = args.headers_and_trailers[args.header_count..];
1351
1352 try self.writevAll(headers);
1353
1354 try in_file.reader().skipBytes(args.in_offset, .{ .buf_size = 4096 });
1355
1356 var fifo = std.fifo.LinearFifo(u8, .{ .Static = 4096 }).init();
1357 if (args.in_len) |len| {
1358 var stream = std.io.limitedReader(in_file.reader(), len);
1359 try fifo.pump(stream.reader(), self.writer());
1360 } else {
1361 try fifo.pump(in_file.reader(), self.writer());
1362 }
1363
1364 try self.writevAll(trailers);
1365 }
1366
1367 /// Low level function which can fail for OS-specific reasons.
1368 /// See `writeFileAll` as an alternative to calling this.
1369 /// TODO integrate with async I/O
1370 fn writeFileAllSendfile(self: File, in_file: File, args: WriteFileOptions) os.SendFileError!void {
1371 const count = blk: {
1372 if (args.in_len) |l| {
1373 if (l == 0) {
1374 return self.writevAll(args.headers_and_trailers);
1375 } else {
1376 break :blk l;
1377 }
1378 } else {
1379 break :blk 0;
1380 }
1381 };
1382 const headers = args.headers_and_trailers[0..args.header_count];
1383 const trailers = args.headers_and_trailers[args.header_count..];
1384 const zero_iovec = &[0]os.iovec_const{};
1385 // When reading the whole file, we cannot put the trailers in the sendfile() syscall,
1386 // because we have no way to determine whether a partial write is past the end of the file or not.
1387 const trls = if (count == 0) zero_iovec else trailers;
1388 const offset = args.in_offset;
1389 const out_fd = self.handle;
1390 const in_fd = in_file.handle;
1391 const flags = 0;
1392 var amt: usize = 0;
1393 hdrs: {
1394 var i: usize = 0;
1395 while (i < headers.len) {
1396 amt = try os.sendfile(out_fd, in_fd, offset, count, headers[i..], trls, flags);
1397 while (amt >= headers[i].iov_len) {
1398 amt -= headers[i].iov_len;
1399 i += 1;
1400 if (i >= headers.len) break :hdrs;
1401 }
1402 headers[i].iov_base += amt;
1403 headers[i].iov_len -= amt;
1404 }
1405 }
1406 if (count == 0) {
1407 var off: u64 = amt;
1408 while (true) {
1409 amt = try os.sendfile(out_fd, in_fd, offset + off, 0, zero_iovec, zero_iovec, flags);
1410 if (amt == 0) break;
1411 off += amt;
1412 }
1413 } else {
1414 var off: u64 = amt;
1415 while (off < count) {
1416 amt = try os.sendfile(out_fd, in_fd, offset + off, count - off, zero_iovec, trailers, flags);
1417 off += amt;
1418 }
1419 amt = @as(usize, @intCast(off - count));
1420 }
1421 var i: usize = 0;
1422 while (i < trailers.len) {
1423 while (amt >= trailers[i].iov_len) {
1424 amt -= trailers[i].iov_len;
1425 i += 1;
1426 if (i >= trailers.len) return;
1427 }
1428 trailers[i].iov_base += amt;
1429 trailers[i].iov_len -= amt;
1430 amt = try os.writev(self.handle, trailers[i..]);
1431 }
1432 }
1433
1434 pub const Reader = io.Reader(File, ReadError, read);
1435
1436 pub fn reader(file: File) Reader {
1437 return .{ .context = file };
1438 }
1439
1440 pub const Writer = io.Writer(File, WriteError, write);
1441
1442 pub fn writer(file: File) Writer {
1443 return .{ .context = file };
1444 }
1445
1446 pub const SeekableStream = io.SeekableStream(
1447 File,
1448 SeekError,
1449 GetSeekPosError,
1450 seekTo,
1451 seekBy,
1452 getPos,
1453 getEndPos,
1454 );
1455
1456 pub fn seekableStream(file: File) SeekableStream {
1457 return .{ .context = file };
1458 }
1459
1460 const range_off: windows.LARGE_INTEGER = 0;
1461 const range_len: windows.LARGE_INTEGER = 1;
1462
1463 pub const LockError = error{
1464 SystemResources,
1465 FileLocksNotSupported,
1466 } || os.UnexpectedError;
1467
1468 /// Blocks when an incompatible lock is held by another process.
1469 /// A process may hold only one type of lock (shared or exclusive) on
1470 /// a file. When a process terminates in any way, the lock is released.
1471 ///
1472 /// Assumes the file is unlocked.
1473 ///
1474 /// TODO: integrate with async I/O
1475 pub fn lock(file: File, l: Lock) LockError!void {
1476 if (is_windows) {
1477 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
1478 const exclusive = switch (l) {
1479 .none => return,
1480 .shared => false,
1481 .exclusive => true,
1482 };
1483 return windows.LockFile(
1484 file.handle,
1485 null,
1486 null,
1487 null,
1488 &io_status_block,
1489 &range_off,
1490 &range_len,
1491 null,
1492 windows.FALSE, // non-blocking=false
1493 @intFromBool(exclusive),
1494 ) catch |err| switch (err) {
1495 error.WouldBlock => unreachable, // non-blocking=false
1496 else => |e| return e,
1497 };
1498 } else {
1499 return os.flock(file.handle, switch (l) {
1500 .none => os.LOCK.UN,
1501 .shared => os.LOCK.SH,
1502 .exclusive => os.LOCK.EX,
1503 }) catch |err| switch (err) {
1504 error.WouldBlock => unreachable, // non-blocking=false
1505 else => |e| return e,
1506 };
1507 }
1508 }
1509
1510 /// Assumes the file is locked.
1511 pub fn unlock(file: File) void {
1512 if (is_windows) {
1513 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
1514 return windows.UnlockFile(
1515 file.handle,
1516 &io_status_block,
1517 &range_off,
1518 &range_len,
1519 null,
1520 ) catch |err| switch (err) {
1521 error.RangeNotLocked => unreachable, // Function assumes unlocked.
1522 error.Unexpected => unreachable, // Resource deallocation must succeed.
1523 };
1524 } else {
1525 return os.flock(file.handle, os.LOCK.UN) catch |err| switch (err) {
1526 error.WouldBlock => unreachable, // unlocking can't block
1527 error.SystemResources => unreachable, // We are deallocating resources.
1528 error.FileLocksNotSupported => unreachable, // We already got the lock.
1529 error.Unexpected => unreachable, // Resource deallocation must succeed.
1530 };
1531 }
1532 }
1533
1534 /// Attempts to obtain a lock, returning `true` if the lock is
1535 /// obtained, and `false` if there was an existing incompatible lock held.
1536 /// A process may hold only one type of lock (shared or exclusive) on
1537 /// a file. When a process terminates in any way, the lock is released.
1538 ///
1539 /// Assumes the file is unlocked.
1540 ///
1541 /// TODO: integrate with async I/O
1542 pub fn tryLock(file: File, l: Lock) LockError!bool {
1543 if (is_windows) {
1544 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
1545 const exclusive = switch (l) {
1546 .none => return,
1547 .shared => false,
1548 .exclusive => true,
1549 };
1550 windows.LockFile(
1551 file.handle,
1552 null,
1553 null,
1554 null,
1555 &io_status_block,
1556 &range_off,
1557 &range_len,
1558 null,
1559 windows.TRUE, // non-blocking=true
1560 @intFromBool(exclusive),
1561 ) catch |err| switch (err) {
1562 error.WouldBlock => return false,
1563 else => |e| return e,
1564 };
1565 } else {
1566 os.flock(file.handle, switch (l) {
1567 .none => os.LOCK.UN,
1568 .shared => os.LOCK.SH | os.LOCK.NB,
1569 .exclusive => os.LOCK.EX | os.LOCK.NB,
1570 }) catch |err| switch (err) {
1571 error.WouldBlock => return false,
1572 else => |e| return e,
1573 };
1574 }
1575 return true;
1576 }
1577
1578 /// Assumes the file is already locked in exclusive mode.
1579 /// Atomically modifies the lock to be in shared mode, without releasing it.
1580 ///
1581 /// TODO: integrate with async I/O
1582 pub fn downgradeLock(file: File) LockError!void {
1583 if (is_windows) {
1584 // On Windows it works like a semaphore + exclusivity flag. To implement this
1585 // function, we first obtain another lock in shared mode. This changes the
1586 // exclusivity flag, but increments the semaphore to 2. So we follow up with
1587 // an NtUnlockFile which decrements the semaphore but does not modify the
1588 // exclusivity flag.
1589 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
1590 windows.LockFile(
1591 file.handle,
1592 null,
1593 null,
1594 null,
1595 &io_status_block,
1596 &range_off,
1597 &range_len,
1598 null,
1599 windows.TRUE, // non-blocking=true
1600 windows.FALSE, // exclusive=false
1601 ) catch |err| switch (err) {
1602 error.WouldBlock => unreachable, // File was not locked in exclusive mode.
1603 else => |e| return e,
1604 };
1605 return windows.UnlockFile(
1606 file.handle,
1607 &io_status_block,
1608 &range_off,
1609 &range_len,
1610 null,
1611 ) catch |err| switch (err) {
1612 error.RangeNotLocked => unreachable, // File was not locked.
1613 error.Unexpected => unreachable, // Resource deallocation must succeed.
1614 };
1615 } else {
1616 return os.flock(file.handle, os.LOCK.SH | os.LOCK.NB) catch |err| switch (err) {
1617 error.WouldBlock => unreachable, // File was not locked in exclusive mode.
1618 else => |e| return e,
1619 };
1620 }
1621 }
1622};