github.com/google/cadvisor@v0.49.1/fs/fs.go (about)

     1  // Copyright 2014 Google Inc. All Rights Reserved.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  //go:build linux
    16  // +build linux
    17  
    18  // Provides Filesystem Stats
    19  package fs
    20  
    21  import (
    22  	"bufio"
    23  	"fmt"
    24  	"os"
    25  	"os/exec"
    26  	"path"
    27  	"path/filepath"
    28  	"regexp"
    29  	"strconv"
    30  	"strings"
    31  	"syscall"
    32  
    33  	zfs "github.com/mistifyio/go-zfs"
    34  	mount "github.com/moby/sys/mountinfo"
    35  
    36  	"github.com/google/cadvisor/devicemapper"
    37  	"github.com/google/cadvisor/utils"
    38  
    39  	"k8s.io/klog/v2"
    40  )
    41  
    42  const (
    43  	LabelSystemRoot          = "root"
    44  	LabelDockerImages        = "docker-images"
    45  	LabelCrioImages          = "crio-images"
    46  	LabelCrioContainers      = "crio-containers"
    47  	DriverStatusPoolName     = "Pool Name"
    48  	DriverStatusDataLoopFile = "Data loop file"
    49  )
    50  
    51  const (
    52  	// The block size in bytes.
    53  	statBlockSize uint64 = 512
    54  	// The maximum number of `disk usage` tasks that can be running at once.
    55  	maxConcurrentOps = 20
    56  )
    57  
    58  // A pool for restricting the number of consecutive `du` and `find` tasks running.
    59  var pool = make(chan struct{}, maxConcurrentOps)
    60  
    61  func init() {
    62  	for i := 0; i < maxConcurrentOps; i++ {
    63  		releaseToken()
    64  	}
    65  }
    66  
    67  func claimToken() {
    68  	<-pool
    69  }
    70  
    71  func releaseToken() {
    72  	pool <- struct{}{}
    73  }
    74  
    75  type partition struct {
    76  	mountpoint string
    77  	major      uint
    78  	minor      uint
    79  	fsType     string
    80  	blockSize  uint
    81  }
    82  
    83  type RealFsInfo struct {
    84  	// Map from block device path to partition information.
    85  	partitions map[string]partition
    86  	// Map from label to block device path.
    87  	// Labels are intent-specific tags that are auto-detected.
    88  	labels map[string]string
    89  	// Map from mountpoint to mount information.
    90  	mounts map[string]mount.Info
    91  	// devicemapper client
    92  	dmsetup devicemapper.DmsetupClient
    93  	// fsUUIDToDeviceName is a map from the filesystem UUID to its device name.
    94  	fsUUIDToDeviceName map[string]string
    95  }
    96  
    97  func NewFsInfo(context Context) (FsInfo, error) {
    98  	fileReader, err := os.Open("/proc/self/mountinfo")
    99  	if err != nil {
   100  		return nil, err
   101  	}
   102  	mounts, err := mount.GetMountsFromReader(fileReader, nil)
   103  	if err != nil {
   104  		return nil, err
   105  	}
   106  
   107  	fsUUIDToDeviceName, err := getFsUUIDToDeviceNameMap()
   108  	if err != nil {
   109  		// UUID is not always available across different OS distributions.
   110  		// Do not fail if there is an error.
   111  		klog.Warningf("Failed to get disk UUID mapping, getting disk info by uuid will not work: %v", err)
   112  	}
   113  
   114  	// Avoid devicemapper container mounts - these are tracked by the ThinPoolWatcher
   115  	excluded := []string{fmt.Sprintf("%s/devicemapper/mnt", context.Docker.Root)}
   116  	fsInfo := &RealFsInfo{
   117  		partitions:         processMounts(mounts, excluded),
   118  		labels:             make(map[string]string),
   119  		mounts:             make(map[string]mount.Info),
   120  		dmsetup:            devicemapper.NewDmsetupClient(),
   121  		fsUUIDToDeviceName: fsUUIDToDeviceName,
   122  	}
   123  
   124  	for _, mnt := range mounts {
   125  		fsInfo.mounts[mnt.Mountpoint] = *mnt
   126  	}
   127  
   128  	// need to call this before the log line below printing out the partitions, as this function may
   129  	// add a "partition" for devicemapper to fsInfo.partitions
   130  	fsInfo.addDockerImagesLabel(context, mounts)
   131  	fsInfo.addCrioImagesLabel(context, mounts)
   132  
   133  	klog.V(1).Infof("Filesystem UUIDs: %+v", fsInfo.fsUUIDToDeviceName)
   134  	klog.V(1).Infof("Filesystem partitions: %+v", fsInfo.partitions)
   135  	fsInfo.addSystemRootLabel(mounts)
   136  	return fsInfo, nil
   137  }
   138  
   139  // getFsUUIDToDeviceNameMap creates the filesystem uuid to device name map
   140  // using the information in /dev/disk/by-uuid. If the directory does not exist,
   141  // this function will return an empty map.
   142  func getFsUUIDToDeviceNameMap() (map[string]string, error) {
   143  	const dir = "/dev/disk/by-uuid"
   144  
   145  	if _, err := os.Stat(dir); os.IsNotExist(err) {
   146  		return make(map[string]string), nil
   147  	}
   148  
   149  	files, err := os.ReadDir(dir)
   150  	if err != nil {
   151  		return nil, err
   152  	}
   153  
   154  	fsUUIDToDeviceName := make(map[string]string)
   155  	for _, file := range files {
   156  		fpath := filepath.Join(dir, file.Name())
   157  		target, err := os.Readlink(fpath)
   158  		if err != nil {
   159  			klog.Warningf("Failed to resolve symlink for %q", fpath)
   160  			continue
   161  		}
   162  		device, err := filepath.Abs(filepath.Join(dir, target))
   163  		if err != nil {
   164  			return nil, fmt.Errorf("failed to resolve the absolute path of %q", filepath.Join(dir, target))
   165  		}
   166  		fsUUIDToDeviceName[file.Name()] = device
   167  	}
   168  	return fsUUIDToDeviceName, nil
   169  }
   170  
   171  func processMounts(mounts []*mount.Info, excludedMountpointPrefixes []string) map[string]partition {
   172  	partitions := make(map[string]partition)
   173  
   174  	supportedFsType := map[string]bool{
   175  		// all ext and nfs systems are checked through prefix
   176  		// because there are a number of families (e.g., ext3, ext4, nfs3, nfs4...)
   177  		"btrfs":   true,
   178  		"overlay": true,
   179  		"tmpfs":   true,
   180  		"xfs":     true,
   181  		"zfs":     true,
   182  	}
   183  
   184  	for _, mnt := range mounts {
   185  		if !strings.HasPrefix(mnt.FSType, "ext") && !strings.HasPrefix(mnt.FSType, "nfs") &&
   186  			!supportedFsType[mnt.FSType] {
   187  			continue
   188  		}
   189  		// Avoid bind mounts, exclude tmpfs.
   190  		if _, ok := partitions[mnt.Source]; ok {
   191  			if mnt.FSType != "tmpfs" {
   192  				continue
   193  			}
   194  		}
   195  
   196  		hasPrefix := false
   197  		for _, prefix := range excludedMountpointPrefixes {
   198  			if strings.HasPrefix(mnt.Mountpoint, prefix) {
   199  				hasPrefix = true
   200  				break
   201  			}
   202  		}
   203  		if hasPrefix {
   204  			continue
   205  		}
   206  
   207  		// using mountpoint to replace device once fstype it tmpfs
   208  		if mnt.FSType == "tmpfs" {
   209  			mnt.Source = mnt.Mountpoint
   210  		}
   211  		// btrfs fix: following workaround fixes wrong btrfs Major and Minor Ids reported in /proc/self/mountinfo.
   212  		// instead of using values from /proc/self/mountinfo we use stat to get Ids from btrfs mount point
   213  		if mnt.FSType == "btrfs" && mnt.Major == 0 && strings.HasPrefix(mnt.Source, "/dev/") {
   214  			major, minor, err := getBtrfsMajorMinorIds(mnt)
   215  			if err != nil {
   216  				klog.Warningf("%s", err)
   217  			} else {
   218  				mnt.Major = major
   219  				mnt.Minor = minor
   220  			}
   221  		}
   222  
   223  		// overlay fix: Making mount source unique for all overlay mounts, using the mount's major and minor ids.
   224  		if mnt.FSType == "overlay" {
   225  			mnt.Source = fmt.Sprintf("%s_%d-%d", mnt.Source, mnt.Major, mnt.Minor)
   226  		}
   227  
   228  		partitions[mnt.Source] = partition{
   229  			fsType:     mnt.FSType,
   230  			mountpoint: mnt.Mountpoint,
   231  			major:      uint(mnt.Major),
   232  			minor:      uint(mnt.Minor),
   233  		}
   234  	}
   235  
   236  	return partitions
   237  }
   238  
   239  // getDockerDeviceMapperInfo returns information about the devicemapper device and "partition" if
   240  // docker is using devicemapper for its storage driver. If a loopback device is being used, don't
   241  // return any information or error, as we want to report based on the actual partition where the
   242  // loopback file resides, inside of the loopback file itself.
   243  func (i *RealFsInfo) getDockerDeviceMapperInfo(context DockerContext) (string, *partition, error) {
   244  	if context.Driver != DeviceMapper.String() {
   245  		return "", nil, nil
   246  	}
   247  
   248  	dataLoopFile := context.DriverStatus[DriverStatusDataLoopFile]
   249  	if len(dataLoopFile) > 0 {
   250  		return "", nil, nil
   251  	}
   252  
   253  	dev, major, minor, blockSize, err := dockerDMDevice(context.DriverStatus, i.dmsetup)
   254  	if err != nil {
   255  		return "", nil, err
   256  	}
   257  
   258  	return dev, &partition{
   259  		fsType:    DeviceMapper.String(),
   260  		major:     major,
   261  		minor:     minor,
   262  		blockSize: blockSize,
   263  	}, nil
   264  }
   265  
   266  // addSystemRootLabel attempts to determine which device contains the mount for /.
   267  func (i *RealFsInfo) addSystemRootLabel(mounts []*mount.Info) {
   268  	for _, m := range mounts {
   269  		if m.Mountpoint == "/" {
   270  			i.partitions[m.Source] = partition{
   271  				fsType:     m.FSType,
   272  				mountpoint: m.Mountpoint,
   273  				major:      uint(m.Major),
   274  				minor:      uint(m.Minor),
   275  			}
   276  			i.labels[LabelSystemRoot] = m.Source
   277  			return
   278  		}
   279  	}
   280  }
   281  
   282  // addDockerImagesLabel attempts to determine which device contains the mount for docker images.
   283  func (i *RealFsInfo) addDockerImagesLabel(context Context, mounts []*mount.Info) {
   284  	if context.Docker.Driver != "" {
   285  		dockerDev, dockerPartition, err := i.getDockerDeviceMapperInfo(context.Docker)
   286  		if err != nil {
   287  			klog.Warningf("Could not get Docker devicemapper device: %v", err)
   288  		}
   289  		if len(dockerDev) > 0 && dockerPartition != nil {
   290  			i.partitions[dockerDev] = *dockerPartition
   291  			i.labels[LabelDockerImages] = dockerDev
   292  		} else {
   293  			i.updateContainerImagesPath(LabelDockerImages, mounts, getDockerImagePaths(context))
   294  		}
   295  	}
   296  }
   297  
   298  func (i *RealFsInfo) addCrioImagesLabel(context Context, mounts []*mount.Info) {
   299  	labelCrioImageOrContainers := LabelCrioContainers
   300  	// If imagestore is not specified, let's fall back to the original case.
   301  	// Everything will be stored in crio-images
   302  	if context.Crio.ImageStore == "" {
   303  		labelCrioImageOrContainers = LabelCrioImages
   304  	}
   305  	if context.Crio.Root != "" {
   306  		crioPath := context.Crio.Root
   307  		crioImagePaths := map[string]struct{}{
   308  			"/": {},
   309  		}
   310  		imageOrContainerPath := context.Crio.Driver + "-containers"
   311  		if context.Crio.ImageStore == "" {
   312  			// If ImageStore is not specified then we will assume ImageFs is complete separate.
   313  			// No need to split the image store.
   314  			imageOrContainerPath = context.Crio.Driver + "-images"
   315  
   316  		}
   317  		crioImagePaths[path.Join(crioPath, imageOrContainerPath)] = struct{}{}
   318  		for crioPath != "/" && crioPath != "." {
   319  			crioImagePaths[crioPath] = struct{}{}
   320  			crioPath = filepath.Dir(crioPath)
   321  		}
   322  		i.updateContainerImagesPath(labelCrioImageOrContainers, mounts, crioImagePaths)
   323  	}
   324  	if context.Crio.ImageStore != "" {
   325  		crioPath := context.Crio.ImageStore
   326  		crioImagePaths := map[string]struct{}{
   327  			"/": {},
   328  		}
   329  		crioImagePaths[path.Join(crioPath, context.Crio.Driver+"-images")] = struct{}{}
   330  		for crioPath != "/" && crioPath != "." {
   331  			crioImagePaths[crioPath] = struct{}{}
   332  			crioPath = filepath.Dir(crioPath)
   333  		}
   334  		i.updateContainerImagesPath(LabelCrioImages, mounts, crioImagePaths)
   335  	}
   336  }
   337  
   338  // Generate a list of possible mount points for docker image management from the docker root directory.
   339  // Right now, we look for each type of supported graph driver directories, but we can do better by parsing
   340  // some of the context from `docker info`.
   341  func getDockerImagePaths(context Context) map[string]struct{} {
   342  	dockerImagePaths := map[string]struct{}{
   343  		"/": {},
   344  	}
   345  
   346  	// TODO(rjnagal): Detect docker root and graphdriver directories from docker info.
   347  	dockerRoot := context.Docker.Root
   348  	for _, dir := range []string{"devicemapper", "btrfs", "aufs", "overlay", "overlay2", "zfs"} {
   349  		dockerImagePaths[path.Join(dockerRoot, dir)] = struct{}{}
   350  	}
   351  	for dockerRoot != "/" && dockerRoot != "." {
   352  		dockerImagePaths[dockerRoot] = struct{}{}
   353  		dockerRoot = filepath.Dir(dockerRoot)
   354  	}
   355  	return dockerImagePaths
   356  }
   357  
   358  // This method compares the mountpoints with possible container image mount points. If a match is found,
   359  // the label is added to the partition.
   360  func (i *RealFsInfo) updateContainerImagesPath(label string, mounts []*mount.Info, containerImagePaths map[string]struct{}) {
   361  	var useMount *mount.Info
   362  	for _, m := range mounts {
   363  		if _, ok := containerImagePaths[m.Mountpoint]; ok {
   364  			if useMount == nil || (len(useMount.Mountpoint) < len(m.Mountpoint)) {
   365  				useMount = m
   366  			}
   367  		}
   368  	}
   369  	if useMount != nil {
   370  		i.partitions[useMount.Source] = partition{
   371  			fsType:     useMount.FSType,
   372  			mountpoint: useMount.Mountpoint,
   373  			major:      uint(useMount.Major),
   374  			minor:      uint(useMount.Minor),
   375  		}
   376  		i.labels[label] = useMount.Source
   377  	}
   378  }
   379  
   380  func (i *RealFsInfo) GetDeviceForLabel(label string) (string, error) {
   381  	dev, ok := i.labels[label]
   382  	if !ok {
   383  		return "", fmt.Errorf("non-existent label %q", label)
   384  	}
   385  	return dev, nil
   386  }
   387  
   388  func (i *RealFsInfo) GetLabelsForDevice(device string) ([]string, error) {
   389  	var labels []string
   390  	for label, dev := range i.labels {
   391  		if dev == device {
   392  			labels = append(labels, label)
   393  		}
   394  	}
   395  	return labels, nil
   396  }
   397  
   398  func (i *RealFsInfo) GetMountpointForDevice(dev string) (string, error) {
   399  	p, ok := i.partitions[dev]
   400  	if !ok {
   401  		return "", fmt.Errorf("no partition info for device %q", dev)
   402  	}
   403  	return p.mountpoint, nil
   404  }
   405  
   406  func (i *RealFsInfo) GetFsInfoForPath(mountSet map[string]struct{}) ([]Fs, error) {
   407  	filesystems := make([]Fs, 0)
   408  	deviceSet := make(map[string]struct{})
   409  	diskStatsMap, err := getDiskStatsMap("/proc/diskstats")
   410  	if err != nil {
   411  		return nil, err
   412  	}
   413  	nfsInfo := make(map[string]Fs, 0)
   414  	for device, partition := range i.partitions {
   415  		_, hasMount := mountSet[partition.mountpoint]
   416  		_, hasDevice := deviceSet[device]
   417  		if mountSet == nil || (hasMount && !hasDevice) {
   418  			var (
   419  				err error
   420  				fs  Fs
   421  			)
   422  			fsType := partition.fsType
   423  			if strings.HasPrefix(partition.fsType, "nfs") {
   424  				fsType = "nfs"
   425  			}
   426  			switch fsType {
   427  			case DeviceMapper.String():
   428  				fs.Capacity, fs.Free, fs.Available, err = getDMStats(device, partition.blockSize)
   429  				klog.V(5).Infof("got devicemapper fs capacity stats: capacity: %v free: %v available: %v:", fs.Capacity, fs.Free, fs.Available)
   430  				fs.Type = DeviceMapper
   431  			case ZFS.String():
   432  				if _, devzfs := os.Stat("/dev/zfs"); os.IsExist(devzfs) {
   433  					fs.Capacity, fs.Free, fs.Available, err = getZfstats(device)
   434  					fs.Type = ZFS
   435  					break
   436  				}
   437  				// if /dev/zfs is not present default to VFS
   438  				fallthrough
   439  			case NFS.String():
   440  				devId := fmt.Sprintf("%d:%d", partition.major, partition.minor)
   441  				if v, ok := nfsInfo[devId]; ok {
   442  					fs = v
   443  					break
   444  				}
   445  				var inodes, inodesFree uint64
   446  				fs.Capacity, fs.Free, fs.Available, inodes, inodesFree, err = getVfsStats(partition.mountpoint)
   447  				if err != nil {
   448  					klog.V(4).Infof("the file system type is %s, partition mountpoint does not exist: %v, error: %v", partition.fsType, partition.mountpoint, err)
   449  					break
   450  				}
   451  				fs.Inodes = &inodes
   452  				fs.InodesFree = &inodesFree
   453  				fs.Type = VFS
   454  				nfsInfo[devId] = fs
   455  			default:
   456  				var inodes, inodesFree uint64
   457  				if utils.FileExists(partition.mountpoint) {
   458  					fs.Capacity, fs.Free, fs.Available, inodes, inodesFree, err = getVfsStats(partition.mountpoint)
   459  					fs.Inodes = &inodes
   460  					fs.InodesFree = &inodesFree
   461  					fs.Type = VFS
   462  				} else {
   463  					klog.V(4).Infof("unable to determine file system type, partition mountpoint does not exist: %v", partition.mountpoint)
   464  				}
   465  			}
   466  			if err != nil {
   467  				klog.V(4).Infof("Stat fs failed. Error: %v", err)
   468  			} else {
   469  				deviceSet[device] = struct{}{}
   470  				fs.DeviceInfo = DeviceInfo{
   471  					Device: device,
   472  					Major:  uint(partition.major),
   473  					Minor:  uint(partition.minor),
   474  				}
   475  
   476  				if val, ok := diskStatsMap[device]; ok {
   477  					fs.DiskStats = val
   478  				} else {
   479  					for k, v := range diskStatsMap {
   480  						if v.MajorNum == uint64(partition.major) && v.MinorNum == uint64(partition.minor) {
   481  							fs.DiskStats = diskStatsMap[k]
   482  							break
   483  						}
   484  					}
   485  				}
   486  				filesystems = append(filesystems, fs)
   487  			}
   488  		}
   489  	}
   490  	return filesystems, nil
   491  }
   492  
   493  var partitionRegex = regexp.MustCompile(`^(?:(?:s|v|xv)d[a-z]+\d*|dm-\d+)$`)
   494  
   495  func getDiskStatsMap(diskStatsFile string) (map[string]DiskStats, error) {
   496  	diskStatsMap := make(map[string]DiskStats)
   497  	file, err := os.Open(diskStatsFile)
   498  	if err != nil {
   499  		if os.IsNotExist(err) {
   500  			klog.Warningf("Not collecting filesystem statistics because file %q was not found", diskStatsFile)
   501  			return diskStatsMap, nil
   502  		}
   503  		return nil, err
   504  	}
   505  
   506  	defer file.Close()
   507  	scanner := bufio.NewScanner(file)
   508  
   509  	for scanner.Scan() {
   510  		line := scanner.Text()
   511  		words := strings.Fields(line)
   512  		if !partitionRegex.MatchString(words[2]) {
   513  			continue
   514  		}
   515  		// 8      50 sdd2 40 0 280 223 7 0 22 108 0 330 330
   516  		deviceName := path.Join("/dev", words[2])
   517  
   518  		var err error
   519  		devInfo := make([]uint64, 2)
   520  		for i := 0; i < len(devInfo); i++ {
   521  			devInfo[i], err = strconv.ParseUint(words[i], 10, 64)
   522  			if err != nil {
   523  				return nil, err
   524  			}
   525  		}
   526  
   527  		wordLength := len(words)
   528  		offset := 3
   529  		var stats = make([]uint64, wordLength-offset)
   530  		if len(stats) < 11 {
   531  			return nil, fmt.Errorf("could not parse all 11 columns of /proc/diskstats")
   532  		}
   533  		for i := offset; i < wordLength; i++ {
   534  			stats[i-offset], err = strconv.ParseUint(words[i], 10, 64)
   535  			if err != nil {
   536  				return nil, err
   537  			}
   538  		}
   539  
   540  		major64, err := strconv.ParseUint(words[0], 10, 64)
   541  		if err != nil {
   542  			return nil, err
   543  		}
   544  
   545  		minor64, err := strconv.ParseUint(words[1], 10, 64)
   546  		if err != nil {
   547  			return nil, err
   548  		}
   549  
   550  		diskStats := DiskStats{
   551  			MajorNum:        devInfo[0],
   552  			MinorNum:        devInfo[1],
   553  			ReadsCompleted:  stats[0],
   554  			ReadsMerged:     stats[1],
   555  			SectorsRead:     stats[2],
   556  			ReadTime:        stats[3],
   557  			WritesCompleted: stats[4],
   558  			WritesMerged:    stats[5],
   559  			SectorsWritten:  stats[6],
   560  			WriteTime:       stats[7],
   561  			IoInProgress:    stats[8],
   562  			IoTime:          stats[9],
   563  			WeightedIoTime:  stats[10],
   564  			Major:           major64,
   565  			Minor:           minor64,
   566  		}
   567  		diskStatsMap[deviceName] = diskStats
   568  	}
   569  	return diskStatsMap, nil
   570  }
   571  
   572  func (i *RealFsInfo) GetGlobalFsInfo() ([]Fs, error) {
   573  	return i.GetFsInfoForPath(nil)
   574  }
   575  
   576  func major(devNumber uint64) uint {
   577  	return uint((devNumber >> 8) & 0xfff)
   578  }
   579  
   580  func minor(devNumber uint64) uint {
   581  	return uint((devNumber & 0xff) | ((devNumber >> 12) & 0xfff00))
   582  }
   583  
   584  func (i *RealFsInfo) GetDeviceInfoByFsUUID(uuid string) (*DeviceInfo, error) {
   585  	deviceName, found := i.fsUUIDToDeviceName[uuid]
   586  	if !found {
   587  		return nil, ErrNoSuchDevice
   588  	}
   589  	p, found := i.partitions[deviceName]
   590  	if !found {
   591  		return nil, fmt.Errorf("cannot find device %q in partitions", deviceName)
   592  	}
   593  	return &DeviceInfo{deviceName, p.major, p.minor}, nil
   594  }
   595  
   596  func (i *RealFsInfo) mountInfoFromDir(dir string) (*mount.Info, bool) {
   597  	mnt, found := i.mounts[dir]
   598  	// try the parent dir if not found until we reach the root dir
   599  	// this is an issue on btrfs systems where the directory is not
   600  	// the subvolume
   601  	for !found {
   602  		pathdir, _ := filepath.Split(dir)
   603  		// break when we reach root
   604  		if pathdir == "/" {
   605  			mnt, found = i.mounts["/"]
   606  			break
   607  		}
   608  		// trim "/" from the new parent path otherwise the next possible
   609  		// filepath.Split in the loop will not split the string any further
   610  		dir = strings.TrimSuffix(pathdir, "/")
   611  		mnt, found = i.mounts[dir]
   612  	}
   613  	return &mnt, found
   614  }
   615  
   616  func (i *RealFsInfo) GetDirFsDevice(dir string) (*DeviceInfo, error) {
   617  	buf := new(syscall.Stat_t)
   618  	err := syscall.Stat(dir, buf)
   619  	if err != nil {
   620  		return nil, fmt.Errorf("stat failed on %s with error: %s", dir, err)
   621  	}
   622  
   623  	// The type Dev in Stat_t is 32bit on mips.
   624  	major := major(uint64(buf.Dev)) // nolint: unconvert
   625  	minor := minor(uint64(buf.Dev)) // nolint: unconvert
   626  	for device, partition := range i.partitions {
   627  		if partition.major == major && partition.minor == minor {
   628  			return &DeviceInfo{device, major, minor}, nil
   629  		}
   630  	}
   631  
   632  	mnt, found := i.mountInfoFromDir(dir)
   633  	if found && strings.HasPrefix(mnt.Source, "/dev/") {
   634  		major, minor := mnt.Major, mnt.Minor
   635  
   636  		if mnt.FSType == "btrfs" && major == 0 {
   637  			major, minor, err = getBtrfsMajorMinorIds(mnt)
   638  			if err != nil {
   639  				klog.Warningf("Unable to get btrfs mountpoint IDs: %v", err)
   640  			}
   641  		}
   642  
   643  		return &DeviceInfo{mnt.Source, uint(major), uint(minor)}, nil
   644  	}
   645  
   646  	return nil, fmt.Errorf("with major: %d, minor: %d: %w", major, minor, ErrDeviceNotInPartitionsMap)
   647  }
   648  
   649  func GetDirUsage(dir string) (UsageInfo, error) {
   650  	var usage UsageInfo
   651  
   652  	if dir == "" {
   653  		return usage, fmt.Errorf("invalid directory")
   654  	}
   655  
   656  	rootInfo, err := os.Stat(dir)
   657  	if err != nil {
   658  		return usage, fmt.Errorf("could not stat %q to get inode usage: %v", dir, err)
   659  	}
   660  
   661  	rootStat, ok := rootInfo.Sys().(*syscall.Stat_t)
   662  	if !ok {
   663  		return usage, fmt.Errorf("unsupported fileinfo for getting inode usage of %q", dir)
   664  	}
   665  
   666  	rootDevID := rootStat.Dev
   667  
   668  	// dedupedInode stores inodes that could be duplicates (nlink > 1)
   669  	dedupedInodes := make(map[uint64]struct{})
   670  
   671  	err = filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
   672  		if os.IsNotExist(err) {
   673  			// expected if files appear/vanish
   674  			return nil
   675  		}
   676  		if err != nil {
   677  			return fmt.Errorf("unable to count inodes for part of dir %s: %s", dir, err)
   678  		}
   679  
   680  		// according to the docs, Sys can be nil
   681  		if info.Sys() == nil {
   682  			return fmt.Errorf("fileinfo Sys is nil")
   683  		}
   684  
   685  		s, ok := info.Sys().(*syscall.Stat_t)
   686  		if !ok {
   687  			return fmt.Errorf("unsupported fileinfo; could not convert to stat_t")
   688  		}
   689  
   690  		if s.Dev != rootDevID {
   691  			// don't descend into directories on other devices
   692  			return filepath.SkipDir
   693  		}
   694  		if s.Nlink > 1 {
   695  			if _, ok := dedupedInodes[s.Ino]; !ok {
   696  				// Dedupe things that could be hardlinks
   697  				dedupedInodes[s.Ino] = struct{}{}
   698  
   699  				usage.Bytes += uint64(s.Blocks) * statBlockSize
   700  				usage.Inodes++
   701  			}
   702  		} else {
   703  			usage.Bytes += uint64(s.Blocks) * statBlockSize
   704  			usage.Inodes++
   705  		}
   706  		return nil
   707  	})
   708  
   709  	return usage, err
   710  }
   711  
   712  func (i *RealFsInfo) GetDirUsage(dir string) (UsageInfo, error) {
   713  	claimToken()
   714  	defer releaseToken()
   715  	return GetDirUsage(dir)
   716  }
   717  
   718  func getVfsStats(path string) (total uint64, free uint64, avail uint64, inodes uint64, inodesFree uint64, err error) {
   719  	var s syscall.Statfs_t
   720  	if err = syscall.Statfs(path, &s); err != nil {
   721  		return 0, 0, 0, 0, 0, err
   722  	}
   723  	total = uint64(s.Frsize) * s.Blocks
   724  	free = uint64(s.Frsize) * s.Bfree
   725  	avail = uint64(s.Frsize) * s.Bavail
   726  	inodes = uint64(s.Files)
   727  	inodesFree = uint64(s.Ffree)
   728  	return total, free, avail, inodes, inodesFree, nil
   729  }
   730  
   731  // Devicemapper thin provisioning is detailed at
   732  // https://www.kernel.org/doc/Documentation/device-mapper/thin-provisioning.txt
   733  func dockerDMDevice(driverStatus map[string]string, dmsetup devicemapper.DmsetupClient) (string, uint, uint, uint, error) {
   734  	poolName, ok := driverStatus[DriverStatusPoolName]
   735  	if !ok || len(poolName) == 0 {
   736  		return "", 0, 0, 0, fmt.Errorf("Could not get dm pool name")
   737  	}
   738  
   739  	out, err := dmsetup.Table(poolName)
   740  	if err != nil {
   741  		return "", 0, 0, 0, err
   742  	}
   743  
   744  	major, minor, dataBlkSize, err := parseDMTable(string(out))
   745  	if err != nil {
   746  		return "", 0, 0, 0, err
   747  	}
   748  
   749  	return poolName, major, minor, dataBlkSize, nil
   750  }
   751  
   752  // parseDMTable parses a single line of `dmsetup table` output and returns the
   753  // major device, minor device, block size, and an error.
   754  func parseDMTable(dmTable string) (uint, uint, uint, error) {
   755  	dmTable = strings.Replace(dmTable, ":", " ", -1)
   756  	dmFields := strings.Fields(dmTable)
   757  
   758  	if len(dmFields) < 8 {
   759  		return 0, 0, 0, fmt.Errorf("Invalid dmsetup status output: %s", dmTable)
   760  	}
   761  
   762  	major, err := strconv.ParseUint(dmFields[5], 10, 32)
   763  	if err != nil {
   764  		return 0, 0, 0, err
   765  	}
   766  	minor, err := strconv.ParseUint(dmFields[6], 10, 32)
   767  	if err != nil {
   768  		return 0, 0, 0, err
   769  	}
   770  	dataBlkSize, err := strconv.ParseUint(dmFields[7], 10, 32)
   771  	if err != nil {
   772  		return 0, 0, 0, err
   773  	}
   774  
   775  	return uint(major), uint(minor), uint(dataBlkSize), nil
   776  }
   777  
   778  func getDMStats(poolName string, dataBlkSize uint) (uint64, uint64, uint64, error) {
   779  	out, err := exec.Command("dmsetup", "status", poolName).Output()
   780  	if err != nil {
   781  		return 0, 0, 0, err
   782  	}
   783  
   784  	used, total, err := parseDMStatus(string(out))
   785  	if err != nil {
   786  		return 0, 0, 0, err
   787  	}
   788  
   789  	used *= 512 * uint64(dataBlkSize)
   790  	total *= 512 * uint64(dataBlkSize)
   791  	free := total - used
   792  
   793  	return total, free, free, nil
   794  }
   795  
   796  func parseDMStatus(dmStatus string) (uint64, uint64, error) {
   797  	dmStatus = strings.Replace(dmStatus, "/", " ", -1)
   798  	dmFields := strings.Fields(dmStatus)
   799  
   800  	if len(dmFields) < 8 {
   801  		return 0, 0, fmt.Errorf("Invalid dmsetup status output: %s", dmStatus)
   802  	}
   803  
   804  	used, err := strconv.ParseUint(dmFields[6], 10, 64)
   805  	if err != nil {
   806  		return 0, 0, err
   807  	}
   808  	total, err := strconv.ParseUint(dmFields[7], 10, 64)
   809  	if err != nil {
   810  		return 0, 0, err
   811  	}
   812  
   813  	return used, total, nil
   814  }
   815  
   816  // getZfstats returns ZFS mount stats using zfsutils
   817  func getZfstats(poolName string) (uint64, uint64, uint64, error) {
   818  	dataset, err := zfs.GetDataset(poolName)
   819  	if err != nil {
   820  		return 0, 0, 0, err
   821  	}
   822  
   823  	total := dataset.Used + dataset.Avail + dataset.Usedbydataset
   824  
   825  	return total, dataset.Avail, dataset.Avail, nil
   826  }
   827  
   828  // Get major and minor Ids for a mount point using btrfs as filesystem.
   829  func getBtrfsMajorMinorIds(mount *mount.Info) (int, int, error) {
   830  	// btrfs fix: following workaround fixes wrong btrfs Major and Minor Ids reported in /proc/self/mountinfo.
   831  	// instead of using values from /proc/self/mountinfo we use stat to get Ids from btrfs mount point
   832  
   833  	buf := new(syscall.Stat_t)
   834  	err := syscall.Stat(mount.Source, buf)
   835  	if err != nil {
   836  		err = fmt.Errorf("stat failed on %s with error: %s", mount.Source, err)
   837  		return 0, 0, err
   838  	}
   839  
   840  	klog.V(4).Infof("btrfs mount %#v", mount)
   841  	if buf.Mode&syscall.S_IFMT == syscall.S_IFBLK {
   842  		err := syscall.Stat(mount.Mountpoint, buf)
   843  		if err != nil {
   844  			err = fmt.Errorf("stat failed on %s with error: %s", mount.Mountpoint, err)
   845  			return 0, 0, err
   846  		}
   847  
   848  		// The type Dev and Rdev in Stat_t are 32bit on mips.
   849  		klog.V(4).Infof("btrfs dev major:minor %d:%d\n", int(major(uint64(buf.Dev))), int(minor(uint64(buf.Dev))))    // nolint: unconvert
   850  		klog.V(4).Infof("btrfs rdev major:minor %d:%d\n", int(major(uint64(buf.Rdev))), int(minor(uint64(buf.Rdev)))) // nolint: unconvert
   851  
   852  		return int(major(uint64(buf.Dev))), int(minor(uint64(buf.Dev))), nil // nolint: unconvert
   853  	}
   854  	return 0, 0, fmt.Errorf("%s is not a block device", mount.Source)
   855  }