| ... | ... | @@ -104,30 +104,17 @@ pub fn getRandomBytes(buf: []u8) !void { |
| 104 | 104 | Os.linux => while (true) { |
| 105 | 105 | // TODO check libc version and potentially call c.getrandom. |
| 106 | 106 | // 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), |
| 122 | 115 | } |
| 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); |
| 130 | 116 | }, |
| 117 | Os.macosx, Os.ios => return getRandomBytesDevURandom(buf), |
| 131 | 118 | Os.windows => { |
| 132 | 119 | // Call RtlGenRandom() instead of CryptGetRandom() on Windows |
| 133 | 120 | // https://github.com/rust-lang-nursery/rand/issues/111 |
| ... | ... | @@ -151,6 +138,22 @@ pub fn getRandomBytes(buf: []u8) !void { |
| 151 | 138 | } |
| 152 | 139 | } |
| 153 | 140 | |
| 141 | fn 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 | |
| 154 | 157 | test "os.getRandomBytes" { |
| 155 | 158 | var buf_a: [50]u8 = undefined; |
| 156 | 159 | var buf_b: [50]u8 = undefined; |
| ... | ... | @@ -235,8 +238,9 @@ pub const PosixReadError = error{ |
| 235 | 238 | Unexpected, |
| 236 | 239 | }; |
| 237 | 240 | |
| 238 | | /// Calls POSIX read, and keeps trying if it gets interrupted. |
| 239 | | pub 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. |
| 243 | pub fn posixRead(fd: i32, buf: []u8) PosixReadError!usize { |
| 240 | 244 | // Linux can return EINVAL when read amount is > 0x7ffff000 |
| 241 | 245 | // See https://github.com/ziglang/zig/pull/743#issuecomment-363158274 |
| 242 | 246 | const max_buf_len = 0x7ffff000; |
| ... | ... | @@ -249,7 +253,9 @@ pub fn posixRead(fd: i32, buf: []u8) !void { |
| 249 | 253 | switch (err) { |
| 250 | 254 | 0 => { |
| 251 | 255 | index += rc; |
| 252 | | continue; |
| 256 | if (rc == want_to_read) continue; |
| 257 | // Read returned less than buf.len. |
| 258 | return index; |
| 253 | 259 | }, |
| 254 | 260 | posix.EINTR => continue, |
| 255 | 261 | posix.EINVAL => unreachable, |
| ... | ... | @@ -263,6 +269,7 @@ pub fn posixRead(fd: i32, buf: []u8) !void { |
| 263 | 269 | else => return unexpectedErrorPosix(err), |
| 264 | 270 | } |
| 265 | 271 | } |
| 272 | return index; |
| 266 | 273 | } |
| 267 | 274 | |
| 268 | 275 | /// Number of bytes read is returned. Upon reading end-of-file, zero is returned. |
| ... | ... | @@ -962,16 +969,16 @@ pub const DeleteFileError = error{ |
| 962 | 969 | |
| 963 | 970 | pub fn deleteFile(file_path: []const u8) DeleteFileError!void { |
| 964 | 971 | 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); |
| 966 | 974 | } else { |
| 967 | | return deleteFilePosix(file_path); |
| 975 | const file_path_c = try toPosixPath(file_path); |
| 976 | return deleteFileC(&file_path_c); |
| 968 | 977 | } |
| 969 | 978 | } |
| 970 | 979 | |
| 971 | | pub 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) { |
| 980 | pub fn deleteFileW(file_path: [*]const u16) DeleteFileError!void { |
| 981 | if (windows.DeleteFileW(file_path) == 0) { |
| 975 | 982 | const err = windows.GetLastError(); |
| 976 | 983 | switch (err) { |
| 977 | 984 | windows.ERROR.FILE_NOT_FOUND => return error.FileNotFound, |
| ... | ... | @@ -983,50 +990,49 @@ pub fn deleteFileWindows(file_path: []const u8) !void { |
| 983 | 990 | } |
| 984 | 991 | } |
| 985 | 992 | |
| 986 | | pub 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), |
| 993 | pub 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 | } |
| 1004 | 1016 | } |
| 1005 | 1017 | } |
| 1006 | 1018 | |
| 1007 | | pub fn deleteFilePosix(file_path: []const u8) !void { |
| 1008 | | const file_path_c = try toPosixPath(file_path); |
| 1009 | | return deleteFilePosixC(&file_path_c); |
| 1010 | | } |
| 1011 | | |
| 1012 | 1019 | /// Guaranteed to be atomic. However until https://patchwork.kernel.org/patch/9636735/ is |
| 1013 | 1020 | /// merged and readily available, |
| 1014 | 1021 | /// there is a possibility of power loss or application termination leaving temporary files present |
| 1015 | 1022 | /// in the same directory as dest_path. |
| 1016 | 1023 | /// Destination file will have the same mode as the source file. |
| 1017 | | /// TODO investigate if this can work with no allocator |
| 1018 | | pub fn copyFile(allocator: *Allocator, source_path: []const u8, dest_path: []const u8) !void { |
| 1024 | pub fn copyFile(source_path: []const u8, dest_path: []const u8) !void { |
| 1019 | 1025 | var in_file = try os.File.openRead(source_path); |
| 1020 | 1026 | defer in_file.close(); |
| 1021 | 1027 | |
| 1022 | 1028 | const mode = try in_file.mode(); |
| 1023 | 1029 | |
| 1024 | | var atomic_file = try AtomicFile.init(allocator, dest_path, mode); |
| 1030 | var atomic_file = try AtomicFile.init(dest_path, mode); |
| 1025 | 1031 | defer atomic_file.deinit(); |
| 1026 | 1032 | |
| 1027 | 1033 | var buf: [page_size]u8 = undefined; |
| 1028 | 1034 | while (true) { |
| 1029 | | const amt = try in_file.read(buf[0..]); |
| 1035 | const amt = try in_file.readFull(buf[0..]); |
| 1030 | 1036 | try atomic_file.file.write(buf[0..amt]); |
| 1031 | 1037 | if (amt != buf.len) { |
| 1032 | 1038 | return atomic_file.finish(); |
| ... | ... | @@ -1037,12 +1043,11 @@ pub fn copyFile(allocator: *Allocator, source_path: []const u8, dest_path: []con |
| 1037 | 1043 | /// Guaranteed to be atomic. However until https://patchwork.kernel.org/patch/9636735/ is |
| 1038 | 1044 | /// merged and readily available, |
| 1039 | 1045 | /// there is a possibility of power loss or application termination leaving temporary files present |
| 1040 | | /// TODO investigate if this can work with no allocator |
| 1041 | | pub fn copyFileMode(allocator: *Allocator, source_path: []const u8, dest_path: []const u8, mode: File.Mode) !void { |
| 1046 | pub fn copyFileMode(source_path: []const u8, dest_path: []const u8, mode: File.Mode) !void { |
| 1042 | 1047 | var in_file = try os.File.openRead(source_path); |
| 1043 | 1048 | defer in_file.close(); |
| 1044 | 1049 | |
| 1045 | | var atomic_file = try AtomicFile.init(allocator, dest_path, mode); |
| 1050 | var atomic_file = try AtomicFile.init(dest_path, mode); |
| 1046 | 1051 | defer atomic_file.deinit(); |
| 1047 | 1052 | |
| 1048 | 1053 | var buf: [page_size]u8 = undefined; |
| ... | ... | @@ -1056,35 +1061,38 @@ pub fn copyFileMode(allocator: *Allocator, source_path: []const u8, dest_path: [ |
| 1056 | 1061 | } |
| 1057 | 1062 | |
| 1058 | 1063 | pub const AtomicFile = struct { |
| 1059 | | /// TODO investigate if we can make this work with no allocator |
| 1060 | | allocator: *Allocator, |
| 1061 | 1064 | file: os.File, |
| 1062 | | tmp_path: []u8, |
| 1065 | tmp_path_buf: [MAX_PATH_BYTES]u8, |
| 1063 | 1066 | dest_path: []const u8, |
| 1064 | 1067 | finished: bool, |
| 1065 | 1068 | |
| 1069 | const InitError = os.File.OpenError; |
| 1070 | |
| 1066 | 1071 | /// dest_path must remain valid for the lifetime of AtomicFile |
| 1067 | 1072 | /// 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 { |
| 1069 | 1076 | const dirname = os.path.dirname(dest_path); |
| 1070 | | |
| 1071 | 1077 | var rand_buf: [12]u8 = undefined; |
| 1072 | | |
| 1073 | 1078 | 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; |
| 1077 | 1083 | |
| 1078 | 1084 | 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; |
| 1081 | 1087 | } |
| 1082 | 1088 | |
| 1089 | tmp_path_buf[tmp_path_len] = 0; |
| 1090 | |
| 1083 | 1091 | while (true) { |
| 1084 | 1092 | 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); |
| 1086 | 1094 | |
| 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) { |
| 1088 | 1096 | error.PathAlreadyExists => continue, |
| 1089 | 1097 | // TODO zig should figure out that this error set does not include PathAlreadyExists since |
| 1090 | 1098 | // it is handled in the above switch |
| ... | ... | @@ -1092,9 +1100,8 @@ pub const AtomicFile = struct { |
| 1092 | 1100 | }; |
| 1093 | 1101 | |
| 1094 | 1102 | return AtomicFile{ |
| 1095 | | .allocator = allocator, |
| 1096 | 1103 | .file = file, |
| 1097 | | .tmp_path = tmp_path, |
| 1104 | .tmp_path_buf = tmp_path_buf, |
| 1098 | 1105 | .dest_path = dest_path, |
| 1099 | 1106 | .finished = false, |
| 1100 | 1107 | }; |
| ... | ... | @@ -1105,8 +1112,7 @@ pub const AtomicFile = struct { |
| 1105 | 1112 | pub fn deinit(self: *AtomicFile) void { |
| 1106 | 1113 | if (!self.finished) { |
| 1107 | 1114 | self.file.close(); |
| 1108 | | deleteFile(self.tmp_path) catch {}; |
| 1109 | | self.allocator.free(self.tmp_path); |
| 1115 | deleteFileC(&self.tmp_path_buf) catch {}; |
| 1110 | 1116 | self.finished = true; |
| 1111 | 1117 | } |
| 1112 | 1118 | } |
| ... | ... | @@ -1114,15 +1120,25 @@ pub const AtomicFile = struct { |
| 1114 | 1120 | pub fn finish(self: *AtomicFile) !void { |
| 1115 | 1121 | assert(!self.finished); |
| 1116 | 1122 | self.file.close(); |
| 1117 | | try rename(self.tmp_path, self.dest_path); |
| 1118 | | self.allocator.free(self.tmp_path); |
| 1119 | 1123 | 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 | } |
| 1120 | 1134 | } |
| 1121 | 1135 | }; |
| 1122 | 1136 | |
| 1123 | 1137 | pub fn renameC(old_path: [*]const u8, new_path: [*]const u8) !void { |
| 1124 | 1138 | 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); |
| 1126 | 1142 | } else { |
| 1127 | 1143 | const err = posix.getErrno(posix.rename(old_path, new_path)); |
| 1128 | 1144 | switch (err) { |
| ... | ... | @@ -1150,17 +1166,21 @@ pub fn renameC(old_path: [*]const u8, new_path: [*]const u8) !void { |
| 1150 | 1166 | } |
| 1151 | 1167 | } |
| 1152 | 1168 | |
| 1169 | pub 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 | |
| 1153 | 1179 | pub fn rename(old_path: []const u8, new_path: []const u8) !void { |
| 1154 | 1180 | if (is_windows) { |
| 1155 | | const flags = windows.MOVEFILE_REPLACE_EXISTING | windows.MOVEFILE_WRITE_THROUGH; |
| 1156 | 1181 | const old_path_w = try windows_util.sliceToPrefixedFileW(old_path); |
| 1157 | 1182 | 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); |
| 1164 | 1184 | } else { |
| 1165 | 1185 | const old_path_c = try toPosixPath(old_path); |
| 1166 | 1186 | const new_path_c = try toPosixPath(new_path); |