static int cmd_find_new(int argc, char **argv) { int fd; int ret; char *subvol; u64 last_gen; DIR *dirstream = NULL; if (check_argc_exact(argc, 3)) usage(cmd_find_new_usage); subvol = argv[1]; last_gen = arg_strtou64(argv[2]); 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); return 1; } fd = open_file_or_dir(subvol, &dirstream); if (fd < 0) { fprintf(stderr, "ERROR: can't access '%s'\n", subvol); return 1; } ret = ioctl(fd, BTRFS_IOC_SYNC); if (ret < 0) { fprintf(stderr, "ERROR: unable to fs-syncing '%s' - %s\n", subvol, strerror(errno)); close_file_or_dir(fd, dirstream); return 1; } ret = btrfs_list_find_updated_files(fd, 0, last_gen); close_file_or_dir(fd, dirstream); return !!ret; }
static int cmd_subvol_find_new(int argc, char **argv) { int fd; int ret; char *subvol; u64 last_gen; DIR *dirstream = NULL; clean_args_no_options(argc, argv, cmd_subvol_find_new_usage); if (check_argc_exact(argc - optind, 2)) usage(cmd_subvol_find_new_usage); subvol = argv[optind]; last_gen = arg_strtou64(argv[optind + 1]); ret = test_issubvolume(subvol); if (ret < 0) { error("cannot access subvolume %s: %s", subvol, strerror(-ret)); return 1; } if (!ret) { error("not a subvolume: %s", subvol); return 1; } fd = btrfs_open_dir(subvol, &dirstream, 1); if (fd < 0) return 1; ret = ioctl(fd, BTRFS_IOC_SYNC); if (ret < 0) { error("sync ioctl failed on '%s': %s", subvol, strerror(errno)); close_file_or_dir(fd, dirstream); return 1; } ret = btrfs_list_find_updated_files(fd, 0, last_gen); close_file_or_dir(fd, dirstream); return !!ret; }
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[BTRFS_UUID_UNPARSED_SIZE]; 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; DIR *dirstream1 = NULL, *dirstream2 = NULL; if (check_argc_exact(argc, 2)) usage(cmd_subvol_show_usage); fullpath = realpath(argv[1], NULL); if (!fullpath) { fprintf(stderr, "ERROR: finding real path for '%s', %s\n", argv[1], strerror(errno)); goto out; } ret = test_issubvolume(fullpath); if (ret < 0) { fprintf(stderr, "ERROR: error accessing '%s'\n", fullpath); goto out; } if (!ret) { fprintf(stderr, "ERROR: '%s' is not a subvolume\n", fullpath); ret = 1; goto out; } ret = find_mount_root(fullpath, &mnt); if (ret < 0) { fprintf(stderr, "ERROR: find_mount_root failed on '%s': " "%s\n", fullpath, strerror(-ret)); goto out; } if (ret > 0) { fprintf(stderr, "ERROR: %s doesn't belong to btrfs mount point\n", fullpath); goto out; } ret = 1; svpath = get_subvol_name(mnt, fullpath); fd = open_file_or_dir(fullpath, &dirstream1); if (fd < 0) { fprintf(stderr, "ERROR: can't access '%s'\n", 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; } mntfd = open_file_or_dir(mnt, &dirstream2); if (mntfd < 0) { fprintf(stderr, "ERROR: can't access '%s'\n", mnt); 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 (sv_id == BTRFS_FS_TREE_OBJECTID) { printf("%s is btrfs root\n", fullpath); goto out; } memset(&get_ri, 0, sizeof(get_ri)); get_ri.root_id = sv_id; ret = btrfs_get_subvol(mntfd, &get_ri); if (ret) { fprintf(stderr, "ERROR: can't find '%s'\n", svpath); goto out; } /* print the info */ printf("%s\n", fullpath); printf("\tName: \t\t\t%s\n", get_ri.name); if (uuid_is_null(get_ri.uuid)) strcpy(uuidparse, "-"); else uuid_unparse(get_ri.uuid, uuidparse); printf("\tUUID: \t\t\t%s\n", uuidparse); if (uuid_is_null(get_ri.puuid)) strcpy(uuidparse, "-"); else uuid_unparse(get_ri.puuid, uuidparse); printf("\tParent UUID: \t\t%s\n", uuidparse); if (uuid_is_null(get_ri.ruuid)) strcpy(uuidparse, "-"); else uuid_unparse(get_ri.ruuid, uuidparse); printf("\tReceived UUID: \t\t%s\n", uuidparse); if (get_ri.otime) { struct tm tm; localtime_r(&get_ri.otime, &tm); strftime(tstr, 256, "%Y-%m-%d %X %z", &tm); } else strcpy(tstr, "-"); printf("\tCreation time: \t\t%s\n", tstr); printf("\tSubvolume ID: \t\t%llu\n", get_ri.root_id); printf("\tGeneration: \t\t%llu\n", get_ri.gen); printf("\tGen at creation: \t%llu\n", get_ri.ogen); printf("\tParent ID: \t\t%llu\n", get_ri.ref_tree); printf("\tTop level ID: \t\t%llu\n", get_ri.top_id); if (get_ri.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)(unsigned long)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 */ free(get_ri.path); free(get_ri.name); free(get_ri.full_path); btrfs_list_free_filter_set(filter_set); out: close_file_or_dir(fd, dirstream1); close_file_or_dir(mntfd, dirstream2); free(mnt); free(fullpath); return !!ret; }
static int cmd_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; 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"); if (c < 0) break; switch (c) { case 'c': res = qgroup_inherit_add_copy(&inherit, optarg, 0); if (res) { retval = res; goto out; } break; case 'i': res = qgroup_inherit_add_group(&inherit, optarg); if (res) { retval = res; goto out; } break; case 'r': readonly = 1; break; case 'x': res = qgroup_inherit_add_copy(&inherit, optarg, 1); if (res) { retval = res; goto out; } break; default: usage(cmd_snapshot_usage); } } if (check_argc_exact(argc - optind, 2)) usage(cmd_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); goto out; } res = test_isdir(dst); if (res == 0) { fprintf(stderr, "ERROR: '%s' exists and it is not a directory\n", dst); goto out; } if (res > 0) { dupname = strdup(subvol); newname = basename(dupname); dstdir = dst; } else { dupname = strdup(dst); newname = basename(dupname); dupdir = strdup(dst); dstdir = dirname(dupdir); } if (!test_issubvolname(newname)) { fprintf(stderr, "ERROR: incorrect snapshot name '%s'\n", newname); goto out; } len = strlen(newname); if (len == 0 || len >= BTRFS_VOL_NAME_MAX) { fprintf(stderr, "ERROR: snapshot name too long '%s'\n", newname); goto out; } fddst = open_file_or_dir(dstdir, &dirstream1); if (fddst < 0) { fprintf(stderr, "ERROR: can't access '%s'\n", dstdir); goto out; } fd = open_file_or_dir(subvol, &dirstream2); if (fd < 0) { fprintf(stderr, "ERROR: can't access '%s'\n", dstdir); goto out; } if (readonly) { args.flags |= BTRFS_SUBVOL_RDONLY; printf("Create a readonly snapshot of '%s' in '%s/%s'\n", subvol, dstdir, newname); } else { printf("Create a snapshot of '%s' in '%s/%s'\n", subvol, dstdir, newname); } args.fd = fd; if (inherit) { args.flags |= BTRFS_SUBVOL_QGROUP_INHERIT; args.size = qgroup_inherit_size(inherit); args.qgroup_inherit = inherit; } strncpy_null(args.name, newname); res = ioctl(fddst, BTRFS_IOC_SNAP_CREATE_V2, &args); if (res < 0) { fprintf( stderr, "ERROR: cannot snapshot '%s' - %s\n", subvol, strerror(errno)); goto out; } retval = 0; /* success */ out: close_file_or_dir(fddst, dirstream1); close_file_or_dir(fd, dirstream2); free(inherit); free(dupname); free(dupdir); return retval; }
static int cmd_subvol_delete(int argc, char **argv) { int res, e, ret = 0; int cnt; int fd = -1; struct btrfs_ioctl_vol_args args; char *dname, *vname, *cpath; char *dupdname = NULL; char *dupvname = NULL; char *path; DIR *dirstream = NULL; int verbose = 0; int commit_mode = 0; optind = 1; while (1) { int c; static const struct option long_options[] = { {"commit-after", no_argument, NULL, 'c'}, /* commit mode 1 */ {"commit-each", no_argument, NULL, 'C'}, /* commit mode 2 */ {NULL, 0, NULL, 0} }; c = getopt_long(argc, argv, "cC", long_options, NULL); if (c < 0) break; switch(c) { case 'c': commit_mode = 1; break; case 'C': commit_mode = 2; break; case 'v': verbose++; break; default: usage(cmd_subvol_delete_usage); } } 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 == 1 ? "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 = 1; goto out; } if (!res) { fprintf(stderr, "ERROR: '%s' is not a subvolume\n", path); ret = 1; goto out; } cpath = realpath(path, NULL); if (!cpath) { ret = errno; fprintf(stderr, "ERROR: finding real path for '%s': %s\n", path, strerror(errno)); goto out; } dupdname = strdup(cpath); dname = dirname(dupdname); dupvname = strdup(cpath); vname = basename(dupvname); free(cpath); fd = open_file_or_dir(dname, &dirstream); if (fd < 0) { fprintf(stderr, "ERROR: can't access '%s'\n", dname); ret = 1; goto out; } printf("Delete subvolume (%s): '%s/%s'\n", commit_mode == 2 || (commit_mode == 1 && cnt + 1 == argc) ? "commit" : "no-commit", dname, vname); memset(&args, 0, sizeof(args)); strncpy_null(args.name, vname); res = ioctl(fd, BTRFS_IOC_SNAP_DESTROY, &args); e = errno; if(res < 0 ){ fprintf( stderr, "ERROR: cannot delete '%s/%s' - %s\n", dname, vname, strerror(e)); ret = 1; goto out; } if (commit_mode == 1) { res = wait_for_commit(fd); if (res < 0) { fprintf(stderr, "ERROR: unable to wait for commit after '%s': %s\n", path, strerror(errno)); ret = 1; } } out: free(dupdname); free(dupvname); dupdname = NULL; dupvname = NULL; cnt++; if (cnt < argc) { close_file_or_dir(fd, dirstream); /* avoid double free */ fd = -1; dirstream = NULL; goto again; } if (commit_mode == 2 && fd != -1) { res = wait_for_commit(fd); if (res < 0) { fprintf(stderr, "ERROR: unable to do final sync: %s\n", strerror(errno)); ret = 1; } } close_file_or_dir(fd, dirstream); return ret; }
static int cmd_qgroup_limit(int argc, char **argv) { int ret = 0; int fd; int e; char *path = NULL; struct btrfs_ioctl_qgroup_limit_args args; unsigned long long size; int compressed = 0; int exclusive = 0; DIR *dirstream = NULL; optind = 1; while (1) { int c = getopt(argc, argv, "ce"); if (c < 0) break; switch (c) { case 'c': compressed = 1; break; case 'e': exclusive = 1; break; default: usage(cmd_qgroup_limit_usage); } } if (check_argc_min(argc - optind, 2)) usage(cmd_qgroup_limit_usage); if (!parse_limit(argv[optind], &size)) { error("invalid size argument: %s", argv[optind]); return 1; } memset(&args, 0, sizeof(args)); if (compressed) args.lim.flags |= BTRFS_QGROUP_LIMIT_RFER_CMPR | BTRFS_QGROUP_LIMIT_EXCL_CMPR; if (exclusive) { args.lim.flags |= BTRFS_QGROUP_LIMIT_MAX_EXCL; args.lim.max_exclusive = size; } else { args.lim.flags |= BTRFS_QGROUP_LIMIT_MAX_RFER; args.lim.max_referenced = size; } if (argc - optind == 2) { args.qgroupid = 0; path = argv[optind + 1]; ret = test_issubvolume(path); if (ret < 0) { error("cannot access '%s': %s", path, strerror(-ret)); return 1; } if (!ret) { error("'%s' is not a subvolume", path); return 1; } /* * keep qgroupid at 0, this indicates that the subvolume the * fd refers to is to be limited */ } else if (argc - optind == 3) { args.qgroupid = parse_qgroupid(argv[optind + 1]); path = argv[optind + 2]; } else usage(cmd_qgroup_limit_usage); fd = btrfs_open_dir(path, &dirstream, 1); if (fd < 0) return 1; ret = ioctl(fd, BTRFS_IOC_QGROUP_LIMIT, &args); e = errno; close_file_or_dir(fd, dirstream); if (ret < 0) { error("unable to limit requested quota group: %s", strerror(e)); return 1; } return 0; }