Skip to content

Commit

Permalink
Merge pull request #54 from google/uuid
Browse files Browse the repository at this point in the history
Use `/dev/disk/by-uuid` to get UUID links to other filesystems
  • Loading branch information
josephlr authored Aug 31, 2017
2 parents fe92c00 + 0fcb660 commit 5285a8c
Show file tree
Hide file tree
Showing 5 changed files with 71 additions and 84 deletions.
1 change: 0 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ addons:
- sourceline: 'deb http://en.archive.ubuntu.com/ubuntu/ artful main universe'
packages:
- libargon2-0-dev
- libblkid-dev
- libpam0g-dev
- e2fsprogs
- protobuf-compiler
Expand Down
8 changes: 3 additions & 5 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,9 @@ endif
# builds the C code with high optimizations, and C warnings fail.
# LDFLAGS
# Change the flags passed to the C linker. Empty by default.
# For example:
# make fscrypt "LDFLAGS = -static -luuid -ldl -laudit -lpthread"
# will build a static binary.
# For example (on my system with additional dev packages):
# make fscrypt "LDFLAGS = -static -ldl -laudit -lcap-ng"
# will build a static fscrypt binary.
# GO_FLAGS
# Change the flags passed to "go build". Empty by default.
# For example:
Expand Down Expand Up @@ -179,8 +179,6 @@ test-setup:
sudo mkdir -p $(MOUNT)
sudo mount -o rw,loop $(IMAGE) $(MOUNT)
sudo chmod +777 $(MOUNT)
# Add UUID to BLKID cache
sudo blkid $$(df $(MOUNT) --output=source | grep /dev/)

test-teardown:
sudo umount $(MOUNT)
Expand Down
12 changes: 5 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,11 +119,10 @@ fscrypt has the following build dependencies:
>>>>> make
>>>>> sudo make install
```
* Headers for `libblkid` and `libpam`. These can be installed with the
appropriate package manager.
- `sudo apt-get install libblkid-dev libpam0g-dev`
- `sudo yum install libblkid-devel pam-devel`
- `pam` and `util-liux` packages for Arch
* Headers for `libpam`. Install them with the appropriate package manager.
- `sudo apt-get install libpam0g-dev`
- `sudo yum install pam-devel`
- `pam` package for Arch (part of the `base` group)

Once all the dependencies are installed, you can get the repository by running:
```shell
Expand All @@ -149,8 +148,7 @@ fscrypt has the following runtime dependencies:
* Kernel support for filesystem encryption (this will depend on your kernel
configuration and specific filesystem)
* `libargon2.so` (see the above installation instructions for Argon2)
* `libblkid.so` and `libpam.so`. These libraries are almost certainly already
on your system.
* `libpam.so` (almost certainly already on your system)

The dynamic libraries are not needed if you built a static executable.

Expand Down
4 changes: 1 addition & 3 deletions filesystem/filesystem.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,9 +84,7 @@ var (
// There is also the ability to reference another filesystem's metadata. This is
// used when a Policy on filesystem A is protected with Protector on filesystem
// B. In this scenario, we store a "link file" in the protectors directory whose
// contents look like "UUID=3a6d9a76-47f0-4f13-81bf-3332fbe984fb". These
// contents can be anything parsable by libblkid (i.e. anything that could be in
// the Device column of /etc/fstab).
// contents look like "UUID=3a6d9a76-47f0-4f13-81bf-3332fbe984fb".
type Mount struct {
Path string
Filesystem string
Expand Down
130 changes: 62 additions & 68 deletions filesystem/mountpoint.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,20 +21,17 @@

package filesystem

import (
"io/ioutil"
"os"
)

/*
#cgo LDFLAGS: -lblkid
#include <blkid/blkid.h> // blkid functions
#include <stdlib.h> // free()
#include <mntent.h> // setmntent, getmntent, endmntent
// The file containing mountpoints info and how we should read it
const char* mountpoints_filename = "/proc/mounts";
const char* read_mode = "r";
// Helper function for freeing strings
void string_free(char* str) { free(str); }
// Helper function to lookup tokens
*/
import "C"

Expand All @@ -53,12 +50,14 @@ var (
// These maps hold data about the state of the system's mountpoints.
mountsByPath map[string]*Mount
mountsByDevice map[string][]*Mount
// Cache for information about the devices
cache C.blkid_cache
// Used to make the mount functions thread safe
mountMutex sync.Mutex
// True if the maps have been successfully initialized.
mountsInitialized bool
// Supported tokens for filesystem links
uuidToken = "UUID"
// Location to perform UUID lookup
uuidDirectory = "/dev/disk/by-uuid"
)

// getMountInfo populates the Mount mappings by parsing the filesystem
Expand All @@ -81,14 +80,6 @@ func getMountInfo() error {
}
defer C.endmntent(fileHandle)

// Load the device information from the default blkid cache
if cache != nil {
C.blkid_put_cache(cache)
}
if C.blkid_get_cache(&cache, nil) != 0 {
return errors.Wrap(ErrGlobalMountInfo, "could not read blkid cache")
}

for {
entry := C.getmntent(fileHandle)
// When getmntent returns nil, we have read all of the entries.
Expand Down Expand Up @@ -214,72 +205,75 @@ func GetMount(mountpoint string) (*Mount, error) {
}

// getMountsFromLink returns the Mount objects which match the provided link.
// This link can be an unparsed tag (e.g. <token>=<value>) or path (e.g.
// /dev/dm-0). The matching rules are determined by libblkid. These are the same
// matching rules for things like UUID=3a6d9a76-47f0-4f13-81bf-3332fbe984fb in
// "/etc/fstab". Note that this can match multiple Mounts. An error is returned
// if the link is invalid or we cannot load the required mount data. If a
// filesystem has been updated since the last call to one of the mount
// functions, run UpdateMountInfo to see the change.
// This link if formatted as a tag (e.g. <token>=<value>) similar to how they
// apprear in "/etc/fstab". Currently, only "UUID" tokens are supported. Note
// that this can match multiple Mounts (due to the existance of bind mounts). An
// error is returned if the link is invalid or we cannot load the required mount
// data. If a filesystem has been updated since the last call to one of the
// mount functions, run UpdateMountInfo to see the change.
func getMountsFromLink(link string) ([]*Mount, error) {
// Use blkid_evaluate_spec to get the device name.
cLink := C.CString(link)
defer C.string_free(cLink)

cDeviceName := C.blkid_evaluate_spec(cLink, &cache)
defer C.string_free(cDeviceName)
deviceName := C.GoString(cDeviceName)

log.Printf("blkid_evaluate_spec(%q, <cache>) = %q", link, deviceName)
// Parse the link
linkComponents := strings.Split(link, "=")
if len(linkComponents) != 2 {
return nil, errors.Wrapf(ErrFollowLink, "link %q format in invalid", link)
}
token := linkComponents[0]
value := linkComponents[1]
if token != uuidToken {
return nil, errors.Wrapf(ErrFollowLink, "token type %q not supported", token)
}

if deviceName == "" {
return nil, errors.Wrapf(ErrFollowLink, "link %q is invalid", link)
// See if UUID points to an existing device
searchPath := filepath.Join(uuidDirectory, value)
if filepath.Base(searchPath) != value {
return nil, errors.Wrapf(ErrFollowLink, "value %q is not a UUID", value)
}
deviceName, err := cannonicalizePath(deviceName)
devicePath, err := cannonicalizePath(searchPath)
if err != nil {
return nil, err
return nil, errors.Wrapf(ErrFollowLink, "no device with UUID %q", value)
}

// Lookup mountpoints for device in global store
mountMutex.Lock()
defer mountMutex.Unlock()
if err := getMountInfo(); err != nil {
return nil, err
}

if mnts, ok := mountsByDevice[deviceName]; ok {
return mnts, nil
mnts, ok := mountsByDevice[devicePath]
if !ok {
return nil, errors.Wrapf(ErrFollowLink, "no mounts for device %q", devicePath)
}

return nil, errors.Wrapf(ErrFollowLink, "device %q is invalid", deviceName)
return mnts, nil
}

// makeLink returns a link of the form <token>=<value> where value is the tag
// value for the Mount's device according to libblkid. An error is returned if
// the device/token pair has no value.
// value for the Mount's device. Currently, only "UUID" tokens are supported. An
// error is returned if the mount has no device, or no UUID.
func makeLink(mnt *Mount, token string) (string, error) {
// The blkid cache may not always hold the canonical device path. To
// solve this we first use blkid_evaluate_spec to find the right entry
// in the cache. Then that name is used to get the token value.
cDevice := C.CString(mnt.Device)
defer C.string_free(cDevice)

cDeviceEntry := C.blkid_evaluate_spec(cDevice, &cache)
defer C.string_free(cDeviceEntry)
deviceEntry := C.GoString(cDeviceEntry)

log.Printf("blkid_evaluate_spec(%q, <cache>) = %q", mnt.Device, deviceEntry)

cToken := C.CString(token)
defer C.string_free(cToken)

cValue := C.blkid_get_tag_value(cache, cToken, cDeviceEntry)
defer C.string_free(cValue)
value := C.GoString(cValue)

log.Printf("blkid_get_tag_value(<cache>, %s, %s) = %s", token, deviceEntry, value)
if token != uuidToken {
return "", errors.Wrapf(ErrMakeLink, "token type %q not supported", token)
}
if mnt.Device == "" {
return "", errors.Wrapf(ErrMakeLink, "no device for mount %q", mnt.Path)
}

if value == "" {
return "", errors.Wrapf(ErrMakeLink, "no %s", token)
dirContents, err := ioutil.ReadDir(uuidDirectory)
if err != nil {
return "", errors.Wrap(ErrMakeLink, err.Error())
}
for _, fileInfo := range dirContents {
if fileInfo.Mode()&os.ModeSymlink == 0 {
continue // Only interested in UUID symlinks
}
uuid := fileInfo.Name()
devicePath, err := cannonicalizePath(filepath.Join(uuidDirectory, uuid))
if err != nil {
log.Print(err)
continue
}
if mnt.Device == devicePath {
return fmt.Sprintf("%s=%s", uuidToken, uuid), nil
}
}
return fmt.Sprintf("%s=%s", token, C.GoString(cValue)), nil
return "", errors.Wrapf(ErrMakeLink, "device %q has no UUID", mnt.Device)
}

0 comments on commit 5285a8c

Please sign in to comment.