Reputation: 663
Here is my main.go, and I use go run main.go run sh
to create a process which runs shell in it.
package main
import (
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"strconv"
"syscall"
"github.com/sirupsen/logrus"
)
func main() {
if len(os.Args) < 2 {
logrus.Errorf("missing commands")
return
}
switch os.Args[1] {
case "run":
run()
case "child":
child()
default:
logrus.Errorf("wrong command")
return
}
}
func run() {
logrus.Info("Setting up...")
cmd := exec.Command("/proc/self/exe", append([]string{"child"}, os.Args[2:]...)...)
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.SysProcAttr = &syscall.SysProcAttr{
Cloneflags: syscall.CLONE_NEWUTS | syscall.CLONE_NEWPID | syscall.CLONE_NEWNS,
}
check(cmd.Run())
}
func child() {
logrus.Infof("Running %v", os.Args[2:])
cmd := exec.Command(os.Args[2], os.Args[3:]...)
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
check(syscall.Sethostname([]byte("newhost")))
check(syscall.Chroot("/root/busybox"))
check(os.Chdir("/"))
check(syscall.Mount("proc", "proc", "proc", 0, ""))
check(syscall.Mount("tempdir", "temp", "tmpfs", 0, ""))
check(cmd.Run())
check(syscall.Unmount("proc", 0))
check(syscall.Unmount("temp", 0))
}
func check(err error) {
if err != nil {
logrus.Errorln(err)
}
}
When I run mount
in the new shell, it returns
proc on /proc type proc (rw,relatime)
tempdir on /temp type tmpfs (rw,relatime)
that just works fine.
But when I change the child function into
func child() {
...
check(os.Chdir("/"))
check(syscall.Mount("proc", "proc", "proc", 0, ""))
check(syscall.Mount("tempdir", "temp", "tmpfs", 0, ""))
defer check(syscall.Unmount("proc", 0))
defer check(syscall.Unmount("temp", 0))
check(cmd.Run())
}
and run mount
again, it returns mount: no /proc/mounts
.
defer
is illustrated to defer the function after it to the end of the outer function. But it seems that syscall.Umount()
is invoked before cmd.Run()
. Thanks for your help.
Upvotes: 0
Views: 488
Reputation: 166724
The Go Programming Language Specification
Each time a "defer" statement executes, the function value and parameters to the call are evaluated as usual and saved anew but the actual function is not invoked. Instead, deferred functions are invoked immediately before the surrounding function returns, in the reverse order they were deferred.
For
check(cmd.Run())
check(syscall.Unmount("proc", 0))
check(syscall.Unmount("temp", 0))
defer in reverse order
defer check(syscall.Unmount("temp", 0))
defer check(syscall.Unmount("proc", 0))
check(cmd.Run())
For your second problem, "the file systems are unmounted before cmd.Run()
is invoked ", "Each time a "defer" statement executes, the function value and parameters to the call are evaluated as usual and saved anew but the actual function is not invoked."
Upvotes: 1
Reputation: 14279
defer check(syscall.Unmount("proc", 0))
defer check(syscall.Unmount("temp", 0))
You are defering the check
-call, but it's arguments are evaluated immediately, which means, syscall.Unmount
is not defered. See https://golang.org/ref/spec#Defer_statements
Each time a "defer" statement executes, the function value and parameters to the call are evaluated as usual and saved anew but the actual function is not invoked. Instead, deferred functions are invoked immediately before the surrounding function returns, in the reverse order they were deferred. If a deferred function value evaluates to nil, execution panics when the function is invoked, not when the "defer" statement is executed.
Use a anonymous function if you can't get rid of the check-call:
defer func() { check(syscall.Unmount("proc", 0)) }()
defer func() { check(syscall.Unmount("temp", 0)) }()
Upvotes: 2