chrislu
3 years ago
7 changed files with 43 additions and 416 deletions
-
1weed/command/command.go
-
86weed/command/mount.go
-
65weed/command/mount2.go
-
18weed/command/mount2_std.go
-
13weed/command/mount_freebsd.go
-
15weed/command/mount_notsupported.go
-
261weed/command/mount_std.go
@ -1,86 +0,0 @@ |
|||
package command |
|||
|
|||
import ( |
|||
"os" |
|||
"time" |
|||
) |
|||
|
|||
type MountOptions struct { |
|||
filer *string |
|||
filerMountRootPath *string |
|||
dir *string |
|||
dirAutoCreate *bool |
|||
collection *string |
|||
replication *string |
|||
diskType *string |
|||
ttlSec *int |
|||
chunkSizeLimitMB *int |
|||
concurrentWriters *int |
|||
cacheDir *string |
|||
cacheSizeMB *int64 |
|||
dataCenter *string |
|||
allowOthers *bool |
|||
umaskString *string |
|||
nonempty *bool |
|||
volumeServerAccess *string |
|||
uidMap *string |
|||
gidMap *string |
|||
readOnly *bool |
|||
debug *bool |
|||
debugPort *int |
|||
} |
|||
|
|||
var ( |
|||
mountOptions MountOptions |
|||
mountCpuProfile *string |
|||
mountMemProfile *string |
|||
mountReadRetryTime *time.Duration |
|||
) |
|||
|
|||
func init() { |
|||
cmdMount.Run = runMount // break init cycle
|
|||
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.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.replication = cmdMount.Flag.String("replication", "", "replication(e.g. 000, 001) to create to files. If empty, let filer decide.") |
|||
mountOptions.diskType = cmdMount.Flag.String("disk", "", "[hdd|ssd|<tag>] hard drive or solid state drive or any tag") |
|||
mountOptions.ttlSec = cmdMount.Flag.Int("ttl", 0, "file ttl in seconds") |
|||
mountOptions.chunkSizeLimitMB = cmdMount.Flag.Int("chunkSizeLimitMB", 2, "local write buffer size, also chunk large files") |
|||
mountOptions.concurrentWriters = cmdMount.Flag.Int("concurrentWriters", 32, "limit concurrent goroutine writers if not 0") |
|||
mountOptions.cacheDir = cmdMount.Flag.String("cacheDir", os.TempDir(), "local cache directory for file chunks and meta data") |
|||
mountOptions.cacheSizeMB = cmdMount.Flag.Int64("cacheCapacityMB", 0, "local file chunk cache capacity in MB") |
|||
mountOptions.dataCenter = cmdMount.Flag.String("dataCenter", "", "prefer to write to the data center") |
|||
mountOptions.allowOthers = cmdMount.Flag.Bool("allowOthers", true, "allows other users to access the file system") |
|||
mountOptions.umaskString = cmdMount.Flag.String("umask", "022", "octal umask, e.g., 022, 0111") |
|||
mountOptions.nonempty = cmdMount.Flag.Bool("nonempty", false, "allows the mounting over a non-empty directory") |
|||
mountOptions.volumeServerAccess = cmdMount.Flag.String("volumeServerAccess", "direct", "access volume servers by [direct|publicUrl|filerProxy]") |
|||
mountOptions.uidMap = cmdMount.Flag.String("map.uid", "", "map local uid to uid on filer, comma-separated <local_uid>:<filer_uid>") |
|||
mountOptions.gidMap = cmdMount.Flag.String("map.gid", "", "map local gid to gid on filer, comma-separated <local_gid>:<filer_gid>") |
|||
mountOptions.readOnly = cmdMount.Flag.Bool("readOnly", false, "read only") |
|||
mountOptions.debug = cmdMount.Flag.Bool("debug", false, "serves runtime profiling data, e.g., http://localhost:<debug.port>/debug/pprof/goroutine?debug=2") |
|||
mountOptions.debugPort = cmdMount.Flag.Int("debug.port", 6061, "http port for debugging") |
|||
|
|||
mountCpuProfile = cmdMount.Flag.String("cpuprofile", "", "cpu profile output file") |
|||
mountMemProfile = cmdMount.Flag.String("memprofile", "", "memory profile output file") |
|||
mountReadRetryTime = cmdMount.Flag.Duration("readRetryTime", 6*time.Second, "maximum read retry wait time") |
|||
} |
|||
|
|||
var cmdMount = &Command{ |
|||
UsageLine: "mount -filer=localhost:8888 -dir=/some/dir", |
|||
Short: "mount weed filer to a directory as file system in userspace(FUSE)", |
|||
Long: `mount weed filer to userspace. |
|||
|
|||
Pre-requisites: |
|||
1) have SeaweedFS master and volume servers running |
|||
2) have a "weed filer" running |
|||
These 2 requirements can be achieved with one command "weed server -filer=true" |
|||
|
|||
This uses github.com/seaweedfs/fuse, which enables writing FUSE file systems on |
|||
Linux, and OS X. |
|||
|
|||
On OS X, it requires OSXFUSE (http://osxfuse.github.com/).
|
|||
|
|||
`, |
|||
} |
@ -1,13 +0,0 @@ |
|||
package command |
|||
|
|||
import ( |
|||
"github.com/seaweedfs/fuse" |
|||
) |
|||
|
|||
func osSpecificMountOptions() []fuse.MountOption { |
|||
return []fuse.MountOption{} |
|||
} |
|||
|
|||
func checkMountPointAvailable(dir string) bool { |
|||
return true |
|||
} |
@ -1,15 +0,0 @@ |
|||
//go:build !linux && !darwin && !freebsd
|
|||
// +build !linux,!darwin,!freebsd
|
|||
|
|||
package command |
|||
|
|||
import ( |
|||
"fmt" |
|||
"runtime" |
|||
) |
|||
|
|||
func runMount(cmd *Command, args []string) bool { |
|||
fmt.Printf("Mount is not supported on %s %s\n", runtime.GOOS, runtime.GOARCH) |
|||
|
|||
return true |
|||
} |
@ -1,261 +0,0 @@ |
|||
//go:build linux || darwin || freebsd
|
|||
// +build linux darwin freebsd
|
|||
|
|||
package command |
|||
|
|||
import ( |
|||
"context" |
|||
"fmt" |
|||
"net/http" |
|||
"os" |
|||
"os/user" |
|||
"path" |
|||
"path/filepath" |
|||
"runtime" |
|||
"strconv" |
|||
"strings" |
|||
"syscall" |
|||
"time" |
|||
|
|||
"github.com/chrislusf/seaweedfs/weed/storage/types" |
|||
|
|||
"github.com/chrislusf/seaweedfs/weed/filesys/meta_cache" |
|||
|
|||
"github.com/seaweedfs/fuse" |
|||
"github.com/seaweedfs/fuse/fs" |
|||
|
|||
"github.com/chrislusf/seaweedfs/weed/filesys" |
|||
"github.com/chrislusf/seaweedfs/weed/glog" |
|||
"github.com/chrislusf/seaweedfs/weed/pb" |
|||
"github.com/chrislusf/seaweedfs/weed/pb/filer_pb" |
|||
"github.com/chrislusf/seaweedfs/weed/security" |
|||
"github.com/chrislusf/seaweedfs/weed/util" |
|||
"github.com/chrislusf/seaweedfs/weed/util/grace" |
|||
) |
|||
|
|||
func runMount(cmd *Command, args []string) bool { |
|||
|
|||
if *mountOptions.debug { |
|||
go http.ListenAndServe(fmt.Sprintf(":%d", *mountOptions.debugPort), nil) |
|||
} |
|||
|
|||
grace.SetupProfiling(*mountCpuProfile, *mountMemProfile) |
|||
if *mountReadRetryTime < time.Second { |
|||
*mountReadRetryTime = time.Second |
|||
} |
|||
util.RetryWaitTime = *mountReadRetryTime |
|||
|
|||
umask, umaskErr := strconv.ParseUint(*mountOptions.umaskString, 8, 64) |
|||
if umaskErr != nil { |
|||
fmt.Printf("can not parse umask %s", *mountOptions.umaskString) |
|||
return false |
|||
} |
|||
|
|||
if len(args) > 0 { |
|||
return false |
|||
} |
|||
|
|||
return RunMount(&mountOptions, os.FileMode(umask)) |
|||
} |
|||
|
|||
func getParentInode(mountDir string) (uint64, error) { |
|||
parentDir := filepath.Clean(filepath.Join(mountDir, "..")) |
|||
fi, err := os.Stat(parentDir) |
|||
if err != nil { |
|||
return 0, err |
|||
} |
|||
|
|||
stat, ok := fi.Sys().(*syscall.Stat_t) |
|||
if !ok { |
|||
return 0, nil |
|||
} |
|||
|
|||
return stat.Ino, nil |
|||
} |
|||
|
|||
func RunMount(option *MountOptions, umask os.FileMode) bool { |
|||
|
|||
filerAddresses := pb.ServerAddresses(*option.filer).ToAddresses() |
|||
|
|||
util.LoadConfiguration("security", false) |
|||
// try to connect to filer, filerBucketsPath may be useful later
|
|||
grpcDialOption := security.LoadClientTLS(util.GetViper(), "grpc.client") |
|||
var cipher bool |
|||
var err error |
|||
for i := 0; i < 10; i++ { |
|||
err = pb.WithOneOfGrpcFilerClients(false, filerAddresses, grpcDialOption, func(client filer_pb.SeaweedFilerClient) error { |
|||
resp, err := client.GetFilerConfiguration(context.Background(), &filer_pb.GetFilerConfigurationRequest{}) |
|||
if err != nil { |
|||
return fmt.Errorf("get filer grpc address %v configuration: %v", filerAddresses, err) |
|||
} |
|||
cipher = resp.Cipher |
|||
return nil |
|||
}) |
|||
if err != nil { |
|||
glog.V(0).Infof("failed to talk to filer %v: %v", filerAddresses, err) |
|||
glog.V(0).Infof("wait for %d seconds ...", i+1) |
|||
time.Sleep(time.Duration(i+1) * time.Second) |
|||
} |
|||
} |
|||
if err != nil { |
|||
glog.Errorf("failed to talk to filer %v: %v", filerAddresses, err) |
|||
return true |
|||
} |
|||
|
|||
filerMountRootPath := *option.filerMountRootPath |
|||
dir := util.ResolvePath(*option.dir) |
|||
parentInode, err := getParentInode(dir) |
|||
if err != nil { |
|||
glog.Errorf("failed to retrieve inode for parent directory of %s: %v", dir, err) |
|||
return true |
|||
} |
|||
|
|||
fmt.Printf("This is SeaweedFS version %s %s %s\n", util.Version(), runtime.GOOS, runtime.GOARCH) |
|||
if dir == "" { |
|||
fmt.Printf("Please specify the mount directory via \"-dir\"") |
|||
return false |
|||
} |
|||
|
|||
chunkSizeLimitMB := *mountOptions.chunkSizeLimitMB |
|||
if chunkSizeLimitMB <= 0 { |
|||
fmt.Printf("Please specify a reasonable buffer size.") |
|||
return false |
|||
} |
|||
|
|||
fuse.Unmount(dir) |
|||
|
|||
// detect mount folder mode
|
|||
if *option.dirAutoCreate { |
|||
os.MkdirAll(dir, os.FileMode(0777)&^umask) |
|||
} |
|||
fileInfo, err := os.Stat(dir) |
|||
|
|||
uid, gid := uint32(0), uint32(0) |
|||
mountMode := os.ModeDir | 0777 |
|||
if err == nil { |
|||
mountMode = os.ModeDir | os.FileMode(0777)&^umask |
|||
uid, gid = util.GetFileUidGid(fileInfo) |
|||
fmt.Printf("mount point owner uid=%d gid=%d mode=%s\n", uid, gid, mountMode) |
|||
} else { |
|||
fmt.Printf("can not stat %s\n", dir) |
|||
return false |
|||
} |
|||
|
|||
if uid == 0 { |
|||
if u, err := user.Current(); err == nil { |
|||
if parsedId, pe := strconv.ParseUint(u.Uid, 10, 32); pe == nil { |
|||
uid = uint32(parsedId) |
|||
} |
|||
if parsedId, pe := strconv.ParseUint(u.Gid, 10, 32); pe == nil { |
|||
gid = uint32(parsedId) |
|||
} |
|||
fmt.Printf("current uid=%d gid=%d\n", uid, gid) |
|||
} |
|||
} |
|||
|
|||
// mapping uid, gid
|
|||
uidGidMapper, err := meta_cache.NewUidGidMapper(*option.uidMap, *option.gidMap) |
|||
if err != nil { |
|||
fmt.Printf("failed to parse %s %s: %v\n", *option.uidMap, *option.gidMap, err) |
|||
return false |
|||
} |
|||
|
|||
// Ensure target mount point availability
|
|||
if isValid := checkMountPointAvailable(dir); !isValid { |
|||
glog.Fatalf("Expected mount to still be active, target mount point: %s, please check!", dir) |
|||
return true |
|||
} |
|||
|
|||
mountName := path.Base(dir) |
|||
|
|||
options := []fuse.MountOption{ |
|||
fuse.VolumeName(mountName), |
|||
fuse.FSName(*option.filer + ":" + filerMountRootPath), |
|||
fuse.Subtype("seaweedfs"), |
|||
// fuse.NoAppleDouble(), // include .DS_Store, otherwise can not delete non-empty folders
|
|||
fuse.NoAppleXattr(), |
|||
fuse.ExclCreate(), |
|||
fuse.DaemonTimeout("3600"), |
|||
fuse.AllowDev(), |
|||
fuse.AllowSUID(), |
|||
fuse.DefaultPermissions(), |
|||
fuse.MaxReadahead(1024 * 512), |
|||
fuse.AsyncRead(), |
|||
// fuse.WritebackCache(),
|
|||
// fuse.MaxBackground(1024),
|
|||
// fuse.CongestionThreshold(1024),
|
|||
} |
|||
|
|||
options = append(options, osSpecificMountOptions()...) |
|||
if *option.allowOthers { |
|||
options = append(options, fuse.AllowOther()) |
|||
} |
|||
if *option.nonempty { |
|||
options = append(options, fuse.AllowNonEmptyMount()) |
|||
} |
|||
if *option.readOnly { |
|||
options = append(options, fuse.ReadOnly()) |
|||
} |
|||
|
|||
// find mount point
|
|||
mountRoot := filerMountRootPath |
|||
if mountRoot != "/" && strings.HasSuffix(mountRoot, "/") { |
|||
mountRoot = mountRoot[0 : len(mountRoot)-1] |
|||
} |
|||
|
|||
diskType := types.ToDiskType(*option.diskType) |
|||
|
|||
seaweedFileSystem := filesys.NewSeaweedFileSystem(&filesys.Option{ |
|||
MountDirectory: dir, |
|||
FilerAddresses: filerAddresses, |
|||
GrpcDialOption: grpcDialOption, |
|||
FilerMountRootPath: mountRoot, |
|||
Collection: *option.collection, |
|||
Replication: *option.replication, |
|||
TtlSec: int32(*option.ttlSec), |
|||
DiskType: diskType, |
|||
ChunkSizeLimit: int64(chunkSizeLimitMB) * 1024 * 1024, |
|||
ConcurrentWriters: *option.concurrentWriters, |
|||
CacheDir: *option.cacheDir, |
|||
CacheSizeMB: *option.cacheSizeMB, |
|||
DataCenter: *option.dataCenter, |
|||
MountUid: uid, |
|||
MountGid: gid, |
|||
MountMode: mountMode, |
|||
MountCtime: fileInfo.ModTime(), |
|||
MountMtime: time.Now(), |
|||
MountParentInode: parentInode, |
|||
Umask: umask, |
|||
VolumeServerAccess: *mountOptions.volumeServerAccess, |
|||
Cipher: cipher, |
|||
UidGidMapper: uidGidMapper, |
|||
}) |
|||
|
|||
// mount
|
|||
c, err := fuse.Mount(dir, options...) |
|||
if err != nil { |
|||
glog.V(0).Infof("mount: %v", err) |
|||
return true |
|||
} |
|||
defer fuse.Unmount(dir) |
|||
|
|||
grace.OnInterrupt(func() { |
|||
fuse.Unmount(dir) |
|||
c.Close() |
|||
}) |
|||
|
|||
glog.V(0).Infof("mounted %s%s to %v", *option.filer, mountRoot, dir) |
|||
server := fs.New(c, nil) |
|||
seaweedFileSystem.Server = server |
|||
seaweedFileSystem.StartBackgroundTasks() |
|||
err = server.Serve(seaweedFileSystem) |
|||
|
|||
// check if the mount process has an error to report
|
|||
<-c.Ready |
|||
if err := c.MountError; err != nil { |
|||
glog.V(0).Infof("mount process: %v", err) |
|||
return true |
|||
} |
|||
|
|||
return true |
|||
} |
Write
Preview
Loading…
Cancel
Save
Reference in new issue