github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/kbfs/libfs/root_fs.go (about)

     1  // Copyright 2019 Keybase Inc. All rights reserved.
     2  // Use of this source code is governed by a BSD
     3  // license that can be found in the LICENSE file.
     4  
     5  package libfs
     6  
     7  import (
     8  	"context"
     9  	"fmt"
    10  	"net/http"
    11  	"os"
    12  	"path"
    13  	"time"
    14  
    15  	"github.com/keybase/client/go/kbfs/libkbfs"
    16  	"github.com/keybase/client/go/logger"
    17  	"github.com/pkg/errors"
    18  	billy "gopkg.in/src-d/go-billy.v4"
    19  )
    20  
    21  func newStatusFileNode(
    22  	config libkbfs.Config, log logger.Logger) *namedFileNode {
    23  	return &namedFileNode{
    24  		Node: nil,
    25  		log:  log,
    26  		name: StatusFileName,
    27  		reader: func(ctx context.Context) ([]byte, time.Time, error) {
    28  			return GetEncodedStatus(ctx, config)
    29  		},
    30  	}
    31  }
    32  
    33  func newUserEditHistoryFileNode(
    34  	config libkbfs.Config, log logger.Logger) *namedFileNode {
    35  	return &namedFileNode{
    36  		Node: nil,
    37  		log:  log,
    38  		name: EditHistoryName,
    39  		reader: func(ctx context.Context) ([]byte, time.Time, error) {
    40  			return GetEncodedUserEditHistory(ctx, config)
    41  		},
    42  	}
    43  }
    44  
    45  // RootFS is a browseable (read-only) version of `/keybase`.  It
    46  // does not support traversal into any subdirectories.
    47  type RootFS struct {
    48  	config libkbfs.Config
    49  	log    logger.Logger
    50  }
    51  
    52  // NewRootFS creates a new RootFS instance.
    53  func NewRootFS(config libkbfs.Config) *RootFS {
    54  	return &RootFS{
    55  		config: config,
    56  		log:    config.MakeLogger(""),
    57  	}
    58  }
    59  
    60  var _ billy.Filesystem = (*RootFS)(nil)
    61  
    62  ///// Read-only functions:
    63  
    64  var rootWrappedNodeNames = map[string]bool{
    65  	StatusFileName:     true,
    66  	MetricsFileName:    true,
    67  	ErrorFileName:      true,
    68  	EditHistoryName:    true,
    69  	ProfileListDirName: true,
    70  }
    71  
    72  // Open implements the billy.Filesystem interface for RootFS.
    73  func (rfs *RootFS) Open(filename string) (f billy.File, err error) {
    74  	if !rootWrappedNodeNames[filename] {
    75  		// In particular, this FS doesn't let you open the folderlist
    76  		// directories or anything in them.
    77  		return nil, os.ErrNotExist
    78  	}
    79  
    80  	ctx := context.TODO()
    81  	switch filename {
    82  	case StatusFileName:
    83  		return newStatusFileNode(rfs.config, rfs.log).GetFile(ctx), nil
    84  	case MetricsFileName:
    85  		return newMetricsFileNode(rfs.config, nil, rfs.log).GetFile(ctx), nil
    86  	case ErrorFileName:
    87  		return newErrorFileNode(rfs.config, nil, rfs.log).GetFile(ctx), nil
    88  	case EditHistoryName:
    89  		return newUserEditHistoryFileNode(rfs.config, rfs.log).GetFile(ctx), nil
    90  	default:
    91  		panic(fmt.Sprintf("Name %s was in map, but not in switch", filename))
    92  	}
    93  }
    94  
    95  // OpenFile implements the billy.Filesystem interface for RootFS.
    96  func (rfs *RootFS) OpenFile(filename string, flag int, _ os.FileMode) (
    97  	f billy.File, err error) {
    98  	if flag&os.O_CREATE != 0 {
    99  		return nil, errors.New("RootFS can't create files")
   100  	}
   101  
   102  	return rfs.Open(filename)
   103  }
   104  
   105  // Lstat implements the billy.Filesystem interface for RootFS.
   106  func (rfs *RootFS) Lstat(filename string) (fi os.FileInfo, err error) {
   107  	if filename == "" {
   108  		filename = "."
   109  	}
   110  	if filename == "." {
   111  		return &wrappedReadFileInfo{
   112  			"keybase", 0, rfs.config.Clock().Now(), true}, nil
   113  	}
   114  	if !rootWrappedNodeNames[filename] {
   115  		return nil, os.ErrNotExist
   116  	}
   117  
   118  	ctx := context.TODO()
   119  	switch filename {
   120  	case StatusFileName:
   121  		sfn := newStatusFileNode(rfs.config, rfs.log).GetFile(ctx)
   122  		return sfn.(*wrappedReadFile).GetInfo(), nil
   123  	case MetricsFileName:
   124  		mfn := newMetricsFileNode(rfs.config, nil, rfs.log).GetFile(ctx)
   125  		return mfn.(*wrappedReadFile).GetInfo(), nil
   126  	case ErrorFileName:
   127  		efn := newErrorFileNode(rfs.config, nil, rfs.log).GetFile(ctx)
   128  		return efn.(*wrappedReadFile).GetInfo(), nil
   129  	case EditHistoryName:
   130  		uehfn := newUserEditHistoryFileNode(rfs.config, rfs.log).GetFile(ctx)
   131  		return uehfn.(*wrappedReadFile).GetInfo(), nil
   132  	case ProfileListDirName:
   133  		return &wrappedReadFileInfo{
   134  			filename, 0, rfs.config.Clock().Now(), true}, nil
   135  	default:
   136  		panic(fmt.Sprintf("Name %s was in map, but not in switch", filename))
   137  	}
   138  }
   139  
   140  // Stat implements the billy.Filesystem interface for RootFS.
   141  func (rfs *RootFS) Stat(filename string) (fi os.FileInfo, err error) {
   142  	return rfs.Lstat(filename)
   143  }
   144  
   145  // Join implements the billy.Filesystem interface for RootFS.
   146  func (rfs *RootFS) Join(elem ...string) string {
   147  	return path.Clean(path.Join(elem...))
   148  }
   149  
   150  // ReadDir implements the billy.Filesystem interface for RootFS.
   151  func (rfs *RootFS) ReadDir(p string) (fis []os.FileInfo, err error) {
   152  	switch p {
   153  	case ProfileListDirName:
   154  		return NewProfileFS(rfs.config).ReadDir("")
   155  	case "", ".":
   156  		// Fall through.
   157  	default:
   158  		return nil, os.ErrNotExist
   159  	}
   160  
   161  	now := rfs.config.Clock().Now()
   162  	return []os.FileInfo{
   163  		&wrappedReadFileInfo{"private", 0, now, true},
   164  		&wrappedReadFileInfo{"public", 0, now, true},
   165  		&wrappedReadFileInfo{"team", 0, now, true},
   166  	}, nil
   167  }
   168  
   169  // Readlink implements the billy.Filesystem interface for RootFS.
   170  func (rfs *RootFS) Readlink(_ string) (target string, err error) {
   171  	return "", errors.New("RootFS cannot read links")
   172  }
   173  
   174  // Chroot implements the billy.Filesystem interface for RootFS.
   175  func (rfs *RootFS) Chroot(p string) (newFS billy.Filesystem, err error) {
   176  	if p == ProfileListDirName {
   177  		return dummyFSReadOnly{ProfileFS{rfs.config}}, nil
   178  	}
   179  	// Don't allow chroot'ing anywhere elsewhere outside of the root
   180  	// FS since we haven't yet implemented folderlist browsing.
   181  	return nil, errors.New("RootFS cannot chroot")
   182  }
   183  
   184  // Root implements the billy.Filesystem interface for RootFS.
   185  func (rfs *RootFS) Root() string {
   186  	return "/keybase"
   187  }
   188  
   189  ///// Modifying functions (not supported):
   190  
   191  // Create implements the billy.Filesystem interface for RootFS.
   192  func (rfs *RootFS) Create(_ string) (billy.File, error) {
   193  	return nil, errors.New("RootFS cannot create files")
   194  }
   195  
   196  // Rename implements the billy.Filesystem interface for RootFS.
   197  func (rfs *RootFS) Rename(_, _ string) (err error) {
   198  	return errors.New("RootFS cannot rename files")
   199  }
   200  
   201  // Remove implements the billy.Filesystem interface for RootFS.
   202  func (rfs *RootFS) Remove(_ string) (err error) {
   203  	return errors.New("RootFS cannot remove files")
   204  }
   205  
   206  // TempFile implements the billy.Filesystem interface for RootFS.
   207  func (rfs *RootFS) TempFile(_, _ string) (billy.File, error) {
   208  	return nil, errors.New("RootFS cannot make temp files")
   209  }
   210  
   211  // MkdirAll implements the billy.Filesystem interface for RootFS.
   212  func (rfs *RootFS) MkdirAll(_ string, _ os.FileMode) (err error) {
   213  	return errors.New("RootFS cannot mkdir")
   214  }
   215  
   216  // Symlink implements the billy.Filesystem interface for RootFS.
   217  func (rfs *RootFS) Symlink(_, _ string) (err error) {
   218  	return errors.New("RootFS cannot make symlinks")
   219  }
   220  
   221  // ToHTTPFileSystem calls fs.WithCtx with ctx to create a *RootFS with the new
   222  // ctx, and returns a wrapper around it that satisfies the http.FileSystem
   223  // interface. ctx is ignored here.
   224  func (rfs *RootFS) ToHTTPFileSystem(ctx context.Context) http.FileSystem {
   225  	return httpRootFileSystem{rfs: &RootFS{
   226  		config: rfs.config,
   227  		log:    rfs.log,
   228  	}}
   229  }