2020-12-17 12:28:29 +00:00
|
|
|
// +build !windows
|
|
|
|
|
|
|
|
/*
|
|
|
|
Copyright 2019 The Kubernetes Authors.
|
|
|
|
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
you may not use this file except in compliance with the License.
|
|
|
|
You may obtain a copy of the License at
|
|
|
|
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
See the License for the specific language governing permissions and
|
|
|
|
limitations under the License.
|
|
|
|
*/
|
|
|
|
|
|
|
|
package mount
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"os"
|
|
|
|
"strconv"
|
|
|
|
"strings"
|
|
|
|
"syscall"
|
|
|
|
|
|
|
|
utilio "k8s.io/utils/io"
|
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
// At least number of fields per line in /proc/<pid>/mountinfo.
|
|
|
|
expectedAtLeastNumFieldsPerMountInfo = 10
|
|
|
|
// How many times to retry for a consistent read of /proc/mounts.
|
2021-08-09 07:19:24 +00:00
|
|
|
maxListTries = 10
|
2020-12-17 12:28:29 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
// IsCorruptedMnt return true if err is about corrupted mount point
|
|
|
|
func IsCorruptedMnt(err error) bool {
|
|
|
|
if err == nil {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
var underlyingError error
|
|
|
|
switch pe := err.(type) {
|
|
|
|
case nil:
|
|
|
|
return false
|
|
|
|
case *os.PathError:
|
|
|
|
underlyingError = pe.Err
|
|
|
|
case *os.LinkError:
|
|
|
|
underlyingError = pe.Err
|
|
|
|
case *os.SyscallError:
|
|
|
|
underlyingError = pe.Err
|
|
|
|
}
|
|
|
|
|
2021-06-25 04:59:51 +00:00
|
|
|
return underlyingError == syscall.ENOTCONN || underlyingError == syscall.ESTALE || underlyingError == syscall.EIO || underlyingError == syscall.EACCES || underlyingError == syscall.EHOSTDOWN
|
2020-12-17 12:28:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// MountInfo represents a single line in /proc/<pid>/mountinfo.
|
|
|
|
type MountInfo struct { // nolint: golint
|
|
|
|
// Unique ID for the mount (maybe reused after umount).
|
|
|
|
ID int
|
|
|
|
// The ID of the parent mount (or of self for the root of this mount namespace's mount tree).
|
|
|
|
ParentID int
|
|
|
|
// Major indicates one half of the device ID which identifies the device class
|
|
|
|
// (parsed from `st_dev` for files on this filesystem).
|
|
|
|
Major int
|
|
|
|
// Minor indicates one half of the device ID which identifies a specific
|
|
|
|
// instance of device (parsed from `st_dev` for files on this filesystem).
|
|
|
|
Minor int
|
|
|
|
// The pathname of the directory in the filesystem which forms the root of this mount.
|
|
|
|
Root string
|
|
|
|
// Mount source, filesystem-specific information. e.g. device, tmpfs name.
|
|
|
|
Source string
|
|
|
|
// Mount point, the pathname of the mount point.
|
|
|
|
MountPoint string
|
|
|
|
// Optional fieds, zero or more fields of the form "tag[:value]".
|
|
|
|
OptionalFields []string
|
|
|
|
// The filesystem type in the form "type[.subtype]".
|
|
|
|
FsType string
|
|
|
|
// Per-mount options.
|
|
|
|
MountOptions []string
|
|
|
|
// Per-superblock options.
|
|
|
|
SuperOptions []string
|
|
|
|
}
|
|
|
|
|
|
|
|
// ParseMountInfo parses /proc/xxx/mountinfo.
|
|
|
|
func ParseMountInfo(filename string) ([]MountInfo, error) {
|
|
|
|
content, err := utilio.ConsistentRead(filename, maxListTries)
|
|
|
|
if err != nil {
|
|
|
|
return []MountInfo{}, err
|
|
|
|
}
|
|
|
|
contentStr := string(content)
|
|
|
|
infos := []MountInfo{}
|
|
|
|
|
|
|
|
for _, line := range strings.Split(contentStr, "\n") {
|
|
|
|
if line == "" {
|
|
|
|
// the last split() item is empty string following the last \n
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
// See `man proc` for authoritative description of format of the file.
|
|
|
|
fields := strings.Fields(line)
|
|
|
|
if len(fields) < expectedAtLeastNumFieldsPerMountInfo {
|
|
|
|
return nil, fmt.Errorf("wrong number of fields in (expected at least %d, got %d): %s", expectedAtLeastNumFieldsPerMountInfo, len(fields), line)
|
|
|
|
}
|
|
|
|
id, err := strconv.Atoi(fields[0])
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
parentID, err := strconv.Atoi(fields[1])
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
mm := strings.Split(fields[2], ":")
|
|
|
|
if len(mm) != 2 {
|
|
|
|
return nil, fmt.Errorf("parsing '%s' failed: unexpected minor:major pair %s", line, mm)
|
|
|
|
}
|
|
|
|
major, err := strconv.Atoi(mm[0])
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("parsing '%s' failed: unable to parse major device id, err:%v", mm[0], err)
|
|
|
|
}
|
|
|
|
minor, err := strconv.Atoi(mm[1])
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("parsing '%s' failed: unable to parse minor device id, err:%v", mm[1], err)
|
|
|
|
}
|
|
|
|
|
|
|
|
info := MountInfo{
|
|
|
|
ID: id,
|
|
|
|
ParentID: parentID,
|
|
|
|
Major: major,
|
|
|
|
Minor: minor,
|
|
|
|
Root: fields[3],
|
|
|
|
MountPoint: fields[4],
|
|
|
|
MountOptions: strings.Split(fields[5], ","),
|
|
|
|
}
|
|
|
|
// All fields until "-" are "optional fields".
|
|
|
|
i := 6
|
|
|
|
for ; i < len(fields) && fields[i] != "-"; i++ {
|
|
|
|
info.OptionalFields = append(info.OptionalFields, fields[i])
|
|
|
|
}
|
|
|
|
// Parse the rest 3 fields.
|
|
|
|
i++
|
|
|
|
if len(fields)-i < 3 {
|
|
|
|
return nil, fmt.Errorf("expect 3 fields in %s, got %d", line, len(fields)-i)
|
|
|
|
}
|
|
|
|
info.FsType = fields[i]
|
|
|
|
info.Source = fields[i+1]
|
|
|
|
info.SuperOptions = strings.Split(fields[i+2], ",")
|
|
|
|
infos = append(infos, info)
|
|
|
|
}
|
|
|
|
return infos, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// isMountPointMatch returns true if the path in mp is the same as dir.
|
|
|
|
// Handles case where mountpoint dir has been renamed due to stale NFS mount.
|
|
|
|
func isMountPointMatch(mp MountPoint, dir string) bool {
|
|
|
|
deletedDir := fmt.Sprintf("%s\\040(deleted)", dir)
|
|
|
|
return ((mp.Path == dir) || (mp.Path == deletedDir))
|
|
|
|
}
|