| ... | @@ -37,8 +37,6 @@ pub const MAX_PATH_BYTES = switch (builtin.os) { | ... | @@ -37,8 +37,6 @@ pub const MAX_PATH_BYTES = switch (builtin.os) { |
| 37 | else => @compileError("Unsupported OS"), | 37 | else => @compileError("Unsupported OS"), |
| 38 | }; | 38 | }; |
| 39 | | 39 | |
| 40 | pub const MAX_BUF_BYTES: usize = 8192; | | |
| 41 | | | |
| 42 | // here we replace the standard +/ with -_ so that it can be used in a file name | 40 | // here we replace the standard +/ with -_ so that it can be used in a file name |
| 43 | const b64_fs_encoder = base64.Base64Encoder.init("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_", base64.standard_pad_char); | 41 | const b64_fs_encoder = base64.Base64Encoder.init("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_", base64.standard_pad_char); |
| 44 | | 42 | |
| ... | @@ -337,136 +335,35 @@ pub fn deleteDirW(dir_path: [*]const u16) !void { | ... | @@ -337,136 +335,35 @@ pub fn deleteDirW(dir_path: [*]const u16) !void { |
| 337 | return os.rmdirW(dir_path); | 335 | return os.rmdirW(dir_path); |
| 338 | } | 336 | } |
| 339 | | 337 | |
| 340 | const DeleteTreeError = error{ | 338 | /// Removes a symlink, file, or directory. |
| 341 | OutOfMemory, | 339 | /// If `full_path` is relative, this is equivalent to `Dir.deleteTree` with the |
| 342 | AccessDenied, | 340 | /// current working directory as the open directory handle. |
| 343 | FileTooBig, | 341 | /// If `full_path` is absolute, this is equivalent to `Dir.deleteTree` with the |
| 344 | IsDir, | 342 | /// base directory. |
| 345 | SymLinkLoop, | 343 | pub fn deleteTree(full_path: []const u8) !void { |
| 346 | ProcessFdQuotaExceeded, | 344 | if (path.isAbsolute(full_path)) { |
| 347 | NameTooLong, | 345 | const dirname = path.dirname(full_path) orelse return error{ |
| 348 | SystemFdQuotaExceeded, | 346 | /// Attempt to remove the root file system path. |
| 349 | NoDevice, | 347 | /// This error is unreachable if `full_path` is relative. |
| 350 | SystemResources, | 348 | CannotDeleteRootDirectory, |
| 351 | NoSpaceLeft, | 349 | }.CannotDeleteRootDirectory; |
| 352 | PathAlreadyExists, | 350 | |
| 353 | ReadOnlyFileSystem, | 351 | var dir = try Dir.open(dirname); |
| 354 | NotDir, | 352 | defer dir.close(); |
| 355 | FileNotFound, | 353 | |
| 356 | FileSystem, | 354 | return dir.deleteTree(path.basename(full_path)); |
| 357 | FileBusy, | 355 | } else { |
| 358 | DirNotEmpty, | 356 | return Dir.posix_cwd.deleteTree(full_path); |
| 359 | DeviceBusy, | | |
| 360 | | | |
| 361 | /// On Windows, file paths must be valid Unicode. | | |
| 362 | InvalidUtf8, | | |
| 363 | | | |
| 364 | /// On Windows, file paths cannot contain these characters: | | |
| 365 | /// '/', '*', '?', '"', '<', '>', '|' | | |
| 366 | BadPathName, | | |
| 367 | | | |
| 368 | Unexpected, | | |
| 369 | }; | | |
| 370 | | | |
| 371 | /// Whether `full_path` describes a symlink, file, or directory, this function | | |
| 372 | /// removes it. If it cannot be removed because it is a non-empty directory, | | |
| 373 | /// this function recursively removes its entries and then tries again. | | |
| 374 | /// TODO determine if we can remove the allocator requirement | | |
| 375 | /// https://github.com/ziglang/zig/issues/2886 | | |
| 376 | pub fn deleteTree(full_path: []const u8) DeleteTreeError!void { | | |
| 377 | start_over: while (true) { | | |
| 378 | var got_access_denied = false; | | |
| 379 | // First, try deleting the item as a file. This way we don't follow sym links. | | |
| 380 | if (deleteFile(full_path)) { | | |
| 381 | return; | | |
| 382 | } else |err| switch (err) { | | |
| 383 | error.FileNotFound => return, | | |
| 384 | error.IsDir => {}, | | |
| 385 | error.AccessDenied => got_access_denied = true, | | |
| 386 | | | |
| 387 | error.InvalidUtf8, | | |
| 388 | error.SymLinkLoop, | | |
| 389 | error.NameTooLong, | | |
| 390 | error.SystemResources, | | |
| 391 | error.ReadOnlyFileSystem, | | |
| 392 | error.NotDir, | | |
| 393 | error.FileSystem, | | |
| 394 | error.FileBusy, | | |
| 395 | error.BadPathName, | | |
| 396 | error.Unexpected, | | |
| 397 | => return err, | | |
| 398 | } | | |
| 399 | { | | |
| 400 | var dir = Dir.open(full_path) catch |err| switch (err) { | | |
| 401 | error.NotDir => { | | |
| 402 | if (got_access_denied) { | | |
| 403 | return error.AccessDenied; | | |
| 404 | } | | |
| 405 | continue :start_over; | | |
| 406 | }, | | |
| 407 | | | |
| 408 | error.OutOfMemory, | | |
| 409 | error.AccessDenied, | | |
| 410 | error.FileTooBig, | | |
| 411 | error.IsDir, | | |
| 412 | error.SymLinkLoop, | | |
| 413 | error.ProcessFdQuotaExceeded, | | |
| 414 | error.NameTooLong, | | |
| 415 | error.SystemFdQuotaExceeded, | | |
| 416 | error.NoDevice, | | |
| 417 | error.FileNotFound, | | |
| 418 | error.SystemResources, | | |
| 419 | error.NoSpaceLeft, | | |
| 420 | error.PathAlreadyExists, | | |
| 421 | error.Unexpected, | | |
| 422 | error.InvalidUtf8, | | |
| 423 | error.BadPathName, | | |
| 424 | error.DeviceBusy, | | |
| 425 | => return err, | | |
| 426 | }; | | |
| 427 | defer dir.close(); | | |
| 428 | | | |
| 429 | while (try dir.next()) |entry| { | | |
| 430 | var full_entry_buf: [MAX_BUF_BYTES]u8 = undefined; | | |
| 431 | const full_entry_path = full_entry_buf[0..]; | | |
| 432 | mem.copy(u8, full_entry_path, full_path); | | |
| 433 | full_entry_path[full_path.len] = path.sep; | | |
| 434 | mem.copy(u8, full_entry_path[full_path.len + 1 ..], entry.name); | | |
| 435 | | | |
| 436 | try deleteTree(full_entry_path[0..full_path.len + entry.name.len + 1]); | | |
| 437 | } | | |
| 438 | } | | |
| 439 | return deleteDir(full_path); | | |
| 440 | } | 357 | } |
| 441 | } | 358 | } |
| 442 | | 359 | |
| 443 | /// TODO: separate this API into the one that opens directory handles to then subsequently open | | |
| 444 | /// files, and into the one that reads files from an open directory handle. | | |
| 445 | pub const Dir = struct { | 360 | pub const Dir = struct { |
| 446 | handle: Handle, | 361 | fd: os.fd_t, |
| 447 | | 362 | |
| 448 | pub const Handle = switch (builtin.os) { | 363 | /// An open handle to the current working directory. |
| 449 | .macosx, .ios, .freebsd, .netbsd => struct { | 364 | /// Closing this directory is safety-checked illegal behavior. |
| 450 | fd: i32, | 365 | /// Not available on Windows. |
| 451 | seek: i64, | 366 | pub const posix_cwd = Dir{ .fd = os.AT_FDCWD }; |
| 452 | buf: [MAX_BUF_BYTES]u8, | | |
| 453 | index: usize, | | |
| 454 | end_index: usize, | | |
| 455 | }, | | |
| 456 | .linux => struct { | | |
| 457 | fd: i32, | | |
| 458 | buf: [MAX_BUF_BYTES]u8, | | |
| 459 | index: usize, | | |
| 460 | end_index: usize, | | |
| 461 | }, | | |
| 462 | .windows => struct { | | |
| 463 | handle: os.windows.HANDLE, | | |
| 464 | find_file_data: os.windows.WIN32_FIND_DATAW, | | |
| 465 | first: bool, | | |
| 466 | name_data: [256]u8, | | |
| 467 | }, | | |
| 468 | else => @compileError("unimplemented"), | | |
| 469 | }; | | |
| 470 | | 367 | |
| 471 | pub const Entry = struct { | 368 | pub const Entry = struct { |
| 472 | name: []const u8, | 369 | name: []const u8, |
| ... | @@ -485,269 +382,504 @@ pub const Dir = struct { | ... | @@ -485,269 +382,504 @@ pub const Dir = struct { |
| 485 | }; | 382 | }; |
| 486 | }; | 383 | }; |
| 487 | | 384 | |
| | 385 | pub const Iterator = switch (builtin.os) { |
| | 386 | .macosx, .ios, .freebsd, .netbsd => struct { |
| | 387 | dir: Dir, |
| | 388 | seek: i64, |
| | 389 | buf: [buffer_len]u8, |
| | 390 | index: usize, |
| | 391 | end_index: usize, |
| | 392 | |
| | 393 | pub const buffer_len = 8192; |
| | 394 | |
| | 395 | const Self = @This(); |
| | 396 | |
| | 397 | /// Memory such as file names referenced in this returned entry becomes invalid |
| | 398 | /// with subsequent calls to `next`, as well as when this `Dir` is deinitialized. |
| | 399 | pub fn next(self: *Self) !?Entry { |
| | 400 | switch (builtin.os) { |
| | 401 | .macosx, .ios => return self.nextDarwin(), |
| | 402 | .freebsd, .netbsd => return self.nextBsd(), |
| | 403 | else => @compileError("unimplemented"), |
| | 404 | } |
| | 405 | } |
| | 406 | |
| | 407 | fn nextDarwin(self: *Self) !?Entry { |
| | 408 | start_over: while (true) { |
| | 409 | if (self.index >= self.end_index) { |
| | 410 | while (true) { |
| | 411 | const rc = os.system.__getdirentries64( |
| | 412 | self.dir.fd, |
| | 413 | &self.buf, |
| | 414 | self.buf.len, |
| | 415 | &self.seek, |
| | 416 | ); |
| | 417 | if (rc == 0) return null; |
| | 418 | if (rc < 0) { |
| | 419 | switch (os.errno(rc)) { |
| | 420 | os.EBADF => unreachable, |
| | 421 | os.EFAULT => unreachable, |
| | 422 | os.ENOTDIR => unreachable, |
| | 423 | os.EINVAL => unreachable, |
| | 424 | else => |err| return os.unexpectedErrno(err), |
| | 425 | } |
| | 426 | } |
| | 427 | self.index = 0; |
| | 428 | self.end_index = @intCast(usize, rc); |
| | 429 | break; |
| | 430 | } |
| | 431 | } |
| | 432 | const darwin_entry = @ptrCast(*align(1) os.dirent, &self.buf[self.index]); |
| | 433 | const next_index = self.index + darwin_entry.d_reclen; |
| | 434 | self.index = next_index; |
| | 435 | |
| | 436 | const name = @ptrCast([*]u8, &darwin_entry.d_name)[0..darwin_entry.d_namlen]; |
| | 437 | |
| | 438 | if (mem.eql(u8, name, ".") or mem.eql(u8, name, "..")) { |
| | 439 | continue :start_over; |
| | 440 | } |
| | 441 | |
| | 442 | const entry_kind = switch (darwin_entry.d_type) { |
| | 443 | os.DT_BLK => Entry.Kind.BlockDevice, |
| | 444 | os.DT_CHR => Entry.Kind.CharacterDevice, |
| | 445 | os.DT_DIR => Entry.Kind.Directory, |
| | 446 | os.DT_FIFO => Entry.Kind.NamedPipe, |
| | 447 | os.DT_LNK => Entry.Kind.SymLink, |
| | 448 | os.DT_REG => Entry.Kind.File, |
| | 449 | os.DT_SOCK => Entry.Kind.UnixDomainSocket, |
| | 450 | os.DT_WHT => Entry.Kind.Whiteout, |
| | 451 | else => Entry.Kind.Unknown, |
| | 452 | }; |
| | 453 | return Entry{ |
| | 454 | .name = name, |
| | 455 | .kind = entry_kind, |
| | 456 | }; |
| | 457 | } |
| | 458 | } |
| | 459 | |
| | 460 | fn nextBsd(self: *Self) !?Entry { |
| | 461 | start_over: while (true) { |
| | 462 | if (self.index >= self.end_index) { |
| | 463 | while (true) { |
| | 464 | const rc = os.system.getdirentries( |
| | 465 | self.dir.fd, |
| | 466 | self.buf[0..].ptr, |
| | 467 | self.buf.len, |
| | 468 | &self.seek, |
| | 469 | ); |
| | 470 | switch (os.errno(rc)) { |
| | 471 | 0 => {}, |
| | 472 | os.EBADF => unreachable, |
| | 473 | os.EFAULT => unreachable, |
| | 474 | os.ENOTDIR => unreachable, |
| | 475 | os.EINVAL => unreachable, |
| | 476 | else => |err| return os.unexpectedErrno(err), |
| | 477 | } |
| | 478 | if (rc == 0) return null; |
| | 479 | self.index = 0; |
| | 480 | self.end_index = @intCast(usize, rc); |
| | 481 | break; |
| | 482 | } |
| | 483 | } |
| | 484 | const freebsd_entry = @ptrCast(*align(1) os.dirent, &self.buf[self.index]); |
| | 485 | const next_index = self.index + freebsd_entry.d_reclen; |
| | 486 | self.index = next_index; |
| | 487 | |
| | 488 | const name = @ptrCast([*]u8, &freebsd_entry.d_name)[0..freebsd_entry.d_namlen]; |
| | 489 | |
| | 490 | if (mem.eql(u8, name, ".") or mem.eql(u8, name, "..")) { |
| | 491 | continue :start_over; |
| | 492 | } |
| | 493 | |
| | 494 | const entry_kind = switch (freebsd_entry.d_type) { |
| | 495 | os.DT_BLK => Entry.Kind.BlockDevice, |
| | 496 | os.DT_CHR => Entry.Kind.CharacterDevice, |
| | 497 | os.DT_DIR => Entry.Kind.Directory, |
| | 498 | os.DT_FIFO => Entry.Kind.NamedPipe, |
| | 499 | os.DT_LNK => Entry.Kind.SymLink, |
| | 500 | os.DT_REG => Entry.Kind.File, |
| | 501 | os.DT_SOCK => Entry.Kind.UnixDomainSocket, |
| | 502 | os.DT_WHT => Entry.Kind.Whiteout, |
| | 503 | else => Entry.Kind.Unknown, |
| | 504 | }; |
| | 505 | return Entry{ |
| | 506 | .name = name, |
| | 507 | .kind = entry_kind, |
| | 508 | }; |
| | 509 | } |
| | 510 | } |
| | 511 | }, |
| | 512 | .linux => struct { |
| | 513 | dir: Dir, |
| | 514 | buf: [buffer_len]u8, |
| | 515 | index: usize, |
| | 516 | end_index: usize, |
| | 517 | |
| | 518 | pub const buffer_len = 8192; |
| | 519 | |
| | 520 | const Self = @This(); |
| | 521 | |
| | 522 | /// Memory such as file names referenced in this returned entry becomes invalid |
| | 523 | /// with subsequent calls to `next`, as well as when this `Dir` is deinitialized. |
| | 524 | pub fn next(self: *Self) !?Entry { |
| | 525 | start_over: while (true) { |
| | 526 | if (self.index >= self.end_index) { |
| | 527 | while (true) { |
| | 528 | const rc = os.linux.getdents64(self.dir.fd, &self.buf, self.buf.len); |
| | 529 | switch (os.linux.getErrno(rc)) { |
| | 530 | 0 => {}, |
| | 531 | os.EBADF => unreachable, |
| | 532 | os.EFAULT => unreachable, |
| | 533 | os.ENOTDIR => unreachable, |
| | 534 | os.EINVAL => unreachable, |
| | 535 | else => |err| return os.unexpectedErrno(err), |
| | 536 | } |
| | 537 | if (rc == 0) return null; |
| | 538 | self.index = 0; |
| | 539 | self.end_index = rc; |
| | 540 | break; |
| | 541 | } |
| | 542 | } |
| | 543 | const linux_entry = @ptrCast(*align(1) os.dirent64, &self.buf[self.index]); |
| | 544 | const next_index = self.index + linux_entry.d_reclen; |
| | 545 | self.index = next_index; |
| | 546 | |
| | 547 | const name = mem.toSlice(u8, @ptrCast([*]u8, &linux_entry.d_name)); |
| | 548 | |
| | 549 | // skip . and .. entries |
| | 550 | if (mem.eql(u8, name, ".") or mem.eql(u8, name, "..")) { |
| | 551 | continue :start_over; |
| | 552 | } |
| | 553 | |
| | 554 | const entry_kind = switch (linux_entry.d_type) { |
| | 555 | os.DT_BLK => Entry.Kind.BlockDevice, |
| | 556 | os.DT_CHR => Entry.Kind.CharacterDevice, |
| | 557 | os.DT_DIR => Entry.Kind.Directory, |
| | 558 | os.DT_FIFO => Entry.Kind.NamedPipe, |
| | 559 | os.DT_LNK => Entry.Kind.SymLink, |
| | 560 | os.DT_REG => Entry.Kind.File, |
| | 561 | os.DT_SOCK => Entry.Kind.UnixDomainSocket, |
| | 562 | else => Entry.Kind.Unknown, |
| | 563 | }; |
| | 564 | return Entry{ |
| | 565 | .name = name, |
| | 566 | .kind = entry_kind, |
| | 567 | }; |
| | 568 | } |
| | 569 | } |
| | 570 | }, |
| | 571 | .windows => struct { |
| | 572 | dir: Dir, |
| | 573 | find_file_data: os.windows.WIN32_FIND_DATAW, |
| | 574 | first: bool, |
| | 575 | name_data: [256]u8, |
| | 576 | }, |
| | 577 | else => @compileError("unimplemented"), |
| | 578 | }; |
| | 579 | |
| 488 | pub const OpenError = error{ | 580 | pub const OpenError = error{ |
| 489 | FileNotFound, | 581 | FileNotFound, |
| 490 | NotDir, | 582 | NotDir, |
| 491 | AccessDenied, | 583 | AccessDenied, |
| 492 | FileTooBig, | | |
| 493 | IsDir, | | |
| 494 | SymLinkLoop, | 584 | SymLinkLoop, |
| 495 | ProcessFdQuotaExceeded, | 585 | ProcessFdQuotaExceeded, |
| 496 | NameTooLong, | 586 | NameTooLong, |
| 497 | SystemFdQuotaExceeded, | 587 | SystemFdQuotaExceeded, |
| 498 | NoDevice, | 588 | NoDevice, |
| 499 | SystemResources, | 589 | SystemResources, |
| 500 | NoSpaceLeft, | | |
| 501 | PathAlreadyExists, | | |
| 502 | OutOfMemory, | | |
| 503 | InvalidUtf8, | 590 | InvalidUtf8, |
| 504 | BadPathName, | 591 | BadPathName, |
| 505 | DeviceBusy, | 592 | DeviceBusy, |
| | 593 | } || os.UnexpectedError; |
| 506 | | 594 | |
| 507 | Unexpected, | 595 | /// Call `close` to free the directory handle. |
| 508 | }; | | |
| 509 | | | |
| 510 | /// Call close when done. | | |
| 511 | /// TODO remove the allocator requirement from this API | | |
| 512 | /// https://github.com/ziglang/zig/issues/2885 | | |
| 513 | pub fn open(dir_path: []const u8) OpenError!Dir { | 596 | pub fn open(dir_path: []const u8) OpenError!Dir { |
| 514 | return Dir{ | 597 | return posix_cwd.openDir(dir_path); |
| 515 | .handle = switch (builtin.os) { | | |
| 516 | .windows => blk: { | | |
| 517 | var find_file_data: os.windows.WIN32_FIND_DATAW = undefined; | | |
| 518 | const handle = try os.windows.FindFirstFile(dir_path, &find_file_data); | | |
| 519 | break :blk Handle{ | | |
| 520 | .handle = handle, | | |
| 521 | .find_file_data = find_file_data, // TODO guaranteed copy elision | | |
| 522 | .first = true, | | |
| 523 | .name_data = undefined, | | |
| 524 | }; | | |
| 525 | }, | | |
| 526 | .macosx, .ios, .freebsd, .netbsd => Handle{ | | |
| 527 | .fd = try os.open(dir_path, os.O_RDONLY | os.O_NONBLOCK | os.O_DIRECTORY | os.O_CLOEXEC, 0), | | |
| 528 | .seek = 0, | | |
| 529 | .index = 0, | | |
| 530 | .end_index = 0, | | |
| 531 | .buf = [_]u8{}, | | |
| 532 | }, | | |
| 533 | .linux => Handle{ | | |
| 534 | .fd = try os.open(dir_path, os.O_RDONLY | os.O_DIRECTORY | os.O_CLOEXEC, 0), | | |
| 535 | .index = 0, | | |
| 536 | .end_index = 0, | | |
| 537 | .buf = [_]u8{}, | | |
| 538 | }, | | |
| 539 | else => @compileError("unimplemented"), | | |
| 540 | }, | | |
| 541 | }; | | |
| 542 | } | 598 | } |
| 543 | | 599 | |
| 544 | pub fn close(self: *Dir) void { | 600 | /// Same as `open` except the parameter is null-terminated. |
| 545 | if (os.windows.is_the_target) { | 601 | pub fn openC(dir_path_c: [*]const u8) OpenError!Dir { |
| 546 | return os.windows.FindClose(self.handle.handle); | 602 | return posix_cwd.openDirC(dir_path_c); |
| 547 | } | | |
| 548 | os.close(self.handle.fd); | | |
| 549 | } | 603 | } |
| 550 | | 604 | |
| 551 | /// Memory such as file names referenced in this returned entry becomes invalid | 605 | pub fn close(self: *Dir) void { |
| 552 | /// with subsequent calls to next, as well as when this `Dir` is deinitialized. | 606 | if (os.windows.is_the_target) { |
| 553 | pub fn next(self: *Dir) !?Entry { | 607 | @panic("TODO"); |
| 554 | switch (builtin.os) { | | |
| 555 | .linux => return self.nextLinux(), | | |
| 556 | .macosx, .ios => return self.nextDarwin(), | | |
| 557 | .windows => return self.nextWindows(), | | |
| 558 | .freebsd => return self.nextBsd(), | | |
| 559 | .netbsd => return self.nextBsd(), | | |
| 560 | else => @compileError("unimplemented"), | | |
| 561 | } | 608 | } |
| | 609 | os.close(self.fd); |
| | 610 | self.* = undefined; |
| 562 | } | 611 | } |
| 563 | | 612 | |
| 564 | pub fn openRead(self: Dir, file_path: []const u8) os.OpenError!File { | 613 | /// Call `File.close` on the result when done. |
| 565 | const path_c = try os.toPosixPath(file_path); | 614 | pub fn openRead(self: Dir, sub_path: []const u8) File.OpenError!File { |
| | 615 | const path_c = try os.toPosixPath(sub_path); |
| 566 | return self.openReadC(&path_c); | 616 | return self.openReadC(&path_c); |
| 567 | } | 617 | } |
| 568 | | 618 | |
| 569 | pub fn openReadC(self: Dir, file_path: [*]const u8) OpenError!File { | 619 | /// Call `File.close` on the result when done. |
| | 620 | pub fn openReadC(self: Dir, sub_path: [*]const u8) File.OpenError!File { |
| 570 | const flags = os.O_LARGEFILE | os.O_RDONLY; | 621 | const flags = os.O_LARGEFILE | os.O_RDONLY; |
| 571 | const fd = try os.openatC(self.handle.fd, file_path, flags, 0); | 622 | const fd = try os.openatC(self.fd, sub_path, flags, 0); |
| 572 | return File.openHandle(fd); | 623 | return File.openHandle(fd); |
| 573 | } | 624 | } |
| 574 | | 625 | |
| 575 | fn nextDarwin(self: *Dir) !?Entry { | 626 | /// Call `close` on the result when done. |
| 576 | start_over: while (true) { | 627 | pub fn openDir(self: Dir, sub_path: []const u8) OpenError!Dir { |
| 577 | if (self.handle.index >= self.handle.end_index) { | 628 | const sub_path_c = try os.toPosixPath(sub_path); |
| 578 | while (true) { | 629 | return self.openDirC(&sub_path_c); |
| 579 | const rc = os.system.__getdirentries64( | 630 | } |
| 580 | self.handle.fd, | | |
| 581 | self.handle.buf[0..].ptr, | | |
| 582 | self.handle.buf.len, | | |
| 583 | &self.handle.seek, | | |
| 584 | ); | | |
| 585 | if (rc == 0) return null; | | |
| 586 | if (rc < 0) { | | |
| 587 | switch (os.errno(rc)) { | | |
| 588 | os.EBADF => unreachable, | | |
| 589 | os.EFAULT => unreachable, | | |
| 590 | os.ENOTDIR => unreachable, | | |
| 591 | os.EINVAL => unreachable, | | |
| 592 | else => |err| return os.unexpectedErrno(err), | | |
| 593 | } | | |
| 594 | } | | |
| 595 | self.handle.index = 0; | | |
| 596 | self.handle.end_index = @intCast(usize, rc); | | |
| 597 | break; | | |
| 598 | } | | |
| 599 | } | | |
| 600 | const darwin_entry = @ptrCast(*align(1) os.dirent, &self.handle.buf[self.handle.index]); | | |
| 601 | const next_index = self.handle.index + darwin_entry.d_reclen; | | |
| 602 | self.handle.index = next_index; | | |
| 603 | | 631 | |
| 604 | const name = @ptrCast([*]u8, &darwin_entry.d_name)[0..darwin_entry.d_namlen]; | 632 | /// Call `close` on the result when done. |
| | 633 | pub fn openDirC(self: Dir, sub_path: [*]const u8) OpenError!Dir { |
| | 634 | const flags = os.O_RDONLY | os.O_DIRECTORY | os.O_CLOEXEC; |
| | 635 | const fd = os.openatC(self.fd, sub_path, flags, 0) catch |err| switch (err) { |
| | 636 | error.FileTooBig => unreachable, // can't happen for directories |
| | 637 | error.IsDir => unreachable, // we're providing O_DIRECTORY |
| | 638 | error.NoSpaceLeft => unreachable, // not providing O_CREAT |
| | 639 | error.PathAlreadyExists => unreachable, // not providing O_CREAT |
| | 640 | else => |e| return e, |
| | 641 | }; |
| | 642 | return Dir{ .fd = fd }; |
| | 643 | } |
| 605 | | 644 | |
| 606 | if (mem.eql(u8, name, ".") or mem.eql(u8, name, "..")) { | 645 | pub const DeleteFileError = os.UnlinkError; |
| 607 | continue :start_over; | | |
| 608 | } | | |
| 609 | | 646 | |
| 610 | const entry_kind = switch (darwin_entry.d_type) { | 647 | /// Delete a file name and possibly the file it refers to, based on an open directory handle. |
| 611 | os.DT_BLK => Entry.Kind.BlockDevice, | 648 | pub fn deleteFile(self: Dir, sub_path: []const u8) DeleteFileError!void { |
| 612 | os.DT_CHR => Entry.Kind.CharacterDevice, | 649 | const sub_path_c = try os.toPosixPath(sub_path); |
| 613 | os.DT_DIR => Entry.Kind.Directory, | 650 | return self.deleteFileC(&sub_path_c); |
| 614 | os.DT_FIFO => Entry.Kind.NamedPipe, | | |
| 615 | os.DT_LNK => Entry.Kind.SymLink, | | |
| 616 | os.DT_REG => Entry.Kind.File, | | |
| 617 | os.DT_SOCK => Entry.Kind.UnixDomainSocket, | | |
| 618 | os.DT_WHT => Entry.Kind.Whiteout, | | |
| 619 | else => Entry.Kind.Unknown, | | |
| 620 | }; | | |
| 621 | return Entry{ | | |
| 622 | .name = name, | | |
| 623 | .kind = entry_kind, | | |
| 624 | }; | | |
| 625 | } | | |
| 626 | } | 651 | } |
| 627 | | 652 | |
| 628 | fn nextWindows(self: *Dir) !?Entry { | 653 | /// Same as `deleteFile` except the parameter is null-terminated. |
| 629 | while (true) { | 654 | pub fn deleteFileC(self: Dir, sub_path_c: [*]const u8) DeleteFileError!void { |
| 630 | if (self.handle.first) { | 655 | os.unlinkatC(self.fd, sub_path_c, 0) catch |err| switch (err) { |
| 631 | self.handle.first = false; | 656 | error.DirNotEmpty => unreachable, // not passing AT_REMOVEDIR |
| 632 | } else { | 657 | else => |e| return e, |
| 633 | if (!try os.windows.FindNextFile(self.handle.handle, &self.handle.find_file_data)) | 658 | }; |
| 634 | return null; | | |
| 635 | } | | |
| 636 | const name_utf16le = mem.toSlice(u16, self.handle.find_file_data.cFileName[0..].ptr); | | |
| 637 | if (mem.eql(u16, name_utf16le, [_]u16{'.'}) or mem.eql(u16, name_utf16le, [_]u16{ '.', '.' })) | | |
| 638 | continue; | | |
| 639 | // Trust that Windows gives us valid UTF-16LE | | |
| 640 | const name_utf8_len = std.unicode.utf16leToUtf8(self.handle.name_data[0..], name_utf16le) catch unreachable; | | |
| 641 | const name_utf8 = self.handle.name_data[0..name_utf8_len]; | | |
| 642 | const kind = blk: { | | |
| 643 | const attrs = self.handle.find_file_data.dwFileAttributes; | | |
| 644 | if (attrs & os.windows.FILE_ATTRIBUTE_DIRECTORY != 0) break :blk Entry.Kind.Directory; | | |
| 645 | if (attrs & os.windows.FILE_ATTRIBUTE_REPARSE_POINT != 0) break :blk Entry.Kind.SymLink; | | |
| 646 | break :blk Entry.Kind.File; | | |
| 647 | }; | | |
| 648 | return Entry{ | | |
| 649 | .name = name_utf8, | | |
| 650 | .kind = kind, | | |
| 651 | }; | | |
| 652 | } | | |
| 653 | } | 659 | } |
| 654 | | 660 | |
| 655 | fn nextLinux(self: *Dir) !?Entry { | 661 | pub const DeleteDirError = error{ |
| 656 | start_over: while (true) { | 662 | DirNotEmpty, |
| 657 | if (self.handle.index >= self.handle.end_index) { | 663 | FileNotFound, |
| 658 | while (true) { | 664 | AccessDenied, |
| 659 | const rc = os.linux.getdents64(self.handle.fd, self.handle.buf[0..].ptr, self.handle.buf.len); | 665 | FileBusy, |
| 660 | switch (os.linux.getErrno(rc)) { | 666 | FileSystem, |
| 661 | 0 => {}, | 667 | SymLinkLoop, |
| 662 | os.EBADF => unreachable, | 668 | NameTooLong, |
| 663 | os.EFAULT => unreachable, | 669 | NotDir, |
| 664 | os.ENOTDIR => unreachable, | 670 | SystemResources, |
| 665 | os.EINVAL => unreachable, | 671 | ReadOnlyFileSystem, |
| 666 | else => |err| return os.unexpectedErrno(err), | 672 | InvalidUtf8, |
| 667 | } | 673 | BadPathName, |
| 668 | if (rc == 0) return null; | 674 | Unexpected, |
| 669 | self.handle.index = 0; | 675 | }; |
| 670 | self.handle.end_index = rc; | | |
| 671 | break; | | |
| 672 | } | | |
| 673 | } | | |
| 674 | const linux_entry = @ptrCast(*align(1) os.dirent64, &self.handle.buf[self.handle.index]); | | |
| 675 | const next_index = self.handle.index + linux_entry.d_reclen; | | |
| 676 | self.handle.index = next_index; | | |
| 677 | | 676 | |
| 678 | const name = mem.toSlice(u8, @ptrCast([*]u8, &linux_entry.d_name)); | 677 | /// Returns `error.DirNotEmpty` if the directory is not empty. |
| | 678 | /// To delete a directory recursively, see `deleteTree`. |
| | 679 | pub fn deleteDir(self: Dir, sub_path: []const u8) DeleteDirError!void { |
| | 680 | const sub_path_c = try os.toPosixPath(sub_path); |
| | 681 | return self.deleteDirC(&sub_path_c); |
| | 682 | } |
| 679 | | 683 | |
| 680 | // skip . and .. entries | 684 | /// Same as `deleteDir` except the parameter is null-terminated. |
| 681 | if (mem.eql(u8, name, ".") or mem.eql(u8, name, "..")) { | 685 | pub fn deleteDirC(self: Dir, sub_path_c: [*]const u8) DeleteDirError!void { |
| 682 | continue :start_over; | 686 | os.unlinkatC(self.fd, sub_path_c, os.AT_REMOVEDIR) catch |err| switch (err) { |
| 683 | } | 687 | error.IsDir => unreachable, // not possible since we pass AT_REMOVEDIR |
| | 688 | else => |e| return e, |
| | 689 | }; |
| | 690 | } |
| 684 | | 691 | |
| 685 | const entry_kind = switch (linux_entry.d_type) { | 692 | pub fn iterate(self: Dir) Iterator { |
| 686 | os.DT_BLK => Entry.Kind.BlockDevice, | 693 | switch (builtin.os) { |
| 687 | os.DT_CHR => Entry.Kind.CharacterDevice, | 694 | .macosx, .ios, .freebsd, .netbsd => return Iterator{ |
| 688 | os.DT_DIR => Entry.Kind.Directory, | 695 | .dir = self, |
| 689 | os.DT_FIFO => Entry.Kind.NamedPipe, | 696 | .seek = 0, |
| 690 | os.DT_LNK => Entry.Kind.SymLink, | 697 | .index = 0, |
| 691 | os.DT_REG => Entry.Kind.File, | 698 | .end_index = 0, |
| 692 | os.DT_SOCK => Entry.Kind.UnixDomainSocket, | 699 | .buf = undefined, |
| 693 | else => Entry.Kind.Unknown, | 700 | }, |
| 694 | }; | 701 | .linux => return Iterator{ |
| 695 | return Entry{ | 702 | .dir = self, |
| 696 | .name = name, | 703 | .index = 0, |
| 697 | .kind = entry_kind, | 704 | .end_index = 0, |
| 698 | }; | 705 | .buf = undefined, |
| | 706 | }, |
| | 707 | .windows => @panic("TODO"), |
| | 708 | else => @compileError("unimplemented"), |
| 699 | } | 709 | } |
| 700 | } | 710 | } |
| 701 | | 711 | |
| 702 | fn nextBsd(self: *Dir) !?Entry { | 712 | pub const DeleteTreeError = error{ |
| 703 | start_over: while (true) { | 713 | AccessDenied, |
| 704 | if (self.handle.index >= self.handle.end_index) { | 714 | FileTooBig, |
| 705 | while (true) { | 715 | SymLinkLoop, |
| 706 | const rc = os.system.getdirentries( | 716 | ProcessFdQuotaExceeded, |
| 707 | self.handle.fd, | 717 | NameTooLong, |
| 708 | self.handle.buf[0..].ptr, | 718 | SystemFdQuotaExceeded, |
| 709 | self.handle.buf.len, | 719 | NoDevice, |
| 710 | &self.handle.seek, | 720 | SystemResources, |
| 711 | ); | 721 | ReadOnlyFileSystem, |
| 712 | switch (os.errno(rc)) { | 722 | FileSystem, |
| 713 | 0 => {}, | 723 | FileBusy, |
| 714 | os.EBADF => unreachable, | 724 | DeviceBusy, |
| 715 | os.EFAULT => unreachable, | | |
| 716 | os.ENOTDIR => unreachable, | | |
| 717 | os.EINVAL => unreachable, | | |
| 718 | else => |err| return os.unexpectedErrno(err), | | |
| 719 | } | | |
| 720 | if (rc == 0) return null; | | |
| 721 | self.handle.index = 0; | | |
| 722 | self.handle.end_index = @intCast(usize, rc); | | |
| 723 | break; | | |
| 724 | } | | |
| 725 | } | | |
| 726 | const freebsd_entry = @ptrCast(*align(1) os.dirent, &self.handle.buf[self.handle.index]); | | |
| 727 | const next_index = self.handle.index + freebsd_entry.d_reclen; | | |
| 728 | self.handle.index = next_index; | | |
| 729 | | 725 | |
| 730 | const name = @ptrCast([*]u8, &freebsd_entry.d_name)[0..freebsd_entry.d_namlen]; | 726 | /// One of the path components was not a directory. |
| | 727 | /// This error is unreachable if `sub_path` does not contain a path separator. |
| | 728 | NotDir, |
| 731 | | 729 | |
| 732 | if (mem.eql(u8, name, ".") or mem.eql(u8, name, "..")) { | 730 | /// On Windows, file paths must be valid Unicode. |
| 733 | continue :start_over; | 731 | InvalidUtf8, |
| | 732 | |
| | 733 | /// On Windows, file paths cannot contain these characters: |
| | 734 | /// '/', '*', '?', '"', '<', '>', '|' |
| | 735 | BadPathName, |
| | 736 | } || os.UnexpectedError; |
| | 737 | |
| | 738 | /// Whether `full_path` describes a symlink, file, or directory, this function |
| | 739 | /// removes it. If it cannot be removed because it is a non-empty directory, |
| | 740 | /// this function recursively removes its entries and then tries again. |
| | 741 | /// This operation is not atomic on most file systems. |
| | 742 | pub fn deleteTree(self: Dir, sub_path: []const u8) DeleteTreeError!void { |
| | 743 | start_over: while (true) { |
| | 744 | var got_access_denied = false; |
| | 745 | // First, try deleting the item as a file. This way we don't follow sym links. |
| | 746 | if (self.deleteFile(sub_path)) { |
| | 747 | return; |
| | 748 | } else |err| switch (err) { |
| | 749 | error.FileNotFound => return, |
| | 750 | error.IsDir => {}, |
| | 751 | error.AccessDenied => got_access_denied = true, |
| | 752 | |
| | 753 | error.InvalidUtf8, |
| | 754 | error.SymLinkLoop, |
| | 755 | error.NameTooLong, |
| | 756 | error.SystemResources, |
| | 757 | error.ReadOnlyFileSystem, |
| | 758 | error.NotDir, |
| | 759 | error.FileSystem, |
| | 760 | error.FileBusy, |
| | 761 | error.BadPathName, |
| | 762 | error.Unexpected, |
| | 763 | => |e| return e, |
| 734 | } | 764 | } |
| | 765 | var dir = self.openDir(sub_path) catch |err| switch (err) { |
| | 766 | error.NotDir => { |
| | 767 | if (got_access_denied) { |
| | 768 | return error.AccessDenied; |
| | 769 | } |
| | 770 | continue :start_over; |
| | 771 | }, |
| | 772 | error.FileNotFound => { |
| | 773 | // That's fine, we were trying to remove this directory anyway. |
| | 774 | continue :start_over; |
| | 775 | }, |
| 735 | | 776 | |
| 736 | const entry_kind = switch (freebsd_entry.d_type) { | 777 | error.AccessDenied, |
| 737 | os.DT_BLK => Entry.Kind.BlockDevice, | 778 | error.SymLinkLoop, |
| 738 | os.DT_CHR => Entry.Kind.CharacterDevice, | 779 | error.ProcessFdQuotaExceeded, |
| 739 | os.DT_DIR => Entry.Kind.Directory, | 780 | error.NameTooLong, |
| 740 | os.DT_FIFO => Entry.Kind.NamedPipe, | 781 | error.SystemFdQuotaExceeded, |
| 741 | os.DT_LNK => Entry.Kind.SymLink, | 782 | error.NoDevice, |
| 742 | os.DT_REG => Entry.Kind.File, | 783 | error.SystemResources, |
| 743 | os.DT_SOCK => Entry.Kind.UnixDomainSocket, | 784 | error.Unexpected, |
| 744 | os.DT_WHT => Entry.Kind.Whiteout, | 785 | error.InvalidUtf8, |
| 745 | else => Entry.Kind.Unknown, | 786 | error.BadPathName, |
| 746 | }; | 787 | error.DeviceBusy, |
| 747 | return Entry{ | 788 | => |e| return e, |
| 748 | .name = name, | | |
| 749 | .kind = entry_kind, | | |
| 750 | }; | 789 | }; |
| | 790 | var cleanup_dir_parent: ?Dir = null; |
| | 791 | defer if (cleanup_dir_parent) |*d| d.close(); |
| | 792 | |
| | 793 | var cleanup_dir = true; |
| | 794 | defer if (cleanup_dir) dir.close(); |
| | 795 | |
| | 796 | var dir_name_buf: [MAX_PATH_BYTES]u8 = undefined; |
| | 797 | var dir_name: []const u8 = sub_path; |
| | 798 | var parent_dir = self; |
| | 799 | |
| | 800 | // Here we must avoid recursion, in order to provide O(1) memory guarantee of this function. |
| | 801 | // Go through each entry and if it is not a directory, delete it. If it is a directory, |
| | 802 | // open it, and close the original directory. Repeat. Then start the entire operation over. |
| | 803 | |
| | 804 | scan_dir: while (true) { |
| | 805 | var dir_it = dir.iterate(); |
| | 806 | while (try dir_it.next()) |entry| { |
| | 807 | if (dir.deleteFile(entry.name)) { |
| | 808 | continue; |
| | 809 | } else |err| switch (err) { |
| | 810 | error.FileNotFound => continue, |
| | 811 | |
| | 812 | // Impossible because we do not pass any path separators. |
| | 813 | error.NotDir => unreachable, |
| | 814 | |
| | 815 | error.IsDir => {}, |
| | 816 | error.AccessDenied => got_access_denied = true, |
| | 817 | |
| | 818 | error.InvalidUtf8, |
| | 819 | error.SymLinkLoop, |
| | 820 | error.NameTooLong, |
| | 821 | error.SystemResources, |
| | 822 | error.ReadOnlyFileSystem, |
| | 823 | error.FileSystem, |
| | 824 | error.FileBusy, |
| | 825 | error.BadPathName, |
| | 826 | error.Unexpected, |
| | 827 | => |e| return e, |
| | 828 | } |
| | 829 | |
| | 830 | const new_dir = dir.openDir(entry.name) catch |err| switch (err) { |
| | 831 | error.NotDir => { |
| | 832 | if (got_access_denied) { |
| | 833 | return error.AccessDenied; |
| | 834 | } |
| | 835 | continue :scan_dir; |
| | 836 | }, |
| | 837 | error.FileNotFound => { |
| | 838 | // That's fine, we were trying to remove this directory anyway. |
| | 839 | continue :scan_dir; |
| | 840 | }, |
| | 841 | |
| | 842 | error.AccessDenied, |
| | 843 | error.SymLinkLoop, |
| | 844 | error.ProcessFdQuotaExceeded, |
| | 845 | error.NameTooLong, |
| | 846 | error.SystemFdQuotaExceeded, |
| | 847 | error.NoDevice, |
| | 848 | error.SystemResources, |
| | 849 | error.Unexpected, |
| | 850 | error.InvalidUtf8, |
| | 851 | error.BadPathName, |
| | 852 | error.DeviceBusy, |
| | 853 | => |e| return e, |
| | 854 | }; |
| | 855 | if (cleanup_dir_parent) |*d| d.close(); |
| | 856 | cleanup_dir_parent = dir; |
| | 857 | dir = new_dir; |
| | 858 | mem.copy(u8, &dir_name_buf, entry.name); |
| | 859 | dir_name = dir_name_buf[0..entry.name.len]; |
| | 860 | continue :scan_dir; |
| | 861 | } |
| | 862 | // Reached the end of the directory entries, which means we successfully deleted all of them. |
| | 863 | // Now to remove the directory itself. |
| | 864 | dir.close(); |
| | 865 | cleanup_dir = false; |
| | 866 | |
| | 867 | if (cleanup_dir_parent) |d| { |
| | 868 | d.deleteDir(dir_name) catch |err| switch (err) { |
| | 869 | // These two things can happen due to file system race conditions. |
| | 870 | error.FileNotFound, error.DirNotEmpty => continue :start_over, |
| | 871 | else => |e| return e, |
| | 872 | }; |
| | 873 | continue :start_over; |
| | 874 | } else { |
| | 875 | self.deleteDir(sub_path) catch |err| switch (err) { |
| | 876 | error.FileNotFound => return, |
| | 877 | error.DirNotEmpty => continue :start_over, |
| | 878 | else => |e| return e, |
| | 879 | }; |
| | 880 | return; |
| | 881 | } |
| | 882 | } |
| 751 | } | 883 | } |
| 752 | } | 884 | } |
| 753 | }; | 885 | }; |
| ... | @@ -757,13 +889,18 @@ pub const Walker = struct { | ... | @@ -757,13 +889,18 @@ pub const Walker = struct { |
| 757 | name_buffer: std.Buffer, | 889 | name_buffer: std.Buffer, |
| 758 | | 890 | |
| 759 | pub const Entry = struct { | 891 | pub const Entry = struct { |
| 760 | path: []const u8, | 892 | /// The containing directory. This can be used to operate directly on `basename` |
| | 893 | /// rather than `path`, avoiding `error.NameTooLong` for deeply nested paths. |
| | 894 | /// The directory remains open until `next` or `deinit` is called. |
| | 895 | dir: Dir, |
| 761 | basename: []const u8, | 896 | basename: []const u8, |
| | 897 | |
| | 898 | path: []const u8, |
| 762 | kind: Dir.Entry.Kind, | 899 | kind: Dir.Entry.Kind, |
| 763 | }; | 900 | }; |
| 764 | | 901 | |
| 765 | const StackItem = struct { | 902 | const StackItem = struct { |
| 766 | dir_it: Dir, | 903 | dir_it: Dir.Iterator, |
| 767 | dirname_len: usize, | 904 | dirname_len: usize, |
| 768 | }; | 905 | }; |
| 769 | | 906 | |
| ... | @@ -781,23 +918,26 @@ pub const Walker = struct { | ... | @@ -781,23 +918,26 @@ pub const Walker = struct { |
| 781 | try self.name_buffer.appendByte(path.sep); | 918 | try self.name_buffer.appendByte(path.sep); |
| 782 | try self.name_buffer.append(base.name); | 919 | try self.name_buffer.append(base.name); |
| 783 | if (base.kind == .Directory) { | 920 | if (base.kind == .Directory) { |
| 784 | // TODO https://github.com/ziglang/zig/issues/2888 | 921 | var new_dir = top.dir_it.dir.openDir(base.name) catch |err| switch (err) { |
| 785 | var new_dir = try Dir.open(self.name_buffer.toSliceConst()); | 922 | error.NameTooLong => unreachable, // no path sep in base.name |
| | 923 | else => |e| return e, |
| | 924 | }; |
| 786 | { | 925 | { |
| 787 | errdefer new_dir.close(); | 926 | errdefer new_dir.close(); |
| 788 | try self.stack.append(StackItem{ | 927 | try self.stack.append(StackItem{ |
| 789 | .dir_it = new_dir, | 928 | .dir_it = new_dir.iterate(), |
| 790 | .dirname_len = self.name_buffer.len(), | 929 | .dirname_len = self.name_buffer.len(), |
| 791 | }); | 930 | }); |
| 792 | } | 931 | } |
| 793 | } | 932 | } |
| 794 | return Entry{ | 933 | return Entry{ |
| | 934 | .dir = top.dir_it.dir, |
| 795 | .basename = self.name_buffer.toSliceConst()[dirname_len + 1 ..], | 935 | .basename = self.name_buffer.toSliceConst()[dirname_len + 1 ..], |
| 796 | .path = self.name_buffer.toSliceConst(), | 936 | .path = self.name_buffer.toSliceConst(), |
| 797 | .kind = base.kind, | 937 | .kind = base.kind, |
| 798 | }; | 938 | }; |
| 799 | } else { | 939 | } else { |
| 800 | self.stack.pop().dir_it.close(); | 940 | self.stack.pop().dir_it.dir.close(); |
| 801 | } | 941 | } |
| 802 | } | 942 | } |
| 803 | } | 943 | } |
| ... | @@ -812,12 +952,13 @@ pub const Walker = struct { | ... | @@ -812,12 +952,13 @@ pub const Walker = struct { |
| 812 | /// Recursively iterates over a directory. | 952 | /// Recursively iterates over a directory. |
| 813 | /// Must call `Walker.deinit` when done. | 953 | /// Must call `Walker.deinit` when done. |
| 814 | /// `dir_path` must not end in a path separator. | 954 | /// `dir_path` must not end in a path separator. |
| | 955 | /// The order of returned file system entries is undefined. |
| 815 | /// TODO: https://github.com/ziglang/zig/issues/2888 | 956 | /// TODO: https://github.com/ziglang/zig/issues/2888 |
| 816 | pub fn walkPath(allocator: *Allocator, dir_path: []const u8) !Walker { | 957 | pub fn walkPath(allocator: *Allocator, dir_path: []const u8) !Walker { |
| 817 | assert(!mem.endsWith(u8, dir_path, path.sep_str)); | 958 | assert(!mem.endsWith(u8, dir_path, path.sep_str)); |
| 818 | | 959 | |
| 819 | var dir_it = try Dir.open(dir_path); | 960 | var dir = try Dir.open(dir_path); |
| 820 | errdefer dir_it.close(); | 961 | errdefer dir.close(); |
| 821 | | 962 | |
| 822 | var name_buffer = try std.Buffer.init(allocator, dir_path); | 963 | var name_buffer = try std.Buffer.init(allocator, dir_path); |
| 823 | errdefer name_buffer.deinit(); | 964 | errdefer name_buffer.deinit(); |
| ... | @@ -828,7 +969,7 @@ pub fn walkPath(allocator: *Allocator, dir_path: []const u8) !Walker { | ... | @@ -828,7 +969,7 @@ pub fn walkPath(allocator: *Allocator, dir_path: []const u8) !Walker { |
| 828 | }; | 969 | }; |
| 829 | | 970 | |
| 830 | try walker.stack.append(Walker.StackItem{ | 971 | try walker.stack.append(Walker.StackItem{ |
| 831 | .dir_it = dir_it, | 972 | .dir_it = dir.iterate(), |
| 832 | .dirname_len = dir_path.len, | 973 | .dirname_len = dir_path.len, |
| 833 | }); | 974 | }); |
| 834 | | 975 | |