21578: Add -log-level flag, log failed fuse calls at debug level.
[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         pprof := flags.String("pprof", "", "serve Go profile data at `[addr]:port`")
54         if ok, code := cmd.ParseFlags(flags, prog, args, "[FUSE mount options]", stderr); !ok {
55                 return code
56         }
57         if !*experimental {
58                 logger.Errorf("experimental command %q used without --experimental flag", prog)
59                 return 2
60         }
61         lvl, err := logrus.ParseLevel(*logLevel)
62         if err != nil {
63                 logger.WithError(err).Error("invalid argument for -log-level flag")
64                 return 2
65         }
66         logger.SetLevel(lvl)
67         if *pprof != "" {
68                 go func() {
69                         log.Println(http.ListenAndServe(*pprof, nil))
70                 }()
71         }
72
73         client := arvados.NewClientFromEnv()
74         if err := yaml.Unmarshal([]byte(*cacheSizeStr), &client.DiskCacheSize); err != nil {
75                 logger.Errorf("error parsing -cache-size argument: %s", err)
76                 return 2
77         }
78         ac, err := arvadosclient.New(client)
79         if err != nil {
80                 logger.Error(err)
81                 return 1
82         }
83         kc, err := keepclient.MakeKeepClient(ac)
84         if err != nil {
85                 logger.Error(err)
86                 return 1
87         }
88         host := fuse.NewFileSystemHost(&keepFS{
89                 Client:     client,
90                 KeepClient: kc,
91                 ReadOnly:   *ro,
92                 Uid:        os.Getuid(),
93                 Gid:        os.Getgid(),
94                 Logger:     logger,
95                 ready:      c.ready,
96         })
97         c.Unmount = host.Unmount
98
99         logger.WithField("mountargs", flags.Args()).Debug("mounting")
100         ok := host.Mount("", flags.Args())
101         if !ok {
102                 return 1
103         }
104         return 0
105 }