blob: 926dc4ca6603a8eb4bba01f8802f56e6274293d0 [file] [log] [blame]
/*
* 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->honor_end_cmd = 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 */
if (!frctx->honor_end_cmd)
ret = 0;
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;
}