Skip to content

Commit

Permalink
add mounts validation to runtimetest
Browse files Browse the repository at this point in the history
Signed-off-by: liang chenye <liangchenye@huawei.com>
  • Loading branch information
liangchenye committed Jun 2, 2016
1 parent 923e88c commit 43496a6
Show file tree
Hide file tree
Showing 8 changed files with 294 additions and 0 deletions.
57 changes: 57 additions & 0 deletions cmd/runtimetest/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"syscall"

"github.com/Sirupsen/logrus"
"github.com/opencontainers/ocitools/cmd/runtimetest/mount"
rspec "github.com/opencontainers/runtime-spec/specs-go"
"github.com/syndtr/gocapability/capability"
)
Expand Down Expand Up @@ -215,6 +216,61 @@ func validateRootFS(spec *rspec.Spec) error {
return nil
}

func mountMatch(specMount rspec.Mount, sysMount rspec.Mount) error {
if specMount.Destination != sysMount.Destination {
return fmt.Errorf("mount destination expected: %v, actual: %v", specMount.Destination, sysMount.Destination)
}

if specMount.Type != sysMount.Type {
return fmt.Errorf("mount %v type expected: %v, actual: %v", specMount.Destination, specMount.Type, sysMount.Type)
}

if specMount.Source != sysMount.Source {
return fmt.Errorf("mount %v source expected: %v, actual: %v", specMount.Destination, specMount.Source, sysMount.Source)
}

optMap := make(map[string]bool)
for _, opt := range sysMount.Options {
optMap[opt] = true
}
for _, option := range specMount.Options {
if _, ok := optMap[option]; !ok {
return fmt.Errorf("mount %v option %v does not exist", specMount.Destination, option)
}
}

return nil
}

func validateMounts(spec *rspec.Spec) error {
fmt.Println("validating mounts")
infos, err := mount.GetMounts()
if err != nil {
return err
}

mountsMap := make(map[string]rspec.Mount)
for _, info := range infos {
mountsMap[info.Mountpoint] = rspec.Mount{
Destination: info.Mountpoint,
Type: info.Fstype,
Source: info.Source,
Options: append(strings.Split(info.Opts, ","), strings.Split(info.VfsOpts, ",")...)}
}

for _, specMount := range spec.Mounts {
if sysMount, ok := mountsMap[specMount.Destination]; ok {
if err := mountMatch(specMount, sysMount); err != nil {
return err
}
} else {
return fmt.Errorf("Expected mount %v does not exist", specMount.Destination)
}
}

return nil
}

func main() {
spec, err := loadSpecConfig()
if err != nil {
Expand All @@ -228,6 +284,7 @@ func main() {
validateHostname,
validateRlimits,
validateSysctls,
validateMounts,
}

for _, v := range validations {
Expand Down
1 change: 1 addition & 0 deletions cmd/runtimetest/mount/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[code from](github.com/docker/docker/pkg/mount)
45 changes: 45 additions & 0 deletions cmd/runtimetest/mount/mountinfo.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
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

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

// 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

// VfsOpts represents per super block options.
VfsOpts string
}

// GetMounts retrieves a list of mounts for the current running process.
func GetMounts() ([]*Info, error) {
return parseMountTable()
}
41 changes: 41 additions & 0 deletions cmd/runtimetest/mount/mountinfo_freebsd.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package mount

/*
#include <sys/param.h>
#include <sys/ucred.h>
#include <sys/mount.h>
*/
import "C"

import (
"fmt"
"reflect"
"unsafe"
)

// Parse /proc/self/mountinfo because comparing Dev and ino does not work from
// bind mounts.
func parseMountTable() ([]*Info, error) {
var rawEntries *C.struct_statfs

count := int(C.getmntinfo(&rawEntries, C.MNT_WAIT))
if count == 0 {
return nil, fmt.Errorf("Failed to call getmntinfo")
}

var entries []C.struct_statfs
header := (*reflect.SliceHeader)(unsafe.Pointer(&entries))
header.Cap = count
header.Len = count
header.Data = uintptr(unsafe.Pointer(rawEntries))

var out []*Info
for _, entry := range entries {
var mountinfo Info
mountinfo.Mountpoint = C.GoString(&entry.f_mntonname[0])
mountinfo.Source = C.GoString(&entry.f_mntfromname[0])
mountinfo.Fstype = C.GoString(&entry.f_fstypename[0])
out = append(out, &mountinfo)
}
return out, nil
}
95 changes: 95 additions & 0 deletions cmd/runtimetest/mount/mountinfo_linux.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
// +build linux

package mount

import (
"bufio"
"fmt"
"io"
"os"
"strings"
)

const (
/* 36 35 98:0 /mnt1 /mnt2 rw,noatime master:1 - ext3 /dev/root rw,errors=continue
(1)(2)(3) (4) (5) (6) (7) (8) (9) (10) (11)
(1) mount ID: unique identifier of the mount (may be reused after umount)
(2) parent ID: ID of parent (or of self for the top of the mount tree)
(3) major:minor: value of st_dev for files on filesystem
(4) root: root of the mount within the filesystem
(5) mount point: mount point relative to the process's root
(6) mount options: per mount options
(7) optional fields: zero or more fields of the form "tag[:value]"
(8) separator: marks the end of the optional fields
(9) filesystem type: name of filesystem of the form "type[.subtype]"
(10) mount source: filesystem specific information or "none"
(11) super options: per super block options*/
mountinfoFormat = "%d %d %d:%d %s %s %s %s"
)

// 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) {
var (
s = bufio.NewScanner(r)
out = []*Info{}
)

for s.Scan() {
if err := s.Err(); err != nil {
return nil, err
}

var (
p = &Info{}
text = s.Text()
optionalFields string
)

if _, err := fmt.Sscanf(text, mountinfoFormat,
&p.ID, &p.Parent, &p.Major, &p.Minor,
&p.Root, &p.Mountpoint, &p.Opts, &optionalFields); err != nil {
return nil, fmt.Errorf("Scanning '%s' failed: %s", text, err)
}
// Safe as mountinfo encodes mountpoints with spaces as \040.
index := strings.Index(text, " - ")
postSeparatorFields := strings.Fields(text[index+3:])
if len(postSeparatorFields) < 3 {
return nil, fmt.Errorf("Error found less than 3 fields post '-' in %q", text)
}

if optionalFields != "-" {
p.Optional = optionalFields
}

p.Fstype = postSeparatorFields[0]
p.Source = postSeparatorFields[1]
p.VfsOpts = strings.Join(postSeparatorFields[2:], " ")
out = append(out, p)
}
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)
}
37 changes: 37 additions & 0 deletions cmd/runtimetest/mount/mountinfo_solaris.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// +build solaris,cgo

package mount

/*
#include <stdio.h>
#include <sys/mnttab.h>
*/
import "C"

import (
"fmt"
)

func parseMountTable() ([]*Info, error) {
mnttab := C.fopen(C.CString(C.MNTTAB), C.CString("r"))
if mnttab == nil {
return nil, fmt.Errorf("Failed to open %s", C.MNTTAB)
}

var out []*Info
var mp C.struct_mnttab

ret := C.getmntent(mnttab, &mp)
for ret == 0 {
var mountinfo Info
mountinfo.Mountpoint = C.GoString(mp.mnt_mountp)
mountinfo.Source = C.GoString(mp.mnt_special)
mountinfo.Fstype = C.GoString(mp.mnt_fstype)
mountinfo.Opts = C.GoString(mp.mnt_mntopts)
out = append(out, &mountinfo)
ret = C.getmntent(mnttab, &mp)
}

C.fclose(mnttab)
return out, nil
}
12 changes: 12 additions & 0 deletions cmd/runtimetest/mount/mountinfo_unsupported.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// +build !windows,!linux,!freebsd,!solaris freebsd,!cgo solaris,!cgo

package mount

import (
"fmt"
"runtime"
)

func parseMountTable() ([]*Info, error) {
return nil, fmt.Errorf("mount.parseMountTable is not implemented on %s/%s", runtime.GOOS, runtime.GOARCH)
}
6 changes: 6 additions & 0 deletions cmd/runtimetest/mount/mountinfo_windows.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package mount

func parseMountTable() ([]*Info, error) {
// Do NOT return an error!
return nil, nil
}

0 comments on commit 43496a6

Please sign in to comment.