Tsonglew
Tsonglew

Reputation: 663

Go `defer` does not behave as expected when Unmount

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

Answers (2)

peterSO
peterSO

Reputation: 166724

The Go Programming Language Specification

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.

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

tkausl
tkausl

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

Related Questions