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  }