diff --git a/std/build.zig b/std/build.zig index f9fed6ad2fd35bc51382585af6fb340220a095c2..cc12a75df5893e52a8a7e2265b3ecd93aa7399c4 100644 --- a/std/build.zig +++ b/std/build.zig @@ -634,7 +634,7 @@ pub const Builder = struct { warn("Unable to create path {}: {}\n", dirname, @errorName(err)); return err; }; - os.copyFileMode(self.allocator, abs_source_path, dest_path, mode) catch |err| { + os.copyFileMode(abs_source_path, dest_path, mode) catch |err| { warn("Unable to copy {} to {}: {}\n", abs_source_path, dest_path, @errorName(err)); return err; }; diff --git a/std/event/io.zig b/std/event/io.zig index 6bdec54c1bcf6f2e428814e2b519a8bdeefb7e50..fa05c61abe2b9f7943ba90dbb61beff2125d8218 100644 --- a/std/event/io.zig +++ b/std/event/io.zig @@ -21,6 +21,24 @@ pub fn InStream(comptime ReadError: type) type { return await (async self.readFn(self, buffer) catch unreachable); } + /// Return the number of bytes read. If it is less than buffer.len + /// it means end of stream. + pub async fn readFull(self: *Self, buffer: []u8) !usize { + var index: usize = 0; + while (index != buf.len) { + const amt_read = try await (async self.read(buf[index..]) catch unreachable); + if (amt_read == 0) return index; + index += amt_read; + } + return index; + } + + /// Same as `readFull` but end of stream returns `error.EndOfStream`. + pub async fn readNoEof(self: *Self, buf: []u8) !void { + const amt_read = try await (async self.readFull(buf[index..]) catch unreachable); + if (amt_read < buf.len) return error.EndOfStream; + } + pub async fn readIntLe(self: *Self, comptime T: type) !T { return await (async self.readInt(builtin.Endian.Little, T) catch unreachable); } @@ -31,24 +49,14 @@ pub fn InStream(comptime ReadError: type) type { pub async fn readInt(self: *Self, endian: builtin.Endian, comptime T: type) !T { var bytes: [@sizeOf(T)]u8 = undefined; - try await (async self.readFull(bytes[0..]) catch unreachable); + try await (async self.readNoEof(bytes[0..]) catch unreachable); return mem.readInt(bytes, T, endian); } - /// Same as `read` but end of stream returns `error.EndOfStream`. - pub async fn readFull(self: *Self, buf: []u8) !void { - var index: usize = 0; - while (index != buf.len) { - const amt_read = try await (async self.read(buf[index..]) catch unreachable); - if (amt_read == 0) return error.EndOfStream; - index += amt_read; - } - } - pub async fn readStruct(self: *Self, comptime T: type, ptr: *T) !void { // Only extern and packed structs have defined in-memory layout. comptime assert(@typeInfo(T).Struct.layout != builtin.TypeInfo.ContainerLayout.Auto); - return await (async self.readFull(@sliceToBytes((*[1]T)(ptr)[0..])) catch unreachable); + return await (async self.readNoEof(@sliceToBytes((*[1]T)(ptr)[0..])) catch unreachable); } }; } diff --git a/std/io.zig b/std/io.zig index 3d20d8b9dcefef3d914a2844615942fe77fa97b3..c22a8ca78beece2696379748200e2c9d9cc32726 100644 --- a/std/io.zig +++ b/std/io.zig @@ -51,7 +51,7 @@ pub fn InStream(comptime ReadError: type) type { var actual_buf_len: usize = 0; while (true) { const dest_slice = buffer.toSlice()[actual_buf_len..]; - const bytes_read = try self.readFn(self, dest_slice); + const bytes_read = try self.readFull(dest_slice); actual_buf_len += bytes_read; if (bytes_read != dest_slice.len) { @@ -111,14 +111,27 @@ pub fn InStream(comptime ReadError: type) type { return buf.toOwnedSlice(); } - /// Returns the number of bytes read. If the number read is smaller than buf.len, it - /// means the stream reached the end. Reaching the end of a stream is not an error - /// condition. + /// Returns the number of bytes read. It may be less than buffer.len. + /// If the number of bytes read is 0, it means end of stream. + /// End of stream is not an error condition. pub fn read(self: *Self, buffer: []u8) !usize { return self.readFn(self, buffer); } - /// Same as `read` but end of stream returns `error.EndOfStream`. + /// Returns the number of bytes read. If the number read is smaller than buf.len, it + /// means the stream reached the end. Reaching the end of a stream is not an error + /// condition. + pub fn readFull(self: *Self, buffer: []u8) !usize { + var index: usize = 0; + while (index != buffer.len) { + const amt = try self.read(buffer[index..]); + if (amt == 0) return index; + index += amt; + } + return index; + } + + /// Same as `readFull` but end of stream returns `error.EndOfStream`. pub fn readNoEof(self: *Self, buf: []u8) !void { const amt_read = try self.read(buf); if (amt_read < buf.len) return error.EndOfStream; @@ -136,6 +149,11 @@ pub fn InStream(comptime ReadError: type) type { return @bitCast(i8, try self.readByte()); } + /// Reads a native-endian integer + pub fn readIntNe(self: *Self, comptime T: type) !T { + return self.readInt(builtin.endian, T); + } + pub fn readIntLe(self: *Self, comptime T: type) !T { return self.readInt(builtin.Endian.Little, T); } @@ -202,6 +220,11 @@ pub fn OutStream(comptime WriteError: type) type { } } + /// Write a native-endian integer. + pub fn writeIntNe(self: *Self, comptime T: type, value: T) !void { + return self.writeInt(builtin.endian, T, value); + } + pub fn writeIntLe(self: *Self, comptime T: type, value: T) !void { return self.writeInt(builtin.Endian.Little, T, value); } @@ -537,6 +560,7 @@ pub const BufferedAtomicFile = struct { atomic_file: os.AtomicFile, file_stream: os.File.OutStream, buffered_stream: BufferedOutStream(os.File.WriteError), + allocator: *mem.Allocator, pub fn create(allocator: *mem.Allocator, dest_path: []const u8) !*BufferedAtomicFile { // TODO with well defined copy elision we don't need this allocation @@ -544,10 +568,11 @@ pub const BufferedAtomicFile = struct { .atomic_file = undefined, .file_stream = undefined, .buffered_stream = undefined, + .allocator = allocator, }); errdefer allocator.destroy(self); - self.atomic_file = try os.AtomicFile.init(allocator, dest_path, os.File.default_mode); + self.atomic_file = try os.AtomicFile.init(dest_path, os.File.default_mode); errdefer self.atomic_file.deinit(); self.file_stream = self.atomic_file.file.outStream(); @@ -557,9 +582,8 @@ pub const BufferedAtomicFile = struct { /// always call destroy, even after successful finish() pub fn destroy(self: *BufferedAtomicFile) void { - const allocator = self.atomic_file.allocator; self.atomic_file.deinit(); - allocator.destroy(self); + self.allocator.destroy(self); } pub fn finish(self: *BufferedAtomicFile) !void { diff --git a/std/os/child_process.zig b/std/os/child_process.zig index 5c02f1ebe0a1c15d0245b7d859c45c237bc0b171..8bfd3f89597828e751f112628815dc37d8f6e0f3 100644 --- a/std/os/child_process.zig +++ b/std/os/child_process.zig @@ -792,13 +792,11 @@ fn forkChildErrReport(fd: i32, err: ChildProcess.SpawnError) noreturn { const ErrInt = @IntType(false, @sizeOf(error) * 8); fn writeIntFd(fd: i32, value: ErrInt) !void { - var bytes: [@sizeOf(ErrInt)]u8 = undefined; - mem.writeInt(bytes[0..], value, builtin.endian); - os.posixWrite(fd, bytes[0..]) catch return error.SystemResources; + const stream = &os.File.openHandle(fd).outStream().stream; + stream.writeIntNe(ErrInt, value) catch return error.SystemResources; } fn readIntFd(fd: i32) !ErrInt { - var bytes: [@sizeOf(ErrInt)]u8 = undefined; - os.posixRead(fd, bytes[0..]) catch return error.SystemResources; - return mem.readInt(bytes[0..], ErrInt, builtin.endian); + const stream = &os.File.openHandle(fd).inStream().stream; + return stream.readIntNe(ErrInt) catch return error.SystemResources; } diff --git a/std/os/file.zig b/std/os/file.zig index fcc626a8f2f5783ff149520144108f6b4aead7b6..f5e754cdc7831a1280fb7ca703b851f52783f422 100644 --- a/std/os/file.zig +++ b/std/os/file.zig @@ -102,12 +102,24 @@ pub const File = struct { /// 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 (is_posix) { + const path_c = try os.toPosixPath(path); + return openWriteNoClobberC(path_c, file_mode); + } else if (is_windows) { + const path_w = try windows_util.sliceToPrefixedFileW(path); + return openWriteNoClobberW(&path_w, file_mode); + } else { + @compileError("TODO implement openWriteMode for this OS"); + } + } + + pub fn openWriteNoClobberC(path: [*]const u8, file_mode: Mode) OpenError!File { if (is_posix) { const flags = posix.O_LARGEFILE | posix.O_WRONLY | posix.O_CREAT | posix.O_CLOEXEC | posix.O_EXCL; - const fd = try os.posixOpen(path, flags, file_mode); + const fd = try os.posixOpenC(path, flags, file_mode); return openHandle(fd); } else if (is_windows) { - const path_w = try windows_util.sliceToPrefixedFileW(path); + const path_w = try windows_util.cStrToPrefixedFileW(path); return openWriteNoClobberW(&path_w, file_mode); } else { @compileError("TODO implement openWriteMode for this OS"); @@ -369,28 +381,7 @@ pub const File = struct { pub fn read(self: File, buffer: []u8) ReadError!usize { if (is_posix) { - var index: usize = 0; - while (index < buffer.len) { - const amt_read = posix.read(self.handle, buffer.ptr + index, buffer.len - index); - const read_err = posix.getErrno(amt_read); - if (read_err > 0) { - switch (read_err) { - posix.EINTR => continue, - posix.EINVAL => unreachable, - posix.EFAULT => unreachable, - posix.EAGAIN => unreachable, - posix.EBADF => unreachable, // always a race condition - posix.EIO => return error.InputOutput, - posix.EISDIR => return error.IsDir, - posix.ENOBUFS => return error.SystemResources, - posix.ENOMEM => return error.SystemResources, - else => return os.unexpectedErrorPosix(read_err), - } - } - if (amt_read == 0) return index; - index += amt_read; - } - return index; + return os.posixRead(self.handle, buffer); } else if (is_windows) { var index: usize = 0; while (index < buffer.len) { @@ -409,7 +400,7 @@ pub const File = struct { } return index; } else { - unreachable; + @compileError("Unsupported OS"); } } diff --git a/std/os/index.zig b/std/os/index.zig index 5c9e6eee8250a275df6c8f7ba6698d99f9a71915..40219b9cfa68f249fbb5b2da594f2eb720abe29c 100644 --- a/std/os/index.zig +++ b/std/os/index.zig @@ -104,30 +104,17 @@ pub fn getRandomBytes(buf: []u8) !void { Os.linux => while (true) { // TODO check libc version and potentially call c.getrandom. // See #397 - const err = posix.getErrno(posix.getrandom(buf.ptr, buf.len, 0)); - if (err > 0) { - switch (err) { - posix.EINVAL => unreachable, - posix.EFAULT => unreachable, - posix.EINTR => continue, - posix.ENOSYS => { - const fd = try posixOpenC(c"/dev/urandom", posix.O_RDONLY | posix.O_CLOEXEC, 0); - defer close(fd); - - try posixRead(fd, buf); - return; - }, - else => return unexpectedErrorPosix(err), - } + const errno = posix.getErrno(posix.getrandom(buf.ptr, buf.len, 0)); + switch (errno) { + 0 => return, + posix.EINVAL => unreachable, + posix.EFAULT => unreachable, + posix.EINTR => continue, + posix.ENOSYS => return getRandomBytesDevURandom(buf), + else => return unexpectedErrorPosix(errno), } - return; - }, - Os.macosx, Os.ios => { - const fd = try posixOpenC(c"/dev/urandom", posix.O_RDONLY | posix.O_CLOEXEC, 0); - defer close(fd); - - try posixRead(fd, buf); }, + Os.macosx, Os.ios => return getRandomBytesDevURandom(buf), Os.windows => { // Call RtlGenRandom() instead of CryptGetRandom() on Windows // https://github.com/rust-lang-nursery/rand/issues/111 @@ -151,6 +138,22 @@ pub fn getRandomBytes(buf: []u8) !void { } } +fn getRandomBytesDevURandom(buf: []u8) !void { + const fd = try posixOpenC(c"/dev/urandom", posix.O_RDONLY | posix.O_CLOEXEC, 0); + defer close(fd); + + const stream = &File.openHandle(fd).inStream().stream; + stream.readNoEof(buf) catch |err| switch (err) { + error.EndOfStream => unreachable, + error.OperationAborted => unreachable, + error.BrokenPipe => unreachable, + error.Unexpected => return error.Unexpected, + error.InputOutput => return error.Unexpected, + error.SystemResources => return error.Unexpected, + error.IsDir => unreachable, + }; +} + test "os.getRandomBytes" { var buf_a: [50]u8 = undefined; var buf_b: [50]u8 = undefined; @@ -235,8 +238,9 @@ pub const PosixReadError = error{ Unexpected, }; -/// Calls POSIX read, and keeps trying if it gets interrupted. -pub fn posixRead(fd: i32, buf: []u8) !void { +/// Returns the number of bytes that were read, which can be less than +/// buf.len. If 0 bytes were read, that means EOF. +pub fn posixRead(fd: i32, buf: []u8) PosixReadError!usize { // Linux can return EINVAL when read amount is > 0x7ffff000 // See https://github.com/ziglang/zig/pull/743#issuecomment-363158274 const max_buf_len = 0x7ffff000; @@ -249,7 +253,9 @@ pub fn posixRead(fd: i32, buf: []u8) !void { switch (err) { 0 => { index += rc; - continue; + if (rc == want_to_read) continue; + // Read returned less than buf.len. + return index; }, posix.EINTR => continue, posix.EINVAL => unreachable, @@ -263,6 +269,7 @@ pub fn posixRead(fd: i32, buf: []u8) !void { else => return unexpectedErrorPosix(err), } } + return index; } /// Number of bytes read is returned. Upon reading end-of-file, zero is returned. @@ -962,16 +969,16 @@ pub const DeleteFileError = error{ pub fn deleteFile(file_path: []const u8) DeleteFileError!void { if (builtin.os == Os.windows) { - return deleteFileWindows(file_path); + const file_path_w = try windows_util.sliceToPrefixedFileW(file_path); + return deleteFileW(&file_path_w); } else { - return deleteFilePosix(file_path); + const file_path_c = try toPosixPath(file_path); + return deleteFileC(&file_path_c); } } -pub fn deleteFileWindows(file_path: []const u8) !void { - const file_path_w = try windows_util.sliceToPrefixedFileW(file_path); - - if (windows.DeleteFileW(&file_path_w) == 0) { +pub fn deleteFileW(file_path: [*]const u16) DeleteFileError!void { + if (windows.DeleteFileW(file_path) == 0) { const err = windows.GetLastError(); switch (err) { windows.ERROR.FILE_NOT_FOUND => return error.FileNotFound, @@ -983,50 +990,49 @@ pub fn deleteFileWindows(file_path: []const u8) !void { } } -pub fn deleteFilePosixC(file_path: [*]const u8) !void { - const err = posix.getErrno(posix.unlink(file_path)); - switch (err) { - 0 => return, - posix.EACCES => return error.AccessDenied, - posix.EPERM => return error.AccessDenied, - posix.EBUSY => return error.FileBusy, - posix.EFAULT => unreachable, - posix.EINVAL => unreachable, - posix.EIO => return error.FileSystem, - posix.EISDIR => return error.IsDir, - posix.ELOOP => return error.SymLinkLoop, - posix.ENAMETOOLONG => return error.NameTooLong, - posix.ENOENT => return error.FileNotFound, - posix.ENOTDIR => return error.NotDir, - posix.ENOMEM => return error.SystemResources, - posix.EROFS => return error.ReadOnlyFileSystem, - else => return unexpectedErrorPosix(err), +pub fn deleteFileC(file_path: [*]const u8) DeleteFileError!void { + if (is_windows) { + const file_path_w = try windows_util.cStrToPrefixedFileW(file_path); + return deleteFileW(&file_path_w); + } else { + const err = posix.getErrno(posix.unlink(file_path)); + switch (err) { + 0 => return, + posix.EACCES => return error.AccessDenied, + posix.EPERM => return error.AccessDenied, + posix.EBUSY => return error.FileBusy, + posix.EFAULT => unreachable, + posix.EINVAL => unreachable, + posix.EIO => return error.FileSystem, + posix.EISDIR => return error.IsDir, + posix.ELOOP => return error.SymLinkLoop, + posix.ENAMETOOLONG => return error.NameTooLong, + posix.ENOENT => return error.FileNotFound, + posix.ENOTDIR => return error.NotDir, + posix.ENOMEM => return error.SystemResources, + posix.EROFS => return error.ReadOnlyFileSystem, + else => return unexpectedErrorPosix(err), + } } } -pub fn deleteFilePosix(file_path: []const u8) !void { - const file_path_c = try toPosixPath(file_path); - return deleteFilePosixC(&file_path_c); -} - /// Guaranteed to be atomic. However until https://patchwork.kernel.org/patch/9636735/ is /// merged and readily available, /// there is a possibility of power loss or application termination leaving temporary files present /// in the same directory as dest_path. /// Destination file will have the same mode as the source file. -/// TODO investigate if this can work with no allocator -pub fn copyFile(allocator: *Allocator, source_path: []const u8, dest_path: []const u8) !void { +pub fn copyFile(source_path: []const u8, dest_path: []const u8) !void { var in_file = try os.File.openRead(source_path); defer in_file.close(); const mode = try in_file.mode(); - var atomic_file = try AtomicFile.init(allocator, dest_path, mode); + var atomic_file = try AtomicFile.init(dest_path, mode); defer atomic_file.deinit(); var buf: [page_size]u8 = undefined; while (true) { - const amt = try in_file.read(buf[0..]); + const amt = try in_file.readFull(buf[0..]); try atomic_file.file.write(buf[0..amt]); if (amt != buf.len) { return atomic_file.finish(); @@ -1037,12 +1043,11 @@ pub fn copyFile(allocator: *Allocator, source_path: []const u8, dest_path: []con /// Guaranteed to be atomic. However until https://patchwork.kernel.org/patch/9636735/ is /// merged and readily available, /// there is a possibility of power loss or application termination leaving temporary files present -/// TODO investigate if this can work with no allocator -pub fn copyFileMode(allocator: *Allocator, source_path: []const u8, dest_path: []const u8, mode: File.Mode) !void { +pub fn copyFileMode(source_path: []const u8, dest_path: []const u8, mode: File.Mode) !void { var in_file = try os.File.openRead(source_path); defer in_file.close(); - var atomic_file = try AtomicFile.init(allocator, dest_path, mode); + var atomic_file = try AtomicFile.init(dest_path, mode); defer atomic_file.deinit(); var buf: [page_size]u8 = undefined; @@ -1056,35 +1061,38 @@ pub fn copyFileMode(allocator: *Allocator, source_path: []const u8, dest_path: [ } pub const AtomicFile = struct { - /// TODO investigate if we can make this work with no allocator - allocator: *Allocator, file: os.File, - tmp_path: []u8, + tmp_path_buf: [MAX_PATH_BYTES]u8, dest_path: []const u8, finished: bool, + const InitError = os.File.OpenError; + /// dest_path must remain valid for the lifetime of AtomicFile /// call finish to atomically replace dest_path with contents - pub fn init(allocator: *Allocator, dest_path: []const u8, mode: File.Mode) !AtomicFile { + /// TODO once we have null terminated pointers, use the + /// openWriteNoClobberN function + pub fn init(dest_path: []const u8, mode: File.Mode) InitError!AtomicFile { const dirname = os.path.dirname(dest_path); - var rand_buf: [12]u8 = undefined; - const dirname_component_len = if (dirname) |d| d.len + 1 else 0; - const tmp_path = try allocator.alloc(u8, dirname_component_len + - base64.Base64Encoder.calcSize(rand_buf.len)); - errdefer allocator.free(tmp_path); + const encoded_rand_len = comptime base64.Base64Encoder.calcSize(rand_buf.len); + const tmp_path_len = dirname_component_len + encoded_rand_len; + var tmp_path_buf: [MAX_PATH_BYTES]u8 = undefined; + if (tmp_path_len >= tmp_path_buf.len) return error.NameTooLong; if (dirname) |dir| { - mem.copy(u8, tmp_path[0..], dir); - tmp_path[dir.len] = os.path.sep; + mem.copy(u8, tmp_path_buf[0..], dir); + tmp_path_buf[dir.len] = os.path.sep; } + tmp_path_buf[tmp_path_len] = 0; + while (true) { try getRandomBytes(rand_buf[0..]); - b64_fs_encoder.encode(tmp_path[dirname_component_len..], rand_buf); + b64_fs_encoder.encode(tmp_path_buf[dirname_component_len..tmp_path_len], rand_buf); - const file = os.File.openWriteNoClobber(tmp_path, mode) catch |err| switch (err) { + const file = os.File.openWriteNoClobberC(&tmp_path_buf, mode) catch |err| switch (err) { error.PathAlreadyExists => continue, // TODO zig should figure out that this error set does not include PathAlreadyExists since // it is handled in the above switch @@ -1092,9 +1100,8 @@ pub const AtomicFile = struct { }; return AtomicFile{ - .allocator = allocator, .file = file, - .tmp_path = tmp_path, + .tmp_path_buf = tmp_path_buf, .dest_path = dest_path, .finished = false, }; @@ -1105,8 +1112,7 @@ pub const AtomicFile = struct { pub fn deinit(self: *AtomicFile) void { if (!self.finished) { self.file.close(); - deleteFile(self.tmp_path) catch {}; - self.allocator.free(self.tmp_path); + deleteFileC(&self.tmp_path_buf) catch {}; self.finished = true; } } @@ -1114,15 +1120,25 @@ pub const AtomicFile = struct { pub fn finish(self: *AtomicFile) !void { assert(!self.finished); self.file.close(); - try rename(self.tmp_path, self.dest_path); - self.allocator.free(self.tmp_path); self.finished = true; + if (is_posix) { + const dest_path_c = try toPosixPath(self.dest_path); + return renameC(&self.tmp_path_buf, &dest_path_c); + } else if (is_windows) { + const dest_path_w = try windows_util.sliceToPrefixedFileW(self.dest_path); + const tmp_path_w = try windows_util.cStrToPrefixedFileW(&self.tmp_path_buf); + return renameW(&tmp_path_w, &dest_path_w); + } else { + @compileError("Unsupported OS"); + } } }; pub fn renameC(old_path: [*]const u8, new_path: [*]const u8) !void { if (is_windows) { - @compileError("TODO implement for windows"); + const old_path_w = try windows_util.cStrToPrefixedFileW(old_path); + const new_path_w = try windows_util.cStrToPrefixedFileW(new_path); + return renameW(&old_path_w, &new_path_w); } else { const err = posix.getErrno(posix.rename(old_path, new_path)); switch (err) { @@ -1150,17 +1166,21 @@ pub fn renameC(old_path: [*]const u8, new_path: [*]const u8) !void { } } +pub fn renameW(old_path: [*]const u16, new_path: [*]const u16) !void { + const flags = windows.MOVEFILE_REPLACE_EXISTING | windows.MOVEFILE_WRITE_THROUGH; + if (windows.MoveFileExW(old_path, new_path, flags) == 0) { + const err = windows.GetLastError(); + switch (err) { + else => return unexpectedErrorWindows(err), + } + } +} + pub fn rename(old_path: []const u8, new_path: []const u8) !void { if (is_windows) { - const flags = windows.MOVEFILE_REPLACE_EXISTING | windows.MOVEFILE_WRITE_THROUGH; const old_path_w = try windows_util.sliceToPrefixedFileW(old_path); const new_path_w = try windows_util.sliceToPrefixedFileW(new_path); - if (windows.MoveFileExW(&old_path_w, &new_path_w, flags) == 0) { - const err = windows.GetLastError(); - switch (err) { - else => return unexpectedErrorWindows(err), - } - } + return renameW(&old_path_w, &new_path_w); } else { const old_path_c = try toPosixPath(old_path); const new_path_c = try toPosixPath(new_path); diff --git a/std/os/test.zig b/std/os/test.zig index 653ab13fd854668cc93983d405f5154a2a170b5b..5142920687105dcd534c109aaa163a39995b1813 100644 --- a/std/os/test.zig +++ b/std/os/test.zig @@ -2,6 +2,7 @@ const std = @import("../index.zig"); const os = std.os; const assert = std.debug.assert; const io = std.io; +const mem = std.mem; const a = std.debug.global_allocator; @@ -80,3 +81,23 @@ test "cpu count" { const cpu_count = try std.os.cpuCount(a); assert(cpu_count >= 1); } + +test "AtomicFile" { + var buffer: [1024]u8 = undefined; + const allocator = &std.heap.FixedBufferAllocator.init(buffer[0..]).allocator; + const test_out_file = "tmp_atomic_file_test_dest.txt"; + const test_content = + \\ hello! + \\ this is a test file + ; + { + var af = try os.AtomicFile.init(test_out_file, os.File.default_mode); + defer af.deinit(); + try af.file.write(test_content); + try af.finish(); + } + const content = try io.readFileAlloc(allocator, test_out_file); + assert(mem.eql(u8, content, test_content)); + + try os.deleteFile(test_out_file); +}