github.com/t2y/goofys@v0.19.1-0.20190123053037-27053313e616/main.go (about)

     1  // Copyright 2015 - 2017 Ka-Hing Cheung
     2  // Copyright 2015 - 2017 Google Inc. All Rights Reserved.
     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  //
     8  //     http://www.apache.org/licenses/LICENSE-2.0
     9  //
    10  // Unless required by applicable law or agreed to in writing, software
    11  // distributed under the License is distributed on an "AS IS" BASIS,
    12  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  // See the License for the specific language governing permissions and
    14  // limitations under the License.
    15  
    16  package main
    17  
    18  import (
    19  	goofys "github.com/kahing/goofys/api"
    20  	. "github.com/kahing/goofys/internal"
    21  
    22  	"fmt"
    23  	"os"
    24  	"os/signal"
    25  	"strings"
    26  	"sync"
    27  	"syscall"
    28  	"time"
    29  
    30  	"golang.org/x/net/context"
    31  
    32  	"github.com/jacobsa/fuse"
    33  	"github.com/jinzhu/copier"
    34  	"github.com/kardianos/osext"
    35  	"github.com/urfave/cli"
    36  
    37  	daemon "github.com/sevlyar/go-daemon"
    38  )
    39  
    40  var log = GetLogger("main")
    41  
    42  func registerSIGINTHandler(fs *Goofys, flags *FlagStorage) {
    43  	// Register for SIGINT.
    44  	signalChan := make(chan os.Signal, 1)
    45  	signal.Notify(signalChan, os.Interrupt, syscall.SIGTERM, syscall.SIGUSR1)
    46  
    47  	// Start a goroutine that will unmount when the signal is received.
    48  	go func() {
    49  		for {
    50  			s := <-signalChan
    51  			if s == syscall.SIGUSR1 {
    52  				log.Infof("Received %v", s)
    53  				fs.SigUsr1()
    54  				continue
    55  			}
    56  
    57  			if len(flags.Cache) == 0 {
    58  				log.Infof("Received %v, attempting to unmount...", s)
    59  
    60  				err := TryUnmount(flags.MountPoint)
    61  				if err != nil {
    62  					log.Errorf("Failed to unmount in response to %v: %v", s, err)
    63  				} else {
    64  					log.Printf("Successfully unmounted %v in response to %v",
    65  						flags.MountPoint, s)
    66  					return
    67  				}
    68  			} else {
    69  				log.Infof("Received %v", s)
    70  				// wait for catfs to die and cleanup
    71  			}
    72  		}
    73  	}()
    74  }
    75  
    76  var waitedForSignal os.Signal
    77  
    78  func waitForSignal(wg *sync.WaitGroup) {
    79  	signalChan := make(chan os.Signal, 1)
    80  	signal.Notify(signalChan, syscall.SIGUSR1, syscall.SIGUSR2)
    81  
    82  	wg.Add(1)
    83  	go func() {
    84  		waitedForSignal = <-signalChan
    85  		wg.Done()
    86  	}()
    87  }
    88  
    89  func kill(pid int, s os.Signal) (err error) {
    90  	p, err := os.FindProcess(pid)
    91  	if err != nil {
    92  		return err
    93  	}
    94  
    95  	defer p.Release()
    96  
    97  	err = p.Signal(s)
    98  	if err != nil {
    99  		return err
   100  	}
   101  	return
   102  }
   103  
   104  // Mount the file system based on the supplied arguments, returning a
   105  // fuse.MountedFileSystem that can be joined to wait for unmounting.
   106  func mount(
   107  	ctx context.Context,
   108  	bucketName string,
   109  	flags *FlagStorage) (fs *Goofys, mfs *fuse.MountedFileSystem, err error) {
   110  
   111  	// XXX really silly copy here! in goofys.Mount we will copy it
   112  	// back to FlagStorage. But I don't see a easier way to expose
   113  	// Config in the api package
   114  	var config goofys.Config
   115  	copier.Copy(&config, *flags)
   116  
   117  	return goofys.Mount(ctx, bucketName, &config)
   118  }
   119  
   120  func massagePath() {
   121  	for _, e := range os.Environ() {
   122  		if strings.HasPrefix(e, "PATH=") {
   123  			return
   124  		}
   125  	}
   126  
   127  	// mount -a seems to run goofys without PATH
   128  	// usually fusermount is in /bin
   129  	os.Setenv("PATH", "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin")
   130  }
   131  
   132  func massageArg0() {
   133  	var err error
   134  	os.Args[0], err = osext.Executable()
   135  	if err != nil {
   136  		panic(fmt.Sprintf("Unable to discover current executable: %v", err))
   137  	}
   138  }
   139  
   140  var Version = "use `make build' to fill version hash correctly"
   141  
   142  func main() {
   143  	VersionHash = Version
   144  
   145  	massagePath()
   146  
   147  	app := NewApp()
   148  
   149  	var flags *FlagStorage
   150  	var child *os.Process
   151  
   152  	app.Action = func(c *cli.Context) (err error) {
   153  		// We should get two arguments exactly. Otherwise error out.
   154  		if len(c.Args()) != 2 {
   155  			fmt.Fprintf(
   156  				os.Stderr,
   157  				"Error: %s takes exactly two arguments.\n\n",
   158  				app.Name)
   159  			cli.ShowAppHelp(c)
   160  			os.Exit(1)
   161  		}
   162  
   163  		// Populate and parse flags.
   164  		bucketName := c.Args()[0]
   165  		flags = PopulateFlags(c)
   166  		if flags == nil {
   167  			cli.ShowAppHelp(c)
   168  			err = fmt.Errorf("invalid arguments")
   169  			return
   170  		}
   171  		defer func() {
   172  			time.Sleep(time.Second)
   173  			flags.Cleanup()
   174  		}()
   175  
   176  		if !flags.Foreground {
   177  			var wg sync.WaitGroup
   178  			waitForSignal(&wg)
   179  
   180  			massageArg0()
   181  
   182  			ctx := new(daemon.Context)
   183  			child, err = ctx.Reborn()
   184  
   185  			if err != nil {
   186  				panic(fmt.Sprintf("unable to daemonize: %v", err))
   187  			}
   188  
   189  			InitLoggers(!flags.Foreground && child == nil)
   190  
   191  			if child != nil {
   192  				// attempt to wait for child to notify parent
   193  				wg.Wait()
   194  				if waitedForSignal == syscall.SIGUSR1 {
   195  					return
   196  				} else {
   197  					return fuse.EINVAL
   198  				}
   199  			} else {
   200  				// kill our own waiting goroutine
   201  				kill(os.Getpid(), syscall.SIGUSR1)
   202  				wg.Wait()
   203  				defer ctx.Release()
   204  			}
   205  
   206  		} else {
   207  			InitLoggers(!flags.Foreground)
   208  		}
   209  
   210  		// Mount the file system.
   211  		var mfs *fuse.MountedFileSystem
   212  		var fs *Goofys
   213  		fs, mfs, err = mount(
   214  			context.Background(),
   215  			bucketName,
   216  			flags)
   217  
   218  		if err != nil {
   219  			if !flags.Foreground {
   220  				kill(os.Getppid(), syscall.SIGUSR2)
   221  			}
   222  			log.Fatalf("Mounting file system: %v", err)
   223  			// fatal also terminates itself
   224  		} else {
   225  			if !flags.Foreground {
   226  				kill(os.Getppid(), syscall.SIGUSR1)
   227  			}
   228  			log.Println("File system has been successfully mounted.")
   229  			// Let the user unmount with Ctrl-C
   230  			// (SIGINT). But if cache is on, catfs will
   231  			// receive the signal and we would detect that exiting
   232  			registerSIGINTHandler(fs, flags)
   233  
   234  			// Wait for the file system to be unmounted.
   235  			err = mfs.Join(context.Background())
   236  			if err != nil {
   237  				err = fmt.Errorf("MountedFileSystem.Join: %v", err)
   238  				return
   239  			}
   240  
   241  			log.Println("Successfully exiting.")
   242  		}
   243  		return
   244  	}
   245  
   246  	err := app.Run(MassageMountFlags(os.Args))
   247  	if err != nil {
   248  		if flags != nil && !flags.Foreground && child != nil {
   249  			log.Fatalln("Unable to mount file system, see syslog for details")
   250  		}
   251  		os.Exit(1)
   252  	}
   253  }