diff --git a/doc.go b/doc.go new file mode 100644 index 00000000..a38dcc27 --- /dev/null +++ b/doc.go @@ -0,0 +1,18 @@ +/* +Copyright 2014 Google Inc. All rights reserved. + +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 mount defines an interface to mounting filesystems. +package mount diff --git a/linux.go b/linux.go new file mode 100644 index 00000000..9d8e75ef --- /dev/null +++ b/linux.go @@ -0,0 +1,127 @@ +// +build linux + +/* +Copyright 2014 Google Inc. All rights reserved. + +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 mount + +import ( + "bufio" + "fmt" + "hash/adler32" + "io" + "os" + "strconv" + "strings" + "syscall" + + "github.com/golang/glog" +) + +const FlagBind = syscall.MS_BIND +const FlagReadOnly = syscall.MS_RDONLY + +type mounter struct{} + +// Wraps syscall.Mount() +func (mounter *mounter) Mount(source string, target string, fstype string, flags uintptr, data string) error { + glog.V(5).Infof("Mounting %s %s %s %d %s", source, target, fstype, flags, data) + return syscall.Mount(source, target, fstype, flags, data) +} + +// Wraps syscall.Unmount() +func (mounter *mounter) Unmount(target string, flags int) error { + return syscall.Unmount(target, flags) +} + +// How many times to retry for a consistent read of /proc/mounts. +const maxListTries = 3 + +func (mounter *mounter) List() ([]MountPoint, error) { + hash1, err := readProcMounts(nil) + if err != nil { + return nil, err + } + + for i := 0; i < maxListTries; i++ { + mps := []MountPoint{} + hash2, err := readProcMounts(&mps) + if err != nil { + return nil, err + } + if hash1 == hash2 { + // Success + return mps, nil + } + hash1 = hash2 + } + return nil, fmt.Errorf("failed to get a consistent snapshot of /proc/mounts after %d tries", maxListTries) +} + +// As per the fstab man page. +const expectedNumFieldsPerLine = 6 + +// Read /proc/mounts and produces a hash of the contents. If the out argument +// is not nil, this fills it with MountPoint structs. +func readProcMounts(out *[]MountPoint) (uint32, error) { + file, err := os.Open("/proc/mounts") + if err != nil { + return 0, err + } + defer file.Close() + return readProcMountsFrom(file, out) +} + +func readProcMountsFrom(file io.Reader, out *[]MountPoint) (uint32, error) { + hash := adler32.New() + scanner := bufio.NewReader(file) + for { + line, err := scanner.ReadString('\n') + if err == io.EOF { + break + } + fields := strings.Fields(line) + if len(fields) != expectedNumFieldsPerLine { + return 0, fmt.Errorf("wrong number of fields (expected %d, got %d): %s", expectedNumFieldsPerLine, len(fields), line) + } + + fmt.Fprintf(hash, "%s", line) + + if out != nil { + mp := MountPoint{ + Device: fields[0], + Path: fields[1], + Type: fields[2], + Opts: strings.Split(fields[3], ","), + } + + freq, err := strconv.Atoi(fields[4]) + if err != nil { + return 0, err + } + mp.Freq = freq + + pass, err := strconv.Atoi(fields[5]) + if err != nil { + return 0, err + } + mp.Pass = pass + + *out = append(*out, mp) + } + } + return hash.Sum32(), nil +} diff --git a/linux_test.go b/linux_test.go new file mode 100644 index 00000000..9f2f1d43 --- /dev/null +++ b/linux_test.go @@ -0,0 +1,93 @@ +// +build linux + +/* +Copyright 2014 Google Inc. All rights reserved. + +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 mount + +import ( + "strings" + "testing" +) + +func TestReadProcMountsFrom(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 + ` + hash, err := readProcMountsFrom(strings.NewReader(successCase), nil) + if err != nil { + t.Errorf("expected success") + } + if hash != 0xa3522051 { + t.Errorf("expected 0xa3522051, got %#x", hash) + } + mounts := []MountPoint{} + hash, err = readProcMountsFrom(strings.NewReader(successCase), &mounts) + if err != nil { + t.Errorf("expected success") + } + if hash != 0xa3522051 { + t.Errorf("expected 0xa3522051, got %#x", hash) + } + if len(mounts) != 3 { + t.Fatalf("expected 3 mounts, got %d", len(mounts)) + } + mp := MountPoint{"/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 = MountPoint{"/dev/1", "/path/to/1", "type1", []string{"flags"}, 1, 1} + if !mountPointsEqual(&mounts[1], &mp) { + t.Errorf("got unexpected MountPoint[1]: %#v", mounts[1]) + } + mp = MountPoint{"/dev/2", "/path/to/2", "type2", []string{"flags", "1", "2=3"}, 2, 2} + if !mountPointsEqual(&mounts[2], &mp) { + t.Errorf("got unexpected MountPoint[2]: %#v", mounts[2]) + } + + 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 { + _, err := readProcMountsFrom(strings.NewReader(ec), &mounts) + if err == nil { + t.Errorf("expected error") + } + } +} + +func mountPointsEqual(a, b *MountPoint) bool { + if a.Device != b.Device || a.Path != b.Path || a.Type != b.Type || !slicesEqual(a.Opts, b.Opts) || a.Pass != b.Pass || a.Freq != b.Freq { + return false + } + return true +} + +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 +} diff --git a/mount.go b/mount.go new file mode 100644 index 00000000..c00f859d --- /dev/null +++ b/mount.go @@ -0,0 +1,51 @@ +/* +Copyright 2014 Google Inc. All rights reserved. + +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. +*/ + +// TODO(thockin): This whole pkg is pretty linux-centric. As soon as we have +// an alternate platform, we will need to abstract further. +package mount + +// Each supported platform must define the following flags: +// - FlagBind: specifies a bind mount +// - FlagReadOnly: the mount will be read-only +type Interface interface { + // Mount wraps syscall.Mount(). + Mount(source string, target string, fstype string, flags uintptr, data string) error + + // Umount wraps syscall.Mount(). + Unmount(target string, flags int) error + + // List returns a list of all mounted filesystems. This can be large. + // On some platforms, reading mounts is not guaranteed consistent (i.e. + // it could change between chunked reads). This is guaranteed to be + // consistent. + List() ([]MountPoint, error) +} + +// New returns a mount.Interface for the current system. +func New() Interface { + return &mounter{} +} + +// This represents a single line in /proc/mounts or /etc/fstab. +type MountPoint struct { + Device string + Path string + Type string + Opts []string + Freq int + Pass int +} diff --git a/unsupported.go b/unsupported.go new file mode 100644 index 00000000..4be5f61d --- /dev/null +++ b/unsupported.go @@ -0,0 +1,36 @@ +// +build !linux + +/* +Copyright 2014 Google Inc. All rights reserved. + +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 mount + +const FlagBind = 0 +const FlagReadOnly = 0 + +type mounter struct{} + +func (mounter *mounter) Mount(source string, target string, fstype string, flags uintptr, data string) error { + return nil +} + +func (mounter *mounter) Unmount(target string, flags int) error { + return nil +} + +func (mounter *mounter) List() ([]MountPoint, error) { + return []MountPoint{}, nil +}