3451 lines
96 KiB
Go
3451 lines
96 KiB
Go
package cmd
|
|
|
|
import (
|
|
"io"
|
|
"os"
|
|
"fmt"
|
|
//"log"
|
|
"sort"
|
|
"bufio"
|
|
"errors"
|
|
"os/exec"
|
|
"reflect"
|
|
"strconv"
|
|
"strings"
|
|
"io/ioutil"
|
|
"encoding/json"
|
|
"github.com/google/shlex"
|
|
"github.com/c2h5oh/datasize"
|
|
log "github.com/sirupsen/logrus"
|
|
)
|
|
|
|
const (
|
|
ipv4re = `[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}`
|
|
ifconfigipv4re = `inet[[:space:]](` + ipv4re + `)`
|
|
// Maximum thread qty for start/stop
|
|
gMaxThreads = 4
|
|
|
|
fbsdUpdateConfig = `# $FreeBSD$
|
|
|
|
# Trusted keyprint. Changing this is a Bad Idea unless you've received
|
|
# a PGP-signed email from <security-officer@FreeBSD.org> telling you to
|
|
# change it and explaining why.
|
|
KeyPrint 800651ef4b4c71c27e60786d7b487188970f4b4169cc055784e21eb71d410cc5
|
|
|
|
# Server or server pool from which to fetch updates. You can change
|
|
# this to point at a specific server if you want, but in most cases
|
|
# using a "nearby" server won't provide a measurable improvement in
|
|
# performance.
|
|
ServerName update.FreeBSD.org
|
|
|
|
# Components of the base system which should be kept updated.
|
|
Components world
|
|
|
|
# Example for updating the userland and the kernel source code only:
|
|
# Components src/base src/sys world
|
|
|
|
# Paths which start with anything matching an entry in an IgnorePaths
|
|
# statement will be ignored.
|
|
IgnorePaths
|
|
|
|
# Paths which start with anything matching an entry in an IDSIgnorePaths
|
|
# statement will be ignored by "freebsd-update IDS".
|
|
IDSIgnorePaths /usr/share/man/cat
|
|
IDSIgnorePaths /usr/share/man/whatis
|
|
IDSIgnorePaths /var/db/locate.database
|
|
IDSIgnorePaths /var/log
|
|
|
|
# Paths which start with anything matching an entry in an UpdateIfUnmodified
|
|
# statement will only be updated if the contents of the file have not been
|
|
# modified by the user (unless changes are merged; see below).
|
|
UpdateIfUnmodified /etc/ /var/ /root/ /.cshrc /.profile
|
|
|
|
# When upgrading to a new FreeBSD release, files which match MergeChanges
|
|
# will have any local changes merged into the version from the new release.
|
|
MergeChanges /etc/
|
|
|
|
### Default configuration options:
|
|
|
|
# Directory in which to store downloaded updates and temporary
|
|
# files used by FreeBSD Update.
|
|
WorkDir /iocage/freebsd-update
|
|
|
|
# Destination to send output of "freebsd-update cron" if an error
|
|
# occurs or updates have been downloaded.
|
|
# MailTo root
|
|
|
|
# Is FreeBSD Update allowed to create new files?
|
|
# AllowAdd yes
|
|
|
|
# Is FreeBSD Update allowed to delete files?
|
|
# AllowDelete yes
|
|
|
|
# If the user has modified file ownership, permissions, or flags, should
|
|
# FreeBSD Update retain this modified metadata when installing a new version
|
|
# of that file?
|
|
# KeepModifiedMetadata yes
|
|
|
|
# When upgrading between releases, should the list of Components be
|
|
# read strictly (StrictComponents yes) or merely as a list of components
|
|
# which *might* be installed of which FreeBSD Update should figure out
|
|
# which actually are installed and upgrade those (StrictComponents no)?
|
|
StrictComponents yes
|
|
|
|
# When installing a new kernel perform a backup of the old one first
|
|
# so it is possible to boot the old kernel in case of problems.
|
|
BackupKernel no
|
|
|
|
# If BackupKernel is enabled, the backup kernel is saved to this
|
|
# directory.
|
|
# BackupKernelDir /boot/kernel.old
|
|
|
|
# When backing up a kernel also back up debug symbol files?
|
|
BackupKernelSymbolFiles no
|
|
|
|
# Create a new boot environment when installing patches
|
|
CreateBootEnv no
|
|
`
|
|
|
|
gDefaultsJson = `{
|
|
"CONFIG_VERSION": "27",
|
|
"allow_chflags": 0,
|
|
"allow_mlock": 0,
|
|
"allow_mount": 0,
|
|
"allow_mount_devfs": 0,
|
|
"allow_mount_fusefs": 0,
|
|
"allow_mount_nullfs": 0,
|
|
"allow_mount_procfs": 0,
|
|
"allow_mount_tmpfs": 0,
|
|
"allow_mount_zfs": 0,
|
|
"allow_quotas": 0,
|
|
"allow_raw_sockets": 0,
|
|
"allow_set_hostname": 1,
|
|
"allow_socket_af": 0,
|
|
"allow_sysvipc": 0,
|
|
"allow_tun": 0,
|
|
"allow_vmm": 0,
|
|
"assign_localhost": 0,
|
|
"available": "readonly",
|
|
"basejail": 0,
|
|
"boot": 0,
|
|
"bpf": 0,
|
|
"children_max": "0",
|
|
"comment": "none",
|
|
"compression": "lz4",
|
|
"compressratio": "readonly",
|
|
"coredumpsize": "off",
|
|
"count": "1",
|
|
"cpuset": "off",
|
|
"cputime": "off",
|
|
"datasize": "off",
|
|
"dedup": "off",
|
|
"defaultrouter": "auto",
|
|
"defaultrouter6": "auto",
|
|
"depends": "none",
|
|
"devfs_ruleset": "4",
|
|
"dhcp": 0,
|
|
"enforce_statfs": "2",
|
|
"exec_clean": 1,
|
|
"exec_created": "/usr/bin/true",
|
|
"exec_fib": "0",
|
|
"exec_jail_user": "root",
|
|
"exec_poststart": "/usr/bin/true",
|
|
"exec_poststop": "/usr/bin/true",
|
|
"exec_prestart": "/usr/bin/true",
|
|
"exec_prestop": "/usr/bin/true",
|
|
"exec_start": "/bin/sh /etc/rc",
|
|
"exec_stop": "/bin/sh /etc/rc.shutdown",
|
|
"exec_system_jail_user": "0",
|
|
"exec_system_user": "root",
|
|
"exec_timeout": "60",
|
|
"host_domainname": "none",
|
|
"host_time": 1,
|
|
"hostid": "TO-BE-REPLACED-WITH-HOSTID",
|
|
"hostid_strict_check": 0,
|
|
"interfaces": "vnet0:TO-BE-REPLACED-WITH-BRIDGE",
|
|
"ip4": "new",
|
|
"ip4_addr": "none",
|
|
"ip4_saddrsel": 1,
|
|
"ip6": "new",
|
|
"ip6_addr": "none",
|
|
"ip6_saddrsel": 1,
|
|
"ip_hostname": 0,
|
|
"jail_zfs": 0,
|
|
"jail_zfs_mountpoint": "none",
|
|
"last_started": "none",
|
|
"localhost_ip": "none",
|
|
"login_flags": "-f root",
|
|
"mac_prefix": "2c44fd",
|
|
"maxproc": "off",
|
|
"memorylocked": "off",
|
|
"memoryuse": "off",
|
|
"min_dyn_devfs_ruleset": "1000",
|
|
"mount_devfs": 1,
|
|
"mount_fdescfs": 1,
|
|
"mount_linprocfs": 0,
|
|
"mount_procfs": 0,
|
|
"mountpoint": "readonly",
|
|
"msgqqueued": "off",
|
|
"msgqsize": "off",
|
|
"nat": 0,
|
|
"nat_backend": "ipfw",
|
|
"nat_forwards": "none",
|
|
"nat_interface": "none",
|
|
"nat_prefix": "172.16",
|
|
"nmsgq": "off",
|
|
"notes": "none",
|
|
"nsem": "off",
|
|
"nsemop": "off",
|
|
"nshm": "off",
|
|
"nthr": "off",
|
|
"openfiles": "off",
|
|
"origin": "readonly",
|
|
"owner": "root",
|
|
"pcpu": "off",
|
|
"plugin_name": "none",
|
|
"plugin_repository": "none",
|
|
"priority": "99",
|
|
"pseudoterminals": "off",
|
|
"quota": "none",
|
|
"readbps": "off",
|
|
"readiops": "off",
|
|
"reservation": "none",
|
|
"resolver": "/etc/resolv.conf",
|
|
"rlimits": "off",
|
|
"rtsold": 0,
|
|
"securelevel": "2",
|
|
"shmsize": "off",
|
|
"stacksize": "off",
|
|
"stop_timeout": "30",
|
|
"swapuse": "off",
|
|
"sync_state": "none",
|
|
"sync_target": "none",
|
|
"sync_tgt_zpool": "none",
|
|
"sysvmsg": "new",
|
|
"sysvsem": "new",
|
|
"sysvshm": "new",
|
|
"template": 0,
|
|
"type": "jail",
|
|
"used": "readonly",
|
|
"vmemoryuse": "off",
|
|
"vnet": 0,
|
|
"vnet0_mac": "none",
|
|
"vnet1_mac": "none",
|
|
"vnet2_mac": "none",
|
|
"vnet3_mac": "none",
|
|
"vnet_default_interface": "auto",
|
|
"vnet_interfaces": "none",
|
|
"wallclock": "off",
|
|
"writebps": "off",
|
|
"writeiops": "off"
|
|
}
|
|
`
|
|
)
|
|
|
|
/*****************************************************************************
|
|
* Mandatory constructor for JailConfig type. It set default values
|
|
*****************************************************************************/
|
|
func NewJailConfig() (JailConfig, error) {
|
|
var jc JailConfig
|
|
|
|
hostid, err := ioutil.ReadFile("/etc/hostid")
|
|
if err != nil {
|
|
return jc, err
|
|
} else {
|
|
hostid = []byte(strings.Replace(string(hostid), "\n", "", -1))
|
|
}
|
|
|
|
jc.Allow_chflags = 0
|
|
jc.Allow_mlock = 0
|
|
jc.Allow_mount = 0
|
|
jc.Allow_mount_devfs = 0
|
|
jc.Allow_mount_fusefs = 0
|
|
jc.Allow_mount_nullfs = 0
|
|
jc.Allow_mount_procfs = 0
|
|
jc.Allow_mount_tmpfs = 0
|
|
jc.Allow_mount_zfs = 0
|
|
jc.Allow_quotas = 0
|
|
jc.Allow_raw_sockets = 0
|
|
jc.Allow_socket_af = 0
|
|
jc.Allow_set_hostname = 1
|
|
jc.Allow_sysvipc = 0
|
|
jc.Allow_tun = 0
|
|
jc.Allow_vmm = 0
|
|
jc.Assign_localhost = 0
|
|
jc.Available = "readonly"
|
|
jc.Basejail = 0
|
|
jc.Bpf = 0
|
|
jc.Boot = 0
|
|
jc.Children_max = "0"
|
|
jc.Comment = "none"
|
|
jc.Compression = "lz4"
|
|
jc.Compressratio = "readonly"
|
|
jc.Coredumpsize = "off"
|
|
jc.Count = "1"
|
|
jc.Cpuset = "off"
|
|
jc.Cputime = "off"
|
|
jc.Datasize = "off"
|
|
jc.Dedup = "off"
|
|
jc.Defaultrouter = "auto"
|
|
jc.Defaultrouter6 = "auto"
|
|
jc.Depends = "none"
|
|
jc.Devfs_ruleset = "4"
|
|
jc.Dhcp = 0
|
|
jc.Enforce_statfs = "2"
|
|
jc.Exec_clean = 1
|
|
jc.Exec_created = "/usr/bin/true"
|
|
jc.Exec_jail_user = "root"
|
|
jc.Exec_fib = "0"
|
|
jc.Exec_poststart = "/usr/bin/true"
|
|
jc.Exec_poststop = "/usr/bin/true"
|
|
jc.Exec_prestart = "/usr/bin/true"
|
|
jc.Exec_prestop = "/usr/bin/true"
|
|
jc.Exec_system_jail_user = "0"
|
|
jc.Exec_system_user = "root"
|
|
jc.Exec_start = "/bin/sh /etc/rc"
|
|
jc.Exec_stop = "/bin/sh /etc/rc.shutdown"
|
|
jc.Exec_timeout = "60"
|
|
jc.Hostid = string(hostid)
|
|
jc.Hostid_strict_check = 0
|
|
jc.Host_time = 1
|
|
jc.Interfaces = "vnet0:bridge0"
|
|
jc.Ip4_addr = "none"
|
|
jc.Ip4_saddrsel = "1"
|
|
jc.Ip4 = "new"
|
|
jc.Ip6_addr = "none"
|
|
jc.Ip6_saddrsel = "1"
|
|
jc.Ip6 = "new"
|
|
jc.Ip_hostname = 0
|
|
jc.Jailtype = "jail"
|
|
jc.Jail_zfs = 0
|
|
jc.Jail_zfs_mountpoint = "none"
|
|
jc.Last_started = "none"
|
|
jc.Localhost_ip = "none"
|
|
jc.Login_flags = "-f root"
|
|
jc.Maxproc = "off"
|
|
jc.Min_dyn_devfs_ruleset = "1000"
|
|
jc.Memoryuse = "off"
|
|
jc.Memorylocked = "off"
|
|
jc.Mountpoint = "readonly"
|
|
jc.Mount_devfs = 1
|
|
jc.Mount_fdescfs = 1
|
|
jc.Mount_procfs = 0
|
|
jc.Mount_linprocfs = 0
|
|
jc.Msgqqueued = "off"
|
|
jc.Msgqsize = "off"
|
|
jc.Nat = 0
|
|
jc.Nat_backend = "ipfw"
|
|
jc.Nat_forwards = "none"
|
|
jc.Nat_interface = "none"
|
|
jc.Nat_prefix = "172.16"
|
|
jc.Nmsgq = "off"
|
|
jc.Notes = "none"
|
|
jc.Nsem = "off"
|
|
jc.Nsemop = "off"
|
|
jc.Nshm = "off"
|
|
jc.Nthr = "off"
|
|
jc.Openfiles = "off"
|
|
jc.Origin = "readonly"
|
|
jc.Owner = "root"
|
|
jc.Pcpu = "off"
|
|
jc.Plugin_name = "none"
|
|
jc.Plugin_repository = "none"
|
|
jc.Priority = "99"
|
|
jc.Pseudoterminals = "off"
|
|
jc.Quota = "none"
|
|
jc.Readbps = "off"
|
|
jc.Readiops = "off"
|
|
jc.Reservation = "none"
|
|
jc.Resolver = "/etc/resolv.conf"
|
|
jc.Rlimits = "off"
|
|
jc.Rtsold = 0
|
|
jc.Securelevel = "2"
|
|
jc.Shmsize = "off"
|
|
jc.Stacksize = "off"
|
|
jc.Stop_timeout = "30"
|
|
jc.Sync_state = "none"
|
|
jc.Sync_target = "none"
|
|
jc.Sync_tgt_zpool = "none"
|
|
jc.Sysvmsg = "new"
|
|
jc.Sysvsem = "new"
|
|
jc.Sysvshm = "new"
|
|
jc.Swapuse = "off"
|
|
jc.Template = 0
|
|
jc.Used = "readonly"
|
|
jc.Vmemoryuse = "off"
|
|
jc.Vnet = 0
|
|
jc.Vnet0_mac = "none"
|
|
jc.Vnet1_mac = "none"
|
|
jc.Vnet2_mac = "none"
|
|
jc.Vnet3_mac = "none"
|
|
jc.Vnet_default_interface = "auto"
|
|
jc.Vnet_interfaces = "none"
|
|
jc.Wallclock = "off"
|
|
jc.Writebps = "off"
|
|
jc.Writeiops = "off"
|
|
|
|
return jc, nil
|
|
}
|
|
|
|
/*****************************************************************************
|
|
*
|
|
* Command execution
|
|
*
|
|
*****************************************************************************/
|
|
func executeCommand(cmdline string) (string, error) {
|
|
var cmd []string
|
|
var out []byte
|
|
var err error
|
|
|
|
if gUseSudo {
|
|
cmd = append(cmd, "sudo")
|
|
}
|
|
|
|
var word string
|
|
var in_escaped bool
|
|
// Split by words, or " enclosed words
|
|
for i, c := range (cmdline) {
|
|
if string(c) == "\"" {
|
|
if in_escaped {
|
|
// This is the closing "
|
|
cmd = append(cmd, word)
|
|
in_escaped = false
|
|
} else {
|
|
in_escaped = true
|
|
}
|
|
continue
|
|
}
|
|
if string(c) == " " {
|
|
if in_escaped {
|
|
word = word + string(c)
|
|
continue
|
|
} else {
|
|
cmd = append(cmd, word)
|
|
word = ""
|
|
continue
|
|
}
|
|
}
|
|
if i == (len(cmdline) - 1) {
|
|
word = word + string(c)
|
|
cmd = append(cmd, word)
|
|
break
|
|
}
|
|
|
|
// else
|
|
word = word + string(c)
|
|
}
|
|
|
|
log.Debugf("executeCommand: %s\n", strings.Join(cmd, " "))
|
|
|
|
if len(cmd) > 1 {
|
|
out, err = exec.Command(cmd[0], cmd[1:]...).CombinedOutput()
|
|
} else {
|
|
out, err = exec.Command(cmd[0]).CombinedOutput()
|
|
}
|
|
|
|
return strings.TrimSuffix(string(out), "\n"), err
|
|
}
|
|
|
|
/* From iocage:
|
|
* # Courtesy of @william-gr
|
|
* # service(8) and some rc.d scripts have the bad h*abit of
|
|
* # exec'ing and never closing stdout/stderr. This makes
|
|
* # sure we read only enough until the command exits and do
|
|
* # not wait on the pipe to close on the other end.
|
|
* So this function executes process without waiting after completion
|
|
*/
|
|
func executeCommandNonBlocking(cmdline string) (error) {
|
|
var cmd []string
|
|
var oCmd *exec.Cmd
|
|
var err error
|
|
|
|
if gUseSudo {
|
|
cmd = append(cmd, "sudo")
|
|
}
|
|
|
|
var word string
|
|
var in_escaped bool
|
|
// Split by words, or " enclosed words
|
|
for i, c := range (cmdline) {
|
|
if string(c) == "\"" {
|
|
if in_escaped {
|
|
// This is the closing "
|
|
cmd = append(cmd, word)
|
|
in_escaped = false
|
|
} else {
|
|
in_escaped = true
|
|
}
|
|
continue
|
|
}
|
|
if string(c) == " " {
|
|
if in_escaped {
|
|
word = word + string(c)
|
|
continue
|
|
} else {
|
|
cmd = append(cmd, word)
|
|
word = ""
|
|
continue
|
|
}
|
|
}
|
|
if i == (len(cmdline) - 1) {
|
|
word = word + string(c)
|
|
cmd = append(cmd, word)
|
|
break
|
|
}
|
|
|
|
// else
|
|
word = word + string(c)
|
|
}
|
|
|
|
if len(cmd) > 1 {
|
|
oCmd = exec.Command(cmd[0], cmd[1:]...)
|
|
} else {
|
|
oCmd = exec.Command(cmd[0])
|
|
}
|
|
|
|
if err = oCmd.Start(); err != nil {
|
|
return err
|
|
}
|
|
|
|
err = oCmd.Wait()
|
|
|
|
return err
|
|
}
|
|
|
|
// Executed command outputs to stdout in realtime
|
|
func executeCommandWithOutputToStdout(cmdline string) (error) {
|
|
var cmd []string
|
|
var err error
|
|
|
|
if gUseSudo {
|
|
cmd = append(cmd, "sudo")
|
|
}
|
|
|
|
var word string
|
|
var in_escaped bool
|
|
// Split by words, or " enclosed words
|
|
for i, c := range (cmdline) {
|
|
if string(c) == "\"" {
|
|
if in_escaped {
|
|
// This is the closing "
|
|
cmd = append(cmd, word)
|
|
in_escaped = false
|
|
} else {
|
|
in_escaped = true
|
|
}
|
|
continue
|
|
}
|
|
if string(c) == " " {
|
|
if in_escaped {
|
|
word = word + string(c)
|
|
continue
|
|
} else {
|
|
cmd = append(cmd, word)
|
|
word = ""
|
|
continue
|
|
}
|
|
}
|
|
if i == (len(cmdline) - 1) {
|
|
word = word + string(c)
|
|
cmd = append(cmd, word)
|
|
break
|
|
}
|
|
|
|
// else
|
|
word = word + string(c)
|
|
}
|
|
|
|
log.Debugf("executeCommandWithOutputToStdout: will execute \"%s\"\n", strings.Join(cmd, " "))
|
|
|
|
var execHandle *exec.Cmd
|
|
if len(cmd) > 1 {
|
|
execHandle = exec.Command(cmd[0], cmd[1:]...)
|
|
} else {
|
|
execHandle = exec.Command(cmd[0])
|
|
}
|
|
stdout, err := execHandle.StdoutPipe()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
execHandle.Start()
|
|
|
|
buf := bufio.NewReader(stdout)
|
|
for {
|
|
line, _, err := buf.ReadLine()
|
|
if err != nil {
|
|
if err.Error() == "EOF" {
|
|
return nil
|
|
} else {
|
|
return err
|
|
}
|
|
}
|
|
fmt.Println(string(line))
|
|
}
|
|
|
|
return fmt.Errorf("Unknown error: you shouldn't be here!\n")
|
|
}
|
|
|
|
/* Execute command plugging stdin and stdout to those of the running command.
|
|
* Blocking while the command run
|
|
*/
|
|
func executeCommandWithStdinStdoutStderr(cmdline string) (error) {
|
|
var cmd []string
|
|
|
|
if gUseSudo {
|
|
cmd = append(cmd, "sudo")
|
|
}
|
|
|
|
var word string
|
|
var in_escaped bool
|
|
// Split by words, or " enclosed words
|
|
for i, c := range (cmdline) {
|
|
if string(c) == "\"" {
|
|
if in_escaped {
|
|
// This is the closing "
|
|
cmd = append(cmd, word)
|
|
in_escaped = false
|
|
} else {
|
|
in_escaped = true
|
|
}
|
|
continue
|
|
}
|
|
if string(c) == " " {
|
|
if in_escaped {
|
|
word = word + string(c)
|
|
continue
|
|
} else {
|
|
cmd = append(cmd, word)
|
|
word = ""
|
|
continue
|
|
}
|
|
}
|
|
if i == (len(cmdline) - 1) {
|
|
word = word + string(c)
|
|
cmd = append(cmd, word)
|
|
break
|
|
}
|
|
|
|
// else
|
|
word = word + string(c)
|
|
}
|
|
|
|
var command *exec.Cmd
|
|
if len(cmd) > 1 {
|
|
command = exec.Command(cmd[0], cmd[1:]...)
|
|
} else {
|
|
command = exec.Command(cmd[0])
|
|
}
|
|
|
|
// Get environment
|
|
command.Env = os.Environ()
|
|
|
|
// Connect command to current stdin/out/err
|
|
command.Stdin = os.Stdin
|
|
command.Stdout = os.Stdout
|
|
command.Stderr = os.Stderr
|
|
|
|
if err := command.Start(); err != nil {
|
|
return err
|
|
}
|
|
|
|
err := command.Wait()
|
|
|
|
return err
|
|
}
|
|
|
|
|
|
func executeCommandInJail(jail *Jail, cmdline string) (string, error) {
|
|
var cmd []string
|
|
|
|
// We can't execute on non-running jail
|
|
if jail.Running == false {
|
|
return "", errors.New("Can't execute command on stopped jail")
|
|
}
|
|
|
|
if gUseSudo {
|
|
cmd = append(cmd, "sudo")
|
|
}
|
|
|
|
cmd = append(cmd, "setfib")
|
|
if len(jail.Config.Exec_fib) > 0 {
|
|
cmd = append(cmd, jail.Config.Exec_fib)
|
|
} else {
|
|
cmd = append(cmd, "0")
|
|
}
|
|
|
|
cmd = append(cmd, "jexec", jail.InternalName)
|
|
|
|
var word string
|
|
var in_escaped bool
|
|
// Split by words, or " enclosed words
|
|
for i, c := range (cmdline) {
|
|
if string(c) == "\"" {
|
|
if in_escaped {
|
|
// This is the closing "
|
|
cmd = append(cmd, word)
|
|
in_escaped = false
|
|
} else {
|
|
in_escaped = true
|
|
}
|
|
continue
|
|
}
|
|
if string(c) == " " {
|
|
if in_escaped {
|
|
word = word + string(c)
|
|
continue
|
|
} else {
|
|
cmd = append(cmd, word)
|
|
word = ""
|
|
continue
|
|
}
|
|
}
|
|
if i == (len(cmdline) - 1) {
|
|
word = word + string(c)
|
|
cmd = append(cmd, word)
|
|
break
|
|
}
|
|
|
|
// else
|
|
word = word + string(c)
|
|
}
|
|
|
|
log.Debugf("executeCommandInJail: will execute \"%s\"\n", strings.Join(cmd, " "))
|
|
|
|
out, err := exec.Command(cmd[0], cmd[1:]...).CombinedOutput()
|
|
|
|
return string(out), err
|
|
}
|
|
|
|
/********************************************************************************
|
|
* Execute a script, or shell command. Need to double escape special characters
|
|
* Ex.: executeScript("echo '\\\\_o\\< \\~ COINCOIN' > /tmp/coincoin.txt")
|
|
*******************************************************************************/
|
|
func executeScript(script string) (string, error) {
|
|
var cmd []string
|
|
var out []byte
|
|
var err error
|
|
|
|
if gUseSudo {
|
|
cmd = append(cmd, "sudo")
|
|
}
|
|
|
|
cmd = append(cmd, []string{"/bin/sh", "-c"}...)
|
|
|
|
s, err := shlex.Split(script)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
cmd = append(cmd, strings.Join(s, " "))
|
|
|
|
//fmt.Printf("DEBUG: Prepare to execute %v\n", cmd)
|
|
|
|
if len(cmd) > 1 {
|
|
out, err = exec.Command(cmd[0], cmd[1:]...).CombinedOutput()
|
|
} else {
|
|
out, err = exec.Command(cmd[0]).CombinedOutput()
|
|
}
|
|
|
|
return string(out), err
|
|
}
|
|
|
|
/*****************************************************************************
|
|
*
|
|
* ZFS datasets/pools operations
|
|
*
|
|
*****************************************************************************/
|
|
func zfsSnapshot(dataset string, snapname string) error {
|
|
cmd := fmt.Sprintf("zfs snapshot %s@%s", dataset, snapname)
|
|
out, err := executeCommand(cmd)
|
|
if err != nil {
|
|
return errors.New(fmt.Sprintf("%v; command returned \"%s\"", err, out))
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Copy snapshot to a new dataset
|
|
// TODO : Intercept death of sending process, then kill receiving
|
|
func zfsCopy(src string, dest string) error {
|
|
// First, declare sending process & pipe
|
|
log.Debugf("Execute: zfs send %s\n", src)
|
|
cmd_send := exec.Command("zfs", "send", src)
|
|
stdout_send, err := cmd_send.StdoutPipe()
|
|
if err != nil {
|
|
//fmt.Printf("Error executing command \"zfs send %s\": %v\n", fmt.Sprintf("%s@gocage_mig_init", dsconf), err)
|
|
return errors.New(fmt.Sprintf("Error: %v\n", err))
|
|
}
|
|
|
|
// then declare receiving process & pipe
|
|
log.Debugf("Execute: zfs receive %s\n", dest)
|
|
cmd_recv := exec.Command("zfs", "receive", dest)
|
|
stdin_recv, err := cmd_recv.StdinPipe()
|
|
if err != nil {
|
|
//fmt.Printf("Error executing command \"zfs receive %s\": %v\n", dest, err)
|
|
return errors.New(fmt.Sprintf("Error: %v\n", err))
|
|
}
|
|
|
|
// Copy data in a go routine
|
|
go io.Copy(stdin_recv, stdout_send)
|
|
|
|
// then start processes and wait for finish
|
|
if err := cmd_recv.Start(); err != nil {
|
|
//fmt.Printf("Error: %v\n", err)
|
|
log.Debugf("zfs receive %s started: %v", dest, err)
|
|
return errors.New(fmt.Sprintf("Error starting receive process: %v\n", err))
|
|
}
|
|
//fmt.Printf("DEBUG: Start \"zfs send %s\"\n", dsconf)
|
|
if err := cmd_send.Start(); err != nil {
|
|
//fmt.Printf("Error: %v\n", err)
|
|
log.Debugf("zfs send %s started: %v", src, err)
|
|
return errors.New(fmt.Sprintf("Error starting send process: %v\n", err))
|
|
}
|
|
|
|
//fmt.Printf("DEBUG: Wait for zfs send to finish\n")
|
|
if err := cmd_send.Wait(); err != nil {
|
|
log.Debugf("zfs send %s stopped with %v", err)
|
|
//fmt.Printf("Error: zfs send halted with %v\n", err)
|
|
return errors.New(fmt.Sprintf("send halted with: %v\n", err))
|
|
}
|
|
//fmt.Printf("DEBUG: Wait for zfs recv to finish\n")
|
|
if err := cmd_recv.Wait(); err != nil {
|
|
//fmt.Printf("Error: zfs recv halted with %v\n", err)
|
|
return errors.New(fmt.Sprintf("receive halted with: %v\n", err))
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Copy incremental snasphot to destination
|
|
func zfsCopyIncremental(firstsnap string, secondsnap string, dest string) error {
|
|
// First, declare sending process & pipe
|
|
cmd_send := exec.Command("zfs", "send", "-i", firstsnap, secondsnap)
|
|
stdout_send, err := cmd_send.StdoutPipe()
|
|
if err != nil {
|
|
return errors.New(fmt.Sprintf("Error: %v\n", err))
|
|
}
|
|
|
|
// then declare receiving process & pipe
|
|
/* "-Fu" flags to avoid "cannot receive incremental stream: destination qstore/iocage/jails/kibana has been modified
|
|
* since most recent snapshot" error message */
|
|
cmd_recv := exec.Command("zfs", "receive", "-Fu", dest)
|
|
stdin_recv, err := cmd_recv.StdinPipe()
|
|
if err != nil {
|
|
return errors.New(fmt.Sprintf("Error: %v\n", err))
|
|
}
|
|
|
|
// Copy data in a go routine
|
|
go io.Copy(stdin_recv, stdout_send)
|
|
|
|
// then start processes and wait for finish
|
|
if err := cmd_recv.Start(); err != nil {
|
|
return errors.New(fmt.Sprintf("Error starting receive process: %v\n", err))
|
|
}
|
|
//fmt.Printf("DEBUG: Start \"zfs send %s\"\n", dsconf)
|
|
if err := cmd_send.Start(); err != nil {
|
|
return errors.New(fmt.Sprintf("Error starting send process: %v\n", err))
|
|
}
|
|
|
|
//fmt.Printf("DEBUG: Wait for zfs send to finish\n")
|
|
if err := cmd_send.Wait(); err != nil {
|
|
return errors.New(fmt.Sprintf("send halted with: %v\n", err))
|
|
}
|
|
//fmt.Printf("DEBUG: Wait for zfs recv to finish\n")
|
|
if err := cmd_recv.Wait(); err != nil {
|
|
return errors.New(fmt.Sprintf("receive halted with: %v\n", err))
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Return true if dataset exist, false if not, false & error if anything else
|
|
func doZfsDatasetExist(dataset string) (bool, error) {
|
|
cmd := fmt.Sprintf("zfs list %s", dataset)
|
|
out, err := executeCommand(cmd)
|
|
if err != nil {
|
|
if strings.HasSuffix(strings.TrimSuffix(out, "\n"), "dataset does not exist") {
|
|
return false, nil
|
|
} else {
|
|
return false, errors.New(fmt.Sprintf("%v; command returned \"%s\"", err, out))
|
|
}
|
|
}
|
|
return true, nil
|
|
}
|
|
|
|
/* Create ZFS dataset
|
|
* mountpoint can be "none", then the dataset won't be mounted
|
|
* mountpoint can be "", then it will be inherited
|
|
* compression can be "", then it wil be inherited
|
|
*/
|
|
func zfsCreateDataset(dataset, mountpoint, compression string) error {
|
|
cmd := "zfs create"
|
|
if len(mountpoint) > 0 {
|
|
cmd = fmt.Sprintf("%s -o mountpoint=%s", cmd, mountpoint)
|
|
}
|
|
if len(compression) > 0 {
|
|
cmd = fmt.Sprintf("%s -o compression=%s", cmd, compression)
|
|
}
|
|
cmd = fmt.Sprintf("%s %s", cmd, dataset)
|
|
out, err := executeCommand(cmd)
|
|
if err != nil {
|
|
return errors.New(fmt.Sprintf("%v; command returned \"%s\"", err, out))
|
|
}
|
|
return nil
|
|
}
|
|
// Return dataset name for a given mountpoint
|
|
func zfsGetDatasetByMountpoint(mountpoint string) (string, error) {
|
|
// We dont want no recursivity
|
|
//cmd := fmt.Sprintf("zfs list -p -r -H -o name %s", mountpoint)
|
|
cmd := fmt.Sprintf("zfs list -p -H -o name %s", mountpoint)
|
|
out, err := executeCommand(cmd)
|
|
if err != nil {
|
|
return "", errors.New(fmt.Sprintf("%v; command returned \"%s\"", err, out))
|
|
}
|
|
|
|
return strings.TrimSuffix(out, "\n"), nil
|
|
}
|
|
|
|
// Delete a ZFS Dataset by name
|
|
func zfsDestroy(dataset string) error {
|
|
log.Debugf("execute \"zfs destroy -r %s\"\n", dataset)
|
|
cmd := fmt.Sprintf("zfs destroy -r %s", dataset)
|
|
out, err := executeCommand(cmd)
|
|
if err != nil {
|
|
return errors.New(fmt.Sprintf("%v; command returned \"%s\"", err, out))
|
|
}
|
|
return nil
|
|
}
|
|
|
|
/*****************************************************************************
|
|
*
|
|
* Filesystem operations
|
|
*
|
|
*****************************************************************************/
|
|
/* Copy file */
|
|
func copyFile(src, dst string) error {
|
|
srcfinfo, err := os.Stat(src)
|
|
if err != nil {
|
|
return fmt.Errorf("Cannot find source file: %s", err.Error())
|
|
}
|
|
if !srcfinfo.Mode().IsRegular() {
|
|
return fmt.Errorf("%s is not a regular file", src)
|
|
}
|
|
|
|
srcHandle, err := os.Open(src)
|
|
if err != nil {
|
|
return fmt.Errorf("Cannot open source file: %s", err.Error())
|
|
}
|
|
defer srcHandle.Close()
|
|
dstHandle, err := os.Create(dst)
|
|
if err != nil {
|
|
return fmt.Errorf("Cannot create destination file: %s", err.Error())
|
|
}
|
|
defer dstHandle.Close()
|
|
_, err = io.Copy(dstHandle, srcHandle)
|
|
return err
|
|
}
|
|
|
|
// Get permissions of file or folder
|
|
func getPermissions(path string) (os.FileInfo, error) {
|
|
return os.Stat(path)
|
|
}
|
|
|
|
func doFileExist(filePath string) (bool, error) {
|
|
if _, err := os.Stat(filePath); err != nil {
|
|
if errors.Is(err, os.ErrNotExist) {
|
|
return false, nil
|
|
} else {
|
|
return false, err
|
|
}
|
|
}
|
|
return true, nil
|
|
}
|
|
|
|
/*****************************************************************************
|
|
*
|
|
* rc.conf management
|
|
*
|
|
*****************************************************************************/
|
|
func enableRcKeyValue(rcconfpath string, key string, value string) error {
|
|
cmd := fmt.Sprintf("/usr/sbin/sysrc -f %s %s=%s", rcconfpath, key, value)
|
|
_, err := executeCommand(cmd)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func disableRcKey(rcconfpath string, key string) error {
|
|
// First check if key exist
|
|
cmd := fmt.Sprintf("/usr/sbin/sysrc -f %s %s", rcconfpath, key)
|
|
out, err := executeCommand(cmd)
|
|
if err != nil {
|
|
if strings.HasPrefix(out, "sysrc: unknown variable") {
|
|
return nil
|
|
} else {
|
|
return err
|
|
}
|
|
}
|
|
|
|
cmd = fmt.Sprintf("/usr/sbin/sysrc -f %s -x %s", rcconfpath, key)
|
|
_, err = executeCommand(cmd)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// returns no error if rc key does not exist
|
|
func getCurrentRcKeyValue(rcconfpath string, key string) (string, error) {
|
|
cmd := "/usr/sbin/sysrc -a"
|
|
kvs, err := executeCommand(cmd)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
for _, kv := range strings.Split(string(kvs), "\n") {
|
|
fmt.Printf("%s\n", kv)
|
|
if strings.HasPrefix(kv, fmt.Sprintf("%s:", key)) {
|
|
return strings.TrimPrefix(strings.Join(strings.Split(kv, ":")[1:], ":"), " "), nil
|
|
}
|
|
}
|
|
|
|
return "", nil
|
|
}
|
|
|
|
// Add a value to current existing key value
|
|
func addRcKeyValue(rcconfpath string, key string, value string) error {
|
|
var nv string
|
|
cv, err := getCurrentRcKeyValue(rcconfpath, key)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if len(cv) > 0 {
|
|
log.Debugf("Current value of %s: %s\n", key, cv)
|
|
nv = fmt.Sprintf("\"%s %s\"", cv, value)
|
|
} else {
|
|
nv = fmt.Sprintf("\"%s\"", value)
|
|
}
|
|
cmd := fmt.Sprintf("/usr/sbin/sysrc -f %s %s=%s", rcconfpath, key, nv)
|
|
_, err = executeCommand(cmd)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
/*****************************************************************************
|
|
* Parse an fstab file, returning an array of Mount
|
|
*****************************************************************************/
|
|
func getFstab(path string) ([]Mount, error) {
|
|
var mounts []Mount
|
|
|
|
f, err := os.Open(path)
|
|
if err != nil {
|
|
return mounts, err
|
|
}
|
|
defer f.Close()
|
|
|
|
scan := bufio.NewScanner(f)
|
|
for scan.Scan() {
|
|
res := strings.Fields(scan.Text())
|
|
// iocage create lines like that : "/iocage/releases/13.2-RELEASE/root/bin /iocage/jails/smtp-router-02/root/bin nullfs ro 0 0 # Added by iocage on 2023-10-10 17:20:51"
|
|
if (len(res) > 6 && !strings.EqualFold(res[6], "#")) || 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
|
|
}
|
|
|
|
/*****************************************************************************
|
|
*
|
|
* devfs rules management
|
|
*
|
|
*****************************************************************************/
|
|
func getDevfsRuleset(ruleset int) []string {
|
|
cmd := fmt.Sprintf("/sbin/devfs rule -s %d show", ruleset)
|
|
out, err := executeCommand(cmd)
|
|
if err != nil {
|
|
return []string{}
|
|
}
|
|
// Get rid of the last "\n"
|
|
return strings.Split(out, "\n")[:len(strings.Split(out, "\n"))-1]
|
|
}
|
|
|
|
func copyDevfsRuleset(ruleset int, srcrs int) error {
|
|
// Resulting ruleset as an array of line
|
|
//var result []string
|
|
out := getDevfsRuleset(srcrs)
|
|
for _, line := range out {
|
|
//fields := strings.Fields(line)
|
|
cmd := fmt.Sprintf("/sbin/devfs rule -s %d add %s", ruleset, line)
|
|
out, err := executeCommand(cmd)
|
|
if err != nil {
|
|
return errors.New(fmt.Sprintf("Error adding rule \"%s\" to ruleset %d: %s", line, ruleset, out))
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
/********************************************************************************
|
|
* Add a rule to specified ruleset
|
|
* Ex.: addDevfsRuleToRuleset("path bpf* unhide", 1002)
|
|
*******************************************************************************/
|
|
func addDevfsRuleToRuleset(rule string, ruleset int) error {
|
|
// TODO: Check if rule not already enabled. We will need to recurse into includes.
|
|
// Get last rule index
|
|
rules := getDevfsRuleset(ruleset)
|
|
if len(rules) == 0 {
|
|
fmt.Printf("Error listing ruleset %d\n", ruleset)
|
|
return errors.New(fmt.Sprintf("Error listing rueset %d\n", ruleset))
|
|
}
|
|
|
|
f := strings.Fields(rules[(len(rules)-1)])
|
|
//fmt.Printf("Dernier index du ruleset %d: %s\n", ruleset, f[0])
|
|
index, _ := strconv.Atoi(f[0])
|
|
index += 100
|
|
|
|
cmd := fmt.Sprintf("/sbin/devfs rule -s %d add %d %s", ruleset, index, rule)
|
|
out, err := executeCommand(cmd)
|
|
if err != nil {
|
|
return errors.New(fmt.Sprintf("Error adding rule \"%s\" to ruleset %d: %s", rule, ruleset, out))
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
/********************************************************************************
|
|
* Returns value of parameter as read in /var/run/jail.$InternalName.conf
|
|
* Directives without value will return "true" if found
|
|
* Returns an error if parameter not found in file
|
|
*******************************************************************************/
|
|
func getValueFromRunningConfig(jname string, param string) (string, error) {
|
|
content, err := ioutil.ReadFile(fmt.Sprintf("/var/run/jail.%s.conf", jname))
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
for _, line := range strings.Split(string(content), "\n") {
|
|
if strings.Contains(line, fmt.Sprintf("%s = ", param)) {
|
|
split := strings.Split(line, "=")
|
|
switch len(split) {
|
|
// directives without value
|
|
case 0:
|
|
return "true", nil
|
|
case 1:
|
|
return "", fmt.Errorf("Invalid format: %s", line)
|
|
case 2:
|
|
return strings.Trim(split[1], " ;\""), nil
|
|
}
|
|
}
|
|
}
|
|
|
|
return "", fmt.Errorf("Parameter not found: %s", param)
|
|
}
|
|
|
|
|
|
/******************************************************************************
|
|
*
|
|
* Generic utilities
|
|
*
|
|
*****************************************************************************/
|
|
func isStringInArray(strarr []string, searched string) bool {
|
|
for _, s := range strarr {
|
|
if strings.EqualFold(s, searched) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (j Jail) isFirstNetDhcp() bool {
|
|
for _, n := range strings.Split(j.Config.Ip4_addr, ",") {
|
|
splitd := strings.Split(n, "|")
|
|
if len(splitd) > 1 && strings.EqualFold(splitd[1], "dhcp") {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
/********************************************************************************
|
|
* Get a specific jail reference, to update properties after a range loop
|
|
* Name can be short or long form ("myjail" vs "mystore/myjail")
|
|
* An empty jailtype means "all types"
|
|
*******************************************************************************/
|
|
func getJailFromArray(name string, jailtypes []string, jarray []Jail) (*Jail, error) {
|
|
var ds, jail string
|
|
var jails []Jail
|
|
|
|
if (len(jailtypes) == 1 && len(jailtypes[0]) == 0) || len(jailtypes) == 0 {
|
|
jailtypes = []string{"basejail", "jail", "template"}
|
|
}
|
|
|
|
if strings.Contains(name, "/") {
|
|
split := strings.Split(name, "/")
|
|
if len(split) != 2 {
|
|
return &Jail{}, errors.New("Invalid format for jail name")
|
|
}
|
|
ds = split[0]
|
|
jail = split[1]
|
|
} else {
|
|
jail = name
|
|
}
|
|
|
|
for i, j := range jarray {
|
|
if strings.HasPrefix(j.Name, jail) {
|
|
if isStringInArray(jailtypes, j.Config.Jailtype) {
|
|
if len(ds) > 0 {
|
|
if strings.EqualFold(ds, j.Datastore) {
|
|
return &jarray[i], nil
|
|
} else {
|
|
continue
|
|
}
|
|
} else {
|
|
jails = append(jails, j)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if len(jails) > 0 {
|
|
if len(jails) > 1 {
|
|
return &Jail{}, errors.New("More than one jail matching, please use datastore/jail format or full name")
|
|
} else {
|
|
return &jails[0], nil
|
|
}
|
|
}
|
|
|
|
return &Jail{}, errors.New("Jail not found")
|
|
}
|
|
|
|
func getDatastoreFromArray(name string, dsa []Datastore) (*Datastore, error) {
|
|
for _, d := range dsa {
|
|
if name == d.Name {
|
|
return &d, nil
|
|
}
|
|
}
|
|
return &Datastore{}, errors.New("Datastore not found")
|
|
}
|
|
|
|
/******************************************************************************
|
|
* Flag a jail so config on disk will be updated when calling WriteConfigToDisk
|
|
*****************************************************************************/
|
|
func setJailConfigUpdated(jail *Jail) error {
|
|
if len(jail.ConfigPath) == 0 {
|
|
return errors.New(fmt.Sprintf("No config path for jail %s", jail.Name))
|
|
}
|
|
|
|
j, err := getJailFromArray(jail.Name, []string{""}, gJails)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
j.ConfigUpdated = true
|
|
|
|
return nil
|
|
}
|
|
|
|
func getVersion(jail *Jail) (string, error) {
|
|
cvers, err := executeCommand(fmt.Sprintf("%s/bin/freebsd-version", jail.RootPath))
|
|
if err != nil {
|
|
fmt.Printf("ERROR: %s\n", err.Error())
|
|
return "", err
|
|
}
|
|
return strings.TrimRight(cvers, "\n"), nil
|
|
}
|
|
|
|
func updateVersion(jail *Jail) error {
|
|
cvers, err := executeCommand(fmt.Sprintf("%s/bin/freebsd-version", jail.RootPath))
|
|
if err != nil {
|
|
fmt.Printf("ERROR: %s\n", err.Error())
|
|
return err
|
|
}
|
|
cvers = strings.TrimRight(cvers, "\n")
|
|
jail.Config.Release = cvers
|
|
jail.WriteConfigToDisk(false)
|
|
|
|
return nil
|
|
}
|
|
|
|
/********************************************************************************
|
|
* Write jail(s) config which been updated to disk.
|
|
* If name is specified, work on the jail. If name is empty string, work on all.
|
|
* If changeauto not set, values which are in "auto" mode on disk
|
|
* won't be overwritten (p.ex defaultrouter wont be overwritten with current
|
|
* default route, so if route change on jailhost this will reflect on jail next
|
|
* start)
|
|
*******************************************************************************/
|
|
func writeConfigToDisk(j *Jail, changeauto bool) {
|
|
// we will manipulate properties so get a copy
|
|
jc := j.Config
|
|
|
|
if changeauto == false {
|
|
// Overwrite "auto" properties
|
|
ondiskjc, err := getJailConfig(j.ConfigPath)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
// TODO : List all fields, then call getStructFieldValue to compare value with "auto"
|
|
// If "auto" then keep it that way before writing ondiskjc to disk
|
|
var properties []string
|
|
properties = getStructFieldNames(ondiskjc, properties, "")
|
|
|
|
for _, p := range properties {
|
|
v, _, err := getStructFieldValue(ondiskjc, p)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
if v.String() == "auto" {
|
|
err = setStructFieldValue(&jc, p, "auto")
|
|
if err != nil {
|
|
fmt.Printf("ERROR sanitizing config: %s\n", err.Error())
|
|
os.Exit(1)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
marshaled, err := json.MarshalIndent(jc, "", " ")
|
|
if err != nil {
|
|
fmt.Printf("ERROR marshaling config: %s\n", err.Error())
|
|
}
|
|
|
|
//fmt.Printf("DEBUG: Will write config to disk, with content:\n")
|
|
//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 (j Jail) WriteConfigToDisk(changeauto bool) {
|
|
// we will manipulate properties so get a copy
|
|
jc := j.Config
|
|
|
|
if changeauto == false {
|
|
// Overwrite "auto" properties
|
|
ondiskjc, err := getJailConfig(j.ConfigPath)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
// TODO : List all fields, then call getStructFieldValue to compare value with "auto"
|
|
// If "auto" then keep it that way before writing ondiskjc to disk
|
|
var properties []string
|
|
properties = getStructFieldNames(ondiskjc, properties, "")
|
|
|
|
for _, p := range properties {
|
|
v, _, err := getStructFieldValue(ondiskjc, p)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
if v.String() == "auto" {
|
|
err = setStructFieldValue(&jc, p, "auto")
|
|
if err != nil {
|
|
fmt.Printf("ERROR sanitizing config: %s\n", err.Error())
|
|
os.Exit(1)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
marshaled, err := json.MarshalIndent(jc, "", " ")
|
|
if err != nil {
|
|
fmt.Printf("ERROR marshaling config: %s\n", err.Error())
|
|
}
|
|
|
|
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)
|
|
}
|
|
}
|
|
|
|
/******************************************************************************
|
|
* Return the quantity of jails with the name passed as parameter
|
|
*****************************************************************************/
|
|
func countOfJailsWithThisName(name string) int {
|
|
count := 0
|
|
for _, j := range gJails {
|
|
if strings.EqualFold(j.Name, name) {
|
|
count++
|
|
}
|
|
}
|
|
return count
|
|
}
|
|
|
|
|
|
func isNameDistinctive(name string, jails []Jail) bool {
|
|
_, err := getJailFromArray(name, []string{""}, jails)
|
|
if err != nil {
|
|
return false
|
|
} else {
|
|
return true
|
|
}
|
|
}
|
|
|
|
|
|
/********************************************************************************
|
|
* Recurse into structure, returning reflect.Kind of named field.
|
|
* Nested fields are named with a dot (ex "MyStruct.MyField")
|
|
*******************************************************************************/
|
|
func getStructFieldKind(parentStruct interface{}, fieldName string) (reflect.Kind, string, error) {
|
|
v := reflect.ValueOf(parentStruct)
|
|
|
|
if v.Kind() == reflect.Ptr {
|
|
v = v.Elem()
|
|
}
|
|
|
|
// For debugging
|
|
if false {
|
|
for i := 0; i < v.NumField(); i++ {
|
|
f := v.Field(i)
|
|
if f.Kind() == reflect.String {
|
|
fmt.Printf("%v=%v\n", v.Type().Field(i).Name, f.Interface())
|
|
}
|
|
}
|
|
}
|
|
|
|
if strings.Contains(fieldName, ".") {
|
|
fs := strings.Split(fieldName, ".")
|
|
f := v.FieldByName(fs[0])
|
|
if f.Kind() == reflect.Struct {
|
|
return getStructFieldKind(f.Interface(), strings.Join(fs[1:], "."))
|
|
} else {
|
|
return reflect.Kind(0), fieldName, errors.New(fmt.Sprintf("%s is not a struct: %s\n", fs[0], f.Kind().String()))
|
|
}
|
|
} else {
|
|
f := v.FieldByName(fieldName)
|
|
if f.IsValid() {
|
|
return f.Kind(), fieldName, nil
|
|
} else {
|
|
return reflect.Kind(0), fieldName, errors.New(fmt.Sprintf("Field not found: %s", fieldName))
|
|
}
|
|
}
|
|
|
|
return reflect.Kind(0), fieldName, errors.New(fmt.Sprintf("Field not found: %s", fieldName))
|
|
}
|
|
|
|
/********************************************************************************
|
|
* Display struct attributes name for a given struct.
|
|
* Recurse into struct attributes of type struct
|
|
* Used to show user jail properties
|
|
*******************************************************************************/
|
|
func getStructFieldNames(parentStruct interface{}, result []string, prefix string) []string {
|
|
v := reflect.ValueOf(parentStruct)
|
|
|
|
for i := 0; i < v.NumField(); i++ {
|
|
if v.Type().Field(i).Type.Kind() == reflect.Struct {
|
|
result = getStructFieldNames(v.Field(i).Interface(), result, v.Type().Field(i).Name)
|
|
} else {
|
|
if len(prefix) > 0 {
|
|
result = append(result, fmt.Sprintf("%s.%s", prefix, v.Type().Field(i).Name))
|
|
} else {
|
|
result = append(result, v.Type().Field(i).Name)
|
|
}
|
|
}
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
/********************************************************************************
|
|
* Recurse into structure, returning reflect.Value of wanted field.
|
|
* Nested fields are named with a dot (ex "MyStruct.MyField")
|
|
* Returns (value, field_name, error)
|
|
*******************************************************************************/
|
|
func getStructFieldValue(parentStruct interface{}, fieldName string) (*reflect.Value, string, error) {
|
|
v := reflect.ValueOf(parentStruct)
|
|
|
|
// Get value while we're dealing with pointers
|
|
for ; v.Kind() == reflect.Ptr; v = v.Elem() {
|
|
}
|
|
|
|
if v.Kind() != reflect.Struct {
|
|
return &v, fieldName, errors.New(fmt.Sprintf("parentStruct is not a struct! Kind: %s", v.Kind().String()))
|
|
}
|
|
|
|
typeOfV := v.Type()
|
|
|
|
if false {
|
|
for i := 0; i < v.NumField(); i++ {
|
|
f := v.Field(i)
|
|
if f.Kind() == reflect.String {
|
|
fmt.Printf("%v=%v\n", v.Type().Field(i).Name, f.Interface())
|
|
}
|
|
}
|
|
}
|
|
|
|
var f reflect.Value
|
|
var found bool
|
|
|
|
fs := strings.Split(fieldName, ".")
|
|
// Loop through properties
|
|
for i, curF := range fs {
|
|
found = false
|
|
for j := 0; j < v.NumField(); j++ {
|
|
if typeOfV.Field(j).Name == curF {
|
|
f = v.Field(j)
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
if !found {
|
|
return &v, fieldName, errors.New(fmt.Sprintf("Field not found: %s", fieldName))
|
|
}
|
|
|
|
for ; f.Kind() == reflect.Ptr; f = f.Elem() {
|
|
}
|
|
|
|
/*fmt.Printf("v.kind() = %v\n", v.Kind().String())
|
|
fmt.Printf("v = %v\n", v)
|
|
fmt.Printf("f.kind() = %v\n", f.Kind().String())
|
|
fmt.Printf("f = %v\n", f)*/
|
|
|
|
// If this is the last loop, return result even if it's a struct
|
|
// FIXME : What if we got interface?
|
|
if f.Kind() != reflect.Struct && i < len(fs)-1 {
|
|
if f.IsValid() {
|
|
//fmt.Printf("Return f = %v of Kind %s\n", f, f.Kind().String())
|
|
return &f, fieldName, nil
|
|
} else {
|
|
return &v, fieldName, errors.New(fmt.Sprintf("Field not found: %s", fieldName))
|
|
}
|
|
} else {
|
|
v = f
|
|
typeOfV = v.Type()
|
|
}
|
|
}
|
|
|
|
return &v, fieldName, nil
|
|
}
|
|
|
|
/********************************************************************************
|
|
* TODO : Replace by getStructFieldValue
|
|
* Recurse into structure, returning reflect.Value of wanted field.
|
|
* Nested fields are named with a dot (ex "MyStruct.MyField")
|
|
*******************************************************************************/
|
|
func getStructField(parentStruct interface{}, fieldName string) (reflect.Value, string) {
|
|
v := reflect.ValueOf(parentStruct)
|
|
|
|
if v.Kind() == reflect.Ptr {
|
|
v = v.Elem()
|
|
}
|
|
|
|
if false {
|
|
for i := 0; i < v.NumField(); i++ {
|
|
f := v.Field(i)
|
|
if f.Kind() == reflect.String {
|
|
fmt.Printf("%v=%v\n", v.Type().Field(i).Name, f.Interface())
|
|
}
|
|
}
|
|
}
|
|
|
|
if strings.Contains(fieldName, ".") {
|
|
fs := strings.Split(fieldName, ".")
|
|
f := v.FieldByName(fs[0])
|
|
if f.Kind() == reflect.Struct {
|
|
return getStructField(f.Interface(), strings.Join(fs[1:], "."))
|
|
} else {
|
|
log.Fatalln(fmt.Sprintf("%s is not a struct: %s\n", fs[0], f.Kind().String()))
|
|
}
|
|
}
|
|
|
|
return v, fieldName
|
|
}
|
|
|
|
// setStructFieldValue takes a string as propValue, whatever the real property type is.
|
|
// It will be converted.
|
|
func setStructFieldValue(parentStruct interface{}, propName string, propValue string) error {
|
|
val, _, err := getStructFieldValue(parentStruct, propName)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if val.CanSet() {
|
|
switch val.Kind() {
|
|
case reflect.String:
|
|
val.SetString(propValue)
|
|
case reflect.Int:
|
|
ival, err := strconv.ParseInt(propValue, 10, 64)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
val.SetInt(ival)
|
|
case reflect.Bool:
|
|
bval, err := strconv.ParseBool(propValue)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
val.SetBool(bval)
|
|
default:
|
|
return errors.New(fmt.Sprintf("Field is an unkown type: %s: %s", propName, val.Kind().String()))
|
|
}
|
|
} else {
|
|
return errors.New(fmt.Sprintf("Field is not writable : %s", propName))
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
/********************************************************************************
|
|
* Pretty display of jails field
|
|
* Fields to show are given in a string array parameter
|
|
* Ex. : displayJailsFields(gJails, ["Name", "JID", "RootPath"])
|
|
*******************************************************************************/
|
|
func displayJailsFields(jails []Jail, valsToDisplay []string) {
|
|
/* A line is defined by :
|
|
* Nr of fields
|
|
* For each field :
|
|
* Its name
|
|
* Its max length
|
|
* Its value
|
|
*/
|
|
type Field struct {
|
|
Name string
|
|
MaxLen int
|
|
Value string
|
|
}
|
|
|
|
type Line []Field
|
|
|
|
type Output []Line
|
|
var out Output
|
|
|
|
//fmt.Printf("%d fields in a %d items structure\n", len(valsToDisplay), len(jails))
|
|
|
|
// Browse structure to get max length and values
|
|
for _, j := range jails {
|
|
// Have to use a pointer, else reflect.Value.Elem() will panic : https://pkg.go.dev/reflect#Value.Elem
|
|
tj := &j
|
|
|
|
line := make([]Field, len(valsToDisplay))
|
|
for i, f := range valsToDisplay {
|
|
a, f := getStructField(tj, f)
|
|
|
|
field := Field{
|
|
Name: f,
|
|
}
|
|
if a.FieldByName(f).IsValid() {
|
|
// For now this just contains this item length, will adjust later
|
|
// We need to divide length by number of items in string fields (because we can have more than 1 ip4_addr)
|
|
if reflect.TypeOf(a.FieldByName(f).Interface()).Kind() == reflect.String {
|
|
itnr := len(strings.Split(string(a.FieldByName(f).Interface().(string)), ","))
|
|
field.MaxLen = len(fmt.Sprintf("%v", a.FieldByName(f).Interface())) / itnr
|
|
} else {
|
|
field.MaxLen = len(fmt.Sprintf("%v", a.FieldByName(f).Interface()))
|
|
}
|
|
field.Value = fmt.Sprintf("%v", a.FieldByName(f).Interface())
|
|
} else {
|
|
fmt.Printf("Invalid field name: %s\n", f)
|
|
}
|
|
line[i] = field
|
|
|
|
}
|
|
out = append(out, line)
|
|
}
|
|
|
|
if len(out) == 0 {
|
|
fmt.Printf("Nothing to see here!\n")
|
|
return
|
|
}
|
|
|
|
// Get real maximum length
|
|
maxlen := make([]int, len(valsToDisplay))
|
|
for i := 0; i < len(valsToDisplay); i++ {
|
|
maxlen[i] = len(valsToDisplay[i])
|
|
}
|
|
for _, l := range out {
|
|
for i, f := range l {
|
|
if f.MaxLen > maxlen[i] {
|
|
maxlen[i] = f.MaxLen
|
|
}
|
|
}
|
|
}
|
|
|
|
// Align maxlen to the real maximum length
|
|
for io, l := range out {
|
|
for ii, _ := range l {
|
|
// We need to access slice by index if we want to modify content
|
|
out[io][ii].MaxLen = maxlen[ii]
|
|
}
|
|
}
|
|
|
|
totalLen := 0
|
|
// For each field, add separator and 2 blank spaces
|
|
for _, f := range out[0] {
|
|
totalLen += f.MaxLen + 3
|
|
}
|
|
// Then add starting delimiter
|
|
totalLen += 1
|
|
|
|
Debug := false
|
|
if Debug == true {
|
|
for _, f := range out[0] {
|
|
fmt.Printf("%s max length : %d\n", f.Name, f.MaxLen)
|
|
}
|
|
}
|
|
|
|
// Lets draw things on the screen!
|
|
// First, headers: 1st separator line
|
|
for i, f := range out[0] {
|
|
if i == 0 {
|
|
fmt.Printf("+")
|
|
}
|
|
for i := 0; i < f.MaxLen+2; i++ {
|
|
fmt.Printf("=")
|
|
}
|
|
fmt.Printf("+")
|
|
}
|
|
fmt.Printf("\n")
|
|
|
|
// Column names
|
|
for i, f := range out[0] {
|
|
if i == 0 {
|
|
fmt.Printf("|")
|
|
}
|
|
/* Use vlsToDisplay to get real name (with "Config.")
|
|
* fmt.Printf(" %s", f.Name)
|
|
* for i := len(f.Name)+1 ; i < f.MaxLen+1 ; i++ { */
|
|
fmt.Printf(" %s", valsToDisplay[i])
|
|
for i := len(valsToDisplay[i]) + 1; i < f.MaxLen+1; i++ {
|
|
fmt.Printf(" ")
|
|
}
|
|
fmt.Printf(" |")
|
|
}
|
|
// Finally separator line
|
|
fmt.Printf("\n")
|
|
for i, f := range out[0] {
|
|
if i == 0 {
|
|
fmt.Printf("+")
|
|
}
|
|
for i := 0; i < f.MaxLen+2; i++ {
|
|
fmt.Printf("=")
|
|
}
|
|
fmt.Printf("+")
|
|
}
|
|
fmt.Printf("\n")
|
|
|
|
// Then display data
|
|
// Loop through lines
|
|
for _, l := range out {
|
|
|
|
// Loop through fields
|
|
// In case we need to add a line for a 2nd IP, or whatever object
|
|
var supplines = make(map[string]string)
|
|
for i, f := range l {
|
|
if i == 0 {
|
|
fmt.Printf("|")
|
|
}
|
|
// Special cases of value displaying
|
|
if f.Name == "JID" && f.Value == "0" {
|
|
fmt.Printf(" ")
|
|
} else if f.Name == "Ip4_addr" {
|
|
ia := strings.Split(f.Value, ",")
|
|
// If we have more than 1 value we need to finish this line, and store value for writing at the end of line loop
|
|
for i, inter := range ia {
|
|
if i > 0 {
|
|
supplines[f.Name] = inter
|
|
} else {
|
|
fmt.Printf(" %s", inter)
|
|
}
|
|
}
|
|
//fmt.Printf(" %s", strings.Split(strings.Split(f.Value, "|")[1], "/")[0])
|
|
} else {
|
|
fmt.Printf(" %s", f.Value)
|
|
}
|
|
// Complete with spaces to the max length
|
|
for i := len(f.Value) + 1; i < f.MaxLen+1; i++ {
|
|
fmt.Printf(" ")
|
|
}
|
|
fmt.Printf(" |")
|
|
}
|
|
// Draw supplementary lines
|
|
if len(supplines) > 0 {
|
|
for i, f := range l {
|
|
if i == 0 {
|
|
fmt.Printf("\n|")
|
|
}
|
|
// Overwrite value in this scope
|
|
v, exists := supplines[f.Name]
|
|
if exists {
|
|
fmt.Printf(" %s", v)
|
|
} else {
|
|
// 1st option : Do not redisplay precedent line values
|
|
fmt.Printf(" ")
|
|
// 2nd option : Redisplay precedenet line values
|
|
//fmt.Printf(" %s", f.Value)
|
|
}
|
|
// Complete with spaces to the max length
|
|
for i := len(v) + 1; i < f.MaxLen+1; i++ {
|
|
fmt.Printf(" ")
|
|
}
|
|
fmt.Printf(" |")
|
|
}
|
|
}
|
|
// Draw line separator between jails
|
|
if !gNoJailLineSep {
|
|
fmt.Printf("\n")
|
|
for i, f := range out[0] {
|
|
if i == 0 {
|
|
fmt.Printf("+")
|
|
}
|
|
for i := 0; i < f.MaxLen+2; i++ {
|
|
fmt.Printf("-")
|
|
}
|
|
fmt.Printf("+")
|
|
}
|
|
}
|
|
fmt.Printf("\n")
|
|
}
|
|
if gNoJailLineSep {
|
|
for i, f := range out[0] {
|
|
if i == 0 {
|
|
fmt.Printf("+")
|
|
}
|
|
for i := 0; i < f.MaxLen+2; i++ {
|
|
fmt.Printf("-")
|
|
}
|
|
fmt.Printf("+")
|
|
}
|
|
}
|
|
|
|
fmt.Printf("\n")
|
|
}
|
|
|
|
/********************************************************************************
|
|
* Pretty display of snapshots field
|
|
* Fields to show are given in a string array parameter
|
|
* Ex. : displaySnapshotsFields(snapshots, ["Name", "Datastore", "Used"])
|
|
*******************************************************************************/
|
|
func displaySnapshotsFields(snaps []Snapshot, valsToDisplay []string) {
|
|
/* A line is defined by :
|
|
* Nr of fields
|
|
* For each field :
|
|
* Its name
|
|
* Its max length
|
|
* Its value
|
|
*/
|
|
type Field struct {
|
|
Name string
|
|
MaxLen int
|
|
Value string
|
|
}
|
|
|
|
type Line []Field
|
|
|
|
type Output []Line
|
|
var out Output
|
|
|
|
//fmt.Printf("%d fields in a %d items structure\n", len(valsToDisplay), len(jails))
|
|
|
|
// Browse structure to get max length and values
|
|
for _, s := range snaps {
|
|
// Have to use a pointer, else reflect.Value.Elem() will panic : https://pkg.go.dev/reflect#Value.Elem
|
|
tj := &s
|
|
|
|
line := make([]Field, len(valsToDisplay))
|
|
for i, f := range valsToDisplay {
|
|
a, f := getStructField(tj, f)
|
|
|
|
field := Field{
|
|
Name: f,
|
|
}
|
|
if a.FieldByName(f).IsValid() {
|
|
// For now this just contains this item length, will adjust later
|
|
// We need to divide length by number of items in string fields (because we can have more than 1 ip4_addr)
|
|
if reflect.TypeOf(a.FieldByName(f).Interface()).Kind() == reflect.String {
|
|
itnr := len(strings.Split(string(a.FieldByName(f).Interface().(string)), ","))
|
|
field.MaxLen = len(fmt.Sprintf("%v", a.FieldByName(f).Interface())) / itnr
|
|
} else {
|
|
// Special case of disk size : We will print human readable values
|
|
if field.Name == "Used" || field.Name == "Referenced" || field.Name == "Available" {
|
|
var v datasize.ByteSize
|
|
v.UnmarshalText([]byte(fmt.Sprintf("%v", a.FieldByName(f).Interface())))
|
|
field.MaxLen = len(fmt.Sprintf("%v", v.HumanReadable()))
|
|
} else {
|
|
field.MaxLen = len(fmt.Sprintf("%v", a.FieldByName(f).Interface()))
|
|
}
|
|
}
|
|
field.Value = fmt.Sprintf("%v", a.FieldByName(f).Interface())
|
|
} else {
|
|
fmt.Printf("Invalid field name: %s\n", f)
|
|
}
|
|
line[i] = field
|
|
|
|
}
|
|
out = append(out, line)
|
|
}
|
|
|
|
if len(out) == 0 {
|
|
fmt.Printf("Nothing to see here!\n")
|
|
return
|
|
}
|
|
|
|
// Get real maximum length
|
|
maxlen := make([]int, len(valsToDisplay))
|
|
for i := 0; i < len(valsToDisplay); i++ {
|
|
maxlen[i] = len(valsToDisplay[i])
|
|
}
|
|
for _, l := range out {
|
|
for i, f := range l {
|
|
if f.MaxLen > maxlen[i] {
|
|
maxlen[i] = f.MaxLen
|
|
}
|
|
}
|
|
}
|
|
|
|
// Align maxlen to the real maximum length
|
|
for io, l := range out {
|
|
for ii, _ := range l {
|
|
// We need to access slice by index if we want to modify content
|
|
out[io][ii].MaxLen = maxlen[ii]
|
|
}
|
|
}
|
|
|
|
totalLen := 0
|
|
// For each field, add separator and 2 blank spaces
|
|
for _, f := range out[0] {
|
|
totalLen += f.MaxLen + 3
|
|
}
|
|
// Then add starting delimiter
|
|
totalLen += 1
|
|
|
|
Debug := false
|
|
if Debug == true {
|
|
for _, f := range out[0] {
|
|
fmt.Printf("%s max length : %d\n", f.Name, f.MaxLen)
|
|
}
|
|
}
|
|
|
|
// Lets draw things on the screen!
|
|
// First, headers: 1st separator line
|
|
for i, f := range out[0] {
|
|
if i == 0 {
|
|
fmt.Printf("+")
|
|
}
|
|
for i := 0; i < f.MaxLen+2; i++ {
|
|
fmt.Printf("=")
|
|
}
|
|
fmt.Printf("+")
|
|
}
|
|
fmt.Printf("\n")
|
|
|
|
// Column names
|
|
for i, f := range out[0] {
|
|
if i == 0 {
|
|
fmt.Printf("|")
|
|
}
|
|
/* Use vlsToDisplay to get real name (with "Config.")
|
|
* fmt.Printf(" %s", f.Name)
|
|
* for i := len(f.Name)+1 ; i < f.MaxLen+1 ; i++ { */
|
|
fmt.Printf(" %s", valsToDisplay[i])
|
|
for i := len(valsToDisplay[i]) + 1; i < f.MaxLen+1; i++ {
|
|
fmt.Printf(" ")
|
|
}
|
|
fmt.Printf(" |")
|
|
}
|
|
// Finally separator line
|
|
fmt.Printf("\n")
|
|
for i, f := range out[0] {
|
|
if i == 0 {
|
|
fmt.Printf("+")
|
|
}
|
|
for i := 0; i < f.MaxLen+2; i++ {
|
|
fmt.Printf("=")
|
|
}
|
|
fmt.Printf("+")
|
|
}
|
|
fmt.Printf("\n")
|
|
|
|
// Then display data
|
|
// Loop through lines
|
|
for _, l := range out {
|
|
|
|
// Loop through fields
|
|
// In case we need to add a line for a 2nd IP, or whatever object
|
|
var supplines = make(map[string]string)
|
|
for i, f := range l {
|
|
if i == 0 {
|
|
fmt.Printf("|")
|
|
}
|
|
// Special cases of value displaying
|
|
// Pretty print of sizes
|
|
if f.Name == "Used" || f.Name == "Referenced" || f.Name == "Available" {
|
|
var v datasize.ByteSize
|
|
err := v.UnmarshalText([]byte(f.Value))
|
|
if err != nil {
|
|
return
|
|
}
|
|
fmt.Printf(" %s", v.HumanReadable())
|
|
// Complete with spaces to the max length
|
|
for i := len(v.HumanReadable()) + 1; i < f.MaxLen+1; i++ {
|
|
fmt.Printf(" ")
|
|
}
|
|
} else {
|
|
fmt.Printf(" %s", f.Value)
|
|
// Complete with spaces to the max length
|
|
for i := len(f.Value) + 1; i < f.MaxLen+1; i++ {
|
|
fmt.Printf(" ")
|
|
}
|
|
}
|
|
fmt.Printf(" |")
|
|
}
|
|
// Draw supplementary lines
|
|
if len(supplines) > 0 {
|
|
for i, f := range l {
|
|
if i == 0 {
|
|
fmt.Printf("\n|")
|
|
}
|
|
// Overwrite value in this scope
|
|
v, exists := supplines[f.Name]
|
|
if exists {
|
|
fmt.Printf(" %s", v)
|
|
} else {
|
|
// 1st option : Do not redisplay precedent line values
|
|
fmt.Printf(" ")
|
|
// 2nd option : Redisplay precedenet line values
|
|
//fmt.Printf(" %s", f.Value)
|
|
}
|
|
// Complete with spaces to the max length
|
|
for i := len(v) + 1; i < f.MaxLen+1; i++ {
|
|
fmt.Printf(" ")
|
|
}
|
|
fmt.Printf(" |")
|
|
}
|
|
}
|
|
// Draw line separator between jails
|
|
if !gNoSnapLineSep {
|
|
fmt.Printf("\n")
|
|
for i, f := range out[0] {
|
|
if i == 0 {
|
|
fmt.Printf("+")
|
|
}
|
|
for i := 0; i < f.MaxLen+2; i++ {
|
|
fmt.Printf("-")
|
|
}
|
|
fmt.Printf("+")
|
|
}
|
|
}
|
|
fmt.Printf("\n")
|
|
}
|
|
if gNoSnapLineSep {
|
|
for i, f := range out[0] {
|
|
if i == 0 {
|
|
fmt.Printf("+")
|
|
}
|
|
for i := 0; i < f.MaxLen+2; i++ {
|
|
fmt.Printf("-")
|
|
}
|
|
fmt.Printf("+")
|
|
}
|
|
}
|
|
|
|
fmt.Printf("\n")
|
|
}
|
|
|
|
/********************************************************************************
|
|
* Pretty display of datastores field
|
|
* Fields to show are given in a string array parameter
|
|
* Ex. : displaySnapshotsFields(datastores, ["Name", "ZFSDataset", "Available"])
|
|
*******************************************************************************/
|
|
func displayDatastoresFields(datastores []Datastore, valsToDisplay []string) {
|
|
/* A line is defined by :
|
|
* Nr of fields
|
|
* For each field :
|
|
* Its name
|
|
* Its max length
|
|
* Its value
|
|
*/
|
|
type Field struct {
|
|
Name string
|
|
MaxLen int
|
|
Value string
|
|
}
|
|
|
|
type Line []Field
|
|
|
|
type Output []Line
|
|
var out Output
|
|
|
|
//fmt.Printf("%d fields in a %d items structure\n", len(valsToDisplay), len(jails))
|
|
|
|
// Browse structure to get max length and values
|
|
for _, d := range datastores {
|
|
// Have to use a pointer, else reflect.Value.Elem() will panic : https://pkg.go.dev/reflect#Value.Elem
|
|
td := &d
|
|
|
|
line := make([]Field, len(valsToDisplay))
|
|
for i, f := range valsToDisplay {
|
|
a, f := getStructField(td, f)
|
|
|
|
field := Field{
|
|
Name: f,
|
|
}
|
|
if a.FieldByName(f).IsValid() {
|
|
// For now this just contains this item length, will adjust later
|
|
// We need to divide length by number of items in string fields (because we can have more than 1 ip4_addr)
|
|
if reflect.TypeOf(a.FieldByName(f).Interface()).Kind() == reflect.String {
|
|
itnr := len(strings.Split(string(a.FieldByName(f).Interface().(string)), ","))
|
|
field.MaxLen = len(fmt.Sprintf("%v", a.FieldByName(f).Interface())) / itnr
|
|
} else {
|
|
// Special case of disk size : We will print human readable values
|
|
if field.Name == "Used" || field.Name == "Referenced" || field.Name == "Available" {
|
|
var v datasize.ByteSize
|
|
v.UnmarshalText([]byte(fmt.Sprintf("%v", a.FieldByName(f).Interface())))
|
|
field.MaxLen = len(fmt.Sprintf("%v", v.HumanReadable()))
|
|
} else {
|
|
field.MaxLen = len(fmt.Sprintf("%v", a.FieldByName(f).Interface()))
|
|
}
|
|
}
|
|
field.Value = fmt.Sprintf("%v", a.FieldByName(f).Interface())
|
|
} else {
|
|
fmt.Printf("Invalid field name: %s\n", f)
|
|
}
|
|
line[i] = field
|
|
|
|
}
|
|
out = append(out, line)
|
|
}
|
|
|
|
if len(out) == 0 {
|
|
fmt.Printf("Nothing to see here!\n")
|
|
return
|
|
}
|
|
|
|
// Get real maximum length
|
|
maxlen := make([]int, len(valsToDisplay))
|
|
for i := 0; i < len(valsToDisplay); i++ {
|
|
maxlen[i] = len(valsToDisplay[i])
|
|
}
|
|
for _, l := range out {
|
|
for i, f := range l {
|
|
if f.MaxLen > maxlen[i] {
|
|
maxlen[i] = f.MaxLen
|
|
}
|
|
}
|
|
}
|
|
|
|
// Align maxlen to the real maximum length
|
|
for io, l := range out {
|
|
for ii, _ := range l {
|
|
// We need to access slice by index if we want to modify content
|
|
out[io][ii].MaxLen = maxlen[ii]
|
|
}
|
|
}
|
|
|
|
totalLen := 0
|
|
// For each field, add separator and 2 blank spaces
|
|
for _, f := range out[0] {
|
|
totalLen += f.MaxLen + 3
|
|
}
|
|
// Then add starting delimiter
|
|
totalLen += 1
|
|
|
|
Debug := false
|
|
if Debug == true {
|
|
for _, f := range out[0] {
|
|
fmt.Printf("%s max length : %d\n", f.Name, f.MaxLen)
|
|
}
|
|
}
|
|
|
|
// Lets draw things on the screen!
|
|
// First, headers: 1st separator line
|
|
for i, f := range out[0] {
|
|
if i == 0 {
|
|
fmt.Printf("+")
|
|
}
|
|
for i := 0; i < f.MaxLen+2; i++ {
|
|
fmt.Printf("=")
|
|
}
|
|
fmt.Printf("+")
|
|
}
|
|
fmt.Printf("\n")
|
|
|
|
// Column names
|
|
for i, f := range out[0] {
|
|
if i == 0 {
|
|
fmt.Printf("|")
|
|
}
|
|
/* Use vlsToDisplay to get real name (with "Config.")
|
|
* fmt.Printf(" %s", f.Name)
|
|
* for i := len(f.Name)+1 ; i < f.MaxLen+1 ; i++ { */
|
|
fmt.Printf(" %s", valsToDisplay[i])
|
|
for i := len(valsToDisplay[i]) + 1; i < f.MaxLen+1; i++ {
|
|
fmt.Printf(" ")
|
|
}
|
|
fmt.Printf(" |")
|
|
}
|
|
// Finally separator line
|
|
fmt.Printf("\n")
|
|
for i, f := range out[0] {
|
|
if i == 0 {
|
|
fmt.Printf("+")
|
|
}
|
|
for i := 0; i < f.MaxLen+2; i++ {
|
|
fmt.Printf("=")
|
|
}
|
|
fmt.Printf("+")
|
|
}
|
|
fmt.Printf("\n")
|
|
|
|
// Then display data
|
|
// Loop through lines
|
|
for _, l := range out {
|
|
|
|
// Loop through fields
|
|
// In case we need to add a line for a 2nd IP, or whatever object
|
|
var supplines = make(map[string]string)
|
|
for i, f := range l {
|
|
if i == 0 {
|
|
fmt.Printf("|")
|
|
}
|
|
// Special cases of value displaying
|
|
// Pretty print of sizes
|
|
if f.Name == "Used" || f.Name == "Referenced" || f.Name == "Available" {
|
|
var v datasize.ByteSize
|
|
err := v.UnmarshalText([]byte(f.Value))
|
|
if err != nil {
|
|
return
|
|
}
|
|
fmt.Printf(" %s", v.HumanReadable())
|
|
// Complete with spaces to the max length
|
|
for i := len(v.HumanReadable()) + 1; i < f.MaxLen+1; i++ {
|
|
fmt.Printf(" ")
|
|
}
|
|
} else {
|
|
fmt.Printf(" %s", f.Value)
|
|
// Complete with spaces to the max length
|
|
for i := len(f.Value) + 1; i < f.MaxLen+1; i++ {
|
|
fmt.Printf(" ")
|
|
}
|
|
}
|
|
fmt.Printf(" |")
|
|
}
|
|
// Draw supplementary lines
|
|
if len(supplines) > 0 {
|
|
for i, f := range l {
|
|
if i == 0 {
|
|
fmt.Printf("\n|")
|
|
}
|
|
// Overwrite value in this scope
|
|
v, exists := supplines[f.Name]
|
|
if exists {
|
|
fmt.Printf(" %s", v)
|
|
} else {
|
|
// 1st option : Do not redisplay precedent line values
|
|
fmt.Printf(" ")
|
|
// 2nd option : Redisplay precedenet line values
|
|
//fmt.Printf(" %s", f.Value)
|
|
}
|
|
// Complete with spaces to the max length
|
|
for i := len(v) + 1; i < f.MaxLen+1; i++ {
|
|
fmt.Printf(" ")
|
|
}
|
|
fmt.Printf(" |")
|
|
}
|
|
}
|
|
// Draw line separator between jails
|
|
if !gNoDSLineSep {
|
|
fmt.Printf("\n")
|
|
for i, f := range out[0] {
|
|
if i == 0 {
|
|
fmt.Printf("+")
|
|
}
|
|
for i := 0; i < f.MaxLen+2; i++ {
|
|
fmt.Printf("-")
|
|
}
|
|
fmt.Printf("+")
|
|
}
|
|
}
|
|
fmt.Printf("\n")
|
|
}
|
|
if gNoDSLineSep {
|
|
for i, f := range out[0] {
|
|
if i == 0 {
|
|
fmt.Printf("+")
|
|
}
|
|
for i := 0; i < f.MaxLen+2; i++ {
|
|
fmt.Printf("-")
|
|
}
|
|
fmt.Printf("+")
|
|
}
|
|
}
|
|
|
|
fmt.Printf("\n")
|
|
}
|
|
|
|
/*****************************************************************************
|
|
*
|
|
* Sorting jails
|
|
*
|
|
*****************************************************************************/
|
|
// This struct hold "sort by jail fields" functions
|
|
type jailLessFunc func(j1 *Jail, j2 *Jail) bool
|
|
|
|
func initJailSortStruct() JailSort {
|
|
jcs := JailConfigSort{
|
|
Allow_chflagsInc: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Allow_chflags < j2.Config.Allow_chflags
|
|
},
|
|
Allow_chflagsDec: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Allow_chflags > j2.Config.Allow_chflags
|
|
},
|
|
Allow_mlockInc: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Allow_mlock < j2.Config.Allow_mlock
|
|
},
|
|
Allow_mlockDec: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Allow_mlock > j2.Config.Allow_mlock
|
|
},
|
|
Allow_mountInc: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Allow_mount < j2.Config.Allow_mount
|
|
},
|
|
Allow_mountDec: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Allow_mount > j2.Config.Allow_mount
|
|
},
|
|
Allow_mount_devfsInc: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Allow_mount_devfs < j2.Config.Allow_mount_devfs
|
|
},
|
|
Allow_mount_devfsDec: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Allow_mount_devfs > j2.Config.Allow_mount_devfs
|
|
},
|
|
Allow_mount_fusefsInc: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Allow_mount_fusefs < j2.Config.Allow_mount_fusefs
|
|
},
|
|
Allow_mount_fusefsDec: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Allow_mount_fusefs > j2.Config.Allow_mount_fusefs
|
|
},
|
|
Allow_mount_nullfsInc: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Allow_mount_nullfs < j2.Config.Allow_mount_nullfs
|
|
},
|
|
Allow_mount_nullfsDec: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Allow_mount_nullfs > j2.Config.Allow_mount_nullfs
|
|
},
|
|
Allow_mount_procfsInc: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Allow_mount_procfs < j2.Config.Allow_mount_procfs
|
|
},
|
|
Allow_mount_procfsDec: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Allow_mount_procfs > j2.Config.Allow_mount_procfs
|
|
},
|
|
Allow_mount_tmpfsInc: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Allow_mount_tmpfs < j2.Config.Allow_mount_tmpfs
|
|
},
|
|
Allow_mount_tmpfsDec: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Allow_mount_tmpfs > j2.Config.Allow_mount_tmpfs
|
|
},
|
|
Allow_mount_zfsInc: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Allow_mount_zfs < j2.Config.Allow_mount_zfs
|
|
},
|
|
Allow_mount_zfsDec: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Allow_mount_zfs > j2.Config.Allow_mount_zfs
|
|
},
|
|
Allow_quotasInc: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Allow_quotas < j2.Config.Allow_quotas
|
|
},
|
|
Allow_quotasDec: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Allow_quotas > j2.Config.Allow_quotas
|
|
},
|
|
Allow_raw_socketsInc: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Allow_raw_sockets < j2.Config.Allow_raw_sockets
|
|
},
|
|
Allow_raw_socketsDec: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Allow_raw_sockets > j2.Config.Allow_raw_sockets
|
|
},
|
|
Allow_set_hostnameInc: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Allow_set_hostname < j2.Config.Allow_set_hostname
|
|
},
|
|
Allow_set_hostnameDec: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Allow_set_hostname > j2.Config.Allow_set_hostname
|
|
},
|
|
Allow_socket_afInc: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Allow_socket_af < j2.Config.Allow_socket_af
|
|
},
|
|
Allow_socket_afDec: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Allow_socket_af > j2.Config.Allow_socket_af
|
|
},
|
|
Allow_sysvipcInc: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Allow_sysvipc < j2.Config.Allow_sysvipc
|
|
},
|
|
Allow_sysvipcDec: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Allow_sysvipc > j2.Config.Allow_sysvipc
|
|
},
|
|
Allow_tunInc: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Allow_tun < j2.Config.Allow_tun
|
|
},
|
|
Allow_tunDec: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Allow_tun > j2.Config.Allow_tun
|
|
},
|
|
Allow_vmmInc: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Allow_vmm < j2.Config.Allow_vmm
|
|
},
|
|
Allow_vmmDec: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Allow_vmm > j2.Config.Allow_vmm
|
|
},
|
|
Assign_localhostInc: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Assign_localhost < j2.Config.Assign_localhost
|
|
},
|
|
Assign_localhostDec: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Assign_localhost > j2.Config.Assign_localhost
|
|
},
|
|
AvailableInc: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Available < j2.Config.Available
|
|
},
|
|
AvailableDec: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Available > j2.Config.Available
|
|
},
|
|
BasejailInc: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Basejail < j2.Config.Basejail
|
|
},
|
|
BasejailDec: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Basejail > j2.Config.Basejail
|
|
},
|
|
BootInc: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Boot < j2.Config.Boot
|
|
},
|
|
BootDec: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Boot > j2.Config.Boot
|
|
},
|
|
BpfInc: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Bpf < j2.Config.Bpf
|
|
},
|
|
BpfDec: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Bpf > j2.Config.Bpf
|
|
},
|
|
Children_maxInc: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Children_max < j2.Config.Children_max
|
|
},
|
|
Children_maxDec: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Children_max > j2.Config.Children_max
|
|
},
|
|
Cloned_releaseInc: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Cloned_release < j2.Config.Cloned_release
|
|
},
|
|
Cloned_releaseDec: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Cloned_release > j2.Config.Cloned_release
|
|
},
|
|
CommentInc: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Comment < j2.Config.Comment
|
|
},
|
|
CommentDec: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Comment > j2.Config.Comment
|
|
},
|
|
CompressionInc: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Compression < j2.Config.Compression
|
|
},
|
|
CompressionDec: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Compression > j2.Config.Compression
|
|
},
|
|
CompressratioInc: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Compressratio < j2.Config.Compressratio
|
|
},
|
|
CompressratioDec: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Compressratio > j2.Config.Compressratio
|
|
},
|
|
Config_versionInc: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Config_version < j2.Config.Config_version
|
|
},
|
|
Config_versionDec: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Config_version > j2.Config.Config_version
|
|
},
|
|
CoredumpsizeInc: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Coredumpsize < j2.Config.Coredumpsize
|
|
},
|
|
CoredumpsizeDec: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Coredumpsize > j2.Config.Coredumpsize
|
|
},
|
|
CountInc: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Count < j2.Config.Count
|
|
},
|
|
CountDec: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Count > j2.Config.Count
|
|
},
|
|
CpusetInc: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Cpuset < j2.Config.Cpuset
|
|
},
|
|
CpusetDec: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Cpuset > j2.Config.Cpuset
|
|
},
|
|
CputimeInc: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Cputime < j2.Config.Cputime
|
|
},
|
|
CputimeDec: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Cputime > j2.Config.Cputime
|
|
},
|
|
DatasizeInc: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Datasize < j2.Config.Datasize
|
|
},
|
|
DatasizeDec: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Datasize > j2.Config.Datasize
|
|
},
|
|
DedupInc: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Dedup < j2.Config.Dedup
|
|
},
|
|
DedupDec: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Dedup > j2.Config.Dedup
|
|
},
|
|
DefaultrouterInc: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Defaultrouter < j2.Config.Defaultrouter
|
|
},
|
|
DefaultrouterDec: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Defaultrouter > j2.Config.Defaultrouter
|
|
},
|
|
Defaultrouter6Inc: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Defaultrouter6 < j2.Config.Defaultrouter6
|
|
},
|
|
Defaultrouter6Dec: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Defaultrouter6 > j2.Config.Defaultrouter6
|
|
},
|
|
DependsInc: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Depends < j2.Config.Depends
|
|
},
|
|
DependsDec: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Depends > j2.Config.Depends
|
|
},
|
|
Devfs_rulesetInc: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Devfs_ruleset < j2.Config.Devfs_ruleset
|
|
},
|
|
Devfs_rulesetDec: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Devfs_ruleset > j2.Config.Devfs_ruleset
|
|
},
|
|
DhcpInc: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Dhcp < j2.Config.Dhcp
|
|
},
|
|
DhcpDec: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Dhcp > j2.Config.Dhcp
|
|
},
|
|
Enforce_statfsInc: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Enforce_statfs < j2.Config.Enforce_statfs
|
|
},
|
|
Enforce_statfsDec: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Enforce_statfs > j2.Config.Enforce_statfs
|
|
},
|
|
Exec_cleanInc: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Exec_clean < j2.Config.Exec_clean
|
|
},
|
|
Exec_cleanDec: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Exec_clean > j2.Config.Exec_clean
|
|
},
|
|
Exec_createdInc: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Exec_created < j2.Config.Exec_created
|
|
},
|
|
Exec_createdDec: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Exec_created > j2.Config.Exec_created
|
|
},
|
|
Exec_fibInc: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Exec_fib < j2.Config.Exec_fib
|
|
},
|
|
Exec_fibDec: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Exec_fib > j2.Config.Exec_fib
|
|
},
|
|
Exec_jail_userInc: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Exec_jail_user < j2.Config.Exec_jail_user
|
|
},
|
|
Exec_jail_userDec: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Exec_jail_user > j2.Config.Exec_jail_user
|
|
},
|
|
Exec_poststartInc: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Exec_poststart < j2.Config.Exec_poststart
|
|
},
|
|
Exec_poststartDec: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Exec_poststart > j2.Config.Exec_poststart
|
|
},
|
|
Exec_poststopInc: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Exec_poststop < j2.Config.Exec_poststop
|
|
},
|
|
Exec_poststopDec: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Exec_poststop > j2.Config.Exec_poststop
|
|
},
|
|
Exec_prestartInc: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Exec_prestart < j2.Config.Exec_prestart
|
|
},
|
|
Exec_prestartDec: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Exec_prestart > j2.Config.Exec_prestart
|
|
},
|
|
Exec_prestopInc: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Exec_prestop < j2.Config.Exec_prestop
|
|
},
|
|
Exec_prestopDec: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Exec_prestop > j2.Config.Exec_prestop
|
|
},
|
|
Exec_startInc: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Exec_start < j2.Config.Exec_start
|
|
},
|
|
Exec_startDec: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Exec_start > j2.Config.Exec_start
|
|
},
|
|
Exec_stopInc: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Exec_stop < j2.Config.Exec_stop
|
|
},
|
|
Exec_stopDec: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Exec_stop > j2.Config.Exec_stop
|
|
},
|
|
Exec_system_jail_userInc: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Exec_system_jail_user < j2.Config.Exec_system_jail_user
|
|
},
|
|
Exec_system_jail_userDec: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Exec_system_jail_user > j2.Config.Exec_system_jail_user
|
|
},
|
|
Exec_system_userInc: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Exec_system_user < j2.Config.Exec_system_user
|
|
},
|
|
Exec_system_userDec: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Exec_system_user > j2.Config.Exec_system_user
|
|
},
|
|
Exec_timeoutInc: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Exec_timeout < j2.Config.Exec_timeout
|
|
},
|
|
Exec_timeoutDec: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Exec_timeout > j2.Config.Exec_timeout
|
|
},
|
|
Host_domainnameInc: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Host_domainname < j2.Config.Host_domainname
|
|
},
|
|
Host_domainnameDec: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Host_domainname > j2.Config.Host_domainname
|
|
},
|
|
Host_hostnameInc: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Host_hostname < j2.Config.Host_hostname
|
|
},
|
|
Host_hostnameDec: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Host_hostname > j2.Config.Host_hostname
|
|
},
|
|
Host_hostuuidInc: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Host_hostuuid < j2.Config.Host_hostuuid
|
|
},
|
|
Host_hostuuidDec: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Host_hostuuid > j2.Config.Host_hostuuid
|
|
},
|
|
Host_timeInc: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Host_time < j2.Config.Host_time
|
|
},
|
|
Host_timeDec: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Host_time > j2.Config.Host_time
|
|
},
|
|
HostidInc: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Hostid < j2.Config.Hostid
|
|
},
|
|
HostidDec: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Hostid > j2.Config.Hostid
|
|
},
|
|
Hostid_strict_checkInc: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Hostid_strict_check < j2.Config.Hostid_strict_check
|
|
},
|
|
Hostid_strict_checkDec: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Hostid_strict_check > j2.Config.Hostid_strict_check
|
|
},
|
|
InterfacesInc: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Interfaces < j2.Config.Interfaces
|
|
},
|
|
InterfacesDec: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Interfaces > j2.Config.Interfaces
|
|
},
|
|
Ip_hostnameInc: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Ip_hostname < j2.Config.Ip_hostname
|
|
},
|
|
Ip_hostnameDec: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Ip_hostname > j2.Config.Ip_hostname
|
|
},
|
|
Ip4Inc: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Ip4 < j2.Config.Ip4
|
|
},
|
|
Ip4Dec: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Ip4 > j2.Config.Ip4
|
|
},
|
|
Ip4_addrInc: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Ip4_addr < j2.Config.Ip4_addr
|
|
},
|
|
Ip4_addrDec: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Ip4_addr > j2.Config.Ip4_addr
|
|
},
|
|
Ip4_saddrselInc: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Ip4_saddrsel < j2.Config.Ip4_saddrsel
|
|
},
|
|
Ip4_saddrselDec: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Ip4_saddrsel > j2.Config.Ip4_saddrsel
|
|
},
|
|
Ip6Inc: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Ip6 < j2.Config.Ip6
|
|
},
|
|
Ip6Dec: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Ip6 > j2.Config.Ip6
|
|
},
|
|
Ip6_addrInc: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Ip6_addr < j2.Config.Ip6_addr
|
|
},
|
|
Ip6_addrDec: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Ip6_addr > j2.Config.Ip6_addr
|
|
},
|
|
Ip6_saddrselInc: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Ip6_saddrsel < j2.Config.Ip6_saddrsel
|
|
},
|
|
Ip6_saddrselDec: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Ip6_saddrsel > j2.Config.Ip6_saddrsel
|
|
},
|
|
Jail_zfsInc: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Jail_zfs < j2.Config.Jail_zfs
|
|
},
|
|
Jail_zfsDec: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Jail_zfs > j2.Config.Jail_zfs
|
|
},
|
|
Jail_zfs_datasetInc: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Jail_zfs_dataset < j2.Config.Jail_zfs_dataset
|
|
},
|
|
Jail_zfs_datasetDec: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Jail_zfs_dataset > j2.Config.Jail_zfs_dataset
|
|
},
|
|
Jail_zfs_mountpointInc: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Jail_zfs_mountpoint < j2.Config.Jail_zfs_mountpoint
|
|
},
|
|
Jail_zfs_mountpointDec: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Jail_zfs_mountpoint > j2.Config.Jail_zfs_mountpoint
|
|
},
|
|
JailtypeInc: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Jailtype < j2.Config.Jailtype
|
|
},
|
|
JailtypeDec: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Jailtype > j2.Config.Jailtype
|
|
},
|
|
Last_startedInc: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Last_started < j2.Config.Last_started
|
|
},
|
|
Last_startedDec: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Last_started > j2.Config.Last_started
|
|
},
|
|
Localhost_ipInc: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Localhost_ip < j2.Config.Localhost_ip
|
|
},
|
|
Localhost_ipDec: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Localhost_ip > j2.Config.Localhost_ip
|
|
},
|
|
Login_flagsInc: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Login_flags < j2.Config.Login_flags
|
|
},
|
|
Login_flagsDec: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Login_flags > j2.Config.Login_flags
|
|
},
|
|
Mac_prefixInc: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Mac_prefix < j2.Config.Mac_prefix
|
|
},
|
|
Mac_prefixDec: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Mac_prefix > j2.Config.Mac_prefix
|
|
},
|
|
MaxprocInc: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Maxproc < j2.Config.Maxproc
|
|
},
|
|
MaxprocDec: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Maxproc > j2.Config.Maxproc
|
|
},
|
|
MemorylockedInc: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Memorylocked < j2.Config.Memorylocked
|
|
},
|
|
MemorylockedDec: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Memorylocked > j2.Config.Memorylocked
|
|
},
|
|
MemoryuseInc: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Memoryuse < j2.Config.Memoryuse
|
|
},
|
|
MemoryuseDec: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Memoryuse > j2.Config.Memoryuse
|
|
},
|
|
Min_dyn_devfs_rulesetInc: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Min_dyn_devfs_ruleset < j2.Config.Min_dyn_devfs_ruleset
|
|
},
|
|
Min_dyn_devfs_rulesetDec: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Min_dyn_devfs_ruleset > j2.Config.Min_dyn_devfs_ruleset
|
|
},
|
|
Mount_devfsInc: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Mount_devfs < j2.Config.Mount_devfs
|
|
},
|
|
Mount_devfsDec: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Mount_devfs > j2.Config.Mount_devfs
|
|
},
|
|
Mount_fdescfsInc: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Mount_fdescfs < j2.Config.Mount_fdescfs
|
|
},
|
|
Mount_fdescfsDec: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Mount_fdescfs > j2.Config.Mount_fdescfs
|
|
},
|
|
Mount_linprocfsInc: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Mount_linprocfs < j2.Config.Mount_linprocfs
|
|
},
|
|
Mount_linprocfsDec: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Mount_linprocfs > j2.Config.Mount_linprocfs
|
|
},
|
|
Mount_procfsInc: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Mount_procfs < j2.Config.Mount_procfs
|
|
},
|
|
Mount_procfsDec: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Mount_procfs > j2.Config.Mount_procfs
|
|
},
|
|
MountpointInc: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Mountpoint < j2.Config.Mountpoint
|
|
},
|
|
MountpointDec: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Mountpoint > j2.Config.Mountpoint
|
|
},
|
|
MsgqqueuedInc: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Msgqqueued < j2.Config.Msgqqueued
|
|
},
|
|
MsgqqueuedDec: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Msgqqueued > j2.Config.Msgqqueued
|
|
},
|
|
MsgqsizeInc: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Msgqsize < j2.Config.Msgqsize
|
|
},
|
|
MsgqsizeDec: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Msgqsize > j2.Config.Msgqsize
|
|
},
|
|
NatInc: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Nat < j2.Config.Nat
|
|
},
|
|
NatDec: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Nat > j2.Config.Nat
|
|
},
|
|
Nat_backendInc: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Nat_backend < j2.Config.Nat_backend
|
|
},
|
|
Nat_backendDec: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Nat_backend > j2.Config.Nat_backend
|
|
},
|
|
Nat_forwardsInc: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Nat_forwards < j2.Config.Nat_forwards
|
|
},
|
|
Nat_forwardsDec: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Nat_forwards > j2.Config.Nat_forwards
|
|
},
|
|
Nat_interfaceInc: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Nat_interface < j2.Config.Nat_interface
|
|
},
|
|
Nat_interfaceDec: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Nat_interface > j2.Config.Nat_interface
|
|
},
|
|
Nat_prefixInc: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Nat_prefix < j2.Config.Nat_prefix
|
|
},
|
|
Nat_prefixDec: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Nat_prefix > j2.Config.Nat_prefix
|
|
},
|
|
NmsgqInc: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Nmsgq < j2.Config.Nmsgq
|
|
},
|
|
NmsgqDec: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Nmsgq > j2.Config.Nmsgq
|
|
},
|
|
NotesInc: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Notes < j2.Config.Notes
|
|
},
|
|
NotesDec: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Notes > j2.Config.Notes
|
|
},
|
|
NsemInc: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Nsem < j2.Config.Nsem
|
|
},
|
|
NsemDec: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Nsem > j2.Config.Nsem
|
|
},
|
|
NsemopInc: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Nsemop < j2.Config.Nsemop
|
|
},
|
|
NsemopDec: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Nsemop > j2.Config.Nsemop
|
|
},
|
|
NshmInc: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Nshm < j2.Config.Nshm
|
|
},
|
|
NshmDec: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Nshm > j2.Config.Nshm
|
|
},
|
|
NthrInc: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Nthr < j2.Config.Nthr
|
|
},
|
|
NthrDec: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Nthr > j2.Config.Nthr
|
|
},
|
|
OpenfilesInc: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Openfiles < j2.Config.Openfiles
|
|
},
|
|
OpenfilesDec: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Openfiles > j2.Config.Openfiles
|
|
},
|
|
OriginInc: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Origin < j2.Config.Origin
|
|
},
|
|
OriginDec: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Origin > j2.Config.Origin
|
|
},
|
|
OwnerInc: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Owner < j2.Config.Owner
|
|
},
|
|
OwnerDec: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Owner > j2.Config.Owner
|
|
},
|
|
PcpuInc: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Pcpu < j2.Config.Pcpu
|
|
},
|
|
PcpuDec: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Pcpu > j2.Config.Pcpu
|
|
},
|
|
Plugin_nameInc: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Plugin_name < j2.Config.Plugin_name
|
|
},
|
|
Plugin_nameDec: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Plugin_name > j2.Config.Plugin_name
|
|
},
|
|
Plugin_repositoryInc: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Plugin_repository < j2.Config.Plugin_repository
|
|
},
|
|
Plugin_repositoryDec: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Plugin_repository > j2.Config.Plugin_repository
|
|
},
|
|
PriorityInc: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Priority < j2.Config.Priority
|
|
},
|
|
PriorityDec: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Priority > j2.Config.Priority
|
|
},
|
|
PseudoterminalsInc: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Pseudoterminals < j2.Config.Pseudoterminals
|
|
},
|
|
PseudoterminalsDec: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Pseudoterminals > j2.Config.Pseudoterminals
|
|
},
|
|
QuotaInc: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Quota < j2.Config.Quota
|
|
},
|
|
QuotaDec: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Quota > j2.Config.Quota
|
|
},
|
|
ReadbpsInc: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Readbps < j2.Config.Readbps
|
|
},
|
|
ReadbpsDec: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Readbps > j2.Config.Readbps
|
|
},
|
|
ReadiopsInc: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Readiops < j2.Config.Readiops
|
|
},
|
|
ReadiopsDec: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Readiops > j2.Config.Readiops
|
|
},
|
|
ReleaseInc: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Release < j2.Config.Release
|
|
},
|
|
ReleaseDec: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Release > j2.Config.Release
|
|
},
|
|
ReservationInc: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Reservation < j2.Config.Reservation
|
|
},
|
|
ReservationDec: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Reservation > j2.Config.Reservation
|
|
},
|
|
ResolverInc: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Resolver < j2.Config.Resolver
|
|
},
|
|
ResolverDec: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Resolver > j2.Config.Resolver
|
|
},
|
|
RlimitsInc: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Rlimits < j2.Config.Rlimits
|
|
},
|
|
RlimitsDec: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Rlimits > j2.Config.Rlimits
|
|
},
|
|
RtsoldInc: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Rtsold < j2.Config.Rtsold
|
|
},
|
|
RtsoldDec: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Rtsold > j2.Config.Rtsold
|
|
},
|
|
SecurelevelInc: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Securelevel < j2.Config.Securelevel
|
|
},
|
|
SecurelevelDec: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Securelevel > j2.Config.Securelevel
|
|
},
|
|
ShmsizeInc: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Shmsize < j2.Config.Shmsize
|
|
},
|
|
ShmsizeDec: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Shmsize > j2.Config.Shmsize
|
|
},
|
|
StacksizeInc: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Stacksize < j2.Config.Stacksize
|
|
},
|
|
StacksizeDec: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Stacksize > j2.Config.Stacksize
|
|
},
|
|
Stop_timeoutInc: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Stop_timeout < j2.Config.Stop_timeout
|
|
},
|
|
Stop_timeoutDec: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Stop_timeout > j2.Config.Stop_timeout
|
|
},
|
|
SwapuseInc: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Swapuse < j2.Config.Swapuse
|
|
},
|
|
SwapuseDec: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Swapuse > j2.Config.Swapuse
|
|
},
|
|
Sync_stateInc: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Sync_state < j2.Config.Sync_state
|
|
},
|
|
Sync_stateDec: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Sync_state > j2.Config.Sync_state
|
|
},
|
|
Sync_targetInc: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Sync_target < j2.Config.Sync_target
|
|
},
|
|
Sync_targetDec: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Sync_target > j2.Config.Sync_target
|
|
},
|
|
Sync_tgt_zpoolInc: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Sync_tgt_zpool < j2.Config.Sync_tgt_zpool
|
|
},
|
|
Sync_tgt_zpoolDec: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Sync_tgt_zpool > j2.Config.Sync_tgt_zpool
|
|
},
|
|
SysvmsgInc: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Sysvmsg < j2.Config.Sysvmsg
|
|
},
|
|
SysvmsgDec: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Sysvmsg > j2.Config.Sysvmsg
|
|
},
|
|
SysvsemInc: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Sysvsem < j2.Config.Sysvsem
|
|
},
|
|
SysvsemDec: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Sysvsem > j2.Config.Sysvsem
|
|
},
|
|
SysvshmInc: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Sysvshm < j2.Config.Sysvshm
|
|
},
|
|
SysvshmDec: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Sysvshm > j2.Config.Sysvshm
|
|
},
|
|
TemplateInc: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Template < j2.Config.Template
|
|
},
|
|
TemplateDec: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Template > j2.Config.Template
|
|
},
|
|
UsedInc: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Used < j2.Config.Used
|
|
},
|
|
UsedDec: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Used > j2.Config.Used
|
|
},
|
|
VmemoryuseInc: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Vmemoryuse < j2.Config.Vmemoryuse
|
|
},
|
|
VmemoryuseDec: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Vmemoryuse > j2.Config.Vmemoryuse
|
|
},
|
|
VnetInc: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Vnet < j2.Config.Vnet
|
|
},
|
|
VnetDec: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Vnet > j2.Config.Vnet
|
|
},
|
|
Vnet_default_interfaceInc: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Vnet_default_interface < j2.Config.Vnet_default_interface
|
|
},
|
|
Vnet_default_interfaceDec: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Vnet_default_interface > j2.Config.Vnet_default_interface
|
|
},
|
|
Vnet_interfacesInc: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Vnet_interfaces < j2.Config.Vnet_interfaces
|
|
},
|
|
Vnet_interfacesDec: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Vnet_interfaces > j2.Config.Vnet_interfaces
|
|
},
|
|
Vnet0_macInc: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Vnet0_mac < j2.Config.Vnet0_mac
|
|
},
|
|
Vnet0_macDec: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Vnet0_mac > j2.Config.Vnet0_mac
|
|
},
|
|
Vnet1_macInc: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Vnet1_mac < j2.Config.Vnet1_mac
|
|
},
|
|
Vnet1_macDec: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Vnet1_mac > j2.Config.Vnet1_mac
|
|
},
|
|
Vnet2_macInc: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Vnet2_mac < j2.Config.Vnet2_mac
|
|
},
|
|
Vnet2_macDec: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Vnet2_mac > j2.Config.Vnet2_mac
|
|
},
|
|
Vnet3_macInc: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Vnet3_mac < j2.Config.Vnet3_mac
|
|
},
|
|
Vnet3_macDec: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Vnet3_mac > j2.Config.Vnet3_mac
|
|
},
|
|
WallclockInc: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Wallclock < j2.Config.Wallclock
|
|
},
|
|
WallclockDec: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Wallclock > j2.Config.Wallclock
|
|
},
|
|
WritebpsInc: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Writebps < j2.Config.Writebps
|
|
},
|
|
WritebpsDec: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Writebps > j2.Config.Writebps
|
|
},
|
|
WriteiopsInc: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Writeiops < j2.Config.Writeiops
|
|
},
|
|
WriteiopsDec: func(j1, j2 *Jail) bool {
|
|
return j1.Config.Writeiops > j2.Config.Writeiops
|
|
},
|
|
}
|
|
|
|
js := JailSort{
|
|
ConfigPathInc: func(j1, j2 *Jail) bool {
|
|
return j1.ConfigPath < j2.ConfigPath
|
|
},
|
|
ConfigPathDec: func(j1, j2 *Jail) bool {
|
|
return j1.ConfigPath > j2.ConfigPath
|
|
},
|
|
DatastoreInc: func(j1, j2 *Jail) bool {
|
|
return j1.Datastore < j2.Datastore
|
|
},
|
|
DatastoreDec: func(j1, j2 *Jail) bool {
|
|
return j1.Datastore > j2.Datastore
|
|
},
|
|
InternalNameInc: func(j1, j2 *Jail) bool {
|
|
return j1.InternalName < j2.InternalName
|
|
},
|
|
InternalNameDec: func(j1, j2 *Jail) bool {
|
|
return j1.InternalName > j2.InternalName
|
|
},
|
|
JIDInc: func(j1, j2 *Jail) bool {
|
|
return j1.JID < j2.JID
|
|
},
|
|
JIDDec: func(j1, j2 *Jail) bool {
|
|
return j1.JID > j2.JID
|
|
},
|
|
NameInc: func(j1, j2 *Jail) bool {
|
|
return j1.Name < j2.Name
|
|
},
|
|
NameDec: func(j1, j2 *Jail) bool {
|
|
return j1.Name > j2.Name
|
|
},
|
|
RootPathInc: func(j1, j2 *Jail) bool {
|
|
return j1.RootPath < j2.RootPath
|
|
},
|
|
RootPathDec: func(j1, j2 *Jail) bool {
|
|
return j1.RootPath > j2.RootPath
|
|
},
|
|
RunningInc: func(j1, j2 *Jail) bool {
|
|
return !j1.Running && j2.Running
|
|
},
|
|
RunningDec: func(j1, j2 *Jail) bool {
|
|
return j1.Running && !j2.Running
|
|
},
|
|
ZpoolInc: func(j1, j2 *Jail) bool {
|
|
return j1.Zpool < j2.Zpool
|
|
},
|
|
ZpoolDec: func(j1, j2 *Jail) bool {
|
|
return j1.Zpool > j2.Zpool
|
|
},
|
|
Config: jcs,
|
|
}
|
|
|
|
return js
|
|
}
|
|
|
|
// jailSorter implements the Sort interface, sorting the jails within.
|
|
type jailSorter struct {
|
|
jails []Jail
|
|
less []jailLessFunc
|
|
}
|
|
|
|
// Sort sorts the argument slice according to the less functions passed to JailsOrderedBy.
|
|
func (js *jailSorter) Sort(jails []Jail) {
|
|
js.jails = jails
|
|
sort.Sort(js)
|
|
}
|
|
|
|
// JailsOrderedBy returns a Sorter that sorts using the less functions, in order.
|
|
// Call its Sort method to sort the data.
|
|
func JailsOrderedBy(less ...jailLessFunc) *jailSorter {
|
|
return &jailSorter{
|
|
less: less,
|
|
}
|
|
}
|
|
|
|
// Len is part of sort.Interface.
|
|
func (js *jailSorter) Len() int {
|
|
return len(js.jails)
|
|
}
|
|
|
|
// Swap is part of sort.Interface.
|
|
func (js *jailSorter) Swap(i, j int) {
|
|
js.jails[i], js.jails[j] = js.jails[j], js.jails[i]
|
|
}
|
|
|
|
// Less is part of sort.Interface. It is implemented by looping along the
|
|
// less functions until it finds a comparison that discriminates between
|
|
// the two items (one is less than the other). Note that it can call the
|
|
// less functions twice per call. We could change the functions to return
|
|
// -1, 0, 1 and reduce the number of calls for greater efficiency: an
|
|
// exercise for the reader.
|
|
func (js *jailSorter) Less(i, j int) bool {
|
|
p, q := &js.jails[i], &js.jails[j]
|
|
// Try all but the last comparison.
|
|
var k int
|
|
for k = 0; k < len(js.less)-1; k++ {
|
|
less := js.less[k]
|
|
switch {
|
|
case less(p, q):
|
|
// p < q, so we have a decision.
|
|
return true
|
|
case less(q, p):
|
|
// p > q, so we have a decision.
|
|
return false
|
|
}
|
|
// p == q; try the next comparison.
|
|
}
|
|
// All comparisons to here said "equal", so just return whatever
|
|
// the final comparison reports.
|
|
return js.less[k](p, q)
|
|
}
|
|
|
|
/*****************************************************************************
|
|
*
|
|
* Sorting snapshots
|
|
*
|
|
*****************************************************************************/
|
|
// This struct hold "sort by jail fields" functions
|
|
type snapshotLessFunc func(s1 *Snapshot, s2 *Snapshot) bool
|
|
|
|
func initSnapshotSortStruct() SnapshotSort {
|
|
ss := SnapshotSort{
|
|
NameInc: func(s1, s2 *Snapshot) bool {
|
|
return s1.Name < s2.Name
|
|
},
|
|
NameDec: func(s1, s2 *Snapshot) bool {
|
|
return s1.Name > s2.Name
|
|
},
|
|
DatastoreInc: func(s1, s2 *Snapshot) bool {
|
|
return s1.Datastore < s2.Datastore
|
|
},
|
|
DatastoreDec: func(s1, s2 *Snapshot) bool {
|
|
return s1.Datastore > s2.Datastore
|
|
},
|
|
JailnameInc: func(s1, s2 *Snapshot) bool {
|
|
return s1.Jailname < s2.Jailname
|
|
},
|
|
JailnameDec: func(s1, s2 *Snapshot) bool {
|
|
return s1.Jailname > s2.Jailname
|
|
},
|
|
MountpointInc: func(s1, s2 *Snapshot) bool {
|
|
return s1.Mountpoint < s2.Mountpoint
|
|
},
|
|
MountpointDec: func(s1, s2 *Snapshot) bool {
|
|
return s1.Mountpoint > s2.Mountpoint
|
|
},
|
|
UsedInc: func(s1, s2 *Snapshot) bool {
|
|
return s1.Used < s2.Used
|
|
},
|
|
UsedDec: func(s1, s2 *Snapshot) bool {
|
|
return s1.Used > s2.Used
|
|
},
|
|
ReferencedInc: func(s1, s2 *Snapshot) bool {
|
|
return s1.Referenced < s2.Referenced
|
|
},
|
|
ReferencedDec: func(s1, s2 *Snapshot) bool {
|
|
return s1.Referenced > s2.Referenced
|
|
},
|
|
CreationInc: func(s1, s2 *Snapshot) bool {
|
|
return s1.Creation.Unix() < s2.Creation.Unix()
|
|
},
|
|
CreationDec: func(s1, s2 *Snapshot) bool {
|
|
return s1.Creation.Unix() > s2.Creation.Unix()
|
|
},
|
|
}
|
|
return ss
|
|
}
|
|
|
|
// snapshotSorter implements the Sort interface, sorting the jails within.
|
|
type snapshotSorter struct {
|
|
snapshots []Snapshot
|
|
less []snapshotLessFunc
|
|
}
|
|
|
|
// Sort sorts the argument slice according to the less functions passed to OrderedBy.
|
|
func (ss *snapshotSorter) Sort(snapshots []Snapshot) {
|
|
ss.snapshots = snapshots
|
|
sort.Sort(ss)
|
|
}
|
|
|
|
// OrderedBy returns a Sorter that sorts using the less functions, in order.
|
|
// Call its Sort method to sort the data.
|
|
func SnapshotsOrderedBy(less ...snapshotLessFunc) *snapshotSorter {
|
|
return &snapshotSorter{
|
|
less: less,
|
|
}
|
|
}
|
|
|
|
// Len is part of sort.Interface.
|
|
func (ss *snapshotSorter) Len() int {
|
|
return len(ss.snapshots)
|
|
}
|
|
|
|
// Swap is part of sort.Interface.
|
|
func (ss *snapshotSorter) Swap(i, j int) {
|
|
ss.snapshots[i], ss.snapshots[j] = ss.snapshots[j], ss.snapshots[i]
|
|
}
|
|
|
|
// Less is part of sort.Interface. It is implemented by looping along the
|
|
// less functions until it finds a comparison that discriminates between
|
|
// the two items (one is less than the other). Note that it can call the
|
|
// less functions twice per call. We could change the functions to return
|
|
// -1, 0, 1 and reduce the number of calls for greater efficiency: an
|
|
// exercise for the reader.
|
|
func (ss *snapshotSorter) Less(i, j int) bool {
|
|
p, q := &ss.snapshots[i], &ss.snapshots[j]
|
|
// Try all but the last comparison.
|
|
var k int
|
|
for k = 0; k < len(ss.less)-1; k++ {
|
|
less := ss.less[k]
|
|
switch {
|
|
case less(p, q):
|
|
// p < q, so we have a decision.
|
|
return true
|
|
case less(q, p):
|
|
// p > q, so we have a decision.
|
|
return false
|
|
}
|
|
// p == q; try the next comparison.
|
|
}
|
|
// All comparisons to here said "equal", so just return whatever
|
|
// the final comparison reports.
|
|
return ss.less[k](p, q)
|
|
}
|
|
|
|
/*****************************************************************************
|
|
*
|
|
* Sorting datastores
|
|
*
|
|
*****************************************************************************/
|
|
// This struct hold "sort by datastores fields" functions
|
|
type datastoreLessFunc func(s1 *Datastore, s2 *Datastore) bool
|
|
|
|
func initDatastoreSortStruct() DatastoreSort {
|
|
ss := DatastoreSort{
|
|
NameInc: func(s1, s2 *Datastore) bool {
|
|
return s1.Name < s2.Name
|
|
},
|
|
NameDec: func(s1, s2 *Datastore) bool {
|
|
return s1.Name > s2.Name
|
|
},
|
|
MountpointInc: func(s1, s2 *Datastore) bool {
|
|
return s1.Mountpoint < s2.Mountpoint
|
|
},
|
|
MountpointDec: func(s1, s2 *Datastore) bool {
|
|
return s1.Mountpoint > s2.Mountpoint
|
|
},
|
|
ZFSDatasetInc: func(s1, s2 *Datastore) bool {
|
|
return s1.ZFSDataset < s2.ZFSDataset
|
|
},
|
|
ZFSDatasetDec: func(s1, s2 *Datastore) bool {
|
|
return s1.ZFSDataset > s2.ZFSDataset
|
|
},
|
|
UsedInc: func(s1, s2 *Datastore) bool {
|
|
return s1.Used < s2.Used
|
|
},
|
|
UsedDec: func(s1, s2 *Datastore) bool {
|
|
return s1.Used > s2.Used
|
|
},
|
|
ReferencedInc: func(s1, s2 *Datastore) bool {
|
|
return s1.Referenced < s2.Referenced
|
|
},
|
|
ReferencedDec: func(s1, s2 *Datastore) bool {
|
|
return s1.Referenced > s2.Referenced
|
|
},
|
|
AvailableInc: func(s1, s2 *Datastore) bool {
|
|
return s1.Available < s2.Available
|
|
},
|
|
AvailableDec: func(s1, s2 *Datastore) bool {
|
|
return s1.Available > s2.Available
|
|
},
|
|
}
|
|
return ss
|
|
}
|
|
|
|
// DatastoreSorter implements the Sort interface, sorting the jails within.
|
|
type DatastoreSorter struct {
|
|
Datastores []Datastore
|
|
less []datastoreLessFunc
|
|
}
|
|
|
|
// Sort sorts the argument slice according to the less functions passed to OrderedBy.
|
|
func (ss *DatastoreSorter) Sort(Datastores []Datastore) {
|
|
ss.Datastores = Datastores
|
|
sort.Sort(ss)
|
|
}
|
|
|
|
// OrderedBy returns a Sorter that sorts using the less functions, in order.
|
|
// Call its Sort method to sort the data.
|
|
func DatastoresOrderedBy(less ...datastoreLessFunc) *DatastoreSorter {
|
|
return &DatastoreSorter{
|
|
less: less,
|
|
}
|
|
}
|
|
|
|
// Len is part of sort.Interface.
|
|
func (ss *DatastoreSorter) Len() int {
|
|
return len(ss.Datastores)
|
|
}
|
|
|
|
// Swap is part of sort.Interface.
|
|
func (ss *DatastoreSorter) Swap(i, j int) {
|
|
ss.Datastores[i], ss.Datastores[j] = ss.Datastores[j], ss.Datastores[i]
|
|
}
|
|
|
|
// Less is part of sort.Interface. It is implemented by looping along the
|
|
// less functions until it finds a comparison that discriminates between
|
|
// the two items (one is less than the other). Note that it can call the
|
|
// less functions twice per call. We could change the functions to return
|
|
// -1, 0, 1 and reduce the number of calls for greater efficiency: an
|
|
// exercise for the reader.
|
|
func (ss *DatastoreSorter) Less(i, j int) bool {
|
|
p, q := &ss.Datastores[i], &ss.Datastores[j]
|
|
// Try all but the last comparison.
|
|
var k int
|
|
for k = 0; k < len(ss.less)-1; k++ {
|
|
less := ss.less[k]
|
|
switch {
|
|
case less(p, q):
|
|
// p < q, so we have a decision.
|
|
return true
|
|
case less(q, p):
|
|
// p > q, so we have a decision.
|
|
return false
|
|
}
|
|
// p == q; try the next comparison.
|
|
}
|
|
// All comparisons to here said "equal", so just return whatever
|
|
// the final comparison reports.
|
|
return ss.less[k](p, q)
|
|
}
|