static enum btrfs_util_error pop_search_stack(struct btrfs_util_subvolume_iterator *iter) { struct search_stack_entry *top, *parent; int fd, parent_fd; size_t i; if (iter->use_tree_search || iter->search_stack_len == 1) { iter->search_stack_len--; return BTRFS_UTIL_OK; } top = top_search_stack_entry(iter); iter->search_stack_len--; parent = top_search_stack_entry(iter); fd = iter->cur_fd; for (i = parent->path_len; i < top->path_len; i++) { if (i == 0 || iter->cur_path[i] == '/') { parent_fd = openat(fd, "..", O_RDONLY); if (fd != iter->cur_fd) SAVE_ERRNO_AND_CLOSE(fd); if (parent_fd == -1) return BTRFS_UTIL_ERROR_OPEN_FAILED; fd = parent_fd; } } if (iter->cur_fd != iter->fd) close(iter->cur_fd); iter->cur_fd = fd; return BTRFS_UTIL_OK; }
static enum btrfs_util_error build_subvol_path(struct btrfs_util_subvolume_iterator *iter, const char *name, size_t name_len, const char *dir, size_t dir_len, size_t *path_len_ret) { struct search_stack_entry *top = top_search_stack_entry(iter); size_t path_len; char *p; path_len = top->path_len; /* * We need a joining slash if we have a current path and a subdirectory. */ if (top->path_len && dir_len) path_len++; path_len += dir_len; /* * We need another joining slash if we have a current path and a name, * but not if we have a subdirectory, because the lookup ioctl includes * a trailing slash. */ if (top->path_len && !dir_len && name_len) path_len++; path_len += name_len; /* We need one extra character for the NUL terminator. */ if (path_len + 1 > iter->cur_path_capacity) { char *tmp = realloc(iter->cur_path, path_len + 1); if (!tmp) return BTRFS_UTIL_ERROR_NO_MEMORY; iter->cur_path = tmp; iter->cur_path_capacity = path_len + 1; } p = iter->cur_path + top->path_len; if (top->path_len && dir_len) *p++ = '/'; memcpy(p, dir, dir_len); p += dir_len; if (top->path_len && !dir_len && name_len) *p++ = '/'; memcpy(p, name, name_len); p += name_len; *p = '\0'; *path_len_ret = path_len; return BTRFS_UTIL_OK; }
static enum btrfs_util_error build_subvol_path(struct btrfs_util_subvolume_iterator *iter, const struct btrfs_ioctl_search_header *header, const struct btrfs_root_ref *ref, const char *name, size_t *path_len_ret) { struct btrfs_ioctl_ino_lookup_args lookup = { .treeid = header->objectid, .objectid = le64_to_cpu(ref->dirid), }; struct search_stack_entry *top = top_search_stack_entry(iter); size_t dir_len, name_len, path_len; char *p; int ret; ret = ioctl(iter->fd, BTRFS_IOC_INO_LOOKUP, &lookup); if (ret == -1) return BTRFS_UTIL_ERROR_INO_LOOKUP_FAILED; dir_len = strlen(lookup.name); name_len = le16_to_cpu(ref->name_len); path_len = top->path_len; /* * We need a joining slash if we have a current path and a subdirectory. */ if (top->path_len && dir_len) path_len++; path_len += dir_len; /* * We need another joining slash if we have a current path and a name, * but not if we have a subdirectory, because the lookup ioctl includes * a trailing slash. */ if (top->path_len && !dir_len && name_len) path_len++; path_len += name_len; if (path_len > iter->cur_path_capacity) { char *tmp = realloc(iter->cur_path, path_len); if (!tmp) return BTRFS_UTIL_ERROR_NO_MEMORY; iter->cur_path = tmp; iter->cur_path_capacity = path_len; } p = iter->cur_path + top->path_len; if (top->path_len && dir_len) *p++ = '/'; memcpy(p, lookup.name, dir_len); p += dir_len; if (top->path_len && !dir_len && name_len) *p++ = '/'; memcpy(p, name, name_len); p += name_len; *path_len_ret = path_len; return BTRFS_UTIL_OK; } PUBLIC enum btrfs_util_error btrfs_util_subvolume_iterator_next(struct btrfs_util_subvolume_iterator *iter, char **path_ret, uint64_t *id_ret) { struct search_stack_entry *top; const struct btrfs_ioctl_search_header *header; const struct btrfs_root_ref *ref; const char *name; enum btrfs_util_error err; size_t path_len; int ret; for (;;) { for (;;) { if (iter->search_stack_len == 0) return BTRFS_UTIL_ERROR_STOP_ITERATION; top = top_search_stack_entry(iter); if (top->items_pos < top->search.key.nr_items) { break; } else { top->search.key.nr_items = 4096; ret = ioctl(iter->fd, BTRFS_IOC_TREE_SEARCH, &top->search); if (ret == -1) return BTRFS_UTIL_ERROR_SEARCH_FAILED; top->items_pos = 0; top->buf_off = 0; if (top->search.key.nr_items == 0) { iter->search_stack_len--; if ((iter->flags & BTRFS_UTIL_SUBVOLUME_ITERATOR_POST_ORDER) && iter->search_stack_len) goto out; } } } header = (struct btrfs_ioctl_search_header *)(top->search.buf + top->buf_off); top->items_pos++; top->buf_off += sizeof(*header) + header->len; top->search.key.min_offset = header->offset + 1; /* This shouldn't happen, but handle it just in case. */ if (header->type != BTRFS_ROOT_REF_KEY) continue; ref = (struct btrfs_root_ref *)(header + 1); name = (const char *)(ref + 1); err = build_subvol_path(iter, header, ref, name, &path_len); if (err) return err; err = append_to_search_stack(iter, header->offset, path_len); if (err) return err; if (!(iter->flags & BTRFS_UTIL_SUBVOLUME_ITERATOR_POST_ORDER)) { top = top_search_stack_entry(iter); goto out; } } out: if (path_ret) { *path_ret = malloc(top->path_len + 1); if (!*path_ret) return BTRFS_UTIL_ERROR_NO_MEMORY; memcpy(*path_ret, iter->cur_path, top->path_len); (*path_ret)[top->path_len] = '\0'; } if (id_ret) *id_ret = top->search.key.min_objectid; return BTRFS_UTIL_OK; } PUBLIC enum btrfs_util_error btrfs_util_subvolume_iterator_next_info(struct btrfs_util_subvolume_iterator *iter, char **path_ret, struct btrfs_util_subvolume_info *subvol) { enum btrfs_util_error err; uint64_t id; err = btrfs_util_subvolume_iterator_next(iter, path_ret, &id); if (err) return err; return btrfs_util_subvolume_info_fd(iter->fd, id, subvol); } PUBLIC enum btrfs_util_error btrfs_util_deleted_subvolumes(const char *path, uint64_t **ids, size_t *n) { enum btrfs_util_error err; int fd; fd = open(path, O_RDONLY); if (fd == -1) return BTRFS_UTIL_ERROR_OPEN_FAILED; err = btrfs_util_deleted_subvolumes_fd(fd, ids, n); SAVE_ERRNO_AND_CLOSE(fd); return err; } PUBLIC enum btrfs_util_error btrfs_util_deleted_subvolumes_fd(int fd, uint64_t **ids, size_t *n) { size_t capacity = 0; struct btrfs_ioctl_search_args search = { .key = { .tree_id = BTRFS_ROOT_TREE_OBJECTID, .min_objectid = BTRFS_ORPHAN_OBJECTID, .max_objectid = BTRFS_ORPHAN_OBJECTID, .min_type = BTRFS_ORPHAN_ITEM_KEY, .max_type = BTRFS_ORPHAN_ITEM_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; int ret; *ids = NULL; *n = 0; for (;;) { 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) { err = BTRFS_UTIL_ERROR_SEARCH_FAILED; goto out; } items_pos = 0; buf_off = 0; if (search.key.nr_items == 0) break; } header = (struct btrfs_ioctl_search_header *)(search.buf + buf_off); /* * The orphan item might be for a free space cache inode, so * check if there's a matching root item. */ err = btrfs_util_subvolume_info_fd(fd, header->offset, NULL); if (!err) { if (*n >= capacity) { size_t new_capacity; uint64_t *new_ids; new_capacity = capacity ? capacity * 2 : 1; new_ids = reallocarray(*ids, new_capacity, sizeof(**ids)); if (!new_ids) { err = BTRFS_UTIL_ERROR_NO_MEMORY; goto out; } *ids = new_ids; capacity = new_capacity; } (*ids)[(*n)++] = header->offset; } else if (err != BTRFS_UTIL_ERROR_SUBVOLUME_NOT_FOUND) { goto out; } items_pos++; buf_off += sizeof(*header) + header->len; search.key.min_offset = header->offset + 1; } err = BTRFS_UTIL_OK; out: if (err) { free(*ids); *ids = NULL; *n = 0; } return err; }
static enum btrfs_util_error append_to_search_stack(struct btrfs_util_subvolume_iterator *iter, uint64_t tree_id, size_t path_len) { struct search_stack_entry *entry; if (iter->search_stack_len >= iter->search_stack_capacity) { size_t new_capacity = iter->search_stack_capacity * 2; struct search_stack_entry *new_search_stack; new_search_stack = reallocarray(iter->search_stack, new_capacity, sizeof(*iter->search_stack)); if (!new_search_stack) return BTRFS_UTIL_ERROR_NO_MEMORY; iter->search_stack_capacity = new_capacity; iter->search_stack = new_search_stack; } entry = &iter->search_stack[iter->search_stack_len]; memset(entry, 0, sizeof(*entry)); entry->path_len = path_len; if (iter->use_tree_search) { entry->search.key.tree_id = BTRFS_ROOT_TREE_OBJECTID; entry->search.key.min_objectid = tree_id; entry->search.key.max_objectid = tree_id; entry->search.key.min_type = BTRFS_ROOT_REF_KEY; entry->search.key.max_type = BTRFS_ROOT_REF_KEY; entry->search.key.min_offset = 0; entry->search.key.max_offset = UINT64_MAX; entry->search.key.min_transid = 0; entry->search.key.max_transid = UINT64_MAX; entry->search.key.nr_items = 0; } else { entry->id = tree_id; if (iter->search_stack_len) { struct search_stack_entry *top; enum btrfs_util_error err; char *path; int fd; top = top_search_stack_entry(iter); path = &iter->cur_path[top->path_len]; if (*path == '/') path++; fd = openat(iter->cur_fd, path, O_RDONLY); if (fd == -1) return BTRFS_UTIL_ERROR_OPEN_FAILED; err = check_expected_subvolume(fd, iter->cur_fd, tree_id); if (err) { close(fd); return err; } close(iter->cur_fd); iter->cur_fd = fd; } } iter->search_stack_len++; return BTRFS_UTIL_OK; }
static enum btrfs_util_error build_subvol_path_privileged(struct btrfs_util_subvolume_iterator *iter, const struct btrfs_ioctl_search_header *header, const struct btrfs_root_ref *ref, const char *name, size_t *path_len_ret) { struct btrfs_ioctl_ino_lookup_args lookup = { .treeid = header->objectid, .objectid = le64_to_cpu(ref->dirid), }; int ret; ret = ioctl(iter->fd, BTRFS_IOC_INO_LOOKUP, &lookup); if (ret == -1) return BTRFS_UTIL_ERROR_INO_LOOKUP_FAILED; return build_subvol_path(iter, name, le16_to_cpu(ref->name_len), lookup.name, strlen(lookup.name), path_len_ret); } static enum btrfs_util_error build_subvol_path_unprivileged(struct btrfs_util_subvolume_iterator *iter, uint64_t treeid, uint64_t dirid, size_t *path_len_ret) { struct btrfs_ioctl_ino_lookup_user_args args = { .treeid = treeid, .dirid = dirid, }; int ret; ret = ioctl(iter->cur_fd, BTRFS_IOC_INO_LOOKUP_USER, &args); if (ret == -1) return BTRFS_UTIL_ERROR_INO_LOOKUP_USER_FAILED; return build_subvol_path(iter, args.name, strlen(args.name), args.path, strlen(args.path), path_len_ret); } static enum btrfs_util_error subvolume_iterator_next_tree_search(struct btrfs_util_subvolume_iterator *iter, char **path_ret, uint64_t *id_ret) { struct search_stack_entry *top; const struct btrfs_ioctl_search_header *header; const struct btrfs_root_ref *ref; const char *name; enum btrfs_util_error err; size_t path_len; int ret; for (;;) { for (;;) { if (iter->search_stack_len == 0) return BTRFS_UTIL_ERROR_STOP_ITERATION; top = top_search_stack_entry(iter); if (top->items_pos < top->search.key.nr_items) { break; } else { top->search.key.nr_items = 4096; ret = ioctl(iter->fd, BTRFS_IOC_TREE_SEARCH, &top->search); if (ret == -1) return BTRFS_UTIL_ERROR_SEARCH_FAILED; top->items_pos = 0; top->buf_off = 0; if (top->search.key.nr_items == 0) { /* * This never fails for use_tree_search. */ pop_search_stack(iter); if ((iter->flags & BTRFS_UTIL_SUBVOLUME_ITERATOR_POST_ORDER) && iter->search_stack_len) goto out; } } } header = (struct btrfs_ioctl_search_header *)(top->search.buf + top->buf_off); top->items_pos++; top->buf_off += sizeof(*header) + header->len; top->search.key.min_offset = header->offset + 1; /* This shouldn't happen, but handle it just in case. */ if (header->type != BTRFS_ROOT_REF_KEY) continue; ref = (struct btrfs_root_ref *)(header + 1); name = (const char *)(ref + 1); err = build_subvol_path_privileged(iter, header, ref, name, &path_len); if (err) return err; err = append_to_search_stack(iter, header->offset, path_len); if (err) return err; if (!(iter->flags & BTRFS_UTIL_SUBVOLUME_ITERATOR_POST_ORDER)) { top = top_search_stack_entry(iter); goto out; } } out: if (path_ret) { *path_ret = malloc(top->path_len + 1); if (!*path_ret) return BTRFS_UTIL_ERROR_NO_MEMORY; memcpy(*path_ret, iter->cur_path, top->path_len); (*path_ret)[top->path_len] = '\0'; } if (id_ret) *id_ret = top->search.key.min_objectid; return BTRFS_UTIL_OK; } static enum btrfs_util_error subvolume_iterator_next_unprivileged(struct btrfs_util_subvolume_iterator *iter, char **path_ret, uint64_t *id_ret) { struct search_stack_entry *top; uint64_t treeid, dirid; enum btrfs_util_error err; size_t path_len; int ret; for (;;) { for (;;) { if (iter->search_stack_len == 0) return BTRFS_UTIL_ERROR_STOP_ITERATION; top = top_search_stack_entry(iter); if (top->items_pos < top->rootref_args.num_items) { break; } else { ret = ioctl(iter->cur_fd, BTRFS_IOC_GET_SUBVOL_ROOTREF, &top->rootref_args); if (ret == -1 && errno != EOVERFLOW) return BTRFS_UTIL_ERROR_GET_SUBVOL_ROOTREF_FAILED; top->items_pos = 0; if (top->rootref_args.num_items == 0) { err = pop_search_stack(iter); if (err) return err; if ((iter->flags & BTRFS_UTIL_SUBVOLUME_ITERATOR_POST_ORDER) && iter->search_stack_len) goto out; } } } treeid = top->rootref_args.rootref[top->items_pos].treeid; dirid = top->rootref_args.rootref[top->items_pos].dirid; top->items_pos++; err = build_subvol_path_unprivileged(iter, treeid, dirid, &path_len); if (err) { /* Skip the subvolume if we can't access it. */ if (errno == EACCES) continue; return err; } err = append_to_search_stack(iter, treeid, path_len); if (err) { /* * Skip the subvolume if it does not exist (which can * happen if there is another filesystem mounted over a * parent directory) or we don't have permission to * access it. */ if (errno == ENOENT || errno == EACCES) continue; return err; } if (!(iter->flags & BTRFS_UTIL_SUBVOLUME_ITERATOR_POST_ORDER)) { top = top_search_stack_entry(iter); goto out; } } out: if (path_ret) { *path_ret = malloc(top->path_len + 1); if (!*path_ret) return BTRFS_UTIL_ERROR_NO_MEMORY; memcpy(*path_ret, iter->cur_path, top->path_len); (*path_ret)[top->path_len] = '\0'; } if (id_ret) *id_ret = top->id; return BTRFS_UTIL_OK; } PUBLIC enum btrfs_util_error btrfs_util_subvolume_iterator_next(struct btrfs_util_subvolume_iterator *iter, char **path_ret, uint64_t *id_ret) { if (iter->use_tree_search) { return subvolume_iterator_next_tree_search(iter, path_ret, id_ret); } else { return subvolume_iterator_next_unprivileged(iter, path_ret, id_ret); } } PUBLIC enum btrfs_util_error btrfs_util_subvolume_iterator_next_info(struct btrfs_util_subvolume_iterator *iter, char **path_ret, struct btrfs_util_subvolume_info *subvol) { enum btrfs_util_error err; uint64_t id; err = btrfs_util_subvolume_iterator_next(iter, path_ret, &id); if (err) return err; if (iter->use_tree_search) return btrfs_util_subvolume_info_fd(iter->fd, id, subvol); else return btrfs_util_subvolume_info_fd(iter->cur_fd, 0, subvol); } PUBLIC enum btrfs_util_error btrfs_util_deleted_subvolumes(const char *path, uint64_t **ids, size_t *n) { enum btrfs_util_error err; int fd; fd = open(path, O_RDONLY); if (fd == -1) return BTRFS_UTIL_ERROR_OPEN_FAILED; err = btrfs_util_deleted_subvolumes_fd(fd, ids, n); SAVE_ERRNO_AND_CLOSE(fd); return err; } PUBLIC enum btrfs_util_error btrfs_util_deleted_subvolumes_fd(int fd, uint64_t **ids, size_t *n) { size_t capacity = 0; struct btrfs_ioctl_search_args search = { .key = { .tree_id = BTRFS_ROOT_TREE_OBJECTID, .min_objectid = BTRFS_ORPHAN_OBJECTID, .max_objectid = BTRFS_ORPHAN_OBJECTID, .min_type = BTRFS_ORPHAN_ITEM_KEY, .max_type = BTRFS_ORPHAN_ITEM_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; int ret; *ids = NULL; *n = 0; for (;;) { 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) { err = BTRFS_UTIL_ERROR_SEARCH_FAILED; goto out; } items_pos = 0; buf_off = 0; if (search.key.nr_items == 0) break; } header = (struct btrfs_ioctl_search_header *)(search.buf + buf_off); /* * The orphan item might be for a free space cache inode, so * check if there's a matching root item. */ err = btrfs_util_subvolume_info_fd(fd, header->offset, NULL); if (!err) { if (*n >= capacity) { size_t new_capacity; uint64_t *new_ids; new_capacity = capacity ? capacity * 2 : 1; new_ids = reallocarray(*ids, new_capacity, sizeof(**ids)); if (!new_ids) { err = BTRFS_UTIL_ERROR_NO_MEMORY; goto out; } *ids = new_ids; capacity = new_capacity; } (*ids)[(*n)++] = header->offset; } else if (err != BTRFS_UTIL_ERROR_SUBVOLUME_NOT_FOUND) { goto out; } items_pos++; buf_off += sizeof(*header) + header->len; search.key.min_offset = header->offset + 1; } err = BTRFS_UTIL_OK; out: if (err) { free(*ids); *ids = NULL; *n = 0; } return err; }