gocage/cmd/stop.go

347 lines
8.5 KiB
Go

package cmd
import (
"fmt"
"os"
// "log"
"errors"
"os/exec"
"regexp"
// "reflect"
"strings"
)
// 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/%s", jail.Zpool, zd)
out, err := executeCommand(cmd)
if err != nil {
fmt.Printf(fmt.Sprintf("ERROR listing dataset %s/%s\n", jail.Zpool, 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/%s", jail.InternalName, jail.Zpool, ds[len(ds)-1])
_, err := executeCommand(cmd)
if err != nil {
fmt.Printf("ERROR unjailing %s/%s: %s\n", jail.Zpool, ds[len(ds)-1], err.Error())
return err
}
return nil
}
func destroyVNetInterfaces(jail *Jail) error {
for _, i := range strings.Split(jail.Config.Ip4_addr, ",") {
iname := fmt.Sprintf("%s.%d", strings.Split(i, "|")[0], jail.JID)
fmt.Printf("%s: ", iname)
_, err := executeCommand(fmt.Sprintf("ifconfig %s destroy", iname))
if err != nil {
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(jail *Jail) error {
cmd := "devfs rule showsets"
out, err := executeCommand(cmd)
if err != nil {
return errors.New(fmt.Sprintf("ERROR listing rulesets: %s", err.Error()))
}
for _, r := range strings.Split(out, "\n") {
if r == jail.Config.Devfs_ruleset {
cmd := fmt.Sprintf("devfs rule -s %s delset", jail.Config.Devfs_ruleset)
_, err := executeCommand(cmd)
return err
}
}
return nil
}
func umountJailFsFromHost(jail *Jail, 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], fmt.Sprintf("%s%s", jail.RootPath, mountpoint)) {
cmd = fmt.Sprintf("umount %s%s", jail.RootPath, mountpoint)
_, err := executeCommand(cmd)
if err != nil {
return errors.New(fmt.Sprintf("Error umounting %s%s: %s", jail.RootPath, mountpoint, err.Error()))
}
return nil
}
}
}
return nil
}
// 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 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
for _, j := range args {
fmt.Printf("> Stopping jail %s\n", j)
for _, rj := range gJails {
if rj.Name == j {
cj = &rj
break
}
}
if cj == nil {
fmt.Printf("Jail not found: %s\n", j)
continue
}
if cj.Running == false {
fmt.Printf("Jail %s is not running!\n", cj.Name)
continue
}
out, err := executeCommand(fmt.Sprintf("rctl jail:%s", cj.InternalName))
if err == nil && len(out) > 0 {
fmt.Printf(" > Remove RCTL rules:\n")
err := removeRctlRules(cj.InternalName, []string{""})
if err != nil {
fmt.Printf("ERROR: %s\n", err.Error())
} else {
fmt.Printf(" > Remove RCTL rules: OK\n")
}
}
if len(cj.Config.Exec_prestop) > 0 {
fmt.Printf(" > Execute prestop:\n")
_, err := executeCommand(cj.Config.Exec_prestop)
if err != nil {
fmt.Printf("ERROR: %s\n", err.Error())
} else {
fmt.Printf(" > Execute prestop: OK\n")
}
}
if len(cj.Config.Exec_stop) > 0 {
fmt.Printf(" > Execute stop:\n")
_, err := executeCommandInJail(cj, cj.Config.Exec_stop)
if err != nil {
fmt.Printf("ERROR: %s\n", err.Error())
} else {
fmt.Printf(" > Execute stop: OK\n")
}
}
if cj.Config.Jail_zfs > 0 {
fmt.Printf(" > Umount jailed ZFS:\n")
err := umountAndUnjailZFS(cj)
if err != nil {
fmt.Printf("ERROR: %s\n", err.Error())
} else {
fmt.Printf(" > Umount jailed ZFS: OK\n")
}
}
if cj.Config.Vnet > 0 && len(cj.Config.Ip4_addr) > 0 {
fmt.Printf(" > Destroy VNet interfaces:\n")
err := destroyVNetInterfaces(cj)
if err != nil {
fmt.Printf("ERROR: %s\n", err.Error())
} else {
fmt.Printf(" > Destroy VNet interfaces: OK\n")
}
}
/*fmt.Printf(" > Remove devfsruleset %s:\n", cj.Config.Devfs_ruleset)
err = deleteDevfsRuleset(cj)
if err != nil {
fmt.Printf("ERROR: %s\n", err.Error())
} else {
fmt.Printf(" > Remove devfsruleset %s: OK\n", cj.Config.Devfs_ruleset)
}*/
fmt.Printf(" > Stop jail %s:\n", cj.Name)
err = stopJail(cj)
if err != nil {
fmt.Printf("ERROR: %s\n", err.Error())
} else {
fmt.Printf(" > Stop jail %s: OK\n", cj.Name)
}
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 {
err = umountJailFsFromHost(cj, m.Mountpoint)
if err != nil {
fmt.Printf("ERROR: %s\n", err.Error())
errs += 1
}
}
if errs == 0 {
fmt.Printf(" > Umount mountpoints from %s: OK\n", fstab)
}
}
// 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)
}
cj.InternalName = ""
}
}