github.com/NVIDIA/aistore@v1.3.23-0.20240517131212-7df6609be51d/ios/dutils_linux.go (about) 1 // Package ios is a collection of interfaces to the local storage subsystem; 2 // the package includes OS-dependent implementations for those interfaces. 3 /* 4 * Copyright (c) 2018-2023, NVIDIA CORPORATION. All rights reserved. 5 */ 6 package ios 7 8 import ( 9 "errors" 10 "flag" 11 "fmt" 12 "os/exec" 13 "strings" 14 15 "github.com/NVIDIA/aistore/cmn" 16 "github.com/NVIDIA/aistore/cmn/cos" 17 "github.com/NVIDIA/aistore/cmn/nlog" 18 jsoniter "github.com/json-iterator/go" 19 ) 20 21 // parse `lsblk -Jt` to associate filesystem (`fs`) with its disks; 22 // devPrefix* constants are the (two) distinct prefixes expected in `lsblk` output 23 const ( 24 devPrefixReg = "/dev/" 25 devPrefixLVM = "/dev/mapper/" 26 ) 27 28 type ( 29 LsBlk struct { 30 BlockDevices []*blkdev `json:"blockdevices"` 31 } 32 33 // `lsblk -Jt` structure 34 blkdev struct { 35 Name string `json:"name"` 36 PhySec jsoniter.Number `json:"phy-sec"` 37 BlockDevices []*blkdev `json:"children"` 38 } 39 ) 40 41 func lsblk(fs string, testingEnv bool) (res *LsBlk) { 42 // skip docker union mounts 43 if fs == "overlay" { 44 return 45 } 46 var ( 47 cmd = exec.Command("lsblk", "-Jt") // JSON output format (TODO: '-e7' to skip loopbacks) 48 out, err = cmd.CombinedOutput() 49 ) 50 if err != nil { 51 switch { 52 case len(out) == 0: 53 case strings.Contains(err.Error(), "exit status"): 54 err = errors.New(string(out)) 55 default: 56 err = fmt.Errorf("%s: %v", string(out), err) 57 } 58 if !testingEnv { 59 cos.ExitLog(err) // FATAL 60 } 61 nlog.Errorln(err) 62 return 63 } 64 if len(out) == 0 { 65 nlog.Errorf("%s: no disks (empty lsblk output)", fs) 66 return 67 } 68 69 // unmarshal 70 res = &LsBlk{} 71 if err = jsoniter.Unmarshal(out, res); err != nil { 72 err = fmt.Errorf("failed to unmarshal lsblk output: %v", err) 73 if !testingEnv { 74 cos.ExitLog(err) // FATAL 75 } 76 nlog.Errorln(err) 77 res = nil 78 } 79 return 80 } 81 82 // given parsed lsblk and `fs` (filesystem) fs2disks retrieves the underlying 83 // disk or disks; it may return multiple disks but only if the filesystem is 84 // RAID; it is called upong adding/enabling mountpath 85 func fs2disks(res *LsBlk, fs string, label Label, num int, testingEnv bool) (disks FsDisks, err error) { 86 var trimmedFS string 87 if strings.HasPrefix(fs, devPrefixLVM) { 88 trimmedFS = strings.TrimPrefix(fs, devPrefixLVM) 89 } else { 90 trimmedFS = strings.TrimPrefix(fs, devPrefixReg) 91 } 92 disks = make(FsDisks, num) 93 findDevs(res.BlockDevices, trimmedFS, label, disks) // map trimmed(fs) <= disk(s) 94 95 if !flag.Parsed() { 96 return disks, nil // unit tests 97 } 98 99 switch { 100 case len(disks) > 0: 101 s := disks._str() 102 nlog.Infoln("["+fs+label.ToLog()+"]:", s) 103 case testingEnv || cmn.AllowSharedDisksAndNoDisks: 104 // anything goes 105 case label.IsNil(): 106 err = fmt.Errorf("No disks for %s(%q) (empty label implies _resolvable_ underlying disk(s))", fs, trimmedFS) 107 nlog.Errorln(err) 108 dump, _ := jsoniter.MarshalIndent(res.BlockDevices, "", " ") 109 nlog.Infoln("Begin lsblk output ================================") 110 nlog.Infoln(string(dump)) 111 nlog.Infoln("End lsblk output ==================================") 112 default: 113 nlog.Infof("No disks for %s(%q, disk label: %s)", fs, trimmedFS, label) 114 } 115 return disks, err 116 } 117 118 // 119 // private 120 // 121 122 func findDevs(devList []*blkdev, trimmedFS string, label Label, disks FsDisks) { 123 for _, bd := range devList { 124 // by dev name 125 if bd.Name == trimmedFS { 126 _add(bd, disks) 127 continue 128 } 129 // NOTE: by label 130 if label != "" && strings.Contains(bd.Name, string(label)) { 131 _add(bd, disks) 132 continue 133 } 134 if len(bd.BlockDevices) > 0 && _match(bd.BlockDevices, trimmedFS) { 135 _add(bd, disks) 136 } 137 } 138 } 139 140 func _add(bd *blkdev, disks FsDisks) { 141 var err error 142 if disks[bd.Name], err = bd.PhySec.Int64(); err != nil { 143 nlog.Errorf("%s[%v]: failed to parse sector: %v", bd.Name, bd, err) 144 disks[bd.Name] = 512 145 } 146 } 147 148 func _match(devList []*blkdev, device string) bool { 149 for _, dev := range devList { 150 if dev.Name == device { 151 return true 152 } 153 // recurs 154 if len(dev.BlockDevices) != 0 && _match(dev.BlockDevices, device) { 155 return true 156 } 157 } 158 return false 159 }