...

Source file src/github.com/Microsoft/hcsshim/internal/guest/storage/devicemapper/devicemapper.go

Documentation: github.com/Microsoft/hcsshim/internal/guest/storage/devicemapper

     1  //go:build linux
     2  // +build linux
     3  
     4  package devicemapper
     5  
     6  import (
     7  	"fmt"
     8  	"os"
     9  	"path"
    10  	"syscall"
    11  	"time"
    12  	"unsafe"
    13  
    14  	"golang.org/x/sys/unix"
    15  
    16  	"github.com/Microsoft/hcsshim/internal/guest/linux"
    17  )
    18  
    19  // CreateFlags modify the operation of CreateDevice
    20  type CreateFlags int
    21  
    22  const (
    23  	// CreateReadOnly specifies that the device is not writable
    24  	CreateReadOnly CreateFlags = 1 << iota
    25  )
    26  
    27  var (
    28  	removeDeviceWrapper = removeDevice
    29  	openMapperWrapper   = openMapper
    30  )
    31  
    32  //nolint:stylecheck // ST1003: ALL_CAPS
    33  const (
    34  	_DM_IOCTL      = 0xfd
    35  	_DM_IOCTL_SIZE = 312
    36  	_DM_IOCTL_BASE = linux.IocWRBase | _DM_IOCTL<<linux.IocTypeShift | _DM_IOCTL_SIZE<<linux.IocSizeShift
    37  
    38  	_DM_READONLY_FLAG       = 1 << 0
    39  	_DM_SUSPEND_FLAG        = 1 << 1
    40  	_DM_PERSISTENT_DEV_FLAG = 1 << 3
    41  )
    42  
    43  const blockSize = 512
    44  
    45  //nolint:stylecheck // ST1003: ALL_CAPS
    46  const (
    47  	_DM_VERSION = iota
    48  	_DM_REMOVE_ALL
    49  	_DM_LIST_DEVICES
    50  	_DM_DEV_CREATE
    51  	_DM_DEV_REMOVE
    52  	_DM_DEV_RENAME
    53  	_DM_DEV_SUSPEND
    54  	_DM_DEV_STATUS
    55  	_DM_DEV_WAIT
    56  	_DM_TABLE_LOAD
    57  	_DM_TABLE_CLEAR
    58  	_DM_TABLE_DEPS
    59  	_DM_TABLE_STATUS
    60  )
    61  
    62  var dmOpName = []string{
    63  	"version",
    64  	"remove all",
    65  	"list devices",
    66  	"device create",
    67  	"device remove",
    68  	"device rename",
    69  	"device suspend",
    70  	"device status",
    71  	"device wait",
    72  	"table load",
    73  	"table clear",
    74  	"table deps",
    75  	"table status",
    76  }
    77  
    78  type dmIoctl struct {
    79  	Version     [3]uint32
    80  	DataSize    uint32
    81  	DataStart   uint32
    82  	TargetCount uint32
    83  	OpenCount   int32
    84  	Flags       uint32
    85  	EventNumber uint32
    86  	_           uint32
    87  	Dev         uint64
    88  	Name        [128]byte
    89  	UUID        [129]byte
    90  	_           [7]byte
    91  }
    92  
    93  type targetSpec struct {
    94  	SectorStart    int64
    95  	LengthInBlocks int64
    96  	Status         int32
    97  	Next           uint32
    98  	Type           [16]byte
    99  }
   100  
   101  // initIoctl initializes a device-mapper ioctl input struct with the given size
   102  // and device name.
   103  func initIoctl(d *dmIoctl, size int, name string) {
   104  	*d = dmIoctl{
   105  		Version:  [3]uint32{4, 0, 0},
   106  		DataSize: uint32(size),
   107  	}
   108  	copy(d.Name[:], name)
   109  }
   110  
   111  type dmError struct {
   112  	Op  int
   113  	Err error
   114  }
   115  
   116  func (err *dmError) Error() string {
   117  	op := "<bad operation>"
   118  	if err.Op < len(dmOpName) {
   119  		op = dmOpName[err.Op]
   120  	}
   121  	return "device-mapper " + op + ": " + err.Err.Error()
   122  }
   123  
   124  // devMapperIoctl issues the specified device-mapper ioctl.
   125  func devMapperIoctl(f *os.File, code int, data *dmIoctl) error {
   126  	if err := linux.Ioctl(f, code|_DM_IOCTL_BASE, unsafe.Pointer(data)); err != nil {
   127  		return &dmError{Op: code, Err: err}
   128  	}
   129  	return nil
   130  }
   131  
   132  // openMapper opens the device-mapper control device and validates that it
   133  // supports the required version.
   134  func openMapper() (f *os.File, err error) {
   135  	f, err = os.OpenFile("/dev/mapper/control", os.O_RDWR, 0)
   136  	if err != nil {
   137  		return nil, err
   138  	}
   139  	defer func() {
   140  		if err != nil {
   141  			f.Close()
   142  		}
   143  	}()
   144  	var d dmIoctl
   145  	initIoctl(&d, int(unsafe.Sizeof(d)), "")
   146  	err = devMapperIoctl(f, _DM_VERSION, &d)
   147  	if err != nil {
   148  		return nil, err
   149  	}
   150  	return f, nil
   151  }
   152  
   153  // Target specifies a single entry in a device's target specification.
   154  type Target struct {
   155  	Type           string
   156  	SectorStart    int64
   157  	LengthInBlocks int64
   158  	Params         string
   159  }
   160  
   161  // sizeof returns the size of a targetSpec needed to fit this specification.
   162  func (t *Target) sizeof() int {
   163  	// include a null terminator (not sure if necessary) and round up to 8-byte
   164  	// alignment
   165  	return (int(unsafe.Sizeof(targetSpec{})) + len(t.Params) + 1 + 7) &^ 7
   166  }
   167  
   168  // LinearTarget constructs a device-mapper target that maps a portion of a block
   169  // device at the specified offset.
   170  //
   171  //	Example linear target table:
   172  //	0 20971520 linear /dev/hda 384
   173  //	|     |      |        |     |
   174  //	start |   target   data_dev |
   175  //	     size                 offset
   176  func LinearTarget(sectorStart, lengthBlocks int64, path string, deviceStart int64) Target {
   177  	return Target{
   178  		Type:           "linear",
   179  		SectorStart:    sectorStart,
   180  		LengthInBlocks: lengthBlocks,
   181  		Params:         fmt.Sprintf("%s %d", path, deviceStart),
   182  	}
   183  }
   184  
   185  // zeroSectorLinearTarget creates a Target for devices with 0 sector start and length/device start
   186  // expected to be in bytes rather than blocks.
   187  func zeroSectorLinearTarget(lengthBytes int64, path string, deviceStartBytes int64) Target {
   188  	lengthInBlocks := lengthBytes / blockSize
   189  	startInBlocks := deviceStartBytes / blockSize
   190  	return LinearTarget(0, lengthInBlocks, path, startInBlocks)
   191  }
   192  
   193  // makeTableIoctl builds an ioctl input structure with a table of the specified
   194  // targets.
   195  func makeTableIoctl(name string, targets []Target) *dmIoctl {
   196  	off := int(unsafe.Sizeof(dmIoctl{}))
   197  	n := off
   198  	for _, t := range targets {
   199  		n += t.sizeof()
   200  	}
   201  	b := make([]byte, n)
   202  	d := (*dmIoctl)(unsafe.Pointer(&b[0]))
   203  	initIoctl(d, n, name)
   204  	d.DataStart = uint32(off)
   205  	d.TargetCount = uint32(len(targets))
   206  	for _, t := range targets {
   207  		spec := (*targetSpec)(unsafe.Pointer(&b[off]))
   208  		sn := t.sizeof()
   209  		spec.SectorStart = t.SectorStart
   210  		spec.LengthInBlocks = t.LengthInBlocks
   211  		spec.Next = uint32(sn)
   212  		copy(spec.Type[:], t.Type)
   213  		copy(b[off+int(unsafe.Sizeof(*spec)):], t.Params)
   214  		off += sn
   215  	}
   216  	return d
   217  }
   218  
   219  // CreateDevice creates a device-mapper device with the given target spec. It returns
   220  // the path of the new device node.
   221  func CreateDevice(name string, flags CreateFlags, targets []Target) (_ string, err error) {
   222  	f, err := openMapperWrapper()
   223  	if err != nil {
   224  		return "", err
   225  	}
   226  	defer f.Close()
   227  
   228  	var d dmIoctl
   229  	size := int(unsafe.Sizeof(d))
   230  	initIoctl(&d, size, name)
   231  	err = devMapperIoctl(f, _DM_DEV_CREATE, &d)
   232  	if err != nil {
   233  		return "", err
   234  	}
   235  	defer func() {
   236  		if err != nil {
   237  			_ = removeDeviceWrapper(f, name)
   238  		}
   239  	}()
   240  
   241  	dev := int(d.Dev)
   242  
   243  	di := makeTableIoctl(name, targets)
   244  	if flags&CreateReadOnly != 0 {
   245  		di.Flags |= _DM_READONLY_FLAG
   246  	}
   247  	err = devMapperIoctl(f, _DM_TABLE_LOAD, di)
   248  	if err != nil {
   249  		return "", err
   250  	}
   251  	initIoctl(&d, size, name)
   252  	err = devMapperIoctl(f, _DM_DEV_SUSPEND, &d)
   253  	if err != nil {
   254  		return "", err
   255  	}
   256  
   257  	p := path.Join("/dev/mapper", name)
   258  	os.Remove(p)
   259  	err = unix.Mknod(p, unix.S_IFBLK|0600, dev)
   260  	if err != nil {
   261  		return "", nil
   262  	}
   263  
   264  	return p, nil
   265  }
   266  
   267  // RemoveDevice removes a device-mapper device and its associated device node.
   268  func RemoveDevice(name string) (err error) {
   269  	rm := func() error {
   270  		f, err := openMapperWrapper()
   271  		if err != nil {
   272  			return err
   273  		}
   274  		defer f.Close()
   275  		os.Remove(path.Join("/dev/mapper", name))
   276  		return removeDeviceWrapper(f, name)
   277  	}
   278  
   279  	// This is workaround for "device or resource busy" error, which occasionally happens after the device mapper
   280  	// target has been unmounted.
   281  	for i := 0; i < 10; i++ {
   282  		if err = rm(); err != nil {
   283  			if e, ok := err.(*dmError); !ok || e.Err != syscall.EBUSY { //nolint:errorlint
   284  				break
   285  			}
   286  			time.Sleep(10 * time.Millisecond)
   287  			continue
   288  		}
   289  		break
   290  	}
   291  	return err
   292  }
   293  
   294  func removeDevice(f *os.File, name string) error {
   295  	var d dmIoctl
   296  	initIoctl(&d, int(unsafe.Sizeof(d)), name)
   297  	err := devMapperIoctl(f, _DM_DEV_REMOVE, &d)
   298  	if err != nil {
   299  		return err
   300  	}
   301  	return nil
   302  }
   303  

View as plain text