github.com/keybase/client/go@v0.0.0-20241007131713-f10651d043c8/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 }