package cmd import ( "os" "fmt" "log" "sort" "bufio" "errors" "os/exec" "reflect" "strings" "strconv" ) /***************************************************************************** * * 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:]...).Output() } else { out, err = exec.Command(cmd[0]).Output() } 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:]...).Output() return string(out), err } /***************************************************************************** * 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 source reference, to update properties after a range loop *******************************************************************************/ func getJailFromArray(internalName string, jarray []Jail) (*Jail, error) { for _, j := range jarray { if internalName == j.InternalName { return &j, nil } } return &Jail{}, errors.New("Jail not found") } /******************************************************************************** * 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") *******************************************************************************/ 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 } /******************************************************************************** * Pretty display of jails field * Fields to show are given in a string array parameter * Ex. : displayJails(["Name", "JID", "RootPath"]) *******************************************************************************/ func displayStructFields(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 !gNoLineSep { 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 gNoLineSep { 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 * *****************************************************************************/ // already defined in cmd/struct.go //type lessFunc func(j1 *Jail, j2 *Jail) bool func initSortStruct() 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 }, 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 } // multiSorter implements the Sort interface, sorting the jails within. type multiSorter struct { jails []Jail less []lessFunc } // Sort sorts the argument slice according to the less functions passed to OrderedBy. func (ms *multiSorter) Sort(jails []Jail) { ms.jails = jails sort.Sort(ms) } // OrderedBy returns a Sorter that sorts using the less functions, in order. // Call its Sort method to sort the data. func OrderedBy(less ...lessFunc) *multiSorter { return &multiSorter{ less: less, } } // Len is part of sort.Interface. func (ms *multiSorter) Len() int { return len(ms.jails) } // Swap is part of sort.Interface. func (ms *multiSorter) Swap(i, j int) { ms.jails[i], ms.jails[j] = ms.jails[j], ms.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 (ms *multiSorter) Less(i, j int) bool { p, q := &ms.jails[i], &ms.jails[j] // Try all but the last comparison. var k int for k = 0; k < len(ms.less)-1; k++ { less := ms.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 ms.less[k](p, q) }