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
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
|
package container
import (
"encoding/gob"
"fmt"
"slices"
"hakurei.app/check"
"hakurei.app/ext"
"hakurei.app/fhs"
)
const (
// intermediate root file name pattern for [MountOverlayOp.Upper];
// remains after apply returns
intermediatePatternOverlayUpper = "overlay.upper.*"
// intermediate root file name pattern for [MountOverlayOp.Work];
// remains after apply returns
intermediatePatternOverlayWork = "overlay.work.*"
)
func init() { gob.Register(new(MountOverlayOp)) }
const (
// OverlayEphemeralUnexpectedUpper is set when [MountOverlayOp.Work] is nil
// and [MountOverlayOp.Upper] holds an unexpected value.
OverlayEphemeralUnexpectedUpper = iota
// OverlayReadonlyLower is set when [MountOverlayOp.Lower] contains less than
// two entries when mounting readonly.
OverlayReadonlyLower
// OverlayEmptyLower is set when [MountOverlayOp.Lower] has length of zero.
OverlayEmptyLower
)
// OverlayArgumentError is returned for [MountOverlayOp] supplied with invalid argument.
type OverlayArgumentError struct {
Type uintptr
Value string
}
func (e *OverlayArgumentError) Error() string {
switch e.Type {
case OverlayEphemeralUnexpectedUpper:
return fmt.Sprintf("upperdir has unexpected value %q", e.Value)
case OverlayReadonlyLower:
return "readonly overlay requires at least two lowerdir"
case OverlayEmptyLower:
return "overlay requires at least one lowerdir"
default:
return fmt.Sprintf("invalid overlay argument error %#x", e.Type)
}
}
// Overlay is a helper for appending [MountOverlayOp] to [Ops].
func (f *Ops) Overlay(
target, state, work *check.Absolute,
layers ...*check.Absolute,
) *Ops {
*f = append(*f, &MountOverlayOp{
Target: target,
Lower: layers,
Upper: state,
Work: work,
})
return f
}
// OverlayEphemeral appends a [MountOverlayOp] with an ephemeral upperdir and workdir.
func (f *Ops) OverlayEphemeral(target *check.Absolute, layers ...*check.Absolute) *Ops {
return f.Overlay(target, fhs.AbsRoot, nil, layers...)
}
// OverlayReadonly appends a readonly [MountOverlayOp].
func (f *Ops) OverlayReadonly(target *check.Absolute, layers ...*check.Absolute) *Ops {
return f.Overlay(target, nil, nil, layers...)
}
// MountOverlayOp mounts [FstypeOverlay] on container path Target.
type MountOverlayOp struct {
Target *check.Absolute
// Any filesystem, does not need to be on a writable filesystem.
Lower []*check.Absolute
// Formatted for [OptionOverlayLowerdir].
//
// Resolved, prefixed and escaped during early.
lower []string
// The upperdir is normally on a writable filesystem.
//
// If Work is nil and Upper holds the special value [fhs.AbsRoot], an
// ephemeral upperdir and workdir will be set up.
//
// If both Work and Upper are nil, upperdir and workdir is omitted and the
// overlay is mounted readonly.
Upper *check.Absolute
// Formatted for [OptionOverlayUpperdir].
//
// Resolved, prefixed and escaped during early.
upper string
// The workdir needs to be an empty directory on the same filesystem as upperdir.
Work *check.Absolute
// Formatted for [OptionOverlayWorkdir].
//
// Resolved, prefixed and escaped during early.
work string
ephemeral bool
// Used internally for mounting to the intermediate root.
noPrefix bool
}
func (o *MountOverlayOp) Valid() bool {
if o == nil {
return false
}
if o.Work != nil && o.Upper == nil {
return false
}
if slices.Contains(o.Lower, nil) {
return false
}
return o.Target != nil
}
func (o *MountOverlayOp) early(_ *setupState, k syscallDispatcher) error {
if o.Work == nil && o.Upper != nil {
switch o.Upper.String() {
case fhs.Root: // ephemeral
o.ephemeral = true // intermediate root not yet available
default:
return &OverlayArgumentError{OverlayEphemeralUnexpectedUpper, o.Upper.String()}
}
}
// readonly handled in apply
if !o.ephemeral {
if o.Upper != o.Work && (o.Upper == nil || o.Work == nil) {
// unreachable
return OpStateError("overlay")
}
if o.Upper != nil {
if v, err := k.evalSymlinks(o.Upper.String()); err != nil {
return err
} else {
o.upper = toHost(v)
}
}
if o.Work != nil {
if v, err := k.evalSymlinks(o.Work.String()); err != nil {
return err
} else {
o.work = toHost(v)
}
}
}
o.lower = make([]string, len(o.Lower))
for i, a := range o.Lower { // nil checked in Valid
if v, err := k.evalSymlinks(a.String()); err != nil {
return err
} else {
o.lower[i] = toHost(v)
}
}
return nil
}
// mountOverlay sets up an overlay mount via [ext.FS].
func mountOverlay(target string, options [][2]string) error {
fs, err := ext.OpenFS(SourceOverlay, 0)
if err != nil {
return err
}
if err = fs.SetString("source", SourceOverlay); err != nil {
_ = fs.Close()
return err
}
for _, option := range options {
if err = fs.SetString(option[0], option[1]); err != nil {
_ = fs.Close()
return err
}
}
if err = fs.SetFlag(OptionOverlayUserxattr); err != nil {
_ = fs.Close()
return err
}
if err = fs.Mount(target, 0); err != nil {
_ = fs.Close()
return err
}
return fs.Close()
}
func (o *MountOverlayOp) apply(state *setupState, k syscallDispatcher) error {
target := o.Target.String()
if !o.noPrefix {
target = toSysroot(target)
}
if err := k.mkdirAll(target, state.ParentPerm); err != nil {
return err
}
if o.ephemeral {
var err error
// these directories are created internally, therefore early (absolute, symlink, prefix, escape) is bypassed
if o.upper, err = k.mkdirTemp(fhs.Root, intermediatePatternOverlayUpper); err != nil {
return err
}
if o.work, err = k.mkdirTemp(fhs.Root, intermediatePatternOverlayWork); err != nil {
return err
}
}
options := make([][2]string, 0, 2+len(o.lower))
if o.upper == zeroString && o.work == zeroString { // readonly
if len(o.Lower) < 2 {
return &OverlayArgumentError{OverlayReadonlyLower, zeroString}
}
// "upperdir=" and "workdir=" may be omitted. In that case the overlay will be read-only
} else {
if len(o.Lower) == 0 {
return &OverlayArgumentError{OverlayEmptyLower, zeroString}
}
options = append(options, [][2]string{
{OptionOverlayUpperdir, o.upper},
{OptionOverlayWorkdir, o.work},
}...)
}
for _, lower := range o.lower {
options = append(options, [2]string{OptionOverlayLowerdir + "+", lower})
}
return k.mountOverlay(target, options)
}
func (o *MountOverlayOp) late(*setupState, syscallDispatcher) error { return nil }
func (o *MountOverlayOp) Is(op Op) bool {
vo, ok := op.(*MountOverlayOp)
return ok && o.Valid() && vo.Valid() &&
o.Target.Is(vo.Target) &&
slices.EqualFunc(o.Lower, vo.Lower, func(a, v *check.Absolute) bool { return a.Is(v) }) &&
o.Upper.Is(vo.Upper) && o.Work.Is(vo.Work)
}
func (*MountOverlayOp) prefix() (string, bool) { return "mounting", true }
func (o *MountOverlayOp) String() string {
return fmt.Sprintf("overlay on %q with %d layers", o.Target, len(o.Lower))
}
|