The struct is now part of golang.org/x/sys. Follow-up for #9805 Signed-off-by: Alexandru Matei <alexandru.matei@uipath.com>
		
			
				
	
	
		
			223 lines
		
	
	
		
			5.9 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			223 lines
		
	
	
		
			5.9 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
/*
 | 
						|
   Copyright The containerd Authors.
 | 
						|
 | 
						|
   Licensed under the Apache License, Version 2.0 (the "License");
 | 
						|
   you may not use this file except in compliance with the License.
 | 
						|
   You may obtain a copy of the License at
 | 
						|
 | 
						|
       http://www.apache.org/licenses/LICENSE-2.0
 | 
						|
 | 
						|
   Unless required by applicable law or agreed to in writing, software
 | 
						|
   distributed under the License is distributed on an "AS IS" BASIS,
 | 
						|
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
						|
   See the License for the specific language governing permissions and
 | 
						|
   limitations under the License.
 | 
						|
*/
 | 
						|
 | 
						|
package mount
 | 
						|
 | 
						|
import (
 | 
						|
	"errors"
 | 
						|
	"fmt"
 | 
						|
	"os"
 | 
						|
	"strings"
 | 
						|
	"time"
 | 
						|
 | 
						|
	"github.com/containerd/containerd/v2/internal/randutil"
 | 
						|
	kernel "github.com/containerd/containerd/v2/pkg/kernelversion"
 | 
						|
	"golang.org/x/sys/unix"
 | 
						|
)
 | 
						|
 | 
						|
const (
 | 
						|
	loopControlPath = "/dev/loop-control"
 | 
						|
	loopDevFormat   = "/dev/loop%d"
 | 
						|
 | 
						|
	ebusyString = "device or resource busy"
 | 
						|
)
 | 
						|
 | 
						|
// LoopParams parameters to control loop device setup
 | 
						|
type LoopParams struct {
 | 
						|
	// Loop device should forbid write
 | 
						|
	Readonly bool
 | 
						|
	// Loop device is automatically cleared by kernel when the
 | 
						|
	// last opener closes it
 | 
						|
	Autoclear bool
 | 
						|
	// Use direct IO to access the loop backing file
 | 
						|
	Direct bool
 | 
						|
}
 | 
						|
 | 
						|
func getFreeLoopDev() (uint32, error) {
 | 
						|
	ctrl, err := os.OpenFile(loopControlPath, os.O_RDWR, 0)
 | 
						|
	if err != nil {
 | 
						|
		return 0, fmt.Errorf("could not open %v: %v", loopControlPath, err)
 | 
						|
	}
 | 
						|
	defer ctrl.Close()
 | 
						|
	num, err := unix.IoctlRetInt(int(ctrl.Fd()), unix.LOOP_CTL_GET_FREE)
 | 
						|
	if err != nil {
 | 
						|
		return 0, fmt.Errorf("could not get free loop device: %w", err)
 | 
						|
	}
 | 
						|
	return uint32(num), nil
 | 
						|
}
 | 
						|
 | 
						|
// setupLoopDev attaches the backing file to the loop device and returns
 | 
						|
// the file handle for the loop device. The caller is responsible for
 | 
						|
// closing the file handle.
 | 
						|
func setupLoopDev(backingFile, loopDev string, param LoopParams) (_ *os.File, retErr error) {
 | 
						|
	// 1. Open backing file and loop device
 | 
						|
	flags := os.O_RDWR
 | 
						|
	if param.Readonly {
 | 
						|
		flags = os.O_RDONLY
 | 
						|
	}
 | 
						|
 | 
						|
	back, err := os.OpenFile(backingFile, flags, 0)
 | 
						|
	if err != nil {
 | 
						|
		return nil, fmt.Errorf("could not open backing file: %s: %w", backingFile, err)
 | 
						|
	}
 | 
						|
	defer back.Close()
 | 
						|
 | 
						|
	loop, err := os.OpenFile(loopDev, flags, 0)
 | 
						|
	if err != nil {
 | 
						|
		return nil, fmt.Errorf("could not open loop device: %s: %w", loopDev, err)
 | 
						|
	}
 | 
						|
	defer func() {
 | 
						|
		if retErr != nil {
 | 
						|
			loop.Close()
 | 
						|
		}
 | 
						|
	}()
 | 
						|
 | 
						|
	fiveDotEight := kernel.KernelVersion{Kernel: 5, Major: 8}
 | 
						|
	if ok, err := kernel.GreaterEqualThan(fiveDotEight); err == nil && ok {
 | 
						|
		config := unix.LoopConfig{
 | 
						|
			Fd: uint32(back.Fd()),
 | 
						|
		}
 | 
						|
 | 
						|
		copy(config.Info.File_name[:], backingFile)
 | 
						|
		if param.Readonly {
 | 
						|
			config.Info.Flags |= unix.LO_FLAGS_READ_ONLY
 | 
						|
		}
 | 
						|
 | 
						|
		if param.Autoclear {
 | 
						|
			config.Info.Flags |= unix.LO_FLAGS_AUTOCLEAR
 | 
						|
		}
 | 
						|
 | 
						|
		if param.Direct {
 | 
						|
			config.Info.Flags |= unix.LO_FLAGS_DIRECT_IO
 | 
						|
		}
 | 
						|
 | 
						|
		if err := unix.IoctlLoopConfigure(int(loop.Fd()), &config); err != nil {
 | 
						|
			return nil, fmt.Errorf("failed to configure loop device: %s: %w", loopDev, err)
 | 
						|
		}
 | 
						|
 | 
						|
		return loop, nil
 | 
						|
	}
 | 
						|
 | 
						|
	// 2. Set FD
 | 
						|
	if err := unix.IoctlSetInt(int(loop.Fd()), unix.LOOP_SET_FD, int(back.Fd())); err != nil {
 | 
						|
		return nil, fmt.Errorf("could not set loop fd for device: %s: %w", loopDev, err)
 | 
						|
	}
 | 
						|
 | 
						|
	defer func() {
 | 
						|
		if retErr != nil {
 | 
						|
			_ = unix.IoctlSetInt(int(loop.Fd()), unix.LOOP_CLR_FD, 0)
 | 
						|
		}
 | 
						|
	}()
 | 
						|
 | 
						|
	// 3. Set Info
 | 
						|
	info := unix.LoopInfo64{}
 | 
						|
	copy(info.File_name[:], backingFile)
 | 
						|
	if param.Readonly {
 | 
						|
		info.Flags |= unix.LO_FLAGS_READ_ONLY
 | 
						|
	}
 | 
						|
 | 
						|
	if param.Autoclear {
 | 
						|
		info.Flags |= unix.LO_FLAGS_AUTOCLEAR
 | 
						|
	}
 | 
						|
 | 
						|
	err = unix.IoctlLoopSetStatus64(int(loop.Fd()), &info)
 | 
						|
	if err != nil {
 | 
						|
		return nil, fmt.Errorf("failed to set loop device info: %w", err)
 | 
						|
	}
 | 
						|
 | 
						|
	// 4. Set Direct IO
 | 
						|
	if param.Direct {
 | 
						|
		err = unix.IoctlSetInt(int(loop.Fd()), unix.LOOP_SET_DIRECT_IO, 1)
 | 
						|
		if err != nil {
 | 
						|
			return nil, fmt.Errorf("failed to setup loop with direct: %w", err)
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return loop, nil
 | 
						|
}
 | 
						|
 | 
						|
// setupLoop looks for (and possibly creates) a free loop device, and
 | 
						|
// then attaches backingFile to it.
 | 
						|
//
 | 
						|
// When autoclear is true, caller should take care to close it when
 | 
						|
// done with the loop device. The loop device file handle keeps
 | 
						|
// loFlagsAutoclear in effect and we rely on it to clean up the loop
 | 
						|
// device. If caller closes the file handle after mounting the device,
 | 
						|
// kernel will clear the loop device after it is umounted. Otherwise
 | 
						|
// the loop device is cleared when the file handle is closed.
 | 
						|
//
 | 
						|
// When autoclear is false, caller should be responsible to remove
 | 
						|
// the loop device when done with it.
 | 
						|
//
 | 
						|
// Upon success, the file handle to the loop device is returned.
 | 
						|
func setupLoop(backingFile string, param LoopParams) (*os.File, error) {
 | 
						|
	for retry := 1; retry < 100; retry++ {
 | 
						|
		num, err := getFreeLoopDev()
 | 
						|
		if err != nil {
 | 
						|
			return nil, err
 | 
						|
		}
 | 
						|
 | 
						|
		loopDev := fmt.Sprintf(loopDevFormat, num)
 | 
						|
		file, err := setupLoopDev(backingFile, loopDev, param)
 | 
						|
		if err != nil {
 | 
						|
			// Per util-linux/sys-utils/losetup.c:create_loop(),
 | 
						|
			// free loop device can race and we end up failing
 | 
						|
			// with EBUSY when trying to set it up.
 | 
						|
			if strings.Contains(err.Error(), ebusyString) {
 | 
						|
				// Fallback a bit to avoid live lock
 | 
						|
				time.Sleep(time.Millisecond * time.Duration(randutil.Intn(retry*10)))
 | 
						|
				continue
 | 
						|
			}
 | 
						|
			return nil, err
 | 
						|
		}
 | 
						|
 | 
						|
		return file, nil
 | 
						|
	}
 | 
						|
 | 
						|
	return nil, errors.New("timeout creating new loopback device")
 | 
						|
}
 | 
						|
 | 
						|
func removeLoop(loopdev string) error {
 | 
						|
	file, err := os.Open(loopdev)
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
	defer file.Close()
 | 
						|
 | 
						|
	return unix.IoctlSetInt(int(file.Fd()), unix.LOOP_CLR_FD, 0)
 | 
						|
}
 | 
						|
 | 
						|
// AttachLoopDevice attaches a specified backing file to a loop device
 | 
						|
func AttachLoopDevice(backingFile string) (string, error) {
 | 
						|
	file, err := setupLoop(backingFile, LoopParams{})
 | 
						|
	if err != nil {
 | 
						|
		return "", err
 | 
						|
	}
 | 
						|
	defer file.Close()
 | 
						|
	return file.Name(), nil
 | 
						|
}
 | 
						|
 | 
						|
// DetachLoopDevice detaches the provided loop devices
 | 
						|
func DetachLoopDevice(devices ...string) error {
 | 
						|
	for _, dev := range devices {
 | 
						|
		if err := removeLoop(dev); err != nil {
 | 
						|
			return fmt.Errorf("failed to remove loop device: %s: %w", dev, err)
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return nil
 | 
						|
}
 |