// 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 fsutils

import (
	"bufio"
	"bytes"
	"fmt"
	"os"
	"regexp"
	"strconv"
	"strings"

	"yunion.io/x/log"
	"yunion.io/x/pkg/errors"
	"yunion.io/x/pkg/utils"

	"yunion.io/x/onecloud/pkg/hostman/diskutils/deploy_iface"
	"yunion.io/x/onecloud/pkg/hostman/diskutils/fsutils/driver"
	"yunion.io/x/onecloud/pkg/hostman/guestfs"
	"yunion.io/x/onecloud/pkg/hostman/guestfs/fsdriver"
	"yunion.io/x/onecloud/pkg/hostman/hostdeployer/apis"
	"yunion.io/x/onecloud/pkg/util/fileutils2"
	"yunion.io/x/onecloud/pkg/util/procutils"
	"yunion.io/x/onecloud/pkg/util/regutils2"
	"yunion.io/x/onecloud/pkg/util/xfsutils"
)

var ext4UsageTypeLargefileSize int64 = 1024 * 1024 * 1024 * 1024 * 4
var ext4UsageTypeHugefileSize int64 = 1024 * 1024 * 1024 * 512

func SetExt4UsageTypeThresholds(largefile, hugefile int64) {
	if largefile > 0 {
		ext4UsageTypeLargefileSize = largefile
	}
	if hugefile > 0 {
		ext4UsageTypeHugefileSize = hugefile
	}
}

func IsPartedFsString(fsstr string) bool {
	return utils.IsInStringArray(strings.ToLower(fsstr), []string{
		"ext2", "ext3", "ext4", "xfs",
		"fat16", "fat32",
		"hfs", "hfs+", "hfsx",
		"linux-swap", "linux-swap(v1)",
		"ntfs", "reiserfs", "ufs", "btrfs",
	})
}

func ParseDiskPartition(dev string, lines []string) ([][]string, string) {
	var (
		parts        = [][]string{}
		label        string
		labelPartten = regexp.MustCompile(`Partition Table:\s+(?P<label>\w+)`)
		partten      = regexp.MustCompile(`(?P<idx>\d+)\s+(?P<start>\d+)s\s+(?P<end>\d+)s\s+(?P<count>\d+)s`)
	)

	for _, line := range lines {
		if len(label) == 0 {
			m := regutils2.GetParams(labelPartten, line)
			if len(m) > 0 {
				label = m["label"]
			}
		}
		m := regutils2.GetParams(partten, line)
		if len(m) > 0 {
			var (
				idx     = m["idx"]
				start   = m["start"]
				end     = m["end"]
				count   = m["count"]
				devname = dev
			)
			if '0' <= dev[len(dev)-1] && dev[len(dev)-1] <= '9' {
				devname += "p"
			}
			devname += idx
			data := regexp.MustCompile(`\s+`).Split(strings.TrimSpace(line), -1)

			var disktype, fs, flag string
			var offset = 0
			if len(data) > 4 {
				if label == "msdos" {
					disktype = data[4]
					if len(data) > 5 && IsPartedFsString(data[5]) {
						fs = data[5]
						offset += 1
					}
					if len(data) > 5+offset {
						flag = data[5+offset]
					}
				} else if label == "gpt" {
					if IsPartedFsString(data[4]) {
						fs = data[4]
						offset += 1
					}
					if len(data) > 4+offset {
						disktype = data[4+offset]
					}
					if len(data) > 4+offset+1 {
						flag = data[4+offset+1]
					}
				}
			}
			var bootable = ""
			if len(flag) > 0 && strings.Index(flag, "boot") >= 0 {
				bootable = "true"
			}
			parts = append(parts, []string{idx, bootable, start, end, count, disktype, fs,
				devname})
		}
	}
	return parts, label
}

// use proc driver do resizefs
func ResizeDiskFs(diskPath string, sizeMb int, mounted bool) error {
	fsutilDriver := NewFsutilDriver(driver.NewProcDriver())
	return fsutilDriver.ResizeDiskFs(diskPath, sizeMb, mounted)
}

func (d *SFsutilDriver) ResizeDiskFs(diskPath string, sizeMb int, mounted bool) error {
	fPath, fs, err := d.ResizeDiskPartition(diskPath, sizeMb)
	if err != nil {
		return err
	}
	err, _ = d.ResizePartitionFs(fPath, fs, false, mounted)
	if err != nil {
		return errors.Wrapf(err, "resize fs %s", fs)
	}
	return nil
}

func (d *SFsutilDriver) ResizeDiskPartition(diskPath string, sizeMb int) (string, string, error) {
	var cmds = []string{"parted", "-a", "none", "-s", diskPath, "--", "unit", "s", "print"}
	lines, err := d.Exec(cmds[0], cmds[1:]...)
	if err != nil {
		hasPartTable := func() bool {
			for _, line := range strings.Split(string(lines), "\n") {
				if strings.Contains(line, "Partition Table") {
					return true
				}
			}
			return false
		}
		if hasPartTable() {
			return "", "", nil
		}
		log.Errorf("resize disk fs fail, output: %s , error: %s", lines, err)
		return "", "", err
	}
	parts, label := ParseDiskPartition(diskPath, strings.Split(string(lines), "\n"))
	log.Infof("Parts: %v label: %s", parts, label)
	if label == "gpt" {
		retCode, stdout, stderr, e := d.ExecInputWait("gdisk", []string{diskPath}, []string{"r", "e", "Y", "w", "Y", "Y"})
		if e != nil {
			return "", "", errors.Wrap(e, "gdisk exec failed")
		}
		log.Infof("gdisk: %s %s", stdout, stderr)
		if retCode != 1 && retCode != 0 {
			return "", "", errors.Errorf("Exit Code %d: %s\n%s", retCode, stdout, stderr)
		}
	}
	if len(parts) > 0 && (label == "gpt" ||
		(label == "msdos" && parts[len(parts)-1][5] == "primary")) {
		var part = parts[len(parts)-1]
		fsType := part[6]
		if len(fsType) == 0 {
			partDev := fmt.Sprintf("%s%s", diskPath, part[0])
			fsType = d.GetFsFormat(partDev)
			log.Infof("blkid get fstype %s", fsType)
		}
		if part[5] == "lvm" || IsSupportResizeFs(fsType) || part[5] == "primary" || fsType == "LVM2_member" {
			// growpart script replace parted resizepart
			output, err := d.Exec("growpart", diskPath, part[0])
			if err != nil {
				return "", "", errors.Wrapf(err, "growpart failed %s", output)
			}
			return part[7], fsType, nil
		}
	}
	return "", "", nil
}

func (d *SFsutilDriver) ResizeDiskWithDiskId(diskId string, rootPartDev string, onlineResize bool) error {
	// find partition need resize
	resizeDev, err := d.GetResizeDevBySerial(diskId)
	if err != nil {
		return err
	}
	if resizeDev == "" {
		log.Errorf("failed find disk serial %s", diskId)
		return nil
	}
	partDev, fsType, err := d.ResizeDiskPartition(resizeDev, 0)
	if err != nil {
		return err
	}
	if partDev == "" || fsType == "" || fsType == "LVM2_member" {
		if (fsType == "" || fsType == "LVM2_member") && partDev != "" {
			// fsType empty and partDev not empty is lvm device
			resizeDev = partDev
		}

		if !d.IsLvmPvDevice(resizeDev) {
			fsType = d.GetFsFormat(resizeDev)
			err, _ := d.ResizePartitionFs(resizeDev, fsType, false, onlineResize)
			return err
		}
		if err := d.Pvresize(resizeDev); err != nil {
			return err
		}

		vg := d.GetVgOfPvDevice(resizeDev)
		if vg == "" {
			return nil
		}
		lvs, err := d.GetVgLvs(vg)
		if err != nil {
			log.Errorf("failed get vg lvs %s: %s", vg, err)
		}
		if len(lvs) == 0 {
			log.Infof("disk %s has no lv, skip resize", diskId)
			return nil
		}

		var resizeLv string
		if rootPartDev != "" {
			for i := range lvs {
				if lvs[i].LvPath == rootPartDev {
					resizeLv = rootPartDev
					break
				}
			}
		}
		if resizeLv == "" {
			if len(lvs) != 1 {
				log.Errorf("disk %s has multi lv and no rootfs, skip resize partition", diskId)
				return nil
			} else {
				resizeLv = lvs[0].LvPath
			}
		}
		err = d.ExtendLv(resizeLv)
		if err != nil {
			return err
		}
		fsType = d.GetFsFormat(resizeLv)
		err, _ = d.ResizePartitionFs(resizeLv, fsType, false, onlineResize)
		return err
	} else {
		err, _ = d.ResizePartitionFs(partDev, fsType, false, onlineResize)
		return err
	}
}

func IsSupportResizeFs(fs string) bool {
	if strings.HasPrefix(fs, "linux-swap") {
		return true
	} else if strings.HasPrefix(fs, "ext") {
		return true
	} else if fs == "xfs" || fs == "f2fs" {
		return true
	}
	return false
}

func (d *SFsutilDriver) ResizePartitionFs(fpath, fs string, raiseError, onlineResize bool) (error, bool) {
	log.Errorf("ResizePartitionFs fstype %s", fs)
	if len(fs) == 0 {
		return nil, false
	}
	var (
		cmds     = [][]string{}
		uuids, _ = d.GetDevUuid(fpath)
	)
	if strings.HasPrefix(fs, "linux-swap") {
		if v, ok := uuids["UUID"]; ok {
			cmds = [][]string{{"mkswap", "-U", v, fpath}}
		} else {
			cmds = [][]string{{"mkswap", fpath}}
		}
	} else if strings.HasPrefix(fs, "ext") {
		if !onlineResize {
			if !d.FsckExtFs(fpath) {
				if raiseError {
					return fmt.Errorf("Failed to fsck ext fs %s", fpath), false
				} else {
					return nil, false
				}
			}
		}

		cmds = [][]string{{"resize2fs", fpath}}
	} else if fs == "xfs" {
		uuid := uuids["UUID"]
		if len(uuid) > 0 {
			xfsutils.LockXfsPartition(uuid)
			defer xfsutils.UnlockXfsPartition(uuid)
		}
		if !onlineResize {
			var tmpPoint = fmt.Sprintf("/tmp/%s", strings.Replace(fpath, "/", "_", -1))
			if _, err := d.Exec("mountpoint", tmpPoint); err == nil {
				output, err := d.Exec("umount", "-f", tmpPoint)
				if err != nil {
					log.Errorf("failed umount %s: %s, %s", tmpPoint, err, output)
					return err, false
				}
			}
			d.FsckXfsFs(fpath)
			cmds = [][]string{{"mkdir", "-p", tmpPoint},
				{"mount", fpath, tmpPoint},
				{"sleep", "2"},
				{"xfs_growfs", tmpPoint},
				{"sleep", "2"},
				{"umount", tmpPoint},
				{"sleep", "2"},
				{"rm", "-fr", tmpPoint}}
		} else {
			cmds = [][]string{{"xfs_growfs", fpath}}
		}
	} else if fs == "ntfs" {
		// the following cmds may cause disk damage on Windows 10 with new version of NTFS
		// comment out the following codes only impact Windows 2003
		// as windows 2003 deprecated, so choose to sacrifies windows 2003
		// cmds = [][]string{{"ntfsresize", "-c", fpath}, {"ntfsresize", "-P", "-f", fpath}}
	} else if fs == "f2fs" {
		if !onlineResize {
			cmds = [][]string{{"resize.f2fs", fpath}}
		}
	}

	if len(cmds) > 0 {
		for _, cmd := range cmds {
			output, err := d.Exec(cmd[0], cmd[1:]...)
			if err != nil {
				log.Errorf("resize partition: %s, %s", err, output)
				if raiseError {
					return err, false
				} else {
					return nil, false
				}
			}
		}
	}
	return nil, true
}

func (d *SFsutilDriver) FsckExtFs(fpath string) bool {
	log.Debugf("Exec command: %v", []string{"e2fsck", "-f", "-p", fpath})
	retCode, stdout, stderr, err := d.ExecInputWait("e2fsck", []string{"-f", "-p", fpath}, nil)
	if err != nil {
		log.Errorf("exec e2fsck error %s retcode %d stdout %s stderr %s", err, retCode, stdout, stderr)
		if retCode >= 4 {
			return false
		}
	}
	if retCode < 4 {
		return true
	}
	log.Errorf("failed e2fsck retcode %d %s %s", retCode, stdout, stderr)
	return false
}

// https://bugs.launchpad.net/ubuntu/+source/xfsprogs/+bug/1718244
// use xfs_repair -n instead
func (d *SFsutilDriver) FsckXfsFs(fpath string) bool {
	if output, err := d.Exec("xfs_check", fpath); err != nil {
		log.Errorf("xfs_check failed: %s, %s, try xfs_repair -n <dev> instead", err, output)
		if output, err := procutils.NewCommand("xfs_repair", "-n", fpath).Output(); err != nil {
			log.Errorf("xfs_repair -n dev failed: %s, %s", err, output)
			// repair the xfs
			d.Exec("xfs_repair", fpath)
			return false
		}
	}
	return true
}

func Mkpartition(imagePath, fsFormat string) error {
	var (
		parted    = "parted"
		labelType = "gpt"
		diskType  = fileutils2.FsFormatToDiskType(fsFormat)
	)

	if len(diskType) == 0 {
		return fmt.Errorf("Unknown fsFormat %s", fsFormat)
	}

	// 创建一个新磁盘分区表类型, ex: mbr gpt msdos ...
	output, err := procutils.NewCommand(parted, "-s", imagePath, "mklabel", labelType).Output()
	if err != nil {
		log.Errorf("mklabel %s %s error: %s, %s", imagePath, fsFormat, err, output)
		return errors.Wrapf(err, "parted mklabel failed: %s", output)
	}

	// 创建一个part-type类型的分区, part-type可以是："primary", "logical", "extended"
	// 如果指定fs-type(即diskType)则在创建分区的同时进行格式化
	output, err = procutils.NewCommand(parted, "-s", "-a", "cylinder",
		imagePath, "mkpart", "primary", diskType, "0", "100%").Output()
	if err != nil {
		log.Errorf("mkpart %s %s error: %s, %s", imagePath, fsFormat, err, output)
		return errors.Wrapf(err, "parted mkpart failed: %s", output)
	}
	return nil
}

func ext4UsageType(path string) string {
	out, err := procutils.NewCommand("blockdev", "--getsize64", path).Output()
	if err != nil {
		log.Errorf("failed get blockdev %s size: %s", path, err)
		return ""
	}
	size, err := strconv.ParseInt(strings.TrimSpace(string(out)), 10, 64)
	if err != nil {
		log.Errorf("failed parse blocksize %s", out)
		return ""
	}
	if size > ext4UsageTypeLargefileSize {
		// node_ratio 1M
		return "largefile"
	} else if size > ext4UsageTypeHugefileSize {
		// node_ratio 64K
		return "huge"
	}
	return ""
}

func FormatPartition(path, fs, uuid string, fsFeatures *apis.FsFeatures) error {
	var cmd, cmdUuid []string
	switch {
	case fs == "swap":
		cmd = []string{"mkswap", "-U", uuid}
	case fs == "ext2":
		cmd = []string{"mkfs.ext2"}
		cmdUuid = []string{"tune2fs", "-U", uuid}
	case fs == "ext3":
		cmd = []string{"mkfs.ext3"}
		cmdUuid = []string{"tune2fs", "-U", uuid}
	case fs == "ext4":
		feature := "^64bit"
		extendOpts := "lazy_itable_init=1"
		featureOpts := []string{}
		if fsFeatures != nil && fsFeatures.Ext4 != nil {
			if fsFeatures.Ext4.CaseInsensitive {
				feature = fmt.Sprintf("%s,casefold,project,quota", feature)
				//feature = fmt.Sprintf("casefold,project,quota")
				extendOpts = fmt.Sprintf("%s,nodiscard,lazy_journal_init=1,encoding_flags=strict,encoding=utf8-12.1", extendOpts)
			}
			if fsFeatures.Ext4.ReservedBlocksPercentage > 0 {
				featureOpts = append(featureOpts, []string{"-m", fmt.Sprintf("%d", fsFeatures.Ext4.ReservedBlocksPercentage)}...)
			}
		}
		cmd = []string{"mkfs.ext4", "-O", feature, "-E", extendOpts}
		cmd = append(cmd, featureOpts...)
		log.Infof("===========format partion cmd: %#v", cmd)
		/*
			// see /etc/mke2fs.conf, default inode_ratio is 16384
			If  this option is is not specified, mke2fs will pick a single default usage type based on the size
			of the filesystem to be created.  If the filesystem size is less than  or  equal  to  3  megabytes,
			mke2fs will use the filesystem type floppy.  If the filesystem size is greater than 3 but less than
			or equal to 512 megabytes, mke2fs(8) will use the filesystem type small.  If the filesystem size is
			greater  than or equal to 4 terabytes but less than 16 terabytes, mke2fs(8) will use the filesystem
			type big.  If the filesystem size is greater than or equal to 16 terabytes, mke2fs(8) will use  the
			filesystem type huge.  Otherwise, mke2fs(8) will use the default filesystem type default.
		*/
		if usageType := ext4UsageType(path); usageType != "" {
			cmd = append(cmd, "-T", usageType)
		}

		cmdUuid = []string{"tune2fs", "-U", uuid}
	case fs == "ext4dev":
		cmd = []string{"mkfs.ext4dev", "-E", "lazy_itable_init=1"}
		cmdUuid = []string{"tune2fs", "-U", uuid}
	case strings.HasPrefix(fs, "fat"):
		cmd = []string{"mkfs.msdos"}
	// #case fs == "ntfs":
	// #    cmd = []string{"/sbin/mkfs.ntfs"}
	case fs == "xfs":
		cmd = []string{"mkfs.xfs", "-f", "-m", "crc=0", "-i", "projid32bit=0", "-n", "ftype=1"}
		cmdUuid = []string{"xfs_admin", "-U", uuid}
	case fs == "f2fs":
		cmd = []string{"mkfs.f2fs", "-U", uuid, "-O"}
		options := "extra_attr,inode_checksum,sb_checksum"
		if fsFeatures != nil && fsFeatures.F2Fs.CaseInsensitive {
			options += ",casefold"
		}
		cmd = append(cmd, options)
		if fsFeatures != nil && fsFeatures.F2Fs.CaseInsensitive {
			cmd = append(cmd, "-C", "utf8")
		}

		if fsFeatures != nil && fsFeatures.F2Fs.OverprovisionRatioPercentage > 0 {
			cmd = append(cmd, "-o", fmt.Sprintf("%d", fsFeatures.F2Fs.OverprovisionRatioPercentage))
		}
	}

	if len(cmd) > 0 {
		var cmds = cmd
		cmds = append(cmds, path)
		if output, err := procutils.NewCommand(cmds[0], cmds[1:]...).Output(); err != nil {
			log.Errorf("%v failed: %s, %s", cmds, err, output)
			return errors.Wrapf(err, "format partition failed %s", output)
		}
		if len(cmdUuid) > 0 {
			cmds = cmdUuid
			cmds = append(cmds, path)
			if output, err := procutils.NewCommand(cmds[0], cmds[1:]...).Output(); err != nil {
				log.Errorf("%v failed: %s, %s", cmds, err, output)
				return errors.Wrapf(err, "format partition set uuid failed %s", output)
			}
		}
		return nil
	}
	return fmt.Errorf("Unknown fs %s", fs)
}

func DetectIsUEFISupport(rootfs fsdriver.IRootFsDriver, partitions []fsdriver.IDiskPartition) bool {
	for i := 0; i < len(partitions); i++ {
		if partitions[i].IsMounted() {
			if rootfs.DetectIsUEFISupport(partitions[i]) {
				return true
			}
		} else {
			if partitions[i].Mount() {
				support := rootfs.DetectIsUEFISupport(partitions[i])
				if err := partitions[i].Umount(); err != nil {
					log.Errorf("failed umount %s: %s", partitions[i].GetPartDev(), err)
				}
				if support {
					return true
				}
			}
		}
	}
	return false
}

func DetectIsBIOSSupport(partDev string, rootfs fsdriver.IRootFsDriver) bool {
	fi, err := os.OpenFile(partDev, os.O_RDONLY, 0444)
	if err != nil {
		log.Errorf("failed open partdev %s: %s", partDev, err)
		return false
	}
	defer fi.Close()

	// read first sector
	sector := make([]byte, 512)
	n, err := fi.Read(sector)
	if err != nil || n != 512 {
		log.Errorf("failed read first sector %d %s: %s", n, partDev, err)
		return false
	}

	bootSignature := sector[510:512]
	expectedSignature := []byte{0x55, 0xAA}
	log.Infof("Detect is bios support bootSignature: %x", bootSignature)
	if bootSignature[0] != expectedSignature[0] || bootSignature[1] != expectedSignature[1] {
		return false
	}
	partitionType := rootfs.GetPartition().GetPhysicalPartitionType()
	if partitionType == "mbr" {
		return true
	} else if partitionType == "gpt" {
		/*
			   Number  Start (sector)    End (sector)  Size       Code  Name
			      1          227328        83886046   39.9 GiB    8300
			     14            2048           10239   4.0 MiB     EF02
			     15           10240          227327   106.0 MiB   EF00

				# EF02 is BIOS Boot partition
		*/
		out, err := procutils.NewCommand("sgdisk", "-p", partDev).Output()
		if err != nil {
			log.Errorf("sgdisk -p %s failed: %s, %s", partDev, err, out)
			return false
		}
		re := regexp.MustCompile(`^\s*(\d+)\s+(\d+)\s+(\d+)\s+([\d\.]+\s+\w+)\s+(\w+)\s*(.*)$`)
		scanner := bufio.NewScanner(bytes.NewReader(out))
		for scanner.Scan() {
			line := scanner.Text()
			m := re.FindStringSubmatch(line)
			if m == nil {
				continue
			}
			code := m[5]
			if code == "EF02" {
				return true
			}
		}
	}

	return false
}

func MountRootfs(readonly bool, partitions []fsdriver.IDiskPartition) (fsdriver.IRootFsDriver, error) {
	errs := []error{}
	for i := 0; i < len(partitions); i++ {
		log.Infof("detect partition %s", partitions[i].GetPartDev())
		mountFunc := partitions[i].Mount
		if readonly {
			mountFunc = partitions[i].MountPartReadOnly
		}
		if mountFunc() {
			fs, err := guestfs.DetectRootFs(partitions[i])
			if err == nil {
				log.Infof("Use rootfs %s, partition %s", fs, partitions[i].GetPartDev())
				return fs, nil
			}
			errs = append(errs, err)
			if err := partitions[i].Umount(); err != nil {
				log.Errorf("failed umount %s: %s", partitions[i].GetPartDev(), err)
			}
		}
	}
	if len(partitions) == 0 {
		return nil, errors.Wrap(errors.ErrNotFound, "not found any partition")
	}
	var err error = errors.ErrNotFound
	if len(errs) > 0 {
		err = errors.Wrapf(errors.ErrNotFound, "%s", errors.NewAggregate(errs).Error())
	}
	return nil, err
}

func DeployGuestfs(d deploy_iface.IDeployer, req *apis.DeployParams) (res *apis.DeployGuestFsResponse, err error) {
	root, err := d.MountRootfs(false)
	if err != nil {
		if errors.Cause(err) == errors.ErrNotFound && req.DeployInfo.IsInit {
			// if init deploy, ignore no partition error
			log.Errorf("disk.MountRootfs not found partition, not init, quit")
			return nil, nil
		}
		log.Errorf("Failed mounting rootfs for %s disk: %s", req.GuestDesc.Hypervisor, err)
		return nil, err
	}
	defer d.UmountRootfs(root)
	ret, err := guestfs.DoDeployGuestFs(root, req.GuestDesc, req.DeployInfo)
	if err != nil {
		log.Errorf("guestfs.DoDeployGuestFs fail %s", err)
		return nil, err
	}
	if ret == nil {
		log.Errorf("guestfs.DoDeployGuestFs return empty results")
		return nil, nil
	}
	return ret, nil
}

func ResizeFs(d deploy_iface.IDeployer, diskId string) (*apis.Empty, error) {
	unmount := func(root fsdriver.IRootFsDriver) error {
		err := d.UmountRootfs(root)
		if err != nil {
			return errors.Wrap(err, "unmount rootfs")
		}
		return nil
	}

	var rootPartDev string
	root, err := d.MountRootfs(false)
	if err != nil && errors.Cause(err) != errors.ErrNotFound {
		return new(apis.Empty), errors.Wrapf(err, "disk.MountRootfs")
	} else if err == nil {
		rootPartDev = root.GetPartition().GetPartDev()
		if !root.IsResizeFsPartitionSupport() {
			err := unmount(root)
			if err != nil {
				return new(apis.Empty), err
			}
			return new(apis.Empty), errors.ErrNotSupported
		}

		// must umount rootfs before resize partition
		err = unmount(root)
		if err != nil {
			return new(apis.Empty), err
		}
	}
	err = d.ResizePartition(diskId, rootPartDev)
	if err != nil {
		return new(apis.Empty), errors.Wrap(err, "resize disk partition")
	}
	return new(apis.Empty), nil
}

func FormatFs(d deploy_iface.IDeployer, req *apis.FormatFsParams) (*apis.Empty, error) {
	err := d.MakePartition(req.FsFormat)
	if err != nil {
		return new(apis.Empty), errors.Wrap(err, "MakePartition")
	}
	err = d.FormatPartition(req.FsFormat, req.Uuid, req.FsFeatures)
	if err != nil {
		return new(apis.Empty), errors.Wrap(err, "FormatPartition")
	}
	return new(apis.Empty), nil
}

func SaveToGlance(d deploy_iface.IDeployer, req *apis.SaveToGlanceParams) (*apis.SaveToGlanceResponse, error) {
	var (
		osInfo  string
		relInfo *apis.ReleaseInfo
	)

	ret := &apis.SaveToGlanceResponse{
		OsInfo:      osInfo,
		ReleaseInfo: relInfo,
	}

	root, err := d.MountRootfs(false)
	if err != nil {
		if errors.Cause(err) == errors.ErrNotFound {
			return ret, nil
		}
		return ret, errors.Wrapf(err, "MountKvmRootfs")
	}
	defer d.UmountRootfs(root)

	osInfo = root.GetOs()
	relInfo = root.GetReleaseInfo(root.GetPartition())
	if req.Compress {
		err = root.PrepareFsForTemplate(root.GetPartition())
		if err != nil {
			log.Errorf("PrepareFsForTemplate %s", err)
		}
	}
	if req.Compress {
		d.Zerofree()
	}
	return ret, err
}

func ProbeImageInfo(d deploy_iface.IDeployer) (*apis.ImageInfo, error) {
	// Fsck is executed during mount
	rootfs, err := d.MountRootfs(false)
	if err != nil {
		if errors.Cause(err) == errors.ErrNotFound {
			return new(apis.ImageInfo), nil
		}
		return new(apis.ImageInfo), errors.Wrapf(err, "d.MountKvmRootfs")
	}
	partition := rootfs.GetPartition()
	imageInfo := &apis.ImageInfo{
		OsInfo:               rootfs.GetReleaseInfo(partition),
		OsType:               rootfs.GetOs(),
		IsLvmPartition:       d.IsLVMPartition(),
		IsReadonly:           partition.IsReadonly(),
		IsInstalledCloudInit: rootfs.IsCloudinitInstall(),
	}
	d.UmountRootfs(rootfs)

	// In case of deploy driver is guestfish, we can't mount
	// multi partition concurrent, so we need umount rootfs first
	imageInfo.IsUefiSupport = d.DetectIsUEFISupport(rootfs)
	imageInfo.PhysicalPartitionType = partition.GetPhysicalPartitionType()
	imageInfo.IsBiosSupport = d.DetectIsBIOSSupport(rootfs)
	log.Infof("ProbeImageInfo response %s", imageInfo)
	return imageInfo, nil
}
