/*
 * Copyright(c) 1997, 1998 by Jun-ichiro Itoh. All rights reserved.
 * Freely redistributable unless otherwise noted.  Absolutely no warranty.
 *
 * author contact: Jun-ichiro itojun Itoh <itojun@mt.cs.keio.ac.jp>
 * $Id: main.c,v 1.8 2001/02/11 01:03:44 itojun Exp $
 */
#include "common.h"
#include "camedia.h"

#if HAVE_TERMIOS_H
# include <termios.h>
#else
/*# if HAVE_TERMIO_H*/
# if 0
#  include <termio.h>
# else
#  if HAVE_SYS_IOCTL_H
#   include <sys/ioctl.h>
#  endif
#  include <sgtty.h>
# endif
#endif
#include <setjmp.h>
#include <sys/stat.h>

/*
 * SETTIME functionality is disabled due to the handling of timezone.
 */
#define NO_SETTIME

/* serial lines */
static int baudrateflag = 19200;
static char *n0;
static int fd0;

static int dumpflag = 0;
static int dumpmaxlen = 20;
static int debugflag = 0;
static int openflag = O_WRONLY | O_TRUNC | O_CREAT;
static enum pictmode { FULLSIZE, THUMBNAIL, VOICE } pictmode = FULLSIZE;
static int verbose = 0;

static enum {
	SHOWNPICTS, GETPICTS, GETPICT1, GETSTATUS,
#ifndef NO_SETTIME
	SETTIME,
#endif
	SETID, SETSERIALNO, TAKEPICTURE,
} mode = SHOWNPICTS;

static int startpict = 0;
static int endpict = 0;
static int allpict = 0;

static char *idstring = "";
static char *serialnostring = "";

#define	SMALLWAIT	200*1000	/*usec*/

#define dprintf(x)						\
	{ if (debugflag) {					\
		fprintf(stderr, __FILE__ ":%d: ", __LINE__);	\
		fprintf x;					\
	  }							\
	}

/* uid */
static uid_t uid, euid;
static gid_t gid, egid;
static int uidswapped = 0;

/* uucp locking */
static int useuucplock = 1;
extern int uu_lock __P((char *));
extern int uu_unlock __P((char *));

/* error handling */
jmp_buf fatal;

void
daemonuid()
{
	if (uidswapped == 0)
		return;

#ifdef HAVE_SETREUID
	setreuid(uid, euid);
	setregid(gid, egid);
#else
	setuid(uid);
	seteuid(euid);
	setgid(gid);
	setegid(egid);
#endif
	uidswapped = 0;
}

void
useruid()
{
	if (uidswapped == 1)
		return;

#ifdef HAVE_SETREUID
	setregid(egid, gid);
	setreuid(euid, uid);
#else
	setgid(egid);
	setegid(gid);
	setuid(euid);
	seteuid(uid);
#endif
	uidswapped = 1;
}

static int
uucplock(name)
	char *name;
{
	int ret;

	if (useuucplock) {
		daemonuid();
		ret = uu_lock(rindex(name, '/') + 1);
		useruid();
	} else
		ret = 0;
	return ret;
}

static int
uucpunlock(name)
	char *name;
{
	int ret;

	if (useuucplock) {
		daemonuid();
		ret = uu_unlock(rindex(name, '/') + 1);
		useruid();
	} else
		ret = 0;
	return ret;
}

static char *
canondev(name)
	char *name;
{
	int fd;
	char *p;

	daemonuid();

	p = malloc(strlen(name) + 1);
	if (!p) {
		perror("malloc");
		goto back;
	}
	strcpy(p, name);
	fd = open(p, O_RDWR|O_NONBLOCK);
	if (0 <= fd) {
		close(fd);
		goto back;
	}

	p = realloc(p, strlen(name) + 6);
	if (!p) {
		perror("realloc");
		goto back;
	}
	strcpy(p, "/dev/");
	strcat(p, name);
	fd = open(p, O_RDWR|O_NONBLOCK);
	if (0 <= fd) {
		close(fd);
		goto back;
	}

	free(p);
	p = NULL;
back:
	useruid();
	return p;
}

speed_t
baudconv(int baud)
{
#define	BAUDCASE(x)	case (x): { ret = B##x; break; }
	speed_t ret;

	ret = (speed_t) baud;
	switch (baud) {
	/* POSIX defined baudrates */
	BAUDCASE(0);	/*is it meaningful? */
	BAUDCASE(50);
	BAUDCASE(75);
	BAUDCASE(110);
	BAUDCASE(134);
	BAUDCASE(150);
	BAUDCASE(200);
	BAUDCASE(300);
	BAUDCASE(600);
	BAUDCASE(1200);
	BAUDCASE(1800);
	BAUDCASE(2400);
	BAUDCASE(4800);
	BAUDCASE(9600);
	BAUDCASE(19200);
	BAUDCASE(38400);

	/* non POSIX values */
#ifdef B7200
	BAUDCASE(7200);
#endif
#ifdef B14400
	BAUDCASE(14400);
#endif
#ifdef B28800
	BAUDCASE(28800);
#endif
#ifdef B57600
	BAUDCASE(57600);
#endif
#ifdef B115200
	BAUDCASE(115200);
#endif
#ifdef B230400
	BAUDCASE(230400);
#endif

	/* last resort */
	default:
		fprintf(stderr, "no defined value for baudrate %d found, "
			"use the value without conversion\n", baud);
	}

	return ret;
#undef BAUDCASE
}

int
setbaud(fd, baud)
	int fd;
	int baud;
{
#if HAVE_TERMIOS_H
	/* termios */
	struct termios tio;

	if (tcgetattr(fd, &tio) < 0) {
		perror("tcgetattr");
		return 1;
	}
	tio.c_iflag = 0;
	tio.c_oflag = 0;
	tio.c_cflag = CS8 | CREAD | CLOCAL;
	tio.c_lflag = 0;
	tio.c_cc[VMIN] = 1;
	tio.c_cc[VTIME] = 5;
	cfsetispeed(&tio, baudconv(baud));
	cfsetospeed(&tio, baudconv(baud));
	if (tcsetattr(fd, TCSANOW, &tio) < 0) {
		perror("tcsetattr");
		return 1;
	}
#else
/*#if HAVE_TERMIO_H*/
# if 0
	/* termio */
# else
	/* sgtty */
	struct sgttyb ttyb;

	if (ioctl(fd, TIOCGETP, &ttyb) < 0) {
		perror("ioctl(TIOCGETP)");
		return 1;
	}
	ttyb.sg_ispeed = baud;
	ttyb.sg_ospeed = baud;
	ttyb.sg_flags = 0;
	if (ioctl(fd, TIOCSETP, &ttyb) < 0) {
		perror("ioctl(TIOCSETP)");
		return 1;
	}
# endif
#endif

	dprintf((stderr, "baudrate set to %d\n", baud));
	return 0;
}

void
dump_stream(dir, buf, len)
	int dir;
	u_char *buf;
	int len;
{
	size_t i;
	int truncate;

	if (!dumpflag)
		return;

	truncate = 0;
	if (dumpmaxlen < len) {
		len = dumpmaxlen;
		truncate = 1;
	}

	dir &= 0xff;
	if (dir == '>')
		fprintf(stderr, "camera>cpu: ");
	else
		fprintf(stderr, "cpu>camera: ");

	for (i = 0; i < len; i++)
		fprintf(stderr, "%02x ", buf[i] & 0xff);
	if (truncate)
		fprintf(stderr, "...");
	fprintf(stderr, "\n");
}

/*------------------------------------------------------------*/

u_char camedia_buf[1024 * 4];
size_t camedia_len;

void
camedia_init()
{
	/* initialize buffers */
	camedia_len = 0;
}

int
camedia_wait(rlen)
	int rlen;
{
	fd_set rdfd;
	int maxfd;
	int len;
	struct timeval t;

	while (camedia_len < rlen) {
		/* obtain chars from the camera */
		FD_ZERO(&rdfd);
		FD_SET(fd0, &rdfd);
		maxfd = fd0;
		/* timeout: 5sec */
		t.tv_sec = 5;
		t.tv_usec = 0;
		switch (select(maxfd + 1, &rdfd, NULL, NULL, &t)) {
		case -1:
			if (errno == EINTR)
				break;
			perror("select");
			exit(1);
		case 0:
			dprintf((stderr, "read timeout.\n"));
			return 1;
		default:
			break;
		}

		if (FD_ISSET(fd0, &rdfd)) {
			len = read(fd0, camedia_buf + camedia_len,
				sizeof(camedia_buf) - camedia_len);
			camedia_len += len;
		} else {
			dprintf((stderr, "something wrong in camedia_get\n"));
			return 1;
		}
	}
	dump_stream('>', camedia_buf, camedia_len);

	return 0;
}

int
camedia_get(buf, len)
	u_char *buf;
	int len;
{
	switch (camedia_wait(len)) {
	case 1:
		dprintf((stderr, "timeouted in camedia_get\n"));
		fprintf(stderr, "camera not ready.\n");
		longjmp(fatal, 1);
	case 0:
		break;
	}

	if (camedia_len < len)
		abort();

	memcpy(buf, camedia_buf, len);
	if (camedia_len - len)
		memmove(camedia_buf, camedia_buf + len, camedia_len - len);
	camedia_len -= len;

	return 0;
}

int
camedia_put(buf, len)
	u_char *buf;
	int len;
{
	int wlen;
	int i;

	/* Thanks goes to Thierry Bousch */
	for (i = 0; i < len; i++) {
		wlen = write(fd0, &buf[i], 1);
		if (wlen != 1) {
			dprintf((stderr, "failed in camedia_put\n"));
			return 1;
		}
		usleep(20*1000);
#if HAVE_TERMIOS_H
		/*termios*/
		tcdrain(fd0);
#else
/*#if HAVE_TERMIO_H*/
#if 0
		/*termio - yet to be done*/
#else
		/*sgtty*/
		ioctl(fd0, TCDRAIN, 0);
#endif
#endif
	}

	dump_stream('<', buf, len);
	return 0;
}

/*------------------------------------------------------------*/

int
camedia_attention()
{
	u_char buf[10];
	int timeout;

#define ATTN_WAIT	10

	camedia_put("\0", 1);
	timeout = 0;
	do {
		camedia_get(buf, 1);
		if (ATTN_WAIT <= timeout++) {
			dprintf((stderr,
				"timed out while waiting 0x15 "
				"in camedia_attention"));
			return 1;
		}
	} while (buf[0] != 0x00 && buf[0] != 0x15 && buf[0] != 0xff);

	switch (buf[0]) {
	case 0x00:	/*Agfa ePhoto307*/
	case 0x15:	/*others*/
		return 0;

	case 0xff:
		dprintf((stderr,
			"got baudrate error reported in camedia_attention"));
		return 1;

	default:
		dprintf((stderr,
			"unknown char 0x%02x arrived in camedia_attention",
			buf[0]));
		return 1;
	}
}

int
camedia_getack()
{
	u_char buf[10];

	camedia_get(buf, 1);
	if (buf[0] != 0x06) {
		dprintf((stderr, "got 0x%02x while waiting ACK(0x06)\n",
			buf[0] && 0xff));
		return 1;
	}
	return 0;
}

int
camedia_sendcmd(ch, buf, len)
	int ch;
	u_char *buf;
	int len;
{
	u_char tbuf[2];
	u_short sum;
	size_t i;
	int err;

	usleep(SMALLWAIT);	/*YYY*/

	/* generate checksum */
	sum = 0;
	for (i = 0; i < len; i++) {
		sum += (buf[i] & 0xff);
	}

	tbuf[0] = '\033';
	tbuf[1] = ch & 0xff;
	err = camedia_put(tbuf, 2);

	tbuf[0] = len & 0x00ff;
	tbuf[1] = (len & 0xff00) >> 8;
	err += camedia_put(tbuf, 2);

	err += camedia_put(buf, len);

	tbuf[0] = sum & 0x00ff;
	tbuf[1] = (sum & 0xff00) >> 8;
	err += camedia_put(tbuf, 2);

	return err ? 1 : 0;
}

int
camedia_getpacket(buf, plen, pfinal, ack, sector)
	u_char *buf;
	int *plen;
	int *pfinal;
	int *ack;
	int *sector;
{
	int err;
	u_char tbuf[2];
	u_short len;
	u_short cksum;
	size_t i;

	err = 0;

	/* init */
	*plen = 0;
	*pfinal = 1;
	*ack = 0;
	if (sector)
		*sector = -1;

	/* get the packet type. */
	if (camedia_get(tbuf, 1)) {
		dprintf((stderr, "err in camedia_getpacket: getting type\n"));
		err++;
	}
	if (tbuf[0] == 0x15) {
		dprintf((stderr, "got NAK\n"));
		return 1;
	} else if (tbuf[0] == 0xff) {
		dprintf((stderr, "got framing error\n"));
		return 1;
	} else if (tbuf[0] == 0x06) {
		dprintf((stderr, "got ACK\n"));
		*ack = 1;
		return 0;
	} else if (tbuf[0] != 0x02 && tbuf[0] != 0x03) {
		dprintf((stderr, "illegal packet received: 0x%02x\n", tbuf[0]));
		err++;
		goto back;
	}

	if (camedia_get(tbuf + 1, 1)) {
		dprintf((stderr, "err in camedia_getpacket: getting type\n"));
		err++;
	}

	if (sector)
		*sector = tbuf[1] & 0xff;
	switch (tbuf[0]) {
	case 0x02:
		*pfinal = 0;
		break;
	case 0x03:
		*pfinal = 1;
		break;
	default:
		dprintf((stderr, "err in camedia_getpacket: type = %02x%02x\n",
			tbuf[1], tbuf[0]));
		err++;
	}

	/* get the packet len. */
	if (camedia_get(tbuf, 2)) {
		dprintf((stderr, "err in camedia_getpacket: getting len\n"));
		err++;
	}
	len = ((u_short)tbuf[1] << 8) + tbuf[0];

	/* get the packet content. */
	if (camedia_get(buf, len)) {
		dprintf((stderr, "err in camedia_getpacket: body\n"));
		err++;
	}

	/* check for checksum. */
	if (camedia_get(tbuf, 2)) {
		dprintf((stderr,
			"err in camedia_getpacket: getting checksum\n"));
		err++;
	}

	cksum = 0;
	for (i = 0; i < len; i++) {
		cksum += (buf[i] & 0xff);
	}

	if (tbuf[0] != (cksum & 0x00ff) || tbuf[1] != (cksum & 0xff00) >> 8) {
		dprintf((stderr, "err in camedia_getpacket: cksum\n"));
#if 0
		err++;
#endif
	}

	/* XXX must check for retransmit */
#if 0
	if (!err)
		camedia_put("\006", 1);	/*ACK*/
	else
		camedia_put("\025", 1);	/*NAK*/
#else
	camedia_put("\006", 1);	/*ACK*/
#endif

	if (!err)
		*plen = len;

back:
	return err ? 1 : 0;
}

int
camedia_getint(cmd, n)
	int cmd;
	int *n;
{
	u_char buf[1024];
	int len;
	int final;
	int ack;
	int err;

	err = 0;

	cmd &= 0xff;
	buf[0] = CAM_GETNUM;
	buf[1] = cmd;
	if (camedia_sendcmd('C', buf, 2)) {
		dprintf((stderr, "sendcmd err in camedia_getint(%02x %02x)\n",
			CAM_GETNUM, cmd));
		err++;
		goto ng;
	}

	if (camedia_getpacket(buf, &len, &final, &ack, NULL)) {
		dprintf((stderr, "getpacket err in camedia_getint(%02x %02x)\n",
			CAM_GETNUM, cmd));
		err++;
		goto ng;
	}
	if (!final || ack) {
		dprintf((stderr,
			"getpacket err in camedia_getint(%02x %02x): %d/%d\n",
			CAM_GETNUM, cmd, final, ack));
		err++;
		goto ng;
	}

	if (n) {
		*n = (buf[3] & 0xff);
		*n = (*n * 256) + (buf[2] & 0xff);
		*n = (*n * 256) + (buf[1] & 0xff);
		*n = (*n * 256) + (buf[0] & 0xff);
	}

ng:
	return err ? 1 : 0;
}

int
camedia_setint(cmd, n)
	int cmd;
	int n;
{
	u_char buf[1024];
	int len;
	int final;
	int ack;
	int err;

	err = 0;

	cmd &= 0xff;
	buf[0] = CAM_SETNUM;
	buf[1] = cmd;
	buf[2] = (n & 0x000000ff) >> 0;
	buf[3] = (n & 0x0000ff00) >> 8;
	buf[4] = (n & 0x00ff0000) >> 16;
	buf[5] = (n & 0xff000000) >> 24;
	if (camedia_sendcmd('C', buf, 6)) {
		dprintf((stderr, "sendcmd err in camedia_setint(%02x %02x)\n",
			CAM_SETNUM, cmd));
		err++;
		goto ng;
	}
	if (camedia_getpacket(buf, &len, &final, &ack, NULL)) {
		dprintf((stderr, "getpacket err in camedia_setint(%02x %02x)\n",
			CAM_SETNUM, cmd));
		err++;
		goto ng;
	}
	if (!ack) {
		dprintf((stderr,
			"getpacket err in camedia_setint(%02x %02x): %d/%d\n",
			CAM_SETNUM, cmd, final, ack));
		err++;
	}

ng:
	return err ? 1 : 0;
}

int
camedia_getstr(cmd, buf, plen)
	int cmd;
	u_char *buf;
	int *plen;
{
	u_char tbuf[10];
	int final;
	int ack;
	int err;

	err = 0;

	cmd &= 0xff;
	tbuf[0] = CAM_GETSTR;
	tbuf[1] = cmd;
	if (camedia_sendcmd('C', tbuf, 2)) {
		dprintf((stderr, "sendcmd err in camedia_getstr(%02x %02x)\n",
			CAM_GETSTR, cmd));
		err++;
		goto ng;
	}

	if (camedia_getpacket(buf, plen, &final, &ack, NULL)) {
		dprintf((stderr, "getpacket err in camedia_getstr(%02x %02x)\n",
			CAM_GETSTR, cmd));
		err++;
		goto ng;
	}
	if (!final || ack) {
		dprintf((stderr,
			"getpacket err in camedia_getstr(%02x %02x): %d/%d\n",
			CAM_GETSTR, cmd, final, ack));
		err++;
		goto ng;
	}

ng:
	return err ? 1 : 0;
}

int
camedia_setstr(cmd, src, srclen)
	int cmd;
	u_char *src;
	int srclen;
{
	u_char buf[1024];
	int len;
	int final;
	int ack;
	int err;

	err = 0;

	cmd &= 0xff;
	buf[0] = CAM_SETSTR;
	buf[1] = cmd;
	memcpy(&buf[2], src, srclen);
	if (camedia_sendcmd('C', buf, srclen + 2)) {
		dprintf((stderr, "sendcmd err in camedia_setstr(%02x %02x)\n",
			CAM_SETSTR, cmd));
		err++;
		goto ng;
	}
	if (camedia_getpacket(buf, &len, &final, &ack, NULL)) {
		dprintf((stderr, "getpacket err in camedia_setstr(%02x %02x)\n",
			CAM_SETSTR, cmd));
		err++;
		goto ng;
	}
	if (!ack) {
		dprintf((stderr,
			"getpacket err in camedia_setstr(%02x %02x): %d/%d\n",
			CAM_SETSTR, cmd, final, ack));
		err++;
	}

ng:
	return err ? 1 : 0;
}

int
camedia_control(cmd)
	int cmd;
{
	u_char buf[1024];
	int final;
	int ack;
	int err;
	int len;

	err = 0;

	cmd &= 0xff;
	buf[0] = CAM_CONTROL;
	buf[1] = cmd;
	if (camedia_sendcmd('C', buf, 2)) {
		dprintf((stderr, "sendcmd err in camedia_control(%02x %02x)\n",
			CAM_CONTROL, cmd));
		err++;
		goto ng;
	}

	if (camedia_getpacket(buf, &len, &final, &ack, NULL)) {
		dprintf((stderr, "getpacket err in "
			"camedia_control(%02x %02x)\n", CAM_CONTROL, cmd));
		err++;
		goto ng;
	}
	if (!final || ack) {
		dprintf((stderr,
			"getpacket err in camedia_control(%02x %02x): %d/%d\n",
			CAM_CONTROL, cmd, final, ack));
		err++;
		goto ng;
	}

ng:
	return err ? 1 : 0;
}


/*------------------------------------------------------------*/

int
camedia_setspeed(baud)
	int baud;
{
	u_char buf[10];
	int err;
	u_char value;

	err = 0;

	usleep(SMALLWAIT);

	while (camedia_attention()) {
		dprintf((stderr, "ATTN err in camedia_setspeed\n"));
		usleep(SMALLWAIT);
	}

	switch (baud) {
	case -1:
		value = '\000';
		break;
	case 9600:
		value = '\001';
		break;
	case 19200:
		value = '\002';
		break;
	case 38400:
		value = '\003';
		break;
	case 57600:
		value = '\004';
		break;
	case 115200:
		value = '\005';
		break;
	default:
		/* unsupported baud rate. */
		dprintf((stderr, "unsupported baudrate %d\n", baud));
		return 1;
	}

	/* set baudrate */
	memcpy(buf, "\0\021\0\0\0\0", 6);
	buf[2] = value;
	if (camedia_sendcmd('S', buf, 6)) {
		dprintf((stderr, "sendcmd err in camedia_setspeed\n"));
		err++;
	}

	if (camedia_getack()) {
		dprintf((stderr, "ACK err in camedia_setspeed\n"));
		err++;
	}

	usleep(20*1000);

	if (baud == -1)
		err += setbaud(fd0, 19200);
	else
		err += setbaud(fd0, baud);

	usleep(1000*1000);

	return err ? 1 : 0;
}

int
camedia_checkcamera()
{
	int err;

	err = 0;

	if (camedia_attention()) {
		dprintf((stderr, "ATTN err in camedia_checkcamera\n"));
		err++;
	}

	if (camedia_setspeed(-1)) {
		dprintf((stderr, "err in camedia_checkcamera\n"));
		err++;
	}

	if (camedia_setspeed(baudrateflag)) {
		dprintf((stderr, "err in camedia_checkcamera\n"));
		err++;
	}

	return err ? 1 : 0;
}

#ifndef NO_SETTIME
int
camedia_settime(t)
	time_t t;
{
	int err;

	err = 0;

	/* get battery capacity - should check for battery shortage */
	if (camedia_getint(CAM_NUM_BATTERY, NULL)) {
		dprintf((stderr, "getint err in camedia_settime\n"));
		err++;
	}

	/* get picture # */
	if (camedia_setint(CAM_NUM_DATE, (int)t)) {
		dprintf((stderr, "setint err in camedia_settime\n"));
		err++;
	}

	return err ? 1 : 0;
}
#endif

int
camedia_setid(s)
	char *s;
{
	int err;

	err = 0;

	/* get battery capacity - should check for battery shortage */
	if (camedia_getint(CAM_NUM_BATTERY, NULL)) {
		dprintf((stderr, "getint err in camedia_setid\n"));
		err++;
	}

	/* get picture # */
	if (camedia_setstr(CAM_STR_MODELID, s, strlen(s) + 1)) {
		dprintf((stderr, "setstr err in camedia_setid\n"));
		err++;
	}

	return err ? 1 : 0;
}

int
camedia_setserialno(s)
	char *s;
{
	int err;

	err = 0;

	/* get battery capacity - should check for battery shortage */
	if (camedia_getint(CAM_NUM_BATTERY, NULL)) {
		dprintf((stderr, "getint err in camedia_setserialno\n"));
		err++;
	}

	/* get picture # */
	if (camedia_setstr(CAM_STR_SERIALNO, s, strlen(s) + 1)) {
		dprintf((stderr, "setstr err in camedia_setserialno\n"));
		err++;
	}

	return err ? 1 : 0;
}

int
camedia_takepicture()
{
	int err;

	err = 0;

	/* get battery capacity - should check for battery shortage */
	if (camedia_getint(CAM_NUM_BATTERY, NULL)) {
		dprintf((stderr, "getint err in camedia_setserialno\n"));
		err++;
	}

	/* get picture # */
	if (camedia_control(CAM_CTL_SHUTTER)) {
		dprintf((stderr, "control err in camedia_takepicture\n"));
		err++;
	}

	return err ? 1 : 0;
}

int
camedia_getnpicts(n)
	int *n;
{
	int err;

	err = 0;

	*n = -1;

	/* get battery capacity - should check for battery shortage */
	if (camedia_getint(CAM_NUM_BATTERY, NULL)) {
		dprintf((stderr, "getint err in camedia_getnpicts\n"));
		err++;
	}

	/* get picture # */
	if (camedia_getint(CAM_NUM_NPICTS, n)) {
		dprintf((stderr, "getint err in camedia_getnpicts\n"));
		err++;
	}

	return err ? 1 : 0;
}

int
camedia_getnpictremain(n)
	int *n;
{
	int err;

	err = 0;

	*n = -1;

	/* get battery capacity - should check for battery shortage */
	if (camedia_getint(CAM_NUM_BATTERY, NULL)) {
		dprintf((stderr, "getint err in camedia_getnpicts\n"));
		err++;
	}

	/* get picture # */
	if (camedia_getint(CAM_NUM_NREMAIN, n)) {
		dprintf((stderr, "getint err in camedia_getnpicts\n"));
		err++;
	}

	return err ? 1 : 0;
}

int
camedia_getpict(n, pmode, fd)
	int n;
	enum pictmode pmode;
	int fd;
{
	u_char buf[1024 * 4];
	int len;
	int final;
	int ack;
	int sector;
	int err;
	int totallen;

	int cmd;

	err = 0;

	/* get battery capacity - should check for battery shortage */
	if (camedia_getint(CAM_NUM_BATTERY, NULL)) {
		dprintf((stderr, "getint err in camedia_getnpicts\n"));
		err++;
	}

	/* specify the picture # */
	if (camedia_setint(CAM_NUM_PTR, n)) {
		dprintf((stderr, "setint err in camedia_getnpicts\n"));
		err++;
	}

	/* get the size */
	switch (pmode) {
	case FULLSIZE:
		cmd = CAM_NUM_PICTSIZE;
		break;
	case THUMBNAIL:
		cmd = CAM_NUM_THUMBSIZE;
		break;
	case VOICE:
		/* cannot esetimate size right now, protocol unknown */
		break;
	}
	if (pmode != VOICE) {
		if (camedia_getint(cmd, &totallen)) {
			dprintf((stderr, "getint err in camedia_getnpicts\n"));
			err++;
		} else
			fprintf(stderr, "size will be %d\n", totallen);
	}

	/* start xfer */
	buf[0] = CAM_GETSTR;
	switch (pmode) {
	case FULLSIZE:
		buf[1] = CAM_STR_PICT;
		break;
	case THUMBNAIL:
		buf[1] = CAM_STR_THUMB;
		break;
	case VOICE:
		buf[1] = CAM_STR_VOICE;
		break;
	}

	if (camedia_sendcmd('C', buf, 2)) {
		dprintf((stderr, "sendcmd err in camedia_getpict\n"));
		err++;
	}

	/* get all sectors, and write them down into file */
	final = 0;
	totallen = 0;
	while (!final) {
		/*
		 * XXX
		 * must check for sector number skips/rewinds,
		 * and checksum NAKs
		 */
		if (camedia_getpacket(buf, &len, &final, &ack, &sector)) {
			dprintf((stderr, "getpacket err in camedia_getpict\n"));
			err++;
		} else if (ack) {
			dprintf((stderr,
				"getpacket err in camedia_getpict: %d/%d\n",
				final, ack));
			err++;

		} else {
			if (verbose) {
				fprintf(stderr,
					"got sector %3d: %d bytes \r",
						sector, totallen);
			}
			write(fd, buf, len);

			totallen += len;
		}
	}
	if (verbose) {
		fprintf(stderr,
			"got sector %3d: %d bytes ...done%s\n",
				sector, totallen, err ? " with err" : "");
	}

	return err ? 1 : 0;
}

int
camedia_getstatus()
{
	u_char buf[1024 * 4];
	int len;
	int err;
	int n;
	char *t;

	err = 0;

	/* get battery capacity - should check for battery shortage */
	if (camedia_getint(CAM_NUM_BATTERY, &n)) {
		dprintf((stderr, "getint err in camedia_getnpicts\n"));
		err++;
	} else
		fprintf(stderr, "battery capacity: %d%%\n", n);

	/* get camera id */
	if (camedia_getstr(CAM_STR_MODELID, buf, &len)) {
		dprintf((stderr, "can't get camera id\n"));
		err++;
	} else
		fprintf(stderr, "camera id: %s\n", buf);

	/* get camera type */
	if (camedia_getstr(CAM_STR_MODELTYPE, buf, &len)) {
		dprintf((stderr, "can't get camera type\n"));
		err++;
	} else
		fprintf(stderr, "camera type: %s\n", buf);

	/* get serial number */
	if (camedia_getstr(CAM_STR_SERIALNO, buf, &len)) {
		dprintf((stderr, "can't get serial number\n"));
		err++;
	} else
		fprintf(stderr, "serial number: %s\n", buf);

	/* get software version*/
	if (camedia_getstr(CAM_STR_VERSION, buf, &len)) {
		dprintf((stderr, "can't get software version\n"));
		err++;
	} else
		fprintf(stderr, "software version: %s\n", buf);

	/* get lens mode */
	if (camedia_getint(CAM_NUM_LENS, &n)) {
		dprintf((stderr, "can't get lens mode\n"));
		err++;
	} else {
		switch (n) {
		case 1:
			t = "macro"; break;
		case 2:
			t = "normal"; break;
		default:
			t = "???"; break;
		}
		fprintf(stderr, "lens mode: %s\n", t);
	}

	/* get flash mode */
	if (camedia_getint(CAM_NUM_FLASH, &n)) {
		dprintf((stderr, "can't get flash mode\n"));
		err++;
	} else {
		switch (n) {
		case 0:
			t = "auto"; break;
		case 1:
			t = "force on"; break;
		case 2:
			t = "force off"; break;
		case 3:
			t = "on(redeye prevention)"; break;
		default:
			t = "???"; break;
		}
		fprintf(stderr, "flash mode: %s\n", t);
	}

	/* get quality mode */
	if (camedia_getint(CAM_NUM_QUALITY, &n)) {
		dprintf((stderr, "can't get quality mode\n"));
		err++;
	} else {
		switch (n) {
		case 1:
			t = "normal"; break;
		case 2:
			t = "HQ"; break;
		default:
			t = "???"; break;
		}

		if (verbose)
			fprintf(stderr, "quality mode: %s(%d)\n", t, n);
		else
			fprintf(stderr, "quality mode: %s\n", t);
	}

#ifndef NO_SETTIME
	/* get time setting */
	if (camedia_getint(CAM_NUM_DATE, &n)) {
		dprintf((stderr, "can't get time setting\n"));
		err++;
	} else {
		time_t t1;

		/*
		 * XXX
		 * we got seconds from epoch into n, but in local time!
		 * how can we convert it into seconds from epoch in GMT,
		 * in a portable manner?
		 */
		t1 = n;
		fprintf(stderr, "date on camera: %s", ctime(&t1));
	}
#endif

	return err ? 1 : 0;
}

void
mainloop()
{
	int n;
	char fnamebuf[MAXPATHLEN];
	int i;
	int fd;

	switch (setjmp(fatal)) {
	case 0:
		break;
	default:
		goto fatalerr;
	}

	/*initial value*/
	if (setbaud(fd0, 19200)) {
		fprintf(stderr, "can't set baudrate\n");
		goto fatalerr;
	}

	camedia_init();

	camedia_setspeed(baudrateflag);

	usleep(SMALLWAIT);

	if (camedia_getnpicts(&n)) {
		fprintf(stderr, "error getting npicts\n");
		goto fatalerr;
	}

	if (!(mode == SHOWNPICTS || mode == GETPICTS || mode == GETPICT1))
		goto afterclarify;

	/*
	 * parameter clarification
	 */
	if (allpict) {
		startpict = 1;
		endpict = n;
	} else {
		/* default settings */
		if (startpict == 0)
			startpict = 1;
		if (endpict == 0)
			endpict = n;
	}
	if (startpict < 1 || n < startpict) {
		fprintf(stderr,
			"illegal startpict %d (must be 1 to %d)\n",
			startpict, n);
		goto fatalerr;
	}
	if (endpict < 1 || n < endpict) {
		fprintf(stderr,
			"illegal endpict %d (must be 1 to %d)\n",
			endpict, n);
		goto fatalerr;
	}
	if (endpict < startpict) {
		int tmp;
		tmp = startpict;
		startpict = endpict;
		endpict = tmp;
	}

afterclarify:;
	switch (mode) {
	case SHOWNPICTS:
		fprintf(stderr, "picts=%d/", n);
		if (camedia_getnpictremain(&n)) {
			fprintf(stderr, "error getting npictremain\n");
			goto fatalerr;
		}
		fprintf(stderr, "remain=%d\n", n);
		break;

	case GETPICTS:
		for (i = startpict; i <= endpict; i++) {
			snprintf(fnamebuf, sizeof(fnamebuf), "pic%05d.jpg", i);
			if (verbose) {
				fprintf(stderr, "getting image %d to %s\n",
					i, fnamebuf);
			}
			fd = open(fnamebuf, openflag, 0644);
			if (fd < 0) {
				perror("open");
				goto fatalerr;
			}
			camedia_getpict(i, pictmode, fd);
			close(fd);
		}
		break;

	case GETPICT1:
		if (startpict != endpict) {
			fprintf(stderr, "more than 1 picts specified\n");
			goto fatalerr;
		}
		if (verbose) {
			fprintf(stderr,
				"getting image %d to stdout\n", startpict);
		}
		camedia_getpict(startpict, pictmode, STDOUT_FILENO);
		break;

	case GETSTATUS:
		camedia_getstatus();
		break;

#ifndef NO_SETTIME
	case SETTIME:
		camedia_settime(time((time_t *)NULL));
		break;
#endif

	case SETID:
		camedia_setid(idstring);
		break;

	case SETSERIALNO:
		camedia_setserialno(serialnostring);
		break;

	case TAKEPICTURE:
		camedia_takepicture();
		break;

	default:
		dprintf((stderr, "internal error - case not matched\n"));
		break;
	}

	camedia_setspeed(-1);

	return;

fatalerr:
	/* cleanup() will be called automatically */
	exit(1);
}

void
usage()
{
	fprintf(stderr,
		"usage:\n"
		"\tcamediaplay [options] port\n"
		"\tcamediaplay [options] -o port > foo.jpg\n"
		"\tcamediaplay [options] -g port\n"
		"\tcamediaplay [options] -S port\n"
#ifndef NO_SETTIME
		"\tcamediaplay [options] -T port\n"
#endif
		"\tcamediaplay [options] -I ID port\n"
		"\tcamediaplay [options] -# SERIALNO port\n"
		"\tcamediaplay [options] -P port\n"
		);
}

void
cleanup()
{
	close(fd0);
	uucpunlock(n0);
}

void
main(argc, argv)
	int argc;
	char **argv;
{
	int opt;
	extern char *optarg;

	uid = getuid();
	euid = geteuid();
	gid = getgid();
	egid = getegid();
	useruid();

	useuucplock = 1;

#ifdef NO_SETTIME
# define ACCEPTOPT	"b:dDitVuve:s:n:agoSI:#:P"
#else
# define ACCEPTOPT	"b:dDitVuve:s:n:agoSTI:#:P"
#endif
	while ((opt = getopt(argc, argv, ACCEPTOPT)) != -1) {
#undef ACCEPTOPT
		switch (opt) {

		/* controls */
		case 'b':
			baudrateflag = atoi(optarg);
			break;
		case 'd':
			dumpflag = 1;
			break;
		case 'D':
			debugflag = 1;
			break;
		case 'i':
			openflag = O_WRONLY | O_EXCL | O_CREAT;
			break;
		case 't':
			pictmode = THUMBNAIL;
			break;
		case 'V':
			pictmode = VOICE;	/* specific to DSC-V1 */
			break;
		case 'u':
			useuucplock = 0;
			break;
		case 'v':
			verbose = 1;
			break;

		/* picture range */
		case 'e':
			endpict = atoi(optarg);
			break;
		case 's':
			startpict = atoi(optarg);
			break;
		case 'n':
			startpict = endpict = atoi(optarg);
			break;
		case 'a':
			allpict = 1;
			break;

		/* mode */
		case 'g':
			mode = GETPICTS;
			break;
		case 'o':
			mode = GETPICT1;
			break;
		case 'S':
			mode = GETSTATUS;
			break;
#ifndef NO_SETTIME
		case 'T':
			mode = SETTIME;
			break;
#endif
		case 'I':
			mode = SETID;
			idstring = optarg;
			break;
		case '#':
			mode = SETSERIALNO;
			serialnostring = optarg;
			break;
		case 'P':
			mode = TAKEPICTURE;
			break;

		default:
			usage();
			exit(1);
		}
	}
	argc -= optind;
	argv += optind;

	if (1 < argc) {
		fprintf(stderr, "invalid argument\n");
		usage();
		exit(1);
	} else if (argc == 0) {
		fprintf(stderr, "no device specified\n");
		usage();
		exit(1);
	}

	n0 = canondev(argv[0]);
	if (!n0) {
		fprintf(stderr, "inappropriate device specified, "
			"or device permission error\n");
		exit(1);
	}

	if (uucplock(n0) < 0) {
		fprintf(stderr, "device %s in use\n", n0);
		exit(1);
	}

	daemonuid();
	fd0 = open(n0, O_RDWR|O_NDELAY);
	if (fd0 < 0) {
		perror("open");
		exit(1);
	}
	useruid();

	atexit(cleanup);

	mainloop();

	exit(0);
}
