github.com/gaukas/goofys100m@v0.24.0/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/api/common"
    21  	. "github.com/kahing/goofys/internal"
    22  
    23  	"fmt"
    24  	"os"
    25  	"os/signal"
    26  	"strings"
    27  	"sync"
    28  	"syscall"
    29  	"time"
    30  
    31  	"context"
    32  
    33  	"github.com/jacobsa/fuse"
    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  	return goofys.Mount(ctx, bucketName, flags)
   112  }
   113  
   114  func massagePath() {
   115  	for _, e := range os.Environ() {
   116  		if strings.HasPrefix(e, "PATH=") {
   117  			return
   118  		}
   119  	}
   120  
   121  	// mount -a seems to run goofys without PATH
   122  	// usually fusermount is in /bin
   123  	os.Setenv("PATH", "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin")
   124  }
   125  
   126  func massageArg0() {
   127  	var err error
   128  	os.Args[0], err = osext.Executable()
   129  	if err != nil {
   130  		panic(fmt.Sprintf("Unable to discover current executable: %v", err))
   131  	}
   132  }
   133  
   134  var Version = "use `make build' to fill version hash correctly"
   135  
   136  func main() {
   137  	VersionHash = Version
   138  
   139  	massagePath()
   140  
   141  	app := NewApp()
   142  
   143  	var flags *FlagStorage
   144  	var child *os.Process
   145  
   146  	app.Action = func(c *cli.Context) (err error) {
   147  		// We should get two arguments exactly. Otherwise error out.
   148  		if len(c.Args()) != 2 {
   149  			fmt.Fprintf(
   150  				os.Stderr,
   151  				"Error: %s takes exactly two arguments.\n\n",
   152  				app.Name)
   153  			cli.ShowAppHelp(c)
   154  			os.Exit(1)
   155  		}
   156  
   157  		// Populate and parse flags.
   158  		bucketName := c.Args()[0]
   159  		flags = PopulateFlags(c)
   160  		if flags == nil {
   161  			cli.ShowAppHelp(c)
   162  			err = fmt.Errorf("invalid arguments")
   163  			return
   164  		}
   165  		defer func() {
   166  			time.Sleep(time.Second)
   167  			flags.Cleanup()
   168  		}()
   169  
   170  		if !flags.Foreground {
   171  			var wg sync.WaitGroup
   172  			waitForSignal(&wg)
   173  
   174  			massageArg0()
   175  
   176  			ctx := new(daemon.Context)
   177  			child, err = ctx.Reborn()
   178  
   179  			if err != nil {
   180  				panic(fmt.Sprintf("unable to daemonize: %v", err))
   181  			}
   182  
   183  			InitLoggers(!flags.Foreground && child == nil)
   184  
   185  			if child != nil {
   186  				// attempt to wait for child to notify parent
   187  				wg.Wait()
   188  				if waitedForSignal == syscall.SIGUSR1 {
   189  					return
   190  				} else {
   191  					return fuse.EINVAL
   192  				}
   193  			} else {
   194  				// kill our own waiting goroutine
   195  				kill(os.Getpid(), syscall.SIGUSR1)
   196  				wg.Wait()
   197  				defer ctx.Release()
   198  			}
   199  
   200  		} else {
   201  			InitLoggers(!flags.Foreground)
   202  		}
   203  
   204  		// Mount the file system.
   205  		var mfs *fuse.MountedFileSystem
   206  		var fs *Goofys
   207  		fs, mfs, err = mount(
   208  			context.Background(),
   209  			bucketName,
   210  			flags)
   211  
   212  		if err != nil {
   213  			if !flags.Foreground {
   214  				kill(os.Getppid(), syscall.SIGUSR2)
   215  			}
   216  			log.Fatalf("Mounting file system: %v", err)
   217  			// fatal also terminates itself
   218  		} else {
   219  			if !flags.Foreground {
   220  				kill(os.Getppid(), syscall.SIGUSR1)
   221  			}
   222  			log.Println("File system has been successfully mounted.")
   223  			// Let the user unmount with Ctrl-C
   224  			// (SIGINT). But if cache is on, catfs will
   225  			// receive the signal and we would detect that exiting
   226  			registerSIGINTHandler(fs, flags)
   227  
   228  			// Wait for the file system to be unmounted.
   229  			err = mfs.Join(context.Background())
   230  			if err != nil {
   231  				err = fmt.Errorf("MountedFileSystem.Join: %v", err)
   232  				return
   233  			}
   234  
   235  			log.Println("Successfully exiting.")
   236  		}
   237  		return
   238  	}
   239  
   240  	err := app.Run(MassageMountFlags(os.Args))
   241  	if err != nil {
   242  		if flags != nil && !flags.Foreground && child != nil {
   243  			log.Fatalln("Unable to mount file system, see syslog for details")
   244  		}
   245  		os.Exit(1)
   246  	}
   247  }