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

     1  // Copyright 2016 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 fsrpc
     6  
     7  import (
     8  	"fmt"
     9  	"path/filepath"
    10  	"strings"
    11  
    12  	"github.com/keybase/client/go/kbfs/data"
    13  	"github.com/keybase/client/go/kbfs/idutil"
    14  	"github.com/keybase/client/go/kbfs/libkbfs"
    15  	"github.com/keybase/client/go/kbfs/tlf"
    16  	"github.com/keybase/client/go/kbfs/tlfhandle"
    17  	"github.com/pkg/errors"
    18  	"golang.org/x/net/context"
    19  )
    20  
    21  const (
    22  	topName     = "keybase"
    23  	publicName  = "public"
    24  	privateName = "private"
    25  	teamName    = "team"
    26  )
    27  
    28  // PathType describes the types for different paths
    29  type PathType int
    30  
    31  const (
    32  	// InvalidPathType denotes an invalid path type
    33  	InvalidPathType PathType = iota
    34  	// RootPathType is a root path type (like /)
    35  	RootPathType
    36  	// KeybasePathType is the keybase root (like /keybase)
    37  	KeybasePathType
    38  	// KeybaseChildPathType is a keybase reserved path (like /keybase/public)
    39  	KeybaseChildPathType
    40  	// TLFPathType is a top level folder (/keybase/public/gabrielh)
    41  	TLFPathType
    42  )
    43  
    44  // Path defines a file path in KBFS such as /keybase/public or /keybase/private/gabrielh
    45  type Path struct {
    46  	PathType      PathType
    47  	TLFType       tlf.Type
    48  	TLFName       string
    49  	TLFComponents []string
    50  }
    51  
    52  func splitHelper(cleanPath string) []string {
    53  	parentPathSlash, child := filepath.Split(cleanPath)
    54  	parentPath := parentPathSlash[:len(parentPathSlash)-1]
    55  	var parentComponents, childComponents []string
    56  	if child != "" {
    57  		childComponents = []string{child}
    58  	}
    59  	if parentPath != "" {
    60  		parentComponents = splitHelper(parentPath)
    61  	}
    62  	return append(parentComponents, childComponents...)
    63  }
    64  
    65  func split(pathStr string) ([]string, error) {
    66  	cleanPath := filepath.Clean(pathStr)
    67  	if !filepath.IsAbs(cleanPath) {
    68  		return nil, fmt.Errorf("split: %s is not an absolute path", pathStr)
    69  	}
    70  	return splitHelper(cleanPath), nil
    71  }
    72  
    73  func listTypeToTLFType(c string) tlf.Type {
    74  	switch c {
    75  	case privateName:
    76  		return tlf.Private
    77  	case publicName:
    78  		return tlf.Public
    79  	case teamName:
    80  		return tlf.SingleTeam
    81  	default:
    82  		// TODO: support team TLFs.
    83  		panic(fmt.Sprintf("Unknown folder list type: %s", c))
    84  	}
    85  }
    86  
    87  // NewPath constructs a Path from a string
    88  func NewPath(pathStr string) (Path, error) {
    89  	components, err := split(pathStr)
    90  	if err != nil {
    91  		return Path{}, err
    92  	}
    93  	len := len(components)
    94  
    95  	if (len >= 1 && components[0] != topName) ||
    96  		(len >= 2 && components[1] != publicName &&
    97  			components[1] != privateName && components[1] != teamName) {
    98  		return Path{}, InvalidPathErr{pathStr}
    99  	}
   100  
   101  	if len == 0 {
   102  		p := Path{
   103  			PathType: RootPathType,
   104  		}
   105  		return p, nil
   106  	}
   107  
   108  	if len == 1 {
   109  		p := Path{
   110  			PathType: KeybasePathType,
   111  		}
   112  		return p, nil
   113  	}
   114  
   115  	if len == 2 {
   116  		p := Path{
   117  			PathType: KeybaseChildPathType,
   118  			TLFType:  listTypeToTLFType(components[1]),
   119  		}
   120  		return p, nil
   121  	}
   122  
   123  	p := Path{
   124  		PathType:      TLFPathType,
   125  		TLFType:       listTypeToTLFType(components[1]),
   126  		TLFName:       components[2],
   127  		TLFComponents: components[3:],
   128  	}
   129  	return p, nil
   130  }
   131  
   132  func (p Path) String() string {
   133  	if p.PathType < RootPathType || p.PathType > TLFPathType {
   134  		return ""
   135  	}
   136  
   137  	var components []string
   138  	if p.PathType >= KeybasePathType && p.PathType <= TLFPathType {
   139  		components = append(components, topName)
   140  	}
   141  	if p.PathType >= KeybaseChildPathType && p.PathType <= TLFPathType {
   142  		switch p.TLFType {
   143  		case tlf.Public:
   144  			components = append(components, publicName)
   145  		case tlf.Private:
   146  			components = append(components, privateName)
   147  		default:
   148  			// TODO: add support for team TLFs.
   149  			panic(fmt.Sprintf("Unknown TLF type: %s", p.TLFType))
   150  		}
   151  	}
   152  	if p.PathType == TLFPathType {
   153  		components = append(append(components, p.TLFName), p.TLFComponents...)
   154  	}
   155  	return "/" + strings.Join(components, "/")
   156  }
   157  
   158  // DirAndBasename returns directory and base filename
   159  func (p Path) DirAndBasename() (dir Path, basename string, err error) {
   160  	switch p.PathType {
   161  	case KeybasePathType:
   162  		dir = Path{
   163  			PathType: RootPathType,
   164  		}
   165  		basename = topName
   166  		return
   167  
   168  	case KeybaseChildPathType:
   169  		dir = Path{
   170  			PathType: KeybasePathType,
   171  		}
   172  
   173  		switch p.TLFType {
   174  		case tlf.Public:
   175  			basename = publicName
   176  		case tlf.Private:
   177  			basename = privateName
   178  		default:
   179  			panic(fmt.Sprintf("Unknown TLF type: %s", p.TLFType))
   180  		}
   181  		return
   182  
   183  	case TLFPathType:
   184  		len := len(p.TLFComponents)
   185  		if len == 0 {
   186  			dir = Path{
   187  				PathType: KeybaseChildPathType,
   188  				TLFType:  p.TLFType,
   189  			}
   190  			basename = p.TLFName
   191  		} else {
   192  			dir = Path{
   193  				PathType:      TLFPathType,
   194  				TLFType:       p.TLFType,
   195  				TLFName:       p.TLFName,
   196  				TLFComponents: p.TLFComponents[:len-1],
   197  			}
   198  			basename = p.TLFComponents[len-1]
   199  		}
   200  		return
   201  	}
   202  
   203  	err = errors.New("cannot split path")
   204  	return
   205  }
   206  
   207  // Join will append a path to this path
   208  func (p Path) Join(childName string) (childPath Path, err error) {
   209  	switch p.PathType {
   210  	case RootPathType:
   211  		if childName != topName {
   212  			err = CannotJoinPathErr{p, childName}
   213  			return
   214  		}
   215  
   216  		childPath = Path{
   217  			PathType: KeybasePathType,
   218  		}
   219  		return
   220  
   221  	case KeybasePathType:
   222  		if childName != publicName && childName != privateName {
   223  			err = CannotJoinPathErr{p, childName}
   224  		}
   225  
   226  		childPath = Path{
   227  			PathType: KeybaseChildPathType,
   228  			TLFType:  listTypeToTLFType(childName),
   229  		}
   230  		return
   231  
   232  	case KeybaseChildPathType:
   233  		childPath = Path{
   234  			PathType: TLFPathType,
   235  			TLFType:  p.TLFType,
   236  			TLFName:  childName,
   237  		}
   238  		return
   239  
   240  	case TLFPathType:
   241  		childPath = Path{
   242  			PathType:      TLFPathType,
   243  			TLFType:       p.TLFType,
   244  			TLFName:       p.TLFName,
   245  			TLFComponents: append(p.TLFComponents, childName),
   246  		}
   247  		return
   248  	}
   249  
   250  	err = CannotJoinPathErr{p, childName}
   251  	return
   252  }
   253  
   254  // ParseTlfHandle is a wrapper around libkbfs.ParseTlfHandle that
   255  // automatically resolves non-canonical names.
   256  func ParseTlfHandle(
   257  	ctx context.Context, kbpki libkbfs.KBPKI, mdOps libkbfs.MDOps,
   258  	osg idutil.OfflineStatusGetter, name string, t tlf.Type) (
   259  	*tlfhandle.Handle, error) {
   260  	var tlfHandle *tlfhandle.Handle
   261  outer:
   262  	for {
   263  		var parseErr error
   264  		tlfHandle, parseErr = tlfhandle.ParseHandle(
   265  			ctx, kbpki, mdOps, osg, name, t)
   266  		switch parseErr := errors.Cause(parseErr).(type) {
   267  		case nil:
   268  			// No error.
   269  			break outer
   270  
   271  		case idutil.TlfNameNotCanonical:
   272  			// Non-canonical name, so try again.
   273  			name = parseErr.NameToTry
   274  
   275  		default:
   276  			// Some other error.
   277  			return nil, parseErr
   278  		}
   279  	}
   280  
   281  	return tlfHandle, nil
   282  }
   283  
   284  // GetNode returns a node
   285  func (p Path) GetNode(ctx context.Context, config libkbfs.Config) (libkbfs.Node, data.EntryInfo, error) {
   286  	if p.PathType != TLFPathType {
   287  		entryInfo := data.EntryInfo{
   288  			Type: data.Dir,
   289  		}
   290  		return nil, entryInfo, nil
   291  	}
   292  
   293  	tlfHandle, err := ParseTlfHandle(
   294  		ctx, config.KBPKI(), config.MDOps(), config, p.TLFName, p.TLFType)
   295  	if err != nil {
   296  		return nil, data.EntryInfo{}, err
   297  	}
   298  
   299  	node, entryInfo, err := config.KBFSOps().GetOrCreateRootNode(ctx, tlfHandle, data.MasterBranch)
   300  	if err != nil {
   301  		return nil, data.EntryInfo{}, err
   302  	}
   303  
   304  	for _, component := range p.TLFComponents {
   305  		lookupNode, lookupEntryInfo, lookupErr := config.KBFSOps().Lookup(
   306  			ctx, node, node.ChildName(component))
   307  		if lookupErr != nil {
   308  			return nil, data.EntryInfo{}, lookupErr
   309  		}
   310  		node = lookupNode
   311  		entryInfo = lookupEntryInfo
   312  	}
   313  
   314  	return node, entryInfo, nil
   315  }
   316  
   317  // GetFileNode returns a file node
   318  func (p Path) GetFileNode(ctx context.Context, config libkbfs.Config) (libkbfs.Node, error) {
   319  	n, de, err := p.GetNode(ctx, config)
   320  	if err != nil {
   321  		return nil, err
   322  	}
   323  
   324  	// TODO: What to do with symlinks?
   325  
   326  	if de.Type != data.File && de.Type != data.Exec {
   327  		return nil, fmt.Errorf("openFile: %s is not a file, but a %s", p, de.Type)
   328  	}
   329  
   330  	return n, nil
   331  }
   332  
   333  // GetDirNode returns a nil node if this doesn't have type TLFPathType
   334  func (p Path) GetDirNode(ctx context.Context, config libkbfs.Config) (libkbfs.Node, error) {
   335  	// TODO: Handle non-TLFPathTypes.
   336  
   337  	n, de, err := p.GetNode(ctx, config)
   338  	if err != nil {
   339  		return nil, err
   340  	}
   341  
   342  	// TODO: What to do with symlinks?
   343  
   344  	if de.Type != data.Dir {
   345  		return nil, fmt.Errorf("openDir: %s is not a dir, but a %s", p, de.Type)
   346  	}
   347  
   348  	return n, nil
   349  }
   350  
   351  // InvalidPathErr is error for invalid paths
   352  type InvalidPathErr struct {
   353  	pathStr string
   354  }
   355  
   356  func (e InvalidPathErr) Error() string {
   357  	return fmt.Sprintf("invalid kbfs path %s", e.pathStr)
   358  }
   359  
   360  // CannotJoinPathErr is returned on Join error
   361  type CannotJoinPathErr struct {
   362  	p    Path
   363  	name string
   364  }
   365  
   366  func (e CannotJoinPathErr) Error() string {
   367  	return fmt.Sprintf("cannot join %s to %s", e.p, e.name)
   368  }