Skip to content

Commit be16498

Browse files
authored
Merge pull request #340 from google/fix-btrfs
Allow the root directory to be a btrfs filesystem
2 parents 8f619f9 + c2fb96c commit be16498

3 files changed

Lines changed: 128 additions & 29 deletions

File tree

filesystem/mountpoint.go

Lines changed: 61 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -38,13 +38,15 @@ import (
3838
)
3939

4040
var (
41-
// This map holds data about the state of the system's filesystems.
41+
// These maps hold data about the state of the system's filesystems.
4242
//
43-
// It only contains one Mount per filesystem, even if there are
43+
// They only contain one Mount per filesystem, even if there are
4444
// additional bind mounts, since we want to store fscrypt metadata in
45-
// only one place per filesystem. If it is ambiguous which Mount should
46-
// be used, an explicit nil entry is stored.
45+
// only one place per filesystem. When it is ambiguous which Mount
46+
// should be used for a filesystem, mountsByDevice will contain an
47+
// explicit nil entry, and mountsByPath won't contain an entry.
4748
mountsByDevice map[DeviceNumber]*Mount
49+
mountsByPath map[string]*Mount
4850
// Used to make the mount functions thread safe
4951
mountMutex sync.Mutex
5052
// True if the maps have been successfully initialized.
@@ -197,18 +199,18 @@ func findMainMount(filesystemMounts []*Mount) *Mount {
197199
// since non-last mounts were already excluded earlier.
198200
//
199201
// Also build the set of all mounted subtrees.
200-
mountsByPath := make(map[string]*mountpointTreeNode)
202+
filesystemMountsByPath := make(map[string]*mountpointTreeNode)
201203
allSubtrees := make(map[string]bool)
202204
for _, mnt := range filesystemMounts {
203-
mountsByPath[mnt.Path] = &mountpointTreeNode{mount: mnt}
205+
filesystemMountsByPath[mnt.Path] = &mountpointTreeNode{mount: mnt}
204206
allSubtrees[mnt.Subtree] = true
205207
}
206208

207209
// Divide the mounts into non-overlapping trees of mountpoints.
208-
for path, mntNode := range mountsByPath {
210+
for path, mntNode := range filesystemMountsByPath {
209211
for path != "/" && mntNode.parent == nil {
210212
path = filepath.Dir(path)
211-
if parent := mountsByPath[path]; parent != nil {
213+
if parent := filesystemMountsByPath[path]; parent != nil {
212214
mntNode.parent = parent
213215
parent.children = append(parent.children, mntNode)
214216
}
@@ -233,7 +235,7 @@ func findMainMount(filesystemMounts []*Mount) *Mount {
233235
// *all* mounted subtrees. Equivalently, select a mountpoint tree in
234236
// which every uncontained subtree is mounted.
235237
var mainMount *Mount
236-
for _, mntNode := range mountsByPath {
238+
for _, mntNode := range filesystemMountsByPath {
237239
mnt := mntNode.mount
238240
if mntNode.parent != nil {
239241
continue
@@ -260,8 +262,10 @@ func findMainMount(filesystemMounts []*Mount) *Mount {
260262

261263
// This is separate from loadMountInfo() only for unit testing.
262264
func readMountInfo(r io.Reader) error {
263-
mountsByPath := make(map[string]*Mount)
264265
mountsByDevice = make(map[DeviceNumber]*Mount)
266+
mountsByPath = make(map[string]*Mount)
267+
allMountsByDevice := make(map[DeviceNumber][]*Mount)
268+
allMountsByPath := make(map[string]*Mount)
265269

266270
scanner := bufio.NewScanner(r)
267271
for scanner.Scan() {
@@ -281,19 +285,22 @@ func readMountInfo(r io.Reader) error {
281285
// Note this overrides the info if we have seen the mountpoint
282286
// earlier in the file. This is correct behavior because the
283287
// mountpoints are listed in mount order.
284-
mountsByPath[mnt.Path] = mnt
288+
allMountsByPath[mnt.Path] = mnt
285289
}
286290
// For each filesystem, choose a "main" Mount and discard any additional
287291
// bind mounts. fscrypt only cares about the main Mount, since it's
288-
// where the fscrypt metadata is stored. Store all main Mounts in
289-
// mountsByDevice so that they can be found by device number later.
290-
allMountsByDevice := make(map[DeviceNumber][]*Mount)
291-
for _, mnt := range mountsByPath {
292+
// where the fscrypt metadata is stored. Store all the main Mounts in
293+
// mountsByDevice and mountsByPath so that they can be found later.
294+
for _, mnt := range allMountsByPath {
292295
allMountsByDevice[mnt.DeviceNumber] =
293296
append(allMountsByDevice[mnt.DeviceNumber], mnt)
294297
}
295298
for deviceNumber, filesystemMounts := range allMountsByDevice {
296-
mountsByDevice[deviceNumber] = findMainMount(filesystemMounts)
299+
mnt := findMainMount(filesystemMounts)
300+
mountsByDevice[deviceNumber] = mnt // may store an explicit nil entry
301+
if mnt != nil {
302+
mountsByPath[mnt.Path] = mnt
303+
}
297304
}
298305
return nil
299306
}
@@ -329,11 +336,9 @@ func AllFilesystems() ([]*Mount, error) {
329336
return nil, err
330337
}
331338

332-
mounts := make([]*Mount, 0, len(mountsByDevice))
333-
for _, mount := range mountsByDevice {
334-
if mount != nil {
335-
mounts = append(mounts, mount)
336-
}
339+
mounts := make([]*Mount, 0, len(mountsByPath))
340+
for _, mount := range mountsByPath {
341+
mounts = append(mounts, mount)
337342
}
338343

339344
sort.Sort(PathSorter(mounts))
@@ -359,18 +364,38 @@ func FindMount(path string) (*Mount, error) {
359364
if err := loadMountInfo(); err != nil {
360365
return nil, err
361366
}
367+
// First try to find the mount by the number of the containing device.
362368
deviceNumber, err := getNumberOfContainingDevice(path)
363369
if err != nil {
364370
return nil, err
365371
}
366372
mnt, ok := mountsByDevice[deviceNumber]
367-
if !ok {
368-
return nil, errors.Errorf("couldn't find mountpoint containing %q", path)
373+
if ok {
374+
if mnt == nil {
375+
return nil, filesystemLacksMainMountError(deviceNumber)
376+
}
377+
return mnt, nil
378+
}
379+
// The mount couldn't be found by the number of the containing device.
380+
// Fall back to walking up the directory hierarchy and checking for a
381+
// mount at each directory path. This is necessary for btrfs, where
382+
// files report a different st_dev from the /proc/self/mountinfo entry.
383+
curPath, err := canonicalizePath(path)
384+
if err != nil {
385+
return nil, err
369386
}
370-
if mnt == nil {
371-
return nil, filesystemLacksMainMountError(deviceNumber)
387+
for {
388+
mnt := mountsByPath[curPath]
389+
if mnt != nil {
390+
return mnt, nil
391+
}
392+
// Move to the parent directory unless we have reached the root.
393+
parent := filepath.Dir(curPath)
394+
if parent == curPath {
395+
return nil, errors.Errorf("couldn't find mountpoint containing %q", path)
396+
}
397+
curPath = parent
372398
}
373-
return mnt, nil
374399
}
375400

376401
// GetMount is like FindMount, except GetMount also returns an error if the path
@@ -520,12 +545,19 @@ func (mnt *Mount) getFilesystemUUID() (string, error) {
520545
}
521546

522547
// 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.
548+
// filesystem. This will normally be a string of the form
549+
// "UUID=<uuid>\nPATH=<path>\n". If the UUID cannot be determined, the UUID
550+
// portion will be omitted.
525551
func makeLink(mnt *Mount) (string, error) {
526552
uuid, err := mnt.getFilesystemUUID()
527553
if err != nil {
528-
return "", &ErrMakeLink{mnt, err}
554+
// The UUID could not be determined. This happens for btrfs
555+
// filesystems, as the device number found via
556+
// /dev/disk/by-uuid/* for btrfs filesystems differs from the
557+
// actual device number of the mounted filesystem. Just rely
558+
// entirely on the fallback to mountpoint path.
559+
log.Print(err)
560+
return fmt.Sprintf("%s=%s\n", pathToken, mnt.Path), nil
529561
}
530562
return fmt.Sprintf("%s=%s\n%s=%s\n", uuidToken, uuid, pathToken, mnt.Path), nil
531563
}

filesystem/mountpoint_test.go

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,12 @@ func TestLoadMountInfoBasic(t *testing.T) {
9090
if mnt.ReadOnly {
9191
t.Error("Wrong readonly flag")
9292
}
93+
if len(mountsByPath) != 1 {
94+
t.Error("mountsByPath doesn't contain exactly one entry")
95+
}
96+
if mountsByPath[mnt.Path] != mnt {
97+
t.Error("mountsByPath doesn't contain the correct entry")
98+
}
9399
}
94100

95101
// Test that Mount.Device is set to the mountpoint's source device if
@@ -405,6 +411,40 @@ func TestGetMountFromLink(t *testing.T) {
405411
}
406412
}
407413

414+
// Test that makeLink() is including the expected information in links.
415+
func TestMakeLink(t *testing.T) {
416+
mnt, err := getTestMount(t)
417+
if err != nil {
418+
t.Skip(err)
419+
}
420+
link, err := makeLink(mnt)
421+
if err != nil {
422+
t.Fatal(err)
423+
}
424+
425+
// Normally, both UUID and PATH should be included.
426+
if !strings.Contains(link, "UUID=") {
427+
t.Fatal("Link doesn't contain UUID")
428+
}
429+
if !strings.Contains(link, "PATH=") {
430+
t.Fatal("Link doesn't contain PATH")
431+
}
432+
433+
// Without a valid device number, only PATH should be included.
434+
mntCopy := *mnt
435+
mntCopy.DeviceNumber = 0
436+
link, err = makeLink(&mntCopy)
437+
if err != nil {
438+
t.Fatal(err)
439+
}
440+
if strings.Contains(link, "UUID=") {
441+
t.Fatal("Link shouldn't contain UUID")
442+
}
443+
if !strings.Contains(link, "PATH=") {
444+
t.Fatal("Link doesn't contain PATH")
445+
}
446+
}
447+
408448
// Test that old filesystem links that contain a UUID only still work.
409449
func TestGetMountFromLegacyLink(t *testing.T) {
410450
mnt, err := getTestMount(t)
@@ -450,6 +490,16 @@ func TestGetMountFromLinkFallback(t *testing.T) {
450490
t.Fatal("Link doesn't point to the same Mount")
451491
}
452492

493+
// only PATH given at all (should succeed)
494+
link = fmt.Sprintf("PATH=%s\n", mnt.Path)
495+
linkedMnt, err = getMountFromLink(link)
496+
if err != nil {
497+
t.Fatal(err)
498+
}
499+
if linkedMnt != mnt {
500+
t.Fatal("Link doesn't point to the same Mount")
501+
}
502+
453503
// only UUID valid (should succeed)
454504
link = fmt.Sprintf("UUID=%s\nPATH=%s\n", goodUUID, badPath)
455505
if linkedMnt, err = getMountFromLink(link); err != nil {

filesystem/path.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import (
2323
"fmt"
2424
"log"
2525
"os"
26+
"path/filepath"
2627

2728
"golang.org/x/sys/unix"
2829

@@ -40,6 +41,22 @@ func OpenFileOverridingUmask(name string, flag int, perm os.FileMode) (*os.File,
4041
// We only check the unix permissions and the sticky bit
4142
const permMask = os.ModeSticky | os.ModePerm
4243

44+
// canonicalizePath turns path into an absolute path without symlinks.
45+
func canonicalizePath(path string) (string, error) {
46+
path, err := filepath.Abs(path)
47+
if err != nil {
48+
return "", err
49+
}
50+
path, err = filepath.EvalSymlinks(path)
51+
52+
// Get a better error if we have an invalid path
53+
if pathErr, ok := err.(*os.PathError); ok {
54+
err = errors.Wrap(pathErr.Err, pathErr.Path)
55+
}
56+
57+
return path, err
58+
}
59+
4360
// loggedStat runs os.Stat, but it logs the error if stat returns any error
4461
// other than nil or IsNotExist.
4562
func loggedStat(name string) (os.FileInfo, error) {

0 commit comments

Comments
 (0)