diff options
Diffstat (limited to 'lib/std/fs/file.zig')
| -rw-r--r-- | lib/std/fs/file.zig | 394 |
1 files changed, 394 insertions, 0 deletions
diff --git a/lib/std/fs/file.zig b/lib/std/fs/file.zig new file mode 100644 index 0000000000..5ecad01026 --- /dev/null +++ b/lib/std/fs/file.zig @@ -0,0 +1,394 @@ +const std = @import("../std.zig"); +const builtin = @import("builtin"); +const os = std.os; +const io = std.io; +const mem = std.mem; +const math = std.math; +const assert = std.debug.assert; +const windows = os.windows; +const Os = builtin.Os; +const maxInt = std.math.maxInt; + +pub const File = struct { + /// The OS-specific file descriptor or file handle. + handle: os.fd_t, + + pub const Mode = switch (builtin.os) { + Os.windows => void, + else => u32, + }; + + pub const default_mode = switch (builtin.os) { + Os.windows => {}, + else => 0o666, + }; + + pub const OpenError = windows.CreateFileError || os.OpenError; + + /// Call close to clean up. + pub fn openRead(path: []const u8) OpenError!File { + if (windows.is_the_target) { + const path_w = try windows.sliceToPrefixedFileW(path); + return openReadW(&path_w); + } + const path_c = try os.toPosixPath(path); + return openReadC(&path_c); + } + + /// `openRead` except with a null terminated path + pub fn openReadC(path: [*]const u8) OpenError!File { + if (windows.is_the_target) { + const path_w = try windows.cStrToPrefixedFileW(path); + return openReadW(&path_w); + } + const flags = os.O_LARGEFILE | os.O_RDONLY; + const fd = try os.openC(path, flags, 0); + return openHandle(fd); + } + + /// `openRead` except with a null terminated UTF16LE encoded path + pub fn openReadW(path_w: [*]const u16) OpenError!File { + const handle = try windows.CreateFileW( + path_w, + windows.GENERIC_READ, + windows.FILE_SHARE_READ, + null, + windows.OPEN_EXISTING, + windows.FILE_ATTRIBUTE_NORMAL, + null, + ); + return openHandle(handle); + } + + /// Calls `openWriteMode` with `default_mode` for the mode. + pub fn openWrite(path: []const u8) OpenError!File { + return openWriteMode(path, default_mode); + } + + /// If the path does not exist it will be created. + /// If a file already exists in the destination it will be truncated. + /// Call close to clean up. + pub fn openWriteMode(path: []const u8, file_mode: Mode) OpenError!File { + if (windows.is_the_target) { + const path_w = try windows.sliceToPrefixedFileW(path); + return openWriteModeW(&path_w, file_mode); + } + const path_c = try os.toPosixPath(path); + return openWriteModeC(&path_c, file_mode); + } + + /// Same as `openWriteMode` except `path` is null-terminated. + pub fn openWriteModeC(path: [*]const u8, file_mode: Mode) OpenError!File { + if (windows.is_the_target) { + const path_w = try windows.cStrToPrefixedFileW(path); + return openWriteModeW(&path_w, file_mode); + } + const flags = os.O_LARGEFILE | os.O_WRONLY | os.O_CREAT | os.O_CLOEXEC | os.O_TRUNC; + const fd = try os.openC(path, flags, file_mode); + return openHandle(fd); + } + + /// Same as `openWriteMode` except `path` is null-terminated and UTF16LE encoded + pub fn openWriteModeW(path_w: [*]const u16, file_mode: Mode) OpenError!File { + const handle = try windows.CreateFileW( + path_w, + windows.GENERIC_WRITE, + windows.FILE_SHARE_WRITE | windows.FILE_SHARE_READ | windows.FILE_SHARE_DELETE, + null, + windows.CREATE_ALWAYS, + windows.FILE_ATTRIBUTE_NORMAL, + null, + ); + return openHandle(handle); + } + + /// If the path does not exist it will be created. + /// If a file already exists in the destination this returns OpenError.PathAlreadyExists + /// Call close to clean up. + pub fn openWriteNoClobber(path: []const u8, file_mode: Mode) OpenError!File { + if (windows.is_the_target) { + const path_w = try windows.sliceToPrefixedFileW(path); + return openWriteNoClobberW(&path_w, file_mode); + } + const path_c = try os.toPosixPath(path); + return openWriteNoClobberC(&path_c, file_mode); + } + + pub fn openWriteNoClobberC(path: [*]const u8, file_mode: Mode) OpenError!File { + if (windows.is_the_target) { + const path_w = try windows.cStrToPrefixedFileW(path); + return openWriteNoClobberW(&path_w, file_mode); + } + const flags = os.O_LARGEFILE | os.O_WRONLY | os.O_CREAT | os.O_CLOEXEC | os.O_EXCL; + const fd = try os.openC(path, flags, file_mode); + return openHandle(fd); + } + + pub fn openWriteNoClobberW(path_w: [*]const u16, file_mode: Mode) OpenError!File { + const handle = try windows.CreateFileW( + path_w, + windows.GENERIC_WRITE, + windows.FILE_SHARE_WRITE | windows.FILE_SHARE_READ | windows.FILE_SHARE_DELETE, + null, + windows.CREATE_NEW, + windows.FILE_ATTRIBUTE_NORMAL, + null, + ); + return openHandle(handle); + } + + pub fn openHandle(handle: os.fd_t) File { + return File{ .handle = handle }; + } + + /// Test for the existence of `path`. + /// `path` is UTF8-encoded. + /// In general it is recommended to avoid this function. For example, + /// instead of testing if a file exists and then opening it, just + /// open it and handle the error for file not found. + pub fn access(path: []const u8) !void { + return os.access(path, os.F_OK); + } + + /// Same as `access` except the parameter is null-terminated. + pub fn accessC(path: [*]const u8) !void { + return os.accessC(path, os.F_OK); + } + + /// Same as `access` except the parameter is null-terminated UTF16LE-encoded. + pub fn accessW(path: [*]const u16) !void { + return os.accessW(path, os.F_OK); + } + + /// Upon success, the stream is in an uninitialized state. To continue using it, + /// you must use the open() function. + pub fn close(self: File) void { + return os.close(self.handle); + } + + /// Test whether the file refers to a terminal. + /// See also `supportsAnsiEscapeCodes`. + pub fn isTty(self: File) bool { + return os.isatty(self.handle); + } + + /// Test whether ANSI escape codes will be treated as such. + pub fn supportsAnsiEscapeCodes(self: File) bool { + if (windows.is_the_target) { + return os.isCygwinPty(self.handle); + } + return self.isTty(); + } + + pub const SeekError = os.SeekError; + + /// Repositions read/write file offset relative to the current offset. + pub fn seekBy(self: File, offset: i64) SeekError!void { + return os.lseek_CUR(self.handle, offset); + } + + /// Repositions read/write file offset relative to the end. + pub fn seekFromEnd(self: File, offset: i64) SeekError!void { + return os.lseek_END(self.handle, offset); + } + + /// Repositions read/write file offset relative to the beginning. + pub fn seekTo(self: File, offset: u64) SeekError!void { + return os.lseek_SET(self.handle, offset); + } + + pub const GetPosError = os.SeekError || os.FStatError; + + pub fn getPos(self: File) GetPosError!u64 { + return os.lseek_CUR_get(self.handle); + } + + pub fn getEndPos(self: File) GetPosError!u64 { + if (windows.is_the_target) { + return windows.GetFileSizeEx(self.handle); + } + return (try self.stat()).size; + } + + pub const ModeError = os.FStatError; + + pub fn mode(self: File) ModeError!Mode { + if (windows.is_the_target) { + return {}; + } + return (try self.stat()).mode; + } + + pub const Stat = struct { + size: u64, + mode: Mode, + + /// access time in nanoseconds + atime: i64, + + /// last modification time in nanoseconds + mtime: i64, + + /// creation time in nanoseconds + ctime: i64, + }; + + pub const StatError = os.FStatError; + + pub fn stat(self: File) StatError!Stat { + if (windows.is_the_target) { + var io_status_block: windows.IO_STATUS_BLOCK = undefined; + var info: windows.FILE_ALL_INFORMATION = undefined; + const rc = windows.ntdll.NtQueryInformationFile(self.handle, &io_status_block, &info, @sizeOf(windows.FILE_ALL_INFORMATION), .FileAllInformation); + switch (rc) { + windows.STATUS.SUCCESS => {}, + windows.STATUS.BUFFER_OVERFLOW => {}, + else => return windows.unexpectedStatus(rc), + } + return Stat{ + .size = @bitCast(u64, info.StandardInformation.EndOfFile), + .mode = {}, + .atime = windows.fromSysTime(info.BasicInformation.LastAccessTime), + .mtime = windows.fromSysTime(info.BasicInformation.LastWriteTime), + .ctime = windows.fromSysTime(info.BasicInformation.CreationTime), + }; + } + + const st = try os.fstat(self.handle); + const atime = st.atime(); + const mtime = st.mtime(); + const ctime = st.ctime(); + return Stat{ + .size = @bitCast(u64, st.size), + .mode = st.mode, + .atime = i64(atime.tv_sec) * std.time.ns_per_s + atime.tv_nsec, + .mtime = i64(mtime.tv_sec) * std.time.ns_per_s + mtime.tv_nsec, + .ctime = i64(ctime.tv_sec) * std.time.ns_per_s + ctime.tv_nsec, + }; + } + + pub const UpdateTimesError = os.FutimensError || windows.SetFileTimeError; + + /// `atime`: access timestamp in nanoseconds + /// `mtime`: last modification timestamp in nanoseconds + pub fn updateTimes(self: File, atime: i64, mtime: i64) UpdateTimesError!void { + if (windows.is_the_target) { + const atime_ft = windows.nanoSecondsToFileTime(atime); + const mtime_ft = windows.nanoSecondsToFileTime(mtime); + return windows.SetFileTime(self.handle, null, &atime_ft, &mtime_ft); + } + const times = [2]os.timespec{ + os.timespec{ + .tv_sec = @divFloor(atime, std.time.ns_per_s), + .tv_nsec = @mod(atime, std.time.ns_per_s), + }, + os.timespec{ + .tv_sec = @divFloor(mtime, std.time.ns_per_s), + .tv_nsec = @mod(mtime, std.time.ns_per_s), + }, + }; + try os.futimens(self.handle, ×); + } + + pub const ReadError = os.ReadError; + + pub fn read(self: File, buffer: []u8) ReadError!usize { + return os.read(self.handle, buffer); + } + + pub const WriteError = os.WriteError; + + pub fn write(self: File, bytes: []const u8) WriteError!void { + return os.write(self.handle, bytes); + } + + pub fn writev_iovec(self: File, iovecs: []const os.iovec_const) WriteError!void { + if (std.event.Loop.instance) |loop| { + return std.event.fs.writevPosix(loop, self.handle, iovecs); + } else { + return os.writev(self.handle, iovecs); + } + } + + pub fn inStream(file: File) InStream { + return InStream{ + .file = file, + .stream = InStream.Stream{ .readFn = InStream.readFn }, + }; + } + + pub fn outStream(file: File) OutStream { + return OutStream{ + .file = file, + .stream = OutStream.Stream{ .writeFn = OutStream.writeFn }, + }; + } + + pub fn seekableStream(file: File) SeekableStream { + return SeekableStream{ + .file = file, + .stream = SeekableStream.Stream{ + .seekToFn = SeekableStream.seekToFn, + .seekByFn = SeekableStream.seekByFn, + .getPosFn = SeekableStream.getPosFn, + .getEndPosFn = SeekableStream.getEndPosFn, + }, + }; + } + + /// Implementation of io.InStream trait for File + pub const InStream = struct { + file: File, + stream: Stream, + + pub const Error = ReadError; + pub const Stream = io.InStream(Error); + + fn readFn(in_stream: *Stream, buffer: []u8) Error!usize { + const self = @fieldParentPtr(InStream, "stream", in_stream); + return self.file.read(buffer); + } + }; + + /// Implementation of io.OutStream trait for File + pub const OutStream = struct { + file: File, + stream: Stream, + + pub const Error = WriteError; + pub const Stream = io.OutStream(Error); + + fn writeFn(out_stream: *Stream, bytes: []const u8) Error!void { + const self = @fieldParentPtr(OutStream, "stream", out_stream); + return self.file.write(bytes); + } + }; + + /// Implementation of io.SeekableStream trait for File + pub const SeekableStream = struct { + file: File, + stream: Stream, + + pub const Stream = io.SeekableStream(SeekError, GetPosError); + + pub fn seekToFn(seekable_stream: *Stream, pos: u64) SeekError!void { + const self = @fieldParentPtr(SeekableStream, "stream", seekable_stream); + return self.file.seekTo(pos); + } + + pub fn seekByFn(seekable_stream: *Stream, amt: i64) SeekError!void { + const self = @fieldParentPtr(SeekableStream, "stream", seekable_stream); + return self.file.seekBy(amt); + } + + pub fn getEndPosFn(seekable_stream: *Stream) GetPosError!u64 { + const self = @fieldParentPtr(SeekableStream, "stream", seekable_stream); + return self.file.getEndPos(); + } + + pub fn getPosFn(seekable_stream: *Stream) GetPosError!u64 { + const self = @fieldParentPtr(SeekableStream, "stream", seekable_stream); + return self.file.getPos(); + } + }; +}; |
