| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341 |
- // Copyright 2019 Yunion
- //
- // Licensed under the Apache License, Version 2.0 (the "License");
- // you may not use this file except in compliance with the License.
- // You may obtain a copy of the License at
- //
- // http://www.apache.org/licenses/LICENSE-2.0
- //
- // Unless required by applicable law or agreed to in writing, software
- // distributed under the License is distributed on an "AS IS" BASIS,
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- // See the License for the specific language governing permissions and
- // limitations under the License.
- package losetup
- import (
- "fmt"
- "path/filepath"
- "strconv"
- "strings"
- "sync"
- "yunion.io/x/jsonutils"
- "yunion.io/x/log"
- "yunion.io/x/pkg/errors"
- "yunion.io/x/onecloud/pkg/util/mountutils"
- "yunion.io/x/onecloud/pkg/util/procutils"
- )
- const (
- LOSETUP_COMMAND = "losetup"
- )
- var (
- attachDeviceLock = sync.Mutex{}
- )
- type Command struct {
- Path string
- Args []string
- output string
- }
- func NewCommand(path string, args ...string) *Command {
- return &Command{
- Path: path,
- Args: args,
- output: "",
- }
- }
- func (cmd *Command) AddArgs(args ...string) *Command {
- cmd.Args = append(cmd.Args, args...)
- return cmd
- }
- func (cmd *Command) Run() (*Command, error) {
- ecmd := procutils.NewRemoteCommandAsFarAsPossible(cmd.Path, cmd.Args...)
- out, err := ecmd.Output()
- if err != nil {
- err = errors.Wrapf(err, "%s %v: %s", cmd.Path, cmd.Args, out)
- }
- cmd.output = string(out)
- return cmd, err
- }
- func (cmd *Command) Output() string {
- return cmd.output
- }
- type LosetupCommand struct {
- *Command
- }
- func NewLosetupCommand() *LosetupCommand {
- return &LosetupCommand{
- Command: NewCommand(LOSETUP_COMMAND),
- }
- }
- func parseJsonOutput(content string) (*Devices, error) {
- obj, err := jsonutils.ParseString(content)
- if err != nil {
- return nil, errors.Wrapf(err, "parse json: %s", content)
- }
- devs := new(Devices)
- if err := obj.Unmarshal(devs); err != nil {
- return nil, errors.Wrapf(err, "unmarshal json: %s", content)
- }
- return devs, nil
- }
- func ListDevices() (*Devices, error) {
- cmd, err := NewLosetupCommand().AddArgs("--json").Run()
- errs := make([]error, 0)
- if err != nil {
- errs = append(errs, errors.Wrap(err, "list by json"))
- devs, err2 := listDevicesOldVersion()
- if err2 != nil {
- errs = append(errs, errors.Wrap(err, "list by using old way"))
- } else {
- return devs, nil
- }
- return nil, errors.NewAggregate(errs)
- }
- output := cmd.Output()
- return parseJsonOutput(output)
- }
- func listDevicesOldVersion() (*Devices, error) {
- cmd, err := NewLosetupCommand().AddArgs("-l", "-O", "NAME,BACK-FILE").Run()
- if err != nil {
- return nil, err
- }
- output := cmd.Output()
- devs, err := parseDevices(output)
- return devs, err
- }
- /*func GetUnusedDevice() (string, error) {
- // find first unused device
- cmd, err := NewLosetupCommand().AddArgs("-f").Run()
- if err != nil {
- return "", err
- }
- return strings.TrimSuffix(cmd.Output(), "\n"), nil
- }*/
- func attachDevice(devPath string, filePath string, partScan bool) (*Device, error) {
- // See man-page: https://man7.org/linux/man-pages/man8/losetup.8.html
- // The loop device setup is not an atomic operation when used with
- // --find, and losetup does not protect this operation by any lock.
- attachDeviceLock.Lock()
- defer attachDeviceLock.Unlock()
- oldDevs, err := ListDevices()
- if err != nil {
- return nil, err
- }
- oldDev := oldDevs.GetDeviceByFile(filePath)
- if oldDev != nil {
- //return nil, fmt.Errorf("file %q already attached to %q, attached twice???", oldDev.BackFile, oldDev.Name)
- return oldDev, nil
- }
- args := []string{}
- if partScan {
- args = append(args, "-P")
- }
- if devPath != "" {
- args = append(args, []string{devPath, filePath}...)
- } else {
- // args = append(args, []string{"--find", "--nooverlap", filePath}...)
- args = append(args, []string{"--find", filePath}...)
- }
- _, err = NewLosetupCommand().AddArgs(args...).Run()
- if err != nil {
- return nil, err
- }
- devs, err := ListDevices()
- if err != nil {
- return nil, err
- }
- dev := devs.GetDeviceByFile(filePath)
- if dev == nil {
- return nil, fmt.Errorf("Not found loop device by file: %s", filePath)
- }
- return dev, nil
- }
- func AttachDeviceWithPath(devPath string, filePath string, partScan bool) (*Device, error) {
- return attachDevice(devPath, filePath, partScan)
- }
- func AttachDevice(filePath string, partScan bool) (*Device, error) {
- return attachDevice("", filePath, partScan)
- }
- // converts a raw key value pair string into a map of key value pairs
- // example raw string of `foo="0" bar="1" baz="biz"` is returned as:
- // map[string]string{"foo":"0", "bar":"1", "baz":"biz"}
- func parseKeyValuePairString(propsRaw string) map[string]string {
- // first split the single raw string on spaces and initialize a map of
- // a length equal to the number of pairs
- props := strings.Split(propsRaw, " ")
- propMap := make(map[string]string, len(props))
- for _, kvpRaw := range props {
- // split each individual key value pair on the equals sign
- kvp := strings.Split(kvpRaw, "=")
- if len(kvp) == 2 {
- // first element is the final key, second element is the final value
- // (don't forget to remove surrounding quotes from the value)
- propMap[kvp[0]] = strings.Replace(kvp[1], `"`, "", -1)
- }
- }
- return propMap
- }
- const (
- // DiskType is a disk type
- DiskType = "disk"
- // SSDType is an sdd type
- SSDType = "ssd"
- // PartType is a partition type
- PartType = "part"
- // CryptType is an encrypted type
- CryptType = "crypt"
- // LVMType is an LVM type
- LVMType = "lvm"
- // MultiPath is for multipath devices
- MultiPath = "mpath"
- // LinearType is a linear type
- LinearType = "linear"
- // LoopType is a loop device type
- LoopType = "loop"
- sgdiskCmd = "sgdisk"
- // CephLVPrefix is the prefix of a LV owned by ceph-volume
- CephLVPrefix = "ceph--"
- // DeviceMapperPrefix is the prefix of a LV from the device mapper interface
- DeviceMapperPrefix = "dm-"
- )
- // Partition represents a partition metadata
- type Partition struct {
- Name string
- Size uint64
- Label string
- Filesystem string
- }
- // ref: https://github.com/rook/rook/blob/master/pkg/util/sys/device.go#L135
- func GetDevicePartions(devicePath string) ([]string, error) {
- cmd, err := NewCommand("lsblk", devicePath, "--bytes", "--pairs", "--output", "NAME,SIZE,TYPE,PKNAME").Run()
- if err != nil {
- return nil, errors.Wrapf(err, "get device %s partions", devicePath)
- }
- device := filepath.Base(devicePath)
- output := cmd.Output()
- partInfo := strings.Split(output, "\n")
- var partitions = make([]string, 0)
- var totalPartitionSize uint64
- for _, info := range partInfo {
- props := parseKeyValuePairString(info)
- name := props["NAME"]
- if name == device {
- // found the main device
- log.Infof("Device found - %s", name)
- _, err = strconv.ParseUint(props["SIZE"], 10, 64)
- if err != nil {
- return nil, fmt.Errorf("failed to get device %s size. %+v", device, err)
- }
- } else if props["PKNAME"] == device && props["TYPE"] == PartType {
- // found a partition
- p := Partition{Name: name}
- p.Size, err = strconv.ParseUint(props["SIZE"], 10, 64)
- if err != nil {
- return nil, fmt.Errorf("failed to get partition %s size. %+v", name, err)
- }
- totalPartitionSize += p.Size
- partitions = append(partitions, name)
- } else if strings.HasPrefix(name, CephLVPrefix) && props["TYPE"] == LVMType {
- partitions = append(partitions, name)
- }
- }
- return partitions, nil
- }
- func DetachDevice(devPath string) error {
- getDev := func() (*Device, error) {
- devs, err := ListDevices()
- if err != nil {
- return nil, errors.Wrapf(err, "list devices")
- }
- dev := devs.GetDeviceByName(devPath)
- return dev, nil
- }
- dev, err := getDev()
- if err != nil {
- return err
- }
- if dev == nil {
- return nil
- }
- // check mountpoints
- partions, err := GetDevicePartions(devPath)
- if err != nil {
- return errors.Wrapf(err, "get device %s partions", devPath)
- }
- for _, part := range partions {
- checkMntCmd, _ := NewCommand("sh", "-c", fmt.Sprintf("mount | grep %s | awk '{print $3}'", part)).Run()
- if out := checkMntCmd.Output(); out != "" {
- mntPoints := strings.Split(out, "\n")
- for _, mntPoint := range mntPoints {
- if mntPoint != "" {
- if err := mountutils.Unmount(mntPoint, false); err != nil {
- return errors.Wrapf(err, "umount %s of dev: %s, part: %s", mntPoint, dev.Name, part)
- }
- }
- }
- }
- }
- _, err = NewLosetupCommand().AddArgs("-d", dev.Name).Run()
- if err != nil {
- return errors.Wrapf(err, "detach device")
- }
- // recheck
- /*dev, err = getDev()
- if err != nil {
- return errors.Wrapf(err, "get device by %s for rechecking", devPath)
- }
- if dev != nil {
- return errors.Errorf("device %s still exists, %s", devPath, jsonutils.Marshal(dev))
- }*/
- return nil
- }
- func DetachDeviceByFile(filePath string) error {
- devs, err := ListDevices()
- if err != nil {
- return err
- }
- dev := devs.GetDeviceByFile(filePath)
- if dev == nil {
- return nil
- }
- _, err = NewLosetupCommand().AddArgs("-d", dev.Name).Run()
- return err
- }
- func ResizeLoopDevice(loopDev string) error {
- _, err := NewLosetupCommand().AddArgs("-c", loopDev).Run()
- return err
- }
|