diff --git a/lib/std/fs.zig b/lib/std/fs.zig index 892b977291..de6be91f71 100644 --- a/lib/std/fs.zig +++ b/lib/std/fs.zig @@ -96,6 +96,7 @@ pub fn updateFile(source_path: []const u8, dest_path: []const u8) !PrevStatus { /// atime, and mode of the source file so that the next call to `updateFile` will not need a copy. /// Returns the previous status of the file before updating. /// If any of the directories do not exist for dest_path, they are created. +/// TODO rework this to integrate with Dir pub fn updateFileMode(source_path: []const u8, dest_path: []const u8, mode: ?File.Mode) !PrevStatus { const my_cwd = cwd(); @@ -141,29 +142,25 @@ pub fn updateFileMode(source_path: []const u8, dest_path: []const u8, mode: ?Fil /// 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 rework this to integrate with Dir pub fn copyFile(source_path: []const u8, dest_path: []const u8) !void { var in_file = try cwd().openFile(source_path, .{}); defer in_file.close(); - const mode = try in_file.mode(); - const in_stream = &in_file.inStream().stream; + const stat = try in_file.stat(); - var atomic_file = try AtomicFile.init(dest_path, mode); + var atomic_file = try AtomicFile.init(dest_path, stat.mode); defer atomic_file.deinit(); - var buf: [mem.page_size]u8 = undefined; - while (true) { - const amt = try in_stream.readFull(buf[0..]); - try atomic_file.file.write(buf[0..amt]); - if (amt != buf.len) { - return atomic_file.finish(); - } - } + try atomic_file.file.writeFileAll(in_file, .{ .in_len = stat.size }); + return atomic_file.finish(); } -/// Guaranteed to be atomic. However until https://patchwork.kernel.org/patch/9636735/ is -/// merged and readily available, +/// Guaranteed to be atomic. +/// On Linux, 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. +/// TODO rework this to integrate with Dir pub fn copyFileMode(source_path: []const u8, dest_path: []const u8, mode: File.Mode) !void { var in_file = try cwd().openFile(source_path, .{}); defer in_file.close(); @@ -171,14 +168,8 @@ pub fn copyFileMode(source_path: []const u8, dest_path: []const u8, mode: File.M var atomic_file = try AtomicFile.init(dest_path, mode); defer atomic_file.deinit(); - var buf: [mem.page_size * 6]u8 = undefined; - while (true) { - const amt = try in_file.read(buf[0..]); - try atomic_file.file.write(buf[0..amt]); - if (amt != buf.len) { - return atomic_file.finish(); - } - } + try atomic_file.file.writeFileAll(in_file, .{}); + return atomic_file.finish(); } /// TODO update this API to avoid a getrandom syscall for every operation. It diff --git a/lib/std/fs/file.zig b/lib/std/fs/file.zig index 845e4bc75e..8362f94ca5 100644 --- a/lib/std/fs/file.zig +++ b/lib/std/fs/file.zig @@ -250,11 +250,16 @@ pub const File = struct { } } - pub fn readAll(self: File, buffer: []u8) ReadError!void { + /// Returns the number of bytes read. If the number read is smaller than `buffer.len`, it + /// means the file reached the end. Reaching the end of a file is not an error condition. + pub fn readAll(self: File, buffer: []u8) ReadError!usize { var index: usize = 0; - while (index < buffer.len) { - index += try self.read(buffer[index..]); + while (index != buffer.len) { + const amt = try self.read(buffer[index..]); + if (amt == 0) break; + index += amt; } + return index; } pub fn pread(self: File, buffer: []u8, offset: u64) PReadError!usize { @@ -265,11 +270,16 @@ pub const File = struct { } } - pub fn preadAll(self: File, buffer: []u8, offset: u64) PReadError!void { + /// Returns the number of bytes read. If the number read is smaller than `buffer.len`, it + /// means the file reached the end. Reaching the end of a file is not an error condition. + pub fn preadAll(self: File, buffer: []u8, offset: u64) PReadError!usize { var index: usize = 0; - while (index < buffer.len) { - index += try self.pread(buffer[index..], offset + index); + while (index != buffer.len) { + const amt = try self.pread(buffer[index..], offset + index); + if (amt == 0) break; + index += amt; } + return index; } pub fn readv(self: File, iovecs: []const os.iovec) ReadError!usize { @@ -280,19 +290,27 @@ pub const File = struct { } } + /// Returns the number of bytes read. If the number read is smaller than the total bytes + /// from all the buffers, it means the file reached the end. Reaching the end of a file + /// is not an error condition. /// The `iovecs` parameter is mutable because this function needs to mutate the fields in /// order to handle partial reads from the underlying OS layer. - pub fn readvAll(self: File, iovecs: []os.iovec) ReadError!void { + pub fn readvAll(self: File, iovecs: []os.iovec) ReadError!usize { if (iovecs.len == 0) return; var i: usize = 0; + var off: usize = 0; while (true) { var amt = try self.readv(iovecs[i..]); + var eof = amt == 0; + off += amt; while (amt >= iovecs[i].iov_len) { amt -= iovecs[i].iov_len; i += 1; - if (i >= iovecs.len) return; + if (i >= iovecs.len) return off; + eof = false; } + if (eof) return off; iovecs[i].iov_base += amt; iovecs[i].iov_len -= amt; } @@ -306,6 +324,9 @@ pub const File = struct { } } + /// Returns the number of bytes read. If the number read is smaller than the total bytes + /// from all the buffers, it means the file reached the end. Reaching the end of a file + /// is not an error condition. /// The `iovecs` parameter is mutable because this function needs to mutate the fields in /// order to handle partial reads from the underlying OS layer. pub fn preadvAll(self: File, iovecs: []const os.iovec, offset: u64) PReadError!void { @@ -315,12 +336,15 @@ pub const File = struct { var off: usize = 0; while (true) { var amt = try self.preadv(iovecs[i..], offset + off); + var eof = amt == 0; off += amt; while (amt >= iovecs[i].iov_len) { amt -= iovecs[i].iov_len; i += 1; - if (i >= iovecs.len) return; + if (i >= iovecs.len) return off; + eof = false; } + if (eof) return off; iovecs[i].iov_base += amt; iovecs[i].iov_len -= amt; } diff --git a/lib/std/io/in_stream.zig b/lib/std/io/in_stream.zig index 2830582482..d1d24ab427 100644 --- a/lib/std/io/in_stream.zig +++ b/lib/std/io/in_stream.zig @@ -28,10 +28,7 @@ pub fn InStream( return readFn(self.context, buffer); } - /// Deprecated: use `readAll`. - pub const readFull = readAll; - - /// Returns the number of bytes read. If the number read is smaller than buf.len, it + /// Returns the number of bytes read. If the number read is smaller than `buffer.len`, it /// means the stream reached the end. Reaching the end of a stream is not an error /// condition. pub fn readAll(self: Self, buffer: []u8) Error!usize { diff --git a/lib/std/os/test.zig b/lib/std/os/test.zig index 75a6d5db91..1ef6798b50 100644 --- a/lib/std/os/test.zig +++ b/lib/std/os/test.zig @@ -95,15 +95,41 @@ test "sendfile" { }, }; - var written_buf: [header1.len + header2.len + 10 + trailer1.len + trailer2.len]u8 = undefined; + var written_buf: [100]u8 = undefined; try dest_file.writeFileAll(src_file, .{ .in_offset = 1, .in_len = 10, .headers_and_trailers = &hdtr, .header_count = 2, }); - try dest_file.preadAll(&written_buf, 0); - expect(mem.eql(u8, &written_buf, "header1\nsecond header\nine1\nsecontrailer1\nsecond trailer\n")); + const amt = try dest_file.preadAll(&written_buf, 0); + expect(mem.eql(u8, written_buf[0..amt], "header1\nsecond header\nine1\nsecontrailer1\nsecond trailer\n")); +} + +test "fs.copyFile" { + const data = "u6wj+JmdF3qHsFPE BUlH2g4gJCmEz0PP"; + const src_file = "tmp_test_copy_file.txt"; + const dest_file = "tmp_test_copy_file2.txt"; + const dest_file2 = "tmp_test_copy_file3.txt"; + + try fs.cwd().writeFile(src_file, data); + defer fs.cwd().deleteFile(src_file) catch {}; + + try fs.copyFile(src_file, dest_file); + defer fs.cwd().deleteFile(dest_file) catch {}; + + try fs.copyFileMode(src_file, dest_file2, File.default_mode); + defer fs.cwd().deleteFile(dest_file2) catch {}; + + try expectFileContents(dest_file, data); + try expectFileContents(dest_file2, data); +} + +fn expectFileContents(file_path: []const u8, data: []const u8) !void { + const contents = try fs.cwd().readFileAlloc(testing.allocator, file_path, 1000); + defer testing.allocator.free(contents); + + testing.expectEqualSlices(u8, data, contents); } test "std.Thread.getCurrentId" {