func (l *LogicalVolume) Pool() (*ThinPool, error) {
	if l.pool == nil {
		return nil, nil
	}
	return l.vg.FindPool(*l.pool)
}
Pool returns thin pool if this is a thin pool, or nil if not.
package command
import (
	"errors"
	"fmt"
	"io"
	"os"
	"os/exec"
	"path"
	"strconv"
	"strings"
	"time"
	"github.com/cybozu-go/log"
)
const (
	nsenter  = "/usr/bin/nsenter"
	lvm      = "/sbin/lvm"
	blockdev = "/sbin/blockdev"
	cowMin   = 50
	cowMax   = 300
)
var Containerized bool = false
var ErrNotFound = errors.New("not found")
func wrapExecCommand(cmd string, args ...string) *exec.Cmd {
	if Containerized {
		args = append([]string{"-m", "-u", "-i", "-n", "-p", "-t", "1", cmd}, args...)
		cmd = nsenter
	}
	c := exec.Command(cmd, args...)
	return c
}
func CallLVM(cmd string, args ...string) error {
	args = append([]string{cmd}, args...)
	c := wrapExecCommand(lvm, args...)
	log.Info("invoking LVM command", map[string]interface{}{
		"args": args,
	})
	c.Stderr = os.Stderr
	return c.Run()
}
type LVInfo map[string]string
func parseOneLine(line string) LVInfo {
	ret := LVInfo{}
	line = strings.TrimSpace(line)
	for _, token := range strings.Split(line, " ") {
		if len(token) == 0 {
			continue
		}
		kv := strings.Split(token, "=")
		k, v := kv[0], kv[1]
		k = strings.ToLower(k[5:])
		v = strings.Trim(v, "'")
		ret[k] = v
	}
	return ret
}
func parseLines(output string) []LVInfo {
	ret := []LVInfo{}
	lines := strings.Split(string(output), "\n")
	for _, line := range lines {
		if len(line) == 0 {
			continue
		}
		info := parseOneLine(line)
		ret = append(ret, info)
	}
	return ret
}
func parseOutput(cmd, fields string, args ...string) ([]LVInfo, error) {
	arg := []string{
		cmd, "-o", fields,
		"--noheadings", "--separator= ",
		"--units=b", "--nosuffix",
		"--unbuffered", "--nameprefixes",
	}
	arg = append(arg, args...)
	c := wrapExecCommand(lvm, arg...)
	c.Stderr = os.Stderr
	stdout, err := c.StdoutPipe()
	if err != nil {
		return nil, err
	}
	if err := c.Start(); err != nil {
		return nil, err
	}
	out, err := io.ReadAll(stdout)
	if err != nil {
		return nil, err
	}
	if err := c.Wait(); err != nil {
		return nil, err
	}
	return parseLines(string(out)), nil
}
type VolumeGroup struct {
	name string
}
func (g *VolumeGroup) Name() string {
	return g.name
}
func (g *VolumeGroup) Size() (uint64, error) {
	infoList, err := parseOutput("vgs", "vg_size", g.name)
	if err != nil {
		return 0, err
	}
	if len(infoList) != 1 {
		return 0, errors.New("volume group not found: " + g.name)
	}
	info := infoList[0]
	vgSize, err := strconv.ParseUint(info["vg_size"], 10, 64)
	if err != nil {
		return 0, err
	}
	return vgSize, nil
}
func (g *VolumeGroup) Free() (uint64, error) {
	infoList, err := parseOutput("vgs", "vg_free", g.name)
	if err != nil {
		return 0, err
	}
	if len(infoList) != 1 {
		return 0, errors.New("volume group not found: " + g.name)
	}
	info := infoList[0]
	vgFree, err := strconv.ParseUint(info["vg_free"], 10, 64)
	if err != nil {
		return 0, err
	}
	return vgFree, nil
}
func CreateVolumeGroup(name, device string) (*VolumeGroup, error) {
	err := CallLVM("vgcreate", "-ff", "-y", name, device)
	if err != nil {
		return nil, err
	}
	return FindVolumeGroup(name)
}
func FindVolumeGroup(name string) (*VolumeGroup, error) {
	groups, err := ListVolumeGroups()
	if err != nil {
		return nil, err
	}
	for _, group := range groups {
		if group.Name() == name {
			return group, nil
		}
	}
	return nil, ErrNotFound
}
func ListVolumeGroups() ([]*VolumeGroup, error) {
	infoList, err := parseOutput("vgs", "vg_name")
	if err != nil {
		return nil, err
	}
	groups := []*VolumeGroup{}
	for _, info := range infoList {
		groups = append(groups, &VolumeGroup{info["vg_name"]})
	}
	return groups, nil
}
func (g *VolumeGroup) FindVolume(name string) (*LogicalVolume, error) {
	volumes, err := g.ListVolumes()
	if err != nil {
		return nil, err
	}
	for _, volume := range volumes {
		if volume.Name() == name {
			return volume, nil
		}
	}
	return nil, ErrNotFound
}
func (g *VolumeGroup) ListVolumes() ([]*LogicalVolume, error) {
	infoList, err := parseOutput(
		"lvs",
		"lv_name,lv_path,lv_size,lv_kernel_major,lv_kernel_minor,origin,origin_size,pool_lv,thin_count,lv_tags",
		g.Name())
	if err != nil {
		return nil, err
	}
	var ret []*LogicalVolume
	lvNameSet := make(map[string]struct{})
	for _, info := range infoList {
		if len(info["thin_count"]) > 0 {
			continue
		}
		if _, ok := lvNameSet[info["lv_name"]]; ok {
			continue
		}
		lvNameSet[info["lv_name"]] = struct{}{}
		size, err := strconv.ParseUint(info["lv_size"], 10, 64)
		if err != nil {
			return nil, err
		}
		var origin *string
		if len(info["origin"]) > 0 {
			originName := info["origin"]
			origin = &originName
		}
		var pool *string
		if len(info["pool_lv"]) > 0 {
			poolLv := info["pool_lv"]
			pool = &poolLv
		}
		if origin != nil && pool == nil {
			size, err = strconv.ParseUint(info["origin_size"], 10, 64)
			if err != nil {
				return nil, err
			}
		}
		major, _ := strconv.ParseUint(info["lv_kernel_major"], 10, 32)
		minor, _ := strconv.ParseUint(info["lv_kernel_minor"], 10, 32)
		ret = append(ret, newLogicalVolume(
			info["lv_name"],
			info["lv_path"],
			g,
			size,
			origin,
			pool,
			uint32(major),
			uint32(minor),
			strings.Split(info["lv_tags"], ","),
		))
	}
	return ret, nil
}
func (g *VolumeGroup) CreateVolume(name string, size uint64, tags []string, stripe uint, stripeSize string) (*LogicalVolume, error) {
	lvcreateArgs := []string{"-n", name, "-L", fmt.Sprintf("%vg", size>>30), "-W", "y", "-y"}
	for _, tag := range tags {
		lvcreateArgs = append(lvcreateArgs, "--addtag")
		lvcreateArgs = append(lvcreateArgs, tag)
	}
	if stripe != 0 {
		lvcreateArgs = append(lvcreateArgs, "-i", fmt.Sprintf("%d", stripe))
		if stripeSize != "" {
			lvcreateArgs = append(lvcreateArgs, "-I", stripeSize)
		}
	}
	lvcreateArgs = append(lvcreateArgs, g.Name())
	if err := CallLVM("lvcreate", lvcreateArgs...); err != nil {
		return nil, err
	}
	return g.FindVolume(name)
}
func (g *VolumeGroup) FindPool(name string) (*ThinPool, error) {
	pools, err := g.ListPools()
	if err != nil {
		return nil, err
	}
	for _, pool := range pools {
		if pool.Name() == name {
			return pool, nil
		}
	}
	return nil, fmt.Errorf("not found thin pool: %v", name)
}
func (g *VolumeGroup) ListPools() ([]*ThinPool, error) {
	infoList, err := parseOutput("lvs", "lv_name,lv_size,thin_count", g.Name())
	if err != nil {
		return nil, err
	}
	ret := []*ThinPool{}
	for _, info := range infoList {
		if len(info["thin_count"]) == 0 {
			continue
		}
		lvSize, err := strconv.ParseUint(info["lv_size"], 10, 64)
		if err != nil {
			return nil, err
		}
		ret = append(ret, newThinPool(info["lv_name"], g, lvSize))
	}
	return ret, nil
}
func (g *VolumeGroup) CreatePool(name string, size uint64) (*ThinPool, error) {
	if err := CallLVM("lvcreate", "-T", fmt.Sprintf("%v/%v", g.Name(), name),
		"--size", fmt.Sprintf("%vg", size>>30)); err != nil {
		return nil, err
	}
	return g.FindPool(name)
}
type ThinPool struct {
	fullname string
	name     string
	vg       *VolumeGroup
	size     uint64
}
func fullName(name string, vg *VolumeGroup) string {
	return fmt.Sprintf("%v/%v", vg.Name(), name)
}
func newThinPool(name string, vg *VolumeGroup, size uint64) *ThinPool {
	fullname := fullName(name, vg)
	return &ThinPool{
		fullname,
		name,
		vg,
		size,
	}
}
func (t *ThinPool) Name() string {
	return t.name
}
func (t *ThinPool) FullName() string {
	return t.fullname
}
func (t *ThinPool) VG() *VolumeGroup {
	return t.vg
}
func (t *ThinPool) Size() uint64 {
	return t.size
}
func (t *ThinPool) Resize(newSize uint64) error {
	if t.size == newSize {
		return nil
	}
	if err := CallLVM("lvresize", "-f", "-L", fmt.Sprintf("%vb", newSize), t.fullname); err != nil {
		return err
	}
	t.size = newSize
	return nil
}
func (t *ThinPool) ListVolumes() ([]*LogicalVolume, error) {
	volumes, err := t.vg.ListVolumes()
	if err != nil {
		return nil, err
	}
	ret := []*LogicalVolume{}
	for _, volume := range volumes {
		if volume.pool != nil && *volume.pool == t.name {
			ret = append(ret, volume)
		}
	}
	return ret, nil
}
func (t *ThinPool) CreateVolume(name string, size uint64) (*LogicalVolume, error) {
	if err := CallLVM("lvcreate", "-T", t.fullname, "-n", name, "-V", fmt.Sprintf("%vg", size>>30)); err != nil {
		return nil, err
	}
	return t.vg.FindVolume(name)
}
type LogicalVolume struct {
	fullname string
	name     string
	path     string
	vg       *VolumeGroup
	size     uint64
	origin   *string
	pool     *string
	devMajor uint32
	devMinor uint32
	tags     []string
}
func newLogicalVolume(name, path string, vg *VolumeGroup, size uint64, origin, pool *string, major, minor uint32, tags []string) *LogicalVolume {
	fullname := fullName(name, vg)
	return &LogicalVolume{
		fullname,
		name,
		path,
		vg,
		size,
		origin,
		pool,
		major,
		minor,
		tags,
	}
}
func (l *LogicalVolume) Name() string {
	return l.name
}
func (l *LogicalVolume) FullName() string {
	return l.fullname
}
func (l *LogicalVolume) Path() string {
	return l.path
}
func (l *LogicalVolume) VG() *VolumeGroup {
	return l.vg
}
func (l *LogicalVolume) Size() uint64 {
	return l.size
}
func (l *LogicalVolume) IsSnapshot() bool {
	return l.origin != nil
}
func (l *LogicalVolume) Origin() (*LogicalVolume, error) {
	if l.origin == nil {
		return nil, nil
	}
	return l.vg.FindVolume(*l.origin)
}
func (l *LogicalVolume) IsThin() bool {
	return l.pool != nil
}