20 | 20 |
|
21 | 21 |
package filesystem
|
22 | 22 |
|
|
23 |
import (
|
|
24 |
"io/ioutil"
|
|
25 |
"os"
|
|
26 |
)
|
|
27 |
|
23 | 28 |
/*
|
24 | |
#cgo LDFLAGS: -lblkid
|
25 | |
#include <blkid/blkid.h> // blkid functions
|
26 | |
#include <stdlib.h> // free()
|
27 | 29 |
#include <mntent.h> // setmntent, getmntent, endmntent
|
28 | 30 |
|
29 | 31 |
// The file containing mountpoints info and how we should read it
|
30 | 32 |
const char* mountpoints_filename = "/proc/mounts";
|
31 | 33 |
const char* read_mode = "r";
|
32 | |
|
33 | |
// Helper function for freeing strings
|
34 | |
void string_free(char* str) { free(str); }
|
35 | |
|
36 | |
// Helper function to lookup tokens
|
37 | 34 |
*/
|
38 | 35 |
import "C"
|
39 | 36 |
|
|
52 | 49 |
// These maps hold data about the state of the system's mountpoints.
|
53 | 50 |
mountsByPath map[string]*Mount
|
54 | 51 |
mountsByDevice map[string][]*Mount
|
55 | |
// Cache for information about the devices
|
56 | |
cache C.blkid_cache
|
57 | 52 |
// Used to make the mount functions thread safe
|
58 | 53 |
mountMutex sync.Mutex
|
59 | 54 |
// True if the maps have been successfully initialized.
|
60 | 55 |
mountsInitialized bool
|
|
56 |
// Supported tokens for filesystem links
|
|
57 |
uuidToken = "UUID"
|
|
58 |
// Location to perform UUID lookup
|
|
59 |
uuidDirectory = "/dev/disk/by-uuid"
|
61 | 60 |
)
|
62 | 61 |
|
63 | 62 |
// getMountInfo populates the Mount mappings by parsing the filesystem
|
|
79 | 78 |
C.GoString(C.mountpoints_filename))
|
80 | 79 |
}
|
81 | 80 |
defer C.endmntent(fileHandle)
|
82 | |
|
83 | |
// Load the device information from the default blkid cache
|
84 | |
if cache != nil {
|
85 | |
C.blkid_put_cache(cache)
|
86 | |
}
|
87 | |
if C.blkid_get_cache(&cache, nil) != 0 {
|
88 | |
return errors.Wrap(ErrGlobalMountInfo, "could not read blkid cache")
|
89 | |
}
|
90 | 81 |
|
91 | 82 |
for {
|
92 | 83 |
entry := C.getmntent(fileHandle)
|
|
213 | 204 |
}
|
214 | 205 |
|
215 | 206 |
// getMountsFromLink returns the Mount objects which match the provided link.
|
216 | |
// This link can be an unparsed tag (e.g. <token>=<value>) or path (e.g.
|
217 | |
// /dev/dm-0). The matching rules are determined by libblkid. These are the same
|
218 | |
// matching rules for things like UUID=3a6d9a76-47f0-4f13-81bf-3332fbe984fb in
|
219 | |
// "/etc/fstab". Note that this can match multiple Mounts. An error is returned
|
220 | |
// if the link is invalid or we cannot load the required mount data. If a
|
221 | |
// filesystem has been updated since the last call to one of the mount
|
222 | |
// functions, run UpdateMountInfo to see the change.
|
|
207 |
// This link if formatted as a tag (e.g. <token>=<value>) similar to how they
|
|
208 |
// apprear in "/etc/fstab". Currently, only "UUID" tokens are supported. Note
|
|
209 |
// that this can match multiple Mounts (due to the existance of bind mounts). An
|
|
210 |
// error is returned if the link is invalid or we cannot load the required mount
|
|
211 |
// data. If a filesystem has been updated since the last call to one of the
|
|
212 |
// mount functions, run UpdateMountInfo to see the change.
|
223 | 213 |
func getMountsFromLink(link string) ([]*Mount, error) {
|
224 | |
// Use blkid_evaluate_spec to get the device name.
|
225 | |
cLink := C.CString(link)
|
226 | |
defer C.string_free(cLink)
|
227 | |
|
228 | |
cDeviceName := C.blkid_evaluate_spec(cLink, &cache)
|
229 | |
defer C.string_free(cDeviceName)
|
230 | |
deviceName := C.GoString(cDeviceName)
|
231 | |
|
232 | |
log.Printf("blkid_evaluate_spec(%q, <cache>) = %q", link, deviceName)
|
233 | |
|
234 | |
if deviceName == "" {
|
235 | |
return nil, errors.Wrapf(ErrFollowLink, "link %q is invalid", link)
|
236 | |
}
|
237 | |
deviceName, err := cannonicalizePath(deviceName)
|
238 | |
if err != nil {
|
239 | |
return nil, err
|
240 | |
}
|
241 | |
|
|
214 |
// Parse the link
|
|
215 |
linkComponents := strings.Split(link, "=")
|
|
216 |
if len(linkComponents) != 2 {
|
|
217 |
return nil, errors.Wrapf(ErrFollowLink, "link %q format in invalid", link)
|
|
218 |
}
|
|
219 |
token := linkComponents[0]
|
|
220 |
value := linkComponents[1]
|
|
221 |
if token != uuidToken {
|
|
222 |
return nil, errors.Wrapf(ErrFollowLink, "token type %q not supported", token)
|
|
223 |
}
|
|
224 |
|
|
225 |
// See if UUID points to an existing device
|
|
226 |
searchPath := filepath.Join(uuidDirectory, value)
|
|
227 |
if filepath.Base(searchPath) != value {
|
|
228 |
return nil, errors.Wrapf(ErrFollowLink, "value %q is not a UUID", value)
|
|
229 |
}
|
|
230 |
devicePath, err := cannonicalizePath(searchPath)
|
|
231 |
if err != nil {
|
|
232 |
return nil, errors.Wrapf(ErrFollowLink, "no device with UUID %q", value)
|
|
233 |
}
|
|
234 |
|
|
235 |
// Lookup mountpoints for device in global store
|
242 | 236 |
mountMutex.Lock()
|
243 | 237 |
defer mountMutex.Unlock()
|
244 | 238 |
if err := getMountInfo(); err != nil {
|
245 | 239 |
return nil, err
|
246 | 240 |
}
|
247 | |
|
248 | |
if mnts, ok := mountsByDevice[deviceName]; ok {
|
249 | |
return mnts, nil
|
250 | |
}
|
251 | |
|
252 | |
return nil, errors.Wrapf(ErrFollowLink, "device %q is invalid", deviceName)
|
|
241 |
mnts, ok := mountsByDevice[devicePath]
|
|
242 |
if !ok {
|
|
243 |
return nil, errors.Wrapf(ErrFollowLink, "no mounts for device %q", devicePath)
|
|
244 |
}
|
|
245 |
return mnts, nil
|
253 | 246 |
}
|
254 | 247 |
|
255 | 248 |
// makeLink returns a link of the form <token>=<value> where value is the tag
|
256 | |
// value for the Mount's device according to libblkid. An error is returned if
|
257 | |
// the device/token pair has no value.
|
|
249 |
// value for the Mount's device. Currently, only "UUID" tokens are supported. An
|
|
250 |
// error is returned if the mount has no device, or no UUID.
|
258 | 251 |
func makeLink(mnt *Mount, token string) (string, error) {
|
259 | |
// The blkid cache may not always hold the canonical device path. To
|
260 | |
// solve this we first use blkid_evaluate_spec to find the right entry
|
261 | |
// in the cache. Then that name is used to get the token value.
|
262 | |
cDevice := C.CString(mnt.Device)
|
263 | |
defer C.string_free(cDevice)
|
264 | |
|
265 | |
cDeviceEntry := C.blkid_evaluate_spec(cDevice, &cache)
|
266 | |
defer C.string_free(cDeviceEntry)
|
267 | |
deviceEntry := C.GoString(cDeviceEntry)
|
268 | |
|
269 | |
log.Printf("blkid_evaluate_spec(%q, <cache>) = %q", mnt.Device, deviceEntry)
|
270 | |
|
271 | |
cToken := C.CString(token)
|
272 | |
defer C.string_free(cToken)
|
273 | |
|
274 | |
cValue := C.blkid_get_tag_value(cache, cToken, cDeviceEntry)
|
275 | |
defer C.string_free(cValue)
|
276 | |
value := C.GoString(cValue)
|
277 | |
|
278 | |
log.Printf("blkid_get_tag_value(<cache>, %s, %s) = %s", token, deviceEntry, value)
|
279 | |
|
280 | |
if value == "" {
|
281 | |
return "", errors.Wrapf(ErrMakeLink, "no %s", token)
|
282 | |
}
|
283 | |
return fmt.Sprintf("%s=%s", token, C.GoString(cValue)), nil
|
284 | |
}
|
|
252 |
if token != uuidToken {
|
|
253 |
return "", errors.Wrapf(ErrMakeLink, "token type %q not supported", token)
|
|
254 |
}
|
|
255 |
if mnt.Device == "" {
|
|
256 |
return "", errors.Wrapf(ErrMakeLink, "no device for mount %q", mnt.Path)
|
|
257 |
}
|
|
258 |
|
|
259 |
dirContents, err := ioutil.ReadDir(uuidDirectory)
|
|
260 |
if err != nil {
|
|
261 |
return "", errors.Wrap(ErrMakeLink, err.Error())
|
|
262 |
}
|
|
263 |
for _, fileInfo := range dirContents {
|
|
264 |
if fileInfo.Mode()&os.ModeSymlink == 0 {
|
|
265 |
continue // Only interested in UUID symlinks
|
|
266 |
}
|
|
267 |
uuid := fileInfo.Name()
|
|
268 |
devicePath, err := cannonicalizePath(filepath.Join(uuidDirectory, uuid))
|
|
269 |
if err != nil {
|
|
270 |
log.Print(err)
|
|
271 |
continue
|
|
272 |
}
|
|
273 |
if mnt.Device == devicePath {
|
|
274 |
return fmt.Sprintf("%s=%s", uuidToken, uuid), nil
|
|
275 |
}
|
|
276 |
}
|
|
277 |
return "", errors.Wrapf(ErrMakeLink, "device %q has no UUID", mnt.Device)
|
|
278 |
}
|