mirror of
https://github.com/kdave/btrfs-progs
synced 2024-12-27 16:42:17 +00:00
feb8f56ba2
The current mount detection code in btrfs receive is not quite perfect. For example, suppose /tmp is mounted as a tmpfs. In that case, btrfs receive /tmp2 will find /tmp as the longest mount that matches a prefix of /tmp2 and blow up because it is not a btrfs filesystem, even if /tmp2 is just a directory in / mounted as btrfs. Fix this by replacing the substring check with a dirname recursion to only check the directories in the path of the dir, rather than every substring. Add a new test for this case. Signed-off-by: Boris Burkov <boris@bur.io> Signed-off-by: David Sterba <dsterba@suse.com>
480 lines
10 KiB
C
480 lines
10 KiB
C
/*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public
|
|
* License v2 as published by the Free Software Foundation.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public
|
|
* License along with this program; if not, write to the
|
|
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
|
* Boston, MA 021110-1307, USA.
|
|
*/
|
|
|
|
#include <sys/stat.h>
|
|
#include <sys/sysmacros.h>
|
|
#include <sys/ioctl.h>
|
|
#include <linux/major.h>
|
|
#include <linux/kdev_t.h>
|
|
#include <linux/loop.h>
|
|
#include <linux/limits.h>
|
|
#include <fcntl.h>
|
|
#include <unistd.h>
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <mntent.h>
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
#include <ctype.h>
|
|
#include <libgen.h>
|
|
#include "common/path-utils.h"
|
|
|
|
/*
|
|
* Check if @path is a block device node
|
|
* Returns:
|
|
* 1 - path is a block device
|
|
* 0 - not a block device
|
|
* <0 - negative errno
|
|
*/
|
|
int path_is_block_device(const char *path)
|
|
{
|
|
struct stat statbuf;
|
|
|
|
if (stat(path, &statbuf) < 0)
|
|
return -errno;
|
|
|
|
return !!S_ISBLK(statbuf.st_mode);
|
|
}
|
|
|
|
/*
|
|
* check if given path is a mount point
|
|
* return 1 if yes. 0 if no. -1 for error
|
|
*/
|
|
int path_is_mount_point(const char *path)
|
|
{
|
|
FILE *f;
|
|
struct mntent *mnt;
|
|
int ret = 0;
|
|
|
|
f = setmntent("/proc/self/mounts", "r");
|
|
if (f == NULL)
|
|
return -1;
|
|
|
|
while ((mnt = getmntent(f)) != NULL) {
|
|
if (strcmp(mnt->mnt_dir, path))
|
|
continue;
|
|
ret = 1;
|
|
break;
|
|
}
|
|
endmntent(f);
|
|
return ret;
|
|
}
|
|
|
|
int path_is_reg_file(const char *path)
|
|
{
|
|
struct stat statbuf;
|
|
|
|
if (stat(path, &statbuf) < 0)
|
|
return -errno;
|
|
return S_ISREG(statbuf.st_mode);
|
|
}
|
|
|
|
int path_exists(const char *path)
|
|
{
|
|
struct stat statbuf;
|
|
int ret;
|
|
|
|
ret = stat(path, &statbuf);
|
|
if (ret < 0) {
|
|
if (errno == ENOENT)
|
|
return 0;
|
|
else
|
|
return -errno;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
/* checks if a device is a loop device */
|
|
static int is_loop_device(const char *device)
|
|
{
|
|
struct stat statbuf;
|
|
|
|
if(stat(device, &statbuf) < 0)
|
|
return -errno;
|
|
|
|
return (S_ISBLK(statbuf.st_mode) &&
|
|
MAJOR(statbuf.st_rdev) == LOOP_MAJOR);
|
|
}
|
|
|
|
/*
|
|
* Takes a loop device path (e.g. /dev/loop0) and returns
|
|
* the associated file (e.g. /images/my_btrfs.img) using
|
|
* loopdev API
|
|
*/
|
|
static int resolve_loop_device_with_loopdev(const char* loop_dev, char* loop_file)
|
|
{
|
|
int fd;
|
|
int ret;
|
|
struct loop_info64 lo64;
|
|
|
|
fd = open(loop_dev, O_RDONLY | O_NONBLOCK);
|
|
if (fd < 0)
|
|
return -errno;
|
|
ret = ioctl(fd, LOOP_GET_STATUS64, &lo64);
|
|
if (ret < 0) {
|
|
ret = -errno;
|
|
goto out;
|
|
}
|
|
|
|
memcpy(loop_file, lo64.lo_file_name, sizeof(lo64.lo_file_name));
|
|
loop_file[sizeof(lo64.lo_file_name)] = 0;
|
|
|
|
out:
|
|
close(fd);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Takes a loop device path (e.g. /dev/loop0) and returns
|
|
* the associated file (e.g. /images/my_btrfs.img)
|
|
*/
|
|
static int resolve_loop_device(const char* loop_dev, char* loop_file,
|
|
int max_len)
|
|
{
|
|
int ret;
|
|
FILE *f;
|
|
char fmt[20];
|
|
char p[PATH_MAX];
|
|
char real_loop_dev[PATH_MAX];
|
|
|
|
if (!realpath(loop_dev, real_loop_dev))
|
|
return -errno;
|
|
snprintf(p, PATH_MAX, "/sys/block/%s/loop/backing_file", strrchr(real_loop_dev, '/'));
|
|
if (!(f = fopen(p, "r"))) {
|
|
if (errno == ENOENT)
|
|
/*
|
|
* It's possibly a partitioned loop device, which is
|
|
* resolvable with loopdev API.
|
|
*/
|
|
return resolve_loop_device_with_loopdev(loop_dev, loop_file);
|
|
return -errno;
|
|
}
|
|
|
|
snprintf(fmt, 20, "%%%i[^\n]", max_len - 1);
|
|
ret = fscanf(f, fmt, loop_file);
|
|
fclose(f);
|
|
if (ret == EOF)
|
|
return -errno;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Checks whether a and b are identical or device
|
|
* files associated with the same block device
|
|
*/
|
|
static int is_same_blk_file(const char* a, const char* b)
|
|
{
|
|
struct stat st_buf_a, st_buf_b;
|
|
char real_a[PATH_MAX];
|
|
char real_b[PATH_MAX];
|
|
|
|
if (!realpath(a, real_a))
|
|
strncpy_null(real_a, a);
|
|
|
|
if (!realpath(b, real_b))
|
|
strncpy_null(real_b, b);
|
|
|
|
/* Identical path? */
|
|
if (strcmp(real_a, real_b) == 0)
|
|
return 1;
|
|
|
|
if (stat(a, &st_buf_a) < 0 || stat(b, &st_buf_b) < 0) {
|
|
if (errno == ENOENT)
|
|
return 0;
|
|
return -errno;
|
|
}
|
|
|
|
/* Same blockdevice? */
|
|
if (S_ISBLK(st_buf_a.st_mode) && S_ISBLK(st_buf_b.st_mode) &&
|
|
st_buf_a.st_rdev == st_buf_b.st_rdev) {
|
|
return 1;
|
|
}
|
|
|
|
/* Hardlink? */
|
|
if (st_buf_a.st_dev == st_buf_b.st_dev &&
|
|
st_buf_a.st_ino == st_buf_b.st_ino) {
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Checks if a and b are identical or device files associated with the same
|
|
* block device or if one file is a loop device that uses the other file.
|
|
*/
|
|
int is_same_loop_file(const char *a, const char *b)
|
|
{
|
|
char res_a[PATH_MAX];
|
|
char res_b[PATH_MAX];
|
|
const char* final_a = NULL;
|
|
const char* final_b = NULL;
|
|
int ret;
|
|
|
|
/* Resolve a if it is a loop device */
|
|
if ((ret = is_loop_device(a)) < 0) {
|
|
if (ret == -ENOENT)
|
|
return 0;
|
|
return ret;
|
|
} else if (ret) {
|
|
ret = resolve_loop_device(a, res_a, sizeof(res_a));
|
|
if (ret < 0) {
|
|
if (errno != EPERM)
|
|
return ret;
|
|
} else {
|
|
final_a = res_a;
|
|
}
|
|
} else {
|
|
final_a = a;
|
|
}
|
|
|
|
/* Resolve b if it is a loop device */
|
|
if ((ret = is_loop_device(b)) < 0) {
|
|
if (ret == -ENOENT)
|
|
return 0;
|
|
return ret;
|
|
} else if (ret) {
|
|
ret = resolve_loop_device(b, res_b, sizeof(res_b));
|
|
if (ret < 0) {
|
|
if (errno != EPERM)
|
|
return ret;
|
|
} else {
|
|
final_b = res_b;
|
|
}
|
|
} else {
|
|
final_b = b;
|
|
}
|
|
|
|
return is_same_blk_file(final_a, final_b);
|
|
}
|
|
|
|
/* Checks if a file exists and is a block or regular file*/
|
|
int path_is_reg_or_block_device(const char *filename)
|
|
{
|
|
struct stat st_buf;
|
|
|
|
if (stat(filename, &st_buf) < 0) {
|
|
if(errno == ENOENT)
|
|
return 0;
|
|
else
|
|
return -errno;
|
|
}
|
|
|
|
return (S_ISBLK(st_buf.st_mode) || S_ISREG(st_buf.st_mode));
|
|
}
|
|
|
|
/*
|
|
* Resolve a pathname to a device mapper node to /dev/mapper/<name>
|
|
* Returns NULL on invalid input or malloc failure; Other failures
|
|
* will be handled by the caller using the input pathname.
|
|
*/
|
|
char *path_canonicalize_dm_name(const char *ptname)
|
|
{
|
|
FILE *f;
|
|
size_t sz;
|
|
char path[PATH_MAX], name[PATH_MAX], *res = NULL;
|
|
|
|
if (!ptname || !*ptname)
|
|
return NULL;
|
|
|
|
snprintf(path, sizeof(path), "/sys/block/%s/dm/name", ptname);
|
|
if (!(f = fopen(path, "r")))
|
|
return NULL;
|
|
|
|
/* read <name>\n from sysfs */
|
|
if (fgets(name, sizeof(name), f) && (sz = strlen(name)) > 1) {
|
|
name[sz - 1] = '\0';
|
|
snprintf(path, sizeof(path), "/dev/mapper/%s", name);
|
|
|
|
if (access(path, F_OK) == 0)
|
|
res = strdup(path);
|
|
}
|
|
fclose(f);
|
|
return res;
|
|
}
|
|
|
|
/*
|
|
* Resolve a pathname to a canonical device node, e.g. /dev/sda1 or
|
|
* to a device mapper pathname.
|
|
* Returns NULL on invalid input or malloc failure; Other failures
|
|
* will be handled by the caller using the input pathname.
|
|
*/
|
|
char *path_canonicalize(const char *path)
|
|
{
|
|
char *canonical, *p;
|
|
|
|
if (!path || !*path)
|
|
return NULL;
|
|
|
|
canonical = realpath(path, NULL);
|
|
if (!canonical)
|
|
return strdup(path);
|
|
p = strrchr(canonical, '/');
|
|
if (p && strncmp(p, "/dm-", 4) == 0 && isdigit(*(p + 4))) {
|
|
char *dm = path_canonicalize_dm_name(p + 1);
|
|
|
|
if (dm) {
|
|
free(canonical);
|
|
return dm;
|
|
}
|
|
}
|
|
return canonical;
|
|
}
|
|
|
|
/*
|
|
* __strncpy_null - strncpy with null termination
|
|
* @dest: the target array
|
|
* @src: the source string
|
|
* @n: maximum bytes to copy (size of *dest)
|
|
*
|
|
* Like strncpy, but ensures destination is null-terminated.
|
|
*
|
|
* Copies the string pointed to by src, including the terminating null
|
|
* byte ('\0'), to the buffer pointed to by dest, up to a maximum
|
|
* of n bytes. Then ensure that dest is null-terminated.
|
|
*/
|
|
char *__strncpy_null(char *dest, const char *src, size_t n)
|
|
{
|
|
strncpy(dest, src, n);
|
|
if (n > 0)
|
|
dest[n - 1] = '\0';
|
|
return dest;
|
|
}
|
|
|
|
/*
|
|
* Test if path is a directory
|
|
* Returns:
|
|
* 0 - path exists but it is not a directory
|
|
* 1 - path exists and it is a directory
|
|
* < 0 - error
|
|
*/
|
|
int path_is_dir(const char *path)
|
|
{
|
|
struct stat st;
|
|
int ret;
|
|
|
|
ret = stat(path, &st);
|
|
if (ret < 0)
|
|
return -errno;
|
|
|
|
return !!S_ISDIR(st.st_mode);
|
|
}
|
|
|
|
/*
|
|
* Test if a path is recursively contained in parent. Assumes parent and path
|
|
* are null terminated absolute paths.
|
|
*
|
|
* Returns:
|
|
* 0 - path not contained in parent
|
|
* 1 - path contained in parent
|
|
* < 0 - error
|
|
*
|
|
* e.g. (/, /foo) -> 1
|
|
* (/foo, /) -> 0
|
|
* (/foo, /foo/bar/baz) -> 1
|
|
*/
|
|
int path_is_in_dir(const char *parent, const char *path)
|
|
{
|
|
char *tmp = strdup(path);
|
|
char *curr_dir = tmp;
|
|
int ret;
|
|
|
|
while (strcmp(parent, curr_dir) != 0) {
|
|
if (strcmp(curr_dir, "/") == 0) {
|
|
ret = 0;
|
|
goto out;
|
|
}
|
|
curr_dir = dirname(curr_dir);
|
|
}
|
|
ret = 1;
|
|
|
|
out:
|
|
free(tmp);
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Copy a path argument from SRC to DEST and check the SRC length if it's at
|
|
* most PATH_MAX and fits into DEST. DESTLEN is supposed to be exact size of
|
|
* the buffer.
|
|
* The destination buffer is zero terminated.
|
|
* Return < 0 for error, 0 otherwise.
|
|
*/
|
|
int arg_copy_path(char *dest, const char *src, int destlen)
|
|
{
|
|
size_t len = strlen(src);
|
|
|
|
if (len >= PATH_MAX || len >= destlen)
|
|
return -ENAMETOOLONG;
|
|
|
|
__strncpy_null(dest, src, destlen);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int path_cat_out(char *out, const char *p1, const char *p2)
|
|
{
|
|
int p1_len = strlen(p1);
|
|
int p2_len = strlen(p2);
|
|
|
|
if (p1_len + p2_len + 2 >= PATH_MAX)
|
|
return -ENAMETOOLONG;
|
|
|
|
if (p1_len && p1[p1_len - 1] == '/')
|
|
p1_len--;
|
|
if (p2_len && p2[p2_len - 1] == '/')
|
|
p2_len--;
|
|
sprintf(out, "%.*s/%.*s", p1_len, p1, p2_len, p2);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int path_cat3_out(char *out, const char *p1, const char *p2, const char *p3)
|
|
{
|
|
int p1_len = strlen(p1);
|
|
int p2_len = strlen(p2);
|
|
int p3_len = strlen(p3);
|
|
|
|
if (p1_len + p2_len + p3_len + 3 >= PATH_MAX)
|
|
return -ENAMETOOLONG;
|
|
|
|
if (p1_len && p1[p1_len - 1] == '/')
|
|
p1_len--;
|
|
if (p2_len && p2[p2_len - 1] == '/')
|
|
p2_len--;
|
|
if (p3_len && p3[p3_len - 1] == '/')
|
|
p3_len--;
|
|
sprintf(out, "%.*s/%.*s/%.*s", p1_len, p1, p2_len, p2, p3_len, p3);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Subvolume helper functions */
|
|
/*
|
|
* test if name is a correct subvolume name
|
|
* this function return
|
|
* 0-> name is not a correct subvolume name
|
|
* 1-> name is a correct subvolume name
|
|
*/
|
|
int test_issubvolname(const char *name)
|
|
{
|
|
return name[0] != '\0' && !strchr(name, '/') &&
|
|
strcmp(name, ".") && strcmp(name, "..");
|
|
}
|
|
|