forked from TrueCloudLab/restic
311 lines
8.9 KiB
Go
311 lines
8.9 KiB
Go
|
package fuse
|
||
|
|
||
|
import (
|
||
|
"errors"
|
||
|
"strings"
|
||
|
)
|
||
|
|
||
|
func dummyOption(conf *mountConfig) error {
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// mountConfig holds the configuration for a mount operation.
|
||
|
// Use it by passing MountOption values to Mount.
|
||
|
type mountConfig struct {
|
||
|
options map[string]string
|
||
|
maxReadahead uint32
|
||
|
initFlags InitFlags
|
||
|
osxfuseLocations []OSXFUSEPaths
|
||
|
}
|
||
|
|
||
|
func escapeComma(s string) string {
|
||
|
s = strings.Replace(s, `\`, `\\`, -1)
|
||
|
s = strings.Replace(s, `,`, `\,`, -1)
|
||
|
return s
|
||
|
}
|
||
|
|
||
|
// getOptions makes a string of options suitable for passing to FUSE
|
||
|
// mount flag `-o`. Returns an empty string if no options were set.
|
||
|
// Any platform specific adjustments should happen before the call.
|
||
|
func (m *mountConfig) getOptions() string {
|
||
|
var opts []string
|
||
|
for k, v := range m.options {
|
||
|
k = escapeComma(k)
|
||
|
if v != "" {
|
||
|
k += "=" + escapeComma(v)
|
||
|
}
|
||
|
opts = append(opts, k)
|
||
|
}
|
||
|
return strings.Join(opts, ",")
|
||
|
}
|
||
|
|
||
|
type mountOption func(*mountConfig) error
|
||
|
|
||
|
// MountOption is passed to Mount to change the behavior of the mount.
|
||
|
type MountOption mountOption
|
||
|
|
||
|
// FSName sets the file system name (also called source) that is
|
||
|
// visible in the list of mounted file systems.
|
||
|
//
|
||
|
// FreeBSD ignores this option.
|
||
|
func FSName(name string) MountOption {
|
||
|
return func(conf *mountConfig) error {
|
||
|
conf.options["fsname"] = name
|
||
|
return nil
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Subtype sets the subtype of the mount. The main type is always
|
||
|
// `fuse`. The type in a list of mounted file systems will look like
|
||
|
// `fuse.foo`.
|
||
|
//
|
||
|
// OS X ignores this option.
|
||
|
// FreeBSD ignores this option.
|
||
|
func Subtype(fstype string) MountOption {
|
||
|
return func(conf *mountConfig) error {
|
||
|
conf.options["subtype"] = fstype
|
||
|
return nil
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// LocalVolume sets the volume to be local (instead of network),
|
||
|
// changing the behavior of Finder, Spotlight, and such.
|
||
|
//
|
||
|
// OS X only. Others ignore this option.
|
||
|
func LocalVolume() MountOption {
|
||
|
return localVolume
|
||
|
}
|
||
|
|
||
|
// VolumeName sets the volume name shown in Finder.
|
||
|
//
|
||
|
// OS X only. Others ignore this option.
|
||
|
func VolumeName(name string) MountOption {
|
||
|
return volumeName(name)
|
||
|
}
|
||
|
|
||
|
// NoAppleDouble makes OSXFUSE disallow files with names used by OS X
|
||
|
// to store extended attributes on file systems that do not support
|
||
|
// them natively.
|
||
|
//
|
||
|
// Such file names are:
|
||
|
//
|
||
|
// ._*
|
||
|
// .DS_Store
|
||
|
//
|
||
|
// OS X only. Others ignore this option.
|
||
|
func NoAppleDouble() MountOption {
|
||
|
return noAppleDouble
|
||
|
}
|
||
|
|
||
|
// NoAppleXattr makes OSXFUSE disallow extended attributes with the
|
||
|
// prefix "com.apple.". This disables persistent Finder state and
|
||
|
// other such information.
|
||
|
//
|
||
|
// OS X only. Others ignore this option.
|
||
|
func NoAppleXattr() MountOption {
|
||
|
return noAppleXattr
|
||
|
}
|
||
|
|
||
|
// ExclCreate causes O_EXCL flag to be set for only "truly" exclusive creates,
|
||
|
// i.e. create calls for which the initiator explicitly set the O_EXCL flag.
|
||
|
//
|
||
|
// OSXFUSE expects all create calls to return EEXIST in case the file
|
||
|
// already exists, regardless of whether O_EXCL was specified or not.
|
||
|
// To ensure this behavior, it normally sets OpenExclusive for all
|
||
|
// Create calls, regardless of whether the original call had it set.
|
||
|
// For distributed filesystems, that may force every file create to be
|
||
|
// a distributed consensus action, causing undesirable delays.
|
||
|
//
|
||
|
// This option makes the FUSE filesystem see the original flag value,
|
||
|
// and better decide when to ensure global consensus.
|
||
|
//
|
||
|
// Note that returning EEXIST on existing file create is still
|
||
|
// expected with OSXFUSE, regardless of the presence of the
|
||
|
// OpenExclusive flag.
|
||
|
//
|
||
|
// For more information, see
|
||
|
// https://github.com/osxfuse/osxfuse/issues/209
|
||
|
//
|
||
|
// OS X only. Others ignore this options.
|
||
|
// Requires OSXFUSE 3.4.1 or newer.
|
||
|
func ExclCreate() MountOption {
|
||
|
return exclCreate
|
||
|
}
|
||
|
|
||
|
// DaemonTimeout sets the time in seconds between a request and a reply before
|
||
|
// the FUSE mount is declared dead.
|
||
|
//
|
||
|
// OS X and FreeBSD only. Others ignore this option.
|
||
|
func DaemonTimeout(name string) MountOption {
|
||
|
return daemonTimeout(name)
|
||
|
}
|
||
|
|
||
|
var ErrCannotCombineAllowOtherAndAllowRoot = errors.New("cannot combine AllowOther and AllowRoot")
|
||
|
|
||
|
// AllowOther allows other users to access the file system.
|
||
|
//
|
||
|
// Only one of AllowOther or AllowRoot can be used.
|
||
|
func AllowOther() MountOption {
|
||
|
return func(conf *mountConfig) error {
|
||
|
if _, ok := conf.options["allow_root"]; ok {
|
||
|
return ErrCannotCombineAllowOtherAndAllowRoot
|
||
|
}
|
||
|
conf.options["allow_other"] = ""
|
||
|
return nil
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// AllowRoot allows other users to access the file system.
|
||
|
//
|
||
|
// Only one of AllowOther or AllowRoot can be used.
|
||
|
//
|
||
|
// FreeBSD ignores this option.
|
||
|
func AllowRoot() MountOption {
|
||
|
return func(conf *mountConfig) error {
|
||
|
if _, ok := conf.options["allow_other"]; ok {
|
||
|
return ErrCannotCombineAllowOtherAndAllowRoot
|
||
|
}
|
||
|
conf.options["allow_root"] = ""
|
||
|
return nil
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// AllowDev enables interpreting character or block special devices on the
|
||
|
// filesystem.
|
||
|
func AllowDev() MountOption {
|
||
|
return func(conf *mountConfig) error {
|
||
|
conf.options["dev"] = ""
|
||
|
return nil
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// AllowSUID allows set-user-identifier or set-group-identifier bits to take
|
||
|
// effect.
|
||
|
func AllowSUID() MountOption {
|
||
|
return func(conf *mountConfig) error {
|
||
|
conf.options["suid"] = ""
|
||
|
return nil
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// DefaultPermissions makes the kernel enforce access control based on
|
||
|
// the file mode (as in chmod).
|
||
|
//
|
||
|
// Without this option, the Node itself decides what is and is not
|
||
|
// allowed. This is normally ok because FUSE file systems cannot be
|
||
|
// accessed by other users without AllowOther/AllowRoot.
|
||
|
//
|
||
|
// FreeBSD ignores this option.
|
||
|
func DefaultPermissions() MountOption {
|
||
|
return func(conf *mountConfig) error {
|
||
|
conf.options["default_permissions"] = ""
|
||
|
return nil
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// ReadOnly makes the mount read-only.
|
||
|
func ReadOnly() MountOption {
|
||
|
return func(conf *mountConfig) error {
|
||
|
conf.options["ro"] = ""
|
||
|
return nil
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// MaxReadahead sets the number of bytes that can be prefetched for
|
||
|
// sequential reads. The kernel can enforce a maximum value lower than
|
||
|
// this.
|
||
|
//
|
||
|
// This setting makes the kernel perform speculative reads that do not
|
||
|
// originate from any client process. This usually tremendously
|
||
|
// improves read performance.
|
||
|
func MaxReadahead(n uint32) MountOption {
|
||
|
return func(conf *mountConfig) error {
|
||
|
conf.maxReadahead = n
|
||
|
return nil
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// AsyncRead enables multiple outstanding read requests for the same
|
||
|
// handle. Without this, there is at most one request in flight at a
|
||
|
// time.
|
||
|
func AsyncRead() MountOption {
|
||
|
return func(conf *mountConfig) error {
|
||
|
conf.initFlags |= InitAsyncRead
|
||
|
return nil
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// WritebackCache enables the kernel to buffer writes before sending
|
||
|
// them to the FUSE server. Without this, writethrough caching is
|
||
|
// used.
|
||
|
func WritebackCache() MountOption {
|
||
|
return func(conf *mountConfig) error {
|
||
|
conf.initFlags |= InitWritebackCache
|
||
|
return nil
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// OSXFUSEPaths describes the paths used by an installed OSXFUSE
|
||
|
// version. See OSXFUSELocationV3 for typical values.
|
||
|
type OSXFUSEPaths struct {
|
||
|
// Prefix for the device file. At mount time, an incrementing
|
||
|
// number is suffixed until a free FUSE device is found.
|
||
|
DevicePrefix string
|
||
|
// Path of the load helper, used to load the kernel extension if
|
||
|
// no device files are found.
|
||
|
Load string
|
||
|
// Path of the mount helper, used for the actual mount operation.
|
||
|
Mount string
|
||
|
// Environment variable used to pass the path to the executable
|
||
|
// calling the mount helper.
|
||
|
DaemonVar string
|
||
|
}
|
||
|
|
||
|
// Default paths for OSXFUSE. See OSXFUSELocations.
|
||
|
var (
|
||
|
OSXFUSELocationV3 = OSXFUSEPaths{
|
||
|
DevicePrefix: "/dev/osxfuse",
|
||
|
Load: "/Library/Filesystems/osxfuse.fs/Contents/Resources/load_osxfuse",
|
||
|
Mount: "/Library/Filesystems/osxfuse.fs/Contents/Resources/mount_osxfuse",
|
||
|
DaemonVar: "MOUNT_OSXFUSE_DAEMON_PATH",
|
||
|
}
|
||
|
OSXFUSELocationV2 = OSXFUSEPaths{
|
||
|
DevicePrefix: "/dev/osxfuse",
|
||
|
Load: "/Library/Filesystems/osxfusefs.fs/Support/load_osxfusefs",
|
||
|
Mount: "/Library/Filesystems/osxfusefs.fs/Support/mount_osxfusefs",
|
||
|
DaemonVar: "MOUNT_FUSEFS_DAEMON_PATH",
|
||
|
}
|
||
|
)
|
||
|
|
||
|
// OSXFUSELocations sets where to look for OSXFUSE files. The
|
||
|
// arguments are all the possible locations. The previous locations
|
||
|
// are replaced.
|
||
|
//
|
||
|
// Without this option, OSXFUSELocationV3 and OSXFUSELocationV2 are
|
||
|
// used.
|
||
|
//
|
||
|
// OS X only. Others ignore this option.
|
||
|
func OSXFUSELocations(paths ...OSXFUSEPaths) MountOption {
|
||
|
return func(conf *mountConfig) error {
|
||
|
if len(paths) == 0 {
|
||
|
return errors.New("must specify at least one location for OSXFUSELocations")
|
||
|
}
|
||
|
// replace previous values, but make a copy so there's no
|
||
|
// worries about caller mutating their slice
|
||
|
conf.osxfuseLocations = append(conf.osxfuseLocations[:0], paths...)
|
||
|
return nil
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// AllowNonEmptyMount allows the mounting over a non-empty directory.
|
||
|
//
|
||
|
// The files in it will be shadowed by the freshly created mount. By
|
||
|
// default these mounts are rejected to prevent accidental covering up
|
||
|
// of data, which could for example prevent automatic backup.
|
||
|
func AllowNonEmptyMount() MountOption {
|
||
|
return func(conf *mountConfig) error {
|
||
|
conf.options["nonempty"] = ""
|
||
|
return nil
|
||
|
}
|
||
|
}
|