github.com/emc-advanced-dev/unik@v0.0.0-20190717152701-a58d3e8e33b7/pkg/os/device_linux.go (about) 1 package os 2 3 import ( 4 "bufio" 5 "bytes" 6 "fmt" 7 "io/ioutil" 8 "os" 9 "os/exec" 10 "strconv" 11 "strings" 12 13 "github.com/emc-advanced-dev/pkg/errors" 14 15 log "github.com/sirupsen/logrus" 16 ) 17 18 func Mount(device BlockDevice) (mntpoint string, err error) { 19 return MountDevice(device.Name()) 20 } 21 func MountDevice(device string) (mntpoint string, err error) { 22 defer func() { 23 if err != nil { 24 os.Remove(mntpoint) 25 } 26 }() 27 28 mntpoint, err = ioutil.TempDir("", "stgr.mntpoint.") 29 if err != nil { 30 return 31 } 32 err = RunLogCommand("mount", device, mntpoint) 33 return 34 } 35 36 func Umount(point string) error { 37 38 err := RunLogCommand("umount", point) 39 if err != nil { 40 return err 41 } 42 // ignore errors. 43 err = os.Remove(point) 44 if err != nil { 45 log.WithField("err", err).Warn("umount rmdir failed") 46 } 47 48 return nil 49 } 50 51 func runParted(device string, args ...string) ([]byte, error) { 52 log.WithFields(log.Fields{"device": device, "args": args}).Debug("running parted") 53 args = append([]string{"--script", "--machine", device}, args...) 54 out, err := exec.Command("parted", args...).CombinedOutput() 55 if err != nil { 56 log.WithFields(log.Fields{"args": args, "err": err, "out": string(out)}).Error("parted failed") 57 } 58 return out, err 59 } 60 61 type MsDosPartioner struct { 62 Device string 63 } 64 65 func (m *MsDosPartioner) MakeTable() error { 66 _, err := runParted(m.Device, "mklabel", "msdos") 67 return err 68 } 69 70 func (m *MsDosPartioner) MakePart(partType string, start, size DiskSize) error { 71 _, err := runParted(m.Device, "mkpart", partType, start.ToPartedFormat(), size.ToPartedFormat()) 72 return err 73 } 74 75 func (m *MsDosPartioner) MakePartTillEnd(partType string, start DiskSize) error { 76 _, err := runParted(m.Device, "mkpart", partType, start.ToPartedFormat(), "100%") 77 return err 78 } 79 80 func (m *MsDosPartioner) Makebootable(partnum int) error { 81 _, err := runParted(m.Device, "set", fmt.Sprintf("%d", partnum), "boot", "on") 82 return err 83 } 84 85 type DiskLabelPartioner struct { 86 Device string 87 } 88 89 func (m *DiskLabelPartioner) MakeTable() error { 90 _, err := runParted(m.Device, "mklabel", "bsd") 91 return err 92 } 93 94 func (m *DiskLabelPartioner) MakePart(partType string, start, size DiskSize) error { 95 _, err := runParted(m.Device, "mkpart", partType, start.ToPartedFormat(), size.ToPartedFormat()) 96 return err 97 } 98 99 func ListParts(device BlockDevice) ([]Part, error) { 100 var parts []Part 101 out, err := runParted(device.Name(), "unit B", "print") 102 if err != nil { 103 return parts, nil 104 } 105 scanner := bufio.NewScanner(bytes.NewReader(out)) 106 /* example output 107 108 BYT; 109 /dev/xvda:42949672960B:xvd:512:512:msdos:Xen Virtual Block Device; 110 1:8225280B:42944186879B:42935961600B:ext4::boot; 111 112 ================ 113 114 BYT; 115 /home/ubuntu/yuval:1073741824B:file:512:512:bsd:; 116 1:2097152B:99614719B:97517568B:::; 117 2:99614720B:200278015B:100663296B:::; 118 3:200278016B:299892735B:99614720B:::; 119 120 ========= basically: 121 device:size: 122 partnum:start:end:size 123 124 */ 125 126 // skip to the parts.. 127 for scanner.Scan() { 128 line := scanner.Text() 129 if strings.Contains(line, device.Name()) { 130 break 131 } 132 } 133 134 for scanner.Scan() { 135 line := scanner.Text() 136 tokens := strings.Split(line, ":") 137 138 partNum, err := strconv.ParseInt(tokens[0], 0, 0) 139 if err != nil { 140 return parts, err 141 } 142 143 start, err := getByteNumber(tokens[1]) 144 if err != nil { 145 return parts, err 146 } 147 148 end, err := getByteNumber(tokens[2]) 149 if err != nil { 150 return parts, err 151 } 152 153 size, err := getByteNumber(tokens[3]) 154 if err != nil { 155 return parts, err 156 } 157 158 //validate Part is consistent: 159 if end-start != size-1 { 160 log.WithFields(log.Fields{"start": start, "end": end, "size": size}).Error("Sizes not consistent") 161 return parts, errors.New("Sizes are inconsistent. part not continous?", nil) 162 } 163 164 var part Part 165 partName := getDevicePart(device.Name(), partNum) 166 // part devices may or may not created the partition mappings. so deal with both options 167 if _, err := os.Stat(partName); os.IsNotExist(err) { 168 // device does not exist 169 sectorsStart, err := ToSectors(start) 170 if err != nil { 171 return parts, err 172 } 173 sectorsSize, err := ToSectors(size) 174 if err != nil { 175 return parts, err 176 } 177 part = NewPartLoDevice(device.Name(), sectorsStart, sectorsSize) 178 } else { 179 // device exists 180 var release func(BlockDevice) error = nil 181 182 // prated might have created the mapping for us. unfortunatly it does not remove it... 183 if strings.HasPrefix(partName, "/dev/mapper") { 184 release = func(d BlockDevice) error { 185 return RunLogCommand("dmsetup", "remove", d.Name()) 186 } 187 } 188 189 part = &PartedPart{BlockDevice(partName), start, size, release} 190 } 191 parts = append(parts, part) 192 } 193 194 return parts, nil 195 } 196 197 func getDevicePart(device string, part int64) string { 198 return fmt.Sprintf("%s%c", device, '0'+part) 199 } 200 201 func getByteNumber(token string) (Bytes, error) { 202 tokenLen := len(token) 203 if tokenLen == 0 { 204 return 0, errors.New("Not a number", nil) 205 } 206 // remove the B 207 208 if token[tokenLen-1] != 'B' { 209 210 return 0, errors.New("Unknown unit for number", nil) 211 } 212 213 res, err := strconv.ParseInt(token[:tokenLen-1], 0, 0) 214 return Bytes(res), err 215 } 216 217 type PartedPart struct { 218 Device BlockDevice 219 offset DiskSize 220 size DiskSize 221 release func(BlockDevice) error 222 } 223 224 func (p *PartedPart) Size() DiskSize { 225 return p.size 226 } 227 func (p *PartedPart) Offset() DiskSize { 228 return p.offset 229 } 230 231 func (p *PartedPart) Acquire() (BlockDevice, error) { 232 233 return p.Get(), nil 234 } 235 236 func (p *PartedPart) Release() error { 237 if p.release != nil { 238 return p.release(p.Device) 239 } 240 return nil 241 } 242 243 func (p *PartedPart) Get() BlockDevice { 244 return p.Device 245 } 246 247 func randomDeviceName() string { 248 return "dev" + RandStringBytes(4) 249 } 250 251 // TODO: change this to api; like in here: https://www.versioneye.com/python/losetup/2.0.7 or here https://github.com/karelzak/util-linux/blob/master/sys-utils/losetup.c 252 type LoDevice struct { 253 device string 254 createdDevice BlockDevice 255 offset DiskSize 256 size DiskSize 257 } 258 259 func NewLoDevice(device string) Resource { 260 return &LoDevice{device, BlockDevice(""), nil, nil} 261 } 262 func NewPartLoDevice(device string, offset DiskSize, size DiskSize) Part { 263 return &LoDevice{device, BlockDevice(""), offset, size} 264 } 265 266 func (p *LoDevice) Acquire() (BlockDevice, error) { 267 log.WithFields(log.Fields{"cmd": "losetup", "device": p.device}).Debug("running losetup -f") 268 269 args := []string{"-f", "--show", p.device} 270 271 if p.size != nil { 272 args = append(args, "--sizelimit", fmt.Sprintf("%d", p.size.ToBytes())) 273 } 274 275 if p.offset != nil { 276 args = append(args, "--offset", fmt.Sprintf("%d", p.offset.ToBytes())) 277 } 278 279 out, err := exec.Command("losetup", args...).CombinedOutput() 280 281 if err != nil { 282 log.WithFields(log.Fields{"cmd": "losetup", "out": string(out), "device": p.device}).Debug("losetup -f failed") 283 return BlockDevice(""), err 284 } 285 outString := strings.TrimSpace(string(out)) 286 p.createdDevice = BlockDevice(outString) 287 return p.createdDevice, nil 288 } 289 290 func (p *LoDevice) Get() BlockDevice { 291 return p.createdDevice 292 } 293 294 func (p *LoDevice) Release() error { 295 return RunLogCommand("losetup", "-d", p.createdDevice.Name()) 296 } 297 298 func (p *LoDevice) Size() DiskSize { 299 return p.size 300 } 301 302 func (p *LoDevice) Offset() DiskSize { 303 return p.offset 304 }