Skip to content

Commit d8d7856

Browse files
committed
commands: (un)mount - add support for nofuse and noipfs build tags
1 parent 650e23f commit d8d7856

File tree

7 files changed

+288
-130
lines changed

7 files changed

+288
-130
lines changed

internal/commands/mount.go

Lines changed: 70 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,13 @@ import (
77
"flag"
88
"fmt"
99
"io/fs"
10+
"slices"
1011
"strings"
1112

1213
"github.com/djdv/go-filesystem-utils/internal/command"
1314
"github.com/djdv/go-filesystem-utils/internal/filesystem"
1415
p9fs "github.com/djdv/go-filesystem-utils/internal/filesystem/9p"
15-
"github.com/djdv/go-filesystem-utils/internal/filesystem/cgofuse"
16-
"github.com/djdv/go-filesystem-utils/internal/filesystem/ipfs"
16+
"github.com/djdv/go-filesystem-utils/internal/generic"
1717
"github.com/djdv/p9/p9"
1818
"github.com/jaevor/go-nanoid"
1919
)
@@ -31,7 +31,10 @@ type (
3131
mountCmdConstraint[T, M]
3232
usage(filesystem.ID) string
3333
}
34-
mountCmdGuest[T any, M marshaller] interface {
34+
mountCmdGuest[
35+
T any,
36+
M marshaller,
37+
] interface {
3538
mountCmdConstraint[T, M]
3639
usage(filesystem.Host) string
3740
}
@@ -96,21 +99,6 @@ func WithGID(gid p9.GID) MountOption {
9699
}
97100
}
98101

99-
func supportedHosts() []filesystem.Host {
100-
return []filesystem.Host{
101-
cgofuse.HostID,
102-
}
103-
}
104-
105-
func supportedSystems() []filesystem.ID {
106-
return []filesystem.ID{
107-
ipfs.IPFSID,
108-
ipfs.PinFSID,
109-
ipfs.IPNSID,
110-
ipfs.KeyFSID,
111-
}
112-
}
113-
114102
func (mo *mountCmdOptions[HT, GT, HM, GM, HC, GC]) BindFlags(flagSet *flag.FlagSet) {
115103
type cmdSettings = mountCmdSettings[HM, GM]
116104
var clientOptions clientOptions
@@ -247,33 +235,74 @@ func Mount() command.Command {
247235
name = "mount"
248236
synopsis = "Mount file systems."
249237
)
250-
return command.SubcommandGroup(name, synopsis, makeMountSubcommands())
238+
if subcommands := makeMountSubcommands(); len(subcommands) != 0 {
239+
return command.SubcommandGroup(name, synopsis, makeMountSubcommands())
240+
}
241+
const usage = "No mount host APIs were built into this executable."
242+
return command.MakeNiladicCommand(
243+
name, synopsis, usage,
244+
func(ctx context.Context) error {
245+
return command.UsageError{
246+
Err: generic.ConstError("no host systems"),
247+
}
248+
},
249+
)
251250
}
252251

253252
func makeMountSubcommands() []command.Command {
253+
hosts := makeHostCommands()
254+
sortCommands(hosts)
255+
return hosts
256+
}
257+
258+
func sortCommands(commands []command.Command) {
259+
slices.SortFunc(
260+
commands,
261+
func(a, b command.Command) int {
262+
return strings.Compare(
263+
a.Name(),
264+
b.Name(),
265+
)
266+
},
267+
)
268+
}
269+
270+
func makeMountSubcommand(host filesystem.Host, guestCommands []command.Command) command.Command {
254271
var (
255-
hostTable = supportedHosts()
256-
subCommands = make([]command.Command, len(hostTable))
272+
formalName = string(host)
273+
commandName = strings.ToLower(formalName)
274+
synopsis = fmt.Sprintf("Mount a file system via the %s API.", formalName)
257275
)
258-
for i, hostAPI := range hostTable {
259-
var (
260-
formalName = string(hostAPI)
261-
commandName = strings.ToLower(formalName)
262-
synopsis = fmt.Sprintf("Mount a file system via the %s API.", formalName)
263-
)
264-
switch hostAPI {
265-
case cgofuse.HostID:
266-
guestCommands := makeGuestCommands[fuseOptions, fuseSettings](hostAPI)
267-
subCommands[i] = command.SubcommandGroup(
268-
commandName, synopsis,
269-
guestCommands,
270-
)
271-
default:
272-
err := fmt.Errorf("unexpected Host API: %v", hostAPI)
273-
panic(err)
276+
if len(guestCommands) > 0 {
277+
return command.SubcommandGroup(commandName, synopsis, guestCommands)
278+
}
279+
const usage = "No mount guest APIs were built into this executable."
280+
return command.MakeNiladicCommand(
281+
commandName, synopsis, usage,
282+
func(ctx context.Context) error {
283+
return command.UsageError{
284+
Err: generic.ConstError("no guest systems"),
285+
}
286+
},
287+
)
288+
}
289+
290+
func makeHostCommands() []command.Command {
291+
type makeCommand func() command.Command
292+
var (
293+
commandMakers = []makeCommand{
294+
makeFUSECommand,
295+
}
296+
commands = make([]command.Command, 0, len(commandMakers))
297+
)
298+
for _, makeCommand := range commandMakers {
299+
// Commands can be nil if system
300+
// is disabled by build constraints.
301+
if command := makeCommand(); command != nil {
302+
commands = append(commands, command)
274303
}
275304
}
276-
return subCommands
305+
return commands
277306
}
278307

279308
func makeGuestCommands[
@@ -282,25 +311,9 @@ func makeGuestCommands[
282311
HC mountCmdHost[HT, HM],
283312
](host filesystem.Host,
284313
) []command.Command {
285-
var (
286-
fsidTable = supportedSystems()
287-
subcommands = make([]command.Command, len(fsidTable))
288-
)
289-
for i, fsid := range fsidTable {
290-
switch fsid {
291-
case ipfs.IPFSID:
292-
subcommands[i] = makeMountCommand[HC, HM, ipfsOptions, ipfsSettings](host, fsid)
293-
case ipfs.PinFSID:
294-
subcommands[i] = makeMountCommand[HC, HM, pinFSOptions, pinFSSettings](host, fsid)
295-
case ipfs.IPNSID:
296-
subcommands[i] = makeMountCommand[HC, HM, ipnsOptions, ipnsSettings](host, fsid)
297-
case ipfs.KeyFSID:
298-
subcommands[i] = makeMountCommand[HC, HM, keyFSOptions, keyFSSettings](host, fsid)
299-
default:
300-
panic("unexpected API ID for host file system interface")
301-
}
302-
}
303-
return subcommands
314+
guests := makeIPFSCommands[HC, HM](host)
315+
sortCommands(guests)
316+
return guests
304317
}
305318

306319
func makeMountCommand[

internal/commands/mountpoint.go

Lines changed: 77 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,6 @@ import (
99

1010
"github.com/djdv/go-filesystem-utils/internal/filesystem"
1111
p9fs "github.com/djdv/go-filesystem-utils/internal/filesystem/9p"
12-
"github.com/djdv/go-filesystem-utils/internal/filesystem/cgofuse"
13-
"github.com/djdv/go-filesystem-utils/internal/filesystem/ipfs"
1412
"github.com/djdv/go-filesystem-utils/internal/generic"
1513
"github.com/djdv/p9/p9"
1614
)
@@ -34,61 +32,23 @@ type (
3432
Host HT `json:"host"`
3533
Guest GT `json:"guest"`
3634
}
35+
mountPointHosts map[filesystem.Host]p9fs.MakeGuestFunc
36+
mountPointGuests map[filesystem.ID]p9fs.MakeMountPointFunc
3737
)
3838

39-
func newGuestFunc[HC mountPointHost[T], T any](path ninePath, autoUnlink bool) p9fs.MakeGuestFunc {
40-
return func(parent p9.File, guest filesystem.ID, mode p9.FileMode, uid p9.UID, gid p9.GID) (p9.QID, p9.File, error) {
41-
permissions, err := mountsDirCreatePreamble(mode)
42-
if err != nil {
43-
return p9.QID{}, nil, err
44-
}
45-
var makeMountPointFn p9fs.MakeMountPointFunc
46-
// TODO: share IPFS instances
47-
// when server API is the same
48-
// (needs some wrapper too so
49-
// Close works properly.)
50-
switch guest {
51-
case ipfs.IPFSID:
52-
makeMountPointFn = newMountPointFunc[HC, *ipfs.IPFSGuest](path)
53-
case ipfs.PinFSID:
54-
makeMountPointFn = newMountPointFunc[HC, *ipfs.PinFSGuest](path)
55-
case ipfs.IPNSID:
56-
makeMountPointFn = newMountPointFunc[HC, *ipfs.IPNSGuest](path)
57-
case ipfs.KeyFSID:
58-
makeMountPointFn = newMountPointFunc[HC, *ipfs.KeyFSGuest](path)
59-
default:
60-
err := fmt.Errorf(`unexpected guest "%v"`, guest)
61-
return p9.QID{}, nil, err
62-
}
63-
return p9fs.NewGuestFile(
64-
makeMountPointFn,
65-
p9fs.UnlinkEmptyChildren[p9fs.GuestOption](autoUnlink),
66-
p9fs.UnlinkWhenEmpty[p9fs.GuestOption](autoUnlink),
67-
p9fs.WithParent[p9fs.GuestOption](parent, string(guest)),
68-
p9fs.WithPath[p9fs.GuestOption](path),
69-
p9fs.WithUID[p9fs.GuestOption](uid),
70-
p9fs.WithGID[p9fs.GuestOption](gid),
71-
p9fs.WithPermissions[p9fs.GuestOption](permissions),
72-
)
73-
}
74-
}
75-
7639
func newMounter(parent p9.File, path ninePath,
7740
uid p9.UID, gid p9.GID, permissions p9.FileMode,
7841
) (mountSubsystem, error) {
7942
const autoUnlink = true
80-
var (
81-
makeHostFn = newHostFunc(path, autoUnlink)
82-
_, mountFS, err = p9fs.NewMounter(
83-
makeHostFn,
84-
p9fs.WithParent[p9fs.MounterOption](parent, mountsFileName),
85-
p9fs.WithPath[p9fs.MounterOption](path),
86-
p9fs.WithUID[p9fs.MounterOption](uid),
87-
p9fs.WithGID[p9fs.MounterOption](gid),
88-
p9fs.WithPermissions[p9fs.MounterOption](permissions),
89-
p9fs.UnlinkEmptyChildren[p9fs.MounterOption](autoUnlink),
90-
p9fs.WithoutRename[p9fs.MounterOption](true),
91-
)
43+
_, mountFS, err := p9fs.NewMounter(
44+
newMakeHostFunc(path, autoUnlink),
45+
p9fs.WithParent[p9fs.MounterOption](parent, mountsFileName),
46+
p9fs.WithPath[p9fs.MounterOption](path),
47+
p9fs.WithUID[p9fs.MounterOption](uid),
48+
p9fs.WithGID[p9fs.MounterOption](gid),
49+
p9fs.WithPermissions[p9fs.MounterOption](permissions),
50+
p9fs.UnlinkEmptyChildren[p9fs.MounterOption](autoUnlink),
51+
p9fs.WithoutRename[p9fs.MounterOption](true),
9252
)
9353
if err != nil {
9454
return mountSubsystem{}, err
@@ -99,17 +59,15 @@ func newMounter(parent p9.File, path ninePath,
9959
}, nil
10060
}
10161

102-
func newHostFunc(path ninePath, autoUnlink bool) p9fs.MakeHostFunc {
62+
func newMakeHostFunc(path ninePath, autoUnlink bool) p9fs.MakeHostFunc {
63+
hosts := makeMountPointHosts(path, autoUnlink)
10364
return func(parent p9.File, host filesystem.Host, mode p9.FileMode, uid p9.UID, gid p9.GID) (p9.QID, p9.File, error) {
10465
permissions, err := mountsDirCreatePreamble(mode)
10566
if err != nil {
10667
return p9.QID{}, nil, err
10768
}
108-
var makeGuestFn p9fs.MakeGuestFunc
109-
switch host {
110-
case cgofuse.HostID:
111-
makeGuestFn = newGuestFunc[*cgofuse.Host](path, autoUnlink)
112-
default:
69+
makeGuestFn, ok := hosts[host]
70+
if !ok {
11371
err := fmt.Errorf(`unexpected host "%v"`, host)
11472
return p9.QID{}, nil, err
11573
}
@@ -127,6 +85,66 @@ func newHostFunc(path ninePath, autoUnlink bool) p9fs.MakeHostFunc {
12785
}
12886
}
12987

88+
func makeMountPointHosts(path ninePath, autoUnlink bool) mountPointHosts {
89+
type makeHostsFunc func(ninePath, bool) (filesystem.Host, p9fs.MakeGuestFunc)
90+
var (
91+
hostMakers = []makeHostsFunc{
92+
makeFUSEHost,
93+
}
94+
hosts = make(mountPointHosts, len(hostMakers))
95+
)
96+
for _, hostMaker := range hostMakers {
97+
host, guestMaker := hostMaker(path, autoUnlink)
98+
if guestMaker == nil {
99+
continue // System (likely) disabled by build constraints.
100+
}
101+
// No clobbering, accidental or otherwise.
102+
if _, exists := hosts[host]; exists {
103+
err := fmt.Errorf(
104+
"%s file constructor already registered",
105+
host,
106+
)
107+
panic(err)
108+
}
109+
hosts[host] = guestMaker
110+
}
111+
return hosts
112+
}
113+
114+
func newMakeGuestFunc(guests mountPointGuests, path ninePath, autoUnlink bool) p9fs.MakeGuestFunc {
115+
return func(parent p9.File, guest filesystem.ID, mode p9.FileMode, uid p9.UID, gid p9.GID) (p9.QID, p9.File, error) {
116+
permissions, err := mountsDirCreatePreamble(mode)
117+
if err != nil {
118+
return p9.QID{}, nil, err
119+
}
120+
makeMountPointFn, ok := guests[guest]
121+
if !ok {
122+
err := fmt.Errorf(`unexpected guest "%v"`, guest)
123+
return p9.QID{}, nil, err
124+
}
125+
return p9fs.NewGuestFile(
126+
makeMountPointFn,
127+
p9fs.UnlinkEmptyChildren[p9fs.GuestOption](autoUnlink),
128+
p9fs.UnlinkWhenEmpty[p9fs.GuestOption](autoUnlink),
129+
p9fs.WithParent[p9fs.GuestOption](parent, string(guest)),
130+
p9fs.WithPath[p9fs.GuestOption](path),
131+
p9fs.WithUID[p9fs.GuestOption](uid),
132+
p9fs.WithGID[p9fs.GuestOption](gid),
133+
p9fs.WithPermissions[p9fs.GuestOption](permissions),
134+
)
135+
}
136+
}
137+
138+
func makeMountPointGuests[
139+
T any,
140+
HC mountPointHost[T],
141+
](path ninePath,
142+
) mountPointGuests {
143+
guests := make(mountPointGuests)
144+
makeIPFSGuests[HC](guests, path)
145+
return guests
146+
}
147+
130148
func mountsDirCreatePreamble(mode p9.FileMode) (p9.FileMode, error) {
131149
if !mode.IsDir() {
132150
return 0, generic.ConstError("expected to be called from mkdir")
@@ -136,8 +154,9 @@ func mountsDirCreatePreamble(mode p9.FileMode) (p9.FileMode, error) {
136154

137155
func newMountPointFunc[
138156
HC mountPointHost[HT],
157+
GT any,
139158
GC mountPointGuest[GT],
140-
HT, GT any,
159+
HT any,
141160
](path ninePath,
142161
) p9fs.MakeMountPointFunc {
143162
return func(parent p9.File, name string, mode p9.FileMode, uid p9.UID, gid p9.GID) (p9.QID, p9.File, error) {

internal/commands/mountpoint_fuse.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
//go:build !nofuse
2+
13
package commands
24

35
import (
@@ -11,6 +13,7 @@ import (
1113

1214
"github.com/djdv/go-filesystem-utils/internal/command"
1315
"github.com/djdv/go-filesystem-utils/internal/filesystem"
16+
p9fs "github.com/djdv/go-filesystem-utils/internal/filesystem/9p"
1417
"github.com/djdv/go-filesystem-utils/internal/filesystem/cgofuse"
1518
"github.com/djdv/go-filesystem-utils/internal/generic"
1619
)
@@ -27,6 +30,26 @@ const (
2730
fuseRawOptionsName = fuseFlagPrefix + "options"
2831
)
2932

33+
func makeFUSECommand() command.Command {
34+
return makeMountSubcommand(
35+
cgofuse.HostID,
36+
makeGuestCommands[fuseOptions, fuseSettings](cgofuse.HostID),
37+
)
38+
}
39+
40+
func makeFUSEHost(path ninePath, autoUnlink bool) (filesystem.Host, p9fs.MakeGuestFunc) {
41+
guests := makeMountPointGuests[cgofuse.Host](path)
42+
return cgofuse.HostID, newMakeGuestFunc(guests, path, autoUnlink)
43+
}
44+
45+
func unmarshalFUSE() (filesystem.Host, decodeFunc) {
46+
return cgofuse.HostID, func(b []byte) (string, error) {
47+
var host cgofuse.Host
48+
err := json.Unmarshal(b, &host)
49+
return host.Point, err
50+
}
51+
}
52+
3053
func (*fuseOptions) usage(guest filesystem.ID) string {
3154
var (
3255
execName = filepath.Base(os.Args[0])

0 commit comments

Comments
 (0)