internal/fs/inode/base_dir.go (159 lines of code) (raw):

// Copyright 2020 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package inode import ( "syscall" "time" "github.com/googlecloudplatform/gcsfuse/v2/common" "github.com/googlecloudplatform/gcsfuse/v2/internal/locker" "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" "github.com/jacobsa/fuse" "github.com/googlecloudplatform/gcsfuse/v2/internal/gcsx" "github.com/jacobsa/fuse/fuseops" "github.com/jacobsa/fuse/fuseutil" "golang.org/x/net/context" ) // An inode that // // (1) represents a base directory which contains a list of // subdirectories as the roots of different GCS buckets; // (2) implements BaseDirInode, allowing read only ops. type baseDirInode struct { ///////////////////////// // Constant data ///////////////////////// id fuseops.InodeID // INVARIANT: name.IsDir() name Name attrs fuseops.InodeAttributes ///////////////////////// // Mutable state ///////////////////////// // A mutex that must be held when calling certain methods. See documentation // for each method. mu locker.RWLocker lc lookupCount // GUARDED_BY(mu) bucketManager gcsx.BucketManager // GUARDED_BY(mu) buckets map[string]gcsx.SyncerBucket metricHandle common.MetricHandle } // NewBaseDirInode returns a baseDirInode that acts as the directory of // buckets. func NewBaseDirInode( id fuseops.InodeID, name Name, attrs fuseops.InodeAttributes, bm gcsx.BucketManager, metricHandle common.MetricHandle) (d DirInode) { typed := &baseDirInode{ id: id, name: NewRootName(""), attrs: attrs, bucketManager: bm, buckets: make(map[string]gcsx.SyncerBucket), metricHandle: metricHandle, } typed.lc.Init(id) typed.mu = locker.NewRW("BaseDirInode"+name.GcsObjectName(), func() {}) d = typed return } //////////////////////////////////////////////////////////////////////// // Public interface //////////////////////////////////////////////////////////////////////// func (d *baseDirInode) Lock() { d.mu.Lock() } func (d *baseDirInode) Unlock() { d.mu.Unlock() } func (d *baseDirInode) RLock() { d.mu.RLock() } func (d *baseDirInode) RUnlock() { d.mu.RUnlock() } // LockForChildLookup takes exclusive lock on inode when the inode's child is // looked up. It is different from non-base dir inode because during lookup of // child in base directory inode, the buckets map is modified and hence should // be guarded by exclusive lock. func (d *baseDirInode) LockForChildLookup() { d.mu.Lock() } func (d *baseDirInode) UnlockForChildLookup() { d.mu.Unlock() } func (d *baseDirInode) ID() fuseops.InodeID { return d.id } func (d *baseDirInode) Name() Name { return d.name } // LOCKS_REQUIRED(d) func (d *baseDirInode) IncrementLookupCount() { d.lc.Inc() } // LOCKS_REQUIRED(d) func (d *baseDirInode) DecrementLookupCount(n uint64) (destroy bool) { destroy = d.lc.Dec(n) return } // LOCKS_REQUIRED(d) func (d *baseDirInode) Destroy() (err error) { // Nothing interesting to do. return } // LOCKS_REQUIRED(d) func (d *baseDirInode) Attributes( ctx context.Context) (attrs fuseops.InodeAttributes, err error) { // Set up basic attributes. attrs = d.attrs attrs.Nlink = 1 return } // LOCKS_REQUIRED(d) func (d *baseDirInode) LookUpChild(ctx context.Context, name string) (*Core, error) { var err error bucket, ok := d.buckets[name] if !ok { bucket, err = d.bucketManager.SetUpBucket(ctx, name, true, d.metricHandle) if err != nil { return nil, err } d.buckets[name] = bucket } return &Core{ Bucket: &bucket, FullName: NewRootName(bucket.Name()), MinObject: nil, }, nil } // Not implemented func (d *baseDirInode) ReadDescendants(ctx context.Context, limit int) (map[Name]*Core, error) { return nil, fuse.ENOSYS } // LOCKS_REQUIRED(d) func (d *baseDirInode) ReadEntries( ctx context.Context, tok string) (entries []fuseutil.Dirent, newTok string, err error) { // The subdirectories of the base directory should be all the accessible // buckets. Although the user is allowed to visit each individual // subdirectory, listing all the subdirectories (i.e. the buckets) can be // very expensive and currently not supported. return nil, "", syscall.ENOTSUP } //////////////////////////////////////////////////////////////////////// // Forbidden Public interface //////////////////////////////////////////////////////////////////////// // The base directory is a directory of buckets. Opeations for mutating // buckets (such as creation or deletion) are not supported. When the user // tries to mutate the base directory, they will receive a ENOSYS error // indicating such operation is not supported. func (d *baseDirInode) CreateChildFile(ctx context.Context, name string) (*Core, error) { return nil, fuse.ENOSYS } func (d *baseDirInode) InsertFileIntoTypeCache(_ string) {} func (d *baseDirInode) EraseFromTypeCache(_ string) {} func (d *baseDirInode) CreateLocalChildFileCore(_ string) (Core, error) { return Core{}, fuse.ENOSYS } func (d *baseDirInode) CloneToChildFile(ctx context.Context, name string, src *gcs.MinObject) (*Core, error) { return nil, fuse.ENOSYS } func (d *baseDirInode) CreateChildSymlink(ctx context.Context, name string, target string) (*Core, error) { return nil, fuse.ENOSYS } func (d *baseDirInode) CreateChildDir(ctx context.Context, name string) (*Core, error) { return nil, fuse.ENOSYS } func (d *baseDirInode) DeleteChildFile( ctx context.Context, name string, generation int64, metaGeneration *int64) (err error) { err = fuse.ENOSYS return } func (d *baseDirInode) DeleteChildDir( ctx context.Context, name string, isImplicitDir bool, dirInode DirInode) (err error) { err = fuse.ENOSYS return } func (d *baseDirInode) LocalFileEntries(localFileInodes map[Name]Inode) (localEntries map[string]fuseutil.Dirent) { // Base directory can not contain local files. return nil } func (d *baseDirInode) ShouldInvalidateKernelListCache(ttl time.Duration) bool { // Keeping the default behavior although list operation is not supported // for baseDirInode. return true } // List operation is not supported for baseDirInode. func (d *baseDirInode) InvalidateKernelListCache() {} func (d *baseDirInode) RenameFile(ctx context.Context, fileToRename *gcs.MinObject, destinationFileName string) (*gcs.Object, error) { err := fuse.ENOSYS return nil, err } func (d *baseDirInode) RenameFolder(ctx context.Context, folderName string, destinationFolderId string) (op *gcs.Folder, err error) { err = fuse.ENOSYS return } // This operation is not supported on base_dir. func (d *baseDirInode) IsUnlinked() bool { return false } func (d *baseDirInode) Unlink() { }