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 }