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