| author | |
| committer | |
| log | 46aa416c48c283849059292267ac25a6d0db76d6 |
| tree | ce260c8f27835122f09c010387e7c59aabe3a59d |
| parent | 8c31eaf2a87d39fe2f9ed8f5af2a059048bfffb3 |
* move std.io.File to std.os.File
* add `zig fmt` to self hosted compiler
* introduce std.io.BufferedAtomicFile API
* introduce std.os.AtomicFile API
* add `std.os.default_file_mode`
* change FileMode on posix from being a usize to a u32
* add std.os.File.mode to return mode of an open file
* std.os.copyFile copies the mode from the source file instead of
using the default file mode for the dest file
* move `std.os.line_sep` to `std.cstr.line_sep`15 files changed, 543 insertions(+), 351 deletions(-)
CMakeLists.txt+2-1| ... | ... | @@ -437,11 +437,12 @@ set(ZIG_STD_FILES |
| 437 | 437 | "os/child_process.zig" |
| 438 | 438 | "os/darwin.zig" |
| 439 | 439 | "os/darwin_errno.zig" |
| 440 | "os/file.zig" | |
| 440 | 441 | "os/get_user_id.zig" |
| 441 | 442 | "os/index.zig" |
| 442 | "os/linux/index.zig" | |
| 443 | 443 | "os/linux/errno.zig" |
| 444 | 444 | "os/linux/i386.zig" |
| 445 | "os/linux/index.zig" | |
| 445 | 446 | "os/linux/x86_64.zig" |
| 446 | 447 | "os/path.zig" |
| 447 | 448 | "os/windows/error.zig" |
doc/docgen.zig+2-2| ... | ... | @@ -31,10 +31,10 @@ pub fn main() !void { |
| 31 | 31 | const out_file_name = try (args_it.next(allocator) ?? @panic("expected output arg")); |
| 32 | 32 | defer allocator.free(out_file_name); |
| 33 | 33 | |
| 34 | var in_file = try io.File.openRead(allocator, in_file_name); | |
| 34 | var in_file = try os.File.openRead(allocator, in_file_name); | |
| 35 | 35 | defer in_file.close(); |
| 36 | 36 | |
| 37 | var out_file = try io.File.openWrite(allocator, out_file_name); | |
| 37 | var out_file = try os.File.openWrite(allocator, out_file_name); | |
| 38 | 38 | defer out_file.close(); |
| 39 | 39 | |
| 40 | 40 | var file_in_stream = io.FileInStream.init(&in_file); |
example/cat/main.zig+2-2| ... | ... | @@ -20,7 +20,7 @@ pub fn main() !void { |
| 20 | 20 | } else if (arg[0] == '-') { |
| 21 | 21 | return usage(exe); |
| 22 | 22 | } else { |
| 23 | var file = io.File.openRead(allocator, arg) catch |err| { | |
| 23 | var file = os.File.openRead(allocator, arg) catch |err| { | |
| 24 | 24 | warn("Unable to open file: {}\n", @errorName(err)); |
| 25 | 25 | return err; |
| 26 | 26 | }; |
| ... | ... | @@ -41,7 +41,7 @@ fn usage(exe: []const u8) !void { |
| 41 | 41 | return error.Invalid; |
| 42 | 42 | } |
| 43 | 43 | |
| 44 | fn cat_file(stdout: &io.File, file: &io.File) !void { | |
| 44 | fn cat_file(stdout: &os.File, file: &os.File) !void { | |
| 45 | 45 | var buf: [1024 * 4]u8 = undefined; |
| 46 | 46 | |
| 47 | 47 | while (true) { |
src-self-hosted/main.zig+10-3| ... | ... | @@ -562,7 +562,7 @@ fn printZen() !void { |
| 562 | 562 | |
| 563 | 563 | fn fmtMain(allocator: &mem.Allocator, file_paths: []const []const u8) !void { |
| 564 | 564 | for (file_paths) |file_path| { |
| 565 | var file = try io.File.openRead(allocator, file_path); | |
| 565 | var file = try os.File.openRead(allocator, file_path); | |
| 566 | 566 | defer file.close(); |
| 567 | 567 | |
| 568 | 568 | const source_code = io.readFileAlloc(allocator, file_path) catch |err| { |
| ... | ... | @@ -574,7 +574,14 @@ fn fmtMain(allocator: &mem.Allocator, file_paths: []const []const u8) !void { |
| 574 | 574 | var tokenizer = std.zig.Tokenizer.init(source_code); |
| 575 | 575 | var parser = std.zig.Parser.init(&tokenizer, allocator, file_path); |
| 576 | 576 | defer parser.deinit(); |
| 577 | warn("opened {} (todo tokenize and parse and render)\n", file_path); | |
| 577 | ||
| 578 | const tree = try parser.parse(); | |
| 579 | defer tree.deinit(); | |
| 580 | ||
| 581 | const baf = try io.BufferedAtomicFile.create(allocator, file_path); | |
| 582 | defer baf.destroy(); | |
| 583 | ||
| 584 | try parser.renderSource(baf.stream(), tree.root_node); | |
| 578 | 585 | } |
| 579 | 586 | } |
| 580 | 587 | |
| ... | ... | @@ -602,7 +609,7 @@ fn testZigInstallPrefix(allocator: &mem.Allocator, test_path: []const u8) ![]u8 |
| 602 | 609 | const test_index_file = try os.path.join(allocator, test_zig_dir, "std", "index.zig"); |
| 603 | 610 | defer allocator.free(test_index_file); |
| 604 | 611 | |
| 605 | var file = try io.File.openRead(allocator, test_index_file); | |
| 612 | var file = try os.File.openRead(allocator, test_index_file); | |
| 606 | 613 | file.close(); |
| 607 | 614 | |
| 608 | 615 | return test_zig_dir; |
std/build.zig+9-6| ... | ... | @@ -624,10 +624,10 @@ pub const Builder = struct { |
| 624 | 624 | } |
| 625 | 625 | |
| 626 | 626 | fn copyFile(self: &Builder, source_path: []const u8, dest_path: []const u8) !void { |
| 627 | return self.copyFileMode(source_path, dest_path, 0o666); | |
| 627 | return self.copyFileMode(source_path, dest_path, os.default_file_mode); | |
| 628 | 628 | } |
| 629 | 629 | |
| 630 | fn copyFileMode(self: &Builder, source_path: []const u8, dest_path: []const u8, mode: usize) !void { | |
| 630 | fn copyFileMode(self: &Builder, source_path: []const u8, dest_path: []const u8, mode: os.FileMode) !void { | |
| 631 | 631 | if (self.verbose) { |
| 632 | 632 | warn("cp {} {}\n", source_path, dest_path); |
| 633 | 633 | } |
| ... | ... | @@ -1833,10 +1833,13 @@ const InstallArtifactStep = struct { |
| 1833 | 1833 | const self = @fieldParentPtr(Self, "step", step); |
| 1834 | 1834 | const builder = self.builder; |
| 1835 | 1835 | |
| 1836 | const mode = switch (self.artifact.kind) { | |
| 1837 | LibExeObjStep.Kind.Obj => unreachable, | |
| 1838 | LibExeObjStep.Kind.Exe => usize(0o755), | |
| 1839 | LibExeObjStep.Kind.Lib => if (self.artifact.static) usize(0o666) else usize(0o755), | |
| 1836 | const mode = switch (builtin.os) { | |
| 1837 | builtin.Os.windows => {}, | |
| 1838 | else => switch (self.artifact.kind) { | |
| 1839 | LibExeObjStep.Kind.Obj => unreachable, | |
| 1840 | LibExeObjStep.Kind.Exe => u32(0o755), | |
| 1841 | LibExeObjStep.Kind.Lib => if (self.artifact.static) u32(0o666) else u32(0o755), | |
| 1842 | }, | |
| 1840 | 1843 | }; |
| 1841 | 1844 | try builder.copyFileMode(self.artifact.getOutputPath(), self.dest_file, mode); |
| 1842 | 1845 | if (self.artifact.kind == LibExeObjStep.Kind.Lib and !self.artifact.static) { |
std/cstr.zig+7| ... | ... | @@ -1,8 +1,15 @@ |
| 1 | 1 | const std = @import("index.zig"); |
| 2 | const builtin = @import("builtin"); | |
| 2 | 3 | const debug = std.debug; |
| 3 | 4 | const mem = std.mem; |
| 4 | 5 | const assert = debug.assert; |
| 5 | 6 | |
| 7 | pub const line_sep = switch (builtin.os) { | |
| 8 | builtin.Os.windows => "\r\n", | |
| 9 | else => "\n", | |
| 10 | }; | |
| 11 | ||
| 12 | ||
| 6 | 13 | pub fn len(ptr: &const u8) usize { |
| 7 | 14 | var count: usize = 0; |
| 8 | 15 | while (ptr[count] != 0) : (count += 1) {} |
std/debug/index.zig+3-3| ... | ... | @@ -13,7 +13,7 @@ pub const FailingAllocator = @import("failing_allocator.zig").FailingAllocator; |
| 13 | 13 | /// Tries to write to stderr, unbuffered, and ignores any error returned. |
| 14 | 14 | /// Does not append a newline. |
| 15 | 15 | /// TODO atomic/multithread support |
| 16 | var stderr_file: io.File = undefined; | |
| 16 | var stderr_file: os.File = undefined; | |
| 17 | 17 | var stderr_file_out_stream: io.FileOutStream = undefined; |
| 18 | 18 | var stderr_stream: ?&io.OutStream(io.FileOutStream.Error) = null; |
| 19 | 19 | pub fn warn(comptime fmt: []const u8, args: ...) void { |
| ... | ... | @@ -265,7 +265,7 @@ pub fn openSelfDebugInfo(allocator: &mem.Allocator) !&ElfStackTrace { |
| 265 | 265 | } |
| 266 | 266 | |
| 267 | 267 | fn printLineFromFile(allocator: &mem.Allocator, out_stream: var, line_info: &const LineInfo) !void { |
| 268 | var f = try io.File.openRead(allocator, line_info.file_name); | |
| 268 | var f = try os.File.openRead(allocator, line_info.file_name); | |
| 269 | 269 | defer f.close(); |
| 270 | 270 | // TODO fstat and make sure that the file has the correct size |
| 271 | 271 | |
| ... | ... | @@ -298,7 +298,7 @@ fn printLineFromFile(allocator: &mem.Allocator, out_stream: var, line_info: &con |
| 298 | 298 | } |
| 299 | 299 | |
| 300 | 300 | pub const ElfStackTrace = struct { |
| 301 | self_exe_file: io.File, | |
| 301 | self_exe_file: os.File, | |
| 302 | 302 | elf: elf.Elf, |
| 303 | 303 | debug_info: &elf.SectionHeader, |
| 304 | 304 | debug_abbrev: &elf.SectionHeader, |
std/elf.zig+4-3| ... | ... | @@ -1,6 +1,7 @@ |
| 1 | 1 | const builtin = @import("builtin"); |
| 2 | 2 | const std = @import("index.zig"); |
| 3 | 3 | const io = std.io; |
| 4 | const os = std.os; | |
| 4 | 5 | const math = std.math; |
| 5 | 6 | const mem = std.mem; |
| 6 | 7 | const debug = std.debug; |
| ... | ... | @@ -63,7 +64,7 @@ pub const SectionHeader = struct { |
| 63 | 64 | }; |
| 64 | 65 | |
| 65 | 66 | pub const Elf = struct { |
| 66 | in_file: &io.File, | |
| 67 | in_file: &os.File, | |
| 67 | 68 | auto_close_stream: bool, |
| 68 | 69 | is_64: bool, |
| 69 | 70 | endian: builtin.Endian, |
| ... | ... | @@ -76,7 +77,7 @@ pub const Elf = struct { |
| 76 | 77 | string_section: &SectionHeader, |
| 77 | 78 | section_headers: []SectionHeader, |
| 78 | 79 | allocator: &mem.Allocator, |
| 79 | prealloc_file: io.File, | |
| 80 | prealloc_file: os.File, | |
| 80 | 81 | |
| 81 | 82 | /// Call close when done. |
| 82 | 83 | pub fn openPath(elf: &Elf, allocator: &mem.Allocator, path: []const u8) !void { |
| ... | ... | @@ -86,7 +87,7 @@ pub const Elf = struct { |
| 86 | 87 | } |
| 87 | 88 | |
| 88 | 89 | /// Call close when done. |
| 89 | pub fn openFile(elf: &Elf, allocator: &mem.Allocator, file: &io.File) !void { | |
| 90 | pub fn openFile(elf: &Elf, allocator: &mem.Allocator, file: &os.File) !void { | |
| 90 | 91 | elf.allocator = allocator; |
| 91 | 92 | elf.in_file = file; |
| 92 | 93 | elf.auto_close_stream = false; |
std/io.zig+62-276| ... | ... | @@ -1,12 +1,6 @@ |
| 1 | 1 | const std = @import("index.zig"); |
| 2 | 2 | const builtin = @import("builtin"); |
| 3 | 3 | const Os = builtin.Os; |
| 4 | const system = switch(builtin.os) { | |
| 5 | Os.linux => @import("os/linux/index.zig"), | |
| 6 | Os.macosx, Os.ios => @import("os/darwin.zig"), | |
| 7 | Os.windows => @import("os/windows/index.zig"), | |
| 8 | else => @compileError("Unsupported OS"), | |
| 9 | }; | |
| 10 | 4 | const c = std.c; |
| 11 | 5 | |
| 12 | 6 | const math = std.math; |
| ... | ... | @@ -16,23 +10,18 @@ const os = std.os; |
| 16 | 10 | const mem = std.mem; |
| 17 | 11 | const Buffer = std.Buffer; |
| 18 | 12 | const fmt = std.fmt; |
| 13 | const File = std.os.File; | |
| 19 | 14 | |
| 20 | 15 | const is_posix = builtin.os != builtin.Os.windows; |
| 21 | 16 | const is_windows = builtin.os == builtin.Os.windows; |
| 22 | 17 | |
| 23 | test "import io tests" { | |
| 24 | comptime { | |
| 25 | _ = @import("io_test.zig"); | |
| 26 | } | |
| 27 | } | |
| 28 | ||
| 29 | 18 | const GetStdIoErrs = os.WindowsGetStdHandleErrs; |
| 30 | 19 | |
| 31 | 20 | pub fn getStdErr() GetStdIoErrs!File { |
| 32 | 21 | const handle = if (is_windows) |
| 33 | try os.windowsGetStdHandle(system.STD_ERROR_HANDLE) | |
| 22 | try os.windowsGetStdHandle(os.windows.STD_ERROR_HANDLE) | |
| 34 | 23 | else if (is_posix) |
| 35 | system.STDERR_FILENO | |
| 24 | os.posix.STDERR_FILENO | |
| 36 | 25 | else |
| 37 | 26 | unreachable; |
| 38 | 27 | return File.openHandle(handle); |
| ... | ... | @@ -40,9 +29,9 @@ pub fn getStdErr() GetStdIoErrs!File { |
| 40 | 29 | |
| 41 | 30 | pub fn getStdOut() GetStdIoErrs!File { |
| 42 | 31 | const handle = if (is_windows) |
| 43 | try os.windowsGetStdHandle(system.STD_OUTPUT_HANDLE) | |
| 32 | try os.windowsGetStdHandle(os.windows.STD_OUTPUT_HANDLE) | |
| 44 | 33 | else if (is_posix) |
| 45 | system.STDOUT_FILENO | |
| 34 | os.posix.STDOUT_FILENO | |
| 46 | 35 | else |
| 47 | 36 | unreachable; |
| 48 | 37 | return File.openHandle(handle); |
| ... | ... | @@ -50,9 +39,9 @@ pub fn getStdOut() GetStdIoErrs!File { |
| 50 | 39 | |
| 51 | 40 | pub fn getStdIn() GetStdIoErrs!File { |
| 52 | 41 | const handle = if (is_windows) |
| 53 | try os.windowsGetStdHandle(system.STD_INPUT_HANDLE) | |
| 42 | try os.windowsGetStdHandle(os.windows.STD_INPUT_HANDLE) | |
| 54 | 43 | else if (is_posix) |
| 55 | system.STDIN_FILENO | |
| 44 | os.posix.STDIN_FILENO | |
| 56 | 45 | else |
| 57 | 46 | unreachable; |
| 58 | 47 | return File.openHandle(handle); |
| ... | ... | @@ -104,260 +93,10 @@ pub const FileOutStream = struct { |
| 104 | 93 | } |
| 105 | 94 | }; |
| 106 | 95 | |
| 107 | pub const File = struct { | |
| 108 | /// The OS-specific file descriptor or file handle. | |
| 109 | handle: os.FileHandle, | |
| 110 | ||
| 111 | const OpenError = os.WindowsOpenError || os.PosixOpenError; | |
| 112 | ||
| 113 | /// `path` needs to be copied in memory to add a null terminating byte, hence the allocator. | |
| 114 | /// Call close to clean up. | |
| 115 | pub fn openRead(allocator: &mem.Allocator, path: []const u8) OpenError!File { | |
| 116 | if (is_posix) { | |
| 117 | const flags = system.O_LARGEFILE|system.O_RDONLY; | |
| 118 | const fd = try os.posixOpen(allocator, path, flags, 0); | |
| 119 | return openHandle(fd); | |
| 120 | } else if (is_windows) { | |
| 121 | const handle = try os.windowsOpen(allocator, path, system.GENERIC_READ, system.FILE_SHARE_READ, | |
| 122 | system.OPEN_EXISTING, system.FILE_ATTRIBUTE_NORMAL); | |
| 123 | return openHandle(handle); | |
| 124 | } else { | |
| 125 | unreachable; | |
| 126 | } | |
| 127 | } | |
| 128 | ||
| 129 | /// Calls `openWriteMode` with 0o666 for the mode. | |
| 130 | pub fn openWrite(allocator: &mem.Allocator, path: []const u8) !File { | |
| 131 | return openWriteMode(allocator, path, 0o666); | |
| 132 | ||
| 133 | } | |
| 134 | ||
| 135 | /// `path` needs to be copied in memory to add a null terminating byte, hence the allocator. | |
| 136 | /// Call close to clean up. | |
| 137 | pub fn openWriteMode(allocator: &mem.Allocator, path: []const u8, mode: usize) !File { | |
| 138 | if (is_posix) { | |
| 139 | const flags = system.O_LARGEFILE|system.O_WRONLY|system.O_CREAT|system.O_CLOEXEC|system.O_TRUNC; | |
| 140 | const fd = try os.posixOpen(allocator, path, flags, mode); | |
| 141 | return openHandle(fd); | |
| 142 | } else if (is_windows) { | |
| 143 | const handle = try os.windowsOpen(allocator, path, system.GENERIC_WRITE, | |
| 144 | system.FILE_SHARE_WRITE|system.FILE_SHARE_READ|system.FILE_SHARE_DELETE, | |
| 145 | system.CREATE_ALWAYS, system.FILE_ATTRIBUTE_NORMAL); | |
| 146 | return openHandle(handle); | |
| 147 | } else { | |
| 148 | unreachable; | |
| 149 | } | |
| 150 | ||
| 151 | } | |
| 152 | ||
| 153 | pub fn openHandle(handle: os.FileHandle) File { | |
| 154 | return File { | |
| 155 | .handle = handle, | |
| 156 | }; | |
| 157 | } | |
| 158 | ||
| 159 | ||
| 160 | /// Upon success, the stream is in an uninitialized state. To continue using it, | |
| 161 | /// you must use the open() function. | |
| 162 | pub fn close(self: &File) void { | |
| 163 | os.close(self.handle); | |
| 164 | self.handle = undefined; | |
| 165 | } | |
| 166 | ||
| 167 | /// Calls `os.isTty` on `self.handle`. | |
| 168 | pub fn isTty(self: &File) bool { | |
| 169 | return os.isTty(self.handle); | |
| 170 | } | |
| 171 | ||
| 172 | pub fn seekForward(self: &File, amount: isize) !void { | |
| 173 | switch (builtin.os) { | |
| 174 | Os.linux, Os.macosx, Os.ios => { | |
| 175 | const result = system.lseek(self.handle, amount, system.SEEK_CUR); | |
| 176 | const err = system.getErrno(result); | |
| 177 | if (err > 0) { | |
| 178 | return switch (err) { | |
| 179 | system.EBADF => error.BadFd, | |
| 180 | system.EINVAL => error.Unseekable, | |
| 181 | system.EOVERFLOW => error.Unseekable, | |
| 182 | system.ESPIPE => error.Unseekable, | |
| 183 | system.ENXIO => error.Unseekable, | |
| 184 | else => os.unexpectedErrorPosix(err), | |
| 185 | }; | |
| 186 | } | |
| 187 | }, | |
| 188 | Os.windows => { | |
| 189 | if (system.SetFilePointerEx(self.handle, amount, null, system.FILE_CURRENT) == 0) { | |
| 190 | const err = system.GetLastError(); | |
| 191 | return switch (err) { | |
| 192 | system.ERROR.INVALID_PARAMETER => error.BadFd, | |
| 193 | else => os.unexpectedErrorWindows(err), | |
| 194 | }; | |
| 195 | } | |
| 196 | }, | |
| 197 | else => @compileError("unsupported OS"), | |
| 198 | } | |
| 199 | } | |
| 200 | ||
| 201 | pub fn seekTo(self: &File, pos: usize) !void { | |
| 202 | switch (builtin.os) { | |
| 203 | Os.linux, Os.macosx, Os.ios => { | |
| 204 | const ipos = try math.cast(isize, pos); | |
| 205 | const result = system.lseek(self.handle, ipos, system.SEEK_SET); | |
| 206 | const err = system.getErrno(result); | |
| 207 | if (err > 0) { | |
| 208 | return switch (err) { | |
| 209 | system.EBADF => error.BadFd, | |
| 210 | system.EINVAL => error.Unseekable, | |
| 211 | system.EOVERFLOW => error.Unseekable, | |
| 212 | system.ESPIPE => error.Unseekable, | |
| 213 | system.ENXIO => error.Unseekable, | |
| 214 | else => os.unexpectedErrorPosix(err), | |
| 215 | }; | |
| 216 | } | |
| 217 | }, | |
| 218 | Os.windows => { | |
| 219 | const ipos = try math.cast(isize, pos); | |
| 220 | if (system.SetFilePointerEx(self.handle, ipos, null, system.FILE_BEGIN) == 0) { | |
| 221 | const err = system.GetLastError(); | |
| 222 | return switch (err) { | |
| 223 | system.ERROR.INVALID_PARAMETER => error.BadFd, | |
| 224 | else => os.unexpectedErrorWindows(err), | |
| 225 | }; | |
| 226 | } | |
| 227 | }, | |
| 228 | else => @compileError("unsupported OS: " ++ @tagName(builtin.os)), | |
| 229 | } | |
| 230 | } | |
| 231 | ||
| 232 | pub fn getPos(self: &File) !usize { | |
| 233 | switch (builtin.os) { | |
| 234 | Os.linux, Os.macosx, Os.ios => { | |
| 235 | const result = system.lseek(self.handle, 0, system.SEEK_CUR); | |
| 236 | const err = system.getErrno(result); | |
| 237 | if (err > 0) { | |
| 238 | return switch (err) { | |
| 239 | system.EBADF => error.BadFd, | |
| 240 | system.EINVAL => error.Unseekable, | |
| 241 | system.EOVERFLOW => error.Unseekable, | |
| 242 | system.ESPIPE => error.Unseekable, | |
| 243 | system.ENXIO => error.Unseekable, | |
| 244 | else => os.unexpectedErrorPosix(err), | |
| 245 | }; | |
| 246 | } | |
| 247 | return result; | |
| 248 | }, | |
| 249 | Os.windows => { | |
| 250 | var pos : system.LARGE_INTEGER = undefined; | |
| 251 | if (system.SetFilePointerEx(self.handle, 0, &pos, system.FILE_CURRENT) == 0) { | |
| 252 | const err = system.GetLastError(); | |
| 253 | return switch (err) { | |
| 254 | system.ERROR.INVALID_PARAMETER => error.BadFd, | |
| 255 | else => os.unexpectedErrorWindows(err), | |
| 256 | }; | |
| 257 | } | |
| 258 | ||
| 259 | assert(pos >= 0); | |
| 260 | if (@sizeOf(@typeOf(pos)) > @sizeOf(usize)) { | |
| 261 | if (pos > @maxValue(usize)) { | |
| 262 | return error.FilePosLargerThanPointerRange; | |
| 263 | } | |
| 264 | } | |
| 265 | ||
| 266 | return usize(pos); | |
| 267 | }, | |
| 268 | else => @compileError("unsupported OS"), | |
| 269 | } | |
| 270 | } | |
| 271 | ||
| 272 | pub fn getEndPos(self: &File) !usize { | |
| 273 | if (is_posix) { | |
| 274 | var stat: system.Stat = undefined; | |
| 275 | const err = system.getErrno(system.fstat(self.handle, &stat)); | |
| 276 | if (err > 0) { | |
| 277 | return switch (err) { | |
| 278 | system.EBADF => error.BadFd, | |
| 279 | system.ENOMEM => error.SystemResources, | |
| 280 | else => os.unexpectedErrorPosix(err), | |
| 281 | }; | |
| 282 | } | |
| 283 | ||
| 284 | return usize(stat.size); | |
| 285 | } else if (is_windows) { | |
| 286 | var file_size: system.LARGE_INTEGER = undefined; | |
| 287 | if (system.GetFileSizeEx(self.handle, &file_size) == 0) { | |
| 288 | const err = system.GetLastError(); | |
| 289 | return switch (err) { | |
| 290 | else => os.unexpectedErrorWindows(err), | |
| 291 | }; | |
| 292 | } | |
| 293 | if (file_size < 0) | |
| 294 | return error.Overflow; | |
| 295 | return math.cast(usize, u64(file_size)); | |
| 296 | } else { | |
| 297 | unreachable; | |
| 298 | } | |
| 299 | } | |
| 300 | ||
| 301 | pub const ReadError = error {}; | |
| 302 | ||
| 303 | pub fn read(self: &File, buffer: []u8) !usize { | |
| 304 | if (is_posix) { | |
| 305 | var index: usize = 0; | |
| 306 | while (index < buffer.len) { | |
| 307 | const amt_read = system.read(self.handle, &buffer[index], buffer.len - index); | |
| 308 | const read_err = system.getErrno(amt_read); | |
| 309 | if (read_err > 0) { | |
| 310 | switch (read_err) { | |
| 311 | system.EINTR => continue, | |
| 312 | system.EINVAL => unreachable, | |
| 313 | system.EFAULT => unreachable, | |
| 314 | system.EBADF => return error.BadFd, | |
| 315 | system.EIO => return error.Io, | |
| 316 | else => return os.unexpectedErrorPosix(read_err), | |
| 317 | } | |
| 318 | } | |
| 319 | if (amt_read == 0) return index; | |
| 320 | index += amt_read; | |
| 321 | } | |
| 322 | return index; | |
| 323 | } else if (is_windows) { | |
| 324 | var index: usize = 0; | |
| 325 | while (index < buffer.len) { | |
| 326 | const want_read_count = system.DWORD(math.min(system.DWORD(@maxValue(system.DWORD)), buffer.len - index)); | |
| 327 | var amt_read: system.DWORD = undefined; | |
| 328 | if (system.ReadFile(self.handle, @ptrCast(&c_void, &buffer[index]), want_read_count, &amt_read, null) == 0) { | |
| 329 | const err = system.GetLastError(); | |
| 330 | return switch (err) { | |
| 331 | system.ERROR.OPERATION_ABORTED => continue, | |
| 332 | system.ERROR.BROKEN_PIPE => return index, | |
| 333 | else => os.unexpectedErrorWindows(err), | |
| 334 | }; | |
| 335 | } | |
| 336 | if (amt_read == 0) return index; | |
| 337 | index += amt_read; | |
| 338 | } | |
| 339 | return index; | |
| 340 | } else { | |
| 341 | unreachable; | |
| 342 | } | |
| 343 | } | |
| 344 | ||
| 345 | pub const WriteError = os.WindowsWriteError || os.PosixWriteError; | |
| 346 | ||
| 347 | fn write(self: &File, bytes: []const u8) WriteError!void { | |
| 348 | if (is_posix) { | |
| 349 | try os.posixWrite(self.handle, bytes); | |
| 350 | } else if (is_windows) { | |
| 351 | try os.windowsWrite(self.handle, bytes); | |
| 352 | } else { | |
| 353 | @compileError("Unsupported OS"); | |
| 354 | } | |
| 355 | } | |
| 356 | }; | |
| 357 | ||
| 358 | pub fn InStream(comptime Error: type) type { | |
| 96 | pub fn InStream(comptime ReadError: type) type { | |
| 359 | 97 | return struct { |
| 360 | 98 | const Self = this; |
| 99 | pub const Error = ReadError; | |
| 361 | 100 | |
| 362 | 101 | /// Return the number of bytes read. If the number read is smaller than buf.len, it |
| 363 | 102 | /// means the stream reached the end. Reaching the end of a stream is not an error |
| ... | ... | @@ -486,9 +225,10 @@ pub fn InStream(comptime Error: type) type { |
| 486 | 225 | }; |
| 487 | 226 | } |
| 488 | 227 | |
| 489 | pub fn OutStream(comptime Error: type) type { | |
| 228 | pub fn OutStream(comptime WriteError: type) type { | |
| 490 | 229 | return struct { |
| 491 | 230 | const Self = this; |
| 231 | pub const Error = WriteError; | |
| 492 | 232 | |
| 493 | 233 | writeFn: fn(self: &Self, bytes: []const u8) Error!void, |
| 494 | 234 | |
| ... | ... | @@ -614,10 +354,11 @@ pub fn BufferedOutStream(comptime Error: type) type { |
| 614 | 354 | return BufferedOutStreamCustom(os.page_size, Error); |
| 615 | 355 | } |
| 616 | 356 | |
| 617 | pub fn BufferedOutStreamCustom(comptime buffer_size: usize, comptime Error: type) type { | |
| 357 | pub fn BufferedOutStreamCustom(comptime buffer_size: usize, comptime OutStreamError: type) type { | |
| 618 | 358 | return struct { |
| 619 | 359 | const Self = this; |
| 620 | const Stream = OutStream(Error); | |
| 360 | pub const Stream = OutStream(Error); | |
| 361 | pub const Error = OutStreamError; | |
| 621 | 362 | |
| 622 | 363 | pub stream: Stream, |
| 623 | 364 | |
| ... | ... | @@ -638,9 +379,6 @@ pub fn BufferedOutStreamCustom(comptime buffer_size: usize, comptime Error: type |
| 638 | 379 | } |
| 639 | 380 | |
| 640 | 381 | pub fn flush(self: &Self) !void { |
| 641 | if (self.index == 0) | |
| 642 | return; | |
| 643 | ||
| 644 | 382 | try self.unbuffered_out_stream.write(self.buffer[0..self.index]); |
| 645 | 383 | self.index = 0; |
| 646 | 384 | } |
| ... | ... | @@ -692,3 +430,51 @@ pub const BufferOutStream = struct { |
| 692 | 430 | } |
| 693 | 431 | }; |
| 694 | 432 | |
| 433 | ||
| 434 | pub const BufferedAtomicFile = struct { | |
| 435 | atomic_file: os.AtomicFile, | |
| 436 | file_stream: FileOutStream, | |
| 437 | buffered_stream: BufferedOutStream(FileOutStream.Error), | |
| 438 | ||
| 439 | pub fn create(allocator: &mem.Allocator, dest_path: []const u8) !&BufferedAtomicFile { | |
| 440 | // TODO with well defined copy elision we don't need this allocation | |
| 441 | var self = try allocator.create(BufferedAtomicFile); | |
| 442 | errdefer allocator.destroy(self); | |
| 443 | ||
| 444 | *self = BufferedAtomicFile { | |
| 445 | .atomic_file = undefined, | |
| 446 | .file_stream = undefined, | |
| 447 | .buffered_stream = undefined, | |
| 448 | }; | |
| 449 | ||
| 450 | self.atomic_file = try os.AtomicFile.init(allocator, dest_path, os.default_file_mode); | |
| 451 | errdefer self.atomic_file.deinit(); | |
| 452 | ||
| 453 | self.file_stream = FileOutStream.init(&self.atomic_file.file); | |
| 454 | self.buffered_stream = BufferedOutStream(FileOutStream.Error).init(&self.file_stream.stream); | |
| 455 | return self; | |
| 456 | } | |
| 457 | ||
| 458 | /// always call destroy, even after successful finish() | |
| 459 | pub fn destroy(self: &BufferedAtomicFile) void { | |
| 460 | const allocator = self.atomic_file.allocator; | |
| 461 | self.atomic_file.deinit(); | |
| 462 | allocator.destroy(self); | |
| 463 | } | |
| 464 | ||
| 465 | pub fn finish(self: &BufferedAtomicFile) !void { | |
| 466 | try self.buffered_stream.flush(); | |
| 467 | try self.atomic_file.finish(); | |
| 468 | } | |
| 469 | ||
| 470 | pub fn stream(self: &BufferedAtomicFile) &OutStream(FileOutStream.Error) { | |
| 471 | return &self.buffered_stream.stream; | |
| 472 | } | |
| 473 | }; | |
| 474 | ||
| 475 | test "import io tests" { | |
| 476 | comptime { | |
| 477 | _ = @import("io_test.zig"); | |
| 478 | } | |
| 479 | } | |
| 480 |
std/io_test.zig+2-2| ... | ... | @@ -13,7 +13,7 @@ test "write a file, read it, then delete it" { |
| 13 | 13 | rng.fillBytes(data[0..]); |
| 14 | 14 | const tmp_file_name = "temp_test_file.txt"; |
| 15 | 15 | { |
| 16 | var file = try io.File.openWrite(allocator, tmp_file_name); | |
| 16 | var file = try os.File.openWrite(allocator, tmp_file_name); | |
| 17 | 17 | defer file.close(); |
| 18 | 18 | |
| 19 | 19 | var file_out_stream = io.FileOutStream.init(&file); |
| ... | ... | @@ -25,7 +25,7 @@ test "write a file, read it, then delete it" { |
| 25 | 25 | try buf_stream.flush(); |
| 26 | 26 | } |
| 27 | 27 | { |
| 28 | var file = try io.File.openRead(allocator, tmp_file_name); | |
| 28 | var file = try os.File.openRead(allocator, tmp_file_name); | |
| 29 | 29 | defer file.close(); |
| 30 | 30 | |
| 31 | 31 | const file_size = try file.getEndPos(); |
std/os/child_process.zig+9-9| ... | ... | @@ -24,9 +24,9 @@ pub const ChildProcess = struct { |
| 24 | 24 | |
| 25 | 25 | pub allocator: &mem.Allocator, |
| 26 | 26 | |
| 27 | pub stdin: ?io.File, | |
| 28 | pub stdout: ?io.File, | |
| 29 | pub stderr: ?io.File, | |
| 27 | pub stdin: ?os.File, | |
| 28 | pub stdout: ?os.File, | |
| 29 | pub stderr: ?os.File, | |
| 30 | 30 | |
| 31 | 31 | pub term: ?(SpawnError!Term), |
| 32 | 32 | |
| ... | ... | @@ -428,17 +428,17 @@ pub const ChildProcess = struct { |
| 428 | 428 | // we are the parent |
| 429 | 429 | const pid = i32(pid_result); |
| 430 | 430 | if (self.stdin_behavior == StdIo.Pipe) { |
| 431 | self.stdin = io.File.openHandle(stdin_pipe[1]); | |
| 431 | self.stdin = os.File.openHandle(stdin_pipe[1]); | |
| 432 | 432 | } else { |
| 433 | 433 | self.stdin = null; |
| 434 | 434 | } |
| 435 | 435 | if (self.stdout_behavior == StdIo.Pipe) { |
| 436 | self.stdout = io.File.openHandle(stdout_pipe[0]); | |
| 436 | self.stdout = os.File.openHandle(stdout_pipe[0]); | |
| 437 | 437 | } else { |
| 438 | 438 | self.stdout = null; |
| 439 | 439 | } |
| 440 | 440 | if (self.stderr_behavior == StdIo.Pipe) { |
| 441 | self.stderr = io.File.openHandle(stderr_pipe[0]); | |
| 441 | self.stderr = os.File.openHandle(stderr_pipe[0]); | |
| 442 | 442 | } else { |
| 443 | 443 | self.stderr = null; |
| 444 | 444 | } |
| ... | ... | @@ -620,17 +620,17 @@ pub const ChildProcess = struct { |
| 620 | 620 | }; |
| 621 | 621 | |
| 622 | 622 | if (g_hChildStd_IN_Wr) |h| { |
| 623 | self.stdin = io.File.openHandle(h); | |
| 623 | self.stdin = os.File.openHandle(h); | |
| 624 | 624 | } else { |
| 625 | 625 | self.stdin = null; |
| 626 | 626 | } |
| 627 | 627 | if (g_hChildStd_OUT_Rd) |h| { |
| 628 | self.stdout = io.File.openHandle(h); | |
| 628 | self.stdout = os.File.openHandle(h); | |
| 629 | 629 | } else { |
| 630 | 630 | self.stdout = null; |
| 631 | 631 | } |
| 632 | 632 | if (g_hChildStd_ERR_Rd) |h| { |
| 633 | self.stderr = io.File.openHandle(h); | |
| 633 | self.stderr = os.File.openHandle(h); | |
| 634 | 634 | } else { |
| 635 | 635 | self.stderr = null; |
| 636 | 636 | } |
std/os/file.zig created+311| ... | ... | @@ -0,0 +1,311 @@ |
| 1 | const std = @import("../index.zig"); | |
| 2 | const builtin = @import("builtin"); | |
| 3 | const os = std.os; | |
| 4 | const mem = std.mem; | |
| 5 | const math = std.math; | |
| 6 | const assert = std.debug.assert; | |
| 7 | const posix = os.posix; | |
| 8 | const windows = os.windows; | |
| 9 | const Os = builtin.Os; | |
| 10 | ||
| 11 | const is_posix = builtin.os != builtin.Os.windows; | |
| 12 | const is_windows = builtin.os == builtin.Os.windows; | |
| 13 | ||
| 14 | pub const File = struct { | |
| 15 | /// The OS-specific file descriptor or file handle. | |
| 16 | handle: os.FileHandle, | |
| 17 | ||
| 18 | const OpenError = os.WindowsOpenError || os.PosixOpenError; | |
| 19 | ||
| 20 | /// `path` needs to be copied in memory to add a null terminating byte, hence the allocator. | |
| 21 | /// Call close to clean up. | |
| 22 | pub fn openRead(allocator: &mem.Allocator, path: []const u8) OpenError!File { | |
| 23 | if (is_posix) { | |
| 24 | const flags = posix.O_LARGEFILE|posix.O_RDONLY; | |
| 25 | const fd = try os.posixOpen(allocator, path, flags, 0); | |
| 26 | return openHandle(fd); | |
| 27 | } else if (is_windows) { | |
| 28 | const handle = try os.windowsOpen(allocator, path, windows.GENERIC_READ, windows.FILE_SHARE_READ, | |
| 29 | windows.OPEN_EXISTING, windows.FILE_ATTRIBUTE_NORMAL); | |
| 30 | return openHandle(handle); | |
| 31 | } else { | |
| 32 | @compileError("TODO implement openRead for this OS"); | |
| 33 | } | |
| 34 | } | |
| 35 | ||
| 36 | /// Calls `openWriteMode` with os.default_file_mode for the mode. | |
| 37 | pub fn openWrite(allocator: &mem.Allocator, path: []const u8) OpenError!File { | |
| 38 | return openWriteMode(allocator, path, os.default_file_mode); | |
| 39 | ||
| 40 | } | |
| 41 | ||
| 42 | /// If the path does not exist it will be created. | |
| 43 | /// If a file already exists in the destination it will be truncated. | |
| 44 | /// `path` needs to be copied in memory to add a null terminating byte, hence the allocator. | |
| 45 | /// Call close to clean up. | |
| 46 | pub fn openWriteMode(allocator: &mem.Allocator, path: []const u8, file_mode: os.FileMode) OpenError!File { | |
| 47 | if (is_posix) { | |
| 48 | const flags = posix.O_LARGEFILE|posix.O_WRONLY|posix.O_CREAT|posix.O_CLOEXEC|posix.O_TRUNC; | |
| 49 | const fd = try os.posixOpen(allocator, path, flags, file_mode); | |
| 50 | return openHandle(fd); | |
| 51 | } else if (is_windows) { | |
| 52 | const handle = try os.windowsOpen(allocator, path, windows.GENERIC_WRITE, | |
| 53 | windows.FILE_SHARE_WRITE|windows.FILE_SHARE_READ|windows.FILE_SHARE_DELETE, | |
| 54 | windows.CREATE_ALWAYS, windows.FILE_ATTRIBUTE_NORMAL); | |
| 55 | return openHandle(handle); | |
| 56 | } else { | |
| 57 | @compileError("TODO implement openWriteMode for this OS"); | |
| 58 | } | |
| 59 | ||
| 60 | } | |
| 61 | ||
| 62 | /// If the path does not exist it will be created. | |
| 63 | /// If a file already exists in the destination this returns OpenError.PathAlreadyExists | |
| 64 | /// `path` needs to be copied in memory to add a null terminating byte, hence the allocator. | |
| 65 | /// Call close to clean up. | |
| 66 | pub fn openWriteNoClobber(allocator: &mem.Allocator, path: []const u8, file_mode: os.FileMode) OpenError!File { | |
| 67 | if (is_posix) { | |
| 68 | const flags = posix.O_LARGEFILE|posix.O_WRONLY|posix.O_CREAT|posix.O_CLOEXEC|posix.O_EXCL; | |
| 69 | const fd = try os.posixOpen(allocator, path, flags, file_mode); | |
| 70 | return openHandle(fd); | |
| 71 | } else if (is_windows) { | |
| 72 | const handle = try os.windowsOpen(allocator, path, windows.GENERIC_WRITE, | |
| 73 | windows.FILE_SHARE_WRITE|windows.FILE_SHARE_READ|windows.FILE_SHARE_DELETE, | |
| 74 | windows.CREATE_NEW, windows.FILE_ATTRIBUTE_NORMAL); | |
| 75 | return openHandle(handle); | |
| 76 | } else { | |
| 77 | @compileError("TODO implement openWriteMode for this OS"); | |
| 78 | } | |
| 79 | ||
| 80 | } | |
| 81 | ||
| 82 | pub fn openHandle(handle: os.FileHandle) File { | |
| 83 | return File { | |
| 84 | .handle = handle, | |
| 85 | }; | |
| 86 | } | |
| 87 | ||
| 88 | ||
| 89 | /// Upon success, the stream is in an uninitialized state. To continue using it, | |
| 90 | /// you must use the open() function. | |
| 91 | pub fn close(self: &File) void { | |
| 92 | os.close(self.handle); | |
| 93 | self.handle = undefined; | |
| 94 | } | |
| 95 | ||
| 96 | /// Calls `os.isTty` on `self.handle`. | |
| 97 | pub fn isTty(self: &File) bool { | |
| 98 | return os.isTty(self.handle); | |
| 99 | } | |
| 100 | ||
| 101 | pub fn seekForward(self: &File, amount: isize) !void { | |
| 102 | switch (builtin.os) { | |
| 103 | Os.linux, Os.macosx, Os.ios => { | |
| 104 | const result = posix.lseek(self.handle, amount, posix.SEEK_CUR); | |
| 105 | const err = posix.getErrno(result); | |
| 106 | if (err > 0) { | |
| 107 | return switch (err) { | |
| 108 | posix.EBADF => error.BadFd, | |
| 109 | posix.EINVAL => error.Unseekable, | |
| 110 | posix.EOVERFLOW => error.Unseekable, | |
| 111 | posix.ESPIPE => error.Unseekable, | |
| 112 | posix.ENXIO => error.Unseekable, | |
| 113 | else => os.unexpectedErrorPosix(err), | |
| 114 | }; | |
| 115 | } | |
| 116 | }, | |
| 117 | Os.windows => { | |
| 118 | if (windows.SetFilePointerEx(self.handle, amount, null, windows.FILE_CURRENT) == 0) { | |
| 119 | const err = windows.GetLastError(); | |
| 120 | return switch (err) { | |
| 121 | windows.ERROR.INVALID_PARAMETER => error.BadFd, | |
| 122 | else => os.unexpectedErrorWindows(err), | |
| 123 | }; | |
| 124 | } | |
| 125 | }, | |
| 126 | else => @compileError("unsupported OS"), | |
| 127 | } | |
| 128 | } | |
| 129 | ||
| 130 | pub fn seekTo(self: &File, pos: usize) !void { | |
| 131 | switch (builtin.os) { | |
| 132 | Os.linux, Os.macosx, Os.ios => { | |
| 133 | const ipos = try math.cast(isize, pos); | |
| 134 | const result = posix.lseek(self.handle, ipos, posix.SEEK_SET); | |
| 135 | const err = posix.getErrno(result); | |
| 136 | if (err > 0) { | |
| 137 | return switch (err) { | |
| 138 | posix.EBADF => error.BadFd, | |
| 139 | posix.EINVAL => error.Unseekable, | |
| 140 | posix.EOVERFLOW => error.Unseekable, | |
| 141 | posix.ESPIPE => error.Unseekable, | |
| 142 | posix.ENXIO => error.Unseekable, | |
| 143 | else => os.unexpectedErrorPosix(err), | |
| 144 | }; | |
| 145 | } | |
| 146 | }, | |
| 147 | Os.windows => { | |
| 148 | const ipos = try math.cast(isize, pos); | |
| 149 | if (windows.SetFilePointerEx(self.handle, ipos, null, windows.FILE_BEGIN) == 0) { | |
| 150 | const err = windows.GetLastError(); | |
| 151 | return switch (err) { | |
| 152 | windows.ERROR.INVALID_PARAMETER => error.BadFd, | |
| 153 | else => os.unexpectedErrorWindows(err), | |
| 154 | }; | |
| 155 | } | |
| 156 | }, | |
| 157 | else => @compileError("unsupported OS: " ++ @tagName(builtin.os)), | |
| 158 | } | |
| 159 | } | |
| 160 | ||
| 161 | pub fn getPos(self: &File) !usize { | |
| 162 | switch (builtin.os) { | |
| 163 | Os.linux, Os.macosx, Os.ios => { | |
| 164 | const result = posix.lseek(self.handle, 0, posix.SEEK_CUR); | |
| 165 | const err = posix.getErrno(result); | |
| 166 | if (err > 0) { | |
| 167 | return switch (err) { | |
| 168 | posix.EBADF => error.BadFd, | |
| 169 | posix.EINVAL => error.Unseekable, | |
| 170 | posix.EOVERFLOW => error.Unseekable, | |
| 171 | posix.ESPIPE => error.Unseekable, | |
| 172 | posix.ENXIO => error.Unseekable, | |
| 173 | else => os.unexpectedErrorPosix(err), | |
| 174 | }; | |
| 175 | } | |
| 176 | return result; | |
| 177 | }, | |
| 178 | Os.windows => { | |
| 179 | var pos : windows.LARGE_INTEGER = undefined; | |
| 180 | if (windows.SetFilePointerEx(self.handle, 0, &pos, windows.FILE_CURRENT) == 0) { | |
| 181 | const err = windows.GetLastError(); | |
| 182 | return switch (err) { | |
| 183 | windows.ERROR.INVALID_PARAMETER => error.BadFd, | |
| 184 | else => os.unexpectedErrorWindows(err), | |
| 185 | }; | |
| 186 | } | |
| 187 | ||
| 188 | assert(pos >= 0); | |
| 189 | if (@sizeOf(@typeOf(pos)) > @sizeOf(usize)) { | |
| 190 | if (pos > @maxValue(usize)) { | |
| 191 | return error.FilePosLargerThanPointerRange; | |
| 192 | } | |
| 193 | } | |
| 194 | ||
| 195 | return usize(pos); | |
| 196 | }, | |
| 197 | else => @compileError("unsupported OS"), | |
| 198 | } | |
| 199 | } | |
| 200 | ||
| 201 | pub fn getEndPos(self: &File) !usize { | |
| 202 | if (is_posix) { | |
| 203 | var stat: posix.Stat = undefined; | |
| 204 | const err = posix.getErrno(posix.fstat(self.handle, &stat)); | |
| 205 | if (err > 0) { | |
| 206 | return switch (err) { | |
| 207 | posix.EBADF => error.BadFd, | |
| 208 | posix.ENOMEM => error.SystemResources, | |
| 209 | else => os.unexpectedErrorPosix(err), | |
| 210 | }; | |
| 211 | } | |
| 212 | ||
| 213 | return usize(stat.size); | |
| 214 | } else if (is_windows) { | |
| 215 | var file_size: windows.LARGE_INTEGER = undefined; | |
| 216 | if (windows.GetFileSizeEx(self.handle, &file_size) == 0) { | |
| 217 | const err = windows.GetLastError(); | |
| 218 | return switch (err) { | |
| 219 | else => os.unexpectedErrorWindows(err), | |
| 220 | }; | |
| 221 | } | |
| 222 | if (file_size < 0) | |
| 223 | return error.Overflow; | |
| 224 | return math.cast(usize, u64(file_size)); | |
| 225 | } else { | |
| 226 | @compileError("TODO support getEndPos on this OS"); | |
| 227 | } | |
| 228 | } | |
| 229 | ||
| 230 | pub const ModeError = error { | |
| 231 | BadFd, | |
| 232 | SystemResources, | |
| 233 | Unexpected, | |
| 234 | }; | |
| 235 | ||
| 236 | fn mode(self: &File) ModeError!FileMode { | |
| 237 | if (is_posix) { | |
| 238 | var stat: posix.Stat = undefined; | |
| 239 | const err = posix.getErrno(posix.fstat(self.handle, &stat)); | |
| 240 | if (err > 0) { | |
| 241 | return switch (err) { | |
| 242 | posix.EBADF => error.BadFd, | |
| 243 | posix.ENOMEM => error.SystemResources, | |
| 244 | else => os.unexpectedErrorPosix(err), | |
| 245 | }; | |
| 246 | } | |
| 247 | ||
| 248 | return stat.mode; | |
| 249 | } else if (is_windows) { | |
| 250 | return {}; | |
| 251 | } else { | |
| 252 | @compileError("TODO support file mode on this OS"); | |
| 253 | } | |
| 254 | } | |
| 255 | ||
| 256 | pub const ReadError = error {}; | |
| 257 | ||
| 258 | pub fn read(self: &File, buffer: []u8) !usize { | |
| 259 | if (is_posix) { | |
| 260 | var index: usize = 0; | |
| 261 | while (index < buffer.len) { | |
| 262 | const amt_read = posix.read(self.handle, &buffer[index], buffer.len - index); | |
| 263 | const read_err = posix.getErrno(amt_read); | |
| 264 | if (read_err > 0) { | |
| 265 | switch (read_err) { | |
| 266 | posix.EINTR => continue, | |
| 267 | posix.EINVAL => unreachable, | |
| 268 | posix.EFAULT => unreachable, | |
| 269 | posix.EBADF => return error.BadFd, | |
| 270 | posix.EIO => return error.Io, | |
| 271 | else => return os.unexpectedErrorPosix(read_err), | |
| 272 | } | |
| 273 | } | |
| 274 | if (amt_read == 0) return index; | |
| 275 | index += amt_read; | |
| 276 | } | |
| 277 | return index; | |
| 278 | } else if (is_windows) { | |
| 279 | var index: usize = 0; | |
| 280 | while (index < buffer.len) { | |
| 281 | const want_read_count = windows.DWORD(math.min(windows.DWORD(@maxValue(windows.DWORD)), buffer.len - index)); | |
| 282 | var amt_read: windows.DWORD = undefined; | |
| 283 | if (windows.ReadFile(self.handle, @ptrCast(&c_void, &buffer[index]), want_read_count, &amt_read, null) == 0) { | |
| 284 | const err = windows.GetLastError(); | |
| 285 | return switch (err) { | |
| 286 | windows.ERROR.OPERATION_ABORTED => continue, | |
| 287 | windows.ERROR.BROKEN_PIPE => return index, | |
| 288 | else => os.unexpectedErrorWindows(err), | |
| 289 | }; | |
| 290 | } | |
| 291 | if (amt_read == 0) return index; | |
| 292 | index += amt_read; | |
| 293 | } | |
| 294 | return index; | |
| 295 | } else { | |
| 296 | unreachable; | |
| 297 | } | |
| 298 | } | |
| 299 | ||
| 300 | pub const WriteError = os.WindowsWriteError || os.PosixWriteError; | |
| 301 | ||
| 302 | fn write(self: &File, bytes: []const u8) WriteError!void { | |
| 303 | if (is_posix) { | |
| 304 | try os.posixWrite(self.handle, bytes); | |
| 305 | } else if (is_windows) { | |
| 306 | try os.windowsWrite(self.handle, bytes); | |
| 307 | } else { | |
| 308 | @compileError("Unsupported OS"); | |
| 309 | } | |
| 310 | } | |
| 311 | }; |
std/os/index.zig+115-38| ... | ... | @@ -17,10 +17,16 @@ pub const posix = switch(builtin.os) { |
| 17 | 17 | |
| 18 | 18 | pub const ChildProcess = @import("child_process.zig").ChildProcess; |
| 19 | 19 | pub const path = @import("path.zig"); |
| 20 | pub const File = @import("file.zig").File; | |
| 20 | 21 | |
| 21 | pub const line_sep = switch (builtin.os) { | |
| 22 | Os.windows => "\r\n", | |
| 23 | else => "\n", | |
| 22 | pub const FileMode = switch (builtin.os) { | |
| 23 | Os.windows => void, | |
| 24 | else => u32, | |
| 25 | }; | |
| 26 | ||
| 27 | pub const default_file_mode = switch (builtin.os) { | |
| 28 | Os.windows => {}, | |
| 29 | else => 0o666, | |
| 24 | 30 | }; |
| 25 | 31 | |
| 26 | 32 | pub const page_size = 4 * 1024; |
| ... | ... | @@ -672,27 +678,27 @@ const b64_fs_encoder = base64.Base64Encoder.init( |
| 672 | 678 | pub fn atomicSymLink(allocator: &Allocator, existing_path: []const u8, new_path: []const u8) !void { |
| 673 | 679 | if (symLink(allocator, existing_path, new_path)) { |
| 674 | 680 | return; |
| 675 | } else |err| { | |
| 676 | if (err != error.PathAlreadyExists) { | |
| 677 | return err; | |
| 678 | } | |
| 681 | } else |err| switch (err) { | |
| 682 | error.PathAlreadyExists => {}, | |
| 683 | else => return err, // TODO zig should know this set does not include PathAlreadyExists | |
| 679 | 684 | } |
| 680 | 685 | |
| 686 | const dirname = os.path.dirname(new_path); | |
| 687 | ||
| 681 | 688 | var rand_buf: [12]u8 = undefined; |
| 682 | const tmp_path = try allocator.alloc(u8, new_path.len + base64.Base64Encoder.calcSize(rand_buf.len)); | |
| 689 | const tmp_path = try allocator.alloc(u8, dirname.len + 1 + base64.Base64Encoder.calcSize(rand_buf.len)); | |
| 683 | 690 | defer allocator.free(tmp_path); |
| 684 | mem.copy(u8, tmp_path[0..], new_path); | |
| 691 | mem.copy(u8, tmp_path[0..], dirname); | |
| 692 | tmp_path[dirname.len] = os.path.sep; | |
| 685 | 693 | while (true) { |
| 686 | 694 | try getRandomBytes(rand_buf[0..]); |
| 687 | b64_fs_encoder.encode(tmp_path[new_path.len..], rand_buf); | |
| 695 | b64_fs_encoder.encode(tmp_path[dirname.len + 1 ..], rand_buf); | |
| 696 | ||
| 688 | 697 | if (symLink(allocator, existing_path, tmp_path)) { |
| 689 | 698 | return rename(allocator, tmp_path, new_path); |
| 690 | } else |err| { | |
| 691 | if (err == error.PathAlreadyExists) { | |
| 692 | continue; | |
| 693 | } else { | |
| 694 | return err; | |
| 695 | } | |
| 699 | } else |err| switch (err) { | |
| 700 | error.PathAlreadyExists => continue, | |
| 701 | else => return err, // TODO zig should know this set does not include PathAlreadyExists | |
| 696 | 702 | } |
| 697 | 703 | } |
| 698 | 704 | |
| ... | ... | @@ -750,37 +756,108 @@ pub fn deleteFilePosix(allocator: &Allocator, file_path: []const u8) !void { |
| 750 | 756 | } |
| 751 | 757 | } |
| 752 | 758 | |
| 753 | /// Calls ::copyFileMode with 0o666 for the mode. | |
| 759 | /// Guaranteed to be atomic. However until https://patchwork.kernel.org/patch/9636735/ is | |
| 760 | /// merged and readily available, | |
| 761 | /// there is a possibility of power loss or application termination leaving temporary files present | |
| 762 | /// in the same directory as dest_path. | |
| 763 | /// Destination file will have the same mode as the source file. | |
| 754 | 764 | pub fn copyFile(allocator: &Allocator, source_path: []const u8, dest_path: []const u8) !void { |
| 755 | return copyFileMode(allocator, source_path, dest_path, 0o666); | |
| 756 | } | |
| 765 | var in_file = try os.File.openRead(allocator, source_path); | |
| 766 | defer in_file.close(); | |
| 757 | 767 | |
| 758 | // TODO instead of accepting a mode argument, use the mode from fstat'ing the source path once open | |
| 759 | /// Guaranteed to be atomic. | |
| 760 | pub fn copyFileMode(allocator: &Allocator, source_path: []const u8, dest_path: []const u8, mode: usize) !void { | |
| 761 | var rand_buf: [12]u8 = undefined; | |
| 762 | const tmp_path = try allocator.alloc(u8, dest_path.len + base64.Base64Encoder.calcSize(rand_buf.len)); | |
| 763 | defer allocator.free(tmp_path); | |
| 764 | mem.copy(u8, tmp_path[0..], dest_path); | |
| 765 | try getRandomBytes(rand_buf[0..]); | |
| 766 | b64_fs_encoder.encode(tmp_path[dest_path.len..], rand_buf); | |
| 768 | const mode = try in_file.mode(); | |
| 767 | 769 | |
| 768 | var out_file = try io.File.openWriteMode(allocator, tmp_path, mode); | |
| 769 | defer out_file.close(); | |
| 770 | errdefer _ = deleteFile(allocator, tmp_path); | |
| 770 | var atomic_file = try AtomicFile.init(allocator, dest_path, mode); | |
| 771 | defer atomic_file.deinit(); | |
| 771 | 772 | |
| 772 | var in_file = try io.File.openRead(allocator, source_path); | |
| 773 | var buf: [page_size]u8 = undefined; | |
| 774 | while (true) { | |
| 775 | const amt = try in_file.read(buf[0..]); | |
| 776 | try atomic_file.file.write(buf[0..amt]); | |
| 777 | if (amt != buf.len) { | |
| 778 | return atomic_file.finish(); | |
| 779 | } | |
| 780 | } | |
| 781 | } | |
| 782 | ||
| 783 | /// Guaranteed to be atomic. However until https://patchwork.kernel.org/patch/9636735/ is | |
| 784 | /// merged and readily available, | |
| 785 | /// there is a possibility of power loss or application termination leaving temporary files present | |
| 786 | pub fn copyFileMode(allocator: &Allocator, source_path: []const u8, dest_path: []const u8, mode: FileMode) !void { | |
| 787 | var in_file = try os.File.openRead(allocator, source_path); | |
| 773 | 788 | defer in_file.close(); |
| 774 | 789 | |
| 790 | var atomic_file = try AtomicFile.init(allocator, dest_path, mode); | |
| 791 | defer atomic_file.deinit(); | |
| 792 | ||
| 775 | 793 | var buf: [page_size]u8 = undefined; |
| 776 | 794 | while (true) { |
| 777 | 795 | const amt = try in_file.read(buf[0..]); |
| 778 | try out_file.write(buf[0..amt]); | |
| 779 | if (amt != buf.len) | |
| 780 | return rename(allocator, tmp_path, dest_path); | |
| 796 | try atomic_file.file.write(buf[0..amt]); | |
| 797 | if (amt != buf.len) { | |
| 798 | return atomic_file.finish(); | |
| 799 | } | |
| 781 | 800 | } |
| 782 | 801 | } |
| 783 | 802 | |
| 803 | pub const AtomicFile = struct { | |
| 804 | allocator: &Allocator, | |
| 805 | file: os.File, | |
| 806 | tmp_path: []u8, | |
| 807 | dest_path: []const u8, | |
| 808 | finished: bool, | |
| 809 | ||
| 810 | /// dest_path must remain valid for the lifetime of AtomicFile | |
| 811 | /// call finish to atomically replace dest_path with contents | |
| 812 | pub fn init(allocator: &Allocator, dest_path: []const u8, mode: FileMode) !AtomicFile { | |
| 813 | const dirname = os.path.dirname(dest_path); | |
| 814 | ||
| 815 | var rand_buf: [12]u8 = undefined; | |
| 816 | const tmp_path = try allocator.alloc(u8, dirname.len + 1 + base64.Base64Encoder.calcSize(rand_buf.len)); | |
| 817 | errdefer allocator.free(tmp_path); | |
| 818 | mem.copy(u8, tmp_path[0..], dirname); | |
| 819 | tmp_path[dirname.len] = os.path.sep; | |
| 820 | ||
| 821 | while (true) { | |
| 822 | try getRandomBytes(rand_buf[0..]); | |
| 823 | b64_fs_encoder.encode(tmp_path[dirname.len + 1 ..], rand_buf); | |
| 824 | ||
| 825 | const file = os.File.openWriteNoClobber(allocator, tmp_path, mode) catch |err| switch (err) { | |
| 826 | error.PathAlreadyExists => continue, | |
| 827 | // TODO zig should figure out that this error set does not include PathAlreadyExists since | |
| 828 | // it is handled in the above switch | |
| 829 | else => return err, | |
| 830 | }; | |
| 831 | ||
| 832 | return AtomicFile { | |
| 833 | .allocator = allocator, | |
| 834 | .file = file, | |
| 835 | .tmp_path = tmp_path, | |
| 836 | .dest_path = dest_path, | |
| 837 | .finished = false, | |
| 838 | }; | |
| 839 | } | |
| 840 | } | |
| 841 | ||
| 842 | /// always call deinit, even after successful finish() | |
| 843 | pub fn deinit(self: &AtomicFile) void { | |
| 844 | if (!self.finished) { | |
| 845 | self.file.close(); | |
| 846 | deleteFile(self.allocator, self.tmp_path) catch {}; | |
| 847 | self.allocator.free(self.tmp_path); | |
| 848 | self.finished = true; | |
| 849 | } | |
| 850 | } | |
| 851 | ||
| 852 | pub fn finish(self: &AtomicFile) !void { | |
| 853 | assert(!self.finished); | |
| 854 | self.file.close(); | |
| 855 | try rename(self.allocator, self.tmp_path, self.dest_path); | |
| 856 | self.allocator.free(self.tmp_path); | |
| 857 | self.finished = true; | |
| 858 | } | |
| 859 | }; | |
| 860 | ||
| 784 | 861 | pub fn rename(allocator: &Allocator, old_path: []const u8, new_path: []const u8) !void { |
| 785 | 862 | const full_buf = try allocator.alloc(u8, old_path.len + new_path.len + 2); |
| 786 | 863 | defer allocator.free(full_buf); |
| ... | ... | @@ -1620,19 +1697,19 @@ pub fn unexpectedErrorWindows(err: windows.DWORD) (error{Unexpected}) { |
| 1620 | 1697 | return error.Unexpected; |
| 1621 | 1698 | } |
| 1622 | 1699 | |
| 1623 | pub fn openSelfExe() !io.File { | |
| 1700 | pub fn openSelfExe() !os.File { | |
| 1624 | 1701 | switch (builtin.os) { |
| 1625 | 1702 | Os.linux => { |
| 1626 | 1703 | const proc_file_path = "/proc/self/exe"; |
| 1627 | 1704 | var fixed_buffer_mem: [proc_file_path.len + 1]u8 = undefined; |
| 1628 | 1705 | var fixed_allocator = mem.FixedBufferAllocator.init(fixed_buffer_mem[0..]); |
| 1629 | return io.File.openRead(&fixed_allocator.allocator, proc_file_path); | |
| 1706 | return os.File.openRead(&fixed_allocator.allocator, proc_file_path); | |
| 1630 | 1707 | }, |
| 1631 | 1708 | Os.macosx, Os.ios => { |
| 1632 | 1709 | var fixed_buffer_mem: [darwin.PATH_MAX * 2]u8 = undefined; |
| 1633 | 1710 | var fixed_allocator = mem.FixedBufferAllocator.init(fixed_buffer_mem[0..]); |
| 1634 | 1711 | const self_exe_path = try selfExePath(&fixed_allocator.allocator); |
| 1635 | return io.File.openRead(&fixed_allocator.allocator, self_exe_path); | |
| 1712 | return os.File.openRead(&fixed_allocator.allocator, self_exe_path); | |
| 1636 | 1713 | }, |
| 1637 | 1714 | else => @compileError("Unsupported OS"), |
| 1638 | 1715 | } |
std/zig/parser.zig+1-4| ... | ... | @@ -1038,11 +1038,8 @@ var fixed_buffer_mem: [100 * 1024]u8 = undefined; |
| 1038 | 1038 | fn testParse(source: []const u8, allocator: &mem.Allocator) ![]u8 { |
| 1039 | 1039 | var padded_source: [0x100]u8 = undefined; |
| 1040 | 1040 | std.mem.copy(u8, padded_source[0..source.len], source); |
| 1041 | padded_source[source.len + 0] = '\n'; | |
| 1042 | padded_source[source.len + 1] = '\n'; | |
| 1043 | padded_source[source.len + 2] = '\n'; | |
| 1044 | 1041 | |
| 1045 | var tokenizer = Tokenizer.init(padded_source[0..source.len + 3]); | |
| 1042 | var tokenizer = Tokenizer.init(padded_source[0..source.len]); | |
| 1046 | 1043 | var parser = Parser.init(&tokenizer, allocator, "(memory buffer)"); |
| 1047 | 1044 | defer parser.deinit(); |
| 1048 | 1045 |
test/compare_output.zig+4-2| ... | ... | @@ -1,4 +1,6 @@ |
| 1 | const os = @import("std").os; | |
| 1 | const builtin = @import("builtin"); | |
| 2 | const std = @import("std"); | |
| 3 | const os = std.os; | |
| 2 | 4 | const tests = @import("tests.zig"); |
| 3 | 5 | |
| 4 | 6 | pub fn addCases(cases: &tests.CompareOutputContext) void { |
| ... | ... | @@ -8,7 +10,7 @@ pub fn addCases(cases: &tests.CompareOutputContext) void { |
| 8 | 10 | \\ _ = c.puts(c"Hello, world!"); |
| 9 | 11 | \\ return 0; |
| 10 | 12 | \\} |
| 11 | , "Hello, world!" ++ os.line_sep); | |
| 13 | , "Hello, world!" ++ std.cstr.line_sep); | |
| 12 | 14 | |
| 13 | 15 | cases.addCase(x: { |
| 14 | 16 | var tc = cases.create("multiple files with private function", |