diff --git a/HISTORY b/HISTORY index 74ead858..336fd8f6 100644 --- a/HISTORY +++ b/HISTORY @@ -3,6 +3,9 @@ SnapRAID HISTORY 11.0 2016/05 ============ + * In Linux add the missing support for filesystems using null device + numbers, like Btrfs. Note that to have full support you need also + the 'libblkid' library, otherwise you won't get the UUIDs. * In the Windows version allow to use the escape char '^' to handle file patterns containing real characters matching the globbing '*?[]' ones. In Unix it was already possible to do the same escaping with diff --git a/cmdline/support.c b/cmdline/support.c index a31c81fa..433fca22 100644 --- a/cmdline/support.c +++ b/cmdline/support.c @@ -586,6 +586,35 @@ char* polish(char* s) return s; } +unsigned split(char** split_map, unsigned split_max, char* str) +{ + unsigned mac = 0; + + /* skip initial spaces */ + while (isspace(*str)) + ++str; + + while (*str != 0 || mac == split_max) { + /* start of the token */ + split_map[mac] = str; + ++mac; + + /* find the first separator or the end of the string */ + while (*str != 0 && !isspace(*str)) + ++str; + + /* put the final terminator if missing */ + if (*str != 0) + *str++ = 0; + + /* skip trailing spaces */ + while (isspace(*str)) + ++str; + } + + return mac; +} + /****************************************************************************/ /* path */ diff --git a/cmdline/support.h b/cmdline/support.h index 0ca4dbf2..c9e3ab0a 100644 --- a/cmdline/support.h +++ b/cmdline/support.h @@ -211,7 +211,7 @@ void printl(const char* str, size_t pad); void printp(double v, size_t pad); /****************************************************************************/ -/* escape */ +/* string */ #define ESC_MAX (PATH_MAX*2 + 1) @@ -243,6 +243,13 @@ const char* quote(char prefix, const char* str, char* buffer); */ char* polish(char* s); +/** + * Split a string in multiple tokens separated by spaces. + * + * Multiple spaces are grouped together. + */ +unsigned split(char** split_map, unsigned split_max, char* line); + /****************************************************************************/ /* path */ diff --git a/cmdline/unix.c b/cmdline/unix.c index e65e5a42..eeba8e33 100644 --- a/cmdline/unix.c +++ b/cmdline/unix.c @@ -68,6 +68,189 @@ const char* stat_desc(struct stat* st) return "unknown"; } +/** + * Get the device file from the device number. + * + * It uses /proc/self/mountinfo. + * + * Null devices (major==0) are resolved to the device indicated in mountinfo. + */ +#if HAVE_LINUX_DEVICE +static int devresolve_proc(uint64_t device, char* path, size_t path_size) +{ + FILE* f; + char match[32]; + + /* generate the matching string */ + snprintf(match, sizeof(match), "%u:%u", major(device), minor(device)); + + f = fopen("/proc/self/mountinfo", "r"); + if (!f) { + log_tag("resolve:proc:%u:%u: failed to open /proc/self/mountinfo\n", major(device), minor(device)); + return -1; + } + + while (1) { + char buf[256]; + char* split_map[9]; + unsigned split_mac; + char* s; + + s = fgets(buf, sizeof(buf), f); + if (s == 0) + break; + + /* split the line */ + split_mac = split(split_map, 9, s); + + /* if too short, it's the wrong line */ + if (split_mac < 9) + continue; + + /* if it's the right line */ + if (strcmp(split_map[2], match) == 0) { + pathcpy(path, path_size, split_map[8]); + + log_tag("resolve:proc:%u:%u:%s: found\n", major(device), minor(device), path); + + fclose(f); + return 0; + } + } + + log_tag("resolve:proc:%u:%u: not found\n", major(device), minor(device)); + + fclose(f); + return -1; +} +#endif + +/** + * Resolve and get again the device number. + * + * This is intended to resolve the case of null devices (major==0). + */ +static int devdereference(uint64_t device, uint64_t* new_device) +{ + char path[PATH_MAX]; + struct stat st; + + /* use the proc interface to get the device containinig the filesystem */ + if (devresolve_proc(device, path, sizeof(path)) != 0) { + /* LCOV_EXCL_START */ + return -1; + /* LCOV_EXCL_STOP */ + } + + /* check the device */ + if (stat(path, &st) != 0) { + /* LCOV_EXCL_START */ + log_tag("dereference:%u:%u: failed to stat %s\n", major(device), minor(device), path); + return -1; + /* LCOV_EXCL_STOP */ + } + + if (major(st.st_rdev) == 0) { + /* LCOV_EXCL_START */ + log_tag("dereference:%u:%u: still null device %s -> %u:%u\n", major(device), minor(device), path, major(st.st_rdev), minor(st.st_rdev)); + return -1; + /* LCOV_EXCL_STOP */ + } + + *new_device = st.st_rdev; + log_tag("dereference:%u:%u: found %u:%u\n", major(device), minor(device), major(st.st_rdev), minor(st.st_rdev)); + return 0; +} + +/** + * Get the device file from the device number. + * + * It uses /dev/block. + * + * For null device (major==0) it fails. + */ +#if HAVE_LINUX_DEVICE +static int devresolve_dev(dev_t device, char* path, size_t path_size) +{ + struct stat st; + char buf[PATH_MAX]; + int ret; + + /* default device path from device number */ + pathprint(path, path_size, "/dev/block/%u:%u", major(device), minor(device)); + + /* resolve the link from /dev/block */ + ret = readlink(path, buf, sizeof(buf)); + if (ret < 0) { + /* LCOV_EXCL_START */ + log_tag("resolve:dev:%u:%u: failed to readlink '%s'\n", major(device), minor(device), path); + return -1; + /* LCOV_EXCL_STOP */ + } + if (ret == sizeof(buf)) { + /* LCOV_EXCL_START */ + log_tag("resolve:dev:%u:%u: too long readlink '%s'\n", major(device), minor(device), path); + return -1; + /* LCOV_EXCL_STOP */ + } + + buf[ret] = 0; + + if (buf[0] != '.' || buf[1] != '.' || buf[2] != '/') { + /* LCOV_EXCL_START */ + log_tag("resolve:dev:%u:%u: unexpected link '%s' at '%s'\n", major(device), minor(device), buf, path); + return -1; + /* LCOV_EXCL_STOP */ + } + + /* set the real device path */ + pathprint(path, path_size, "/dev/%s", buf + 3); + + /* check the device */ + if (stat(path, &st) != 0) { + /* LCOV_EXCL_START */ + log_tag("resolve:dev:%u:%u: failed to stat '%s'\n", major(device), minor(device), path); + return -1; + /* LCOV_EXCL_STOP */ + } + if (st.st_rdev != device) { + /* LCOV_EXCL_START */ + log_tag("resolve:dev:%u:%u: unexpected device '%u:%u' for '%s'.\n", major(device), minor(device), major(st.st_rdev), minor(st.st_rdev), path); + return -1; + /* LCOV_EXCL_STOP */ + } + + log_tag("resolve:dev:%u:%u:%s: found\n", major(device), minor(device), path); + + return 0; +} +#endif + +/** + * Get the device file from the device number. + */ +#if HAVE_LINUX_DEVICE +static int devresolve(uint64_t device, char* path, size_t path_size) +{ + /* if the major is the null device */ + if (major(device) == 0) { + /* obtain the real device */ + if (devdereference(device, &device) != 0) { + /* LCOV_EXCL_START */ + log_fatal("Failed to map device %u:%u.\n", major(device), minor(device)); + return -1; + /* LCOV_EXCL_STOP */ + } + } + + if (devresolve_dev(device, path, path_size) == 0) + return 0; + + log_fatal("Failed to map device %u:%u.\n", major(device), minor(device)); + return -1; +} +#endif + /** * Cache used by blkid. */ @@ -81,7 +264,7 @@ static blkid_cache cache = 0; * It doesn't work with Btrfs file-systems that don't export the main UUID * in /dev/disk/by-uuid/. */ -#if HAVE_FSTATAT +#if HAVE_LINUX_DEVICE static int devuuid_dev(uint64_t device, char* uuid, size_t uuid_size) { int ret; @@ -183,8 +366,20 @@ static int devuuid_blkid(uint64_t device, char* uuid, size_t uuid_size) int devuuid(uint64_t device, char* uuid, size_t uuid_size) { +#if HAVE_LINUX_DEVICE + /* if the major is the null device */ + if (major(device) == 0) { + /* obtain the real device */ + if (devdereference(device, &device) != 0) { + /* LCOV_EXCL_START */ + return -1; + /* LCOV_EXCL_STOP */ + } + } +#endif + /* first try with the /dev/disk/by-uuid version */ -#if HAVE_FSTATAT +#if HAVE_LINUX_DEVICE if (devuuid_dev(device, uuid, uuid_size) == 0) return 0; #else @@ -538,64 +733,6 @@ static dev_t devread(const char* path) } #endif -#if HAVE_LINUX_DEVICE -/** - * Get the device file from the device number. - */ -static int devresolve(dev_t device, char* path, size_t path_size) -{ - struct stat st; - char buf[PATH_MAX]; - int ret; - - /* default device path from device number */ - pathprint(path, path_size, "/dev/block/%u:%u", major(device), minor(device)); - - /* resolve the link from /dev/block */ - ret = readlink(path, buf, sizeof(buf)); - if (ret < 0) { - /* LCOV_EXCL_START */ - log_fatal("Failed to readlink '%s'.\n", path); - return -1; - /* LCOV_EXCL_STOP */ - } - if (ret == sizeof(buf)) { - /* LCOV_EXCL_START */ - log_fatal("Too long readlink '%s'.\n", path); - return -1; - /* LCOV_EXCL_STOP */ - } - - buf[ret] = 0; - - if (buf[0] != '.' || buf[1] != '.' || buf[2] != '/') { - /* LCOV_EXCL_START */ - log_fatal("Unexpected link '%s' at '%s'.\n", buf, path); - return -1; - /* LCOV_EXCL_STOP */ - } - - /* set the real device path */ - pathprint(path, path_size, "/dev/%s", buf + 3); - - /* check the device */ - if (stat(path, &st) != 0) { - /* LCOV_EXCL_START */ - log_fatal("Failed to stat '%s'.\n", path); - return -1; - /* LCOV_EXCL_STOP */ - } - if (st.st_rdev != device) { - /* LCOV_EXCL_START */ - log_fatal("Unexpected device '%u:%u' instead of '%u:%u' for '%s'.\n", major(st.st_rdev), minor(st.st_rdev), major(device), minor(device), path); - return -1; - /* LCOV_EXCL_STOP */ - } - - return 0; -} -#endif - /** * Read a device tree filling the specified list of disk_t entries. */