github.com/tompao/docker@v1.9.1/pkg/devicemapper/attach_loopback.go (about)

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