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 }