gocage/cmd/stop.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)
}
}