github.com/grailbio/base@v0.0.11/cmd/grail-fuse/gfs/main.go (about) 1 package gfs 2 3 import ( 4 "context" 5 "fmt" 6 "os" 7 "os/exec" 8 "path/filepath" 9 "strings" 10 "syscall" 11 "time" 12 13 "github.com/grailbio/base/log" 14 "github.com/hanwen/go-fuse/v2/fs" 15 "github.com/hanwen/go-fuse/v2/fuse" 16 ) 17 18 const daemonEnv = "_GFS_DAEMON" 19 20 func logSuffix() string { 21 return time.Now().Format(time.RFC3339) + ".log" 22 } 23 24 // Main starts the FUSE server. It arranges so that contents of remoteRootDir 25 // can be accessed through mountDir. Arg remoteRootDir is typically 26 // "s3://". mountDir must be a directory in the local file system. 27 // 28 // If daemon==true, this function will fork itself to become a background 29 // process, and this process will exit. Otherwise this function blocks until the 30 // filesystem is unmounted by a superuser running "umount <dir>". 31 // 32 // Arg tmpDir specifies the directory to store temporary files. If tmpDir=="", 33 // it is set to /tmp/gfs-cache-<uid>. 34 // 35 // logDir specifies the directory for storing log files. If "", log messages are 36 // sent to stderr. 37 func Main(ctx context.Context, remoteRootDir, mountDir string, daemon bool, tmpDir, logDir string) { 38 if daemon { 39 daemonize(logDir) 40 // daemonize will exit the parent process if daemon==true 41 } else if logDir != "" { 42 path := filepath.Join(logDir, "gfs."+logSuffix()) 43 fd, err := os.OpenFile(path, syscall.O_CREAT|syscall.O_WRONLY|syscall.O_APPEND, 0600) 44 if err != nil { 45 log.Panicf("create %s: %v", path, err) 46 } 47 log.Printf("Storing log files in %s", path) 48 log.SetOutput(fd) 49 } 50 if err := os.MkdirAll(mountDir, 0700); err != nil { 51 log.Panicf("mkdir %s: %v", mountDir, err) 52 } 53 root := NewRoot(ctx, remoteRootDir, tmpDir) 54 server, err := fs.Mount(mountDir, root, &fs.Options{ 55 MountOptions: fuse.MountOptions{ 56 FsName: "grail", 57 DisableXAttrs: true, 58 Debug: log.At(log.Debug), 59 }, 60 }) 61 if err != nil { 62 log.Panicf("mount %s: %v", mountDir, err) 63 } 64 server.Wait() 65 } 66 67 func daemonize(logDir string) { 68 if os.Getenv(daemonEnv) == "" { 69 suffix := logSuffix() 70 if logDir == "" { 71 logDir = "/tmp" 72 log.Printf("Storing log files in %s/gfs-*%s", logDir, suffix) 73 } 74 if logDir == "" { 75 log.Panic("-log-dir must set with -daemon") 76 } 77 stdinFd, err := os.Open("/dev/null") 78 if err != nil { 79 log.Panic(err) 80 } 81 stdoutFd, err := os.Create(filepath.Join(logDir, "gfs-stdout."+suffix)) 82 if err != nil { 83 log.Panic(err) 84 } 85 stderrFd, err := os.Create(filepath.Join(logDir, "gfs-stderr."+suffix)) 86 if err != nil { 87 log.Panic(err) 88 } 89 os.Stdout.Sync() 90 os.Stderr.Sync() 91 cmd := exec.Command(os.Args[0], os.Args[1:]...) 92 cmd.Stdout = stdoutFd 93 cmd.Stderr = stderrFd 94 cmd.Stdin = stdinFd 95 cmd.Env = append([]string(nil), os.Environ()...) 96 cmd.Env = append(cmd.Env, daemonEnv+"=1") 97 cmd.Start() 98 os.Exit(0) 99 } 100 } 101 102 func NewRoot(ctx context.Context, remoteRootDir, tmpDir string) fs.InodeEmbedder { 103 if tmpDir == "" { 104 tmpDir = fmt.Sprintf("/tmp/gfscache-%d", os.Geteuid()) 105 } 106 if !strings.HasSuffix(remoteRootDir, "/") { 107 // getFileName misbehaves otherwise. 108 remoteRootDir += "/" 109 } 110 if err := os.MkdirAll(tmpDir, 0700); err != nil { 111 log.Panic(err) 112 } 113 ent := fuse.DirEntry{ 114 Name: "/", 115 Ino: getIno(""), 116 Mode: getModeBits(true)} 117 return &rootInode{inode: inode{path: remoteRootDir, ent: ent}, ctx: ctx, tmpDir: tmpDir} 118 }