authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2018-10-01 13:43:25-04:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2018-10-01 13:50:55-04:00
logaf229c1fdc89f69273f69627ab5a0304dab11572
treed49bd02b57017ce751c999cd8e6e970e15ad6ee4
parentd1ec8377d1fcc9874c40e6603f64087f0b310677
signaturelock-open Commit is signed but in an unrecognized format.

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

7 files changed, 201 insertions(+), 139 deletions(-)

std/build.zig+1-1
......@@ -634,7 +634,7 @@ pub const Builder = struct {
634634 warn("Unable to create path {}: {}\n", dirname, @errorName(err));
635635 return err;
636636 };
637 os.copyFileMode(self.allocator, abs_source_path, dest_path, mode) catch |err| {
637 os.copyFileMode(abs_source_path, dest_path, mode) catch |err| {
638638 warn("Unable to copy {} to {}: {}\n", abs_source_path, dest_path, @errorName(err));
639639 return err;
640640 };
std/event/io.zig+20-12
......@@ -21,6 +21,24 @@ pub fn InStream(comptime ReadError: type) type {
2121 return await (async self.readFn(self, buffer) catch unreachable);
2222 }
2323
24 /// Return the number of bytes read. If it is less than buffer.len
25 /// it means end of stream.
26 pub async fn readFull(self: *Self, buffer: []u8) !usize {
27 var index: usize = 0;
28 while (index != buf.len) {
29 const amt_read = try await (async self.read(buf[index..]) catch unreachable);
30 if (amt_read == 0) return index;
31 index += amt_read;
32 }
33 return index;
34 }
35
36 /// Same as `readFull` but end of stream returns `error.EndOfStream`.
37 pub async fn readNoEof(self: *Self, buf: []u8) !void {
38 const amt_read = try await (async self.readFull(buf[index..]) catch unreachable);
39 if (amt_read < buf.len) return error.EndOfStream;
40 }
41
2442 pub async fn readIntLe(self: *Self, comptime T: type) !T {
2543 return await (async self.readInt(builtin.Endian.Little, T) catch unreachable);
2644 }
......@@ -31,24 +49,14 @@ pub fn InStream(comptime ReadError: type) type {
3149
3250 pub async fn readInt(self: *Self, endian: builtin.Endian, comptime T: type) !T {
3351 var bytes: [@sizeOf(T)]u8 = undefined;
34 try await (async self.readFull(bytes[0..]) catch unreachable);
52 try await (async self.readNoEof(bytes[0..]) catch unreachable);
3553 return mem.readInt(bytes, T, endian);
3654 }
3755
38 /// Same as `read` but end of stream returns `error.EndOfStream`.
39 pub async fn readFull(self: *Self, buf: []u8) !void {
40 var index: usize = 0;
41 while (index != buf.len) {
42 const amt_read = try await (async self.read(buf[index..]) catch unreachable);
43 if (amt_read == 0) return error.EndOfStream;
44 index += amt_read;
45 }
46 }
47
4856 pub async fn readStruct(self: *Self, comptime T: type, ptr: *T) !void {
4957 // Only extern and packed structs have defined in-memory layout.
5058 comptime assert(@typeInfo(T).Struct.layout != builtin.TypeInfo.ContainerLayout.Auto);
51 return await (async self.readFull(@sliceToBytes((*[1]T)(ptr)[0..])) catch unreachable);
59 return await (async self.readNoEof(@sliceToBytes((*[1]T)(ptr)[0..])) catch unreachable);
5260 }
5361 };
5462}
std/io.zig+31-7
......@@ -51,7 +51,7 @@ pub fn InStream(comptime ReadError: type) type {
5151 var actual_buf_len: usize = 0;
5252 while (true) {
5353 const dest_slice = buffer.toSlice()[actual_buf_len..];
54 const bytes_read = try self.readFn(self, dest_slice);
54 const bytes_read = try self.readFull(dest_slice);
5555 actual_buf_len += bytes_read;
5656
5757 if (bytes_read != dest_slice.len) {
......@@ -111,14 +111,27 @@ pub fn InStream(comptime ReadError: type) type {
111111 return buf.toOwnedSlice();
112112 }
113113
114 /// Returns the number of bytes read. It may be less than buffer.len.
115 /// If the number of bytes read is 0, it means end of stream.
116 /// End of stream is not an error condition.
117 pub fn read(self: *Self, buffer: []u8) !usize {
118 return self.readFn(self, buffer);
119 }
120
114121 /// Returns the number of bytes read. If the number read is smaller than buf.len, it
115122 /// means the stream reached the end. Reaching the end of a stream is not an error
116123 /// condition.
117 pub fn read(self: *Self, buffer: []u8) !usize {
118 return self.readFn(self, buffer);
124 pub fn readFull(self: *Self, buffer: []u8) !usize {
125 var index: usize = 0;
126 while (index != buffer.len) {
127 const amt = try self.read(buffer[index..]);
128 if (amt == 0) return index;
129 index += amt;
130 }
131 return index;
119132 }
120133
121 /// Same as `read` but end of stream returns `error.EndOfStream`.
134 /// Same as `readFull` but end of stream returns `error.EndOfStream`.
122135 pub fn readNoEof(self: *Self, buf: []u8) !void {
123136 const amt_read = try self.read(buf);
124137 if (amt_read < buf.len) return error.EndOfStream;
......@@ -136,6 +149,11 @@ pub fn InStream(comptime ReadError: type) type {
136149 return @bitCast(i8, try self.readByte());
137150 }
138151
152 /// Reads a native-endian integer
153 pub fn readIntNe(self: *Self, comptime T: type) !T {
154 return self.readInt(builtin.endian, T);
155 }
156
139157 pub fn readIntLe(self: *Self, comptime T: type) !T {
140158 return self.readInt(builtin.Endian.Little, T);
141159 }
......@@ -202,6 +220,11 @@ pub fn OutStream(comptime WriteError: type) type {
202220 }
203221 }
204222
223 /// Write a native-endian integer.
224 pub fn writeIntNe(self: *Self, comptime T: type, value: T) !void {
225 return self.writeInt(builtin.endian, T, value);
226 }
227
205228 pub fn writeIntLe(self: *Self, comptime T: type, value: T) !void {
206229 return self.writeInt(builtin.Endian.Little, T, value);
207230 }
......@@ -537,6 +560,7 @@ pub const BufferedAtomicFile = struct {
537560 atomic_file: os.AtomicFile,
538561 file_stream: os.File.OutStream,
539562 buffered_stream: BufferedOutStream(os.File.WriteError),
563 allocator: *mem.Allocator,
540564
541565 pub fn create(allocator: *mem.Allocator, dest_path: []const u8) !*BufferedAtomicFile {
542566 // TODO with well defined copy elision we don't need this allocation
......@@ -544,10 +568,11 @@ pub const BufferedAtomicFile = struct {
544568 .atomic_file = undefined,
545569 .file_stream = undefined,
546570 .buffered_stream = undefined,
571 .allocator = allocator,
547572 });
548573 errdefer allocator.destroy(self);
549574
550 self.atomic_file = try os.AtomicFile.init(allocator, dest_path, os.File.default_mode);
575 self.atomic_file = try os.AtomicFile.init(dest_path, os.File.default_mode);
551576 errdefer self.atomic_file.deinit();
552577
553578 self.file_stream = self.atomic_file.file.outStream();
......@@ -557,9 +582,8 @@ pub const BufferedAtomicFile = struct {
557582
558583 /// always call destroy, even after successful finish()
559584 pub fn destroy(self: *BufferedAtomicFile) void {
560 const allocator = self.atomic_file.allocator;
561585 self.atomic_file.deinit();
562 allocator.destroy(self);
586 self.allocator.destroy(self);
563587 }
564588
565589 pub fn finish(self: *BufferedAtomicFile) !void {
std/os/child_process.zig+4-6
......@@ -792,13 +792,11 @@ fn forkChildErrReport(fd: i32, err: ChildProcess.SpawnError) noreturn {
792792const ErrInt = @IntType(false, @sizeOf(error) * 8);
793793
794794fn writeIntFd(fd: i32, value: ErrInt) !void {
795 var bytes: [@sizeOf(ErrInt)]u8 = undefined;
796 mem.writeInt(bytes[0..], value, builtin.endian);
797 os.posixWrite(fd, bytes[0..]) catch return error.SystemResources;
795 const stream = &os.File.openHandle(fd).outStream().stream;
796 stream.writeIntNe(ErrInt, value) catch return error.SystemResources;
798797}
799798
800799fn readIntFd(fd: i32) !ErrInt {
801 var bytes: [@sizeOf(ErrInt)]u8 = undefined;
802 os.posixRead(fd, bytes[0..]) catch return error.SystemResources;
803 return mem.readInt(bytes[0..], ErrInt, builtin.endian);
800 const stream = &os.File.openHandle(fd).inStream().stream;
801 return stream.readIntNe(ErrInt) catch return error.SystemResources;
804802}
std/os/file.zig+16-25
......@@ -102,12 +102,24 @@ pub const File = struct {
102102 /// If a file already exists in the destination this returns OpenError.PathAlreadyExists
103103 /// Call close to clean up.
104104 pub fn openWriteNoClobber(path: []const u8, file_mode: Mode) OpenError!File {
105 if (is_posix) {
106 const path_c = try os.toPosixPath(path);
107 return openWriteNoClobberC(path_c, file_mode);
108 } else if (is_windows) {
109 const path_w = try windows_util.sliceToPrefixedFileW(path);
110 return openWriteNoClobberW(&path_w, file_mode);
111 } else {
112 @compileError("TODO implement openWriteMode for this OS");
113 }
114 }
115
116 pub fn openWriteNoClobberC(path: [*]const u8, file_mode: Mode) OpenError!File {
105117 if (is_posix) {
106118 const flags = posix.O_LARGEFILE | posix.O_WRONLY | posix.O_CREAT | posix.O_CLOEXEC | posix.O_EXCL;
107 const fd = try os.posixOpen(path, flags, file_mode);
119 const fd = try os.posixOpenC(path, flags, file_mode);
108120 return openHandle(fd);
109121 } else if (is_windows) {
110 const path_w = try windows_util.sliceToPrefixedFileW(path);
122 const path_w = try windows_util.cStrToPrefixedFileW(path);
111123 return openWriteNoClobberW(&path_w, file_mode);
112124 } else {
113125 @compileError("TODO implement openWriteMode for this OS");
......@@ -369,28 +381,7 @@ pub const File = struct {
369381
370382 pub fn read(self: File, buffer: []u8) ReadError!usize {
371383 if (is_posix) {
372 var index: usize = 0;
373 while (index < buffer.len) {
374 const amt_read = posix.read(self.handle, buffer.ptr + index, buffer.len - index);
375 const read_err = posix.getErrno(amt_read);
376 if (read_err > 0) {
377 switch (read_err) {
378 posix.EINTR => continue,
379 posix.EINVAL => unreachable,
380 posix.EFAULT => unreachable,
381 posix.EAGAIN => unreachable,
382 posix.EBADF => unreachable, // always a race condition
383 posix.EIO => return error.InputOutput,
384 posix.EISDIR => return error.IsDir,
385 posix.ENOBUFS => return error.SystemResources,
386 posix.ENOMEM => return error.SystemResources,
387 else => return os.unexpectedErrorPosix(read_err),
388 }
389 }
390 if (amt_read == 0) return index;
391 index += amt_read;
392 }
393 return index;
384 return os.posixRead(self.handle, buffer);
394385 } else if (is_windows) {
395386 var index: usize = 0;
396387 while (index < buffer.len) {
......@@ -409,7 +400,7 @@ pub const File = struct {
409400 }
410401 return index;
411402 } else {
412 unreachable;
403 @compileError("Unsupported OS");
413404 }
414405 }
415406
std/os/index.zig+108-88
......@@ -104,30 +104,17 @@ pub fn getRandomBytes(buf: []u8) !void {
104104 Os.linux => while (true) {
105105 // TODO check libc version and potentially call c.getrandom.
106106 // See #397
107 const err = posix.getErrno(posix.getrandom(buf.ptr, buf.len, 0));
108 if (err > 0) {
109 switch (err) {
110 posix.EINVAL => unreachable,
111 posix.EFAULT => unreachable,
112 posix.EINTR => continue,
113 posix.ENOSYS => {
114 const fd = try posixOpenC(c"/dev/urandom", posix.O_RDONLY | posix.O_CLOEXEC, 0);
115 defer close(fd);
116
117 try posixRead(fd, buf);
118 return;
119 },
120 else => return unexpectedErrorPosix(err),
121 }
107 const errno = posix.getErrno(posix.getrandom(buf.ptr, buf.len, 0));
108 switch (errno) {
109 0 => return,
110 posix.EINVAL => unreachable,
111 posix.EFAULT => unreachable,
112 posix.EINTR => continue,
113 posix.ENOSYS => return getRandomBytesDevURandom(buf),
114 else => return unexpectedErrorPosix(errno),
122115 }
123 return;
124 },
125 Os.macosx, Os.ios => {
126 const fd = try posixOpenC(c"/dev/urandom", posix.O_RDONLY | posix.O_CLOEXEC, 0);
127 defer close(fd);
128
129 try posixRead(fd, buf);
130116 },
117 Os.macosx, Os.ios => return getRandomBytesDevURandom(buf),
131118 Os.windows => {
132119 // Call RtlGenRandom() instead of CryptGetRandom() on Windows
133120 // https://github.com/rust-lang-nursery/rand/issues/111
......@@ -151,6 +138,22 @@ pub fn getRandomBytes(buf: []u8) !void {
151138 }
152139}
153140
141fn getRandomBytesDevURandom(buf: []u8) !void {
142 const fd = try posixOpenC(c"/dev/urandom", posix.O_RDONLY | posix.O_CLOEXEC, 0);
143 defer close(fd);
144
145 const stream = &File.openHandle(fd).inStream().stream;
146 stream.readNoEof(buf) catch |err| switch (err) {
147 error.EndOfStream => unreachable,
148 error.OperationAborted => unreachable,
149 error.BrokenPipe => unreachable,
150 error.Unexpected => return error.Unexpected,
151 error.InputOutput => return error.Unexpected,
152 error.SystemResources => return error.Unexpected,
153 error.IsDir => unreachable,
154 };
155}
156
154157test "os.getRandomBytes" {
155158 var buf_a: [50]u8 = undefined;
156159 var buf_b: [50]u8 = undefined;
......@@ -235,8 +238,9 @@ pub const PosixReadError = error{
235238 Unexpected,
236239};
237240
238/// Calls POSIX read, and keeps trying if it gets interrupted.
239pub fn posixRead(fd: i32, buf: []u8) !void {
241/// Returns the number of bytes that were read, which can be less than
242/// buf.len. If 0 bytes were read, that means EOF.
243pub fn posixRead(fd: i32, buf: []u8) PosixReadError!usize {
240244 // Linux can return EINVAL when read amount is > 0x7ffff000
241245 // See https://github.com/ziglang/zig/pull/743#issuecomment-363158274
242246 const max_buf_len = 0x7ffff000;
......@@ -249,7 +253,9 @@ pub fn posixRead(fd: i32, buf: []u8) !void {
249253 switch (err) {
250254 0 => {
251255 index += rc;
252 continue;
256 if (rc == want_to_read) continue;
257 // Read returned less than buf.len.
258 return index;
253259 },
254260 posix.EINTR => continue,
255261 posix.EINVAL => unreachable,
......@@ -263,6 +269,7 @@ pub fn posixRead(fd: i32, buf: []u8) !void {
263269 else => return unexpectedErrorPosix(err),
264270 }
265271 }
272 return index;
266273}
267274
268275/// Number of bytes read is returned. Upon reading end-of-file, zero is returned.
......@@ -962,16 +969,16 @@ pub const DeleteFileError = error{
962969
963970pub fn deleteFile(file_path: []const u8) DeleteFileError!void {
964971 if (builtin.os == Os.windows) {
965 return deleteFileWindows(file_path);
972 const file_path_w = try windows_util.sliceToPrefixedFileW(file_path);
973 return deleteFileW(&file_path_w);
966974 } else {
967 return deleteFilePosix(file_path);
975 const file_path_c = try toPosixPath(file_path);
976 return deleteFileC(&file_path_c);
968977 }
969978}
970979
971pub fn deleteFileWindows(file_path: []const u8) !void {
972 const file_path_w = try windows_util.sliceToPrefixedFileW(file_path);
973
974 if (windows.DeleteFileW(&file_path_w) == 0) {
980pub fn deleteFileW(file_path: [*]const u16) DeleteFileError!void {
981 if (windows.DeleteFileW(file_path) == 0) {
975982 const err = windows.GetLastError();
976983 switch (err) {
977984 windows.ERROR.FILE_NOT_FOUND => return error.FileNotFound,
......@@ -983,50 +990,49 @@ pub fn deleteFileWindows(file_path: []const u8) !void {
983990 }
984991}
985992
986pub fn deleteFilePosixC(file_path: [*]const u8) !void {
987 const err = posix.getErrno(posix.unlink(file_path));
988 switch (err) {
989 0 => return,
990 posix.EACCES => return error.AccessDenied,
991 posix.EPERM => return error.AccessDenied,
992 posix.EBUSY => return error.FileBusy,
993 posix.EFAULT => unreachable,
994 posix.EINVAL => unreachable,
995 posix.EIO => return error.FileSystem,
996 posix.EISDIR => return error.IsDir,
997 posix.ELOOP => return error.SymLinkLoop,
998 posix.ENAMETOOLONG => return error.NameTooLong,
999 posix.ENOENT => return error.FileNotFound,
1000 posix.ENOTDIR => return error.NotDir,
1001 posix.ENOMEM => return error.SystemResources,
1002 posix.EROFS => return error.ReadOnlyFileSystem,
1003 else => return unexpectedErrorPosix(err),
993pub fn deleteFileC(file_path: [*]const u8) DeleteFileError!void {
994 if (is_windows) {
995 const file_path_w = try windows_util.cStrToPrefixedFileW(file_path);
996 return deleteFileW(&file_path_w);
997 } else {
998 const err = posix.getErrno(posix.unlink(file_path));
999 switch (err) {
1000 0 => return,
1001 posix.EACCES => return error.AccessDenied,
1002 posix.EPERM => return error.AccessDenied,
1003 posix.EBUSY => return error.FileBusy,
1004 posix.EFAULT => unreachable,
1005 posix.EINVAL => unreachable,
1006 posix.EIO => return error.FileSystem,
1007 posix.EISDIR => return error.IsDir,
1008 posix.ELOOP => return error.SymLinkLoop,
1009 posix.ENAMETOOLONG => return error.NameTooLong,
1010 posix.ENOENT => return error.FileNotFound,
1011 posix.ENOTDIR => return error.NotDir,
1012 posix.ENOMEM => return error.SystemResources,
1013 posix.EROFS => return error.ReadOnlyFileSystem,
1014 else => return unexpectedErrorPosix(err),
1015 }
10041016 }
10051017}
10061018
1007pub fn deleteFilePosix(file_path: []const u8) !void {
1008 const file_path_c = try toPosixPath(file_path);
1009 return deleteFilePosixC(&file_path_c);
1010}
1011
10121019/// Guaranteed to be atomic. However until https://patchwork.kernel.org/patch/9636735/ is
10131020/// merged and readily available,
10141021/// there is a possibility of power loss or application termination leaving temporary files present
10151022/// in the same directory as dest_path.
10161023/// Destination file will have the same mode as the source file.
1017/// TODO investigate if this can work with no allocator
1018pub fn copyFile(allocator: *Allocator, source_path: []const u8, dest_path: []const u8) !void {
1024pub fn copyFile(source_path: []const u8, dest_path: []const u8) !void {
10191025 var in_file = try os.File.openRead(source_path);
10201026 defer in_file.close();
10211027
10221028 const mode = try in_file.mode();
10231029
1024 var atomic_file = try AtomicFile.init(allocator, dest_path, mode);
1030 var atomic_file = try AtomicFile.init(dest_path, mode);
10251031 defer atomic_file.deinit();
10261032
10271033 var buf: [page_size]u8 = undefined;
10281034 while (true) {
1029 const amt = try in_file.read(buf[0..]);
1035 const amt = try in_file.readFull(buf[0..]);
10301036 try atomic_file.file.write(buf[0..amt]);
10311037 if (amt != buf.len) {
10321038 return atomic_file.finish();
......@@ -1037,12 +1043,11 @@ pub fn copyFile(allocator: *Allocator, source_path: []const u8, dest_path: []con
10371043/// Guaranteed to be atomic. However until https://patchwork.kernel.org/patch/9636735/ is
10381044/// merged and readily available,
10391045/// there is a possibility of power loss or application termination leaving temporary files present
1040/// TODO investigate if this can work with no allocator
1041pub fn copyFileMode(allocator: *Allocator, source_path: []const u8, dest_path: []const u8, mode: File.Mode) !void {
1046pub fn copyFileMode(source_path: []const u8, dest_path: []const u8, mode: File.Mode) !void {
10421047 var in_file = try os.File.openRead(source_path);
10431048 defer in_file.close();
10441049
1045 var atomic_file = try AtomicFile.init(allocator, dest_path, mode);
1050 var atomic_file = try AtomicFile.init(dest_path, mode);
10461051 defer atomic_file.deinit();
10471052
10481053 var buf: [page_size]u8 = undefined;
......@@ -1056,35 +1061,38 @@ pub fn copyFileMode(allocator: *Allocator, source_path: []const u8, dest_path: [
10561061}
10571062
10581063pub const AtomicFile = struct {
1059 /// TODO investigate if we can make this work with no allocator
1060 allocator: *Allocator,
10611064 file: os.File,
1062 tmp_path: []u8,
1065 tmp_path_buf: [MAX_PATH_BYTES]u8,
10631066 dest_path: []const u8,
10641067 finished: bool,
10651068
1069 const InitError = os.File.OpenError;
1070
10661071 /// dest_path must remain valid for the lifetime of AtomicFile
10671072 /// call finish to atomically replace dest_path with contents
1068 pub fn init(allocator: *Allocator, dest_path: []const u8, mode: File.Mode) !AtomicFile {
1073 /// TODO once we have null terminated pointers, use the
1074 /// openWriteNoClobberN function
1075 pub fn init(dest_path: []const u8, mode: File.Mode) InitError!AtomicFile {
10691076 const dirname = os.path.dirname(dest_path);
1070
10711077 var rand_buf: [12]u8 = undefined;
1072
10731078 const dirname_component_len = if (dirname) |d| d.len + 1 else 0;
1074 const tmp_path = try allocator.alloc(u8, dirname_component_len +
1075 base64.Base64Encoder.calcSize(rand_buf.len));
1076 errdefer allocator.free(tmp_path);
1079 const encoded_rand_len = comptime base64.Base64Encoder.calcSize(rand_buf.len);
1080 const tmp_path_len = dirname_component_len + encoded_rand_len;
1081 var tmp_path_buf: [MAX_PATH_BYTES]u8 = undefined;
1082 if (tmp_path_len >= tmp_path_buf.len) return error.NameTooLong;
10771083
10781084 if (dirname) |dir| {
1079 mem.copy(u8, tmp_path[0..], dir);
1080 tmp_path[dir.len] = os.path.sep;
1085 mem.copy(u8, tmp_path_buf[0..], dir);
1086 tmp_path_buf[dir.len] = os.path.sep;
10811087 }
10821088
1089 tmp_path_buf[tmp_path_len] = 0;
1090
10831091 while (true) {
10841092 try getRandomBytes(rand_buf[0..]);
1085 b64_fs_encoder.encode(tmp_path[dirname_component_len..], rand_buf);
1093 b64_fs_encoder.encode(tmp_path_buf[dirname_component_len..tmp_path_len], rand_buf);
10861094
1087 const file = os.File.openWriteNoClobber(tmp_path, mode) catch |err| switch (err) {
1095 const file = os.File.openWriteNoClobberC(&tmp_path_buf, mode) catch |err| switch (err) {
10881096 error.PathAlreadyExists => continue,
10891097 // TODO zig should figure out that this error set does not include PathAlreadyExists since
10901098 // it is handled in the above switch
......@@ -1092,9 +1100,8 @@ pub const AtomicFile = struct {
10921100 };
10931101
10941102 return AtomicFile{
1095 .allocator = allocator,
10961103 .file = file,
1097 .tmp_path = tmp_path,
1104 .tmp_path_buf = tmp_path_buf,
10981105 .dest_path = dest_path,
10991106 .finished = false,
11001107 };
......@@ -1105,8 +1112,7 @@ pub const AtomicFile = struct {
11051112 pub fn deinit(self: *AtomicFile) void {
11061113 if (!self.finished) {
11071114 self.file.close();
1108 deleteFile(self.tmp_path) catch {};
1109 self.allocator.free(self.tmp_path);
1115 deleteFileC(&self.tmp_path_buf) catch {};
11101116 self.finished = true;
11111117 }
11121118 }
......@@ -1114,15 +1120,25 @@ pub const AtomicFile = struct {
11141120 pub fn finish(self: *AtomicFile) !void {
11151121 assert(!self.finished);
11161122 self.file.close();
1117 try rename(self.tmp_path, self.dest_path);
1118 self.allocator.free(self.tmp_path);
11191123 self.finished = true;
1124 if (is_posix) {
1125 const dest_path_c = try toPosixPath(self.dest_path);
1126 return renameC(&self.tmp_path_buf, &dest_path_c);
1127 } else if (is_windows) {
1128 const dest_path_w = try windows_util.sliceToPrefixedFileW(self.dest_path);
1129 const tmp_path_w = try windows_util.cStrToPrefixedFileW(&self.tmp_path_buf);
1130 return renameW(&tmp_path_w, &dest_path_w);
1131 } else {
1132 @compileError("Unsupported OS");
1133 }
11201134 }
11211135};
11221136
11231137pub fn renameC(old_path: [*]const u8, new_path: [*]const u8) !void {
11241138 if (is_windows) {
1125 @compileError("TODO implement for windows");
1139 const old_path_w = try windows_util.cStrToPrefixedFileW(old_path);
1140 const new_path_w = try windows_util.cStrToPrefixedFileW(new_path);
1141 return renameW(&old_path_w, &new_path_w);
11261142 } else {
11271143 const err = posix.getErrno(posix.rename(old_path, new_path));
11281144 switch (err) {
......@@ -1150,17 +1166,21 @@ pub fn renameC(old_path: [*]const u8, new_path: [*]const u8) !void {
11501166 }
11511167}
11521168
1169pub fn renameW(old_path: [*]const u16, new_path: [*]const u16) !void {
1170 const flags = windows.MOVEFILE_REPLACE_EXISTING | windows.MOVEFILE_WRITE_THROUGH;
1171 if (windows.MoveFileExW(old_path, new_path, flags) == 0) {
1172 const err = windows.GetLastError();
1173 switch (err) {
1174 else => return unexpectedErrorWindows(err),
1175 }
1176 }
1177}
1178
11531179pub fn rename(old_path: []const u8, new_path: []const u8) !void {
11541180 if (is_windows) {
1155 const flags = windows.MOVEFILE_REPLACE_EXISTING | windows.MOVEFILE_WRITE_THROUGH;
11561181 const old_path_w = try windows_util.sliceToPrefixedFileW(old_path);
11571182 const new_path_w = try windows_util.sliceToPrefixedFileW(new_path);
1158 if (windows.MoveFileExW(&old_path_w, &new_path_w, flags) == 0) {
1159 const err = windows.GetLastError();
1160 switch (err) {
1161 else => return unexpectedErrorWindows(err),
1162 }
1163 }
1183 return renameW(&old_path_w, &new_path_w);
11641184 } else {
11651185 const old_path_c = try toPosixPath(old_path);
11661186 const new_path_c = try toPosixPath(new_path);
std/os/test.zig+21
......@@ -2,6 +2,7 @@ const std = @import("../index.zig");
22const os = std.os;
33const assert = std.debug.assert;
44const io = std.io;
5const mem = std.mem;
56
67const a = std.debug.global_allocator;
78
......@@ -80,3 +81,23 @@ test "cpu count" {
8081 const cpu_count = try std.os.cpuCount(a);
8182 assert(cpu_count >= 1);
8283}
84
85test "AtomicFile" {
86 var buffer: [1024]u8 = undefined;
87 const allocator = &std.heap.FixedBufferAllocator.init(buffer[0..]).allocator;
88 const test_out_file = "tmp_atomic_file_test_dest.txt";
89 const test_content =
90 \\ hello!
91 \\ this is a test file
92 ;
93 {
94 var af = try os.AtomicFile.init(test_out_file, os.File.default_mode);
95 defer af.deinit();
96 try af.file.write(test_content);
97 try af.finish();
98 }
99 const content = try io.readFileAlloc(allocator, test_out_file);
100 assert(mem.eql(u8, content, test_content));
101
102 try os.deleteFile(test_out_file);
103}