github.com/hpcng/singularity@v3.1.1+incompatible/pkg/util/loop/loop_linux.go (about)

     1  // Copyright (c) 2018-2019, Sylabs Inc. All rights reserved.
     2  // This software is licensed under a 3-clause BSD license. Please consult the
     3  // LICENSE.md file distributed with the sources of this project regarding your
     4  // rights to use or distribute this software.
     5  
     6  package loop
     7  
     8  import (
     9  	"fmt"
    10  	"os"
    11  	"syscall"
    12  	"unsafe"
    13  
    14  	"github.com/sylabs/singularity/pkg/util/fs/lock"
    15  )
    16  
    17  // AttachFromFile finds a free loop device, opens it, and stores file descriptor
    18  // provided by image file pointer
    19  func (loop *Device) AttachFromFile(image *os.File, mode int, number *int) error {
    20  	var path string
    21  	var loopFd int
    22  
    23  	if image == nil {
    24  		return fmt.Errorf("empty file pointer")
    25  	}
    26  
    27  	fi, err := image.Stat()
    28  	if err != nil {
    29  		return err
    30  	}
    31  	st := fi.Sys().(*syscall.Stat_t)
    32  	imageIno := st.Ino
    33  	imageDev := st.Dev
    34  
    35  	fd, err := lock.Exclusive("/dev")
    36  	if err != nil {
    37  		return err
    38  	}
    39  	defer lock.Release(fd)
    40  
    41  	freeDevice := -1
    42  
    43  	for device := 0; device <= loop.MaxLoopDevices; device++ {
    44  		*number = device
    45  
    46  		if device == loop.MaxLoopDevices {
    47  			if loop.Shared {
    48  				loop.Shared = false
    49  				if freeDevice != -1 {
    50  					device = freeDevice
    51  					continue
    52  				}
    53  			}
    54  			return fmt.Errorf("no loop devices available")
    55  		}
    56  
    57  		path = fmt.Sprintf("/dev/loop%d", device)
    58  		if fi, err := os.Stat(path); err != nil {
    59  			dev := int((7 << 8) | (device & 0xff) | ((device & 0xfff00) << 12))
    60  			esys := syscall.Mknod(path, syscall.S_IFBLK|0660, dev)
    61  			if errno, ok := esys.(syscall.Errno); ok {
    62  				if errno != syscall.EEXIST {
    63  					return esys
    64  				}
    65  			}
    66  		} else if fi.Mode()&os.ModeDevice == 0 {
    67  			return fmt.Errorf("%s is not a block device", path)
    68  		}
    69  
    70  		if loopFd, err = syscall.Open(path, mode, 0600); err != nil {
    71  			continue
    72  		}
    73  		if loop.Shared {
    74  			status, err := GetStatusFromFd(uintptr(loopFd))
    75  			syscall.Close(loopFd)
    76  			if err != nil {
    77  				return err
    78  			}
    79  			// there is no associated image with loop device, save indice so second loop
    80  			// iteration will start from this device
    81  			if status.Inode == 0 && freeDevice == -1 {
    82  				freeDevice = device
    83  				continue
    84  			}
    85  			if status.Inode == imageIno && status.Device == imageDev &&
    86  				status.Flags&FlagsReadOnly == loop.Info.Flags&FlagsReadOnly &&
    87  				status.Offset == loop.Info.Offset && status.SizeLimit == loop.Info.SizeLimit {
    88  				return nil
    89  			}
    90  		} else {
    91  			_, _, esys := syscall.Syscall(syscall.SYS_IOCTL, uintptr(loopFd), CmdSetFd, image.Fd())
    92  			if esys != 0 {
    93  				syscall.Close(loopFd)
    94  				continue
    95  			}
    96  			break
    97  		}
    98  	}
    99  
   100  	if _, _, err := syscall.Syscall(syscall.SYS_FCNTL, uintptr(loopFd), syscall.F_SETFD, syscall.FD_CLOEXEC); err != 0 {
   101  		return fmt.Errorf("failed to set close-on-exec on loop device %s: %s", path, err.Error())
   102  	}
   103  
   104  	if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, uintptr(loopFd), CmdSetStatus64, uintptr(unsafe.Pointer(loop.Info))); err != 0 {
   105  		return fmt.Errorf("Failed to set loop flags on loop device: %s", syscall.Errno(err))
   106  	}
   107  
   108  	return nil
   109  }
   110  
   111  // AttachFromPath finds a free loop device, opens it, and stores file descriptor
   112  // of opened image path
   113  func (loop *Device) AttachFromPath(image string, mode int, number *int) error {
   114  	file, err := os.OpenFile(image, mode, 0600)
   115  	if err != nil {
   116  		return err
   117  	}
   118  	return loop.AttachFromFile(file, mode, number)
   119  }
   120  
   121  // GetStatusFromFd gets info status about an opened loop device
   122  func GetStatusFromFd(fd uintptr) (*Info64, error) {
   123  	info := &Info64{}
   124  	_, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, CmdGetStatus64, uintptr(unsafe.Pointer(info)))
   125  	if err != syscall.ENXIO && err != 0 {
   126  		return nil, fmt.Errorf("Failed to get loop flags for loop device: %s", err.Error())
   127  	}
   128  	return info, nil
   129  }
   130  
   131  // GetStatusFromPath gets info status about a loop device from path
   132  func GetStatusFromPath(path string) (*Info64, error) {
   133  	loop, err := os.Open(path)
   134  	if err != nil {
   135  		return nil, fmt.Errorf("failed to open loop device %s: %s", path, err)
   136  	}
   137  	return GetStatusFromFd(loop.Fd())
   138  }