| // Copyright 2019 The Chromium OS Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| package sysutil |
| |
| import ( |
| "fmt" |
| "io/ioutil" |
| "os" |
| "regexp" |
| "strconv" |
| "strings" |
| |
| "chromiumos/tast/errors" |
| ) |
| |
| // MountOpt is a bit flag for the mount option. |
| type MountOpt uint32 |
| |
| const ( |
| // MntReadonly represents "ro". |
| MntReadonly MountOpt = 1 << iota |
| // MntNosuid represents "nosuid". |
| MntNosuid |
| // MntNodev represents "nodev". |
| MntNodev |
| // MntNoexec represents "noexec". |
| MntNoexec |
| // MntNoatime represents "noatime". |
| MntNoatime |
| // MntNodiratime represents "nodiratime". |
| MntNodiratime |
| // MntRelatime represents "relatime". |
| MntRelatime |
| // MntNosymfollow represents "nosymfollow". |
| MntNosymfollow |
| ) |
| |
| // Map from string representation in /proc/${PID}/mountinfo to a bit flag. |
| var optMap = map[string]MountOpt{ |
| // "rw" is valid mount option, but no bit flag will be set. |
| // If the flag does not contain MntReadonly, it is writable. |
| "rw": 0, |
| "ro": MntReadonly, |
| "nosuid": MntNosuid, |
| "nodev": MntNodev, |
| "noexec": MntNoexec, |
| "noatime": MntNoatime, |
| "nodiratime": MntNodiratime, |
| "relatime": MntRelatime, |
| "nosymfollow": MntNosymfollow, |
| } |
| |
| // MountInfo is a struct containing mount point info. |
| // TODO(chavey): crbug/1126921 - replace master in structure. |
| type MountInfo struct { |
| MountID int |
| ParentID int |
| Major int |
| Minor int |
| Root string |
| MountPath string |
| MountOpts MountOpt |
| Shared int // 0 if not shared |
| Master int // 0 if not a slave mount |
| PropagateFrom int // 0 if propagated_from is unavailable. |
| Unbindable bool |
| Fstype string |
| MountSource string |
| SuperOpts []string |
| } |
| |
| // lineRe is the regex to be matched with a line entry in /proc/${PID}/mountinfo. |
| var lineRe = regexp.MustCompile( |
| "^(\\d+) (\\d+) (\\d+):(\\d+) (\\S+) (\\S+) (\\S+)(?: shared:(\\d+))?(?: master:(\\d+))?(?: propagate_from:(\\d+))?(?: (unbindable))? - (\\S+) (\\S+) (\\S+)$") |
| |
| // String components has escaped characters for ' ', Tab, LF and '\'. |
| var unescapeRe = regexp.MustCompile(`\\040|\\011|\\012|\\134`) |
| |
| func unescape(s string) (string, error) { |
| var errs []error |
| val := unescapeRe.ReplaceAllStringFunc(s, func(c string) string { |
| u, _, _, err := strconv.UnquoteChar(c, 0) |
| if err != nil { |
| errs = append(errs, err) |
| return c |
| } |
| return string(u) |
| }) |
| if errs != nil { |
| return "", errors.Errorf("failed to unescape %q: %v", s, errs) |
| } |
| return val, nil |
| } |
| |
| // parseLine parses an entry in /proc/${PID}/mountinfo. |
| // Please see also "man proc" and show_mountinfo() in fs/proc_namespace.c for |
| // the format details. |
| func parseLine(line string) (MountInfo, error) { |
| matches := lineRe.FindStringSubmatch(line) |
| if matches == nil { |
| return MountInfo{}, errors.New("unknown format: " + line) |
| } |
| |
| mountID, err := strconv.Atoi(matches[1]) |
| if err != nil { |
| return MountInfo{}, errors.Wrap(err, "failed to parse mount_id") |
| } |
| parentID, err := strconv.Atoi(matches[2]) |
| if err != nil { |
| return MountInfo{}, errors.Wrap(err, "failed to parse parent_id") |
| } |
| major, err := strconv.Atoi(matches[3]) |
| if err != nil { |
| return MountInfo{}, errors.Wrap(err, "failed to parse major") |
| } |
| minor, err := strconv.Atoi(matches[4]) |
| if err != nil { |
| return MountInfo{}, errors.Wrap(err, "failed to parse minor") |
| } |
| root, err := unescape(matches[5]) |
| if err != nil { |
| return MountInfo{}, errors.Wrap(err, "failed to parse root") |
| } |
| mountPath, err := unescape(matches[6]) |
| if err != nil { |
| return MountInfo{}, errors.Wrap(err, "failed to parse mount path") |
| } |
| |
| var mountOpts MountOpt |
| for _, token := range strings.Split(matches[7], ",") { |
| val, ok := optMap[token] |
| if !ok { |
| return MountInfo{}, errors.New("unknwon opt token: " + token) |
| } |
| mountOpts |= val |
| } |
| |
| shared := 0 |
| if matches[8] != "" { |
| shared, err = strconv.Atoi(matches[8]) |
| if err != nil { |
| return MountInfo{}, errors.Wrap(err, "failed to parse shared") |
| } |
| } |
| |
| master := 0 |
| if matches[9] != "" { |
| master, err = strconv.Atoi(matches[9]) |
| if err != nil { |
| return MountInfo{}, errors.Wrap(err, "failed to parse master") |
| } |
| } |
| |
| propagated := 0 |
| if matches[10] != "" { |
| propagated, err = strconv.Atoi(matches[10]) |
| if err != nil { |
| return MountInfo{}, errors.Wrap(err, "failed to parse propagated_from") |
| } |
| } |
| |
| unbindable := matches[11] == "unbindable" |
| fstype, err := unescape(matches[12]) |
| if err != nil { |
| return MountInfo{}, errors.Wrap(err, "failed to parse fstype") |
| } |
| mountSource, err := unescape(matches[13]) |
| if err != nil { |
| return MountInfo{}, errors.Wrap(err, "failed to parse mount source") |
| } |
| superOpts := strings.Split(matches[14], ",") |
| |
| return MountInfo{ |
| mountID, parentID, major, minor, root, mountPath, mountOpts, |
| shared, master, propagated, unbindable, fstype, mountSource, |
| superOpts}, nil |
| } |
| |
| // ParseMountInfo parses the content of a mountinfo file using parseLine, and |
| // returns an array of mount point info. |
| func ParseMountInfo(b []byte) ([]MountInfo, error) { |
| var result []MountInfo |
| for _, line := range strings.Split(string(b), "\n") { |
| if line == "" { |
| continue |
| } |
| info, err := parseLine(line) |
| if err != nil { |
| return nil, errors.Wrap(err, "failed to parse mount info") |
| } |
| result = append(result, info) |
| } |
| return result, nil |
| } |
| |
| const ( |
| // SelfPID can be used as an argument of MountInfoForPID to return |
| // the result for the current process. |
| SelfPID = 0 |
| ) |
| |
| // MountInfoForPID reads and parses the /proc/${PID}/mountinfo, and returns |
| // an array of mount point info for the given process. |
| // pid needs to be a valid PID or SelfPID. |
| func MountInfoForPID(pid int) ([]MountInfo, error) { |
| if pid == 0 { |
| pid = os.Getpid() |
| } |
| path := fmt.Sprintf("/proc/%d/mountinfo", pid) |
| |
| b, err := ioutil.ReadFile(path) |
| if err != nil { |
| return nil, errors.Wrap(err, "failed to read: "+path) |
| } |
| |
| return ParseMountInfo(b) |
| } |