fix/exec-umask 2cd66dc

mount.go RawHistoryFolder

1// SPDX-License-Identifier: MIT
2
3package disk
4
5import (
6	"bufio"
7	"context"
8	"fmt"
9	"os"
10	"strings"
11
12	"bnema.dev/ermite-runner/internal/cmdrun"
13)
14
15// Mounter loop-mounts and unmounts scratch disks. It needs root (loop devices),
16// like the rest of the container mode.
17type Mounter struct {
18	Run cmdrun.Runner
19	// Mountinfo is /proc/self/mountinfo; tests point it at a fixture.
20	Mountinfo string
21}
22
23// Mount loop-mounts the ext4 file at dir (which must exist). The mount is
24// deliberately not nosuid/nodev: the nested dockerd keeps its overlay2 layers
25// here, and overlayfs opens the real files of a layer through the layer's own
26// mount, so device nodes and setuid binaries of container images would stop
27// working on a nodev/nosuid backing mount.
28func (m Mounter) Mount(ctx context.Context, file, dir string) error {
29	if _, err := m.Run.Run(ctx, nil, "mount", "-o", "loop,noatime", "-t", "ext4", file, dir); err != nil {
30		return fmt.Errorf("loop-mount scratch disk: %w", err)
31	}
32	return nil
33}
34
35// Unmount unmounts dir; the loop device is released with it (mount -o loop sets
36// autoclear). A directory that is not a mount point is not an error.
37func (m Mounter) Unmount(ctx context.Context, dir string) error {
38	mounted, err := m.IsMounted(dir)
39	if err != nil {
40		return err
41	}
42	if !mounted {
43		return nil
44	}
45	if _, err := m.Run.Run(ctx, nil, "umount", dir); err != nil {
46		return fmt.Errorf("unmount scratch disk: %w", err)
47	}
48	return nil
49}
50
51// IsMounted reports whether dir is a mount point.
52func (m Mounter) IsMounted(dir string) (bool, error) {
53	mp, err := m.MountPoints()
54	if err != nil {
55		return false, err
56	}
57	for _, p := range mp {
58		if p == dir {
59			return true, nil
60		}
61	}
62	return false, nil
63}
64
65// MountPoints lists every mount point of the mount namespace.
66func (m Mounter) MountPoints() ([]string, error) {
67	path := m.Mountinfo
68	if path == "" {
69		path = "/proc/self/mountinfo"
70	}
71	f, err := os.Open(path)
72	if err != nil {
73		return nil, err
74	}
75	defer func() { _ = f.Close() }()
76	var out []string
77	sc := bufio.NewScanner(f)
78	sc.Buffer(make([]byte, 64<<10), 1<<20)
79	for sc.Scan() {
80		if mp, ok := mountPointOf(sc.Text()); ok {
81			out = append(out, mp)
82		}
83	}
84	return out, sc.Err()
85}
86
87// mountPointOf extracts the mount point (5th field) of a mountinfo line, with
88// the kernel's octal escapes (\040 for a space) decoded.
89func mountPointOf(line string) (string, bool) {
90	f := strings.Fields(line)
91	if len(f) < 5 {
92		return "", false
93	}
94	return unescapeMountinfo(f[4]), true
95}
96
97func unescapeMountinfo(s string) string {
98	if !strings.Contains(s, `\`) {
99		return s
100	}
101	var b strings.Builder
102	for i := 0; i < len(s); i++ {
103		if s[i] == '\\' && i+3 < len(s) {
104			if v, ok := octal3(s[i+1 : i+4]); ok {
105				b.WriteByte(v)
106				i += 3
107				continue
108			}
109		}
110		b.WriteByte(s[i])
111	}
112	return b.String()
113}
114
115func octal3(s string) (byte, bool) {
116	if len(s) != 3 {
117		return 0, false
118	}
119	var v int
120	for _, c := range s {
121		if c < '0' || c > '7' {
122			return 0, false
123		}
124		v = v*8 + int(c-'0')
125	}
126	return byte(v), v < 256
127}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 4

Tags 11

↑↓ moveenter openesc close