|
51 | 51 | mountsInitialized bool |
52 | 52 | // Supported tokens for filesystem links |
53 | 53 | uuidToken = "UUID" |
| 54 | + pathToken = "PATH" |
54 | 55 | // Location to perform UUID lookup |
55 | 56 | uuidDirectory = "/dev/disk/by-uuid" |
56 | 57 | ) |
@@ -399,78 +400,132 @@ func GetMount(mountpoint string) (*Mount, error) { |
399 | 400 | return mnt, nil |
400 | 401 | } |
401 | 402 |
|
402 | | -// getMountFromLink returns the Mount object which matches the provided link. |
403 | | -// This link is formatted as a tag (e.g. <token>=<value>) similar to how they |
404 | | -// appear in "/etc/fstab". Currently, only "UUID" tokens are supported. An error |
405 | | -// is returned if the link is invalid or we cannot load the required mount data. |
406 | | -// If a mount has been updated since the last call to one of the mount |
407 | | -// functions, run UpdateMountInfo to see the change. |
408 | | -func getMountFromLink(link string) (*Mount, error) { |
409 | | - // Parse the link |
410 | | - link = strings.TrimSpace(link) |
411 | | - linkComponents := strings.Split(link, "=") |
412 | | - if len(linkComponents) != 2 { |
413 | | - return nil, &ErrFollowLink{link, errors.New("invalid link format")} |
414 | | - } |
415 | | - token := linkComponents[0] |
416 | | - value := linkComponents[1] |
417 | | - if token != uuidToken { |
418 | | - return nil, &ErrFollowLink{link, errors.Errorf("token type %q not supported", token)} |
419 | | - } |
420 | | - |
421 | | - // See if UUID points to an existing device |
422 | | - searchPath := filepath.Join(uuidDirectory, value) |
423 | | - if filepath.Base(searchPath) != value { |
424 | | - return nil, &ErrFollowLink{link, errors.Errorf("invalid UUID format %q", value)} |
425 | | - } |
426 | | - deviceNumber, err := getDeviceNumber(searchPath) |
427 | | - if err != nil { |
428 | | - return nil, &ErrFollowLink{link, errors.Errorf("no device with UUID %s", value)} |
429 | | - } |
| 403 | +func uuidToDeviceNumber(uuid string) (DeviceNumber, error) { |
| 404 | + uuidSymlinkPath := filepath.Join(uuidDirectory, uuid) |
| 405 | + return getDeviceNumber(uuidSymlinkPath) |
| 406 | +} |
430 | 407 |
|
431 | | - // Lookup mountpoints for device in global store |
| 408 | +func deviceNumberToMount(deviceNumber DeviceNumber) (*Mount, bool) { |
432 | 409 | mountMutex.Lock() |
433 | 410 | defer mountMutex.Unlock() |
434 | 411 | if err := loadMountInfo(); err != nil { |
435 | | - return nil, err |
| 412 | + log.Print(err) |
| 413 | + return nil, false |
436 | 414 | } |
437 | 415 | mnt, ok := mountsByDevice[deviceNumber] |
438 | | - if !ok { |
439 | | - return nil, &ErrFollowLink{link, errors.Errorf("no mounts for device %q (%v)", |
440 | | - getDeviceName(deviceNumber), deviceNumber)} |
| 416 | + return mnt, ok |
| 417 | +} |
| 418 | + |
| 419 | +// getMountFromLink returns the main Mount, if any, for the filesystem which the |
| 420 | +// given link points to. The link should contain a series of token-value pairs |
| 421 | +// (<token>=<value>), one per line. The supported tokens are "UUID" and "PATH". |
| 422 | +// If the UUID is present and it works, then it is used; otherwise, PATH is used |
| 423 | +// if it is present. (The fallback from UUID to PATH will keep the link working |
| 424 | +// if the UUID of the target filesystem changes but its mountpoint doesn't.) |
| 425 | +// |
| 426 | +// If a mount has been updated since the last call to one of the mount |
| 427 | +// functions, make sure to run UpdateMountInfo first. |
| 428 | +func getMountFromLink(link string) (*Mount, error) { |
| 429 | + // Parse the link. |
| 430 | + uuid := "" |
| 431 | + path := "" |
| 432 | + lines := strings.Split(link, "\n") |
| 433 | + for _, line := range lines { |
| 434 | + line := strings.TrimSpace(line) |
| 435 | + if line == "" { |
| 436 | + continue |
| 437 | + } |
| 438 | + pair := strings.Split(line, "=") |
| 439 | + if len(pair) != 2 { |
| 440 | + log.Printf("ignoring invalid line in filesystem link file: %q", line) |
| 441 | + continue |
| 442 | + } |
| 443 | + token := pair[0] |
| 444 | + value := pair[1] |
| 445 | + switch token { |
| 446 | + case uuidToken: |
| 447 | + uuid = value |
| 448 | + case pathToken: |
| 449 | + path = value |
| 450 | + default: |
| 451 | + log.Printf("ignoring unknown link token %q", token) |
| 452 | + } |
441 | 453 | } |
442 | | - if mnt == nil { |
443 | | - return nil, &ErrFollowLink{link, filesystemLacksMainMountError(deviceNumber)} |
| 454 | + // At least one of UUID and PATH must be present. |
| 455 | + if uuid == "" && path == "" { |
| 456 | + return nil, &ErrFollowLink{link, errors.Errorf("invalid filesystem link file")} |
444 | 457 | } |
445 | | - return mnt, nil |
446 | | -} |
447 | 458 |
|
448 | | -// makeLink returns a link of the form <token>=<value> where value is the tag |
449 | | -// value for the Mount's device. Currently, only "UUID" tokens are supported. An |
450 | | -// error is returned if the mount has no device, or no UUID. |
451 | | -func makeLink(mnt *Mount, token string) (string, error) { |
452 | | - if token != uuidToken { |
453 | | - return "", &ErrMakeLink{mnt, errors.Errorf("token type %q not supported", token)} |
| 459 | + // Try following the UUID. |
| 460 | + errMsg := "" |
| 461 | + if uuid != "" { |
| 462 | + deviceNumber, err := uuidToDeviceNumber(uuid) |
| 463 | + if err == nil { |
| 464 | + mnt, ok := deviceNumberToMount(deviceNumber) |
| 465 | + if mnt != nil { |
| 466 | + log.Printf("resolved filesystem link using UUID %q", uuid) |
| 467 | + return mnt, nil |
| 468 | + } |
| 469 | + if ok { |
| 470 | + return nil, &ErrFollowLink{link, filesystemLacksMainMountError(deviceNumber)} |
| 471 | + } |
| 472 | + log.Printf("cannot find filesystem with UUID %q", uuid) |
| 473 | + } else { |
| 474 | + log.Printf("cannot find filesystem with UUID %q: %v", uuid, err) |
| 475 | + } |
| 476 | + errMsg += fmt.Sprintf("cannot find filesystem with UUID %q", uuid) |
| 477 | + if path != "" { |
| 478 | + log.Printf("falling back to using mountpoint path instead of UUID") |
| 479 | + } |
454 | 480 | } |
| 481 | + // UUID didn't work. As a fallback, try the mountpoint path. |
| 482 | + if path != "" { |
| 483 | + mnt, err := GetMount(path) |
| 484 | + if mnt != nil { |
| 485 | + log.Printf("resolved filesystem link using mountpoint path %q", path) |
| 486 | + return mnt, nil |
| 487 | + } |
| 488 | + log.Print(err) |
| 489 | + if errMsg == "" { |
| 490 | + errMsg = fmt.Sprintf("cannot find filesystem with main mountpoint %q", path) |
| 491 | + } else { |
| 492 | + errMsg += fmt.Sprintf(" or main mountpoint %q", path) |
| 493 | + } |
| 494 | + } |
| 495 | + // No method worked; return an error. |
| 496 | + return nil, &ErrFollowLink{link, errors.New(errMsg)} |
| 497 | +} |
455 | 498 |
|
| 499 | +func (mnt *Mount) getFilesystemUUID() (string, error) { |
456 | 500 | dirContents, err := ioutil.ReadDir(uuidDirectory) |
457 | 501 | if err != nil { |
458 | | - return "", &ErrMakeLink{mnt, err} |
| 502 | + return "", err |
459 | 503 | } |
460 | 504 | for _, fileInfo := range dirContents { |
461 | 505 | if fileInfo.Mode()&os.ModeSymlink == 0 { |
462 | 506 | continue // Only interested in UUID symlinks |
463 | 507 | } |
464 | 508 | uuid := fileInfo.Name() |
465 | | - deviceNumber, err := getDeviceNumber(filepath.Join(uuidDirectory, uuid)) |
| 509 | + deviceNumber, err := uuidToDeviceNumber(uuid) |
466 | 510 | if err != nil { |
467 | 511 | log.Print(err) |
468 | 512 | continue |
469 | 513 | } |
470 | 514 | if mnt.DeviceNumber == deviceNumber { |
471 | | - return fmt.Sprintf("%s=%s", uuidToken, uuid), nil |
| 515 | + return uuid, nil |
472 | 516 | } |
473 | 517 | } |
474 | | - return "", &ErrMakeLink{mnt, errors.Errorf("cannot determine UUID of device %q (%v)", |
475 | | - mnt.Device, mnt.DeviceNumber)} |
| 518 | + return "", errors.Errorf("cannot determine UUID of device %q (%v)", |
| 519 | + mnt.Device, mnt.DeviceNumber) |
| 520 | +} |
| 521 | + |
| 522 | +// makeLink creates the contents of a link file which will point to the given |
| 523 | +// filesystem. This will be a string of the form "UUID=<uuid>\nPATH=<path>\n". |
| 524 | +// An error is returned if the filesystem's UUID cannot be determined. |
| 525 | +func makeLink(mnt *Mount) (string, error) { |
| 526 | + uuid, err := mnt.getFilesystemUUID() |
| 527 | + if err != nil { |
| 528 | + return "", &ErrMakeLink{mnt, err} |
| 529 | + } |
| 530 | + return fmt.Sprintf("%s=%s\n%s=%s\n", uuidToken, uuid, pathToken, mnt.Path), nil |
476 | 531 | } |
0 commit comments