Add support for null device numbers

They are resolved using the mountinfo interface and
mapping them to the block device listed there.
This commit is contained in:
Andrea Mazzoleni 2016-04-23 16:24:26 +02:00
parent cca81e7ff6
commit 4cc1109444
4 changed files with 237 additions and 61 deletions

View file

@ -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

View file

@ -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 */

View file

@ -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 */

View file

@ -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.
*/