github.com/rigado/snapd@v2.42.5-go-mod+incompatible/osutil/mountinfo_linux.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2017 Canonical Ltd
     5   *
     6   * This program is free software: you can redistribute it and/or modify
     7   * it under the terms of the GNU General Public License version 3 as
     8   * published by the Free Software Foundation.
     9   *
    10   * This program is distributed in the hope that it will be useful,
    11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
    12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13   * GNU General Public License for more details.
    14   *
    15   * You should have received a copy of the GNU General Public License
    16   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
    17   *
    18   */
    19  
    20  package osutil
    21  
    22  import (
    23  	"bufio"
    24  	"bytes"
    25  	"fmt"
    26  	"io"
    27  	"os"
    28  	"sort"
    29  	"strconv"
    30  	"strings"
    31  )
    32  
    33  // MountInfoEntry contains data from /proc/$PID/mountinfo
    34  //
    35  // For details please refer to mountinfo documentation at
    36  // https://www.kernel.org/doc/Documentation/filesystems/proc.txt
    37  type MountInfoEntry struct {
    38  	MountID        int
    39  	ParentID       int
    40  	DevMajor       int
    41  	DevMinor       int
    42  	Root           string
    43  	MountDir       string
    44  	MountOptions   map[string]string
    45  	OptionalFields []string
    46  	FsType         string
    47  	MountSource    string
    48  	SuperOptions   map[string]string
    49  }
    50  
    51  func flattenMap(m map[string]string) string {
    52  	keys := make([]string, 0, len(m))
    53  	for key := range m {
    54  		keys = append(keys, key)
    55  	}
    56  	sort.Strings(keys)
    57  	var buf bytes.Buffer
    58  	for i, key := range keys {
    59  		if i > 0 {
    60  			buf.WriteRune(',')
    61  		}
    62  		if m[key] != "" {
    63  			fmt.Fprintf(&buf, "%s=%s", escape(key), escape(m[key]))
    64  		} else {
    65  			buf.WriteString(escape(key))
    66  		}
    67  	}
    68  	return buf.String()
    69  }
    70  
    71  func flattenList(l []string) string {
    72  	var buf bytes.Buffer
    73  	for i, item := range l {
    74  		if i > 0 {
    75  			buf.WriteRune(',')
    76  		}
    77  		buf.WriteString(escape(item))
    78  	}
    79  	return buf.String()
    80  }
    81  
    82  func (mi *MountInfoEntry) String() string {
    83  	maybeSpace := " "
    84  	if len(mi.OptionalFields) == 0 {
    85  		maybeSpace = ""
    86  	}
    87  	return fmt.Sprintf("%d %d %d:%d %s %s %s %s%s- %s %s %s",
    88  		mi.MountID, mi.ParentID, mi.DevMajor, mi.DevMinor, escape(mi.Root),
    89  		escape(mi.MountDir), flattenMap(mi.MountOptions), flattenList(mi.OptionalFields),
    90  		maybeSpace, escape(mi.FsType), escape(mi.MountSource),
    91  		flattenMap(mi.SuperOptions))
    92  }
    93  
    94  // LoadMountInfo loads list of mounted entries from a given file.
    95  //
    96  // The file is typically ProcSelfMountInfo but any other process mount table
    97  // can be read the same way.
    98  func LoadMountInfo(fname string) ([]*MountInfoEntry, error) {
    99  	f, err := os.Open(fname)
   100  	if err != nil {
   101  		return nil, err
   102  	}
   103  	defer f.Close()
   104  	return ReadMountInfo(f)
   105  }
   106  
   107  // ReadMountInfo reads and parses a mountinfo file.
   108  func ReadMountInfo(reader io.Reader) ([]*MountInfoEntry, error) {
   109  	scanner := bufio.NewScanner(reader)
   110  	var entries []*MountInfoEntry
   111  	for scanner.Scan() {
   112  		s := scanner.Text()
   113  		entry, err := ParseMountInfoEntry(s)
   114  		if err != nil {
   115  			return nil, err
   116  		}
   117  		entries = append(entries, entry)
   118  	}
   119  	if err := scanner.Err(); err != nil {
   120  		return nil, err
   121  	}
   122  	return entries, nil
   123  }
   124  
   125  // ParseMountInfoEntry parses a single line of /proc/$PID/mountinfo file.
   126  func ParseMountInfoEntry(s string) (*MountInfoEntry, error) {
   127  	var e MountInfoEntry
   128  	var err error
   129  	fields := strings.FieldsFunc(s, func(r rune) bool { return r == ' ' })
   130  
   131  	// The format is variable-length, but at least 10 fields are mandatory.
   132  	// The (7) below is a list of optional field which is terminated with (8).
   133  	// 36 35 98:0 /mnt1 /mnt2 rw,noatime master:1 - ext3 /dev/root rw,errors=continue
   134  	// (1)(2)(3)   (4)   (5)      (6)      (7)   (8) (9)   (10)         (11)
   135  	if len(fields) < 10 {
   136  		return nil, fmt.Errorf("incorrect number of fields, expected at least 10 but found %d", len(fields))
   137  	}
   138  	// Parse MountID (decimal number).
   139  	e.MountID, err = strconv.Atoi(fields[0])
   140  	if err != nil {
   141  		return nil, fmt.Errorf("cannot parse mount ID: %q", fields[0])
   142  	}
   143  	// Parse ParentID (decimal number).
   144  	e.ParentID, err = strconv.Atoi(fields[1])
   145  	if err != nil {
   146  		return nil, fmt.Errorf("cannot parse parent mount ID: %q", fields[1])
   147  	}
   148  	// Parses DevMajor:DevMinor pair (decimal numbers separated by colon).
   149  	subFields := strings.FieldsFunc(fields[2], func(r rune) bool { return r == ':' })
   150  	if len(subFields) != 2 {
   151  		return nil, fmt.Errorf("cannot parse device major:minor number pair: %q", fields[2])
   152  	}
   153  	e.DevMajor, err = strconv.Atoi(subFields[0])
   154  	if err != nil {
   155  		return nil, fmt.Errorf("cannot parse device major number: %q", subFields[0])
   156  	}
   157  	e.DevMinor, err = strconv.Atoi(subFields[1])
   158  	if err != nil {
   159  		return nil, fmt.Errorf("cannot parse device minor number: %q", subFields[1])
   160  	}
   161  	// NOTE: All string fields use the same escape/unescape logic as fstab files.
   162  	// Parse Root, MountDir and MountOptions fields.
   163  	e.Root = unescape(fields[3])
   164  	e.MountDir = unescape(fields[4])
   165  	e.MountOptions = parseMountOpts(unescape(fields[5]))
   166  	// Optional fields are terminated with a "-" value and start
   167  	// after the mount options field. Skip ahead until we see the "-"
   168  	// marker.
   169  	var i int
   170  	for i = 6; i < len(fields) && fields[i] != "-"; i++ {
   171  	}
   172  	if i == len(fields) {
   173  		return nil, fmt.Errorf("list of optional fields is not terminated properly")
   174  	}
   175  	e.OptionalFields = fields[6:i]
   176  	for j := range e.OptionalFields {
   177  		e.OptionalFields[j] = unescape(e.OptionalFields[j])
   178  	}
   179  	// Parse the last three fixed fields.
   180  	tailFields := fields[i+1:]
   181  	if len(tailFields) != 3 {
   182  		return nil, fmt.Errorf("incorrect number of tail fields, expected 3 but found %d", len(tailFields))
   183  	}
   184  	e.FsType = unescape(tailFields[0])
   185  	e.MountSource = unescape(tailFields[1])
   186  	e.SuperOptions = parseMountOpts(unescape(tailFields[2]))
   187  	return &e, nil
   188  }
   189  
   190  func parseMountOpts(opts string) map[string]string {
   191  	result := make(map[string]string)
   192  	for _, opt := range strings.Split(opts, ",") {
   193  		keyValue := strings.SplitN(opt, "=", 2)
   194  		key := keyValue[0]
   195  		if len(keyValue) == 2 {
   196  			value := keyValue[1]
   197  			result[key] = value
   198  		} else {
   199  			result[key] = ""
   200  		}
   201  	}
   202  	return result
   203  }