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_qgroup_show(int argc, char **argv) { char *path; int ret = 0; int fd; int e; DIR *dirstream = NULL; u64 qgroupid; int filter_flag = 0; unsigned unit_mode; struct btrfs_qgroup_comparer_set *comparer_set; struct btrfs_qgroup_filter_set *filter_set; filter_set = btrfs_qgroup_alloc_filter_set(); comparer_set = btrfs_qgroup_alloc_comparer_set(); unit_mode = get_unit_mode_from_arg(&argc, argv, 0); 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, "pcreFf", long_options, NULL); if (c < 0) break; switch (c) { case 'p': btrfs_qgroup_setup_print_column( BTRFS_QGROUP_PARENT); break; case 'c': btrfs_qgroup_setup_print_column( BTRFS_QGROUP_CHILD); break; case 'r': btrfs_qgroup_setup_print_column( BTRFS_QGROUP_MAX_RFER); break; case 'e': btrfs_qgroup_setup_print_column( BTRFS_QGROUP_MAX_EXCL); break; case 'F': filter_flag |= 0x1; break; case 'f': filter_flag |= 0x2; break; case 'S': ret = btrfs_qgroup_parse_sort_string(optarg, &comparer_set); if (ret) usage(cmd_qgroup_show_usage); break; default: usage(cmd_qgroup_show_usage); } } btrfs_qgroup_setup_units(unit_mode); if (check_argc_exact(argc - optind, 1)) usage(cmd_qgroup_show_usage); path = argv[optind]; fd = btrfs_open_dir(path, &dirstream, 1); if (fd < 0) { btrfs_qgroup_free_filter_set(filter_set); btrfs_qgroup_free_comparer_set(comparer_set); return 1; } if (filter_flag) { qgroupid = btrfs_get_path_rootid(fd); if (filter_flag & 0x1) btrfs_qgroup_setup_filter(&filter_set, BTRFS_QGROUP_FILTER_ALL_PARENT, qgroupid); if (filter_flag & 0x2) btrfs_qgroup_setup_filter(&filter_set, BTRFS_QGROUP_FILTER_PARENT, qgroupid); } ret = btrfs_show_qgroups(fd, filter_set, comparer_set); e = errno; close_file_or_dir(fd, dirstream); if (ret < 0) error("can't list qgroups: %s", strerror(e)); return !!ret; }
static int cmd_inspect_logical_resolve(int argc, char **argv) { int ret; int fd; int i; int verbose = 0; int getpath = 1; int bytes_left; struct btrfs_ioctl_logical_ino_args loi; struct btrfs_data_container *inodes; u64 size = 4096; char full_path[4096]; char *path_ptr; DIR *dirstream = NULL; optind = 1; while (1) { int c = getopt(argc, argv, "Pvs:"); if (c < 0) break; switch (c) { case 'P': getpath = 0; break; case 'v': verbose = 1; break; case 's': size = arg_strtou64(optarg); break; default: usage(cmd_inspect_logical_resolve_usage); } } if (check_argc_exact(argc - optind, 2)) usage(cmd_inspect_logical_resolve_usage); size = min(size, (u64)64 * 1024); inodes = malloc(size); if (!inodes) return 1; memset(inodes, 0, sizeof(*inodes)); loi.logical = arg_strtou64(argv[optind]); loi.size = size; loi.inodes = ptr_to_u64(inodes); fd = btrfs_open_dir(argv[optind + 1], &dirstream, 1); if (fd < 0) { ret = 12; goto out; } ret = ioctl(fd, BTRFS_IOC_LOGICAL_INO, &loi); if (ret < 0) { error("logical ino ioctl: %s", strerror(errno)); goto out; } if (verbose) printf("ioctl ret=%d, total_size=%llu, bytes_left=%lu, " "bytes_missing=%lu, cnt=%d, missed=%d\n", ret, size, (unsigned long)inodes->bytes_left, (unsigned long)inodes->bytes_missing, inodes->elem_cnt, inodes->elem_missed); bytes_left = sizeof(full_path); ret = snprintf(full_path, bytes_left, "%s/", argv[optind+1]); path_ptr = full_path + ret; bytes_left -= ret + 1; BUG_ON(bytes_left < 0); for (i = 0; i < inodes->elem_cnt; i += 3) { u64 inum = inodes->val[i]; u64 offset = inodes->val[i+1]; u64 root = inodes->val[i+2]; int path_fd; char *name; DIR *dirs = NULL; if (getpath) { name = btrfs_list_path_for_root(fd, root); if (IS_ERR(name)) { ret = PTR_ERR(name); goto out; } if (!name) { path_ptr[-1] = '\0'; path_fd = fd; } else { path_ptr[-1] = '/'; ret = snprintf(path_ptr, bytes_left, "%s", name); BUG_ON(ret >= bytes_left); free(name); path_fd = btrfs_open_dir(full_path, &dirs, 1); if (path_fd < 0) { ret = -ENOENT; goto out; } } __ino_to_path_fd(inum, path_fd, verbose, full_path); if (path_fd != fd) close_file_or_dir(path_fd, dirs); } else { printf("inode %llu offset %llu root %llu\n", inum, offset, root); } } out: close_file_or_dir(fd, dirstream); free(inodes); return !!ret; }
static int cmd_device_add(int argc, char **argv) { char *mntpnt; int i, fdmnt, ret = 0; DIR *dirstream = NULL; int discard = 1; int force = 0; int last_dev; optind = 0; while (1) { int c; static const struct option long_options[] = { { "nodiscard", optional_argument, NULL, 'K'}, { "force", no_argument, NULL, 'f'}, { NULL, 0, NULL, 0} }; c = getopt_long(argc, argv, "Kf", long_options, NULL); if (c < 0) break; switch (c) { case 'K': discard = 0; break; case 'f': force = 1; break; default: usage(cmd_device_add_usage); } } if (check_argc_min(argc - optind, 2)) usage(cmd_device_add_usage); last_dev = argc - 1; mntpnt = argv[last_dev]; fdmnt = btrfs_open_dir(mntpnt, &dirstream, 1); if (fdmnt < 0) return 1; for (i = optind; i < last_dev; i++){ struct btrfs_ioctl_vol_args ioctl_args; int devfd, res; u64 dev_block_count = 0; char *path; res = test_dev_for_mkfs(argv[i], force); if (res) { ret++; continue; } devfd = open(argv[i], O_RDWR); if (devfd < 0) { error("unable to open device '%s'", argv[i]); ret++; continue; } res = btrfs_prepare_device(devfd, argv[i], &dev_block_count, 0, PREP_DEVICE_ZERO_END | PREP_DEVICE_VERBOSE | (discard ? PREP_DEVICE_DISCARD : 0)); close(devfd); if (res) { ret++; goto error_out; } path = canonicalize_path(argv[i]); if (!path) { error("could not canonicalize pathname '%s': %m", argv[i]); ret++; goto error_out; } memset(&ioctl_args, 0, sizeof(ioctl_args)); strncpy_null(ioctl_args.name, path); res = ioctl(fdmnt, BTRFS_IOC_ADD_DEV, &ioctl_args); if (res < 0) { error("error adding device '%s': %m", path); ret++; } free(path); } error_out: close_file_or_dir(fdmnt, dirstream); return !!ret; }
static int _cmd_device_remove(int argc, char **argv, const char * const *usagestr) { char *mntpnt; int i, fdmnt, ret = 0; DIR *dirstream = NULL; clean_args_no_options(argc, argv, usagestr); if (check_argc_min(argc - optind, 2)) usage(usagestr); mntpnt = argv[argc - 1]; fdmnt = btrfs_open_dir(mntpnt, &dirstream, 1); if (fdmnt < 0) return 1; for(i = optind; i < argc - 1; i++) { struct btrfs_ioctl_vol_args arg; struct btrfs_ioctl_vol_args_v2 argv2 = {0}; int is_devid = 0; int res; if (string_is_numerical(argv[i])) { argv2.devid = arg_strtou64(argv[i]); argv2.flags = BTRFS_DEVICE_SPEC_BY_ID; is_devid = 1; } else if (is_block_device(argv[i]) == 1 || strcmp(argv[i], "missing") == 0) { strncpy_null(argv2.name, argv[i]); } else { error("not a block device: %s", argv[i]); ret++; continue; } /* * Positive values are from BTRFS_ERROR_DEV_*, * otherwise it's a generic error, one of errnos */ res = ioctl(fdmnt, BTRFS_IOC_RM_DEV_V2, &argv2); /* * If BTRFS_IOC_RM_DEV_V2 is not supported we get ENOTTY and if * argv2.flags includes a flag which kernel doesn't understand then * we shall get EOPNOTSUPP */ if (res < 0 && (errno == ENOTTY || errno == EOPNOTSUPP)) { if (is_devid) { error("device delete by id failed: %m"); ret++; continue; } memset(&arg, 0, sizeof(arg)); strncpy_null(arg.name, argv[i]); res = ioctl(fdmnt, BTRFS_IOC_RM_DEV, &arg); } if (res) { const char *msg; if (res > 0) msg = btrfs_err_str(res); else msg = strerror(errno); if (is_devid) { error("error removing devid %llu: %s", (unsigned long long)argv2.devid, msg); } else { error("error removing device '%s': %s", argv[i], msg); } ret++; } } close_file_or_dir(fdmnt, dirstream); return !!ret; }
/* Checks the status of the balance if any * return codes: * 2 : Error failed to know if there is any pending balance * 1 : Successful to know status of a pending balance * 0 : When there is no pending balance or completed */ static int cmd_balance_status(int argc, char **argv) { struct btrfs_ioctl_balance_args args; const char *path; DIR *dirstream = NULL; int fd; int verbose = 0; int ret; int e; optind = 1; while (1) { int opt; static const struct option longopts[] = { { "verbose", no_argument, NULL, 'v' }, { NULL, 0, NULL, 0 } }; opt = getopt_long(argc, argv, "v", longopts, NULL); if (opt < 0) break; switch (opt) { case 'v': verbose = 1; break; default: usage(cmd_balance_status_usage); } } if (check_argc_exact(argc - optind, 1)) usage(cmd_balance_status_usage); path = argv[optind]; fd = btrfs_open_dir(path, &dirstream, 1); if (fd < 0) return 2; ret = ioctl(fd, BTRFS_IOC_BALANCE_PROGRESS, &args); e = errno; close_file_or_dir(fd, dirstream); if (ret < 0) { if (e == ENOTCONN) { printf("No balance found on '%s'\n", path); return 0; } error("balance status on '%s' failed: %s", path, strerror(e)); return 2; } if (args.state & BTRFS_BALANCE_STATE_RUNNING) { printf("Balance on '%s' is running", path); if (args.state & BTRFS_BALANCE_STATE_CANCEL_REQ) printf(", cancel requested\n"); else if (args.state & BTRFS_BALANCE_STATE_PAUSE_REQ) printf(", pause requested\n"); else printf("\n"); } else { printf("Balance on '%s' is paused\n", path); } printf("%llu out of about %llu chunks balanced (%llu considered), " "%3.f%% left\n", (unsigned long long)args.stat.completed, (unsigned long long)args.stat.expected, (unsigned long long)args.stat.considered, 100 * (1 - (float)args.stat.completed/args.stat.expected)); if (verbose) dump_ioctl_balance_args(&args); return 1; }
static int dev_replace_handle_sigint(int fd) { struct sigaction sa = { .sa_handler = fd == -1 ? SIG_DFL : dev_replace_sigint_handler }; dev_replace_cancel_fd = fd; return sigaction(SIGINT, &sa, NULL); } static const char *const cmd_replace_start_usage[] = { "btrfs replace start [-Bfr] <srcdev>|<devid> <targetdev> <mount_point>", "Replace device of a btrfs filesystem.", "On a live filesystem, duplicate the data to the target device which", "is currently stored on the source device. If the source device is not", "available anymore, or if the -r option is set, the data is built", "only using the RAID redundancy mechanisms. After completion of the", "operation, the source device is removed from the filesystem.", "If the <srcdev> is a numerical value, it is assumed to be the device id", "of the filesystem which is mounted at <mount_point>, otherwise it is", "the path to the source device. If the source device is disconnected,", "from the system, you have to use the <devid> parameter format.", "The <targetdev> needs to be same size or larger than the <srcdev>.", "", "-r only read from <srcdev> if no other zero-defect mirror exists", " (enable this if your drive has lots of read errors, the access", " would be very slow)", "-f force using and overwriting <targetdev> even if it looks like", " containing a valid btrfs filesystem. A valid filesystem is", " assumed if a btrfs superblock is found which contains a", " correct checksum. Devices which are currently mounted are", " never allowed to be used as the <targetdev>", "-B do not background", NULL }; static int cmd_replace_start(int argc, char **argv) { struct btrfs_ioctl_dev_replace_args start_args = {0}; struct btrfs_ioctl_dev_replace_args status_args = {0}; int ret; int i; int c; int fdmnt = -1; int fddstdev = -1; char *path; char *srcdev; char *dstdev = NULL; int avoid_reading_from_srcdev = 0; int force_using_targetdev = 0; u64 dstdev_block_count; int do_not_background = 0; DIR *dirstream = NULL; u64 srcdev_size; u64 dstdev_size; while ((c = getopt(argc, argv, "Brf")) != -1) { switch (c) { case 'B': do_not_background = 1; break; case 'r': avoid_reading_from_srcdev = 1; break; case 'f': force_using_targetdev = 1; break; case '?': default: usage(cmd_replace_start_usage); } } start_args.start.cont_reading_from_srcdev_mode = avoid_reading_from_srcdev ? BTRFS_IOCTL_DEV_REPLACE_CONT_READING_FROM_SRCDEV_MODE_AVOID : BTRFS_IOCTL_DEV_REPLACE_CONT_READING_FROM_SRCDEV_MODE_ALWAYS; if (check_argc_exact(argc - optind, 3)) usage(cmd_replace_start_usage); path = argv[optind + 2]; fdmnt = open_path_or_dev_mnt(path, &dirstream, 1); if (fdmnt < 0) goto leave_with_error; /* check for possible errors before backgrounding */ status_args.cmd = BTRFS_IOCTL_DEV_REPLACE_CMD_STATUS; status_args.result = BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_RESULT; ret = ioctl(fdmnt, BTRFS_IOC_DEV_REPLACE, &status_args); if (ret < 0) { fprintf(stderr, "ERROR: ioctl(DEV_REPLACE_STATUS) failed on \"%s\": %s", path, strerror(errno)); if (status_args.result != BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_RESULT) fprintf(stderr, ", %s\n", replace_dev_result2string(status_args.result)); else fprintf(stderr, "\n"); goto leave_with_error; } if (status_args.result != BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_ERROR) { error("ioctl(DEV_REPLACE_STATUS) on '%s' returns error: %s", path, replace_dev_result2string(status_args.result)); goto leave_with_error; } if (status_args.status.replace_state == BTRFS_IOCTL_DEV_REPLACE_STATE_STARTED) { error("device replace on '%s' already started", path); goto leave_with_error; } srcdev = argv[optind]; dstdev = canonicalize_path(argv[optind + 1]); if (!dstdev) { error("cannot canonicalize path '%s': %s", argv[optind + 1], strerror(errno)); goto leave_with_error; } if (string_is_numerical(srcdev)) { struct btrfs_ioctl_fs_info_args fi_args; struct btrfs_ioctl_dev_info_args *di_args = NULL; start_args.start.srcdevid = arg_strtou64(srcdev); ret = get_fs_info(path, &fi_args, &di_args); if (ret) { error("failed to get device info: %s", strerror(-ret)); free(di_args); goto leave_with_error; } if (!fi_args.num_devices) { error("no devices found"); free(di_args); goto leave_with_error; } for (i = 0; i < fi_args.num_devices; i++) if (start_args.start.srcdevid == di_args[i].devid) break; srcdev_size = di_args[i].total_bytes; free(di_args); if (i == fi_args.num_devices) { error("'%s' is not a valid devid for filesystem '%s'", srcdev, path); goto leave_with_error; } } else if (is_block_device(srcdev) > 0) { strncpy((char *)start_args.start.srcdev_name, srcdev, BTRFS_DEVICE_PATH_NAME_MAX); start_args.start.srcdevid = 0; srcdev_size = get_partition_size(srcdev); } else { error("source device must be a block device or a devid"); goto leave_with_error; } ret = test_dev_for_mkfs(dstdev, force_using_targetdev); if (ret) goto leave_with_error; dstdev_size = get_partition_size(dstdev); if (srcdev_size > dstdev_size) { error("target device smaller than source device (required %llu bytes)", srcdev_size); goto leave_with_error; } fddstdev = open(dstdev, O_RDWR); if (fddstdev < 0) { error("unable to open %s: %s", dstdev, strerror(errno)); goto leave_with_error; } strncpy((char *)start_args.start.tgtdev_name, dstdev, BTRFS_DEVICE_PATH_NAME_MAX); ret = btrfs_prepare_device(fddstdev, dstdev, &dstdev_block_count, 0, PREP_DEVICE_ZERO_END | PREP_DEVICE_VERBOSE); if (ret) goto leave_with_error; close(fddstdev); fddstdev = -1; free(dstdev); dstdev = NULL; dev_replace_handle_sigint(fdmnt); if (!do_not_background) { if (daemon(0, 0) < 0) { error("backgrounding failed: %s", strerror(errno)); goto leave_with_error; } } start_args.cmd = BTRFS_IOCTL_DEV_REPLACE_CMD_START; start_args.result = BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_RESULT; ret = ioctl(fdmnt, BTRFS_IOC_DEV_REPLACE, &start_args); if (do_not_background) { if (ret < 0) { fprintf(stderr, "ERROR: ioctl(DEV_REPLACE_START) failed on \"%s\": %s", path, strerror(errno)); if (start_args.result != BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_RESULT) fprintf(stderr, ", %s\n", replace_dev_result2string(start_args.result)); else fprintf(stderr, "\n"); if (errno == EOPNOTSUPP) warning("device replace of RAID5/6 not supported with this kernel"); goto leave_with_error; } if (start_args.result != BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_ERROR) { error("ioctl(DEV_REPLACE_START) on '%s' returns error: %s", path, replace_dev_result2string(start_args.result)); goto leave_with_error; } } close_file_or_dir(fdmnt, dirstream); return 0; leave_with_error: if (dstdev) free(dstdev); if (fdmnt != -1) close(fdmnt); if (fddstdev != -1) close(fddstdev); return 1; } static const char *const cmd_replace_status_usage[] = { "btrfs replace status [-1] <mount_point>", "Print status and progress information of a running device replace", "operation", "", "-1 print once instead of print continuously until the replace", " operation finishes (or is canceled)", NULL }; static int cmd_replace_status(int argc, char **argv) { int fd; int c; char *path; int once = 0; int ret; DIR *dirstream = NULL; while ((c = getopt(argc, argv, "1")) != -1) { switch (c) { case '1': once = 1; break; case '?': default: usage(cmd_replace_status_usage); } } if (check_argc_exact(argc - optind, 1)) usage(cmd_replace_status_usage); path = argv[optind]; fd = btrfs_open_dir(path, &dirstream, 1); if (fd < 0) return 1; ret = print_replace_status(fd, path, once); close_file_or_dir(fd, dirstream); return !!ret; }
static int cmd_subvol_sync(int argc, char **argv) { int fd = -1; int i; int ret = 1; DIR *dirstream = NULL; u64 *ids = NULL; int id_count; int sleep_interval = 1; 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) { id_count = enumerate_dead_subvols(fd, &ids); if (id_count < 0) { error("can't enumerate dead subvolumes: %s", strerror(-id_count)); ret = 1; goto out; } if (id_count == 0) { ret = 0; goto out; } } else { ids = (u64*)malloc(id_count * sizeof(u64)); 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 < 0) { 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\n", 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 int do_balance(const char *path, struct btrfs_ioctl_balance_args *args, unsigned flags) { int fd; int ret; int e; DIR *dirstream = NULL; fd = btrfs_open_dir(path, &dirstream, 1); if (fd < 0) return 1; if (!(flags & BALANCE_START_FILTERS) && !(flags & BALANCE_START_NOWARN)) { int delay = 10; printf("WARNING:\n\n"); printf("\tFull balance without filters requested. This operation is very\n"); printf("\tintense and takes potentially very long. It is recommended to\n"); printf("\tuse the balance filters to narrow down the balanced data.\n"); printf("\tUse 'btrfs balance start --full-balance' option to skip this\n"); printf("\twarning. The operation will start in %d seconds.\n", delay); printf("\tUse Ctrl-C to stop it.\n"); while (delay) { printf("%2d", delay--); fflush(stdout); sleep(1); } printf("\nStarting balance without any filters.\n"); } ret = ioctl(fd, BTRFS_IOC_BALANCE_V2, args); e = errno; if (ret < 0) { /* * older kernels don't have the new balance ioctl, try the * old one. But, the old one doesn't know any filters, so * don't fall back if they tried to use the fancy new things */ if (e == ENOTTY && !(flags & BALANCE_START_FILTERS)) { ret = do_balance_v1(fd); if (ret == 0) goto out; e = errno; } if (e == ECANCELED) { if (args->state & BTRFS_BALANCE_STATE_PAUSE_REQ) fprintf(stderr, "balance paused by user\n"); if (args->state & BTRFS_BALANCE_STATE_CANCEL_REQ) fprintf(stderr, "balance canceled by user\n"); ret = 0; } else { error("error during balancing '%s': %s", path, strerror(e)); if (e != EINPROGRESS) fprintf(stderr, "There may be more info in syslog - try dmesg | tail\n"); ret = 1; } } else { printf("Done, had to relocate %llu out of %llu chunks\n", (unsigned long long)args->stat.completed, (unsigned long long)args->stat.considered); ret = 0; } out: close_file_or_dir(fd, dirstream); return ret; }
int cmd_filesystem_usage(int argc, char **argv) { int ret = 0; unsigned unit_mode; int i; int more_than_one = 0; int tabular = 0; unit_mode = get_unit_mode_from_arg(&argc, argv, 1); optind = 1; while (1) { int c; c = getopt(argc, argv, "T"); if (c < 0) break; switch (c) { case 'T': tabular = 1; break; default: usage(cmd_filesystem_usage_usage); } } if (check_argc_min(argc - optind, 1)) usage(cmd_filesystem_usage_usage); for (i = optind; i < argc; i++) { int fd; DIR *dirstream = NULL; struct chunk_info *chunkinfo = NULL; struct device_info *devinfo = NULL; int chunkcount = 0; int devcount = 0; fd = btrfs_open_dir(argv[i], &dirstream, 1); if (fd < 0) { ret = 1; goto out; } if (more_than_one) printf("\n"); ret = load_chunk_and_device_info(fd, &chunkinfo, &chunkcount, &devinfo, &devcount); if (ret) goto cleanup; ret = print_filesystem_usage_overall(fd, chunkinfo, chunkcount, devinfo, devcount, argv[i], unit_mode); if (ret) goto cleanup; printf("\n"); ret = print_filesystem_usage_by_chunk(fd, chunkinfo, chunkcount, devinfo, devcount, argv[i], unit_mode, tabular); cleanup: close_file_or_dir(fd, dirstream); free(chunkinfo); free(devinfo); if (ret) goto out; more_than_one = 1; } out: return !!ret; }
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; struct btrfs_ioctl_vol_args_v2 args; struct btrfs_qgroup_inherit *inherit = NULL; DIR *dirstream1 = NULL, *dirstream2 = NULL; 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_subvol_snapshot_usage); } } if (check_argc_exact(argc - optind, 2)) usage(cmd_subvol_snapshot_usage); subvol = argv[optind]; dst = argv[optind + 1]; retval = 1; /* failure */ res = test_issubvolume(subvol); if (res < 0) { error("cannot access subvolume %s: %s", subvol, strerror(-res)); goto out; } if (!res) { error("not a subvolume: %s", subvol); goto out; } res = test_isdir(dst); if (res < 0 && res != -ENOENT) { error("cannot access %s: %s", dst, strerror(-res)); goto out; } if (res == 0) { error("'%s' exists and it is not a directory", 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)) { error("invalid snapshot name '%s'", newname); goto out; } len = strlen(newname); if (len == 0 || len >= BTRFS_VOL_NAME_MAX) { error("snapshot name too long '%s'", newname); goto out; } fddst = btrfs_open_dir(dstdir, &dirstream1, 1); if (fddst < 0) goto out; fd = btrfs_open_dir(subvol, &dirstream2, 1); if (fd < 0) 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) { error("cannot snapshot '%s': %s", 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_list(int argc, char **argv) { struct btrfs_list_filter_set *filter_set; struct btrfs_list_comparer_set *comparer_set; u64 flags = 0; int fd = -1; u64 top_id; int ret = -1, uerr = 0; char *subvol; int is_tab_result = 0; int is_list_all = 0; int is_only_in_path = 0; DIR *dirstream = NULL; filter_set = btrfs_list_alloc_filter_set(); comparer_set = btrfs_list_alloc_comparer_set(); while(1) { int c; static const struct option long_options[] = { {"sort", required_argument, NULL, 'S'}, {NULL, 0, NULL, 0} }; c = getopt_long(argc, argv, "acdgopqsurRG:C:t", long_options, NULL); if (c < 0) break; switch(c) { case 'p': btrfs_list_setup_print_column(BTRFS_LIST_PARENT); break; case 'a': is_list_all = 1; break; 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; case 'o': is_only_in_path = 1; break; case 't': is_tab_result = 1; break; case 's': btrfs_list_setup_filter(&filter_set, BTRFS_LIST_FILTER_SNAPSHOT_ONLY, 0); btrfs_list_setup_print_column(BTRFS_LIST_OGENERATION); btrfs_list_setup_print_column(BTRFS_LIST_OTIME); break; case 'u': btrfs_list_setup_print_column(BTRFS_LIST_UUID); break; 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; case 'G': btrfs_list_setup_print_column(BTRFS_LIST_GENERATION); ret = btrfs_list_parse_filter_string(optarg, &filter_set, BTRFS_LIST_FILTER_GEN); if (ret) { uerr = 1; goto out; } break; case 'C': btrfs_list_setup_print_column(BTRFS_LIST_OGENERATION); ret = btrfs_list_parse_filter_string(optarg, &filter_set, BTRFS_LIST_FILTER_CGEN); if (ret) { uerr = 1; goto out; } break; case 'S': ret = btrfs_list_parse_sort_string(optarg, &comparer_set); if (ret) { uerr = 1; goto out; } break; default: uerr = 1; goto out; } } 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]; fd = btrfs_open_dir(subvol, &dirstream, 1); if (fd < 0) { ret = -1; error("can't access '%s'", subvol); goto out; } ret = btrfs_list_get_path_rootid(fd, &top_id); if (ret) { error("can't get rootid for '%s'", subvol); goto out; } if (is_list_all) btrfs_list_setup_filter(&filter_set, BTRFS_LIST_FILTER_FULL_PATH, top_id); else if (is_only_in_path) btrfs_list_setup_filter(&filter_set, BTRFS_LIST_FILTER_TOPID_EQUAL, top_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); 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); out: close_file_or_dir(fd, dirstream); if (filter_set) free(filter_set); if (comparer_set) free(comparer_set); if (uerr) usage(cmd_subvol_list_usage); return !!ret; }
static int cmd_subvol_delete(int argc, char **argv) { int res, 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; 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 */ {"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 = 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) { error("cannot access subvolume %s: %s", path, strerror(-res)); ret = 1; goto out; } if (!res) { error("not a subvolume: %s", path); ret = 1; goto out; } cpath = realpath(path, NULL); if (!cpath) { ret = errno; error("cannot find real path for '%s': %s", path, strerror(errno)); goto out; } dupdname = strdup(cpath); dname = dirname(dupdname); dupvname = strdup(cpath); vname = basename(dupvname); free(cpath); fd = btrfs_open_dir(dname, &dirstream, 1); if (fd < 0) { 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); if(res < 0 ){ error("cannot delete '%s/%s': %s", dname, vname, strerror(errno)); ret = 1; goto out; } if (commit_mode == 1) { res = wait_for_commit(fd); if (res < 0) { error("unable to wait for commit after '%s': %s", 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) { error("unable to do final sync after deletion: %s", strerror(errno)); ret = 1; } } close_file_or_dir(fd, dirstream); return ret; }
static int _cmd_qgroup_assign(int assign, int argc, char **argv, const char * const *usage_str) { int ret = 0; int fd; int rescan = 0; char *path; struct btrfs_ioctl_qgroup_assign_args args; DIR *dirstream = NULL; if (assign) { while (1) { enum { GETOPT_VAL_RESCAN = 256, GETOPT_VAL_NO_RESCAN }; static const struct option long_options[] = { { "rescan", no_argument, NULL, GETOPT_VAL_RESCAN }, { "no-rescan", no_argument, NULL, GETOPT_VAL_NO_RESCAN }, { NULL, 0, NULL, 0 } }; int c = getopt_long(argc, argv, "", long_options, NULL); if (c < 0) break; switch (c) { case GETOPT_VAL_RESCAN: rescan = 1; break; case GETOPT_VAL_NO_RESCAN: rescan = 0; break; default: /* Usage printed by the caller */ return -1; } } } else { clean_args_no_options(argc, argv, usage_str); } if (check_argc_exact(argc - optind, 3)) usage(usage_str); memset(&args, 0, sizeof(args)); args.assign = assign; args.src = parse_qgroupid(argv[optind]); args.dst = parse_qgroupid(argv[optind + 1]); path = argv[optind + 2]; /* * FIXME src should accept subvol path */ if (btrfs_qgroup_level(args.src) >= btrfs_qgroup_level(args.dst)) { error("bad relation requested: %s", path); return 1; } fd = btrfs_open_dir(path, &dirstream, 1); if (fd < 0) return 1; ret = ioctl(fd, BTRFS_IOC_QGROUP_ASSIGN, &args); if (ret < 0) { error("unable to assign quota group: %s", strerror(errno)); close_file_or_dir(fd, dirstream); return 1; } /* * If ret > 0, it means assign caused qgroup data inconsistent state. * Schedule a quota rescan if requested. * * The return value change only happens in newer kernel. But will not * cause problem since old kernel has a bug that will never clear * INCONSISTENT bit. */ if (ret > 0) { if (rescan) { struct btrfs_ioctl_quota_rescan_args qargs; printf("Quota data changed, rescan scheduled\n"); memset(&qargs, 0, sizeof(qargs)); ret = ioctl(fd, BTRFS_IOC_QUOTA_RESCAN, &qargs); if (ret < 0) error("quota rescan failed: %s", strerror(errno)); } else { warning("quotas may be inconsistent, rescan needed"); } } close_file_or_dir(fd, dirstream); return ret; }
static int cmd_quota_rescan(int argc, char **argv) { int ret = 0; int fd; int e; char *path = NULL; struct btrfs_ioctl_quota_rescan_args args; unsigned long ioctlnum = BTRFS_IOC_QUOTA_RESCAN; DIR *dirstream = NULL; int wait_for_completion = 0; optind = 1; while (1) { int c = getopt(argc, argv, "sw"); if (c < 0) break; switch (c) { case 's': ioctlnum = BTRFS_IOC_QUOTA_RESCAN_STATUS; break; case 'w': wait_for_completion = 1; break; default: usage(cmd_quota_rescan_usage); } } if (ioctlnum != BTRFS_IOC_QUOTA_RESCAN && wait_for_completion) { fprintf(stderr, "ERROR: -w cannot be used with -s\n"); return 1; } if (check_argc_exact(argc - optind, 1)) usage(cmd_quota_rescan_usage); memset(&args, 0, sizeof(args)); path = argv[optind]; fd = btrfs_open_dir(path, &dirstream, 1); if (fd < 0) return 1; ret = ioctl(fd, ioctlnum, &args); e = errno; if (wait_for_completion && (ret == 0 || e == EINPROGRESS)) { ret = ioctl(fd, BTRFS_IOC_QUOTA_RESCAN_WAIT, &args); e = errno; } close_file_or_dir(fd, dirstream); if (ioctlnum == BTRFS_IOC_QUOTA_RESCAN) { if (ret < 0) { fprintf(stderr, "ERROR: quota rescan failed: " "%s\n", strerror(e)); return 1; } else { printf("quota rescan started\n"); } } else { if (!args.flags) { printf("no rescan operation in progress\n"); } else { printf("rescan operation running (current key %lld)\n", args.progress); } } return 0; }
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; }
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; while (1) { 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) { retval = res; goto out; } break; case 'i': res = qgroup_inherit_add_group(&inherit, optarg); if (res) { retval = res; goto out; } break; default: usage(cmd_subvol_create_usage); } } if (check_argc_exact(argc - optind, 1)) usage(cmd_subvol_create_usage); dst = argv[optind]; 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) { error("target path already exists: %s", dst); goto out; } dupname = strdup(dst); newname = basename(dupname); dupdir = strdup(dst); dstdir = dirname(dupdir); if (!test_issubvolname(newname)) { error("invalid subvolume name: %s", newname); goto out; } len = strlen(newname); if (len == 0 || len >= BTRFS_VOL_NAME_MAX) { error("subvolume name too long: %s", newname); goto out; } fddst = btrfs_open_dir(dstdir, &dirstream, 1); if (fddst < 0) goto out; printf("Create subvolume '%s/%s'\n", dstdir, newname); if (inherit) { struct btrfs_ioctl_vol_args_v2 args; memset(&args, 0, sizeof(args)); strncpy_null(args.name, newname); args.flags |= BTRFS_SUBVOL_QGROUP_INHERIT; args.size = qgroup_inherit_size(inherit); args.qgroup_inherit = inherit; res = ioctl(fddst, BTRFS_IOC_SUBVOL_CREATE_V2, &args); } else { struct btrfs_ioctl_vol_args args; memset(&args, 0, sizeof(args)); strncpy_null(args.name, newname); res = ioctl(fddst, BTRFS_IOC_SUBVOL_CREATE, &args); } if (res < 0) { error("cannot create subvolume: %s", strerror(errno)); goto out; } retval = 0; /* success */ out: close_file_or_dir(fddst, dirstream); free(inherit); free(dupname); free(dupdir); return retval; }