mirror of
https://github.com/token2/snapd.git
synced 2026-03-13 11:15:47 -07:00
* many: remove usages of deprecated io/ioutil package Signed-off-by: Miguel Pires <miguel.pires@canonical.com> * .golangci.yml: remove errcheck ignore rule for io/ioutil Signed-off-by: Miguel Pires <miguel.pires@canonical.com> * run-checks: prevent new usages of io/ioutil Signed-off-by: Miguel Pires <miguel.pires@canonical.com> --------- Signed-off-by: Miguel Pires <miguel.pires@canonical.com>
212 lines
6.4 KiB
Go
212 lines
6.4 KiB
Go
// -*- Mode: Go; indent-tabs-mode: t -*-
|
|
|
|
/*
|
|
* Copyright (C) 2017 Canonical Ltd
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 3 as
|
|
* published by the Free Software Foundation.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
*/
|
|
|
|
package osutil
|
|
|
|
import (
|
|
"bufio"
|
|
"bytes"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
func flattenMap(m map[string]string) string {
|
|
keys := make([]string, 0, len(m))
|
|
for key := range m {
|
|
keys = append(keys, key)
|
|
}
|
|
sort.Strings(keys)
|
|
var buf bytes.Buffer
|
|
for i, key := range keys {
|
|
if i > 0 {
|
|
buf.WriteRune(',')
|
|
}
|
|
if m[key] != "" {
|
|
fmt.Fprintf(&buf, "%s=%s", escape(key), escape(m[key]))
|
|
} else {
|
|
buf.WriteString(escape(key))
|
|
}
|
|
}
|
|
return buf.String()
|
|
}
|
|
|
|
func flattenList(l []string) string {
|
|
var buf bytes.Buffer
|
|
for i, item := range l {
|
|
if i > 0 {
|
|
buf.WriteRune(',')
|
|
}
|
|
buf.WriteString(escape(item))
|
|
}
|
|
return buf.String()
|
|
}
|
|
|
|
func (mi *MountInfoEntry) String() string {
|
|
maybeSpace := " "
|
|
if len(mi.OptionalFields) == 0 {
|
|
maybeSpace = ""
|
|
}
|
|
return fmt.Sprintf("%d %d %d:%d %s %s %s %s%s- %s %s %s",
|
|
mi.MountID, mi.ParentID, mi.DevMajor, mi.DevMinor, escape(mi.Root),
|
|
escape(mi.MountDir), flattenMap(mi.MountOptions), flattenList(mi.OptionalFields),
|
|
maybeSpace, escape(mi.FsType), escape(mi.MountSource),
|
|
flattenMap(mi.SuperOptions))
|
|
}
|
|
|
|
var openMountInfoFile = func() (io.ReadCloser, error) {
|
|
if IsTestBinary() {
|
|
panic("/proc/self/mountinfo must be mocked in tests")
|
|
}
|
|
return os.Open("/proc/self/mountinfo")
|
|
}
|
|
|
|
func mockMountInfo(mock func() (io.ReadCloser, error)) (restore func()) {
|
|
old := openMountInfoFile
|
|
openMountInfoFile = mock
|
|
return func() {
|
|
openMountInfoFile = old
|
|
}
|
|
}
|
|
|
|
// this should not be used except to test the actual implementation logic of
|
|
// LoadMountInfo, if you are trying to mock /proc/self/mountinfo in a test,
|
|
// use MockMountInfo(), which is exported and the right way to do that.
|
|
func MockProcSelfMountInfoLocation(filename string) (restore func()) {
|
|
return mockMountInfo(func() (io.ReadCloser, error) { return os.Open(filename) })
|
|
}
|
|
|
|
func MockMountInfo(content string) (restore func()) {
|
|
return mockMountInfo(func() (io.ReadCloser, error) { return io.NopCloser(bytes.NewBufferString(content)), nil })
|
|
}
|
|
|
|
// LoadMountInfo loads list of mounted entries from /proc/self/mountinfo. This
|
|
// can be mocked by using osutil.MockMountInfo to hard-code a specific mountinfo
|
|
// file content to be loaded by this function
|
|
func LoadMountInfo() ([]*MountInfoEntry, error) {
|
|
f, err := openMountInfoFile()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer f.Close()
|
|
return ReadMountInfo(f)
|
|
}
|
|
|
|
// ReadMountInfo reads and parses a mountinfo file.
|
|
func ReadMountInfo(reader io.Reader) ([]*MountInfoEntry, error) {
|
|
scanner := bufio.NewScanner(reader)
|
|
var entries []*MountInfoEntry
|
|
for scanner.Scan() {
|
|
s := scanner.Text()
|
|
entry, err := ParseMountInfoEntry(s)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
entries = append(entries, entry)
|
|
}
|
|
if err := scanner.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return entries, nil
|
|
}
|
|
|
|
// ParseMountInfoEntry parses a single line of /proc/$PID/mountinfo file.
|
|
func ParseMountInfoEntry(s string) (*MountInfoEntry, error) {
|
|
var e MountInfoEntry
|
|
var err error
|
|
fields := strings.FieldsFunc(s, func(r rune) bool { return r == ' ' })
|
|
|
|
// The format is variable-length, but at least 10 fields are mandatory.
|
|
// The (7) below is a list of optional field which is terminated with (8).
|
|
// 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)
|
|
if len(fields) < 10 {
|
|
return nil, fmt.Errorf("incorrect number of fields, expected at least 10 but found %d", len(fields))
|
|
}
|
|
// Parse MountID (decimal number).
|
|
e.MountID, err = strconv.Atoi(fields[0])
|
|
if err != nil {
|
|
return nil, fmt.Errorf("cannot parse mount ID: %q", fields[0])
|
|
}
|
|
// Parse ParentID (decimal number).
|
|
e.ParentID, err = strconv.Atoi(fields[1])
|
|
if err != nil {
|
|
return nil, fmt.Errorf("cannot parse parent mount ID: %q", fields[1])
|
|
}
|
|
// Parses DevMajor:DevMinor pair (decimal numbers separated by colon).
|
|
subFields := strings.FieldsFunc(fields[2], func(r rune) bool { return r == ':' })
|
|
if len(subFields) != 2 {
|
|
return nil, fmt.Errorf("cannot parse device major:minor number pair: %q", fields[2])
|
|
}
|
|
e.DevMajor, err = strconv.Atoi(subFields[0])
|
|
if err != nil {
|
|
return nil, fmt.Errorf("cannot parse device major number: %q", subFields[0])
|
|
}
|
|
e.DevMinor, err = strconv.Atoi(subFields[1])
|
|
if err != nil {
|
|
return nil, fmt.Errorf("cannot parse device minor number: %q", subFields[1])
|
|
}
|
|
// NOTE: All string fields use the same escape/unescape logic as fstab files.
|
|
// Parse Root, MountDir and MountOptions fields.
|
|
e.Root = unescape(fields[3])
|
|
e.MountDir = unescape(fields[4])
|
|
e.MountOptions = parseMountOpts(unescape(fields[5]))
|
|
// Optional fields are terminated with a "-" value and start
|
|
// after the mount options field. Skip ahead until we see the "-"
|
|
// marker.
|
|
var i int
|
|
for i = 6; i < len(fields) && fields[i] != "-"; i++ {
|
|
}
|
|
if i == len(fields) {
|
|
return nil, fmt.Errorf("list of optional fields is not terminated properly")
|
|
}
|
|
e.OptionalFields = fields[6:i]
|
|
for j := range e.OptionalFields {
|
|
e.OptionalFields[j] = unescape(e.OptionalFields[j])
|
|
}
|
|
// Parse the last three fixed fields.
|
|
tailFields := fields[i+1:]
|
|
// XXX: The last field (options) *may* contain spaces that are incorrectly escaped by some file-systems.
|
|
if len(tailFields) < 3 {
|
|
return nil, fmt.Errorf("incorrect number of tail fields, expected 3 but found %d", len(tailFields))
|
|
}
|
|
e.FsType = unescape(tailFields[0])
|
|
e.MountSource = unescape(tailFields[1])
|
|
e.SuperOptions = parseMountOpts(unescape(strings.Join(tailFields[2:], " ")))
|
|
return &e, nil
|
|
}
|
|
|
|
func parseMountOpts(opts string) map[string]string {
|
|
result := make(map[string]string)
|
|
for _, opt := range strings.Split(opts, ",") {
|
|
keyValue := strings.SplitN(opt, "=", 2)
|
|
key := keyValue[0]
|
|
if len(keyValue) == 2 {
|
|
value := keyValue[1]
|
|
result[key] = value
|
|
} else {
|
|
result[key] = ""
|
|
}
|
|
}
|
|
return result
|
|
}
|