github.com/adrianjagielak/goofys@v0.24.1-0.20230810095418-94919a5d2254/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  	VersionNumber = "0.24.0"
   138  	VersionHash = Version
   139  
   140  	massagePath()
   141  
   142  	app := NewApp()
   143  
   144  	var flags *FlagStorage
   145  	var child *os.Process
   146  
   147  	app.Action = func(c *cli.Context) (err error) {
   148  		// We should get two arguments exactly. Otherwise error out.
   149  		if len(c.Args()) != 2 {
   150  			fmt.Fprintf(
   151  				os.Stderr,
   152  				"Error: %s takes exactly two arguments.\n\n",
   153  				app.Name)
   154  			cli.ShowAppHelp(c)
   155  			os.Exit(1)
   156  		}
   157  
   158  		// Populate and parse flags.
   159  		bucketName := c.Args()[0]
   160  		flags = PopulateFlags(c)
   161  		if flags == nil {
   162  			cli.ShowAppHelp(c)
   163  			err = fmt.Errorf("invalid arguments")
   164  			return
   165  		}
   166  		defer func() {
   167  			time.Sleep(time.Second)
   168  			flags.Cleanup()
   169  		}()
   170  
   171  		if !flags.Foreground {
   172  			var wg sync.WaitGroup
   173  			waitForSignal(&wg)
   174  
   175  			massageArg0()
   176  
   177  			ctx := new(daemon.Context)
   178  			child, err = ctx.Reborn()
   179  
   180  			if err != nil {
   181  				panic(fmt.Sprintf("unable to daemonize: %v", err))
   182  			}
   183  
   184  			InitLoggers(!flags.Foreground && child == nil)
   185  
   186  			if child != nil {
   187  				// attempt to wait for child to notify parent
   188  				wg.Wait()
   189  				if waitedForSignal == syscall.SIGUSR1 {
   190  					return
   191  				} else {
   192  					return fuse.EINVAL
   193  				}
   194  			} else {
   195  				// kill our own waiting goroutine
   196  				kill(os.Getpid(), syscall.SIGUSR1)
   197  				wg.Wait()
   198  				defer ctx.Release()
   199  			}
   200  
   201  		} else {
   202  			InitLoggers(!flags.Foreground)
   203  		}
   204  
   205  		// Mount the file system.
   206  		var mfs *fuse.MountedFileSystem
   207  		var fs *Goofys
   208  		fs, mfs, err = mount(
   209  			context.Background(),
   210  			bucketName,
   211  			flags)
   212  
   213  		if err != nil {
   214  			if !flags.Foreground {
   215  				kill(os.Getppid(), syscall.SIGUSR2)
   216  			}
   217  			log.Fatalf("Mounting file system: %v", err)
   218  			// fatal also terminates itself
   219  		} else {
   220  			if !flags.Foreground {
   221  				kill(os.Getppid(), syscall.SIGUSR1)
   222  			}
   223  			log.Println("File system has been successfully mounted.")
   224  			// Let the user unmount with Ctrl-C
   225  			// (SIGINT). But if cache is on, catfs will
   226  			// receive the signal and we would detect that exiting
   227  			registerSIGINTHandler(fs, flags)
   228  
   229  			// Wait for the file system to be unmounted.
   230  			err = mfs.Join(context.Background())
   231  			if err != nil {
   232  				err = fmt.Errorf("MountedFileSystem.Join: %v", err)
   233  				return
   234  			}
   235  
   236  			log.Println("Successfully exiting.")
   237  		}
   238  		return
   239  	}
   240  
   241  	err := app.Run(MassageMountFlags(os.Args))
   242  	if err != nil {
   243  		if flags != nil && !flags.Foreground && child != nil {
   244  			log.Fatalln("Unable to mount file system, see syslog for details")
   245  		}
   246  		os.Exit(1)
   247  	}
   248  }