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() {
}