+ attr->attr = 0;
+ return 0;
+ }
+ attr->len = len-1;
+
+ byte *ptr;
+ int avail = bdirect_read_prepare(b, &ptr);
+ if (avail >= len)
+ {
+ attr->val = ptr;
+ attr->attr = ptr[len-1];
+ bdirect_read_commit(b, ptr + len);
+ return attr->attr;
+ }
+ bb_grow(&buf, --len);
+ breadb(b, buf.ptr, len);
+ attr->val = buf.ptr;
+ attr->len = len;
+ attr->attr = bgetc(b);
+ if (attr->attr < 0)
+ die("Incomplete attribute %c", attr->attr);
+ }
+ return attr->attr;
+}
+
+struct buck2obj_buf *
+buck2obj_alloc(void)
+{
+ struct buck2obj_buf *buf = xmalloc(sizeof(struct buck2obj_buf));
+ bb_init(&buf->bb);
+ buf->lizard = lizard_alloc();
+ return buf;
+}
+
+void
+buck2obj_free(struct buck2obj_buf *buf)
+{
+ lizard_free(buf->lizard);
+ bb_done(&buf->bb);
+ xfree(buf);
+}
+
+static inline byte *
+decode_attributes(byte *ptr, byte *end, struct odes *o, uns can_overwrite)
+{
+ if (can_overwrite >= 2)
+ while (ptr < end)
+ {
+ uns len;
+ GET_UTF8_32(ptr, len);
+ if (!len--)
+ break;
+ byte type = ptr[len];
+
+ ptr[len] = 0;
+ obj_add_attr_ref(o, type, ptr);
+
+ ptr += len + 1;
+ }
+ else
+ while (ptr < end)
+ {
+ uns len;
+ GET_UTF8_32(ptr, len);
+ if (!len--)
+ break;
+ byte type = ptr[len];
+
+ byte *dup = mp_alloc_fast_noalign(o->pool, len+1);
+ memcpy(dup, ptr, len);
+ dup[len] = 0;
+ obj_add_attr_ref(o, type, dup);
+
+ ptr += len + 1;
+ }
+ return ptr;
+}
+
+int
+buck2obj_parse(struct buck2obj_buf *buf, uns buck_type, uns buck_len, struct fastbuf *body, struct odes *o_hdr, uns *body_start, struct odes *o_body)
+{
+ if (buck_type <= BUCKET_TYPE_PLAIN)
+ {
+ if (body_start) // there is no header part
+ *body_start = 0;
+ // ignore empty lines and read until the end of the bucket
+ sh_off_t end = btell(body) + buck_len;
+ byte buf[MAX_ATTR_SIZE];
+ while (btell(body) < end && bgets(body, buf, sizeof(buf)))
+ if (buf[0])
+ obj_add_attr(o_hdr, buf[0], buf+1);
+ ASSERT(btell(body) == end);
+ }
+ else if (buck_type == BUCKET_TYPE_V30)
+ {
+ sh_off_t start = btell(body);
+ sh_off_t end = start + buck_len;
+ byte buf[MAX_ATTR_SIZE];
+ while (btell(body) < end && bgets(body, buf, sizeof(buf)) && buf[0])
+ obj_add_attr(o_hdr, buf[0], buf+1);
+ if (body_start)
+ *body_start = btell(body) - start;
+ else
+ {
+ while (btell(body) < end && bgets(body, buf, sizeof(buf)))
+ if (buf[0])
+ obj_add_attr(o_body, buf[0], buf+1);
+ ASSERT(btell(body) == end);
+ }
+ }
+ else if (buck_type == BUCKET_TYPE_V33 || buck_type == BUCKET_TYPE_V33_LIZARD)
+ {
+ /* Avoid reading the whole bucket if only its header is needed. */
+ if (body_start)
+ {
+ sh_off_t start = btell(body);
+ sh_off_t end = start + buck_len;
+ while (btell(body) < end)