-
Notifications
You must be signed in to change notification settings - Fork 43
/
Copy pathremount.go
222 lines (189 loc) · 6.55 KB
/
remount.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
package ebutil
import (
"errors"
"fmt"
"os"
"path/filepath"
"strings"
"sync"
"syscall"
"github.com/coder/envbuilder/log"
"github.com/hashicorp/go-multierror"
"github.com/prometheus/procfs"
)
// TempRemount iterates through all read-only mounted filesystems, bind-mounts them at dest,
// and unmounts them from their original source. All mount points underneath ignorePrefixes
// will not be touched.
//
// Some container runtimes such as sysbox-runc will mount in `/lib/modules` read-only.
// See https://github.com/nestybox/sysbox/issues/564
// This trips us up because:
// 1. We call a Kaniko library function `util.DeleteFilesystem` that does exactly what it says
// on the tin. If this hits a read-only volume mounted in, unhappiness is the result.
// 2. After deleting the filesystem and building the image, we extract it to the filesystem.
// If some paths mounted in via volume are present at that time, unhappiness is also likely
// to result -- especially in case of read-only mounts.
//
// To work around this we move the mounts out of the way temporarily by bind-mounting them
// while we do our thing, and move them back when we're done.
//
// It is the responsibility of the caller to call the returned function
// to restore the original mount points. If an error is encountered while attempting to perform
// the operation, calling the returned function will make a best-effort attempt to restore
// the original state.
func TempRemount(logf log.Func, dest string, ignorePrefixes ...string) (restore func() error, err error,
) {
return tempRemount(&realMounter{}, logf, dest, ignorePrefixes...)
}
func tempRemount(m mounter, logf log.Func, base string, ignorePrefixes ...string) (restore func() error, err error) {
mountInfos, err := m.GetMounts()
if err != nil {
return func() error { return nil }, fmt.Errorf("get mounts: %w", err)
}
libDir, err := libraryDirectoryPath(m)
if err != nil {
return func() error { return nil }, fmt.Errorf("get lib directory: %w", err)
}
libsSymlinks, err := libraryDirectorySymlinks(m, libDir)
if err != nil && !errors.Is(err, os.ErrNotExist) {
return func() error { return nil }, fmt.Errorf("read lib symlinks: %w", err)
}
// temp move of all ro mounts
mounts := map[string]string{}
var restoreOnce sync.Once
var merr error
// closer to attempt to restore original mount points
restore = func() error {
restoreOnce.Do(func() {
if len(mounts) == 0 {
return
}
newLibDir, err := libraryDirectoryPath(m)
if err != nil {
merr = multierror.Append(merr, fmt.Errorf("get new lib directory: %w", err))
return
}
for orig, moved := range mounts {
logf(log.LevelDebug, "restore mount %s", orig)
if err := remount(m, moved, orig, newLibDir, libsSymlinks); err != nil {
merr = multierror.Append(merr, fmt.Errorf("restore mount: %w", err))
}
}
})
return merr
}
outer:
for _, mountInfo := range mountInfos {
// TODO: do this for all mounts
if _, ok := mountInfo.Options["ro"]; !ok {
logf(log.LevelDebug, "skip rw mount %s", mountInfo.MountPoint)
continue
}
for _, prefix := range ignorePrefixes {
if strings.HasPrefix(mountInfo.MountPoint, prefix) {
logf(log.LevelDebug, "skip mount %s under ignored prefix %s", mountInfo.MountPoint, prefix)
continue outer
}
}
src := mountInfo.MountPoint
dest := filepath.Join(base, src)
logf(log.LevelDebug, "temp remount %s", src)
if err := remount(m, src, dest, libDir, libsSymlinks); err != nil {
return restore, fmt.Errorf("temp remount: %w", err)
}
mounts[src] = dest
}
return restore, nil
}
func remount(m mounter, src, dest, libDir string, libsSymlinks map[string][]string) error {
stat, err := m.Stat(src)
if err != nil {
return fmt.Errorf("stat %s: %w", src, err)
}
var destDir string
if stat.IsDir() {
destDir = dest
} else {
destDir = filepath.Dir(dest)
if destDir == usrLibDir || destDir == usrLibMultiarchDir {
// Restore mount to libDir
destDir = libDir
dest = filepath.Join(destDir, stat.Name())
}
}
if err := m.MkdirAll(destDir, 0o750); err != nil {
return fmt.Errorf("ensure path: %w", err)
}
if !stat.IsDir() {
f, err := m.OpenFile(dest, os.O_CREATE, 0o640)
if err != nil {
return fmt.Errorf("ensure file path: %w", err)
}
// This ensure the file is created, it will not be used. It can be closed immediately.
f.Close()
if symlinks, ok := libsSymlinks[stat.Name()]; ok {
srcDir := filepath.Dir(src)
if err := moveLibSymlinks(m, symlinks, srcDir, destDir); err != nil {
return err
}
}
}
if err := m.Mount(src, dest, "bind", syscall.MS_BIND, ""); err != nil {
return fmt.Errorf("bind mount %s => %s: %w", src, dest, err)
}
if err := m.Unmount(src, 0); err != nil {
return fmt.Errorf("unmount orig src %s: %w", src, err)
}
return nil
}
// mounter is an interface to system-level calls used by TempRemount.
type mounter interface {
// GetMounts wraps procfs.GetMounts
GetMounts() ([]*procfs.MountInfo, error)
// Stat wraps os.Stat
Stat(string) (os.FileInfo, error)
// MkdirAll wraps os.MkdirAll
MkdirAll(string, os.FileMode) error
// OpenFile wraps os.OpenFile
OpenFile(string, int, os.FileMode) (*os.File, error)
// Mount wraps syscall.Mount
Mount(string, string, string, uintptr, string) error
// Unmount wraps syscall.Unmount
Unmount(string, int) error
// ReadDir wraps os.ReadDir
ReadDir(string) ([]os.DirEntry, error)
// EvalSymlinks wraps filepath.EvalSymlinks
EvalSymlinks(string) (string, error)
// Rename wraps os.Rename
Rename(string, string) error
}
// realMounter implements mounter and actually does the thing.
type realMounter struct{}
var _ mounter = &realMounter{}
func (m *realMounter) Mount(src string, dest string, fstype string, flags uintptr, data string) error {
return syscall.Mount(src, dest, fstype, flags, data)
}
func (m *realMounter) Unmount(tgt string, flags int) error {
return syscall.Unmount(tgt, flags)
}
func (m *realMounter) GetMounts() ([]*procfs.MountInfo, error) {
return procfs.GetMounts()
}
func (m *realMounter) MkdirAll(path string, perm os.FileMode) error {
return os.MkdirAll(path, perm)
}
func (m *realMounter) OpenFile(name string, flag int, perm os.FileMode) (*os.File, error) {
return os.OpenFile(name, flag, perm)
}
func (m *realMounter) Stat(path string) (os.FileInfo, error) {
return os.Stat(path)
}
func (m *realMounter) ReadDir(name string) ([]os.DirEntry, error) {
return os.ReadDir(name)
}
func (m *realMounter) EvalSymlinks(path string) (string, error) {
return filepath.EvalSymlinks(path)
}
func (m *realMounter) Rename(oldpath, newpath string) error {
return os.Rename(oldpath, newpath)
}