btrfs-progs: print-tree: Remove btrfs_root parameter
[platform/upstream/btrfs-progs.git] / send-stream.c
index 1a498f3..78f2571 100644 (file)
@@ -22,6 +22,7 @@
 #include "send.h"
 #include "send-stream.h"
 #include "crc32c.h"
+#include "utils.h"
 
 struct btrfs_send_stream {
        int fd;
@@ -32,141 +33,172 @@ struct btrfs_send_stream {
        struct btrfs_tlv_header *cmd_attrs[BTRFS_SEND_A_MAX + 1];
        u32 version;
 
+       /*
+        * end of last successful read, equivalent to start of current
+        * malformated part of block
+        */
+       size_t stream_pos;
+
        struct btrfs_send_ops *ops;
        void *user;
 };
 
-static int read_buf(struct btrfs_send_stream *s, void *buf, int len)
+/*
+ * Read len bytes to buf.
+ * Return:
+ *   0 - success
+ * < 0 - negative errno in case of error
+ * > 0 - no data read, EOF
+ */
+static int read_buf(struct btrfs_send_stream *sctx, char *buf, size_t len)
 {
        int ret;
-       int pos = 0;
+       size_t pos = 0;
 
        while (pos < len) {
-               ret = read(s->fd, (char*)buf + pos, len - pos);
-               if (ret < 0) {
+               ssize_t rbytes;
+
+               rbytes = read(sctx->fd, buf + pos, len - pos);
+               if (rbytes < 0) {
                        ret = -errno;
-                       fprintf(stderr, "ERROR: read from stream failed. %s\n",
+                       error("read from stream failed: %s",
                                        strerror(-ret));
                        goto out;
                }
-               if (ret == 0) {
+               if (rbytes == 0) {
                        ret = 1;
-                       goto out;
+                       goto out_eof;
                }
-               pos += ret;
+               pos += rbytes;
        }
-
        ret = 0;
 
+out_eof:
+       if (0 < pos && pos < len) {
+               error("short read from stream: expected %zu read %zu", len, pos);
+               ret = -EIO;
+       } else {
+               sctx->stream_pos += pos;
+       }
+
 out:
        return ret;
 }
 
 /*
  * Reads a single command from kernel space and decodes the TLV's into
- * s->cmd_attrs
+ * sctx->cmd_attrs
+ *
+ * Returns:
+ *   0 - success
+ * < 0 - an error in the command
  */
-static int read_cmd(struct btrfs_send_stream *s)
+static int read_cmd(struct btrfs_send_stream *sctx)
 {
        int ret;
-       int cmd;
-       int cmd_len;
-       int tlv_type;
-       int tlv_len;
+       u16 cmd;
+       u32 cmd_len;
        char *data;
-       int pos;
-       struct btrfs_tlv_header *tlv_hdr;
+       u32 pos;
        u32 crc;
        u32 crc2;
 
-       memset(s->cmd_attrs, 0, sizeof(s->cmd_attrs));
+       memset(sctx->cmd_attrs, 0, sizeof(sctx->cmd_attrs));
 
-       ret = read_buf(s, s->read_buf, sizeof(*s->cmd_hdr));
+       ASSERT(sizeof(*sctx->cmd_hdr) <= sizeof(sctx->read_buf));
+       ret = read_buf(sctx, sctx->read_buf, sizeof(*sctx->cmd_hdr));
        if (ret < 0)
                goto out;
        if (ret) {
                ret = -EINVAL;
-               fprintf(stderr, "ERROR: unexpected EOF in stream.\n");
+               error("unexpected EOF in stream");
                goto out;
        }
 
-       s->cmd_hdr = (struct btrfs_cmd_header *)s->read_buf;
-       cmd = le16_to_cpu(s->cmd_hdr->cmd);
-       cmd_len = le32_to_cpu(s->cmd_hdr->len);
+       sctx->cmd_hdr = (struct btrfs_cmd_header *)sctx->read_buf;
+       cmd = le16_to_cpu(sctx->cmd_hdr->cmd);
+       cmd_len = le32_to_cpu(sctx->cmd_hdr->len);
+
+       if (cmd_len + sizeof(*sctx->cmd_hdr) >= sizeof(sctx->read_buf)) {
+               ret = -EINVAL;
+               error("command length %u too big for buffer %zu",
+                               cmd_len, sizeof(sctx->read_buf));
+               goto out;
+       }
 
-       data = s->read_buf + sizeof(*s->cmd_hdr);
-       ret = read_buf(s, data, cmd_len);
+       data = sctx->read_buf + sizeof(*sctx->cmd_hdr);
+       ret = read_buf(sctx, data, cmd_len);
        if (ret < 0)
                goto out;
        if (ret) {
                ret = -EINVAL;
-               fprintf(stderr, "ERROR: unexpected EOF in stream.\n");
+               error("unexpected EOF in stream");
                goto out;
        }
 
-       crc = le32_to_cpu(s->cmd_hdr->crc);
-       s->cmd_hdr->crc = 0;
+       crc = le32_to_cpu(sctx->cmd_hdr->crc);
+       sctx->cmd_hdr->crc = 0;
 
-       crc2 = crc32c(0, (unsigned char*)s->read_buf,
-                       sizeof(*s->cmd_hdr) + cmd_len);
+       crc2 = crc32c(0, (unsigned char*)sctx->read_buf,
+                       sizeof(*sctx->cmd_hdr) + cmd_len);
 
        if (crc != crc2) {
                ret = -EINVAL;
-               fprintf(stderr, "ERROR: crc32 mismatch in command.\n");
+               error("crc32 mismatch in command");
                goto out;
        }
 
        pos = 0;
        while (pos < cmd_len) {
+               struct btrfs_tlv_header *tlv_hdr;
+               u16 tlv_type;
+               u16 tlv_len;
+
                tlv_hdr = (struct btrfs_tlv_header *)data;
                tlv_type = le16_to_cpu(tlv_hdr->tlv_type);
                tlv_len = le16_to_cpu(tlv_hdr->tlv_len);
 
-               if (tlv_type <= 0 || tlv_type > BTRFS_SEND_A_MAX ||
-                   tlv_len < 0 || tlv_len > BTRFS_SEND_BUF_SIZE) {
-                       fprintf(stderr, "ERROR: invalid tlv in cmd. "
-                                       "tlv_type = %d, tlv_len = %d\n",
+               if (tlv_type == 0 || tlv_type > BTRFS_SEND_A_MAX
+                   || tlv_len > BTRFS_SEND_BUF_SIZE) {
+                       error("invalid tlv in cmd tlv_type = %hu, tlv_len = %hu",
                                        tlv_type, tlv_len);
                        ret = -EINVAL;
                        goto out;
                }
 
-               s->cmd_attrs[tlv_type] = tlv_hdr;
+               sctx->cmd_attrs[tlv_type] = tlv_hdr;
 
                data += sizeof(*tlv_hdr) + tlv_len;
                pos += sizeof(*tlv_hdr) + tlv_len;
        }
 
-       s->cmd = cmd;
+       sctx->cmd = cmd;
        ret = 0;
 
 out:
        return ret;
 }
 
-static int tlv_get(struct btrfs_send_stream *s, int attr, void **data, int *len)
+static int tlv_get(struct btrfs_send_stream *sctx, int attr, void **data, int *len)
 {
        int ret;
-       struct btrfs_tlv_header *h;
+       struct btrfs_tlv_header *hdr;
 
        if (attr <= 0 || attr > BTRFS_SEND_A_MAX) {
-               fprintf(stderr, "ERROR: invalid attribute requested. "
-                               "attr = %d\n",
-                               attr);
+               error("invalid attribute requested, attr = %d", attr);
                ret = -EINVAL;
                goto out;
        }
 
-       h = s->cmd_attrs[attr];
-       if (!h) {
-               fprintf(stderr, "ERROR: attribute %d requested "
-                               "but not present.\n", attr);
+       hdr = sctx->cmd_attrs[attr];
+       if (!hdr) {
+               error("attribute %d requested but not present", attr);
                ret = -ENOENT;
                goto out;
        }
 
-       *len = le16_to_cpu(h->tlv_len);
-       *data = h + 1;
+       *len = le16_to_cpu(hdr->tlv_len);
+       *data = hdr + 1;
 
        ret = 0;
 
@@ -190,8 +222,8 @@ out:
 #define TLV_CHECK_LEN(expected, got) \
        do { \
                if (expected != got) { \
-                       fprintf(stderr, "ERROR: invalid size for attribute. " \
-                                       "expected = %d, got = %d\n", \
+                       error("invalid size for attribute, " \
+                                       "expected = %d, got = %d", \
                                        (int)expected, (int)got); \
                        ret = -EINVAL; \
                        goto tlv_get_failed; \
@@ -204,7 +236,7 @@ out:
                int __len; \
                TLV_GET(s, attr, (void**)&__tmp, &__len); \
                TLV_CHECK_LEN(sizeof(*__tmp), __len); \
-               *v = le##bits##_to_cpu(*__tmp); \
+               *v = get_unaligned_le##bits(__tmp); \
        } while (0)
 
 #define TLV_GET_U8(s, attr, v) TLV_GET_INT(s, attr, 8, v)
@@ -212,13 +244,13 @@ out:
 #define TLV_GET_U32(s, attr, v) TLV_GET_INT(s, attr, 32, v)
 #define TLV_GET_U64(s, attr, v) TLV_GET_INT(s, attr, 64, v)
 
-static int tlv_get_string(struct btrfs_send_stream *s, int attr, char **str)
+static int tlv_get_string(struct btrfs_send_stream *sctx, int attr, char **str)
 {
        int ret;
        void *data;
-       int len;
+       int len = 0;
 
-       TLV_GET(s, attr, &data, &len);
+       TLV_GET(sctx, attr, &data, &len);
 
        *str = malloc(len + 1);
        if (!*str)
@@ -234,14 +266,14 @@ tlv_get_failed:
 #define TLV_GET_STRING(s, attr, str) \
        __TLV_DO_WHILE_GOTO_FAIL(tlv_get_string(s, attr, str))
 
-static int tlv_get_timespec(struct btrfs_send_stream *s,
+static int tlv_get_timespec(struct btrfs_send_stream *sctx,
                            int attr, struct timespec *ts)
 {
        int ret;
        int len;
        struct btrfs_timespec *bts;
 
-       TLV_GET(s, attr, (void**)&bts, &len);
+       TLV_GET(sctx, attr, (void**)&bts, &len);
        TLV_CHECK_LEN(sizeof(*bts), len);
 
        ts->tv_sec = le64_to_cpu(bts->sec);
@@ -254,13 +286,13 @@ tlv_get_failed:
 #define TLV_GET_TIMESPEC(s, attr, ts) \
        __TLV_DO_WHILE_GOTO_FAIL(tlv_get_timespec(s, attr, ts))
 
-static int tlv_get_uuid(struct btrfs_send_stream *s, int attr, u8 *uuid)
+static int tlv_get_uuid(struct btrfs_send_stream *sctx, int attr, u8 *uuid)
 {
        int ret;
        int len;
        void *data;
 
-       TLV_GET(s, attr, &data, &len);
+       TLV_GET(sctx, attr, &data, &len);
        TLV_CHECK_LEN(BTRFS_UUID_SIZE, len);
        memcpy(uuid, data, BTRFS_UUID_SIZE);
 
@@ -272,7 +304,7 @@ tlv_get_failed:
 #define TLV_GET_UUID(s, attr, uuid) \
        __TLV_DO_WHILE_GOTO_FAIL(tlv_get_uuid(s, attr, uuid))
 
-static int read_and_process_cmd(struct btrfs_send_stream *s)
+static int read_and_process_cmd(struct btrfs_send_stream *sctx)
 {
        int ret;
        char *path = NULL;
@@ -297,132 +329,129 @@ static int read_and_process_cmd(struct btrfs_send_stream *s)
        int len;
        int xattr_len;
 
-       ret = read_cmd(s);
+       ret = read_cmd(sctx);
        if (ret)
                goto out;
 
-       switch (s->cmd) {
+       switch (sctx->cmd) {
        case BTRFS_SEND_C_SUBVOL:
-               TLV_GET_STRING(s, BTRFS_SEND_A_PATH, &path);
-               TLV_GET_UUID(s, BTRFS_SEND_A_UUID, uuid);
-               TLV_GET_U64(s, BTRFS_SEND_A_CTRANSID, &ctransid);
-               ret = s->ops->subvol(path, uuid, ctransid, s->user);
+               TLV_GET_STRING(sctx, BTRFS_SEND_A_PATH, &path);
+               TLV_GET_UUID(sctx, BTRFS_SEND_A_UUID, uuid);
+               TLV_GET_U64(sctx, BTRFS_SEND_A_CTRANSID, &ctransid);
+               ret = sctx->ops->subvol(path, uuid, ctransid, sctx->user);
                break;
        case BTRFS_SEND_C_SNAPSHOT:
-               TLV_GET_STRING(s, BTRFS_SEND_A_PATH, &path);
-               TLV_GET_UUID(s, BTRFS_SEND_A_UUID, uuid);
-               TLV_GET_U64(s, BTRFS_SEND_A_CTRANSID, &ctransid);
-               TLV_GET_UUID(s, BTRFS_SEND_A_CLONE_UUID, clone_uuid);
-               TLV_GET_U64(s, BTRFS_SEND_A_CLONE_CTRANSID, &clone_ctransid);
-               ret = s->ops->snapshot(path, uuid, ctransid, clone_uuid,
-                               clone_ctransid, s->user);
+               TLV_GET_STRING(sctx, BTRFS_SEND_A_PATH, &path);
+               TLV_GET_UUID(sctx, BTRFS_SEND_A_UUID, uuid);
+               TLV_GET_U64(sctx, BTRFS_SEND_A_CTRANSID, &ctransid);
+               TLV_GET_UUID(sctx, BTRFS_SEND_A_CLONE_UUID, clone_uuid);
+               TLV_GET_U64(sctx, BTRFS_SEND_A_CLONE_CTRANSID, &clone_ctransid);
+               ret = sctx->ops->snapshot(path, uuid, ctransid, clone_uuid,
+                               clone_ctransid, sctx->user);
                break;
        case BTRFS_SEND_C_MKFILE:
-               TLV_GET_STRING(s, BTRFS_SEND_A_PATH, &path);
-               ret = s->ops->mkfile(path, s->user);
+               TLV_GET_STRING(sctx, BTRFS_SEND_A_PATH, &path);
+               ret = sctx->ops->mkfile(path, sctx->user);
                break;
        case BTRFS_SEND_C_MKDIR:
-               TLV_GET_STRING(s, BTRFS_SEND_A_PATH, &path);
-               ret = s->ops->mkdir(path, s->user);
+               TLV_GET_STRING(sctx, BTRFS_SEND_A_PATH, &path);
+               ret = sctx->ops->mkdir(path, sctx->user);
                break;
        case BTRFS_SEND_C_MKNOD:
-               TLV_GET_STRING(s, BTRFS_SEND_A_PATH, &path);
-               TLV_GET_U64(s, BTRFS_SEND_A_MODE, &mode);
-               TLV_GET_U64(s, BTRFS_SEND_A_RDEV, &dev);
-               ret = s->ops->mknod(path, mode, dev, s->user);
+               TLV_GET_STRING(sctx, BTRFS_SEND_A_PATH, &path);
+               TLV_GET_U64(sctx, BTRFS_SEND_A_MODE, &mode);
+               TLV_GET_U64(sctx, BTRFS_SEND_A_RDEV, &dev);
+               ret = sctx->ops->mknod(path, mode, dev, sctx->user);
                break;
        case BTRFS_SEND_C_MKFIFO:
-               TLV_GET_STRING(s, BTRFS_SEND_A_PATH, &path);
-               ret = s->ops->mkfifo(path, s->user);
+               TLV_GET_STRING(sctx, BTRFS_SEND_A_PATH, &path);
+               ret = sctx->ops->mkfifo(path, sctx->user);
                break;
        case BTRFS_SEND_C_MKSOCK:
-               TLV_GET_STRING(s, BTRFS_SEND_A_PATH, &path);
-               ret = s->ops->mksock(path, s->user);
+               TLV_GET_STRING(sctx, BTRFS_SEND_A_PATH, &path);
+               ret = sctx->ops->mksock(path, sctx->user);
                break;
        case BTRFS_SEND_C_SYMLINK:
-               TLV_GET_STRING(s, BTRFS_SEND_A_PATH, &path);
-               TLV_GET_STRING(s, BTRFS_SEND_A_PATH_LINK, &path_to);
-               ret = s->ops->symlink(path, path_to, s->user);
+               TLV_GET_STRING(sctx, BTRFS_SEND_A_PATH, &path);
+               TLV_GET_STRING(sctx, BTRFS_SEND_A_PATH_LINK, &path_to);
+               ret = sctx->ops->symlink(path, path_to, sctx->user);
                break;
        case BTRFS_SEND_C_RENAME:
-               TLV_GET_STRING(s, BTRFS_SEND_A_PATH, &path);
-               TLV_GET_STRING(s, BTRFS_SEND_A_PATH_TO, &path_to);
-               ret = s->ops->rename(path, path_to, s->user);
+               TLV_GET_STRING(sctx, BTRFS_SEND_A_PATH, &path);
+               TLV_GET_STRING(sctx, BTRFS_SEND_A_PATH_TO, &path_to);
+               ret = sctx->ops->rename(path, path_to, sctx->user);
                break;
        case BTRFS_SEND_C_LINK:
-               TLV_GET_STRING(s, BTRFS_SEND_A_PATH, &path);
-               TLV_GET_STRING(s, BTRFS_SEND_A_PATH_LINK, &path_to);
-               ret = s->ops->link(path, path_to, s->user);
+               TLV_GET_STRING(sctx, BTRFS_SEND_A_PATH, &path);
+               TLV_GET_STRING(sctx, BTRFS_SEND_A_PATH_LINK, &path_to);
+               ret = sctx->ops->link(path, path_to, sctx->user);
                break;
        case BTRFS_SEND_C_UNLINK:
-               TLV_GET_STRING(s, BTRFS_SEND_A_PATH, &path);
-               ret = s->ops->unlink(path, s->user);
+               TLV_GET_STRING(sctx, BTRFS_SEND_A_PATH, &path);
+               ret = sctx->ops->unlink(path, sctx->user);
                break;
        case BTRFS_SEND_C_RMDIR:
-               TLV_GET_STRING(s, BTRFS_SEND_A_PATH, &path);
-               ret = s->ops->rmdir(path, s->user);
+               TLV_GET_STRING(sctx, BTRFS_SEND_A_PATH, &path);
+               ret = sctx->ops->rmdir(path, sctx->user);
                break;
        case BTRFS_SEND_C_WRITE:
-               TLV_GET_STRING(s, BTRFS_SEND_A_PATH, &path);
-               TLV_GET_U64(s, BTRFS_SEND_A_FILE_OFFSET, &offset);
-               TLV_GET(s, BTRFS_SEND_A_DATA, &data, &len);
-               ret = s->ops->write(path, data, offset, len, s->user);
+               TLV_GET_STRING(sctx, BTRFS_SEND_A_PATH, &path);
+               TLV_GET_U64(sctx, BTRFS_SEND_A_FILE_OFFSET, &offset);
+               TLV_GET(sctx, BTRFS_SEND_A_DATA, &data, &len);
+               ret = sctx->ops->write(path, data, offset, len, sctx->user);
                break;
        case BTRFS_SEND_C_CLONE:
-               TLV_GET_STRING(s, BTRFS_SEND_A_PATH, &path);
-               TLV_GET_U64(s, BTRFS_SEND_A_FILE_OFFSET, &offset);
-               TLV_GET_U64(s, BTRFS_SEND_A_CLONE_LEN, &len);
-               TLV_GET_UUID(s, BTRFS_SEND_A_CLONE_UUID, clone_uuid);
-               TLV_GET_U64(s, BTRFS_SEND_A_CLONE_CTRANSID, &clone_ctransid);
-               TLV_GET_STRING(s, BTRFS_SEND_A_CLONE_PATH, &clone_path);
-               TLV_GET_U64(s, BTRFS_SEND_A_CLONE_OFFSET, &clone_offset);
-               ret = s->ops->clone(path, offset, len, clone_uuid,
+               TLV_GET_STRING(sctx, BTRFS_SEND_A_PATH, &path);
+               TLV_GET_U64(sctx, BTRFS_SEND_A_FILE_OFFSET, &offset);
+               TLV_GET_U64(sctx, BTRFS_SEND_A_CLONE_LEN, &len);
+               TLV_GET_UUID(sctx, BTRFS_SEND_A_CLONE_UUID, clone_uuid);
+               TLV_GET_U64(sctx, BTRFS_SEND_A_CLONE_CTRANSID, &clone_ctransid);
+               TLV_GET_STRING(sctx, BTRFS_SEND_A_CLONE_PATH, &clone_path);
+               TLV_GET_U64(sctx, BTRFS_SEND_A_CLONE_OFFSET, &clone_offset);
+               ret = sctx->ops->clone(path, offset, len, clone_uuid,
                                clone_ctransid, clone_path, clone_offset,
-                               s->user);
+                               sctx->user);
                break;
        case BTRFS_SEND_C_SET_XATTR:
-               TLV_GET_STRING(s, BTRFS_SEND_A_PATH, &path);
-               TLV_GET_STRING(s, BTRFS_SEND_A_XATTR_NAME, &xattr_name);
-               TLV_GET(s, BTRFS_SEND_A_XATTR_DATA, &xattr_data, &xattr_len);
-               ret = s->ops->set_xattr(path, xattr_name, xattr_data,
-                               xattr_len, s->user);
+               TLV_GET_STRING(sctx, BTRFS_SEND_A_PATH, &path);
+               TLV_GET_STRING(sctx, BTRFS_SEND_A_XATTR_NAME, &xattr_name);
+               TLV_GET(sctx, BTRFS_SEND_A_XATTR_DATA, &xattr_data, &xattr_len);
+               ret = sctx->ops->set_xattr(path, xattr_name, xattr_data,
+                               xattr_len, sctx->user);
                break;
        case BTRFS_SEND_C_REMOVE_XATTR:
-               TLV_GET_STRING(s, BTRFS_SEND_A_PATH, &path);
-               TLV_GET_STRING(s, BTRFS_SEND_A_XATTR_NAME, &xattr_name);
-               ret = s->ops->remove_xattr(path, xattr_name, s->user);
+               TLV_GET_STRING(sctx, BTRFS_SEND_A_PATH, &path);
+               TLV_GET_STRING(sctx, BTRFS_SEND_A_XATTR_NAME, &xattr_name);
+               ret = sctx->ops->remove_xattr(path, xattr_name, sctx->user);
                break;
        case BTRFS_SEND_C_TRUNCATE:
-               TLV_GET_STRING(s, BTRFS_SEND_A_PATH, &path);
-               TLV_GET_U64(s, BTRFS_SEND_A_SIZE, &tmp);
-               ret = s->ops->truncate(path, tmp, s->user);
+               TLV_GET_STRING(sctx, BTRFS_SEND_A_PATH, &path);
+               TLV_GET_U64(sctx, BTRFS_SEND_A_SIZE, &tmp);
+               ret = sctx->ops->truncate(path, tmp, sctx->user);
                break;
        case BTRFS_SEND_C_CHMOD:
-               TLV_GET_STRING(s, BTRFS_SEND_A_PATH, &path);
-               TLV_GET_U64(s, BTRFS_SEND_A_MODE, &tmp);
-               ret = s->ops->chmod(path, tmp, s->user);
+               TLV_GET_STRING(sctx, BTRFS_SEND_A_PATH, &path);
+               TLV_GET_U64(sctx, BTRFS_SEND_A_MODE, &tmp);
+               ret = sctx->ops->chmod(path, tmp, sctx->user);
                break;
        case BTRFS_SEND_C_CHOWN:
-               TLV_GET_STRING(s, BTRFS_SEND_A_PATH, &path);
-               TLV_GET_U64(s, BTRFS_SEND_A_UID, &tmp);
-               TLV_GET_U64(s, BTRFS_SEND_A_GID, &tmp2);
-               ret = s->ops->chown(path, tmp, tmp2, s->user);
+               TLV_GET_STRING(sctx, BTRFS_SEND_A_PATH, &path);
+               TLV_GET_U64(sctx, BTRFS_SEND_A_UID, &tmp);
+               TLV_GET_U64(sctx, BTRFS_SEND_A_GID, &tmp2);
+               ret = sctx->ops->chown(path, tmp, tmp2, sctx->user);
                break;
        case BTRFS_SEND_C_UTIMES:
-               TLV_GET_STRING(s, BTRFS_SEND_A_PATH, &path);
-               if (strstr(path, ".bak_1.log")) {
-                       ret = 0;
-               }
-               TLV_GET_TIMESPEC(s, BTRFS_SEND_A_ATIME, &at);
-               TLV_GET_TIMESPEC(s, BTRFS_SEND_A_MTIME, &mt);
-               TLV_GET_TIMESPEC(s, BTRFS_SEND_A_CTIME, &ct);
-               ret = s->ops->utimes(path, &at, &mt, &ct, s->user);
+               TLV_GET_STRING(sctx, BTRFS_SEND_A_PATH, &path);
+               TLV_GET_TIMESPEC(sctx, BTRFS_SEND_A_ATIME, &at);
+               TLV_GET_TIMESPEC(sctx, BTRFS_SEND_A_MTIME, &mt);
+               TLV_GET_TIMESPEC(sctx, BTRFS_SEND_A_CTIME, &ct);
+               ret = sctx->ops->utimes(path, &at, &mt, &ct, sctx->user);
                break;
        case BTRFS_SEND_C_UPDATE_EXTENT:
-               TLV_GET_STRING(s, BTRFS_SEND_A_PATH, &path);
-               TLV_GET_U64(s, BTRFS_SEND_A_FILE_OFFSET, &offset);
-               TLV_GET_U64(s, BTRFS_SEND_A_SIZE, &tmp);
-               ret = s->ops->update_extent(path, offset, tmp, s->user);
+               TLV_GET_STRING(sctx, BTRFS_SEND_A_PATH, &path);
+               TLV_GET_U64(sctx, BTRFS_SEND_A_FILE_OFFSET, &offset);
+               TLV_GET_U64(sctx, BTRFS_SEND_A_SIZE, &tmp);
+               ret = sctx->ops->update_extent(path, offset, tmp, sctx->user);
                break;
        case BTRFS_SEND_C_END:
                ret = 1;
@@ -438,45 +467,57 @@ out:
        return ret;
 }
 
+/*
+ * If max_errors is 0, then don't stop processing the stream if one of the
+ * callbacks in btrfs_send_ops structure returns an error. If greater than
+ * zero, stop after max_errors errors happened.
+ */
 int btrfs_read_and_process_send_stream(int fd,
                                       struct btrfs_send_ops *ops, void *user,
-                                      int honor_end_cmd)
+                                      int honor_end_cmd,
+                                      u64 max_errors)
 {
        int ret;
-       struct btrfs_send_stream s;
+       struct btrfs_send_stream sctx;
        struct btrfs_stream_header hdr;
+       u64 errors = 0;
+       int last_err = 0;
 
-       s.fd = fd;
-       s.ops = ops;
-       s.user = user;
+       sctx.fd = fd;
+       sctx.ops = ops;
+       sctx.user = user;
+       sctx.stream_pos = 0;
 
-       ret = read_buf(&s&hdr, sizeof(hdr));
+       ret = read_buf(&sctx, (char*)&hdr, sizeof(hdr));
        if (ret < 0)
                goto out;
        if (ret) {
-               ret = 1;
+               ret = -ENODATA;
                goto out;
        }
 
        if (strcmp(hdr.magic, BTRFS_SEND_STREAM_MAGIC)) {
                ret = -EINVAL;
-               fprintf(stderr, "ERROR: Unexpected header\n");
+               error("unexpected header");
                goto out;
        }
 
-       s.version = le32_to_cpu(hdr.version);
-       if (s.version > BTRFS_SEND_STREAM_VERSION) {
+       sctx.version = le32_to_cpu(hdr.version);
+       if (sctx.version > BTRFS_SEND_STREAM_VERSION) {
                ret = -EINVAL;
-               fprintf(stderr, "ERROR: Stream version %d not supported. "
-                               "Please upgrade btrfs-progs\n", s.version);
+               error("stream version %d not supported, please use newer version",
+                               sctx.version);
                goto out;
        }
 
        while (1) {
-               ret = read_and_process_cmd(&s);
-               if (ret < 0)
-                       goto out;
-               if (ret) {
+               ret = read_and_process_cmd(&sctx);
+               if (ret < 0) {
+                       last_err = ret;
+                       errors++;
+                       if (max_errors > 0 && errors >= max_errors)
+                               goto out;
+               } else if (ret > 0) {
                        if (!honor_end_cmd)
                                ret = 0;
                        goto out;
@@ -484,5 +525,8 @@ int btrfs_read_and_process_send_stream(int fd,
        }
 
 out:
+       if (last_err && !ret)
+               ret = last_err;
+
        return ret;
 }