/* * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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. (Note: a similar function also exists * in libudev so it's been renamed to avoid clash.) * * Return: 1 if yes, * 0 if no, * -1 for error */ int path_is_a_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/ * 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 \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, ".."); }