| ... | ... | @@ -335,444 +335,708 @@ pub fn deleteDirW(dir_path: [*]const u16) !void { |
| 335 | 335 | return os.rmdirW(dir_path); |
| 336 | 336 | } |
| 337 | 337 | |
| 338 | | const DeleteTreeError = error{ |
| 339 | | OutOfMemory, |
| 340 | | AccessDenied, |
| 341 | | FileTooBig, |
| 342 | | IsDir, |
| 343 | | SymLinkLoop, |
| 344 | | ProcessFdQuotaExceeded, |
| 345 | | NameTooLong, |
| 346 | | SystemFdQuotaExceeded, |
| 347 | | NoDevice, |
| 348 | | SystemResources, |
| 349 | | NoSpaceLeft, |
| 350 | | PathAlreadyExists, |
| 351 | | ReadOnlyFileSystem, |
| 352 | | NotDir, |
| 353 | | FileNotFound, |
| 354 | | FileSystem, |
| 355 | | FileBusy, |
| 356 | | DirNotEmpty, |
| 357 | | DeviceBusy, |
| 358 | | |
| 359 | | /// On Windows, file paths must be valid Unicode. |
| 360 | | InvalidUtf8, |
| 361 | | |
| 362 | | /// On Windows, file paths cannot contain these characters: |
| 363 | | /// '/', '*', '?', '"', '<', '>', '|' |
| 364 | | BadPathName, |
| 365 | | |
| 366 | | Unexpected, |
| 367 | | }; |
| 368 | | |
| 369 | | /// Whether `full_path` describes a symlink, file, or directory, this function |
| 370 | | /// removes it. If it cannot be removed because it is a non-empty directory, |
| 371 | | /// this function recursively removes its entries and then tries again. |
| 372 | | /// TODO determine if we can remove the allocator requirement |
| 373 | | /// https://github.com/ziglang/zig/issues/2886 |
| 374 | | pub fn deleteTree(allocator: *Allocator, full_path: []const u8) DeleteTreeError!void { |
| 375 | | start_over: while (true) { |
| 376 | | var got_access_denied = false; |
| 377 | | // First, try deleting the item as a file. This way we don't follow sym links. |
| 378 | | if (deleteFile(full_path)) { |
| 379 | | return; |
| 380 | | } else |err| switch (err) { |
| 381 | | error.FileNotFound => return, |
| 382 | | error.IsDir => {}, |
| 383 | | error.AccessDenied => got_access_denied = true, |
| 384 | | |
| 385 | | error.InvalidUtf8, |
| 386 | | error.SymLinkLoop, |
| 387 | | error.NameTooLong, |
| 388 | | error.SystemResources, |
| 389 | | error.ReadOnlyFileSystem, |
| 390 | | error.NotDir, |
| 391 | | error.FileSystem, |
| 392 | | error.FileBusy, |
| 393 | | error.BadPathName, |
| 394 | | error.Unexpected, |
| 395 | | => return err, |
| 396 | | } |
| 397 | | { |
| 398 | | var dir = Dir.open(allocator, full_path) catch |err| switch (err) { |
| 399 | | error.NotDir => { |
| 400 | | if (got_access_denied) { |
| 401 | | return error.AccessDenied; |
| 402 | | } |
| 403 | | continue :start_over; |
| 404 | | }, |
| 405 | | |
| 406 | | error.OutOfMemory, |
| 407 | | error.AccessDenied, |
| 408 | | error.FileTooBig, |
| 409 | | error.IsDir, |
| 410 | | error.SymLinkLoop, |
| 411 | | error.ProcessFdQuotaExceeded, |
| 412 | | error.NameTooLong, |
| 413 | | error.SystemFdQuotaExceeded, |
| 414 | | error.NoDevice, |
| 415 | | error.FileNotFound, |
| 416 | | error.SystemResources, |
| 417 | | error.NoSpaceLeft, |
| 418 | | error.PathAlreadyExists, |
| 419 | | error.Unexpected, |
| 420 | | error.InvalidUtf8, |
| 421 | | error.BadPathName, |
| 422 | | error.DeviceBusy, |
| 423 | | => return err, |
| 424 | | }; |
| 425 | | defer dir.close(); |
| 426 | | |
| 427 | | var full_entry_buf = std.ArrayList(u8).init(allocator); |
| 428 | | defer full_entry_buf.deinit(); |
| 429 | | |
| 430 | | while (try dir.next()) |entry| { |
| 431 | | try full_entry_buf.resize(full_path.len + entry.name.len + 1); |
| 432 | | const full_entry_path = full_entry_buf.toSlice(); |
| 433 | | mem.copy(u8, full_entry_path, full_path); |
| 434 | | full_entry_path[full_path.len] = path.sep; |
| 435 | | mem.copy(u8, full_entry_path[full_path.len + 1 ..], entry.name); |
| 436 | | |
| 437 | | try deleteTree(allocator, full_entry_path); |
| 438 | | } |
| 439 | | } |
| 440 | | return deleteDir(full_path); |
| 338 | /// Removes a symlink, file, or directory. |
| 339 | /// If `full_path` is relative, this is equivalent to `Dir.deleteTree` with the |
| 340 | /// current working directory as the open directory handle. |
| 341 | /// If `full_path` is absolute, this is equivalent to `Dir.deleteTree` with the |
| 342 | /// base directory. |
| 343 | pub fn deleteTree(full_path: []const u8) !void { |
| 344 | if (path.isAbsolute(full_path)) { |
| 345 | const dirname = path.dirname(full_path) orelse return error{ |
| 346 | /// Attempt to remove the root file system path. |
| 347 | /// This error is unreachable if `full_path` is relative. |
| 348 | CannotDeleteRootDirectory, |
| 349 | }.CannotDeleteRootDirectory; |
| 350 | |
| 351 | var dir = try Dir.open(dirname); |
| 352 | defer dir.close(); |
| 353 | |
| 354 | return dir.deleteTree(path.basename(full_path)); |
| 355 | } else { |
| 356 | return Dir.cwd().deleteTree(full_path); |
| 441 | 357 | } |
| 442 | 358 | } |
| 443 | 359 | |
| 444 | | /// TODO: separate this API into the one that opens directory handles to then subsequently open |
| 445 | | /// files, and into the one that reads files from an open directory handle. |
| 446 | 360 | pub const Dir = struct { |
| 447 | | handle: Handle, |
| 448 | | allocator: *Allocator, |
| 361 | fd: os.fd_t, |
| 362 | |
| 363 | pub const Entry = struct { |
| 364 | name: []const u8, |
| 365 | kind: Kind, |
| 449 | 366 | |
| 450 | | pub const Handle = switch (builtin.os) { |
| 367 | pub const Kind = enum { |
| 368 | BlockDevice, |
| 369 | CharacterDevice, |
| 370 | Directory, |
| 371 | NamedPipe, |
| 372 | SymLink, |
| 373 | File, |
| 374 | UnixDomainSocket, |
| 375 | Whiteout, |
| 376 | Unknown, |
| 377 | }; |
| 378 | }; |
| 379 | |
| 380 | const IteratorError = error{AccessDenied} || os.UnexpectedError; |
| 381 | |
| 382 | pub const Iterator = switch (builtin.os) { |
| 451 | 383 | .macosx, .ios, .freebsd, .netbsd => struct { |
| 452 | | fd: i32, |
| 384 | dir: Dir, |
| 453 | 385 | seek: i64, |
| 454 | | buf: []u8, |
| 386 | buf: [8192]u8, // TODO align(@alignOf(os.dirent)), |
| 455 | 387 | index: usize, |
| 456 | 388 | end_index: usize, |
| 389 | |
| 390 | const Self = @This(); |
| 391 | |
| 392 | pub const Error = IteratorError; |
| 393 | |
| 394 | /// Memory such as file names referenced in this returned entry becomes invalid |
| 395 | /// with subsequent calls to `next`, as well as when this `Dir` is deinitialized. |
| 396 | pub fn next(self: *Self) Error!?Entry { |
| 397 | switch (builtin.os) { |
| 398 | .macosx, .ios => return self.nextDarwin(), |
| 399 | .freebsd, .netbsd => return self.nextBsd(), |
| 400 | else => @compileError("unimplemented"), |
| 401 | } |
| 402 | } |
| 403 | |
| 404 | fn nextDarwin(self: *Self) !?Entry { |
| 405 | start_over: while (true) { |
| 406 | if (self.index >= self.end_index) { |
| 407 | const rc = os.system.__getdirentries64( |
| 408 | self.dir.fd, |
| 409 | &self.buf, |
| 410 | self.buf.len, |
| 411 | &self.seek, |
| 412 | ); |
| 413 | if (rc == 0) return null; |
| 414 | if (rc < 0) { |
| 415 | switch (os.errno(rc)) { |
| 416 | os.EBADF => unreachable, |
| 417 | os.EFAULT => unreachable, |
| 418 | os.ENOTDIR => unreachable, |
| 419 | os.EINVAL => unreachable, |
| 420 | else => |err| return os.unexpectedErrno(err), |
| 421 | } |
| 422 | } |
| 423 | self.index = 0; |
| 424 | self.end_index = @intCast(usize, rc); |
| 425 | } |
| 426 | const darwin_entry = @ptrCast(*align(1) os.dirent, &self.buf[self.index]); |
| 427 | const next_index = self.index + darwin_entry.d_reclen; |
| 428 | self.index = next_index; |
| 429 | |
| 430 | const name = @ptrCast([*]u8, &darwin_entry.d_name)[0..darwin_entry.d_namlen]; |
| 431 | |
| 432 | if (mem.eql(u8, name, ".") or mem.eql(u8, name, "..")) { |
| 433 | continue :start_over; |
| 434 | } |
| 435 | |
| 436 | const entry_kind = switch (darwin_entry.d_type) { |
| 437 | os.DT_BLK => Entry.Kind.BlockDevice, |
| 438 | os.DT_CHR => Entry.Kind.CharacterDevice, |
| 439 | os.DT_DIR => Entry.Kind.Directory, |
| 440 | os.DT_FIFO => Entry.Kind.NamedPipe, |
| 441 | os.DT_LNK => Entry.Kind.SymLink, |
| 442 | os.DT_REG => Entry.Kind.File, |
| 443 | os.DT_SOCK => Entry.Kind.UnixDomainSocket, |
| 444 | os.DT_WHT => Entry.Kind.Whiteout, |
| 445 | else => Entry.Kind.Unknown, |
| 446 | }; |
| 447 | return Entry{ |
| 448 | .name = name, |
| 449 | .kind = entry_kind, |
| 450 | }; |
| 451 | } |
| 452 | } |
| 453 | |
| 454 | fn nextBsd(self: *Self) !?Entry { |
| 455 | start_over: while (true) { |
| 456 | if (self.index >= self.end_index) { |
| 457 | const rc = os.system.getdirentries( |
| 458 | self.dir.fd, |
| 459 | self.buf[0..].ptr, |
| 460 | self.buf.len, |
| 461 | &self.seek, |
| 462 | ); |
| 463 | switch (os.errno(rc)) { |
| 464 | 0 => {}, |
| 465 | os.EBADF => unreachable, |
| 466 | os.EFAULT => unreachable, |
| 467 | os.ENOTDIR => unreachable, |
| 468 | os.EINVAL => unreachable, |
| 469 | else => |err| return os.unexpectedErrno(err), |
| 470 | } |
| 471 | if (rc == 0) return null; |
| 472 | self.index = 0; |
| 473 | self.end_index = @intCast(usize, rc); |
| 474 | } |
| 475 | const freebsd_entry = @ptrCast(*align(1) os.dirent, &self.buf[self.index]); |
| 476 | const next_index = self.index + freebsd_entry.d_reclen; |
| 477 | self.index = next_index; |
| 478 | |
| 479 | const name = @ptrCast([*]u8, &freebsd_entry.d_name)[0..freebsd_entry.d_namlen]; |
| 480 | |
| 481 | if (mem.eql(u8, name, ".") or mem.eql(u8, name, "..")) { |
| 482 | continue :start_over; |
| 483 | } |
| 484 | |
| 485 | const entry_kind = switch (freebsd_entry.d_type) { |
| 486 | os.DT_BLK => Entry.Kind.BlockDevice, |
| 487 | os.DT_CHR => Entry.Kind.CharacterDevice, |
| 488 | os.DT_DIR => Entry.Kind.Directory, |
| 489 | os.DT_FIFO => Entry.Kind.NamedPipe, |
| 490 | os.DT_LNK => Entry.Kind.SymLink, |
| 491 | os.DT_REG => Entry.Kind.File, |
| 492 | os.DT_SOCK => Entry.Kind.UnixDomainSocket, |
| 493 | os.DT_WHT => Entry.Kind.Whiteout, |
| 494 | else => Entry.Kind.Unknown, |
| 495 | }; |
| 496 | return Entry{ |
| 497 | .name = name, |
| 498 | .kind = entry_kind, |
| 499 | }; |
| 500 | } |
| 501 | } |
| 457 | 502 | }, |
| 458 | 503 | .linux => struct { |
| 459 | | fd: i32, |
| 460 | | buf: []u8, |
| 504 | dir: Dir, |
| 505 | buf: [8192]u8, // TODO align(@alignOf(os.dirent64)), |
| 461 | 506 | index: usize, |
| 462 | 507 | end_index: usize, |
| 508 | |
| 509 | const Self = @This(); |
| 510 | |
| 511 | pub const Error = IteratorError; |
| 512 | |
| 513 | /// Memory such as file names referenced in this returned entry becomes invalid |
| 514 | /// with subsequent calls to `next`, as well as when this `Dir` is deinitialized. |
| 515 | pub fn next(self: *Self) Error!?Entry { |
| 516 | start_over: while (true) { |
| 517 | if (self.index >= self.end_index) { |
| 518 | const rc = os.linux.getdents64(self.dir.fd, &self.buf, self.buf.len); |
| 519 | switch (os.linux.getErrno(rc)) { |
| 520 | 0 => {}, |
| 521 | os.EBADF => unreachable, |
| 522 | os.EFAULT => unreachable, |
| 523 | os.ENOTDIR => unreachable, |
| 524 | os.EINVAL => unreachable, |
| 525 | else => |err| return os.unexpectedErrno(err), |
| 526 | } |
| 527 | if (rc == 0) return null; |
| 528 | self.index = 0; |
| 529 | self.end_index = rc; |
| 530 | } |
| 531 | const linux_entry = @ptrCast(*align(1) os.dirent64, &self.buf[self.index]); |
| 532 | const next_index = self.index + linux_entry.d_reclen; |
| 533 | self.index = next_index; |
| 534 | |
| 535 | const name = mem.toSlice(u8, @ptrCast([*]u8, &linux_entry.d_name)); |
| 536 | |
| 537 | // skip . and .. entries |
| 538 | if (mem.eql(u8, name, ".") or mem.eql(u8, name, "..")) { |
| 539 | continue :start_over; |
| 540 | } |
| 541 | |
| 542 | const entry_kind = switch (linux_entry.d_type) { |
| 543 | os.DT_BLK => Entry.Kind.BlockDevice, |
| 544 | os.DT_CHR => Entry.Kind.CharacterDevice, |
| 545 | os.DT_DIR => Entry.Kind.Directory, |
| 546 | os.DT_FIFO => Entry.Kind.NamedPipe, |
| 547 | os.DT_LNK => Entry.Kind.SymLink, |
| 548 | os.DT_REG => Entry.Kind.File, |
| 549 | os.DT_SOCK => Entry.Kind.UnixDomainSocket, |
| 550 | else => Entry.Kind.Unknown, |
| 551 | }; |
| 552 | return Entry{ |
| 553 | .name = name, |
| 554 | .kind = entry_kind, |
| 555 | }; |
| 556 | } |
| 557 | } |
| 463 | 558 | }, |
| 464 | 559 | .windows => struct { |
| 465 | | handle: os.windows.HANDLE, |
| 466 | | find_file_data: os.windows.WIN32_FIND_DATAW, |
| 560 | dir: Dir, |
| 561 | buf: [8192]u8 align(@alignOf(os.windows.FILE_BOTH_DIR_INFORMATION)), |
| 562 | index: usize, |
| 563 | end_index: usize, |
| 467 | 564 | first: bool, |
| 468 | 565 | name_data: [256]u8, |
| 566 | |
| 567 | const Self = @This(); |
| 568 | |
| 569 | pub const Error = IteratorError; |
| 570 | |
| 571 | pub fn next(self: *Self) Error!?Entry { |
| 572 | start_over: while (true) { |
| 573 | const w = os.windows; |
| 574 | if (self.index >= self.end_index) { |
| 575 | var io: w.IO_STATUS_BLOCK = undefined; |
| 576 | const rc = w.ntdll.NtQueryDirectoryFile( |
| 577 | self.dir.fd, |
| 578 | null, |
| 579 | null, |
| 580 | null, |
| 581 | &io, |
| 582 | &self.buf, |
| 583 | self.buf.len, |
| 584 | .FileBothDirectoryInformation, |
| 585 | w.FALSE, |
| 586 | null, |
| 587 | if (self.first) w.BOOLEAN(w.TRUE) else w.BOOLEAN(w.FALSE), |
| 588 | ); |
| 589 | self.first = false; |
| 590 | if (io.Information == 0) return null; |
| 591 | self.index = 0; |
| 592 | self.end_index = io.Information; |
| 593 | switch (rc) { |
| 594 | w.STATUS.SUCCESS => {}, |
| 595 | w.STATUS.ACCESS_DENIED => return error.AccessDenied, |
| 596 | else => return w.unexpectedStatus(rc), |
| 597 | } |
| 598 | } |
| 599 | |
| 600 | const aligned_ptr = @alignCast(@alignOf(w.FILE_BOTH_DIR_INFORMATION), &self.buf[self.index]); |
| 601 | const dir_info = @ptrCast(*w.FILE_BOTH_DIR_INFORMATION, aligned_ptr); |
| 602 | if (dir_info.NextEntryOffset != 0) { |
| 603 | self.index += dir_info.NextEntryOffset; |
| 604 | } else { |
| 605 | self.index = self.buf.len; |
| 606 | } |
| 607 | |
| 608 | const name_utf16le = @ptrCast([*]u16, &dir_info.FileName)[0 .. dir_info.FileNameLength / 2]; |
| 609 | |
| 610 | if (mem.eql(u16, name_utf16le, [_]u16{'.'}) or mem.eql(u16, name_utf16le, [_]u16{ '.', '.' })) |
| 611 | continue; |
| 612 | // Trust that Windows gives us valid UTF-16LE |
| 613 | const name_utf8_len = std.unicode.utf16leToUtf8(self.name_data[0..], name_utf16le) catch unreachable; |
| 614 | const name_utf8 = self.name_data[0..name_utf8_len]; |
| 615 | const kind = blk: { |
| 616 | const attrs = dir_info.FileAttributes; |
| 617 | if (attrs & w.FILE_ATTRIBUTE_DIRECTORY != 0) break :blk Entry.Kind.Directory; |
| 618 | if (attrs & w.FILE_ATTRIBUTE_REPARSE_POINT != 0) break :blk Entry.Kind.SymLink; |
| 619 | break :blk Entry.Kind.File; |
| 620 | }; |
| 621 | return Entry{ |
| 622 | .name = name_utf8, |
| 623 | .kind = kind, |
| 624 | }; |
| 625 | } |
| 626 | } |
| 469 | 627 | }, |
| 470 | 628 | else => @compileError("unimplemented"), |
| 471 | 629 | }; |
| 472 | 630 | |
| 473 | | pub const Entry = struct { |
| 474 | | name: []const u8, |
| 475 | | kind: Kind, |
| 631 | pub fn iterate(self: Dir) Iterator { |
| 632 | switch (builtin.os) { |
| 633 | .macosx, .ios, .freebsd, .netbsd => return Iterator{ |
| 634 | .dir = self, |
| 635 | .seek = 0, |
| 636 | .index = 0, |
| 637 | .end_index = 0, |
| 638 | .buf = undefined, |
| 639 | }, |
| 640 | .linux => return Iterator{ |
| 641 | .dir = self, |
| 642 | .index = 0, |
| 643 | .end_index = 0, |
| 644 | .buf = undefined, |
| 645 | }, |
| 646 | .windows => return Iterator{ |
| 647 | .dir = self, |
| 648 | .index = 0, |
| 649 | .end_index = 0, |
| 650 | .first = true, |
| 651 | .buf = undefined, |
| 652 | .name_data = undefined, |
| 653 | }, |
| 654 | else => @compileError("unimplemented"), |
| 655 | } |
| 656 | } |
| 476 | 657 | |
| 477 | | pub const Kind = enum { |
| 478 | | BlockDevice, |
| 479 | | CharacterDevice, |
| 480 | | Directory, |
| 481 | | NamedPipe, |
| 482 | | SymLink, |
| 483 | | File, |
| 484 | | UnixDomainSocket, |
| 485 | | Whiteout, |
| 486 | | Unknown, |
| 487 | | }; |
| 488 | | }; |
| 658 | /// Returns an open handle to the current working directory. |
| 659 | /// Closing the returned `Dir` is checked illegal behavior. |
| 660 | /// On POSIX targets, this function is comptime-callable. |
| 661 | pub fn cwd() Dir { |
| 662 | if (os.windows.is_the_target) { |
| 663 | return Dir{ .fd = os.windows.peb().ProcessParameters.CurrentDirectory.Handle }; |
| 664 | } else { |
| 665 | return Dir{ .fd = os.AT_FDCWD }; |
| 666 | } |
| 667 | } |
| 489 | 668 | |
| 490 | 669 | pub const OpenError = error{ |
| 491 | 670 | FileNotFound, |
| 492 | 671 | NotDir, |
| 493 | 672 | AccessDenied, |
| 494 | | FileTooBig, |
| 495 | | IsDir, |
| 496 | 673 | SymLinkLoop, |
| 497 | 674 | ProcessFdQuotaExceeded, |
| 498 | 675 | NameTooLong, |
| 499 | 676 | SystemFdQuotaExceeded, |
| 500 | 677 | NoDevice, |
| 501 | 678 | SystemResources, |
| 502 | | NoSpaceLeft, |
| 503 | | PathAlreadyExists, |
| 504 | | OutOfMemory, |
| 505 | 679 | InvalidUtf8, |
| 506 | 680 | BadPathName, |
| 507 | 681 | DeviceBusy, |
| 682 | } || os.UnexpectedError; |
| 508 | 683 | |
| 509 | | Unexpected, |
| 510 | | }; |
| 511 | | |
| 512 | | /// Call close when done. |
| 513 | | /// TODO remove the allocator requirement from this API |
| 514 | | /// https://github.com/ziglang/zig/issues/2885 |
| 515 | | pub fn open(allocator: *Allocator, dir_path: []const u8) OpenError!Dir { |
| 516 | | return Dir{ |
| 517 | | .allocator = allocator, |
| 518 | | .handle = switch (builtin.os) { |
| 519 | | .windows => blk: { |
| 520 | | var find_file_data: os.windows.WIN32_FIND_DATAW = undefined; |
| 521 | | const handle = try os.windows.FindFirstFile(dir_path, &find_file_data); |
| 522 | | break :blk Handle{ |
| 523 | | .handle = handle, |
| 524 | | .find_file_data = find_file_data, // TODO guaranteed copy elision |
| 525 | | .first = true, |
| 526 | | .name_data = undefined, |
| 527 | | }; |
| 528 | | }, |
| 529 | | .macosx, .ios, .freebsd, .netbsd => Handle{ |
| 530 | | .fd = try os.open(dir_path, os.O_RDONLY | os.O_NONBLOCK | os.O_DIRECTORY | os.O_CLOEXEC, 0), |
| 531 | | .seek = 0, |
| 532 | | .index = 0, |
| 533 | | .end_index = 0, |
| 534 | | .buf = [_]u8{}, |
| 535 | | }, |
| 536 | | .linux => Handle{ |
| 537 | | .fd = try os.open(dir_path, os.O_RDONLY | os.O_DIRECTORY | os.O_CLOEXEC, 0), |
| 538 | | .index = 0, |
| 539 | | .end_index = 0, |
| 540 | | .buf = [_]u8{}, |
| 541 | | }, |
| 542 | | else => @compileError("unimplemented"), |
| 543 | | }, |
| 544 | | }; |
| 684 | /// Call `close` to free the directory handle. |
| 685 | pub fn open(dir_path: []const u8) OpenError!Dir { |
| 686 | return cwd().openDir(dir_path); |
| 545 | 687 | } |
| 546 | 688 | |
| 547 | | pub fn close(self: *Dir) void { |
| 548 | | if (os.windows.is_the_target) { |
| 549 | | return os.windows.FindClose(self.handle.handle); |
| 550 | | } |
| 551 | | self.allocator.free(self.handle.buf); |
| 552 | | os.close(self.handle.fd); |
| 689 | /// Same as `open` except the parameter is null-terminated. |
| 690 | pub fn openC(dir_path_c: [*]const u8) OpenError!Dir { |
| 691 | return cwd().openDirC(dir_path_c); |
| 553 | 692 | } |
| 554 | 693 | |
| 555 | | /// Memory such as file names referenced in this returned entry becomes invalid |
| 556 | | /// with subsequent calls to next, as well as when this `Dir` is deinitialized. |
| 557 | | pub fn next(self: *Dir) !?Entry { |
| 558 | | switch (builtin.os) { |
| 559 | | .linux => return self.nextLinux(), |
| 560 | | .macosx, .ios => return self.nextDarwin(), |
| 561 | | .windows => return self.nextWindows(), |
| 562 | | .freebsd => return self.nextBsd(), |
| 563 | | .netbsd => return self.nextBsd(), |
| 564 | | else => @compileError("unimplemented"), |
| 565 | | } |
| 694 | pub fn close(self: *Dir) void { |
| 695 | os.close(self.fd); |
| 696 | self.* = undefined; |
| 566 | 697 | } |
| 567 | 698 | |
| 568 | | pub fn openRead(self: Dir, file_path: []const u8) os.OpenError!File { |
| 569 | | const path_c = try os.toPosixPath(file_path); |
| 699 | /// Call `File.close` on the result when done. |
| 700 | pub fn openRead(self: Dir, sub_path: []const u8) File.OpenError!File { |
| 701 | const path_c = try os.toPosixPath(sub_path); |
| 570 | 702 | return self.openReadC(&path_c); |
| 571 | 703 | } |
| 572 | 704 | |
| 573 | | pub fn openReadC(self: Dir, file_path: [*]const u8) OpenError!File { |
| 705 | /// Call `File.close` on the result when done. |
| 706 | pub fn openReadC(self: Dir, sub_path: [*]const u8) File.OpenError!File { |
| 574 | 707 | const flags = os.O_LARGEFILE | os.O_RDONLY; |
| 575 | | const fd = try os.openatC(self.handle.fd, file_path, flags, 0); |
| 708 | const fd = try os.openatC(self.fd, sub_path, flags, 0); |
| 576 | 709 | return File.openHandle(fd); |
| 577 | 710 | } |
| 578 | 711 | |
| 579 | | fn nextDarwin(self: *Dir) !?Entry { |
| 580 | | start_over: while (true) { |
| 581 | | if (self.handle.index >= self.handle.end_index) { |
| 582 | | if (self.handle.buf.len == 0) { |
| 583 | | self.handle.buf = try self.allocator.alloc(u8, mem.page_size); |
| 584 | | } |
| 585 | | |
| 586 | | while (true) { |
| 587 | | const rc = os.system.__getdirentries64( |
| 588 | | self.handle.fd, |
| 589 | | self.handle.buf.ptr, |
| 590 | | self.handle.buf.len, |
| 591 | | &self.handle.seek, |
| 592 | | ); |
| 593 | | if (rc == 0) return null; |
| 594 | | if (rc < 0) { |
| 595 | | switch (os.errno(rc)) { |
| 596 | | os.EBADF => unreachable, |
| 597 | | os.EFAULT => unreachable, |
| 598 | | os.ENOTDIR => unreachable, |
| 599 | | os.EINVAL => { |
| 600 | | self.handle.buf = try self.allocator.realloc(self.handle.buf, self.handle.buf.len * 2); |
| 601 | | continue; |
| 602 | | }, |
| 603 | | else => |err| return os.unexpectedErrno(err), |
| 604 | | } |
| 605 | | } |
| 606 | | self.handle.index = 0; |
| 607 | | self.handle.end_index = @intCast(usize, rc); |
| 608 | | break; |
| 609 | | } |
| 610 | | } |
| 611 | | const darwin_entry = @ptrCast(*align(1) os.dirent, &self.handle.buf[self.handle.index]); |
| 612 | | const next_index = self.handle.index + darwin_entry.d_reclen; |
| 613 | | self.handle.index = next_index; |
| 614 | | |
| 615 | | const name = @ptrCast([*]u8, &darwin_entry.d_name)[0..darwin_entry.d_namlen]; |
| 712 | /// Call `close` on the result when done. |
| 713 | pub fn openDir(self: Dir, sub_path: []const u8) OpenError!Dir { |
| 714 | if (os.windows.is_the_target) { |
| 715 | const sub_path_w = try os.windows.sliceToPrefixedFileW(sub_path); |
| 716 | return self.openDirW(&sub_path_w); |
| 717 | } |
| 616 | 718 | |
| 617 | | if (mem.eql(u8, name, ".") or mem.eql(u8, name, "..")) { |
| 618 | | continue :start_over; |
| 619 | | } |
| 719 | const sub_path_c = try os.toPosixPath(sub_path); |
| 720 | return self.openDirC(&sub_path_c); |
| 721 | } |
| 620 | 722 | |
| 621 | | const entry_kind = switch (darwin_entry.d_type) { |
| 622 | | os.DT_BLK => Entry.Kind.BlockDevice, |
| 623 | | os.DT_CHR => Entry.Kind.CharacterDevice, |
| 624 | | os.DT_DIR => Entry.Kind.Directory, |
| 625 | | os.DT_FIFO => Entry.Kind.NamedPipe, |
| 626 | | os.DT_LNK => Entry.Kind.SymLink, |
| 627 | | os.DT_REG => Entry.Kind.File, |
| 628 | | os.DT_SOCK => Entry.Kind.UnixDomainSocket, |
| 629 | | os.DT_WHT => Entry.Kind.Whiteout, |
| 630 | | else => Entry.Kind.Unknown, |
| 631 | | }; |
| 632 | | return Entry{ |
| 633 | | .name = name, |
| 634 | | .kind = entry_kind, |
| 635 | | }; |
| 723 | /// Same as `openDir` except the parameter is null-terminated. |
| 724 | pub fn openDirC(self: Dir, sub_path_c: [*]const u8) OpenError!Dir { |
| 725 | if (os.windows.is_the_target) { |
| 726 | const sub_path_w = try os.windows.cStrToPrefixedFileW(sub_path_c); |
| 727 | return self.openDirW(&sub_path_w); |
| 636 | 728 | } |
| 729 | |
| 730 | const flags = os.O_RDONLY | os.O_DIRECTORY | os.O_CLOEXEC; |
| 731 | const fd = os.openatC(self.fd, sub_path_c, flags, 0) catch |err| switch (err) { |
| 732 | error.FileTooBig => unreachable, // can't happen for directories |
| 733 | error.IsDir => unreachable, // we're providing O_DIRECTORY |
| 734 | error.NoSpaceLeft => unreachable, // not providing O_CREAT |
| 735 | error.PathAlreadyExists => unreachable, // not providing O_CREAT |
| 736 | else => |e| return e, |
| 737 | }; |
| 738 | return Dir{ .fd = fd }; |
| 637 | 739 | } |
| 638 | 740 | |
| 639 | | fn nextWindows(self: *Dir) !?Entry { |
| 640 | | while (true) { |
| 641 | | if (self.handle.first) { |
| 642 | | self.handle.first = false; |
| 643 | | } else { |
| 644 | | if (!try os.windows.FindNextFile(self.handle.handle, &self.handle.find_file_data)) |
| 645 | | return null; |
| 646 | | } |
| 647 | | const name_utf16le = mem.toSlice(u16, self.handle.find_file_data.cFileName[0..].ptr); |
| 648 | | if (mem.eql(u16, name_utf16le, [_]u16{'.'}) or mem.eql(u16, name_utf16le, [_]u16{ '.', '.' })) |
| 649 | | continue; |
| 650 | | // Trust that Windows gives us valid UTF-16LE |
| 651 | | const name_utf8_len = std.unicode.utf16leToUtf8(self.handle.name_data[0..], name_utf16le) catch unreachable; |
| 652 | | const name_utf8 = self.handle.name_data[0..name_utf8_len]; |
| 653 | | const kind = blk: { |
| 654 | | const attrs = self.handle.find_file_data.dwFileAttributes; |
| 655 | | if (attrs & os.windows.FILE_ATTRIBUTE_DIRECTORY != 0) break :blk Entry.Kind.Directory; |
| 656 | | if (attrs & os.windows.FILE_ATTRIBUTE_REPARSE_POINT != 0) break :blk Entry.Kind.SymLink; |
| 657 | | break :blk Entry.Kind.File; |
| 658 | | }; |
| 659 | | return Entry{ |
| 660 | | .name = name_utf8, |
| 661 | | .kind = kind, |
| 662 | | }; |
| 741 | /// Same as `openDir` except the path parameter is UTF16LE, NT-prefixed. |
| 742 | /// This function is Windows-only. |
| 743 | pub fn openDirW(self: Dir, sub_path_w: [*]const u16) OpenError!Dir { |
| 744 | const w = os.windows; |
| 745 | |
| 746 | var result = Dir{ |
| 747 | .fd = undefined, |
| 748 | }; |
| 749 | |
| 750 | const path_len_bytes = @intCast(u16, mem.toSliceConst(u16, sub_path_w).len * 2); |
| 751 | var nt_name = w.UNICODE_STRING{ |
| 752 | .Length = path_len_bytes, |
| 753 | .MaximumLength = path_len_bytes, |
| 754 | .Buffer = @intToPtr([*]u16, @ptrToInt(sub_path_w)), |
| 755 | }; |
| 756 | var attr = w.OBJECT_ATTRIBUTES{ |
| 757 | .Length = @sizeOf(w.OBJECT_ATTRIBUTES), |
| 758 | .RootDirectory = if (path.isAbsoluteW(sub_path_w)) null else self.fd, |
| 759 | .Attributes = 0, // Note we do not use OBJ_CASE_INSENSITIVE here. |
| 760 | .ObjectName = &nt_name, |
| 761 | .SecurityDescriptor = null, |
| 762 | .SecurityQualityOfService = null, |
| 763 | }; |
| 764 | if (sub_path_w[0] == '.' and sub_path_w[1] == 0) { |
| 765 | // Windows does not recognize this, but it does work with empty string. |
| 766 | nt_name.Length = 0; |
| 767 | } |
| 768 | if (sub_path_w[0] == '.' and sub_path_w[1] == '.' and sub_path_w[2] == 0) { |
| 769 | // If you're looking to contribute to zig and fix this, see here for an example of how to |
| 770 | // implement this: https://git.midipix.org/ntapi/tree/src/fs/ntapi_tt_open_physical_parent_directory.c |
| 771 | @panic("TODO opening '..' with a relative directory handle is not yet implemented on Windows"); |
| 772 | } |
| 773 | var io: w.IO_STATUS_BLOCK = undefined; |
| 774 | const rc = w.ntdll.NtCreateFile( |
| 775 | &result.fd, |
| 776 | w.GENERIC_READ | w.SYNCHRONIZE, |
| 777 | &attr, |
| 778 | &io, |
| 779 | null, |
| 780 | 0, |
| 781 | w.FILE_SHARE_READ | w.FILE_SHARE_WRITE, |
| 782 | w.FILE_OPEN, |
| 783 | w.FILE_DIRECTORY_FILE | w.FILE_SYNCHRONOUS_IO_NONALERT | w.FILE_OPEN_FOR_BACKUP_INTENT, |
| 784 | null, |
| 785 | 0, |
| 786 | ); |
| 787 | switch (rc) { |
| 788 | w.STATUS.SUCCESS => return result, |
| 789 | w.STATUS.OBJECT_NAME_INVALID => unreachable, |
| 790 | w.STATUS.OBJECT_NAME_NOT_FOUND => return error.FileNotFound, |
| 791 | w.STATUS.OBJECT_PATH_NOT_FOUND => return error.FileNotFound, |
| 792 | w.STATUS.INVALID_PARAMETER => unreachable, |
| 793 | else => return w.unexpectedStatus(rc), |
| 663 | 794 | } |
| 664 | 795 | } |
| 665 | 796 | |
| 666 | | fn nextLinux(self: *Dir) !?Entry { |
| 667 | | start_over: while (true) { |
| 668 | | if (self.handle.index >= self.handle.end_index) { |
| 669 | | if (self.handle.buf.len == 0) { |
| 670 | | self.handle.buf = try self.allocator.alloc(u8, mem.page_size); |
| 671 | | } |
| 797 | pub const DeleteFileError = os.UnlinkError; |
| 672 | 798 | |
| 673 | | while (true) { |
| 674 | | const rc = os.linux.getdents64(self.handle.fd, self.handle.buf.ptr, self.handle.buf.len); |
| 675 | | switch (os.linux.getErrno(rc)) { |
| 676 | | 0 => {}, |
| 677 | | os.EBADF => unreachable, |
| 678 | | os.EFAULT => unreachable, |
| 679 | | os.ENOTDIR => unreachable, |
| 680 | | os.EINVAL => { |
| 681 | | self.handle.buf = try self.allocator.realloc(self.handle.buf, self.handle.buf.len * 2); |
| 682 | | continue; |
| 683 | | }, |
| 684 | | else => |err| return os.unexpectedErrno(err), |
| 685 | | } |
| 686 | | if (rc == 0) return null; |
| 687 | | self.handle.index = 0; |
| 688 | | self.handle.end_index = rc; |
| 689 | | break; |
| 690 | | } |
| 691 | | } |
| 692 | | const linux_entry = @ptrCast(*align(1) os.dirent64, &self.handle.buf[self.handle.index]); |
| 693 | | const next_index = self.handle.index + linux_entry.d_reclen; |
| 694 | | self.handle.index = next_index; |
| 799 | /// Delete a file name and possibly the file it refers to, based on an open directory handle. |
| 800 | pub fn deleteFile(self: Dir, sub_path: []const u8) DeleteFileError!void { |
| 801 | const sub_path_c = try os.toPosixPath(sub_path); |
| 802 | return self.deleteFileC(&sub_path_c); |
| 803 | } |
| 695 | 804 | |
| 696 | | const name = mem.toSlice(u8, @ptrCast([*]u8, &linux_entry.d_name)); |
| 805 | /// Same as `deleteFile` except the parameter is null-terminated. |
| 806 | pub fn deleteFileC(self: Dir, sub_path_c: [*]const u8) DeleteFileError!void { |
| 807 | os.unlinkatC(self.fd, sub_path_c, 0) catch |err| switch (err) { |
| 808 | error.DirNotEmpty => unreachable, // not passing AT_REMOVEDIR |
| 809 | else => |e| return e, |
| 810 | }; |
| 811 | } |
| 697 | 812 | |
| 698 | | // skip . and .. entries |
| 699 | | if (mem.eql(u8, name, ".") or mem.eql(u8, name, "..")) { |
| 700 | | continue :start_over; |
| 701 | | } |
| 813 | pub const DeleteDirError = error{ |
| 814 | DirNotEmpty, |
| 815 | FileNotFound, |
| 816 | AccessDenied, |
| 817 | FileBusy, |
| 818 | FileSystem, |
| 819 | SymLinkLoop, |
| 820 | NameTooLong, |
| 821 | NotDir, |
| 822 | SystemResources, |
| 823 | ReadOnlyFileSystem, |
| 824 | InvalidUtf8, |
| 825 | BadPathName, |
| 826 | Unexpected, |
| 827 | }; |
| 702 | 828 | |
| 703 | | const entry_kind = switch (linux_entry.d_type) { |
| 704 | | os.DT_BLK => Entry.Kind.BlockDevice, |
| 705 | | os.DT_CHR => Entry.Kind.CharacterDevice, |
| 706 | | os.DT_DIR => Entry.Kind.Directory, |
| 707 | | os.DT_FIFO => Entry.Kind.NamedPipe, |
| 708 | | os.DT_LNK => Entry.Kind.SymLink, |
| 709 | | os.DT_REG => Entry.Kind.File, |
| 710 | | os.DT_SOCK => Entry.Kind.UnixDomainSocket, |
| 711 | | else => Entry.Kind.Unknown, |
| 712 | | }; |
| 713 | | return Entry{ |
| 714 | | .name = name, |
| 715 | | .kind = entry_kind, |
| 716 | | }; |
| 829 | /// Returns `error.DirNotEmpty` if the directory is not empty. |
| 830 | /// To delete a directory recursively, see `deleteTree`. |
| 831 | pub fn deleteDir(self: Dir, sub_path: []const u8) DeleteDirError!void { |
| 832 | if (os.windows.is_the_target) { |
| 833 | const sub_path_w = try os.windows.sliceToPrefixedFileW(sub_path); |
| 834 | return self.deleteDirW(&sub_path_w); |
| 717 | 835 | } |
| 836 | const sub_path_c = try os.toPosixPath(sub_path); |
| 837 | return self.deleteDirC(&sub_path_c); |
| 718 | 838 | } |
| 719 | 839 | |
| 720 | | fn nextBsd(self: *Dir) !?Entry { |
| 721 | | start_over: while (true) { |
| 722 | | if (self.handle.index >= self.handle.end_index) { |
| 723 | | if (self.handle.buf.len == 0) { |
| 724 | | self.handle.buf = try self.allocator.alloc(u8, mem.page_size); |
| 725 | | } |
| 840 | /// Same as `deleteDir` except the parameter is null-terminated. |
| 841 | pub fn deleteDirC(self: Dir, sub_path_c: [*]const u8) DeleteDirError!void { |
| 842 | os.unlinkatC(self.fd, sub_path_c, os.AT_REMOVEDIR) catch |err| switch (err) { |
| 843 | error.IsDir => unreachable, // not possible since we pass AT_REMOVEDIR |
| 844 | else => |e| return e, |
| 845 | }; |
| 846 | } |
| 726 | 847 | |
| 727 | | while (true) { |
| 728 | | const rc = os.system.getdirentries( |
| 729 | | self.handle.fd, |
| 730 | | self.handle.buf.ptr, |
| 731 | | self.handle.buf.len, |
| 732 | | &self.handle.seek, |
| 733 | | ); |
| 734 | | switch (os.errno(rc)) { |
| 735 | | 0 => {}, |
| 736 | | os.EBADF => unreachable, |
| 737 | | os.EFAULT => unreachable, |
| 738 | | os.ENOTDIR => unreachable, |
| 739 | | os.EINVAL => { |
| 740 | | self.handle.buf = try self.allocator.realloc(self.handle.buf, self.handle.buf.len * 2); |
| 741 | | continue; |
| 742 | | }, |
| 743 | | else => |err| return os.unexpectedErrno(err), |
| 744 | | } |
| 745 | | if (rc == 0) return null; |
| 746 | | self.handle.index = 0; |
| 747 | | self.handle.end_index = @intCast(usize, rc); |
| 748 | | break; |
| 749 | | } |
| 750 | | } |
| 751 | | const freebsd_entry = @ptrCast(*align(1) os.dirent, &self.handle.buf[self.handle.index]); |
| 752 | | const next_index = self.handle.index + freebsd_entry.d_reclen; |
| 753 | | self.handle.index = next_index; |
| 848 | /// Same as `deleteDir` except the parameter is UTF16LE, NT prefixed. |
| 849 | /// This function is Windows-only. |
| 850 | pub fn deleteDirW(self: Dir, sub_path_w: [*]const u16) DeleteDirError!void { |
| 851 | os.unlinkatW(self.fd, sub_path_w, os.AT_REMOVEDIR) catch |err| switch (err) { |
| 852 | error.IsDir => unreachable, // not possible since we pass AT_REMOVEDIR |
| 853 | else => |e| return e, |
| 854 | }; |
| 855 | } |
| 754 | 856 | |
| 755 | | const name = @ptrCast([*]u8, &freebsd_entry.d_name)[0..freebsd_entry.d_namlen]; |
| 857 | /// Read value of a symbolic link. |
| 858 | /// The return value is a slice of `buffer`, from index `0`. |
| 859 | pub fn readLink(self: Dir, sub_path: []const u8, buffer: *[MAX_PATH_BYTES]u8) ![]u8 { |
| 860 | const sub_path_c = try os.toPosixPath(sub_path); |
| 861 | return self.readLinkC(&sub_path_c, buffer); |
| 862 | } |
| 756 | 863 | |
| 757 | | if (mem.eql(u8, name, ".") or mem.eql(u8, name, "..")) { |
| 758 | | continue :start_over; |
| 864 | /// Same as `readLink`, except the `pathname` parameter is null-terminated. |
| 865 | pub fn readLinkC(self: Dir, sub_path_c: [*]const u8, buffer: *[MAX_PATH_BYTES]u8) ![]u8 { |
| 866 | return os.readlinkatC(self.fd, sub_path_c, buffer); |
| 867 | } |
| 868 | |
| 869 | pub const DeleteTreeError = error{ |
| 870 | AccessDenied, |
| 871 | FileTooBig, |
| 872 | SymLinkLoop, |
| 873 | ProcessFdQuotaExceeded, |
| 874 | NameTooLong, |
| 875 | SystemFdQuotaExceeded, |
| 876 | NoDevice, |
| 877 | SystemResources, |
| 878 | ReadOnlyFileSystem, |
| 879 | FileSystem, |
| 880 | FileBusy, |
| 881 | DeviceBusy, |
| 882 | |
| 883 | /// One of the path components was not a directory. |
| 884 | /// This error is unreachable if `sub_path` does not contain a path separator. |
| 885 | NotDir, |
| 886 | |
| 887 | /// On Windows, file paths must be valid Unicode. |
| 888 | InvalidUtf8, |
| 889 | |
| 890 | /// On Windows, file paths cannot contain these characters: |
| 891 | /// '/', '*', '?', '"', '<', '>', '|' |
| 892 | BadPathName, |
| 893 | } || os.UnexpectedError; |
| 894 | |
| 895 | /// Whether `full_path` describes a symlink, file, or directory, this function |
| 896 | /// removes it. If it cannot be removed because it is a non-empty directory, |
| 897 | /// this function recursively removes its entries and then tries again. |
| 898 | /// This operation is not atomic on most file systems. |
| 899 | pub fn deleteTree(self: Dir, sub_path: []const u8) DeleteTreeError!void { |
| 900 | start_over: while (true) { |
| 901 | var got_access_denied = false; |
| 902 | // First, try deleting the item as a file. This way we don't follow sym links. |
| 903 | if (self.deleteFile(sub_path)) { |
| 904 | return; |
| 905 | } else |err| switch (err) { |
| 906 | error.FileNotFound => return, |
| 907 | error.IsDir => {}, |
| 908 | error.AccessDenied => got_access_denied = true, |
| 909 | |
| 910 | error.InvalidUtf8, |
| 911 | error.SymLinkLoop, |
| 912 | error.NameTooLong, |
| 913 | error.SystemResources, |
| 914 | error.ReadOnlyFileSystem, |
| 915 | error.NotDir, |
| 916 | error.FileSystem, |
| 917 | error.FileBusy, |
| 918 | error.BadPathName, |
| 919 | error.Unexpected, |
| 920 | => |e| return e, |
| 759 | 921 | } |
| 922 | var dir = self.openDir(sub_path) catch |err| switch (err) { |
| 923 | error.NotDir => { |
| 924 | if (got_access_denied) { |
| 925 | return error.AccessDenied; |
| 926 | } |
| 927 | continue :start_over; |
| 928 | }, |
| 929 | error.FileNotFound => { |
| 930 | // That's fine, we were trying to remove this directory anyway. |
| 931 | continue :start_over; |
| 932 | }, |
| 760 | 933 | |
| 761 | | const entry_kind = switch (freebsd_entry.d_type) { |
| 762 | | os.DT_BLK => Entry.Kind.BlockDevice, |
| 763 | | os.DT_CHR => Entry.Kind.CharacterDevice, |
| 764 | | os.DT_DIR => Entry.Kind.Directory, |
| 765 | | os.DT_FIFO => Entry.Kind.NamedPipe, |
| 766 | | os.DT_LNK => Entry.Kind.SymLink, |
| 767 | | os.DT_REG => Entry.Kind.File, |
| 768 | | os.DT_SOCK => Entry.Kind.UnixDomainSocket, |
| 769 | | os.DT_WHT => Entry.Kind.Whiteout, |
| 770 | | else => Entry.Kind.Unknown, |
| 771 | | }; |
| 772 | | return Entry{ |
| 773 | | .name = name, |
| 774 | | .kind = entry_kind, |
| 934 | error.AccessDenied, |
| 935 | error.SymLinkLoop, |
| 936 | error.ProcessFdQuotaExceeded, |
| 937 | error.NameTooLong, |
| 938 | error.SystemFdQuotaExceeded, |
| 939 | error.NoDevice, |
| 940 | error.SystemResources, |
| 941 | error.Unexpected, |
| 942 | error.InvalidUtf8, |
| 943 | error.BadPathName, |
| 944 | error.DeviceBusy, |
| 945 | => |e| return e, |
| 775 | 946 | }; |
| 947 | var cleanup_dir_parent: ?Dir = null; |
| 948 | defer if (cleanup_dir_parent) |*d| d.close(); |
| 949 | |
| 950 | var cleanup_dir = true; |
| 951 | defer if (cleanup_dir) dir.close(); |
| 952 | |
| 953 | var dir_name_buf: [MAX_PATH_BYTES]u8 = undefined; |
| 954 | var dir_name: []const u8 = sub_path; |
| 955 | var parent_dir = self; |
| 956 | |
| 957 | // Here we must avoid recursion, in order to provide O(1) memory guarantee of this function. |
| 958 | // Go through each entry and if it is not a directory, delete it. If it is a directory, |
| 959 | // open it, and close the original directory. Repeat. Then start the entire operation over. |
| 960 | |
| 961 | scan_dir: while (true) { |
| 962 | var dir_it = dir.iterate(); |
| 963 | while (try dir_it.next()) |entry| { |
| 964 | if (dir.deleteFile(entry.name)) { |
| 965 | continue; |
| 966 | } else |err| switch (err) { |
| 967 | error.FileNotFound => continue, |
| 968 | |
| 969 | // Impossible because we do not pass any path separators. |
| 970 | error.NotDir => unreachable, |
| 971 | |
| 972 | error.IsDir => {}, |
| 973 | error.AccessDenied => got_access_denied = true, |
| 974 | |
| 975 | error.InvalidUtf8, |
| 976 | error.SymLinkLoop, |
| 977 | error.NameTooLong, |
| 978 | error.SystemResources, |
| 979 | error.ReadOnlyFileSystem, |
| 980 | error.FileSystem, |
| 981 | error.FileBusy, |
| 982 | error.BadPathName, |
| 983 | error.Unexpected, |
| 984 | => |e| return e, |
| 985 | } |
| 986 | |
| 987 | const new_dir = dir.openDir(entry.name) catch |err| switch (err) { |
| 988 | error.NotDir => { |
| 989 | if (got_access_denied) { |
| 990 | return error.AccessDenied; |
| 991 | } |
| 992 | continue :scan_dir; |
| 993 | }, |
| 994 | error.FileNotFound => { |
| 995 | // That's fine, we were trying to remove this directory anyway. |
| 996 | continue :scan_dir; |
| 997 | }, |
| 998 | |
| 999 | error.AccessDenied, |
| 1000 | error.SymLinkLoop, |
| 1001 | error.ProcessFdQuotaExceeded, |
| 1002 | error.NameTooLong, |
| 1003 | error.SystemFdQuotaExceeded, |
| 1004 | error.NoDevice, |
| 1005 | error.SystemResources, |
| 1006 | error.Unexpected, |
| 1007 | error.InvalidUtf8, |
| 1008 | error.BadPathName, |
| 1009 | error.DeviceBusy, |
| 1010 | => |e| return e, |
| 1011 | }; |
| 1012 | if (cleanup_dir_parent) |*d| d.close(); |
| 1013 | cleanup_dir_parent = dir; |
| 1014 | dir = new_dir; |
| 1015 | mem.copy(u8, &dir_name_buf, entry.name); |
| 1016 | dir_name = dir_name_buf[0..entry.name.len]; |
| 1017 | continue :scan_dir; |
| 1018 | } |
| 1019 | // Reached the end of the directory entries, which means we successfully deleted all of them. |
| 1020 | // Now to remove the directory itself. |
| 1021 | dir.close(); |
| 1022 | cleanup_dir = false; |
| 1023 | |
| 1024 | if (cleanup_dir_parent) |d| { |
| 1025 | d.deleteDir(dir_name) catch |err| switch (err) { |
| 1026 | // These two things can happen due to file system race conditions. |
| 1027 | error.FileNotFound, error.DirNotEmpty => continue :start_over, |
| 1028 | else => |e| return e, |
| 1029 | }; |
| 1030 | continue :start_over; |
| 1031 | } else { |
| 1032 | self.deleteDir(sub_path) catch |err| switch (err) { |
| 1033 | error.FileNotFound => return, |
| 1034 | error.DirNotEmpty => continue :start_over, |
| 1035 | else => |e| return e, |
| 1036 | }; |
| 1037 | return; |
| 1038 | } |
| 1039 | } |
| 776 | 1040 | } |
| 777 | 1041 | } |
| 778 | 1042 | }; |
| ... | ... | @@ -782,13 +1046,18 @@ pub const Walker = struct { |
| 782 | 1046 | name_buffer: std.Buffer, |
| 783 | 1047 | |
| 784 | 1048 | pub const Entry = struct { |
| 785 | | path: []const u8, |
| 1049 | /// The containing directory. This can be used to operate directly on `basename` |
| 1050 | /// rather than `path`, avoiding `error.NameTooLong` for deeply nested paths. |
| 1051 | /// The directory remains open until `next` or `deinit` is called. |
| 1052 | dir: Dir, |
| 786 | 1053 | basename: []const u8, |
| 1054 | |
| 1055 | path: []const u8, |
| 787 | 1056 | kind: Dir.Entry.Kind, |
| 788 | 1057 | }; |
| 789 | 1058 | |
| 790 | 1059 | const StackItem = struct { |
| 791 | | dir_it: Dir, |
| 1060 | dir_it: Dir.Iterator, |
| 792 | 1061 | dirname_len: usize, |
| 793 | 1062 | }; |
| 794 | 1063 | |
| ... | ... | @@ -806,23 +1075,26 @@ pub const Walker = struct { |
| 806 | 1075 | try self.name_buffer.appendByte(path.sep); |
| 807 | 1076 | try self.name_buffer.append(base.name); |
| 808 | 1077 | if (base.kind == .Directory) { |
| 809 | | // TODO https://github.com/ziglang/zig/issues/2888 |
| 810 | | var new_dir = try Dir.open(self.stack.allocator, self.name_buffer.toSliceConst()); |
| 1078 | var new_dir = top.dir_it.dir.openDir(base.name) catch |err| switch (err) { |
| 1079 | error.NameTooLong => unreachable, // no path sep in base.name |
| 1080 | else => |e| return e, |
| 1081 | }; |
| 811 | 1082 | { |
| 812 | 1083 | errdefer new_dir.close(); |
| 813 | 1084 | try self.stack.append(StackItem{ |
| 814 | | .dir_it = new_dir, |
| 1085 | .dir_it = new_dir.iterate(), |
| 815 | 1086 | .dirname_len = self.name_buffer.len(), |
| 816 | 1087 | }); |
| 817 | 1088 | } |
| 818 | 1089 | } |
| 819 | 1090 | return Entry{ |
| 1091 | .dir = top.dir_it.dir, |
| 820 | 1092 | .basename = self.name_buffer.toSliceConst()[dirname_len + 1 ..], |
| 821 | 1093 | .path = self.name_buffer.toSliceConst(), |
| 822 | 1094 | .kind = base.kind, |
| 823 | 1095 | }; |
| 824 | 1096 | } else { |
| 825 | | self.stack.pop().dir_it.close(); |
| 1097 | self.stack.pop().dir_it.dir.close(); |
| 826 | 1098 | } |
| 827 | 1099 | } |
| 828 | 1100 | } |
| ... | ... | @@ -837,12 +1109,12 @@ pub const Walker = struct { |
| 837 | 1109 | /// Recursively iterates over a directory. |
| 838 | 1110 | /// Must call `Walker.deinit` when done. |
| 839 | 1111 | /// `dir_path` must not end in a path separator. |
| 840 | | /// TODO: https://github.com/ziglang/zig/issues/2888 |
| 1112 | /// The order of returned file system entries is undefined. |
| 841 | 1113 | pub fn walkPath(allocator: *Allocator, dir_path: []const u8) !Walker { |
| 842 | 1114 | assert(!mem.endsWith(u8, dir_path, path.sep_str)); |
| 843 | 1115 | |
| 844 | | var dir_it = try Dir.open(allocator, dir_path); |
| 845 | | errdefer dir_it.close(); |
| 1116 | var dir = try Dir.open(dir_path); |
| 1117 | errdefer dir.close(); |
| 846 | 1118 | |
| 847 | 1119 | var name_buffer = try std.Buffer.init(allocator, dir_path); |
| 848 | 1120 | errdefer name_buffer.deinit(); |
| ... | ... | @@ -853,7 +1125,7 @@ pub fn walkPath(allocator: *Allocator, dir_path: []const u8) !Walker { |
| 853 | 1125 | }; |
| 854 | 1126 | |
| 855 | 1127 | try walker.stack.append(Walker.StackItem{ |
| 856 | | .dir_it = dir_it, |
| 1128 | .dir_it = dir.iterate(), |
| 857 | 1129 | .dirname_len = dir_path.len, |
| 858 | 1130 | }); |
| 859 | 1131 | |
| ... | ... | @@ -862,15 +1134,13 @@ pub fn walkPath(allocator: *Allocator, dir_path: []const u8) !Walker { |
| 862 | 1134 | |
| 863 | 1135 | /// Read value of a symbolic link. |
| 864 | 1136 | /// The return value is a slice of buffer, from index `0`. |
| 865 | | /// TODO https://github.com/ziglang/zig/issues/2888 |
| 866 | | pub fn readLink(pathname: []const u8, buffer: *[os.PATH_MAX]u8) ![]u8 { |
| 1137 | pub fn readLink(pathname: []const u8, buffer: *[MAX_PATH_BYTES]u8) ![]u8 { |
| 867 | 1138 | return os.readlink(pathname, buffer); |
| 868 | 1139 | } |
| 869 | 1140 | |
| 870 | | /// Same as `readLink`, except the `pathname` parameter is null-terminated. |
| 871 | | /// TODO https://github.com/ziglang/zig/issues/2888 |
| 872 | | pub fn readLinkC(pathname: [*]const u8, buffer: *[os.PATH_MAX]u8) ![]u8 { |
| 873 | | return os.readlinkC(pathname, buffer); |
| 1141 | /// Same as `readLink`, except the parameter is null-terminated. |
| 1142 | pub fn readLinkC(pathname_c: [*]const u8, buffer: *[MAX_PATH_BYTES]u8) ![]u8 { |
| 1143 | return os.readlinkC(pathname_c, buffer); |
| 874 | 1144 | } |
| 875 | 1145 | |
| 876 | 1146 | pub const OpenSelfExeError = os.OpenError || os.windows.CreateFileError || SelfExePathError; |