package cmd import ( "os" "fmt" //"log" "sync" "errors" "regexp" "os/exec" //"reflect" "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 { if !strings.EqualFold(jail.Config.Ip4_addr, "none") { 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)) //_, err := executeScript(fmt.Sprintf("ifconfig %s destroy >/dev/null 2>&1", iname)) if err != nil { return err } else { fmt.Printf("OK\n") } } } if !strings.EqualFold(jail.Config.Ip6_addr, "none") { for _, i := range strings.Split(jail.Config.Ip6_addr, ",") { iname := fmt.Sprintf("%s.%d", strings.Split(i, "|")[0], jail.JID) fmt.Printf("%s: ", iname) _, err := executeCommand(fmt.Sprintf("ifconfig %s destroy", iname)) //_, err := executeScript(fmt.Sprintf("ifconfig %s destroy >/dev/null 2>&1", 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(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) } }