25 Commits
v0.2 ... v0.25

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
yo
eacc165481 Mount local FS; get struct pointer so we can modify values 2022-04-02 14:18:50 +02:00
yo
349ea12979 Unmount local FS before stopping jail 2022-04-02 14:17:10 +02:00
yo
30209d2890 Start implementing gocage start 2021-12-21 20:48:15 +01:00
yo
ea25db2f27 Start implementing gocage start 2021-12-21 20:48:07 +01:00
yo
12c0a37617 BUGFIX when no -s 2021-12-21 20:47:46 +01:00
yo
fb94921afd update doc with sort example 2021-12-20 22:23:17 +01:00
yo
74da6909c3 update doc with sort example 2021-12-20 22:16:36 +01:00
9 changed files with 2133 additions and 645 deletions

View File

@ -80,6 +80,40 @@ gocage list -f Config.Boot=1,Running=true -o JID,Name,Running,Config.Boot
+-----+----------+---------+-------------+ +-----+----------+---------+-------------+
</pre></code> </pre></code>
Sort jails
----------
Use -s switch followed by sort criteria. Criteria is a field name, prefixed with + or - for sort order (increase/decrease):
<pre><code>
gocage list -f Config.Boot=1,Running=true -o JID,Name,Running,Config.Boot -s +JID
+=====+==========+=========+=============+
| JID | Name | Running | Config.Boot |
+=====+==========+=========+=============+
| 22 | srv-dns1 | true | 1 |
+-----+----------+---------+-------------+
| 29 | bdd-tst | true | 1 |
+-----+----------+---------+-------------+
| 183 | test | true | 1 |
+-----+----------+---------+-------------+
</pre></code>
You can use up to 3 criteria, delimited with comma.
As an example, you want to list boot priorities of automatically starting jails:
<pre><code>
gocage list -o JID,Name,Config.Ip4_addr,Config.Priority,Config.Boot,Running -s -Config.Priority,-Config.Boot -f Running=true
+=====+==============+=======================+=================+=============+=========+
| JID | Name | Config.Ip4_addr | Config.Priority | Config.Boot | Running |
+=====+==============+=======================+=================+=============+=========+
| 1 | srv-dhcp | vnet0|192.168.1.2/24 | 99 | 1 | true |
+-----+--------------+-----------------------+-----------------+-------------+---------+
| 8 | srv-dns | vnet0|192.168.1.1/24 | 80 | 1 | true |
+-----+--------------+-----------------------+-----------------+-------------+---------+
| 7 | srv-random | vnet0|192.168.1.12/24 | 20 | 1 | true |
+-----+--------------+-----------------------+-----------------+-------------+---------+
| 4 | coincoin | vnet0|192.168.1.9/24 | 20 | 0 | true |
+-----+--------------+-----------------------+-----------------+-------------+---------+
</pre></code>
Stop jails Stop jails
---------- ----------
`gocage stop test` `gocage stop test`

View File

@ -4,7 +4,6 @@ import (
"os" "os"
"fmt" "fmt"
"log" "log"
"errors"
"reflect" "reflect"
"strings" "strings"
"io/ioutil" "io/ioutil"
@ -13,290 +12,28 @@ import (
"github.com/spf13/viper" "github.com/spf13/viper"
) )
/********************************************************************************
// Recurse into structure, returning reflect.Value of wanted field. * List all properties a jail have, with their internal name
// Nested fields are named with a dot (ex "MyStruct.MyField") * Only print properties name. To get name & values, use GetJailProperties()
func getStructFieldValue(parentStruct interface{}, fieldName string) (reflect.Value, string, error) { *******************************************************************************/
v := reflect.ValueOf(parentStruct) func ListJailsProps(args []string) {
var conf Jail
if v.Kind() == reflect.Ptr { var jailconf JailConfig
v = v.Elem() var result []string
}
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 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("+")
}
}
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) { func ListJails(args []string, display bool) {
fields := strings.Split(gDisplayColumns, ",") fields := strings.Split(gDisplayColumns, ",")
@ -307,9 +44,9 @@ func ListJails(args []string, display bool) {
// This is the structure we will filter, then display // This is the structure we will filter, then display
var jails []Jail var jails []Jail
/************************************************************************************ /***************************************************************
/ Filter jails with "filter" options / Filter jails with "filter" options
/***********************************************************************************/ /**************************************************************/
if len(gFilterJails) > 0 && gFilterJails != "none" { if len(gFilterJails) > 0 && gFilterJails != "none" {
flts := make(map[string]string) flts := make(map[string]string)
for _, flt := range strings.Split(gFilterJails, ",") { for _, flt := range strings.Split(gFilterJails, ",") {
@ -341,9 +78,9 @@ func ListJails(args []string, display bool) {
} }
/************************************************************************************ /***************************************************************
/ Filter jails by names given on command line / Filter jails by names given on command line
/***********************************************************************************/ /**************************************************************/
if len(args) > 0 { if len(args) > 0 {
var js []Jail var js []Jail
for _, a := range args { for _, a := range args {
@ -357,15 +94,15 @@ func ListJails(args []string, display bool) {
jails = js jails = js
} }
/************************************************************************************ /***************************************************************
/ Sort jails / Sort jails
/ We support 3 sort criteria max / We support 3 sort criteria max
/***********************************************************************************/ /**************************************************************/
if len(gSortFields) > 0 { if len(gSortFields) > 0 && gSortFields != "none" {
js := initSortStruct() js := initJailSortStruct()
// The way we manage criteria quantity is not very elegant... // The way we manage criteria quantity is not very elegant...
var fct1, fct2, fct3 reflect.Value var fct1, fct2, fct3 *reflect.Value
for i, c := range strings.Split(gSortFields, ",") { for i, c := range strings.Split(gSortFields, ",") {
var fctName string var fctName string
if strings.HasPrefix(c, "-") { if strings.HasPrefix(c, "-") {
@ -390,19 +127,19 @@ func ListJails(args []string, display bool) {
switch len(strings.Split(gSortFields, ",")) { switch len(strings.Split(gSortFields, ",")) {
case 1: case 1:
OrderedBy(fct1.Interface().(lessFunc)).Sort(jails) JailsOrderedBy(fct1.Interface().(jailLessFunc)).Sort(jails)
case 2: case 2:
OrderedBy(fct1.Interface().(lessFunc), fct2.Interface().(lessFunc)).Sort(jails) JailsOrderedBy(fct1.Interface().(jailLessFunc), fct2.Interface().(jailLessFunc)).Sort(jails)
case 3: 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 / Finally, display jails
/***********************************************************************************/ /**************************************************************/
if display { 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

@ -3,14 +3,17 @@ package cmd
import ( import (
"os" "os"
"fmt" "fmt"
"strconv"
"strings" "strings"
"io/ioutil"
"encoding/json"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/viper" "github.com/spf13/viper"
) )
const ( const (
version = "0.02" gVersion = "0.25"
) )
var ( var (
@ -24,28 +27,33 @@ var (
gSortFields string gSortFields string
gNoLineSep bool gNoLineSep bool
gHostVersion float64
gTimeZone string
gSnapshotName string
rootCmd = & cobra.Command{ rootCmd = & cobra.Command {
Use: "gocage", Use: "gocage",
Short: "GoCage is a FreeBSD Jail management tool", 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. 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.`, It support iocage jails and can coexist with iocage.`,
Run: func(cmd *cobra.Command, args []string) {
Run: func(cmd *cobra.Command, args []string) { fmt.Println("Here we are in the Run")
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{ listCmd = &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\n", version)
},
}
listCmd = &cobra.Command{
Use: "list", Use: "list",
Short: "Print jails", Short: "Print jails",
Long: `Display jails, their IP and OS. Long: `Display jails, their IP and OS.
@ -53,17 +61,113 @@ Jail list can be restricted by adding name on command line
ex: gocage list srv-db srv-web`, ex: gocage list srv-db srv-web`,
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
ListJails(args, true) ListJails(args, true)
cleanAfterRun()
}, },
} }
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", Use: "stop",
Short: "stop jail", Short: "stop jail",
Long: `shutdown jail`, Long: "shutdown jail",
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
// Get the inventory // Load inventory
ListJails(args, false) ListJails(args, false)
StopJail(args) StopJail(args)
cleanAfterRun()
},
}
startCmd = &cobra.Command {
Use: "start",
Short: "start jail",
Run: func(cmd *cobra.Command, args []string) {
// 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)
}, },
} }
) )
@ -74,19 +178,45 @@ func init() {
cobra.OnInitialize(initConfig) cobra.OnInitialize(initConfig)
// Global switches // Global switches
rootCmd.PersistentFlags().StringVarP(&gConfigFile, "config", "c", "/usr/local/etc/gocage.conf.yml", "GoCage configuration file") 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().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 // 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") // These are persistent so we can reuse them in "gocage list snapshot myjail" command (TODO)
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(&gDisplayColumns, "outcol", "o", "JID,Name,Config.Release,Config.Ip4_addr,Running", "Show these columns in output")
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.") 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 // Now declare commands
rootCmd.AddCommand(versionCmd) rootCmd.AddCommand(versionCmd)
rootCmd.AddCommand(listCmd) rootCmd.AddCommand(listCmd)
rootCmd.AddCommand(stopCmd) 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 {
fmt.Printf("Error running \"freebsd-version\": %s", err.Error())
os.Exit(1)
}
gHostVersion, _ = strconv.ParseFloat(strings.Split(out, "-")[0], 32)
} }
func initConfig() { func initConfig() {
@ -112,6 +242,18 @@ func initConfig() {
if rootCmd.Flags().Lookup("sudo") != nil && false == rootCmd.Flags().Lookup("sudo").Changed { if rootCmd.Flags().Lookup("sudo") != nil && false == rootCmd.Flags().Lookup("sudo").Changed {
gUseSudo = viper.GetBool("sudo") 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 { if listCmd.Flags().Lookup("outcol") != nil && false == listCmd.Flags().Lookup("outcol").Changed {
gDisplayColumns = viper.GetString("outcol") gDisplayColumns = viper.GetString("outcol")
} }
@ -130,11 +272,28 @@ func initConfig() {
} }
} }
// Called after execution
func cleanAfterRun() {
for _, j := range gJails {
if j.ConfigUpdated {
marshaled, err := json.MarshalIndent(j.Config, "", " ")
if err != nil {
fmt.Printf("ERROR marshaling config: %s\n", err.Error())
}
//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() { func Execute() {
if err := rootCmd.Execute(); err != nil { if err := rootCmd.Execute(); err != nil {
fmt.Fprintln(os.Stderr, err) fmt.Fprintln(os.Stderr, err)
os.Exit(1) 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
}

370
cmd/start.go Normal file
View File

@ -0,0 +1,370 @@
package cmd
import (
"os"
"fmt"
"errors"
"regexp"
"strings"
)
// 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 {
return errors.New(fmt.Sprintf("No config path for jail %s", jail.Name))
}
for i, j := range gJails {
if jail.Name == j.Name {
fmt.Printf("Tag %s as configUpdated\n", jail.Name)
gJails[i].ConfigUpdated = true
return nil
}
}
return errors.New("Jail not found")
}
func mountProcFs(jail *Jail) error {
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()))
}
return nil
}
func mountLinProcFs(jail *Jail) error {
ldir := fmt.Sprintf("%s/compat/linux/proc", jail.RootPath)
_, err := os.Stat(ldir)
if os.IsNotExist(err) {
errDir := os.MkdirAll(ldir, 0755)
if errDir != nil {
return errors.New(fmt.Sprintf("Error creating directory %s: %s", ldir, errDir.Error()))
}
}
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()))
}
return nil
}
func mountDevFs(jail *Jail) error {
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()))
}
return nil
}
func mountFdescFs(jail *Jail) error {
// FreeBSD <= 9.3 do not support fdescfs
if gHostVersion <= 9.3 {
fmt.Printf(" FreeBSD <= 9.3 does not support fdescfs, disabling in config\n")
jail.Config.Mount_fdescfs = 0
// Tag config so it will be synced on disk
jail.ConfigUpdated = true
// Should we consider this an error?
return nil
}
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()))
}
return nil
}
func mountAllJailFsFromHost(jail *Jail) error {
procfsFound := false
linProcfsFound := false
devfsFound := false
fdescfsFound := false
cmd := "mount -p"
out, err := executeCommand(cmd)
if err != nil {
return errors.New(fmt.Sprintf("Error executing mount: %s", err.Error()))
}
var outclean []string
remSpPtrn := regexp.MustCompile(`\s+`)
for _, l := range strings.Split(out, "\n") {
outclean = append(outclean, remSpPtrn.ReplaceAllString(l, " "))
}
// Check if these FS are already mounted
for _, l := range outclean {
f := strings.Split(l, " ")
if len(f) > 2 {
if strings.EqualFold(f[1], fmt.Sprintf("%s/proc", jail.RootPath)) {
procfsFound = true
}
if strings.EqualFold(f[1], fmt.Sprintf("%s/compat/linux/proc", jail.RootPath)) {
linProcfsFound = true
}
if strings.EqualFold(f[1], fmt.Sprintf("%s/dev", jail.RootPath)) {
devfsFound = true
}
if strings.EqualFold(f[1], fmt.Sprintf("%s/dev/fd", jail.RootPath)) {
fdescfsFound = true
}
}
}
// Mount wanted FS
if jail.Config.Mount_procfs > 0 && procfsFound == false {
err := mountProcFs(jail)
if err != nil {
return err
}
}
if jail.Config.Mount_linprocfs > 0 && linProcfsFound == false {
err = mountLinProcFs(jail)
if err != nil {
return err
}
}
if jail.Config.Mount_devfs > 0 && devfsFound == false {
err := mountDevFs(jail)
if err != nil {
return err
}
}
if jail.Config.Mount_fdescfs > 0 && fdescfsFound == false {
err := mountFdescFs(jail)
if err != nil {
return err
}
}
// Ces montages doivent-ils etre effectués une fois le jail démarré?
// FreeBSD <= 9.3 do not support fdescfs
//if gHostVersion <= 9.3 && jail.Config.Allow_mount_tmpfs > 0 {
if gHostVersion <= 9.3 && jail.Config.Allow_mount_tmpfs > 0 {
fmt.Printf(" FreeBSD <= 9.3 does not support tmpfs, disabling in config\n")
jail.Config.Allow_mount_tmpfs = 0
// Tag config so it will be synced on disk
jail.ConfigUpdated = true
err = setJailConfigUpdated(jail)
if err != nil {
fmt.Printf(fmt.Sprintf("Error updating config for jail %s: %s", jail.Name, err.Error()))
return err
}
}
if gHostVersion < 12 {
if jail.Config.Allow_mlock > 0 {
jail.Config.Allow_mlock = 0
jail.ConfigUpdated = true
/* WIP
err = setJailProperty(jail, "Config.Allow_mlock", "0")
if err != nil {
return err
}*/
}
if jail.Config.Allow_mount_fusefs > 0 {
}
if jail.Config.Allow_vmm > 0 {
}
}
return nil
}
// TODO
func prepareJailedZfsDatasets(jail *Jail) error {
if jail.Config.Jail_zfs > 0 {
// For jail to mount filesystem, enforce_statfs should be 1 or lower (2 is the default)
// TODO : Write these changes in jail config file
jail.Config.Allow_mount = 1
jail.Config.Allow_mount_zfs = 1
// TODO : Overload Json Unmarshalling to fix bad typed values, keeping iocage compatibility
if jail.Config.Enforce_statfs > "1" {
jail.Config.Enforce_statfs = "1"
}
for _, d := range strings.Split(jail.Config.Jail_zfs_dataset, " ") {
// Check if dataset exist, create if necessary
cmd := fmt.Sprintf("zfs get -H creation %s/%s", jail.Zpool, d)
out, err := executeCommand(cmd)
if err != nil {
if strings.HasSuffix(out, "dataset does not exist") {
cmd = fmt.Sprintf("zfs create -o compression=lz4 -o mountpoint=none %s/%s", jail.Zpool, d)
_, err = executeCommand(cmd)
if err != nil {
return errors.New(fmt.Sprintf("Error creating dataset %s/%s: %s", jail.Zpool, d, err.Error()))
}
} else {
return errors.New(fmt.Sprintf("Error getting zfs dataset %s: %s", d, err.Error()))
}
}
cmd = fmt.Sprintf("zfs set jailed=on %s/%s", jail.Zpool, d)
out, err = executeCommand(cmd)
if err != nil {
return errors.New(fmt.Sprintf("Error executing \"zfs set jailed=on %s/%s\": %s", jail.Zpool, d, err.Error()))
}
// TODO : Execute "zfs jail $jailname $dataset" when jail will be up
}
}
return nil
}
/*
Start jail:
Check jail fstab?
Mount procfs
Mount linprocfs
Mount devfs?
Mount fdescfs?
If jail_zfs, then check jail_zfs_dataset exist (and create otherwise)
TODO : Check NAT settings and compatibility with other jails
Generate devfsruleset from configured
Write config file in /var/run/jails.ioc-$NAME.conf
Execute PreStart (Exec_prestart)
Start jail (With ENV VARS for network conf)
Start networking
Mount jail_zfs_datasets inside jail
Generate resolv.Conf
Copy /etc/localtime into jail (?)
Configure NAT
Execute Exec_start into jail
Execute Exec_poststart
If DHCP, check with ifconfig inside jail
Set RCTL Rules
Use setfib for each jail command
*/
func StartJail(args []string) {
// jail we have to start
var cj *Jail
for _, j := range args {
fmt.Printf("> Starting jail %s\n", j)
for i, rj := range gJails {
if rj.Name == j {
// Get jail reference, not a copy of it; So we can modify attributes
cj = &gJails[i]
break
}
}
if cj == nil {
fmt.Printf("Jail not found: %s\n", j)
continue
}
if cj.Running == true {
fmt.Printf("Jail %s is already running!\n", cj.Name)
continue
}
fmt.Printf(" > Mount special filesystems:\n")
err := mountAllJailFsFromHost(cj)
if err != nil {
fmt.Printf("ERROR: %s\n", err.Error())
} else {
fmt.Printf(" > Mount special filesystems: OK\n")
}
if cj.Config.Jail_zfs > 0 {
fmt.Printf(" > Prepare ZFS Datasets:\n")
err := prepareJailedZfsDatasets(cj)
if err != nil {
fmt.Printf("ERROR: %s\n", err.Error())
} else {
fmt.Printf(" > Prepare ZFS Datasets: OK\n")
}
}
/*
out, err := executeCommand(fmt.Sprintf("rctl jail:%s", cj.InternalName))
if err == nil && len(out) > 0 {
fmt.Printf(" > Remove RCTL rules:\n")
err := removeRctlRules(cj.InternalName, []string{""})
if err != nil {
fmt.Printf("ERROR: %s\n", err.Error())
} else {
fmt.Printf(" > Remove RCTL rules: OK\n")
}
}
if len (cj.Config.Exec_prestop) > 0 {
fmt.Printf(" > Execute prestop:\n")
_, err := executeCommand(cj.Config.Exec_prestop)
if err != nil {
fmt.Printf("ERROR: %s\n", err.Error())
} else {
fmt.Printf(" > Execute prestop: OK\n")
}
}
if len (cj.Config.Exec_stop) > 0 {
fmt.Printf(" > Execute stop:\n")
_, err := executeCommandInJail(cj, cj.Config.Exec_stop)
if err != nil {
fmt.Printf("ERROR: %s\n", err.Error())
} else {
fmt.Printf(" > Execute stop: OK\n")
}
}
if cj.Config.Jail_zfs > 0 {
fmt.Printf(" > Umount jailed ZFS:\n")
err := umountAndUnjailZFS(cj)
if err != nil {
fmt.Printf("ERROR: %s\n", err.Error())
} else {
fmt.Printf(" > Umount jailed ZFS: OK\n")
}
}
if cj.Config.Vnet > 0 && len(cj.Config.Ip4_addr) > 0 {
fmt.Printf(" > Destroy VNet interfaces:\n")
err := destroyVNetInterfaces(cj)
if err != nil {
fmt.Printf("ERROR: %s\n", err.Error())
} else {
fmt.Printf(" > Destroy VNet interfaces: OK\n")
}
}
fmt.Printf(" > Remove devfsruleset %s:\n", cj.Config.Devfs_ruleset)
err = deleteDevfsRuleset(cj)
if err != nil {
fmt.Printf("ERROR: %s\n", err.Error())
} else {
fmt.Printf(" > Remove devfsruleset %s: OK\n", cj.Config.Devfs_ruleset)
}
fmt.Printf(" > Stop jail %s:\n", cj.Name)
err = stopJail(cj)
if err != nil {
fmt.Printf("ERROR: %s\n", err.Error())
} else {
fmt.Printf(" > Stop jail %s: OK\n", cj.Name)
}
*/
}
}

View File

@ -95,7 +95,12 @@ func destroyVNetInterfaces(jail *Jail) error {
return nil return nil
} }
// Jails copy the ruleset referenced as "devfs_ruleset" when starting, getting a new devsf_ruleset ID.
// This new ID can be obtained with 'jls -j $JID devfs_ruleset'
// This is the ID which needs to be removed. Original ID referenced is json should not be deleted
// or else it will require a restart of "devfs" service.
// But, stoppign the jail already removes this >1000 ID.
// So no need to call this function.
func deleteDevfsRuleset(jail *Jail) error { func deleteDevfsRuleset(jail *Jail) error {
cmd := "devfs rule showsets" cmd := "devfs rule showsets"
out, err := executeCommand(cmd) out, err := executeCommand(cmd)
@ -256,13 +261,13 @@ func StopJail(args []string) {
} }
} }
fmt.Printf(" > Remove devfsruleset %s:\n", cj.Config.Devfs_ruleset) /*fmt.Printf(" > Remove devfsruleset %s:\n", cj.Config.Devfs_ruleset)
err = deleteDevfsRuleset(cj) err = deleteDevfsRuleset(cj)
if err != nil { if err != nil {
fmt.Printf("ERROR: %s\n", err.Error()) fmt.Printf("ERROR: %s\n", err.Error())
} else { } else {
fmt.Printf(" > Remove devfsruleset %s: OK\n", cj.Config.Devfs_ruleset) fmt.Printf(" > Remove devfsruleset %s: OK\n", cj.Config.Devfs_ruleset)
} }*/
fmt.Printf(" > Stop jail %s:\n", cj.Name) fmt.Printf(" > Stop jail %s:\n", cj.Name)
err = stopJail(cj) err = stopJail(cj)
@ -311,6 +316,28 @@ func StopJail(args []string) {
fmt.Printf(" > Umount devfs: OK\n") fmt.Printf(" > Umount devfs: OK\n")
} }
} }
// Remove local mounts from $JAIL/fstab
// The way we get fstab is presumptuous
fstab := strings.Replace(cj.ConfigPath, "config.json", "fstab", 1)
mounts, err := getFstab(fstab)
if err != nil {
fmt.Printf("ERROR: %s\n", err.Error())
}
if len(mounts) > 0 {
fmt.Printf(" > Umount mountpoints from %s\n", fstab)
errs := 0
for _, m := range mounts {
err = umountJailFsFromHost(cj, m.Mountpoint)
if err != nil {
fmt.Printf("ERROR: %s\n", err.Error())
errs += 1
}
}
if errs == 0 {
fmt.Printf(" > Umount mountpoints from %s: OK\n", fstab)
}
}
} }
} }

View File

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

@ -1,9 +1,16 @@
package cmd package cmd
import ( import (
"os"
"fmt"
"log"
"sort" "sort"
"bufio"
"errors"
"os/exec" "os/exec"
"reflect"
"strings" "strings"
"strconv"
) )
/***************************************************************************** /*****************************************************************************
@ -57,6 +64,647 @@ func executeCommandInJail(jail *Jail, cmdline string) (string, error) {
return string(out), err 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. : 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")
}
/***************************************************************************** /*****************************************************************************
* *
@ -64,9 +712,10 @@ func executeCommandInJail(jail *Jail, cmdline string) (string, error) {
* *
*****************************************************************************/ *****************************************************************************/
// already defined in cmd/struct.go // This struct hold "sort by jail fields" functions
//type lessFunc func(j1 *Jail, j2 *Jail) bool type jailLessFunc func(j1 *Jail, j2 *Jail) bool
func initSortStruct() JailSort {
func initJailSortStruct() JailSort {
jcs := JailConfigSort{ jcs := JailConfigSort{
Allow_chflagsInc: func(j1, j2 *Jail) bool { Allow_chflagsInc: func(j1, j2 *Jail) bool {
return j1.Config.Allow_chflags < j2.Config.Allow_chflags return j1.Config.Allow_chflags < j2.Config.Allow_chflags
@ -935,34 +1584,34 @@ func initSortStruct() JailSort {
return js return js
} }
// multiSorter implements the Sort interface, sorting the jails within. // jailSorter implements the Sort interface, sorting the jails within.
type multiSorter struct { type jailSorter struct {
jails []Jail jails []Jail
less []lessFunc less []jailLessFunc
} }
// Sort sorts the argument slice according to the less functions passed to OrderedBy. // Sort sorts the argument slice according to the less functions passed to JailsOrderedBy.
func (ms *multiSorter) Sort(jails []Jail) { func (js *jailSorter) Sort(jails []Jail) {
ms.jails = jails js.jails = jails
sort.Sort(ms) 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. // Call its Sort method to sort the data.
func OrderedBy(less ...lessFunc) *multiSorter { func JailsOrderedBy(less ...jailLessFunc) *jailSorter {
return &multiSorter{ return &jailSorter{
less: less, less: less,
} }
} }
// Len is part of sort.Interface. // Len is part of sort.Interface.
func (ms *multiSorter) Len() int { func (js *jailSorter) Len() int {
return len(ms.jails) return len(js.jails)
} }
// Swap is part of sort.Interface. // Swap is part of sort.Interface.
func (ms *multiSorter) Swap(i, j int) { func (js *jailSorter) Swap(i, j int) {
ms.jails[i], ms.jails[j] = ms.jails[j], ms.jails[i] js.jails[i], js.jails[j] = js.jails[j], js.jails[i]
} }
// Less is part of sort.Interface. It is implemented by looping along the // Less is part of sort.Interface. It is implemented by looping along the
@ -971,12 +1620,12 @@ func (ms *multiSorter) Swap(i, j int) {
// less functions twice per call. We could change the functions to return // 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 // -1, 0, 1 and reduce the number of calls for greater efficiency: an
// exercise for the reader. // exercise for the reader.
func (ms *multiSorter) Less(i, j int) bool { func (js *jailSorter) Less(i, j int) bool {
p, q := &ms.jails[i], &ms.jails[j] p, q := &js.jails[i], &js.jails[j]
// Try all but the last comparison. // Try all but the last comparison.
var k int var k int
for k = 0; k < len(ms.less)-1; k++ { for k = 0; k < len(js.less)-1; k++ {
less := ms.less[k] less := js.less[k]
switch { switch {
case less(p, q): case less(p, q):
// p < q, so we have a decision. // p < q, so we have a decision.
@ -989,7 +1638,136 @@ func (ms *multiSorter) Less(i, j int) bool {
} }
// All comparisons to here said "equal", so just return whatever // All comparisons to here said "equal", so just return whatever
// the final comparison reports. // 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
}