diff --git a/cmds-send.c b/cmds-send.c index 0057e6b8..72804a9a 100644 --- a/cmds-send.c +++ b/cmds-send.c @@ -244,7 +244,8 @@ out: return ERR_PTR(ret); } -static int do_send(struct btrfs_send *send, u64 root_id, u64 parent_root_id) +static int do_send(struct btrfs_send *send, u64 root_id, u64 parent_root_id, + int is_first_subvol, int is_last_subvol) { int ret; pthread_t t_read; @@ -298,11 +299,18 @@ static int do_send(struct btrfs_send *send, u64 root_id, u64 parent_root_id) io_send.clone_sources = (__u64*)send->clone_sources; io_send.clone_sources_count = send->clone_sources_count; io_send.parent_root = parent_root_id; + if (!is_first_subvol) + io_send.flags |= BTRFS_SEND_FLAG_OMIT_STREAM_HEADER; + if (!is_last_subvol) + io_send.flags |= BTRFS_SEND_FLAG_OMIT_END_CMD; ret = ioctl(subvol_fd, BTRFS_IOC_SEND, &io_send); if (ret) { ret = -errno; fprintf(stderr, "ERROR: send ioctl failed with %d: %s\n", ret, strerror(-ret)); + if (ret == -EINVAL && (!is_first_subvol || !is_last_subvol)) + fprintf(stderr, + "Try upgrading your kernel or don't use -e.\n"); goto out; } if (g_verbose > 0) @@ -435,15 +443,19 @@ int cmd_send_start(int argc, char **argv) u64 root_id; u64 parent_root_id = 0; int full_send = 1; + int new_end_cmd_semantic = 0; memset(&send, 0, sizeof(send)); send.dump_fd = fileno(stdout); - while ((c = getopt(argc, argv, "vc:f:i:p:")) != -1) { + while ((c = getopt(argc, argv, "vec:f:i:p:")) != -1) { switch (c) { case 'v': g_verbose++; break; + case 'e': + new_end_cmd_semantic = 1; + break; case 'c': subvol = realpath(optarg, NULL); if (!subvol) { @@ -594,6 +606,9 @@ int cmd_send_start(int argc, char **argv) } for (i = optind; i < argc; i++) { + int is_first_subvol; + int is_last_subvol; + free(subvol); subvol = argv[i]; @@ -634,7 +649,17 @@ int cmd_send_start(int argc, char **argv) goto out; } - ret = do_send(&send, root_id, parent_root_id); + if (new_end_cmd_semantic) { + /* require new kernel */ + is_first_subvol = (i == optind); + is_last_subvol = (i == argc - 1); + } else { + /* be compatible to old and new kernel */ + is_first_subvol = 1; + is_last_subvol = 1; + } + ret = do_send(&send, root_id, parent_root_id, + is_first_subvol, is_last_subvol); if (ret < 0) goto out; @@ -664,7 +689,7 @@ static const char * const send_cmd_group_usage[] = { }; const char * const cmd_send_usage[] = { - "btrfs send [-v] [-p ] [-c ] ", + "btrfs send [-ve] [-p ] [-c ] ", "Send the subvolume to stdout.", "Sends the subvolume specified by to stdout.", "By default, this will send the whole subvolume. To do an incremental", @@ -679,6 +704,8 @@ const char * const cmd_send_usage[] = { "\n", "-v Enable verbose debug output. Each occurrence of", " this option increases the verbose level more.", + "-e If sending multiple subvols at once, use the new", + " format and omit the end-cmd between the subvols.", "-p Send an incremental stream from to", " .", "-c Use this snapshot as a clone source for an ", diff --git a/ioctl.h b/ioctl.h index abe6dd42..f7864100 100644 --- a/ioctl.h +++ b/ioctl.h @@ -373,7 +373,20 @@ struct btrfs_ioctl_received_subvol_args { * search of clone sources doesn't find an extent. UPDATE_EXTENT * commands will be sent instead of WRITE commands. */ -#define BTRFS_SEND_FLAG_NO_FILE_DATA 0x1 +#define BTRFS_SEND_FLAG_NO_FILE_DATA 0x1 + +/* + * Do not add the leading stream header. Used when multiple snapshots + * are sent back to back. + */ +#define BTRFS_SEND_FLAG_OMIT_STREAM_HEADER 0x2 + +/* + * Omit the command at the end of the stream that indicated the end + * of the stream. This option is used when multiple snapshots are + * sent back to back. + */ +#define BTRFS_SEND_FLAG_OMIT_END_CMD 0x4 struct btrfs_ioctl_send_args { __s64 send_fd; /* in */