489 lines
12 KiB
Go
489 lines
12 KiB
Go
package cmd
|
|
|
|
import (
|
|
"os"
|
|
"fmt"
|
|
//"log"
|
|
"sync"
|
|
"errors"
|
|
"regexp"
|
|
"slices"
|
|
"os/exec"
|
|
"strconv"
|
|
"strings"
|
|
|
|
log "github.com/sirupsen/logrus"
|
|
)
|
|
|
|
// TODO : Use SYS_RCTL_GET_RACCT syscall
|
|
func removeRctlRules(jail string, rules []string) error {
|
|
var cmd []string
|
|
|
|
if len(rules) == 0 {
|
|
rules[0] = ""
|
|
}
|
|
|
|
for _, r := range rules {
|
|
if gUseSudo {
|
|
cmd = append(cmd, "sudo")
|
|
}
|
|
cmd = append(cmd, "/usr/bin/rctl")
|
|
cmd = append(cmd, "-r")
|
|
cmd = append(cmd, fmt.Sprintf("jail:%s:%s", jail, r))
|
|
|
|
// TODO : Log in another channel than stdout (will scramble display)
|
|
//log.Println(fmt.Sprintf("Removing all rules for jail %s: %s", jail, cmd))
|
|
|
|
//out, err := exec.Command(cmd[0], cmd[1:]...).Output()
|
|
_, err := exec.Command(cmd[0], cmd[1:]...).Output()
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// TODO: Validate with >1 dataset
|
|
func umountAndUnjailZFS(jail *Jail) error {
|
|
var ds []string
|
|
|
|
// Make sure we have a string array
|
|
ds = append(ds, jail.Config.Jail_zfs_dataset)
|
|
|
|
for _, zd := range ds {
|
|
// 1. Get dataset and childs
|
|
cmd := fmt.Sprintf("zfs list -H -r -o name -S name %s", zd)
|
|
out, err := executeCommand(cmd)
|
|
if err != nil {
|
|
fmt.Printf(fmt.Sprintf("ERROR listing dataset %s\n", zd))
|
|
os.Exit(1)
|
|
}
|
|
for _, c := range strings.Split(out, "\n") {
|
|
if len(c) == 0 {
|
|
continue
|
|
}
|
|
fmt.Printf("Unmounting dataset %s: ", c)
|
|
cmd := fmt.Sprintf("zfs umount %s", c)
|
|
_, err := executeCommandInJail(jail, cmd)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
fmt.Printf("OK\n")
|
|
}
|
|
}
|
|
|
|
// 2. Unjail dataset from the host
|
|
cmd := fmt.Sprintf("zfs unjail %s %s", jail.InternalName, ds[len(ds)-1])
|
|
_, err := executeCommand(cmd)
|
|
if err != nil {
|
|
fmt.Printf("ERROR unjailing %s: %s\n", ds[len(ds)-1], err.Error())
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func destroyVNetInterfaces(jail *Jail) error {
|
|
// Wherever ipv6/ipv4 is enabled, if interface exist we destroy it
|
|
var vnetnames []string
|
|
for _, i := range strings.Split(jail.Config.Ip4_addr, ",") {
|
|
if len(strings.Split(i, "|")) == 2 {
|
|
iname := fmt.Sprintf("%s.%d", strings.Split(i, "|")[0], jail.JID)
|
|
if !slices.Contains(vnetnames, iname) {
|
|
vnetnames = append(vnetnames, iname)
|
|
}
|
|
}
|
|
}
|
|
for _, i := range strings.Split(jail.Config.Ip6_addr, ",") {
|
|
if len(strings.Split(i, "|")) == 2 {
|
|
iname := fmt.Sprintf("%s.%d", strings.Split(i, "|")[0], jail.JID)
|
|
if !slices.Contains(vnetnames, iname) {
|
|
vnetnames = append(vnetnames, iname)
|
|
}
|
|
}
|
|
}
|
|
|
|
for _, iname := range vnetnames {
|
|
fmt.Printf(" >%s: ", iname)
|
|
_, err := executeCommand(fmt.Sprintf("ifconfig %s", iname))
|
|
if err != nil {
|
|
if strings.Contains(err.Error(), "does not exist") {
|
|
fmt.Printf("OK\n")
|
|
} else {
|
|
fmt.Printf("ERR: %v\n", err)
|
|
return err
|
|
}
|
|
} else {
|
|
_, err := executeCommand(fmt.Sprintf("ifconfig %s destroy", iname))
|
|
if err != nil {
|
|
fmt.Printf("ERR: %v\n", err)
|
|
return err
|
|
} else {
|
|
fmt.Printf("OK\n")
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Jails copy the ruleset referenced as "devfs_ruleset" when starting, getting a new devsf_ruleset ID.
|
|
// This new ID can be obtained with 'jls -j $JID devfs_ruleset'
|
|
// This is the ID which needs to be removed. Original ID referenced is json should not be deleted
|
|
// or else it will require a restart of "devfs" service.
|
|
// But, stoppign the jail already removes this >1000 ID.
|
|
// So no need to call this function.
|
|
func deleteDevfsRuleset(ruleset int) error {
|
|
cmd := "devfs rule showsets"
|
|
out, err := executeCommand(cmd)
|
|
if err != nil {
|
|
return errors.New(fmt.Sprintf("ERROR listing rulesets: %s", err.Error()))
|
|
}
|
|
|
|
rs := strconv.Itoa(ruleset)
|
|
for _, r := range strings.Split(out, "\n") {
|
|
if r == rs {
|
|
cmd := fmt.Sprintf("devfs rule -s %d delset", ruleset)
|
|
_, err := executeCommand(cmd)
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func umountFsFromHost(mountpoint string) error {
|
|
cmd := "mount -p"
|
|
out, err := executeCommand(cmd)
|
|
if err != nil {
|
|
return errors.New(fmt.Sprintf("Error executing mount: %s", err.Error()))
|
|
}
|
|
|
|
remSpPtrn := regexp.MustCompile(`\s+`)
|
|
for _, l := range strings.Split(out, "\n") {
|
|
f := strings.Split(remSpPtrn.ReplaceAllString(l, " "), " ")
|
|
if len(f) > 2 {
|
|
if strings.EqualFold(f[1], mountpoint) {
|
|
cmd = fmt.Sprintf("umount %s", mountpoint)
|
|
_, err := executeCommand(cmd)
|
|
if err != nil {
|
|
return errors.New(fmt.Sprintf("Error umounting %s: %s", mountpoint, err.Error()))
|
|
}
|
|
return nil
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func umountJailFsFromHost(jail *Jail, mountpoint string) error {
|
|
return umountFsFromHost(fmt.Sprintf("%s%s", jail.RootPath, mountpoint))
|
|
}
|
|
|
|
// Internal usage only
|
|
func stopJail(jail *Jail) error {
|
|
cmd := "jail -q"
|
|
|
|
// Test if conf file exist (iocage created)
|
|
cf := fmt.Sprintf("/var/run/jail.%s.conf", jail.InternalName)
|
|
file, err := os.Open(cf)
|
|
if err != nil {
|
|
file.Close()
|
|
cmd = fmt.Sprintf("%s -f %s", cmd, cf)
|
|
}
|
|
cmd = fmt.Sprintf("%s -r %s", cmd, jail.InternalName)
|
|
|
|
_, err = executeCommand(cmd)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Stop all running jails by reverse priority
|
|
// Parallelize up to gMaxThreads
|
|
// Only parallelize same priority level jails
|
|
func StopAllRunningJails() {
|
|
var stopList []Jail
|
|
var wg *sync.WaitGroup
|
|
var curThNb int
|
|
var curPri int
|
|
|
|
// Get boot enabled jails
|
|
for _, j := range gJails {
|
|
if j.Running == true {
|
|
stopList = append(stopList, j)
|
|
}
|
|
}
|
|
|
|
// Order by priority
|
|
js := initJailSortStruct()
|
|
fct, _, err := getStructFieldValue(js, "Config.PriorityDec")
|
|
if err != nil {
|
|
log.Errorf("ERROR getting JailSort struct field \"Config.PriorityDec\"\n")
|
|
return
|
|
}
|
|
JailsOrderedBy(fct.Interface().(jailLessFunc)).Sort(stopList)
|
|
|
|
|
|
wg = new(sync.WaitGroup)
|
|
curThNb = 0
|
|
for i, j := range stopList {
|
|
jFullName := fmt.Sprintf("%s/%s", j.Datastore, j.Name)
|
|
log.Debugf("Stopping %s with priority %s\n", jFullName, j.Config.Priority)
|
|
jailPri, err := strconv.Atoi(j.Config.Priority)
|
|
if err != nil {
|
|
panic(fmt.Sprintf("Invalid format for Priority (Jail %s)\n", jFullName))
|
|
}
|
|
|
|
if (curThNb >= gMaxThreads || i == 0) {
|
|
// FIXME : Use a pool instead of waiting for all threads to run a new one
|
|
wg.Wait()
|
|
curThNb = 0
|
|
|
|
wg.Add(1)
|
|
curThNb++
|
|
curPri = jailPri
|
|
go func(jailFullName string) {
|
|
defer wg.Done()
|
|
StopJail([]string{jailFullName})
|
|
}(jFullName)
|
|
} else {
|
|
if (curPri == jailPri) {
|
|
wg.Add(1)
|
|
curThNb++
|
|
go func(jailFullName string) {
|
|
defer wg.Done()
|
|
StopJail([]string{jailFullName})
|
|
}(jFullName)
|
|
} else {
|
|
wg.Wait()
|
|
curThNb = 0
|
|
|
|
wg.Add(1)
|
|
curThNb++
|
|
curPri = jailPri
|
|
go func(jailFullName string) {
|
|
defer wg.Done()
|
|
StopJail([]string{jailFullName})
|
|
}(jFullName)
|
|
}
|
|
}
|
|
}
|
|
wg.Wait()
|
|
}
|
|
|
|
/*
|
|
Stop jail:
|
|
Remove rctl rules
|
|
Execute prestop if set (jailhost perimeter)
|
|
Execute stop if set (inside jail)
|
|
Umount ZFS dataset from inside jail
|
|
Unjail ZFS dataset from jailhost
|
|
If VNet
|
|
Delete VNet interface on host
|
|
Delete devfs ruleset
|
|
Effectively stop jail process
|
|
Umount all mountpoints from $jail/fstab
|
|
Umount proc if set
|
|
Umount linprocfs if set
|
|
Umount fdescfs if set
|
|
Umount devfs if set
|
|
|
|
Use setfib for each command
|
|
|
|
Shouldnt rctl rules be removed last, when jail is stopped?
|
|
*/
|
|
func StopJail(args []string) {
|
|
// Current jail were stopping
|
|
var cj *Jail
|
|
var err error
|
|
|
|
for _, a := range args {
|
|
// Check if jail exist and is distinctly named
|
|
cj, err = getJailFromArray(a, []string{"basejail", "jail"}, gJails)
|
|
if err != nil {
|
|
fmt.Printf("Error getting jail: %s\n", err)
|
|
continue
|
|
}
|
|
|
|
if cj.Running == false {
|
|
fmt.Printf("Jail %s is not running!\n", cj.Name)
|
|
continue
|
|
}
|
|
|
|
fmt.Printf("> Stopping jail %s\n", cj.Name)
|
|
|
|
// Get and write new release into config.json
|
|
updateVersion(cj)
|
|
|
|
out, err := executeCommand(fmt.Sprintf("rctl jail:%s", cj.InternalName))
|
|
if err == nil && len(out) > 0 {
|
|
fmt.Printf(" > Remove RCTL rules:\n")
|
|
err := removeRctlRules(cj.InternalName, []string{""})
|
|
if err != nil {
|
|
fmt.Printf("ERROR: %s\n", err.Error())
|
|
} else {
|
|
fmt.Printf(" > Remove RCTL rules: OK\n")
|
|
}
|
|
}
|
|
|
|
if len(cj.Config.Exec_prestop) > 0 {
|
|
fmt.Printf(" > Execute pre-stop:\n")
|
|
_, err := executeCommand(cj.Config.Exec_prestop)
|
|
if err != nil {
|
|
fmt.Printf("ERROR: %s\n", err.Error())
|
|
} else {
|
|
fmt.Printf(" > Execute pre-stop: OK\n")
|
|
}
|
|
}
|
|
|
|
if len(cj.Config.Exec_stop) > 0 {
|
|
fmt.Printf(" > Execute stop:\n")
|
|
_, err := executeCommandInJail(cj, cj.Config.Exec_stop)
|
|
if err != nil {
|
|
fmt.Printf("ERROR: %s\n", err.Error())
|
|
} else {
|
|
fmt.Printf(" > Execute stop: OK\n")
|
|
}
|
|
}
|
|
|
|
if cj.Config.Jail_zfs > 0 {
|
|
fmt.Printf(" > Umount jailed ZFS:\n")
|
|
err := umountAndUnjailZFS(cj)
|
|
if err != nil {
|
|
fmt.Printf("ERROR: %s\n", err.Error())
|
|
} else {
|
|
fmt.Printf(" > Umount jailed ZFS: OK\n")
|
|
}
|
|
}
|
|
|
|
if cj.Config.Vnet > 0 && len(cj.Config.Ip4_addr) > 0 {
|
|
fmt.Printf(" > Destroy VNet interfaces:\n")
|
|
err := destroyVNetInterfaces(cj)
|
|
if err != nil {
|
|
fmt.Printf("ERROR: %s\n", err.Error())
|
|
} else {
|
|
fmt.Printf(" > Destroy VNet interfaces: OK\n")
|
|
}
|
|
}
|
|
|
|
fmt.Printf(" > Remove devfs ruleset %d: \n", cj.Devfs_ruleset)
|
|
err = deleteDevfsRuleset(cj.Devfs_ruleset)
|
|
if err != nil {
|
|
fmt.Printf("ERROR: %s\n", err.Error())
|
|
} else {
|
|
fmt.Printf(" > Remove devfsruleset %d: OK\n", cj.Devfs_ruleset)
|
|
}
|
|
|
|
fmt.Printf(" > Stop jail %s:\n", cj.Name)
|
|
err = stopJail(cj)
|
|
if err != nil {
|
|
fmt.Printf("ERROR: %s\n", err.Error())
|
|
} else {
|
|
fmt.Printf(" > Stop jail %s: OK\n", cj.Name)
|
|
}
|
|
|
|
if cj.Config.Mount_procfs > 0 {
|
|
fmt.Printf(" > Umount procfs:\n")
|
|
err := umountJailFsFromHost(cj, "/proc")
|
|
if err != nil {
|
|
fmt.Printf("ERROR: %s\n", err.Error())
|
|
} else {
|
|
fmt.Printf(" > Umount procfs: OK\n")
|
|
}
|
|
}
|
|
|
|
if cj.Config.Mount_linprocfs > 0 {
|
|
fmt.Printf(" > Umount linprocfs:\n")
|
|
err := umountJailFsFromHost(cj, "/compat/linux/proc")
|
|
if err != nil {
|
|
fmt.Printf("ERROR: %s\n", err.Error())
|
|
} else {
|
|
fmt.Printf(" > Umount linprocfs: OK\n")
|
|
}
|
|
}
|
|
|
|
// FIXME: /dev/fd is mounted even with Mount_fdescfs = 0 ?!
|
|
//if cj.Config.Mount_fdescfs > 0 {
|
|
fmt.Printf(" > Umount fdescfs:\n")
|
|
err = umountJailFsFromHost(cj, "/dev/fd")
|
|
if err != nil {
|
|
fmt.Printf("ERROR: %s\n", err.Error())
|
|
} else {
|
|
fmt.Printf(" > Umount fdescfs: OK\n")
|
|
}
|
|
//}
|
|
|
|
if cj.Config.Mount_devfs > 0 {
|
|
fmt.Printf(" > Umount devfs:\n")
|
|
err := umountJailFsFromHost(cj, "/dev")
|
|
if err != nil {
|
|
fmt.Printf("ERROR: %s\n", err.Error())
|
|
} else {
|
|
fmt.Printf(" > Umount devfs: OK\n")
|
|
}
|
|
}
|
|
|
|
// Remove local mounts from $JAIL/fstab
|
|
fstab := strings.Replace(cj.ConfigPath, "config.json", "fstab", 1)
|
|
mounts, err := getFstab(fstab)
|
|
if err != nil {
|
|
fmt.Printf("ERROR: %s\n", err.Error())
|
|
}
|
|
if len(mounts) > 0 {
|
|
fmt.Printf(" > Umount mountpoints from %s\n", fstab)
|
|
errs := 0
|
|
for _, m := range mounts {
|
|
log.Debugf("Umounting %s\n", m.Mountpoint)
|
|
err = umountFsFromHost(m.Mountpoint)
|
|
if err != nil {
|
|
fmt.Printf("ERROR: %s\n", err.Error())
|
|
errs += 1
|
|
}
|
|
}
|
|
if errs == 0 {
|
|
fmt.Printf(" > Umount mountpoints from %s: OK\n", fstab)
|
|
}
|
|
}
|
|
|
|
// TODO: Execute poststop
|
|
if len(cj.Config.Exec_poststop) > 0 {
|
|
fmt.Printf(" > Execute post-stop:\n")
|
|
_, err := executeCommand(cj.Config.Exec_poststop)
|
|
if err != nil {
|
|
fmt.Printf("ERROR: %s\n", err.Error())
|
|
} else {
|
|
fmt.Printf(" > Execute post-stop: OK\n")
|
|
}
|
|
}
|
|
|
|
// Remove parameter file
|
|
pfile := fmt.Sprintf("/var/run/jail.%s.conf", cj.InternalName)
|
|
if err = os.Remove(pfile); err != nil {
|
|
fmt.Printf("Error deleting parameter file %s\n", pfile)
|
|
}
|
|
|
|
// We need this to get a reference to cj.Running (bc cj.Running is just a copy of value in the scope of StopJail())
|
|
for i, j := range gJails {
|
|
if strings.EqualFold(j.Name, cj.Name) && strings.EqualFold(j.Datastore, cj.Datastore) {
|
|
if err = setStructFieldValue(&gJails[i], "Running", "false"); err != nil {
|
|
fmt.Printf("ERROR: setting Running property to false: %s\n", err.Error())
|
|
}
|
|
if err = setStructFieldValue(&gJails[i], "JID", "0"); err != nil {
|
|
fmt.Printf("ERROR: setting JID property to 0: %s\n", err.Error())
|
|
}
|
|
if err = setStructFieldValue(&gJails[i], "InternalName", ""); err != nil {
|
|
fmt.Printf("ERROR: clearing InternalName property: %s\n", err.Error())
|
|
}
|
|
if err = setStructFieldValue(&gJails[i], "Devfs_ruleset", "0"); err != nil {
|
|
fmt.Printf("ERROR: setting Devfs_ruleset property to 0: %s\n", err.Error())
|
|
}
|
|
}
|
|
}
|
|
writeConfigToDisk(cj, false)
|
|
|
|
}
|
|
}
|