@@ -385,6 +385,284 @@ 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+ pub fn setPermissions (self : File , permissions : Permissions ) SetPermissionsError ! void {
501+ switch (builtin .os .tag ) {
502+ .windows = > {
503+ var io_status_block : windows.IO_STATUS_BLOCK = undefined ;
504+ var info = windows.FILE_BASIC_INFORMATION {
505+ .CreationTime = 0 ,
506+ .LastAccessTime = 0 ,
507+ .LastWriteTime = 0 ,
508+ .ChangeTime = 0 ,
509+ .FileAttributes = permissions .attributes ,
510+ };
511+ const rc = windows .ntdll .NtSetInformationFile (
512+ self .handle ,
513+ & io_status_block ,
514+ & info ,
515+ @sizeOf (windows .FILE_BASIC_INFORMATION ),
516+ .FileBasicInformation ,
517+ );
518+ switch (rc ) {
519+ .SUCCESS = > return ,
520+ .INVALID_HANDLE = > unreachable ,
521+ .ACCESS_DENIED = > return error .AccessDenied ,
522+ else = > return windows .unexpectedStatus (rc ),
523+ }
524+ },
525+ else = > {
526+ try self .chmod (permissions .mode );
527+ },
528+ }
529+ }
530+
531+ /// Cross-platform representation of file metadata.
532+ /// Can be obtained with `File.metadata()`
533+ pub const Metadata = switch (builtin .os .tag ) {
534+ .windows = > struct {
535+ attributes : windows.DWORD ,
536+ reparse_tag : windows.DWORD ,
537+ _size : u64 ,
538+ _atime : i128 ,
539+ _mtime : i128 ,
540+ _ctime : i128 ,
541+
542+ const Self = @This ();
543+
544+ /// Returns the size of the file
545+ pub fn size (self : Self ) u64 {
546+ return self ._size ;
547+ }
548+
549+ /// Returns a `Permissions` struct, representing the permissions on the file
550+ pub fn permissions (self : Self ) Permissions {
551+ return Permissions { .attributes = self .attributes };
552+ }
553+
554+ /// Returns the `Kind` of the file.
555+ /// On Windows, returns: `.File`, `.Directory`, `.SymLink` or `.Unknown`
556+ pub fn kind (self : Self ) Kind {
557+ if (self .attributes & windows .FILE_ATTRIBUTE_REPARSE_POINT != 0 ) {
558+ if (self .reparse_tag & 0x20000000 != 0 ) {
559+ return .SymLink ;
560+ }
561+ } else if (self .attributes & windows .FILE_ATTRIBUTE_DIRECTORY != 0 ) {
562+ return .Directory ;
563+ } else {
564+ return .File ;
565+ }
566+ return .Unknown ;
567+ }
568+
569+ /// Returns the last time the file was accessed in nanoseconds since UTC 1970-01-01
570+ pub fn atime (self : Self ) i128 {
571+ return self ._atime ;
572+ }
573+
574+ /// Returns the last time the file was modified in nanoseconds since UTC 1970-01-01
575+ pub fn mtime (self : Self ) i128 {
576+ return self ._mtime ;
577+ }
578+
579+ /// Returns the time the file was created in nanoseconds since UTC 1970-01-01
580+ pub fn ctime (self : Self ) i128 {
581+ return self ._ctime ;
582+ }
583+ },
584+ else = > struct {
585+ stat : Stat ,
586+
587+ const Self = @This ();
588+
589+ /// Returns the size of the file
590+ pub fn size (self : Self ) u64 {
591+ return self .stat .size ;
592+ }
593+
594+ /// Returns a `Permissions` struct, representing the permissions on the file
595+ pub fn permissions (self : Self ) Permissions {
596+ return Permissions { .mode = self .stat .mode };
597+ }
598+
599+ /// Returns the `Kind` of file.
600+ /// On Windows, returns: `.File`, `.Directory`, `.SymLink` or `.Unknown`
601+ pub fn kind (self : Self ) Kind {
602+ return self .stat .kind ;
603+ }
604+
605+ /// Returns the last time the file was accessed in nanoseconds since UTC 1970-01-01
606+ pub fn atime (self : Self ) i128 {
607+ return self .stat .atime ;
608+ }
609+
610+ /// Returns the last time the file was accessed in nanoseconds since UTC 1970-01-01
611+ pub fn mtime (self : Self ) i128 {
612+ return self .stat .mtime ;
613+ }
614+
615+ /// Returns the last time the file was accessed in nanoseconds since UTC 1970-01-01
616+ pub fn ctime (self : Self ) i128 {
617+ return self .stat .ctime ;
618+ }
619+ },
620+ };
621+
622+ pub const MetadataError = StatError ;
623+
624+ /// Returns a `Metadata` struct, representing the permissions on the file
625+ pub fn metadata (self : File ) MetadataError ! Metadata {
626+ switch (builtin .os .tag ) {
627+ .windows = > {
628+ var io_status_block : windows.IO_STATUS_BLOCK = undefined ;
629+ var info : windows.FILE_ALL_INFORMATION = undefined ;
630+ const rc = windows .ntdll .NtQueryInformationFile (self .handle , & io_status_block , & info , @sizeOf (windows .FILE_ALL_INFORMATION ), .FileAllInformation );
631+ switch (rc ) {
632+ .SUCCESS = > {},
633+ .BUFFER_OVERFLOW = > {},
634+ .INVALID_PARAMETER = > unreachable ,
635+ .ACCESS_DENIED = > return error .AccessDenied ,
636+ else = > return windows .unexpectedStatus (rc ),
637+ }
638+
639+ const reparse_tag : windows.DWORD = blk : {
640+ if (info .BasicInformation .FileAttributes & windows .FILE_ATTRIBUTE_REPARSE_POINT != 0 ) {
641+ var reparse_buf : [windows .MAXIMUM_REPARSE_DATA_BUFFER_SIZE ]u8 = undefined ;
642+ try windows .DeviceIoControl (self .handle , windows .FSCTL_GET_REPARSE_POINT , null , reparse_buf [0.. ]);
643+ const reparse_struct = @ptrCast (* const windows .REPARSE_DATA_BUFFER , @alignCast (@alignOf (windows .REPARSE_DATA_BUFFER ), & reparse_buf [0 ]));
644+ break :blk reparse_struct .ReparseTag ;
645+ }
646+ break :blk 0 ;
647+ };
648+
649+ return Metadata {
650+ .attributes = info .BasicInformation .FileAttributes ,
651+ .reparse_tag = reparse_tag ,
652+ ._size = @bitCast (u64 , info .StandardInformation .EndOfFile ),
653+ ._atime = windows .fromSysTime (info .BasicInformation .LastAccessTime ),
654+ ._mtime = windows .fromSysTime (info .BasicInformation .LastWriteTime ),
655+ ._ctime = windows .fromSysTime (info .BasicInformation .CreationTime ),
656+ };
657+ },
658+ else = > {
659+ return Metadata {
660+ .stat = try self .stat (),
661+ };
662+ },
663+ }
664+ }
665+
388666 pub const UpdateTimesError = os .FutimensError || windows .SetFileTimeError ;
389667
390668 /// The underlying file system may have a different granularity than nanoseconds,
0 commit comments