mirror of
https://github.com/ceph/ceph-csi.git
synced 2025-03-09 08:59:30 +00:00
Several packages are only used while running the e2e suite. These packages are less important to update, as the they can not influence the final executable that is part of the Ceph-CSI container-image. By moving these dependencies out of the main Ceph-CSI go.mod, it is easier to identify if a reported CVE affects Ceph-CSI, or only the testing (like most of the Kubernetes CVEs). Signed-off-by: Niels de Vos <ndevos@ibm.com>
361 lines
7.6 KiB
Go
361 lines
7.6 KiB
Go
package zfs
|
|
|
|
import (
|
|
"bytes"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"os/exec"
|
|
"regexp"
|
|
"runtime"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/google/uuid"
|
|
)
|
|
|
|
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
|
|
|
|
id := uuid.New().String()
|
|
joinedArgs := strings.Join(cmd.Args, " ")
|
|
|
|
logger.Log([]string{"ID:" + id, "START", joinedArgs})
|
|
err := cmd.Run()
|
|
logger.Log([]string{"ID:" + id, "FINISH"})
|
|
|
|
if err != nil {
|
|
return nil, &Error{
|
|
Err: err,
|
|
Debug: strings.Join([]string{cmd.Path, joinedArgs[1:]}, " "),
|
|
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 {
|
|
var err error
|
|
|
|
if len(line) != len(dsPropList) {
|
|
return errors.New("Output does not match what is expected on this platform")
|
|
}
|
|
setString(&ds.Name, line[0])
|
|
setString(&ds.Origin, line[1])
|
|
|
|
if err = setUint(&ds.Used, line[2]); err != nil {
|
|
return err
|
|
}
|
|
if err = setUint(&ds.Avail, line[3]); err != nil {
|
|
return err
|
|
}
|
|
|
|
setString(&ds.Mountpoint, line[4])
|
|
setString(&ds.Compression, line[5])
|
|
setString(&ds.Type, line[6])
|
|
|
|
if err = setUint(&ds.Volsize, line[7]); err != nil {
|
|
return err
|
|
}
|
|
if err = setUint(&ds.Quota, line[8]); err != nil {
|
|
return err
|
|
}
|
|
if err = setUint(&ds.Referenced, line[9]); err != nil {
|
|
return err
|
|
}
|
|
|
|
if runtime.GOOS == "solaris" {
|
|
return nil
|
|
}
|
|
|
|
if err = setUint(&ds.Written, line[10]); err != nil {
|
|
return err
|
|
}
|
|
if err = setUint(&ds.Logicalused, line[11]); err != nil {
|
|
return err
|
|
}
|
|
if err = setUint(&ds.Usedbydataset, line[12]); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
/*
|
|
* 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{"list", "-rHp", "-t", t, "-o", dsPropListOptions}
|
|
|
|
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 "name":
|
|
setString(&z.Name, val)
|
|
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)
|
|
case "fragmentation":
|
|
// Trim trailing "%" before parsing uint
|
|
i := strings.Index(val, "%")
|
|
if i < 0 {
|
|
i = len(val)
|
|
}
|
|
err = setUint(&z.Fragmentation, val[:i])
|
|
case "readonly":
|
|
z.ReadOnly = val == "on"
|
|
case "freeing":
|
|
err = setUint(&z.Freeing, val)
|
|
case "leaked":
|
|
err = setUint(&z.Leaked, val)
|
|
case "dedupratio":
|
|
// Trim trailing "x" before parsing float64
|
|
z.DedupRatio, err = strconv.ParseFloat(val[:len(val)-1], 64)
|
|
}
|
|
return err
|
|
}
|