-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathswitch_root.go
240 lines (230 loc) · 6.05 KB
/
switch_root.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
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
package ginit
import (
"errors"
"fmt"
"golang.org/x/sys/unix"
"io"
"os"
"path/filepath"
"syscall"
)
const (
ramfsMagic = 0x858458f6
tmpfsMagic = 0x01021994
switchBufSize = 32768
)
type SwitchOptions struct {
BufSize int
RootDev uint64
NewRoot string
}
func NewSwitchOptions(path string) (*SwitchOptions, error) {
// find the device of the root filesystem so we can avoid changing filesystem
info, err := os.Stat("/")
if err != nil {
return nil, err
}
return &SwitchOptions{
NewRoot: path,
BufSize: switchBufSize,
RootDev: info.Sys().(*syscall.Stat_t).Dev,
}, nil
}
func copyTree(opts SwitchOptions) filepath.WalkFunc {
return func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
// skip non directories
if !info.Mode().IsDir() {
return nil
}
dest := filepath.Join(opts.NewRoot, path)
// create the directory
if path == "/" {
// the mountpoint already exists but may have wrong mode, metadata
if err := os.Chmod(opts.NewRoot, info.Mode()); err != nil {
return err
}
} else {
if err := os.Mkdir(dest, info.Mode()); err != nil {
return err
}
}
if err := CopyFileInfo(info, dest); err != nil {
return err
}
// skip recurse into other filesystems
stat := info.Sys().(*syscall.Stat_t)
if opts.RootDev != stat.Dev {
return filepath.SkipDir
}
return nil
}
}
func copyFiles(opts SwitchOptions) filepath.WalkFunc {
return func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
// skip other filesystems
stat := info.Sys().(*syscall.Stat_t)
if opts.RootDev != stat.Dev && info.Mode().IsDir() {
return filepath.SkipDir
}
dest := filepath.Join(opts.NewRoot, path)
buf := make([]byte, opts.BufSize)
switch {
case info.Mode().IsDir():
// already done the directories
return nil
case info.Mode().IsRegular():
// TODO support hard links (currently not handled well in initramfs)
new, err := os.OpenFile(dest, os.O_WRONLY|os.O_CREATE, info.Mode())
if err != nil {
return err
}
old, err := os.Open(path)
if err != nil {
return err
}
if _, err := io.CopyBuffer(new, old, buf); err != nil {
return err
}
if err := old.Close(); err != nil {
return err
}
if err := new.Close(); err != nil {
return err
}
// it is ok if we do not remove all files now
err = os.Remove(path)
if err != nil {
// TODO: Remove this. Just curious to see what cannot be deleted.
fmt.Printf("error removing file: %s %s", path, err.Error())
}
case (info.Mode() & os.ModeSymlink) == os.ModeSymlink:
link, err := os.Readlink(path)
if err != nil {
return err
}
if err := os.Symlink(link, dest); err != nil {
return err
}
case (info.Mode() & os.ModeDevice) == os.ModeDevice:
if err := unix.Mknod(dest, uint32(info.Mode()), int(stat.Rdev)); err != nil {
return err
}
case (info.Mode() & os.ModeNamedPipe) == os.ModeNamedPipe:
// TODO support named pipes, although no real use case
return errors.New("Unsupported named pipe on rootfs")
case (info.Mode() & os.ModeSocket) == os.ModeSocket:
// TODO support sockets, although no real use case
return errors.New("Unsupported socket on rootfs")
default:
return errors.New("Unknown file type")
}
if err := CopyFileInfo(info, dest); err != nil {
return err
}
// TODO copy extended attributes if needed
return nil
}
}
func removeFiles(opts SwitchOptions) filepath.WalkFunc {
return func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
// ignore root itself
if path == "/" {
return nil
}
switch {
case info.Mode().IsDir():
// skip other filesystems (ie newRoot)
stat := info.Sys().(*syscall.Stat_t)
if opts.RootDev != stat.Dev {
return filepath.SkipDir
}
// do our best to delete
err = os.RemoveAll(path)
if err != nil {
// TODO: Remove this. Just curious to see what cannot be deleted.
fmt.Printf("error removing file: %s %s", path, err.Error())
}
return filepath.SkipDir
default:
err = os.RemoveAll(path)
if err != nil {
// TODO: Remove this. Just curious to see what cannot be deleted.
fmt.Printf("error removing file: %s %s", path, err.Error())
}
return nil
}
}
}
// CopyFileInfo takes an os.FileInfo and applies it
// to a file at the given path.
func CopyFileInfo(info os.FileInfo, path string) error {
// would rather use fd than path but Go makes this very difficult at present
stat := info.Sys().(*syscall.Stat_t)
if err := unix.Lchown(path, int(stat.Uid), int(stat.Gid)); err != nil {
return err
}
timespec := []unix.Timespec{unix.Timespec(stat.Atim), unix.Timespec(stat.Mtim)}
if err := unix.UtimesNanoAt(unix.AT_FDCWD, path, timespec, unix.AT_SYMLINK_NOFOLLOW); err != nil {
return err
}
// after chown suid bits may be dropped; re-set on non symlink files
if info.Mode()&os.ModeSymlink == 0 {
if err := os.Chmod(path, info.Mode()); err != nil {
return err
}
}
return nil
}
// SwitchRoot performs a memory efficient
// file copy of the root file system onto
// a new mount point and then pivots to
// the new location. Opts.NewRoot must
// already be mounted for this to work.
// Most of this code comes from Linuxkit's init package:
// https://github.com/linuxkit/linuxkit/blob/master/pkg/init/cmd/init/init.go
func SwitchRoot(opts SwitchOptions) error {
// Copy the directory tree of the current
// root path into the new mount point
err := filepath.Walk("/", copyTree(opts))
if err != nil {
return err
}
// Copy all the files of the root path
// into the new mount point
err = filepath.Walk("/", copyFiles(opts))
if err != nil {
return err
}
// chdir to the new root directory
if err := os.Chdir(opts.NewRoot); err != nil {
return err
}
// mount --move cwd (/mnt) to /
if err := Mount(
MountArgs{
Source: ".",
Target: "/",
Flags: unix.MS_MOVE,
},
); err != nil {
return err
}
// chroot to .
if err := unix.Chroot("."); err != nil {
return err
}
// chdir to "/" to fix up . and ..
if err := os.Chdir("/"); err != nil {
return err
}
return nil
}