github.com/demonoid81/moby@v0.0.0-20200517203328-62dd8e17c460/pkg/loopback/attach_loopback.go (about)

     1  // +build linux
     2  
     3  package loopback // import "github.com/demonoid81/moby/pkg/loopback"
     4  
     5  import (
     6  	"errors"
     7  	"fmt"
     8  	"os"
     9  
    10  	"github.com/sirupsen/logrus"
    11  	"golang.org/x/sys/unix"
    12  )
    13  
    14  // Loopback related errors
    15  var (
    16  	ErrAttachLoopbackDevice   = errors.New("loopback attach failed")
    17  	ErrGetLoopbackBackingFile = errors.New("Unable to get loopback backing file")
    18  	ErrSetCapacity            = errors.New("Unable set loopback capacity")
    19  )
    20  
    21  func stringToLoopName(src string) [LoNameSize]uint8 {
    22  	var dst [LoNameSize]uint8
    23  	copy(dst[:], src[:])
    24  	return dst
    25  }
    26  
    27  func getNextFreeLoopbackIndex() (int, error) {
    28  	f, err := os.OpenFile("/dev/loop-control", os.O_RDONLY, 0644)
    29  	if err != nil {
    30  		return 0, err
    31  	}
    32  	defer f.Close()
    33  
    34  	index, err := ioctlLoopCtlGetFree(f.Fd())
    35  	if index < 0 {
    36  		index = 0
    37  	}
    38  	return index, err
    39  }
    40  
    41  func openNextAvailableLoopback(index int, sparseFile *os.File) (loopFile *os.File, err error) {
    42  	// Start looking for a free /dev/loop
    43  	for {
    44  		target := fmt.Sprintf("/dev/loop%d", index)
    45  		index++
    46  
    47  		fi, err := os.Stat(target)
    48  		if err != nil {
    49  			if os.IsNotExist(err) {
    50  				logrus.Error("There are no more loopback devices available.")
    51  			}
    52  			return nil, ErrAttachLoopbackDevice
    53  		}
    54  
    55  		if fi.Mode()&os.ModeDevice != os.ModeDevice {
    56  			logrus.Errorf("Loopback device %s is not a block device.", target)
    57  			continue
    58  		}
    59  
    60  		// OpenFile adds O_CLOEXEC
    61  		loopFile, err = os.OpenFile(target, os.O_RDWR, 0644)
    62  		if err != nil {
    63  			logrus.Errorf("Error opening loopback device: %s", err)
    64  			return nil, ErrAttachLoopbackDevice
    65  		}
    66  
    67  		// Try to attach to the loop file
    68  		if err := ioctlLoopSetFd(loopFile.Fd(), sparseFile.Fd()); err != nil {
    69  			loopFile.Close()
    70  
    71  			// If the error is EBUSY, then try the next loopback
    72  			if err != unix.EBUSY {
    73  				logrus.Errorf("Cannot set up loopback device %s: %s", target, err)
    74  				return nil, ErrAttachLoopbackDevice
    75  			}
    76  
    77  			// Otherwise, we keep going with the loop
    78  			continue
    79  		}
    80  		// In case of success, we finished. Break the loop.
    81  		break
    82  	}
    83  
    84  	// This can't happen, but let's be sure
    85  	if loopFile == nil {
    86  		logrus.Errorf("Unreachable code reached! Error attaching %s to a loopback device.", sparseFile.Name())
    87  		return nil, ErrAttachLoopbackDevice
    88  	}
    89  
    90  	return loopFile, nil
    91  }
    92  
    93  // AttachLoopDevice attaches the given sparse file to the next
    94  // available loopback device. It returns an opened *os.File.
    95  func AttachLoopDevice(sparseName string) (loop *os.File, err error) {
    96  
    97  	// Try to retrieve the next available loopback device via syscall.
    98  	// If it fails, we discard error and start looping for a
    99  	// loopback from index 0.
   100  	startIndex, err := getNextFreeLoopbackIndex()
   101  	if err != nil {
   102  		logrus.Debugf("Error retrieving the next available loopback: %s", err)
   103  	}
   104  
   105  	// OpenFile adds O_CLOEXEC
   106  	sparseFile, err := os.OpenFile(sparseName, os.O_RDWR, 0644)
   107  	if err != nil {
   108  		logrus.Errorf("Error opening sparse file %s: %s", sparseName, err)
   109  		return nil, ErrAttachLoopbackDevice
   110  	}
   111  	defer sparseFile.Close()
   112  
   113  	loopFile, err := openNextAvailableLoopback(startIndex, sparseFile)
   114  	if err != nil {
   115  		return nil, err
   116  	}
   117  
   118  	// Set the status of the loopback device
   119  	loopInfo := &unix.LoopInfo64{
   120  		File_name: stringToLoopName(loopFile.Name()),
   121  		Offset:    0,
   122  		Flags:     LoFlagsAutoClear,
   123  	}
   124  
   125  	if err := ioctlLoopSetStatus64(loopFile.Fd(), loopInfo); err != nil {
   126  		logrus.Errorf("Cannot set up loopback device info: %s", err)
   127  
   128  		// If the call failed, then free the loopback device
   129  		if err := ioctlLoopClrFd(loopFile.Fd()); err != nil {
   130  			logrus.Error("Error while cleaning up the loopback device")
   131  		}
   132  		loopFile.Close()
   133  		return nil, ErrAttachLoopbackDevice
   134  	}
   135  
   136  	return loopFile, nil
   137  }