From 6ad942294b65746338b03b81d72f89dd2a65b52f Mon Sep 17 00:00:00 2001 From: Itxaka Date: Thu, 2 Jan 2025 15:51:33 +0100 Subject: [PATCH] Use a singleton loopManager with mutex locking Signed-off-by: Itxaka --- pkg/elemental/elemental.go | 4 +- pkg/utils/loop/loopback.go | 81 ++++++++++++++++++++++++-------------- 2 files changed, 53 insertions(+), 32 deletions(-) diff --git a/pkg/elemental/elemental.go b/pkg/elemental/elemental.go index 932422e..9839e6f 100644 --- a/pkg/elemental/elemental.go +++ b/pkg/elemental/elemental.go @@ -236,7 +236,7 @@ func (e Elemental) MountImage(img *v1.Image, opts ...string) error { if err != nil { return err } - loopDevice, err := loop.Loop(img, e.config) + loopDevice, err := loop.GetLoopManager(e.config).Loop(img) if err != nil { return err } @@ -265,7 +265,7 @@ func (e Elemental) UnmountImage(img *v1.Image) error { if err != nil { return err } - err = loop.Unloop(img.LoopDevice, e.config) + err = loop.GetLoopManager(e.config).Unloop(img.LoopDevice) if err != nil { return err } diff --git a/pkg/utils/loop/loopback.go b/pkg/utils/loop/loopback.go index 74673f0..28b67d2 100644 --- a/pkg/utils/loop/loopback.go +++ b/pkg/utils/loop/loopback.go @@ -2,87 +2,106 @@ package loop import ( "fmt" - "github.com/kairos-io/kairos-agent/v2/pkg/config" + "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" - "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 { +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 } -// 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 +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 := 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 { 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 { + 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 := cfg.Fs.OpenFile(loopDevice, os.O_RDWR, 0) + 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 := 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 { 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( + _, _, err = lm.cfg.Syscall.Syscall( syscall.SYS_IOCTL, loopFile.Fd(), unix.LOOP_SET_FD, imageFile.Fd(), ) - if errnoIsErr(err) != nil { + if lm.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( + _, _, err = lm.cfg.Syscall.Syscall( syscall.SYS_IOCTL, loopFile.Fd(), unix.LOOP_SET_STATUS64, uintptr(unsafe.Pointer(status)), ) - - if errnoIsErr(err) != nil { + if lm.errnoIsErr(err) != nil { log.Error("failed to set loop device status") return loopDevice, err } @@ -90,20 +109,22 @@ func Loop(img *v1.Image, cfg *config.Config) (loopDevice string, err error) { 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 +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 := cfg.Fs.OpenFile(loopDevice, os.O_RDONLY, 0o644) + 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 = 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") return err }