Commit af229c1fdc

Andrew Kelley <superjoe30@gmail.com>
2018-10-01 19:43:25
std lib (breaking): posixRead can return less than buffer size
closes #1414 std.io.InStream.read now can return less than buffer size introduce std.io.InStream.readFull for previous behavior add std.os.File.openWriteNoClobberC rename std.os.deleteFileWindows to std.os.deleteFileW remove std.os.deleteFilePosix add std.os.deleteFileC std.os.copyFile no longer takes an allocator std.os.copyFileMode no longer takes an allocator std.os.AtomicFile no longer takes an allocator add std.os.renameW add windows support for std.os.renameC add a test for std.os.AtomicFile
1 parent d1ec837
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);
         }
     };
 }
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;
 }
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");
         }
     }
 
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);
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);
+}
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;
         };
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. 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);
+        }
+
         /// 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 read(self: *Self, buffer: []u8) !usize {
-            return self.readFn(self, buffer);
+        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 `read` but end of stream returns `error.EndOfStream`.
+        /// 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 {