gopkg.in/docker/docker.v23@v23.0.11/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) [unix.LO_NAME_SIZE]uint8 {
    23  	var dst [unix.LO_NAME_SIZE]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  	return unix.IoctlRetInt(int(f.Fd()), unix.LOOP_CTL_GET_FREE)
    35  }
    36  
    37  func openNextAvailableLoopback(index int, sparseFile *os.File) (loopFile *os.File, err error) {
    38  	// Start looking for a free /dev/loop
    39  	for {
    40  		target := fmt.Sprintf("/dev/loop%d", index)
    41  		index++
    42  
    43  		fi, err := os.Stat(target)
    44  		if err != nil {
    45  			if os.IsNotExist(err) {
    46  				logrus.Error("There are no more loopback devices available.")
    47  			}
    48  			return nil, ErrAttachLoopbackDevice
    49  		}
    50  
    51  		if fi.Mode()&os.ModeDevice != os.ModeDevice {
    52  			logrus.Errorf("Loopback device %s is not a block device.", target)
    53  			continue
    54  		}
    55  
    56  		// OpenFile adds O_CLOEXEC
    57  		loopFile, err = os.OpenFile(target, os.O_RDWR, 0644)
    58  		if err != nil {
    59  			logrus.Errorf("Error opening loopback device: %s", err)
    60  			return nil, ErrAttachLoopbackDevice
    61  		}
    62  
    63  		// Try to attach to the loop file
    64  		if err = unix.IoctlSetInt(int(loopFile.Fd()), unix.LOOP_SET_FD, int(sparseFile.Fd())); err != nil {
    65  			loopFile.Close()
    66  
    67  			// If the error is EBUSY, then try the next loopback
    68  			if err != unix.EBUSY {
    69  				logrus.Errorf("Cannot set up loopback device %s: %s", target, err)
    70  				return nil, ErrAttachLoopbackDevice
    71  			}
    72  
    73  			// Otherwise, we keep going with the loop
    74  			continue
    75  		}
    76  		// In case of success, we finished. Break the loop.
    77  		break
    78  	}
    79  
    80  	// This can't happen, but let's be sure
    81  	if loopFile == nil {
    82  		logrus.Errorf("Unreachable code reached! Error attaching %s to a loopback device.", sparseFile.Name())
    83  		return nil, ErrAttachLoopbackDevice
    84  	}
    85  
    86  	return loopFile, nil
    87  }
    88  
    89  // AttachLoopDevice attaches the given sparse file to the next
    90  // available loopback device. It returns an opened *os.File.
    91  func AttachLoopDevice(sparseName string) (loop *os.File, err error) {
    92  	// Try to retrieve the next available loopback device via syscall.
    93  	// If it fails, we discard error and start looping for a
    94  	// loopback from index 0.
    95  	startIndex, err := getNextFreeLoopbackIndex()
    96  	if err != nil {
    97  		logrus.Debugf("Error retrieving the next available loopback: %s", err)
    98  	}
    99  
   100  	// OpenFile adds O_CLOEXEC
   101  	sparseFile, err := os.OpenFile(sparseName, os.O_RDWR, 0644)
   102  	if err != nil {
   103  		logrus.Errorf("Error opening sparse file %s: %s", sparseName, err)
   104  		return nil, ErrAttachLoopbackDevice
   105  	}
   106  	defer sparseFile.Close()
   107  
   108  	loopFile, err := openNextAvailableLoopback(startIndex, sparseFile)
   109  	if err != nil {
   110  		return nil, err
   111  	}
   112  
   113  	// Set the status of the loopback device
   114  	loopInfo := &unix.LoopInfo64{
   115  		File_name: stringToLoopName(loopFile.Name()),
   116  		Offset:    0,
   117  		Flags:     unix.LO_FLAGS_AUTOCLEAR,
   118  	}
   119  
   120  	if err = unix.IoctlLoopSetStatus64(int(loopFile.Fd()), loopInfo); err != nil {
   121  		logrus.Errorf("Cannot set up loopback device info: %s", err)
   122  
   123  		// If the call failed, then free the loopback device
   124  		if err = unix.IoctlSetInt(int(loopFile.Fd()), unix.LOOP_CLR_FD, 0); err != nil {
   125  			logrus.Error("Error while cleaning up the loopback device")
   126  		}
   127  		loopFile.Close()
   128  		return nil, ErrAttachLoopbackDevice
   129  	}
   130  
   131  	return loopFile, nil
   132  }