k8s.io/kubernetes@v1.29.3/pkg/volume/util/fs/fs.go (about) 1 //go:build linux || darwin 2 // +build linux darwin 3 4 /* 5 Copyright 2014 The Kubernetes Authors. 6 7 Licensed under the Apache License, Version 2.0 (the "License"); 8 you may not use this file except in compliance with the License. 9 You may obtain a copy of the License at 10 11 http://www.apache.org/licenses/LICENSE-2.0 12 13 Unless required by applicable law or agreed to in writing, software 14 distributed under the License is distributed on an "AS IS" BASIS, 15 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 See the License for the specific language governing permissions and 17 limitations under the License. 18 */ 19 20 package fs 21 22 import ( 23 "fmt" 24 "os" 25 "path/filepath" 26 "syscall" 27 "time" 28 29 "golang.org/x/sys/unix" 30 31 servermetrics "k8s.io/kubernetes/pkg/kubelet/server/metrics" 32 "k8s.io/kubernetes/pkg/volume/util/fsquota" 33 ) 34 35 type UsageInfo struct { 36 Bytes int64 37 Inodes int64 38 } 39 40 // Info linux returns (available bytes, byte capacity, byte usage, total inodes, inodes free, inode usage, error) 41 // for the filesystem that path resides upon. 42 func Info(path string) (int64, int64, int64, int64, int64, int64, error) { 43 statfs := &unix.Statfs_t{} 44 err := unix.Statfs(path, statfs) 45 if err != nil { 46 return 0, 0, 0, 0, 0, 0, err 47 } 48 49 // Available is blocks available * fragment size 50 available := int64(statfs.Bavail) * int64(statfs.Bsize) 51 52 // Capacity is total block count * fragment size 53 capacity := int64(statfs.Blocks) * int64(statfs.Bsize) 54 55 // Usage is block being used * fragment size (aka block size). 56 usage := (int64(statfs.Blocks) - int64(statfs.Bfree)) * int64(statfs.Bsize) 57 58 inodes := int64(statfs.Files) 59 inodesFree := int64(statfs.Ffree) 60 inodesUsed := inodes - inodesFree 61 62 return available, capacity, usage, inodes, inodesFree, inodesUsed, nil 63 } 64 65 // DiskUsage calculates the number of inodes and disk usage for a given directory 66 func DiskUsage(path string) (UsageInfo, error) { 67 var usage UsageInfo 68 69 if path == "" { 70 return usage, fmt.Errorf("invalid directory") 71 } 72 73 // First check whether the quota system knows about this directory 74 // A nil quantity or error means that the path does not support quotas 75 // or xfs_quota tool is missing and we should use other mechanisms. 76 startTime := time.Now() 77 consumption, _ := fsquota.GetConsumption(path) 78 if consumption != nil { 79 usage.Bytes = consumption.Value() 80 defer servermetrics.CollectVolumeStatCalDuration("fsquota", startTime) 81 } else { 82 defer servermetrics.CollectVolumeStatCalDuration("du", startTime) 83 } 84 85 inodes, _ := fsquota.GetInodes(path) 86 if inodes != nil { 87 usage.Inodes = inodes.Value() 88 } 89 90 if inodes != nil && consumption != nil { 91 return usage, nil 92 } 93 94 topLevelStat := &unix.Stat_t{} 95 err := unix.Stat(path, topLevelStat) 96 if err != nil { 97 return usage, err 98 } 99 100 // dedupedInode stores inodes that could be duplicates (nlink > 1) 101 dedupedInodes := make(map[uint64]struct{}) 102 103 err = filepath.Walk(path, func(path string, info os.FileInfo, err error) error { 104 // ignore files that have been deleted after directory was read 105 if os.IsNotExist(err) { 106 return nil 107 } 108 if err != nil { 109 return fmt.Errorf("unable to count inodes for %s: %s", path, err) 110 } 111 112 // according to the docs, Sys can be nil 113 if info.Sys() == nil { 114 return fmt.Errorf("fileinfo Sys is nil") 115 } 116 117 s, ok := info.Sys().(*syscall.Stat_t) 118 if !ok { 119 return fmt.Errorf("unsupported fileinfo; could not convert to stat_t") 120 } 121 122 if s.Dev != topLevelStat.Dev { 123 // don't descend into directories on other devices 124 return filepath.SkipDir 125 } 126 127 // Dedupe hardlinks 128 if s.Nlink > 1 { 129 if _, ok := dedupedInodes[s.Ino]; !ok { 130 dedupedInodes[s.Ino] = struct{}{} 131 } else { 132 return nil 133 } 134 } 135 136 if consumption == nil { 137 usage.Bytes += int64(s.Blocks) * int64(512) // blocksize in bytes 138 } 139 140 if inodes == nil { 141 usage.Inodes++ 142 } 143 144 return nil 145 }) 146 147 return usage, err 148 }