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  }