1
2
3
4
19
20 package mount
21
22 import (
23 "fmt"
24 "os"
25 "os/exec"
26 "path/filepath"
27 "strconv"
28 "strings"
29 "syscall"
30
31 "k8s.io/klog/v2"
32 utilexec "k8s.io/utils/exec"
33 utilio "k8s.io/utils/io"
34 )
35
36 const (
37
38 expectedNumFieldsPerLine = 6
39
40 procMountsPath = "/proc/mounts"
41
42 procMountInfoPath = "/proc/self/mountinfo"
43
44 fsckErrorsCorrected = 1
45
46 fsckErrorsUncorrected = 4
47 )
48
49
50
51
52 type Mounter struct {
53 mounterPath string
54 withSystemd bool
55 }
56
57
58
59
60 func New(mounterPath string) Interface {
61 return &Mounter{
62 mounterPath: mounterPath,
63 withSystemd: detectSystemd(),
64 }
65 }
66
67
68
69
70
71
72 func (mounter *Mounter) Mount(source string, target string, fstype string, options []string) error {
73 return mounter.MountSensitive(source, target, fstype, options, nil)
74 }
75
76
77
78
79
80
81 func (mounter *Mounter) MountSensitive(source string, target string, fstype string, options []string, sensitiveOptions []string) error {
82
83
84 mounterPath := ""
85 bind, bindOpts, bindRemountOpts, bindRemountOptsSensitive := MakeBindOptsSensitive(options, sensitiveOptions)
86 if bind {
87 err := mounter.doMount(mounterPath, defaultMountCommand, source, target, fstype, bindOpts, bindRemountOptsSensitive)
88 if err != nil {
89 return err
90 }
91 return mounter.doMount(mounterPath, defaultMountCommand, source, target, fstype, bindRemountOpts, bindRemountOptsSensitive)
92 }
93
94 fsTypesNeedMounter := map[string]struct{}{
95 "nfs": {},
96 "glusterfs": {},
97 "ceph": {},
98 "cifs": {},
99 }
100 if _, ok := fsTypesNeedMounter[fstype]; ok {
101 mounterPath = mounter.mounterPath
102 }
103 return mounter.doMount(mounterPath, defaultMountCommand, source, target, fstype, options, sensitiveOptions)
104 }
105
106
107
108 func (mounter *Mounter) doMount(mounterPath string, mountCmd string, source string, target string, fstype string, options []string, sensitiveOptions []string) error {
109 mountArgs, mountArgsLogStr := MakeMountArgsSensitive(source, target, fstype, options, sensitiveOptions)
110 if len(mounterPath) > 0 {
111 mountArgs = append([]string{mountCmd}, mountArgs...)
112 mountArgsLogStr = mountCmd + " " + mountArgsLogStr
113 mountCmd = mounterPath
114 }
115
116 if mounter.withSystemd {
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139 mountCmd, mountArgs, mountArgsLogStr = AddSystemdScopeSensitive("systemd-run", target, mountCmd, mountArgs, mountArgsLogStr)
140 } else {
141
142
143
144 }
145
146
147 klog.V(4).Infof("Mounting cmd (%s) with arguments (%s)", mountCmd, mountArgsLogStr)
148 command := exec.Command(mountCmd, mountArgs...)
149 output, err := command.CombinedOutput()
150 if err != nil {
151 klog.Errorf("Mount failed: %v\nMounting command: %s\nMounting arguments: %s\nOutput: %s\n", err, mountCmd, mountArgsLogStr, string(output))
152 return fmt.Errorf("mount failed: %v\nMounting command: %s\nMounting arguments: %s\nOutput: %s",
153 err, mountCmd, mountArgsLogStr, string(output))
154 }
155 return err
156 }
157
158
159
160
161
162 func detectSystemd() bool {
163 if _, err := exec.LookPath("systemd-run"); err != nil {
164 klog.V(2).Infof("Detected OS without systemd")
165 return false
166 }
167
168
169
170
171 cmd := exec.Command("systemd-run", "--description=Kubernetes systemd probe", "--scope", "true")
172 output, err := cmd.CombinedOutput()
173 if err != nil {
174 klog.V(2).Infof("Cannot run systemd-run, assuming non-systemd OS")
175 klog.V(4).Infof("systemd-run failed with: %v", err)
176 klog.V(4).Infof("systemd-run output: %s", string(output))
177 return false
178 }
179 klog.V(2).Infof("Detected OS with systemd")
180 return true
181 }
182
183
184
185 func MakeMountArgs(source, target, fstype string, options []string) (mountArgs []string) {
186 mountArgs, _ = MakeMountArgsSensitive(source, target, fstype, options, nil )
187 return mountArgs
188 }
189
190
191
192 func MakeMountArgsSensitive(source, target, fstype string, options []string, sensitiveOptions []string) (mountArgs []string, mountArgsLogStr string) {
193
194
195 mountArgs = []string{}
196 mountArgsLogStr = ""
197 if len(fstype) > 0 {
198 mountArgs = append(mountArgs, "-t", fstype)
199 mountArgsLogStr += strings.Join(mountArgs, " ")
200 }
201 if len(options) > 0 || len(sensitiveOptions) > 0 {
202 combinedOptions := []string{}
203 combinedOptions = append(combinedOptions, options...)
204 combinedOptions = append(combinedOptions, sensitiveOptions...)
205 mountArgs = append(mountArgs, "-o", strings.Join(combinedOptions, ","))
206
207 mountArgsLogStr += " -o " + sanitizedOptionsForLogging(options, sensitiveOptions)
208 }
209 if len(source) > 0 {
210 mountArgs = append(mountArgs, source)
211 mountArgsLogStr += " " + source
212 }
213 mountArgs = append(mountArgs, target)
214 mountArgsLogStr += " " + target
215
216 return mountArgs, mountArgsLogStr
217 }
218
219
220
221
222 func AddSystemdScope(systemdRunPath, mountName, command string, args []string) (string, []string) {
223 descriptionArg := fmt.Sprintf("--description=Kubernetes transient mount for %s", mountName)
224 systemdRunArgs := []string{descriptionArg, "--scope", "--", command}
225 return systemdRunPath, append(systemdRunArgs, args...)
226 }
227
228
229
230
231 func AddSystemdScopeSensitive(systemdRunPath, mountName, command string, args []string, mountArgsLogStr string) (string, []string, string) {
232 descriptionArg := fmt.Sprintf("--description=Kubernetes transient mount for %s", mountName)
233 systemdRunArgs := []string{descriptionArg, "--scope", "--", command}
234 return systemdRunPath, append(systemdRunArgs, args...), strings.Join(systemdRunArgs, " ") + " " + mountArgsLogStr
235 }
236
237
238 func (mounter *Mounter) Unmount(target string) error {
239 klog.V(4).Infof("Unmounting %s", target)
240 command := exec.Command("umount", target)
241 output, err := command.CombinedOutput()
242 if err != nil {
243 return fmt.Errorf("unmount failed: %v\nUnmounting arguments: %s\nOutput: %s", err, target, string(output))
244 }
245 return nil
246 }
247
248
249 func (*Mounter) List() ([]MountPoint, error) {
250 return ListProcMounts(procMountsPath)
251 }
252
253
254
255
256
257
258
259
260 func (mounter *Mounter) IsLikelyNotMountPoint(file string) (bool, error) {
261 stat, err := os.Stat(file)
262 if err != nil {
263 return true, err
264 }
265 rootStat, err := os.Stat(filepath.Dir(strings.TrimSuffix(file, "/")))
266 if err != nil {
267 return true, err
268 }
269
270 if stat.Sys().(*syscall.Stat_t).Dev != rootStat.Sys().(*syscall.Stat_t).Dev {
271 return false, nil
272 }
273
274 return true, nil
275 }
276
277
278
279
280 func (mounter *Mounter) GetMountRefs(pathname string) ([]string, error) {
281 pathExists, pathErr := PathExists(pathname)
282 if !pathExists {
283 return []string{}, nil
284 } else if IsCorruptedMnt(pathErr) {
285 klog.Warningf("GetMountRefs found corrupted mount at %s, treating as unmounted path", pathname)
286 return []string{}, nil
287 } else if pathErr != nil {
288 return nil, fmt.Errorf("error checking path %s: %v", pathname, pathErr)
289 }
290 realpath, err := filepath.EvalSymlinks(pathname)
291 if err != nil {
292 return nil, err
293 }
294 return SearchMountPoints(realpath, procMountInfoPath)
295 }
296
297
298 func (mounter *SafeFormatAndMount) checkAndRepairFilesystem(source string) error {
299 klog.V(4).Infof("Checking for issues with fsck on disk: %s", source)
300 args := []string{"-a", source}
301 out, err := mounter.Exec.Command("fsck", args...).CombinedOutput()
302 if err != nil {
303 ee, isExitError := err.(utilexec.ExitError)
304 switch {
305 case err == utilexec.ErrExecutableNotFound:
306 klog.Warningf("'fsck' not found on system; continuing mount without running 'fsck'.")
307 case isExitError && ee.ExitStatus() == fsckErrorsCorrected:
308 klog.Infof("Device %s has errors which were corrected by fsck.", source)
309 case isExitError && ee.ExitStatus() == fsckErrorsUncorrected:
310 return NewMountError(HasFilesystemErrors, "'fsck' found errors on device %s but could not correct them: %s", source, string(out))
311 case isExitError && ee.ExitStatus() > fsckErrorsUncorrected:
312 klog.Infof("`fsck` error %s", string(out))
313 }
314 }
315 return nil
316 }
317
318
319 func (mounter *SafeFormatAndMount) formatAndMountSensitive(source string, target string, fstype string, options []string, sensitiveOptions []string) error {
320 readOnly := false
321 for _, option := range options {
322 if option == "ro" {
323 readOnly = true
324 break
325 }
326 }
327 if !readOnly {
328
329 for _, option := range sensitiveOptions {
330 if option == "ro" {
331 readOnly = true
332 break
333 }
334 }
335 }
336
337 options = append(options, "defaults")
338 mountErrorValue := UnknownMountError
339
340
341 existingFormat, err := mounter.GetDiskFormat(source)
342 if err != nil {
343 return NewMountError(GetDiskFormatFailed, "failed to get disk format of disk %s: %v", source, err)
344 }
345
346
347 if len(fstype) == 0 {
348 fstype = "ext4"
349 }
350
351 if existingFormat == "" {
352
353 if readOnly {
354 return NewMountError(UnformattedReadOnly, "cannot mount unformatted disk %s as we are manipulating it in read-only mode", source)
355 }
356
357
358 args := []string{source}
359 if fstype == "ext4" || fstype == "ext3" {
360 args = []string{
361 "-F",
362 "-m0",
363 source,
364 }
365 }
366
367 klog.Infof("Disk %q appears to be unformatted, attempting to format as type: %q with options: %v", source, fstype, args)
368 output, err := mounter.Exec.Command("mkfs."+fstype, args...).CombinedOutput()
369 if err != nil {
370
371 sensitiveOptionsLog := sanitizedOptionsForLogging(options, sensitiveOptions)
372 detailedErr := fmt.Sprintf("format of disk %q failed: type:(%q) target:(%q) options:(%q) errcode:(%v) output:(%v) ", source, fstype, target, sensitiveOptionsLog, err, string(output))
373 klog.Error(detailedErr)
374 return NewMountError(FormatFailed, detailedErr)
375 }
376
377 klog.Infof("Disk successfully formatted (mkfs): %s - %s %s", fstype, source, target)
378 } else {
379 if fstype != existingFormat {
380
381 mountErrorValue = FilesystemMismatch
382 klog.Warningf("Configured to mount disk %s as %s but current format is %s, things might break", source, existingFormat, fstype)
383 }
384
385 if !readOnly {
386
387 err := mounter.checkAndRepairFilesystem(source)
388 if err != nil {
389 return err
390 }
391 }
392 }
393
394
395 klog.V(4).Infof("Attempting to mount disk %s in %s format at %s", source, fstype, target)
396 if err := mounter.MountSensitive(source, target, fstype, options, sensitiveOptions); err != nil {
397 return NewMountError(mountErrorValue, err.Error())
398 }
399
400 return nil
401 }
402
403
404 func (mounter *SafeFormatAndMount) GetDiskFormat(disk string) (string, error) {
405 args := []string{"-p", "-s", "TYPE", "-s", "PTTYPE", "-o", "export", disk}
406 klog.V(4).Infof("Attempting to determine if disk %q is formatted using blkid with args: (%v)", disk, args)
407 dataOut, err := mounter.Exec.Command("blkid", args...).CombinedOutput()
408 output := string(dataOut)
409 klog.V(4).Infof("Output: %q, err: %v", output, err)
410
411 if err != nil {
412 if exit, ok := err.(utilexec.ExitError); ok {
413 if exit.ExitStatus() == 2 {
414
415
416
417
418 return "", nil
419 }
420 }
421 klog.Errorf("Could not determine if disk %q is formatted (%v)", disk, err)
422 return "", err
423 }
424
425 var fstype, pttype string
426
427 lines := strings.Split(output, "\n")
428 for _, l := range lines {
429 if len(l) <= 0 {
430
431 continue
432 }
433 cs := strings.Split(l, "=")
434 if len(cs) != 2 {
435 return "", fmt.Errorf("blkid returns invalid output: %s", output)
436 }
437
438
439 if cs[0] == "TYPE" {
440 fstype = cs[1]
441 } else if cs[0] == "PTTYPE" {
442 pttype = cs[1]
443 }
444 }
445
446 if len(pttype) > 0 {
447 klog.V(4).Infof("Disk %s detected partition table type: %s", disk, pttype)
448
449
450 return "unknown data, probably partitions", nil
451 }
452
453 return fstype, nil
454 }
455
456
457 func ListProcMounts(mountFilePath string) ([]MountPoint, error) {
458 content, err := utilio.ConsistentRead(mountFilePath, maxListTries)
459 if err != nil {
460 return nil, err
461 }
462 return parseProcMounts(content)
463 }
464
465 func parseProcMounts(content []byte) ([]MountPoint, error) {
466 out := []MountPoint{}
467 lines := strings.Split(string(content), "\n")
468 for _, line := range lines {
469 if line == "" {
470
471 continue
472 }
473 fields := strings.Fields(line)
474 if len(fields) != expectedNumFieldsPerLine {
475
476 return nil, fmt.Errorf("wrong number of fields (expected %d, got %d)", expectedNumFieldsPerLine, len(fields))
477 }
478
479 mp := MountPoint{
480 Device: fields[0],
481 Path: fields[1],
482 Type: fields[2],
483 Opts: strings.Split(fields[3], ","),
484 }
485
486 freq, err := strconv.Atoi(fields[4])
487 if err != nil {
488 return nil, err
489 }
490 mp.Freq = freq
491
492 pass, err := strconv.Atoi(fields[5])
493 if err != nil {
494 return nil, err
495 }
496 mp.Pass = pass
497
498 out = append(out, mp)
499 }
500 return out, nil
501 }
502
503
504
505
506
507
508
509
510
511 func SearchMountPoints(hostSource, mountInfoPath string) ([]string, error) {
512 mis, err := ParseMountInfo(mountInfoPath)
513 if err != nil {
514 return nil, err
515 }
516
517 mountID := 0
518 rootPath := ""
519 major := -1
520 minor := -1
521
522
523
524
525 for i := len(mis) - 1; i >= 0; i-- {
526 if hostSource == mis[i].MountPoint || PathWithinBase(hostSource, mis[i].MountPoint) {
527
528 mountID = mis[i].ID
529 rootPath = filepath.Join(mis[i].Root, strings.TrimPrefix(hostSource, mis[i].MountPoint))
530 major = mis[i].Major
531 minor = mis[i].Minor
532 break
533 }
534 }
535
536 if rootPath == "" || major == -1 || minor == -1 {
537 return nil, fmt.Errorf("failed to get root path and major:minor for %s", hostSource)
538 }
539
540 var refs []string
541 for i := range mis {
542 if mis[i].ID == mountID {
543
544 continue
545 }
546 if mis[i].Root == rootPath && mis[i].Major == major && mis[i].Minor == minor {
547 refs = append(refs, mis[i].MountPoint)
548 }
549 }
550
551 return refs, nil
552 }
553
View as plain text