kairos-agent/pkg/utils/loop/loopback.go

134 lines
3.1 KiB
Go
Raw Normal View History

package loop
import (
"fmt"
"golang.org/x/sys/unix"
"os"
"sync"
"syscall"
"unsafe"
"github.com/kairos-io/kairos-agent/v2/pkg/config"
v1 "github.com/kairos-io/kairos-agent/v2/pkg/types/v1"
)
var (
instance *LoopManager
once sync.Once
)
type LoopManager struct {
mu sync.Mutex
cfg *config.Config
}
// GetLoopManager returns a singleton instance of LoopManager
// So we can safely call it from different places and it will work properly with the mutex locking
func GetLoopManager(cfg *config.Config) *LoopManager {
once.Do(func() {
instance = &LoopManager{
cfg: cfg,
}
})
return instance
}
func (lm *LoopManager) errnoIsErr(err error) error {
if err != nil && err.(syscall.Errno) != 0 {
return err
}
return nil
}
func (lm *LoopManager) Loop(img *v1.Image) (loopDevice string, err error) {
lm.mu.Lock()
defer lm.mu.Unlock()
log := lm.cfg.Logger
log.Debugf("Opening loop control device")
fd, err := lm.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 := lm.cfg.Syscall.Syscall(syscall.SYS_IOCTL, fd.Fd(), unix.LOOP_CTL_GET_FREE, 0)
if lm.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 := lm.cfg.Fs.OpenFile(loopDevice, os.O_RDWR, 0)
if err != nil {
log.Error("failed to open loop device")
return loopDevice, err
}
defer loopFile.Close()
log.Logger.Debug().Str("image", img.File).Msg("Opening img file")
imageFile, err := lm.cfg.Fs.OpenFile(img.File, os.O_RDWR, os.ModePerm)
if err != nil {
log.Error("failed to open image file")
return loopDevice, err
}
defer imageFile.Close()
log.Debugf("Setting loop device")
_, _, err = lm.cfg.Syscall.Syscall(
syscall.SYS_IOCTL,
loopFile.Fd(),
unix.LOOP_SET_FD,
imageFile.Fd(),
)
if lm.errnoIsErr(err) != nil {
log.Error("failed to set loop device")
return loopDevice, err
}
status := &unix.LoopInfo64{
Flags: unix.LO_FLAGS_PARTSCAN,
}
status.Flags &= ^uint32(unix.LO_FLAGS_READ_ONLY)
log.Debugf("Setting loop flags")
_, _, err = lm.cfg.Syscall.Syscall(
syscall.SYS_IOCTL,
loopFile.Fd(),
unix.LOOP_SET_STATUS64,
uintptr(unsafe.Pointer(status)),
)
if lm.errnoIsErr(err) != nil {
log.Error("failed to set loop device status")
return loopDevice, err
}
return loopDevice, nil
}
func (lm *LoopManager) Unloop(loopDevice string) error {
lm.mu.Lock()
defer lm.mu.Unlock()
log := lm.cfg.Logger
log.Logger.Debug().Str("device", loopDevice).Msg("Opening loop device")
fd, err := lm.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 = lm.cfg.Syscall.Syscall(syscall.SYS_IOCTL, fd.Fd(), unix.LOOP_CLR_FD, 0)
if lm.errnoIsErr(err) != nil {
log.Error("failed to set loop device status")
return err
}
return nil
}