github.com/creachadair/ffs@v0.17.3/file/stat.go (about)

     1  // Copyright 2019 Michael J. Fromberger. All Rights Reserved.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package file
    16  
    17  import (
    18  	"io/fs"
    19  	"time"
    20  
    21  	"github.com/creachadair/ffs/file/wiretype"
    22  )
    23  
    24  // A Stat is a view into the stat metadata for a file.
    25  // Modifying fields of the Stat value does not affect the underlying file
    26  // unless the caller explicitly calls Update.
    27  type Stat struct {
    28  	f *File // set for stat views of an existing file; nil OK
    29  
    30  	Mode    fs.FileMode `json:"mode,omitempty"`
    31  	ModTime time.Time   `json:"mod_time,omitempty"`
    32  
    33  	// Numeric ID and name of file owner.
    34  	OwnerID   int    `json:"owner_id,omitempty"`
    35  	OwnerName string `json:"owner_name,omitempty"`
    36  
    37  	// Numeric ID and name of file group.
    38  	GroupID   int    `json:"group_id,omitempty"`
    39  	GroupName string `json:"group_name,omitempty"`
    40  
    41  	// To add additional metadata, add a field to this type and a corresponding
    42  	// field to wiretype.Stat, then update the toWireType and fromWireType
    43  	// methods to encode and decode the value.
    44  }
    45  
    46  // WithMode returns a copy of s with its Mode set to m.
    47  func (s Stat) WithMode(m fs.FileMode) Stat { s.Mode = m; return s }
    48  
    49  // WithModTime returns a copy of s with its ModTime set to ts.
    50  func (s Stat) WithModTime(ts time.Time) Stat { s.ModTime = ts; return s }
    51  
    52  // WithOwnerID returns a copy of s with its OwnerID set to id.
    53  func (s Stat) WithOwnerID(id int) Stat { s.OwnerID = id; return s }
    54  
    55  // WithOwnerName returns a copy of s with its OwnerName set to name.
    56  func (s Stat) WithOwnerName(name string) Stat { s.OwnerName = name; return s }
    57  
    58  // WithGroupID returns a copy of s with its GroupID set to id.
    59  func (s Stat) WithGroupID(id int) Stat { s.GroupID = id; return s }
    60  
    61  // WithGroupName returns a copy of s with its GroupName set to name.
    62  func (s Stat) WithGroupName(name string) Stat { s.GroupName = name; return s }
    63  
    64  // Clear returns a copy of s with the same file but all other fields set to
    65  // their zero values.
    66  func (s Stat) Clear() Stat { return Stat{f: s.f} }
    67  
    68  // Update replaces the stat metadata for the file associated with s with the
    69  // current contents of s. Calling this method does not change whether stat is
    70  // persisted. Update returns s.
    71  func (s Stat) Update() Stat { s.f.mu.Lock(); defer s.f.mu.Unlock(); s.f.setStatLocked(s); return s }
    72  
    73  // Persist enables (ok == true) or disables (ok == false) stat persistence for
    74  // the file associated with s. The contents of s are not changed. It returns s.
    75  func (s Stat) Persist(ok bool) Stat {
    76  	s.f.mu.Lock()
    77  	defer s.f.mu.Unlock()
    78  	if ok != s.f.saveStat {
    79  		s.f.saveStat = ok
    80  		s.f.invalLocked()
    81  	}
    82  	return s
    83  }
    84  
    85  // Persistent reports whether the file associated with s persists stat.
    86  func (s Stat) Persistent() bool { s.f.mu.RLock(); defer s.f.mu.RUnlock(); return s.f.saveStat }
    87  
    88  const (
    89  	bitSetuid = 04000
    90  	bitSetgid = 02000
    91  	bitSticky = 01000
    92  )
    93  
    94  // toWireType encodes s as an equivalent wiretype.Stat.
    95  func (s Stat) toWireType() *wiretype.Stat {
    96  	perm := s.Mode.Perm()
    97  	if s.Mode&fs.ModeSetuid != 0 {
    98  		perm |= bitSetuid
    99  	}
   100  	if s.Mode&fs.ModeSetgid != 0 {
   101  		perm |= bitSetgid
   102  	}
   103  	if s.Mode&fs.ModeSticky != 0 {
   104  		perm |= bitSticky
   105  	}
   106  	pb := &wiretype.Stat{
   107  		Permissions: uint32(perm),
   108  		FileType:    modeToType(s.Mode),
   109  	}
   110  	if !s.ModTime.IsZero() {
   111  		pb.ModTime = &wiretype.Timestamp{
   112  			Seconds: uint64(s.ModTime.Unix()),
   113  			Nanos:   uint32(s.ModTime.Nanosecond()),
   114  		}
   115  	}
   116  	if s.OwnerID != 0 || s.OwnerName != "" {
   117  		pb.Owner = &wiretype.Stat_Ident{
   118  			Id:   uint64(s.OwnerID),
   119  			Name: s.OwnerName,
   120  		}
   121  	}
   122  	if s.GroupID != 0 || s.GroupName != "" {
   123  		pb.Group = &wiretype.Stat_Ident{
   124  			Id:   uint64(s.GroupID),
   125  			Name: s.GroupName,
   126  		}
   127  	}
   128  	return pb
   129  }
   130  
   131  func modeToType(mode fs.FileMode) wiretype.Stat_FileType {
   132  	switch {
   133  	case mode&fs.ModeType == 0:
   134  		return wiretype.Stat_REGULAR
   135  	case mode&fs.ModeDir != 0:
   136  		return wiretype.Stat_DIRECTORY
   137  	case mode&fs.ModeSymlink != 0:
   138  		return wiretype.Stat_SYMLINK
   139  	case mode&fs.ModeSocket != 0:
   140  		return wiretype.Stat_SOCKET
   141  	case mode&fs.ModeNamedPipe != 0:
   142  		return wiretype.Stat_NAMED_PIPE
   143  	case mode&fs.ModeDevice != 0:
   144  		if mode&fs.ModeCharDevice != 0 {
   145  			return wiretype.Stat_CHAR_DEVICE
   146  		}
   147  		return wiretype.Stat_DEVICE
   148  	default:
   149  		return wiretype.Stat_UNKNOWN
   150  	}
   151  }
   152  
   153  var ftypeMode = [...]fs.FileMode{
   154  	wiretype.Stat_REGULAR:     0,
   155  	wiretype.Stat_DIRECTORY:   fs.ModeDir,
   156  	wiretype.Stat_SYMLINK:     fs.ModeSymlink,
   157  	wiretype.Stat_SOCKET:      fs.ModeSocket,
   158  	wiretype.Stat_NAMED_PIPE:  fs.ModeNamedPipe,
   159  	wiretype.Stat_DEVICE:      fs.ModeDevice,
   160  	wiretype.Stat_CHAR_DEVICE: fs.ModeDevice | fs.ModeCharDevice,
   161  }
   162  
   163  func typeToMode(ftype wiretype.Stat_FileType) fs.FileMode {
   164  	if n := int(ftype); n >= 0 && n < len(ftypeMode) {
   165  		return ftypeMode[n]
   166  	}
   167  	return fs.ModeIrregular
   168  }
   169  
   170  // fromWireType decodes a wiretype.Stat into s. If pb == nil, s is unmodified.
   171  func (s *Stat) fromWireType(pb *wiretype.Stat) {
   172  	if pb == nil {
   173  		return // no stat was persisted for this file
   174  	}
   175  	mode := fs.FileMode(pb.Permissions & 0777)
   176  	if pb.Permissions&bitSetuid != 0 {
   177  		mode |= fs.ModeSetuid
   178  	}
   179  	if pb.Permissions&bitSetgid != 0 {
   180  		mode |= fs.ModeSetgid
   181  	}
   182  	if pb.Permissions&bitSticky != 0 {
   183  		mode |= fs.ModeSticky
   184  	}
   185  	s.Mode = mode | typeToMode(pb.FileType)
   186  	if id := pb.Owner; id != nil {
   187  		s.OwnerID = int(id.Id)
   188  		s.OwnerName = id.Name
   189  	}
   190  	if id := pb.Group; id != nil {
   191  		s.GroupID = int(id.Id)
   192  		s.GroupName = id.Name
   193  	}
   194  	if t := pb.ModTime; t != nil {
   195  		s.ModTime = time.Unix(int64(t.Seconds), int64(t.Nanos))
   196  	}
   197  }