github.com/codingfuture/orig-energi3@v0.8.4/swarm/fuse/swarmfs_unix.go (about)

     1  // Copyright 2017 The go-ethereum Authors
     2  // This file is part of the go-ethereum library.
     3  //
     4  // The go-ethereum library is free software: you can redistribute it and/or modify
     5  // it under the terms of the GNU Lesser General Public License as published by
     6  // the Free Software Foundation, either version 3 of the License, or
     7  // (at your option) any later version.
     8  //
     9  // The go-ethereum library is distributed in the hope that it will be useful,
    10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    12  // GNU Lesser General Public License for more details.
    13  //
    14  // You should have received a copy of the GNU Lesser General Public License
    15  // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  // +build linux darwin freebsd
    18  
    19  package fuse
    20  
    21  import (
    22  	"context"
    23  	"errors"
    24  	"fmt"
    25  	"os"
    26  	"path/filepath"
    27  	"strings"
    28  	"sync"
    29  	"time"
    30  
    31  	"bazil.org/fuse"
    32  	"bazil.org/fuse/fs"
    33  	"github.com/ethereum/go-ethereum/common"
    34  	"github.com/ethereum/go-ethereum/swarm/api"
    35  	"github.com/ethereum/go-ethereum/swarm/log"
    36  )
    37  
    38  var (
    39  	errEmptyMountPoint      = errors.New("need non-empty mount point")
    40  	errNoRelativeMountPoint = errors.New("invalid path for mount point (need absolute path)")
    41  	errMaxMountCount        = errors.New("max FUSE mount count reached")
    42  	errMountTimeout         = errors.New("mount timeout")
    43  	errAlreadyMounted       = errors.New("mount point is already serving")
    44  )
    45  
    46  func isFUSEUnsupportedError(err error) bool {
    47  	if perr, ok := err.(*os.PathError); ok {
    48  		return perr.Op == "open" && perr.Path == "/dev/fuse"
    49  	}
    50  	return err == fuse.ErrOSXFUSENotFound
    51  }
    52  
    53  // MountInfo contains information about every active mount
    54  type MountInfo struct {
    55  	MountPoint     string
    56  	StartManifest  string
    57  	LatestManifest string
    58  	rootDir        *SwarmDir
    59  	fuseConnection *fuse.Conn
    60  	swarmApi       *api.API
    61  	lock           *sync.RWMutex
    62  	serveClose     chan struct{}
    63  }
    64  
    65  func NewMountInfo(mhash, mpoint string, sapi *api.API) *MountInfo {
    66  	log.Debug("swarmfs NewMountInfo", "hash", mhash, "mount point", mpoint)
    67  	newMountInfo := &MountInfo{
    68  		MountPoint:     mpoint,
    69  		StartManifest:  mhash,
    70  		LatestManifest: mhash,
    71  		rootDir:        nil,
    72  		fuseConnection: nil,
    73  		swarmApi:       sapi,
    74  		lock:           &sync.RWMutex{},
    75  		serveClose:     make(chan struct{}),
    76  	}
    77  	return newMountInfo
    78  }
    79  
    80  func (swarmfs *SwarmFS) Mount(mhash, mountpoint string) (*MountInfo, error) {
    81  	log.Info("swarmfs", "mounting hash", mhash, "mount point", mountpoint)
    82  	if mountpoint == "" {
    83  		return nil, errEmptyMountPoint
    84  	}
    85  	if !strings.HasPrefix(mountpoint, "/") {
    86  		return nil, errNoRelativeMountPoint
    87  	}
    88  	cleanedMountPoint, err := filepath.Abs(filepath.Clean(mountpoint))
    89  	if err != nil {
    90  		return nil, err
    91  	}
    92  	log.Trace("swarmfs mount", "cleanedMountPoint", cleanedMountPoint)
    93  
    94  	swarmfs.swarmFsLock.Lock()
    95  	defer swarmfs.swarmFsLock.Unlock()
    96  
    97  	noOfActiveMounts := len(swarmfs.activeMounts)
    98  	log.Debug("swarmfs mount", "# active mounts", noOfActiveMounts)
    99  	if noOfActiveMounts >= maxFuseMounts {
   100  		return nil, errMaxMountCount
   101  	}
   102  
   103  	if _, ok := swarmfs.activeMounts[cleanedMountPoint]; ok {
   104  		return nil, errAlreadyMounted
   105  	}
   106  
   107  	log.Trace("swarmfs mount: getting manifest tree")
   108  	_, manifestEntryMap, err := swarmfs.swarmApi.BuildDirectoryTree(context.TODO(), mhash, true)
   109  	if err != nil {
   110  		return nil, err
   111  	}
   112  
   113  	log.Trace("swarmfs mount: building mount info")
   114  	mi := NewMountInfo(mhash, cleanedMountPoint, swarmfs.swarmApi)
   115  
   116  	dirTree := map[string]*SwarmDir{}
   117  	rootDir := NewSwarmDir("/", mi)
   118  	log.Trace("swarmfs mount", "rootDir", rootDir)
   119  	mi.rootDir = rootDir
   120  
   121  	log.Trace("swarmfs mount: traversing manifest map")
   122  	for suffix, entry := range manifestEntryMap {
   123  		if suffix == "" { //empty suffix means that the file has no name - i.e. this is the default entry in a manifest. Since we cannot have files without a name, let us ignore this entry
   124  			log.Warn("Manifest has an empty-path (default) entry which will be ignored in FUSE mount.")
   125  			continue
   126  		}
   127  		addr := common.Hex2Bytes(entry.Hash)
   128  		fullpath := "/" + suffix
   129  		basepath := filepath.Dir(fullpath)
   130  		parentDir := rootDir
   131  		dirUntilNow := ""
   132  		paths := strings.Split(basepath, "/")
   133  		for i := range paths {
   134  			if paths[i] != "" {
   135  				thisDir := paths[i]
   136  				dirUntilNow = dirUntilNow + "/" + thisDir
   137  
   138  				if _, ok := dirTree[dirUntilNow]; !ok {
   139  					dirTree[dirUntilNow] = NewSwarmDir(dirUntilNow, mi)
   140  					parentDir.directories = append(parentDir.directories, dirTree[dirUntilNow])
   141  					parentDir = dirTree[dirUntilNow]
   142  
   143  				} else {
   144  					parentDir = dirTree[dirUntilNow]
   145  				}
   146  			}
   147  		}
   148  		thisFile := NewSwarmFile(basepath, filepath.Base(fullpath), mi)
   149  		thisFile.addr = addr
   150  
   151  		parentDir.files = append(parentDir.files, thisFile)
   152  	}
   153  
   154  	fconn, err := fuse.Mount(cleanedMountPoint, fuse.FSName("swarmfs"), fuse.VolumeName(mhash))
   155  	if isFUSEUnsupportedError(err) {
   156  		log.Error("swarmfs error - FUSE not installed", "mountpoint", cleanedMountPoint, "err", err)
   157  		return nil, err
   158  	} else if err != nil {
   159  		fuse.Unmount(cleanedMountPoint)
   160  		log.Error("swarmfs error mounting swarm manifest", "mountpoint", cleanedMountPoint, "err", err)
   161  		return nil, err
   162  	}
   163  	mi.fuseConnection = fconn
   164  
   165  	serverr := make(chan error, 1)
   166  	go func() {
   167  		log.Info("swarmfs", "serving hash", mhash, "at", cleanedMountPoint)
   168  		filesys := &SwarmRoot{root: rootDir}
   169  		//start serving the actual file system; see note below
   170  		if err := fs.Serve(fconn, filesys); err != nil {
   171  			log.Warn("swarmfs could not serve the requested hash", "error", err)
   172  			serverr <- err
   173  		}
   174  		mi.serveClose <- struct{}{}
   175  	}()
   176  
   177  	/*
   178  	   IMPORTANT NOTE: the fs.Serve function is blocking;
   179  	   Serve builds up the actual fuse file system by calling the
   180  	   Attr functions on each SwarmFile, creating the file inodes;
   181  	   specifically calling the swarm's LazySectionReader.Size() to set the file size.
   182  
   183  	   This can take some time, and it appears that if we access the fuse file system
   184  	   too early, we can bring the tests to deadlock. The assumption so far is that
   185  	   at this point, the fuse driver didn't finish to initialize the file system.
   186  
   187  	   Accessing files too early not only deadlocks the tests, but locks the access
   188  	   of the fuse file completely, resulting in blocked resources at OS system level.
   189  	   Even a simple `ls /tmp/testDir/testMountDir` could deadlock in a shell.
   190  
   191  	   Workaround so far is to wait some time to give the OS enough time to initialize
   192  	   the fuse file system. During tests, this seemed to address the issue.
   193  
   194  	   HOWEVER IT SHOULD BE NOTED THAT THIS MAY ONLY BE AN EFFECT,
   195  	   AND THE DEADLOCK CAUSED BY SOMETHING ELSE BLOCKING ACCESS DUE TO SOME RACE CONDITION
   196  	   (caused in the bazil.org library and/or the SwarmRoot, SwarmDir and SwarmFile implementations)
   197  	*/
   198  	time.Sleep(2 * time.Second)
   199  
   200  	timer := time.NewTimer(mountTimeout)
   201  	defer timer.Stop()
   202  	// Check if the mount process has an error to report.
   203  	select {
   204  	case <-timer.C:
   205  		log.Warn("swarmfs timed out mounting over FUSE", "mountpoint", cleanedMountPoint, "err", err)
   206  		err := fuse.Unmount(cleanedMountPoint)
   207  		if err != nil {
   208  			return nil, err
   209  		}
   210  		return nil, errMountTimeout
   211  	case err := <-serverr:
   212  		log.Warn("swarmfs error serving over FUSE", "mountpoint", cleanedMountPoint, "err", err)
   213  		err = fuse.Unmount(cleanedMountPoint)
   214  		return nil, err
   215  
   216  	case <-fconn.Ready:
   217  		//this signals that the actual mount point from the fuse.Mount call is ready;
   218  		//it does not signal though that the file system from fs.Serve is actually fully built up
   219  		if err := fconn.MountError; err != nil {
   220  			log.Error("Mounting error from fuse driver: ", "err", err)
   221  			return nil, err
   222  		}
   223  		log.Info("swarmfs now served over FUSE", "manifest", mhash, "mountpoint", cleanedMountPoint)
   224  	}
   225  
   226  	timer.Stop()
   227  	swarmfs.activeMounts[cleanedMountPoint] = mi
   228  	return mi, nil
   229  }
   230  
   231  func (swarmfs *SwarmFS) Unmount(mountpoint string) (*MountInfo, error) {
   232  	swarmfs.swarmFsLock.Lock()
   233  	defer swarmfs.swarmFsLock.Unlock()
   234  
   235  	cleanedMountPoint, err := filepath.Abs(filepath.Clean(mountpoint))
   236  	if err != nil {
   237  		return nil, err
   238  	}
   239  
   240  	mountInfo := swarmfs.activeMounts[cleanedMountPoint]
   241  
   242  	if mountInfo == nil || mountInfo.MountPoint != cleanedMountPoint {
   243  		return nil, fmt.Errorf("swarmfs %s is not mounted", cleanedMountPoint)
   244  	}
   245  	err = fuse.Unmount(cleanedMountPoint)
   246  	if err != nil {
   247  		err1 := externalUnmount(cleanedMountPoint)
   248  		if err1 != nil {
   249  			errStr := fmt.Sprintf("swarmfs unmount error: %v", err)
   250  			log.Warn(errStr)
   251  			return nil, err1
   252  		}
   253  	}
   254  
   255  	err = mountInfo.fuseConnection.Close()
   256  	if err != nil {
   257  		return nil, err
   258  	}
   259  	delete(swarmfs.activeMounts, cleanedMountPoint)
   260  
   261  	<-mountInfo.serveClose
   262  
   263  	succString := fmt.Sprintf("swarmfs unmounting %v succeeded", cleanedMountPoint)
   264  	log.Info(succString)
   265  
   266  	return mountInfo, nil
   267  }
   268  
   269  func (swarmfs *SwarmFS) Listmounts() []*MountInfo {
   270  	swarmfs.swarmFsLock.RLock()
   271  	defer swarmfs.swarmFsLock.RUnlock()
   272  	rows := make([]*MountInfo, 0, len(swarmfs.activeMounts))
   273  	for _, mi := range swarmfs.activeMounts {
   274  		rows = append(rows, mi)
   275  	}
   276  	return rows
   277  }
   278  
   279  func (swarmfs *SwarmFS) Stop() bool {
   280  	for mp := range swarmfs.activeMounts {
   281  		mountInfo := swarmfs.activeMounts[mp]
   282  		swarmfs.Unmount(mountInfo.MountPoint)
   283  	}
   284  	return true
   285  }