321 lines
		
	
	
		
			6.6 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			321 lines
		
	
	
		
			6.6 KiB
		
	
	
	
		
			Go
		
	
	
	
package zfs
 | 
						|
 | 
						|
import (
 | 
						|
	"bytes"
 | 
						|
	"fmt"
 | 
						|
	"io"
 | 
						|
	"os/exec"
 | 
						|
	"regexp"
 | 
						|
	"strconv"
 | 
						|
	"strings"
 | 
						|
)
 | 
						|
 | 
						|
type command struct {
 | 
						|
	Command string
 | 
						|
	Stdin   io.Reader
 | 
						|
	Stdout  io.Writer
 | 
						|
}
 | 
						|
 | 
						|
func (c *command) Run(arg ...string) ([][]string, error) {
 | 
						|
 | 
						|
	cmd := exec.Command(c.Command, arg...)
 | 
						|
 | 
						|
	var stdout, stderr bytes.Buffer
 | 
						|
 | 
						|
	if c.Stdout == nil {
 | 
						|
		cmd.Stdout = &stdout
 | 
						|
	} else {
 | 
						|
		cmd.Stdout = c.Stdout
 | 
						|
	}
 | 
						|
 | 
						|
	if c.Stdin != nil {
 | 
						|
		cmd.Stdin = c.Stdin
 | 
						|
 | 
						|
	}
 | 
						|
	cmd.Stderr = &stderr
 | 
						|
 | 
						|
	debug := strings.Join([]string{cmd.Path, strings.Join(cmd.Args, " ")}, " ")
 | 
						|
	if logger != nil {
 | 
						|
		logger.Log(cmd.Args)
 | 
						|
	}
 | 
						|
	err := cmd.Run()
 | 
						|
 | 
						|
	if err != nil {
 | 
						|
		return nil, &Error{
 | 
						|
			Err:    err,
 | 
						|
			Debug:  debug,
 | 
						|
			Stderr: stderr.String(),
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	// assume if you passed in something for stdout, that you know what to do with it
 | 
						|
	if c.Stdout != nil {
 | 
						|
		return nil, nil
 | 
						|
	}
 | 
						|
 | 
						|
	lines := strings.Split(stdout.String(), "\n")
 | 
						|
 | 
						|
	//last line is always blank
 | 
						|
	lines = lines[0 : len(lines)-1]
 | 
						|
	output := make([][]string, len(lines))
 | 
						|
 | 
						|
	for i, l := range lines {
 | 
						|
		output[i] = strings.Fields(l)
 | 
						|
	}
 | 
						|
 | 
						|
	return output, nil
 | 
						|
}
 | 
						|
 | 
						|
func setString(field *string, value string) {
 | 
						|
	v := ""
 | 
						|
	if value != "-" {
 | 
						|
		v = value
 | 
						|
	}
 | 
						|
	*field = v
 | 
						|
}
 | 
						|
 | 
						|
func setUint(field *uint64, value string) error {
 | 
						|
	var v uint64
 | 
						|
	if value != "-" {
 | 
						|
		var err error
 | 
						|
		v, err = strconv.ParseUint(value, 10, 64)
 | 
						|
		if err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
	}
 | 
						|
	*field = v
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
func (ds *Dataset) parseLine(line []string) error {
 | 
						|
	prop := line[1]
 | 
						|
	val := line[2]
 | 
						|
 | 
						|
	var err error
 | 
						|
 | 
						|
	switch prop {
 | 
						|
	case "available":
 | 
						|
		err = setUint(&ds.Avail, val)
 | 
						|
	case "compression":
 | 
						|
		setString(&ds.Compression, val)
 | 
						|
	case "mountpoint":
 | 
						|
		setString(&ds.Mountpoint, val)
 | 
						|
	case "quota":
 | 
						|
		err = setUint(&ds.Quota, val)
 | 
						|
	case "type":
 | 
						|
		setString(&ds.Type, val)
 | 
						|
	case "origin":
 | 
						|
		setString(&ds.Origin, val)
 | 
						|
	case "used":
 | 
						|
		err = setUint(&ds.Used, val)
 | 
						|
	case "volsize":
 | 
						|
		err = setUint(&ds.Volsize, val)
 | 
						|
	case "written":
 | 
						|
		err = setUint(&ds.Written, val)
 | 
						|
	case "logicalused":
 | 
						|
		err = setUint(&ds.Logicalused, val)
 | 
						|
	}
 | 
						|
	return err
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * from zfs diff`s escape function:
 | 
						|
 *
 | 
						|
 * Prints a file name out a character at a time.  If the character is
 | 
						|
 * not in the range of what we consider "printable" ASCII, display it
 | 
						|
 * as an escaped 3-digit octal value.  ASCII values less than a space
 | 
						|
 * are all control characters and we declare the upper end as the
 | 
						|
 * DELete character.  This also is the last 7-bit ASCII character.
 | 
						|
 * We choose to treat all 8-bit ASCII as not printable for this
 | 
						|
 * application.
 | 
						|
 */
 | 
						|
func unescapeFilepath(path string) (string, error) {
 | 
						|
	buf := make([]byte, 0, len(path))
 | 
						|
	llen := len(path)
 | 
						|
	for i := 0; i < llen; {
 | 
						|
		if path[i] == '\\' {
 | 
						|
			if llen < i+4 {
 | 
						|
				return "", fmt.Errorf("Invalid octal code: too short")
 | 
						|
			}
 | 
						|
			octalCode := path[(i + 1):(i + 4)]
 | 
						|
			val, err := strconv.ParseUint(octalCode, 8, 8)
 | 
						|
			if err != nil {
 | 
						|
				return "", fmt.Errorf("Invalid octal code: %v", err)
 | 
						|
			}
 | 
						|
			buf = append(buf, byte(val))
 | 
						|
			i += 4
 | 
						|
		} else {
 | 
						|
			buf = append(buf, path[i])
 | 
						|
			i++
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return string(buf), nil
 | 
						|
}
 | 
						|
 | 
						|
var changeTypeMap = map[string]ChangeType{
 | 
						|
	"-": Removed,
 | 
						|
	"+": Created,
 | 
						|
	"M": Modified,
 | 
						|
	"R": Renamed,
 | 
						|
}
 | 
						|
var inodeTypeMap = map[string]InodeType{
 | 
						|
	"B": BlockDevice,
 | 
						|
	"C": CharacterDevice,
 | 
						|
	"/": Directory,
 | 
						|
	">": Door,
 | 
						|
	"|": NamedPipe,
 | 
						|
	"@": SymbolicLink,
 | 
						|
	"P": EventPort,
 | 
						|
	"=": Socket,
 | 
						|
	"F": File,
 | 
						|
}
 | 
						|
 | 
						|
// matches (+1) or (-1)
 | 
						|
var referenceCountRegex = regexp.MustCompile("\\(([+-]\\d+?)\\)")
 | 
						|
 | 
						|
func parseReferenceCount(field string) (int, error) {
 | 
						|
	matches := referenceCountRegex.FindStringSubmatch(field)
 | 
						|
	if matches == nil {
 | 
						|
		return 0, fmt.Errorf("Regexp does not match")
 | 
						|
	}
 | 
						|
	return strconv.Atoi(matches[1])
 | 
						|
}
 | 
						|
 | 
						|
func parseInodeChange(line []string) (*InodeChange, error) {
 | 
						|
	llen := len(line)
 | 
						|
	if llen < 1 {
 | 
						|
		return nil, fmt.Errorf("Empty line passed")
 | 
						|
	}
 | 
						|
 | 
						|
	changeType := changeTypeMap[line[0]]
 | 
						|
	if changeType == 0 {
 | 
						|
		return nil, fmt.Errorf("Unknown change type '%s'", line[0])
 | 
						|
	}
 | 
						|
 | 
						|
	switch changeType {
 | 
						|
	case Renamed:
 | 
						|
		if llen != 4 {
 | 
						|
			return nil, fmt.Errorf("Mismatching number of fields: expect 4, got: %d", llen)
 | 
						|
		}
 | 
						|
	case Modified:
 | 
						|
		if llen != 4 && llen != 3 {
 | 
						|
			return nil, fmt.Errorf("Mismatching number of fields: expect 3..4, got: %d", llen)
 | 
						|
		}
 | 
						|
	default:
 | 
						|
		if llen != 3 {
 | 
						|
			return nil, fmt.Errorf("Mismatching number of fields: expect 3, got: %d", llen)
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	inodeType := inodeTypeMap[line[1]]
 | 
						|
	if inodeType == 0 {
 | 
						|
		return nil, fmt.Errorf("Unknown inode type '%s'", line[1])
 | 
						|
	}
 | 
						|
 | 
						|
	path, err := unescapeFilepath(line[2])
 | 
						|
	if err != nil {
 | 
						|
		return nil, fmt.Errorf("Failed to parse filename: %v", err)
 | 
						|
	}
 | 
						|
 | 
						|
	var newPath string
 | 
						|
	var referenceCount int
 | 
						|
	switch changeType {
 | 
						|
	case Renamed:
 | 
						|
		newPath, err = unescapeFilepath(line[3])
 | 
						|
		if err != nil {
 | 
						|
			return nil, fmt.Errorf("Failed to parse filename: %v", err)
 | 
						|
		}
 | 
						|
	case Modified:
 | 
						|
		if llen == 4 {
 | 
						|
			referenceCount, err = parseReferenceCount(line[3])
 | 
						|
			if err != nil {
 | 
						|
				return nil, fmt.Errorf("Failed to parse reference count: %v", err)
 | 
						|
			}
 | 
						|
		}
 | 
						|
	default:
 | 
						|
		newPath = ""
 | 
						|
	}
 | 
						|
 | 
						|
	return &InodeChange{
 | 
						|
		Change:               changeType,
 | 
						|
		Type:                 inodeType,
 | 
						|
		Path:                 path,
 | 
						|
		NewPath:              newPath,
 | 
						|
		ReferenceCountChange: referenceCount,
 | 
						|
	}, nil
 | 
						|
}
 | 
						|
 | 
						|
// example input
 | 
						|
//M       /       /testpool/bar/
 | 
						|
//+       F       /testpool/bar/hello.txt
 | 
						|
//M       /       /testpool/bar/hello.txt (+1)
 | 
						|
//M       /       /testpool/bar/hello-hardlink
 | 
						|
func parseInodeChanges(lines [][]string) ([]*InodeChange, error) {
 | 
						|
	changes := make([]*InodeChange, len(lines))
 | 
						|
 | 
						|
	for i, line := range lines {
 | 
						|
		c, err := parseInodeChange(line)
 | 
						|
		if err != nil {
 | 
						|
			return nil, fmt.Errorf("Failed to parse line %d of zfs diff: %v, got: '%s'", i, err, line)
 | 
						|
		}
 | 
						|
		changes[i] = c
 | 
						|
	}
 | 
						|
	return changes, nil
 | 
						|
}
 | 
						|
 | 
						|
func listByType(t, filter string) ([]*Dataset, error) {
 | 
						|
	args := []string{"get", "-rHp", "-t", t, "all"}
 | 
						|
	if filter != "" {
 | 
						|
		args = append(args, filter)
 | 
						|
	}
 | 
						|
	out, err := zfs(args...)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	var datasets []*Dataset
 | 
						|
 | 
						|
	name := ""
 | 
						|
	var ds *Dataset
 | 
						|
	for _, line := range out {
 | 
						|
		if name != line[0] {
 | 
						|
			name = line[0]
 | 
						|
			ds = &Dataset{Name: name}
 | 
						|
			datasets = append(datasets, ds)
 | 
						|
		}
 | 
						|
		if err := ds.parseLine(line); err != nil {
 | 
						|
			return nil, err
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return datasets, nil
 | 
						|
}
 | 
						|
 | 
						|
func propsSlice(properties map[string]string) []string {
 | 
						|
	args := make([]string, 0, len(properties)*3)
 | 
						|
	for k, v := range properties {
 | 
						|
		args = append(args, "-o")
 | 
						|
		args = append(args, fmt.Sprintf("%s=%s", k, v))
 | 
						|
	}
 | 
						|
	return args
 | 
						|
}
 | 
						|
 | 
						|
func (z *Zpool) parseLine(line []string) error {
 | 
						|
	prop := line[1]
 | 
						|
	val := line[2]
 | 
						|
 | 
						|
	var err error
 | 
						|
 | 
						|
	switch prop {
 | 
						|
	case "health":
 | 
						|
		setString(&z.Health, val)
 | 
						|
	case "allocated":
 | 
						|
		err = setUint(&z.Allocated, val)
 | 
						|
	case "size":
 | 
						|
		err = setUint(&z.Size, val)
 | 
						|
	case "free":
 | 
						|
		err = setUint(&z.Free, val)
 | 
						|
	}
 | 
						|
	return err
 | 
						|
}
 |