PUBLIC enum btrfs_util_error btrfs_util_subvolume_info_fd(int fd, uint64_t id, struct btrfs_util_subvolume_info *subvol) { enum btrfs_util_error err; if (id == 0) { err = btrfs_util_is_subvolume_fd(fd); if (err) return err; if (!is_root()) return get_subvolume_info_unprivileged(fd, subvol); err = btrfs_util_subvolume_id_fd(fd, &id); if (err) return err; } if ((id < BTRFS_FIRST_FREE_OBJECTID && id != BTRFS_FS_TREE_OBJECTID) || id > BTRFS_LAST_FREE_OBJECTID) { errno = ENOENT; return BTRFS_UTIL_ERROR_SUBVOLUME_NOT_FOUND; } return get_subvolume_info_privileged(fd, id, subvol); }
PUBLIC enum btrfs_util_error btrfs_util_subvolume_id(const char *path, uint64_t *id_ret) { enum btrfs_util_error err; int fd; fd = open(path, O_RDONLY); if (fd == -1) return BTRFS_UTIL_ERROR_OPEN_FAILED; err = btrfs_util_subvolume_id_fd(fd, id_ret); SAVE_ERRNO_AND_CLOSE(fd); return err; }
/* * Check that a path that we opened is the subvolume which we expect. It may not * be if there is another filesystem mounted over a parent directory or the * subvolume itself. */ static enum btrfs_util_error check_expected_subvolume(int fd, int parent_fd, uint64_t tree_id) { struct btrfs_ioctl_fs_info_args parent_fs_info, fs_info; enum btrfs_util_error err; uint64_t id; int ret; /* Make sure it's a subvolume. */ err = btrfs_util_is_subvolume_fd(fd); if (err == BTRFS_UTIL_ERROR_NOT_BTRFS || err == BTRFS_UTIL_ERROR_NOT_SUBVOLUME) { errno = ENOENT; return BTRFS_UTIL_ERROR_SUBVOLUME_NOT_FOUND; } else if (err) { return err; } /* Make sure it's on the same filesystem. */ ret = ioctl(parent_fd, BTRFS_IOC_FS_INFO, &parent_fs_info); if (ret == -1) return BTRFS_UTIL_ERROR_FS_INFO_FAILED; ret = ioctl(fd, BTRFS_IOC_FS_INFO, &fs_info); if (ret == -1) return BTRFS_UTIL_ERROR_FS_INFO_FAILED; if (memcmp(parent_fs_info.fsid, fs_info.fsid, sizeof(fs_info.fsid)) != 0) { errno = ENOENT; return BTRFS_UTIL_ERROR_SUBVOLUME_NOT_FOUND; } /* Make sure it's the subvolume that we expected. */ err = btrfs_util_subvolume_id_fd(fd, &id); if (err) return err; if (id != tree_id) { errno = ENOENT; return BTRFS_UTIL_ERROR_SUBVOLUME_NOT_FOUND; } return BTRFS_UTIL_OK; }
PUBLIC enum btrfs_util_error btrfs_util_set_default_subvolume_fd(int fd, uint64_t id) { enum btrfs_util_error err; int ret; if (id == 0) { err = btrfs_util_is_subvolume_fd(fd); if (err) return err; err = btrfs_util_subvolume_id_fd(fd, &id); if (err) return err; } ret = ioctl(fd, BTRFS_IOC_DEFAULT_SUBVOL, &id); if (ret == -1) return BTRFS_UTIL_ERROR_DEFAULT_SUBVOL_FAILED; return BTRFS_UTIL_OK; }
PUBLIC enum btrfs_util_error btrfs_util_create_subvolume_iterator_fd(int fd, uint64_t top, int flags, struct btrfs_util_subvolume_iterator **ret) { struct btrfs_util_subvolume_iterator *iter; enum btrfs_util_error err; if (flags & ~BTRFS_UTIL_SUBVOLUME_ITERATOR_MASK) { errno = EINVAL; return BTRFS_UTIL_ERROR_INVALID_ARGUMENT; } if (top == 0) { err = btrfs_util_is_subvolume_fd(fd); if (err) return err; err = btrfs_util_subvolume_id_fd(fd, &top); if (err) return err; } iter = malloc(sizeof(*iter)); if (!iter) return BTRFS_UTIL_ERROR_NO_MEMORY; iter->fd = fd; iter->flags = flags; iter->search_stack_len = 0; iter->search_stack_capacity = 4; iter->search_stack = malloc(sizeof(*iter->search_stack) * iter->search_stack_capacity); if (!iter->search_stack) { err = BTRFS_UTIL_ERROR_NO_MEMORY; goto out_iter; } iter->cur_path_capacity = 256; iter->cur_path = malloc(iter->cur_path_capacity); if (!iter->cur_path) { err = BTRFS_UTIL_ERROR_NO_MEMORY; goto out_search_stack; } err = append_to_search_stack(iter, top, 0); if (err) goto out_cur_path; *ret = iter; return BTRFS_UTIL_OK; out_cur_path: free(iter->cur_path); out_search_stack: free(iter->search_stack); out_iter: free(iter); return err; }
PUBLIC enum btrfs_util_error btrfs_util_subvolume_info_fd(int fd, uint64_t id, struct btrfs_util_subvolume_info *subvol) { struct btrfs_ioctl_search_args search = { .key = { .tree_id = BTRFS_ROOT_TREE_OBJECTID, .min_type = BTRFS_ROOT_ITEM_KEY, .max_type = BTRFS_ROOT_BACKREF_KEY, .min_offset = 0, .max_offset = UINT64_MAX, .min_transid = 0, .max_transid = UINT64_MAX, .nr_items = 0, }, }; enum btrfs_util_error err; size_t items_pos = 0, buf_off = 0; bool need_root_item = true, need_root_backref = true; int ret; if (id == 0) { err = btrfs_util_is_subvolume_fd(fd); if (err) return err; err = btrfs_util_subvolume_id_fd(fd, &id); if (err) return err; } if ((id < BTRFS_FIRST_FREE_OBJECTID && id != BTRFS_FS_TREE_OBJECTID) || id > BTRFS_LAST_FREE_OBJECTID) { errno = ENOENT; return BTRFS_UTIL_ERROR_SUBVOLUME_NOT_FOUND; } search.key.min_objectid = search.key.max_objectid = id; if (subvol) { subvol->id = id; subvol->parent_id = 0; subvol->dir_id = 0; if (id == BTRFS_FS_TREE_OBJECTID) need_root_backref = false; } else { /* * We only need the backref for filling in the subvolume info. */ need_root_backref = false; } /* Don't bother searching for the backref if we don't need it. */ if (!need_root_backref) search.key.max_type = BTRFS_ROOT_ITEM_KEY; while (need_root_item || need_root_backref) { const struct btrfs_ioctl_search_header *header; if (items_pos >= search.key.nr_items) { search.key.nr_items = 4096; ret = ioctl(fd, BTRFS_IOC_TREE_SEARCH, &search); if (ret == -1) return BTRFS_UTIL_ERROR_SEARCH_FAILED; items_pos = 0; buf_off = 0; if (search.key.nr_items == 0) { if (need_root_item) { errno = ENOENT; return BTRFS_UTIL_ERROR_SUBVOLUME_NOT_FOUND; } else { break; } } } header = (struct btrfs_ioctl_search_header *)(search.buf + buf_off); if (header->type == BTRFS_ROOT_ITEM_KEY) { if (subvol) { const struct btrfs_root_item *root; root = (const struct btrfs_root_item *)(header + 1); copy_root_item(subvol, root); } need_root_item = false; search.key.min_type = BTRFS_ROOT_BACKREF_KEY; } else if (header->type == BTRFS_ROOT_BACKREF_KEY) { if (subvol) { const struct btrfs_root_ref *ref; ref = (const struct btrfs_root_ref *)(header + 1); subvol->parent_id = header->offset; subvol->dir_id = le64_to_cpu(ref->dirid); } need_root_backref = false; search.key.min_type = UINT32_MAX; } items_pos++; buf_off += sizeof(*header) + header->len; } return BTRFS_UTIL_OK; }
PUBLIC enum btrfs_util_error btrfs_util_subvolume_id_fd(int fd, uint64_t *id_ret) { struct btrfs_ioctl_ino_lookup_args args = { .treeid = 0, .objectid = BTRFS_FIRST_FREE_OBJECTID, }; int ret; ret = ioctl(fd, BTRFS_IOC_INO_LOOKUP, &args); if (ret == -1) { close(fd); return BTRFS_UTIL_ERROR_INO_LOOKUP_FAILED; } *id_ret = args.treeid; return BTRFS_UTIL_OK; } PUBLIC enum btrfs_util_error btrfs_util_subvolume_path(const char *path, uint64_t id, char **path_ret) { enum btrfs_util_error err; int fd; fd = open(path, O_RDONLY); if (fd == -1) return BTRFS_UTIL_ERROR_OPEN_FAILED; err = btrfs_util_subvolume_path_fd(fd, id, path_ret); SAVE_ERRNO_AND_CLOSE(fd); return err; } PUBLIC enum btrfs_util_error btrfs_util_subvolume_path_fd(int fd, uint64_t id, char **path_ret) { char *path, *p; size_t capacity = 4096; if (id == 0) { enum btrfs_util_error err; err = btrfs_util_is_subvolume_fd(fd); if (err) return err; err = btrfs_util_subvolume_id_fd(fd, &id); if (err) return err; } path = malloc(capacity); if (!path) return BTRFS_UTIL_ERROR_NO_MEMORY; p = path + capacity - 1; p[0] = '\0'; while (id != BTRFS_FS_TREE_OBJECTID) { struct btrfs_ioctl_search_args search = { .key = { .tree_id = BTRFS_ROOT_TREE_OBJECTID, .min_objectid = id, .max_objectid = id, .min_type = BTRFS_ROOT_BACKREF_KEY, .max_type = BTRFS_ROOT_BACKREF_KEY, .min_offset = 0, .max_offset = UINT64_MAX, .min_transid = 0, .max_transid = UINT64_MAX, .nr_items = 1, }, }; struct btrfs_ioctl_ino_lookup_args lookup; const struct btrfs_ioctl_search_header *header; const struct btrfs_root_ref *ref; const char *name; uint16_t name_len; size_t lookup_len; size_t total_len; int ret; ret = ioctl(fd, BTRFS_IOC_TREE_SEARCH, &search); if (ret == -1) { free(path); return BTRFS_UTIL_ERROR_SEARCH_FAILED; } if (search.key.nr_items == 0) { free(path); errno = ENOENT; return BTRFS_UTIL_ERROR_SUBVOLUME_NOT_FOUND; } header = (struct btrfs_ioctl_search_header *)search.buf; ref = (struct btrfs_root_ref *)(header + 1); name = (char *)(ref + 1); name_len = le16_to_cpu(ref->name_len); id = header->offset; lookup.treeid = id; lookup.objectid = le64_to_cpu(ref->dirid); ret = ioctl(fd, BTRFS_IOC_INO_LOOKUP, &lookup); if (ret == -1) { free(path); return BTRFS_UTIL_ERROR_SEARCH_FAILED; } lookup_len = strlen(lookup.name); total_len = name_len + lookup_len + (id != BTRFS_FS_TREE_OBJECTID); if (p - total_len < path) { char *new_path, *new_p; size_t new_capacity = capacity * 2; new_path = malloc(new_capacity); if (!new_path) { free(path); return BTRFS_UTIL_ERROR_NO_MEMORY; } new_p = new_path + new_capacity - (path + capacity - p); memcpy(new_p, p, path + capacity - p); free(path); path = new_path; p = new_p; capacity = new_capacity; } p -= name_len; memcpy(p, name, name_len); p -= lookup_len; memcpy(p, lookup.name, lookup_len); if (id != BTRFS_FS_TREE_OBJECTID) *--p = '/'; } if (p != path) memmove(path, p, path + capacity - p); *path_ret = path; return BTRFS_UTIL_OK; }