talm

Форк
0
/
mount.go 
504 строки · 11.5 Кб
1
// This Source Code Form is subject to the terms of the Mozilla Public
2
// License, v. 2.0. If a copy of the MPL was not distributed with this
3
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
4

5
package mount
6

7
import (
8
	"bufio"
9
	"context"
10
	"errors"
11
	"fmt"
12
	"os"
13
	"path/filepath"
14
	"strings"
15
	"time"
16

17
	"github.com/siderolabs/go-blockdevice/blockdevice"
18
	"github.com/siderolabs/go-blockdevice/blockdevice/filesystem"
19
	"github.com/siderolabs/go-blockdevice/blockdevice/util"
20
	"github.com/siderolabs/go-retry/retry"
21
	"golang.org/x/sys/unix"
22

23
	"github.com/siderolabs/talos/pkg/machinery/constants"
24
	"github.com/siderolabs/talos/pkg/makefs"
25
)
26

27
// RetryFunc defines the requirements for retrying a mount point operation.
28
type RetryFunc func(*Point) error
29

30
// Mount mounts the device(s).
31
func Mount(mountpoints *Points) (err error) {
32
	iter := mountpoints.Iter()
33

34
	//  Mount the device(s).
35

36
	for iter.Next() {
37
		if _, err = mountMountpoint(iter.Value()); err != nil {
38
			return fmt.Errorf("error mounting %q: %w", iter.Value().Source(), err)
39
		}
40
	}
41

42
	if iter.Err() != nil {
43
		return iter.Err()
44
	}
45

46
	return nil
47
}
48

49
//nolint:gocyclo
50
func mountMountpoint(mountpoint *Point) (skipMount bool, err error) {
51
	// Repair the disk's partition table.
52
	if mountpoint.MountFlags.Check(Resize) {
53
		if _, err = mountpoint.ResizePartition(); err != nil {
54
			return false, fmt.Errorf("error resizing %w", err)
55
		}
56
	}
57

58
	if mountpoint.MountFlags.Check(SkipIfMounted) {
59
		skipMount, err = mountpoint.IsMounted()
60
		if err != nil {
61
			return false, fmt.Errorf("mountpoint is set to skip if mounted, but the mount check failed: %w", err)
62
		}
63
	}
64

65
	if mountpoint.MountFlags.Check(SkipIfNoFilesystem) && mountpoint.Fstype() == filesystem.Unknown {
66
		skipMount = true
67
	}
68

69
	if !skipMount {
70
		if err = mountpoint.Mount(); err != nil {
71
			if mountpoint.MountFlags.Check(SkipIfNoDevice) && errors.Is(err, unix.ENODEV) {
72
				if mountpoint.Logger != nil {
73
					mountpoint.Logger.Printf("error mounting: %q: %s", mountpoint.Source(), err)
74
				}
75

76
				return true, nil
77
			}
78

79
			return false, fmt.Errorf("error mounting: %w", err)
80
		}
81
	}
82

83
	// Grow the filesystem to the maximum allowed size.
84
	//
85
	// Growfs is called always, even if ResizePartition returns false to workaround failure scenario
86
	// when partition was resized, but growfs never got called.
87
	if mountpoint.MountFlags.Check(Resize) {
88
		if err = mountpoint.GrowFilesystem(); err != nil {
89
			return false, fmt.Errorf("error resizing filesystem: %w", err)
90
		}
91
	}
92

93
	return skipMount, nil
94
}
95

96
// Unmount unmounts the device(s).
97
func Unmount(mountpoints *Points) (err error) {
98
	iter := mountpoints.IterRev()
99
	for iter.Next() {
100
		mountpoint := iter.Value()
101
		if err = mountpoint.Unmount(); err != nil {
102
			return fmt.Errorf("unmount: %w", err)
103
		}
104
	}
105

106
	if iter.Err() != nil {
107
		return iter.Err()
108
	}
109

110
	return nil
111
}
112

113
// Move moves the device(s).
114
// TODO(andrewrynhard): We need to skip calling the move method on mountpoints
115
// that are a child of another mountpoint. The kernel will handle moving the
116
// child mountpoints for us.
117
func Move(mountpoints *Points, prefix string) (err error) {
118
	iter := mountpoints.Iter()
119
	for iter.Next() {
120
		mountpoint := iter.Value()
121
		if err = mountpoint.Move(prefix); err != nil {
122
			return fmt.Errorf("move: %w", err)
123
		}
124
	}
125

126
	if iter.Err() != nil {
127
		return iter.Err()
128
	}
129

130
	return nil
131
}
132

133
// PrefixMountTargets prefixes all mountpoints targets with fixed path.
134
func PrefixMountTargets(mountpoints *Points, targetPrefix string) error {
135
	iter := mountpoints.Iter()
136
	for iter.Next() {
137
		mountpoint := iter.Value()
138
		mountpoint.target = filepath.Join(targetPrefix, mountpoint.target)
139
	}
140

141
	return iter.Err()
142
}
143

144
//nolint:gocyclo
145
func mountRetry(f RetryFunc, p *Point, isUnmount bool) (err error) {
146
	err = retry.Constant(5*time.Second, retry.WithUnits(50*time.Millisecond)).Retry(func() error {
147
		if err = f(p); err != nil {
148
			switch err {
149
			case unix.EBUSY:
150
				return retry.ExpectedError(err)
151
			case unix.ENOENT, unix.ENODEV:
152
				// if udevd triggers BLKRRPART ioctl, partition device entry might disappear temporarily
153
				return retry.ExpectedError(err)
154
			case unix.EUCLEAN:
155
				if errRepair := p.Repair(); errRepair != nil {
156
					return fmt.Errorf("error repairing: %w", errRepair)
157
				}
158

159
				return retry.ExpectedError(err)
160
			case unix.EINVAL:
161
				isMounted, checkErr := p.IsMounted()
162
				if checkErr != nil {
163
					return retry.ExpectedError(checkErr)
164
				}
165

166
				if !isMounted && !isUnmount {
167
					if errRepair := p.Repair(); errRepair != nil {
168
						return fmt.Errorf("error repairing: %w", errRepair)
169
					}
170

171
					return retry.ExpectedError(err)
172
				}
173

174
				if !isMounted && isUnmount { // if partition is already unmounted, ignore EINVAL
175
					return nil
176
				}
177

178
				return err
179
			default:
180
				return err
181
			}
182
		}
183

184
		return nil
185
	})
186

187
	return err
188
}
189

190
// Point represents a Linux mount point.
191
type Point struct {
192
	source string
193
	target string
194
	fstype string
195
	flags  uintptr
196
	data   string
197
	*Options
198
}
199

200
// PointMap represents a unique set of mount points.
201
type PointMap = map[string]*Point
202

203
// Points represents an ordered set of mount points.
204
type Points struct {
205
	points PointMap
206
	order  []string
207
}
208

209
// NewMountPoint initializes and returns a Point struct.
210
func NewMountPoint(source, target, fstype string, flags uintptr, data string, setters ...Option) *Point {
211
	opts := NewDefaultOptions(setters...)
212

213
	p := &Point{
214
		source:  source,
215
		target:  target,
216
		fstype:  fstype,
217
		flags:   flags,
218
		data:    data,
219
		Options: opts,
220
	}
221

222
	if p.Prefix != "" {
223
		p.target = filepath.Join(p.Prefix, p.target)
224
	}
225

226
	if p.Options.ProjectQuota {
227
		if len(p.data) > 0 {
228
			p.data += ","
229
		}
230

231
		p.data += "prjquota"
232
	}
233

234
	return p
235
}
236

237
// NewMountPoints initializes and returns a Points struct.
238
func NewMountPoints() *Points {
239
	return &Points{
240
		points: make(PointMap),
241
	}
242
}
243

244
// Source returns the mount points source field.
245
func (p *Point) Source() string {
246
	return p.source
247
}
248

249
// Target returns the mount points target field.
250
func (p *Point) Target() string {
251
	return p.target
252
}
253

254
// Fstype returns the mount points fstype field.
255
func (p *Point) Fstype() string {
256
	return p.fstype
257
}
258

259
// Flags returns the mount points flags field.
260
func (p *Point) Flags() uintptr {
261
	return p.flags
262
}
263

264
// Data returns the mount points data field.
265
func (p *Point) Data() string {
266
	return p.data
267
}
268

269
// Mount attempts to retry a mount on EBUSY. It will attempt a retry
270
// every 100 milliseconds over the course of 5 seconds.
271
func (p *Point) Mount() (err error) {
272
	for _, hook := range p.Options.PreMountHooks {
273
		if err = hook(p); err != nil {
274
			return err
275
		}
276
	}
277

278
	if err = ensureDirectory(p.target); err != nil {
279
		return err
280
	}
281

282
	if p.MountFlags.Check(ReadOnly) {
283
		p.flags |= unix.MS_RDONLY
284
	}
285

286
	switch {
287
	case p.MountFlags.Check(Overlay):
288
		err = mountRetry(overlay, p, false)
289
	case p.MountFlags.Check(ReadonlyOverlay):
290
		err = mountRetry(readonlyOverlay, p, false)
291
	default:
292
		err = mountRetry(mount, p, false)
293
	}
294

295
	if err != nil {
296
		return err
297
	}
298

299
	if p.MountFlags.Check(Shared) {
300
		if err = mountRetry(share, p, false); err != nil {
301
			return fmt.Errorf("error sharing mount point %s: %+v", p.target, err)
302
		}
303
	}
304

305
	return nil
306
}
307

308
// Unmount attempts to retry an unmount on EBUSY. It will attempt a
309
// retry every 100 milliseconds over the course of 5 seconds.
310
func (p *Point) Unmount() (err error) {
311
	var mounted bool
312

313
	if mounted, err = p.IsMounted(); err != nil {
314
		return err
315
	}
316

317
	if mounted {
318
		if err = mountRetry(unmount, p, true); err != nil {
319
			return err
320
		}
321
	}
322

323
	for _, hook := range p.Options.PostUnmountHooks {
324
		if err = hook(p); err != nil {
325
			return err
326
		}
327
	}
328

329
	return nil
330
}
331

332
// IsMounted checks whether mount point is active under /proc/mounts.
333
func (p *Point) IsMounted() (bool, error) {
334
	f, err := os.Open("/proc/mounts")
335
	if err != nil {
336
		return false, err
337
	}
338

339
	defer f.Close() //nolint:errcheck
340

341
	scanner := bufio.NewScanner(f)
342
	for scanner.Scan() {
343
		fields := strings.Fields(scanner.Text())
344

345
		if len(fields) < 2 {
346
			continue
347
		}
348

349
		mountpoint := fields[1]
350

351
		if mountpoint == p.target {
352
			return true, nil
353
		}
354
	}
355

356
	return false, scanner.Err()
357
}
358

359
// Move moves a mountpoint to a new location with a prefix.
360
func (p *Point) Move(prefix string) (err error) {
361
	target := p.Target()
362
	mountpoint := NewMountPoint(target, target, "", unix.MS_MOVE, "", WithPrefix(prefix))
363

364
	if err = mountpoint.Mount(); err != nil {
365
		return fmt.Errorf("error moving mount point %s: %w", target, err)
366
	}
367

368
	return nil
369
}
370

371
// ResizePartition resizes a partition to the maximum size allowed.
372
func (p *Point) ResizePartition() (resized bool, err error) {
373
	var devname string
374

375
	if devname, err = util.DevnameFromPartname(p.Source()); err != nil {
376
		return false, err
377
	}
378

379
	bd, err := blockdevice.Open("/dev/"+devname, blockdevice.WithExclusiveLock(true))
380
	if err != nil {
381
		return false, fmt.Errorf("error opening block device %q: %w", devname, err)
382
	}
383

384
	//nolint:errcheck
385
	defer bd.Close()
386

387
	pt, err := bd.PartitionTable()
388
	if err != nil {
389
		return false, err
390
	}
391

392
	if err := pt.Repair(); err != nil {
393
		return false, err
394
	}
395

396
	for _, partition := range pt.Partitions().Items() {
397
		if partition.Name == constants.EphemeralPartitionLabel {
398
			resized, err := pt.Resize(partition)
399
			if err != nil {
400
				return false, err
401
			}
402

403
			if !resized {
404
				return false, nil
405
			}
406
		}
407
	}
408

409
	if err := pt.Write(); err != nil {
410
		return false, err
411
	}
412

413
	return true, nil
414
}
415

416
// GrowFilesystem grows a partition's filesystem to the maximum size allowed.
417
// NB: An XFS partition MUST be mounted, or this will fail.
418
func (p *Point) GrowFilesystem() (err error) {
419
	if err = makefs.XFSGrow(p.Target()); err != nil {
420
		return fmt.Errorf("xfs_growfs: %w", err)
421
	}
422

423
	return nil
424
}
425

426
// Repair repairs a partition's filesystem.
427
func (p *Point) Repair() error {
428
	if p.Logger != nil {
429
		p.Logger.Printf("filesystem on %s needs cleaning, running repair", p.Source())
430
	}
431

432
	if err := makefs.XFSRepair(p.Source(), p.Fstype()); err != nil {
433
		return fmt.Errorf("xfs_repair: %w", err)
434
	}
435

436
	if p.Logger != nil {
437
		p.Logger.Printf("filesystem successfully repaired on %s", p.Source())
438
	}
439

440
	return nil
441
}
442

443
func mount(p *Point) (err error) {
444
	return unix.Mount(p.source, p.target, p.fstype, p.flags, p.data)
445
}
446

447
func unmount(p *Point) error {
448
	return SafeUnmount(context.Background(), p.Logger, p.target)
449
}
450

451
func share(p *Point) error {
452
	return unix.Mount("", p.target, "", unix.MS_SHARED|unix.MS_REC, "")
453
}
454

455
func overlay(p *Point) error {
456
	parts := strings.Split(p.target, "/")
457
	prefix := strings.Join(parts[1:], "-")
458

459
	basePath := constants.VarSystemOverlaysPath
460

461
	if p.MountFlags.Check(SystemOverlay) {
462
		basePath = constants.SystemOverlaysPath
463
	}
464

465
	diff := fmt.Sprintf(filepath.Join(basePath, "%s-diff"), prefix)
466
	workdir := fmt.Sprintf(filepath.Join(basePath, "%s-workdir"), prefix)
467

468
	for _, target := range []string{diff, workdir} {
469
		if err := ensureDirectory(target); err != nil {
470
			return err
471
		}
472
	}
473

474
	lowerDir := p.target
475
	if p.source != "" {
476
		lowerDir = p.source
477
	}
478

479
	opts := fmt.Sprintf("lowerdir=%s,upperdir=%s,workdir=%s", lowerDir, diff, workdir)
480
	if err := unix.Mount("overlay", p.target, "overlay", 0, opts); err != nil {
481
		return fmt.Errorf("error creating overlay mount to %s: %w", p.target, err)
482
	}
483

484
	return nil
485
}
486

487
func readonlyOverlay(p *Point) error {
488
	opts := fmt.Sprintf("lowerdir=%s", p.source)
489
	if err := unix.Mount("overlay", p.target, "overlay", p.flags, opts); err != nil {
490
		return fmt.Errorf("error creating overlay mount to %s: %w", p.target, err)
491
	}
492

493
	return nil
494
}
495

496
func ensureDirectory(target string) (err error) {
497
	if _, err := os.Stat(target); os.IsNotExist(err) {
498
		if err = os.MkdirAll(target, 0o755); err != nil {
499
			return fmt.Errorf("error creating mount point directory %s: %w", target, err)
500
		}
501
	}
502

503
	return nil
504
}
505

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.