gopkg.in/docker/docker.v20@v20.10.27/pkg/loopback/attach_loopback.go (about)

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