github.com/hanwen/go-fuse@v1.0.0/unionfs/autounion.go (about)

     1  // Copyright 2016 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 unionfs
     6  
     7  import (
     8  	"fmt"
     9  	"io/ioutil"
    10  	"log"
    11  	"os"
    12  	"path/filepath"
    13  	"strings"
    14  	"sync"
    15  	"syscall"
    16  	"time"
    17  
    18  	"github.com/hanwen/go-fuse/fuse"
    19  	"github.com/hanwen/go-fuse/fuse/nodefs"
    20  	"github.com/hanwen/go-fuse/fuse/pathfs"
    21  )
    22  
    23  type knownFs struct {
    24  	unionFS pathfs.FileSystem
    25  	nodeFS  *pathfs.PathNodeFs
    26  }
    27  
    28  // Creates unions for all files under a given directory,
    29  // walking the tree and looking for directories D which have a
    30  // D/READONLY symlink.
    31  //
    32  // A union for A/B/C will placed under directory A-B-C.
    33  type autoUnionFs struct {
    34  	pathfs.FileSystem
    35  	debug bool
    36  
    37  	lock             sync.RWMutex
    38  	zombies          map[string]bool
    39  	knownFileSystems map[string]knownFs
    40  	nameRootMap      map[string]string
    41  	root             string
    42  
    43  	nodeFs  *pathfs.PathNodeFs
    44  	options *AutoUnionFsOptions
    45  }
    46  
    47  type AutoUnionFsOptions struct {
    48  	UnionFsOptions
    49  
    50  	nodefs.Options
    51  	pathfs.PathNodeFsOptions
    52  
    53  	// If set, run updateKnownFses() after mounting.
    54  	UpdateOnMount bool
    55  
    56  	// If set hides the _READONLY file.
    57  	HideReadonly bool
    58  
    59  	// Expose this version in /status/gounionfs_version
    60  	Version string
    61  }
    62  
    63  const (
    64  	_READONLY    = "READONLY"
    65  	_STATUS      = "status"
    66  	_CONFIG      = "config"
    67  	_DEBUG       = "debug"
    68  	_ROOT        = "root"
    69  	_VERSION     = "gounionfs_version"
    70  	_SCAN_CONFIG = ".scan_config"
    71  )
    72  
    73  func NewAutoUnionFs(directory string, options AutoUnionFsOptions) pathfs.FileSystem {
    74  	if options.HideReadonly {
    75  		options.HiddenFiles = append(options.HiddenFiles, _READONLY)
    76  	}
    77  	a := &autoUnionFs{
    78  		knownFileSystems: make(map[string]knownFs),
    79  		nameRootMap:      make(map[string]string),
    80  		zombies:          make(map[string]bool),
    81  		options:          &options,
    82  		FileSystem:       pathfs.NewDefaultFileSystem(),
    83  	}
    84  
    85  	directory, err := filepath.Abs(directory)
    86  	if err != nil {
    87  		panic("filepath.Abs returned err")
    88  	}
    89  	a.root = directory
    90  	return a
    91  }
    92  
    93  func (fs *autoUnionFs) String() string {
    94  	return fmt.Sprintf("autoUnionFs(%s)", fs.root)
    95  }
    96  
    97  func (fs *autoUnionFs) OnMount(nodeFs *pathfs.PathNodeFs) {
    98  	fs.nodeFs = nodeFs
    99  	if fs.options.UpdateOnMount {
   100  		time.AfterFunc(100*time.Millisecond, func() { fs.updateKnownFses() })
   101  	}
   102  }
   103  
   104  func (fs *autoUnionFs) addAutomaticFs(roots []string) {
   105  	relative := strings.TrimLeft(strings.Replace(roots[0], fs.root, "", -1), "/")
   106  	name := strings.Replace(relative, "/", "-", -1)
   107  
   108  	if fs.getUnionFs(name) == nil {
   109  		fs.addFs(name, roots)
   110  	}
   111  }
   112  
   113  func (fs *autoUnionFs) createFs(name string, roots []string) fuse.Status {
   114  	fs.lock.Lock()
   115  	defer fs.lock.Unlock()
   116  
   117  	if fs.zombies[name] {
   118  		log.Printf("filesystem named %q is being removed", name)
   119  		return fuse.EBUSY
   120  	}
   121  
   122  	for workspace, root := range fs.nameRootMap {
   123  		if root == roots[0] && workspace != name {
   124  			log.Printf("Already have a union FS for directory %s in workspace %s",
   125  				roots[0], workspace)
   126  			return fuse.EBUSY
   127  		}
   128  	}
   129  
   130  	known := fs.knownFileSystems[name]
   131  	if known.unionFS != nil {
   132  		log.Println("Already have a workspace:", name)
   133  		return fuse.EBUSY
   134  	}
   135  
   136  	ufs, err := NewUnionFsFromRoots(roots, &fs.options.UnionFsOptions, true)
   137  	if err != nil {
   138  		log.Println("Could not create UnionFs:", err)
   139  		return fuse.EPERM
   140  	}
   141  
   142  	log.Printf("Adding workspace %v for roots %v", name, ufs.String())
   143  	nfs := pathfs.NewPathNodeFs(ufs, &fs.options.PathNodeFsOptions)
   144  	code := fs.nodeFs.Mount(name, nfs.Root(), &fs.options.Options)
   145  	if code.Ok() {
   146  		fs.knownFileSystems[name] = knownFs{
   147  			ufs,
   148  			nfs,
   149  		}
   150  		fs.nameRootMap[name] = roots[0]
   151  	}
   152  	return code
   153  }
   154  
   155  func (fs *autoUnionFs) rmFs(name string) (code fuse.Status) {
   156  	fs.lock.Lock()
   157  	defer fs.lock.Unlock()
   158  
   159  	if fs.zombies[name] {
   160  		return fuse.ENOENT
   161  	}
   162  
   163  	known := fs.knownFileSystems[name]
   164  	if known.unionFS == nil {
   165  		return fuse.ENOENT
   166  	}
   167  
   168  	root := fs.nameRootMap[name]
   169  	delete(fs.knownFileSystems, name)
   170  	delete(fs.nameRootMap, name)
   171  	fs.zombies[name] = true
   172  	fs.lock.Unlock()
   173  	code = fs.nodeFs.Unmount(name)
   174  
   175  	fs.lock.Lock()
   176  	delete(fs.zombies, name)
   177  	if !code.Ok() {
   178  		// Reinstate.
   179  		log.Printf("Unmount failed for %s.  Code %v", name, code)
   180  		fs.knownFileSystems[name] = known
   181  		fs.nameRootMap[name] = root
   182  	}
   183  	return code
   184  }
   185  
   186  func (fs *autoUnionFs) addFs(name string, roots []string) (code fuse.Status) {
   187  	if name == _CONFIG || name == _STATUS || name == _SCAN_CONFIG {
   188  		return fuse.EINVAL
   189  	}
   190  	return fs.createFs(name, roots)
   191  }
   192  
   193  func (fs *autoUnionFs) getRoots(path string) []string {
   194  	ro := filepath.Join(path, _READONLY)
   195  	fi, err := os.Lstat(ro)
   196  	fiDir, errDir := os.Stat(ro)
   197  	if err != nil || errDir != nil {
   198  		return nil
   199  	}
   200  
   201  	if fi.Mode()&os.ModeSymlink != 0 && fiDir.IsDir() {
   202  		// TODO - should recurse and chain all READONLYs
   203  		// together.
   204  		return []string{path, ro}
   205  	}
   206  	return nil
   207  }
   208  
   209  func (fs *autoUnionFs) visit(path string, fi os.FileInfo, err error) error {
   210  	if fi != nil && fi.IsDir() {
   211  		roots := fs.getRoots(path)
   212  		if roots != nil {
   213  			fs.addAutomaticFs(roots)
   214  		}
   215  	}
   216  	return nil
   217  }
   218  
   219  func (fs *autoUnionFs) updateKnownFses() {
   220  	// We unroll the first level of entries in the root manually in order
   221  	// to allow symbolic links on that level.
   222  	directoryEntries, err := ioutil.ReadDir(fs.root)
   223  	if err == nil {
   224  		for _, dir := range directoryEntries {
   225  			if dir.IsDir() || dir.Mode()&os.ModeSymlink != 0 {
   226  				path := filepath.Join(fs.root, dir.Name())
   227  				dir, _ = os.Stat(path)
   228  				fs.visit(path, dir, nil)
   229  				filepath.Walk(path,
   230  					func(path string, fi os.FileInfo, err error) error {
   231  						return fs.visit(path, fi, err)
   232  					})
   233  			}
   234  		}
   235  	}
   236  }
   237  
   238  func (fs *autoUnionFs) Readlink(path string, context *fuse.Context) (out string, code fuse.Status) {
   239  	comps := strings.Split(path, string(filepath.Separator))
   240  	if comps[0] == _STATUS && comps[1] == _ROOT {
   241  		return fs.root, fuse.OK
   242  	}
   243  
   244  	if comps[0] != _CONFIG {
   245  		return "", fuse.ENOENT
   246  	}
   247  
   248  	name := comps[1]
   249  
   250  	fs.lock.RLock()
   251  	defer fs.lock.RUnlock()
   252  
   253  	root, ok := fs.nameRootMap[name]
   254  	if ok {
   255  		return root, fuse.OK
   256  	}
   257  	return "", fuse.ENOENT
   258  }
   259  
   260  func (fs *autoUnionFs) getUnionFs(name string) pathfs.FileSystem {
   261  	fs.lock.RLock()
   262  	defer fs.lock.RUnlock()
   263  	return fs.knownFileSystems[name].unionFS
   264  }
   265  
   266  func (fs *autoUnionFs) Symlink(pointedTo string, linkName string, context *fuse.Context) (code fuse.Status) {
   267  	comps := strings.Split(linkName, "/")
   268  	if len(comps) != 2 {
   269  		return fuse.EPERM
   270  	}
   271  
   272  	if comps[0] == _CONFIG {
   273  		roots := fs.getRoots(pointedTo)
   274  		if roots == nil {
   275  			return fuse.Status(syscall.ENOTDIR)
   276  		}
   277  
   278  		name := comps[1]
   279  		return fs.addFs(name, roots)
   280  	}
   281  	return fuse.EPERM
   282  }
   283  
   284  func (fs *autoUnionFs) Unlink(path string, context *fuse.Context) (code fuse.Status) {
   285  	comps := strings.Split(path, "/")
   286  	if len(comps) != 2 {
   287  		return fuse.EPERM
   288  	}
   289  
   290  	if comps[0] == _CONFIG && comps[1] != _SCAN_CONFIG {
   291  		code = fs.rmFs(comps[1])
   292  	} else {
   293  		code = fuse.ENOENT
   294  	}
   295  	return code
   296  }
   297  
   298  // Must define this, because ENOSYS will suspend all GetXAttr calls.
   299  func (fs *autoUnionFs) GetXAttr(name string, attr string, context *fuse.Context) ([]byte, fuse.Status) {
   300  	return nil, fuse.ENOATTR
   301  }
   302  
   303  func (fs *autoUnionFs) GetAttr(path string, context *fuse.Context) (*fuse.Attr, fuse.Status) {
   304  	a := &fuse.Attr{
   305  		Owner: *fuse.CurrentOwner(),
   306  	}
   307  	if path == "" || path == _CONFIG || path == _STATUS {
   308  		a.Mode = fuse.S_IFDIR | 0755
   309  		return a, fuse.OK
   310  	}
   311  
   312  	if path == filepath.Join(_STATUS, _VERSION) {
   313  		a.Mode = fuse.S_IFREG | 0644
   314  		a.Size = uint64(len(fs.options.Version))
   315  		return a, fuse.OK
   316  	}
   317  
   318  	if path == filepath.Join(_STATUS, _DEBUG) {
   319  		a.Mode = fuse.S_IFREG | 0644
   320  		a.Size = uint64(len(fs.DebugData()))
   321  		return a, fuse.OK
   322  	}
   323  
   324  	if path == filepath.Join(_STATUS, _ROOT) {
   325  		a.Mode = syscall.S_IFLNK | 0644
   326  		return a, fuse.OK
   327  	}
   328  
   329  	if path == filepath.Join(_CONFIG, _SCAN_CONFIG) {
   330  		a.Mode = fuse.S_IFREG | 0644
   331  		return a, fuse.OK
   332  	}
   333  	comps := strings.Split(path, string(filepath.Separator))
   334  
   335  	if len(comps) > 1 && comps[0] == _CONFIG {
   336  		fs := fs.getUnionFs(comps[1])
   337  
   338  		if fs == nil {
   339  			return nil, fuse.ENOENT
   340  		}
   341  
   342  		a.Mode = syscall.S_IFLNK | 0644
   343  		return a, fuse.OK
   344  	}
   345  
   346  	return nil, fuse.ENOENT
   347  }
   348  
   349  func (fs *autoUnionFs) StatusDir() (stream []fuse.DirEntry, status fuse.Status) {
   350  	stream = make([]fuse.DirEntry, 0, 10)
   351  	stream = []fuse.DirEntry{
   352  		{Name: _VERSION, Mode: fuse.S_IFREG | 0644},
   353  		{Name: _DEBUG, Mode: fuse.S_IFREG | 0644},
   354  		{Name: _ROOT, Mode: syscall.S_IFLNK | 0644},
   355  	}
   356  	return stream, fuse.OK
   357  }
   358  
   359  func (fs *autoUnionFs) DebugData() string {
   360  	conn := fs.nodeFs.Connector()
   361  	if conn.Server() == nil {
   362  		return "autoUnionFs.mountState not set"
   363  	}
   364  	setting := conn.Server().KernelSettings()
   365  	msg := fmt.Sprintf(
   366  		"Version: %v\n"+
   367  			"Bufferpool: %v\n"+
   368  			"Kernel: %v\n",
   369  		fs.options.Version,
   370  		conn.Server().DebugData(),
   371  		&setting)
   372  
   373  	if conn != nil {
   374  		msg += fmt.Sprintf("Live inodes: %d\n", conn.InodeHandleCount())
   375  	}
   376  
   377  	return msg
   378  }
   379  
   380  func (fs *autoUnionFs) Open(path string, flags uint32, context *fuse.Context) (nodefs.File, fuse.Status) {
   381  	if path == filepath.Join(_STATUS, _DEBUG) {
   382  		if flags&fuse.O_ANYWRITE != 0 {
   383  			return nil, fuse.EPERM
   384  		}
   385  
   386  		return nodefs.NewDataFile([]byte(fs.DebugData())), fuse.OK
   387  	}
   388  	if path == filepath.Join(_STATUS, _VERSION) {
   389  		if flags&fuse.O_ANYWRITE != 0 {
   390  			return nil, fuse.EPERM
   391  		}
   392  		return nodefs.NewDataFile([]byte(fs.options.Version)), fuse.OK
   393  	}
   394  	if path == filepath.Join(_CONFIG, _SCAN_CONFIG) {
   395  		if flags&fuse.O_ANYWRITE != 0 {
   396  			fs.updateKnownFses()
   397  		}
   398  		return nodefs.NewDevNullFile(), fuse.OK
   399  	}
   400  	return nil, fuse.ENOENT
   401  }
   402  
   403  func (fs *autoUnionFs) Truncate(name string, offset uint64, context *fuse.Context) (code fuse.Status) {
   404  	if name != filepath.Join(_CONFIG, _SCAN_CONFIG) {
   405  		log.Println("Huh? Truncating unsupported write file", name)
   406  		return fuse.EPERM
   407  	}
   408  	return fuse.OK
   409  }
   410  
   411  func (fs *autoUnionFs) OpenDir(name string, context *fuse.Context) (stream []fuse.DirEntry, status fuse.Status) {
   412  	switch name {
   413  	case _STATUS:
   414  		return fs.StatusDir()
   415  	case _CONFIG:
   416  	case "/":
   417  		name = ""
   418  	case "":
   419  	default:
   420  		log.Printf("Argh! Don't know how to list dir %v", name)
   421  		return nil, fuse.ENOSYS
   422  	}
   423  
   424  	fs.lock.RLock()
   425  	defer fs.lock.RUnlock()
   426  
   427  	stream = make([]fuse.DirEntry, 0, len(fs.knownFileSystems)+5)
   428  	if name == _CONFIG {
   429  		for k := range fs.knownFileSystems {
   430  			stream = append(stream, fuse.DirEntry{
   431  				Name: k,
   432  				Mode: syscall.S_IFLNK | 0644,
   433  			})
   434  		}
   435  	}
   436  
   437  	if name == "" {
   438  		stream = append(stream, fuse.DirEntry{
   439  			Name: _CONFIG,
   440  			Mode: uint32(fuse.S_IFDIR | 0755),
   441  		},
   442  			fuse.DirEntry{
   443  				Name: _STATUS,
   444  				Mode: uint32(fuse.S_IFDIR | 0755),
   445  			})
   446  	}
   447  	return stream, status
   448  }
   449  
   450  func (fs *autoUnionFs) StatFs(name string) *fuse.StatfsOut {
   451  	return &fuse.StatfsOut{}
   452  }