X-Git-Url: http://review.tizen.org/git/?a=blobdiff_plain;f=cmds-subvolume.c;h=ba57eaa07080bfec403ed8c3d21f0bfe193068a7;hb=8d6c4447823fa73e928e43f3c2f90f1da0ad5a47;hp=74e21307c7007880cd05a2726568f200e1458ed5;hpb=b1d5f20c3a744c2994c32dd616ba2605c4e4f395;p=platform%2Fupstream%2Fbtrfs-progs.git diff --git a/cmds-subvolume.c b/cmds-subvolume.c index 74e2130..ba57eaa 100644 --- a/cmds-subvolume.c +++ b/cmds-subvolume.c @@ -14,6 +14,7 @@ * Boston, MA 021110-1307, USA. */ +#include #include #include #include @@ -21,10 +22,14 @@ #include #include #include +#include #include #include #include #include +#include + +#include #include "kerncompat.h" #include "ioctl.h" @@ -35,36 +40,53 @@ #include "utils.h" #include "btrfs-list.h" #include "utils.h" +#include "help.h" -static const char * const subvolume_cmd_group_usage[] = { - "btrfs subvolume ", - NULL -}; - -/* - * test if path is a directory - * this function return - * 0-> path exists but it is not a directory - * 1-> path exists and it is a directory - * -1 -> path is unaccessible - */ -static int test_isdir(char *path) +static int wait_for_subvolume_cleaning(int fd, size_t count, uint64_t *ids, + int sleep_interval) { - struct stat st; - int res; + size_t i; + enum btrfs_util_error err; - res = stat(path, &st); - if(res < 0 ) - return -1; + while (1) { + int clean = 1; + + for (i = 0; i < count; i++) { + if (!ids[i]) + continue; + err = btrfs_util_subvolume_info_fd(fd, ids[i], NULL); + if (err == BTRFS_UTIL_ERROR_SUBVOLUME_NOT_FOUND) { + printf("Subvolume id %" PRIu64 " is gone\n", + ids[i]); + ids[i] = 0; + } else if (err) { + error_btrfs_util(err); + return -errno; + } else { + clean = 0; + } + } + if (clean) + break; + sleep(sleep_interval); + } - return S_ISDIR(st.st_mode); + return 0; } +static const char * const subvolume_cmd_group_usage[] = { + "btrfs subvolume ", + NULL +}; + static const char * const cmd_subvol_create_usage[] = { - "btrfs subvolume create [/]", + "btrfs subvolume create [-i ] [/]", "Create a subvolume", "Create a subvolume in . If is not given", "subvolume will be created in the current directory.", + "", + "-i add the newly created subvolume to a qgroup. This", + " option can be given multiple times.", NULL }; @@ -72,27 +94,33 @@ static int cmd_subvol_create(int argc, char **argv) { int retval, res, len; int fddst = -1; + char *dupname = NULL; + char *dupdir = NULL; char *newname; char *dstdir; char *dst; struct btrfs_qgroup_inherit *inherit = NULL; + DIR *dirstream = NULL; - optind = 1; while (1) { - int c = getopt(argc, argv, "c:i:r"); + int c = getopt(argc, argv, "c:i:"); if (c < 0) break; switch (c) { case 'c': res = qgroup_inherit_add_copy(&inherit, optarg, 0); - if (res) - return res; + if (res) { + retval = res; + goto out; + } break; case 'i': res = qgroup_inherit_add_group(&inherit, optarg); - if (res) - return res; + if (res) { + retval = res; + goto out; + } break; default: usage(cmd_subvol_create_usage); @@ -106,35 +134,34 @@ static int cmd_subvol_create(int argc, char **argv) retval = 1; /* failure */ res = test_isdir(dst); + if (res < 0 && res != -ENOENT) { + error("cannot access %s: %s", dst, strerror(-res)); + goto out; + } if (res >= 0) { - fprintf(stderr, "ERROR: '%s' exists\n", dst); + error("target path already exists: %s", dst); goto out; } - newname = strdup(dst); - newname = basename(newname); - dstdir = strdup(dst); - dstdir = dirname(dstdir); + dupname = strdup(dst); + newname = basename(dupname); + dupdir = strdup(dst); + dstdir = dirname(dupdir); - if (!strcmp(newname, ".") || !strcmp(newname, "..") || - strchr(newname, '/') ){ - fprintf(stderr, "ERROR: uncorrect subvolume name ('%s')\n", - newname); + if (!test_issubvolname(newname)) { + error("invalid subvolume name: %s", newname); goto out; } len = strlen(newname); if (len == 0 || len >= BTRFS_VOL_NAME_MAX) { - fprintf(stderr, "ERROR: subvolume name too long ('%s)\n", - newname); + error("subvolume name too long: %s", newname); goto out; } - fddst = open_file_or_dir(dstdir); - if (fddst < 0) { - fprintf(stderr, "ERROR: can't access to '%s'\n", dstdir); + fddst = btrfs_open_dir(dstdir, &dirstream, 1); + if (fddst < 0) goto out; - } printf("Create subvolume '%s/%s'\n", dstdir, newname); if (inherit) { @@ -157,119 +184,223 @@ static int cmd_subvol_create(int argc, char **argv) } if (res < 0) { - fprintf(stderr, "ERROR: cannot create subvolume - %s\n", - strerror(errno)); + error("cannot create subvolume: %m"); goto out; } retval = 0; /* success */ out: - if (fddst != -1) - close(fddst); + close_file_or_dir(fddst, dirstream); free(inherit); + free(dupname); + free(dupdir); return retval; } -/* - * test if path is a subvolume: - * this function return - * 0-> path exists but it is not a subvolume - * 1-> path exists and it is a subvolume - * -1 -> path is unaccessible - */ -int test_issubvolume(char *path) +static int wait_for_commit(int fd) { - struct stat st; - int res; + enum btrfs_util_error err; + uint64_t transid; - res = stat(path, &st); - if(res < 0 ) + err = btrfs_util_start_sync_fd(fd, &transid); + if (err) return -1; - return (st.st_ino == 256) && S_ISDIR(st.st_mode); + err = btrfs_util_wait_sync_fd(fd, transid); + if (err) + return -1; + + return 0; } static const char * const cmd_subvol_delete_usage[] = { - "btrfs subvolume delete [...]", + "btrfs subvolume delete [options] [...]", "Delete subvolume(s)", + "Delete subvolumes from the filesystem. The corresponding directory", + "is removed instantly but the data blocks are removed later.", + "The deletion does not involve full commit by default due to", + "performance reasons (as a consequence, the subvolume may appear again", + "after a crash). Use one of the --commit options to wait until the", + "operation is safely stored on the media.", + "", + "-c|--commit-after wait for transaction commit at the end of the operation", + "-C|--commit-each wait for transaction commit after deleting each subvolume", + "-v|--verbose verbose output of operations", NULL }; static int cmd_subvol_delete(int argc, char **argv) { - int res, fd, len, e, cnt = 1, ret = 0; - struct btrfs_ioctl_vol_args args; + int res, ret = 0; + int cnt; + int fd = -1; char *dname, *vname, *cpath; + char *dupdname = NULL; + char *dupvname = NULL; char *path; + DIR *dirstream = NULL; + int verbose = 0; + int commit_mode = 0; + u8 fsid[BTRFS_FSID_SIZE]; + char uuidbuf[BTRFS_UUID_UNPARSED_SIZE]; + struct seen_fsid *seen_fsid_hash[SEEN_FSID_HASH_SIZE] = { NULL, }; + enum { COMMIT_AFTER = 1, COMMIT_EACH = 2 }; + enum btrfs_util_error err; + + while (1) { + int c; + static const struct option long_options[] = { + {"commit-after", no_argument, NULL, 'c'}, + {"commit-each", no_argument, NULL, 'C'}, + {"verbose", no_argument, NULL, 'v'}, + {NULL, 0, NULL, 0} + }; + + c = getopt_long(argc, argv, "cCv", long_options, NULL); + if (c < 0) + break; + + switch(c) { + case 'c': + commit_mode = COMMIT_AFTER; + break; + case 'C': + commit_mode = COMMIT_EACH; + break; + case 'v': + verbose++; + break; + default: + usage(cmd_subvol_delete_usage); + } + } - if (argc < 2) + if (check_argc_min(argc - optind, 1)) usage(cmd_subvol_delete_usage); + if (verbose > 0) { + printf("Transaction commit: %s\n", + !commit_mode ? "none (default)" : + commit_mode == COMMIT_AFTER ? "at the end" : "after each"); + } + + cnt = optind; + again: path = argv[cnt]; - res = test_issubvolume(path); - if(res<0){ - fprintf(stderr, "ERROR: error accessing '%s'\n", path); - ret = 12; + err = btrfs_util_is_subvolume(path); + if (err) { + error_btrfs_util(err); + ret = 1; goto out; } - if(!res){ - fprintf(stderr, "ERROR: '%s' is not a subvolume\n", path); - ret = 13; + + cpath = realpath(path, NULL); + if (!cpath) { + ret = errno; + error("cannot find real path for '%s': %m", path); goto out; } - - cpath = realpath(path, 0); - dname = strdup(cpath); - dname = dirname(dname); - vname = strdup(cpath); - vname = basename(vname); + dupdname = strdup(cpath); + dname = dirname(dupdname); + dupvname = strdup(cpath); + vname = basename(dupvname); free(cpath); - if( !strcmp(vname,".") || !strcmp(vname,"..") || - strchr(vname, '/') ){ - fprintf(stderr, "ERROR: incorrect subvolume name ('%s')\n", - vname); - ret = 14; + fd = btrfs_open_dir(dname, &dirstream, 1); + if (fd < 0) { + ret = 1; goto out; } - len = strlen(vname); - if (len == 0 || len >= BTRFS_VOL_NAME_MAX) { - fprintf(stderr, "ERROR: snapshot name too long ('%s)\n", - vname); - ret = 14; - goto out; - } + printf("Delete subvolume (%s): '%s/%s'\n", + commit_mode == COMMIT_EACH || (commit_mode == COMMIT_AFTER && cnt + 1 == argc) + ? "commit" : "no-commit", dname, vname); - fd = open_file_or_dir(dname); - if (fd < 0) { - fprintf(stderr, "ERROR: can't access to '%s'\n", dname); - ret = 12; + err = btrfs_util_delete_subvolume_fd(fd, vname, 0); + if (err) { + error_btrfs_util(err); + ret = 1; goto out; } - printf("Delete subvolume '%s/%s'\n", dname, vname); - strncpy_null(args.name, vname); - res = ioctl(fd, BTRFS_IOC_SNAP_DESTROY, &args); - e = errno; - - close(fd); + if (commit_mode == COMMIT_EACH) { + res = wait_for_commit(fd); + if (res < 0) { + error("unable to wait for commit after '%s': %m", path); + ret = 1; + } + } else if (commit_mode == COMMIT_AFTER) { + res = get_fsid(dname, fsid, 0); + if (res < 0) { + error("unable to get fsid for '%s': %s", + path, strerror(-res)); + error( + "delete suceeded but commit may not be done in the end"); + ret = 1; + goto out; + } - if(res < 0 ){ - fprintf( stderr, "ERROR: cannot delete '%s/%s' - %s\n", - dname, vname, strerror(e)); - ret = 11; - goto out; + if (add_seen_fsid(fsid, seen_fsid_hash, fd, dirstream) == 0) { + if (verbose > 0) { + uuid_unparse(fsid, uuidbuf); + printf(" new fs is found for '%s', fsid: %s\n", + path, uuidbuf); + } + /* + * This is the first time a subvolume on this + * filesystem is deleted, keep fd in order to issue + * SYNC ioctl in the end + */ + goto keep_fd; + } } out: + close_file_or_dir(fd, dirstream); +keep_fd: + fd = -1; + dirstream = NULL; + free(dupdname); + free(dupvname); + dupdname = NULL; + dupvname = NULL; cnt++; if (cnt < argc) goto again; + if (commit_mode == COMMIT_AFTER) { + int slot; + + /* + * Traverse seen_fsid_hash and issue SYNC ioctl on each + * filesystem + */ + for (slot = 0; slot < SEEN_FSID_HASH_SIZE; slot++) { + struct seen_fsid *seen = seen_fsid_hash[slot]; + + while (seen) { + res = wait_for_commit(seen->fd); + if (res < 0) { + uuid_unparse(seen->fsid, uuidbuf); + error( + "unable to do final sync after deletion: %m, fsid: %s", + uuidbuf); + ret = 1; + } else if (verbose > 0) { + uuid_unparse(seen->fsid, uuidbuf); + printf("final sync is done for fsid: %s\n", + uuidbuf); + } + seen = seen->next; + } + } + /* fd will also be closed in free_seen_fsid */ + free_seen_fsid(seen_fsid_hash); + } + return ret; } @@ -279,22 +410,32 @@ out: * - lowercase for enabling specific items in the output */ static const char * const cmd_subvol_list_usage[] = { - "btrfs subvolume list [-agopurts] [-G [+|-]value] [-C [+|-]value] " - "[--sort=gen,ogen,rootid,path] ", - "List subvolumes (and snapshots)", + "btrfs subvolume list [options] ", + "List subvolumes and snapshots in the filesystem.", "", - "-p print parent ID", + "Path filtering:", + "-o print only subvolumes below specified path", "-a print all the subvolumes in the filesystem and", " distinguish absolute and relative path with respect", " to the given ", + "", + "Field selection:", + "-p print parent ID", "-c print the ogeneration of the subvolume", "-g print the generation of the subvolume", - "-o print only subvolumes bellow specified path", "-u print the uuid of subvolumes (and snapshots)", "-q print the parent uuid of the snapshots", - "-t print the result as a table", - "-s list snapshots only in the filesystem", + "-R print the uuid of the received snapshots", + "", + "Type filtering:", + "-s list only snapshots", "-r list readonly subvolumes (including snapshots)", + "-d list deleted subvolumes that are not yet cleaned", + "", + "Other:", + "-t print the result as a table", + "", + "Sorting:", "-G [+|-]value", " filter the subvolumes by generation", " (+value: >= value; -value: <= value; value: = value)", @@ -316,23 +457,24 @@ static int cmd_subvol_list(int argc, char **argv) int fd = -1; u64 top_id; int ret = -1, uerr = 0; - int c; char *subvol; - int is_tab_result = 0; int is_list_all = 0; int is_only_in_path = 0; - struct option long_options[] = { - {"sort", 1, NULL, 'S'}, - {0, 0, 0, 0} - }; + DIR *dirstream = NULL; + enum btrfs_list_layout layout = BTRFS_LIST_LAYOUT_DEFAULT; filter_set = btrfs_list_alloc_filter_set(); comparer_set = btrfs_list_alloc_comparer_set(); - optind = 1; while(1) { + int c; + static const struct option long_options[] = { + {"sort", required_argument, NULL, 'S'}, + {NULL, 0, NULL, 0} + }; + c = getopt_long(argc, argv, - "acgopqsurG:C:t", long_options, NULL); + "acdgopqsurRG:C:t", long_options, NULL); if (c < 0) break; @@ -346,6 +488,11 @@ static int cmd_subvol_list(int argc, char **argv) case 'c': btrfs_list_setup_print_column(BTRFS_LIST_OGENERATION); break; + case 'd': + btrfs_list_setup_filter(&filter_set, + BTRFS_LIST_FILTER_DELETED, + 0); + break; case 'g': btrfs_list_setup_print_column(BTRFS_LIST_GENERATION); break; @@ -353,7 +500,7 @@ static int cmd_subvol_list(int argc, char **argv) is_only_in_path = 1; break; case 't': - is_tab_result = 1; + layout = BTRFS_LIST_LAYOUT_TABLE; break; case 's': btrfs_list_setup_filter(&filter_set, @@ -368,6 +515,9 @@ static int cmd_subvol_list(int argc, char **argv) case 'q': btrfs_list_setup_print_column(BTRFS_LIST_PUUID); break; + case 'R': + btrfs_list_setup_print_column(BTRFS_LIST_RUUID); + break; case 'r': flags |= BTRFS_ROOT_SUBVOL_RDONLY; break; @@ -407,40 +557,26 @@ static int cmd_subvol_list(int argc, char **argv) } } - if (flags) - btrfs_list_setup_filter(&filter_set, BTRFS_LIST_FILTER_FLAGS, - flags); - if (check_argc_exact(argc - optind, 1)) { uerr = 1; goto out; } subvol = argv[optind]; - - ret = test_issubvolume(subvol); - if (ret < 0) { - fprintf(stderr, "ERROR: error accessing '%s'\n", subvol); - goto out; - } - if (!ret) { - fprintf(stderr, "ERROR: '%s' is not a subvolume\n", subvol); - ret = -1; - goto out; - } - - fd = open_file_or_dir(subvol); + fd = btrfs_open_dir(subvol, &dirstream, 1); if (fd < 0) { ret = -1; - fprintf(stderr, "ERROR: can't access '%s'\n", subvol); + error("can't access '%s'", subvol); goto out; } + if (flags) + btrfs_list_setup_filter(&filter_set, BTRFS_LIST_FILTER_FLAGS, + flags); + ret = btrfs_list_get_path_rootid(fd, &top_id); - if (ret) { - fprintf(stderr, "ERROR: can't get rootid for '%s'\n", subvol); + if (ret) goto out; - } if (is_list_all) btrfs_list_setup_filter(&filter_set, @@ -457,50 +593,48 @@ static int cmd_subvol_list(int argc, char **argv) btrfs_list_setup_print_column(BTRFS_LIST_TOP_LEVEL); btrfs_list_setup_print_column(BTRFS_LIST_PATH); - if (is_tab_result) - ret = btrfs_list_subvols_print(fd, filter_set, comparer_set, - BTRFS_LIST_LAYOUT_TABLE, - !is_list_all && !is_only_in_path, NULL); - else - ret = btrfs_list_subvols_print(fd, filter_set, comparer_set, - BTRFS_LIST_LAYOUT_DEFAULT, - !is_list_all && !is_only_in_path, NULL); + ret = btrfs_list_subvols_print(fd, filter_set, comparer_set, + layout, !is_list_all && !is_only_in_path, NULL); out: - if (fd != -1) - close(fd); + close_file_or_dir(fd, dirstream); if (filter_set) - btrfs_list_free_filter_set(filter_set); + free(filter_set); if (comparer_set) - btrfs_list_free_comparer_set(comparer_set); + free(comparer_set); if (uerr) usage(cmd_subvol_list_usage); - - return ret; + return !!ret; } -static const char * const cmd_snapshot_usage[] = { - "btrfs subvolume snapshot [-r] [/]", +static const char * const cmd_subvol_snapshot_usage[] = { + "btrfs subvolume snapshot [-r] [-i ] |[/]", "Create a snapshot of the subvolume", "Create a writable/readonly snapshot of the subvolume with", - "the name in the directory", + "the name in the directory. If only is given,", + "the subvolume will be named the basename of .", "", - "-r create a readonly snapshot", + "-r create a readonly snapshot", + "-i add the newly created snapshot to a qgroup. This", + " option can be given multiple times.", NULL }; -static int cmd_snapshot(int argc, char **argv) +static int cmd_subvol_snapshot(int argc, char **argv) { char *subvol, *dst; int res, retval; int fd = -1, fddst = -1; int len, readonly = 0; + char *dupname = NULL; + char *dupdir = NULL; char *newname; char *dstdir; + enum btrfs_util_error err; struct btrfs_ioctl_vol_args_v2 args; struct btrfs_qgroup_inherit *inherit = NULL; + DIR *dirstream1 = NULL, *dirstream2 = NULL; - optind = 1; memset(&args, 0, sizeof(args)); while (1) { int c = getopt(argc, argv, "c:i:r"); @@ -510,86 +644,85 @@ static int cmd_snapshot(int argc, char **argv) switch (c) { case 'c': res = qgroup_inherit_add_copy(&inherit, optarg, 0); - if (res) - return res; + if (res) { + retval = res; + goto out; + } break; case 'i': res = qgroup_inherit_add_group(&inherit, optarg); - if (res) - return res; + if (res) { + retval = res; + goto out; + } break; case 'r': readonly = 1; break; case 'x': res = qgroup_inherit_add_copy(&inherit, optarg, 1); - if (res) - return res; + if (res) { + retval = res; + goto out; + } break; default: - usage(cmd_snapshot_usage); + usage(cmd_subvol_snapshot_usage); } } if (check_argc_exact(argc - optind, 2)) - usage(cmd_snapshot_usage); + usage(cmd_subvol_snapshot_usage); subvol = argv[optind]; dst = argv[optind + 1]; retval = 1; /* failure */ - res = test_issubvolume(subvol); - if (res < 0) { - fprintf(stderr, "ERROR: error accessing '%s'\n", subvol); - goto out; - } - if (!res) { - fprintf(stderr, "ERROR: '%s' is not a subvolume\n", subvol); + err = btrfs_util_is_subvolume(subvol); + if (err) { + error_btrfs_util(err); goto out; } res = test_isdir(dst); + if (res < 0 && res != -ENOENT) { + error("cannot access %s: %s", dst, strerror(-res)); + goto out; + } if (res == 0) { - fprintf(stderr, "ERROR: '%s' exists and it is not a directory\n", dst); + error("'%s' exists and it is not a directory", dst); goto out; } if (res > 0) { - newname = strdup(subvol); - newname = basename(newname); + dupname = strdup(subvol); + newname = basename(dupname); dstdir = dst; } else { - newname = strdup(dst); - newname = basename(newname); - dstdir = strdup(dst); - dstdir = dirname(dstdir); + dupname = strdup(dst); + newname = basename(dupname); + dupdir = strdup(dst); + dstdir = dirname(dupdir); } - if (!strcmp(newname, ".") || !strcmp(newname, "..") || - strchr(newname, '/') ){ - fprintf(stderr, "ERROR: incorrect snapshot name ('%s')\n", - newname); + if (!test_issubvolname(newname)) { + error("invalid snapshot name '%s'", newname); goto out; } len = strlen(newname); if (len == 0 || len >= BTRFS_VOL_NAME_MAX) { - fprintf(stderr, "ERROR: snapshot name too long ('%s)\n", - newname); + error("snapshot name too long '%s'", newname); goto out; } - fddst = open_file_or_dir(dstdir); - if (fddst < 0) { - fprintf(stderr, "ERROR: can't access to '%s'\n", dstdir); + fddst = btrfs_open_dir(dstdir, &dirstream1, 1); + if (fddst < 0) goto out; - } - fd = open_file_or_dir(subvol); - if (fd < 0) { - fprintf(stderr, "ERROR: can't access to '%s'\n", dstdir); + fd = btrfs_open_dir(subvol, &dirstream2, 1); + if (fd < 0) goto out; - } if (readonly) { args.flags |= BTRFS_SUBVOL_RDONLY; @@ -611,19 +744,18 @@ static int cmd_snapshot(int argc, char **argv) res = ioctl(fddst, BTRFS_IOC_SNAP_CREATE_V2, &args); if (res < 0) { - fprintf( stderr, "ERROR: cannot snapshot '%s' - %s\n", - subvol, strerror(errno)); + error("cannot snapshot '%s': %m", subvol); goto out; } retval = 0; /* success */ out: - if (fd != -1) - close(fd); - if (fddst != -1) - close(fddst); + close_file_or_dir(fddst, dirstream1); + close_file_or_dir(fd, dirstream2); free(inherit); + free(dupname); + free(dupdir); return retval; } @@ -637,324 +769,469 @@ static const char * const cmd_subvol_get_default_usage[] = { static int cmd_subvol_get_default(int argc, char **argv) { int fd = -1; - int ret; - char *subvol; - struct btrfs_list_filter_set *filter_set; - u64 default_id; + int ret = 1; + uint64_t default_id; + DIR *dirstream = NULL; + enum btrfs_util_error err; + struct btrfs_util_subvolume_info subvol; + char *path; - if (check_argc_exact(argc, 2)) - usage(cmd_subvol_get_default_usage); + clean_args_no_options(argc, argv, cmd_subvol_get_default_usage); - subvol = argv[1]; + if (check_argc_exact(argc - optind, 1)) + usage(cmd_subvol_get_default_usage); - ret = test_issubvolume(subvol); - if (ret < 0) { - fprintf(stderr, "ERROR: error accessing '%s'\n", subvol); - return 1; - } - if (!ret) { - fprintf(stderr, "ERROR: '%s' is not a subvolume\n", subvol); + fd = btrfs_open_dir(argv[1], &dirstream, 1); + if (fd < 0) return 1; - } - fd = open_file_or_dir(subvol); - if (fd < 0) { - fprintf(stderr, "ERROR: can't access '%s'\n", subvol); - return 1; + err = btrfs_util_get_default_subvolume_fd(fd, &default_id); + if (err) { + error_btrfs_util(err); + goto out; } - ret = btrfs_list_get_default_subvolume(fd, &default_id); - if (ret) { - fprintf(stderr, "ERROR: can't perform the search - %s\n", - strerror(errno)); + /* no need to resolve roots if FS_TREE is default */ + if (default_id == BTRFS_FS_TREE_OBJECTID) { + printf("ID 5 (FS_TREE)\n"); + ret = 0; goto out; } - ret = 1; - if (default_id == 0) { - fprintf(stderr, "ERROR: 'default' dir item not found\n"); + err = btrfs_util_subvolume_info_fd(fd, default_id, &subvol); + if (err) { + error_btrfs_util(err); goto out; } - /* no need to resolve roots if FS_TREE is default */ - if (default_id == BTRFS_FS_TREE_OBJECTID) { - printf("ID 5 (FS_TREE)\n"); + err = btrfs_util_subvolume_path_fd(fd, default_id, &path); + if (err) { + error_btrfs_util(err); goto out; } - filter_set = btrfs_list_alloc_filter_set(); - btrfs_list_setup_filter(&filter_set, BTRFS_LIST_FILTER_ROOTID, - default_id); - - /* by default we shall print the following columns*/ - btrfs_list_setup_print_column(BTRFS_LIST_OBJECTID); - btrfs_list_setup_print_column(BTRFS_LIST_GENERATION); - btrfs_list_setup_print_column(BTRFS_LIST_TOP_LEVEL); - btrfs_list_setup_print_column(BTRFS_LIST_PATH); + printf("ID %" PRIu64 " gen %" PRIu64 " top level %" PRIu64 " path %s\n", + subvol.id, subvol.generation, subvol.parent_id, path); - ret = btrfs_list_subvols_print(fd, filter_set, NULL, - BTRFS_LIST_LAYOUT_DEFAULT, 1, NULL); + free(path); - if (filter_set) - btrfs_list_free_filter_set(filter_set); + ret = 0; out: - if (fd != -1) - close(fd); - if (ret) - return 1; - return 0; + close_file_or_dir(fd, dirstream); + return ret; } static const char * const cmd_subvol_set_default_usage[] = { + "btrfs subvolume set-default \n" "btrfs subvolume set-default ", - "Set the default subvolume of a filesystem", + "Set the default subvolume of the filesystem mounted as default.", + "The subvolume can be specified by its path,", + "or the pair of subvolume id and path to the filesystem.", NULL }; static int cmd_subvol_set_default(int argc, char **argv) { - int ret=0, fd, e; - u64 objectid; - char *path; - char *subvolid; - - if (check_argc_exact(argc, 3)) - usage(cmd_subvol_set_default_usage); + u64 objectid; + char *path; + enum btrfs_util_error err; - subvolid = argv[1]; - path = argv[2]; + clean_args_no_options(argc, argv, cmd_subvol_set_default_usage); - objectid = (unsigned long long)strtoll(subvolid, NULL, 0); - if (errno == ERANGE) { - fprintf(stderr, "ERROR: invalid tree id (%s)\n", subvolid); - return 1; - } + if (check_argc_min(argc - optind, 1) || + check_argc_max(argc - optind, 2)) + usage(cmd_subvol_set_default_usage); - fd = open_file_or_dir(path); - if (fd < 0) { - fprintf(stderr, "ERROR: can't access to '%s'\n", path); - return 1; + if (argc - optind == 1) { + /* path to the subvolume is specified */ + objectid = 0; + path = argv[optind]; + } else { + /* subvol id and path to the filesystem are specified */ + objectid = arg_strtou64(argv[optind]); + path = argv[optind + 1]; } - ret = ioctl(fd, BTRFS_IOC_DEFAULT_SUBVOL, &objectid); - e = errno; - close(fd); - if (ret < 0) { - fprintf(stderr, "ERROR: unable to set a new default subvolume - %s\n", - strerror(e)); + err = btrfs_util_set_default_subvolume(path, objectid); + if (err) { + error_btrfs_util(err); return 1; } return 0; } -static const char * const cmd_find_new_usage[] = { +static const char * const cmd_subvol_find_new_usage[] = { "btrfs subvolume find-new ", "List the recently modified files in a filesystem", NULL }; -static int cmd_find_new(int argc, char **argv) +static int cmd_subvol_find_new(int argc, char **argv) { int fd; int ret; char *subvol; u64 last_gen; + DIR *dirstream = NULL; + enum btrfs_util_error err; - if (check_argc_exact(argc, 3)) - usage(cmd_find_new_usage); + clean_args_no_options(argc, argv, cmd_subvol_find_new_usage); - subvol = argv[1]; - last_gen = atoll(argv[2]); + if (check_argc_exact(argc - optind, 2)) + usage(cmd_subvol_find_new_usage); - ret = test_issubvolume(subvol); - if (ret < 0) { - fprintf(stderr, "ERROR: error accessing '%s'\n", subvol); - return 12; - } - if (!ret) { - fprintf(stderr, "ERROR: '%s' is not a subvolume\n", subvol); - return 13; + subvol = argv[optind]; + last_gen = arg_strtou64(argv[optind + 1]); + + err = btrfs_util_is_subvolume(subvol); + if (err) { + error_btrfs_util(err); + return 1; } - fd = open_file_or_dir(subvol); - if (fd < 0) { - fprintf(stderr, "ERROR: can't access '%s'\n", subvol); - return 12; + fd = btrfs_open_dir(subvol, &dirstream, 1); + if (fd < 0) + return 1; + + err = btrfs_util_sync_fd(fd); + if (err) { + error_btrfs_util(err); + close_file_or_dir(fd, dirstream); + return 1; } + ret = btrfs_list_find_updated_files(fd, 0, last_gen); - close(fd); - if (ret) - return 19; - return 0; + close_file_or_dir(fd, dirstream); + return !!ret; } static const char * const cmd_subvol_show_usage[] = { - "btrfs subvolume show ", - "Show more information of the subvolume", + "btrfs subvolume show [options] |", + "Show more information about the subvolume", + "-r|--rootid rootid of the subvolume", + "-u|--uuid uuid of the subvolume", + "", + "If no option is specified, will be shown, otherwise", + "the rootid or uuid are resolved relative to the path.", NULL }; static int cmd_subvol_show(int argc, char **argv) { - struct root_info get_ri; - struct btrfs_list_filter_set *filter_set; char tstr[256]; - char uuidparse[37]; - char *fullpath = NULL, *svpath = NULL, *mnt = NULL; - char raw_prefix[] = "\t\t\t\t"; - u64 sv_id, mntid; - int fd = -1, mntfd = -1; - int ret = -1; - - if (check_argc_exact(argc, 2)) - usage(cmd_subvol_show_usage); + char uuidparse[BTRFS_UUID_UNPARSED_SIZE]; + char *fullpath = NULL; + int fd = -1; + int ret = 1; + DIR *dirstream1 = NULL; + int by_rootid = 0; + int by_uuid = 0; + u64 rootid_arg = 0; + u8 uuid_arg[BTRFS_UUID_SIZE]; + struct btrfs_util_subvolume_iterator *iter; + struct btrfs_util_subvolume_info subvol; + char *subvol_path = NULL; + enum btrfs_util_error err; - fullpath = realpath(argv[1], 0); - if (!fullpath) { - fprintf(stderr, "ERROR: finding real path for '%s', %s\n", - argv[1], strerror(errno)); - goto out; - } + while (1) { + int c; + static const struct option long_options[] = { + { "rootid", required_argument, NULL, 'r'}, + { "uuid", required_argument, NULL, 'u'}, + { NULL, 0, NULL, 0 } + }; + + c = getopt_long(argc, argv, "r:u:", long_options, NULL); + if (c < 0) + break; - ret = test_issubvolume(fullpath); - if (ret < 0) { - fprintf(stderr, "ERROR: error accessing '%s'\n", fullpath); - goto out; + switch (c) { + case 'r': + rootid_arg = arg_strtou64(optarg); + by_rootid = 1; + break; + case 'u': + uuid_parse(optarg, uuid_arg); + by_uuid = 1; + break; + default: + usage(cmd_subvol_show_usage); + } } - if (!ret) { - fprintf(stderr, "ERROR: '%s' is not a subvolume\n", fullpath); - ret = -1; - goto out; + + if (check_argc_exact(argc - optind, 1)) + usage(cmd_subvol_show_usage); + + if (by_rootid && by_uuid) { + error( + "options --rootid and --uuid cannot be used at the same time"); + usage(cmd_subvol_show_usage); } - ret = find_mount_root(fullpath, &mnt); - if (ret < 0) { - fprintf(stderr, "ERROR: find_mount_root failed on %s: " - "%s\n", fullpath, strerror(-ret)); + fullpath = realpath(argv[optind], NULL); + if (!fullpath) { + error("cannot find real path for '%s': %m", argv[optind]); goto out; } - ret = -1; - svpath = get_subvol_name(mnt, fullpath); - fd = open_file_or_dir(fullpath); + fd = open_file_or_dir(fullpath, &dirstream1); if (fd < 0) { - fprintf(stderr, "ERROR: can't access '%s'\n", fullpath); + error("can't access '%s'", fullpath); goto out; } - ret = btrfs_list_get_path_rootid(fd, &sv_id); - if (ret) { - fprintf(stderr, "ERROR: can't get rootid for '%s'\n", - fullpath); - goto out; - } + if (by_uuid) { + err = btrfs_util_create_subvolume_iterator_fd(fd, + BTRFS_FS_TREE_OBJECTID, + 0, &iter); + if (err) { + error_btrfs_util(err); + goto out; + } - mntfd = open_file_or_dir(mnt); - if (mntfd < 0) { - fprintf(stderr, "ERROR: can't access '%s'\n", mnt); - goto out; - } + for (;;) { + err = btrfs_util_subvolume_iterator_next_info(iter, + &subvol_path, + &subvol); + if (err == BTRFS_UTIL_ERROR_STOP_ITERATION) { + uuid_unparse(uuid_arg, uuidparse); + error("can't find uuid '%s' on '%s'", uuidparse, + fullpath); + btrfs_util_destroy_subvolume_iterator(iter); + goto out; + } else if (err) { + error_btrfs_util(err); + btrfs_util_destroy_subvolume_iterator(iter); + goto out; + } - ret = btrfs_list_get_path_rootid(mntfd, &mntid); - if (ret) { - fprintf(stderr, "ERROR: can't get rootid for '%s'\n", mnt); - goto out; - } + if (uuid_compare(subvol.uuid, uuid_arg) == 0) + break; - if (sv_id == BTRFS_FS_TREE_OBJECTID) { - printf("%s is btrfs root\n", fullpath); - goto out; - } + free(subvol_path); + } + btrfs_util_destroy_subvolume_iterator(iter); + } else { + /* + * If !by_rootid, rootid_arg = 0, which means find the + * subvolume ID of the fd and use that. + */ + err = btrfs_util_subvolume_info_fd(fd, rootid_arg, &subvol); + if (err) { + error_btrfs_util(err); + goto out; + } - memset(&get_ri, 0, sizeof(get_ri)); - get_ri.root_id = sv_id; + err = btrfs_util_subvolume_path_fd(fd, subvol.id, &subvol_path); + if (err) { + error_btrfs_util(err); + goto out; + } - if (btrfs_get_subvol(mntfd, &get_ri)) { - fprintf(stderr, "ERROR: can't find '%s'\n", - svpath); - goto out; } - ret = 0; /* print the info */ - printf("%s\n", fullpath); - printf("\tName: \t\t\t%s\n", get_ri.name); + printf("%s\n", subvol.id == BTRFS_FS_TREE_OBJECTID ? "/" : subvol_path); + printf("\tName: \t\t\t%s\n", + (subvol.id == BTRFS_FS_TREE_OBJECTID ? "" : + basename(subvol_path))); - if (uuid_is_null(get_ri.uuid)) + if (uuid_is_null(subvol.uuid)) strcpy(uuidparse, "-"); else - uuid_unparse(get_ri.uuid, uuidparse); - printf("\tuuid: \t\t\t%s\n", uuidparse); + uuid_unparse(subvol.uuid, uuidparse); + printf("\tUUID: \t\t\t%s\n", uuidparse); - if (uuid_is_null(get_ri.puuid)) + if (uuid_is_null(subvol.parent_uuid)) strcpy(uuidparse, "-"); else - uuid_unparse(get_ri.puuid, uuidparse); - printf("\tParent uuid: \t\t%s\n", uuidparse); + uuid_unparse(subvol.parent_uuid, uuidparse); + printf("\tParent UUID: \t\t%s\n", uuidparse); - if (get_ri.otime) - strftime(tstr, 256, "%Y-%m-%d %X", - localtime(&get_ri.otime)); + if (uuid_is_null(subvol.received_uuid)) + strcpy(uuidparse, "-"); else + uuid_unparse(subvol.received_uuid, uuidparse); + printf("\tReceived UUID: \t\t%s\n", uuidparse); + + if (subvol.otime.tv_sec) { + struct tm tm; + + localtime_r(&subvol.otime.tv_sec, &tm); + strftime(tstr, 256, "%Y-%m-%d %X %z", &tm); + } else strcpy(tstr, "-"); printf("\tCreation time: \t\t%s\n", tstr); - printf("\tObject ID: \t\t%llu\n", get_ri.root_id); - printf("\tGeneration (Gen): \t%llu\n", get_ri.gen); - printf("\tGen at creation: \t%llu\n", get_ri.ogen); - printf("\tParent: \t\t%llu\n", get_ri.ref_tree); - printf("\tTop Level: \t\t%llu\n", get_ri.top_id); + printf("\tSubvolume ID: \t\t%" PRIu64 "\n", subvol.id); + printf("\tGeneration: \t\t%" PRIu64 "\n", subvol.generation); + printf("\tGen at creation: \t%" PRIu64 "\n", subvol.otransid); + printf("\tParent ID: \t\t%" PRIu64 "\n", subvol.parent_id); + printf("\tTop level ID: \t\t%" PRIu64 "\n", subvol.parent_id); - if (get_ri.flags & BTRFS_ROOT_SUBVOL_RDONLY) + if (subvol.flags & BTRFS_ROOT_SUBVOL_RDONLY) printf("\tFlags: \t\t\treadonly\n"); else printf("\tFlags: \t\t\t-\n"); /* print the snapshots of the given subvol if any*/ printf("\tSnapshot(s):\n"); - filter_set = btrfs_list_alloc_filter_set(); - btrfs_list_setup_filter(&filter_set, BTRFS_LIST_FILTER_BY_PARENT, - (u64)get_ri.uuid); - btrfs_list_setup_print_column(BTRFS_LIST_PATH); - btrfs_list_subvols_print(fd, filter_set, NULL, BTRFS_LIST_LAYOUT_RAW, - 1, raw_prefix); - - /* clean up */ - if (get_ri.path) - free(get_ri.path); - if (get_ri.name) - free(get_ri.name); - if (get_ri.full_path) - free(get_ri.full_path); - if (filter_set) - btrfs_list_free_filter_set(filter_set); + err = btrfs_util_create_subvolume_iterator_fd(fd, + BTRFS_FS_TREE_OBJECTID, 0, + &iter); + + for (;;) { + struct btrfs_util_subvolume_info subvol2; + char *path; + + err = btrfs_util_subvolume_iterator_next_info(iter, &path, &subvol2); + if (err == BTRFS_UTIL_ERROR_STOP_ITERATION) { + break; + } else if (err) { + error_btrfs_util(err); + btrfs_util_destroy_subvolume_iterator(iter); + goto out; + } + + if (uuid_compare(subvol2.parent_uuid, subvol.uuid) == 0) + printf("\t\t\t\t%s\n", path); + + free(path); + } + btrfs_util_destroy_subvolume_iterator(iter); + + ret = 0; out: - if (mntfd >= 0) - close(mntfd); - if (fd >= 0) - close(fd); - if (mnt) - free(mnt); - if (fullpath) - free(fullpath); + free(subvol_path); + close_file_or_dir(fd, dirstream1); + free(fullpath); + return !!ret; +} - return ret; +static const char * const cmd_subvol_sync_usage[] = { + "btrfs subvolume sync [...]", + "Wait until given subvolume(s) are completely removed from the filesystem.", + "Wait until given subvolume(s) are completely removed from the filesystem", + "after deletion.", + "If no subvolume id is given, wait until all current deletion requests", + "are completed, but do not wait for subvolumes deleted meanwhile.", + "The status of subvolume ids is checked periodically.", + "", + "-s sleep N seconds between checks (default: 1)", + NULL +}; + +static int cmd_subvol_sync(int argc, char **argv) +{ + int fd = -1; + int ret = 1; + DIR *dirstream = NULL; + uint64_t *ids = NULL; + size_t id_count, i; + int sleep_interval = 1; + enum btrfs_util_error err; + + while (1) { + int c = getopt(argc, argv, "s:"); + + if (c < 0) + break; + + switch (c) { + case 's': + sleep_interval = atoi(optarg); + if (sleep_interval < 1) { + error("invalid sleep interval %s", optarg); + ret = 1; + goto out; + } + break; + default: + usage(cmd_subvol_sync_usage); + } + } + + if (check_argc_min(argc - optind, 1)) + usage(cmd_subvol_sync_usage); + + fd = btrfs_open_dir(argv[optind], &dirstream, 1); + if (fd < 0) { + ret = 1; + goto out; + } + optind++; + + id_count = argc - optind; + if (!id_count) { + err = btrfs_util_deleted_subvolumes_fd(fd, &ids, &id_count); + if (err) { + error_btrfs_util(err); + ret = 1; + goto out; + } + if (id_count == 0) { + ret = 0; + goto out; + } + } else { + ids = malloc(id_count * sizeof(uint64_t)); + if (!ids) { + error("not enough memory"); + ret = 1; + goto out; + } + + for (i = 0; i < id_count; i++) { + u64 id; + const char *arg; + + arg = argv[optind + i]; + errno = 0; + id = strtoull(arg, NULL, 10); + if (errno) { + error("unrecognized subvolume id %s", arg); + ret = 1; + goto out; + } + if (id < BTRFS_FIRST_FREE_OBJECTID || + id > BTRFS_LAST_FREE_OBJECTID) { + error("subvolume id %s out of range", arg); + ret = 1; + goto out; + } + ids[i] = id; + } + } + + ret = wait_for_subvolume_cleaning(fd, id_count, ids, sleep_interval); + +out: + free(ids); + close_file_or_dir(fd, dirstream); + + return !!ret; } +static const char subvolume_cmd_group_info[] = +"manage subvolumes: create, delete, list, etc"; + const struct cmd_group subvolume_cmd_group = { - subvolume_cmd_group_usage, NULL, { + subvolume_cmd_group_usage, subvolume_cmd_group_info, { { "create", cmd_subvol_create, cmd_subvol_create_usage, NULL, 0 }, { "delete", cmd_subvol_delete, cmd_subvol_delete_usage, NULL, 0 }, { "list", cmd_subvol_list, cmd_subvol_list_usage, NULL, 0 }, - { "snapshot", cmd_snapshot, cmd_snapshot_usage, NULL, 0 }, + { "snapshot", cmd_subvol_snapshot, cmd_subvol_snapshot_usage, + NULL, 0 }, { "get-default", cmd_subvol_get_default, cmd_subvol_get_default_usage, NULL, 0 }, { "set-default", cmd_subvol_set_default, cmd_subvol_set_default_usage, NULL, 0 }, - { "find-new", cmd_find_new, cmd_find_new_usage, NULL, 0 }, + { "find-new", cmd_subvol_find_new, cmd_subvol_find_new_usage, + NULL, 0 }, { "show", cmd_subvol_show, cmd_subvol_show_usage, NULL, 0 }, - { 0, 0, 0, 0, 0 } + { "sync", cmd_subvol_sync, cmd_subvol_sync_usage, NULL, 0 }, + NULL_CMD_STRUCT } };