18 Commits

Author SHA1 Message Date
yo
4aa1c81fea Create and delete snapshot OK + version bump to 0.25 2022-04-04 21:00:44 +02:00
yo
966a3d57c1 Correctly display multiple jails snapshots 2022-04-04 20:10:42 +02:00
yo
a12c268be2 BUGFIX on snapshot name 2022-04-04 20:03:08 +02:00
yo
7b34495cf6 displaySnapshotsFields is a copy of displayJailsFields to pretty display jails snapshots 2022-04-04 19:47:15 +02:00
yo
1c40e29eff Cleaning + TODO: PRetty display of snapshot list 2022-04-03 14:30:14 +02:00
yo
ef78245902 Add gocage list snapshot myjail 2022-04-03 14:27:42 +02:00
yo
910be4ea31 Add gocage list snapshot myjail 2022-04-03 14:27:26 +02:00
yo
285229009f Add "gocage get all myjail" 2022-04-03 11:04:01 +02:00
yo
19dd2dfb33 version bump to v0.24 2022-04-03 10:36:23 +02:00
yo
139ea18422 add "gocage get property jail" + "gocage set property jail" now support string, int and bool types 2022-04-03 10:35:48 +02:00
yo
3bedf019dc Move property mgmt functions to properties.go 2022-04-03 10:35:01 +02:00
yo
da74456d6a Set property K for int type + write config to disk 2022-04-02 21:38:54 +02:00
yo
f40db69b9d WIP: Implementing setJailProperty, add recursivity to getStructFieldValue 2022-04-02 21:15:06 +02:00
yo
239bcd4b95 WIP: set jail properties 2022-04-02 17:12:51 +02:00
yo
5a3d26a52c Formatting + add "set" command 2022-04-02 17:11:54 +02:00
yo
bf20c815ce Moved some code to utils.go 2022-04-02 15:50:14 +02:00
yo
f1c4fd960d Add command "list properties" so we can get all internal properties to sort, filter, or set jail properties 2022-04-02 15:40:04 +02:00
yo
8d18bfe55d Bugfix 2022-04-02 15:38:24 +02:00
7 changed files with 1564 additions and 718 deletions

View File

@ -4,7 +4,6 @@ import (
"os"
"fmt"
"log"
"errors"
"reflect"
"strings"
"io/ioutil"
@ -13,291 +12,28 @@ import (
"github.com/spf13/viper"
)
// 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)
/* 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])
f := v.Elem().FieldByName(fs[0])
if f.Kind() == reflect.Struct {
return getStructFieldValue(f.Interface(), strings.Join(fs[1:], "."))
} else {
log.Fatalln(fmt.Sprintf("%s is not a struct: %s\n", fs[0], f.Kind().String()))
}
} else {
f := v.FieldByName(fieldName)
if f.IsValid() {
return &f, fieldName, nil
} else {
return &v, fieldName, errors.New(fmt.Sprintf("Field not found: %s", fieldName))
}
}
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("+")
}
}
/********************************************************************************
* List all properties a jail have, with their internal name
* Only print properties name. To get name & values, use GetJailProperties()
*******************************************************************************/
func ListJailsProps(args []string) {
var conf Jail
var jailconf JailConfig
var result []string
fmt.Printf("\n")
conf.Config = jailconf
result = getStructFieldNames(conf, result, "")
for _, f := range result {
fmt.Printf("%s\n", f)
}
}
/* Get Jails from datastores. Store config and running metadata into gJails global var */
/********************************************************************************
* Get Jails from datastores. Store config and running metadata
* into gJails global var
*******************************************************************************/
func ListJails(args []string, display bool) {
fields := strings.Split(gDisplayColumns, ",")
@ -308,9 +44,9 @@ func ListJails(args []string, display bool) {
// This is the structure we will filter, then display
var jails []Jail
/************************************************************************************
/***************************************************************
/ Filter jails with "filter" options
/***********************************************************************************/
/**************************************************************/
if len(gFilterJails) > 0 && gFilterJails != "none" {
flts := make(map[string]string)
for _, flt := range strings.Split(gFilterJails, ",") {
@ -342,9 +78,9 @@ func ListJails(args []string, display bool) {
}
/************************************************************************************
/***************************************************************
/ Filter jails by names given on command line
/***********************************************************************************/
/**************************************************************/
if len(args) > 0 {
var js []Jail
for _, a := range args {
@ -358,12 +94,12 @@ func ListJails(args []string, display bool) {
jails = js
}
/************************************************************************************
/***************************************************************
/ Sort jails
/ We support 3 sort criteria max
/***********************************************************************************/
/**************************************************************/
if len(gSortFields) > 0 && gSortFields != "none" {
js := initSortStruct()
js := initJailSortStruct()
// The way we manage criteria quantity is not very elegant...
var fct1, fct2, fct3 *reflect.Value
@ -391,19 +127,19 @@ func ListJails(args []string, display bool) {
switch len(strings.Split(gSortFields, ",")) {
case 1:
OrderedBy(fct1.Interface().(lessFunc)).Sort(jails)
JailsOrderedBy(fct1.Interface().(jailLessFunc)).Sort(jails)
case 2:
OrderedBy(fct1.Interface().(lessFunc), fct2.Interface().(lessFunc)).Sort(jails)
JailsOrderedBy(fct1.Interface().(jailLessFunc), fct2.Interface().(jailLessFunc)).Sort(jails)
case 3:
OrderedBy(fct1.Interface().(lessFunc), fct2.Interface().(lessFunc), fct3.Interface().(lessFunc)).Sort(jails)
JailsOrderedBy(fct1.Interface().(jailLessFunc), fct2.Interface().(jailLessFunc), fct3.Interface().(jailLessFunc)).Sort(jails)
}
}
/************************************************************************************
/***************************************************************
/ Finally, display jails
/***********************************************************************************/
/**************************************************************/
if display {
displayStructFields(jails, fields)
displayJailsFields(jails, fields)
}
}

156
cmd/properties.go Normal file
View File

@ -0,0 +1,156 @@
package cmd
import (
"fmt"
"errors"
"reflect"
"strings"
"strconv"
)
func GetJailProperties(args []string) {
var props []string
var jail Jail
if len(args) > 0 {
for i, a := range args {
// Last arg is the jail name
if i == len(args)-1 {
jail.Name = a
} else {
props = append(props, a)
}
}
}
if len(jail.Name) == 0 || len(args) == 0 {
// TODO : Show help
fmt.Printf("Error\n")
return
}
if isStringInArray(props, "all") {
var result []string
result = getStructFieldNames(jail, result, "")
props = result
}
for _, p := range props {
v, err := getJailProperty(&jail, p)
if err != nil {
fmt.Printf("Error: %s\n", err.Error())
return
} else {
fmt.Printf("%s = %s\n", p, v)
}
}
}
func getJailProperty(jail *Jail, propName string) (string, error) {
for i, j := range gJails {
if j.Name == jail.Name {
val, _, err := getStructFieldValue(&gJails[i], propName)
if err != nil {
return "", err
}
switch val.Kind() {
case reflect.String:
return val.String(), nil
case reflect.Int:
return strconv.FormatInt(val.Int(), 10), nil
case reflect.Bool:
return strconv.FormatBool(val.Bool()), nil
default:
return "", errors.New(fmt.Sprintf("Error: Unknown type for property %s: %s\n", propName, val.Kind()))
}
}
}
return "", errors.New(fmt.Sprintf("Jail not found: %s", jail.Name))
}
func SetJailProperties(args []string) {
type properties struct {
name string
value string
}
var jail Jail
var props []properties
if len(args) > 0 {
for i, a := range args {
// This is the jail name
if i == len(args)-1 {
jail.Name = a
} else {
kv := strings.Split(a, "=")
if len(kv) != 2 {
// TODO : Show help
fmt.Printf("Error parsing args: %s\n", a)
return
} else {
p := properties{name: kv[0], value: kv[1]}
props = append(props, p)
}
}
}
}
if len(jail.Name) == 0 || len(args) == 0 {
// TODO : Show help
fmt.Printf("Error\n")
return
}
for _, p := range props {
err := setJailProperty(&jail, p.name, p.value)
if err != nil {
fmt.Printf("Error: %s\n", err.Error())
return
}
}
}
// setJailProperty takes a string as propValue, whatever the real property type is.
// It will be converted.
func setJailProperty(jail *Jail, propName string, propValue string) error {
for i, j := range gJails {
if j.Name == jail.Name {
val, _, err := getStructFieldValue(&gJails[i], propName)
//val, _, err := getStructFieldValue(&gJails[i].Config, strings.Split(propName, ".")[1])
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()))
}
fmt.Printf("%s: %s set to %s\n", jail.Name, propName, propValue)
gJails[i].ConfigUpdated = true
} else {
return errors.New(fmt.Sprintf("Field is not writable : %s", propName))
}
}
}
return nil
}

View File

@ -5,6 +5,7 @@ import (
"fmt"
"strconv"
"strings"
"io/ioutil"
"encoding/json"
"github.com/spf13/cobra"
@ -12,7 +13,7 @@ import (
)
const (
gVersion = "0.022a"
gVersion = "0.25"
)
var (
@ -27,31 +28,32 @@ var (
gNoLineSep bool
gHostVersion float64
gTimeZone string
gSnapshotName string
rootCmd = & cobra.Command{
Use: "gocage",
Short: "GoCage is a FreeBSD Jail management tool",
Long: `GoCage is a jail management tool. It support VNET, host-only, NAT networks. Provides snapshots and cloning.
rootCmd = & cobra.Command {
Use: "gocage",
Short: "GoCage is a FreeBSD Jail management tool",
Long: `GoCage is a jail management tool. It support VNET, host-only, NAT networks. Provides snapshots and cloning.
It support iocage jails and can coexist with iocage.`,
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("Here we are in the Run")
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("Here we are in the Run")
cleanAfterRun()
},
}
versionCmd = &cobra.Command {
Use: "version",
Short: "Print the version number of GoCage",
Long: `Let this show you how much fail I had to get this *cough* perfect`,
Run: func(cmd *cobra.Command, args []string) {
fmt.Printf("GoCage v.%s on FreeBSD %.1f\n", gVersion, gHostVersion)
cleanAfterRun()
},
}
versionCmd = &cobra.Command{
Use: "version",
Short: "Print the version number of GoCage",
Long: `Let this show you how much fail I had to get this *cough* perfect`,
Run: func(cmd *cobra.Command, args []string) {
fmt.Printf("GoCage v.%s on FreeBSD %.1f\n", gVersion, gHostVersion)
cleanAfterRun()
},
}
listCmd = &cobra.Command{
listCmd = &cobra.Command {
Use: "list",
Short: "Print jails",
Long: `Display jails, their IP and OS.
@ -63,28 +65,111 @@ ex: gocage list srv-db srv-web`,
},
}
stopCmd = &cobra.Command{
listPropsCmd = &cobra.Command {
Use: "properties",
Short: "Print jails properties",
Long: "Display jails properties. You can use properties to filter, get or set them.",
Run: func(cmd *cobra.Command, args []string) {
ListJailsProps(args)
cleanAfterRun()
},
}
stopCmd = &cobra.Command {
Use: "stop",
Short: "stop jail",
Long: `shutdown jail`,
Long: "shutdown jail",
Run: func(cmd *cobra.Command, args []string) {
// Get the inventory
// Load inventory
ListJails(args, false)
StopJail(args)
cleanAfterRun()
},
}
startCmd = &cobra.Command{
startCmd = &cobra.Command {
Use: "start",
Short: "start jail",
Run: func(cmd *cobra.Command, args []string) {
// Get the inventory
// Load inventory
ListJails(args, false)
StartJail(args)
cleanAfterRun()
},
}
setCmd = &cobra.Command {
Use: "set",
Short: "Set a jail property",
Long: `Set jail property value. Specify property=value, end command with jail name.
Multiples properties can be specified, separated with space (Ex: gocage set allow_mlock=1 boot=1 myjail)`,
Run: func(cmd *cobra.Command, args []string) {
// Load inventory
ListJails(args, false)
SetJailProperties(args)
cleanAfterRun()
},
}
getCmd = &cobra.Command {
Use: "get",
Short: "Get a jail property",
Long: `Get jail property value. Specify property, end command with jail name.
Multiples properties can be specified, separated with space (Ex: gocage get allow_mlock boot myjail)
For all properties specify "all" (Ex: gocage get all myjail)`,
Run: func(cmd *cobra.Command, args []string) {
// Load inventory
ListJails(args, false)
GetJailProperties(args)
cleanAfterRun()
},
}
snapshotCmd = &cobra.Command {
Use: "snapshot",
Short: "snapshot jail",
Long: "Commands to manage jail snapshots. If no arguments given, ",
Run: func(cmd *cobra.Command, args []string) {
},
}
snapshotListCmd = &cobra.Command {
Use: "list",
Short: "list snapshots",
Long: `List snapshots of a jail by specifying its name.
List all snapshots if no jail name specified.
You can specify multiple jails.`,
Run: func(cmd *cobra.Command, args []string) {
// Load inventory
ListJails(args, false)
ListJailsSnapshots(args)
},
}
snapshotCreateCmd = &cobra.Command {
Use: "create",
Short: "create snapshots",
Long: `Create snapshot of a jail by specifying snapshot name and jail name.`,
// You can specify multiple jails.`,
Run: func(cmd *cobra.Command, args []string) {
// Load inventory
ListJails(args, false)
CreateJailSnapshot(args)
},
}
snapshotDeleteCmd = &cobra.Command {
Use: "destroy",
Short: "destroy snapshots",
Long: `Destroy snapshot of a jail by specifying snapshot name and jail name.`,
// You can specify multiple jails.`,
Run: func(cmd *cobra.Command, args []string) {
// Load inventory
ListJails(args, false)
DeleteJailSnapshot(args)
},
}
)
@ -93,21 +178,37 @@ func init() {
cobra.OnInitialize(initConfig)
// Global switches
rootCmd.PersistentFlags().StringVarP(&gConfigFile, "config", "c", "/usr/local/etc/gocage.conf.yml", "GoCage configuration file")
rootCmd.PersistentFlags().BoolVarP(&gUseSudo, "sudo", "u", false, "Use sudo to run commands")
rootCmd.PersistentFlags().StringVarP(&gConfigFile, "config", "c", "/usr/local/etc/gocage.conf.yml", "GoCage configuration file")
rootCmd.PersistentFlags().BoolVarP(&gUseSudo, "sudo", "u", false, "Use sudo to run commands")
rootCmd.PersistentFlags().StringVarP(&gTimeZone, "timezone", "t", "", "Specify timezone. Will get from /var/db/zoneinfo if not set.")
// Command dependant switches
listCmd.PersistentFlags().StringVarP(&gDisplayColumns, "outcol", "o", "JID,Name,Config.Release,Config.Ip4_addr,Running", "Show these columns in output")
listCmd.PersistentFlags().BoolVarP(&gNoLineSep, "nolinesep", "l", false, "Do not display line separator between jails")
listCmd.PersistentFlags().StringVarP(&gFilterJails, "filter", "f", "none", "Only display jails with these values. Ex: \"gocage list -f Config.Boot=1\" will only list started on boot jails")
listCmd.PersistentFlags().StringVarP(&gSortFields, "sort", "s", "none", "Display jails sorted by field values. Ex: \"gocage list -s +Name,-Config.Priority\" will sort jails by their decreasing name, then increasing start priority. 3 critera max supported.")
// These are persistent so we can reuse them in "gocage list snapshot myjail" command (TODO)
listCmd.PersistentFlags().StringVarP(&gDisplayColumns, "outcol", "o", "JID,Name,Config.Release,Config.Ip4_addr,Running", "Show these columns in output")
listCmd.PersistentFlags().BoolVarP(&gNoLineSep, "nolinesep", "l", false, "Do not display line separator between jails")
listCmd.PersistentFlags().StringVarP(&gFilterJails, "filter", "f", "none", "Only display jails with these values. Ex: \"gocage list -f Config.Boot=1\" will only list started on boot jails")
listCmd.PersistentFlags().StringVarP(&gSortFields, "sort", "s", "none", "Display jails sorted by field values. Ex: \"gocage list -s +Name,-Config.Priority\" will sort jails by their decreasing name, then increasing start priority. 3 critera max supported.")
// This is local flag : Only available to gocage snapshot create command
snapshotCreateCmd.Flags().StringVarP(&gSnapshotName, "snapname", "n", "", "Name of the snapshot to create")
snapshotCreateCmd.MarkFlagRequired("snapname")
snapshotDeleteCmd.Flags().StringVarP(&gSnapshotName, "snapname", "n", "", "Name of the snapshot to destroy")
snapshotDeleteCmd.MarkFlagRequired("snapname")
// Now declare commands
rootCmd.AddCommand(versionCmd)
rootCmd.AddCommand(listCmd)
rootCmd.AddCommand(stopCmd)
rootCmd.AddCommand(startCmd)
rootCmd.AddCommand(versionCmd)
rootCmd.AddCommand(listCmd)
listCmd.AddCommand(listPropsCmd)
rootCmd.AddCommand(stopCmd)
rootCmd.AddCommand(startCmd)
rootCmd.AddCommand(getCmd)
rootCmd.AddCommand(setCmd)
rootCmd.AddCommand(snapshotCmd)
snapshotCmd.AddCommand(snapshotListCmd)
snapshotCmd.AddCommand(snapshotCreateCmd)
snapshotCmd.AddCommand(snapshotDeleteCmd)
// Get FreeBSD version
out, err := executeCommand("freebsd-version")
if err != nil {
@ -115,7 +216,7 @@ func init() {
os.Exit(1)
}
gHostVersion, _ = strconv.ParseFloat(strings.Split(out, "-")[0], 32)
}
func initConfig() {
@ -141,6 +242,18 @@ func initConfig() {
if rootCmd.Flags().Lookup("sudo") != nil && false == rootCmd.Flags().Lookup("sudo").Changed {
gUseSudo = viper.GetBool("sudo")
}
if rootCmd.Flags().Lookup("timezone") != nil && false == rootCmd.Flags().Lookup("timezone").Changed {
gTimeZone = viper.GetString("timezone")
}
// If neither on cmdline nor config file, get from /var/db/zoneinfo
if len(gTimeZone) == 0 {
tz, err := ioutil.ReadFile("/var/db/zoneinfo")
if err != nil {
fmt.Println("Error reading /var/db/zoneinfo: %s\n", err.Error())
os.Exit(1)
}
gTimeZone = strings.Trim(string(tz), "\n")
}
if listCmd.Flags().Lookup("outcol") != nil && false == listCmd.Flags().Lookup("outcol").Changed {
gDisplayColumns = viper.GetString("outcol")
}
@ -163,22 +276,24 @@ func initConfig() {
func cleanAfterRun() {
for _, j := range gJails {
if j.ConfigUpdated {
// TODO : Marshall to disk
fmt.Printf("Config for jail %s will be updated\n", j.Name)
marshaled, err := json.MarshalIndent(j.Config, "", " ")
if err != nil {
fmt.Printf("ERROR marshaling config: %s\n", err.Error())
}
fmt.Printf(string(marshaled))
//fmt.Printf(string(marshaled))
if os.WriteFile(j.ConfigPath, []byte(marshaled), 0644); err != nil {
fmt.Printf("Error writing config file %s: %v\n", j.ConfigPath, err)
os.Exit(1)
}
}
}
}
func Execute() {
if err := rootCmd.Execute(); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
if err := rootCmd.Execute(); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}

188
cmd/snapshots.go Normal file
View File

@ -0,0 +1,188 @@
package cmd
import (
"fmt"
"time"
"regexp"
"strings"
)
/********************************************************************************
* List all snapshots jails have
*******************************************************************************/
func ListJailsSnapshots(args []string) {
var jailNames []string
var snapshots []Snapshot
if len(args) > 0 {
for _, a := range args {
jailNames = append(jailNames, a)
}
}
if len(jailNames) == 0 || len(args) == 0 {
for _, j := range gJails {
snapshots = append(snapshots, listJailSnapshots(j)...)
}
} else {
for _, cj := range gJails {
for _, jn := range jailNames {
if strings.EqualFold(cj.Name, jn) {
snapshots = append(snapshots, listJailSnapshots(cj)...)
}
}
}
}
displaySnapshotsFields(snapshots, []string{"Jailname","Name","Creation","Referenced","Used"})
}
/********************************************************************************
* List all snapshots a jail have
*******************************************************************************/
func listJailSnapshots(jail Jail) []Snapshot {
var snapshots []Snapshot
// 1. List all datasets
// TODO : Include mounted filesystems?
rs := strings.Split(jail.RootPath, "/")
rootDataset := fmt.Sprintf("%s%s", jail.Zpool, strings.Join(rs[:len(rs)-1], "/"))
cmd := fmt.Sprintf("zfs list -r -H -o name,mountpoint,used,referenced,creation -t snapshot %s", rootDataset)
out, err := executeCommand(cmd)
if err != nil {
fmt.Printf("Error: %s\n", err.Error())
return snapshots
}
dateLayout := "Mon Jan _2 15:04 2006"
loc, _ := time.LoadLocation(gTimeZone)
for _, line := range strings.Split(out, "\n") {
if len(line) > 0 {
ls := strings.Split(line, "\t")
// Parse creation date so we can use it to sort snapshots
creationts, err := time.ParseInLocation(dateLayout, ls[4], loc)
if err != nil {
fmt.Println("Error while parsing date %s:", ls[4], err)
return snapshots
}
// Get subdir to append to snapshot name
subdir := strings.Replace(strings.Split(ls[0], "@")[0], rootDataset, "", 1)
snapshots = append(snapshots, Snapshot{Dsname: ls[0],
Name: fmt.Sprintf("%s%s", strings.Split(ls[0], "@")[1], subdir),
Jailname: jail.Name,
Mountpoint: ls[1],
Used: ls[2],
Referenced: ls[3],
Creation: creationts})
}
}
// Sort snapshots by creation date
ss := initSnapshotSortStruct()
SnapshotsOrderedBy(ss.CreationInc).Sort(snapshots)
return snapshots
}
/********************************************************************************
* Create snapshot for jail(s)
*******************************************************************************/
func CreateJailSnapshot(args []string) {
var jailNames []string
if len(args) > 0 {
for _, a := range args {
jailNames = append(jailNames, a)
}
}
for _, cj := range gJails {
for _, jn := range jailNames {
if strings.EqualFold(cj.Name, jn) {
createJailSnapshot(cj)
}
}
}
}
/********************************************************************************
* Create snapshot for a jail
*******************************************************************************/
func createJailSnapshot(jail Jail) error {
rs := strings.Split(jail.RootPath, "/")
rootDataset := fmt.Sprintf("%s%s", jail.Zpool, strings.Join(rs[:len(rs)-1], "/"))
cmd := fmt.Sprintf("zfs snapshot -r %s@%s", rootDataset, gSnapshotName)
_, err := executeCommand(cmd)
if err != nil {
fmt.Printf("Error creating snapshot %s@%s: %s\n", rootDataset, gSnapshotName, err.Error())
return err
}
fmt.Printf("Snapshot %s@%s created\n", rootDataset, gSnapshotName)
return nil
}
/********************************************************************************
* Delete snapshot for jail(s)
*******************************************************************************/
func DeleteJailSnapshot(args []string) {
var jailNames []string
if len(args) > 0 {
for _, a := range args {
jailNames = append(jailNames, a)
}
}
for _, cj := range gJails {
for _, jn := range jailNames {
if strings.EqualFold(cj.Name, jn) {
deleteJailSnapshot(cj)
}
}
}
}
/********************************************************************************
* Delete snapshot for a jail
*******************************************************************************/
func deleteJailSnapshot(jail Jail) error {
var snaptodel []string
// Get all recursive snapshots
rs := strings.Split(jail.RootPath, "/")
rootDataset := fmt.Sprintf("%s%s", jail.Zpool, strings.Join(rs[:len(rs)-1], "/"))
cmd := fmt.Sprintf("zfs list -r -H -o name -t snapshot %s", rootDataset)
out, err := executeCommand(cmd)
if err != nil {
fmt.Printf("Error: listing snapshots: %s\n", err.Error())
return nil
}
for _, line := range strings.Split(out, "\n") {
if len(line) > 0 {
ls := strings.Split(line, "@")
matched, _ := regexp.Match(fmt.Sprintf("^%s(\\/.*)?$", gSnapshotName), []byte(ls[1]))
if matched {
snaptodel = append(snaptodel, strings.Join(ls, "@"))
}
}
}
for _, s := range snaptodel {
cmd := fmt.Sprintf("zfs destroy %s", s)
_, err := executeCommand(cmd)
if err != nil {
fmt.Printf("Error deleting snapshot %s: %s\n", s, err.Error())
return nil
}
fmt.Printf("Snapshot %s deleted\n", s)
}
return nil
}

View File

@ -3,64 +3,12 @@ package cmd
import (
"os"
"fmt"
// "log"
"errors"
"regexp"
// "os/exec"
// "reflect"
"strings"
// "strconv"
)
// WIP. Not working now (need to address the real struct field, not a copy of it)
// setJailProperty takes a string as propValue, whatever the real property type is.
// It will be converted.
func setJailProperty(jail *Jail, propName string, propValue string) error {
// First get propName type, to convert propValue
/* k, _, err := getStructFieldKind(jail, propName)
if err != nil {
return err
}
kind := k.String()
*/
for i, j := range gJails {
if j.Name == jail.Name {
val, _, err := getStructFieldValue(&gJails[i], propName)
if err != nil {
return errors.New(fmt.Sprintf("Field not found: %s", propName))
}
/*if kind == "string" {
// WIll the affectation be done in source object or a copy?
val.Set(propValue)
} else if kind == "int" {
v, err := strconv.Atoi(propValue)
if err != nil {
return errors.New(fmt.Sprintf("propValue have wrong type: %s\n", err.Error()))
}
val.Set(v)
} else {
return errors.New(fmt.Sprintf("Property %s have an unsupported type in setJailProperty!\n", propName))
}*/
// panic: reflect: reflect.Value.Set using unaddressable value
//val.Set(reflect.ValueOf(propValue).Elem())
// ...Because val settability is false :-(
fmt.Printf("settability of val: %v\n", val.CanSet())
// This is OK, using the index to get the real jail object
//gJails[i].Config.Allow_mlock = 1
// TODO : integrate this function
setJailConfigUpdated(jail)
}
}
return nil
}
// FIXME : Do not work?!
// We cant use internalName as the value exist only when jail is running
func setJailConfigUpdated(jail *Jail) error {
if len(jail.ConfigPath) == 0 {
@ -68,7 +16,8 @@ func setJailConfigUpdated(jail *Jail) error {
}
for i, j := range gJails {
if jail.ConfigPath == j.ConfigPath {
if jail.Name == j.Name {
fmt.Printf("Tag %s as configUpdated\n", jail.Name)
gJails[i].ConfigUpdated = true
return nil
}
@ -78,7 +27,7 @@ func setJailConfigUpdated(jail *Jail) error {
}
func mountProcFs(jail *Jail) error {
cmd = fmt.Sprintf("mount -t procfs proc %s/proc", jail.RootPath)
cmd := fmt.Sprintf("mount -t procfs proc %s/proc", jail.RootPath)
_, err := executeCommand(cmd)
if err != nil {
return errors.New(fmt.Sprintf("Error mounting procfs on %s/proc: %s", jail.RootPath, err.Error()))
@ -96,7 +45,7 @@ func mountLinProcFs(jail *Jail) error {
return errors.New(fmt.Sprintf("Error creating directory %s: %s", ldir, errDir.Error()))
}
}
cmd = fmt.Sprintf("mount -t linprocfs linproc %s", ldir)
cmd := fmt.Sprintf("mount -t linprocfs linproc %s", ldir)
_, err = executeCommand(cmd)
if err != nil {
return errors.New(fmt.Sprintf("Error mounting linprocfs on %s: %s", ldir, err.Error()))
@ -106,7 +55,7 @@ func mountLinProcFs(jail *Jail) error {
}
func mountDevFs(jail *Jail) error {
cmd = fmt.Sprintf("mount -t devfs dev %s/dev", jail.RootPath)
cmd := fmt.Sprintf("mount -t devfs dev %s/dev", jail.RootPath)
_, err := executeCommand(cmd)
if err != nil {
return errors.New(fmt.Sprintf("Error mounting devfs on %s/dev: %s", jail.RootPath, err.Error()))
@ -127,7 +76,7 @@ func mountFdescFs(jail *Jail) error {
return nil
}
cmd = fmt.Sprintf("mount -t fdescfs descfs %s/dev/fd", jail.RootPath)
cmd := fmt.Sprintf("mount -t fdescfs descfs %s/dev/fd", jail.RootPath)
_, err := executeCommand(cmd)
if err != nil {
return errors.New(fmt.Sprintf("Error mounting fdescfs on %s/dev/fd: %s", jail.RootPath, err.Error()))

View File

@ -1,5 +1,8 @@
package cmd
import (
"time"
)
// To allow sorting, just duplicate fields in JailSort below
type Jail struct {
@ -174,303 +177,326 @@ type Mount struct {
Fs_Passno int
}
// This struct hold "sort by jail fields" functions
type lessFunc func(j1 *Jail, j2 *Jail) bool
type Snapshot struct {
// snapshot name is stored after '@' in dataset name
Name string
Dsname string
Jailname string
Mountpoint string
Used string
Referenced string
Creation time.Time
}
// Fields in this struct are acquired by their name using reflection
// So these char are forbidden for field name: -+.
//
type JailSort struct {
NameInc lessFunc
NameDec lessFunc
InternalNameInc lessFunc
InternalNameDec lessFunc
JIDInc lessFunc
JIDDec lessFunc
RootPathInc lessFunc
RootPathDec lessFunc
ConfigPathInc lessFunc
ConfigPathDec lessFunc
RunningInc lessFunc
RunningDec lessFunc
ZpoolInc lessFunc
ZpoolDec lessFunc
NameInc jailLessFunc
NameDec jailLessFunc
InternalNameInc jailLessFunc
InternalNameDec jailLessFunc
JIDInc jailLessFunc
JIDDec jailLessFunc
RootPathInc jailLessFunc
RootPathDec jailLessFunc
ConfigPathInc jailLessFunc
ConfigPathDec jailLessFunc
RunningInc jailLessFunc
RunningDec jailLessFunc
ZpoolInc jailLessFunc
ZpoolDec jailLessFunc
Config JailConfigSort
}
type JailConfigSort struct {
Config_versionInc lessFunc
Config_versionDec lessFunc
Allow_chflagsInc lessFunc
Allow_chflagsDec lessFunc
Allow_mlockInc lessFunc
Allow_mlockDec lessFunc
Allow_mountInc lessFunc
Allow_mountDec lessFunc
Allow_mount_devfsInc lessFunc
Allow_mount_devfsDec lessFunc
Allow_mount_fusefsInc lessFunc
Allow_mount_fusefsDec lessFunc
Allow_mount_nullfsInc lessFunc
Allow_mount_nullfsDec lessFunc
Allow_mount_procfsInc lessFunc
Allow_mount_procfsDec lessFunc
Allow_mount_tmpfsInc lessFunc
Allow_mount_tmpfsDec lessFunc
Allow_mount_zfsInc lessFunc
Allow_mount_zfsDec lessFunc
Allow_quotasInc lessFunc
Allow_quotasDec lessFunc
Allow_raw_socketsInc lessFunc
Allow_raw_socketsDec lessFunc
Allow_set_hostnameInc lessFunc
Allow_set_hostnameDec lessFunc
Allow_socket_afInc lessFunc
Allow_socket_afDec lessFunc
Allow_sysvipcInc lessFunc
Allow_sysvipcDec lessFunc
Allow_tunInc lessFunc
Allow_tunDec lessFunc
Allow_vmmInc lessFunc
Allow_vmmDec lessFunc
Assign_localhostInc lessFunc
Assign_localhostDec lessFunc
AvailableInc lessFunc
AvailableDec lessFunc
BasejailInc lessFunc
BasejailDec lessFunc
BootInc lessFunc
BootDec lessFunc
BpfInc lessFunc
BpfDec lessFunc
Children_maxInc lessFunc
Children_maxDec lessFunc
Cloned_releaseInc lessFunc
Cloned_releaseDec lessFunc
CommentInc lessFunc
CommentDec lessFunc
CompressionInc lessFunc
CompressionDec lessFunc
CompressratioInc lessFunc
CompressratioDec lessFunc
CoredumpsizeInc lessFunc
CoredumpsizeDec lessFunc
CountInc lessFunc
CountDec lessFunc
CpusetInc lessFunc
CpusetDec lessFunc
CputimeInc lessFunc
CputimeDec lessFunc
DatasizeInc lessFunc
DatasizeDec lessFunc
DedupInc lessFunc
DedupDec lessFunc
DefaultrouterInc lessFunc
DefaultrouterDec lessFunc
Defaultrouter6Inc lessFunc
Defaultrouter6Dec lessFunc
DependsInc lessFunc
DependsDec lessFunc
Devfs_rulesetInc lessFunc
Devfs_rulesetDec lessFunc
DhcpInc lessFunc
DhcpDec lessFunc
Enforce_statfsInc lessFunc
Enforce_statfsDec lessFunc
Exec_cleanInc lessFunc
Exec_cleanDec lessFunc
Exec_createdInc lessFunc
Exec_createdDec lessFunc
Exec_fibInc lessFunc
Exec_fibDec lessFunc
Exec_jail_userInc lessFunc
Exec_jail_userDec lessFunc
Exec_poststartInc lessFunc
Exec_poststartDec lessFunc
Exec_poststopInc lessFunc
Exec_poststopDec lessFunc
Exec_prestartInc lessFunc
Exec_prestartDec lessFunc
Exec_prestopInc lessFunc
Exec_prestopDec lessFunc
Exec_startInc lessFunc
Exec_startDec lessFunc
Exec_stopInc lessFunc
Exec_stopDec lessFunc
Exec_system_jail_userInc lessFunc
Exec_system_jail_userDec lessFunc
Exec_system_userInc lessFunc
Exec_system_userDec lessFunc
Exec_timeoutInc lessFunc
Exec_timeoutDec lessFunc
Host_domainnameInc lessFunc
Host_domainnameDec lessFunc
Host_hostnameInc lessFunc
Host_hostnameDec lessFunc
Host_hostuuidInc lessFunc
Host_hostuuidDec lessFunc
Host_timeInc lessFunc
Host_timeDec lessFunc
HostidInc lessFunc
HostidDec lessFunc
Hostid_strict_checkInc lessFunc
Hostid_strict_checkDec lessFunc
InterfacesInc lessFunc
InterfacesDec lessFunc
Ip4Inc lessFunc
Ip4Dec lessFunc
Ip4_addrInc lessFunc
Ip4_addrDec lessFunc
Ip4_saddrselInc lessFunc
Ip4_saddrselDec lessFunc
Ip6Inc lessFunc
Ip6Dec lessFunc
Ip6_addrInc lessFunc
Ip6_addrDec lessFunc
Ip6_saddrselInc lessFunc
Ip6_saddrselDec lessFunc
Ip_hostnameInc lessFunc
Ip_hostnameDec lessFunc
Jail_zfsInc lessFunc
Jail_zfsDec lessFunc
Jail_zfs_datasetInc lessFunc
Jail_zfs_datasetDec lessFunc
Jail_zfs_mountpointInc lessFunc
Jail_zfs_mountpointDec lessFunc
Last_startedInc lessFunc
Last_startedDec lessFunc
Localhost_ipInc lessFunc
Localhost_ipDec lessFunc
Login_flagsInc lessFunc
Login_flagsDec lessFunc
Mac_prefixInc lessFunc
Mac_prefixDec lessFunc
MaxprocInc lessFunc
MaxprocDec lessFunc
MemorylockedInc lessFunc
MemorylockedDec lessFunc
MemoryuseInc lessFunc
MemoryuseDec lessFunc
Min_dyn_devfs_rulesetInc lessFunc
Min_dyn_devfs_rulesetDec lessFunc
Mount_devfsInc lessFunc
Mount_devfsDec lessFunc
Mount_fdescfsInc lessFunc
Mount_fdescfsDec lessFunc
Mount_linprocfsInc lessFunc
Mount_linprocfsDec lessFunc
Mount_procfsInc lessFunc
Mount_procfsDec lessFunc
MountpointInc lessFunc
MountpointDec lessFunc
MsgqqueuedInc lessFunc
MsgqqueuedDec lessFunc
MsgqsizeInc lessFunc
MsgqsizeDec lessFunc
NatInc lessFunc
NatDec lessFunc
Nat_backendInc lessFunc
Nat_backendDec lessFunc
Nat_forwardsInc lessFunc
Nat_forwardsDec lessFunc
Nat_interfaceInc lessFunc
Nat_interfaceDec lessFunc
Nat_prefixInc lessFunc
Nat_prefixDec lessFunc
NmsgqInc lessFunc
NmsgqDec lessFunc
NotesInc lessFunc
NotesDec lessFunc
NsemInc lessFunc
NsemDec lessFunc
NsemopInc lessFunc
NsemopDec lessFunc
NshmInc lessFunc
NshmDec lessFunc
NthrInc lessFunc
NthrDec lessFunc
OpenfilesInc lessFunc
OpenfilesDec lessFunc
OriginInc lessFunc
OriginDec lessFunc
OwnerInc lessFunc
OwnerDec lessFunc
PcpuInc lessFunc
PcpuDec lessFunc
Plugin_nameInc lessFunc
Plugin_nameDec lessFunc
Plugin_repositoryInc lessFunc
Plugin_repositoryDec lessFunc
PriorityInc lessFunc
PriorityDec lessFunc
PseudoterminalsInc lessFunc
PseudoterminalsDec lessFunc
QuotaInc lessFunc
QuotaDec lessFunc
ReadbpsInc lessFunc
ReadbpsDec lessFunc
ReadiopsInc lessFunc
ReadiopsDec lessFunc
ReleaseInc lessFunc
ReleaseDec lessFunc
ReservationInc lessFunc
ReservationDec lessFunc
ResolverInc lessFunc
ResolverDec lessFunc
RlimitsInc lessFunc
RlimitsDec lessFunc
RtsoldInc lessFunc
RtsoldDec lessFunc
SecurelevelInc lessFunc
SecurelevelDec lessFunc
ShmsizeInc lessFunc
ShmsizeDec lessFunc
StacksizeInc lessFunc
StacksizeDec lessFunc
Stop_timeoutInc lessFunc
Stop_timeoutDec lessFunc
SwapuseInc lessFunc
SwapuseDec lessFunc
Sync_stateInc lessFunc
Sync_stateDec lessFunc
Sync_targetInc lessFunc
Sync_targetDec lessFunc
Sync_tgt_zpoolInc lessFunc
Sync_tgt_zpoolDec lessFunc
SysvmsgInc lessFunc
SysvmsgDec lessFunc
SysvsemInc lessFunc
SysvsemDec lessFunc
SysvshmInc lessFunc
SysvshmDec lessFunc
TemplateInc lessFunc
TemplateDec lessFunc
JailtypeInc lessFunc
JailtypeDec lessFunc
UsedInc lessFunc
UsedDec lessFunc
VmemoryuseInc lessFunc
VmemoryuseDec lessFunc
VnetInc lessFunc
VnetDec lessFunc
Vnet0_macInc lessFunc
Vnet0_macDec lessFunc
Vnet1_macInc lessFunc
Vnet1_macDec lessFunc
Vnet2_macInc lessFunc
Vnet2_macDec lessFunc
Vnet3_macInc lessFunc
Vnet3_macDec lessFunc
Vnet_default_interfaceInc lessFunc
Vnet_default_interfaceDec lessFunc
Vnet_interfacesInc lessFunc
Vnet_interfacesDec lessFunc
WallclockInc lessFunc
WallclockDec lessFunc
WritebpsInc lessFunc
WritebpsDec lessFunc
WriteiopsInc lessFunc
WriteiopsDec lessFunc
Config_versionInc jailLessFunc
Config_versionDec jailLessFunc
Allow_chflagsInc jailLessFunc
Allow_chflagsDec jailLessFunc
Allow_mlockInc jailLessFunc
Allow_mlockDec jailLessFunc
Allow_mountInc jailLessFunc
Allow_mountDec jailLessFunc
Allow_mount_devfsInc jailLessFunc
Allow_mount_devfsDec jailLessFunc
Allow_mount_fusefsInc jailLessFunc
Allow_mount_fusefsDec jailLessFunc
Allow_mount_nullfsInc jailLessFunc
Allow_mount_nullfsDec jailLessFunc
Allow_mount_procfsInc jailLessFunc
Allow_mount_procfsDec jailLessFunc
Allow_mount_tmpfsInc jailLessFunc
Allow_mount_tmpfsDec jailLessFunc
Allow_mount_zfsInc jailLessFunc
Allow_mount_zfsDec jailLessFunc
Allow_quotasInc jailLessFunc
Allow_quotasDec jailLessFunc
Allow_raw_socketsInc jailLessFunc
Allow_raw_socketsDec jailLessFunc
Allow_set_hostnameInc jailLessFunc
Allow_set_hostnameDec jailLessFunc
Allow_socket_afInc jailLessFunc
Allow_socket_afDec jailLessFunc
Allow_sysvipcInc jailLessFunc
Allow_sysvipcDec jailLessFunc
Allow_tunInc jailLessFunc
Allow_tunDec jailLessFunc
Allow_vmmInc jailLessFunc
Allow_vmmDec jailLessFunc
Assign_localhostInc jailLessFunc
Assign_localhostDec jailLessFunc
AvailableInc jailLessFunc
AvailableDec jailLessFunc
BasejailInc jailLessFunc
BasejailDec jailLessFunc
BootInc jailLessFunc
BootDec jailLessFunc
BpfInc jailLessFunc
BpfDec jailLessFunc
Children_maxInc jailLessFunc
Children_maxDec jailLessFunc
Cloned_releaseInc jailLessFunc
Cloned_releaseDec jailLessFunc
CommentInc jailLessFunc
CommentDec jailLessFunc
CompressionInc jailLessFunc
CompressionDec jailLessFunc
CompressratioInc jailLessFunc
CompressratioDec jailLessFunc
CoredumpsizeInc jailLessFunc
CoredumpsizeDec jailLessFunc
CountInc jailLessFunc
CountDec jailLessFunc
CpusetInc jailLessFunc
CpusetDec jailLessFunc
CputimeInc jailLessFunc
CputimeDec jailLessFunc
DatasizeInc jailLessFunc
DatasizeDec jailLessFunc
DedupInc jailLessFunc
DedupDec jailLessFunc
DefaultrouterInc jailLessFunc
DefaultrouterDec jailLessFunc
Defaultrouter6Inc jailLessFunc
Defaultrouter6Dec jailLessFunc
DependsInc jailLessFunc
DependsDec jailLessFunc
Devfs_rulesetInc jailLessFunc
Devfs_rulesetDec jailLessFunc
DhcpInc jailLessFunc
DhcpDec jailLessFunc
Enforce_statfsInc jailLessFunc
Enforce_statfsDec jailLessFunc
Exec_cleanInc jailLessFunc
Exec_cleanDec jailLessFunc
Exec_createdInc jailLessFunc
Exec_createdDec jailLessFunc
Exec_fibInc jailLessFunc
Exec_fibDec jailLessFunc
Exec_jail_userInc jailLessFunc
Exec_jail_userDec jailLessFunc
Exec_poststartInc jailLessFunc
Exec_poststartDec jailLessFunc
Exec_poststopInc jailLessFunc
Exec_poststopDec jailLessFunc
Exec_prestartInc jailLessFunc
Exec_prestartDec jailLessFunc
Exec_prestopInc jailLessFunc
Exec_prestopDec jailLessFunc
Exec_startInc jailLessFunc
Exec_startDec jailLessFunc
Exec_stopInc jailLessFunc
Exec_stopDec jailLessFunc
Exec_system_jail_userInc jailLessFunc
Exec_system_jail_userDec jailLessFunc
Exec_system_userInc jailLessFunc
Exec_system_userDec jailLessFunc
Exec_timeoutInc jailLessFunc
Exec_timeoutDec jailLessFunc
Host_domainnameInc jailLessFunc
Host_domainnameDec jailLessFunc
Host_hostnameInc jailLessFunc
Host_hostnameDec jailLessFunc
Host_hostuuidInc jailLessFunc
Host_hostuuidDec jailLessFunc
Host_timeInc jailLessFunc
Host_timeDec jailLessFunc
HostidInc jailLessFunc
HostidDec jailLessFunc
Hostid_strict_checkInc jailLessFunc
Hostid_strict_checkDec jailLessFunc
InterfacesInc jailLessFunc
InterfacesDec jailLessFunc
Ip4Inc jailLessFunc
Ip4Dec jailLessFunc
Ip4_addrInc jailLessFunc
Ip4_addrDec jailLessFunc
Ip4_saddrselInc jailLessFunc
Ip4_saddrselDec jailLessFunc
Ip6Inc jailLessFunc
Ip6Dec jailLessFunc
Ip6_addrInc jailLessFunc
Ip6_addrDec jailLessFunc
Ip6_saddrselInc jailLessFunc
Ip6_saddrselDec jailLessFunc
Ip_hostnameInc jailLessFunc
Ip_hostnameDec jailLessFunc
Jail_zfsInc jailLessFunc
Jail_zfsDec jailLessFunc
Jail_zfs_datasetInc jailLessFunc
Jail_zfs_datasetDec jailLessFunc
Jail_zfs_mountpointInc jailLessFunc
Jail_zfs_mountpointDec jailLessFunc
Last_startedInc jailLessFunc
Last_startedDec jailLessFunc
Localhost_ipInc jailLessFunc
Localhost_ipDec jailLessFunc
Login_flagsInc jailLessFunc
Login_flagsDec jailLessFunc
Mac_prefixInc jailLessFunc
Mac_prefixDec jailLessFunc
MaxprocInc jailLessFunc
MaxprocDec jailLessFunc
MemorylockedInc jailLessFunc
MemorylockedDec jailLessFunc
MemoryuseInc jailLessFunc
MemoryuseDec jailLessFunc
Min_dyn_devfs_rulesetInc jailLessFunc
Min_dyn_devfs_rulesetDec jailLessFunc
Mount_devfsInc jailLessFunc
Mount_devfsDec jailLessFunc
Mount_fdescfsInc jailLessFunc
Mount_fdescfsDec jailLessFunc
Mount_linprocfsInc jailLessFunc
Mount_linprocfsDec jailLessFunc
Mount_procfsInc jailLessFunc
Mount_procfsDec jailLessFunc
MountpointInc jailLessFunc
MountpointDec jailLessFunc
MsgqqueuedInc jailLessFunc
MsgqqueuedDec jailLessFunc
MsgqsizeInc jailLessFunc
MsgqsizeDec jailLessFunc
NatInc jailLessFunc
NatDec jailLessFunc
Nat_backendInc jailLessFunc
Nat_backendDec jailLessFunc
Nat_forwardsInc jailLessFunc
Nat_forwardsDec jailLessFunc
Nat_interfaceInc jailLessFunc
Nat_interfaceDec jailLessFunc
Nat_prefixInc jailLessFunc
Nat_prefixDec jailLessFunc
NmsgqInc jailLessFunc
NmsgqDec jailLessFunc
NotesInc jailLessFunc
NotesDec jailLessFunc
NsemInc jailLessFunc
NsemDec jailLessFunc
NsemopInc jailLessFunc
NsemopDec jailLessFunc
NshmInc jailLessFunc
NshmDec jailLessFunc
NthrInc jailLessFunc
NthrDec jailLessFunc
OpenfilesInc jailLessFunc
OpenfilesDec jailLessFunc
OriginInc jailLessFunc
OriginDec jailLessFunc
OwnerInc jailLessFunc
OwnerDec jailLessFunc
PcpuInc jailLessFunc
PcpuDec jailLessFunc
Plugin_nameInc jailLessFunc
Plugin_nameDec jailLessFunc
Plugin_repositoryInc jailLessFunc
Plugin_repositoryDec jailLessFunc
PriorityInc jailLessFunc
PriorityDec jailLessFunc
PseudoterminalsInc jailLessFunc
PseudoterminalsDec jailLessFunc
QuotaInc jailLessFunc
QuotaDec jailLessFunc
ReadbpsInc jailLessFunc
ReadbpsDec jailLessFunc
ReadiopsInc jailLessFunc
ReadiopsDec jailLessFunc
ReleaseInc jailLessFunc
ReleaseDec jailLessFunc
ReservationInc jailLessFunc
ReservationDec jailLessFunc
ResolverInc jailLessFunc
ResolverDec jailLessFunc
RlimitsInc jailLessFunc
RlimitsDec jailLessFunc
RtsoldInc jailLessFunc
RtsoldDec jailLessFunc
SecurelevelInc jailLessFunc
SecurelevelDec jailLessFunc
ShmsizeInc jailLessFunc
ShmsizeDec jailLessFunc
StacksizeInc jailLessFunc
StacksizeDec jailLessFunc
Stop_timeoutInc jailLessFunc
Stop_timeoutDec jailLessFunc
SwapuseInc jailLessFunc
SwapuseDec jailLessFunc
Sync_stateInc jailLessFunc
Sync_stateDec jailLessFunc
Sync_targetInc jailLessFunc
Sync_targetDec jailLessFunc
Sync_tgt_zpoolInc jailLessFunc
Sync_tgt_zpoolDec jailLessFunc
SysvmsgInc jailLessFunc
SysvmsgDec jailLessFunc
SysvsemInc jailLessFunc
SysvsemDec jailLessFunc
SysvshmInc jailLessFunc
SysvshmDec jailLessFunc
TemplateInc jailLessFunc
TemplateDec jailLessFunc
JailtypeInc jailLessFunc
JailtypeDec jailLessFunc
UsedInc jailLessFunc
UsedDec jailLessFunc
VmemoryuseInc jailLessFunc
VmemoryuseDec jailLessFunc
VnetInc jailLessFunc
VnetDec jailLessFunc
Vnet0_macInc jailLessFunc
Vnet0_macDec jailLessFunc
Vnet1_macInc jailLessFunc
Vnet1_macDec jailLessFunc
Vnet2_macInc jailLessFunc
Vnet2_macDec jailLessFunc
Vnet3_macInc jailLessFunc
Vnet3_macDec jailLessFunc
Vnet_default_interfaceInc jailLessFunc
Vnet_default_interfaceDec jailLessFunc
Vnet_interfacesInc jailLessFunc
Vnet_interfacesDec jailLessFunc
WallclockInc jailLessFunc
WallclockDec jailLessFunc
WritebpsInc jailLessFunc
WritebpsDec jailLessFunc
WriteiopsInc jailLessFunc
WriteiopsDec jailLessFunc
}
type SnapshotSort struct {
NameInc snapshotLessFunc
NameDec snapshotLessFunc
DsnameInc snapshotLessFunc
DsnameDec snapshotLessFunc
JailnameInc snapshotLessFunc
JailnameDec snapshotLessFunc
MountpointInc snapshotLessFunc
MountpointDec snapshotLessFunc
UsedInc snapshotLessFunc
UsedDec snapshotLessFunc
ReferencedInc snapshotLessFunc
ReferencedDec snapshotLessFunc
CreationInc snapshotLessFunc
CreationDec snapshotLessFunc
}

View File

@ -3,6 +3,7 @@ package cmd
import (
"os"
"fmt"
"log"
"sort"
"bufio"
"errors"
@ -104,9 +105,9 @@ func getFstab(path string) ([]Mount, error) {
}
/*****************************************************************************
* Get a specific jail source reference, to update properties after a range loop
*****************************************************************************/
/********************************************************************************
* 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 {
@ -116,11 +117,10 @@ func getJailFromArray(internalName string, jarray []Jail) (*Jail, error) {
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")
/********************************************************************************
* 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)
@ -158,6 +158,552 @@ func getStructFieldKind(parentStruct interface{}, fieldName string) (reflect.Kin
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. : displayJailsFields(["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 !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")
}
/********************************************************************************
* Pretty display of snapshots field
* Fields to show are given in a string array parameter
* Ex. : displaySnapshotsFields(["Name", "Dsname", "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 {
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")
}
/*****************************************************************************
@ -166,9 +712,10 @@ func getStructFieldKind(parentStruct interface{}, fieldName string) (reflect.Kin
*
*****************************************************************************/
// already defined in cmd/struct.go
//type lessFunc func(j1 *Jail, j2 *Jail) bool
func initSortStruct() JailSort {
// 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
@ -1037,34 +1584,34 @@ func initSortStruct() JailSort {
return js
}
// multiSorter implements the Sort interface, sorting the jails within.
type multiSorter struct {
// jailSorter implements the Sort interface, sorting the jails within.
type jailSorter struct {
jails []Jail
less []lessFunc
less []jailLessFunc
}
// 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)
// 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)
}
// OrderedBy returns a Sorter that sorts using the less functions, in order.
// JailsOrderedBy 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{
func JailsOrderedBy(less ...jailLessFunc) *jailSorter {
return &jailSorter{
less: less,
}
}
// Len is part of sort.Interface.
func (ms *multiSorter) Len() int {
return len(ms.jails)
func (js *jailSorter) Len() int {
return len(js.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]
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
@ -1073,12 +1620,12 @@ func (ms *multiSorter) Swap(i, j int) {
// 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]
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(ms.less)-1; k++ {
less := ms.less[k]
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.
@ -1091,7 +1638,136 @@ func (ms *multiSorter) Less(i, j int) bool {
}
// All comparisons to here said "equal", so just return whatever
// the final comparison reports.
return ms.less[k](p, q)
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
},
DsnameInc: func(s1, s2 *Snapshot) bool {
return s1.Dsname < s2.Dsname
},
DsnameDec: func(s1, s2 *Snapshot) bool {
return s1.Dsname > s2.Dsname
},
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)
}
/*****************************************************************************
*
* Generic utilities
*
*****************************************************************************/
func isStringInArray(strarr []string, searched string) bool {
for _, s := range strarr {
if strings.EqualFold(s, searched) {
return true
}
}
return false
}