package cmd import ( "io" "os" "fmt" "log" "sort" "bufio" "errors" "os/exec" "reflect" "strconv" "strings" "io/ioutil" "github.com/c2h5oh/datasize" ) const ( ipv4re = `[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}` ifconfigipv4re = `inet[[:space:]](` + ipv4re + `)` ) /***************************************************************************** * Mandatory constructor for JailConfig type. It set default values *****************************************************************************/ func NewJailConfig() (JailConfig, error) { var jc JailConfig hostid, err := ioutil.ReadFile("/etc/hostid") if err != nil { return jc, err } else { hostid = []byte(strings.Replace(string(hostid), "\n", "", -1)) } jc.Allow_chflags = 0 jc.Allow_mlock = 0 jc.Allow_mount = 0 jc.Allow_mount_devfs = 0 jc.Allow_mount_fusefs = 0 jc.Allow_mount_nullfs = 0 jc.Allow_mount_procfs = 0 jc.Allow_mount_tmpfs = 0 jc.Allow_mount_zfs = 0 jc.Allow_quotas = 0 jc.Allow_raw_sockets = 0 jc.Allow_socket_af = 0 jc.Allow_set_hostname = 1 jc.Allow_sysvipc = 0 jc.Allow_tun = 0 jc.Allow_vmm = 0 jc.Assign_localhost = 0 jc.Available = "readonly" jc.Basejail = 0 jc.Bpf = 0 jc.Boot = 0 jc.Children_max = "0" jc.Comment = "none" jc.Compression = "lz4" jc.Compressratio = "readonly" jc.Coredumpsize = "off" jc.Count = "1" jc.Cpuset = "off" jc.Cputime = "off" jc.Datasize = "off" jc.Dedup = "off" jc.Defaultrouter = "auto" jc.Defaultrouter6 = "auto" jc.Depends = "none" jc.Devfs_ruleset = "4" jc.Dhcp = 0 jc.Enforce_statfs = "2" jc.Exec_clean = 1 jc.Exec_created = "/usr/bin/true" jc.Exec_jail_user = "root" jc.Exec_fib = "0" jc.Exec_poststart = "/usr/bin/true" jc.Exec_poststop = "/usr/bin/true" jc.Exec_prestart = "/usr/bin/true" jc.Exec_prestop = "/usr/bin/true" jc.Exec_system_jail_user = "0" jc.Exec_system_user = "root" jc.Exec_start = "/bin/sh /etc/rc" jc.Exec_stop = "/bin/sh /etc/rc.shutdown" jc.Exec_timeout = "60" jc.Hostid = string(hostid) jc.Hostid_strict_check = 0 jc.Host_time = 1 jc.Interfaces = "vnet0:bridge0" jc.Ip4_addr = "none" jc.Ip4_saddrsel = "1" jc.Ip4 = "new" jc.Ip6_addr = "none" jc.Ip6_saddrsel = "1" jc.Ip6 = "new" jc.Ip_hostname = 0 jc.Jailtype = "jail" jc.Jail_zfs = 0 jc.Jail_zfs_mountpoint = "none" jc.Last_started = "none" jc.Localhost_ip = "none" jc.Login_flags = "-f root" jc.Maxproc = "off" jc.Min_dyn_devfs_ruleset = "1000" jc.Memoryuse = "off" jc.Memorylocked = "off" jc.Mountpoint = "readonly" jc.Mount_devfs = 1 jc.Mount_fdescfs = 1 jc.Mount_procfs = 0 jc.Mount_linprocfs = 0 jc.Msgqqueued = "off" jc.Msgqsize = "off" jc.Nat = 0 jc.Nat_backend = "ipfw" jc.Nat_forwards = "none" jc.Nat_interface = "none" jc.Nat_prefix = "172.16" jc.Nmsgq = "off" jc.Notes = "none" jc.Nsem = "off" jc.Nsemop = "off" jc.Nshm = "off" jc.Nthr = "off" jc.Openfiles = "off" jc.Origin = "readonly" jc.Owner = "root" jc.Pcpu = "off" jc.Plugin_name = "none" jc.Plugin_repository = "none" jc.Priority = "99" jc.Pseudoterminals = "off" jc.Quota = "none" jc.Readbps = "off" jc.Readiops = "off" jc.Reservation = "none" jc.Resolver = "/etc/resolv.conf" jc.Rlimits = "off" jc.Rtsold = 0 jc.Securelevel = "2" jc.Shmsize = "off" jc.Stacksize = "off" jc.Stop_timeout = "30" jc.Sync_state = "none" jc.Sync_target = "none" jc.Sync_tgt_zpool = "none" jc.Sysvmsg = "new" jc.Sysvsem = "new" jc.Sysvshm = "new" jc.Swapuse = "off" jc.Template = 0 jc.Used = "readonly" jc.Vmemoryuse = "off" jc.Vnet = 0 jc.Vnet0_mac = "none" jc.Vnet1_mac = "none" jc.Vnet2_mac = "none" jc.Vnet3_mac = "none" jc.Vnet_default_interface = "auto" jc.Vnet_interfaces = "none" jc.Wallclock = "off" jc.Writebps = "off" jc.Writeiops = "off" return jc, nil } /***************************************************************************** * * Command execution * *****************************************************************************/ func executeCommand(cmdline string) (string, error) { var cmd []string var out []byte var err error if gUseSudo { cmd = append(cmd, "sudo") } cs := strings.Split(cmdline, " ") cmd = append(cmd, cs...) if len(cmd) > 1 { out, err = exec.Command(cmd[0], cmd[1:]...).CombinedOutput() } else { out, err = exec.Command(cmd[0]).CombinedOutput() } return string(out), err } func executeCommandInJail(jail *Jail, cmdline string) (string, error) { var cmd []string if gUseSudo { cmd = append(cmd, "sudo") } cmd = append(cmd, "setfib") if len(jail.Config.Exec_fib) > 0 { cmd = append(cmd, jail.Config.Exec_fib) } else { cmd = append(cmd, "0") } cmd = append(cmd, "jexec", jail.InternalName) cs := strings.Split(cmdline, " ") cmd = append(cmd, cs...) out, err := exec.Command(cmd[0], cmd[1:]...).CombinedOutput() return string(out), err } /***************************************************************************** * * ZFS datasets/pools operations * *****************************************************************************/ func zfsSnapshot(dataset string, snapname string) error { cmd := fmt.Sprintf("zfs snapshot %s@%s", dataset, snapname) out, err := executeCommand(cmd) if err != nil { return errors.New(fmt.Sprintf("%v; command returned \"%s\"", err, out)) } return nil } func zfsCopy(src string, dest string) error { // First, declare sending process & pipe cmd_send := exec.Command("zfs", "send", src) stdout_send, err := cmd_send.StdoutPipe() if err != nil { //fmt.Printf("Error executing command \"zfs send %s\": %v\n", fmt.Sprintf("%s@gocage_mig_init", dsconf), err) return errors.New(fmt.Sprintf("Error: %v\n", err)) } // then declare receiving process & pipe cmd_recv := exec.Command("zfs", "receive", dest) stdin_recv, err := cmd_recv.StdinPipe() if err != nil { //fmt.Printf("Error executing command \"zfs receive %s\": %v\n", dest, err) return errors.New(fmt.Sprintf("Error: %v\n", err)) } // Copy data in a go routine go io.Copy(stdin_recv, stdout_send) // then start processes and wait for finish if err := cmd_recv.Start(); err != nil { //fmt.Printf("Error: %v\n", err) return errors.New(fmt.Sprintf("Error starting receive process: %v\n", err)) } //fmt.Printf("DEBUG: Start \"zfs send %s\"\n", dsconf) if err := cmd_send.Start(); err != nil { //fmt.Printf("Error: %v\n", err) return errors.New(fmt.Sprintf("Error starting send process: %v\n", err)) } //fmt.Printf("DEBUG: Wait for zfs send to finish\n") if err := cmd_send.Wait(); err != nil { //fmt.Printf("Error: zfs send halted with %v\n", err) return errors.New(fmt.Sprintf("send halted with: %v\n", err)) } //fmt.Printf("DEBUG: Wait for zfs recv to finish\n") if err := cmd_recv.Wait(); err != nil { //fmt.Printf("Error: zfs recv halted with %v\n", err) return errors.New(fmt.Sprintf("receive halted with: %v\n", err)) } return nil } // Copy incremental snasphot to destination func zfsCopyIncremental(firstsnap string, secondsnap string, dest string) error { // First, declare sending process & pipe cmd_send := exec.Command("zfs", "send", "-i", firstsnap, secondsnap) stdout_send, err := cmd_send.StdoutPipe() if err != nil { return errors.New(fmt.Sprintf("Error: %v\n", err)) } // then declare receiving process & pipe /* "-Fu" flags to avoid "cannot receive incremental stream: destination qstore/iocage/jails/kibana has been modified * since most recent snapshot" error message */ cmd_recv := exec.Command("zfs", "receive", "-Fu", dest) stdin_recv, err := cmd_recv.StdinPipe() if err != nil { return errors.New(fmt.Sprintf("Error: %v\n", err)) } // Copy data in a go routine go io.Copy(stdin_recv, stdout_send) // then start processes and wait for finish if err := cmd_recv.Start(); err != nil { return errors.New(fmt.Sprintf("Error starting receive process: %v\n", err)) } //fmt.Printf("DEBUG: Start \"zfs send %s\"\n", dsconf) if err := cmd_send.Start(); err != nil { return errors.New(fmt.Sprintf("Error starting send process: %v\n", err)) } //fmt.Printf("DEBUG: Wait for zfs send to finish\n") if err := cmd_send.Wait(); err != nil { return errors.New(fmt.Sprintf("send halted with: %v\n", err)) } //fmt.Printf("DEBUG: Wait for zfs recv to finish\n") if err := cmd_recv.Wait(); err != nil { return errors.New(fmt.Sprintf("receive halted with: %v\n", err)) } return nil } /***************************************************************************** * * rc.conf management * *****************************************************************************/ func enableRcKeyValue(rcconfpath string, key string, value string) error { cmd := fmt.Sprintf("/usr/sbin/sysrc -f %s %s=%s", rcconfpath, key, value) _, err := executeCommand(cmd) if err != nil { return err } return nil } func disableRcKey(rcconfpath string, key string) error { cmd := fmt.Sprintf("/usr/sbin/sysrc -f %s -x %s", rcconfpath, key) _, err := executeCommand(cmd) if err != nil { return err } return nil } /***************************************************************************** * Parse an fstab file, returning an array of Mount *****************************************************************************/ func getFstab(path string) ([]Mount, error) { var mounts []Mount f, err := os.Open(path) if err != nil { return mounts, err } defer f.Close() scan := bufio.NewScanner(f) for scan.Scan() { res := strings.Fields(scan.Text()) if len(res) != 6 { return mounts, fmt.Errorf("Incorrect format for fstab line %s", scan.Text()) } freq, err := strconv.Atoi(res[4]) if err != nil { return mounts, fmt.Errorf("Incorrect format for fstab line %s: Dump is not an integer\n", scan.Text()) } pass, err := strconv.Atoi(res[5]) if err != nil { return mounts, fmt.Errorf("Incorrect format for fstab line %s: Pass is not an integer\n", scan.Text()) } m := Mount{ Device: res[0], Mountpoint: res[1], Type: res[2], Options: strings.Split(res[3], ","), Fs_Freq: freq, Fs_Passno: pass, } mounts = append(mounts, m) } return mounts, nil } /******************************************************************************** * Get a specific jail reference, to update properties after a range loop *******************************************************************************/ func getJailFromArray(name string, jarray []Jail) (*Jail, error) { for _, j := range jarray { if name == j.Name { return &j, nil } } return &Jail{}, errors.New("Jail not found") } /****************************************************************************** * * Generic utilities * *****************************************************************************/ func isStringInArray(strarr []string, searched string) bool { for _, s := range strarr { if strings.EqualFold(s, searched) { return true } } return false } func getDatastoreFromArray(name string, dsa []Datastore) (*Datastore, error) { for _, d := range dsa { if name == d.Name { return &d, nil } } return &Datastore{}, errors.New("Datastore not found") } func getDevfsRuleset(ruleset int) []string { cmd := fmt.Sprintf("/sbin/devfs rule -s %d show", ruleset) out, err := executeCommand(cmd) if err != nil { return []string{} } // Get rid of the last "\n" return strings.Split(out, "\n")[:len(strings.Split(out, "\n"))-1] } func copyDevfsRuleset(ruleset int, srcrs int) error { // Resulting ruleset as an array of line //var result []string out := getDevfsRuleset(srcrs) for _, line := range out { //fields := strings.Fields(line) cmd := fmt.Sprintf("/sbin/devfs rule -s %d add %s", ruleset, line) out, err := executeCommand(cmd) if err != nil { return errors.New(fmt.Sprintf("Error adding rule \"%s\" to ruleset %d: %s", line, ruleset, out)) } } return nil } /******************************************************************************** * Add a rule to specified ruleset * Ex.: addDevfsRuleToRuleset("path bpf* unhide", 1002) *******************************************************************************/ func addDevfsRuleToRuleset(rule string, ruleset int) error { // TODO: Check if rule not already enabled. We will need to recurse into includes. // Get last rule index rules := getDevfsRuleset(ruleset) if len(rules) == 0 { fmt.Printf("Error listing ruleset %d\n", ruleset) return errors.New(fmt.Sprintf("Error listing rueset %d\n", ruleset)) } f := strings.Fields(rules[(len(rules)-1)]) //fmt.Printf("Dernier index du ruleset %d: %s\n", ruleset, f[0]) index, _ := strconv.Atoi(f[0]) index += 100 cmd := fmt.Sprintf("/sbin/devfs rule -s %d add %d %s", ruleset, index, rule) out, err := executeCommand(cmd) if err != nil { return errors.New(fmt.Sprintf("Error adding rule \"%s\" to ruleset %d: %s", rule, ruleset, out)) } return nil } /****************************************************************************** * Return the quantity of jails with the name passed as parameter *****************************************************************************/ func countOfJailsWithThisName(name string) int { count := 0 for _, j := range gJails { if strings.EqualFold(j.Name, name) { count++ } } return count } /******************************************************************************** * Recurse into structure, returning reflect.Kind of named field. * Nested fields are named with a dot (ex "MyStruct.MyField") *******************************************************************************/ func getStructFieldKind(parentStruct interface{}, fieldName string) (reflect.Kind, string, error) { v := reflect.ValueOf(parentStruct) if v.Kind() == reflect.Ptr { v = v.Elem() } // For debugging if false { for i := 0; i < v.NumField(); i++ { f := v.Field(i) if f.Kind() == reflect.String { fmt.Printf("%v=%v\n", v.Type().Field(i).Name, f.Interface()) } } } if strings.Contains(fieldName, ".") { fs := strings.Split(fieldName, ".") f := v.FieldByName(fs[0]) if f.Kind() == reflect.Struct { return getStructFieldKind(f.Interface(), strings.Join(fs[1:], ".")) } else { return reflect.Kind(0), fieldName, errors.New(fmt.Sprintf("%s is not a struct: %s\n", fs[0], f.Kind().String())) } } else { f := v.FieldByName(fieldName) if f.IsValid() { return f.Kind(), fieldName, nil } else { return reflect.Kind(0), fieldName, errors.New(fmt.Sprintf("Field not found: %s", fieldName)) } } return reflect.Kind(0), fieldName, errors.New(fmt.Sprintf("Field not found: %s", fieldName)) } /******************************************************************************** * Display struct attributes name for a given struct. * Recurse into struct attributes of type struct * Used to show user jail properties *******************************************************************************/ func getStructFieldNames(parentStruct interface{}, result []string, prefix string) []string { v := reflect.ValueOf(parentStruct) for i := 0; i < v.NumField(); i++ { if v.Type().Field(i).Type.Kind() == reflect.Struct { result = getStructFieldNames(v.Field(i).Interface(), result, v.Type().Field(i).Name) } else { if len(prefix) > 0 { result = append(result, fmt.Sprintf("%s.%s", prefix, v.Type().Field(i).Name)) } else { result = append(result, v.Type().Field(i).Name) } } } return result } /******************************************************************************** * Recurse into structure, returning reflect.Value of wanted field. * Nested fields are named with a dot (ex "MyStruct.MyField") * Returns (value, field_name, error) *******************************************************************************/ func getStructFieldValue(parentStruct interface{}, fieldName string) (*reflect.Value, string, error) { v := reflect.ValueOf(parentStruct) // Get value while we're dealing with pointers for ; v.Kind() == reflect.Ptr; v = v.Elem() { } if v.Kind() != reflect.Struct { return &v, fieldName, errors.New(fmt.Sprintf("parentStruct is not a struct! Kind: %s", v.Kind().String())) } typeOfV := v.Type() if false { for i := 0; i < v.NumField(); i++ { f := v.Field(i) if f.Kind() == reflect.String { fmt.Printf("%v=%v\n", v.Type().Field(i).Name, f.Interface()) } } } var f reflect.Value var found bool fs := strings.Split(fieldName, ".") // Loop through properties for i, curF := range fs { found = false for j := 0; j < v.NumField(); j++ { if typeOfV.Field(j).Name == curF { f = v.Field(j) found = true break } } if !found { return &v, fieldName, errors.New(fmt.Sprintf("Field not found: %s", fieldName)) } for ; f.Kind() == reflect.Ptr; f = f.Elem() { } /*fmt.Printf("v.kind() = %v\n", v.Kind().String()) fmt.Printf("v = %v\n", v) fmt.Printf("f.kind() = %v\n", f.Kind().String()) fmt.Printf("f = %v\n", f)*/ // If this is the last loop, return result even if it's a struct // FIXME : What if we got interface? if f.Kind() != reflect.Struct && i < len(fs)-1 { if f.IsValid() { //fmt.Printf("Return f = %v of Kind %s\n", f, f.Kind().String()) return &f, fieldName, nil } else { return &v, fieldName, errors.New(fmt.Sprintf("Field not found: %s", fieldName)) } } else { v = f typeOfV = v.Type() } } return &v, fieldName, nil } /******************************************************************************** * TODO : Replace by getStructFieldValue * Recurse into structure, returning reflect.Value of wanted field. * Nested fields are named with a dot (ex "MyStruct.MyField") *******************************************************************************/ func getStructField(parentStruct interface{}, fieldName string) (reflect.Value, string) { v := reflect.ValueOf(parentStruct) if v.Kind() == reflect.Ptr { v = v.Elem() } if false { for i := 0; i < v.NumField(); i++ { f := v.Field(i) if f.Kind() == reflect.String { fmt.Printf("%v=%v\n", v.Type().Field(i).Name, f.Interface()) } } } if strings.Contains(fieldName, ".") { fs := strings.Split(fieldName, ".") f := v.FieldByName(fs[0]) if f.Kind() == reflect.Struct { return getStructField(f.Interface(), strings.Join(fs[1:], ".")) } else { log.Fatalln(fmt.Sprintf("%s is not a struct: %s\n", fs[0], f.Kind().String())) } } return v, fieldName } // setStructFieldValue takes a string as propValue, whatever the real property type is. // It will be converted. func setStructFieldValue(parentStruct interface{}, propName string, propValue string) error { val, _, err := getStructFieldValue(parentStruct, propName) if err != nil { return err } if val.CanSet() { switch val.Kind() { case reflect.String: val.SetString(propValue) case reflect.Int: ival, err := strconv.ParseInt(propValue, 10, 64) if err != nil { return err } val.SetInt(ival) case reflect.Bool: bval, err := strconv.ParseBool(propValue) if err != nil { return err } val.SetBool(bval) default: return errors.New(fmt.Sprintf("Field is an unkown type: %s: %s", propName, val.Kind().String())) } } else { return errors.New(fmt.Sprintf("Field is not writable : %s", propName)) } return nil } /******************************************************************************** * Pretty display of jails field * Fields to show are given in a string array parameter * Ex. : displayJailsFields(gJails, ["Name", "JID", "RootPath"]) *******************************************************************************/ func displayJailsFields(jails []Jail, valsToDisplay []string) { /* A line is defined by : * Nr of fields * For each field : * Its name * Its max length * Its value */ type Field struct { Name string MaxLen int Value string } type Line []Field type Output []Line var out Output //fmt.Printf("%d fields in a %d items structure\n", len(valsToDisplay), len(jails)) // Browse structure to get max length and values for _, j := range jails { // Have to use a pointer, else reflect.Value.Elem() will panic : https://pkg.go.dev/reflect#Value.Elem tj := &j line := make([]Field, len(valsToDisplay)) for i, f := range valsToDisplay { a, f := getStructField(tj, f) field := Field{ Name: f, } if a.FieldByName(f).IsValid() { // For now this just contains this item length, will adjust later // We need to divide length by number of items in string fields (because we can have more than 1 ip4_addr) if reflect.TypeOf(a.FieldByName(f).Interface()).Kind() == reflect.String { itnr := len(strings.Split(string(a.FieldByName(f).Interface().(string)), ",")) field.MaxLen = len(fmt.Sprintf("%v", a.FieldByName(f).Interface())) / itnr } else { field.MaxLen = len(fmt.Sprintf("%v", a.FieldByName(f).Interface())) } field.Value = fmt.Sprintf("%v", a.FieldByName(f).Interface()) } else { fmt.Printf("Invalid field name: %s\n", f) } line[i] = field } out = append(out, line) } if len(out) == 0 { fmt.Printf("Nothing to see here!\n") return } // Get real maximum length maxlen := make([]int, len(valsToDisplay)) for i := 0; i < len(valsToDisplay); i++ { maxlen[i] = len(valsToDisplay[i]) } for _, l := range out { for i, f := range l { if f.MaxLen > maxlen[i] { maxlen[i] = f.MaxLen } } } // Align maxlen to the real maximum length for io, l := range out { for ii, _ := range l { // We need to access slice by index if we want to modify content out[io][ii].MaxLen = maxlen[ii] } } totalLen := 0 // For each field, add separator and 2 blank spaces for _, f := range out[0] { totalLen += f.MaxLen + 3 } // Then add starting delimiter totalLen += 1 Debug := false if Debug == true { for _, f := range out[0] { fmt.Printf("%s max length : %d\n", f.Name, f.MaxLen) } } // Lets draw things on the screen! // First, headers: 1st separator line for i, f := range out[0] { if i == 0 { fmt.Printf("+") } for i := 0; i < f.MaxLen+2; i++ { fmt.Printf("=") } fmt.Printf("+") } fmt.Printf("\n") // Column names for i, f := range out[0] { if i == 0 { fmt.Printf("|") } /* Use vlsToDisplay to get real name (with "Config.") * fmt.Printf(" %s", f.Name) * for i := len(f.Name)+1 ; i < f.MaxLen+1 ; i++ { */ fmt.Printf(" %s", valsToDisplay[i]) for i := len(valsToDisplay[i]) + 1; i < f.MaxLen+1; i++ { fmt.Printf(" ") } fmt.Printf(" |") } // Finally separator line fmt.Printf("\n") for i, f := range out[0] { if i == 0 { fmt.Printf("+") } for i := 0; i < f.MaxLen+2; i++ { fmt.Printf("=") } fmt.Printf("+") } fmt.Printf("\n") // Then display data // Loop through lines for _, l := range out { // Loop through fields // In case we need to add a line for a 2nd IP, or whatever object var supplines = make(map[string]string) for i, f := range l { if i == 0 { fmt.Printf("|") } // Special cases of value displaying if f.Name == "JID" && f.Value == "0" { fmt.Printf(" ") } else if f.Name == "Ip4_addr" { ia := strings.Split(f.Value, ",") // If we have more than 1 value we need to finish this line, and store value for writing at the end of line loop for i, inter := range ia { if i > 0 { supplines[f.Name] = inter } else { fmt.Printf(" %s", inter) } } //fmt.Printf(" %s", strings.Split(strings.Split(f.Value, "|")[1], "/")[0]) } else { fmt.Printf(" %s", f.Value) } // Complete with spaces to the max length for i := len(f.Value) + 1; i < f.MaxLen+1; i++ { fmt.Printf(" ") } fmt.Printf(" |") } // Draw supplementary lines if len(supplines) > 0 { for i, f := range l { if i == 0 { fmt.Printf("\n|") } // Overwrite value in this scope v, exists := supplines[f.Name] if exists { fmt.Printf(" %s", v) } else { // 1st option : Do not redisplay precedent line values fmt.Printf(" ") // 2nd option : Redisplay precedenet line values //fmt.Printf(" %s", f.Value) } // Complete with spaces to the max length for i := len(v) + 1; i < f.MaxLen+1; i++ { fmt.Printf(" ") } fmt.Printf(" |") } } // Draw line separator between jails if !gNoJailLineSep { fmt.Printf("\n") for i, f := range out[0] { if i == 0 { fmt.Printf("+") } for i := 0; i < f.MaxLen+2; i++ { fmt.Printf("-") } fmt.Printf("+") } } fmt.Printf("\n") } if gNoJailLineSep { for i, f := range out[0] { if i == 0 { fmt.Printf("+") } for i := 0; i < f.MaxLen+2; i++ { fmt.Printf("-") } fmt.Printf("+") } } fmt.Printf("\n") } /******************************************************************************** * Pretty display of snapshots field * Fields to show are given in a string array parameter * Ex. : displaySnapshotsFields(snapshots, ["Name", "Datastore", "Used"]) *******************************************************************************/ func displaySnapshotsFields(snaps []Snapshot, valsToDisplay []string) { /* A line is defined by : * Nr of fields * For each field : * Its name * Its max length * Its value */ type Field struct { Name string MaxLen int Value string } type Line []Field type Output []Line var out Output //fmt.Printf("%d fields in a %d items structure\n", len(valsToDisplay), len(jails)) // Browse structure to get max length and values for _, s := range snaps { // Have to use a pointer, else reflect.Value.Elem() will panic : https://pkg.go.dev/reflect#Value.Elem tj := &s line := make([]Field, len(valsToDisplay)) for i, f := range valsToDisplay { a, f := getStructField(tj, f) field := Field{ Name: f, } if a.FieldByName(f).IsValid() { // For now this just contains this item length, will adjust later // We need to divide length by number of items in string fields (because we can have more than 1 ip4_addr) if reflect.TypeOf(a.FieldByName(f).Interface()).Kind() == reflect.String { itnr := len(strings.Split(string(a.FieldByName(f).Interface().(string)), ",")) field.MaxLen = len(fmt.Sprintf("%v", a.FieldByName(f).Interface())) / itnr } else { // Special case of disk size : We will print human readable values if field.Name == "Used" || field.Name == "Referenced" || field.Name == "Available" { var v datasize.ByteSize v.UnmarshalText([]byte(fmt.Sprintf("%v", a.FieldByName(f).Interface()))) field.MaxLen = len(fmt.Sprintf("%v", v.HumanReadable())) } else { field.MaxLen = len(fmt.Sprintf("%v", a.FieldByName(f).Interface())) } } field.Value = fmt.Sprintf("%v", a.FieldByName(f).Interface()) } else { fmt.Printf("Invalid field name: %s\n", f) } line[i] = field } out = append(out, line) } if len(out) == 0 { fmt.Printf("Nothing to see here!\n") return } // Get real maximum length maxlen := make([]int, len(valsToDisplay)) for i := 0; i < len(valsToDisplay); i++ { maxlen[i] = len(valsToDisplay[i]) } for _, l := range out { for i, f := range l { if f.MaxLen > maxlen[i] { maxlen[i] = f.MaxLen } } } // Align maxlen to the real maximum length for io, l := range out { for ii, _ := range l { // We need to access slice by index if we want to modify content out[io][ii].MaxLen = maxlen[ii] } } totalLen := 0 // For each field, add separator and 2 blank spaces for _, f := range out[0] { totalLen += f.MaxLen + 3 } // Then add starting delimiter totalLen += 1 Debug := false if Debug == true { for _, f := range out[0] { fmt.Printf("%s max length : %d\n", f.Name, f.MaxLen) } } // Lets draw things on the screen! // First, headers: 1st separator line for i, f := range out[0] { if i == 0 { fmt.Printf("+") } for i := 0; i < f.MaxLen+2; i++ { fmt.Printf("=") } fmt.Printf("+") } fmt.Printf("\n") // Column names for i, f := range out[0] { if i == 0 { fmt.Printf("|") } /* Use vlsToDisplay to get real name (with "Config.") * fmt.Printf(" %s", f.Name) * for i := len(f.Name)+1 ; i < f.MaxLen+1 ; i++ { */ fmt.Printf(" %s", valsToDisplay[i]) for i := len(valsToDisplay[i]) + 1; i < f.MaxLen+1; i++ { fmt.Printf(" ") } fmt.Printf(" |") } // Finally separator line fmt.Printf("\n") for i, f := range out[0] { if i == 0 { fmt.Printf("+") } for i := 0; i < f.MaxLen+2; i++ { fmt.Printf("=") } fmt.Printf("+") } fmt.Printf("\n") // Then display data // Loop through lines for _, l := range out { // Loop through fields // In case we need to add a line for a 2nd IP, or whatever object var supplines = make(map[string]string) for i, f := range l { if i == 0 { fmt.Printf("|") } // Special cases of value displaying // Pretty print of sizes if f.Name == "Used" || f.Name == "Referenced" || f.Name == "Available" { var v datasize.ByteSize err := v.UnmarshalText([]byte(f.Value)) if err != nil { return } fmt.Printf(" %s", v.HumanReadable()) // Complete with spaces to the max length for i := len(v.HumanReadable()) + 1; i < f.MaxLen+1; i++ { fmt.Printf(" ") } } else { fmt.Printf(" %s", f.Value) // Complete with spaces to the max length for i := len(f.Value) + 1; i < f.MaxLen+1; i++ { fmt.Printf(" ") } } fmt.Printf(" |") } // Draw supplementary lines if len(supplines) > 0 { for i, f := range l { if i == 0 { fmt.Printf("\n|") } // Overwrite value in this scope v, exists := supplines[f.Name] if exists { fmt.Printf(" %s", v) } else { // 1st option : Do not redisplay precedent line values fmt.Printf(" ") // 2nd option : Redisplay precedenet line values //fmt.Printf(" %s", f.Value) } // Complete with spaces to the max length for i := len(v) + 1; i < f.MaxLen+1; i++ { fmt.Printf(" ") } fmt.Printf(" |") } } // Draw line separator between jails if !gNoSnapLineSep { fmt.Printf("\n") for i, f := range out[0] { if i == 0 { fmt.Printf("+") } for i := 0; i < f.MaxLen+2; i++ { fmt.Printf("-") } fmt.Printf("+") } } fmt.Printf("\n") } if gNoSnapLineSep { for i, f := range out[0] { if i == 0 { fmt.Printf("+") } for i := 0; i < f.MaxLen+2; i++ { fmt.Printf("-") } fmt.Printf("+") } } fmt.Printf("\n") } /******************************************************************************** * Pretty display of datastores field * Fields to show are given in a string array parameter * Ex. : displaySnapshotsFields(datastores, ["Name", "ZFSDataset", "Available"]) *******************************************************************************/ func displayDatastoresFields(datastores []Datastore, valsToDisplay []string) { /* A line is defined by : * Nr of fields * For each field : * Its name * Its max length * Its value */ type Field struct { Name string MaxLen int Value string } type Line []Field type Output []Line var out Output //fmt.Printf("%d fields in a %d items structure\n", len(valsToDisplay), len(jails)) // Browse structure to get max length and values for _, d := range datastores { // Have to use a pointer, else reflect.Value.Elem() will panic : https://pkg.go.dev/reflect#Value.Elem td := &d line := make([]Field, len(valsToDisplay)) for i, f := range valsToDisplay { a, f := getStructField(td, f) field := Field{ Name: f, } if a.FieldByName(f).IsValid() { // For now this just contains this item length, will adjust later // We need to divide length by number of items in string fields (because we can have more than 1 ip4_addr) if reflect.TypeOf(a.FieldByName(f).Interface()).Kind() == reflect.String { itnr := len(strings.Split(string(a.FieldByName(f).Interface().(string)), ",")) field.MaxLen = len(fmt.Sprintf("%v", a.FieldByName(f).Interface())) / itnr } else { // Special case of disk size : We will print human readable values if field.Name == "Used" || field.Name == "Referenced" || field.Name == "Available" { var v datasize.ByteSize v.UnmarshalText([]byte(fmt.Sprintf("%v", a.FieldByName(f).Interface()))) field.MaxLen = len(fmt.Sprintf("%v", v.HumanReadable())) } else { field.MaxLen = len(fmt.Sprintf("%v", a.FieldByName(f).Interface())) } } field.Value = fmt.Sprintf("%v", a.FieldByName(f).Interface()) } else { fmt.Printf("Invalid field name: %s\n", f) } line[i] = field } out = append(out, line) } if len(out) == 0 { fmt.Printf("Nothing to see here!\n") return } // Get real maximum length maxlen := make([]int, len(valsToDisplay)) for i := 0; i < len(valsToDisplay); i++ { maxlen[i] = len(valsToDisplay[i]) } for _, l := range out { for i, f := range l { if f.MaxLen > maxlen[i] { maxlen[i] = f.MaxLen } } } // Align maxlen to the real maximum length for io, l := range out { for ii, _ := range l { // We need to access slice by index if we want to modify content out[io][ii].MaxLen = maxlen[ii] } } totalLen := 0 // For each field, add separator and 2 blank spaces for _, f := range out[0] { totalLen += f.MaxLen + 3 } // Then add starting delimiter totalLen += 1 Debug := false if Debug == true { for _, f := range out[0] { fmt.Printf("%s max length : %d\n", f.Name, f.MaxLen) } } // Lets draw things on the screen! // First, headers: 1st separator line for i, f := range out[0] { if i == 0 { fmt.Printf("+") } for i := 0; i < f.MaxLen+2; i++ { fmt.Printf("=") } fmt.Printf("+") } fmt.Printf("\n") // Column names for i, f := range out[0] { if i == 0 { fmt.Printf("|") } /* Use vlsToDisplay to get real name (with "Config.") * fmt.Printf(" %s", f.Name) * for i := len(f.Name)+1 ; i < f.MaxLen+1 ; i++ { */ fmt.Printf(" %s", valsToDisplay[i]) for i := len(valsToDisplay[i]) + 1; i < f.MaxLen+1; i++ { fmt.Printf(" ") } fmt.Printf(" |") } // Finally separator line fmt.Printf("\n") for i, f := range out[0] { if i == 0 { fmt.Printf("+") } for i := 0; i < f.MaxLen+2; i++ { fmt.Printf("=") } fmt.Printf("+") } fmt.Printf("\n") // Then display data // Loop through lines for _, l := range out { // Loop through fields // In case we need to add a line for a 2nd IP, or whatever object var supplines = make(map[string]string) for i, f := range l { if i == 0 { fmt.Printf("|") } // Special cases of value displaying // Pretty print of sizes if f.Name == "Used" || f.Name == "Referenced" || f.Name == "Available" { var v datasize.ByteSize err := v.UnmarshalText([]byte(f.Value)) if err != nil { return } fmt.Printf(" %s", v.HumanReadable()) // Complete with spaces to the max length for i := len(v.HumanReadable()) + 1; i < f.MaxLen+1; i++ { fmt.Printf(" ") } } else { fmt.Printf(" %s", f.Value) // Complete with spaces to the max length for i := len(f.Value) + 1; i < f.MaxLen+1; i++ { fmt.Printf(" ") } } fmt.Printf(" |") } // Draw supplementary lines if len(supplines) > 0 { for i, f := range l { if i == 0 { fmt.Printf("\n|") } // Overwrite value in this scope v, exists := supplines[f.Name] if exists { fmt.Printf(" %s", v) } else { // 1st option : Do not redisplay precedent line values fmt.Printf(" ") // 2nd option : Redisplay precedenet line values //fmt.Printf(" %s", f.Value) } // Complete with spaces to the max length for i := len(v) + 1; i < f.MaxLen+1; i++ { fmt.Printf(" ") } fmt.Printf(" |") } } // Draw line separator between jails if !gNoDSLineSep { fmt.Printf("\n") for i, f := range out[0] { if i == 0 { fmt.Printf("+") } for i := 0; i < f.MaxLen+2; i++ { fmt.Printf("-") } fmt.Printf("+") } } fmt.Printf("\n") } if gNoDSLineSep { for i, f := range out[0] { if i == 0 { fmt.Printf("+") } for i := 0; i < f.MaxLen+2; i++ { fmt.Printf("-") } fmt.Printf("+") } } fmt.Printf("\n") } /***************************************************************************** * * Sorting jails * *****************************************************************************/ // This struct hold "sort by jail fields" functions type jailLessFunc func(j1 *Jail, j2 *Jail) bool func initJailSortStruct() JailSort { jcs := JailConfigSort{ Allow_chflagsInc: func(j1, j2 *Jail) bool { return j1.Config.Allow_chflags < j2.Config.Allow_chflags }, Allow_chflagsDec: func(j1, j2 *Jail) bool { return j1.Config.Allow_chflags > j2.Config.Allow_chflags }, Allow_mlockInc: func(j1, j2 *Jail) bool { return j1.Config.Allow_mlock < j2.Config.Allow_mlock }, Allow_mlockDec: func(j1, j2 *Jail) bool { return j1.Config.Allow_mlock > j2.Config.Allow_mlock }, Allow_mountInc: func(j1, j2 *Jail) bool { return j1.Config.Allow_mount < j2.Config.Allow_mount }, Allow_mountDec: func(j1, j2 *Jail) bool { return j1.Config.Allow_mount > j2.Config.Allow_mount }, Allow_mount_devfsInc: func(j1, j2 *Jail) bool { return j1.Config.Allow_mount_devfs < j2.Config.Allow_mount_devfs }, Allow_mount_devfsDec: func(j1, j2 *Jail) bool { return j1.Config.Allow_mount_devfs > j2.Config.Allow_mount_devfs }, Allow_mount_fusefsInc: func(j1, j2 *Jail) bool { return j1.Config.Allow_mount_fusefs < j2.Config.Allow_mount_fusefs }, Allow_mount_fusefsDec: func(j1, j2 *Jail) bool { return j1.Config.Allow_mount_fusefs > j2.Config.Allow_mount_fusefs }, Allow_mount_nullfsInc: func(j1, j2 *Jail) bool { return j1.Config.Allow_mount_nullfs < j2.Config.Allow_mount_nullfs }, Allow_mount_nullfsDec: func(j1, j2 *Jail) bool { return j1.Config.Allow_mount_nullfs > j2.Config.Allow_mount_nullfs }, Allow_mount_procfsInc: func(j1, j2 *Jail) bool { return j1.Config.Allow_mount_procfs < j2.Config.Allow_mount_procfs }, Allow_mount_procfsDec: func(j1, j2 *Jail) bool { return j1.Config.Allow_mount_procfs > j2.Config.Allow_mount_procfs }, Allow_mount_tmpfsInc: func(j1, j2 *Jail) bool { return j1.Config.Allow_mount_tmpfs < j2.Config.Allow_mount_tmpfs }, Allow_mount_tmpfsDec: func(j1, j2 *Jail) bool { return j1.Config.Allow_mount_tmpfs > j2.Config.Allow_mount_tmpfs }, Allow_mount_zfsInc: func(j1, j2 *Jail) bool { return j1.Config.Allow_mount_zfs < j2.Config.Allow_mount_zfs }, Allow_mount_zfsDec: func(j1, j2 *Jail) bool { return j1.Config.Allow_mount_zfs > j2.Config.Allow_mount_zfs }, Allow_quotasInc: func(j1, j2 *Jail) bool { return j1.Config.Allow_quotas < j2.Config.Allow_quotas }, Allow_quotasDec: func(j1, j2 *Jail) bool { return j1.Config.Allow_quotas > j2.Config.Allow_quotas }, Allow_raw_socketsInc: func(j1, j2 *Jail) bool { return j1.Config.Allow_raw_sockets < j2.Config.Allow_raw_sockets }, Allow_raw_socketsDec: func(j1, j2 *Jail) bool { return j1.Config.Allow_raw_sockets > j2.Config.Allow_raw_sockets }, Allow_set_hostnameInc: func(j1, j2 *Jail) bool { return j1.Config.Allow_set_hostname < j2.Config.Allow_set_hostname }, Allow_set_hostnameDec: func(j1, j2 *Jail) bool { return j1.Config.Allow_set_hostname > j2.Config.Allow_set_hostname }, Allow_socket_afInc: func(j1, j2 *Jail) bool { return j1.Config.Allow_socket_af < j2.Config.Allow_socket_af }, Allow_socket_afDec: func(j1, j2 *Jail) bool { return j1.Config.Allow_socket_af > j2.Config.Allow_socket_af }, Allow_sysvipcInc: func(j1, j2 *Jail) bool { return j1.Config.Allow_sysvipc < j2.Config.Allow_sysvipc }, Allow_sysvipcDec: func(j1, j2 *Jail) bool { return j1.Config.Allow_sysvipc > j2.Config.Allow_sysvipc }, Allow_tunInc: func(j1, j2 *Jail) bool { return j1.Config.Allow_tun < j2.Config.Allow_tun }, Allow_tunDec: func(j1, j2 *Jail) bool { return j1.Config.Allow_tun > j2.Config.Allow_tun }, Allow_vmmInc: func(j1, j2 *Jail) bool { return j1.Config.Allow_vmm < j2.Config.Allow_vmm }, Allow_vmmDec: func(j1, j2 *Jail) bool { return j1.Config.Allow_vmm > j2.Config.Allow_vmm }, Assign_localhostInc: func(j1, j2 *Jail) bool { return j1.Config.Assign_localhost < j2.Config.Assign_localhost }, Assign_localhostDec: func(j1, j2 *Jail) bool { return j1.Config.Assign_localhost > j2.Config.Assign_localhost }, AvailableInc: func(j1, j2 *Jail) bool { return j1.Config.Available < j2.Config.Available }, AvailableDec: func(j1, j2 *Jail) bool { return j1.Config.Available > j2.Config.Available }, BasejailInc: func(j1, j2 *Jail) bool { return j1.Config.Basejail < j2.Config.Basejail }, BasejailDec: func(j1, j2 *Jail) bool { return j1.Config.Basejail > j2.Config.Basejail }, BootInc: func(j1, j2 *Jail) bool { return j1.Config.Boot < j2.Config.Boot }, BootDec: func(j1, j2 *Jail) bool { return j1.Config.Boot > j2.Config.Boot }, BpfInc: func(j1, j2 *Jail) bool { return j1.Config.Bpf < j2.Config.Bpf }, BpfDec: func(j1, j2 *Jail) bool { return j1.Config.Bpf > j2.Config.Bpf }, Children_maxInc: func(j1, j2 *Jail) bool { return j1.Config.Children_max < j2.Config.Children_max }, Children_maxDec: func(j1, j2 *Jail) bool { return j1.Config.Children_max > j2.Config.Children_max }, Cloned_releaseInc: func(j1, j2 *Jail) bool { return j1.Config.Cloned_release < j2.Config.Cloned_release }, Cloned_releaseDec: func(j1, j2 *Jail) bool { return j1.Config.Cloned_release > j2.Config.Cloned_release }, CommentInc: func(j1, j2 *Jail) bool { return j1.Config.Comment < j2.Config.Comment }, CommentDec: func(j1, j2 *Jail) bool { return j1.Config.Comment > j2.Config.Comment }, CompressionInc: func(j1, j2 *Jail) bool { return j1.Config.Compression < j2.Config.Compression }, CompressionDec: func(j1, j2 *Jail) bool { return j1.Config.Compression > j2.Config.Compression }, CompressratioInc: func(j1, j2 *Jail) bool { return j1.Config.Compressratio < j2.Config.Compressratio }, CompressratioDec: func(j1, j2 *Jail) bool { return j1.Config.Compressratio > j2.Config.Compressratio }, Config_versionInc: func(j1, j2 *Jail) bool { return j1.Config.Config_version < j2.Config.Config_version }, Config_versionDec: func(j1, j2 *Jail) bool { return j1.Config.Config_version > j2.Config.Config_version }, CoredumpsizeInc: func(j1, j2 *Jail) bool { return j1.Config.Coredumpsize < j2.Config.Coredumpsize }, CoredumpsizeDec: func(j1, j2 *Jail) bool { return j1.Config.Coredumpsize > j2.Config.Coredumpsize }, CountInc: func(j1, j2 *Jail) bool { return j1.Config.Count < j2.Config.Count }, CountDec: func(j1, j2 *Jail) bool { return j1.Config.Count > j2.Config.Count }, CpusetInc: func(j1, j2 *Jail) bool { return j1.Config.Cpuset < j2.Config.Cpuset }, CpusetDec: func(j1, j2 *Jail) bool { return j1.Config.Cpuset > j2.Config.Cpuset }, CputimeInc: func(j1, j2 *Jail) bool { return j1.Config.Cputime < j2.Config.Cputime }, CputimeDec: func(j1, j2 *Jail) bool { return j1.Config.Cputime > j2.Config.Cputime }, DatasizeInc: func(j1, j2 *Jail) bool { return j1.Config.Datasize < j2.Config.Datasize }, DatasizeDec: func(j1, j2 *Jail) bool { return j1.Config.Datasize > j2.Config.Datasize }, DedupInc: func(j1, j2 *Jail) bool { return j1.Config.Dedup < j2.Config.Dedup }, DedupDec: func(j1, j2 *Jail) bool { return j1.Config.Dedup > j2.Config.Dedup }, DefaultrouterInc: func(j1, j2 *Jail) bool { return j1.Config.Defaultrouter < j2.Config.Defaultrouter }, DefaultrouterDec: func(j1, j2 *Jail) bool { return j1.Config.Defaultrouter > j2.Config.Defaultrouter }, Defaultrouter6Inc: func(j1, j2 *Jail) bool { return j1.Config.Defaultrouter6 < j2.Config.Defaultrouter6 }, Defaultrouter6Dec: func(j1, j2 *Jail) bool { return j1.Config.Defaultrouter6 > j2.Config.Defaultrouter6 }, DependsInc: func(j1, j2 *Jail) bool { return j1.Config.Depends < j2.Config.Depends }, DependsDec: func(j1, j2 *Jail) bool { return j1.Config.Depends > j2.Config.Depends }, Devfs_rulesetInc: func(j1, j2 *Jail) bool { return j1.Config.Devfs_ruleset < j2.Config.Devfs_ruleset }, Devfs_rulesetDec: func(j1, j2 *Jail) bool { return j1.Config.Devfs_ruleset > j2.Config.Devfs_ruleset }, DhcpInc: func(j1, j2 *Jail) bool { return j1.Config.Dhcp < j2.Config.Dhcp }, DhcpDec: func(j1, j2 *Jail) bool { return j1.Config.Dhcp > j2.Config.Dhcp }, Enforce_statfsInc: func(j1, j2 *Jail) bool { return j1.Config.Enforce_statfs < j2.Config.Enforce_statfs }, Enforce_statfsDec: func(j1, j2 *Jail) bool { return j1.Config.Enforce_statfs > j2.Config.Enforce_statfs }, Exec_cleanInc: func(j1, j2 *Jail) bool { return j1.Config.Exec_clean < j2.Config.Exec_clean }, Exec_cleanDec: func(j1, j2 *Jail) bool { return j1.Config.Exec_clean > j2.Config.Exec_clean }, Exec_createdInc: func(j1, j2 *Jail) bool { return j1.Config.Exec_created < j2.Config.Exec_created }, Exec_createdDec: func(j1, j2 *Jail) bool { return j1.Config.Exec_created > j2.Config.Exec_created }, Exec_fibInc: func(j1, j2 *Jail) bool { return j1.Config.Exec_fib < j2.Config.Exec_fib }, Exec_fibDec: func(j1, j2 *Jail) bool { return j1.Config.Exec_fib > j2.Config.Exec_fib }, Exec_jail_userInc: func(j1, j2 *Jail) bool { return j1.Config.Exec_jail_user < j2.Config.Exec_jail_user }, Exec_jail_userDec: func(j1, j2 *Jail) bool { return j1.Config.Exec_jail_user > j2.Config.Exec_jail_user }, Exec_poststartInc: func(j1, j2 *Jail) bool { return j1.Config.Exec_poststart < j2.Config.Exec_poststart }, Exec_poststartDec: func(j1, j2 *Jail) bool { return j1.Config.Exec_poststart > j2.Config.Exec_poststart }, Exec_poststopInc: func(j1, j2 *Jail) bool { return j1.Config.Exec_poststop < j2.Config.Exec_poststop }, Exec_poststopDec: func(j1, j2 *Jail) bool { return j1.Config.Exec_poststop > j2.Config.Exec_poststop }, Exec_prestartInc: func(j1, j2 *Jail) bool { return j1.Config.Exec_prestart < j2.Config.Exec_prestart }, Exec_prestartDec: func(j1, j2 *Jail) bool { return j1.Config.Exec_prestart > j2.Config.Exec_prestart }, Exec_prestopInc: func(j1, j2 *Jail) bool { return j1.Config.Exec_prestop < j2.Config.Exec_prestop }, Exec_prestopDec: func(j1, j2 *Jail) bool { return j1.Config.Exec_prestop > j2.Config.Exec_prestop }, Exec_startInc: func(j1, j2 *Jail) bool { return j1.Config.Exec_start < j2.Config.Exec_start }, Exec_startDec: func(j1, j2 *Jail) bool { return j1.Config.Exec_start > j2.Config.Exec_start }, Exec_stopInc: func(j1, j2 *Jail) bool { return j1.Config.Exec_stop < j2.Config.Exec_stop }, Exec_stopDec: func(j1, j2 *Jail) bool { return j1.Config.Exec_stop > j2.Config.Exec_stop }, Exec_system_jail_userInc: func(j1, j2 *Jail) bool { return j1.Config.Exec_system_jail_user < j2.Config.Exec_system_jail_user }, Exec_system_jail_userDec: func(j1, j2 *Jail) bool { return j1.Config.Exec_system_jail_user > j2.Config.Exec_system_jail_user }, Exec_system_userInc: func(j1, j2 *Jail) bool { return j1.Config.Exec_system_user < j2.Config.Exec_system_user }, Exec_system_userDec: func(j1, j2 *Jail) bool { return j1.Config.Exec_system_user > j2.Config.Exec_system_user }, Exec_timeoutInc: func(j1, j2 *Jail) bool { return j1.Config.Exec_timeout < j2.Config.Exec_timeout }, Exec_timeoutDec: func(j1, j2 *Jail) bool { return j1.Config.Exec_timeout > j2.Config.Exec_timeout }, Host_domainnameInc: func(j1, j2 *Jail) bool { return j1.Config.Host_domainname < j2.Config.Host_domainname }, Host_domainnameDec: func(j1, j2 *Jail) bool { return j1.Config.Host_domainname > j2.Config.Host_domainname }, Host_hostnameInc: func(j1, j2 *Jail) bool { return j1.Config.Host_hostname < j2.Config.Host_hostname }, Host_hostnameDec: func(j1, j2 *Jail) bool { return j1.Config.Host_hostname > j2.Config.Host_hostname }, Host_hostuuidInc: func(j1, j2 *Jail) bool { return j1.Config.Host_hostuuid < j2.Config.Host_hostuuid }, Host_hostuuidDec: func(j1, j2 *Jail) bool { return j1.Config.Host_hostuuid > j2.Config.Host_hostuuid }, Host_timeInc: func(j1, j2 *Jail) bool { return j1.Config.Host_time < j2.Config.Host_time }, Host_timeDec: func(j1, j2 *Jail) bool { return j1.Config.Host_time > j2.Config.Host_time }, HostidInc: func(j1, j2 *Jail) bool { return j1.Config.Hostid < j2.Config.Hostid }, HostidDec: func(j1, j2 *Jail) bool { return j1.Config.Hostid > j2.Config.Hostid }, Hostid_strict_checkInc: func(j1, j2 *Jail) bool { return j1.Config.Hostid_strict_check < j2.Config.Hostid_strict_check }, Hostid_strict_checkDec: func(j1, j2 *Jail) bool { return j1.Config.Hostid_strict_check > j2.Config.Hostid_strict_check }, InterfacesInc: func(j1, j2 *Jail) bool { return j1.Config.Interfaces < j2.Config.Interfaces }, InterfacesDec: func(j1, j2 *Jail) bool { return j1.Config.Interfaces > j2.Config.Interfaces }, Ip_hostnameInc: func(j1, j2 *Jail) bool { return j1.Config.Ip_hostname < j2.Config.Ip_hostname }, Ip_hostnameDec: func(j1, j2 *Jail) bool { return j1.Config.Ip_hostname > j2.Config.Ip_hostname }, Ip4Inc: func(j1, j2 *Jail) bool { return j1.Config.Ip4 < j2.Config.Ip4 }, Ip4Dec: func(j1, j2 *Jail) bool { return j1.Config.Ip4 > j2.Config.Ip4 }, Ip4_addrInc: func(j1, j2 *Jail) bool { return j1.Config.Ip4_addr < j2.Config.Ip4_addr }, Ip4_addrDec: func(j1, j2 *Jail) bool { return j1.Config.Ip4_addr > j2.Config.Ip4_addr }, Ip4_saddrselInc: func(j1, j2 *Jail) bool { return j1.Config.Ip4_saddrsel < j2.Config.Ip4_saddrsel }, Ip4_saddrselDec: func(j1, j2 *Jail) bool { return j1.Config.Ip4_saddrsel > j2.Config.Ip4_saddrsel }, Ip6Inc: func(j1, j2 *Jail) bool { return j1.Config.Ip6 < j2.Config.Ip6 }, Ip6Dec: func(j1, j2 *Jail) bool { return j1.Config.Ip6 > j2.Config.Ip6 }, Ip6_addrInc: func(j1, j2 *Jail) bool { return j1.Config.Ip6_addr < j2.Config.Ip6_addr }, Ip6_addrDec: func(j1, j2 *Jail) bool { return j1.Config.Ip6_addr > j2.Config.Ip6_addr }, Ip6_saddrselInc: func(j1, j2 *Jail) bool { return j1.Config.Ip6_saddrsel < j2.Config.Ip6_saddrsel }, Ip6_saddrselDec: func(j1, j2 *Jail) bool { return j1.Config.Ip6_saddrsel > j2.Config.Ip6_saddrsel }, Jail_zfsInc: func(j1, j2 *Jail) bool { return j1.Config.Jail_zfs < j2.Config.Jail_zfs }, Jail_zfsDec: func(j1, j2 *Jail) bool { return j1.Config.Jail_zfs > j2.Config.Jail_zfs }, Jail_zfs_datasetInc: func(j1, j2 *Jail) bool { return j1.Config.Jail_zfs_dataset < j2.Config.Jail_zfs_dataset }, Jail_zfs_datasetDec: func(j1, j2 *Jail) bool { return j1.Config.Jail_zfs_dataset > j2.Config.Jail_zfs_dataset }, Jail_zfs_mountpointInc: func(j1, j2 *Jail) bool { return j1.Config.Jail_zfs_mountpoint < j2.Config.Jail_zfs_mountpoint }, Jail_zfs_mountpointDec: func(j1, j2 *Jail) bool { return j1.Config.Jail_zfs_mountpoint > j2.Config.Jail_zfs_mountpoint }, JailtypeInc: func(j1, j2 *Jail) bool { return j1.Config.Jailtype < j2.Config.Jailtype }, JailtypeDec: func(j1, j2 *Jail) bool { return j1.Config.Jailtype > j2.Config.Jailtype }, Last_startedInc: func(j1, j2 *Jail) bool { return j1.Config.Last_started < j2.Config.Last_started }, Last_startedDec: func(j1, j2 *Jail) bool { return j1.Config.Last_started > j2.Config.Last_started }, Localhost_ipInc: func(j1, j2 *Jail) bool { return j1.Config.Localhost_ip < j2.Config.Localhost_ip }, Localhost_ipDec: func(j1, j2 *Jail) bool { return j1.Config.Localhost_ip > j2.Config.Localhost_ip }, Login_flagsInc: func(j1, j2 *Jail) bool { return j1.Config.Login_flags < j2.Config.Login_flags }, Login_flagsDec: func(j1, j2 *Jail) bool { return j1.Config.Login_flags > j2.Config.Login_flags }, Mac_prefixInc: func(j1, j2 *Jail) bool { return j1.Config.Mac_prefix < j2.Config.Mac_prefix }, Mac_prefixDec: func(j1, j2 *Jail) bool { return j1.Config.Mac_prefix > j2.Config.Mac_prefix }, MaxprocInc: func(j1, j2 *Jail) bool { return j1.Config.Maxproc < j2.Config.Maxproc }, MaxprocDec: func(j1, j2 *Jail) bool { return j1.Config.Maxproc > j2.Config.Maxproc }, MemorylockedInc: func(j1, j2 *Jail) bool { return j1.Config.Memorylocked < j2.Config.Memorylocked }, MemorylockedDec: func(j1, j2 *Jail) bool { return j1.Config.Memorylocked > j2.Config.Memorylocked }, MemoryuseInc: func(j1, j2 *Jail) bool { return j1.Config.Memoryuse < j2.Config.Memoryuse }, MemoryuseDec: func(j1, j2 *Jail) bool { return j1.Config.Memoryuse > j2.Config.Memoryuse }, Min_dyn_devfs_rulesetInc: func(j1, j2 *Jail) bool { return j1.Config.Min_dyn_devfs_ruleset < j2.Config.Min_dyn_devfs_ruleset }, Min_dyn_devfs_rulesetDec: func(j1, j2 *Jail) bool { return j1.Config.Min_dyn_devfs_ruleset > j2.Config.Min_dyn_devfs_ruleset }, Mount_devfsInc: func(j1, j2 *Jail) bool { return j1.Config.Mount_devfs < j2.Config.Mount_devfs }, Mount_devfsDec: func(j1, j2 *Jail) bool { return j1.Config.Mount_devfs > j2.Config.Mount_devfs }, Mount_fdescfsInc: func(j1, j2 *Jail) bool { return j1.Config.Mount_fdescfs < j2.Config.Mount_fdescfs }, Mount_fdescfsDec: func(j1, j2 *Jail) bool { return j1.Config.Mount_fdescfs > j2.Config.Mount_fdescfs }, Mount_linprocfsInc: func(j1, j2 *Jail) bool { return j1.Config.Mount_linprocfs < j2.Config.Mount_linprocfs }, Mount_linprocfsDec: func(j1, j2 *Jail) bool { return j1.Config.Mount_linprocfs > j2.Config.Mount_linprocfs }, Mount_procfsInc: func(j1, j2 *Jail) bool { return j1.Config.Mount_procfs < j2.Config.Mount_procfs }, Mount_procfsDec: func(j1, j2 *Jail) bool { return j1.Config.Mount_procfs > j2.Config.Mount_procfs }, MountpointInc: func(j1, j2 *Jail) bool { return j1.Config.Mountpoint < j2.Config.Mountpoint }, MountpointDec: func(j1, j2 *Jail) bool { return j1.Config.Mountpoint > j2.Config.Mountpoint }, MsgqqueuedInc: func(j1, j2 *Jail) bool { return j1.Config.Msgqqueued < j2.Config.Msgqqueued }, MsgqqueuedDec: func(j1, j2 *Jail) bool { return j1.Config.Msgqqueued > j2.Config.Msgqqueued }, MsgqsizeInc: func(j1, j2 *Jail) bool { return j1.Config.Msgqsize < j2.Config.Msgqsize }, MsgqsizeDec: func(j1, j2 *Jail) bool { return j1.Config.Msgqsize > j2.Config.Msgqsize }, NatInc: func(j1, j2 *Jail) bool { return j1.Config.Nat < j2.Config.Nat }, NatDec: func(j1, j2 *Jail) bool { return j1.Config.Nat > j2.Config.Nat }, Nat_backendInc: func(j1, j2 *Jail) bool { return j1.Config.Nat_backend < j2.Config.Nat_backend }, Nat_backendDec: func(j1, j2 *Jail) bool { return j1.Config.Nat_backend > j2.Config.Nat_backend }, Nat_forwardsInc: func(j1, j2 *Jail) bool { return j1.Config.Nat_forwards < j2.Config.Nat_forwards }, Nat_forwardsDec: func(j1, j2 *Jail) bool { return j1.Config.Nat_forwards > j2.Config.Nat_forwards }, Nat_interfaceInc: func(j1, j2 *Jail) bool { return j1.Config.Nat_interface < j2.Config.Nat_interface }, Nat_interfaceDec: func(j1, j2 *Jail) bool { return j1.Config.Nat_interface > j2.Config.Nat_interface }, Nat_prefixInc: func(j1, j2 *Jail) bool { return j1.Config.Nat_prefix < j2.Config.Nat_prefix }, Nat_prefixDec: func(j1, j2 *Jail) bool { return j1.Config.Nat_prefix > j2.Config.Nat_prefix }, NmsgqInc: func(j1, j2 *Jail) bool { return j1.Config.Nmsgq < j2.Config.Nmsgq }, NmsgqDec: func(j1, j2 *Jail) bool { return j1.Config.Nmsgq > j2.Config.Nmsgq }, NotesInc: func(j1, j2 *Jail) bool { return j1.Config.Notes < j2.Config.Notes }, NotesDec: func(j1, j2 *Jail) bool { return j1.Config.Notes > j2.Config.Notes }, NsemInc: func(j1, j2 *Jail) bool { return j1.Config.Nsem < j2.Config.Nsem }, NsemDec: func(j1, j2 *Jail) bool { return j1.Config.Nsem > j2.Config.Nsem }, NsemopInc: func(j1, j2 *Jail) bool { return j1.Config.Nsemop < j2.Config.Nsemop }, NsemopDec: func(j1, j2 *Jail) bool { return j1.Config.Nsemop > j2.Config.Nsemop }, NshmInc: func(j1, j2 *Jail) bool { return j1.Config.Nshm < j2.Config.Nshm }, NshmDec: func(j1, j2 *Jail) bool { return j1.Config.Nshm > j2.Config.Nshm }, NthrInc: func(j1, j2 *Jail) bool { return j1.Config.Nthr < j2.Config.Nthr }, NthrDec: func(j1, j2 *Jail) bool { return j1.Config.Nthr > j2.Config.Nthr }, OpenfilesInc: func(j1, j2 *Jail) bool { return j1.Config.Openfiles < j2.Config.Openfiles }, OpenfilesDec: func(j1, j2 *Jail) bool { return j1.Config.Openfiles > j2.Config.Openfiles }, OriginInc: func(j1, j2 *Jail) bool { return j1.Config.Origin < j2.Config.Origin }, OriginDec: func(j1, j2 *Jail) bool { return j1.Config.Origin > j2.Config.Origin }, OwnerInc: func(j1, j2 *Jail) bool { return j1.Config.Owner < j2.Config.Owner }, OwnerDec: func(j1, j2 *Jail) bool { return j1.Config.Owner > j2.Config.Owner }, PcpuInc: func(j1, j2 *Jail) bool { return j1.Config.Pcpu < j2.Config.Pcpu }, PcpuDec: func(j1, j2 *Jail) bool { return j1.Config.Pcpu > j2.Config.Pcpu }, Plugin_nameInc: func(j1, j2 *Jail) bool { return j1.Config.Plugin_name < j2.Config.Plugin_name }, Plugin_nameDec: func(j1, j2 *Jail) bool { return j1.Config.Plugin_name > j2.Config.Plugin_name }, Plugin_repositoryInc: func(j1, j2 *Jail) bool { return j1.Config.Plugin_repository < j2.Config.Plugin_repository }, Plugin_repositoryDec: func(j1, j2 *Jail) bool { return j1.Config.Plugin_repository > j2.Config.Plugin_repository }, PriorityInc: func(j1, j2 *Jail) bool { return j1.Config.Priority < j2.Config.Priority }, PriorityDec: func(j1, j2 *Jail) bool { return j1.Config.Priority > j2.Config.Priority }, PseudoterminalsInc: func(j1, j2 *Jail) bool { return j1.Config.Pseudoterminals < j2.Config.Pseudoterminals }, PseudoterminalsDec: func(j1, j2 *Jail) bool { return j1.Config.Pseudoterminals > j2.Config.Pseudoterminals }, QuotaInc: func(j1, j2 *Jail) bool { return j1.Config.Quota < j2.Config.Quota }, QuotaDec: func(j1, j2 *Jail) bool { return j1.Config.Quota > j2.Config.Quota }, ReadbpsInc: func(j1, j2 *Jail) bool { return j1.Config.Readbps < j2.Config.Readbps }, ReadbpsDec: func(j1, j2 *Jail) bool { return j1.Config.Readbps > j2.Config.Readbps }, ReadiopsInc: func(j1, j2 *Jail) bool { return j1.Config.Readiops < j2.Config.Readiops }, ReadiopsDec: func(j1, j2 *Jail) bool { return j1.Config.Readiops > j2.Config.Readiops }, ReleaseInc: func(j1, j2 *Jail) bool { return j1.Config.Release < j2.Config.Release }, ReleaseDec: func(j1, j2 *Jail) bool { return j1.Config.Release > j2.Config.Release }, ReservationInc: func(j1, j2 *Jail) bool { return j1.Config.Reservation < j2.Config.Reservation }, ReservationDec: func(j1, j2 *Jail) bool { return j1.Config.Reservation > j2.Config.Reservation }, ResolverInc: func(j1, j2 *Jail) bool { return j1.Config.Resolver < j2.Config.Resolver }, ResolverDec: func(j1, j2 *Jail) bool { return j1.Config.Resolver > j2.Config.Resolver }, RlimitsInc: func(j1, j2 *Jail) bool { return j1.Config.Rlimits < j2.Config.Rlimits }, RlimitsDec: func(j1, j2 *Jail) bool { return j1.Config.Rlimits > j2.Config.Rlimits }, RtsoldInc: func(j1, j2 *Jail) bool { return j1.Config.Rtsold < j2.Config.Rtsold }, RtsoldDec: func(j1, j2 *Jail) bool { return j1.Config.Rtsold > j2.Config.Rtsold }, SecurelevelInc: func(j1, j2 *Jail) bool { return j1.Config.Securelevel < j2.Config.Securelevel }, SecurelevelDec: func(j1, j2 *Jail) bool { return j1.Config.Securelevel > j2.Config.Securelevel }, ShmsizeInc: func(j1, j2 *Jail) bool { return j1.Config.Shmsize < j2.Config.Shmsize }, ShmsizeDec: func(j1, j2 *Jail) bool { return j1.Config.Shmsize > j2.Config.Shmsize }, StacksizeInc: func(j1, j2 *Jail) bool { return j1.Config.Stacksize < j2.Config.Stacksize }, StacksizeDec: func(j1, j2 *Jail) bool { return j1.Config.Stacksize > j2.Config.Stacksize }, Stop_timeoutInc: func(j1, j2 *Jail) bool { return j1.Config.Stop_timeout < j2.Config.Stop_timeout }, Stop_timeoutDec: func(j1, j2 *Jail) bool { return j1.Config.Stop_timeout > j2.Config.Stop_timeout }, SwapuseInc: func(j1, j2 *Jail) bool { return j1.Config.Swapuse < j2.Config.Swapuse }, SwapuseDec: func(j1, j2 *Jail) bool { return j1.Config.Swapuse > j2.Config.Swapuse }, Sync_stateInc: func(j1, j2 *Jail) bool { return j1.Config.Sync_state < j2.Config.Sync_state }, Sync_stateDec: func(j1, j2 *Jail) bool { return j1.Config.Sync_state > j2.Config.Sync_state }, Sync_targetInc: func(j1, j2 *Jail) bool { return j1.Config.Sync_target < j2.Config.Sync_target }, Sync_targetDec: func(j1, j2 *Jail) bool { return j1.Config.Sync_target > j2.Config.Sync_target }, Sync_tgt_zpoolInc: func(j1, j2 *Jail) bool { return j1.Config.Sync_tgt_zpool < j2.Config.Sync_tgt_zpool }, Sync_tgt_zpoolDec: func(j1, j2 *Jail) bool { return j1.Config.Sync_tgt_zpool > j2.Config.Sync_tgt_zpool }, SysvmsgInc: func(j1, j2 *Jail) bool { return j1.Config.Sysvmsg < j2.Config.Sysvmsg }, SysvmsgDec: func(j1, j2 *Jail) bool { return j1.Config.Sysvmsg > j2.Config.Sysvmsg }, SysvsemInc: func(j1, j2 *Jail) bool { return j1.Config.Sysvsem < j2.Config.Sysvsem }, SysvsemDec: func(j1, j2 *Jail) bool { return j1.Config.Sysvsem > j2.Config.Sysvsem }, SysvshmInc: func(j1, j2 *Jail) bool { return j1.Config.Sysvshm < j2.Config.Sysvshm }, SysvshmDec: func(j1, j2 *Jail) bool { return j1.Config.Sysvshm > j2.Config.Sysvshm }, TemplateInc: func(j1, j2 *Jail) bool { return j1.Config.Template < j2.Config.Template }, TemplateDec: func(j1, j2 *Jail) bool { return j1.Config.Template > j2.Config.Template }, UsedInc: func(j1, j2 *Jail) bool { return j1.Config.Used < j2.Config.Used }, UsedDec: func(j1, j2 *Jail) bool { return j1.Config.Used > j2.Config.Used }, VmemoryuseInc: func(j1, j2 *Jail) bool { return j1.Config.Vmemoryuse < j2.Config.Vmemoryuse }, VmemoryuseDec: func(j1, j2 *Jail) bool { return j1.Config.Vmemoryuse > j2.Config.Vmemoryuse }, VnetInc: func(j1, j2 *Jail) bool { return j1.Config.Vnet < j2.Config.Vnet }, VnetDec: func(j1, j2 *Jail) bool { return j1.Config.Vnet > j2.Config.Vnet }, Vnet_default_interfaceInc: func(j1, j2 *Jail) bool { return j1.Config.Vnet_default_interface < j2.Config.Vnet_default_interface }, Vnet_default_interfaceDec: func(j1, j2 *Jail) bool { return j1.Config.Vnet_default_interface > j2.Config.Vnet_default_interface }, Vnet_interfacesInc: func(j1, j2 *Jail) bool { return j1.Config.Vnet_interfaces < j2.Config.Vnet_interfaces }, Vnet_interfacesDec: func(j1, j2 *Jail) bool { return j1.Config.Vnet_interfaces > j2.Config.Vnet_interfaces }, Vnet0_macInc: func(j1, j2 *Jail) bool { return j1.Config.Vnet0_mac < j2.Config.Vnet0_mac }, Vnet0_macDec: func(j1, j2 *Jail) bool { return j1.Config.Vnet0_mac > j2.Config.Vnet0_mac }, Vnet1_macInc: func(j1, j2 *Jail) bool { return j1.Config.Vnet1_mac < j2.Config.Vnet1_mac }, Vnet1_macDec: func(j1, j2 *Jail) bool { return j1.Config.Vnet1_mac > j2.Config.Vnet1_mac }, Vnet2_macInc: func(j1, j2 *Jail) bool { return j1.Config.Vnet2_mac < j2.Config.Vnet2_mac }, Vnet2_macDec: func(j1, j2 *Jail) bool { return j1.Config.Vnet2_mac > j2.Config.Vnet2_mac }, Vnet3_macInc: func(j1, j2 *Jail) bool { return j1.Config.Vnet3_mac < j2.Config.Vnet3_mac }, Vnet3_macDec: func(j1, j2 *Jail) bool { return j1.Config.Vnet3_mac > j2.Config.Vnet3_mac }, WallclockInc: func(j1, j2 *Jail) bool { return j1.Config.Wallclock < j2.Config.Wallclock }, WallclockDec: func(j1, j2 *Jail) bool { return j1.Config.Wallclock > j2.Config.Wallclock }, WritebpsInc: func(j1, j2 *Jail) bool { return j1.Config.Writebps < j2.Config.Writebps }, WritebpsDec: func(j1, j2 *Jail) bool { return j1.Config.Writebps > j2.Config.Writebps }, WriteiopsInc: func(j1, j2 *Jail) bool { return j1.Config.Writeiops < j2.Config.Writeiops }, WriteiopsDec: func(j1, j2 *Jail) bool { return j1.Config.Writeiops > j2.Config.Writeiops }, } js := JailSort{ ConfigPathInc: func(j1, j2 *Jail) bool { return j1.ConfigPath < j2.ConfigPath }, ConfigPathDec: func(j1, j2 *Jail) bool { return j1.ConfigPath > j2.ConfigPath }, DatastoreInc: func(j1, j2 *Jail) bool { return j1.Datastore < j2.Datastore }, DatastoreDec: func(j1, j2 *Jail) bool { return j1.Datastore > j2.Datastore }, InternalNameInc: func(j1, j2 *Jail) bool { return j1.InternalName < j2.InternalName }, InternalNameDec: func(j1, j2 *Jail) bool { return j1.InternalName > j2.InternalName }, JIDInc: func(j1, j2 *Jail) bool { return j1.JID < j2.JID }, JIDDec: func(j1, j2 *Jail) bool { return j1.JID > j2.JID }, NameInc: func(j1, j2 *Jail) bool { return j1.Name < j2.Name }, NameDec: func(j1, j2 *Jail) bool { return j1.Name > j2.Name }, RootPathInc: func(j1, j2 *Jail) bool { return j1.RootPath < j2.RootPath }, RootPathDec: func(j1, j2 *Jail) bool { return j1.RootPath > j2.RootPath }, RunningInc: func(j1, j2 *Jail) bool { return !j1.Running && j2.Running }, RunningDec: func(j1, j2 *Jail) bool { return j1.Running && !j2.Running }, ZpoolInc: func(j1, j2 *Jail) bool { return j1.Zpool < j2.Zpool }, ZpoolDec: func(j1, j2 *Jail) bool { return j1.Zpool > j2.Zpool }, Config: jcs, } return js } // jailSorter implements the Sort interface, sorting the jails within. type jailSorter struct { jails []Jail less []jailLessFunc } // Sort sorts the argument slice according to the less functions passed to JailsOrderedBy. func (js *jailSorter) Sort(jails []Jail) { js.jails = jails sort.Sort(js) } // JailsOrderedBy returns a Sorter that sorts using the less functions, in order. // Call its Sort method to sort the data. func JailsOrderedBy(less ...jailLessFunc) *jailSorter { return &jailSorter{ less: less, } } // Len is part of sort.Interface. func (js *jailSorter) Len() int { return len(js.jails) } // Swap is part of sort.Interface. func (js *jailSorter) Swap(i, j int) { js.jails[i], js.jails[j] = js.jails[j], js.jails[i] } // Less is part of sort.Interface. It is implemented by looping along the // less functions until it finds a comparison that discriminates between // the two items (one is less than the other). Note that it can call the // less functions twice per call. We could change the functions to return // -1, 0, 1 and reduce the number of calls for greater efficiency: an // exercise for the reader. func (js *jailSorter) Less(i, j int) bool { p, q := &js.jails[i], &js.jails[j] // Try all but the last comparison. var k int for k = 0; k < len(js.less)-1; k++ { less := js.less[k] switch { case less(p, q): // p < q, so we have a decision. return true case less(q, p): // p > q, so we have a decision. return false } // p == q; try the next comparison. } // All comparisons to here said "equal", so just return whatever // the final comparison reports. return js.less[k](p, q) } /***************************************************************************** * * Sorting snapshots * *****************************************************************************/ // This struct hold "sort by jail fields" functions type snapshotLessFunc func(s1 *Snapshot, s2 *Snapshot) bool func initSnapshotSortStruct() SnapshotSort { ss := SnapshotSort{ NameInc: func(s1, s2 *Snapshot) bool { return s1.Name < s2.Name }, NameDec: func(s1, s2 *Snapshot) bool { return s1.Name > s2.Name }, DatastoreInc: func(s1, s2 *Snapshot) bool { return s1.Datastore < s2.Datastore }, DatastoreDec: func(s1, s2 *Snapshot) bool { return s1.Datastore > s2.Datastore }, JailnameInc: func(s1, s2 *Snapshot) bool { return s1.Jailname < s2.Jailname }, JailnameDec: func(s1, s2 *Snapshot) bool { return s1.Jailname > s2.Jailname }, MountpointInc: func(s1, s2 *Snapshot) bool { return s1.Mountpoint < s2.Mountpoint }, MountpointDec: func(s1, s2 *Snapshot) bool { return s1.Mountpoint > s2.Mountpoint }, UsedInc: func(s1, s2 *Snapshot) bool { return s1.Used < s2.Used }, UsedDec: func(s1, s2 *Snapshot) bool { return s1.Used > s2.Used }, ReferencedInc: func(s1, s2 *Snapshot) bool { return s1.Referenced < s2.Referenced }, ReferencedDec: func(s1, s2 *Snapshot) bool { return s1.Referenced > s2.Referenced }, CreationInc: func(s1, s2 *Snapshot) bool { return s1.Creation.Unix() < s2.Creation.Unix() }, CreationDec: func(s1, s2 *Snapshot) bool { return s1.Creation.Unix() > s2.Creation.Unix() }, } return ss } // snapshotSorter implements the Sort interface, sorting the jails within. type snapshotSorter struct { snapshots []Snapshot less []snapshotLessFunc } // Sort sorts the argument slice according to the less functions passed to OrderedBy. func (ss *snapshotSorter) Sort(snapshots []Snapshot) { ss.snapshots = snapshots sort.Sort(ss) } // OrderedBy returns a Sorter that sorts using the less functions, in order. // Call its Sort method to sort the data. func SnapshotsOrderedBy(less ...snapshotLessFunc) *snapshotSorter { return &snapshotSorter{ less: less, } } // Len is part of sort.Interface. func (ss *snapshotSorter) Len() int { return len(ss.snapshots) } // Swap is part of sort.Interface. func (ss *snapshotSorter) Swap(i, j int) { ss.snapshots[i], ss.snapshots[j] = ss.snapshots[j], ss.snapshots[i] } // Less is part of sort.Interface. It is implemented by looping along the // less functions until it finds a comparison that discriminates between // the two items (one is less than the other). Note that it can call the // less functions twice per call. We could change the functions to return // -1, 0, 1 and reduce the number of calls for greater efficiency: an // exercise for the reader. func (ss *snapshotSorter) Less(i, j int) bool { p, q := &ss.snapshots[i], &ss.snapshots[j] // Try all but the last comparison. var k int for k = 0; k < len(ss.less)-1; k++ { less := ss.less[k] switch { case less(p, q): // p < q, so we have a decision. return true case less(q, p): // p > q, so we have a decision. return false } // p == q; try the next comparison. } // All comparisons to here said "equal", so just return whatever // the final comparison reports. return ss.less[k](p, q) } /***************************************************************************** * * Sorting datastores * *****************************************************************************/ // This struct hold "sort by datastores fields" functions type datastoreLessFunc func(s1 *Datastore, s2 *Datastore) bool func initDatastoreSortStruct() DatastoreSort { ss := DatastoreSort{ NameInc: func(s1, s2 *Datastore) bool { return s1.Name < s2.Name }, NameDec: func(s1, s2 *Datastore) bool { return s1.Name > s2.Name }, MountpointInc: func(s1, s2 *Datastore) bool { return s1.Mountpoint < s2.Mountpoint }, MountpointDec: func(s1, s2 *Datastore) bool { return s1.Mountpoint > s2.Mountpoint }, ZFSDatasetInc: func(s1, s2 *Datastore) bool { return s1.ZFSDataset < s2.ZFSDataset }, ZFSDatasetDec: func(s1, s2 *Datastore) bool { return s1.ZFSDataset > s2.ZFSDataset }, UsedInc: func(s1, s2 *Datastore) bool { return s1.Used < s2.Used }, UsedDec: func(s1, s2 *Datastore) bool { return s1.Used > s2.Used }, ReferencedInc: func(s1, s2 *Datastore) bool { return s1.Referenced < s2.Referenced }, ReferencedDec: func(s1, s2 *Datastore) bool { return s1.Referenced > s2.Referenced }, AvailableInc: func(s1, s2 *Datastore) bool { return s1.Available < s2.Available }, AvailableDec: func(s1, s2 *Datastore) bool { return s1.Available > s2.Available }, } return ss } // DatastoreSorter implements the Sort interface, sorting the jails within. type DatastoreSorter struct { Datastores []Datastore less []datastoreLessFunc } // Sort sorts the argument slice according to the less functions passed to OrderedBy. func (ss *DatastoreSorter) Sort(Datastores []Datastore) { ss.Datastores = Datastores sort.Sort(ss) } // OrderedBy returns a Sorter that sorts using the less functions, in order. // Call its Sort method to sort the data. func DatastoresOrderedBy(less ...datastoreLessFunc) *DatastoreSorter { return &DatastoreSorter{ less: less, } } // Len is part of sort.Interface. func (ss *DatastoreSorter) Len() int { return len(ss.Datastores) } // Swap is part of sort.Interface. func (ss *DatastoreSorter) Swap(i, j int) { ss.Datastores[i], ss.Datastores[j] = ss.Datastores[j], ss.Datastores[i] } // Less is part of sort.Interface. It is implemented by looping along the // less functions until it finds a comparison that discriminates between // the two items (one is less than the other). Note that it can call the // less functions twice per call. We could change the functions to return // -1, 0, 1 and reduce the number of calls for greater efficiency: an // exercise for the reader. func (ss *DatastoreSorter) Less(i, j int) bool { p, q := &ss.Datastores[i], &ss.Datastores[j] // Try all but the last comparison. var k int for k = 0; k < len(ss.less)-1; k++ { less := ss.less[k] switch { case less(p, q): // p < q, so we have a decision. return true case less(q, p): // p > q, so we have a decision. return false } // p == q; try the next comparison. } // All comparisons to here said "equal", so just return whatever // the final comparison reports. return ss.less[k](p, q) }