Merge branch '21909-r-sdk-3.0'
[arvados.git] / lib / mount / command.go
1 // Copyright (C) The Arvados Authors. All rights reserved.
2 //
3 // SPDX-License-Identifier: Apache-2.0
4
5 package mount
6
7 import (
8         "flag"
9         "io"
10         "log"
11         "net/http"
12
13         // pprof is only imported to register its HTTP handlers
14         _ "net/http/pprof"
15         "os"
16
17         "git.arvados.org/arvados.git/lib/cmd"
18         "git.arvados.org/arvados.git/sdk/go/arvados"
19         "git.arvados.org/arvados.git/sdk/go/arvadosclient"
20         "git.arvados.org/arvados.git/sdk/go/ctxlog"
21         "git.arvados.org/arvados.git/sdk/go/keepclient"
22         "github.com/arvados/cgofuse/fuse"
23         "github.com/ghodss/yaml"
24         "github.com/sirupsen/logrus"
25 )
26
27 var Command = &mountCommand{}
28
29 type mountCommand struct {
30         // ready, if non-nil, will be closed when the mount is
31         // initialized.  If ready is non-nil, it RunCommand() should
32         // not be called more than once, or when ready is already
33         // closed.  Only intended for testing.
34         ready chan struct{}
35         // It is safe to call Unmount only after ready has been
36         // closed.
37         Unmount func() (ok bool)
38 }
39
40 // RunCommand implements the subcommand "mount <path> [fuse options]".
41 //
42 // The "-d" fuse option (and perhaps other features) ignores the
43 // stderr argument and prints to os.Stderr instead.
44 func (c *mountCommand) RunCommand(prog string, args []string, stdin io.Reader, stdout, stderr io.Writer) int {
45         logger := ctxlog.New(stderr, "text", "info")
46         defer logger.Debug("exiting")
47
48         flags := flag.NewFlagSet(prog, flag.ContinueOnError)
49         ro := flags.Bool("ro", false, "read-only")
50         experimental := flags.Bool("experimental", false, "acknowledge this is an experimental command, and should not be used in production (required)")
51         cacheSizeStr := flags.String("cache-size", "0", "cache size as percent of home filesystem size (\"5%\") or size (\"10GiB\") or 0 for automatic")
52         logLevel := flags.String("log-level", "info", "logging level (debug, info, ...)")
53         debug := flags.Bool("debug", false, "alias for -log-level=debug")
54         pprof := flags.String("pprof", "", "serve Go profile data at `[addr]:port`")
55         if ok, code := cmd.ParseFlags(flags, prog, args, "[FUSE mount options]", stderr); !ok {
56                 return code
57         }
58         if !*experimental {
59                 logger.Errorf("experimental command %q used without --experimental flag", prog)
60                 return 2
61         }
62         lvl, err := logrus.ParseLevel(*logLevel)
63         if err != nil {
64                 logger.WithError(err).Error("invalid argument for -log-level flag")
65                 return 2
66         }
67         if *debug {
68                 lvl = logrus.DebugLevel
69         }
70         logger.SetLevel(lvl)
71         if *pprof != "" {
72                 go func() {
73                         log.Println(http.ListenAndServe(*pprof, nil))
74                 }()
75         }
76
77         client := arvados.NewClientFromEnv()
78         if err := yaml.Unmarshal([]byte(*cacheSizeStr), &client.DiskCacheSize); err != nil {
79                 logger.Errorf("error parsing -cache-size argument: %s", err)
80                 return 2
81         }
82         ac, err := arvadosclient.New(client)
83         if err != nil {
84                 logger.Error(err)
85                 return 1
86         }
87         kc, err := keepclient.MakeKeepClient(ac)
88         if err != nil {
89                 logger.Error(err)
90                 return 1
91         }
92         host := fuse.NewFileSystemHost(&keepFS{
93                 Client:     client,
94                 KeepClient: kc,
95                 ReadOnly:   *ro,
96                 Uid:        os.Getuid(),
97                 Gid:        os.Getgid(),
98                 Logger:     logger,
99                 ready:      c.ready,
100         })
101         c.Unmount = host.Unmount
102
103         logger.WithField("mountargs", flags.Args()).Debug("mounting")
104         ok := host.Mount("", flags.Args())
105         if !ok {
106                 return 1
107         }
108         return 0
109 }