github.com/shindo/goofys@v0.24.1-0.20210326210429-9e930f0b2d5c/internal/handles.go (about) 1 // Copyright 2015 - 2017 Ka-Hing Cheung 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 internal 16 17 import ( 18 "fmt" 19 "net/url" 20 "os" 21 "sort" 22 "strings" 23 "sync" 24 "sync/atomic" 25 "syscall" 26 "time" 27 28 "github.com/aws/aws-sdk-go/aws" 29 30 "github.com/jacobsa/fuse" 31 "github.com/jacobsa/fuse/fuseops" 32 "golang.org/x/sys/unix" 33 34 "github.com/sirupsen/logrus" 35 ) 36 37 type InodeAttributes struct { 38 Size uint64 39 Mtime time.Time 40 } 41 42 func (i InodeAttributes) Equal(other InodeAttributes) bool { 43 return i.Size == other.Size && i.Mtime.Equal(other.Mtime) 44 } 45 46 type Inode struct { 47 Id fuseops.InodeID 48 Name *string 49 fs *Goofys 50 Attributes InodeAttributes 51 KnownSize *uint64 52 // It is generally safe to read `AttrTime` without locking because if some other 53 // operation is modifying `AttrTime`, in most cases the reader is okay with working with 54 // stale data. But Time is a struct and modifying it is not atomic. However 55 // in practice (until the year 2157) we should be okay because 56 // - Almost all uses of AttrTime will be about comparisions (AttrTime < x, AttrTime > x) 57 // - Time object will have Time::monotonic bit set (until the year 2157) => the time 58 // comparision just compares Time::ext field 59 // Ref: https://github.com/golang/go/blob/e42ae65a8507/src/time/time.go#L12:L56 60 AttrTime time.Time 61 62 mu sync.Mutex // everything below is protected by mu 63 64 // We are not very consistent about enforcing locks for `Parent` because, the 65 // parent field very very rarely changes and it is generally fine to operate on 66 // stale parent informaiton 67 Parent *Inode 68 69 dir *DirInodeData 70 71 Invalid bool 72 ImplicitDir bool 73 74 fileHandles int32 75 76 userMetadata map[string][]byte 77 s3Metadata map[string][]byte 78 79 // last known etag from the cloud 80 knownETag *string 81 // tell the next open to invalidate page cache because the 82 // file is changed. This is set when LookUp notices something 83 // about this file is changed 84 invalidateCache bool 85 86 // the refcnt is an exception, it's protected by the global lock 87 // Goofys.mu 88 refcnt uint64 89 } 90 91 func NewInode(fs *Goofys, parent *Inode, name *string) (inode *Inode) { 92 if strings.Index(*name, "/") != -1 { 93 fuseLog.Errorf("%v is not a valid name", *name) 94 } 95 96 inode = &Inode{ 97 Name: name, 98 fs: fs, 99 AttrTime: time.Now(), 100 Parent: parent, 101 s3Metadata: make(map[string][]byte), 102 refcnt: 1, 103 } 104 105 return 106 } 107 108 func (inode *Inode) SetFromBlobItem(item *BlobItemOutput) { 109 inode.mu.Lock() 110 defer inode.mu.Unlock() 111 112 inode.Attributes.Size = item.Size 113 // don't want to point to the attribute because that 114 // can get updated 115 size := item.Size 116 inode.KnownSize = &size 117 if item.LastModified != nil { 118 inode.Attributes.Mtime = *item.LastModified 119 } else { 120 inode.Attributes.Mtime = inode.fs.rootAttrs.Mtime 121 } 122 if item.ETag != nil { 123 inode.s3Metadata["etag"] = []byte(*item.ETag) 124 inode.knownETag = item.ETag 125 } else { 126 delete(inode.s3Metadata, "etag") 127 } 128 if item.StorageClass != nil { 129 inode.s3Metadata["storage-class"] = []byte(*item.StorageClass) 130 } else { 131 delete(inode.s3Metadata, "storage-class") 132 } 133 now := time.Now() 134 // don't want to update time if this inode is setup to never expire 135 if inode.AttrTime.Before(now) { 136 inode.AttrTime = now 137 } 138 } 139 140 // LOCKS_REQUIRED(inode.mu) 141 func (inode *Inode) cloud() (cloud StorageBackend, path string) { 142 var prefix string 143 var dir *Inode 144 145 if inode.dir == nil { 146 path = *inode.Name 147 dir = inode.Parent 148 } else { 149 dir = inode 150 } 151 152 for p := dir; p != nil; p = p.Parent { 153 if p.dir.cloud != nil { 154 cloud = p.dir.cloud 155 // the error backend produces a mount.err file 156 // at the root and is not aware of prefix 157 _, isErr := cloud.(StorageBackendInitError) 158 if !isErr { 159 // we call init here instead of 160 // relying on the wrapper to call init 161 // because we want to return the right 162 // prefix 163 if c, ok := cloud.(*StorageBackendInitWrapper); ok { 164 err := c.Init("") 165 isErr = err != nil 166 } 167 } 168 169 if !isErr { 170 prefix = p.dir.mountPrefix 171 } 172 break 173 } 174 175 if path == "" { 176 path = *p.Name 177 } else if p.Parent != nil { 178 // don't prepend if I am already the root node 179 path = *p.Name + "/" + path 180 } 181 } 182 183 if path == "" { 184 path = strings.TrimRight(prefix, "/") 185 } else { 186 path = prefix + path 187 } 188 return 189 } 190 191 func (inode *Inode) FullName() *string { 192 if inode.Parent == nil { 193 return inode.Name 194 } else { 195 s := inode.Parent.getChildName(*inode.Name) 196 return &s 197 } 198 } 199 200 func (inode *Inode) touch() { 201 inode.Attributes.Mtime = time.Now() 202 } 203 204 func (inode *Inode) InflateAttributes() (attr fuseops.InodeAttributes) { 205 mtime := inode.Attributes.Mtime 206 if mtime.IsZero() { 207 mtime = inode.fs.rootAttrs.Mtime 208 } 209 210 attr = fuseops.InodeAttributes{ 211 Size: inode.Attributes.Size, 212 Atime: mtime, 213 Mtime: mtime, 214 Ctime: mtime, 215 Crtime: mtime, 216 Uid: inode.fs.flags.Uid, 217 Gid: inode.fs.flags.Gid, 218 } 219 220 if inode.dir != nil { 221 attr.Nlink = 2 222 attr.Mode = inode.fs.flags.DirMode | os.ModeDir 223 } else { 224 attr.Nlink = 1 225 attr.Mode = inode.fs.flags.FileMode 226 } 227 return 228 } 229 230 func (inode *Inode) logFuse(op string, args ...interface{}) { 231 if fuseLog.Level >= logrus.DebugLevel { 232 fuseLog.Debugln(op, inode.Id, *inode.FullName(), args) 233 } 234 } 235 236 func (inode *Inode) errFuse(op string, args ...interface{}) { 237 fuseLog.Errorln(op, inode.Id, *inode.FullName(), args) 238 } 239 240 func (inode *Inode) ToDir() { 241 if inode.dir == nil { 242 inode.Attributes = InodeAttributes{ 243 Size: 4096, 244 // Mtime intentionally not initialized 245 } 246 inode.dir = &DirInodeData{} 247 inode.KnownSize = &inode.fs.rootAttrs.Size 248 } 249 } 250 251 // LOCKS_REQUIRED(fs.mu) 252 // XXX why did I put lock required? This used to return a resurrect bool 253 // which no long does anything, need to look into that to see if 254 // that was legacy 255 func (inode *Inode) Ref() { 256 inode.logFuse("Ref", inode.refcnt) 257 258 inode.refcnt++ 259 return 260 } 261 262 func (inode *Inode) DeRef(n uint64) (stale bool) { 263 inode.logFuse("DeRef", n, inode.refcnt) 264 265 if inode.refcnt < n { 266 panic(fmt.Sprintf("deref %v from %v", n, inode.refcnt)) 267 } 268 269 inode.refcnt -= n 270 271 stale = (inode.refcnt == 0) 272 return 273 } 274 275 func (inode *Inode) GetAttributes() (*fuseops.InodeAttributes, error) { 276 // XXX refresh attributes 277 inode.logFuse("GetAttributes") 278 if inode.Invalid { 279 return nil, fuse.ENOENT 280 } 281 attr := inode.InflateAttributes() 282 return &attr, nil 283 } 284 285 func (inode *Inode) isDir() bool { 286 return inode.dir != nil 287 } 288 289 // LOCKS_REQUIRED(inode.mu) 290 func (inode *Inode) fillXattrFromHead(resp *HeadBlobOutput) { 291 inode.userMetadata = make(map[string][]byte) 292 293 if resp.ETag != nil { 294 inode.s3Metadata["etag"] = []byte(*resp.ETag) 295 } 296 if resp.StorageClass != nil { 297 inode.s3Metadata["storage-class"] = []byte(*resp.StorageClass) 298 } else { 299 inode.s3Metadata["storage-class"] = []byte("STANDARD") 300 } 301 302 for k, v := range resp.Metadata { 303 k = strings.ToLower(k) 304 value, err := url.PathUnescape(*v) 305 if err != nil { 306 value = *v 307 } 308 inode.userMetadata[k] = []byte(value) 309 } 310 } 311 312 // LOCKS_REQUIRED(inode.mu) 313 func (inode *Inode) fillXattr() (err error) { 314 if !inode.ImplicitDir && inode.userMetadata == nil { 315 316 fullName := *inode.FullName() 317 if inode.isDir() { 318 fullName += "/" 319 } 320 321 cloud, key := inode.cloud() 322 params := &HeadBlobInput{Key: key} 323 resp, err := cloud.HeadBlob(params) 324 if err != nil { 325 err = mapAwsError(err) 326 if err == fuse.ENOENT { 327 err = nil 328 if inode.isDir() { 329 inode.ImplicitDir = true 330 } 331 } 332 return err 333 } else { 334 inode.fillXattrFromHead(resp) 335 } 336 } 337 338 return 339 } 340 341 // LOCKS_REQUIRED(inode.mu) 342 func (inode *Inode) getXattrMap(name string, userOnly bool) ( 343 meta map[string][]byte, newName string, err error) { 344 345 cloud, _ := inode.cloud() 346 xattrPrefix := cloud.Capabilities().Name + "." 347 348 if strings.HasPrefix(name, xattrPrefix) { 349 if userOnly { 350 return nil, "", syscall.EPERM 351 } 352 353 newName = name[len(xattrPrefix):] 354 meta = inode.s3Metadata 355 } else if strings.HasPrefix(name, "user.") { 356 err = inode.fillXattr() 357 if err != nil { 358 return nil, "", err 359 } 360 361 newName = name[5:] 362 meta = inode.userMetadata 363 } else { 364 if userOnly { 365 return nil, "", syscall.EPERM 366 } else { 367 return nil, "", unix.ENODATA 368 } 369 } 370 371 if meta == nil { 372 return nil, "", unix.ENODATA 373 } 374 375 return 376 } 377 378 func convertMetadata(meta map[string][]byte) (metadata map[string]*string) { 379 metadata = make(map[string]*string) 380 for k, v := range meta { 381 k = strings.ToLower(k) 382 metadata[k] = aws.String(xattrEscape(v)) 383 } 384 return 385 } 386 387 // LOCKS_REQUIRED(inode.mu) 388 func (inode *Inode) updateXattr() (err error) { 389 cloud, key := inode.cloud() 390 _, err = cloud.CopyBlob(&CopyBlobInput{ 391 Source: key, 392 Destination: key, 393 Size: &inode.Attributes.Size, 394 ETag: aws.String(string(inode.s3Metadata["etag"])), 395 Metadata: convertMetadata(inode.userMetadata), 396 }) 397 return 398 } 399 400 func (inode *Inode) SetXattr(name string, value []byte, flags uint32) error { 401 inode.logFuse("SetXattr", name) 402 403 inode.mu.Lock() 404 defer inode.mu.Unlock() 405 406 meta, name, err := inode.getXattrMap(name, true) 407 if err != nil { 408 return err 409 } 410 411 if flags != 0x0 { 412 _, ok := meta[name] 413 if flags == unix.XATTR_CREATE { 414 if ok { 415 return syscall.EEXIST 416 } 417 } else if flags == unix.XATTR_REPLACE { 418 if !ok { 419 return syscall.ENODATA 420 } 421 } 422 } 423 424 meta[name] = Dup(value) 425 err = inode.updateXattr() 426 return err 427 } 428 429 func (inode *Inode) RemoveXattr(name string) error { 430 inode.logFuse("RemoveXattr", name) 431 432 inode.mu.Lock() 433 defer inode.mu.Unlock() 434 435 meta, name, err := inode.getXattrMap(name, true) 436 if err != nil { 437 return err 438 } 439 440 if _, ok := meta[name]; ok { 441 delete(meta, name) 442 err = inode.updateXattr() 443 return err 444 } else { 445 return syscall.ENODATA 446 } 447 } 448 449 func (inode *Inode) GetXattr(name string) ([]byte, error) { 450 inode.logFuse("GetXattr", name) 451 452 inode.mu.Lock() 453 defer inode.mu.Unlock() 454 455 meta, name, err := inode.getXattrMap(name, false) 456 if err != nil { 457 return nil, err 458 } 459 460 value, ok := meta[name] 461 if ok { 462 return value, nil 463 } else { 464 return nil, syscall.ENODATA 465 } 466 } 467 468 func (inode *Inode) ListXattr() ([]string, error) { 469 inode.logFuse("ListXattr") 470 471 inode.mu.Lock() 472 defer inode.mu.Unlock() 473 474 var xattrs []string 475 476 err := inode.fillXattr() 477 if err != nil { 478 return nil, err 479 } 480 481 cloud, _ := inode.cloud() 482 cloudXattrPrefix := cloud.Capabilities().Name + "." 483 484 for k, _ := range inode.s3Metadata { 485 xattrs = append(xattrs, cloudXattrPrefix+k) 486 } 487 488 for k, _ := range inode.userMetadata { 489 xattrs = append(xattrs, "user."+k) 490 } 491 492 sort.Strings(xattrs) 493 494 return xattrs, nil 495 } 496 497 func (inode *Inode) OpenFile(metadata fuseops.OpMetadata) (fh *FileHandle, err error) { 498 inode.logFuse("OpenFile") 499 500 inode.mu.Lock() 501 defer inode.mu.Unlock() 502 503 fh = NewFileHandle(inode, metadata) 504 505 atomic.AddInt32(&inode.fileHandles, 1) 506 return 507 }