-
Notifications
You must be signed in to change notification settings - Fork 523
/
switchroot.go
123 lines (109 loc) · 3.15 KB
/
switchroot.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
// +build linux
package switchroot
import (
"fmt"
"os"
"syscall"
"github.com/autonomy/dianemo/src/initramfs/cmd/init/pkg/mount"
"github.com/autonomy/dianemo/src/initramfs/cmd/init/pkg/mount/cgroups"
"golang.org/x/sys/unix"
)
func recursiveDelete(fd int) error {
parentDev, err := getDev(fd)
if err != nil {
return err
}
// The file descriptor is already open, but allocating a os.File here makes
// reading the files in the dir so much nicer.
dir := os.NewFile(uintptr(fd), "__ignored__")
// nolint: errcheck
defer dir.Close()
names, err := dir.Readdirnames(-1)
if err != nil {
return err
}
for _, name := range names {
// Loop here, but handle loop in separate function to make defer work as
// expected.
if err := recusiveDeleteInner(fd, parentDev, name); err != nil {
return err
}
}
return nil
}
func recusiveDeleteInner(parentFd int, parentDev uint64, childName string) error {
// O_DIRECTORY and O_NOFOLLOW make this open fail for all files and all
// symlinks (even when pointing to a dir). We need to filter out symlinks
// because getDev later follows them.
childFd, err := unix.Openat(parentFd, childName, unix.O_DIRECTORY|unix.O_NOFOLLOW, unix.O_RDWR)
if err != nil {
// childName points to either a file or a symlink, delete in any case.
if err := unix.Unlinkat(parentFd, childName, 0); err != nil {
return err
}
} else {
// Open succeeded, which means childName points to a real directory.
// nolint: errcheck
defer unix.Close(childFd)
// Don't descent into other file systems.
if childFdDev, err := getDev(childFd); err != nil {
return err
} else if childFdDev != parentDev {
// This means continue in recursiveDelete.
return nil
}
if err := recursiveDelete(childFd); err != nil {
return err
}
// Back from recursion, the directory is now empty, delete.
if err := unix.Unlinkat(parentFd, childName, unix.AT_REMOVEDIR); err != nil {
return err
}
}
return nil
}
func getDev(fd int) (dev uint64, err error) {
var stat unix.Stat_t
if err := unix.Fstat(fd, &stat); err != nil {
return 0, err
}
return stat.Dev, nil
}
// Switch performs a switch_root equivalent. See
// https://github.com/karelzak/util-linux/blob/master/sys-utils/switch_root.c
func Switch(s string) error {
// Mount the ROOT and DATA block devices at the new root.
if err := mount.Mount(s); err != nil {
panic(err)
}
// Move the special mount points to the new root.
if err := mount.Move(s); err != nil {
panic(err)
}
// Mount the cgroups file systems to the new root.
if err := cgroups.Mount(s); err != nil {
panic(err)
}
if err := unix.Chdir(s); err != nil {
return fmt.Errorf("chdir: %s", err.Error())
}
oldRoot, err := os.Open("/")
if err != nil {
return err
}
// nolint: errcheck
defer oldRoot.Close()
if err := mount.Finalize(s); err != nil {
return err
}
if err := unix.Chroot("."); err != nil {
return fmt.Errorf("chroot: %s", err.Error())
}
if err := recursiveDelete(int(oldRoot.Fd())); err != nil {
panic(err)
}
if err := syscall.Exec("/proc/self/exe", []string{"exe", "--switch-root"}, []string{}); err != nil {
return fmt.Errorf("exec /proc/self/exe: %s", err.Error())
}
return nil
}