package loop import ( "fmt" "github.com/kairos-io/kairos-agent/v2/pkg/config" "os" "syscall" "unsafe" v1 "github.com/kairos-io/kairos-agent/v2/pkg/types/v1" "golang.org/x/sys/unix" ) // syscalls will return an errno type (which implements error) for all calls, // including success (errno 0) so we need to check its value to know if its an actual error or not func errnoIsErr(err error) error { if err != nil && err.(syscall.Errno) != 0 { return err } return nil } // Loop will setup a /dev/loopX device linked to the image file by using syscalls directly to set it func Loop(img *v1.Image, cfg *config.Config) (loopDevice string, err error) { log := cfg.Logger log.Debugf("Opening loop control device") fd, err := cfg.Fs.OpenFile("/dev/loop-control", os.O_RDONLY, 0o644) if err != nil { log.Error("failed to open /dev/loop-control") return loopDevice, err } defer fd.Close() log.Debugf("Getting free loop device") loopInt, _, err := cfg.Syscall.Syscall(syscall.SYS_IOCTL, fd.Fd(), unix.LOOP_CTL_GET_FREE, 0) if errnoIsErr(err) != nil { log.Error("failed to get loop device") return loopDevice, err } loopDevice = fmt.Sprintf("/dev/loop%d", loopInt) log.Logger.Debug().Str("device", loopDevice).Msg("Opening loop device") loopFile, err := cfg.Fs.OpenFile(loopDevice, os.O_RDWR, 0) if err != nil { log.Error("failed to open loop device") return loopDevice, err } log.Logger.Debug().Str("image", img.File).Msg("Opening img file") imageFile, err := cfg.Fs.OpenFile(img.File, os.O_RDWR, os.ModePerm) if err != nil { log.Error("failed to open image file") return loopDevice, err } defer loopFile.Close() defer imageFile.Close() log.Debugf("Setting loop device") _, _, err = cfg.Syscall.Syscall( syscall.SYS_IOCTL, loopFile.Fd(), unix.LOOP_SET_FD, imageFile.Fd(), ) if errnoIsErr(err) != nil { log.Error("failed to set loop device") return loopDevice, err } // Force kernel to scan partition table on loop device status := &unix.LoopInfo64{ Flags: unix.LO_FLAGS_PARTSCAN, } // Dont set read only flag status.Flags &= ^uint32(unix.LO_FLAGS_READ_ONLY) log.Debugf("Setting loop flags") _, _, err = cfg.Syscall.Syscall( syscall.SYS_IOCTL, loopFile.Fd(), unix.LOOP_SET_STATUS64, uintptr(unsafe.Pointer(status)), ) if errnoIsErr(err) != nil { log.Error("failed to set loop device status") return loopDevice, err } return loopDevice, nil } // Unloop will clear a loop device and free the underlying image linked to it func Unloop(loopDevice string, cfg *config.Config) error { log := cfg.Logger log.Logger.Debug().Str("device", loopDevice).Msg("Opening loop device") fd, err := cfg.Fs.OpenFile(loopDevice, os.O_RDONLY, 0o644) if err != nil { log.Error("failed to set open loop device") return err } defer fd.Close() log.Debugf("Clearing loop device") _, _, err = cfg.Syscall.Syscall(syscall.SYS_IOCTL, fd.Fd(), unix.LOOP_CLR_FD, 0) if errnoIsErr(err) != nil { log.Error("failed to set loop device status") return err } return nil }