github.com/pachyderm/pachyderm@v1.13.4/src/server/pfs/fuse/loopback.go (about) 1 // Copyright 2019 the Go-FUSE Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package fuse 6 7 import ( 8 "context" 9 "os" 10 pathpkg "path" 11 "path/filepath" 12 "strings" 13 "sync" 14 "syscall" 15 16 "github.com/hanwen/go-fuse/v2/fs" 17 "github.com/hanwen/go-fuse/v2/fuse" 18 19 "github.com/pachyderm/pachyderm/src/client" 20 "github.com/pachyderm/pachyderm/src/client/pfs" 21 "github.com/pachyderm/pachyderm/src/client/pkg/errors" 22 pfsserver "github.com/pachyderm/pachyderm/src/server/pfs" 23 "github.com/pachyderm/pachyderm/src/server/pkg/errutil" 24 ) 25 26 type fileState int32 27 28 const ( 29 none fileState = iota // we don't know about this file 30 meta // we have meta information (but not content for this file) 31 full // we have full content for this file 32 dirty // we have full content for this file and the user has written to it 33 ) 34 35 type loopbackRoot struct { 36 loopbackNode 37 38 rootPath string 39 rootDev uint64 40 41 targetPath string 42 43 write bool 44 45 c *client.APIClient 46 47 repoOpts map[string]*RepoOptions 48 branches map[string]string 49 commits map[string]string 50 files map[string]fileState 51 mu sync.Mutex 52 } 53 54 type loopbackNode struct { 55 fs.Inode 56 } 57 58 var _ = (fs.NodeStatfser)((*loopbackNode)(nil)) 59 var _ = (fs.NodeStatfser)((*loopbackNode)(nil)) 60 var _ = (fs.NodeGetattrer)((*loopbackNode)(nil)) 61 var _ = (fs.NodeGetxattrer)((*loopbackNode)(nil)) 62 var _ = (fs.NodeSetxattrer)((*loopbackNode)(nil)) 63 var _ = (fs.NodeRemovexattrer)((*loopbackNode)(nil)) 64 var _ = (fs.NodeListxattrer)((*loopbackNode)(nil)) 65 var _ = (fs.NodeReadlinker)((*loopbackNode)(nil)) 66 var _ = (fs.NodeOpener)((*loopbackNode)(nil)) 67 var _ = (fs.NodeCopyFileRanger)((*loopbackNode)(nil)) 68 var _ = (fs.NodeLookuper)((*loopbackNode)(nil)) 69 var _ = (fs.NodeOpendirer)((*loopbackNode)(nil)) 70 var _ = (fs.NodeReaddirer)((*loopbackNode)(nil)) 71 var _ = (fs.NodeMkdirer)((*loopbackNode)(nil)) 72 var _ = (fs.NodeMknoder)((*loopbackNode)(nil)) 73 var _ = (fs.NodeLinker)((*loopbackNode)(nil)) 74 var _ = (fs.NodeSymlinker)((*loopbackNode)(nil)) 75 var _ = (fs.NodeUnlinker)((*loopbackNode)(nil)) 76 var _ = (fs.NodeRmdirer)((*loopbackNode)(nil)) 77 var _ = (fs.NodeRenamer)((*loopbackNode)(nil)) 78 79 func (n *loopbackNode) Statfs(ctx context.Context, out *fuse.StatfsOut) syscall.Errno { 80 s := syscall.Statfs_t{} 81 err := syscall.Statfs(n.path(), &s) 82 if err != nil { 83 return fs.ToErrno(err) 84 } 85 out.FromStatfsT(&s) 86 return fs.OK 87 } 88 89 func (r *loopbackRoot) Getattr(ctx context.Context, f fs.FileHandle, out *fuse.AttrOut) syscall.Errno { 90 91 st := syscall.Stat_t{} 92 err := syscall.Stat(r.rootPath, &st) 93 if err != nil { 94 return fs.ToErrno(err) 95 } 96 out.FromStat(&st) 97 return fs.OK 98 } 99 100 func (n *loopbackNode) root() *loopbackRoot { 101 return n.Root().Operations().(*loopbackRoot) 102 } 103 104 func (n *loopbackNode) c() *client.APIClient { 105 return n.root().c 106 } 107 108 func (n *loopbackNode) path() string { 109 path := n.Path(nil) 110 return filepath.Join(n.root().rootPath, path) 111 } 112 113 func (n *loopbackNode) Lookup(ctx context.Context, name string, out *fuse.EntryOut) (*fs.Inode, syscall.Errno) { 114 p := filepath.Join(n.path(), name) 115 if err := n.download(p, meta); err != nil { 116 return nil, fs.ToErrno(err) 117 } 118 119 st := syscall.Stat_t{} 120 err := syscall.Lstat(p, &st) 121 if err != nil { 122 return nil, fs.ToErrno(err) 123 } 124 125 out.Attr.FromStat(&st) 126 node := &loopbackNode{} 127 ch := n.NewInode(ctx, node, n.root().idFromStat(&st)) 128 return ch, 0 129 } 130 131 func (n *loopbackNode) Mknod(ctx context.Context, name string, mode, rdev uint32, out *fuse.EntryOut) (*fs.Inode, syscall.Errno) { 132 p := filepath.Join(n.path(), name) 133 if errno := n.checkWrite(p); errno != 0 { 134 return nil, errno 135 } 136 err := syscall.Mknod(p, mode, int(rdev)) 137 if err != nil { 138 return nil, fs.ToErrno(err) 139 } 140 st := syscall.Stat_t{} 141 if err := syscall.Lstat(p, &st); err != nil { 142 syscall.Rmdir(p) 143 return nil, fs.ToErrno(err) 144 } 145 146 out.Attr.FromStat(&st) 147 148 node := &loopbackNode{} 149 ch := n.NewInode(ctx, node, n.root().idFromStat(&st)) 150 151 return ch, 0 152 } 153 154 func (n *loopbackNode) Mkdir(ctx context.Context, name string, mode uint32, out *fuse.EntryOut) (*fs.Inode, syscall.Errno) { 155 p := filepath.Join(n.path(), name) 156 if errno := n.checkWrite(p); errno != 0 { 157 return nil, errno 158 } 159 if err := n.download(p, meta); err != nil { 160 return nil, fs.ToErrno(err) 161 } 162 err := os.Mkdir(p, os.FileMode(mode)) 163 if err != nil { 164 return nil, fs.ToErrno(err) 165 } 166 st := syscall.Stat_t{} 167 if err := syscall.Lstat(p, &st); err != nil { 168 syscall.Rmdir(p) 169 return nil, fs.ToErrno(err) 170 } 171 172 out.Attr.FromStat(&st) 173 174 node := &loopbackNode{} 175 ch := n.NewInode(ctx, node, n.root().idFromStat(&st)) 176 177 return ch, 0 178 } 179 180 func (n *loopbackNode) Rmdir(ctx context.Context, name string) syscall.Errno { 181 p := filepath.Join(n.path(), name) 182 if errno := n.checkWrite(p); errno != 0 { 183 return errno 184 } 185 if err := n.download(p, meta); err != nil { 186 return fs.ToErrno(err) 187 } 188 err := syscall.Rmdir(p) 189 return fs.ToErrno(err) 190 } 191 192 func (n *loopbackNode) Unlink(ctx context.Context, name string) (errno syscall.Errno) { 193 p := filepath.Join(n.path(), name) 194 if errno := n.checkWrite(p); errno != 0 { 195 return errno 196 } 197 if err := n.download(p, meta); err != nil { 198 return fs.ToErrno(err) 199 } 200 defer func() { 201 if errno == 0 { 202 n.setFileState(p, dirty) 203 } 204 }() 205 err := syscall.Unlink(p) 206 return fs.ToErrno(err) 207 } 208 209 func toLoopbackNode(op fs.InodeEmbedder) *loopbackNode { 210 if r, ok := op.(*loopbackRoot); ok { 211 return &r.loopbackNode 212 } 213 return op.(*loopbackNode) 214 } 215 216 func (n *loopbackNode) Rename(ctx context.Context, name string, newParent fs.InodeEmbedder, newName string, flags uint32) syscall.Errno { 217 newParentLoopback := toLoopbackNode(newParent) 218 if flags&fs.RENAME_EXCHANGE != 0 { 219 return n.renameExchange(name, newParentLoopback, newName) 220 } 221 222 p1 := filepath.Join(n.path(), name) 223 224 p2 := filepath.Join(newParentLoopback.path(), newName) 225 if errno := n.checkWrite(p1); errno != 0 { 226 return errno 227 } 228 if errno := n.checkWrite(p2); errno != 0 { 229 return errno 230 } 231 err := os.Rename(p1, p2) 232 return fs.ToErrno(err) 233 } 234 235 func (r *loopbackRoot) idFromStat(st *syscall.Stat_t) fs.StableAttr { 236 return fs.StableAttr{ 237 Mode: uint32(st.Mode), 238 Gen: 1, 239 Ino: 0, // let fuse generate this automatically 240 } 241 } 242 243 var _ = (fs.NodeCreater)((*loopbackNode)(nil)) 244 245 func (n *loopbackNode) Create(ctx context.Context, name string, flags uint32, mode uint32, out *fuse.EntryOut) (inode *fs.Inode, fh fs.FileHandle, fuseFlags uint32, errno syscall.Errno) { 246 p := filepath.Join(n.path(), name) 247 if errno := n.checkWrite(p); errno != 0 { 248 return nil, nil, 0, errno 249 } 250 if err := n.download(p, full); err != nil { 251 return nil, nil, 0, fs.ToErrno(err) 252 } 253 defer func() { 254 if errno == 0 { 255 n.setFileState(p, dirty) 256 } 257 }() 258 259 fd, err := syscall.Open(p, int(flags)|os.O_CREATE, mode) 260 if err != nil { 261 return nil, nil, 0, fs.ToErrno(err) 262 } 263 264 st := syscall.Stat_t{} 265 if err := syscall.Fstat(fd, &st); err != nil { 266 syscall.Close(fd) 267 return nil, nil, 0, fs.ToErrno(err) 268 } 269 270 node := &loopbackNode{} 271 ch := n.NewInode(ctx, node, n.root().idFromStat(&st)) 272 lf := NewLoopbackFile(fd) 273 274 out.FromStat(&st) 275 return ch, lf, 0, 0 276 } 277 278 func (n *loopbackNode) Symlink(ctx context.Context, target, name string, out *fuse.EntryOut) (_ *fs.Inode, errno syscall.Errno) { 279 p := filepath.Join(n.path(), name) 280 if errno := n.checkWrite(p); errno != 0 { 281 return nil, errno 282 } 283 if err := n.download(p, full); err != nil { 284 return nil, fs.ToErrno(err) 285 } 286 target = filepath.Join(n.root().rootPath, n.trimTargetPath(target)) 287 if err := n.download(target, full); err != nil { 288 return nil, fs.ToErrno(err) 289 } 290 defer func() { 291 if errno == 0 { 292 n.setFileState(p, dirty) 293 } 294 }() 295 err := syscall.Symlink(target, p) 296 if err != nil { 297 return nil, fs.ToErrno(err) 298 } 299 st := syscall.Stat_t{} 300 if syscall.Lstat(p, &st); err != nil { 301 syscall.Unlink(p) 302 return nil, fs.ToErrno(err) 303 } 304 node := &loopbackNode{} 305 ch := n.NewInode(ctx, node, n.root().idFromStat(&st)) 306 307 out.Attr.FromStat(&st) 308 return ch, 0 309 } 310 311 func (n *loopbackNode) Link(ctx context.Context, target fs.InodeEmbedder, name string, out *fuse.EntryOut) (_ *fs.Inode, errno syscall.Errno) { 312 p := filepath.Join(n.path(), name) 313 if errno := n.checkWrite(p); errno != 0 { 314 return nil, errno 315 } 316 if err := n.download(p, full); err != nil { 317 return nil, fs.ToErrno(err) 318 } 319 targetNode := toLoopbackNode(target) 320 if err := n.download(targetNode.path(), full); err != nil { 321 return nil, fs.ToErrno(err) 322 } 323 err := syscall.Link(targetNode.path(), p) 324 if err != nil { 325 return nil, fs.ToErrno(err) 326 } 327 defer func() { 328 if errno == 0 { 329 n.setFileState(p, dirty) 330 } 331 }() 332 st := syscall.Stat_t{} 333 if syscall.Lstat(p, &st); err != nil { 334 syscall.Unlink(p) 335 return nil, fs.ToErrno(err) 336 } 337 node := &loopbackNode{} 338 ch := n.NewInode(ctx, node, n.root().idFromStat(&st)) 339 340 out.Attr.FromStat(&st) 341 return ch, 0 342 } 343 344 func (n *loopbackNode) Readlink(ctx context.Context) ([]byte, syscall.Errno) { 345 p := n.path() 346 347 for l := 256; ; l *= 2 { 348 buf := make([]byte, l) 349 sz, err := syscall.Readlink(p, buf) 350 if err != nil { 351 return nil, fs.ToErrno(err) 352 } 353 354 if sz < len(buf) { 355 return buf[:sz], 0 356 } 357 } 358 } 359 360 func (n *loopbackNode) Open(ctx context.Context, flags uint32) (fh fs.FileHandle, fuseFlags uint32, errno syscall.Errno) { 361 p := n.path() 362 state := full 363 if isWrite(flags) { 364 if errno := n.checkWrite(p); errno != 0 { 365 return nil, 0, errno 366 } 367 state = dirty 368 } 369 if err := n.download(p, state); err != nil { 370 return nil, 0, fs.ToErrno(err) 371 } 372 if isCreate(flags) { 373 defer func() { 374 if errno == 0 { 375 n.setFileState(p, dirty) 376 } 377 }() 378 } 379 f, err := syscall.Open(p, int(flags), 0) 380 if err != nil { 381 return nil, 0, fs.ToErrno(err) 382 } 383 lf := NewLoopbackFile(f) 384 return lf, 0, 0 385 } 386 387 func (n *loopbackNode) Opendir(ctx context.Context) syscall.Errno { 388 if err := n.download(n.path(), meta); err != nil { 389 return fs.ToErrno(err) 390 } 391 fd, err := syscall.Open(n.path(), syscall.O_DIRECTORY, 0755) 392 if err != nil { 393 return fs.ToErrno(err) 394 } 395 syscall.Close(fd) 396 return fs.OK 397 } 398 399 func (n *loopbackNode) Readdir(ctx context.Context) (fs.DirStream, syscall.Errno) { 400 if err := n.download(n.path(), meta); err != nil { 401 return nil, fs.ToErrno(err) 402 } 403 return fs.NewLoopbackDirStream(n.path()) 404 } 405 406 func (n *loopbackNode) Getattr(ctx context.Context, f fs.FileHandle, out *fuse.AttrOut) syscall.Errno { 407 if f != nil { 408 return f.(fs.FileGetattrer).Getattr(ctx, out) 409 } 410 p := n.path() 411 if err := n.download(p, meta); err != nil { 412 return fs.ToErrno(err) 413 } 414 415 var err error = nil 416 st := syscall.Stat_t{} 417 err = syscall.Lstat(p, &st) 418 if err != nil { 419 return fs.ToErrno(err) 420 } 421 out.FromStat(&st) 422 return fs.OK 423 } 424 425 var _ = (fs.NodeSetattrer)((*loopbackNode)(nil)) 426 427 func (n *loopbackNode) Setattr(ctx context.Context, f fs.FileHandle, in *fuse.SetAttrIn, out *fuse.AttrOut) syscall.Errno { 428 p := n.path() 429 fsa, ok := f.(fs.FileSetattrer) 430 if ok && fsa != nil { 431 fsa.Setattr(ctx, in, out) 432 } else { 433 if m, ok := in.GetMode(); ok { 434 if err := syscall.Chmod(p, m); err != nil { 435 return fs.ToErrno(err) 436 } 437 } 438 439 uid, uok := in.GetUID() 440 gid, gok := in.GetGID() 441 if uok || gok { 442 suid := -1 443 sgid := -1 444 if uok { 445 suid = int(uid) 446 } 447 if gok { 448 sgid = int(gid) 449 } 450 if err := syscall.Chown(p, suid, sgid); err != nil { 451 return fs.ToErrno(err) 452 } 453 } 454 455 mtime, mok := in.GetMTime() 456 atime, aok := in.GetATime() 457 458 if mok || aok { 459 460 ap := &atime 461 mp := &mtime 462 if !aok { 463 ap = nil 464 } 465 if !mok { 466 mp = nil 467 } 468 var ts [2]syscall.Timespec 469 ts[0] = fuse.UtimeToTimespec(ap) 470 ts[1] = fuse.UtimeToTimespec(mp) 471 472 if err := syscall.UtimesNano(p, ts[:]); err != nil { 473 return fs.ToErrno(err) 474 } 475 } 476 477 if sz, ok := in.GetSize(); ok { 478 if err := syscall.Truncate(p, int64(sz)); err != nil { 479 return fs.ToErrno(err) 480 } 481 } 482 } 483 484 fga, ok := f.(fs.FileGetattrer) 485 if ok && fga != nil { 486 fga.Getattr(ctx, out) 487 } else { 488 st := syscall.Stat_t{} 489 err := syscall.Lstat(p, &st) 490 if err != nil { 491 return fs.ToErrno(err) 492 } 493 out.FromStat(&st) 494 } 495 return fs.OK 496 } 497 498 // newLoopbackRoot returns a root node for a loopback file system whose 499 // root is at the given root. This node implements all NodeXxxxer 500 // operations available. 501 func newLoopbackRoot(root, target string, c *client.APIClient, opts *Options) (*loopbackRoot, error) { 502 var st syscall.Stat_t 503 err := syscall.Stat(root, &st) 504 if err != nil { 505 return nil, errors.WithStack(err) 506 } 507 508 n := &loopbackRoot{ 509 rootPath: root, 510 rootDev: uint64(st.Dev), 511 targetPath: target, 512 write: opts.getWrite(), 513 c: c, 514 repoOpts: opts.getRepoOpts(), 515 branches: opts.getBranches(), 516 commits: make(map[string]string), 517 files: make(map[string]fileState), 518 } 519 return n, nil 520 } 521 522 func (n *loopbackNode) downloadRepos() (retErr error) { 523 if n.getFileState("") != none { 524 return nil 525 } 526 defer func() { 527 if retErr == nil { 528 n.setFileState("", meta) 529 } 530 }() 531 ris, err := n.c().ListRepo() 532 if err != nil { 533 return err 534 } 535 ro := n.root().repoOpts 536 for _, ri := range ris { 537 if len(ro) > 0 && ro[ri.Repo.Name] == nil { 538 continue 539 } 540 p := n.repoPath(ri) 541 if err := os.MkdirAll(p, 0777); err != nil { 542 return errors.WithStack(err) 543 } 544 } 545 return nil 546 } 547 548 // download files into the loopback filesystem, if meta is true then only the 549 // directory structure will be created, no actual data will be downloaded, 550 // files will be truncated to their actual sizes (but will be all zeros). 551 func (n *loopbackNode) download(path string, state fileState) (retErr error) { 552 if n.getFileState(path) >= state { 553 // Already got this file, so we can just return 554 return nil 555 } 556 if err := n.downloadRepos(); err != nil { 557 return err 558 } 559 path = n.trimPath(path) 560 parts := strings.Split(path, "/") 561 defer func() { 562 if retErr == nil { 563 n.setFileState(path, state) 564 } 565 }() 566 // Note, len(parts) < 1 should not actually be possible, but just in case 567 // no need to panic. 568 if len(parts) < 1 || parts[0] == "" { 569 return nil //already downloaded in downloadRepos 570 } 571 commit, err := n.commit(parts[0]) 572 if err != nil { 573 return err 574 } 575 if commit == "" { 576 return nil 577 } 578 if err := n.c().ListFileF(parts[0], commit, pathpkg.Join(parts[1:]...), 0, 579 func(fi *pfs.FileInfo) (retErr error) { 580 if fi.FileType == pfs.FileType_DIR { 581 return os.MkdirAll(n.filePath(fi), 0777) 582 } 583 p := n.filePath(fi) 584 // Make sure the directory exists 585 // I think this may be unnecessary based on the constraints the 586 // OS imposes, but don't want to rely on that, especially 587 // because Mkdir should be pretty cheap. 588 if err := os.MkdirAll(filepath.Dir(p), 0777); err != nil { 589 return errors.WithStack(err) 590 } 591 f, err := os.Create(p) 592 if err != nil { 593 return errors.WithStack(err) 594 } 595 defer func() { 596 if err := f.Close(); err != nil && retErr == nil { 597 retErr = errors.WithStack(err) 598 } 599 }() 600 if state < full { 601 return f.Truncate(int64(fi.SizeBytes)) 602 } 603 if err := n.c().GetFile(fi.File.Commit.Repo.Name, fi.File.Commit.ID, fi.File.Path, 0, 0, f); err != nil { 604 return err 605 } 606 return nil 607 }); err != nil && !errutil.IsNotFoundError(err) && 608 !pfsserver.IsOutputCommitNotFinishedErr(err) { 609 return err 610 } 611 return nil 612 } 613 614 func (n *loopbackNode) trimPath(path string) string { 615 path = strings.TrimPrefix(path, n.root().rootPath) 616 return strings.TrimPrefix(path, "/") 617 } 618 619 func (n *loopbackNode) trimTargetPath(path string) string { 620 path = strings.TrimPrefix(path, n.root().targetPath) 621 return strings.TrimPrefix(path, "/") 622 } 623 624 func (n *loopbackNode) branch(repo string) string { 625 // no need to lock mu for branches since we only ever read from it. 626 if branch, ok := n.root().branches[repo]; ok { 627 return branch 628 } 629 return "master" 630 } 631 632 func (n *loopbackNode) commit(repo string) (string, error) { 633 if commit, ok := func() (string, bool) { 634 n.root().mu.Lock() 635 defer n.root().mu.Unlock() 636 commit, ok := n.root().commits[repo] 637 return commit, ok 638 }(); ok { 639 return commit, nil 640 } 641 branch := n.root().branch(repo) 642 bi, err := n.root().c.InspectBranch(repo, branch) 643 if err != nil && !errutil.IsNotFoundError(err) { 644 return "", err 645 } 646 // Lock mu to assign commits 647 n.root().mu.Lock() 648 defer n.root().mu.Unlock() 649 // You can access branches that don't exist, which allows you to create 650 // branches through the fuse mount. 651 if errutil.IsNotFoundError(err) || bi.Head == nil { 652 n.root().commits[repo] = "" 653 return "", nil 654 } 655 n.root().commits[repo] = bi.Head.ID 656 return bi.Head.ID, nil 657 } 658 659 func (n *loopbackNode) repoPath(ri *pfs.RepoInfo) string { 660 return filepath.Join(n.root().rootPath, ri.Repo.Name) 661 } 662 663 func (n *loopbackNode) filePath(fi *pfs.FileInfo) string { 664 return filepath.Join(n.root().rootPath, fi.File.Commit.Repo.Name, fi.File.Path) 665 } 666 667 func (n *loopbackNode) getFileState(path string) fileState { 668 n.root().mu.Lock() 669 defer n.root().mu.Unlock() 670 return n.root().files[n.trimPath(path)] 671 } 672 673 func (n *loopbackNode) setFileState(path string, state fileState) { 674 n.root().mu.Lock() 675 defer n.root().mu.Unlock() 676 n.root().files[n.trimPath(path)] = state 677 } 678 679 func (n *loopbackNode) checkWrite(path string) syscall.Errno { 680 repo := strings.Split(n.trimPath(path), "/")[0] 681 ros := n.root().repoOpts 682 if len(ros) > 0 { 683 ro, ok := ros[repo] 684 if !ok || !ro.Write { 685 return syscall.EROFS 686 } 687 return 0 688 } 689 if !n.root().write { 690 return syscall.EROFS 691 } 692 return 0 693 } 694 695 func isWrite(flags uint32) bool { 696 return (int(flags) & (os.O_WRONLY | os.O_RDWR)) != 0 697 } 698 699 func isCreate(flags uint32) bool { 700 return int(flags)&os.O_CREATE != 0 701 }