1// +build !windows
2
3/*
4Copyright 2019 The Kubernetes Authors.
5
6Licensed under the Apache License, Version 2.0 (the "License");
7you may not use this file except in compliance with the License.
8You may obtain a copy of the License at
9
10    http://www.apache.org/licenses/LICENSE-2.0
11
12Unless required by applicable law or agreed to in writing, software
13distributed under the License is distributed on an "AS IS" BASIS,
14WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15See the License for the specific language governing permissions and
16limitations under the License.
17*/
18
19package mount
20
21import (
22	"fmt"
23	"os"
24	"strconv"
25	"strings"
26	"syscall"
27
28	utilio "k8s.io/utils/io"
29)
30
31const (
32	// At least number of fields per line in /proc/<pid>/mountinfo.
33	expectedAtLeastNumFieldsPerMountInfo = 10
34	// How many times to retry for a consistent read of /proc/mounts.
35	maxListTries = 3
36)
37
38// IsCorruptedMnt return true if err is about corrupted mount point
39func IsCorruptedMnt(err error) bool {
40	if err == nil {
41		return false
42	}
43	var underlyingError error
44	switch pe := err.(type) {
45	case nil:
46		return false
47	case *os.PathError:
48		underlyingError = pe.Err
49	case *os.LinkError:
50		underlyingError = pe.Err
51	case *os.SyscallError:
52		underlyingError = pe.Err
53	}
54
55	return underlyingError == syscall.ENOTCONN || underlyingError == syscall.ESTALE || underlyingError == syscall.EIO || underlyingError == syscall.EACCES
56}
57
58// MountInfo represents a single line in /proc/<pid>/mountinfo.
59type MountInfo struct { // nolint: golint
60	// Unique ID for the mount (maybe reused after umount).
61	ID int
62	// The ID of the parent mount (or of self for the root of this mount namespace's mount tree).
63	ParentID int
64	// Major indicates one half of the device ID which identifies the device class
65	// (parsed from `st_dev` for files on this filesystem).
66	Major int
67	// Minor indicates one half of the device ID which identifies a specific
68	// instance of device (parsed from `st_dev` for files on this filesystem).
69	Minor int
70	// The pathname of the directory in the filesystem which forms the root of this mount.
71	Root string
72	// Mount source, filesystem-specific information. e.g. device, tmpfs name.
73	Source string
74	// Mount point, the pathname of the mount point.
75	MountPoint string
76	// Optional fieds, zero or more fields of the form "tag[:value]".
77	OptionalFields []string
78	// The filesystem type in the form "type[.subtype]".
79	FsType string
80	// Per-mount options.
81	MountOptions []string
82	// Per-superblock options.
83	SuperOptions []string
84}
85
86// ParseMountInfo parses /proc/xxx/mountinfo.
87func ParseMountInfo(filename string) ([]MountInfo, error) {
88	content, err := utilio.ConsistentRead(filename, maxListTries)
89	if err != nil {
90		return []MountInfo{}, err
91	}
92	contentStr := string(content)
93	infos := []MountInfo{}
94
95	for _, line := range strings.Split(contentStr, "\n") {
96		if line == "" {
97			// the last split() item is empty string following the last \n
98			continue
99		}
100		// See `man proc` for authoritative description of format of the file.
101		fields := strings.Fields(line)
102		if len(fields) < expectedAtLeastNumFieldsPerMountInfo {
103			return nil, fmt.Errorf("wrong number of fields in (expected at least %d, got %d): %s", expectedAtLeastNumFieldsPerMountInfo, len(fields), line)
104		}
105		id, err := strconv.Atoi(fields[0])
106		if err != nil {
107			return nil, err
108		}
109		parentID, err := strconv.Atoi(fields[1])
110		if err != nil {
111			return nil, err
112		}
113		mm := strings.Split(fields[2], ":")
114		if len(mm) != 2 {
115			return nil, fmt.Errorf("parsing '%s' failed: unexpected minor:major pair %s", line, mm)
116		}
117		major, err := strconv.Atoi(mm[0])
118		if err != nil {
119			return nil, fmt.Errorf("parsing '%s' failed: unable to parse major device id, err:%v", mm[0], err)
120		}
121		minor, err := strconv.Atoi(mm[1])
122		if err != nil {
123			return nil, fmt.Errorf("parsing '%s' failed: unable to parse minor device id, err:%v", mm[1], err)
124		}
125
126		info := MountInfo{
127			ID:           id,
128			ParentID:     parentID,
129			Major:        major,
130			Minor:        minor,
131			Root:         fields[3],
132			MountPoint:   fields[4],
133			MountOptions: strings.Split(fields[5], ","),
134		}
135		// All fields until "-" are "optional fields".
136		i := 6
137		for ; i < len(fields) && fields[i] != "-"; i++ {
138			info.OptionalFields = append(info.OptionalFields, fields[i])
139		}
140		// Parse the rest 3 fields.
141		i++
142		if len(fields)-i < 3 {
143			return nil, fmt.Errorf("expect 3 fields in %s, got %d", line, len(fields)-i)
144		}
145		info.FsType = fields[i]
146		info.Source = fields[i+1]
147		info.SuperOptions = strings.Split(fields[i+2], ",")
148		infos = append(infos, info)
149	}
150	return infos, nil
151}
152
153// isMountPointMatch returns true if the path in mp is the same as dir.
154// Handles case where mountpoint dir has been renamed due to stale NFS mount.
155func isMountPointMatch(mp MountPoint, dir string) bool {
156	deletedDir := fmt.Sprintf("%s\\040(deleted)", dir)
157	return ((mp.Path == dir) || (mp.Path == deletedDir))
158}
159