20318: Merge branch 'main' into 20318-disk-cache
[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/keepclient"
21         "github.com/arvados/cgofuse/fuse"
22 )
23
24 var Command = &mountCommand{}
25
26 type mountCommand struct {
27         // ready, if non-nil, will be closed when the mount is
28         // initialized.  If ready is non-nil, it RunCommand() should
29         // not be called more than once, or when ready is already
30         // closed.
31         ready chan struct{}
32         // It is safe to call Unmount only after ready has been
33         // closed.
34         Unmount func() (ok bool)
35 }
36
37 // RunCommand implements the subcommand "mount <path> [fuse options]".
38 //
39 // The "-d" fuse option (and perhaps other features) ignores the
40 // stderr argument and prints to os.Stderr instead.
41 func (c *mountCommand) RunCommand(prog string, args []string, stdin io.Reader, stdout, stderr io.Writer) int {
42         logger := log.New(stderr, prog+" ", 0)
43         flags := flag.NewFlagSet(prog, flag.ContinueOnError)
44         ro := flags.Bool("ro", false, "read-only")
45         experimental := flags.Bool("experimental", false, "acknowledge this is an experimental command, and should not be used in production (required)")
46         pprof := flags.String("pprof", "", "serve Go profile data at `[addr]:port`")
47         if ok, code := cmd.ParseFlags(flags, prog, args, "[FUSE mount options]", stderr); !ok {
48                 return code
49         }
50         if !*experimental {
51                 logger.Printf("error: experimental command %q used without --experimental flag", prog)
52                 return 2
53         }
54         if *pprof != "" {
55                 go func() {
56                         log.Println(http.ListenAndServe(*pprof, nil))
57                 }()
58         }
59
60         client := arvados.NewClientFromEnv()
61         ac, err := arvadosclient.New(client)
62         if err != nil {
63                 logger.Print(err)
64                 return 1
65         }
66         kc, err := keepclient.MakeKeepClient(ac)
67         if err != nil {
68                 logger.Print(err)
69                 return 1
70         }
71         host := fuse.NewFileSystemHost(&keepFS{
72                 Client:     client,
73                 KeepClient: kc,
74                 ReadOnly:   *ro,
75                 Uid:        os.Getuid(),
76                 Gid:        os.Getgid(),
77                 ready:      c.ready,
78         })
79         c.Unmount = host.Unmount
80         ok := host.Mount("", flags.Args())
81         if !ok {
82                 return 1
83         }
84         return 0
85 }