| ... | ... | @@ -734,7 +734,23 @@ pub fn atomicSymLink(allocator: *Allocator, existing_path: []const u8, new_path: |
| 734 | 734 | } |
| 735 | 735 | } |
| 736 | 736 | |
| 737 | | pub fn deleteFile(allocator: *Allocator, file_path: []const u8) !void { |
| 737 | pub const DeleteFileError = error { |
| 738 | FileNotFound, |
| 739 | AccessDenied, |
| 740 | FileBusy, |
| 741 | FileSystem, |
| 742 | IsDir, |
| 743 | SymLinkLoop, |
| 744 | NameTooLong, |
| 745 | NotDir, |
| 746 | SystemResources, |
| 747 | ReadOnlyFileSystem, |
| 748 | OutOfMemory, |
| 749 | |
| 750 | Unexpected, |
| 751 | }; |
| 752 | |
| 753 | pub fn deleteFile(allocator: *Allocator, file_path: []const u8) DeleteFileError!void { |
| 738 | 754 | if (builtin.os == Os.windows) { |
| 739 | 755 | return deleteFileWindows(allocator, file_path); |
| 740 | 756 | } else { |
| ... | ... | @@ -1019,37 +1035,67 @@ pub fn makePath(allocator: *Allocator, full_path: []const u8) !void { |
| 1019 | 1035 | } |
| 1020 | 1036 | } |
| 1021 | 1037 | |
| 1038 | pub const DeleteDirError = error { |
| 1039 | AccessDenied, |
| 1040 | FileBusy, |
| 1041 | SymLinkLoop, |
| 1042 | NameTooLong, |
| 1043 | FileNotFound, |
| 1044 | SystemResources, |
| 1045 | NotDir, |
| 1046 | DirNotEmpty, |
| 1047 | ReadOnlyFileSystem, |
| 1048 | OutOfMemory, |
| 1049 | |
| 1050 | Unexpected, |
| 1051 | }; |
| 1052 | |
| 1022 | 1053 | /// Returns ::error.DirNotEmpty if the directory is not empty. |
| 1023 | 1054 | /// To delete a directory recursively, see ::deleteTree |
| 1024 | | pub fn deleteDir(allocator: *Allocator, dir_path: []const u8) !void { |
| 1055 | pub fn deleteDir(allocator: *Allocator, dir_path: []const u8) DeleteDirError!void { |
| 1025 | 1056 | const path_buf = try allocator.alloc(u8, dir_path.len + 1); |
| 1026 | 1057 | defer allocator.free(path_buf); |
| 1027 | 1058 | |
| 1028 | 1059 | mem.copy(u8, path_buf, dir_path); |
| 1029 | 1060 | path_buf[dir_path.len] = 0; |
| 1030 | 1061 | |
| 1031 | | const err = posix.getErrno(posix.rmdir(path_buf.ptr)); |
| 1032 | | if (err > 0) { |
| 1033 | | return switch (err) { |
| 1034 | | posix.EACCES, posix.EPERM => error.AccessDenied, |
| 1035 | | posix.EBUSY => error.FileBusy, |
| 1036 | | posix.EFAULT, posix.EINVAL => unreachable, |
| 1037 | | posix.ELOOP => error.SymLinkLoop, |
| 1038 | | posix.ENAMETOOLONG => error.NameTooLong, |
| 1039 | | posix.ENOENT => error.FileNotFound, |
| 1040 | | posix.ENOMEM => error.SystemResources, |
| 1041 | | posix.ENOTDIR => error.NotDir, |
| 1042 | | posix.EEXIST, posix.ENOTEMPTY => error.DirNotEmpty, |
| 1043 | | posix.EROFS => error.ReadOnlyFileSystem, |
| 1044 | | else => unexpectedErrorPosix(err), |
| 1045 | | }; |
| 1062 | switch (builtin.os) { |
| 1063 | Os.windows => { |
| 1064 | if (windows.RemoveDirectoryA(path_buf.ptr) == 0) { |
| 1065 | const err = windows.GetLastError(); |
| 1066 | return switch (err) { |
| 1067 | windows.ERROR.PATH_NOT_FOUND => error.FileNotFound, |
| 1068 | windows.ERROR.DIR_NOT_EMPTY => error.DirNotEmpty, |
| 1069 | else => unexpectedErrorWindows(err), |
| 1070 | }; |
| 1071 | } |
| 1072 | }, |
| 1073 | Os.linux, Os.macosx, Os.ios => { |
| 1074 | const err = posix.getErrno(posix.rmdir(path_buf.ptr)); |
| 1075 | if (err > 0) { |
| 1076 | return switch (err) { |
| 1077 | posix.EACCES, posix.EPERM => error.AccessDenied, |
| 1078 | posix.EBUSY => error.FileBusy, |
| 1079 | posix.EFAULT, posix.EINVAL => unreachable, |
| 1080 | posix.ELOOP => error.SymLinkLoop, |
| 1081 | posix.ENAMETOOLONG => error.NameTooLong, |
| 1082 | posix.ENOENT => error.FileNotFound, |
| 1083 | posix.ENOMEM => error.SystemResources, |
| 1084 | posix.ENOTDIR => error.NotDir, |
| 1085 | posix.EEXIST, posix.ENOTEMPTY => error.DirNotEmpty, |
| 1086 | posix.EROFS => error.ReadOnlyFileSystem, |
| 1087 | else => unexpectedErrorPosix(err), |
| 1088 | }; |
| 1089 | } |
| 1090 | }, |
| 1091 | else => @compileError("unimplemented"), |
| 1046 | 1092 | } |
| 1093 | |
| 1047 | 1094 | } |
| 1048 | 1095 | |
| 1049 | 1096 | /// Whether ::full_path describes a symlink, file, or directory, this function |
| 1050 | 1097 | /// removes it. If it cannot be removed because it is a non-empty directory, |
| 1051 | 1098 | /// this function recursively removes its entries and then tries again. |
| 1052 | | /// TODO non-recursive implementation |
| 1053 | 1099 | const DeleteTreeError = error{ |
| 1054 | 1100 | OutOfMemory, |
| 1055 | 1101 | AccessDenied, |
| ... | ... | @@ -1128,7 +1174,7 @@ pub fn deleteTree(allocator: *Allocator, full_path: []const u8) DeleteTreeError! |
| 1128 | 1174 | try full_entry_buf.resize(full_path.len + entry.name.len + 1); |
| 1129 | 1175 | const full_entry_path = full_entry_buf.toSlice(); |
| 1130 | 1176 | mem.copy(u8, full_entry_path, full_path); |
| 1131 | | full_entry_path[full_path.len] = '/'; |
| 1177 | full_entry_path[full_path.len] = path.sep; |
| 1132 | 1178 | mem.copy(u8, full_entry_path[full_path.len + 1 ..], entry.name); |
| 1133 | 1179 | |
| 1134 | 1180 | try deleteTree(allocator, full_entry_path); |
| ... | ... | @@ -1139,16 +1185,29 @@ pub fn deleteTree(allocator: *Allocator, full_path: []const u8) DeleteTreeError! |
| 1139 | 1185 | } |
| 1140 | 1186 | |
| 1141 | 1187 | pub const Dir = struct { |
| 1142 | | fd: i32, |
| 1143 | | darwin_seek: darwin_seek_t, |
| 1188 | handle: Handle, |
| 1144 | 1189 | allocator: *Allocator, |
| 1145 | | buf: []u8, |
| 1146 | | index: usize, |
| 1147 | | end_index: usize, |
| 1148 | 1190 | |
| 1149 | | const darwin_seek_t = switch (builtin.os) { |
| 1150 | | Os.macosx, Os.ios => i64, |
| 1151 | | else => void, |
| 1191 | pub const Handle = switch (builtin.os) { |
| 1192 | Os.macosx, Os.ios => struct { |
| 1193 | fd: i32, |
| 1194 | seek: i64, |
| 1195 | buf: []u8, |
| 1196 | index: usize, |
| 1197 | end_index: usize, |
| 1198 | }, |
| 1199 | Os.linux => struct { |
| 1200 | fd: i32, |
| 1201 | buf: []u8, |
| 1202 | index: usize, |
| 1203 | end_index: usize, |
| 1204 | }, |
| 1205 | Os.windows => struct { |
| 1206 | handle: windows.HANDLE, |
| 1207 | find_file_data: windows.WIN32_FIND_DATAA, |
| 1208 | first: bool, |
| 1209 | }, |
| 1210 | else => @compileError("unimplemented"), |
| 1152 | 1211 | }; |
| 1153 | 1212 | |
| 1154 | 1213 | pub const Entry = struct { |
| ... | ... | @@ -1168,81 +1227,117 @@ pub const Dir = struct { |
| 1168 | 1227 | }; |
| 1169 | 1228 | }; |
| 1170 | 1229 | |
| 1171 | | pub fn open(allocator: *Allocator, dir_path: []const u8) !Dir { |
| 1172 | | const fd = switch (builtin.os) { |
| 1173 | | Os.windows => @compileError("TODO support Dir.open for windows"), |
| 1174 | | Os.linux => try posixOpen(allocator, dir_path, posix.O_RDONLY | posix.O_DIRECTORY | posix.O_CLOEXEC, 0), |
| 1175 | | Os.macosx, Os.ios => try posixOpen( |
| 1176 | | allocator, |
| 1177 | | dir_path, |
| 1178 | | posix.O_RDONLY | posix.O_NONBLOCK | posix.O_DIRECTORY | posix.O_CLOEXEC, |
| 1179 | | 0, |
| 1180 | | ), |
| 1181 | | else => @compileError("Dir.open is not supported for this platform"), |
| 1182 | | }; |
| 1183 | | const darwin_seek_init = switch (builtin.os) { |
| 1184 | | Os.macosx, Os.ios => 0, |
| 1185 | | else => {}, |
| 1186 | | }; |
| 1230 | pub const OpenError = error { |
| 1231 | PathNotFound, |
| 1232 | NotDir, |
| 1233 | AccessDenied, |
| 1234 | FileTooBig, |
| 1235 | IsDir, |
| 1236 | SymLinkLoop, |
| 1237 | ProcessFdQuotaExceeded, |
| 1238 | NameTooLong, |
| 1239 | SystemFdQuotaExceeded, |
| 1240 | NoDevice, |
| 1241 | SystemResources, |
| 1242 | NoSpaceLeft, |
| 1243 | PathAlreadyExists, |
| 1244 | OutOfMemory, |
| 1245 | |
| 1246 | Unexpected, |
| 1247 | }; |
| 1248 | |
| 1249 | pub fn open(allocator: *Allocator, dir_path: []const u8) OpenError!Dir { |
| 1187 | 1250 | return Dir{ |
| 1188 | 1251 | .allocator = allocator, |
| 1189 | | .fd = fd, |
| 1190 | | .darwin_seek = darwin_seek_init, |
| 1191 | | .index = 0, |
| 1192 | | .end_index = 0, |
| 1193 | | .buf = []u8{}, |
| 1252 | .handle = switch (builtin.os) { |
| 1253 | Os.windows => blk: { |
| 1254 | var find_file_data: windows.WIN32_FIND_DATAA = undefined; |
| 1255 | const handle = try windows_util.windowsFindFirstFile(allocator, dir_path, &find_file_data); |
| 1256 | break :blk Handle { |
| 1257 | .handle = handle, |
| 1258 | .find_file_data = find_file_data, // TODO guaranteed copy elision |
| 1259 | .first = true, |
| 1260 | }; |
| 1261 | }, |
| 1262 | Os.macosx, Os.ios => Handle { |
| 1263 | .fd = try posixOpen( |
| 1264 | allocator, |
| 1265 | dir_path, |
| 1266 | posix.O_RDONLY | posix.O_NONBLOCK | posix.O_DIRECTORY | posix.O_CLOEXEC, |
| 1267 | 0, |
| 1268 | ), |
| 1269 | .seek = 0, |
| 1270 | .index = 0, |
| 1271 | .end_index = 0, |
| 1272 | .buf = []u8{}, |
| 1273 | }, |
| 1274 | Os.linux => Handle { |
| 1275 | .fd = try posixOpen(allocator, dir_path, posix.O_RDONLY | posix.O_DIRECTORY | posix.O_CLOEXEC, 0,), |
| 1276 | .index = 0, |
| 1277 | .end_index = 0, |
| 1278 | .buf = []u8{}, |
| 1279 | }, |
| 1280 | else => @compileError("unimplemented"), |
| 1281 | }, |
| 1194 | 1282 | }; |
| 1195 | 1283 | } |
| 1196 | 1284 | |
| 1197 | 1285 | pub fn close(self: *Dir) void { |
| 1198 | | self.allocator.free(self.buf); |
| 1199 | | os.close(self.fd); |
| 1286 | switch (builtin.os) { |
| 1287 | Os.windows => { |
| 1288 | _ = windows.FindClose(self.handle.handle); |
| 1289 | }, |
| 1290 | Os.macosx, Os.ios, Os.linux => { |
| 1291 | self.allocator.free(self.handle.buf); |
| 1292 | os.close(self.handle.fd); |
| 1293 | }, |
| 1294 | else => @compileError("unimplemented"), |
| 1295 | } |
| 1200 | 1296 | } |
| 1201 | 1297 | |
| 1202 | 1298 | /// Memory such as file names referenced in this returned entry becomes invalid |
| 1203 | | /// with subsequent calls to next, as well as when this ::Dir is deinitialized. |
| 1299 | /// with subsequent calls to next, as well as when this `Dir` is deinitialized. |
| 1204 | 1300 | pub fn next(self: *Dir) !?Entry { |
| 1205 | 1301 | switch (builtin.os) { |
| 1206 | 1302 | Os.linux => return self.nextLinux(), |
| 1207 | 1303 | Os.macosx, Os.ios => return self.nextDarwin(), |
| 1208 | 1304 | Os.windows => return self.nextWindows(), |
| 1209 | | else => @compileError("Dir.next not supported on " ++ @tagName(builtin.os)), |
| 1305 | else => @compileError("unimplemented"), |
| 1210 | 1306 | } |
| 1211 | 1307 | } |
| 1212 | 1308 | |
| 1213 | 1309 | fn nextDarwin(self: *Dir) !?Entry { |
| 1214 | 1310 | start_over: while (true) { |
| 1215 | | if (self.index >= self.end_index) { |
| 1216 | | if (self.buf.len == 0) { |
| 1217 | | self.buf = try self.allocator.alloc(u8, page_size); |
| 1311 | if (self.handle.index >= self.handle.end_index) { |
| 1312 | if (self.handle.buf.len == 0) { |
| 1313 | self.handle.buf = try self.allocator.alloc(u8, page_size); |
| 1218 | 1314 | } |
| 1219 | 1315 | |
| 1220 | 1316 | while (true) { |
| 1221 | | const result = posix.getdirentries64(self.fd, self.buf.ptr, self.buf.len, &self.darwin_seek); |
| 1317 | const result = posix.getdirentries64(self.handle.fd, self.handle.buf.ptr, self.handle.buf.len, &self.handle.seek); |
| 1222 | 1318 | const err = posix.getErrno(result); |
| 1223 | 1319 | if (err > 0) { |
| 1224 | 1320 | switch (err) { |
| 1225 | 1321 | posix.EBADF, posix.EFAULT, posix.ENOTDIR => unreachable, |
| 1226 | 1322 | posix.EINVAL => { |
| 1227 | | self.buf = try self.allocator.realloc(u8, self.buf, self.buf.len * 2); |
| 1323 | self.handle.buf = try self.allocator.realloc(u8, self.handle.buf, self.handle.buf.len * 2); |
| 1228 | 1324 | continue; |
| 1229 | 1325 | }, |
| 1230 | 1326 | else => return unexpectedErrorPosix(err), |
| 1231 | 1327 | } |
| 1232 | 1328 | } |
| 1233 | 1329 | if (result == 0) return null; |
| 1234 | | self.index = 0; |
| 1235 | | self.end_index = result; |
| 1330 | self.handle.index = 0; |
| 1331 | self.handle.end_index = result; |
| 1236 | 1332 | break; |
| 1237 | 1333 | } |
| 1238 | 1334 | } |
| 1239 | | const darwin_entry = @ptrCast(*align(1) posix.dirent, &self.buf[self.index]); |
| 1240 | | const next_index = self.index + darwin_entry.d_reclen; |
| 1241 | | self.index = next_index; |
| 1335 | const darwin_entry = @ptrCast(*align(1) posix.dirent, &self.handle.buf[self.handle.index]); |
| 1336 | const next_index = self.handle.index + darwin_entry.d_reclen; |
| 1337 | self.handle.index = next_index; |
| 1242 | 1338 | |
| 1243 | 1339 | const name = @ptrCast([*]u8, &darwin_entry.d_name)[0..darwin_entry.d_namlen]; |
| 1244 | 1340 | |
| 1245 | | // skip . and .. entries |
| 1246 | 1341 | if (mem.eql(u8, name, ".") or mem.eql(u8, name, "..")) { |
| 1247 | 1342 | continue :start_over; |
| 1248 | 1343 | } |
| ... | ... | @@ -1266,38 +1361,59 @@ pub const Dir = struct { |
| 1266 | 1361 | } |
| 1267 | 1362 | |
| 1268 | 1363 | fn nextWindows(self: *Dir) !?Entry { |
| 1269 | | @compileError("TODO support Dir.next for windows"); |
| 1364 | while (true) { |
| 1365 | if (self.handle.first) { |
| 1366 | self.handle.first = false; |
| 1367 | } else { |
| 1368 | if (!try windows_util.windowsFindNextFile(self.handle.handle, &self.handle.find_file_data)) |
| 1369 | return null; |
| 1370 | } |
| 1371 | const name = std.cstr.toSlice(self.handle.find_file_data.cFileName[0..].ptr); |
| 1372 | if (mem.eql(u8, name, ".") or mem.eql(u8, name, "..")) |
| 1373 | continue; |
| 1374 | const kind = blk: { |
| 1375 | const attrs = self.handle.find_file_data.dwFileAttributes; |
| 1376 | if (attrs & windows.FILE_ATTRIBUTE_DIRECTORY != 0) break :blk Entry.Kind.Directory; |
| 1377 | if (attrs & windows.FILE_ATTRIBUTE_REPARSE_POINT != 0) break :blk Entry.Kind.SymLink; |
| 1378 | if (attrs & windows.FILE_ATTRIBUTE_NORMAL != 0) break :blk Entry.Kind.File; |
| 1379 | break :blk Entry.Kind.Unknown; |
| 1380 | }; |
| 1381 | return Entry { |
| 1382 | .name = name, |
| 1383 | .kind = kind, |
| 1384 | }; |
| 1385 | } |
| 1270 | 1386 | } |
| 1271 | 1387 | |
| 1272 | 1388 | fn nextLinux(self: *Dir) !?Entry { |
| 1273 | 1389 | start_over: while (true) { |
| 1274 | | if (self.index >= self.end_index) { |
| 1275 | | if (self.buf.len == 0) { |
| 1276 | | self.buf = try self.allocator.alloc(u8, page_size); |
| 1390 | if (self.handle.index >= self.handle.end_index) { |
| 1391 | if (self.handle.buf.len == 0) { |
| 1392 | self.handle.buf = try self.allocator.alloc(u8, page_size); |
| 1277 | 1393 | } |
| 1278 | 1394 | |
| 1279 | 1395 | while (true) { |
| 1280 | | const result = posix.getdents(self.fd, self.buf.ptr, self.buf.len); |
| 1396 | const result = posix.getdents(self.handle.fd, self.handle.buf.ptr, self.handle.buf.len); |
| 1281 | 1397 | const err = posix.getErrno(result); |
| 1282 | 1398 | if (err > 0) { |
| 1283 | 1399 | switch (err) { |
| 1284 | 1400 | posix.EBADF, posix.EFAULT, posix.ENOTDIR => unreachable, |
| 1285 | 1401 | posix.EINVAL => { |
| 1286 | | self.buf = try self.allocator.realloc(u8, self.buf, self.buf.len * 2); |
| 1402 | self.handle.buf = try self.allocator.realloc(u8, self.handle.buf, self.handle.buf.len * 2); |
| 1287 | 1403 | continue; |
| 1288 | 1404 | }, |
| 1289 | 1405 | else => return unexpectedErrorPosix(err), |
| 1290 | 1406 | } |
| 1291 | 1407 | } |
| 1292 | 1408 | if (result == 0) return null; |
| 1293 | | self.index = 0; |
| 1294 | | self.end_index = result; |
| 1409 | self.handle.index = 0; |
| 1410 | self.handle.end_index = result; |
| 1295 | 1411 | break; |
| 1296 | 1412 | } |
| 1297 | 1413 | } |
| 1298 | | const linux_entry = @ptrCast(*align(1) posix.dirent, &self.buf[self.index]); |
| 1299 | | const next_index = self.index + linux_entry.d_reclen; |
| 1300 | | self.index = next_index; |
| 1414 | const linux_entry = @ptrCast(*align(1) posix.dirent, &self.handle.buf[self.handle.index]); |
| 1415 | const next_index = self.handle.index + linux_entry.d_reclen; |
| 1416 | self.handle.index = next_index; |
| 1301 | 1417 | |
| 1302 | 1418 | const name = cstr.toSlice(@ptrCast([*]u8, &linux_entry.d_name)); |
| 1303 | 1419 | |
| ... | ... | @@ -1306,7 +1422,7 @@ pub const Dir = struct { |
| 1306 | 1422 | continue :start_over; |
| 1307 | 1423 | } |
| 1308 | 1424 | |
| 1309 | | const type_char = self.buf[next_index - 1]; |
| 1425 | const type_char = self.handle.buf[next_index - 1]; |
| 1310 | 1426 | const entry_kind = switch (type_char) { |
| 1311 | 1427 | posix.DT_BLK => Entry.Kind.BlockDevice, |
| 1312 | 1428 | posix.DT_CHR => Entry.Kind.CharacterDevice, |