mount: add -volumeName to name the disk explicitly (#11165)

* mount: let volumeName take an explicit override

volumeName only ever derived the disk's label from -filer.path, -dir,
or the filer address, so a name that happened to collide with
something else - e.g. a UNC share's own name - could not be changed
without moving what was mounted. Give it an override parameter that
wins over all three; nothing passes one yet.

* mount: add -volumeName to name the disk explicitly

Windows has no equivalent of the "weed fuse" -o passthrough that lets
a Linux or macOS mount override its derived volname, so a name picked
up from -dir - e.g. a UNC share's own name - could not be changed
short of moving what was mounted. -volumeName overrides it on every
platform.

* mount: document -volumeName

* mount: scope -volumeName's help text to macOS and Windows

Linux has no volume-label mount option for -volumeName to feed, so
the flag's own description says where it applies instead of leaving
that unstated.

* mount: forward -volumeName through the weed fuse option parser

weed fuse (the /etc/fstab helper) turns -o key=value into the same
MountOptions weed mount takes, but volumeName had no case, so it fell
through to being forwarded as a literal, unrecognized FUSE option
instead of ever reaching mountOptions.volumeName.

* mount: apply -volumeName to FsName on Linux and FreeBSD

FsName only ever took the filer address and -filer.path, so
-volumeName had nothing to override there and silently did nothing;
the skipAutofs case still forces "fuse", since that name is what
util-linux/mount requires to recognize the pseudo filesystem.
This commit is contained in:
Chris Lu
2026-09-04 23:14:57 -07:00
committed by GitHub
parent cfa8afec92
commit 1ca19ea2e2
7 changed files with 66 additions and 8 deletions
+2
View File
@@ -124,6 +124,8 @@ func runFuse(cmd *Command, args []string) bool {
mountOptions.filer = &parameter.value
case "filer.path":
mountOptions.filerMountRootPath = &parameter.value
case "volumeName":
mountOptions.volumeName = &parameter.value
case "dirAutoCreate":
if parsed, err := strconv.ParseBool(parameter.value); err == nil {
mountOptions.dirAutoCreate = &parsed
+29
View File
@@ -36,3 +36,32 @@ func TestRunFuseDoesNotSkipOptionAfterConcurrentWriters(t *testing.T) {
"child=1,concurrentWriters=2,concurrentReaders=bad,umask=bad",
})
}
func TestRunFuseSetsVolumeName(t *testing.T) {
oldVolumeName := mountOptions.volumeName
oldConcurrentReaders := mountOptions.concurrentReaders
oldDir := mountOptions.dir
defer func() {
gotVolumeName := mountOptions.volumeName
mountOptions.volumeName = oldVolumeName
mountOptions.concurrentReaders = oldConcurrentReaders
mountOptions.dir = oldDir
recovered := recover()
if recovered == nil {
t.Fatal("expected invalid concurrentReaders option to be parsed")
}
if gotVolumeName == nil {
t.Fatal("expected -o volumeName=MyDisk to set mountOptions.volumeName, got nil")
}
if *gotVolumeName != "MyDisk" {
t.Fatalf("expected -o volumeName=MyDisk to set mountOptions.volumeName, got %q", *gotVolumeName)
}
}()
runFuse(cmdMount, []string{
"/mnt",
"-o",
"volumeName=MyDisk,concurrentReaders=bad",
})
}
+4
View File
@@ -9,6 +9,7 @@ type MountOptions struct {
filer *string
filerMountRootPath *string
dir *string
volumeName *string
dirAutoCreate *bool
collection *string
collectionQuota *int
@@ -92,6 +93,7 @@ func init() {
mountOptions.filer = cmdMount.Flag.String("filer", "localhost:8888", "comma-separated weed filer location")
mountOptions.filerMountRootPath = cmdMount.Flag.String("filer.path", "/", "mount this remote path from filer server")
mountOptions.dir = cmdMount.Flag.String("dir", ".", "mount weed filer to this directory")
mountOptions.volumeName = cmdMount.Flag.String("volumeName", "", "name the mount shown by the platform (Finder/Explorer, or \"mount\"/\"df\" on Linux and FreeBSD), overriding the name taken from -filer.path or -dir")
mountOptions.dirAutoCreate = cmdMount.Flag.Bool("dirAutoCreate", false, "auto create the directory to mount to")
mountOptions.collection = cmdMount.Flag.String("collection", "", "collection to create the files")
mountOptions.collectionQuota = cmdMount.Flag.Int("collectionQuotaMB", 0, "quota for the collection")
@@ -188,6 +190,8 @@ var cmdMount = &Command{
the whole tree takes the name from the mount point instead, so
-dir=\\seaweedfs\Images labels the disk "Images" while still mounting
everything, and only a bare drive letter falls back to the filer address.
-volumeName overrides whatever name either of those would otherwise give it,
including what Linux and FreeBSD show for the mount in "mount" and "df".
`,
}
+8 -4
View File
@@ -278,10 +278,14 @@ func resolveCacheDirs(option *MountOptions) (string, string) {
}
// volumeName labels the mount where the platform shows one, in Finder and in
// Explorer. The mounted path names the disk, then the mount point; the filer
// address, which every mount from one filer shares, is the last resort.
func volumeName(filer, filerMountRootPath, dir string) string {
name := lastSegment(filerMountRootPath)
// Explorer. An explicit override always wins; failing that, the mounted path
// names the disk, then the mount point; the filer address, which every mount
// from one filer shares, is the last resort.
func volumeName(filer, filerMountRootPath, dir, override string) string {
name := override
if name == "" {
name = lastSegment(filerMountRootPath)
}
if name == "" {
name = lastSegment(dir)
}
+18 -2
View File
@@ -10,8 +10,24 @@ func Test_volumeName(t *testing.T) {
filer string
filerMountRootPath string
dir string
override string
expected string
}{
{
name: "an override outranks the mounted path and the mount point",
filer: "127.0.0.1:8888",
filerMountRootPath: "/buckets/videos",
dir: `\\seaweedfs\Images`,
override: "MyDisk",
expected: "MyDisk",
},
{
name: "an override still gets commas replaced",
filer: "127.0.0.1:8888",
filerMountRootPath: "/",
override: "My, Disk",
expected: "My+ Disk",
},
{
name: "a drive letter leaves only the filer to fall back on",
filer: "127.0.0.1:8888",
@@ -80,8 +96,8 @@ func Test_volumeName(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := volumeName(tt.filer, tt.filerMountRootPath, tt.dir); got != tt.expected {
t.Errorf("volumeName(%q, %q, %q) = %q, want %q", tt.filer, tt.filerMountRootPath, tt.dir, got, tt.expected)
if got := volumeName(tt.filer, tt.filerMountRootPath, tt.dir, tt.override); got != tt.expected {
t.Errorf("volumeName(%q, %q, %q, %q) = %q, want %q", tt.filer, tt.filerMountRootPath, tt.dir, tt.override, got, tt.expected)
}
})
}
+4 -1
View File
@@ -118,6 +118,9 @@ func RunMount(option *MountOptions, umask os.FileMode) bool {
// When autofs/systemd-mount is used, FsName must be "fuse" so util-linux/mount can recognize
// it as a pseudo filesystem. Otherwise, preserve the descriptive name for mount/df output.
fsName := serverFriendlyName + ":" + filerMountRootPath
if *option.volumeName != "" {
fsName = *option.volumeName
}
if skipAutofs {
fsName = "fuse"
}
@@ -179,7 +182,7 @@ func RunMount(option *MountOptions, umask os.FileMode) bool {
fuseMountOptions.Options = append(fuseMountOptions.Options, "novncache")
}
fuseMountOptions.Options = append(fuseMountOptions.Options, "slow_statfs")
fuseMountOptions.Options = append(fuseMountOptions.Options, "volname="+volumeName(*option.filer, filerMountRootPath, dir))
fuseMountOptions.Options = append(fuseMountOptions.Options, "volname="+volumeName(*option.filer, filerMountRootPath, dir, *option.volumeName))
fuseMountOptions.Options = append(fuseMountOptions.Options, fmt.Sprintf("iosize=%d", ioSizeMB*1024*1024))
}
// Last, so an option given on the command line wins over the default
+1 -1
View File
@@ -104,7 +104,7 @@ func RunMount(option *MountOptions, umask os.FileMode) bool {
}
host := winfsp.New(seaweedFileSystem, winfsp.Options{
VolumeName: volumeName(*option.filer, *option.filerMountRootPath, dir),
VolumeName: volumeName(*option.filer, *option.filerMountRootPath, dir, *option.volumeName),
Uid: ownedByMounter,
Gid: ownedByMounter,
CacheTimeout: windowsCacheTimeout,