github.com/containerd/containerd@v22.0.0-20200918172823-438c87b8e050+incompatible/mount/mountinfo_linux.go (about)

     1  // +build linux
     2  
     3  /*
     4     Copyright The containerd Authors.
     5  
     6     Licensed under the Apache License, Version 2.0 (the "License");
     7     you may not use this file except in compliance with the License.
     8     You may obtain a copy of the License at
     9  
    10         http://www.apache.org/licenses/LICENSE-2.0
    11  
    12     Unless required by applicable law or agreed to in writing, software
    13     distributed under the License is distributed on an "AS IS" BASIS,
    14     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    15     See the License for the specific language governing permissions and
    16     limitations under the License.
    17  */
    18  
    19  package mount
    20  
    21  import (
    22  	"bufio"
    23  	"fmt"
    24  	"io"
    25  	"os"
    26  	"strconv"
    27  	"strings"
    28  
    29  	"github.com/pkg/errors"
    30  )
    31  
    32  // Self retrieves a list of mounts for the current running process.
    33  func Self() ([]Info, error) {
    34  	f, err := os.Open("/proc/self/mountinfo")
    35  	if err != nil {
    36  		return nil, err
    37  	}
    38  	defer f.Close()
    39  
    40  	return parseInfoFile(f)
    41  }
    42  
    43  func parseInfoFile(r io.Reader) ([]Info, error) {
    44  	s := bufio.NewScanner(r)
    45  	out := []Info{}
    46  	var err error
    47  	for s.Scan() {
    48  		/*
    49  		   See http://man7.org/linux/man-pages/man5/proc.5.html
    50  
    51  		   36 35 98:0 /mnt1 /mnt2 rw,noatime master:1 - ext3 /dev/root rw,errors=continue
    52  		   (1)(2)(3)   (4)   (5)      (6)      (7)   (8) (9)   (10)         (11)
    53  		   (1) mount ID:  unique identifier of the mount (may be reused after umount)
    54  		   (2) parent ID:  ID of parent (or of self for the top of the mount tree)
    55  		   (3) major:minor:  value of st_dev for files on filesystem
    56  		   (4) root:  root of the mount within the filesystem
    57  		   (5) mount point:  mount point relative to the process's root
    58  		   (6) mount options:  per mount options
    59  		   (7) optional fields:  zero or more fields of the form "tag[:value]"
    60  		   (8) separator:  marks the end of the optional fields
    61  		   (9) filesystem type:  name of filesystem of the form "type[.subtype]"
    62  		   (10) mount source:  filesystem specific information or "none"
    63  		   (11) super options:  per super block options
    64  		*/
    65  
    66  		text := s.Text()
    67  		fields := strings.Split(text, " ")
    68  		numFields := len(fields)
    69  		if numFields < 10 {
    70  			// should be at least 10 fields
    71  			return nil, errors.Errorf("parsing '%s' failed: not enough fields (%d)", text, numFields)
    72  		}
    73  		p := Info{}
    74  		// ignore any numbers parsing errors, as there should not be any
    75  		p.ID, _ = strconv.Atoi(fields[0])
    76  		p.Parent, _ = strconv.Atoi(fields[1])
    77  		mm := strings.Split(fields[2], ":")
    78  		if len(mm) != 2 {
    79  			return nil, errors.Errorf("parsing '%s' failed: unexpected minor:major pair %s", text, mm)
    80  		}
    81  		p.Major, _ = strconv.Atoi(mm[0])
    82  		p.Minor, _ = strconv.Atoi(mm[1])
    83  
    84  		p.Root, err = strconv.Unquote(`"` + strings.Replace(fields[3], `"`, `\"`, -1) + `"`)
    85  		if err != nil {
    86  			return nil, errors.Wrapf(err, "parsing '%s' failed: unable to unquote root field", fields[3])
    87  		}
    88  		p.Mountpoint, err = strconv.Unquote(`"` + strings.Replace(fields[4], `"`, `\"`, -1) + `"`)
    89  		if err != nil {
    90  			return nil, errors.Wrapf(err, "parsing '%s' failed: unable to unquote mount point field", fields[4])
    91  		}
    92  		p.Options = fields[5]
    93  
    94  		// one or more optional fields, when a separator (-)
    95  		i := 6
    96  		for ; i < numFields && fields[i] != "-"; i++ {
    97  			switch i {
    98  			case 6:
    99  				p.Optional = fields[6]
   100  			default:
   101  				/* NOTE there might be more optional fields before the separator
   102  				   such as fields[7]...fields[N] (where N < separatorIndex),
   103  				   although as of Linux kernel 4.15 the only known ones are
   104  				   mount propagation flags in fields[6]. The correct
   105  				   behavior is to ignore any unknown optional fields.
   106  				*/
   107  			}
   108  		}
   109  		if i == numFields {
   110  			return nil, errors.Errorf("parsing '%s' failed: missing separator ('-')", text)
   111  		}
   112  		// There should be 3 fields after the separator...
   113  		if i+4 > numFields {
   114  			return nil, errors.Errorf("parsing '%s' failed: not enough fields after a separator", text)
   115  		}
   116  		// ... but in Linux <= 3.9 mounting a cifs with spaces in a share name
   117  		// (like "//serv/My Documents") _may_ end up having a space in the last field
   118  		// of mountinfo (like "unc=//serv/My Documents"). Since kernel 3.10-rc1, cifs
   119  		// option unc= is ignored,  so a space should not appear. In here we ignore
   120  		// those "extra" fields caused by extra spaces.
   121  		p.FSType = fields[i+1]
   122  		p.Source = fields[i+2]
   123  		p.VFSOptions = fields[i+3]
   124  
   125  		out = append(out, p)
   126  	}
   127  	if err = s.Err(); err != nil {
   128  		return nil, err
   129  	}
   130  
   131  	return out, nil
   132  }
   133  
   134  // PID collects the mounts for a specific process ID. If the process
   135  // ID is unknown, it is better to use `Self` which will inspect
   136  // "/proc/self/mountinfo" instead.
   137  func PID(pid int) ([]Info, error) {
   138  	f, err := os.Open(fmt.Sprintf("/proc/%d/mountinfo", pid))
   139  	if err != nil {
   140  		return nil, err
   141  	}
   142  	defer f.Close()
   143  
   144  	return parseInfoFile(f)
   145  }