/*
 * Copyright (c) 1998,1999,2000
 *	Traakan, Inc., Los Altos, CA
 *	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 unmodified, 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.
 *
 * 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.
 */

/*
 * Project:  NDMJOB
 * Ident:    $Id: $
 *
 * Description:
 *
 */

#include "ndmos.h"
#include "wraplib.h"




int
wrap_main (int ac, char *av[], struct wrap_ccb *wccb)
{
	int		rc;

	rc = wrap_process_args (ac, av, wccb);
	if (rc)
		return rc;

	rc = wrap_main_start_index_file (wccb);
	if (rc)
		return rc;

	rc = wrap_main_start_image_file (wccb);
	if (rc)
		return rc;


	return 0;
}

int
wrap_main_start_index_file (struct wrap_ccb *wccb)
{
	char *		filename = wccb->I_index_file_name;
	FILE *		fp;

	if (!filename)
		return 0;

	if (filename[0] == '#') {
		int	fd = atoi (filename+1);

		if (fd < 2 || fd > 100) {
			/* huey! */
			strcpy (wccb->errmsg, "bad -I#N");
			return -1;
		}
		fp = fdopen (fd, "w");
		if (!fp) {
			sprintf (wccb->errmsg, "failed fdopen %s", filename);
			return -1;
		}
	} else {
		fp = fopen (filename, "w");
		if (!fp) {
			sprintf (wccb->errmsg, "failed open %s", filename);
			return -1;
		}
	}

	wccb->index_fp = fp;

	return 0;
}

int
wrap_main_start_image_file (struct wrap_ccb *wccb)
{
	char *		filename = wccb->f_file_name;
	int		fd, o_mode;

	switch (wccb->op) {
	case WRAP_CCB_OP_BACKUP:
		o_mode = O_CREAT | O_WRONLY;
		break;

	case WRAP_CCB_OP_RECOVER:
	case WRAP_CCB_OP_RECOVER_FILEHIST:
		o_mode = O_RDONLY;
		break;

	default:
		abort();
		return -1;
	}

	if (!filename)
		filename = "-";

	if (strcmp (filename, "-") == 0) {
		if (wccb->op == WRAP_CCB_OP_BACKUP) {
			fd = 1;
		} else {
			fd = 0;
		}
	} else if (filename[0] == '#') {
		fd = atoi (filename+1);

		if (fd < 2 || fd > 100) {
			/* huey! */
			strcpy (wccb->errmsg, "bad -f#N");
			return -1;
		}
	} else {
		fd = open (filename, o_mode, 0666);
		if (fd < 0) {
			sprintf (wccb->errmsg, "failed open %s", filename);
			return -1;
		}
	}

	wccb->data_conn_fd = fd;

	return 0;
}

void
wrap_log (struct wrap_ccb *wccb, char *fmt, ...)
{
	va_list		ap;
	char		buf[4096];

	if (!wccb->index_fp && wccb->d_debug < 1)
		return;

	sprintf (buf, "%04d ", ++wccb->log_seq_num);

	va_start (ap, fmt);
	vsnprintf (buf+5, sizeof(buf)-5, fmt, ap);
	va_end (ap);

	if (wccb->index_fp)
		wrap_send_log_message (wccb->index_fp, buf);

	if (wccb->d_debug > 0)
		fprintf (stderr, "LOG: %s\n", buf);
}

int
wrap_set_error (struct wrap_ccb *wccb, int error)
{
	if (error == 0)
		error = -3;

	wccb->error = error;

	return wccb->error;
}

int
wrap_set_errno (struct wrap_ccb *wccb)
{
	return wrap_set_error (wccb, errno);
}


/*
 * wrap -c [-B TYPE] [-d N] [-I FILE] [-E NAME=VALUE ...]
 * wrap -x [-B TYPE] [-d N] [-I FILE] [-E NAME=VALUE ...]
 *		ORIGINAL_NAME @pos NEW_NAME ...
 * wrap -t [-B TYPE] [-d N] [-I FILE] [-E NAME=VALUE ...]
 *		ORIGINAL_NAME @pos
 */

int
wrap_process_args (int argc, char *argv[], struct wrap_ccb *wccb)
{
	int			c;
	enum wrap_ccb_op	op;
	char *			p;

	NDMOS_MACRO_ZEROFILL (wccb);

	wccb->progname = argv[0];

	if (argc < 2) {
		strcpy (wccb->errmsg, "too few arguments");
		return -1;
	}

	while ((c = getopt (argc, argv, "cxtB:d:I:E:f:o:")) != EOF) {
	    switch (c) {
	    case 'c':
		op = WRAP_CCB_OP_BACKUP;
		goto set_op;

	    case 't':
		op = WRAP_CCB_OP_RECOVER_FILEHIST;
		goto set_op;

	    case 'x':
		op = WRAP_CCB_OP_RECOVER;
		goto set_op;

	    set_op:
		if (wccb->op != WRAP_CCB_OP_NONE) {
			strcpy (wccb->errmsg, "only one of -c, -x, -t");
			return -1;
		}
		wccb->op = op;
		break;

	    case 'B':
		if (wccb->B_butype) {
			strcpy (wccb->errmsg, "only one -B allowed");
			return -1;
		}
		wccb->B_butype = optarg;
		break;

	    case 'd':
		wccb->d_debug = atoi(optarg);
		break;

	    case 'E':
		if (wccb->n_env >= WRAP_MAX_ENV) {
			strcpy (wccb->errmsg, "-E overflow");
			return -1;
		}
		p = strchr (optarg, '=');
		if (p) {
			*p++ = 0;
		} else {
			p = "";
		}
		wccb->env[wccb->n_env].name = optarg;
		wccb->env[wccb->n_env].value = p;
		wccb->n_env++;
		break;

	    case 'f':
		if (wccb->f_file_name) {
			strcpy (wccb->errmsg, "only one -f allowed");
			return -1;
		}
		wccb->f_file_name = optarg;
		break;

	    case 'I':
		if (wccb->I_index_file_name) {
			strcpy (wccb->errmsg, "only one -I allowed");
			return -1;
		}
		wccb->I_index_file_name = optarg;
		break;

	    case 'o':
		if (wccb->n_o_option >= WRAP_MAX_O_OPTION) {
			strcpy (wccb->errmsg, "-o overflow");
			return -1;
		}
		wccb->o_option[wccb->n_o_option] = optarg;
		wccb->n_o_option++;
		break;

	    default:
		strcpy (wccb->errmsg, "unknown option");
		return -1;
	    }
	}

	switch (wccb->op) {
	default:
		abort();	/* just can't happen */

	case WRAP_CCB_OP_NONE:
		strcpy (wccb->errmsg, "one of -c, -x, or -t required");
		return -1;

	case WRAP_CCB_OP_BACKUP:
		if (optind < argc) {
			strcpy (wccb->errmsg, "extra args not allowed for -c");
			return -1;
		}
		break;

	case WRAP_CCB_OP_RECOVER:
	case WRAP_CCB_OP_RECOVER_FILEHIST:
		break;
	}

	for (c = optind; c+2 < argc; c += 3) {
		p = argv[c+1];

		if (p[0] != '@') {
			sprintf (wccb->errmsg, "malformed fhinfo %s", p);
			return -1;
		}

		if (wccb->n_file >= WRAP_MAX_FILE) {
			strcpy (wccb->errmsg, "file table overflow");
			return -1;
		}

		if (strcmp (p, "@-") == 0) {
			wccb->file[wccb->n_file].fhinfo = WRAP_INVALID_FHINFO;
		} else {
			wccb->file[wccb->n_file].fhinfo =
					NDMOS_API_STRTOLL (p+1, &p, 0);
			if (*p != 0) {
				sprintf(wccb->errmsg,"malformed fhinfo %s",p);
				return -1;
			}
		}

		wccb->file[wccb->n_file].original_name = argv[c];
		wccb->file[wccb->n_file].save_to_name = argv[c+2];

		wccb->n_file++;
	}

	if (c < argc) {
		strcpy (wccb->errmsg, "superfluous args at end");
		return -1;
	}

	p = wrap_find_env (wccb, "HIST");
	if (p) {
		switch (*p) {
		case 'y': case 'Y':
			p = wrap_find_env (wccb, "HIST_TYPE");
			if (!p) {
				p = "y";
			}
			break;
		}

		switch (*p) {
		case 'y': case 'Y':
			wccb->hist_enable = 'y';
			break;

		case 'd': case 'D':
			wccb->hist_enable = 'd';
			break;

		case 'f': case 'F':
			wccb->hist_enable = 'f';
			break;

		default:
			/* gripe? */
			break;
		}
	}

	p = wrap_find_env (wccb, "DIRECT");
	if (p) {
		if (*p == 'y') {
			wccb->direct_enable = 1;
		}
	}

	p = wrap_find_env (wccb, "FILESYSTEM");
	if (!p)
		p = wrap_find_env (wccb, "PREFIX");
	if (!p)
		p = "/";

	wccb->backup_root = p;

	return 0;
}

char *
wrap_find_env (struct wrap_ccb *wccb, char *name)
{
	int		i;

	for (i = 0; i < wccb->n_env; i++) {
		if (strcmp (wccb->env[i].name, name) == 0)
			return wccb->env[i].value;
	}

	return 0;
}







int
wrap_cmd_add_with_escapes (char *cmd, char *word, char *special)
{
	char *		cmd_lim = &cmd[WRAP_MAX_COMMAND-3];
	char *		p;
	int		c;

	p = cmd;
	while (*p) p++;
	if (p != cmd) *p++ = ' ';

	while ((c = *word++) != 0) {
		if (p >= cmd_lim)
			return -1;	/* overflow */
		if (c == '\\' || strchr (special, c))
			*p++ = '\\';
		*p++ = c;
	}
	*p = 0;

	return 0;
}

int
wrap_cmd_add_with_sh_escapes (char *cmd, char *word)
{
	return wrap_cmd_add_with_escapes (cmd, word, " \t`'\"*?[]$");
}

int
wrap_cmd_add_allow_file_wildcards (char *cmd, char *word)
{
	return wrap_cmd_add_with_escapes (cmd, word, " \t`'\"$");
}



int
wrap_pipe_fork_exec (char *cmd, int fdmap[3])
{
	int			pipes[3][2];
	int			child_fdmap[3];
	int			nullfd = -1;
	int			i;
	int			rc = -1;

	for (i = 0; i < 3; i++) {
		pipes[i][0] = -1;
		pipes[i][1] = -1;
		child_fdmap[i] = -1;
	}

	for (i = 0; i < 3; i++) {
		if (fdmap[i] >= 0) {
			child_fdmap[i] = fdmap[i];
			continue;
		}
		switch (fdmap[i]) {
		case WRAP_FDMAP_DEV_NULL:
			if (nullfd < 0) {
				nullfd = open ("/dev/null", 2);
				if (nullfd < 0) {
					goto bail_out;
				}
			}
			child_fdmap[i] = nullfd;
			break;

		case WRAP_FDMAP_INPUT_PIPE:
			rc = pipe (pipes[i]);
			if (rc != 0) {
				goto bail_out;
			}
			child_fdmap[i] = pipes[i][0];
			break;

		case WRAP_FDMAP_OUTPUT_PIPE:
			rc = pipe (pipes[i]);
			if (rc != 0) {
				goto bail_out;
			}
			child_fdmap[i] = pipes[i][1];
			break;

		default:
			goto bail_out;
		}
	}

	rc = fork();
	if (rc < 0) {
		goto bail_out;
	}

	if (rc == 0) {
		/* child */
		dup2 (child_fdmap[2], 2);
		dup2 (child_fdmap[1], 1);
		dup2 (child_fdmap[0], 0);

		for (rc = 3; rc < 100; rc++) close(rc);

		execl ("/bin/sh", "sh", "-c", cmd, NULL);

		fprintf (stderr, "EXEC FAILED %s\n", cmd);
		exit(127);
	}

	if (nullfd >= 0)
		close (nullfd);

	for (i = 0; i < 3; i++) {
		if (fdmap[i] >= 0) {
			continue;
		}
		switch (fdmap[i]) {
		case WRAP_FDMAP_DEV_NULL:
			break;

		case WRAP_FDMAP_INPUT_PIPE:
			close (pipes[i][0]);
			fdmap[i] = pipes[i][1];
			break;

		case WRAP_FDMAP_OUTPUT_PIPE:
			close (pipes[i][1]);
			fdmap[i] = pipes[i][0];
			break;

		default:
			abort();
		}
	}

	return rc;	/* PID */

  bail_out:
	if (nullfd >= 0)
		close (nullfd);

	for (i = 0; i < 3; i++) {
		if (pipes[i][0] >= 0)
			close (pipes[i][0]);
		if (pipes[i][1] >= 0)
			close (pipes[i][1]);
	}

	return -1;
}




int
wrap_parse_msg (char *buf, struct wrap_msg_buf *wmsg)
{
	int		c1, c2;

	c1 = buf[0];
	c2 = buf[1];

	if (buf[2] != ' ') {
		return -1;
	}

	if (c1 == 'L' && c2 == 'x') {		/* log_message */
		return wrap_parse_log_message_msg (buf, wmsg);
	}

	if (c1 == 'H' && c2 == 'F') {		/* add_file */
		return wrap_parse_add_file_msg (buf, wmsg);
	}

	if (c1 == 'H' && c2 == 'D') {		/* add_dirent */
		return wrap_parse_add_dirent_msg (buf, wmsg);
	}

	if (c1 == 'H' && c2 == 'N') {		/* add_node */
		return wrap_parse_add_node_msg (buf, wmsg);
	}

	if (c1 == 'D' && c2 == 'E') {		/* add_env */
		return wrap_parse_add_env_msg (buf, wmsg);
	}

	if (c1 == 'D' && c2 == 'R') {		/* data_read */
		return wrap_parse_data_read_msg (buf, wmsg);
	}

	return -1;
}

int
wrap_parse_log_message_msg (char *buf, struct wrap_msg_buf *wmsg)
{
	struct wrap_log_message *res = &wmsg->body.log_message;
	char *			scan = buf+3;
	int			rc;

	wmsg->msg_type = WRAP_MSGTYPE_LOG_MESSAGE;

	while (*scan && *scan == ' ')
		scan++;

	rc = wrap_cstr_to_str (scan, res->message, sizeof res->message);
	if (rc < 0) return -2;

	return 0;
}

int
wrap_send_log_message (FILE *fp, char *message)
{
	struct wrap_msg_buf	wmsg;
	struct wrap_log_message *res = &wmsg.body.log_message;

	if (!fp) return -1;

	wrap_cstr_from_str (message, res->message, sizeof res->message);
	fprintf (fp, "Lx %s\n", res->message);

	return 0;
}

int
wrap_parse_add_file_msg (char *buf, struct wrap_msg_buf *wmsg)
{
	struct wrap_add_file *	res = &wmsg->body.add_file;
	char *			scan = buf+3;
	char *			p;
	int			rc;

	wmsg->msg_type = WRAP_MSGTYPE_ADD_FILE;

	res->fstat.valid = 0;
	res->fhinfo = WRAP_INVALID_FHINFO;

	while (*scan && *scan == ' ')
		scan++;
	if (*scan == 0)
		return -1;

	p = scan;
	while (*scan && *scan != ' ')
		scan++;

	if (*scan) {
		*scan = 0;
		rc = wrap_cstr_to_str (p, res->path, sizeof res->path);
		*scan++ = ' ';
	} else {
		rc = wrap_cstr_to_str (p, res->path, sizeof res->path);
	}
	if (rc < 0) return -2;

	while (*scan) {
		p = scan+1;
		switch (*scan) {
		case ' ':
			scan++;
			continue;

		case '@':
			res->fhinfo = NDMOS_API_STRTOLL (p, &scan, 0);
			break;

		default:
			rc = wrap_parse_fstat_subr(&scan, &res->fstat);
			if (rc < 0)
				return rc;
			break;
		}

		if (*scan != ' ' && *scan != 0) {
			/* bogus */
			return -1;
		}
	}

	return 0;
}

int
wrap_send_add_file (FILE *fp, char *path, unsigned long long fhinfo,
  struct wrap_fstat *fstat)
{
	struct wrap_msg_buf	wmsg;
	struct wrap_add_file *	res = &wmsg.body.add_file;

	if (!fp) return -1;

	wrap_cstr_from_str (path, res->path, sizeof res->path);
	fprintf (fp, "HF %s", res->path);

	if (fhinfo != WRAP_INVALID_FHINFO)
		fprintf (fp, " @%llu", fhinfo);

	wrap_send_fstat_subr (fp, fstat);

	fprintf (fp, "\n");

	return 0;
}

int
wrap_parse_add_dirent_msg (char *buf, struct wrap_msg_buf *wmsg)
{
	struct wrap_add_dirent *res = &wmsg->body.add_dirent;
	char *			scan = buf+3;
	char *			p;
	int			rc;

	wmsg->msg_type = WRAP_MSGTYPE_ADD_DIRENT;

	res->fhinfo = WRAP_INVALID_FHINFO;

	while (*scan && *scan == ' ')
		scan++;
	if (*scan == 0)
		return -1;

	res->dir_fileno = NDMOS_API_STRTOLL (scan, &scan, 0);
	if (*scan != ' ')
		return -1;

	while (*scan == ' ') scan++;

	if (*scan == 0)
		return -1;

	p = scan;
	while (*scan && *scan != ' ')
		scan++;

	if (*scan) {
		*scan = 0;
		rc = wrap_cstr_to_str (p, res->name, sizeof res->name);
		*scan++ = ' ';
	} else {
		rc = wrap_cstr_to_str (p, res->name, sizeof res->name);
	}
	if (rc < 0) return -2;

	res->fileno = NDMOS_API_STRTOLL (scan, &scan, 0);
	if (*scan != ' ' && *scan != 0)
		return -1;

	while (*scan == ' ') scan++;

	if (*scan == '@') {
		res->fhinfo = NDMOS_API_STRTOLL(scan+1, &scan, 0);
	}

	if (*scan != ' ' && *scan != 0)
		return -1;

	while (*scan == ' ') scan++;

	if (*scan)
		return -1;

	return 0;
}

int
wrap_send_add_dirent (FILE *fp, char *name, unsigned long long fhinfo,
  unsigned long long dir_fileno, unsigned long long fileno)
{
	struct wrap_msg_buf	wmsg;
	struct wrap_add_dirent *res = &wmsg.body.add_dirent;

	if (!fp) return -1;

	wrap_cstr_from_str (name, res->name, sizeof res->name);
	fprintf (fp, "HD %llu %s %llu", dir_fileno, res->name, fileno);

	if (fhinfo != WRAP_INVALID_FHINFO)
		fprintf (fp, " @%llu", fhinfo);

	fprintf (fp, "\n");

	return 0;
}


int
wrap_parse_add_node_msg (char *buf, struct wrap_msg_buf *wmsg)
{
	struct wrap_add_node *	res = &wmsg->body.add_node;
	char *			scan = buf+3;
	char *			p;
	int			rc;

	wmsg->msg_type = WRAP_MSGTYPE_ADD_NODE;

	res->fstat.valid = 0;
	res->fhinfo = WRAP_INVALID_FHINFO;

	while (*scan && *scan == ' ')
		scan++;
	if (*scan == 0)
		return -1;

	res->fstat.fileno = NDMOS_API_STRTOLL (scan, &scan, 0);
	if (*scan != ' ' && *scan != 0)
		return -1;

	res->fstat.valid |= WRAP_FSTAT_VALID_FILENO;

	while (*scan) {
		p = scan+1;
		switch (*scan) {
		case ' ':
			scan++;
			continue;

		case '@':
			res->fhinfo = NDMOS_API_STRTOLL (p, &scan, 0);
			break;

		default:
			rc = wrap_parse_fstat_subr(&scan, &res->fstat);
			if (rc < 0)
				return rc;
			break;
		}

		if (*scan != ' ' && *scan != 0) {
			/* bogus */
			return -1;
		}
	}

	if ( (res->fstat.valid & WRAP_FSTAT_VALID_FILENO) == 0)
		return -5;

	return 0;
}

int
wrap_send_add_node (FILE *fp, unsigned long long fhinfo,
  struct wrap_fstat *fstat)
{
	unsigned long		save_valid;

	if (!fp) return -1;

	if (fstat->valid & WRAP_FSTAT_VALID_FILENO) {
		fprintf (fp, "HN %llu", fstat->fileno);
	} else {
		fprintf (fp, "HN 0000000000");
	}

	if (fhinfo != WRAP_INVALID_FHINFO)
		fprintf (fp, " @%llu", fhinfo);

	/* suppress iFILENO */
	save_valid = fstat->valid;
	fstat->valid &= ~WRAP_FSTAT_VALID_FILENO;
	wrap_send_fstat_subr (fp, fstat);
	fstat->valid = save_valid;

	fprintf (fp, "\n");

	return 0;
}


int
wrap_parse_fstat_subr (char **scanp, struct wrap_fstat *fstat)
{
	char *		scan = *scanp;
	char *		p = scan+1;
	unsigned long	valid = 0;

	valid = 0;
	switch (*scan) {
	case 's':	/* size */
		valid = WRAP_FSTAT_VALID_SIZE;
		fstat->size = NDMOS_API_STRTOLL (p, &scan, 0);
		break;

	case 'i':	/* fileno (inum) */
		valid = WRAP_FSTAT_VALID_FILENO;
		fstat->fileno = NDMOS_API_STRTOLL (p, &scan, 0);
		break;

	case 'm':	/* mode low twelve bits */
		valid = WRAP_FSTAT_VALID_MODE;
		fstat->mode = strtol (p, &scan, 8);
		break;

	case 'l':	/* link count */
		valid = WRAP_FSTAT_VALID_LINKS;
		fstat->links = strtol (p, &scan, 0);
		break;

	case 'u':	/* uid */
		valid = WRAP_FSTAT_VALID_UID;
		fstat->uid = strtol (p, &scan, 0);
		break;

	case 'g':	/* gid */
		valid = WRAP_FSTAT_VALID_GID;
		fstat->gid = strtol (p, &scan, 0);
		break;

	case 't':		/* one of the times */
		p = scan+2;
		switch (scan[1]) {
		case 'm':	/* mtime */
			valid = WRAP_FSTAT_VALID_MTIME;
			fstat->mtime = strtol (p, &scan, 0);
			break;

		case 'a':	/* atime */
			valid = WRAP_FSTAT_VALID_ATIME;
			fstat->atime = strtol (p, &scan, 0);
			break;

		case 'c':	/* ctime */
			valid = WRAP_FSTAT_VALID_CTIME;
			fstat->ctime = strtol (p, &scan, 0);
			break;

		default:
			return -3;
		}
		break;

	case 'f':	/* ftype (file type) */
		valid = WRAP_FSTAT_VALID_FTYPE;
		switch (scan[1]) {
		case 'd':	fstat->ftype = WRAP_FTYPE_DIR; break;
		case 'p':	fstat->ftype = WRAP_FTYPE_FIFO; break;
		case 'c':	fstat->ftype = WRAP_FTYPE_CSPEC; break;
		case 'b':	fstat->ftype = WRAP_FTYPE_BSPEC; break;
		case '-':	fstat->ftype = WRAP_FTYPE_REG; break;
		case 'l':	fstat->ftype = WRAP_FTYPE_SLINK; break;
		case 's':	fstat->ftype = WRAP_FTYPE_SOCK; break;
		case 'R':	fstat->ftype = WRAP_FTYPE_REGISTRY; break;
		case 'o':	fstat->ftype = WRAP_FTYPE_OTHER; break;
		default:
			fstat->ftype = WRAP_FTYPE_INVALID;
			return -5;
		}
		scan += 2;
		break;

	default:
		return -3;
	}

	if (*scan != ' ' && *scan != 0)
		return -1;

	fstat->valid |= valid;
	*scanp = scan;

	return 0;
}

int
wrap_send_fstat_subr (FILE *fp, struct wrap_fstat *fstat)
{
	if (!fp) return -1;

	if (fstat->valid & WRAP_FSTAT_VALID_FTYPE) {
		int		c = 0;

		switch (fstat->ftype) {
		default:
		case WRAP_FTYPE_INVALID:
			c = 0;
			break;
		case WRAP_FTYPE_DIR:		c = 'd'; break;
		case WRAP_FTYPE_FIFO:		c = 'p'; break;
		case WRAP_FTYPE_CSPEC:		c = 'c'; break;
		case WRAP_FTYPE_BSPEC:		c = 'b'; break;
		case WRAP_FTYPE_REG:		c = '-'; break;
		case WRAP_FTYPE_SLINK:		c = 'l'; break;
		case WRAP_FTYPE_SOCK:		c = 's'; break;
		case WRAP_FTYPE_REGISTRY:	c = 'R'; break;
		case WRAP_FTYPE_OTHER:		c = 'o'; break;
		}

		if (c) {
			fprintf (fp, " f%c", c);
		} else {
			return -1;
		}
	}

	if (fstat->valid & WRAP_FSTAT_VALID_MODE) {
		fprintf (fp, " m%04o", fstat->mode);
	}

	if (fstat->valid & WRAP_FSTAT_VALID_LINKS) {
		fprintf (fp, " l%lu", fstat->links);
	}

	if (fstat->valid & WRAP_FSTAT_VALID_SIZE) {
		fprintf (fp, " s%llu", fstat->size);
	}

	if (fstat->valid & WRAP_FSTAT_VALID_UID) {
		fprintf (fp, " u%lu", fstat->uid);
	}

	if (fstat->valid & WRAP_FSTAT_VALID_GID) {
		fprintf (fp, " g%lu", fstat->gid);
	}

	if (fstat->valid & WRAP_FSTAT_VALID_ATIME) {
		fprintf (fp, " ta%lu", fstat->atime);
	}

	if (fstat->valid & WRAP_FSTAT_VALID_MTIME) {
		fprintf (fp, " tm%lu", fstat->mtime);
	}

	if (fstat->valid & WRAP_FSTAT_VALID_CTIME) {
		fprintf (fp, " tc%lu", fstat->ctime);
	}

	if (fstat->valid & WRAP_FSTAT_VALID_FILENO) {
		fprintf (fp, " i%llu", fstat->fileno);
	}

	return 0;
}

int
wrap_parse_add_env_msg (char *buf, struct wrap_msg_buf *wmsg)
{
	struct wrap_add_env *	res = &wmsg->body.add_env;
	char *			scan = buf+3;
	char *			p;
	int			rc;

	wmsg->msg_type = WRAP_MSGTYPE_ADD_ENV;

	while (*scan && *scan == ' ')
		scan++;
	if (*scan == 0)
		return -1;

	p = scan;
	while (*scan && *scan != ' ')
		scan++;

	if (*scan) {
		*scan = 0;
		rc = wrap_cstr_to_str (p, res->name, sizeof res->name);
		*scan++ = ' ';
	} else {
		rc = wrap_cstr_to_str (p, res->name, sizeof res->name);
	}
	if (rc < 0) return -2;

	while (*scan && *scan == ' ')
		scan++;

	p = scan;
	while (*scan && *scan != ' ')
		scan++;

	if (*scan) {
		*scan = 0;
		rc = wrap_cstr_to_str (p, res->value, sizeof res->value);
		*scan++ = ' ';
	} else {
		rc = wrap_cstr_to_str (p, res->value, sizeof res->value);
	}
	if (rc < 0) return -2;

	return 0;
}

int
wrap_send_add_env (FILE *fp, char *name, char *value)
{
	struct wrap_msg_buf	wmsg;
	struct wrap_add_env *	res = &wmsg.body.add_env;

	if (!fp) return -1;

	wrap_cstr_from_str (name, res->name, sizeof res->name);
	wrap_cstr_from_str (value, res->value, sizeof res->value);

	fprintf (fp, "DE %s %s\n", res->name, res->value);

	return 0;
}

int
wrap_parse_data_read_msg (char *buf, struct wrap_msg_buf *wmsg)
{
	struct wrap_data_read *	res = &wmsg->body.data_read;
	char *			scan = buf+3;

	wmsg->msg_type = WRAP_MSGTYPE_DATA_READ;

	while (*scan && *scan == ' ')
		scan++;
	if (*scan == 0)
		return -1;

	res->offset = NDMOS_API_STRTOLL (scan, &scan, 0);
	if (*scan != ' ')
		return -1;

	while (*scan && *scan != ' ')
		scan++;

	if (*scan == 0)
		return -1;

	res->length = NDMOS_API_STRTOLL (scan, &scan, 0);

	/* tolerate trailing white */
	while (*scan && *scan != ' ')
		scan++;

	if (*scan != 0)
		return -1;

	return 0;
}

int
wrap_send_data_read (FILE *fp,
  unsigned long long offset, unsigned long long length)
{

	if (!fp) return -1;

	fprintf (fp, "DR %lld %lld\n", (long long) offset, (long long)length);
	fflush (fp);

	return 0;
}

int
wrap_parse_data_stats_msg (char *buf, struct wrap_msg_buf *wmsg)
{
#if 0
	struct wrap_data_stats *res = &wmsg->body.data_stats;
	char *			scan = buf+3;

	wmsg->msg_type = WRAP_MSGTYPE_DATA_STATS;
#endif
	return -1;
}

int
wrap_send_data_stats (FILE *fp)
{
	if (!fp) return -1;

	fprintf (fp, "DS ...\n");
	fflush (fp);

	return 0;
}




/*
 * Recovery helpers
 ****************************************************************
 */

int
wrap_reco_align_to_wanted (struct wrap_ccb *wccb)
{
	unsigned long long	distance;
	unsigned long		unwanted_length;

   top:
	/*
	 * If there is an error, we're toast.
	 */
	if (wccb->error)
		return wccb->error;

	/*
	 * If we're aligned, we're done.
	 */
	if (wccb->expect_offset == wccb->want_offset) {
		if (wccb->expect_length < wccb->want_length
		 && wccb->reading_length == 0) {
			wrap_reco_issue_read (wccb);
		}
		return wccb->error;
	}

	/*
	 * If we have a portion we don't want, consume it now
	 */
	if (wccb->have_length > 0) {
		if (wccb->have_offset < wccb->want_offset) {
			distance = wccb->want_offset - wccb->have_offset;
			if (distance < wccb->have_length) {
				/*
				 * We have some of what we want.
				 * Consume (discard) unwanted part.
				 */
				unwanted_length = distance;
			} else {
				unwanted_length = wccb->have_length;
			}
		} else {
			unwanted_length = wccb->have_length;
		}
		wrap_reco_consume (wccb, unwanted_length);
		goto top;
	}

	if (wccb->expect_length > 0) {
		/* Incoming, but we don't have it yet. */
		wrap_reco_receive (wccb);
		goto top;
	}

	/*
	 * We don't have anything. We don't expect anything.
	 * Time to issue an NDMP_DATA_NOTIFY_READ via this wrapper.
	 */

	wrap_reco_issue_read (wccb);

	goto top;
}

int
wrap_reco_receive (struct wrap_ccb *wccb)
{
	char *		iobuf_end = &wccb->iobuf[wccb->n_iobuf];
	char *		have_end = wccb->have + wccb->have_length;
	unsigned	n_read = iobuf_end - have_end;
	int		rc;

	if (wccb->error)
		return wccb->error;

	if (wccb->have_length == 0) {
		wccb->have = wccb->iobuf;
		have_end = wccb->have + wccb->have_length;
	}

	if (n_read < 512 && wccb->have != wccb->iobuf) {
		/* Not much room at have_end. Front of iobuf available. */
		/* Compress */
		NDMOS_API_BCOPY (wccb->have, wccb->iobuf, wccb->have_length);
		wccb->have = wccb->iobuf;
		have_end = wccb->have + wccb->have_length;
		n_read = iobuf_end - have_end;
	}

	if (n_read > wccb->reading_length)
		n_read = wccb->reading_length;

	if (n_read == 0) {
		/* Hmmm. */
		abort ();
		return -1;
	}

	rc = read (wccb->data_conn_fd, have_end, n_read);
	if (rc > 0) {
		wccb->have_length += rc;
		wccb->reading_offset += rc;
		wccb->reading_length -= rc;
	} else {
		/* EOF or error */
		if (rc == 0) {
			strcpy (wccb->errmsg, "EOF on data connection");
			wrap_set_error (wccb, -1);
		} else {
			sprintf (wccb->errmsg, "errno %d on data connection",
					errno);
			wrap_set_errno (wccb);
		}
	}

	return wccb->error;
}

int
wrap_reco_consume (struct wrap_ccb *wccb, unsigned long length)
{
	assert (wccb->have_length >= length);

	wccb->have_offset += length;
	wccb->have_length -= length;
	wccb->expect_offset += length;
	wccb->expect_length -= length;
	wccb->have += length;

	if (wccb->expect_length == 0) {
		assert (wccb->have_length == 0);
		wccb->expect_offset = -1ull;
	}

	return wccb->error;
}

int
wrap_reco_must_have (struct wrap_ccb *wccb, unsigned long length)
{
	if (wccb->want_length < length)
		wccb->want_length = length;

	wrap_reco_align_to_wanted (wccb);

	while (wccb->have_length < length && !wccb->error) {
		wrap_reco_align_to_wanted (wccb); /* triggers issue_read() */
		wrap_reco_receive (wccb);
	}

	if (wccb->have_length >= length)
		return 0;

	return wccb->error;
}

int
wrap_reco_seek (struct wrap_ccb *wccb,
 unsigned long long want_offset,
 unsigned long long want_length,
 unsigned long must_have_length)
{
	if (wccb->error)
		return wccb->error;

	wccb->want_offset = want_offset;
	wccb->want_length = want_length;

	return wrap_reco_must_have (wccb, must_have_length);
}

int
wrap_reco_pass (struct wrap_ccb *wccb, int write_fd,
  unsigned long long length, unsigned write_bsize)
{
	unsigned		cnt;
	int			rc;

	while (length > 0) {
		if (wccb->error)
			break;

		cnt = write_bsize;
		if (cnt > length)
			cnt = length;

		if (wccb->have_length < cnt) {
			wrap_reco_must_have (wccb, cnt);
		}

		rc = write (write_fd, wccb->have, cnt);

		length -= cnt;
		wrap_reco_consume (wccb, cnt);
	}

	return wccb->error;
}

int
wrap_reco_issue_read (struct wrap_ccb *wccb)
{
	unsigned long long		off;
	unsigned long long		len;

	assert (wccb->reading_length == 0);

	if (wccb->data_conn_mode == 0) {
		struct stat	st;
		int		rc;

		rc = fstat (wccb->data_conn_fd, &st);
		if (rc != 0) {
			sprintf (wccb->errmsg, "Can't fstat() data conn rc=%d",
				rc);
			return wrap_set_errno (wccb);
		}
		if (S_ISFIFO(st.st_mode)) {
			wccb->data_conn_mode = 'p';
			if (!wccb->index_fp) {
				strcpy (wccb->errmsg,
					"data_conn is pipe but no -I");
				return wrap_set_error (wccb, -3);
			}
		} else if (S_ISREG(st.st_mode)) {
			wccb->data_conn_mode = 'f';
		} else {
			sprintf (wccb->errmsg, "Unsupported data_conn type %o",
				st.st_mode);
			return wrap_set_error (wccb, -3);
		}
	}

	off = wccb->want_offset;
	len = wccb->want_length;

	off += wccb->have_length;
	len -= wccb->have_length;

	if (len == 0) {
		abort();
	}

	wccb->last_read_offset = off;
	wccb->last_read_length = len;

	switch (wccb->data_conn_mode) {
	default:
		abort();
		return -1;

	case 'f':
		lseek (wccb->data_conn_fd, off, 0);
		break;

	case 'p':
		wrap_send_data_read (wccb->index_fp, off, len);
		break;
	}

	wccb->reading_offset = wccb->last_read_offset;
	wccb->reading_length = wccb->last_read_length;

	if (wccb->have_length == 0) {
		wccb->expect_offset = wccb->reading_offset;
		wccb->expect_length = wccb->reading_length;
	} else {
		wccb->expect_length += len;
	}

	return wccb->error;
}




/*
 * (Note: this is hoisted from ndml_cstr.c)
 *
 * Description:
 *	Convert strings to/from a canonical strings (CSTR).
 *
 *	The main reason for this is to eliminate spaces
 *	in strings thus making multiple strings easily
 *	delimited by white space.
 *
 *	Canonical strings use the HTTP convention of
 *	percent sign followed by two hex digits (%xx).
 *	Characters outside the printable ASCII range,
 *	space, and percent sign are so converted.
 *
 *	Both interfaces return the length of the resulting
 *	string, -1 if there is an overflow, or -2
 *	there is a conversion error.
 */

int
wrap_cstr_from_str (char *src, char *dst, unsigned dst_max)
{
	static char		cstr_to_hex[] = "0123456789ABCDEF";
	unsigned char *		p = (unsigned char *)src;
	unsigned char *		q = (unsigned char *)dst;
	unsigned char *		q_end = q + dst_max - 1;
	int			c;

	while ((c = *p++) != 0) {
		if (c <= ' ' || c > 0x7E || c == NDMCSTR_WARN) {
			if (q+3 > q_end)
				return -1;
			*q++ = NDMCSTR_WARN;
			*q++ = cstr_to_hex[(c>>4)&0xF];
			*q++ = cstr_to_hex[c&0xF];
		} else {
			if (q+1 > q_end)
				return -1;
			*q++ = c;
		}
	}
	*q = 0;

	return q - (unsigned char *)dst;
}

int
wrap_cstr_to_str (char *src, char *dst, unsigned dst_max)
{
	unsigned char *		p = (unsigned char *)src;
	unsigned char *		q = (unsigned char *)dst;
	unsigned char *		q_end = q + dst_max - 1;
	int			c, c1, c2;

	while ((c = *p++) != 0) {
		if (q+1 > q_end)
			return -1;
		if (c != NDMCSTR_WARN) {
			*q++ = c;
			continue;
		}
		c1 = wrap_cstr_from_hex (p[0]);
		c2 = wrap_cstr_from_hex (p[1]);

		if (c1 < 0 || c2 < 0) {
			/* busted conversion */
			return -2;
		}

		c = (c1<<4) + c2;
		*q++ = c;
		p += 2;
	}
	*q = 0;

	return q - (unsigned char *)dst;
}

int
wrap_cstr_from_hex (int c)
{
	if ('0' <= c && c <= '9')
		return c - '0';
	if ('a' <= c && c <= 'f')
		return (c - 'a') + 10;
	if ('A' <= c && c <= 'F')
		return (c - 'A') + 10;
	return -1;
}
