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