Skip to content

Commit 7f223a0

Browse files
committed
refactor temp remount logic into util package, add unit tests
1 parent 4c1060c commit 7f223a0

File tree

6 files changed

+429
-71
lines changed

6 files changed

+429
-71
lines changed

envbuilder.go

Lines changed: 9 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ import (
2626
"syscall"
2727
"time"
2828

29+
ebutil "github.com/coder/envbuilder/internal/util"
30+
2931
dcontext "github.com/distribution/distribution/v3/context"
3032
"github.com/kballard/go-shellquote"
3133
"github.com/mattn/go-isatty"
@@ -47,7 +49,6 @@ import (
4749
"github.com/go-git/go-git/v5/plumbing/transport"
4850
v1 "github.com/google/go-containerregistry/pkg/v1"
4951
"github.com/google/go-containerregistry/pkg/v1/remote"
50-
"github.com/prometheus/procfs"
5152
"github.com/sirupsen/logrus"
5253
"github.com/tailscale/hujson"
5354
"golang.org/x/xerrors"
@@ -396,58 +397,11 @@ func Run(ctx context.Context, options Options) error {
396397
}
397398

398399
// temp move of all ro mounts
399-
tempRemounts := map[string]string{}
400-
401-
mountInfos, err := procfs.GetMounts()
400+
tempRemountDest := filepath.Join("/", MagicDir)
401+
ignorePrefixes := []string{tempRemountDest, "/proc", "/sys"}
402+
remount, err := ebutil.TempRemount(options.Logger, tempRemountDest, ignorePrefixes...)
402403
if err != nil {
403-
options.Logger(codersdk.LogLevelError, "Failed to read mountinfo: %s", err)
404-
}
405-
406-
prefixes := []string{filepath.Join("/", MagicDir), "/proc", "/sys"}
407-
for _, mountInfo := range mountInfos {
408-
options.Logger(codersdk.LogLevelTrace, "Found mountpoint %s", mountInfo.MountPoint)
409-
if _, ok := mountInfo.Options["ro"]; !ok {
410-
continue
411-
}
412-
options.Logger(codersdk.LogLevelTrace, "Found ro mountpoint %s ", mountInfo.MountPoint)
413-
414-
relevantMountpoint := true
415-
for _, prefix := range prefixes {
416-
if strings.HasPrefix(mountInfo.MountPoint, prefix) {
417-
options.Logger(codersdk.LogLevelTrace, "Mountpoint %s is NOT relevant (prefix: %s)", mountInfo.MountPoint, prefix)
418-
relevantMountpoint = false
419-
break
420-
}
421-
}
422-
if !relevantMountpoint {
423-
continue
424-
}
425-
426-
src := mountInfo.MountPoint
427-
tgt := filepath.Join("/", MagicDir, mountInfo.MountPoint)
428-
429-
options.Logger(codersdk.LogLevelTrace, "Mountpoint %s is relevant", src)
430-
431-
options.Logger(codersdk.LogLevelTrace, "Remount mountpoint %s to %s", src, tgt)
432-
433-
err := os.MkdirAll(tgt, 0750)
434-
if err != nil {
435-
options.Logger(codersdk.LogLevelError, "Could not create temp mountpoint %s for %s: %s", tgt, src, err.Error())
436-
continue
437-
}
438-
439-
err = syscall.Mount(src, tgt, "bind", syscall.MS_BIND, "")
440-
if err != nil {
441-
options.Logger(codersdk.LogLevelError, "Could not bindmount %s to %s: %s", src, tgt, err.Error())
442-
continue
443-
}
444-
err = syscall.Unmount(src, 0)
445-
if err != nil {
446-
options.Logger(codersdk.LogLevelError, "Could not unmount %s: %s", src, err.Error())
447-
continue
448-
}
449-
450-
tempRemounts[src] = tgt
404+
return fmt.Errorf("temp remount: %w", err)
451405
}
452406

453407
skippedRebuild := false
@@ -596,25 +550,9 @@ func Run(ctx context.Context, options Options) error {
596550
closeAfterBuild()
597551
}
598552

599-
for src, tgt := range tempRemounts {
600-
options.Logger(codersdk.LogLevelTrace, "Cleanup mountpoint %s", tgt)
601-
err := os.MkdirAll(src, 0750)
602-
if err != nil {
603-
options.Logger(codersdk.LogLevelError, "Could not create mountpoint %s for %s: %w", src, tgt, err.Error())
604-
continue
605-
}
606-
607-
err = syscall.Mount(tgt, src, "bind", syscall.MS_BIND, "")
608-
if err != nil {
609-
options.Logger(codersdk.LogLevelError, "Could not bindmount %s to %s: %s", tgt, src, err.Error())
610-
continue
611-
}
612-
613-
err = syscall.Unmount(tgt, 0)
614-
if err != nil {
615-
options.Logger(codersdk.LogLevelError, "Could not unmount %s: %s", tgt, err.Error())
616-
continue
617-
}
553+
if err := remount(); err != nil {
554+
// Don't fail at this point as it may be useful to be able to inspect the build.
555+
options.Logger(codersdk.LogLevelError, "recreate mountpoint: %w", err)
618556
}
619557

620558
// Create the magic file to indicate that this build

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,7 @@ require (
262262
go.opentelemetry.io/otel/trace v1.19.0 // indirect
263263
go.opentelemetry.io/proto/otlp v1.0.0 // indirect
264264
go.uber.org/atomic v1.11.0 // indirect
265+
go.uber.org/mock v0.4.0 // indirect
265266
go4.org/intern v0.0.0-20230525184215-6c62f75575cb // indirect
266267
go4.org/mem v0.0.0-20220726221520-4f986261bf13 // indirect
267268
go4.org/netipx v0.0.0-20230728180743-ad4cb58a6516 // indirect

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -910,6 +910,8 @@ go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
910910
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
911911
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
912912
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
913+
go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU=
914+
go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=
913915
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
914916
go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=
915917
go4.org/intern v0.0.0-20211027215823-ae77deb06f29/go.mod h1:cS2ma+47FKrLPdXFpr7CuxiTW3eyJbWew4qx0qtQWDA=

internal/util/mock_mounter_test.go

Lines changed: 98 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

internal/util/remount.go

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
package ebutil
2+
3+
import (
4+
"fmt"
5+
"os"
6+
"path/filepath"
7+
"strings"
8+
"syscall"
9+
10+
"github.com/coder/coder/v2/codersdk"
11+
"github.com/prometheus/procfs"
12+
)
13+
14+
// TempRemount iterates through all read-only mounted filesystems, bind-mounts them at dest,
15+
// and unmounts them from their original source. All mount points underneath ignorePrefixes
16+
// will not be touched.
17+
//
18+
// It is the responsibility of the caller to call the returned function
19+
// to restore the original mount points. If an error is encountered while attempting to perform
20+
// the operation, calling the returned remount function will make a best-effort attempt to
21+
// restore the original state.
22+
func TempRemount(logf func(codersdk.LogLevel, string, ...any), dest string, ignorePrefixes ...string) (remount func() error, err error,
23+
) {
24+
return tempRemount(&realMounter{}, logf, dest, ignorePrefixes...)
25+
}
26+
27+
func tempRemount(m mounter, logf func(codersdk.LogLevel, string, ...any), dest string, ignorePrefixes ...string) (remount func() error, err error) {
28+
mountInfos, err := m.GetMounts()
29+
if err != nil {
30+
return func() error { return nil }, fmt.Errorf("get mounts: %w", err)
31+
}
32+
33+
// temp move of all ro mounts
34+
mounts := map[string]string{}
35+
// closer to attempt to restore original mount points
36+
remount = func() error {
37+
for src, tgt := range mounts {
38+
err := m.MkdirAll(src, 0750)
39+
if err != nil {
40+
return fmt.Errorf("recreate original mountpoint %s: %w", src, err)
41+
}
42+
43+
err = m.Mount(tgt, src, "bind", syscall.MS_BIND, "")
44+
if err != nil {
45+
return fmt.Errorf("bind mount %s => %s: %w", tgt, src, err)
46+
}
47+
48+
err = m.Unmount(tgt, 0)
49+
if err != nil {
50+
return fmt.Errorf("unmount temporary dest %s: %w", tgt, err)
51+
}
52+
}
53+
return nil
54+
}
55+
56+
outer:
57+
for _, mountInfo := range mountInfos {
58+
if _, ok := mountInfo.Options["ro"]; !ok {
59+
logf(codersdk.LogLevelTrace, "skip rw mount %s", mountInfo.MountPoint)
60+
continue
61+
}
62+
63+
for _, prefix := range ignorePrefixes {
64+
if strings.HasPrefix(mountInfo.MountPoint, prefix) {
65+
logf(codersdk.LogLevelTrace, "skip mount %s under ignored prefix %s", mountInfo.MountPoint, prefix)
66+
continue outer
67+
}
68+
}
69+
70+
src := mountInfo.MountPoint
71+
tgt := filepath.Join("/", dest, src)
72+
err := m.MkdirAll(tgt, 0750)
73+
if err != nil {
74+
return remount, fmt.Errorf("create temp mountpoint %s: %w", dest, err)
75+
}
76+
77+
err = m.Mount(src, tgt, "bind", syscall.MS_BIND, "")
78+
if err != nil {
79+
return remount, fmt.Errorf("bind mount %s => %s: %s", src, dest, err.Error())
80+
}
81+
err = m.Unmount(src, 0)
82+
if err != nil {
83+
return remount, fmt.Errorf("temp unmount src %s: %s", src, err.Error())
84+
}
85+
86+
mounts[src] = tgt
87+
}
88+
89+
return remount, nil
90+
}
91+
92+
// mounter is an interface to system-level calls used by TempRemount.
93+
type mounter interface {
94+
// GetMounts wraps procfs.GetMounts
95+
GetMounts() ([]*procfs.MountInfo, error)
96+
// MkdirAll wraps os.MkdirAll
97+
MkdirAll(string, os.FileMode) error
98+
// Mount wraps syscall.Mount
99+
Mount(string, string, string, uintptr, string) error
100+
// Unmount wraps syscall.Unmount
101+
Unmount(string, int) error
102+
}
103+
104+
// realMounter implements mounter and actually does the thing.
105+
type realMounter struct{}
106+
107+
var _ mounter = &realMounter{}
108+
109+
func (m *realMounter) Mount(src string, dest string, fstype string, flags uintptr, data string) error {
110+
return syscall.Mount(src, dest, fstype, flags, data)
111+
}
112+
func (m *realMounter) Unmount(tgt string, flags int) error {
113+
return syscall.Unmount(tgt, flags)
114+
}
115+
func (m *realMounter) GetMounts() ([]*procfs.MountInfo, error) {
116+
return procfs.GetMounts()
117+
}
118+
func (m *realMounter) MkdirAll(path string, perm os.FileMode) error {
119+
return os.MkdirAll(path, perm)
120+
}

0 commit comments

Comments
 (0)