Go的`defer`在卸载时表现不如预期。

huangapple go评论143阅读模式
英文:

Go `defer` does not behave as expected when Unmount

问题

以下是您提供的代码的翻译:

这是我的main.go文件,我使用go run main.go run sh来创建一个在其中运行shell的进程。

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("缺少命令")
		return
	}
	switch os.Args[1] {
	case "run":
		run()
	case "child":
		child()
	default:
		logrus.Errorf("错误的命令")
		return
	}
}

func run() {
	logrus.Info("设置中...")
	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("运行 %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)
	}
}

当我在新的shell中运行mount命令时,它返回以下内容:

proc on /proc type proc (rw,relatime)
tempdir on /temp type tmpfs (rw,relatime)

这个结果是正常的。

但是,当我将child函数更改为以下内容时:

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())
}

然后再次运行mount命令,它返回mount: no /proc/mounts

defer关键字用于将其后的函数推迟到外部函数的末尾执行。但似乎syscall.Umount()cmd.Run()之前被调用。感谢您的帮助。

英文:

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 (
&quot;io/ioutil&quot;
&quot;os&quot;
&quot;os/exec&quot;
&quot;path/filepath&quot;
&quot;strconv&quot;
&quot;syscall&quot;
&quot;github.com/sirupsen/logrus&quot;
)
func main() {
if len(os.Args) &lt; 2 {
logrus.Errorf(&quot;missing commands&quot;)
return
}
switch os.Args[1] {
case &quot;run&quot;:
run()
case &quot;child&quot;:
child()
default:
logrus.Errorf(&quot;wrong command&quot;)
return
}
}
func run() {
logrus.Info(&quot;Setting up...&quot;)
cmd := exec.Command(&quot;/proc/self/exe&quot;, append([]string{&quot;child&quot;}, os.Args[2:]...)...)
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.SysProcAttr = &amp;syscall.SysProcAttr{
Cloneflags: syscall.CLONE_NEWUTS | syscall.CLONE_NEWPID | syscall.CLONE_NEWNS,
}
check(cmd.Run())
}
func child() {
logrus.Infof(&quot;Running %v&quot;, 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(&quot;newhost&quot;)))
check(syscall.Chroot(&quot;/root/busybox&quot;))
check(os.Chdir(&quot;/&quot;))
check(syscall.Mount(&quot;proc&quot;, &quot;proc&quot;, &quot;proc&quot;, 0, &quot;&quot;))
check(syscall.Mount(&quot;tempdir&quot;, &quot;temp&quot;, &quot;tmpfs&quot;, 0, &quot;&quot;))
check(cmd.Run())
check(syscall.Unmount(&quot;proc&quot;, 0))
check(syscall.Unmount(&quot;temp&quot;, 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(&quot;/&quot;))
check(syscall.Mount(&quot;proc&quot;, &quot;proc&quot;, &quot;proc&quot;, 0, &quot;&quot;))
check(syscall.Mount(&quot;tempdir&quot;, &quot;temp&quot;, &quot;tmpfs&quot;, 0, &quot;&quot;))
defer check(syscall.Unmount(&quot;proc&quot;, 0))
defer check(syscall.Unmount(&quot;temp&quot;, 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.

答案1

得分: 2

你正在推迟调用check函数,但它的参数会立即求值,这意味着syscall.Unmount函数不会被推迟执行。请参考https://golang.org/ref/spec#Defer_statements

每次执行“defer”语句时,函数值和调用的参数会像平常一样被求值并重新保存,但实际的函数不会被调用。相反,推迟执行的函数会在包围它的函数返回之前立即被调用,按照它们被推迟的相反顺序执行。如果推迟执行的函数值为nil,则在调用函数时会引发panic,而不是在执行“defer”语句时。

如果无法摆脱check调用,请使用匿名函数:

defer func() { check(syscall.Unmount("proc", 0)) }()
defer func() { check(syscall.Unmount("temp", 0)) }()
英文:
defer check(syscall.Unmount(&quot;proc&quot;, 0))
defer check(syscall.Unmount(&quot;temp&quot;, 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(&quot;proc&quot;, 0)) }()
defer func() { check(syscall.Unmount(&quot;temp&quot;, 0)) }()

答案2

得分: 1

《Go编程语言规范》

延迟语句

每次执行"defer"语句时,函数值和调用的参数会像平常一样被求值并保存,但实际的函数不会被调用。相反,延迟函数会在包围它的函数返回之前立即被调用,按照它们被延迟的相反顺序

对于以下代码:

check(cmd.Run())
check(syscall.Unmount("proc", 0))
check(syscall.Unmount("temp", 0))

延迟按相反顺序执行:

defer check(syscall.Unmount("temp", 0))
defer check(syscall.Unmount("proc", 0))
check(cmd.Run())

对于你的第二个问题,"在调用cmd.Run()之前,文件系统被卸载","每次执行"defer"语句时,函数值和调用的参数会像平常一样被求值并保存,但实际的函数不会被调用。"

英文:

> 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(&quot;proc&quot;, 0))
check(syscall.Unmount(&quot;temp&quot;, 0))

defer in reverse order

defer check(syscall.Unmount(&quot;temp&quot;, 0))
defer check(syscall.Unmount(&quot;proc&quot;, 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."

huangapple
  • 本文由 发表于 2017年9月3日 11:01:13
  • 转载请务必保留本文链接:https://go.coder-hub.com/46019892.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定