Use a singleton loopManager with mutex locking

Signed-off-by: Itxaka <itxaka@kairos.io>
This commit is contained in:
Itxaka 2025-01-02 15:51:33 +01:00
parent 71e8e5b801
commit 6ad942294b
2 changed files with 53 additions and 32 deletions

View File

@ -236,7 +236,7 @@ func (e Elemental) MountImage(img *v1.Image, opts ...string) error {
if err != nil { if err != nil {
return err return err
} }
loopDevice, err := loop.Loop(img, e.config) loopDevice, err := loop.GetLoopManager(e.config).Loop(img)
if err != nil { if err != nil {
return err return err
} }
@ -265,7 +265,7 @@ func (e Elemental) UnmountImage(img *v1.Image) error {
if err != nil { if err != nil {
return err return err
} }
err = loop.Unloop(img.LoopDevice, e.config) err = loop.GetLoopManager(e.config).Unloop(img.LoopDevice)
if err != nil { if err != nil {
return err return err
} }

View File

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