/*-
 * Copyright (c) 2003-2005 MAEKAWA Masahide <maekawa@cvsync.org>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the author nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#include <sys/types.h>

#include <stdio.h>

#include <errno.h>
#include <grp.h>
#include <limits.h>
#include <pwd.h>
#include <string.h>
#include <unistd.h>

#include "compat_stdbool.h"
#include "compat_stdint.h"
#include "compat_stdio.h"
#include "compat_inttypes.h"
#include "compat_limits.h"

#include "attribute.h"
#include "cvsync.h"
#include "cvsync_attr.h"
#include "filetypes.h"
#include "logmsg.h"
#include "scanfile.h"

#include "defs.h"

#define	CVSUP_MAXATTRSIZE	256

size_t cvsup_escape_name(struct scanfile_attr *, void *, size_t);

static char cvsup_userinfo[CVSUP_MAXATTRSIZE]; /* user/group name cache */
static int cvsup_userinfo_length;

bool
cvsup_init(const char *user, const char *group)
{
	struct group *gr;
	struct passwd *pw;
	size_t ulen, glen;
	int wn;

	if (user == NULL) {
		if ((pw = getpwuid(getuid())) == NULL) {
			logmsg_err("Who am I?");
			return (false);
		}
		user = pw->pw_name;
	}
	ulen = strlen(user);

	if (group == NULL) {
		if ((gr = getgrgid(getgid())) == NULL) {
			logmsg_err("Who am I?");
			return (false);
		}
		group = gr->gr_name;
	}
	glen = strlen(group);

	wn = snprintf(cvsup_userinfo, sizeof(cvsup_userinfo),
		      "%lu#%.*s%lu#%.*s", (unsigned long)ulen, (int)ulen,
		      user, (unsigned long)glen, (int)glen, group);
	if ((wn <= 0) || ((size_t)wn >= sizeof(cvsup_userinfo))) {
		logmsg_err("Too long user/group name");
		return (false);
	}

	cvsup_userinfo_length = wn;

	return (true);
}

bool
cvsup_write_header(int fd, time_t mtime)
{
	char head[CVSUP_MAXATTRSIZE];
	size_t len;
	ssize_t wrn;
	int wn;

	wn = snprintf(head, sizeof(head), "F 5 %ld\n", (long)mtime);
	if ((wn <= 0) || ((size_t)wn >= sizeof(head))) {
		logmsg_err("Header: %s", strerror(EINVAL));
		return (false);
	}

	len = (size_t)wn;
	if ((wrn = write(fd, head, len)) == -1) {
		logmsg_err("Header: %s", strerror(errno));
		return (false);
	}
	if ((size_t)wrn != len) {
		logmsg_err("Header: write error");
		return (false);
	}

	return (true);
}

bool
cvsup_write_dirdown(int fd, struct scanfile_attr *attr)
{
	char attrstr[CVSUP_MAXATTRSIZE], name[CVSUP_MAXATTRSIZE];
	size_t namelen, len;
	ssize_t wrn;
	int wn;

	if ((namelen = cvsup_escape_name(attr, name,
					 sizeof(name))) == (size_t)-1) {
		return (false);
	}

	wn = snprintf(attrstr, sizeof(attrstr), "D %.*s\n",
		      (int)namelen, name);
	if ((wn <= 0) || ((size_t)wn >= sizeof(attrstr))) {
		logmsg_err("Too long DirDown attribute");
		return (false);
	}

	len = (size_t)wn;
	if ((wrn = write(fd, attrstr, len)) == -1) {
		logmsg_err("DirDown: %s", strerror(errno));
		return (false);
	}
	if ((size_t)wrn != len) {
		logmsg_err("DirDown: write error");
		return (false);
	}

	return (true);
}

bool
cvsup_write_dirup(int fd, struct scanfile_attr *attr)
{
	struct cvsync_attr cattr;
	char attrstr[CVSUP_MAXATTRSIZE], name[CVSUP_MAXATTRSIZE];
	size_t namelen, len;
	ssize_t wrn;
	int mlen, wn;

	if (!attr_rcs_decode_dir(attr->a_aux, attr->a_auxlen, &cattr))
		return (false);

	if ((namelen = cvsup_escape_name(attr, name,
					 sizeof(name))) == (size_t)-1) {
		return (false);
	}

	mlen = snprintf(attrstr, sizeof(attrstr), "%o", cattr.ca_mode);

	wn = snprintf(attrstr, sizeof(attrstr),
		      "U %.*s 3#1e11#2%.*s%d#%o1#0\n",
		      (int)namelen, name, cvsup_userinfo_length,
		      cvsup_userinfo, mlen, cattr.ca_mode);
	if ((wn <= 0) || ((size_t)wn >= sizeof(attrstr))) {
		logmsg_err("Too long DirDown attribute");
		return (false);
	}

	len = (size_t)wn;
	if ((wrn = write(fd, attrstr, len)) == -1) {
		logmsg_err("DirDown: %s", strerror(errno));
		return (false);
	}
	if ((size_t)wrn != len) {
		logmsg_err("DirDown: write error");
		return (false);
	}

	return (true);
}

bool
cvsup_write_file(int fd, struct scanfile_attr *attr)
{
	struct cvsync_attr cattr;
	char attrstr[CVSUP_MAXATTRSIZE], name[CVSUP_MAXATTRSIZE];
	size_t namelen, len;
	ssize_t wrn;
	int tlen, slen, mlen, wn;

	if (!attr_rcs_decode_file(attr->a_aux, attr->a_auxlen, &cattr))
		return (false);

	if ((namelen = cvsup_escape_name(attr, name,
					 sizeof(name))) == (size_t)-1) {
		return (false);
	}

	tlen = snprintf(attrstr, sizeof(attrstr), "%lld",
			(long long)cattr.ca_mtime);
	slen = snprintf(attrstr, sizeof(attrstr), "%llu",
			(unsigned long long)cattr.ca_size);
	mlen = snprintf(attrstr, sizeof(attrstr), "%o", cattr.ca_mode);

	wn = snprintf(attrstr, sizeof(attrstr),
		      "V %.*s 3#1e71#1%d#%lld%d#%llu%.*s%d#%o1#0\n",
		      (int)namelen, name, tlen, (long long)cattr.ca_mtime,
		      slen, (unsigned long long)cattr.ca_size,
		      cvsup_userinfo_length, cvsup_userinfo,
		      mlen, cattr.ca_mode);
	if ((wn <= 0) || ((size_t)wn >= sizeof(attrstr))) {
		logmsg_err("Too long File attribute");
		return (false);
	}

	len = (size_t)wn;
	if ((wrn = write(fd, attrstr, len)) == -1) {
		logmsg_err("File: %s", strerror(errno));
		return (false);
	}
	if ((size_t)wrn != len) {
		logmsg_err("File: write error");
		return (false);
	}

	return (true);
}

bool
cvsup_write_rcs(int fd, struct scanfile_attr *attr)
{
	struct cvsync_attr cattr;
	char attrstr[CVSUP_MAXATTRSIZE], name[CVSUP_MAXATTRSIZE], c;
	size_t namelen, len;
	ssize_t wrn;
	int tlen, mlen, wn;

	if (!attr_rcs_decode_rcs(attr->a_aux, attr->a_auxlen, &cattr))
		return (false);

	if (attr->a_type == FILETYPE_RCS)
		c = 'V';
	else /* FILETYPE_RCS_ATTIC */
		c = 'v';

	if ((namelen = cvsup_escape_name(attr, name,
					 sizeof(name))) == (size_t)-1) {
		return (false);
	}

	tlen = snprintf(attrstr, sizeof(attrstr), "%lld",
			(long long)cattr.ca_mtime);
	mlen = snprintf(attrstr, sizeof(attrstr), "%o", cattr.ca_mode);

	wn = snprintf(attrstr, sizeof(attrstr),
		      "%c %.*s 3#1e31#1%d#%lld%.*s%d#%o1#0\n",
		      c, (int)namelen, name, tlen, (long long)cattr.ca_mtime,
		      cvsup_userinfo_length, cvsup_userinfo,
		      mlen, cattr.ca_mode);
	if ((wn <= 0) || ((size_t)wn >= sizeof(attrstr))) {
		logmsg_err("Too long RCS attribute");
		return (false);
	}

	len = (size_t)wn;
	if ((wrn = write(fd, attrstr, len)) == -1) {
		logmsg_err("RCS: %s", strerror(errno));
		return (false);
	}
	if ((size_t)wrn != len) {
		logmsg_err("RCS: write error");
		return (false);
	}

	return (true);
}

bool
cvsup_write_symlink(int fd, struct scanfile_attr *attr)
{
	char attrstr[CVSUP_MAXATTRSIZE], name[CVSUP_MAXATTRSIZE];
	size_t namelen, len;
	ssize_t wrn;
	int wn;

	if ((namelen = cvsup_escape_name(attr, name,
					 sizeof(name))) == (size_t)-1) {
		return (false);
	}

	wn = snprintf(attrstr, sizeof(attrstr), "V %.*s 1#91#5%lu#%.*s\n",
		      (int)namelen, name, (unsigned long)attr->a_auxlen,
		      (int)attr->a_auxlen, (char *)attr->a_aux);
	if ((wn <= 0) || ((size_t)wn >= sizeof(attrstr))) {
		logmsg_err("Too long Symlink attribute");
		return (false);
	}

	len = (size_t)wn;
	if ((wrn = write(fd, attrstr, len)) == -1) {
		logmsg_err("Symlink: %s", strerror(errno));
		return (false);
	}
	if ((size_t)wrn != len) {
		logmsg_err("Symlink: write error");
		return (false);
	}

	return (true);
}

size_t
cvsup_escape_name(struct scanfile_attr *attr, void *buffer, size_t length)
{
	char *sp = attr->a_name, *bp = sp + attr->a_namelen, *p = buffer;
	size_t namelen = 0;

	while (sp < bp) {
		switch (*sp) {
		case '\\':
			if (namelen + 2 > length) {
				logmsg_err("No space to escape '\\'");
				return ((size_t)-1);
			}
			*p++ = '\\';
			*p++ = '\\';
			namelen += 2;
			break;
		case ' ':
			if (namelen + 2 > length) {
				logmsg_err("No space to escape ' '");
				return ((size_t)-1);
			}
			*p++ = '\\';
			*p++ = '_';
			namelen += 2;
			break;
		default:
			if (namelen >= length) {
				logmsg_err("No space to put any characters");
				return ((size_t)-1);
			}
			*p++ = *sp;
			namelen++;
			break;
		}
		sp++;
	}

	return (namelen);
}


syntax highlighted by Code2HTML, v. 0.9.1