Skip to content

Commit

Permalink
Merge pull request #703 from kolyshkin/moby-sys-mountinfo
Browse files Browse the repository at this point in the history
pkg/mount: switch to moby/sys/mountinfo
  • Loading branch information
giuseppe authored Aug 27, 2020
2 parents 8b69ba6 + 24d4e7a commit 114898c
Show file tree
Hide file tree
Showing 15 changed files with 549 additions and 671 deletions.
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ require (
github.com/klauspost/pgzip v1.2.4
github.com/mattn/go-shellwords v1.0.10
github.com/mistifyio/go-zfs v2.1.1+incompatible
github.com/moby/sys/mountinfo v0.1.3
github.com/opencontainers/go-digest v1.0.0
github.com/opencontainers/runc v1.0.0-rc91
github.com/opencontainers/runtime-spec v1.0.3-0.20200520003142-237cc4f519e2
Expand Down
29 changes: 0 additions & 29 deletions pkg/mount/mount.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@ import (
"sort"
"strconv"
"strings"

"github.com/containers/storage/pkg/fileutils"
)

// mountError holds an error from a mount or unmount operation
Expand Down Expand Up @@ -43,33 +41,6 @@ func (e *mountError) Cause() error {
return e.err
}

// GetMounts retrieves a list of mounts for the current running process.
func GetMounts() ([]*Info, error) {
return parseMountTable()
}

// Mounted determines if a specified mountpoint has been mounted.
// On Linux it looks at /proc/self/mountinfo and on Solaris at mnttab.
func Mounted(mountpoint string) (bool, error) {
entries, err := parseMountTable()
if err != nil {
return false, err
}

mountpoint, err = fileutils.ReadSymlinkedPath(mountpoint)
if err != nil {
return false, err
}

// Search the table for the mountpoint
for _, e := range entries {
if e.Mountpoint == mountpoint {
return true, nil
}
}
return false, nil
}

// Mount will mount filesystem according to the specified configuration, on the
// condition that the target path is *not* already mounted. Options must be
// specified like the mount or fstab unix commands: "opt1=val1,opt2=val2". See
Expand Down
49 changes: 15 additions & 34 deletions pkg/mount/mountinfo.go
Original file line number Diff line number Diff line change
@@ -1,40 +1,21 @@
package mount

// Info reveals information about a particular mounted filesystem. This
// struct is populated from the content in the /proc/<pid>/mountinfo file.
type Info struct {
// ID is a unique identifier of the mount (may be reused after umount).
ID int
import (
"github.com/containers/storage/pkg/fileutils"
"github.com/moby/sys/mountinfo"
)

// Parent indicates the ID of the mount parent (or of self for the top of the
// mount tree).
Parent int
type Info = mountinfo.Info

// Major indicates one half of the device ID which identifies the device class.
Major int

// Minor indicates one half of the device ID which identifies a specific
// instance of device.
Minor int

// Root of the mount within the filesystem.
Root string

// Mountpoint indicates the mount point relative to the process's root.
Mountpoint string

// Opts represents mount-specific options.
Opts string

// Optional represents optional fields.
Optional string

// Fstype indicates the type of filesystem, such as EXT3.
Fstype string

// Source indicates filesystem specific information or "none".
Source string
func GetMounts() ([]*Info, error) {
return mountinfo.GetMounts(nil)
}

// VfsOpts represents per super block options.
VfsOpts string
// Mounted determines if a specified mountpoint has been mounted.
func Mounted(mountpoint string) (bool, error) {
mountpoint, err := fileutils.ReadSymlinkedPath(mountpoint)
if err != nil {
return false, err
}
return mountinfo.Mounted(mountpoint)
}
119 changes: 2 additions & 117 deletions pkg/mount/mountinfo_linux.go
Original file line number Diff line number Diff line change
@@ -1,120 +1,5 @@
package mount

import (
"bufio"
"fmt"
"io"
"os"
"strconv"
"strings"
import "github.com/moby/sys/mountinfo"

"github.com/pkg/errors"
)

// Parse /proc/self/mountinfo because comparing Dev and ino does not work from
// bind mounts
func parseMountTable() ([]*Info, error) {
f, err := os.Open("/proc/self/mountinfo")
if err != nil {
return nil, err
}
defer f.Close()

return parseInfoFile(f)
}

func parseInfoFile(r io.Reader) ([]*Info, error) {
s := bufio.NewScanner(r)
out := []*Info{}

for s.Scan() {
/*
36 35 98:0 /mnt1 /mnt2 rw,noatime master:1 - ext3 /dev/root rw,errors=continue
(0)(1)(2) (3) (4) (5) (6) (7) (8) (9) (10)
(0) mount ID: unique identifier of the mount (may be reused after umount)
(1) parent ID: ID of parent (or of self for the top of the mount tree)
(2) major:minor: value of st_dev for files on filesystem
(3) root: root of the mount within the filesystem
(4) mount point: mount point relative to the process's root
(5) mount options: per mount options
(6) optional fields: zero or more fields of the form "tag[:value]"
(7) separator: marks the end of the optional fields
(8) filesystem type: name of filesystem of the form "type[.subtype]"
(9) mount source: filesystem specific information or "none"
(10) super options: per super block options
*/
text := s.Text()
fields := strings.Split(text, " ")
numFields := len(fields)
if numFields < 10 {
// should be at least 10 fields
return nil, errors.Errorf("Parsing %q failed: not enough fields (%d)", text, numFields)
}

p := &Info{}
// ignore any number parsing errors, there should not be any
p.ID, _ = strconv.Atoi(fields[0])
p.Parent, _ = strconv.Atoi(fields[1])
mm := strings.Split(fields[2], ":")
if len(mm) != 2 {
return nil, fmt.Errorf("Parsing %q failed: unexpected minor:major pair %s", text, mm)
}
p.Major, _ = strconv.Atoi(mm[0])
p.Minor, _ = strconv.Atoi(mm[1])
p.Root = fields[3]
p.Mountpoint = fields[4]
p.Opts = fields[5]

// one or more optional fields, when a separator (-)
i := 6
for ; i < numFields && fields[i] != "-"; i++ {
switch i {
case 6:
p.Optional = string(fields[6])
default:
/* NOTE there might be more optional fields before the separator,
such as fields[7] or fields[8], although as of Linux kernel 5.5
the only known ones are mount propagation flags in fields[6].
The correct behavior is to ignore any unknown optional fields.
*/
}
}
if i == numFields {
return nil, fmt.Errorf("Parsing %q failed: missing - separator", text)
}

// There should be 3 fields after the separator...
if i+4 > numFields {
return nil, fmt.Errorf("Parsing %q failed: not enough fields after a - separator", text)
}
// ... but in Linux <= 3.9 mounting a cifs with spaces in a share name
// (like "//serv/My Documents") _may_ end up having a space in the last field
// of mountinfo (like "unc=//serv/My Documents"). Since kernel 3.10-rc1, cifs
// option unc= is ignored, so a space should not appear. In here we ignore
// those "extra" fields caused by extra spaces.
p.Fstype = fields[i+1]
p.Source = fields[i+2]
p.VfsOpts = fields[i+3]

out = append(out, p)
}
if err := s.Err(); err != nil {
return nil, err
}

return out, nil
}

// PidMountInfo collects the mounts for a specific process ID. If the process
// ID is unknown, it is better to use `GetMounts` which will inspect
// "/proc/self/mountinfo" instead.
func PidMountInfo(pid int) ([]*Info, error) {
f, err := os.Open(fmt.Sprintf("/proc/%d/mountinfo", pid))
if err != nil {
return nil, err
}
defer f.Close()

return parseInfoFile(f)
}
var PidMountInfo = mountinfo.PidMountInfo
Loading

0 comments on commit 114898c

Please sign in to comment.