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 }