package main import ( "context" "fmt" "strings" ) // PropertiesArgv is every property of one dataset, the name validated against the list first. func PropertiesArgv(dataset string) []string { return []string{"get", "-Hp", "-o", "property,value,source", "all", "--", dataset} } // Withheld are properties whose value the module never answers: where an encryption key is kept can // itself be a secret (a URL with a token). ZFS never gives a key itself. var Withheld = map[string]bool{"keylocation": true} // Property is one property, with where its value comes from. type Property struct { Name string `json:"name"` Value string `json:"value"` Source string `json:"source"` } // ParseProperties reads `zfs get -Hp -o property,value,source`. func ParseProperties(out string) ([]Property, error) { var ps []Property for _, line := range lines(out) { f := strings.Split(line, "\t") if len(f) != 3 { return nil, fmt.Errorf("zfs get answered a line with %d columns, not 3: %q", len(f), line) } p := Property{Name: f[0], Value: f[1], Source: f[2]} if Withheld[p.Name] && p.Value != "none" && p.Value != "-" { p.Value = "(withheld: where the key is kept is not answered)" } ps = append(ps, p) } return ps, nil } // Highlights are the properties zfs_properties says in lines first. var Highlights = []string{"type", "mountpoint", "mounted", "compression", "compressratio", "recordsize", "volblocksize", "atime", "relatime", "sync", "xattr", "acltype", "dedup", "copies", "checksum", "sharenfs", "sharesmb", "readonly", "canmount", "encryption", "keystatus", "keyformat", "encryptionroot", "snapdir", "primarycache", "secondarycache", "logbias", "special_small_blocks", "quota", "refquota", "reservation", "refreservation"} // PropertiesAnswer is what zfs_properties answers. type PropertiesAnswer struct { Applicable bool `json:"applicable"` Dataset string `json:"dataset"` Properties []Property `json:"properties"` Said []string `json:"said"` } // PropertiesOf is every property of one dataset that exists. func (m *Machine) PropertiesOf(ctx context.Context, dataset string) (any, error) { if dataset == "" { return nil, fmt.Errorf("name the dataset; zfs_datasets lists them") } ds, err := m.Datasets(ctx) if err != nil { return notHere(err) } if find(ds, dataset) == nil { return nil, fmt.Errorf("there is no dataset %q on this machine; zfs_datasets lists them", dataset) } out, err := m.ask(ctx, "zfs", PropertiesArgv(dataset)...) if err != nil { return nil, err } ps, err := ParseProperties(out) if err != nil { return nil, err } a := PropertiesAnswer{Applicable: true, Dataset: dataset, Properties: ps, Said: []string{}} by := map[string]Property{} for _, p := range ps { by[p.Name] = p } for _, name := range Highlights { p, ok := by[name] if !ok || p.Value == "-" { continue } line := fmt.Sprintf("%s = %s", name, p.Value) if p.Source != "-" && p.Source != "" { line += " (" + p.Source + ")" } a.Said = append(a.Said, line) } return a, nil }