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