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  }