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 }