@@ -385,6 +385,286 @@ pub const File = struct {
385385 try os .fchown (self .handle , owner , group );
386386 }
387387
388+ /// Cross-platform representation of permissions on a file.
389+ /// The `readonly` and `setReadonly` are the only methods available across all platforms.
390+ /// Unix-only functionality is available through the `unixHas` and `unixSet` methods.
391+ pub const Permissions = switch (builtin .os .tag ) {
392+ .windows = > struct {
393+ attributes : os.windows.DWORD ,
394+
395+ const Self = @This ();
396+
397+ /// Returns `true` if permissions represent an unwritable file.
398+ /// On Unix, `true` is returned only if no class has write permissions.
399+ pub fn readonly (self : Self ) bool {
400+ return self .attributes & os .windows .FILE_ATTRIBUTE_READONLY != 0 ;
401+ }
402+
403+ /// Sets whether write permissions are provided.
404+ /// On Unix, this affects *all* classes. If this is undesired, use `unixSet`
405+ /// This method *DOES NOT* set permissions on the filesystem: use `File.setPermissions(permissions)`
406+ pub fn setReadonly (self : * Self , read_only : bool ) void {
407+ if (read_only ) {
408+ self .attributes |= os .windows .FILE_ATTRIBUTE_READONLY ;
409+ } else {
410+ self .attributes &= ~ @as (os .windows .DWORD , os .windows .FILE_ATTRIBUTE_READONLY );
411+ }
412+ }
413+ },
414+ else = > struct {
415+ mode : Mode ,
416+
417+ const Self = @This ();
418+
419+ /// Returns `true` if permissions represent an unwritable file.
420+ /// On Unix, `true` is returned only if no class has write permissions.
421+ pub fn readonly (self : Self ) bool {
422+ return self .mode & 0o222 == 0 ;
423+ }
424+
425+ /// Sets whether write permissions are provided.
426+ /// On Unix, this affects *all* classes. If this is undesired, use `unixSet`
427+ /// This method *DOES NOT* set permissions on the filesystem: use `File.setPermissions(permissions)`
428+ pub fn setReadonly (self : * Self , read_only : bool ) void {
429+ if (read_only ) {
430+ self .mode &= ~ @as (Mode , 0o222 );
431+ } else {
432+ self .mode |= @as (Mode , 0o222 );
433+ }
434+ }
435+
436+ pub const Class = enum (u2 ) {
437+ user = 2 ,
438+ group = 1 ,
439+ other = 0 ,
440+ };
441+
442+ pub const Permission = enum (u3 ) {
443+ read = 0o4 ,
444+ write = 0o2 ,
445+ execute = 0o1 ,
446+ };
447+
448+ /// Returns `true` if the chosen class has the selected permission.
449+ /// This method is only available on Unix platforms.
450+ pub fn unixHas (self : * Self , class : Class , permission : Permission ) bool {
451+ const mask = @as (Mode , @enumToInt (permission )) << @as (u3 , @enumToInt (class )) * 3 ;
452+ return self .mode & mask != 0 ;
453+ }
454+
455+ /// Sets the permissions for the chosen class. Any permissions set to `null` are left unchanged.
456+ /// This method *DOES NOT* set permissions on the filesystem: use `File.setPermissions(permissions)`
457+ /// This method is only available on Unix platforms.
458+ pub fn unixSet (self : * Self , class : Class , permissions : struct {
459+ read : ? bool = null ,
460+ write : ? bool = null ,
461+ execute : ? bool = null ,
462+ }) void {
463+ const shift = @as (u3 , @enumToInt (class )) * 3 ;
464+ if (permissions .read ) | r | {
465+ if (r ) {
466+ self .mode |= @as (Mode , 0o4 ) << shift ;
467+ } else {
468+ self .mode &= ~ (@as (Mode , 0o4 ) << shift );
469+ }
470+ }
471+ if (permissions .write ) | w | {
472+ if (w ) {
473+ self .mode |= @as (Mode , 0o2 ) << shift ;
474+ } else {
475+ self .mode &= ~ (@as (Mode , 0o2 ) << shift );
476+ }
477+ }
478+ if (permissions .execute ) | x | {
479+ if (x ) {
480+ self .mode |= @as (Mode , 0o1 ) << shift ;
481+ } else {
482+ self .mode &= ~ (@as (Mode , 0o1 ) << shift );
483+ }
484+ }
485+ }
486+
487+ /// Returns a `Permissions` struct representing the permissions from the passed mode.
488+ /// This method is only available on Unix platforms.
489+ pub fn unixNew (new_mode : Mode ) Self {
490+ return Self {
491+ .mode = new_mode ,
492+ };
493+ }
494+ },
495+ };
496+
497+ pub const SetPermissionsError = if (is_windows ) error {AccessDenied } || os .UnexpectedError else ChmodError ;
498+
499+ /// Sets permissions according to the provided `Permissions` struct.
500+ /// This method is *NOT* available on WASI
501+ pub fn setPermissions (self : File , permissions : Permissions ) SetPermissionsError ! void {
502+ switch (builtin .os .tag ) {
503+ .windows = > {
504+ var io_status_block : windows.IO_STATUS_BLOCK = undefined ;
505+ var info = windows.FILE_BASIC_INFORMATION {
506+ .CreationTime = 0 ,
507+ .LastAccessTime = 0 ,
508+ .LastWriteTime = 0 ,
509+ .ChangeTime = 0 ,
510+ .FileAttributes = permissions .attributes ,
511+ };
512+ const rc = windows .ntdll .NtSetInformationFile (
513+ self .handle ,
514+ & io_status_block ,
515+ & info ,
516+ @sizeOf (windows .FILE_BASIC_INFORMATION ),
517+ .FileBasicInformation ,
518+ );
519+ switch (rc ) {
520+ .SUCCESS = > return ,
521+ .INVALID_HANDLE = > unreachable ,
522+ .ACCESS_DENIED = > return error .AccessDenied ,
523+ else = > return windows .unexpectedStatus (rc ),
524+ }
525+ },
526+ .wasi = > @compileError ("Unsupported OS" ), // TODO Waiting for wasi-filesystem to support chmod
527+ else = > {
528+ try self .chmod (permissions .mode );
529+ },
530+ }
531+ }
532+
533+ /// Cross-platform representation of file metadata.
534+ /// Can be obtained with `File.metadata()`
535+ pub const Metadata = switch (builtin .os .tag ) {
536+ .windows = > struct {
537+ attributes : windows.DWORD ,
538+ reparse_tag : windows.DWORD ,
539+ _size : u64 ,
540+ _atime : i128 ,
541+ _mtime : i128 ,
542+ _ctime : i128 ,
543+
544+ const Self = @This ();
545+
546+ /// Returns the size of the file
547+ pub fn size (self : Self ) u64 {
548+ return self ._size ;
549+ }
550+
551+ /// Returns a `Permissions` struct, representing the permissions on the file
552+ pub fn permissions (self : Self ) Permissions {
553+ return Permissions { .attributes = self .attributes };
554+ }
555+
556+ /// Returns the `Kind` of the file.
557+ /// On Windows, returns: `.File`, `.Directory`, `.SymLink` or `.Unknown`
558+ pub fn kind (self : Self ) Kind {
559+ if (self .attributes & windows .FILE_ATTRIBUTE_REPARSE_POINT != 0 ) {
560+ if (self .reparse_tag & 0x20000000 != 0 ) {
561+ return .SymLink ;
562+ }
563+ } else if (self .attributes & windows .FILE_ATTRIBUTE_DIRECTORY != 0 ) {
564+ return .Directory ;
565+ } else {
566+ return .File ;
567+ }
568+ return .Unknown ;
569+ }
570+
571+ /// Returns the last time the file was accessed in nanoseconds since UTC 1970-01-01
572+ pub fn atime (self : Self ) i128 {
573+ return self ._atime ;
574+ }
575+
576+ /// Returns the last time the file was modified in nanoseconds since UTC 1970-01-01
577+ pub fn mtime (self : Self ) i128 {
578+ return self ._mtime ;
579+ }
580+
581+ /// Returns the time the file was created in nanoseconds since UTC 1970-01-01
582+ pub fn ctime (self : Self ) i128 {
583+ return self ._ctime ;
584+ }
585+ },
586+ else = > struct {
587+ stat : Stat ,
588+
589+ const Self = @This ();
590+
591+ /// Returns the size of the file
592+ pub fn size (self : Self ) u64 {
593+ return self .stat .size ;
594+ }
595+
596+ /// Returns a `Permissions` struct, representing the permissions on the file
597+ pub fn permissions (self : Self ) Permissions {
598+ return Permissions { .mode = self .stat .mode };
599+ }
600+
601+ /// Returns the `Kind` of file.
602+ /// On Windows, returns: `.File`, `.Directory`, `.SymLink` or `.Unknown`
603+ pub fn kind (self : Self ) Kind {
604+ return self .stat .kind ;
605+ }
606+
607+ /// Returns the last time the file was accessed in nanoseconds since UTC 1970-01-01
608+ pub fn atime (self : Self ) i128 {
609+ return self .stat .atime ;
610+ }
611+
612+ /// Returns the last time the file was accessed in nanoseconds since UTC 1970-01-01
613+ pub fn mtime (self : Self ) i128 {
614+ return self .stat .mtime ;
615+ }
616+
617+ /// Returns the last time the file was accessed in nanoseconds since UTC 1970-01-01
618+ pub fn ctime (self : Self ) i128 {
619+ return self .stat .ctime ;
620+ }
621+ },
622+ };
623+
624+ pub const MetadataError = StatError ;
625+
626+ /// Returns a `Metadata` struct, representing the permissions on the file
627+ pub fn metadata (self : File ) MetadataError ! Metadata {
628+ switch (builtin .os .tag ) {
629+ .windows = > {
630+ var io_status_block : windows.IO_STATUS_BLOCK = undefined ;
631+ var info : windows.FILE_ALL_INFORMATION = undefined ;
632+ const rc = windows .ntdll .NtQueryInformationFile (self .handle , & io_status_block , & info , @sizeOf (windows .FILE_ALL_INFORMATION ), .FileAllInformation );
633+ switch (rc ) {
634+ .SUCCESS = > {},
635+ .BUFFER_OVERFLOW = > {},
636+ .INVALID_PARAMETER = > unreachable ,
637+ .ACCESS_DENIED = > return error .AccessDenied ,
638+ else = > return windows .unexpectedStatus (rc ),
639+ }
640+
641+ const reparse_tag : windows.DWORD = blk : {
642+ if (info .BasicInformation .FileAttributes & windows .FILE_ATTRIBUTE_REPARSE_POINT != 0 ) {
643+ var reparse_buf : [windows .MAXIMUM_REPARSE_DATA_BUFFER_SIZE ]u8 = undefined ;
644+ try windows .DeviceIoControl (self .handle , windows .FSCTL_GET_REPARSE_POINT , null , reparse_buf [0.. ]);
645+ const reparse_struct = @ptrCast (* const windows .REPARSE_DATA_BUFFER , @alignCast (@alignOf (windows .REPARSE_DATA_BUFFER ), & reparse_buf [0 ]));
646+ break :blk reparse_struct .ReparseTag ;
647+ }
648+ break :blk 0 ;
649+ };
650+
651+ return Metadata {
652+ .attributes = info .BasicInformation .FileAttributes ,
653+ .reparse_tag = reparse_tag ,
654+ ._size = @bitCast (u64 , info .StandardInformation .EndOfFile ),
655+ ._atime = windows .fromSysTime (info .BasicInformation .LastAccessTime ),
656+ ._mtime = windows .fromSysTime (info .BasicInformation .LastWriteTime ),
657+ ._ctime = windows .fromSysTime (info .BasicInformation .CreationTime ),
658+ };
659+ },
660+ else = > {
661+ return Metadata {
662+ .stat = try self .stat (),
663+ };
664+ },
665+ }
666+ }
667+
388668 pub const UpdateTimesError = os .FutimensError || windows .SetFileTimeError ;
389669
390670 /// The underlying file system may have a different granularity than nanoseconds,
0 commit comments