| ... | ... | @@ -398,6 +398,485 @@ pub const File = struct { |
| 398 | 398 | try os.fchown(self.handle, owner, group); |
| 399 | 399 | } |
| 400 | 400 | |
| 401 | /// Cross-platform representation of permissions on a file. |
| 402 | /// The `readonly` and `setReadonly` are the only methods available across all platforms. |
| 403 | /// Platform-specific functionality is available through the `inner` field. |
| 404 | pub const Permissions = struct { |
| 405 | /// You may use the `inner` field to use platform-specific functionality |
| 406 | inner: switch (builtin.os.tag) { |
| 407 | .windows => PermissionsWindows, |
| 408 | else => PermissionsUnix, |
| 409 | }, |
| 410 | |
| 411 | const Self = @This(); |
| 412 | |
| 413 | /// Returns `true` if permissions represent an unwritable file. |
| 414 | /// On Unix, `true` is returned only if no class has write permissions. |
| 415 | pub fn readOnly(self: Self) bool { |
| 416 | return self.inner.readOnly(); |
| 417 | } |
| 418 | |
| 419 | /// Sets whether write permissions are provided. |
| 420 | /// On Unix, this affects *all* classes. If this is undesired, use `unixSet` |
| 421 | /// This method *DOES NOT* set permissions on the filesystem: use `File.setPermissions(permissions)` |
| 422 | pub fn setReadOnly(self: *Self, read_only: bool) void { |
| 423 | self.inner.setReadOnly(read_only); |
| 424 | } |
| 425 | }; |
| 426 | |
| 427 | pub const PermissionsWindows = struct { |
| 428 | attributes: os.windows.DWORD, |
| 429 | |
| 430 | const Self = @This(); |
| 431 | |
| 432 | /// Returns `true` if permissions represent an unwritable file. |
| 433 | pub fn readOnly(self: Self) bool { |
| 434 | return self.attributes & os.windows.FILE_ATTRIBUTE_READONLY != 0; |
| 435 | } |
| 436 | |
| 437 | /// Sets whether write permissions are provided. |
| 438 | /// This method *DOES NOT* set permissions on the filesystem: use `File.setPermissions(permissions)` |
| 439 | pub fn setReadOnly(self: *Self, read_only: bool) void { |
| 440 | if (read_only) { |
| 441 | self.attributes |= os.windows.FILE_ATTRIBUTE_READONLY; |
| 442 | } else { |
| 443 | self.attributes &= ~@as(os.windows.DWORD, os.windows.FILE_ATTRIBUTE_READONLY); |
| 444 | } |
| 445 | } |
| 446 | }; |
| 447 | |
| 448 | pub const PermissionsUnix = struct { |
| 449 | mode: Mode, |
| 450 | |
| 451 | const Self = @This(); |
| 452 | |
| 453 | /// Returns `true` if permissions represent an unwritable file. |
| 454 | /// `true` is returned only if no class has write permissions. |
| 455 | pub fn readOnly(self: Self) bool { |
| 456 | return self.mode & 0o222 == 0; |
| 457 | } |
| 458 | |
| 459 | /// Sets whether write permissions are provided. |
| 460 | /// This affects *all* classes. If this is undesired, use `unixSet` |
| 461 | /// This method *DOES NOT* set permissions on the filesystem: use `File.setPermissions(permissions)` |
| 462 | pub fn setReadOnly(self: *Self, read_only: bool) void { |
| 463 | if (read_only) { |
| 464 | self.mode &= ~@as(Mode, 0o222); |
| 465 | } else { |
| 466 | self.mode |= @as(Mode, 0o222); |
| 467 | } |
| 468 | } |
| 469 | |
| 470 | pub const Class = enum(u2) { |
| 471 | user = 2, |
| 472 | group = 1, |
| 473 | other = 0, |
| 474 | }; |
| 475 | |
| 476 | pub const Permission = enum(u3) { |
| 477 | read = 0o4, |
| 478 | write = 0o2, |
| 479 | execute = 0o1, |
| 480 | }; |
| 481 | |
| 482 | /// Returns `true` if the chosen class has the selected permission. |
| 483 | /// This method is only available on Unix platforms. |
| 484 | pub fn unixHas(self: Self, class: Class, permission: Permission) bool { |
| 485 | const mask = @as(Mode, @enumToInt(permission)) << @as(u3, @enumToInt(class)) * 3; |
| 486 | return self.mode & mask != 0; |
| 487 | } |
| 488 | |
| 489 | /// Sets the permissions for the chosen class. Any permissions set to `null` are left unchanged. |
| 490 | /// This method *DOES NOT* set permissions on the filesystem: use `File.setPermissions(permissions)` |
| 491 | pub fn unixSet(self: *Self, class: Class, permissions: struct { |
| 492 | read: ?bool = null, |
| 493 | write: ?bool = null, |
| 494 | execute: ?bool = null, |
| 495 | }) void { |
| 496 | const shift = @as(u3, @enumToInt(class)) * 3; |
| 497 | if (permissions.read) |r| { |
| 498 | if (r) { |
| 499 | self.mode |= @as(Mode, 0o4) << shift; |
| 500 | } else { |
| 501 | self.mode &= ~(@as(Mode, 0o4) << shift); |
| 502 | } |
| 503 | } |
| 504 | if (permissions.write) |w| { |
| 505 | if (w) { |
| 506 | self.mode |= @as(Mode, 0o2) << shift; |
| 507 | } else { |
| 508 | self.mode &= ~(@as(Mode, 0o2) << shift); |
| 509 | } |
| 510 | } |
| 511 | if (permissions.execute) |x| { |
| 512 | if (x) { |
| 513 | self.mode |= @as(Mode, 0o1) << shift; |
| 514 | } else { |
| 515 | self.mode &= ~(@as(Mode, 0o1) << shift); |
| 516 | } |
| 517 | } |
| 518 | } |
| 519 | |
| 520 | /// Returns a `Permissions` struct representing the permissions from the passed mode. |
| 521 | pub fn unixNew(new_mode: Mode) Self { |
| 522 | return Self{ |
| 523 | .mode = new_mode, |
| 524 | }; |
| 525 | } |
| 526 | }; |
| 527 | |
| 528 | pub const SetPermissionsError = ChmodError; |
| 529 | |
| 530 | /// Sets permissions according to the provided `Permissions` struct. |
| 531 | /// This method is *NOT* available on WASI |
| 532 | pub fn setPermissions(self: File, permissions: Permissions) SetPermissionsError!void { |
| 533 | switch (builtin.os.tag) { |
| 534 | .windows => { |
| 535 | var io_status_block: windows.IO_STATUS_BLOCK = undefined; |
| 536 | var info = windows.FILE_BASIC_INFORMATION{ |
| 537 | .CreationTime = 0, |
| 538 | .LastAccessTime = 0, |
| 539 | .LastWriteTime = 0, |
| 540 | .ChangeTime = 0, |
| 541 | .FileAttributes = permissions.inner.attributes, |
| 542 | }; |
| 543 | const rc = windows.ntdll.NtSetInformationFile( |
| 544 | self.handle, |
| 545 | &io_status_block, |
| 546 | &info, |
| 547 | @sizeOf(windows.FILE_BASIC_INFORMATION), |
| 548 | .FileBasicInformation, |
| 549 | ); |
| 550 | switch (rc) { |
| 551 | .SUCCESS => return, |
| 552 | .INVALID_HANDLE => unreachable, |
| 553 | .ACCESS_DENIED => return error.AccessDenied, |
| 554 | else => return windows.unexpectedStatus(rc), |
| 555 | } |
| 556 | }, |
| 557 | .wasi => @compileError("Unsupported OS"), // Wasi filesystem does not *yet* support chmod |
| 558 | else => { |
| 559 | try self.chmod(permissions.inner.mode); |
| 560 | }, |
| 561 | } |
| 562 | } |
| 563 | |
| 564 | /// Cross-platform representation of file metadata. |
| 565 | /// Platform-specific functionality is available through the `inner` field. |
| 566 | pub const Metadata = struct { |
| 567 | /// You may use the `inner` field to use platform-specific functionality |
| 568 | inner: switch (builtin.os.tag) { |
| 569 | .windows => MetadataWindows, |
| 570 | .linux => MetadataLinux, |
| 571 | else => MetadataUnix, |
| 572 | }, |
| 573 | |
| 574 | const Self = @This(); |
| 575 | |
| 576 | /// Returns the size of the file |
| 577 | pub fn size(self: Self) u64 { |
| 578 | return self.inner.size(); |
| 579 | } |
| 580 | |
| 581 | /// Returns a `Permissions` struct, representing the permissions on the file |
| 582 | pub fn permissions(self: Self) Permissions { |
| 583 | return self.inner.permissions(); |
| 584 | } |
| 585 | |
| 586 | /// Returns the `Kind` of file. |
| 587 | /// On Windows, can only return: `.File`, `.Directory`, `.SymLink` or `.Unknown` |
| 588 | pub fn kind(self: Self) Kind { |
| 589 | return self.inner.kind(); |
| 590 | } |
| 591 | |
| 592 | /// Returns the last time the file was accessed in nanoseconds since UTC 1970-01-01 |
| 593 | pub fn accessed(self: Self) i128 { |
| 594 | return self.inner.accessed(); |
| 595 | } |
| 596 | |
| 597 | /// Returns the time the file was modified in nanoseconds since UTC 1970-01-01 |
| 598 | pub fn modified(self: Self) i128 { |
| 599 | return self.inner.modified(); |
| 600 | } |
| 601 | |
| 602 | /// Returns the time the file was created in nanoseconds since UTC 1970-01-01 |
| 603 | /// On Windows, this cannot return null |
| 604 | /// On Linux, this returns null if the filesystem does not support creation times, or if the kernel is older than 4.11 |
| 605 | /// On Unices, this returns null if the filesystem or OS does not support creation times |
| 606 | /// On MacOS, this returns the ctime if the filesystem does not support creation times; this is insanity, and yet another reason to hate on Apple |
| 607 | pub fn created(self: Self) ?i128 { |
| 608 | return self.inner.created(); |
| 609 | } |
| 610 | }; |
| 611 | |
| 612 | pub const MetadataUnix = struct { |
| 613 | stat: os.Stat, |
| 614 | |
| 615 | const Self = @This(); |
| 616 | |
| 617 | /// Returns the size of the file |
| 618 | pub fn size(self: Self) u64 { |
| 619 | return @intCast(u64, self.stat.size); |
| 620 | } |
| 621 | |
| 622 | /// Returns a `Permissions` struct, representing the permissions on the file |
| 623 | pub fn permissions(self: Self) Permissions { |
| 624 | return Permissions{ .inner = PermissionsUnix{ .mode = self.stat.mode } }; |
| 625 | } |
| 626 | |
| 627 | /// Returns the `Kind` of the file |
| 628 | pub fn kind(self: Self) Kind { |
| 629 | if (builtin.os.tag == .wasi and !builtin.link_libc) return switch (self.stat.filetype) { |
| 630 | .BLOCK_DEVICE => Kind.BlockDevice, |
| 631 | .CHARACTER_DEVICE => Kind.CharacterDevice, |
| 632 | .DIRECTORY => Kind.Directory, |
| 633 | .SYMBOLIC_LINK => Kind.SymLink, |
| 634 | .REGULAR_FILE => Kind.File, |
| 635 | .SOCKET_STREAM, .SOCKET_DGRAM => Kind.UnixDomainSocket, |
| 636 | else => Kind.Unknown, |
| 637 | }; |
| 638 | |
| 639 | const m = self.stat.mode & os.S.IFMT; |
| 640 | |
| 641 | switch (m) { |
| 642 | os.S.IFBLK => return Kind.BlockDevice, |
| 643 | os.S.IFCHR => return Kind.CharacterDevice, |
| 644 | os.S.IFDIR => return Kind.Directory, |
| 645 | os.S.IFIFO => return Kind.NamedPipe, |
| 646 | os.S.IFLNK => return Kind.SymLink, |
| 647 | os.S.IFREG => return Kind.File, |
| 648 | os.S.IFSOCK => return Kind.UnixDomainSocket, |
| 649 | else => {}, |
| 650 | } |
| 651 | |
| 652 | if (builtin.os.tag == .solaris) switch (m) { |
| 653 | os.S.IFDOOR => return Kind.Door, |
| 654 | os.S.IFPORT => return Kind.EventPort, |
| 655 | else => {}, |
| 656 | }; |
| 657 | |
| 658 | return .Unknown; |
| 659 | } |
| 660 | |
| 661 | /// Returns the last time the file was accessed in nanoseconds since UTC 1970-01-01 |
| 662 | pub fn accessed(self: Self) i128 { |
| 663 | const atime = self.stat.atime(); |
| 664 | return @as(i128, atime.tv_sec) * std.time.ns_per_s + atime.tv_nsec; |
| 665 | } |
| 666 | |
| 667 | /// Returns the last time the file was modified in nanoseconds since UTC 1970-01-01 |
| 668 | pub fn modified(self: Self) i128 { |
| 669 | const mtime = self.stat.mtime(); |
| 670 | return @as(i128, mtime.tv_sec) * std.time.ns_per_s + mtime.tv_nsec; |
| 671 | } |
| 672 | |
| 673 | /// Returns the time the file was created in nanoseconds since UTC 1970-01-01 |
| 674 | /// Returns null if this is not supported by the OS or filesystem |
| 675 | pub fn created(self: Self) ?i128 { |
| 676 | if (!@hasDecl(@TypeOf(self.stat), "birthtime")) return null; |
| 677 | const birthtime = self.stat.birthtime(); |
| 678 | |
| 679 | // If the filesystem doesn't support this the value *should* be: |
| 680 | // On FreeBSD: tv_nsec = 0, tv_sec = -1 |
| 681 | // On NetBSD and OpenBSD: tv_nsec = 0, tv_sec = 0 |
| 682 | // On MacOS, it is set to ctime -- we cannot detect this!! |
| 683 | switch (builtin.os.tag) { |
| 684 | .freebsd => if (birthtime.tv_sec == -1 and birthtime.tv_nsec == 0) return null, |
| 685 | .netbsd, .openbsd => if (birthtime.tv_sec == 0 and birthtime.tv_nsec == 0) return null, |
| 686 | .macos => {}, |
| 687 | else => @compileError("Creation time detection not implemented for OS"), |
| 688 | } |
| 689 | |
| 690 | return @as(i128, birthtime.tv_sec) * std.time.ns_per_s + birthtime.tv_nsec; |
| 691 | } |
| 692 | }; |
| 693 | |
| 694 | /// `MetadataUnix`, but using Linux's `statx` syscall. |
| 695 | /// On Linux versions below 4.11, `statx` will be filled with data from stat. |
| 696 | pub const MetadataLinux = struct { |
| 697 | statx: os.linux.Statx, |
| 698 | |
| 699 | const Self = @This(); |
| 700 | |
| 701 | /// Returns the size of the file |
| 702 | pub fn size(self: Self) u64 { |
| 703 | return self.statx.size; |
| 704 | } |
| 705 | |
| 706 | /// Returns a `Permissions` struct, representing the permissions on the file |
| 707 | pub fn permissions(self: Self) Permissions { |
| 708 | return Permissions{ .inner = PermissionsUnix{ .mode = self.statx.mode } }; |
| 709 | } |
| 710 | |
| 711 | /// Returns the `Kind` of the file |
| 712 | pub fn kind(self: Self) Kind { |
| 713 | const m = self.statx.mode & os.S.IFMT; |
| 714 | |
| 715 | switch (m) { |
| 716 | os.S.IFBLK => return Kind.BlockDevice, |
| 717 | os.S.IFCHR => return Kind.CharacterDevice, |
| 718 | os.S.IFDIR => return Kind.Directory, |
| 719 | os.S.IFIFO => return Kind.NamedPipe, |
| 720 | os.S.IFLNK => return Kind.SymLink, |
| 721 | os.S.IFREG => return Kind.File, |
| 722 | os.S.IFSOCK => return Kind.UnixDomainSocket, |
| 723 | else => {}, |
| 724 | } |
| 725 | |
| 726 | return .Unknown; |
| 727 | } |
| 728 | |
| 729 | /// Returns the last time the file was accessed in nanoseconds since UTC 1970-01-01 |
| 730 | pub fn accessed(self: Self) i128 { |
| 731 | return @as(i128, self.statx.atime.tv_sec) * std.time.ns_per_s + self.statx.atime.tv_nsec; |
| 732 | } |
| 733 | |
| 734 | /// Returns the last time the file was modified in nanoseconds since UTC 1970-01-01 |
| 735 | pub fn modified(self: Self) i128 { |
| 736 | return @as(i128, self.statx.mtime.tv_sec) * std.time.ns_per_s + self.statx.mtime.tv_nsec; |
| 737 | } |
| 738 | |
| 739 | /// Returns the time the file was created in nanoseconds since UTC 1970-01-01 |
| 740 | /// Returns null if this is not supported by the filesystem, or on kernels before than version 4.11 |
| 741 | pub fn created(self: Self) ?i128 { |
| 742 | if (self.statx.mask & os.linux.STATX_BTIME == 0) return null; |
| 743 | return @as(i128, self.statx.btime.tv_sec) * std.time.ns_per_s + self.statx.btime.tv_nsec; |
| 744 | } |
| 745 | }; |
| 746 | |
| 747 | pub const MetadataWindows = struct { |
| 748 | attributes: windows.DWORD, |
| 749 | reparse_tag: windows.DWORD, |
| 750 | _size: u64, |
| 751 | access_time: i128, |
| 752 | modified_time: i128, |
| 753 | creation_time: i128, |
| 754 | |
| 755 | const Self = @This(); |
| 756 | |
| 757 | /// Returns the size of the file |
| 758 | pub fn size(self: Self) u64 { |
| 759 | return self._size; |
| 760 | } |
| 761 | |
| 762 | /// Returns a `Permissions` struct, representing the permissions on the file |
| 763 | pub fn permissions(self: Self) Permissions { |
| 764 | return Permissions{ .inner = PermissionsWindows{ .attributes = self.attributes } }; |
| 765 | } |
| 766 | |
| 767 | /// Returns the `Kind` of the file. |
| 768 | /// Can only return: `.File`, `.Directory`, `.SymLink` or `.Unknown` |
| 769 | pub fn kind(self: Self) Kind { |
| 770 | if (self.attributes & windows.FILE_ATTRIBUTE_REPARSE_POINT != 0) { |
| 771 | if (self.reparse_tag & 0x20000000 != 0) { |
| 772 | return .SymLink; |
| 773 | } |
| 774 | } else if (self.attributes & windows.FILE_ATTRIBUTE_DIRECTORY != 0) { |
| 775 | return .Directory; |
| 776 | } else { |
| 777 | return .File; |
| 778 | } |
| 779 | return .Unknown; |
| 780 | } |
| 781 | |
| 782 | /// Returns the last time the file was accessed in nanoseconds since UTC 1970-01-01 |
| 783 | pub fn accessed(self: Self) i128 { |
| 784 | return self.access_time; |
| 785 | } |
| 786 | |
| 787 | /// Returns the time the file was modified in nanoseconds since UTC 1970-01-01 |
| 788 | pub fn modified(self: Self) i128 { |
| 789 | return self.modified_time; |
| 790 | } |
| 791 | |
| 792 | /// Returns the time the file was created in nanoseconds since UTC 1970-01-01 |
| 793 | /// This never returns null, only returning an optional for compatibility with other OSes |
| 794 | pub fn created(self: Self) ?i128 { |
| 795 | return self.creation_time; |
| 796 | } |
| 797 | }; |
| 798 | |
| 799 | pub const MetadataError = os.FStatError; |
| 800 | |
| 801 | pub fn metadata(self: File) MetadataError!Metadata { |
| 802 | return Metadata{ |
| 803 | .inner = switch (builtin.os.tag) { |
| 804 | .windows => blk: { |
| 805 | var io_status_block: windows.IO_STATUS_BLOCK = undefined; |
| 806 | var info: windows.FILE_ALL_INFORMATION = undefined; |
| 807 | |
| 808 | const rc = windows.ntdll.NtQueryInformationFile(self.handle, &io_status_block, &info, @sizeOf(windows.FILE_ALL_INFORMATION), .FileAllInformation); |
| 809 | switch (rc) { |
| 810 | .SUCCESS => {}, |
| 811 | .BUFFER_OVERFLOW => {}, |
| 812 | .INVALID_PARAMETER => unreachable, |
| 813 | .ACCESS_DENIED => return error.AccessDenied, |
| 814 | else => return windows.unexpectedStatus(rc), |
| 815 | } |
| 816 | |
| 817 | const reparse_tag: windows.DWORD = reparse_blk: { |
| 818 | if (info.BasicInformation.FileAttributes & windows.FILE_ATTRIBUTE_REPARSE_POINT != 0) { |
| 819 | var reparse_buf: [windows.MAXIMUM_REPARSE_DATA_BUFFER_SIZE]u8 = undefined; |
| 820 | try windows.DeviceIoControl(self.handle, windows.FSCTL_GET_REPARSE_POINT, null, reparse_buf[0..]); |
| 821 | const reparse_struct = @ptrCast(*const windows.REPARSE_DATA_BUFFER, @alignCast(@alignOf(windows.REPARSE_DATA_BUFFER), &reparse_buf[0])); |
| 822 | break :reparse_blk reparse_struct.ReparseTag; |
| 823 | } |
| 824 | break :reparse_blk 0; |
| 825 | }; |
| 826 | |
| 827 | break :blk MetadataWindows{ |
| 828 | .attributes = info.BasicInformation.FileAttributes, |
| 829 | .reparse_tag = reparse_tag, |
| 830 | ._size = @bitCast(u64, info.StandardInformation.EndOfFile), |
| 831 | .access_time = windows.fromSysTime(info.BasicInformation.LastAccessTime), |
| 832 | .modified_time = windows.fromSysTime(info.BasicInformation.LastWriteTime), |
| 833 | .creation_time = windows.fromSysTime(info.BasicInformation.CreationTime), |
| 834 | }; |
| 835 | }, |
| 836 | .linux => blk: { |
| 837 | var stx = mem.zeroes(os.linux.Statx); |
| 838 | const rcx = os.linux.statx(self.handle, "\x00", os.linux.AT.EMPTY_PATH, os.linux.STATX_TYPE | os.linux.STATX_MODE | os.linux.STATX_ATIME | os.linux.STATX_MTIME | os.linux.STATX_BTIME, &stx); |
| 839 | |
| 840 | switch (os.errno(rcx)) { |
| 841 | .SUCCESS => {}, |
| 842 | // NOSYS happens when `statx` is unsupported, which is the case on kernel versions before 4.11 |
| 843 | // Here, we call `fstat` and fill `stx` with the data we need |
| 844 | .NOSYS => { |
| 845 | const st = try os.fstat(self.handle); |
| 846 | |
| 847 | stx.mode = @intCast(u16, st.mode); |
| 848 | |
| 849 | // Hacky conversion from timespec to statx_timestamp |
| 850 | stx.atime = std.mem.zeroes(os.linux.statx_timestamp); |
| 851 | stx.atime.tv_sec = st.atim.tv_sec; |
| 852 | stx.atime.tv_nsec = @intCast(u32, st.atim.tv_nsec); // Guaranteed to succeed (tv_nsec is always below 10^9) |
| 853 | |
| 854 | stx.mtime = std.mem.zeroes(os.linux.statx_timestamp); |
| 855 | stx.mtime.tv_sec = st.mtim.tv_sec; |
| 856 | stx.mtime.tv_nsec = @intCast(u32, st.mtim.tv_nsec); |
| 857 | |
| 858 | stx.mask = os.linux.STATX_BASIC_STATS | os.linux.STATX_MTIME; |
| 859 | }, |
| 860 | .BADF => unreachable, |
| 861 | .FAULT => unreachable, |
| 862 | .NOMEM => return error.SystemResources, |
| 863 | else => |err| return os.unexpectedErrno(err), |
| 864 | } |
| 865 | |
| 866 | break :blk MetadataLinux{ |
| 867 | .statx = stx, |
| 868 | }; |
| 869 | }, |
| 870 | else => blk: { |
| 871 | const st = try os.fstat(self.handle); |
| 872 | break :blk MetadataUnix{ |
| 873 | .stat = st, |
| 874 | }; |
| 875 | }, |
| 876 | }, |
| 877 | }; |
| 878 | } |
| 879 | |
| 401 | 880 | pub const UpdateTimesError = os.FutimensError || windows.SetFileTimeError; |
| 402 | 881 | |
| 403 | 882 | /// The underlying file system may have a different granularity than nanoseconds, |