@@ -385,6 +385,274 @@ 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 = 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+
543+ const Self = @This ();
544+
545+ /// Returns the size of the file
546+ pub fn size (self : Self ) u64 {
547+ return self ._size ;
548+ }
549+
550+ /// Returns a `Permissions` struct, representing the permissions on the file
551+ pub fn permissions (self : Self ) Permissions {
552+ return Permissions { .attributes = self .attributes };
553+ }
554+
555+ /// Returns the `Kind` of the file.
556+ /// On Windows, returns: `.File`, `.Directory`, `.SymLink` or `.Unknown`
557+ pub fn kind (self : Self ) Kind {
558+ if (self .attributes & windows .FILE_ATTRIBUTE_REPARSE_POINT != 0 ) {
559+ if (self .reparse_tag & 0x20000000 != 0 ) {
560+ return .SymLink ;
561+ }
562+ } else if (self .attributes & windows .FILE_ATTRIBUTE_DIRECTORY != 0 ) {
563+ return .Directory ;
564+ } else {
565+ return .File ;
566+ }
567+ return .Unknown ;
568+ }
569+
570+ /// Returns the last time the file was accessed in nanoseconds since UTC 1970-01-01
571+ pub fn accessed (self : Self ) i128 {
572+ return self .atime ;
573+ }
574+
575+ /// Returns the time the file was modified in nanoseconds since UTC 1970-01-01
576+ pub fn modified (self : Self ) i128 {
577+ return self .mtime ;
578+ }
579+ },
580+ else = > struct {
581+ stat : Stat ,
582+
583+ const Self = @This ();
584+
585+ /// Returns the size of the file
586+ pub fn size (self : Self ) u64 {
587+ return self .stat .size ;
588+ }
589+
590+ /// Returns a `Permissions` struct, representing the permissions on the file
591+ pub fn permissions (self : Self ) Permissions {
592+ return Permissions { .mode = self .stat .mode };
593+ }
594+
595+ /// Returns the `Kind` of file.
596+ /// On Windows, returns: `.File`, `.Directory`, `.SymLink` or `.Unknown`
597+ pub fn kind (self : Self ) Kind {
598+ return self .stat .kind ;
599+ }
600+
601+ /// Returns the last time the file was accessed in nanoseconds since UTC 1970-01-01
602+ pub fn accessed (self : Self ) i128 {
603+ return self .stat .atime ;
604+ }
605+
606+ /// Returns the time the file was modified in nanoseconds since UTC 1970-01-01
607+ pub fn modified (self : Self ) i128 {
608+ return self .stat .mtime ;
609+ }
610+ },
611+ };
612+
613+ pub const MetadataError = StatError ;
614+
615+ /// Returns a `Metadata` struct, representing the permissions on the file
616+ pub fn metadata (self : File ) MetadataError ! Metadata {
617+ switch (builtin .os .tag ) {
618+ .windows = > {
619+ var io_status_block : windows.IO_STATUS_BLOCK = undefined ;
620+ var info : windows.FILE_ALL_INFORMATION = undefined ;
621+ const rc = windows .ntdll .NtQueryInformationFile (self .handle , & io_status_block , & info , @sizeOf (windows .FILE_ALL_INFORMATION ), .FileAllInformation );
622+ switch (rc ) {
623+ .SUCCESS = > {},
624+ .BUFFER_OVERFLOW = > {},
625+ .INVALID_PARAMETER = > unreachable ,
626+ .ACCESS_DENIED = > return error .AccessDenied ,
627+ else = > return windows .unexpectedStatus (rc ),
628+ }
629+
630+ const reparse_tag : windows.DWORD = blk : {
631+ if (info .BasicInformation .FileAttributes & windows .FILE_ATTRIBUTE_REPARSE_POINT != 0 ) {
632+ var reparse_buf : [windows .MAXIMUM_REPARSE_DATA_BUFFER_SIZE ]u8 = undefined ;
633+ try windows .DeviceIoControl (self .handle , windows .FSCTL_GET_REPARSE_POINT , null , reparse_buf [0.. ]);
634+ const reparse_struct = @ptrCast (* const windows .REPARSE_DATA_BUFFER , @alignCast (@alignOf (windows .REPARSE_DATA_BUFFER ), & reparse_buf [0 ]));
635+ break :blk reparse_struct .ReparseTag ;
636+ }
637+ break :blk 0 ;
638+ };
639+
640+ return Metadata {
641+ .attributes = info .BasicInformation .FileAttributes ,
642+ .reparse_tag = reparse_tag ,
643+ ._size = @bitCast (u64 , info .StandardInformation .EndOfFile ),
644+ .atime = windows .fromSysTime (info .BasicInformation .LastAccessTime ),
645+ .mtime = windows .fromSysTime (info .BasicInformation .LastWriteTime ),
646+ };
647+ },
648+ else = > {
649+ return Metadata {
650+ .stat = try self .stat (),
651+ };
652+ },
653+ }
654+ }
655+
388656 pub const UpdateTimesError = os .FutimensError || windows .SetFileTimeError ;
389657
390658 /// The underlying file system may have a different granularity than nanoseconds,
0 commit comments