github.com/icexin/eggos@v0.4.2-0.20220216025428-78b167e4f349/fs/mount/mountfs.go (about) 1 // Copyright © 2017 Blake Williams <code@shabbyrobe.org> 2 // Copyright © 2020 fanbingxin <fanbingxin.me@gmail.com> 3 // 4 // Licensed under the Apache License, Version 2.0 (the "License"); 5 // you may not use this file except in compliance with the License. 6 // You may obtain a copy of the License at 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package mount 16 17 import ( 18 "errors" 19 "os" 20 "path/filepath" 21 "sort" 22 "strings" 23 "time" 24 25 . "github.com/spf13/afero" 26 ) 27 28 // assert that mount.MountableFs implements afero.Fs. 29 var _ Fs = (*MountableFs)(nil) 30 31 // MountableFs allows different paths in a hierarchy to be served by different 32 // afero.Fs objects. 33 type MountableFs struct { 34 node *mountableNode 35 36 // If true, it is possible to mount an Fs over an existing file or directory. 37 // If false, attempting to do so will result in an error. 38 AllowMasking bool 39 40 // If true, the same Fs can be mounted inside an existing mount of the same Fs, 41 // for e.g: 42 // child := afero.NewMemMapFs() 43 // mfs.Mount("/yep", child) 44 // mfs.Mount("/yep/yep", child) 45 AllowRecursiveMount bool 46 47 now func() time.Time 48 } 49 50 func NewMountableFs(base Fs) *MountableFs { 51 if base == nil { 52 base = NewMemMapFs() 53 } 54 mfs := &MountableFs{ 55 now: time.Now, 56 node: &mountableNode{fs: base, nodes: map[string]*mountableNode{}}, 57 } 58 return mfs 59 } 60 61 // Mount an afero.Fs at the specified path. 62 // 63 // This will fail if there is already a Fs at the path, or 64 // any existing mounted Fs contains a file at that path. 65 // 66 // You must wrap an afero.OsFs in an afero.BasePathFs to mount it, 67 // even if that's just to dispose of the Windows drive letter. 68 func (m *MountableFs) Mount(path string, fs Fs) error { 69 // No idea what to do with windows drive letters here, so force BasePathFs 70 if _, ok := fs.(*OsFs); ok { 71 return errOsFs 72 } 73 74 if info, err := m.Stat(path); err != nil { 75 if !os.IsNotExist(err) { 76 return err 77 } 78 } else { 79 if !m.AllowMasking && info != nil && !IsMountNode(info) { 80 return os.ErrExist 81 } 82 } 83 84 parts := splitPath(path) 85 86 cur := m.node 87 for i, p := range parts { 88 var next *mountableNode 89 var ok bool 90 if next, ok = cur.nodes[p]; !ok { 91 next = &mountableNode{ 92 nodes: map[string]*mountableNode{}, 93 parent: cur, 94 name: p, 95 depth: i + 1, 96 modTime: m.now()} 97 } 98 if next.fs == fs && !m.AllowRecursiveMount { 99 return errRecursiveMount 100 } 101 cur.nodes[p] = next 102 cur = next 103 } 104 if cur.fs != nil { 105 return errAlreadyMounted 106 } 107 if cur.parent != nil { 108 cur.parent.mountedNodes++ 109 } 110 111 cur.fs = fs 112 return nil 113 } 114 115 func (m *MountableFs) Umount(path string) error { 116 parts := splitPath(path) 117 118 cur := m.node 119 for _, p := range parts { 120 if next, ok := cur.nodes[p]; ok { 121 cur = next 122 } else { 123 return &os.PathError{Err: errNotMounted, Op: "Umount", Path: path} 124 } 125 } 126 if cur.fs == nil { 127 return &os.PathError{Err: errNotMounted, Op: "Umount", Path: path} 128 } 129 130 for cur != nil { 131 // Don't stuff around with the root node! 132 if cur.parent != nil { 133 cur.fs = nil 134 cur.parent.mountedNodes-- 135 if len(cur.nodes) == 0 { 136 delete(cur.parent.nodes, cur.name) 137 } 138 } 139 cur = cur.parent 140 } 141 142 return nil 143 } 144 145 func (m *MountableFs) Remount(path string, fs Fs) error { 146 if err := m.Umount(path); err != nil { 147 return wrapErrorPath(path, err) 148 } 149 return m.Mount(path, fs) 150 } 151 152 func (m *MountableFs) Mkdir(name string, perm os.FileMode) error { 153 node := m.node.findNode(name) 154 if node != nil { 155 // if the path points to an intermediate node and the intermediate node 156 // doesn't mask a real directory on the underlying filesystem, 157 // make the directory inside the parent filesystem. 158 if exists, err := m.reallyExists(name); err != nil || exists { 159 return wrapErrorPath(name, err) 160 } 161 fsNode := node.parentWithFs() 162 if fsNode == nil { 163 return &os.PathError{Err: os.ErrNotExist, Op: "Mkdir", Path: name} 164 } 165 rel, err := filepath.Rel(fsNode.fullName(), name) 166 if err != nil { 167 return wrapErrorPath(name, err) 168 } 169 rel = string(filepath.Separator) + rel 170 if err := fsNode.fs.Mkdir(rel, perm); err != nil { 171 return wrapErrorPath(name, err) 172 } 173 return nil 174 175 } else { 176 fs, _, rel := m.node.findPath(name) 177 err := wrapErrorPath(name, fs.Mkdir(rel, perm)) 178 return err 179 } 180 } 181 182 func (m *MountableFs) MkdirAll(path string, perm os.FileMode) error { 183 parts := splitPath(path) 184 partlen := len(parts) 185 for i := 0; i <= partlen; i++ { 186 cur := joinPath(parts[0:i]) 187 if err := m.Mkdir(cur, perm); err != nil && !os.IsExist(err) { 188 return err 189 } 190 } 191 return nil 192 } 193 194 func (m *MountableFs) Create(name string) (File, error) { 195 return m.OpenFile(name, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666) 196 } 197 198 func (m *MountableFs) Open(name string) (File, error) { 199 return m.OpenFile(name, os.O_RDONLY, 0) 200 } 201 202 func (m *MountableFs) OpenFile(name string, flag int, perm os.FileMode) (File, error) { 203 fs, _, rel := m.node.findPath(name) 204 205 exists := true 206 isdir, err := IsDir(fs, rel) 207 if err != nil { 208 if !os.IsNotExist(err) { 209 return nil, wrapErrorPath(name, err) 210 } else { 211 exists = false 212 } 213 } 214 215 if isdir || !exists { 216 node := m.node.findNode(name) 217 if node != nil { 218 file, err := fs.OpenFile(rel, flag, perm) 219 if err != nil && !os.IsNotExist(err) { 220 return nil, wrapErrorPath(name, err) 221 } 222 mf := &mountableFile{file: file, node: node, base: rel, name: node.name} 223 return mf, nil 224 } 225 } 226 227 // if we try to write a file into an intermediate node not backed by a 228 // directory, we should create it to preserve their illusion: 229 if flag&os.O_CREATE == os.O_CREATE { 230 parentName := filepath.Dir(name) 231 parent := m.node.findNode(parentName) 232 233 if parent != nil && parent.fs == nil { 234 parts := splitPath(parentName) 235 i := len(parts) 236 237 var fs Fs 238 next := parent 239 for next != nil && fs == nil { 240 if next.fs != nil { 241 fs = next.fs 242 } 243 if next.parent != nil { 244 i-- 245 } 246 next = next.parent 247 } 248 for j := range parts[i:] { 249 if err := fs.Mkdir(joinPath(parts[i:i+j+1]), perm|0111); err != nil && !os.IsExist(err) { 250 return nil, wrapErrorPath(name, err) 251 } 252 } 253 } 254 } 255 256 return fs.OpenFile(rel, flag, perm) 257 } 258 259 func (m *MountableFs) Remove(name string) error { 260 fs, _, rel := m.node.findPath(name) 261 return wrapErrorPath(name, fs.Remove(rel)) 262 } 263 264 func (m *MountableFs) RemoveAll(path string) error { 265 info, err := lstatIfPossible(m, path) 266 if err != nil { 267 return wrapErrorPath(path, err) 268 } 269 err = departWalk(m, path, info, func(path string, info os.FileInfo, err error) error { 270 if err != nil { 271 return err 272 } 273 if IsMountNode(info) { 274 return nil 275 } else { 276 if info.IsDir() { 277 node := m.node.findNode(path) 278 if node != nil { 279 return nil 280 } 281 } 282 return m.Remove(path) 283 } 284 }) 285 return wrapErrorPath(path, err) 286 } 287 288 func (m *MountableFs) Rename(oldname string, newname string) error { 289 ofs, _, orel := m.node.findPath(oldname) 290 nfs, _, nrel := m.node.findPath(newname) 291 292 if ofs == nfs { 293 return wrapErrorPath(oldname, ofs.Rename(orel, nrel)) 294 } else { 295 return errCrossFsRename 296 } 297 } 298 299 func (m *MountableFs) Stat(name string) (os.FileInfo, error) { 300 node := m.node.findNode(name) 301 if node != nil && node != m.node { 302 return mountedDirFromNode(node) 303 } 304 fs, _, rel := m.node.findPath(name) 305 info, err := fs.Stat(rel) 306 if err != nil { 307 return nil, wrapErrorPath(name, err) 308 } 309 return info, nil 310 } 311 312 func (m *MountableFs) Name() string { 313 return "MountableFs" 314 } 315 316 func (m *MountableFs) Chmod(name string, mode os.FileMode) error { 317 fs, _, rel := m.node.findPath(name) 318 return wrapErrorPath(name, fs.Chmod(rel, mode)) 319 } 320 321 // Chown changes the uid and gid of the named file. 322 func (m *MountableFs) Chown(name string, uid, gid int) error { 323 fs, _, rel := m.node.findPath(name) 324 return wrapErrorPath(name, fs.Chown(rel, uid, gid)) 325 } 326 327 func (m *MountableFs) Chtimes(name string, atime time.Time, mtime time.Time) error { 328 fs, _, rel := m.node.findPath(name) 329 ok, err := Exists(fs, rel) 330 if err != nil { 331 return wrapErrorPath(name, err) 332 } 333 if !ok { 334 node := m.node.findNode(name) 335 if node == nil { 336 return &os.PathError{Err: os.ErrNotExist, Op: "Chtimes", Path: name} 337 } 338 node.modTime = mtime 339 return nil 340 } else { 341 return wrapErrorPath(name, fs.Chtimes(rel, atime, mtime)) 342 } 343 } 344 345 // reallyExists returns true if the file or directory exists on the 346 // base fs or any of the mounted fs, but not if the path is an intermediate 347 // mounted node (i.e. if you mount a path but the in-between directories don't 348 // exist). 349 func (m *MountableFs) reallyExists(name string) (bool, error) { 350 s, err := m.Stat(name) 351 if os.IsNotExist(err) { 352 return false, nil 353 } else if err != nil { 354 return false, err 355 } else if IsMountNode(s) { 356 return false, nil 357 } 358 return true, nil 359 } 360 361 func wrapErrorPath(path string, err error) error { 362 if err == nil { 363 return nil 364 } 365 switch err := err.(type) { 366 case *os.PathError: 367 err.Path = path 368 } 369 return err 370 } 371 372 type mountableNode struct { 373 fs Fs 374 parent *mountableNode 375 nodes map[string]*mountableNode 376 name string 377 mountedNodes int 378 modTime time.Time 379 depth int 380 } 381 382 func (n *mountableNode) parentWithFs() (node *mountableNode) { 383 node = n.parent 384 for node != nil { 385 if node.fs != nil { 386 return 387 } 388 node = node.parent 389 } 390 return 391 } 392 393 func (n *mountableNode) fullName() string { 394 out := []string{} 395 cur := n 396 for cur != nil { 397 if cur.name != "" { 398 out = append([]string{cur.name}, out...) 399 } 400 cur = cur.parent 401 } 402 return joinPath(out) 403 } 404 405 func (n *mountableNode) findNode(path string) *mountableNode { 406 parts := splitPath(path) 407 cur := n 408 for _, p := range parts { 409 if next, ok := cur.nodes[p]; ok && next != nil { 410 cur = next 411 } else { 412 return nil 413 } 414 } 415 return cur 416 } 417 418 func (n *mountableNode) findPath(path string) (fs Fs, base, rel string) { 419 parts := splitPath(path) 420 421 var out Fs 422 outIdx := -1 423 out = n.fs 424 cur := n 425 for i, p := range parts { 426 if next, ok := cur.nodes[p]; ok { 427 cur = next 428 if cur.fs != nil { 429 out = cur.fs 430 outIdx = i 431 } 432 } else { 433 break 434 } 435 } 436 437 // afero is a bit fussy and unpredictable about leading slashes. 438 return out, 439 string(filepath.Separator) + filepath.Join(parts[:outIdx+1]...), 440 string(filepath.Separator) + filepath.Join(parts[outIdx+1:]...) 441 } 442 443 type mountableFile struct { 444 name string 445 file File 446 node *mountableNode 447 base string 448 } 449 450 func (m *mountableFile) Readdir(count int) (out []os.FileInfo, err error) { 451 if m.file != nil { 452 out, err = m.file.Readdir(count) 453 if err != nil { 454 return 455 } 456 } 457 if m.node != nil { 458 for _, node := range m.node.nodes { 459 var mdi *mountedDirInfo 460 mdi, err = mountedDirFromNode(node) 461 if err != nil { 462 return 463 } 464 out = append(out, mdi) 465 } 466 } 467 return 468 } 469 470 func (m *mountableFile) Readdirnames(n int) (out []string, err error) { 471 if m.file != nil { 472 out, err = m.file.Readdirnames(n) 473 if err != nil { 474 return 475 } 476 } 477 if m.node != nil { 478 for part := range m.node.nodes { 479 out = append(out, part) 480 } 481 } 482 return 483 } 484 485 func (m *mountableFile) Close() error { 486 if m.file != nil { 487 return m.file.Close() 488 } 489 return nil 490 } 491 492 func (m *mountableFile) Read(p []byte) (n int, err error) { 493 if m.file != nil { 494 return m.file.Read(p) 495 } 496 return 0, errNotAFile 497 } 498 499 func (m *mountableFile) ReadAt(p []byte, off int64) (n int, err error) { 500 if m.file != nil { 501 return m.file.ReadAt(p, off) 502 } 503 return 0, errNotAFile 504 } 505 506 func (m *mountableFile) Seek(offset int64, whence int) (int64, error) { 507 if m.file != nil { 508 return m.file.Seek(offset, whence) 509 } 510 return 0, errNotAFile 511 } 512 513 func (m *mountableFile) Write(p []byte) (n int, err error) { 514 if m.file != nil { 515 return m.file.Write(p) 516 } 517 return 0, errNotAFile 518 } 519 520 func (m *mountableFile) WriteAt(p []byte, off int64) (n int, err error) { 521 if m.file != nil { 522 return m.file.WriteAt(p, off) 523 } 524 return 0, errNotAFile 525 } 526 527 func (m *mountableFile) Name() string { return m.name } 528 529 func (m *mountableFile) Stat() (os.FileInfo, error) { 530 if m.file != nil { 531 return m.file.Stat() 532 } else { 533 if m.node != nil { 534 mdi, err := mountedDirFromNode(m.node) 535 if err != nil { 536 return nil, err 537 } 538 return mdi, nil 539 } 540 } 541 return nil, os.ErrNotExist 542 } 543 544 func (m *mountableFile) Sync() error { 545 if m.file != nil { 546 return m.file.Sync() 547 } 548 return errNotAFile 549 } 550 551 func (m *mountableFile) Truncate(size int64) error { 552 if m.file != nil { 553 return m.file.Truncate(size) 554 } 555 return errNotAFile 556 } 557 558 func (m *mountableFile) WriteString(s string) (ret int, err error) { 559 if m.file != nil { 560 return m.file.WriteString(s) 561 } 562 return 0, errNotAFile 563 } 564 565 type mountedDirInfo struct { 566 name string 567 mode os.FileMode 568 modTime time.Time 569 } 570 571 func (m *mountedDirInfo) Name() string { return m.name } 572 func (m *mountedDirInfo) Mode() os.FileMode { return m.mode | os.ModeDir } 573 func (m *mountedDirInfo) ModTime() time.Time { return m.modTime } 574 func (m *mountedDirInfo) IsDir() bool { return true } 575 func (m *mountedDirInfo) Sys() interface{} { return nil } 576 577 func (m *mountedDirInfo) Size() int64 { 578 // copied from afero, not sure why it's 42. 579 return int64(42) 580 } 581 582 func mountedDirFromNode(node *mountableNode) (*mountedDirInfo, error) { 583 if node.name == "" { 584 panic("missing name from node") 585 } 586 mdi := &mountedDirInfo{ 587 name: node.name, 588 mode: 0777, 589 modTime: node.modTime, 590 } 591 if node.fs != nil { 592 // dir should inherit stat info of mounted fs root node 593 info, err := node.fs.Stat("/") 594 if err != nil { 595 return nil, err 596 } 597 mdi.modTime = info.ModTime() 598 mdi.mode = info.Mode() 599 } 600 return mdi, nil 601 } 602 603 var ( 604 errCrossFsRename = errors.New("cross-fs rename") 605 errRecursiveMount = errors.New("recursive mount") 606 errShortCopy = errors.New("short copy") 607 errAlreadyMounted = errors.New("already mounted") 608 errNotMounted = errors.New("not mounted") 609 errNotAFile = errors.New("not a file") 610 errOsFs = errors.New("afero.OsFs should not be mounted - use afero.BasePathFs instead") 611 ) 612 613 func underlyingError(err error) error { 614 switch err := err.(type) { 615 case *os.PathError: 616 return err.Err 617 } 618 return err 619 } 620 621 func IsErrCrossFsRename(err error) bool { return underlyingError(err) == errCrossFsRename } 622 func IsErrRecursiveMount(err error) bool { return underlyingError(err) == errRecursiveMount } 623 func IsErrShortCopy(err error) bool { return underlyingError(err) == errShortCopy } 624 func IsErrAlreadyMounted(err error) bool { return underlyingError(err) == errAlreadyMounted } 625 func IsErrNotMounted(err error) bool { return underlyingError(err) == errNotMounted } 626 func IsErrNotAFile(err error) bool { return underlyingError(err) == errNotAFile } 627 func IsErrOsFs(err error) bool { return underlyingError(err) == errOsFs } 628 629 func splitPath(path string) []string { 630 in := strings.Trim(path, string(filepath.Separator)) 631 if in == "" { 632 return nil 633 } 634 return strings.Split(in, string(filepath.Separator)) 635 } 636 637 func joinPath(parts []string) string { 638 return string(filepath.Separator) + strings.Join(parts, string(filepath.Separator)) 639 } 640 641 func IsMountNode(info os.FileInfo) bool { 642 if _, ok := info.(*mountedDirInfo); ok { 643 return true 644 } 645 return false 646 } 647 648 // departWalk recursively descends path, calling walkFn. 649 // it calls walkFn on departure rather than arrival, allowing removal 650 // adapted from afero.walk 651 func departWalk(fs Fs, path string, info os.FileInfo, walkFn filepath.WalkFunc) error { 652 if info.IsDir() { 653 names, err := readDirNames(fs, path) 654 if err != nil { 655 return walkFn(path, info, err) 656 } 657 658 for _, name := range names { 659 filename := filepath.Join(path, name) 660 fileInfo, err := lstatIfPossible(fs, filename) 661 if err != nil { 662 if err := walkFn(filename, fileInfo, err); err != nil { 663 return err 664 } 665 } else { 666 err = departWalk(fs, filename, fileInfo, walkFn) 667 if err != nil { 668 return err 669 } 670 } 671 } 672 } 673 return walkFn(path, info, nil) 674 } 675 676 // if the filesystem supports it, use Lstat, else use fs.Stat 677 func lstatIfPossible(fs Fs, path string) (os.FileInfo, error) { 678 if lfs, ok := fs.(Lstater); ok { 679 fi, _, err := lfs.LstatIfPossible(path) 680 return fi, err 681 } 682 return fs.Stat(path) 683 } 684 685 // readDirNames reads the directory named by dirname and returns 686 // a sorted list of directory entries. 687 // adapted from https://golang.org/src/path/filepath/path.go 688 func readDirNames(fs Fs, dirname string) ([]string, error) { 689 f, err := fs.Open(dirname) 690 if err != nil { 691 return nil, err 692 } 693 names, err := f.Readdirnames(-1) 694 f.Close() 695 if err != nil { 696 return nil, err 697 } 698 sort.Strings(names) 699 return names, nil 700 }