github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/kbfs/libfs/node_wrappers.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  	"os"
    11  	"regexp"
    12  	"strconv"
    13  	"strings"
    14  	"time"
    15  
    16  	"github.com/keybase/client/go/kbfs/data"
    17  	"github.com/keybase/client/go/kbfs/kbfsblock"
    18  	"github.com/keybase/client/go/kbfs/kbfsmd"
    19  	"github.com/keybase/client/go/kbfs/libkbfs"
    20  	"github.com/keybase/client/go/logger"
    21  	"github.com/keybase/client/go/protocol/keybase1"
    22  	"github.com/pkg/errors"
    23  	billy "gopkg.in/src-d/go-billy.v4"
    24  )
    25  
    26  type namedFileNode struct {
    27  	libkbfs.Node
    28  
    29  	log    logger.Logger
    30  	name   string
    31  	reader func(ctx context.Context) ([]byte, time.Time, error)
    32  }
    33  
    34  var _ libkbfs.Node = (*namedFileNode)(nil)
    35  
    36  func (nfn *namedFileNode) GetFile(ctx context.Context) billy.File {
    37  	return &wrappedReadFile{
    38  		name:   nfn.name,
    39  		reader: nfn.reader,
    40  		log:    nfn.log,
    41  	}
    42  }
    43  
    44  func (nfn *namedFileNode) FillCacheDuration(d *time.Duration) {
    45  	// Suggest kindly that no one should cache this node, since it
    46  	// could change each time it's read.
    47  	*d = 0
    48  }
    49  
    50  func newFolderStatusFileNode(
    51  	config libkbfs.Config, node libkbfs.Node, fb data.FolderBranch,
    52  	log logger.Logger) *namedFileNode {
    53  	return &namedFileNode{
    54  		Node: node,
    55  		log:  log,
    56  		name: StatusFileName,
    57  		reader: func(ctx context.Context) ([]byte, time.Time, error) {
    58  			return GetEncodedFolderStatus(ctx, config, fb)
    59  		},
    60  	}
    61  }
    62  
    63  func newMetricsFileNode(
    64  	config libkbfs.Config, node libkbfs.Node,
    65  	log logger.Logger) *namedFileNode {
    66  	return &namedFileNode{
    67  		Node:   node,
    68  		log:    log,
    69  		name:   MetricsFileName,
    70  		reader: GetEncodedMetrics(config),
    71  	}
    72  }
    73  
    74  func newErrorFileNode(
    75  	config libkbfs.Config, node libkbfs.Node,
    76  	log logger.Logger) *namedFileNode {
    77  	return &namedFileNode{
    78  		Node:   node,
    79  		log:    log,
    80  		name:   ErrorFileName,
    81  		reader: GetEncodedErrors(config),
    82  	}
    83  }
    84  
    85  func newTlfEditHistoryFileNode(
    86  	config libkbfs.Config, node libkbfs.Node, fb data.FolderBranch,
    87  	log logger.Logger) *namedFileNode {
    88  	return &namedFileNode{
    89  		Node: node,
    90  		log:  log,
    91  		name: EditHistoryName,
    92  		reader: func(ctx context.Context) ([]byte, time.Time, error) {
    93  			return GetEncodedTlfEditHistory(ctx, config, fb)
    94  		},
    95  	}
    96  }
    97  
    98  func newUpdateHistoryFileNode(
    99  	config libkbfs.Config, node libkbfs.Node, fb data.FolderBranch,
   100  	start, end kbfsmd.Revision, log logger.Logger) *namedFileNode {
   101  	return &namedFileNode{
   102  		Node: node,
   103  		log:  log,
   104  		name: UpdateHistoryFileName,
   105  		reader: func(ctx context.Context) ([]byte, time.Time, error) {
   106  			return GetEncodedUpdateHistory(ctx, config, fb, start, end)
   107  		},
   108  	}
   109  }
   110  
   111  var updateHistoryRevsRE = regexp.MustCompile("^\\.([0-9]+)(-([0-9]+))?$") //nolint (`\.` doesn't seem to work in single quotes)
   112  
   113  type profileNode struct {
   114  	libkbfs.Node
   115  
   116  	config libkbfs.Config
   117  	name   string
   118  }
   119  
   120  var _ libkbfs.Node = (*profileNode)(nil)
   121  
   122  func (pn *profileNode) GetFile(ctx context.Context) billy.File {
   123  	fs := NewProfileFS(pn.config)
   124  	f, err := fs.Open(pn.name)
   125  	if err != nil {
   126  		return nil
   127  	}
   128  	return f
   129  }
   130  
   131  func (pn *profileNode) FillCacheDuration(d *time.Duration) {
   132  	// Suggest kindly that no one should cache this node, since it
   133  	// could change each time it's read.
   134  	*d = 0
   135  }
   136  
   137  type profileListNode struct {
   138  	libkbfs.Node
   139  
   140  	config libkbfs.Config
   141  }
   142  
   143  var _ libkbfs.Node = (*profileListNode)(nil)
   144  
   145  func (pln *profileListNode) ShouldCreateMissedLookup(
   146  	ctx context.Context, name data.PathPartString) (
   147  	bool, context.Context, data.EntryType, os.FileInfo, data.PathPartString,
   148  	data.BlockPointer) {
   149  	namePlain := name.Plaintext()
   150  
   151  	fs := NewProfileFS(pln.config)
   152  	fi, err := fs.Lstat(namePlain)
   153  	if err != nil {
   154  		return pln.Node.ShouldCreateMissedLookup(ctx, name)
   155  	}
   156  
   157  	return true, ctx, data.FakeFile, fi, data.PathPartString{}, data.ZeroPtr
   158  }
   159  
   160  func (pln *profileListNode) WrapChild(child libkbfs.Node) libkbfs.Node {
   161  	child = pln.Node.WrapChild(child)
   162  	return &profileNode{child, pln.config, child.GetBasename().Plaintext()}
   163  }
   164  
   165  func (pln *profileListNode) GetFS(ctx context.Context) libkbfs.NodeFSReadOnly {
   166  	return NewProfileFS(pln.config)
   167  }
   168  
   169  // specialFileNode is a Node wrapper around a TLF node, that causes
   170  // special files to be fake-created when they are accessed.
   171  type specialFileNode struct {
   172  	libkbfs.Node
   173  
   174  	config libkbfs.Config
   175  	log    logger.Logger
   176  }
   177  
   178  var _ libkbfs.Node = (*specialFileNode)(nil)
   179  
   180  var perTlfWrappedNodeNames = map[string]bool{
   181  	StatusFileName:        true,
   182  	UpdateHistoryFileName: true,
   183  	ProfileListDirName:    true,
   184  	MetricsFileName:       true,
   185  	ErrorFileName:         true,
   186  	EditHistoryName:       true,
   187  }
   188  
   189  var perTlfWrappedNodePrefixes = []string{
   190  	UpdateHistoryFileName,
   191  	DirBlockPrefix,
   192  }
   193  
   194  func shouldBeTlfWrappedNode(name string) bool {
   195  	for _, p := range perTlfWrappedNodePrefixes {
   196  		if strings.HasPrefix(name, p) {
   197  			return true
   198  		}
   199  	}
   200  	return perTlfWrappedNodeNames[name]
   201  }
   202  
   203  func (sfn *specialFileNode) newUpdateHistoryFileNode(
   204  	node libkbfs.Node, name string) *namedFileNode {
   205  	revs := strings.TrimPrefix(name, UpdateHistoryFileName)
   206  	if revs == "" {
   207  		return newUpdateHistoryFileNode(
   208  			sfn.config, node, sfn.GetFolderBranch(),
   209  			kbfsmd.RevisionInitial, kbfsmd.RevisionUninitialized, sfn.log)
   210  	}
   211  
   212  	matches := updateHistoryRevsRE.FindStringSubmatch(revs)
   213  	if len(matches) != 4 {
   214  		return nil
   215  	}
   216  
   217  	start, err := strconv.ParseUint(matches[1], 10, 64)
   218  	if err != nil {
   219  		return nil
   220  	}
   221  	end := start
   222  	if matches[3] != "" {
   223  		end, err = strconv.ParseUint(matches[3], 10, 64)
   224  		if err != nil {
   225  			return nil
   226  		}
   227  	}
   228  
   229  	return newUpdateHistoryFileNode(
   230  		sfn.config, node, sfn.GetFolderBranch(),
   231  		kbfsmd.Revision(start), kbfsmd.Revision(end), sfn.log)
   232  }
   233  
   234  // parseBlockPointer returns a real BlockPointer given a string.  The
   235  // format for the string is: id.keyGen.dataVer.creatorUID.directType
   236  func parseBlockPointer(plain string) (data.BlockPointer, error) {
   237  	s := strings.Split(plain, ".")
   238  	if len(s) != 5 {
   239  		return data.ZeroPtr, errors.Errorf(
   240  			"%s is not in the right format for a block pointer", plain)
   241  	}
   242  
   243  	id, err := kbfsblock.IDFromString(s[0])
   244  	if err != nil {
   245  		return data.ZeroPtr, err
   246  	}
   247  
   248  	keyGen, err := strconv.Atoi(s[1])
   249  	if err != nil {
   250  		return data.ZeroPtr, err
   251  	}
   252  
   253  	dataVer, err := strconv.Atoi(s[2])
   254  	if err != nil {
   255  		return data.ZeroPtr, err
   256  	}
   257  
   258  	creator, err := keybase1.UserOrTeamIDFromString(s[3])
   259  	if err != nil {
   260  		return data.ZeroPtr, err
   261  	}
   262  
   263  	directType := data.BlockDirectTypeFromString(s[4])
   264  
   265  	return data.BlockPointer{
   266  		ID:         id,
   267  		KeyGen:     kbfsmd.KeyGen(keyGen),
   268  		DataVer:    data.Ver(dataVer),
   269  		DirectType: directType,
   270  		Context: kbfsblock.MakeFirstContext(
   271  			creator, keybase1.BlockType_DATA),
   272  	}, nil
   273  }
   274  
   275  // ShouldCreateMissedLookup implements the Node interface for
   276  // specialFileNode.
   277  func (sfn *specialFileNode) ShouldCreateMissedLookup(
   278  	ctx context.Context, name data.PathPartString) (
   279  	bool, context.Context, data.EntryType, os.FileInfo, data.PathPartString,
   280  	data.BlockPointer) {
   281  	plain := name.Plaintext()
   282  	if !shouldBeTlfWrappedNode(plain) {
   283  		return sfn.Node.ShouldCreateMissedLookup(ctx, name)
   284  	}
   285  
   286  	switch {
   287  	case plain == StatusFileName:
   288  		sfn := newFolderStatusFileNode(
   289  			sfn.config, nil, sfn.GetFolderBranch(), sfn.log)
   290  		f := sfn.GetFile(ctx)
   291  		return true, ctx, data.FakeFile, f.(*wrappedReadFile).GetInfo(),
   292  			data.PathPartString{}, data.ZeroPtr
   293  	case plain == MetricsFileName:
   294  		mfn := newMetricsFileNode(sfn.config, nil, sfn.log)
   295  		f := mfn.GetFile(ctx)
   296  		return true, ctx, data.FakeFile, f.(*wrappedReadFile).GetInfo(),
   297  			data.PathPartString{}, data.ZeroPtr
   298  	case plain == ErrorFileName:
   299  		efn := newErrorFileNode(sfn.config, nil, sfn.log)
   300  		f := efn.GetFile(ctx)
   301  		return true, ctx, data.FakeFile, f.(*wrappedReadFile).GetInfo(),
   302  			data.PathPartString{}, data.ZeroPtr
   303  	case plain == EditHistoryName:
   304  		tehfn := newTlfEditHistoryFileNode(
   305  			sfn.config, nil, sfn.GetFolderBranch(), sfn.log)
   306  		f := tehfn.GetFile(ctx)
   307  		return true, ctx, data.FakeFile, f.(*wrappedReadFile).GetInfo(),
   308  			data.PathPartString{}, data.ZeroPtr
   309  	case plain == ProfileListDirName:
   310  		return true, ctx, data.FakeDir,
   311  			&wrappedReadFileInfo{plain, 0, sfn.config.Clock().Now(), true},
   312  			data.PathPartString{}, data.ZeroPtr
   313  	case strings.HasPrefix(plain, UpdateHistoryFileName):
   314  		uhfn := sfn.newUpdateHistoryFileNode(nil, plain)
   315  		if uhfn == nil {
   316  			return sfn.Node.ShouldCreateMissedLookup(ctx, name)
   317  		}
   318  		f := uhfn.GetFile(ctx)
   319  		return true, ctx, data.FakeFile, f.(*wrappedReadFile).GetInfo(),
   320  			data.PathPartString{}, data.ZeroPtr
   321  	case strings.HasPrefix(plain, DirBlockPrefix):
   322  		ptr, err := parseBlockPointer(strings.TrimPrefix(plain, DirBlockPrefix))
   323  		if err != nil {
   324  			sfn.log.CDebugf(
   325  				ctx, "Couldn't parse block pointer for %s: %+v", name, err)
   326  			return sfn.Node.ShouldCreateMissedLookup(ctx, name)
   327  		}
   328  
   329  		info := &wrappedReadFileInfo{
   330  			name:  plain,
   331  			size:  0,
   332  			mtime: time.Now(),
   333  			dir:   true,
   334  		}
   335  
   336  		return true, ctx, data.RealDir, info, data.PathPartString{}, ptr
   337  	default:
   338  		panic(fmt.Sprintf("Name %s was in map, but not in switch", name))
   339  	}
   340  
   341  }
   342  
   343  // WrapChild implements the Node interface for specialFileNode.
   344  func (sfn *specialFileNode) WrapChild(child libkbfs.Node) libkbfs.Node {
   345  	child = sfn.Node.WrapChild(child)
   346  	name := child.GetBasename().Plaintext()
   347  	if !shouldBeTlfWrappedNode(name) {
   348  		if child.EntryType() == data.Dir {
   349  			// Wrap this child too, so we can look up special files in
   350  			// subdirectories of this node as well.
   351  			return &specialFileNode{
   352  				Node:   child,
   353  				config: sfn.config,
   354  				log:    sfn.log,
   355  			}
   356  		}
   357  		return child
   358  	}
   359  
   360  	switch {
   361  	case name == StatusFileName:
   362  		return newFolderStatusFileNode(
   363  			sfn.config, &libkbfs.ReadonlyNode{Node: child},
   364  			sfn.GetFolderBranch(), sfn.log)
   365  	case name == MetricsFileName:
   366  		return newMetricsFileNode(
   367  			sfn.config, &libkbfs.ReadonlyNode{Node: child}, sfn.log)
   368  	case name == ErrorFileName:
   369  		return newErrorFileNode(
   370  			sfn.config, &libkbfs.ReadonlyNode{Node: child}, sfn.log)
   371  	case name == EditHistoryName:
   372  		return newTlfEditHistoryFileNode(
   373  			sfn.config, &libkbfs.ReadonlyNode{Node: child},
   374  			sfn.GetFolderBranch(), sfn.log)
   375  	case name == ProfileListDirName:
   376  		return &profileListNode{
   377  			Node:   &libkbfs.ReadonlyNode{Node: child},
   378  			config: sfn.config,
   379  		}
   380  	case strings.HasPrefix(name, UpdateHistoryFileName):
   381  		uhfn := sfn.newUpdateHistoryFileNode(child, name)
   382  		if uhfn == nil {
   383  			return child
   384  		}
   385  		return uhfn
   386  	case strings.HasPrefix(name, DirBlockPrefix):
   387  		return &libkbfs.ReadonlyNode{Node: child}
   388  	default:
   389  		panic(fmt.Sprintf("Name %s was in map, but not in switch", name))
   390  	}
   391  }
   392  
   393  // rootWrapper is a struct that manages wrapping root nodes with
   394  // special per-TLF content.
   395  type rootWrapper struct {
   396  	config libkbfs.Config
   397  	log    logger.Logger
   398  }
   399  
   400  func (rw rootWrapper) wrap(node libkbfs.Node) libkbfs.Node {
   401  	return &specialFileNode{
   402  		Node:   node,
   403  		config: rw.config,
   404  		log:    rw.log,
   405  	}
   406  }
   407  
   408  // AddRootWrapper should be called on startup by any KBFS interface
   409  // that wants to handle special files.
   410  func AddRootWrapper(config libkbfs.Config) {
   411  	rw := rootWrapper{config, config.MakeLogger("")}
   412  	config.AddRootNodeWrapper(rw.wrap)
   413  }