github.com/lnzx/goofys@v0.24.0/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  }