far-rcv: add far-rcv lib

Signed-off-by: Stefan Behrens <sbehrens@giantdisaster.de>
diff --git a/far-rcv/Makefile b/far-rcv/Makefile
new file mode 100644
index 0000000..d3bb494
--- /dev/null
+++ b/far-rcv/Makefile
@@ -0,0 +1,38 @@
+OS = $(shell uname -s)
+RELEASE = $(shell uname -r)
+
+ifeq "$(OS)" "Linux"
+CFLAGS = -D__LINUX__
+CFLAGS += -DHAVE_NTOHLL
+CFLAGS += -DHAVE_UTIMENSAT
+else
+ifeq "$(OS)" "SunOS"
+CFLAGS = -D__SOLARIS__
+ifeq "$(RELEASE)" "5.11"
+CFLAGS += -DHAVE_NTOHLL
+CFLAGS += -DHAVE_UTIMENSAT
+endif
+endif
+endif
+
+CC = gcc
+CFLAGS += -O2 -g -Wall -Werror
+DEPFLAGS = -Wp,-MMD,$(@D)/.$(@F).d,-MT,$@
+LDFLAGS = $(LIBS)
+AR = ar
+ARFLAGS = rc
+OBJECTS = far-rcv.o far-crc32c.o far-endian.o far-xattr.o
+TARGET_LIB = far-rcv.a
+
+all: $(TARGET_LIB)
+
+$(TARGET_LIB): $(OBJECTS)
+	$(AR) $(ARFLAGS) $@ $(OBJECTS)
+
+.c.o:
+	$(CC) $(DEPFLAGS) $(CFLAGS) -c $<
+
+clean:
+	-rm -rf $(TARGET_LIB) $(OBJECTS) .*.d
+
+-include .*.d
diff --git a/far-rcv/far-crc32c.c b/far-rcv/far-crc32c.c
new file mode 100644
index 0000000..abd9c8a
--- /dev/null
+++ b/far-rcv/far-crc32c.c
@@ -0,0 +1,145 @@
+/*
+ * This file is derived from work of Ross N. Williams, which is described in
+ * the document "A Painless Guide to CRC Error Detection Algorithms".
+ * The document can be found here:
+ * <http://www.ross.net/crc/download/crc_v3.txt>
+ *
+ * It contains the following copyright and status information:
+ * ----- quote start -----
+ * Version : 3.
+ * Date    : 19 August 1993.
+ * Author  : Ross N. Williams.
+ * Net     : ross@guest.adelaide.edu.au.
+ * FTP     : ftp.adelaide.edu.au/pub/rocksoft/crc_v3.txt
+ * Company : Rocksoft^tm Pty Ltd.
+ * Snail   : 16 Lerwick Avenue, Hazelwood Park 5066, Australia.
+ * Fax     : +61 8 373-4911 (c/- Internode Systems Pty Ltd).
+ * Phone   : +61 8 379-9217 (10am to 10pm Adelaide Australia time).
+ * Note    : "Rocksoft" is a trademark of Rocksoft Pty Ltd, Australia.
+ * Status  : Copyright (C) Ross Williams, 1993. However, permission is
+ *           granted to make and distribute verbatim copies of this
+ *           document provided that this information block and copyright
+ *           notice is included. Also, the C code modules included
+ *           in this document are fully public domain.
+ * Thanks  : Thanks to Jean-loup Gailly (jloup@chorus.fr) and Mark Adler
+ *           (me@quest.jpl.nasa.gov) who both proof read this document
+ *           and picked out lots of nits as well as some big fat bugs.
+ * ----- quote end -----
+ *
+ * The CRC lookup table is generated by using code from the aforementioned
+ * document of Ross N. Williams. The function crc32c_calc() is a slightly
+ * modified copy of the function crc_reflected() from the mentioned document.
+ * The modifications and the modified code are fully public domain.
+ */
+
+/*
+ * This file contains a function to calculate the CRC-32C.
+ * These functions are used internally in the far-rcv library.
+ */
+
+#include <stdint.h>
+#include <stdlib.h>
+
+#define INIT_REFLECTED 0UL
+#define XOROT 0UL
+
+
+/*****************************************************************/
+/*                                                               */
+/* CRC LOOKUP TABLE                                              */
+/* ================                                              */
+/* The following CRC lookup table was generated automagically    */
+/* by the Rocksoft^tm Model CRC Algorithm Table Generation       */
+/* Program V1.0 using the following model parameters:            */
+/*                                                               */
+/*    Width   : 4 bytes.                                         */
+/*    Poly    : 0x1EDC6F41L                                      */
+/*    Reverse : TRUE.                                            */
+/*                                                               */
+/* For more information on the Rocksoft^tm Model CRC Algorithm,  */
+/* see the document titled "A Painless Guide to CRC Error        */
+/* Detection Algorithms" by Ross Williams                        */
+/* (ross@guest.adelaide.edu.au.). This document is likely to be  */
+/* in the FTP archive "ftp.adelaide.edu.au/pub/rocksoft".        */
+/*                                                               */
+/*****************************************************************/
+
+static uint32_t crctable[256] = {
+	0x00000000L, 0xF26B8303L, 0xE13B70F7L, 0x1350F3F4L,
+	0xC79A971FL, 0x35F1141CL, 0x26A1E7E8L, 0xD4CA64EBL,
+	0x8AD958CFL, 0x78B2DBCCL, 0x6BE22838L, 0x9989AB3BL,
+	0x4D43CFD0L, 0xBF284CD3L, 0xAC78BF27L, 0x5E133C24L,
+	0x105EC76FL, 0xE235446CL, 0xF165B798L, 0x030E349BL,
+	0xD7C45070L, 0x25AFD373L, 0x36FF2087L, 0xC494A384L,
+	0x9A879FA0L, 0x68EC1CA3L, 0x7BBCEF57L, 0x89D76C54L,
+	0x5D1D08BFL, 0xAF768BBCL, 0xBC267848L, 0x4E4DFB4BL,
+	0x20BD8EDEL, 0xD2D60DDDL, 0xC186FE29L, 0x33ED7D2AL,
+	0xE72719C1L, 0x154C9AC2L, 0x061C6936L, 0xF477EA35L,
+	0xAA64D611L, 0x580F5512L, 0x4B5FA6E6L, 0xB93425E5L,
+	0x6DFE410EL, 0x9F95C20DL, 0x8CC531F9L, 0x7EAEB2FAL,
+	0x30E349B1L, 0xC288CAB2L, 0xD1D83946L, 0x23B3BA45L,
+	0xF779DEAEL, 0x05125DADL, 0x1642AE59L, 0xE4292D5AL,
+	0xBA3A117EL, 0x4851927DL, 0x5B016189L, 0xA96AE28AL,
+	0x7DA08661L, 0x8FCB0562L, 0x9C9BF696L, 0x6EF07595L,
+	0x417B1DBCL, 0xB3109EBFL, 0xA0406D4BL, 0x522BEE48L,
+	0x86E18AA3L, 0x748A09A0L, 0x67DAFA54L, 0x95B17957L,
+	0xCBA24573L, 0x39C9C670L, 0x2A993584L, 0xD8F2B687L,
+	0x0C38D26CL, 0xFE53516FL, 0xED03A29BL, 0x1F682198L,
+	0x5125DAD3L, 0xA34E59D0L, 0xB01EAA24L, 0x42752927L,
+	0x96BF4DCCL, 0x64D4CECFL, 0x77843D3BL, 0x85EFBE38L,
+	0xDBFC821CL, 0x2997011FL, 0x3AC7F2EBL, 0xC8AC71E8L,
+	0x1C661503L, 0xEE0D9600L, 0xFD5D65F4L, 0x0F36E6F7L,
+	0x61C69362L, 0x93AD1061L, 0x80FDE395L, 0x72966096L,
+	0xA65C047DL, 0x5437877EL, 0x4767748AL, 0xB50CF789L,
+	0xEB1FCBADL, 0x197448AEL, 0x0A24BB5AL, 0xF84F3859L,
+	0x2C855CB2L, 0xDEEEDFB1L, 0xCDBE2C45L, 0x3FD5AF46L,
+	0x7198540DL, 0x83F3D70EL, 0x90A324FAL, 0x62C8A7F9L,
+	0xB602C312L, 0x44694011L, 0x5739B3E5L, 0xA55230E6L,
+	0xFB410CC2L, 0x092A8FC1L, 0x1A7A7C35L, 0xE811FF36L,
+	0x3CDB9BDDL, 0xCEB018DEL, 0xDDE0EB2AL, 0x2F8B6829L,
+	0x82F63B78L, 0x709DB87BL, 0x63CD4B8FL, 0x91A6C88CL,
+	0x456CAC67L, 0xB7072F64L, 0xA457DC90L, 0x563C5F93L,
+	0x082F63B7L, 0xFA44E0B4L, 0xE9141340L, 0x1B7F9043L,
+	0xCFB5F4A8L, 0x3DDE77ABL, 0x2E8E845FL, 0xDCE5075CL,
+	0x92A8FC17L, 0x60C37F14L, 0x73938CE0L, 0x81F80FE3L,
+	0x55326B08L, 0xA759E80BL, 0xB4091BFFL, 0x466298FCL,
+	0x1871A4D8L, 0xEA1A27DBL, 0xF94AD42FL, 0x0B21572CL,
+	0xDFEB33C7L, 0x2D80B0C4L, 0x3ED04330L, 0xCCBBC033L,
+	0xA24BB5A6L, 0x502036A5L, 0x4370C551L, 0xB11B4652L,
+	0x65D122B9L, 0x97BAA1BAL, 0x84EA524EL, 0x7681D14DL,
+	0x2892ED69L, 0xDAF96E6AL, 0xC9A99D9EL, 0x3BC21E9DL,
+	0xEF087A76L, 0x1D63F975L, 0x0E330A81L, 0xFC588982L,
+	0xB21572C9L, 0x407EF1CAL, 0x532E023EL, 0xA145813DL,
+	0x758FE5D6L, 0x87E466D5L, 0x94B49521L, 0x66DF1622L,
+	0x38CC2A06L, 0xCAA7A905L, 0xD9F75AF1L, 0x2B9CD9F2L,
+	0xFF56BD19L, 0x0D3D3E1AL, 0x1E6DCDEEL, 0xEC064EEDL,
+	0xC38D26C4L, 0x31E6A5C7L, 0x22B65633L, 0xD0DDD530L,
+	0x0417B1DBL, 0xF67C32D8L, 0xE52CC12CL, 0x1747422FL,
+	0x49547E0BL, 0xBB3FFD08L, 0xA86F0EFCL, 0x5A048DFFL,
+	0x8ECEE914L, 0x7CA56A17L, 0x6FF599E3L, 0x9D9E1AE0L,
+	0xD3D3E1ABL, 0x21B862A8L, 0x32E8915CL, 0xC083125FL,
+	0x144976B4L, 0xE622F5B7L, 0xF5720643L, 0x07198540L,
+	0x590AB964L, 0xAB613A67L, 0xB831C993L, 0x4A5A4A90L,
+	0x9E902E7BL, 0x6CFBAD78L, 0x7FAB5E8CL, 0x8DC0DD8FL,
+	0xE330A81AL, 0x115B2B19L, 0x020BD8EDL, 0xF0605BEEL,
+	0x24AA3F05L, 0xD6C1BC06L, 0xC5914FF2L, 0x37FACCF1L,
+	0x69E9F0D5L, 0x9B8273D6L, 0x88D28022L, 0x7AB90321L,
+	0xAE7367CAL, 0x5C18E4C9L, 0x4F48173DL, 0xBD23943EL,
+	0xF36E6F75L, 0x0105EC76L, 0x12551F82L, 0xE03E9C81L,
+	0x34F4F86AL, 0xC69F7B69L, 0xD5CF889DL, 0x27A40B9EL,
+	0x79B737BAL, 0x8BDCB4B9L, 0x988C474DL, 0x6AE7C44EL,
+	0xBE2DA0A5L, 0x4C4623A6L, 0x5F16D052L, 0xAD7D5351L
+};
+
+/*****************************************************************/
+/*                   End of CRC Lookup Table                     */
+/*****************************************************************/
+
+uint32_t far_crc32c_calc(uint32_t crc, const unsigned char *blk_adr,
+			 size_t blk_len)
+{
+	while (blk_len--)
+		crc = crctable[(crc ^ *blk_adr++) & 0xFFL] ^ (crc >> 8);
+
+	return crc ^ XOROT;
+}
diff --git a/far-rcv/far-crc32c.h b/far-rcv/far-crc32c.h
new file mode 100644
index 0000000..8279523
--- /dev/null
+++ b/far-rcv/far-crc32c.h
@@ -0,0 +1,47 @@
+/*
+ * This file is derived from work of Ross N. Williams, which is described in
+ * the document "A Painless Guide to CRC Error Detection Algorithms".
+ * The document can be found here:
+ * <http://www.ross.net/crc/download/crc_v3.txt>
+ *
+ * It contains the following copyright and status information:
+ * ----- quote start -----
+ * Version : 3.
+ * Date    : 19 August 1993.
+ * Author  : Ross N. Williams.
+ * Net     : ross@guest.adelaide.edu.au.
+ * FTP     : ftp.adelaide.edu.au/pub/rocksoft/crc_v3.txt
+ * Company : Rocksoft^tm Pty Ltd.
+ * Snail   : 16 Lerwick Avenue, Hazelwood Park 5066, Australia.
+ * Fax     : +61 8 373-4911 (c/- Internode Systems Pty Ltd).
+ * Phone   : +61 8 379-9217 (10am to 10pm Adelaide Australia time).
+ * Note    : "Rocksoft" is a trademark of Rocksoft Pty Ltd, Australia.
+ * Status  : Copyright (C) Ross Williams, 1993. However, permission is
+ *           granted to make and distribute verbatim copies of this
+ *           document provided that this information block and copyright
+ *           notice is included. Also, the C code modules included
+ *           in this document are fully public domain.
+ * Thanks  : Thanks to Jean-loup Gailly (jloup@chorus.fr) and Mark Adler
+ *           (me@quest.jpl.nasa.gov) who both proof read this document
+ *           and picked out lots of nits as well as some big fat bugs.
+ * ----- quote end -----
+ *
+ * The function crc32c_calc() is a slightly modified copy of the function
+ * crc_reflected() from the mentioned document.
+ * The modifications and the modified code are fully public domain.
+ */
+
+/*
+ * This file contains a function to calculate the CRC-32C.
+ * These functions are used internally in the far-rcv library.
+ */
+
+#if !defined (__FAR_RCV_CRC32C_H)
+#define __FAR_RCV_CRC32C_H
+
+#include <stdint.h>
+
+uint32_t far_crc32c_calc(uint32_t crc, const unsigned char *blk_adr,
+			 size_t blk_len);
+
+#endif /* !defined (__FAR_RCV_CRC32C_H) */
diff --git a/far-rcv/far-endian.c b/far-rcv/far-endian.c
new file mode 100644
index 0000000..a53ffad
--- /dev/null
+++ b/far-rcv/far-endian.c
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2012, 2013 STRATO AG.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+ * Some portable functions to convert little endian numbers to the
+ * host byte order.
+ * It was not a goal to make these functions fast.
+ * These functions are used internally in the far-rcv library.
+ */
+
+#include <sys/types.h>
+#include <netinet/in.h>
+#include <inttypes.h>
+#include "far-endian.h"
+
+
+#if !defined (__LINUX__)
+static uint16_t far_rcv_le16tobe(uint16_t val);
+static uint32_t far_rcv_le32tobe(uint32_t val);
+static uint64_t far_rcv_le64tobe(uint64_t val);
+
+
+uint16_t far_rcv_le16toh(uint16_t val)
+{
+	return ntohs(far_rcv_le16tobe(val));
+}
+
+uint32_t far_rcv_le32toh(uint32_t val)
+{
+	return ntohl(far_rcv_le32tobe(val));
+}
+
+uint64_t far_rcv_le64toh(uint64_t val)
+{
+#if defined (HAVE_NTOHLL)
+	return ntohll(far_rcv_le64tobe(val));
+#else /* !defined (HAVE_NTOHLL) */
+	if (ntohs(0xbabe) != 0xbabe)
+		return val;
+	else
+		return far_rcv_le64tobe(val);
+#endif /* !defined (HAVE_NTOHLL) */
+}
+
+static uint16_t far_rcv_le16tobe(uint16_t val)
+{
+	return ((uint16_t)(val << 8)) | (val >> 8);
+}
+
+static uint32_t far_rcv_le32tobe(uint32_t val)
+{
+	uint16_t lower = (uint16_t)val;
+	uint16_t upper = (uint16_t)(val >> 16);
+
+	return (((uint32_t)far_rcv_le16tobe(lower)) << 16) |
+	       (uint32_t)far_rcv_le16tobe(upper);
+}
+
+static uint64_t far_rcv_le64tobe(uint64_t val)
+{
+	uint32_t lower = (uint32_t)val;
+	uint32_t upper = (uint32_t)(val >> 32);
+
+	return (((uint64_t)far_rcv_le32tobe(lower)) << 32) |
+	       (uint64_t)far_rcv_le32tobe(upper);
+}
+#endif /* !defined (__LINUX__) */
diff --git a/far-rcv/far-endian.h b/far-rcv/far-endian.h
new file mode 100644
index 0000000..611d9cf
--- /dev/null
+++ b/far-rcv/far-endian.h
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2012, 2013 STRATO AG.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+ * Some portable functions to convert little endian numbers to the
+ * host byte order.
+ * It was not a goal to make these functions fast.
+ * These functions are used internally in the far-rcv library.
+ */
+
+#if !defined (__FAR_RCV_ENDIAN_H)
+#define __FAR_RCV_ENDIAN_H
+
+#include <stdint.h>
+
+
+#if defined (__LINUX__)
+#include <endian.h>
+
+#define far_rcv_le16toh le16toh
+#define far_rcv_le32toh le32toh
+#define far_rcv_le64toh le64toh
+
+#else /* !defined (__LINUX__) */
+
+uint16_t far_rcv_le16toh(uint16_t val);
+uint32_t far_rcv_le32toh(uint32_t val);
+uint64_t far_rcv_le64toh(uint64_t val);
+#endif /* !defined (__LINUX__) */
+
+#endif /* !defined (__FAR_RCV_ENDIAN_H) */
diff --git a/far-rcv/far-rcv.c b/far-rcv/far-rcv.c
new file mode 100644
index 0000000..6eccc9e
--- /dev/null
+++ b/far-rcv/far-rcv.c
@@ -0,0 +1,1303 @@
+/*
+ * Copyright (C) 2012 Alexander Block.
+ * Copyright (C) 2012, 2013 STRATO AG.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+ * This is the main part of the far-rcv library. All exported functions
+ * are located in this file.
+ */
+
+#define _BSD_SOURCE
+#define _GNU_SOURCE
+#define _ATFILE_SOURCE
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <assert.h>
+#include <stdint.h>
+#include <inttypes.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <netinet/in.h>
+#if !defined (HAVE_UTIMENSAT)
+#include <utime.h>
+#endif /* !defined (HAVE_UTIMENSAT) */
+
+#include "far-rcv.h"
+#include "far-crc32c.h"
+#include "far-endian.h"
+#include "far-xattr.h"
+
+
+#if !defined (O_NOATIME)
+#define O_NOATIME 0
+#endif
+
+
+#define __TLV_GOTO_FAIL(expr) \
+	if ((ret = expr) < 0) \
+		goto tlv_get_failed;
+
+#define __TLV_DO_WHILE_GOTO_FAIL(expr) \
+	do { \
+		__TLV_GOTO_FAIL(expr) \
+	} while (0)
+
+
+#define TLV_GET(s, attr, data, len) \
+	__TLV_DO_WHILE_GOTO_FAIL(tlv_get(s, attr, data, len))
+
+#define TLV_CHECK_LEN(expected, got) \
+	do { \
+		if (expected != got) { \
+			fprintf(stderr, \
+				"ERROR: invalid size for attribute. expected = %d, got = %d\n", \
+				(int)expected, (int)got); \
+			ret = -EINVAL; \
+			goto tlv_get_failed; \
+		} \
+	} while (0)
+
+#define TLV_GET_INT(s, attr, bits, v) \
+	do { \
+		uint##bits##_t __tmp; \
+		void *__ptr; \
+		int __len; \
+		TLV_GET(s, attr, &__ptr, &__len); \
+		TLV_CHECK_LEN(sizeof(__tmp), __len); \
+		memcpy(&__tmp, __ptr, sizeof(__tmp)); \
+		*v = far_rcv_le##bits##toh(__tmp); \
+	} while (0)
+
+#define far_rcv_le8toh(v) (v)
+#define TLV_GET_U8(s, attr, v) TLV_GET_INT(s, attr, 8, v)
+#define TLV_GET_U16(s, attr, v) TLV_GET_INT(s, attr, 16, v)
+#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)
+
+#define TLV_GET_STRING(s, attr, str) \
+	__TLV_DO_WHILE_GOTO_FAIL(tlv_get_string(s, attr, str))
+
+#define TLV_GET_TIMESPEC(s, attr, ts) \
+	__TLV_DO_WHILE_GOTO_FAIL(tlv_get_timespec(s, attr, ts))
+
+#define TLV_GET_UUID(s, attr, uuid) \
+	__TLV_DO_WHILE_GOTO_FAIL(tlv_get_uuid(s, attr, uuid))
+
+
+#define FAR_SEND_STREAM_MAGIC "far-stream\0\0"
+#define FAR_SEND_STREAM_MAGIC_ALT "btrfs-stream"
+#define FAR_SEND_STREAM_VERSION 1
+
+#define FAR_SEND_BUF_SIZE (1024 * 64)
+#define FAR_SEND_READ_SIZE (1024 * 48)
+
+enum far_tlv_type {
+	FAR_TLV_U8,
+	FAR_TLV_U16,
+	FAR_TLV_U32,
+	FAR_TLV_U64,
+	FAR_TLV_BINARY,
+	FAR_TLV_STRING,
+	FAR_TLV_UUID,
+	FAR_TLV_TIMESPEC,
+};
+
+struct far_timespec {
+	uint64_t sec;
+	uint32_t nsec;
+} __attribute__ ((__packed__));
+
+struct far_stream_header {
+	char magic[sizeof(FAR_SEND_STREAM_MAGIC)];
+	uint32_t version;
+} __attribute__ ((__packed__));
+
+struct far_cmd_header {
+	/* len excluding the header */
+	uint32_t len;
+	uint16_t cmd;
+	/* crc including the header with zero crc field */
+	uint32_t crc;
+} __attribute__ ((__packed__));
+
+struct far_tlv_header {
+	uint16_t tlv_type;
+	/* len excluding the header */
+	uint16_t tlv_len;
+} __attribute__ ((__packed__));
+
+/* commands */
+enum far_send_cmd {
+	FAR_SEND_C_UNSPEC,
+
+	FAR_SEND_C_SUBVOL,
+	FAR_SEND_C_SNAPSHOT,
+
+	FAR_SEND_C_MKFILE,
+	FAR_SEND_C_MKDIR,
+	FAR_SEND_C_MKNOD,
+	FAR_SEND_C_MKFIFO,
+	FAR_SEND_C_MKSOCK,
+	FAR_SEND_C_SYMLINK,
+
+	FAR_SEND_C_RENAME,
+	FAR_SEND_C_LINK,
+	FAR_SEND_C_UNLINK,
+	FAR_SEND_C_RMDIR,
+
+	FAR_SEND_C_SET_XATTR,
+	FAR_SEND_C_REMOVE_XATTR,
+
+	FAR_SEND_C_WRITE,
+	FAR_SEND_C_CLONE,
+
+	FAR_SEND_C_TRUNCATE,
+	FAR_SEND_C_CHMOD,
+	FAR_SEND_C_CHOWN,
+	FAR_SEND_C_UTIMES,
+
+	FAR_SEND_C_END,
+	__FAR_SEND_C_MAX,
+};
+#define FAR_SEND_C_MAX (__FAR_SEND_C_MAX - 1)
+
+/* attributes in send stream */
+enum {
+	FAR_SEND_A_UNSPEC,
+
+	FAR_SEND_A_UUID,
+	FAR_SEND_A_CTRANSID,
+
+	FAR_SEND_A_INO,
+	FAR_SEND_A_SIZE,
+	FAR_SEND_A_MODE,
+	FAR_SEND_A_UID,
+	FAR_SEND_A_GID,
+	FAR_SEND_A_RDEV,
+	FAR_SEND_A_CTIME,
+	FAR_SEND_A_MTIME,
+	FAR_SEND_A_ATIME,
+	FAR_SEND_A_OTIME,
+
+	FAR_SEND_A_XATTR_NAME,
+	FAR_SEND_A_XATTR_DATA,
+
+	FAR_SEND_A_PATH,
+	FAR_SEND_A_PATH_TO,
+	FAR_SEND_A_PATH_LINK,
+
+	FAR_SEND_A_FILE_OFFSET,
+	FAR_SEND_A_DATA,
+
+	FAR_SEND_A_CLONE_UUID,
+	FAR_SEND_A_CLONE_CTRANSID,
+	FAR_SEND_A_CLONE_PATH,
+	FAR_SEND_A_CLONE_OFFSET,
+	FAR_SEND_A_CLONE_LEN,
+
+	__FAR_SEND_A_MAX,
+};
+#define FAR_SEND_A_MAX (__FAR_SEND_A_MAX - 1)
+
+struct far_send_stream {
+	int fd;
+	char read_buf[FAR_SEND_BUF_SIZE];
+
+	int cmd;
+	struct far_tlv_header *cmd_attrs[FAR_SEND_A_MAX + 1];
+	uint32_t version;
+};
+
+
+static int read_and_process_send_stream(struct far_rcv_ctx *frctx, int fd);
+static int read_buf(struct far_send_stream *s, void *buf, int len);
+static int read_cmd(struct far_send_stream *s);
+static int tlv_get(struct far_send_stream *s, int attr, void **data, int *len);
+static int tlv_get_string(struct far_send_stream *s, int attr, char **str);
+static int tlv_get_timespec(struct far_send_stream *s, int attr,
+			    struct timespec *ts);
+static int tlv_get_uuid(struct far_send_stream *s, int attr,
+			unsigned char *uuid);
+static int read_and_process_cmd(struct far_rcv_ctx *frctx,
+				struct far_send_stream *s);
+static int process_mkfile(struct far_rcv_ctx *frctx, const char *path);
+static int process_mkdir(struct far_rcv_ctx *frctx, const char *path);
+static int process_mknod(struct far_rcv_ctx *frctx, const char *path,
+			 uint64_t mode, uint64_t dev);
+static int process_mkfifo(struct far_rcv_ctx *frctx, const char *path);
+static int process_mksock(struct far_rcv_ctx *frctx, const char *path);
+static int process_symlink(struct far_rcv_ctx *frctx, const char *path,
+			   const char *lnk);
+static int process_rename(struct far_rcv_ctx *frctx, const char *from,
+			  const char *to);
+static int process_link(struct far_rcv_ctx *frctx, const char *path,
+			const char *lnk);
+static int process_unlink(struct far_rcv_ctx *frctx, const char *path);
+static int process_rmdir(struct far_rcv_ctx *frctx, const char *path);
+static int open_inode_for_write(struct far_rcv_ctx *frctx, const char *path);
+static int close_inode_for_write(struct far_rcv_ctx *frctx);
+static int process_write(struct far_rcv_ctx *frctx, const char *path,
+			 const void *data, uint64_t offset, uint64_t len);
+static int process_clone(struct far_rcv_ctx *frctx, const char *path,
+			 uint64_t offset, uint64_t len,
+			 const unsigned char *clone_uuid,
+			 uint64_t clone_ctransid, const char *clone_path,
+			 uint64_t clone_offset);
+static int process_set_xattr(struct far_rcv_ctx *frctx, const char *path,
+			     const char *name, const void *data, int len);
+static int process_remove_xattr(struct far_rcv_ctx *frctx, const char *path,
+				const char *name);
+static int process_truncate(struct far_rcv_ctx *frctx, const char *path,
+			    uint64_t size);
+static int process_chmod(struct far_rcv_ctx *frctx, const char *path,
+			 uint64_t mode);
+static int process_chown(struct far_rcv_ctx *frctx, const char *path,
+			 uint64_t uid, uint64_t gid);
+static int process_utimes(struct far_rcv_ctx *frctx, const char *path,
+			  struct timespec *at, struct timespec *mt,
+			  struct timespec *ct);
+
+
+int far_rcv_init(struct far_rcv_ctx *frctx)
+{
+	assert(frctx);
+
+	memset(frctx, 0, sizeof(*frctx));
+	frctx->verbose = 0;
+	frctx->support_xattrs = 1;
+	frctx->free_current_base_path = 0;
+	frctx->current_base_path = NULL;
+	frctx->write_fd = -1;
+	frctx->write_path = NULL;
+
+	frctx->ops.finish_subvol = NULL;
+	frctx->ops.subvol = NULL;
+	frctx->ops.snapshot = NULL;
+	frctx->ops.mkfile = process_mkfile;
+	frctx->ops.mkdir = process_mkdir;
+	frctx->ops.mknod = process_mknod;
+	frctx->ops.mkfifo = process_mkfifo;
+	frctx->ops.mksock = process_mksock;
+	frctx->ops.symlink = process_symlink;
+	frctx->ops.rename = process_rename;
+	frctx->ops.link = process_link;
+	frctx->ops.unlink = process_unlink;
+	frctx->ops.rmdir = process_rmdir;
+	frctx->ops.open_inode_for_write = open_inode_for_write;
+	frctx->ops.close_inode_for_write = close_inode_for_write;
+	frctx->ops.write = process_write;
+	frctx->ops.clone = process_clone;
+	frctx->ops.set_xattr = process_set_xattr;
+	frctx->ops.remove_xattr = process_remove_xattr;
+	frctx->ops.truncate = process_truncate;
+	frctx->ops.chmod = process_chmod;
+	frctx->ops.chown = process_chown;
+	frctx->ops.utimes = process_utimes;
+
+	return 0;
+}
+
+void far_rcv_deinit(struct far_rcv_ctx *frctx)
+{
+	if (frctx->free_current_base_path) {
+		free((void *)frctx->current_base_path);
+		frctx->free_current_base_path = 0;
+	}
+	frctx->current_base_path = NULL;
+	frctx->root_path = NULL;
+	free((void *)frctx->write_path);
+	frctx->write_path = NULL;
+	if (frctx->write_fd != -1) {
+		close(frctx->write_fd);
+		frctx->write_fd = -1;
+	}
+}
+
+int far_rcv_mainloop(struct far_rcv_ctx *frctx, int stream_fd,
+		     const char *root_path)
+{
+	int end = 0;
+	int ret;
+	frctx->root_path = root_path;
+	frctx->current_base_path = root_path;
+	frctx->free_current_base_path = 0;
+
+	while (!end) {
+		ret = read_and_process_send_stream(frctx, stream_fd);
+		if (ret < 0)
+			goto out;
+		if (ret)
+			end = 1;
+
+		ret = frctx->ops.close_inode_for_write(frctx);
+		if (ret < 0)
+			goto out;
+		ret = frctx->ops.finish_subvol(frctx);
+		if (ret < 0)
+			goto out;
+	}
+	ret = 0;
+
+out:
+	far_rcv_deinit(frctx);
+	return ret;
+}
+
+static int read_and_process_send_stream(struct far_rcv_ctx *frctx, int fd)
+{
+	int ret;
+	struct far_send_stream s;
+	struct far_stream_header hdr;
+
+	s.fd = fd;
+
+	ret = read_buf(&s, &hdr, sizeof(hdr));
+	if (ret < 0)
+		goto out;
+	if (ret) {
+		ret = 1;
+		goto out;
+	}
+
+	if (strcmp(hdr.magic, FAR_SEND_STREAM_MAGIC) &&
+	    strcmp(hdr.magic, FAR_SEND_STREAM_MAGIC_ALT)) {
+		ret = -EINVAL;
+		fprintf(stderr, "ERROR: Unexpected header\n");
+		goto out;
+	}
+
+	s.version = far_rcv_le32toh(hdr.version);
+	if (s.version > FAR_SEND_STREAM_VERSION) {
+		ret = -EINVAL;
+		fprintf(stderr,
+			"ERROR: Stream version %d not supported. Please upgrade.\n",
+			s.version);
+		goto out;
+	}
+
+	while (1) {
+		ret = read_and_process_cmd(frctx, &s);
+		if (ret < 0)
+			goto out;
+		if (ret) {
+			/* received end marker */
+			goto out;
+		}
+	}
+
+out:
+	return ret;
+}
+
+static int read_buf(struct far_send_stream *s, void *buf, int len)
+{
+	int ret;
+	int pos = 0;
+
+	while (pos < len) {
+		ret = read(s->fd, (char*)buf + pos, len - pos);
+		if (ret < 0) {
+			ret = -errno;
+			fprintf(stderr, "ERROR: read from stream failed. %s\n",
+				strerror(-ret));
+			goto out;
+		}
+		if (ret == 0) {
+			ret = 1;
+			goto out;
+		}
+		pos += ret;
+	}
+
+	ret = 0;
+
+out:
+	return ret;
+}
+
+/*
+ * Reads a single command and decodes the TLV's into s->cmd_attrs
+ */
+static int read_cmd(struct far_send_stream *s)
+{
+	int ret;
+	int cmd;
+	int cmd_len;
+	int tlv_type;
+	int tlv_len;
+	char *data;
+	int pos;
+	struct far_tlv_header tlv_hdr;
+	struct far_cmd_header cmd_hdr;
+	uint32_t crc;
+	uint32_t crc2;
+
+	memset(s->cmd_attrs, 0, sizeof(s->cmd_attrs));
+
+	ret = read_buf(s, &cmd_hdr, sizeof(cmd_hdr));
+	if (ret < 0)
+		goto out;
+	if (ret) {
+		ret = -EINVAL;
+		fprintf(stderr, "ERROR: unexpected EOF in stream.\n");
+		goto out;
+	}
+
+	cmd = far_rcv_le16toh(cmd_hdr.cmd);
+	cmd_len = far_rcv_le32toh(cmd_hdr.len);
+	crc = far_rcv_le32toh(cmd_hdr.crc);
+	cmd_hdr.crc = 0;
+	crc2 = far_crc32c_calc(0, (unsigned char*)&cmd_hdr, sizeof(cmd_hdr));
+
+	ret = read_buf(s, s->read_buf, cmd_len);
+	if (ret < 0)
+		goto out;
+	if (ret) {
+		ret = -EINVAL;
+		fprintf(stderr, "ERROR: unexpected EOF in stream.\n");
+		goto out;
+	}
+
+	crc2 = far_crc32c_calc(crc2, (unsigned char*)s->read_buf, cmd_len);
+
+	if (crc != crc2) {
+		ret = -EINVAL;
+		fprintf(stderr, "ERROR: crc32 mismatch in command.\n");
+		goto out;
+	}
+
+	pos = 0;
+	data = s->read_buf;
+	while (pos < cmd_len) {
+		memcpy(&tlv_hdr, data, sizeof(tlv_hdr));
+		tlv_type = far_rcv_le16toh(tlv_hdr.tlv_type);
+		tlv_len = far_rcv_le16toh(tlv_hdr.tlv_len);
+
+		if (tlv_type <= 0 || tlv_type > FAR_SEND_A_MAX ||
+		    tlv_len < 0 || tlv_len > FAR_SEND_BUF_SIZE) {
+			fprintf(stderr,
+				"ERROR: invalid tlv in cmd. tlv_type = %d, tlv_len = %d\n",
+				tlv_type, tlv_len);
+			ret = -EINVAL;
+			goto out;
+		}
+
+		s->cmd_attrs[tlv_type] = (struct far_tlv_header *)data;
+		data += sizeof(tlv_hdr) + tlv_len;
+		pos += sizeof(tlv_hdr) + tlv_len;
+	}
+
+	s->cmd = cmd;
+	ret = 0;
+
+out:
+	return ret;
+}
+
+static int tlv_get(struct far_send_stream *s, int attr, void **data, int *len)
+{
+	int ret;
+	struct far_tlv_header tlv_hdr;
+	struct far_tlv_header *h;
+
+	if (attr <= 0 || attr > FAR_SEND_A_MAX) {
+		fprintf(stderr,
+			"ERROR: invalid attribute requested. attr = %d\n",
+			attr);
+		ret = -EINVAL;
+		goto out;
+	}
+
+	h = s->cmd_attrs[attr];
+	if (!h) {
+		fprintf(stderr,
+			"ERROR: attribute %d requested but not present.\n",
+			attr);
+		ret = -ENOENT;
+		goto out;
+	}
+
+	memcpy(&tlv_hdr, h, sizeof(tlv_hdr));
+	*len = far_rcv_le16toh(tlv_hdr.tlv_len);
+	*data = h + 1;
+
+	ret = 0;
+
+out:
+	return ret;
+}
+
+static int tlv_get_string(struct far_send_stream *s, int attr, char **str)
+{
+	int ret;
+	void *data;
+	int len;
+
+	TLV_GET(s, attr, &data, &len);
+
+	*str = malloc(len + 1);
+	if (!*str)
+		return -ENOMEM;
+
+	memcpy(*str, data, len);
+	(*str)[len] = 0;
+	ret = 0;
+
+tlv_get_failed:
+	return ret;
+}
+
+static int tlv_get_timespec(struct far_send_stream *s, int attr,
+			    struct timespec *ts)
+{
+	int ret;
+	int len;
+	struct far_timespec bts;
+	void *ptr;
+
+	TLV_GET(s, attr, &ptr, &len);
+	TLV_CHECK_LEN(sizeof(bts), len);
+
+	memcpy(&bts, ptr, sizeof(bts));
+	ts->tv_sec = far_rcv_le64toh(bts.sec);
+	ts->tv_nsec = far_rcv_le32toh(bts.nsec);
+	ret = 0;
+
+tlv_get_failed:
+	return ret;
+}
+
+static int tlv_get_uuid(struct far_send_stream *s, int attr,
+			unsigned char *uuid)
+{
+	int ret;
+	int len;
+	void *data;
+
+	TLV_GET(s, attr, &data, &len);
+	TLV_CHECK_LEN(FAR_UUID_SIZE, len);
+	memcpy(uuid, data, FAR_UUID_SIZE);
+
+	ret = 0;
+
+tlv_get_failed:
+	return ret;
+}
+
+static int read_and_process_cmd(struct far_rcv_ctx *frctx,
+				struct far_send_stream *s)
+{
+	int ret;
+	char *path = NULL;
+	char *path_to = NULL;
+	char *clone_path = NULL;
+	char *xattr_name = NULL;
+	void *xattr_data = NULL;
+	void *data = NULL;
+	struct timespec at;
+	struct timespec ct;
+	struct timespec mt;
+	unsigned char uuid[FAR_UUID_SIZE];
+	unsigned char clone_uuid[FAR_UUID_SIZE];
+	uint64_t tmp;
+	uint64_t tmp2;
+	uint64_t ctransid;
+	uint64_t clone_ctransid;
+	uint64_t mode;
+	uint64_t dev;
+	uint64_t clone_offset;
+	uint64_t offset;
+	int len;
+	int xattr_len;
+	char *tmp_path;
+
+	ret = read_cmd(s);
+	if (ret)
+		goto out;
+
+	switch (s->cmd) {
+	case FAR_SEND_C_SUBVOL:
+		TLV_GET_STRING(s, FAR_SEND_A_PATH, &path);
+		TLV_GET_UUID(s, FAR_SEND_A_UUID, uuid);
+		TLV_GET_U64(s, FAR_SEND_A_CTRANSID, &ctransid);
+		tmp_path = strrchr(path, '/');
+		if (tmp_path)
+			path = strdup(tmp_path + 1);
+		ret = frctx->ops.subvol(frctx, path, uuid, ctransid);
+		break;
+	case FAR_SEND_C_SNAPSHOT:
+		TLV_GET_STRING(s, FAR_SEND_A_PATH, &path);
+		TLV_GET_UUID(s, FAR_SEND_A_UUID, uuid);
+		TLV_GET_U64(s, FAR_SEND_A_CTRANSID, &ctransid);
+		TLV_GET_UUID(s, FAR_SEND_A_CLONE_UUID, clone_uuid);
+		TLV_GET_U64(s, FAR_SEND_A_CLONE_CTRANSID, &clone_ctransid);
+		tmp_path = strrchr(path, '/');
+		if (tmp_path)
+			path = strdup(tmp_path + 1);
+		ret = frctx->ops.snapshot(frctx, path, uuid, ctransid,
+					  clone_uuid, clone_ctransid);
+		break;
+	case FAR_SEND_C_MKFILE:
+		TLV_GET_STRING(s, FAR_SEND_A_PATH, &path);
+		ret = frctx->ops.mkfile(frctx, path);
+		break;
+	case FAR_SEND_C_MKDIR:
+		TLV_GET_STRING(s, FAR_SEND_A_PATH, &path);
+		ret = frctx->ops.mkdir(frctx, path);
+		break;
+	case FAR_SEND_C_MKNOD:
+		TLV_GET_STRING(s, FAR_SEND_A_PATH, &path);
+		TLV_GET_U64(s, FAR_SEND_A_MODE, &mode);
+		TLV_GET_U64(s, FAR_SEND_A_RDEV, &dev);
+		ret = frctx->ops.mknod(frctx, path, mode, dev);
+		break;
+	case FAR_SEND_C_MKFIFO:
+		TLV_GET_STRING(s, FAR_SEND_A_PATH, &path);
+		ret = frctx->ops.mkfifo(frctx, path);
+		break;
+	case FAR_SEND_C_MKSOCK:
+		TLV_GET_STRING(s, FAR_SEND_A_PATH, &path);
+		ret = frctx->ops.mksock(frctx, path);
+		break;
+	case FAR_SEND_C_SYMLINK:
+		TLV_GET_STRING(s, FAR_SEND_A_PATH, &path);
+		TLV_GET_STRING(s, FAR_SEND_A_PATH_LINK, &path_to);
+		ret = frctx->ops.symlink(frctx, path, path_to);
+		break;
+	case FAR_SEND_C_RENAME:
+		TLV_GET_STRING(s, FAR_SEND_A_PATH, &path);
+		TLV_GET_STRING(s, FAR_SEND_A_PATH_TO, &path_to);
+		ret = frctx->ops.rename(frctx, path, path_to);
+		break;
+	case FAR_SEND_C_LINK:
+		TLV_GET_STRING(s, FAR_SEND_A_PATH, &path);
+		TLV_GET_STRING(s, FAR_SEND_A_PATH_LINK, &path_to);
+		ret = frctx->ops.link(frctx, path, path_to);
+		break;
+	case FAR_SEND_C_UNLINK:
+		TLV_GET_STRING(s, FAR_SEND_A_PATH, &path);
+		ret = frctx->ops.unlink(frctx, path);
+		break;
+	case FAR_SEND_C_RMDIR:
+		TLV_GET_STRING(s, FAR_SEND_A_PATH, &path);
+		ret = frctx->ops.rmdir(frctx, path);
+		break;
+	case FAR_SEND_C_WRITE:
+		TLV_GET_STRING(s, FAR_SEND_A_PATH, &path);
+		TLV_GET_U64(s, FAR_SEND_A_FILE_OFFSET, &offset);
+		TLV_GET(s, FAR_SEND_A_DATA, &data, &len);
+		ret = frctx->ops.write(frctx, path, data, offset, len);
+		break;
+	case FAR_SEND_C_CLONE:
+		TLV_GET_STRING(s, FAR_SEND_A_PATH, &path);
+		TLV_GET_U64(s, FAR_SEND_A_FILE_OFFSET, &offset);
+		TLV_GET_U64(s, FAR_SEND_A_CLONE_LEN, &len);
+		TLV_GET_UUID(s, FAR_SEND_A_CLONE_UUID, clone_uuid);
+		TLV_GET_U64(s, FAR_SEND_A_CLONE_CTRANSID, &clone_ctransid);
+		TLV_GET_STRING(s, FAR_SEND_A_CLONE_PATH, &clone_path);
+		TLV_GET_U64(s, FAR_SEND_A_CLONE_OFFSET, &clone_offset);
+		ret = frctx->ops.clone(frctx, path, offset, len, clone_uuid,
+				       clone_ctransid, clone_path,
+				       clone_offset);
+		break;
+	case FAR_SEND_C_SET_XATTR:
+		TLV_GET_STRING(s, FAR_SEND_A_PATH, &path);
+		TLV_GET_STRING(s, FAR_SEND_A_XATTR_NAME, &xattr_name);
+		TLV_GET(s, FAR_SEND_A_XATTR_DATA, &xattr_data, &xattr_len);
+		if (!frctx->support_xattrs)
+			break;
+		ret = frctx->ops.set_xattr(frctx, path, xattr_name, xattr_data,
+					   xattr_len);
+		break;
+	case FAR_SEND_C_REMOVE_XATTR:
+		TLV_GET_STRING(s, FAR_SEND_A_PATH, &path);
+		TLV_GET_STRING(s, FAR_SEND_A_XATTR_NAME, &xattr_name);
+		if (!frctx->support_xattrs)
+			break;
+		ret = frctx->ops.remove_xattr(frctx, path, xattr_name);
+		break;
+	case FAR_SEND_C_TRUNCATE:
+		TLV_GET_STRING(s, FAR_SEND_A_PATH, &path);
+		TLV_GET_U64(s, FAR_SEND_A_SIZE, &tmp);
+		ret = frctx->ops.truncate(frctx, path, tmp);
+		break;
+	case FAR_SEND_C_CHMOD:
+		TLV_GET_STRING(s, FAR_SEND_A_PATH, &path);
+		TLV_GET_U64(s, FAR_SEND_A_MODE, &tmp);
+		ret = frctx->ops.chmod(frctx, path, tmp);
+		break;
+	case FAR_SEND_C_CHOWN:
+		TLV_GET_STRING(s, FAR_SEND_A_PATH, &path);
+		TLV_GET_U64(s, FAR_SEND_A_UID, &tmp);
+		TLV_GET_U64(s, FAR_SEND_A_GID, &tmp2);
+		ret = frctx->ops.chown(frctx, path, tmp, tmp2);
+		break;
+	case FAR_SEND_C_UTIMES:
+		TLV_GET_STRING(s, FAR_SEND_A_PATH, &path);
+		TLV_GET_TIMESPEC(s, FAR_SEND_A_ATIME, &at);
+		TLV_GET_TIMESPEC(s, FAR_SEND_A_MTIME, &mt);
+		TLV_GET_TIMESPEC(s, FAR_SEND_A_CTIME, &ct);
+		ret = frctx->ops.utimes(frctx, path, &at, &mt, &ct);
+		break;
+	case FAR_SEND_C_END:
+		if (frctx->verbose >= 2)
+			fprintf(stderr, "receive end marker\n");
+		ret = 1;
+		break;
+	}
+
+tlv_get_failed:
+out:
+	free(path);
+	free(path_to);
+	free(clone_path);
+	free(xattr_name);
+	return ret;
+}
+
+static int process_mkfile(struct far_rcv_ctx *frctx, const char *path)
+{
+	int ret;
+	char *full_path = far_rcv_path_cat(frctx->current_base_path, path);
+
+	if (frctx->verbose >= 2)
+		fprintf(stderr, "mkfile %s\n", path);
+
+	ret = creat(full_path, 0600);
+	if (ret < 0) {
+		ret = -errno;
+		fprintf(stderr, "ERROR: mkfile %s failed. %s\n", path,
+			strerror(-ret));
+		goto out;
+	}
+	close(ret);
+	ret = 0;
+
+out:
+	free(full_path);
+	return ret;
+}
+
+static int process_mkdir(struct far_rcv_ctx *frctx, const char *path)
+{
+	int ret;
+	char *full_path = far_rcv_path_cat(frctx->current_base_path, path);
+
+	if (frctx->verbose >= 2)
+		fprintf(stderr, "mkdir %s\n", path);
+
+	ret = mkdir(full_path, 0700);
+	if (ret < 0) {
+		ret = -errno;
+		fprintf(stderr, "ERROR: mkdir %s failed. %s\n", path,
+			strerror(-ret));
+	}
+
+	free(full_path);
+	return ret;
+}
+
+static int process_mknod(struct far_rcv_ctx *frctx, const char *path,
+			 uint64_t mode, uint64_t dev)
+{
+	int ret;
+	char *full_path = far_rcv_path_cat(frctx->current_base_path, path);
+
+	if (frctx->verbose >= 2)
+		fprintf(stderr, "mknod %s mode=%" PRIu64 ", dev=%" PRIu64 "\n",
+			path, mode, dev);
+
+	ret = mknod(full_path, mode & S_IFMT, dev);
+	if (ret < 0) {
+		ret = -errno;
+		fprintf(stderr, "ERROR: mknod %s failed. %s\n", path,
+			strerror(-ret));
+	}
+
+	free(full_path);
+	return ret;
+}
+
+static int process_mkfifo(struct far_rcv_ctx *frctx, const char *path)
+{
+	int ret;
+	char *full_path = far_rcv_path_cat(frctx->current_base_path, path);
+
+	if (frctx->verbose >= 2)
+		fprintf(stderr, "mkfifo %s\n", path);
+
+	ret = mkfifo(full_path, 0600);
+	if (ret < 0) {
+		ret = -errno;
+		fprintf(stderr, "ERROR: mkfifo %s failed. %s\n", path,
+			strerror(-ret));
+	}
+
+	free(full_path);
+	return ret;
+}
+
+static int process_mksock(struct far_rcv_ctx *frctx, const char *path)
+{
+	int ret;
+	char *full_path = far_rcv_path_cat(frctx->current_base_path, path);
+
+	if (frctx->verbose >= 2)
+		fprintf(stderr, "mksock %s\n", path);
+
+	ret = mknod(full_path, 0600 | S_IFSOCK, 0);
+	if (ret < 0) {
+		ret = -errno;
+		fprintf(stderr, "ERROR: mknod %s failed. %s\n", path,
+			strerror(-ret));
+	}
+
+	free(full_path);
+	return ret;
+}
+
+static int process_symlink(struct far_rcv_ctx *frctx, const char *path,
+			   const char *lnk)
+{
+	int ret;
+	char *full_path = far_rcv_path_cat(frctx->current_base_path, path);
+
+	if (frctx->verbose >= 2)
+		fprintf(stderr, "symlink %s -> %s\n", path, lnk);
+
+	ret = symlink(lnk, full_path);
+	if (ret < 0) {
+		ret = -errno;
+		fprintf(stderr, "ERROR: symlink %s -> %s failed. %s\n", path,
+			lnk, strerror(-ret));
+	}
+
+	free(full_path);
+	return ret;
+}
+
+static int process_rename(struct far_rcv_ctx *frctx, const char *from,
+			  const char *to)
+{
+	int ret;
+	char *full_from = far_rcv_path_cat(frctx->current_base_path, from);
+	char *full_to = far_rcv_path_cat(frctx->current_base_path, to);
+
+	if (frctx->verbose >= 2)
+		fprintf(stderr, "rename %s -> %s\n", from, to);
+
+	ret = rename(full_from, full_to);
+	if (ret < 0) {
+		ret = -errno;
+		fprintf(stderr, "ERROR: rename %s -> %s failed. %s\n", from,
+			to, strerror(-ret));
+	}
+
+	free(full_from);
+	free(full_to);
+	return ret;
+}
+
+static int process_link(struct far_rcv_ctx *frctx, const char *path,
+			const char *lnk)
+{
+	int ret;
+	char *full_path = far_rcv_path_cat(frctx->current_base_path, path);
+	char *full_link_path = far_rcv_path_cat(frctx->current_base_path, lnk);
+
+	if (frctx->verbose >= 2)
+		fprintf(stderr, "link %s -> %s\n", path, lnk);
+
+	ret = link(full_link_path, full_path);
+	if (ret < 0) {
+		ret = -errno;
+		fprintf(stderr, "ERROR: link %s -> %s failed. %s\n", path,
+			lnk, strerror(-ret));
+	}
+
+	free(full_path);
+	free(full_link_path);
+	return ret;
+}
+
+
+static int process_unlink(struct far_rcv_ctx *frctx, const char *path)
+{
+	int ret;
+	char *full_path = far_rcv_path_cat(frctx->current_base_path, path);
+
+	if (frctx->verbose >= 2)
+		fprintf(stderr, "unlink %s\n", path);
+
+	ret = unlink(full_path);
+	if (ret < 0) {
+		ret = -errno;
+		fprintf(stderr, "ERROR: unlink %s failed. %s\n", path,
+			strerror(-ret));
+	}
+
+	free(full_path);
+	return ret;
+}
+
+static int process_rmdir(struct far_rcv_ctx *frctx, const char *path)
+{
+	int ret;
+	char *full_path = far_rcv_path_cat(frctx->current_base_path, path);
+
+	if (frctx->verbose >= 2)
+		fprintf(stderr, "rmdir %s\n", path);
+
+	ret = rmdir(full_path);
+	if (ret < 0) {
+		ret = -errno;
+		fprintf(stderr, "ERROR: rmdir %s failed. %s\n", path,
+			strerror(-ret));
+	}
+
+	free(full_path);
+	return ret;
+}
+
+static int open_inode_for_write(struct far_rcv_ctx *frctx, const char *path)
+{
+	int ret = 0;
+
+	if (frctx->write_fd != -1) {
+		if (strcmp(frctx->write_path, path) == 0)
+			goto out;
+		close(frctx->write_fd);
+		frctx->write_fd = -1;
+	}
+
+	frctx->write_fd = open(path, O_RDWR);
+	if (frctx->write_fd < 0) {
+		ret = -errno;
+		fprintf(stderr, "ERROR: open %s failed. %s\n", path,
+			strerror(-ret));
+		goto out;
+	}
+	free((void *)frctx->write_path);
+	frctx->write_path = strdup(path);
+
+out:
+	return ret;
+}
+
+static int close_inode_for_write(struct far_rcv_ctx *frctx)
+{
+	free((void *)frctx->write_path);
+	frctx->write_path = NULL;
+	if (frctx->write_fd != -1) {
+		close(frctx->write_fd);
+		frctx->write_fd = -1;
+	}
+
+	return 0;
+}
+
+static int process_write(struct far_rcv_ctx *frctx, const char *path,
+			 const void *data, uint64_t offset, uint64_t len)
+{
+	int ret = 0;
+	char *full_path = far_rcv_path_cat(frctx->current_base_path, path);
+	uint64_t pos = 0;
+	int w;
+
+	ret = frctx->ops.open_inode_for_write(frctx, full_path);
+	if (ret < 0)
+		goto out;
+
+	while (pos < len) {
+		w = pwrite(frctx->write_fd, (char*)data + pos, len - pos,
+			   offset + pos);
+		if (w < 0) {
+			ret = -errno;
+			fprintf(stderr, "ERROR: writing to %s failed. %s\n",
+				path, strerror(-ret));
+			goto out;
+		}
+		pos += w;
+	}
+
+out:
+	free(full_path);
+	return ret;
+}
+
+static int process_clone(struct far_rcv_ctx *frctx, const char *path,
+			 uint64_t offset, uint64_t len,
+			 const unsigned char *clone_uuid,
+			 uint64_t clone_ctransid, const char *clone_path,
+			 uint64_t clone_offset)
+{
+	int ret;
+	char *full_dst_path = far_rcv_path_cat(frctx->current_base_path, path);
+	char *full_clone_path = NULL;
+	int clone_fd = -1;
+
+	ret = frctx->ops.open_inode_for_write(frctx, full_dst_path);
+	if (ret < 0)
+		goto out;
+
+	full_clone_path = far_rcv_path_cat(frctx->current_base_path,
+					   clone_path);
+
+	clone_fd = open(full_clone_path, O_RDONLY | O_NOATIME);
+	if (clone_fd < 0) {
+		ret = -errno;
+		fprintf(stderr, "ERROR: failed to open %s. %s\n",
+			full_clone_path, strerror(-ret));
+		goto out;
+	}
+
+	if (lseek(frctx->write_fd, offset, SEEK_SET) == (off_t)-1) {
+		ret = -errno;
+		fprintf(stderr, "ERROR: failed to lseek %s. %s\n",
+			full_dst_path, strerror(-ret));
+		goto out;
+	}
+	if (lseek(clone_fd, clone_offset, SEEK_SET) == (off_t)-1) {
+		ret = -errno;
+		fprintf(stderr, "ERROR: failed to lseek %s. %s\n",
+			full_clone_path, strerror(-ret));
+		goto out;
+	}
+
+	while (len > 0) {
+		char buf[4096];
+		uint64_t sub_len;
+		int read_len;
+		int write_len;
+		int offset;
+
+		sub_len = sizeof(buf);
+		if (sub_len > len)
+			sub_len = len;
+		read_len = read(clone_fd, buf, sub_len);
+		if (read_len < 0) {
+			if (errno == EINTR)
+				continue;
+			ret = -errno;
+			fprintf(stderr, "ERROR: failed to read from %s. %s\n",
+				full_clone_path, strerror(-ret));
+			goto out;
+		} else if (read_len == 0) {
+			ret = -ESPIPE; /* Hmm */
+			fprintf(stderr,
+				"ERROR: premature EOF while reading from %s.\n",
+				full_clone_path);
+			goto out;
+		}
+
+		len -= read_len;
+		offset = 0;
+		do {
+			write_len = write(frctx->write_fd, buf + offset,
+					  read_len);
+			if (write_len < 0) {
+				if (errno == EINTR)
+					continue;
+				ret = -errno;
+				fprintf(stderr,
+					"ERROR: failed to write to %s. %s\n",
+					full_dst_path, strerror(-ret));
+				goto out;
+			}
+			offset += write_len;
+			read_len -= write_len;
+		} while (read_len > 0);
+	}
+
+out:
+	free(full_dst_path);
+	free(full_clone_path);
+	if (clone_fd != -1)
+		close(clone_fd);
+	return ret;
+}
+
+static int process_set_xattr(struct far_rcv_ctx *frctx, const char *path,
+			     const char *name, const void *data, int len)
+{
+	int ret = 0;
+	char *full_path = far_rcv_path_cat(frctx->current_base_path, path);
+
+	if (frctx->verbose >= 2)
+		fprintf(stderr,
+			"set_xattr %s - name=%s data_len=%d data=%.*s\n",
+			path, name, len, len, (char*)data);
+
+	ret = far_rcv_lsetxattr(full_path, name, data, len);
+	if (ret < 0) {
+		fprintf(stderr,
+			"ERROR: far_rcv_lsetxattr %s %s=%.*s failed. %s\n",
+			path, name, len, (char*)data, strerror(-ret));
+		goto out;
+	}
+
+out:
+	free(full_path);
+	return ret;
+}
+
+static int process_remove_xattr(struct far_rcv_ctx *frctx, const char *path,
+				const char *name)
+{
+	int ret = 0;
+	char *full_path = far_rcv_path_cat(frctx->current_base_path, path);
+
+	if (frctx->verbose >= 2)
+		fprintf(stderr, "remove_xattr %s - name=%s\n", path, name);
+
+	ret = far_rcv_lremovexattr(full_path, name);
+	if (ret < 0) {
+		fprintf(stderr,
+			"ERROR: far_rcv_lremovexattr %s %s failed. %s\n",
+			path, name, strerror(-ret));
+		goto out;
+	}
+
+out:
+	free(full_path);
+	return ret;
+}
+
+static int process_truncate(struct far_rcv_ctx *frctx, const char *path,
+			    uint64_t size)
+{
+	int ret = 0;
+	char *full_path = far_rcv_path_cat(frctx->current_base_path, path);
+
+	if (frctx->verbose >= 2)
+		fprintf(stderr, "truncate %s size=%" PRIu64 "\n", path, size);
+
+	ret = truncate(full_path, size);
+	if (ret < 0) {
+		ret = -errno;
+		fprintf(stderr, "ERROR: truncate %s failed. %s\n", path,
+			strerror(-ret));
+		goto out;
+	}
+
+out:
+	free(full_path);
+	return ret;
+}
+
+static int process_chmod(struct far_rcv_ctx *frctx, const char *path,
+			 uint64_t mode)
+{
+	int ret = 0;
+	char *full_path = far_rcv_path_cat(frctx->current_base_path, path);
+
+	if (frctx->verbose >= 2)
+		fprintf(stderr, "chmod %s - mode=0%o\n", path, (int)mode);
+
+	ret = chmod(full_path, mode);
+	if (ret < 0) {
+		ret = -errno;
+		fprintf(stderr, "ERROR: chmod %s failed. %s\n",
+				path, strerror(-ret));
+		goto out;
+	}
+
+out:
+	free(full_path);
+	return ret;
+}
+
+static int process_chown(struct far_rcv_ctx *frctx, const char *path,
+			 uint64_t uid, uint64_t gid)
+{
+	int ret = 0;
+	char *full_path = far_rcv_path_cat(frctx->current_base_path, path);
+
+	if (frctx->verbose >= 2)
+		fprintf(stderr, "chown %s - uid=%" PRIu64 ", gid=%" PRIu64 "\n",
+			path, uid, gid);
+
+	ret = lchown(full_path, uid, gid);
+	if (ret < 0) {
+		ret = -errno;
+		fprintf(stderr, "ERROR: chown %s failed. %s\n", path,
+			strerror(-ret));
+		goto out;
+	}
+
+out:
+	free(full_path);
+	return ret;
+}
+
+static int process_utimes(struct far_rcv_ctx *frctx, const char *path,
+			  struct timespec *at, struct timespec *mt,
+			  struct timespec *ct)
+{
+	int ret = 0;
+	char *full_path = far_rcv_path_cat(frctx->current_base_path, path);
+#if defined (HAVE_UTIMENSAT)
+	struct timespec tv[2];
+
+	if (frctx->verbose >= 2)
+		fprintf(stderr, "utimes %s\n", path);
+
+	tv[0] = *at;
+	tv[1] = *mt;
+	ret = utimensat(AT_FDCWD, full_path, tv, AT_SYMLINK_NOFOLLOW);
+#else /* !defined (HAVE_UTIMENSAT) */
+	struct utimbuf tv;
+
+	tv.actime = at->tv_sec;
+	tv.modtime = mt->tv_sec;
+	ret = utime(path, &tv);
+#endif /* !defined (HAVE_UTIMENSAT) */
+	if (ret < 0) {
+		ret = -errno;
+		fprintf(stderr, "ERROR: utimes %s failed. %s\n", path,
+			strerror(-ret));
+		goto out;
+	}
+
+out:
+	free(full_path);
+	return ret;
+}
+
+char *far_rcv_path_cat(const char *p1, const char *p2)
+{
+	int p1_len = strlen(p1);
+	int p2_len = strlen(p2);
+	char *new = malloc(p1_len + p2_len + 2);
+
+	if (p1_len && p1[p1_len - 1] == '/')
+		p1_len--;
+	if (p2_len && p2[p2_len - 1] == '/')
+		p2_len--;
+	sprintf(new, "%.*s/%.*s", p1_len, p1, p2_len, p2);
+	return new;
+}
diff --git a/far-rcv/far-rcv.h b/far-rcv/far-rcv.h
new file mode 100644
index 0000000..03de4d3
--- /dev/null
+++ b/far-rcv/far-rcv.h
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2012 Alexander Block.
+ * Copyright (C) 2012, 2013 STRATO AG.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+ * This is the main part of the far-rcv library. All exported types and
+ * functions are located in this file.
+ */
+
+#ifndef FAR_RCV_H_
+#define FAR_RCV_H_
+
+#include <stdint.h>
+#include <time.h>
+
+#define FAR_UUID_SIZE 16
+
+
+struct far_rcv_ctx;
+
+struct far_rcv_ops {
+	int (*finish_subvol)(struct far_rcv_ctx *frctx);
+	int (*subvol)(struct far_rcv_ctx *frctx, const char *path,
+		      const unsigned char *uuid, uint64_t ctransid);
+	int (*snapshot)(struct far_rcv_ctx *frctx, const char *path,
+			const unsigned char *uuid, uint64_t ctransid,
+			const unsigned char *parent_uuid,
+			uint64_t parent_ctransid);
+	int (*mkfile)(struct far_rcv_ctx *frctx, const char *path);
+	int (*mkdir)(struct far_rcv_ctx *frctx, const char *path);
+	int (*mknod)(struct far_rcv_ctx *frctx, const char *path, uint64_t mode,
+		     uint64_t dev);
+	int (*mkfifo)(struct far_rcv_ctx *frctx, const char *path);
+	int (*mksock)(struct far_rcv_ctx *frctx, const char *path);
+	int (*symlink)(struct far_rcv_ctx *frctx, const char *path,
+		       const char *lnk);
+	int (*rename)(struct far_rcv_ctx *frctx, const char *from,
+		      const char *to);
+	int (*link)(struct far_rcv_ctx *frctx, const char *path,
+		    const char *lnk);
+	int (*unlink)(struct far_rcv_ctx *frctx, const char *path);
+	int (*rmdir)(struct far_rcv_ctx *frctx, const char *path);
+	int (*open_inode_for_write)(struct far_rcv_ctx *frctx,
+				    const char *path);
+	int (*close_inode_for_write)(struct far_rcv_ctx *frctx);
+	int (*write)(struct far_rcv_ctx *frctx, const char *path,
+		     const void *data, uint64_t offset, uint64_t len);
+	int (*clone)(struct far_rcv_ctx *frctx, const char *path,
+		     uint64_t offset, uint64_t len,
+		     const unsigned char *clone_uuid, uint64_t clone_ctransid,
+		     const char *clone_path, uint64_t clone_offset);
+	int (*set_xattr)(struct far_rcv_ctx *frctx, const char *path,
+			 const char *name, const void *data, int len);
+	int (*remove_xattr)(struct far_rcv_ctx *frctx, const char *path,
+			    const char *name);
+	int (*truncate)(struct far_rcv_ctx *frctx, const char *path,
+			uint64_t size);
+	int (*chmod)(struct far_rcv_ctx *frctx, const char *path,
+		     uint64_t mode);
+	int (*chown)(struct far_rcv_ctx *frctx, const char *path, uint64_t uid,
+		     uint64_t gid);
+	int (*utimes)(struct far_rcv_ctx *frctx, const char *path,
+		      struct timespec *at, struct timespec *mt,
+		      struct timespec *ct);
+};
+
+struct far_rcv_ctx {
+	struct far_rcv_ops ops;
+
+	int verbose;
+	int support_xattrs;
+	int free_current_base_path;
+	const char *current_base_path;
+	int write_fd;
+	const char *write_path;
+	const char *root_path;
+};
+
+int far_rcv_init(struct far_rcv_ctx *frctx);
+void far_rcv_deinit(struct far_rcv_ctx *frctx);
+int far_rcv_mainloop(struct far_rcv_ctx *frctx, int stream_fd,
+		     const char *root_path);
+char *far_rcv_path_cat(const char *p1, const char *p2);
+
+#endif /* !defined (FAR_RCV_H_) */
diff --git a/far-rcv/far-xattr.c b/far-rcv/far-xattr.c
new file mode 100644
index 0000000..fd4271b
--- /dev/null
+++ b/far-rcv/far-xattr.c
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2012, 2013 STRATO AG.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+ * Handling of extended attributes aka xattr or extattr is located in this
+ * file. It is different for Linux, SunOS and all the other operating systems.
+ * Refer to <http://en.wikipedia.org/wiki/Extended_file_attributes>.
+ * This current implementation supports Linux and SunOS.
+ * These functions are used internally in the far-rcv library.
+ */
+
+#include <sys/types.h>
+#include <errno.h>
+
+#if defined (__LINUX__)
+#include <attr/xattr.h>
+#else /* !defined (__LINUX__) */
+#include <stdio.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <string.h>
+#include <assert.h>
+#endif /* !defined (__LINUX__) */
+
+#include "far-xattr.h"
+
+
+int far_rcv_lsetxattr(const char *path, const char *name, const void *value,
+		      size_t size)
+{
+#if defined (__LINUX__)
+	if (lsetxattr(path, name, value, size, 0) < 0)
+		return -errno;
+	else
+		return 0;
+#else /* !defined (__LINUX__) */
+	int fd = -1;
+	int ret = 0;
+
+	fd = attropen(path, name, O_TRUNC | O_WRONLY | O_CREAT);
+	if (fd < 0) {
+		ret = -errno;
+		fprintf(stderr,
+			"ERROR: attropen(%s, %s) for writing failed. %s\n",
+			path, name, strerror(-ret));
+		goto out;
+	}
+	while (size) {
+		ret = write(fd, value, size);
+		if (ret == -1 && errno == EINTR) {
+			continue;
+		} else if (ret == -1) {
+			ret = -errno;
+			fprintf(stderr,
+				"ERROR: write() xattr %s in %s failed. %s\n",
+				name, path, strerror(-ret));
+			goto out;
+		}
+		assert(ret > 0);
+		size -= ret;
+		value += ret;
+		ret = 0;
+	}
+out:
+	if (fd != -1)
+		close(fd);
+	return ret;
+#endif /* !defined (__LINUX__) */
+}
+
+int far_rcv_lremovexattr(const char *path, const char *name)
+{
+#if defined (__LINUX__)
+	if (lremovexattr(path, name) < 0)
+		return -errno;
+	else
+		return 0;
+#else /* !defined (__LINUX__) */
+	int attrdirfd;
+	int ret = 0;
+
+	attrdirfd = attropen(path, ".", O_RDONLY);
+	if (attrdirfd < 0) {
+		ret = -errno;
+		fprintf(stderr,
+			"ERROR: attropen(%s, \".\") failed. %s\n", path,
+			strerror(-ret));
+		goto out;
+	}
+
+	if (unlinkat(attrdirfd, name, 0) < 0) {
+		ret = -errno;
+		fprintf(stderr,
+			"ERROR: unlinkat(%s) failed in %s. %s\n", name, path,
+			strerror(-ret));
+		goto out;
+	}
+
+out:
+	if (attrdirfd != -1)
+		close(attrdirfd);
+	return ret;
+#endif /* !defined (__LINUX__) */
+}
diff --git a/far-rcv/far-xattr.h b/far-rcv/far-xattr.h
new file mode 100644
index 0000000..f0ddfed
--- /dev/null
+++ b/far-rcv/far-xattr.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2012, 2013 STRATO AG.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+ * Handling of extended attributes aka xattr or extattr is located in this
+ * file. It is different for Linux, SunOS and all the other operating systems.
+ * Refer to <http://en.wikipedia.org/wiki/Extended_file_attributes>.
+ * This current implementation supports Linux and SunOS.
+ * These functions are used internally in the far-rcv library.
+ */
+
+#if !defined (__FAR_RCV_XATTR_H)
+#define __FAR_RCV_XATTR_H
+
+int far_rcv_lsetxattr(const char *path, const char *name, const void *value,
+		      size_t size);
+int far_rcv_lremovexattr(const char *path, const char *name);
+
+#endif /* !defined (__FAR_RCV_XATTR_H) */