mirror of
https://github.com/minio/minio.git
synced 2024-12-23 21:55:53 -05:00
Fail to start server if detected cross-device mounts. (#4807)
Fixes #4764
This commit is contained in:
parent
3d21119ec8
commit
879cef37a1
@ -21,11 +21,13 @@ import (
|
||||
"net"
|
||||
"net/url"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/minio/minio-go/pkg/set"
|
||||
"github.com/minio/minio/pkg/mountinfo"
|
||||
)
|
||||
|
||||
// EndpointType - enum for endpoint type.
|
||||
@ -99,7 +101,8 @@ func NewEndpoint(arg string) (ep Endpoint, e error) {
|
||||
return ep, fmt.Errorf("invalid URL endpoint format")
|
||||
}
|
||||
|
||||
host, port, err := net.SplitHostPort(u.Host)
|
||||
var host, port string
|
||||
host, port, err = net.SplitHostPort(u.Host)
|
||||
if err != nil {
|
||||
if !strings.Contains(err.Error(), "missing port in address") {
|
||||
return ep, fmt.Errorf("invalid URL endpoint format: %s", err)
|
||||
@ -226,6 +229,22 @@ func NewEndpointList(args ...string) (endpoints EndpointList, err error) {
|
||||
return endpoints, nil
|
||||
}
|
||||
|
||||
// Checks if there are any cross device mounts.
|
||||
func checkCrossDeviceMounts(endpoints EndpointList) (err error) {
|
||||
var absPaths []string
|
||||
for _, endpoint := range endpoints {
|
||||
if endpoint.IsLocal {
|
||||
var absPath string
|
||||
absPath, err = filepath.Abs(endpoint.Path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
absPaths = append(absPaths, absPath)
|
||||
}
|
||||
}
|
||||
return mountinfo.CheckCrossDevice(absPaths)
|
||||
}
|
||||
|
||||
// CreateEndpoints - validates and creates new endpoints for given args.
|
||||
func CreateEndpoints(serverAddr string, args ...string) (string, EndpointList, SetupType, error) {
|
||||
var endpoints EndpointList
|
||||
@ -246,13 +265,16 @@ func CreateEndpoints(serverAddr string, args ...string) (string, EndpointList, S
|
||||
if err != nil {
|
||||
return serverAddr, endpoints, setupType, err
|
||||
}
|
||||
|
||||
if endpoint.Type() != PathEndpointType {
|
||||
return serverAddr, endpoints, setupType, fmt.Errorf("use path style endpoint for FS setup")
|
||||
}
|
||||
|
||||
endpoints = append(endpoints, endpoint)
|
||||
setupType = FSSetupType
|
||||
|
||||
// Check for cross device mounts if any.
|
||||
if err = checkCrossDeviceMounts(endpoints); err != nil {
|
||||
return serverAddr, endpoints, setupType, err
|
||||
}
|
||||
return serverAddr, endpoints, setupType, nil
|
||||
}
|
||||
|
||||
@ -261,6 +283,11 @@ func CreateEndpoints(serverAddr string, args ...string) (string, EndpointList, S
|
||||
return serverAddr, endpoints, setupType, err
|
||||
}
|
||||
|
||||
// Check for cross device mounts if any.
|
||||
if err = checkCrossDeviceMounts(endpoints); err != nil {
|
||||
return serverAddr, endpoints, setupType, err
|
||||
}
|
||||
|
||||
// Return XL setup when all endpoints are path style.
|
||||
if endpoints[0].Type() == PathEndpointType {
|
||||
setupType = XLSetupType
|
||||
|
@ -17,6 +17,7 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
pathutil "path"
|
||||
@ -360,6 +361,9 @@ func fsRenameFile(sourcePath, destPath string) error {
|
||||
return traceError(err)
|
||||
}
|
||||
if err := os.Rename((sourcePath), (destPath)); err != nil {
|
||||
if isSysErrCrossDevice(err) {
|
||||
return traceError(fmt.Errorf("%s (%s)->(%s)", errCrossDeviceLink, sourcePath, destPath))
|
||||
}
|
||||
return traceError(err)
|
||||
}
|
||||
return nil
|
||||
|
@ -118,3 +118,10 @@ func isSysErrHandleInvalid(err error) bool {
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func isSysErrCrossDevice(err error) bool {
|
||||
if e, ok := err.(*os.LinkError); ok {
|
||||
return e.Err == syscall.EXDEV
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
@ -102,6 +102,7 @@ func newPosix(path string) (StorageAPI, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
st := &posix{
|
||||
diskPath: diskPath,
|
||||
// 1MiB buffer pool for posix internal operations.
|
||||
|
@ -91,6 +91,7 @@ func serverHandleCmdArgs(ctx *cli.Context) {
|
||||
|
||||
var setupType SetupType
|
||||
var err error
|
||||
|
||||
globalMinioAddr, globalEndpoints, setupType, err = CreateEndpoints(serverAddr, ctx.Args()...)
|
||||
fatalIf(err, "Invalid command line arguments server=‘%s’, args=%s", serverAddr, ctx.Args())
|
||||
globalMinioHost, globalMinioPort = mustSplitHostPort(globalMinioAddr)
|
||||
|
@ -83,6 +83,9 @@ var errFileAccessDenied = errors.New("file access denied")
|
||||
// verification is empty or invalid.
|
||||
var errBitrotHashAlgoInvalid = errors.New("bit-rot hash algorithm is invalid")
|
||||
|
||||
// errCrossDeviceLink - rename across devices not allowed.
|
||||
var errCrossDeviceLink = errors.New("Rename across devices not allowed, please fix your backend configuration")
|
||||
|
||||
// hashMisMatchError - represents a bit-rot hash verification failure
|
||||
// error.
|
||||
type hashMismatchError struct {
|
||||
|
34
pkg/mountinfo/mountinfo.go
Normal file
34
pkg/mountinfo/mountinfo.go
Normal file
@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Minio Cloud Storage, (C) 2017 Minio, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package mountinfo
|
||||
|
||||
// mountInfo - This represents a single line in /proc/mounts.
|
||||
type mountInfo struct {
|
||||
Device string
|
||||
Path string
|
||||
FSType string
|
||||
Options []string
|
||||
Freq string
|
||||
Pass string
|
||||
}
|
||||
|
||||
func (m mountInfo) String() string {
|
||||
return m.Path
|
||||
}
|
||||
|
||||
// mountInfos - This represents the entire /proc/mounts.
|
||||
type mountInfos []mountInfo
|
131
pkg/mountinfo/mountinfo_linux.go
Normal file
131
pkg/mountinfo/mountinfo_linux.go
Normal file
@ -0,0 +1,131 @@
|
||||
// +build linux
|
||||
|
||||
/*
|
||||
* Minio Cloud Storage, (C) 2017 Minio, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package mountinfo
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
// Number of fields per line in /proc/mounts as per the fstab man page.
|
||||
expectedNumFieldsPerLine = 6
|
||||
// Location of the mount file to use
|
||||
procMountsPath = "/proc/mounts"
|
||||
)
|
||||
|
||||
// CheckCrossDevice - check if any list of paths has any sub-mounts at /proc/mounts.
|
||||
func CheckCrossDevice(absPaths []string) error {
|
||||
return checkCrossDevice(absPaths, procMountsPath)
|
||||
}
|
||||
|
||||
// Check cross device is an internal function.
|
||||
func checkCrossDevice(absPaths []string, mountsPath string) error {
|
||||
mounts, err := readProcMounts(mountsPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, path := range absPaths {
|
||||
if err := mounts.checkCrossMounts(path); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CheckCrossDevice - check if given path has any sub-mounts in the input mounts list.
|
||||
func (mts mountInfos) checkCrossMounts(path string) error {
|
||||
if !filepath.IsAbs(path) {
|
||||
return fmt.Errorf("Invalid argument, path (%s) is expected to be absolute", path)
|
||||
}
|
||||
var crossMounts mountInfos
|
||||
for _, mount := range mts {
|
||||
// Add a separator to indicate that this is a proper mount-point.
|
||||
// This is to avoid a situation where prefix is '/tmp/fsmount'
|
||||
// and mount path is /tmp/fs. In such a scenario we need to check for
|
||||
// `/tmp/fs/` to be a common prefix amount other mounts.
|
||||
mpath := strings.TrimSuffix(mount.Path, "/") + "/"
|
||||
ppath := strings.TrimSuffix(path, "/") + "/"
|
||||
if strings.HasPrefix(mpath, ppath) {
|
||||
// At this point if the mount point has a common prefix two conditions can happen.
|
||||
// - mount.Path matches exact with `path` means we can proceed no error here.
|
||||
// - mount.Path doesn't match (means cross-device mount), should error out.
|
||||
if mount.Path != path {
|
||||
crossMounts = append(crossMounts, mount)
|
||||
}
|
||||
}
|
||||
}
|
||||
msg := `Cross-device mounts detected on path (%s) at following locations %s. Export path should not have any sub-mounts, refusing to start.`
|
||||
if len(crossMounts) > 0 {
|
||||
// if paths didn't match then we do have cross-device mount.
|
||||
return fmt.Errorf(msg, path, crossMounts)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// readProcMounts reads the given mountFilePath (normally /proc/mounts) and produces a hash
|
||||
// of the contents. If the out argument is not nil, this fills it with MountPoint structs.
|
||||
func readProcMounts(mountFilePath string) (mountInfos, error) {
|
||||
file, err := os.Open(mountFilePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer file.Close()
|
||||
return parseMountFrom(file)
|
||||
}
|
||||
|
||||
func parseMountFrom(file io.Reader) (mountInfos, error) {
|
||||
var mounts = mountInfos{}
|
||||
scanner := bufio.NewReader(file)
|
||||
for {
|
||||
line, err := scanner.ReadString('\n')
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
fields := strings.Fields(line)
|
||||
if len(fields) != expectedNumFieldsPerLine {
|
||||
return nil, fmt.Errorf("wrong number of fields (expected %d, got %d): %s", expectedNumFieldsPerLine, len(fields), line)
|
||||
}
|
||||
|
||||
// Freq should be an integer.
|
||||
if _, err := strconv.Atoi(fields[4]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Pass should be an integer.
|
||||
if _, err := strconv.Atoi(fields[5]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
mounts = append(mounts, mountInfo{
|
||||
Device: fields[0],
|
||||
Path: fields[1],
|
||||
FSType: fields[2],
|
||||
Options: strings.Split(fields[3], ","),
|
||||
Freq: fields[4],
|
||||
Pass: fields[5],
|
||||
})
|
||||
}
|
||||
return mounts, nil
|
||||
}
|
251
pkg/mountinfo/mountinfo_linux_test.go
Normal file
251
pkg/mountinfo/mountinfo_linux_test.go
Normal file
@ -0,0 +1,251 @@
|
||||
// +build linux
|
||||
|
||||
/*
|
||||
* Minio Cloud Storage, (C) 2017 Minio, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package mountinfo
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// Tests cross device mount verification function, for both failure
|
||||
// and success cases.
|
||||
func TestCrossDeviceMountPaths(t *testing.T) {
|
||||
successCase :=
|
||||
`/dev/0 /path/to/0/1 type0 flags 0 0
|
||||
/dev/1 /path/to/1 type1 flags 1 1
|
||||
/dev/2 /path/to/1/2 type2 flags,1,2=3 2 2
|
||||
/dev/3 /path/to/1.1 type3 falgs,1,2=3 3 3
|
||||
`
|
||||
dir, err := ioutil.TempDir("", "TestReadProcmountInfos")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(dir)
|
||||
mountsPath := filepath.Join(dir, "mounts")
|
||||
if err = ioutil.WriteFile(mountsPath, []byte(successCase), 0666); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// Failure case where we detected successfully cross device mounts.
|
||||
{
|
||||
var absPaths = []string{"/path/to/1"}
|
||||
if err = checkCrossDevice(absPaths, mountsPath); err == nil {
|
||||
t.Fatal("Expected to fail, but found success")
|
||||
}
|
||||
|
||||
mp := []mountInfo{
|
||||
{"/dev/2", "/path/to/1/2", "type2", []string{"flags"}, "2", "2"},
|
||||
}
|
||||
msg := fmt.Sprintf("Cross-device mounts detected on path (/path/to/1) at following locations %s. Export path should not have any sub-mounts, refusing to start.", mp)
|
||||
if err.Error() != msg {
|
||||
t.Fatalf("Expected msg %s, got %s", msg, err)
|
||||
}
|
||||
}
|
||||
// Failure case when input path is not absolute.
|
||||
{
|
||||
var absPaths = []string{"."}
|
||||
if err = checkCrossDevice(absPaths, mountsPath); err == nil {
|
||||
t.Fatal("Expected to fail for non absolute paths")
|
||||
}
|
||||
expectedErrMsg := fmt.Sprintf("Invalid argument, path (%s) is expected to be absolute", ".")
|
||||
if err.Error() != expectedErrMsg {
|
||||
t.Fatalf("Expected %s, got %s", expectedErrMsg, err)
|
||||
}
|
||||
}
|
||||
// Success case, where path doesn't have any mounts.
|
||||
{
|
||||
var absPaths = []string{"/path/to/x"}
|
||||
if err = checkCrossDevice(absPaths, mountsPath); err != nil {
|
||||
t.Fatalf("Expected success, failed instead (%s)", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Tests cross device mount verification function, for both failure
|
||||
// and success cases.
|
||||
func TestCrossDeviceMount(t *testing.T) {
|
||||
successCase :=
|
||||
`/dev/0 /path/to/0/1 type0 flags 0 0
|
||||
/dev/1 /path/to/1 type1 flags 1 1
|
||||
/dev/2 /path/to/1/2 type2 flags,1,2=3 2 2
|
||||
/dev/3 /path/to/1.1 type3 falgs,1,2=3 3 3
|
||||
`
|
||||
dir, err := ioutil.TempDir("", "TestReadProcmountInfos")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(dir)
|
||||
mountsPath := filepath.Join(dir, "mounts")
|
||||
if err = ioutil.WriteFile(mountsPath, []byte(successCase), 0666); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
mounts, err := readProcMounts(mountsPath)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// Failure case where we detected successfully cross device mounts.
|
||||
{
|
||||
if err = mounts.checkCrossMounts("/path/to/1"); err == nil {
|
||||
t.Fatal("Expected to fail, but found success")
|
||||
}
|
||||
|
||||
mp := []mountInfo{
|
||||
{"/dev/2", "/path/to/1/2", "type2", []string{"flags"}, "2", "2"},
|
||||
}
|
||||
msg := fmt.Sprintf("Cross-device mounts detected on path (/path/to/1) at following locations %s. Export path should not have any sub-mounts, refusing to start.", mp)
|
||||
if err.Error() != msg {
|
||||
t.Fatalf("Expected msg %s, got %s", msg, err)
|
||||
}
|
||||
}
|
||||
// Failure case when input path is not absolute.
|
||||
{
|
||||
if err = mounts.checkCrossMounts("."); err == nil {
|
||||
t.Fatal("Expected to fail for non absolute paths")
|
||||
}
|
||||
expectedErrMsg := fmt.Sprintf("Invalid argument, path (%s) is expected to be absolute", ".")
|
||||
if err.Error() != expectedErrMsg {
|
||||
t.Fatalf("Expected %s, got %s", expectedErrMsg, err)
|
||||
}
|
||||
}
|
||||
// Success case, where path doesn't have any mounts.
|
||||
{
|
||||
if err = mounts.checkCrossMounts("/path/to/x"); err != nil {
|
||||
t.Fatalf("Expected success, failed instead (%s)", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Tests read proc mounts file.
|
||||
func TestReadProcmountInfos(t *testing.T) {
|
||||
successCase :=
|
||||
`/dev/0 /path/to/0 type0 flags 0 0
|
||||
/dev/1 /path/to/1 type1 flags 1 1
|
||||
/dev/2 /path/to/2 type2 flags,1,2=3 2 2
|
||||
`
|
||||
dir, err := ioutil.TempDir("", "TestReadProcmountInfos")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
mountsPath := filepath.Join(dir, "mounts")
|
||||
if err = ioutil.WriteFile(mountsPath, []byte(successCase), 0666); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// Verifies if reading each line worked properly.
|
||||
{
|
||||
var mounts mountInfos
|
||||
mounts, err = readProcMounts(mountsPath)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(mounts) != 3 {
|
||||
t.Fatalf("expected 3 mounts, got %d", len(mounts))
|
||||
}
|
||||
mp := mountInfo{"/dev/0", "/path/to/0", "type0", []string{"flags"}, "0", "0"}
|
||||
if !mountPointsEqual(mounts[0], mp) {
|
||||
t.Errorf("got unexpected MountPoint[0]: %#v", mounts[0])
|
||||
}
|
||||
mp = mountInfo{"/dev/1", "/path/to/1", "type1", []string{"flags"}, "1", "1"}
|
||||
if !mountPointsEqual(mounts[1], mp) {
|
||||
t.Errorf("got unexpected mountInfo[1]: %#v", mounts[1])
|
||||
}
|
||||
mp = mountInfo{"/dev/2", "/path/to/2", "type2", []string{"flags", "1", "2=3"}, "2", "2"}
|
||||
if !mountPointsEqual(mounts[2], mp) {
|
||||
t.Errorf("got unexpected mountInfo[2]: %#v", mounts[2])
|
||||
}
|
||||
}
|
||||
// Failure case mounts path doesn't exist, if not fail.
|
||||
{
|
||||
if _, err = readProcMounts(filepath.Join(dir, "non-existent")); err != nil && !os.IsNotExist(err) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Tests read proc mounts reader.
|
||||
func TestReadProcMountFrom(t *testing.T) {
|
||||
successCase :=
|
||||
`/dev/0 /path/to/0 type0 flags 0 0
|
||||
/dev/1 /path/to/1 type1 flags 1 1
|
||||
/dev/2 /path/to/2 type2 flags,1,2=3 2 2
|
||||
`
|
||||
// Success case, verifies if parsing works properly.
|
||||
{
|
||||
mounts, err := parseMountFrom(strings.NewReader(successCase))
|
||||
if err != nil {
|
||||
t.Errorf("expected success")
|
||||
}
|
||||
if len(mounts) != 3 {
|
||||
t.Fatalf("expected 3 mounts, got %d", len(mounts))
|
||||
}
|
||||
mp := mountInfo{"/dev/0", "/path/to/0", "type0", []string{"flags"}, "0", "0"}
|
||||
if !mountPointsEqual(mounts[0], mp) {
|
||||
t.Errorf("got unexpected mountInfo[0]: %#v", mounts[0])
|
||||
}
|
||||
mp = mountInfo{"/dev/1", "/path/to/1", "type1", []string{"flags"}, "1", "1"}
|
||||
if !mountPointsEqual(mounts[1], mp) {
|
||||
t.Errorf("got unexpected mountInfo[1]: %#v", mounts[1])
|
||||
}
|
||||
mp = mountInfo{"/dev/2", "/path/to/2", "type2", []string{"flags", "1", "2=3"}, "2", "2"}
|
||||
if !mountPointsEqual(mounts[2], mp) {
|
||||
t.Errorf("got unexpected mountInfo[2]: %#v", mounts[2])
|
||||
}
|
||||
}
|
||||
// Error cases where parsing fails with invalid Freq and Pass params.
|
||||
{
|
||||
errorCases := []string{
|
||||
"/dev/0 /path/to/mount\n",
|
||||
"/dev/1 /path/to/mount type flags a 0\n",
|
||||
"/dev/2 /path/to/mount type flags 0 b\n",
|
||||
}
|
||||
for _, ec := range errorCases {
|
||||
_, rerr := parseMountFrom(strings.NewReader(ec))
|
||||
if rerr == nil {
|
||||
t.Errorf("expected error")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Helpers for tests.
|
||||
|
||||
// Check if two `mountInfo` are equal.
|
||||
func mountPointsEqual(a, b mountInfo) bool {
|
||||
if a.Device != b.Device || a.Path != b.Path || a.FSType != b.FSType || !slicesEqual(a.Options, b.Options) || a.Pass != b.Pass || a.Freq != b.Freq {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Checks if two string slices are equal.
|
||||
func slicesEqual(a, b []string) bool {
|
||||
if len(a) != len(b) {
|
||||
return false
|
||||
}
|
||||
for i := range a {
|
||||
if a[i] != b[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
25
pkg/mountinfo/mountinfo_others.go
Normal file
25
pkg/mountinfo/mountinfo_others.go
Normal file
@ -0,0 +1,25 @@
|
||||
// +build !linux
|
||||
|
||||
/*
|
||||
* Minio Cloud Storage, (C) 2017 Minio, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package mountinfo
|
||||
|
||||
// CheckCrossDevice - check if any input path has multiple sub-mounts.
|
||||
// this is a dummy function and returns nil for now.
|
||||
func CheckCrossDevice(paths []string) error {
|
||||
return nil
|
||||
}
|
Loading…
Reference in New Issue
Block a user