mirror of
				https://github.com/k3s-io/kubernetes.git
				synced 2025-10-31 05:40:42 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			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
 | |
| }
 |