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  }