mirror of
https://github.com/yunionio/cloudpods.git
synced 2026-09-21 14:19:49 +08:00
227 lines
5.4 KiB
Go
227 lines
5.4 KiB
Go
package manager
|
|
|
|
import (
|
|
"fmt"
|
|
"math/rand"
|
|
"strconv"
|
|
"strings"
|
|
"sync"
|
|
|
|
"yunion.io/x/pkg/errors"
|
|
|
|
"yunion.io/x/onecloud/pkg/util/fileutils2"
|
|
"yunion.io/x/onecloud/pkg/util/losetup"
|
|
"yunion.io/x/onecloud/pkg/util/losetup/ioctl"
|
|
"yunion.io/x/onecloud/pkg/util/mountutils"
|
|
"yunion.io/x/onecloud/pkg/util/procutils"
|
|
)
|
|
|
|
type ILoopDevice interface {
|
|
// GetDevicePath 返回设备路径
|
|
GetDevicePath() string
|
|
// IsUsed 检查设备是否在使用中
|
|
IsUsed() bool
|
|
// SetUsed 设置设备使用状态
|
|
SetUsed(used bool)
|
|
}
|
|
|
|
type ILoopManager interface {
|
|
AttachDevice(filePath string, partScan bool) (*losetup.Device, error)
|
|
DetachDevice(devPath string) error
|
|
}
|
|
|
|
// loopDevice Loop设备实现
|
|
type loopDevice struct {
|
|
devicePath string
|
|
used bool
|
|
lock *sync.Mutex
|
|
}
|
|
|
|
// NewLoopDevice 创建新的Loop设备
|
|
func NewLoopDevice(devicePath string) ILoopDevice {
|
|
return &loopDevice{
|
|
devicePath: devicePath,
|
|
used: fileutils2.IsBlockDeviceUsed(devicePath),
|
|
lock: &sync.Mutex{},
|
|
}
|
|
}
|
|
|
|
func (d *loopDevice) GetDevicePath() string {
|
|
return d.devicePath
|
|
}
|
|
|
|
func (d *loopDevice) IsUsed() bool {
|
|
return d.used
|
|
}
|
|
|
|
func (d *loopDevice) SetUsed(used bool) {
|
|
d.lock.Lock()
|
|
defer d.lock.Unlock()
|
|
|
|
d.used = used
|
|
}
|
|
|
|
func (d *loopDevice) Detach() error {
|
|
// 获取所有挂载点
|
|
mountPoints, err := d.getMountPoints()
|
|
if err != nil {
|
|
return errors.Wrap(err, "getMountPoints")
|
|
}
|
|
|
|
// 卸载所有挂载点
|
|
for _, mountPoint := range mountPoints {
|
|
if err := mountutils.Unmount(mountPoint, false); err != nil {
|
|
return errors.Wrapf(err, "umount %s", mountPoint)
|
|
}
|
|
}
|
|
|
|
// 断开loop设备
|
|
if err := losetup.DetachDevice(d.GetDevicePath()); err != nil {
|
|
return errors.Wrapf(err, "detach loop device %s", d.GetDevicePath())
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (d *loopDevice) getMountPoints() ([]string, error) {
|
|
cmd := fmt.Sprintf("mount | grep %sp1 | awk '{print $3}'", d.GetDevicePath())
|
|
output, err := procutils.NewRemoteCommandAsFarAsPossible("sh", "-c", cmd).Output()
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "exec cmd %s: %s", cmd, output)
|
|
}
|
|
return strings.Split(string(output), "\n"), nil
|
|
}
|
|
|
|
func (m *loopManager) initDevices() error {
|
|
// 使用 ls 和 grep 命令列出所有以数字结尾的 /dev/loop* 设备
|
|
cmd := "ls /dev/loop* | grep -E 'loop[0-9]+$'"
|
|
output, err := procutils.NewRemoteCommandAsFarAsPossible("sh", "-c", cmd).Output()
|
|
if err != nil {
|
|
return errors.Wrap(err, "list loop devices")
|
|
}
|
|
|
|
// 按行分割输出
|
|
devices := strings.Split(string(output), "\n")
|
|
for _, dev := range devices {
|
|
dev = strings.TrimSpace(dev)
|
|
if dev == "" {
|
|
continue
|
|
}
|
|
m.devices[dev] = NewLoopDevice(dev)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// loopManager Loop设备管理器
|
|
type loopManager struct {
|
|
devices map[string]ILoopDevice
|
|
actionLock *sync.Mutex
|
|
mapLock *sync.Mutex
|
|
}
|
|
|
|
// NewLoopManager 创建新的Loop管理器
|
|
func newLoopManager() (ILoopManager, error) {
|
|
ret := &loopManager{
|
|
devices: make(map[string]ILoopDevice),
|
|
actionLock: &sync.Mutex{},
|
|
mapLock: new(sync.Mutex),
|
|
}
|
|
|
|
if err := ret.initDevices(); err != nil {
|
|
return ret, errors.Wrap(err, "initDevices")
|
|
}
|
|
return ret, nil
|
|
}
|
|
|
|
func (m *loopManager) findNewDeviceNumber() (int, error) {
|
|
// 获取所有已使用的设备号
|
|
usedNumbers := make(map[int]bool)
|
|
for name := range m.devices {
|
|
// 从设备名称中提取数字,例如从 /dev/loop0 中提取 0
|
|
numStr := strings.TrimPrefix(name, "/dev/loop")
|
|
num, err := strconv.Atoi(numStr)
|
|
if err != nil {
|
|
return -1, errors.Wrapf(err, "parse device number from %s", name)
|
|
}
|
|
usedNumbers[num] = true
|
|
}
|
|
|
|
// 从0开始查找第一个未使用的设备号
|
|
for i := 0; i < 200; i++ {
|
|
if !usedNumbers[i] {
|
|
// 创建新的loop设备
|
|
return i, nil
|
|
}
|
|
}
|
|
return -1, errors.Wrap(errors.ErrNotFound, "No available device found")
|
|
}
|
|
|
|
func (m *loopManager) AttachDevice(filePath string, partScan bool) (*losetup.Device, error) {
|
|
m.actionLock.Lock()
|
|
defer m.actionLock.Unlock()
|
|
|
|
dev, err := m.acquireDevice()
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "AcquireDevice")
|
|
}
|
|
loDev, err := losetup.AttachDeviceWithPath(dev.GetDevicePath(), filePath, partScan)
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "AttachDeviceWithPath: %s, filePath: %s", dev.GetDevicePath(), filePath)
|
|
}
|
|
|
|
return loDev, nil
|
|
}
|
|
|
|
func (m *loopManager) DetachDevice(devPath string) error {
|
|
m.actionLock.Lock()
|
|
defer m.actionLock.Unlock()
|
|
|
|
if err := losetup.DetachDevice(devPath); err != nil {
|
|
return errors.Wrapf(err, "DetachDevice: %s", devPath)
|
|
}
|
|
m.releaseDevice(devPath)
|
|
return nil
|
|
}
|
|
|
|
func (m *loopManager) acquireDevice() (ILoopDevice, error) {
|
|
m.mapLock.Lock()
|
|
defer m.mapLock.Unlock()
|
|
|
|
freeDevices := make([]ILoopDevice, 0)
|
|
for _, device := range m.devices {
|
|
if !device.IsUsed() {
|
|
freeDevices = append(freeDevices, device)
|
|
}
|
|
}
|
|
if len(freeDevices) > 0 {
|
|
device := freeDevices[rand.Intn(len(freeDevices))]
|
|
device.SetUsed(true)
|
|
return device, nil
|
|
}
|
|
|
|
// create new device
|
|
num, err := m.findNewDeviceNumber()
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "findNewDeviceNumber")
|
|
}
|
|
devPath, err := ioctl.AddDevice(num)
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "add device %d", num)
|
|
}
|
|
device := NewLoopDevice(devPath)
|
|
m.devices[devPath] = device
|
|
device.SetUsed(true)
|
|
|
|
return device, nil
|
|
}
|
|
|
|
func (m *loopManager) releaseDevice(devPath string) {
|
|
m.mapLock.Lock()
|
|
defer m.mapLock.Unlock()
|
|
|
|
if dev, ok := m.devices[devPath]; ok {
|
|
dev.SetUsed(false)
|
|
}
|
|
}
|