#include "btrfs-ctree.h"
#include "btrfs-util.h"
+/* WARNING: Be careful with file system ioctls! When we get an fd, we
+ * need to make sure it either refers to only a regular file or
+ * directory, or that it is located on btrfs, before invoking any
+ * btrfs ioctls. The ioctl numbers are reused by some device drivers
+ * (such as DRM), and hence might have bad effects when invoked on
+ * device nodes (that reference drivers) rather than fds to normal
+ * files or directories. */
+
static int validate_subvolume_name(const char *name) {
if (!filename_is_valid(name))
return 0;
}
-int btrfs_is_snapshot(int fd) {
- struct stat st;
+int btrfs_is_filesystem(int fd) {
struct statfs sfs;
+ assert(fd >= 0);
+
+ if (fstatfs(fd, &sfs) < 0)
+ return -errno;
+
+ return F_TYPE_EQUAL(sfs.f_type, BTRFS_SUPER_MAGIC);
+}
+
+int btrfs_is_subvol(int fd) {
+ struct stat st;
+
+ assert(fd >= 0);
+
/* On btrfs subvolumes always have the inode 256 */
if (fstat(fd, &st) < 0)
if (!S_ISDIR(st.st_mode) || st.st_ino != 256)
return 0;
- if (fstatfs(fd, &sfs) < 0)
- return -errno;
-
- return F_TYPE_EQUAL(sfs.f_type, BTRFS_SUPER_MAGIC);
+ return btrfs_is_filesystem(fd);
}
int btrfs_subvol_make(const char *path) {
int btrfs_subvol_get_read_only_fd(int fd) {
uint64_t flags;
+ struct stat st;
+
+ assert(fd >= 0);
+
+ if (fstat(fd, &st) < 0)
+ return -errno;
+
+ if (!S_ISDIR(st.st_mode) || st.st_ino != 256)
+ return -EINVAL;
if (ioctl(fd, BTRFS_IOC_SUBVOL_GETFLAGS, &flags) < 0)
return -errno;
}
int btrfs_reflink(int infd, int outfd) {
+ struct stat st;
int r;
assert(infd >= 0);
assert(outfd >= 0);
+ /* Make sure we invoke the ioctl on a regular file, so that no
+ * device driver accidentally gets it. */
+
+ if (fstat(outfd, &st) < 0)
+ return -errno;
+
+ if (!S_ISREG(st.st_mode))
+ return -EINVAL;
+
r = ioctl(outfd, BTRFS_IOC_CLONE, infd);
if (r < 0)
return -errno;
.src_length = sz,
.dest_offset = out_offset,
};
+ struct stat st;
int r;
assert(infd >= 0);
assert(outfd >= 0);
assert(sz > 0);
+ if (fstat(outfd, &st) < 0)
+ return -errno;
+
+ if (!S_ISREG(st.st_mode))
+ return -EINVAL;
+
r = ioctl(outfd, BTRFS_IOC_CLONE_RANGE, &args);
if (r < 0)
return -errno;
int btrfs_get_block_device_fd(int fd, dev_t *dev) {
struct btrfs_ioctl_fs_info_args fsi = {};
uint64_t id;
+ int r;
assert(fd >= 0);
assert(dev);
+ r = btrfs_is_filesystem(fd);
+ if (r < 0)
+ return r;
+ if (!r)
+ return -ENOTTY;
+
if (ioctl(fd, BTRFS_IOC_FS_INFO, &fsi) < 0)
return -errno;
struct btrfs_ioctl_ino_lookup_args args = {
.objectid = BTRFS_FIRST_FREE_OBJECTID
};
+ int r;
assert(fd >= 0);
assert(ret);
+ r = btrfs_is_filesystem(fd);
+ if (r < 0)
+ return r;
+ if (!r)
+ return -ENOTTY;
+
if (ioctl(fd, BTRFS_IOC_INO_LOOKUP, &args) < 0)
return -errno;
}
int btrfs_defrag_fd(int fd) {
+ struct stat st;
+
assert(fd >= 0);
+ if (fstat(fd, &st) < 0)
+ return -errno;
+
+ if (!S_ISREG(st.st_mode))
+ return -EINVAL;
+
if (ioctl(fd, BTRFS_IOC_DEFRAG, NULL) < 0)
return -errno;
struct btrfs_ioctl_quota_ctl_args args = {
.cmd = b ? BTRFS_QUOTA_CTL_ENABLE : BTRFS_QUOTA_CTL_DISABLE,
};
+ int r;
assert(fd >= 0);
+ r = btrfs_is_filesystem(fd);
+ if (r < 0)
+ return r;
+ if (!r)
+ return -ENOTTY;
+
if (ioctl(fd, BTRFS_IOC_QUOTA_CTL, &args) < 0)
return -errno;
referenced_max == 0 ? 1 : referenced_max,
.lim.flags = BTRFS_QGROUP_LIMIT_MAX_RFER,
};
+ int r;
assert(fd >= 0);
+ r = btrfs_is_filesystem(fd);
+ if (r < 0)
+ return r;
+ if (!r)
+ return -ENOTTY;
+
if (ioctl(fd, BTRFS_IOC_QGROUP_LIMIT, &args) < 0)
return -errno;
struct btrfs_ioctl_vol_args vol_args = {};
_cleanup_close_ int subvol_fd = -1;
+ struct stat st;
+ bool made_writable = false;
int r;
assert(fd >= 0);
assert(subvolume);
+ if (fstat(fd, &st) < 0)
+ return -errno;
+
+ if (!S_ISDIR(st.st_mode))
+ return -EINVAL;
+
/* First, try to remove the subvolume. If it happens to be
* already empty, this will just work. */
strncpy(vol_args.name, subvolume, sizeof(vol_args.name)-1);
if (ioctl(fd, BTRFS_IOC_INO_LOOKUP, &ino_args) < 0)
return -errno;
+ if (!made_writable) {
+ r = btrfs_subvol_set_read_only_fd(subvol_fd, false);
+ if (r < 0)
+ return r;
+
+ made_writable = true;
+ }
+
if (isempty(ino_args.name))
/* Subvolume is in the top-level
* directory of the subvolume. */
.fd = old_fd,
};
int r;
+ _cleanup_close_ int subvolume_fd = -1;
assert(old_fd >= 0);
assert(new_fd >= 0);
if (new_child_fd < 0)
return -errno;
+ if (flags & BTRFS_SNAPSHOT_READ_ONLY) {
+ /* If the snapshot is read-only we
+ * need to mark it writable
+ * temporarily, to put the subsnapshot
+ * into place. */
+
+ if (subvolume_fd < 0) {
+ subvolume_fd = openat(new_fd, subvolume, O_RDONLY|O_NOCTTY|O_CLOEXEC|O_DIRECTORY);
+ if (subvolume_fd < 0)
+ return -errno;
+ }
+
+ r = btrfs_subvol_set_read_only_fd(subvolume_fd, false);
+ if (r < 0)
+ return r;
+ }
+
/* When btrfs clones the subvolumes, child
* subvolumes appear as directories. Remove
* them, so that we can create a new snapshot
* in their place */
- if (unlinkat(new_child_fd, p, AT_REMOVEDIR) < 0)
- return -errno;
+ if (unlinkat(new_child_fd, p, AT_REMOVEDIR) < 0) {
+ int k = -errno;
+
+ if (flags & BTRFS_SNAPSHOT_READ_ONLY)
+ (void) btrfs_subvol_set_read_only_fd(subvolume_fd, true);
+
+ return k;
+ }
r = subvol_snapshot_children(old_child_fd, new_child_fd, p, sh->objectid, flags & ~BTRFS_SNAPSHOT_FALLBACK_COPY);
+
+ /* Restore the readonly flag */
+ if (flags & BTRFS_SNAPSHOT_READ_ONLY) {
+ int k;
+
+ k = btrfs_subvol_set_read_only_fd(subvolume_fd, true);
+ if (r >= 0 && k < 0)
+ return k;
+ }
+
if (r < 0)
return r;
}
assert(old_fd >= 0);
assert(new_path);
- r = btrfs_is_snapshot(old_fd);
+ r = btrfs_is_subvol(old_fd);
if (r < 0)
return r;
if (r == 0) {