#include "compat.h"
#include "backuppcd.h"
#include "backuppcd-common.h"
#include "net.h"

typedef enum {
        BPC_CDT_END,
        BPC_CDT_UINT8,
        BPC_CDT_UINT16,
        BPC_CDT_UINT32,
        BPC_CDT_UINT64,
        BPC_CDT_BYTEARRAY,
        BPC_CDT_STRING,
        BPC_CDT_STRING_PTR,
} backuppc_clntdt_t;

#ifndef MSG_WAITALL
#define MSG_WAITALL 0
#endif

#ifndef BPC_MAXPATH_LEN
#define BPC_MAXPATH_LEN 4096
#endif                                                                                                                                    

/*
 * Example BackupPCd client with USTAR output.
 */

int backuppcd_client(const char *host, const int port, const char *username, const char *password, const char *root);

int main(int argc, char **argv) {
	char *host, *rootpath, *username, *password;
	int port;
	int cmd_ret = -1;

	if (argc < 3) {
		fprintf(stderr, "Usage: backuppcd-tar -xOf hostname:/path [... ignored...]\n");
		return(EXIT_FAILURE);
	}

	password = getenv("PASSWD");

	if (!password) {
		fprintf(stderr, "Must supply password in the environment variable \"PASSWD\"\n");
		return(EXIT_FAILURE);
	}

	host = strdup(argv[2]);
	rootpath = strchr(host, ':');
	if (rootpath) {
		*rootpath = '\0';
		rootpath++;
	} else {
		rootpath = "/";
	}

	port = 874;
	username = "backuppc-tar";

	cmd_ret = backuppcd_client(host, port, username, password, rootpath);

	if (cmd_ret < 0) {
		return(EXIT_FAILURE);
	}

	return(EXIT_SUCCESS);
}


int backuppcd_client_read(const int sockid, ...) {
	backuppc_clntdt_t typeid;
	va_list ap;
	uint8_t *u8v;
	uint16_t *u16v;
	uint32_t *u32v;
	uint64_t *u64v;
	char *cpv, **cppv;
	void *vpv = NULL, *vpv_s;
	size_t vpv_len;
	ssize_t read_ret;

	va_start(ap, sockid);
	while (1) {
		typeid = va_arg(ap, backuppc_clntdt_t);

		if (typeid == BPC_CDT_END) {
			break;
		}

		switch (typeid) {
			case BPC_CDT_UINT8:
				u8v = va_arg(ap, uint8_t *);
				vpv_len = sizeof(*u8v);
				vpv = u8v;
				break;
			case BPC_CDT_UINT16:
				u16v = va_arg(ap, uint16_t *);
				vpv_len = sizeof(*u16v);
				vpv = u16v;
				break;
			case BPC_CDT_UINT32:
				u32v = va_arg(ap, uint32_t *);
				vpv_len = sizeof(*u32v);
				vpv = u32v;
				break;
			case BPC_CDT_UINT64:
				u64v = va_arg(ap, uint64_t *);
				vpv_len = sizeof(*u64v);
				vpv = u64v;
				break;
			case BPC_CDT_BYTEARRAY:
				vpv_len = va_arg(ap, size_t);
				vpv = va_arg(ap, void *);
				break;
			case BPC_CDT_STRING:
				vpv_len = va_arg(ap, size_t);
				vpv_len--;
				cpv = va_arg(ap, char *);
				cpv[vpv_len] = '\0';
				vpv = cpv;
				break;
			case BPC_CDT_STRING_PTR:
				vpv_len = va_arg(ap, size_t);
				cppv = va_arg(ap, char **);
				if (*cppv == NULL) {
					cpv = *cppv = malloc(vpv_len + 1);
					cpv[vpv_len] = '\0';
				} else {
					cpv = *cppv;
				}
				vpv = cpv;
				break;
			default:
				return(0);
		}

		vpv_s = vpv;
		while (vpv_len) {
			read_ret = recv(sockid, vpv, vpv_len, MSG_WAITALL);

			if (read_ret <= 0) {
				return(0);
			}

			vpv_len -= read_ret;
			vpv += read_ret;
		}
		vpv = vpv_s;

		switch (typeid) {
			case BPC_CDT_UINT16:
				u16v = vpv;
				*u16v = ntohs(*u16v);
				break;
			case BPC_CDT_UINT32:
				u32v = vpv;
				*u32v = ntohl(*u32v);
				break;
			case BPC_CDT_UINT64:
				u64v = vpv;
				*u64v = ntohll(*u64v);
				break;
			default:
				break;
		}
	}
	va_end(ap);

	return(1);
}

int backuppcd_client_write(const int sockid, ...) {
	backuppc_clntdt_t typeid;
	va_list ap;
	uint8_t u8v;
	uint16_t u16v;
	uint32_t u32v;
	uint64_t u64v;
	void *vpv;
	char *cpv;
	size_t vpv_len;
	ssize_t write_ret;

	va_start(ap, sockid);
	while (1) {
		typeid = va_arg(ap, backuppc_clntdt_t);

		if (typeid == BPC_CDT_END) {
			break;
		}

		switch (typeid) {
			case BPC_CDT_UINT8:
				u8v = va_arg(ap, int);
				vpv_len = sizeof(u8v);
				vpv = &u8v;
				break;
			case BPC_CDT_UINT16:
				u16v = va_arg(ap, int);
				u16v = htons(u16v);
				vpv_len = sizeof(u16v);
				vpv = &u16v;
				break;
			case BPC_CDT_UINT32:
				u32v = va_arg(ap, uint32_t);
				u32v = htonl(u32v);
				vpv_len = sizeof(u32v);
				vpv = &u32v;
				break;
			case BPC_CDT_UINT64:
				u64v = va_arg(ap, uint64_t);
				u64v = htonll(u64v);
				vpv_len = sizeof(u64v);
				vpv = &u64v;
				break;
			case BPC_CDT_BYTEARRAY:
				vpv_len = va_arg(ap, size_t);
				vpv = va_arg(ap, void *);
				break;
			case BPC_CDT_STRING:
				cpv = va_arg(ap, char *);
				vpv_len = strlen(cpv);
				vpv = cpv;
				break;
			case BPC_CDT_STRING_PTR:
			default:
				return(0);
		}

		while (vpv_len) {
			write_ret = send(sockid, vpv, vpv_len, 0);

			if (write_ret < 0) {
				return(0);
			}

			vpv_len -= write_ret;
			vpv += write_ret;
		}
	}
	va_end(ap);

	return(1);
}



int backuppcd_client_auth(const int sockid, const char *username, const char *password) {
	uint8_t cmd_reply;
	uint8_t status;

	if (!backuppcd_client_write(sockid,
	                            BPC_CDT_UINT8, (uint8_t) BPC_CMD_AUTH,
	                            BPC_CDT_UINT16, (uint16_t) strlen(username),
	                            BPC_CDT_UINT16, (uint16_t) strlen(password),
	                            BPC_CDT_STRING, username,
	                            BPC_CDT_STRING, password,
	                            BPC_CDT_END)) {
		return(0);
	}

	if (!backuppcd_client_read(sockid,
	                           BPC_CDT_UINT8, (uint8_t *) &cmd_reply,
	                           BPC_CDT_UINT8, (uint8_t *) &status,
	                           BPC_CDT_END)) {
		return(0);
	}

	if (cmd_reply != BPC_CMD_AUTH_REPLY) {
		return(0);
	}

	if (status != BPC_STATUS_OKAY) {
		return(0);
	}

	return(1);
}

int backuppcd_client_target(const int sockid, const char *rootpath, const int opt_verbosity, const int opt_totals) {
	backuppc_cmd_t sendcmd;
	unsigned char pathbuf[8192], *outpathbuf = NULL, tarbuf[512], *shortname, *dirname;
	unsigned char *buf = NULL;
	uint32_t attr_sect_len, blocksize, pathnamelen, blocknum = 0;
	uint64_t filesize, byteswritten = 0;
	uint32_t bufsize = 0;
	uint32_t attr_uid, attr_gid, attr_ctime, attr_mtime, attr_devmajor, attr_devminor;
	uint32_t attrlen;
	uint32_t tar_checksum;
	uint32_t tarblocks = 0;
	uint16_t attrid;
	uint16_t attr_mode;
	uint8_t filetype, cmd;
	ssize_t write_ret;
	size_t writelen, bytes_to_copy;
	time_t totaltime, starttime;
	mode_t modeval;
	char *attr_linkdest, *attr_username, *attr_groupname, *attr_hrdlinkdest;
	char type_string[][7] = {"dir", "file", "syml", "sock", "fifo", "blk", "chr", "hrdl"};
	int tar_type;
	int i, x, skipfile;

	starttime = time(NULL);

	sendcmd = BPC_CMD_GET;

	if (!backuppcd_client_write(sockid,
	                            BPC_CDT_UINT8, (uint8_t) sendcmd,
	                            BPC_CDT_UINT8, (uint8_t) BPC_OPT_RECURSIVE,
	                            BPC_CDT_UINT32, (uint32_t) 0, /* exclude section length */
	                            BPC_CDT_UINT32, (uint32_t) 0, /* include section length */
	                            BPC_CDT_UINT32, (uint32_t) strlen(rootpath),
	                            BPC_CDT_STRING, (char *) rootpath,
	                            /* Exclude section is 0 bytes (XXX) */
	                            /* Include section is 0 bytes (XXX) */
	                            BPC_CDT_END)) {
		CHECKPOINT;
		return(0);
	}

	if (!backuppcd_client_read(sockid,
	                           BPC_CDT_UINT8, (uint8_t *) &cmd,
	                           BPC_CDT_END)) {
		CHECKPOINT;
		return(0);
	}

	/*
	 * If the server sends us a reply for something other than what we
	 * asked for, something went wrong, abort.
	 */
	if (cmd != (sendcmd | 0x80)) {
		CHECKPOINT;
		return(0);
	}

	/*
	 * Process every file entry in the reply.
	 */
	while (1) {
		if (!backuppcd_client_read(sockid,
		                           BPC_CDT_UINT8, (uint8_t *) &filetype,
		                           BPC_CDT_END)) {
			CHECKPOINT;
			return(0);
		}

		for (i = 0; i < sizeof(tarbuf); i++) {
			tarbuf[i] = 0;
		}

		if (filetype == 0xff) {
			break;
		}

		if (!backuppcd_client_read(sockid,
		                           BPC_CDT_UINT32, (uint32_t *) &attr_sect_len,
		                           BPC_CDT_UINT64, (uint64_t *) &filesize,
		                           BPC_CDT_UINT32, (uint32_t *) &blocksize,
		                           BPC_CDT_UINT32, (uint32_t *) &pathnamelen,
		                           BPC_CDT_END)) {
			CHECKPOINT;
			return(0);
		}


		if (pathnamelen >= sizeof(pathbuf)) {
			SPOTVAR_I(pathnamelen);
			CHECKPOINT;
			return(0);
		}

		if (!backuppcd_client_read(sockid,
		                           BPC_CDT_STRING, (size_t) (pathnamelen + 1), &pathbuf,
		                           BPC_CDT_END)) {
			CHECKPOINT;
			return(0);
		}

		attr_uid = 0;
		attr_gid = 0;
		attr_mode = 0;
		attr_ctime = 0;
		attr_mtime = 0;
		attr_devmajor = 0;
		attr_devminor = 0;
		attr_username = NULL;
		attr_groupname = NULL;
		attr_linkdest = NULL;
		attr_hrdlinkdest = NULL;

		/*
		 * Read the sent attributes.
		 * Currently this does not handle all attributes and any
		 * attribute that is not handled is not properly sunk, instead
		 * the data remains in the buffer to be mis-processed later.
		 * Fix this (XXX)
		 */
		if (attr_sect_len != 0) {
			while (1) {
				if (!backuppcd_client_read(sockid,
				                           BPC_CDT_UINT16, (uint16_t *) &attrid,
				                           BPC_CDT_END)) {
					CHECKPOINT;
					return(0);
				}

				if (attrid == 0xffff) {
					break;
				}

				if (!backuppcd_client_read(sockid,
				                           BPC_CDT_UINT32, (uint32_t *) &attrlen,
				                           BPC_CDT_END)) {
					CHECKPOINT;
					return(0);
				}

				switch ((backuppc_attrid_t) attrid) {
					case BPC_ATTRID_NOP:
						if (attrlen != 0) {
							CHECKPOINT;
							return(0);
						}
						break;
					case BPC_ATTRID_MTIME:
						if (!backuppcd_client_read(sockid,
						                           BPC_CDT_UINT32, (uint32_t *) &attr_mtime,
						                           BPC_CDT_END)) {
							CHECKPOINT;
							return(0);
						}
						break;
					case BPC_ATTRID_CTIME:
						if (!backuppcd_client_read(sockid,
						                           BPC_CDT_UINT32, (uint32_t *) &attr_ctime,
						                           BPC_CDT_END)) {
							CHECKPOINT;
							return(0);
						}
						break;
					case BPC_ATTRID_UID:
						if (!backuppcd_client_read(sockid,
						                           BPC_CDT_UINT32, (uint32_t *) &attr_uid,
						                           BPC_CDT_END)) {
							CHECKPOINT;
							return(0);
						}
						break;
					case BPC_ATTRID_GID:
						if (!backuppcd_client_read(sockid,
						                           BPC_CDT_UINT32, (uint32_t *) &attr_gid,
						                           BPC_CDT_END)) {
							CHECKPOINT;
							return(0);
						}
						break;
					case BPC_ATTRID_ACL:
						if (!backuppcd_client_read(sockid,
						                           BPC_CDT_UINT16, (uint16_t *) &attr_mode,
						                           BPC_CDT_END)) {
							CHECKPOINT;
							return(0);
						}

						break;
					case BPC_ATTRID_USER:
						if (attrlen > BPC_MAXPATH_LEN) {
							CHECKPOINT;
							return(0);
						}

						if (attr_username) {
							free(attr_username);
						}

						attr_username = NULL;

						if (!backuppcd_client_read(sockid,
						                           BPC_CDT_STRING_PTR, (size_t) attrlen, (char **) &attr_username,
						                           BPC_CDT_END)) {
							CHECKPOINT;
							return(0);
						}
						break;
					case BPC_ATTRID_GROUP:
						if (attrlen > BPC_MAXPATH_LEN) {
							CHECKPOINT;
							return(0);
						}

						if (attr_groupname) {
							free(attr_groupname);
						}

						attr_groupname = NULL;

						if (!backuppcd_client_read(sockid,
						                           BPC_CDT_STRING_PTR, (size_t) attrlen, (char **) &attr_groupname,
						                           BPC_CDT_END)) {
							CHECKPOINT;
							return(0);
						}
						break;
					case BPC_ATTRID_SYMLINKDEST:
						if (attrlen > BPC_MAXPATH_LEN) {
							CHECKPOINT;
							return(0);
						}

						if (attr_linkdest) {
							free(attr_linkdest);
						}

						attr_linkdest = NULL;

						if (!backuppcd_client_read(sockid,
						                           BPC_CDT_STRING_PTR, (size_t) attrlen, (char **) &attr_linkdest,
						                           BPC_CDT_END)) {
							CHECKPOINT;
							return(0);
						}
						break;
					case BPC_ATTRID_HRDLINKDEST:
						if (attrlen > BPC_MAXPATH_LEN) {
							CHECKPOINT;
							return(0);
						}

						if (attr_hrdlinkdest) {
							free(attr_hrdlinkdest);
						}

						attr_hrdlinkdest = NULL;

						if (!backuppcd_client_read(sockid,
						                           BPC_CDT_STRING_PTR, (size_t) attrlen, (char **) &attr_hrdlinkdest,
						                           BPC_CDT_END)) {
							CHECKPOINT;
							return(0);
						}
						break;
				}
			}
		}

		/*
		 * Split the filename into 2 parts, the directory name and
		 * the file name.  USTAR allows us to fit longer file names
		 * if we do this.
		 */
		shortname = strrchr(pathbuf, '/');
		if (shortname) {
			*shortname = '\0';
			shortname++;
			dirname = pathbuf;
		} else {
			shortname = pathbuf;
			dirname = "";
		}

		/*
		 * Do not attempt to fill the USTAR header with data that will
		 * not fit.  If an overflow condition is found, mark the file
		 * to be skipped and print a warning.
		 */
		skipfile = 0;
		if (strlen(shortname) >= 100 || strlen(dirname) >= 155) {
			fprintf(stderr, "Skipping %s/%s, pathname too long.\n", dirname, shortname);
			skipfile = 1;
		}
		if (filesize >= 0x200000000LLU) {
			fprintf(stderr, "Skipping %s/%s, file too large.\n", dirname, shortname);
			skipfile = 1;
		}

		/*
		 * Determine the POSIX "mode" value from the ACL attrs.
		 */
		modeval = 0;
		if (attr_mode) {
			if ((attr_mode & BPC_ACL_XUSR) == BPC_ACL_XUSR) {
				modeval |= S_IXUSR;
			}
			if ((attr_mode & BPC_ACL_WUSR) == BPC_ACL_WUSR) {
				modeval |= S_IWUSR;
			}
			if ((attr_mode & BPC_ACL_RUSR) == BPC_ACL_RUSR) {
				modeval |= S_IRUSR;
			}
#ifndef _USE_WIN32_
			if ((attr_mode & BPC_ACL_XGRP) == BPC_ACL_XGRP) {
				modeval |= S_IXGRP;
			}
			if ((attr_mode & BPC_ACL_WGRP) == BPC_ACL_WGRP) {
				modeval |= S_IWGRP;
			}
			if ((attr_mode & BPC_ACL_RGRP) == BPC_ACL_RGRP) {
				modeval |= S_IRGRP;
			}
			if ((attr_mode & BPC_ACL_XOTH) == BPC_ACL_XOTH) {
				modeval |= S_IXOTH;
			}
			if ((attr_mode & BPC_ACL_WOTH) == BPC_ACL_WOTH) {
				modeval |= S_IWOTH;
			}
			if ((attr_mode & BPC_ACL_ROTH) == BPC_ACL_ROTH) {
				modeval |= S_IROTH;
			}
			if ((attr_mode & BPC_ACL_STCK) == BPC_ACL_STCK) {
				modeval |= S_ISVTX;
			}
			if ((attr_mode & BPC_ACL_SGID) == BPC_ACL_SGID) {
				modeval |= S_ISGID;
			}
			if ((attr_mode & BPC_ACL_SUID) == BPC_ACL_SUID) {
				modeval |= S_ISUID;
			}
#endif
		}

		/*
		 * Determine the USTAR "type" flag from the file type, and
		 * continue adding bits to the POSIX "mode" value.
		 */
		tar_type = 0;
		if (filetype == BPC_DIRENT_FILE) {
			modeval |= S_IFREG;
			tar_type = 0;
		}
#ifndef _USE_WIN32_
		if (filetype == BPC_DIRENT_SYMLINK) {
			modeval |= S_IFLNK;
			tar_type = 1;
		}
		if (filetype == BPC_DIRENT_SOCKET) {
			modeval |= S_IFSOCK;
			tar_type = 2; /* ??? */
		}
#endif
		if (filetype == BPC_DIRENT_DIR) {
			modeval |= S_IFDIR;
			tar_type = 5;
		}
		if (filetype == BPC_DIRENT_BDEV) {
			modeval |= S_IFBLK;
			tar_type = 4;
		}
		if (filetype == BPC_DIRENT_CDEV) {
			modeval |= S_IFCHR;
			tar_type = 3;
		}
		if (filetype == BPC_DIRENT_FIFO) {
			modeval |= S_IFIFO;
			tar_type = 6;
		}

		/*
		 * Only send a header for this file if we intend to send the
		 * data for it.
		 */
		if (!skipfile) {
			/*
			 * Generate the USTAR header in the buffer.
			 */
			memcpy(tarbuf, shortname, strlen(shortname));
			sprintf(tarbuf + 100, "%07o", modeval);
			sprintf(tarbuf + 108, "%07o", attr_uid);
			sprintf(tarbuf + 116, "%07o", attr_gid);
			sprintf(tarbuf + 124, "%011o", (unsigned int) filesize);
			sprintf(tarbuf + 136, "%011o", attr_mtime);
			sprintf(tarbuf + 148, "        ");
			sprintf(tarbuf + 156, "%i", tar_type);
			if (filetype == BPC_DIRENT_SYMLINK) {
				sprintf(tarbuf + 157, attr_linkdest);
			}
			sprintf(tarbuf + 257, "ustar");
			sprintf(tarbuf + 263, "  ");
			if (attr_username) {
				sprintf(tarbuf + 265, "%s", attr_username);
			}
			if (attr_groupname) {
				sprintf(tarbuf + 297, "%s", attr_groupname);
			}
			if (attr_devmajor) {
				sprintf(tarbuf + 329, "%07o", attr_devmajor);
			}
			if (attr_devminor) {
				sprintf(tarbuf + 337, "%07o", attr_devminor);
			}
			sprintf(tarbuf + 345, "%s", dirname);

			/* Calculate checksum. */
			tar_checksum = 0;
			for (i = 0; i < sizeof(tarbuf); i++) {
				tar_checksum += tarbuf[i];
			}
			sprintf(tarbuf + 148, "%06o", tar_checksum);

			write_ret = write(STDOUT_FILENO, tarbuf, sizeof(tarbuf));
			if (write_ret != sizeof(tarbuf)) {
				CHECKPOINT;
				return(0);
			}

			/*
			 * Calculate the number of blocks this file will need
			 * including the 1-block header.
			 */
			tarblocks += 1 + ((filesize + sizeof(tarbuf) - 1) / sizeof(tarbuf));
		}

		switch (opt_verbosity) {
			case 0:
				break;
			case 1:
				fprintf(stderr, ".%s/%s\n", dirname, shortname);
				break;
			case 2:
				fprintf(stderr, "[%4s] %04o %6lu %6lu %10llu %12lu %s/%s",
				       type_string[filetype],
				       (unsigned int) attr_mode,
				       (unsigned long) attr_uid,
				       (unsigned long) attr_gid,
				       (unsigned long long) filesize,
				       (unsigned long) attr_mtime,
				       dirname,
				       shortname);
				if (filetype == BPC_DIRENT_SYMLINK) {
					fprintf(stderr, " -> %s", attr_linkdest);
				}
				fprintf(stderr, "\n");
				break;
		}

		outpathbuf = pathbuf + 1;

		byteswritten = 0;

		/*
		 * Ensure the buffer is large enough to hold all the data.
		 * If the buffer starts changing a lot between files, we
		 * should make this grow-only.  It's fine for now.
		 */
		if (bufsize != blocksize) {
			if (buf) {
				free(buf);
			}

			bufsize = blocksize;

			buf = malloc(bufsize);

			if (!buf) {
				CHECKPOINT;
				return(0);
			}
		}

		/*
		 * Read the file data from the server.  We must do this even
		 * if we are not planning on sending it in USTAR format.
		 * The data comes in "blocksize" sized blocks and may be
		 * larger than the actual file.
		 */
		while (1) {
			if (!backuppcd_client_read(sockid,
			                          BPC_CDT_UINT32, (uint32_t *) &blocknum,
			                          BPC_CDT_END)) {
				CHECKPOINT;
				return(0);
			}

			if (blocknum == 0xffffffff) {
				break;
			}

			if (!backuppcd_client_read(sockid,
			                          BPC_CDT_BYTEARRAY, (size_t) blocksize, (void *) buf,
			                          BPC_CDT_END)) {

				CHECKPOINT;
				return(0);
			}

			/*
			 * Determine the amount of data in this block that we
			 * should write.  Currently this fails to deal with
			 * sparse files and assumes that an entire file is
			 * with all blocks in order.  This will need to change.
			 * (XXX)
			 */
			if ((byteswritten + blocksize) > filesize) {
				if (filesize > byteswritten) {
					writelen = filesize - byteswritten;
				} else {
					writelen = 0;
				}
			} else {
				writelen = blocksize;
			}

			if (!skipfile) {
				/*
				 * Send the file data if there is any.
				 */
				if (writelen > 0) {
					x = 0;
					while (x < writelen) {
						write_ret = write(STDOUT_FILENO, buf + x, writelen - x);

						if (write_ret <= 0) {
							CHECKPOINT;
							return(0);
						}

						x += write_ret;
					}
				}
			}

			byteswritten += blocksize;
		}

		if (!skipfile) {
			/*
			 * Round the file to 512-byte blocks.
			 */
			bytes_to_copy = sizeof(tarbuf) - (filesize % sizeof(tarbuf));
			if (bytes_to_copy != sizeof(tarbuf)) {
				for (i = 0; i < bytes_to_copy; i++) {
					tarbuf[i] = 0;
				}
				write_ret = write(STDOUT_FILENO, tarbuf, bytes_to_copy);
				if (write_ret != bytes_to_copy) {
					CHECKPOINT;
					return(0);
				}
			}
		}

		/* Clean up any allocated memory, if needed. */
		if (attr_linkdest) {
			free(attr_linkdest);
		}
		if (attr_username) {
			free(attr_username);
		}
		if (attr_groupname) {
			free(attr_groupname);
		}
		if (attr_hrdlinkdest) {
			free(attr_hrdlinkdest);
		}
	}

	/*
	 * USTAR end of archive is two empty blocks.
	 */
	write(STDOUT_FILENO, tarbuf, sizeof(tarbuf));
	write(STDOUT_FILENO, tarbuf, sizeof(tarbuf));
	tarblocks += 2;

	if (opt_totals) {
		totaltime = time(NULL) - starttime;
		if (totaltime == 0) {
			totaltime = 1;
		}

		fprintf(stderr, "Total bytes written: %lu (%luKiB, %fKiB/s)\n", (unsigned long) (tarblocks * sizeof(tarbuf)), (unsigned long) (tarblocks * sizeof(tarbuf) / 1024), (((double) tarblocks * sizeof(tarbuf)) / 1024.0) / ((double) totaltime));
	}
 
	CHECKPOINT;
	return(1);
}

int backuppcd_client(const char *host, const int port, const char *username, const char *password, const char *rootpath) {
	int sockid;

	sockid = net_connect_tcp(host, port);

	if (sockid < 0) {
		return(-1);
	}

	if (!backuppcd_client_auth(sockid, username, password)) {
		return(-1);
	}

	if (!backuppcd_client_target(sockid, rootpath, 1, 1)) {
		return(-1);
	}

	return(0);
}
