/** \ingroup rpmio
 * \file rpmio/rpmio.c
 */

#include "system.h"
#include <stdarg.h>

#if HAVE_MACHINE_TYPES_H
# include <machine/types.h>
#endif

#if HAVE_SYS_SOCKET_H
# include <sys/socket.h>
#endif

#include <netinet/in.h>
#include <arpa/inet.h>		/* XXX for inet_aton and HP-UX */

#if HAVE_NETINET_IN_SYSTM_H
# include <sys/types.h>
# include <netinet/in_systm.h>
#endif

#if HAVE_LIBIO_H && defined(_G_IO_IO_FILE_VERSION)
#define	_USE_LIBIO	1
#endif

/* XXX HP-UX w/o -D_XOPEN_SOURCE needs */
#if !defined(HAVE_HERRNO) && (defined(__hpux) || defined(__LCLINT__))
/*@unchecked@*/
extern int h_errno;
#endif

#ifndef IPPORT_FTP
#define IPPORT_FTP	21
#endif
#ifndef	IPPORT_HTTP
#define	IPPORT_HTTP	80
#endif

#if !defined(HAVE_INET_ATON)
static int inet_aton(const char *cp, struct in_addr *inp)
	/*@modifies *inp @*/
{
    long addr;

    addr = inet_addr(cp);
    if (addr == ((long) -1)) return 0;

    memcpy(inp, &addr, sizeof(addr));
    return 1;
}
#endif

#if defined(USE_ALT_DNS) && USE_ALT_DNS
#include "dns.h"
#endif

#include <rpmio_internal.h>
#undef	fdFileno
#undef	fdOpen
#define	fdOpen	__fdOpen
#undef	fdRead
#define	fdRead	__fdRead
#undef	fdWrite
#define	fdWrite	__fdWrite
#undef	fdClose
#define	fdClose	__fdClose

#include <rpmdav.h>
#include "ugid.h"
#include "rpmmessages.h"

#include "debug.h"

/*@access FILE @*/	/* XXX to permit comparison/conversion with void *. */
/*@access urlinfo @*/
/*@access FDSTAT_t @*/

#define FDNREFS(fd)	(fd ? ((FD_t)fd)->nrefs : -9)
#define FDTO(fd)	(fd ? ((FD_t)fd)->rd_timeoutsecs : -99)
#define FDCPIOPOS(fd)	(fd ? ((FD_t)fd)->fd_cpioPos : -99)

#define	FDONLY(fd)	assert(fdGetIo(fd) == fdio)
#define	GZDONLY(fd)	assert(fdGetIo(fd) == gzdio)
#define	BZDONLY(fd)	assert(fdGetIo(fd) == bzdio)

#define	UFDONLY(fd)	/* assert(fdGetIo(fd) == ufdio) */

#define	fdGetFILE(_fd)	((FILE *)fdGetFp(_fd))

/**
 */
/*@unchecked@*/
#if _USE_LIBIO
int noLibio = 0;
#else
int noLibio = 1;
#endif

#define TIMEOUT_SECS 60

/**
 */
/*@unchecked@*/
static int ftpTimeoutSecs = TIMEOUT_SECS;

/**
 */
/*@unchecked@*/
static int httpTimeoutSecs = TIMEOUT_SECS;

/**
 */
/*@unchecked@*/
int _rpmio_debug = 0;

/**
 */
/*@unchecked@*/
int _av_debug = 0;

/**
 */
/*@unchecked@*/
int _ftp_debug = 0;

/**
 */
/*@unchecked@*/
int _dav_debug = 0;

/**
 * Wrapper to free(3), hides const compilation noise, permit NULL, return NULL.
 * @param p		memory to free
 * @retval		NULL always
 */
/*@unused@*/ static inline /*@null@*/ void *
_free(/*@only@*/ /*@null@*/ /*@out@*/ const void * p)
	/*@modifies p@*/
{
    if (p != NULL)	free((void *)p);
    return NULL;
}

/* =============================================================== */

/*@-boundswrite@*/
static /*@observer@*/ const char * fdbg(/*@null@*/ FD_t fd)
	/*@*/
{
    static char buf[BUFSIZ];
    char *be = buf;
    int i;

    buf[0] = '\0';
    if (fd == NULL)
	return buf;

#ifdef DYING
    sprintf(be, "fd %p", fd);	be += strlen(be);
    if (fd->rd_timeoutsecs >= 0) {
	sprintf(be, " secs %d", fd->rd_timeoutsecs);
	be += strlen(be);
    }
#endif
    if (fd->bytesRemain != -1) {
	sprintf(be, " clen %d", (int)fd->bytesRemain);
	be += strlen(be);
     }
    if (fd->wr_chunked) {
	strcpy(be, " chunked");
	be += strlen(be);
     }
    *be++ = '\t';
    for (i = fd->nfps; i >= 0; i--) {
	FDSTACK_t * fps = &fd->fps[i];
	if (i != fd->nfps)
	    *be++ = ' ';
	*be++ = '|';
	*be++ = ' ';
	if (fps->io == fdio) {
	    sprintf(be, "FD %d fp %p", fps->fdno, fps->fp);
	} else if (fps->io == ufdio) {
	    sprintf(be, "UFD %d fp %p", fps->fdno, fps->fp);
	} else if (fps->io == gzdio) {
	    sprintf(be, "GZD %p fdno %d", fps->fp, fps->fdno);
#if HAVE_BZLIB_H
	} else if (fps->io == bzdio) {
	    sprintf(be, "BZD %p fdno %d", fps->fp, fps->fdno);
#endif
	} else if (fps->io == fpio) {
	    /*@+voidabstract@*/
	    sprintf(be, "%s %p(%d) fdno %d",
		(fps->fdno < 0 ? "LIBIO" : "FP"),
		fps->fp, fileno(((FILE *)fps->fp)), fps->fdno);
	    /*@=voidabstract@*/
	} else {
	    sprintf(be, "??? io %p fp %p fdno %d ???",
		fps->io, fps->fp, fps->fdno);
	}
	be += strlen(be);
	*be = '\0';
    }
    return buf;
}
/*@=boundswrite@*/

/* =============================================================== */
off_t fdSize(FD_t fd)
{
    struct stat sb;
    off_t rc = -1; 

#ifdef	NOISY
DBGIO(0, (stderr, "==>\tfdSize(%p) rc %ld\n", fd, (long)rc));
#endif
    FDSANE(fd);
    if (fd->contentLength >= 0)
	rc = fd->contentLength;
    else switch (fd->urlType) {
    case URL_IS_PATH:
    case URL_IS_UNKNOWN:
	if (fstat(Fileno(fd), &sb) == 0)
	    rc = sb.st_size;
	/*@fallthrough@*/
    case URL_IS_HTTPS:
    case URL_IS_HTTP:
    case URL_IS_HKP:
    case URL_IS_FTP:
    case URL_IS_DASH:
	break;
    }
    return rc;
}

FD_t fdDup(int fdno)
{
    FD_t fd;
    int nfdno;

    if ((nfdno = dup(fdno)) < 0)
	return NULL;
    fd = fdNew("open (fdDup)");
    fdSetFdno(fd, nfdno);
DBGIO(fd, (stderr, "==> fdDup(%d) fd %p %s\n", fdno, (fd ? fd : NULL), fdbg(fd)));
    /*@-refcounttrans@*/ return fd; /*@=refcounttrans@*/
}

static inline /*@unused@*/ int fdSeekNot(void * cookie,
		/*@unused@*/ _libio_pos_t pos,  /*@unused@*/ int whence)
	/*@*/
{
    FD_t fd = c2f(cookie);
    FDSANE(fd);		/* XXX keep gcc quiet */
    return -2;
}

#ifdef UNUSED
FILE *fdFdopen(void * cookie, const char *fmode)
{
    FD_t fd = c2f(cookie);
    int fdno;
    FILE * fp;

    if (fmode == NULL) return NULL;
    fdno = fdFileno(fd);
    if (fdno < 0) return NULL;
    fp = fdopen(fdno, fmode);
DBGIO(fd, (stderr, "==> fdFdopen(%p,\"%s\") fdno %d -> fp %p fdno %d\n", cookie, fmode, fdno, fp, fileno(fp)));
    fd = fdFree(fd, "open (fdFdopen)");
    return fp;
}
#endif

/* =============================================================== */
/*@-mustmod@*/ /* FIX: cookie is modified */
static inline /*@null@*/ FD_t XfdLink(void * cookie, const char * msg,
		const char * file, unsigned line)
	/*@modifies *cookie @*/
{
    FD_t fd;
if (cookie == NULL)
    /*@-castexpose@*/
DBGREFS(0, (stderr, "--> fd  %p ++ %d %s at %s:%u\n", cookie, FDNREFS(cookie)+1, msg, file, line));
    /*@=castexpose@*/
    fd = c2f(cookie);
    if (fd) {
	fd->nrefs++;
DBGREFS(fd, (stderr, "--> fd  %p ++ %d %s at %s:%u %s\n", fd, fd->nrefs, msg, file, line, fdbg(fd)));
    }
    return fd;
}
/*@=mustmod@*/

static inline /*@null@*/
FD_t XfdFree( /*@killref@*/ FD_t fd, const char *msg,
		const char *file, unsigned line)
	/*@modifies fd @*/
{
	int i;

if (fd == NULL)
DBGREFS(0, (stderr, "--> fd  %p -- %d %s at %s:%u\n", fd, FDNREFS(fd), msg, file, line));
    FDSANE(fd);
    if (fd) {
DBGREFS(fd, (stderr, "--> fd  %p -- %d %s at %s:%u %s\n", fd, fd->nrefs, msg, file, line, fdbg(fd)));
	if (--fd->nrefs > 0)
	    /*@-refcounttrans -retalias@*/ return fd; /*@=refcounttrans =retalias@*/
	fd->stats = _free(fd->stats);
	for (i = fd->ndigests - 1; i >= 0; i--) {
	    FDDIGEST_t fddig = fd->digests + i;
	    if (fddig->hashctx == NULL)
		continue;
	    (void) rpmDigestFinal(fddig->hashctx, NULL, NULL, 0);
	    fddig->hashctx = NULL;
	}
	fd->ndigests = 0;
	/*@-refcounttrans@*/ free(fd); /*@=refcounttrans@*/
    }
    return NULL;
}

static inline /*@null@*/
FD_t XfdNew(const char * msg, const char * file, unsigned line)
	/*@globals internalState @*/
	/*@modifies internalState @*/
{
    FD_t fd = xcalloc(1, sizeof(*fd));
    if (fd == NULL) /* XXX xmalloc never returns NULL */
	return NULL;
    fd->nrefs = 0;
    fd->flags = 0;
    fd->magic = FDMAGIC;
    fd->urlType = URL_IS_UNKNOWN;

    fd->nfps = 0;
    memset(fd->fps, 0, sizeof(fd->fps));

    fd->fps[0].io = fdio;
    fd->fps[0].fp = NULL;
    fd->fps[0].fdno = -1;

    fd->url = NULL;
    fd->rd_timeoutsecs = 60;	/* XXX default value used to be -1 */
    fd->contentLength = fd->bytesRemain = -1;
    fd->wr_chunked = 0;
    fd->syserrno = 0;
    fd->errcookie = NULL;
    fd->stats = xcalloc(1, sizeof(*fd->stats));

    fd->ndigests = 0;
    memset(fd->digests, 0, sizeof(fd->digests));

    fd->ftpFileDoneNeeded = 0;
    fd->firstFree = 0;
    fd->fileSize = 0;
    fd->fd_cpioPos = 0;

    return XfdLink(fd, msg, file, line);
}

static ssize_t fdRead(void * cookie, /*@out@*/ char * buf, size_t count)
	/*@globals errno, fileSystem, internalState @*/
	/*@modifies buf, errno, fileSystem, internalState @*/
	/*@requires maxSet(buf) >= (count - 1) @*/
	/*@ensures maxRead(buf) == result @*/
{
    FD_t fd = c2f(cookie);
    ssize_t rc;

    if (fd->bytesRemain == 0) return 0;	/* XXX simulate EOF */

    fdstat_enter(fd, FDSTAT_READ);
/*@-boundswrite@*/
    /* HACK: flimsy wiring for davRead */
    if (fd->req != NULL) {
#ifdef WITH_NEON
	rc = davRead(fd, buf, (count > fd->bytesRemain ? fd->bytesRemain : count));
#else
	rc = -1;
#endif
	/* XXX Chunked davRead EOF. */
	if (rc == 0)
	    fd->bytesRemain = 0;
    } else
	rc = read(fdFileno(fd), buf, (count > fd->bytesRemain ? fd->bytesRemain : count));
/*@=boundswrite@*/
    fdstat_exit(fd, FDSTAT_READ, rc);

    if (fd->ndigests && rc > 0) fdUpdateDigests(fd, buf, rc);

DBGIO(fd, (stderr, "==>\tfdRead(%p,%p,%ld) rc %ld %s\n", cookie, buf, (long)count, (long)rc, fdbg(fd)));

    return rc;
}

static ssize_t fdWrite(void * cookie, const char * buf, size_t count)
	/*@globals errno, fileSystem, internalState @*/
	/*@modifies errno, fileSystem, internalState @*/
{
    FD_t fd = c2f(cookie);
    int fdno = fdFileno(fd);
    ssize_t rc;

    if (fd->bytesRemain == 0) return 0;	/* XXX simulate EOF */

    if (fd->ndigests && count > 0) fdUpdateDigests(fd, buf, count);

    if (count == 0) return 0;

    fdstat_enter(fd, FDSTAT_WRITE);
/*@-boundsread@*/
    /* HACK: flimsy wiring for davWrite */
    if (fd->req != NULL) {
#ifdef WITH_NEON
	rc = davWrite(fd, buf, (count > fd->bytesRemain ? fd->bytesRemain : count));
#else
	return -1;
#endif
    } else
	rc = write(fdno, buf, (count > fd->bytesRemain ? fd->bytesRemain : count));
/*@=boundsread@*/
    fdstat_exit(fd, FDSTAT_WRITE, rc);

DBGIO(fd, (stderr, "==>\tfdWrite(%p,%p,%ld) rc %ld %s\n", cookie, buf, (long)count, (long)rc, fdbg(fd)));

    return rc;
}

static inline int fdSeek(void * cookie, _libio_pos_t pos, int whence)
	/*@globals fileSystem, internalState @*/
	/*@modifies fileSystem, internalState @*/
{
#ifdef USE_COOKIE_SEEK_POINTER
    _IO_off64_t p = *pos;
#else
    off_t p = pos;
#endif
    FD_t fd = c2f(cookie);
    off_t rc;

    assert(fd->bytesRemain == -1);	/* XXX FIXME fadio only for now */
    fdstat_enter(fd, FDSTAT_SEEK);
    rc = lseek(fdFileno(fd), p, whence);
    fdstat_exit(fd, FDSTAT_SEEK, rc);

DBGIO(fd, (stderr, "==>\tfdSeek(%p,%ld,%d) rc %lx %s\n", cookie, (long)p, whence, (unsigned long)rc, fdbg(fd)));

    return rc;
}

static int fdClose( /*@only@*/ void * cookie)
	/*@globals errno, fileSystem, systemState, internalState @*/
	/*@modifies errno, fileSystem, systemState, internalState @*/
{
    FD_t fd;
    int fdno;
    int rc;

    if (cookie == NULL) return -2;
    fd = c2f(cookie);
    fdno = fdFileno(fd);

    fdSetFdno(fd, -1);

    fdstat_enter(fd, FDSTAT_CLOSE);
    /* HACK: flimsy wiring for davClose */
/*@-branchstate@*/
    if (fd->req != NULL) {
#ifdef WITH_NEON
	rc = davClose(fd);
#else
	return -1;
#endif
    } else
	rc = ((fdno >= 0) ? close(fdno) : -2);
/*@=branchstate@*/
    fdstat_exit(fd, FDSTAT_CLOSE, rc);

DBGIO(fd, (stderr, "==>\tfdClose(%p) rc %lx %s\n", (fd ? fd : NULL), (unsigned long)rc, fdbg(fd)));

    fd = fdFree(fd, "open (fdClose)");
    return rc;
}

static /*@null@*/ FD_t fdOpen(const char *path, int flags, mode_t mode)
	/*@globals errno, fileSystem, internalState @*/
	/*@modifies errno, fileSystem, internalState @*/
{
    FD_t fd;
    int fdno;

    fdno = open(path, flags, mode);
    if (fdno < 0) return NULL;
    if (fcntl(fdno, F_SETFD, FD_CLOEXEC)) {
	(void) close(fdno);
	return NULL;
    }
    fd = fdNew("open (fdOpen)");
    fdSetFdno(fd, fdno);
    fd->flags = flags;
DBGIO(fd, (stderr, "==>\tfdOpen(\"%s\",%x,0%o) %s\n", path, (unsigned)flags, (unsigned)mode, fdbg(fd)));
    /*@-refcounttrans@*/ return fd; /*@=refcounttrans@*/
}

/*@-type@*/ /* LCL: function typedefs */
static struct FDIO_s fdio_s = {
  fdRead, fdWrite, fdSeek, fdClose, XfdLink, XfdFree, XfdNew, fdFileno,
  fdOpen, NULL, fdGetFp, NULL,	mkdir, chdir, rmdir, rename, unlink
};
/*@=type@*/
FDIO_t fdio = /*@-compmempass@*/ &fdio_s /*@=compmempass@*/ ;

int fdWritable(FD_t fd, int secs)
{
    int fdno;
    int rc;
#if HAVE_POLL_H
    int msecs = (secs >= 0 ? (1000 * secs) : -1);
    struct pollfd wrfds;
#else
    struct timeval timeout, *tvp = (secs >= 0 ? &timeout : NULL);
    fd_set wrfds;
    FD_ZERO(&wrfds);
#endif
	
    /* HACK: flimsy wiring for davWrite */
    if (fd->req != NULL)
	return 1;

    if ((fdno = fdFileno(fd)) < 0)
	return -1;	/* XXX W2DO? */
	
    do {
#if HAVE_POLL_H
	wrfds.fd = fdno;
	wrfds.events = POLLOUT;
	wrfds.revents = 0;
	rc = poll(&wrfds, 1, msecs);
#else
	if (tvp) {
	    tvp->tv_sec = secs;
	    tvp->tv_usec = 0;
	}
	FD_SET(fdno, &wrfds);
/*@-compdef -nullpass@*/
	rc = select(fdno + 1, NULL, &wrfds, NULL, tvp);
/*@=compdef =nullpass@*/
#endif

	/* HACK: EBADF on PUT chunked termination from ufdClose. */
if (_rpmio_debug && !(rc == 1 && errno == 0))
fprintf(stderr, "*** fdWritable fdno %d rc %d %s\n", fdno, rc, strerror(errno));
	if (rc < 0) {
	    switch (errno) {
	    case EINTR:
		continue;
		/*@notreached@*/ /*@switchbreak@*/ break;
	    default:
		return rc;
		/*@notreached@*/ /*@switchbreak@*/ break;
	    }
	}
	return rc;
    } while (1);
    /*@notreached@*/
}

int fdReadable(FD_t fd, int secs)
{
    int fdno;
    int rc;
#if HAVE_POLL_H
    int msecs = (secs >= 0 ? (1000 * secs) : -1);
    struct pollfd rdfds;
#else
    struct timeval timeout, *tvp = (secs >= 0 ? &timeout : NULL);
    fd_set rdfds;
    FD_ZERO(&rdfds);
#endif

    /* HACK: flimsy wiring for davRead */
    if (fd->req != NULL)
	return 1;

    if ((fdno = fdFileno(fd)) < 0)
	return -1;	/* XXX W2DO? */
	
    do {
#if HAVE_POLL_H
	rdfds.fd = fdno;
	rdfds.events = POLLIN;
	rdfds.revents = 0;
	rc = poll(&rdfds, 1, msecs);
#else
	if (tvp) {
	    tvp->tv_sec = secs;
	    tvp->tv_usec = 0;
	}
	FD_SET(fdno, &rdfds);
	/*@-compdef -nullpass@*/
	rc = select(fdno + 1, &rdfds, NULL, NULL, tvp);
	/*@=compdef =nullpass@*/
#endif

	if (rc < 0) {
	    switch (errno) {
	    case EINTR:
		continue;
		/*@notreached@*/ /*@switchbreak@*/ break;
	    default:
		return rc;
		/*@notreached@*/ /*@switchbreak@*/ break;
	    }
	}
	return rc;
    } while (1);
    /*@notreached@*/
}

/*@-boundswrite@*/
int fdFgets(FD_t fd, char * buf, size_t len)
{
    int fdno;
    int secs = fd->rd_timeoutsecs;
    size_t nb = 0;
    int ec = 0;
    char lastchar = '\0';

    if ((fdno = fdFileno(fd)) < 0)
	return 0;	/* XXX W2DO? */
	
    do {
	int rc;

	/* Is there data to read? */
	rc = fdReadable(fd, secs);

	switch (rc) {
	case -1:	/* error */
	    ec = -1;
	    continue;
	    /*@notreached@*/ /*@switchbreak@*/ break;
	case  0:	/* timeout */
	    ec = -1;
	    continue;
	    /*@notreached@*/ /*@switchbreak@*/ break;
	default:	/* data to read */
	    /*@switchbreak@*/ break;
	}

	errno = 0;
#ifdef	NOISY
	rc = fdRead(fd, buf + nb, 1);
#else
	rc = read(fdFileno(fd), buf + nb, 1);
#endif
	if (rc < 0) {
	    fd->syserrno = errno;
	    switch (errno) {
	    case EWOULDBLOCK:
		continue;
		/*@notreached@*/ /*@switchbreak@*/ break;
	    default:
		/*@switchbreak@*/ break;
	    }
if (_rpmio_debug)
fprintf(stderr, "*** read: fd %p rc %d errno %d %s \"%s\"\n", fd, rc, errno, strerror(errno), buf);
	    ec = -1;
	    break;
	} else if (rc == 0) {
if (_rpmio_debug)
fprintf(stderr, "*** read: fd %p rc %d EOF errno %d %s \"%s\"\n", fd, rc, errno, strerror(errno), buf);
	    break;
	} else {
	    nb += rc;
	    buf[nb] = '\0';
	    lastchar = buf[nb - 1];
	}
    } while (ec == 0 && nb < len && lastchar != '\n');

    return (ec >= 0 ? nb : ec);
}
/*@=boundswrite@*/

/* =============================================================== */
/* Support for FTP/HTTP I/O.
 */
const char *const ftpStrerror(int errorNumber)
{
    switch (errorNumber) {
    case 0:
	return _("Success");

    /* HACK error impediance match, coalesce and rename. */
    case FTPERR_NE_ERROR:
	return ("NE_ERROR: Generic error.");
    case FTPERR_NE_LOOKUP:
	return ("NE_LOOKUP: Hostname lookup failed.");
    case FTPERR_NE_AUTH:
	return ("NE_AUTH: Server authentication failed.");
    case FTPERR_NE_PROXYAUTH:
	return ("NE_PROXYAUTH: Proxy authentication failed.");
    case FTPERR_NE_CONNECT:
	return ("NE_CONNECT: Could not connect to server.");
    case FTPERR_NE_TIMEOUT:
	return ("NE_TIMEOUT: Connection timed out.");
    case FTPERR_NE_FAILED:
	return ("NE_FAILED: The precondition failed.");
    case FTPERR_NE_RETRY:
	return ("NE_RETRY: Retry request.");
    case FTPERR_NE_REDIRECT:
	return ("NE_REDIRECT: Redirect received.");

    case FTPERR_BAD_SERVER_RESPONSE:
	return _("Bad server response");
    case FTPERR_SERVER_IO_ERROR:
	return _("Server I/O error");
    case FTPERR_SERVER_TIMEOUT:
	return _("Server timeout");
    case FTPERR_BAD_HOST_ADDR:
	return _("Unable to lookup server host address");
    case FTPERR_BAD_HOSTNAME:
	return _("Unable to lookup server host name");
    case FTPERR_FAILED_CONNECT:
	return _("Failed to connect to server");
    case FTPERR_FAILED_DATA_CONNECT:
	return _("Failed to establish data connection to server");
    case FTPERR_FILE_IO_ERROR:
	return _("I/O error to local file");
    case FTPERR_PASSIVE_ERROR:
	return _("Error setting remote server to passive mode");
    case FTPERR_FILE_NOT_FOUND:
	return _("File not found on server");
    case FTPERR_NIC_ABORT_IN_PROGRESS:
	return _("Abort in progress");

    case FTPERR_UNKNOWN:
    default:
	return _("Unknown or unexpected error");
    }
}

const char *urlStrerror(const char *url)
{
    const char *retstr;
    /*@-branchstate@*/
    switch (urlIsURL(url)) {
    case URL_IS_HTTPS:
    case URL_IS_HTTP:
    case URL_IS_HKP:
    case URL_IS_FTP:
    {	urlinfo u;
/* XXX This only works for httpReq/ftpLogin/ftpReq failures */
	if (urlSplit(url, &u) == 0) {
	    retstr = ftpStrerror(u->openError);
	} else
	    retstr = "Malformed URL";
    }	break;
    default:
	retstr = strerror(errno);
	break;
    }
    /*@=branchstate@*/
    return retstr;
}

#if !defined(HAVE_GETADDRINFO)
#if !defined(USE_ALT_DNS) || !USE_ALT_DNS 
static int mygethostbyname(const char * host,
		/*@out@*/ struct in_addr * address)
	/*@globals h_errno @*/
	/*@modifies *address @*/
{
    struct hostent * hostinfo;

    /*@-multithreaded @*/
    hostinfo = gethostbyname(host);
    /*@=multithreaded @*/
    if (!hostinfo) return 1;

/*@-boundswrite@*/
    memcpy(address, hostinfo->h_addr_list[0], sizeof(*address));
/*@=boundswrite@*/
    return 0;
}
#endif

/*@-boundsread@*/
/*@-compdef@*/	/* FIX: address->s_addr undefined. */
static int getHostAddress(const char * host, /*@out@*/ struct in_addr * address)
	/*@globals errno, h_errno @*/
	/*@modifies *address, errno @*/
{
#if 0	/* XXX workaround nss_foo module hand-off using valgrind. */
    if (!strcmp(host, "localhost")) {
	/*@-moduncon @*/
	if (!inet_aton("127.0.0.1", address))
	    return FTPERR_BAD_HOST_ADDR;
	/*@=moduncon @*/
    } else
#endif
    if (xisdigit(host[0])) {
	/*@-moduncon @*/
	if (!inet_aton(host, address))
	    return FTPERR_BAD_HOST_ADDR;
	/*@=moduncon @*/
    } else {
	if (mygethostbyname(host, address)) {
	    errno = h_errno;
	    return FTPERR_BAD_HOSTNAME;
	}
    }
    
    return 0;
}
/*@=compdef@*/
/*@=boundsread@*/
#endif

static int tcpConnect(FD_t ctrl, const char * host, int port)
	/*@globals h_errno, fileSystem, internalState @*/
	/*@modifies ctrl, fileSystem, internalState @*/
{
    int fdno = -1;
    int rc;
#ifdef	HAVE_GETADDRINFO
    struct addrinfo hints, *res, *res0;
    char pbuf[NI_MAXSERV];

    memset(&hints, 0, sizeof(hints));
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;
    sprintf(pbuf, "%d", port);
    pbuf[sizeof(pbuf)-1] = '\0';
    rc = FTPERR_FAILED_CONNECT;
    if (getaddrinfo(host, pbuf, &hints, &res0) == 0) {
	for (res = res0; res != NULL; res= res->ai_next) {
	    if ((fdno = socket(res->ai_family, res->ai_socktype, res->ai_protocol)) < 0)
		continue;
	    if (connect(fdno, res->ai_addr, res->ai_addrlen) < 0) {
		close(fdno);
		continue;
	    }
	    /* success */
	    rc = 0;
	    if (_ftp_debug) {
		char hbuf[NI_MAXHOST];
		getnameinfo(res->ai_addr, res->ai_addrlen, hbuf, sizeof(hbuf),
				NULL, 0, NI_NUMERICHOST);
		fprintf(stderr,"++ connect [%s]:%d on fdno %d\n",
				/*@-unrecog@*/ hbuf /*@=unrecog@*/, port, fdno);
	    }
	    break;
	}
	freeaddrinfo(res0);
    }
    if (rc < 0)
	goto errxit;

#else	/* HAVE_GETADDRINFO */			    
    struct sockaddr_in sin;

/*@-boundswrite@*/
    memset(&sin, 0, sizeof(sin));
/*@=boundswrite@*/
    sin.sin_family = AF_INET;
    sin.sin_port = htons(port);
    sin.sin_addr.s_addr = INADDR_ANY;
    
  do {
    if ((rc = getHostAddress(host, &sin.sin_addr)) < 0)
	break;

    if ((fdno = socket(sin.sin_family, SOCK_STREAM, IPPROTO_IP)) < 0) {
	rc = FTPERR_FAILED_CONNECT;
	break;
    }

    /*@-internalglobs@*/
    if (connect(fdno, (struct sockaddr *) &sin, sizeof(sin))) {
	rc = FTPERR_FAILED_CONNECT;
	break;
    }
    /*@=internalglobs@*/
  } while (0);

    if (rc < 0)
	goto errxit;

if (_ftp_debug)
fprintf(stderr,"++ connect %s:%d on fdno %d\n",
/*@-unrecog -moduncon -evalorderuncon @*/
inet_ntoa(sin.sin_addr)
/*@=unrecog =moduncon =evalorderuncon @*/ ,
(int)ntohs(sin.sin_port), fdno);
#endif	/* HAVE_GETADDRINFO */

    fdSetFdno(ctrl, (fdno >= 0 ? fdno : -1));
    return 0;

errxit:
    /*@-observertrans@*/
    fdSetSyserrno(ctrl, errno, ftpStrerror(rc));
    /*@=observertrans@*/
    if (fdno >= 0)
	(void) close(fdno);
    return rc;
}

/*@-boundswrite@*/
static int checkResponse(void * uu, FD_t ctrl,
		/*@out@*/ int *ecp, /*@out@*/ char ** str)
	/*@globals fileSystem @*/
	/*@modifies ctrl, *ecp, *str, fileSystem @*/
{
    urlinfo u = uu;
    char *buf;
    size_t bufAlloced;
    int bufLength = 0; 
    const char *s;
    char *se;
    int ec = 0;
    int moretodo = 1;
    char errorCode[4];
 
    URLSANE(u);
    if (u->bufAlloced == 0 || u->buf == NULL) {
	u->bufAlloced = _url_iobuf_size;
	u->buf = xcalloc(u->bufAlloced, sizeof(u->buf[0]));
    }
    buf = u->buf;
    bufAlloced = u->bufAlloced;
    *buf = '\0';

    errorCode[0] = '\0';
    
    do {
	int rc;

	/*
	 * Read next line from server.
	 */
	se = buf + bufLength;
	*se = '\0';
	rc = fdFgets(ctrl, se, (bufAlloced - bufLength));
	if (rc < 0) {
	    ec = FTPERR_BAD_SERVER_RESPONSE;
	    continue;
	} else if (rc == 0 || fdWritable(ctrl, 0) < 1)
	    moretodo = 0;

	/*
	 * Process next line from server.
	 */
	for (s = se; *s != '\0'; s = se) {
		const char *e;

		while (*se && *se != '\n') se++;

		if (se > s && se[-1] == '\r')
		   se[-1] = '\0';
		if (*se == '\0')
		    /*@innerbreak@*/ break;

if (_ftp_debug)
fprintf(stderr, "<- %s\n", s);

		/* HTTP: header termination on empty line */
		if (*s == '\0') {
		    moretodo = 0;
		    /*@innerbreak@*/ break;
		}
		*se++ = '\0';

		/* HTTP: look for "HTTP/1.1 123 ..." */
		if (!strncmp(s, "HTTP", sizeof("HTTP")-1)) {
		    ctrl->contentLength = -1;
		    if ((e = strchr(s, '.')) != NULL) {
			e++;
			u->httpVersion = *e - '0';
			if (u->httpVersion < 1 || u->httpVersion > 2)
			    ctrl->persist = u->httpVersion = 0;
			else
			    ctrl->persist = 1;
		    }
		    if ((e = strchr(s, ' ')) != NULL) {
			e++;
			if (strchr("0123456789", *e))
			    strncpy(errorCode, e, 3);
			errorCode[3] = '\0';
		    }
		    /*@innercontinue@*/ continue;
		}

		/* HTTP: look for "token: ..." */
		for (e = s; *e && !(*e == ' ' || *e == ':'); e++)
		    {};
		if (e > s && *e++ == ':') {
		    size_t ne = (e - s);
		    while (*e && *e == ' ') e++;
#if 0
		    if (!strncmp(s, "Date:", ne)) {
		    } else
		    if (!strncmp(s, "Server:", ne)) {
		    } else
		    if (!strncmp(s, "Last-Modified:", ne)) {
		    } else
		    if (!strncmp(s, "ETag:", ne)) {
		    } else
#endif
		    if (!strncmp(s, "Accept-Ranges:", ne)) {
			if (!strcmp(e, "bytes"))
			    u->httpHasRange = 1;
			if (!strcmp(e, "none"))
			    u->httpHasRange = 0;
		    } else
		    if (!strncmp(s, "Content-Length:", ne)) {
			if (strchr("0123456789", *e))
			    ctrl->contentLength = atoi(e);
		    } else
		    if (!strncmp(s, "Connection:", ne)) {
			if (!strcmp(e, "close"))
			    ctrl->persist = 0;
		    }
#if 0
		    else
		    if (!strncmp(s, "Content-Type:", ne)) {
		    } else
		    if (!strncmp(s, "Transfer-Encoding:", ne)) {
			if (!strcmp(e, "chunked"))
			    ctrl->wr_chunked = 1;
			else
			    ctrl->wr_chunked = 0;
		    } else
		    if (!strncmp(s, "Allow:", ne)) {
		    }
#endif
		    /*@innercontinue@*/ continue;
		}

		/* HTTP: look for "<TITLE>501 ... </TITLE>" */
		if (!strncmp(s, "<TITLE>", sizeof("<TITLE>")-1))
		    s += sizeof("<TITLE>") - 1;

		/* FTP: look for "123-" and/or "123 " */
		if (strchr("0123456789", *s)) {
		    if (errorCode[0] != '\0') {
			if (!strncmp(s, errorCode, sizeof("123")-1) && s[3] == ' ')
			    moretodo = 0;
		    } else {
			strncpy(errorCode, s, sizeof("123")-1);
			errorCode[3] = '\0';
			if (s[3] != '-')
			    moretodo = 0;
		    }
		}
	}

	if (moretodo && se > s) {
	    bufLength = se - s - 1;
	    if (s != buf)
		memmove(buf, s, bufLength);
	} else {
	    bufLength = 0;
	}
    } while (moretodo && ec == 0);

    if (str)	*str = buf;
    if (ecp)	*ecp = atoi(errorCode);

    return ec;
}
/*@=boundswrite@*/

static int ftpCheckResponse(urlinfo u, /*@out@*/ char ** str)
	/*@globals fileSystem @*/
	/*@modifies u, *str, fileSystem @*/
{
    int ec = 0;
    int rc;

    URLSANE(u);
    rc = checkResponse(u, u->ctrl, &ec, str);

    switch (ec) {
    case 550:
	return FTPERR_FILE_NOT_FOUND;
	/*@notreached@*/ break;
    case 552:
	return FTPERR_NIC_ABORT_IN_PROGRESS;
	/*@notreached@*/ break;
    default:
	if (ec >= 400 && ec <= 599) {
	    return FTPERR_BAD_SERVER_RESPONSE;
	}
	break;
    }
    return rc;
}

static int ftpCommand(urlinfo u, char ** str, ...)
	/*@globals fileSystem, internalState @*/
	/*@modifies u, *str, fileSystem, internalState @*/
{
    va_list ap;
    int len = 0;
    const char * s, * t;
    char * te;
    int rc;

    URLSANE(u);
    va_start(ap, str);
    while ((s = va_arg(ap, const char *)) != NULL) {
	if (len) len++;
	len += strlen(s);
    }
    len += sizeof("\r\n")-1;
    va_end(ap);

/*@-boundswrite@*/
    t = te = alloca(len + 1);

    va_start(ap, str);
    while ((s = va_arg(ap, const char *)) != NULL) {
	if (te > t) *te++ = ' ';
	te = stpcpy(te, s);
    }
    te = stpcpy(te, "\r\n");
    va_end(ap);
/*@=boundswrite@*/

if (_ftp_debug)
fprintf(stderr, "-> %s", t);
    if (fdWrite(u->ctrl, t, (te-t)) != (te-t))
	return FTPERR_SERVER_IO_ERROR;

    rc = ftpCheckResponse(u, str);
    return rc;
}

static int ftpLogin(urlinfo u)
	/*@globals h_errno, fileSystem, internalState @*/
	/*@modifies u, fileSystem, internalState @*/
{
    const char * host;
    const char * user;
    const char * password;
    int port;
    int rc;

    URLSANE(u);
    u->ctrl = fdLink(u->ctrl, "open ctrl");

    if (((host = (u->proxyh ? u->proxyh : u->host)) == NULL)) {
	rc = FTPERR_BAD_HOSTNAME;
	goto errxit;
    }

    if ((port = (u->proxyp > 0 ? u->proxyp : u->port)) < 0) port = IPPORT_FTP;

    /*@-branchstate@*/
    if ((user = (u->proxyu ? u->proxyu : u->user)) == NULL)
	user = "anonymous";
    /*@=branchstate@*/

    /*@-branchstate@*/
    if ((password = u->password) == NULL) {
 	uid_t uid = getuid();
	struct passwd * pw;
	if (uid && (pw = getpwuid(uid)) != NULL) {
/*@-boundswrite@*/
	    char *myp = alloca(strlen(pw->pw_name) + sizeof("@"));
	    strcpy(myp, pw->pw_name);
	    strcat(myp, "@");
/*@=boundswrite@*/
	    password = myp;
	} else {
	    password = "root@";
	}
    }
    /*@=branchstate@*/

    /*@-branchstate@*/
    if (fdFileno(u->ctrl) >= 0 && fdWritable(u->ctrl, 0) < 1)
	/*@-refcounttrans@*/ (void) fdClose(u->ctrl); /*@=refcounttrans@*/
    /*@=branchstate@*/

/*@-usereleased@*/
    if (fdFileno(u->ctrl) < 0) {
	rc = tcpConnect(u->ctrl, host, port);
	if (rc < 0)
	    goto errxit2;
    }

    if ((rc = ftpCheckResponse(u, NULL)))
	goto errxit;

    if ((rc = ftpCommand(u, NULL, "USER", user, NULL)))
	goto errxit;

    if ((rc = ftpCommand(u, NULL, "PASS", password, NULL)))
	goto errxit;

    if ((rc = ftpCommand(u, NULL, "TYPE", "I", NULL)))
	goto errxit;

    /*@-compdef@*/
    return 0;
    /*@=compdef@*/

errxit:
    /*@-observertrans@*/
    fdSetSyserrno(u->ctrl, errno, ftpStrerror(rc));
    /*@=observertrans@*/
errxit2:
    /*@-branchstate@*/
    if (fdFileno(u->ctrl) >= 0)
	/*@-refcounttrans@*/ (void) fdClose(u->ctrl); /*@=refcounttrans@*/
    /*@=branchstate@*/
    /*@-compdef@*/
    return rc;
    /*@=compdef@*/
/*@=usereleased@*/
}

int ftpReq(FD_t data, const char * ftpCmd, const char * ftpArg)
{
    urlinfo u = data->url;
#if !defined(HAVE_GETADDRINFO)
    struct sockaddr_in dataAddress;
#endif	/* HAVE_GETADDRINFO */
    char remoteIP[NI_MAXHOST];
    char * cmd;
    int cmdlen;
    char * passReply;
    char * chptr;
    int rc;
    int epsv;
    int port;

/*@-boundswrite@*/
    URLSANE(u);
    if (ftpCmd == NULL)
	return FTPERR_UNKNOWN;	/* XXX W2DO? */

    cmdlen = strlen(ftpCmd) + (ftpArg ? 1+strlen(ftpArg) : 0) + sizeof("\r\n");
    chptr = cmd = alloca(cmdlen);
    chptr = stpcpy(chptr, ftpCmd);
    if (ftpArg) {
	*chptr++ = ' ';
	chptr = stpcpy(chptr, ftpArg);
    }
    chptr = stpcpy(chptr, "\r\n");
    cmdlen = chptr - cmd;

/*
 * Get the ftp version of the Content-Length.
 */
    if (!strncmp(cmd, "RETR", 4)) {
	unsigned cl;

	passReply = NULL;
	rc = ftpCommand(u, &passReply, "SIZE", ftpArg, NULL);
	if (rc)
	    goto errxit;
	if (sscanf(passReply, "%d %u", &rc, &cl) != 2) {
	    rc = FTPERR_BAD_SERVER_RESPONSE;
	    goto errxit;
	}
	rc = 0;
	data->contentLength = cl;
    }

    epsv = 0;
    passReply = NULL;
#ifdef HAVE_GETNAMEINFO
    rc = ftpCommand(u, &passReply, "EPSV", NULL);
    if (rc==0) {
#ifdef HAVE_GETADDRINFO
	struct sockaddr_storage ss;
#else /* HAVE_GETADDRINFO */
	struct sockaddr_in ss;
#endif /* HAVE_GETADDRINFO */
	int size;
	/* we need to know IP of remote host */
	size=sizeof(ss);
	if ((getpeername(fdFileno(c2f(u->ctrl)), (struct sockaddr *)&ss, &size) == 0) &&
			(getnameinfo((struct sockaddr *)&ss, size, remoteIP, sizeof(remoteIP),
				NULL, 0, NI_NUMERICHOST) == 0))
		epsv++;
	else {
		/* abort EPSV and fall back to PASV */
		rc = ftpCommand(u, &passReply, "ABOR", NULL);
		if (rc) {
		    rc = FTPERR_PASSIVE_ERROR;
		    goto errxit;
		}
	}
    }
    if (epsv==0)
#endif /* HAVE_GETNAMEINFO */
        rc = ftpCommand(u, &passReply, "PASV", NULL);
    if (rc) {
	rc = FTPERR_PASSIVE_ERROR;
	goto errxit;
    }

    chptr = passReply;
    while (*chptr && *chptr != '(') chptr++;
    if (*chptr != '(') return FTPERR_PASSIVE_ERROR; 
    chptr++;
    passReply = chptr;
    while (*chptr && *chptr != ')') chptr++;
    if (*chptr != ')') return FTPERR_PASSIVE_ERROR;
    *chptr-- = '\0';

    if (epsv) {
	int i;
        if(sscanf(passReply,"%*c%*c%*c%d%*c",&i) != 1) {
	   rc = FTPERR_PASSIVE_ERROR;
	   goto errxit;
	}
	port = i;
    } else {
 
    while (*chptr && *chptr != ',') chptr--;
    if (*chptr != ',') return FTPERR_PASSIVE_ERROR;
    chptr--;
    while (*chptr && *chptr != ',') chptr--;
    if (*chptr != ',') return FTPERR_PASSIVE_ERROR;
    *chptr++ = '\0';
    
    /* now passReply points to the IP portion, and chptr points to the
       port number portion */

    {	int i, j;
	if (sscanf(chptr, "%d,%d", &i, &j) != 2) {
	    rc = FTPERR_PASSIVE_ERROR;
	    goto errxit;
	}
	port = (((unsigned)i) << 8) + j;
    }

    chptr = passReply;
    while (*chptr++ != '\0') {
	if (*chptr == ',') *chptr = '.';
    }
/*@=boundswrite@*/
    sprintf(remoteIP, "%s", passReply);
    } /* if (epsv) */

#ifdef HAVE_GETADDRINFO
    {
        struct addrinfo hints, *res, *res0;
	char pbuf[NI_MAXSERV];

	memset(&hints, 0, sizeof(hints));
	hints.ai_family = AF_UNSPEC;
	hints.ai_socktype = SOCK_STREAM;
	hints.ai_flags = AI_NUMERICHOST;
	sprintf(pbuf, "%d", port);
	pbuf[sizeof(pbuf)-1] = '\0';
	if (getaddrinfo(remoteIP, pbuf, &hints, &res0)) {
	    rc = FTPERR_PASSIVE_ERROR;
	    goto errxit;
	}

	for (res = res0; res != NULL; res = res->ai_next) {
	    rc = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
	    fdSetFdno(data, (rc >= 0 ? rc : -1));
	    if (rc < 0) {
	        if (res->ai_next)
		    continue;
		else {
		    rc = FTPERR_FAILED_CONNECT;
		    freeaddrinfo(res0);
		    goto errxit;
		}
	    }
	    data = fdLink(data, "open data (ftpReq)");

	    /* XXX setsockopt SO_LINGER */
	    /* XXX setsockopt SO_KEEPALIVE */
	    /* XXX setsockopt SO_TOS IPTOS_THROUGHPUT */
	    
	    {
		int criterr = 0;
	        while (connect(fdFileno(data), res->ai_addr, res->ai_addrlen) < 0) {
	            if (errno == EINTR)
		        continue;
		    criterr++;
		}
		if (criterr) {
		    if (res->ai_addr) {
		        fdClose(data);
		        continue;
		    } else {
		        rc = FTPERR_PASSIVE_ERROR;
		        freeaddrinfo(res0);
		        goto errxit;
		    }
		}
	    }
	    /* success */
	    rc = 0;
	    break;
	}
	freeaddrinfo(res0);
    }
	    
#else /* HAVE_GETADDRINFO */
    memset(&dataAddress, 0, sizeof(dataAddress));
    dataAddress.sin_family = AF_INET;
    dataAddress.sin_port = htons(port);

    /*@-moduncon@*/
    if (!inet_aton(remoteIP, &dataAddress.sin_addr)) {
	rc = FTPERR_PASSIVE_ERROR;
	goto errxit;
    }
    /*@=moduncon@*/

    rc = socket(AF_INET, SOCK_STREAM, IPPROTO_IP);
    fdSetFdno(data, (rc >= 0 ? rc : -1));
    if (rc < 0) {
	rc = FTPERR_FAILED_CONNECT;
	goto errxit;
    }
    data = fdLink(data, "open data (ftpReq)");

    /* XXX setsockopt SO_LINGER */
    /* XXX setsockopt SO_KEEPALIVE */
    /* XXX setsockopt SO_TOS IPTOS_THROUGHPUT */

    /*@-internalglobs@*/
    while (connect(fdFileno(data), (struct sockaddr *) &dataAddress, 
	        sizeof(dataAddress)) < 0)
    {
	if (errno == EINTR)
	    continue;
	rc = FTPERR_FAILED_DATA_CONNECT;
	goto errxit;
    }
    /*@=internalglobs@*/
#endif /* HAVE_GETADDRINFO */

if (_ftp_debug)
fprintf(stderr, "-> %s", cmd);
    if (fdWrite(u->ctrl, cmd, cmdlen) != cmdlen) {
	rc = FTPERR_SERVER_IO_ERROR;
	goto errxit;
    }

    if ((rc = ftpCheckResponse(u, NULL))) {
	goto errxit;
    }

    data->ftpFileDoneNeeded = 1;
    u->ctrl = fdLink(u->ctrl, "grab data (ftpReq)");
    u->ctrl = fdLink(u->ctrl, "open data (ftpReq)");
    return 0;

errxit:
    /*@-observertrans@*/
    fdSetSyserrno(u->ctrl, errno, ftpStrerror(rc));
    /*@=observertrans@*/
    /*@-branchstate@*/
    if (fdFileno(data) >= 0)
	/*@-refcounttrans@*/ (void) fdClose(data); /*@=refcounttrans@*/
    /*@=branchstate@*/
    return rc;
}

/*@unchecked@*/ /*@null@*/
static rpmCallbackFunction	urlNotify = NULL;

/*@unchecked@*/ /*@null@*/
static void *			urlNotifyData = NULL;

/*@unchecked@*/
static int			urlNotifyCount = -1;

void urlSetCallback(rpmCallbackFunction notify, void *notifyData, int notifyCount) {
    urlNotify = notify;
    urlNotifyData = notifyData;
    urlNotifyCount = (notifyCount >= 0) ? notifyCount : 4096;
}

int ufdCopy(FD_t sfd, FD_t tfd)
{
    char buf[BUFSIZ];
    int itemsRead;
    int itemsCopied = 0;
    int rc = 0;
    int notifier = -1;

    if (urlNotify) {
/*@-boundsread@*/
	/*@-noeffectuncon @*/ /* FIX: check rc */
	(void)(*urlNotify) (NULL, RPMCALLBACK_INST_OPEN_FILE,
		0, 0, NULL, urlNotifyData);
	/*@=noeffectuncon @*/
/*@=boundsread@*/
    }
    
    while (1) {
	rc = Fread(buf, sizeof(buf[0]), sizeof(buf), sfd);
	if (rc < 0)
	    break;
	else if (rc == 0) {
	    rc = itemsCopied;
	    break;
	}
	itemsRead = rc;
	rc = Fwrite(buf, sizeof(buf[0]), itemsRead, tfd);
	if (rc < 0)
	    break;
 	if (rc != itemsRead) {
	    rc = FTPERR_FILE_IO_ERROR;
	    break;
	}

	itemsCopied += itemsRead;
	if (urlNotify && urlNotifyCount > 0) {
	    int n = itemsCopied/urlNotifyCount;
	    if (n != notifier) {
/*@-boundsread@*/
		/*@-noeffectuncon @*/ /* FIX: check rc */
		(void)(*urlNotify) (NULL, RPMCALLBACK_INST_PROGRESS,
			itemsCopied, 0, NULL, urlNotifyData);
		/*@=noeffectuncon @*/
/*@=boundsread@*/
		notifier = n;
	    }
	}
    }

    DBGIO(sfd, (stderr, "++ copied %d bytes: %s\n", itemsCopied,
	ftpStrerror(rc)));

    if (urlNotify) {
/*@-boundsread@*/
	/*@-noeffectuncon @*/ /* FIX: check rc */
	(void)(*urlNotify) (NULL, RPMCALLBACK_INST_OPEN_FILE,
		itemsCopied, itemsCopied, NULL, urlNotifyData);
	/*@=noeffectuncon @*/
/*@=boundsread@*/
    }
    
    return rc;
}

static int urlConnect(const char * url, /*@out@*/ urlinfo * uret)
	/*@globals h_errno, fileSystem, internalState @*/
	/*@modifies *uret, fileSystem, internalState @*/
{
    urlinfo u;
    int rc = 0;

    if (urlSplit(url, &u) < 0)
	return -1;

    if (u->urltype == URL_IS_FTP) {
	FD_t fd;

	if ((fd = u->ctrl) == NULL) {
	    fd = u->ctrl = fdNew("persist ctrl (urlConnect FTP)");
	    fdSetIo(u->ctrl, ufdio);
	}
	
	fd->rd_timeoutsecs = ftpTimeoutSecs;
	fd->contentLength = fd->bytesRemain = -1;
	fd->url = NULL;		/* XXX FTP ctrl has not */
	fd->ftpFileDoneNeeded = 0;
	fd = fdLink(fd, "grab ctrl (urlConnect FTP)");

	if (fdFileno(u->ctrl) < 0) {
	    rpmMessage(RPMMESS_DEBUG, _("logging into %s as %s, pw %s\n"),
			u->host ? u->host : "???",
			u->user ? u->user : "ftp",
			u->password ? u->password : "(username)");

	    if ((rc = ftpLogin(u)) < 0) {	/* XXX save ftpLogin error */
		u->ctrl = fdFree(fd, "grab ctrl (urlConnect FTP)");
		u->openError = rc;
	    }
	}
    }

/*@-boundswrite@*/
    if (uret != NULL)
	*uret = urlLink(u, "urlConnect");
/*@=boundswrite@*/
    u = urlFree(u, "urlSplit (urlConnect)");	

    return rc;
}

int ufdGetFile(FD_t sfd, FD_t tfd)
{
    int rc;

    FDSANE(sfd);
    FDSANE(tfd);
    rc = ufdCopy(sfd, tfd);
    (void) Fclose(sfd);
    if (rc > 0)		/* XXX ufdCopy now returns no. bytes copied */
	rc = 0;
    return rc;
}

int ftpCmd(const char * cmd, const char * url, const char * arg2)
{
    urlinfo u;
    int rc;
    const char * path;

    if (urlConnect(url, &u) < 0)
	return -1;

    (void) urlPath(url, &path);

    rc = ftpCommand(u, NULL, cmd, path, arg2, NULL);
    u->ctrl = fdFree(u->ctrl, "grab ctrl (ftpCmd)");
    return rc;
}

/* XXX these aren't worth the pain of including correctly */
#if !defined(IAC)
#define	IAC	255		/* interpret as command: */
#endif
#if !defined(IP)
#define	IP	244		/* interrupt process--permanently */
#endif
#if !defined(DM)
#define	DM	242		/* data mark--for connect. cleaning */
#endif
#if !defined(SHUT_RDWR)
#define	SHUT_RDWR	1+1
#endif

static int ftpAbort(urlinfo u, FD_t data)
	/*@globals fileSystem, internalState @*/
	/*@modifies u, data, fileSystem, internalState @*/
{
    static unsigned char ipbuf[3] = { IAC, IP, IAC };
    FD_t ctrl;
    int rc;
    int tosecs;

    URLSANE(u);

    if (data != NULL) {
	data->ftpFileDoneNeeded = 0;
	if (fdFileno(data) >= 0)
	    u->ctrl = fdFree(u->ctrl, "open data (ftpAbort)");
	u->ctrl = fdFree(u->ctrl, "grab data (ftpAbort)");
    }
    ctrl = u->ctrl;

    DBGIO(0, (stderr, "-> ABOR\n"));

/*@-usereleased -compdef@*/
    if (send(fdFileno(ctrl), ipbuf, sizeof(ipbuf), MSG_OOB) != sizeof(ipbuf)) {
	/*@-refcounttrans@*/ (void) fdClose(ctrl); /*@=refcounttrans@*/
	return FTPERR_SERVER_IO_ERROR;
    }

    sprintf(u->buf, "%cABOR\r\n",(char) DM);
    if (fdWrite(ctrl, u->buf, 7) != 7) {
	/*@-refcounttrans@*/ (void) fdClose(ctrl); /*@=refcounttrans@*/
	return FTPERR_SERVER_IO_ERROR;
    }

    if (data && fdFileno(data) >= 0) {
	/* XXX shorten data drain time wait */
	tosecs = data->rd_timeoutsecs;
	data->rd_timeoutsecs = 10;
	if (fdReadable(data, data->rd_timeoutsecs) > 0) {
/*@-boundswrite@*/
	    while (timedRead(data, u->buf, u->bufAlloced) > 0)
		u->buf[0] = '\0';
/*@=boundswrite@*/
	}
	data->rd_timeoutsecs = tosecs;
	/* XXX ftp abort needs to close the data channel to receive status */
	(void) shutdown(fdFileno(data), SHUT_RDWR);
	(void) close(fdFileno(data));
	data->fps[0].fdno = -1;	/* XXX WRONG but expedient */
    }

    /* XXX shorten ctrl drain time wait */
    tosecs = u->ctrl->rd_timeoutsecs;
    u->ctrl->rd_timeoutsecs = 10;
    if ((rc = ftpCheckResponse(u, NULL)) == FTPERR_NIC_ABORT_IN_PROGRESS) {
	rc = ftpCheckResponse(u, NULL);
    }
    rc = ftpCheckResponse(u, NULL);
    u->ctrl->rd_timeoutsecs = tosecs;

    return rc;
/*@=usereleased =compdef@*/
}

static int ftpFileDone(urlinfo u, FD_t data)
	/*@globals fileSystem @*/
	/*@modifies u, data, fileSystem @*/
{
    int rc = 0;

    URLSANE(u);
    assert(data->ftpFileDoneNeeded);

    if (data->ftpFileDoneNeeded) {
	data->ftpFileDoneNeeded = 0;
	u->ctrl = fdFree(u->ctrl, "open data (ftpFileDone)");
	u->ctrl = fdFree(u->ctrl, "grab data (ftpFileDone)");
	rc = ftpCheckResponse(u, NULL);
    }
    return rc;
}

static int httpResp(urlinfo u, FD_t ctrl, /*@out@*/ char ** str)
	/*@globals fileSystem @*/
	/*@modifies ctrl, *str, fileSystem @*/
{
    int ec = 0;
    int rc;

    URLSANE(u);
    rc = checkResponse(u, ctrl, &ec, str);

if (_ftp_debug && !(rc == 0 && (ec == 200 || ec == 201)))
fprintf(stderr, "*** httpResp: rc %d ec %d\n", rc, ec);

    switch (ec) {
    case 200:
    case 201:			/* 201 Created. */
	break;
    case 204:			/* HACK: if overwriting, 204 No Content. */
    case 403:			/* 403 Forbidden. */
	ctrl->syserrno = EACCES;	/* HACK */
	rc = FTPERR_UNKNOWN;
	break;
    default:
	rc = FTPERR_FILE_NOT_FOUND;
	break;
    }
    return rc;
}

static int httpReq(FD_t ctrl, const char * httpCmd, const char * httpArg)
	/*@globals h_errno, fileSystem, internalState @*/
	/*@modifies ctrl, fileSystem, internalState @*/
{
    urlinfo u;
    const char * host;
    const char * path;
    char hthost[NI_MAXHOST];
    int port;
    int rc;
    char * req;
    size_t len;
    int retrying = 0;

assert(ctrl != NULL);
    u = ctrl->url;
    URLSANE(u);

    if (((host = (u->proxyh ? u->proxyh : u->host)) == NULL))
	return FTPERR_BAD_HOSTNAME;
    if (strchr(host, ':'))
	sprintf(hthost, "[%s]", host);
    else
	strcpy(hthost, host);

    if ((port = (u->proxyp > 0 ? u->proxyp : u->port)) < 0) port = 80;
    path = (u->proxyh || u->proxyp > 0) ? u->url : httpArg;
    /*@-branchstate@*/
    if (path == NULL) path = "";
    /*@=branchstate@*/

reopen:
    /*@-branchstate@*/
    if (fdFileno(ctrl) >= 0 && (rc = fdWritable(ctrl, 0)) < 1) {
	/*@-refcounttrans@*/ (void) fdClose(ctrl); /*@=refcounttrans@*/
    }
    /*@=branchstate@*/

/*@-usereleased@*/
    if (fdFileno(ctrl) < 0) {
	rc = tcpConnect(ctrl, host, port);
	if (rc < 0)
	    goto errxit2;
	ctrl = fdLink(ctrl, "open ctrl (httpReq)");
    }

    len = sizeof("\
req x HTTP/1.0\r\n\
User-Agent: rpm/3.0.4\r\n\
Host: y:z\r\n\
Accept: text/plain\r\n\
Transfer-Encoding: chunked\r\n\
\r\n\
") + strlen(httpCmd) + strlen(path) + sizeof(VERSION) + strlen(hthost) + 20;

/*@-boundswrite@*/
    req = alloca(len);
    *req = '\0';

  if (!strcmp(httpCmd, "PUT")) {
    sprintf(req, "\
%s %s HTTP/1.%d\r\n\
User-Agent: rpm/%s\r\n\
Host: %s:%d\r\n\
Accept: text/plain\r\n\
Transfer-Encoding: chunked\r\n\
\r\n\
",	httpCmd, path, (u->httpVersion ? 1 : 0), VERSION, hthost, port);
} else {
    sprintf(req, "\
%s %s HTTP/1.%d\r\n\
User-Agent: rpm/%s\r\n\
Host: %s:%d\r\n\
Accept: text/plain\r\n\
\r\n\
",	httpCmd, path, (u->httpVersion ? 1 : 0), VERSION, hthost, port);
}
/*@=boundswrite@*/

if (_ftp_debug)
fprintf(stderr, "-> %s", req);

    len = strlen(req);
    if (fdWrite(ctrl, req, len) != len) {
	rc = FTPERR_SERVER_IO_ERROR;
	goto errxit;
    }

    /*@-branchstate@*/
    if (!strcmp(httpCmd, "PUT")) {
	ctrl->wr_chunked = 1;
    } else {

	rc = httpResp(u, ctrl, NULL);

	if (rc) {
	    if (!retrying) {	/* not HTTP_OK */
		retrying = 1;
		/*@-refcounttrans@*/ (void) fdClose(ctrl); /*@=refcounttrans@*/
		goto reopen;
	    }
	    goto errxit;
	}
    }
    /*@=branchstate@*/

    ctrl = fdLink(ctrl, "open data (httpReq)");
    return 0;

errxit:
    /*@-observertrans@*/
    fdSetSyserrno(ctrl, errno, ftpStrerror(rc));
    /*@=observertrans@*/
errxit2:
    /*@-branchstate@*/
    if (fdFileno(ctrl) >= 0)
	/*@-refcounttrans@*/ (void) fdClose(ctrl); /*@=refcounttrans@*/
    /*@=branchstate@*/
    return rc;
/*@=usereleased@*/
}

/* XXX DYING: unused */
void * ufdGetUrlinfo(FD_t fd)
{
    FDSANE(fd);
    if (fd->url == NULL)
	return NULL;
    return urlLink(fd->url, "ufdGetUrlinfo");
}

/* =============================================================== */
static ssize_t ufdRead(void * cookie, /*@out@*/ char * buf, size_t count)
	/*@globals fileSystem, internalState @*/
	/*@modifies buf, fileSystem, internalState @*/
        /*@requires maxSet(buf) >= (count - 1) @*/
        /*@ensures maxRead(buf) == result @*/
{
    FD_t fd = c2f(cookie);
    int bytesRead;
    int total;

    /* XXX preserve timedRead() behavior */
    if (fdGetIo(fd) == fdio) {
	struct stat sb;
	int fdno = fdFileno(fd);
	(void) fstat(fdno, &sb);
	if (S_ISREG(sb.st_mode))
	    return fdRead(fd, buf, count);
    }

    UFDONLY(fd);
    assert(fd->rd_timeoutsecs >= 0);

    for (total = 0; total < count; total += bytesRead) {

	int rc;

	bytesRead = 0;

	/* Is there data to read? */
	if (fd->bytesRemain == 0) return total;	/* XXX simulate EOF */
	rc = fdReadable(fd, fd->rd_timeoutsecs);

	switch (rc) {
	case -1:	/* error */
	case  0:	/* timeout */
	    return total;
	    /*@notreached@*/ /*@switchbreak@*/ break;
	default:	/* data to read */
	    /*@switchbreak@*/ break;
	}

/*@-boundswrite@*/
	rc = fdRead(fd, buf + total, count - total);
/*@=boundswrite@*/

	if (rc < 0) {
	    switch (errno) {
	    case EWOULDBLOCK:
		continue;
		/*@notreached@*/ /*@switchbreak@*/ break;
	    default:
		/*@switchbreak@*/ break;
	    }
if (_rpmio_debug)
fprintf(stderr, "*** read: rc %d errno %d %s \"%s\"\n", rc, errno, strerror(errno), buf);
	    return rc;
	    /*@notreached@*/ break;
	} else if (rc == 0) {
	    return total;
	    /*@notreached@*/ break;
	}
	bytesRead = rc;
    }

    return count;
}

static ssize_t ufdWrite(void * cookie, const char * buf, size_t count)
	/*@globals fileSystem, internalState @*/
	/*@modifies fileSystem, internalState @*/
{
    FD_t fd = c2f(cookie);
    int bytesWritten;
    int total = 0;

#ifdef	NOTYET
    if (fdGetIo(fd) == fdio) {
	struct stat sb;
	(void) fstat(fdGetFdno(fd), &sb);
	if (S_ISREG(sb.st_mode))
	    return fdWrite(fd, buf, count);
    }
#endif

    UFDONLY(fd);

    for (total = 0; total < count; total += bytesWritten) {

	int rc;

	bytesWritten = 0;

	/* Is there room to write data? */
	if (fd->bytesRemain == 0) {
fprintf(stderr, "*** ufdWrite fd %p WRITE PAST END OF CONTENT\n", fd);
	    return total;	/* XXX simulate EOF */
	}
	rc = fdWritable(fd, 2);		/* XXX configurable? */

	switch (rc) {
	case -1:	/* error */
	case  0:	/* timeout */
	    return total;
	    /*@notreached@*/ /*@switchbreak@*/ break;
	default:	/* data to write */
	    /*@switchbreak@*/ break;
	}

	rc = fdWrite(fd, buf + total, count - total);

	if (rc < 0) {
	    switch (errno) {
	    case EWOULDBLOCK:
		continue;
		/*@notreached@*/ /*@switchbreak@*/ break;
	    default:
		/*@switchbreak@*/ break;
	    }
if (_rpmio_debug)
fprintf(stderr, "*** write: rc %d errno %d %s \"%s\"\n", rc, errno, strerror(errno), buf);
	    return rc;
	    /*@notreached@*/ break;
	} else if (rc == 0) {
	    return total;
	    /*@notreached@*/ break;
	}
	bytesWritten = rc;
    }

    return count;
}

static inline int ufdSeek(void * cookie, _libio_pos_t pos, int whence)
	/*@globals fileSystem, internalState @*/
	/*@modifies fileSystem, internalState @*/
{
    FD_t fd = c2f(cookie);

    switch (fd->urlType) {
    case URL_IS_UNKNOWN:
    case URL_IS_PATH:
	break;
    case URL_IS_HTTPS:
    case URL_IS_HTTP:
    case URL_IS_HKP:
    case URL_IS_FTP:
    case URL_IS_DASH:
    default:
        return -2;
	/*@notreached@*/ break;
    }
    return fdSeek(cookie, pos, whence);
}

/*@-branchstate@*/
/*@-usereleased@*/	/* LCL: fd handling is tricky here. */
int ufdClose( /*@only@*/ void * cookie)
{
    FD_t fd = c2f(cookie);

    UFDONLY(fd);

    /*@-branchstate@*/
    if (fd->url) {
	urlinfo u = fd->url;

	if (fd == u->data)
		fd = u->data = fdFree(fd, "grab data (ufdClose persist)");
	else
		fd = fdFree(fd, "grab data (ufdClose)");
	(void) urlFree(fd->url, "url (ufdClose)");
	fd->url = NULL;
	u->ctrl = fdFree(u->ctrl, "grab ctrl (ufdClose)");

	if (u->urltype == URL_IS_FTP) {

	    /* XXX if not using libio, lose the fp from fpio */
	    {   FILE * fp;
		/*@+voidabstract -nullpass@*/
		fp = fdGetFILE(fd);
		if (noLibio && fp)
		    fdSetFp(fd, NULL);
		/*@=voidabstract =nullpass@*/
	    }

	    /*
	     * Non-error FTP has 4 refs on the data fd:
	     *	"persist data (ufdOpen FTP)"		rpmio.c:888
	     *	"grab data (ufdOpen FTP)"		rpmio.c:892
	     *	"open data (ftpReq)"			ftp.c:633
	     *	"fopencookie"				rpmio.c:1507
	     *
	     * Non-error FTP has 5 refs on the ctrl fd:
	     *	"persist ctrl"				url.c:176
	     *	"grab ctrl (urlConnect FTP)"		rpmio.c:404
	     *	"open ctrl"				ftp.c:504
	     *	"grab data (ftpReq)"			ftp.c:661
	     *	"open data (ftpReq)"			ftp.c:662
	     */
	    if (fd->bytesRemain > 0) {
		if (fd->ftpFileDoneNeeded) {
		    if (fdReadable(u->ctrl, 0) > 0)
			(void) ftpFileDone(u, fd);
		    else
			(void) ftpAbort(u, fd);
		}
	    } else {
		int rc;
		/* XXX STOR et al require close before ftpFileDone */
		/*@-refcounttrans@*/
		rc = fdClose(fd);
		/*@=refcounttrans@*/
#if 0	/* XXX error exit from ufdOpen does not have this set */
		assert(fd->ftpFileDoneNeeded != 0);
#endif
		/*@-compdef@*/ /* FIX: u->data undefined */
		if (fd->ftpFileDoneNeeded)
		    (void) ftpFileDone(u, fd);
		/*@=compdef@*/
		return rc;
	    }
	}

	/* XXX Why not (u->urltype == URL_IS_HTTP) ??? */
	/* XXX Why not (u->urltype == URL_IS_HTTPS) ??? */
	/* XXX Why not (u->urltype == URL_IS_HKP) ??? */
	if (u->scheme != NULL
	 && (!strncmp(u->scheme, "http", sizeof("http")-1) || !strncmp(u->scheme, "hkp", sizeof("hkp")-1)))
	{
	    /*
	     * HTTP has 4 (or 5 if persistent malloc) refs on the fd:
	     *	"persist ctrl"				url.c:177
	     *	"grab ctrl (ufdOpen HTTP)"		rpmio.c:924
	     *	"grab data (ufdOpen HTTP)"		rpmio.c:928
	     *	"open ctrl (httpReq)"			ftp.c:382
	     *	"open data (httpReq)"			ftp.c:435
	     */

	    if (fd == u->ctrl)
		fd = u->ctrl = fdFree(fd, "open data (ufdClose HTTP persist ctrl)");
	    else if (fd == u->data)
		fd = u->data = fdFree(fd, "open data (ufdClose HTTP persist data)");
	    else
		fd = fdFree(fd, "open data (ufdClose HTTP)");

	    /* XXX if not using libio, lose the fp from fpio */
	    {   FILE * fp;
		/*@+voidabstract -nullpass@*/
		fp = fdGetFILE(fd);
		if (noLibio && fp)
		    fdSetFp(fd, NULL);
		/*@=voidabstract =nullpass@*/
	    }

	    /* If content remains, then don't persist. */
	    if (fd->bytesRemain > 0)
		fd->persist = 0;
	    fd->contentLength = fd->bytesRemain = -1;

	    /* If persisting, then Fclose will juggle refcounts. */
	    if (fd->persist && (fd == u->ctrl || fd == u->data))
		return 0;
	}
    }
    return fdClose(fd);
}
/*@=usereleased@*/
/*@=branchstate@*/

/*@-nullstate@*/	/* FIX: u->{ctrl,data}->url undef after XurlLink. */
/*@null@*/ FD_t ftpOpen(const char *url, /*@unused@*/ int flags,
		/*@unused@*/ mode_t mode, /*@out@*/ urlinfo *uret)
	/*@modifies *uret @*/
{
    urlinfo u = NULL;
    FD_t fd = NULL;

#if 0	/* XXX makeTempFile() heartburn */
    assert(!(flags & O_RDWR));
#endif
    if (urlConnect(url, &u) < 0)
	goto exit;

    if (u->data == NULL)
	u->data = fdNew("persist data (ftpOpen)");

    if (u->data->url == NULL)
	fd = fdLink(u->data, "grab data (ftpOpen persist data)");
    else
	fd = fdNew("grab data (ftpOpen)");

    if (fd) {
	fdSetIo(fd, ufdio);
	fd->ftpFileDoneNeeded = 0;
	fd->rd_timeoutsecs = ftpTimeoutSecs;
	fd->contentLength = fd->bytesRemain = -1;
	fd->url = urlLink(u, "url (ufdOpen FTP)");
	fd->urlType = URL_IS_FTP;
    }

exit:
/*@-boundswrite@*/
    if (uret)
	*uret = u;
/*@=boundswrite@*/
    /*@-refcounttrans@*/
    return fd;
    /*@=refcounttrans@*/
}
/*@=nullstate@*/

#ifndef WITH_NEON
/*@-nullstate@*/        /* FIX: u->{ctrl,data}->url undef after XurlLink. */
static /*@null@*/ FD_t httpOpen(const char * url, /*@unused@*/ int flags,
                /*@unused@*/ mode_t mode, /*@out@*/ urlinfo * uret)
        /*@globals internalState @*/
        /*@modifies *uret, internalState @*/
{
    urlinfo u = NULL;
    FD_t fd = NULL;

#if 0   /* XXX makeTempFile() heartburn */
    assert(!(flags & O_RDWR));
#endif
    if (urlSplit(url, &u))
        goto exit;

    if (u->ctrl == NULL)
        u->ctrl = fdNew("persist ctrl (httpOpen)");
    if (u->ctrl->nrefs > 2 && u->data == NULL)
        u->data = fdNew("persist data (httpOpen)");

    if (u->ctrl->url == NULL)
        fd = fdLink(u->ctrl, "grab ctrl (httpOpen persist ctrl)");
    else if (u->data->url == NULL)
        fd = fdLink(u->data, "grab ctrl (httpOpen persist data)");
    else
        fd = fdNew("grab ctrl (httpOpen)");

    if (fd) {
        fdSetIo(fd, ufdio);
        fd->ftpFileDoneNeeded = 0;
        fd->rd_timeoutsecs = httpTimeoutSecs;
        fd->contentLength = fd->bytesRemain = -1;
        fd->url = urlLink(u, "url (httpOpen)");
        fd = fdLink(fd, "grab data (httpOpen)");
        fd->urlType = URL_IS_HTTP;
    }

exit:
/*@-boundswrite@*/
    if (uret)
        *uret = u;
/*@=boundswrite@*/
    /*@-refcounttrans@*/
    return fd;
    /*@=refcounttrans@*/
}
/*@=nullstate@*/
#endif

static /*@null@*/ FD_t ufdOpen(const char * url, int flags, mode_t mode)
	/*@globals h_errno, fileSystem, internalState @*/
	/*@modifies fileSystem, internalState @*/
{
    FD_t fd = NULL;
    const char * cmd;
    urlinfo u;
    const char * path;
    urltype urlType = urlPath(url, &path);

if (_rpmio_debug)
fprintf(stderr, "*** ufdOpen(%s,0x%x,0%o)\n", url, (unsigned)flags, (unsigned)mode);

    /*@-branchstate@*/
    switch (urlType) {
    case URL_IS_FTP:
	fd = ftpOpen(url, flags, mode, &u);
	if (fd == NULL || u == NULL)
	    break;

	/* XXX W2DO? use STOU rather than STOR to prevent clobbering */
	cmd = ((flags & O_WRONLY) 
		?  ((flags & O_APPEND) ? "APPE" :
		   ((flags & O_CREAT) ? "STOR" : "STOR"))
		:  ((flags & O_CREAT) ? "STOR" : "RETR"));
	u->openError = ftpReq(fd, cmd, path);
	if (u->openError < 0) {
	    /* XXX make sure that we can exit through ufdClose */
	    fd = fdLink(fd, "error data (ufdOpen FTP)");
	} else {
	    fd->bytesRemain = ((!strcmp(cmd, "RETR"))
		?  fd->contentLength : -1);
	    fd->wr_chunked = 0;
	}
	break;
    case URL_IS_HTTPS:
    case URL_IS_HTTP:
    case URL_IS_HKP:
#ifdef WITH_NEON
	fd = davOpen(url, flags, mode, &u);
#else
	fd = httpOpen(url, flags, mode, &u);
#endif
	if (fd == NULL || u == NULL)
	    break;

	cmd = ((flags & O_WRONLY)
		?  ((flags & O_APPEND) ? "PUT" :
		   ((flags & O_CREAT) ? "PUT" : "PUT"))
		: "GET");
#ifdef WITH_NEON
	u->openError = davReq(fd, cmd, path);
#else
	u->openError = httpReq(fd, cmd, path);
#endif
	if (u->openError < 0) {
	    /* XXX make sure that we can exit through ufdClose */
	    fd = fdLink(fd, "error ctrl (ufdOpen HTTP)");
	    fd = fdLink(fd, "error data (ufdOpen HTTP)");
	} else {
	    fd->bytesRemain = ((!strcmp(cmd, "GET"))
		?  fd->contentLength : -1);
	    fd->wr_chunked = ((!strcmp(cmd, "PUT"))
		?  fd->wr_chunked : 0);
	}
	break;
    case URL_IS_DASH:
	assert(!(flags & O_RDWR));
	fd = fdDup( ((flags & O_WRONLY) ? STDOUT_FILENO : STDIN_FILENO) );
	if (fd) {
	    fdSetIo(fd, ufdio);
	    fd->rd_timeoutsecs = 600;	/* XXX W2DO? 10 mins? */
	    fd->contentLength = fd->bytesRemain = -1;
	}
	break;
    case URL_IS_PATH:
    case URL_IS_UNKNOWN:
    default:
	fd = fdOpen(path, flags, mode);
	if (fd) {
	    fdSetIo(fd, ufdio);
	    fd->rd_timeoutsecs = 60;
	    fd->contentLength = fd->bytesRemain = -1;
	}
	break;
    }
    /*@=branchstate@*/

    if (fd == NULL) return NULL;
    fd->urlType = urlType;
    if (Fileno(fd) < 0) {
	(void) ufdClose(fd);
	return NULL;
    }
DBGIO(fd, (stderr, "==>\tufdOpen(\"%s\",%x,0%o) %s\n", url, (unsigned)flags, (unsigned)mode, fdbg(fd)));
    return fd;
}

/*@-type@*/ /* LCL: function typedefs */
static struct FDIO_s ufdio_s = {
  ufdRead, ufdWrite, ufdSeek, ufdClose, XfdLink, XfdFree, XfdNew, fdFileno,
  ufdOpen, NULL, fdGetFp, NULL,	Mkdir, Chdir, Rmdir, Rename, Unlink
};
/*@=type@*/
FDIO_t ufdio = /*@-compmempass@*/ &ufdio_s /*@=compmempass@*/ ;

/* =============================================================== */
/* Support for GZIP library.
 */
#ifdef	HAVE_ZLIB_H
/*@-moduncon@*/

/*@-noparams@*/
#include <zlib.h>
/*@=noparams@*/

static inline /*@dependent@*/ /*@null@*/ void * gzdFileno(FD_t fd)
	/*@*/
{
    void * rc = NULL;
    int i;

    FDSANE(fd);
    for (i = fd->nfps; i >= 0; i--) {
/*@-boundsread@*/
	FDSTACK_t * fps = &fd->fps[i];
/*@=boundsread@*/
	if (fps->io != gzdio)
	    continue;
	rc = fps->fp;
	break;
    }
    
    return rc;
}

static /*@null@*/
FD_t gzdOpen(const char * path, const char * fmode)
	/*@globals fileSystem, internalState @*/
	/*@modifies fileSystem, internalState @*/
{
    FD_t fd;
    gzFile gzfile;
    if ((gzfile = gzopen(path, fmode)) == NULL)
	return NULL;
    fd = fdNew("open (gzdOpen)");
    fdPop(fd); fdPush(fd, gzdio, gzfile, -1);
    
DBGIO(fd, (stderr, "==>\tgzdOpen(\"%s\", \"%s\") fd %p %s\n", path, fmode, (fd ? fd : NULL), fdbg(fd)));
    return fdLink(fd, "gzdOpen");
}

static /*@null@*/ FD_t gzdFdopen(void * cookie, const char *fmode)
	/*@globals fileSystem, internalState @*/
	/*@modifies fileSystem, internalState @*/
{
    FD_t fd = c2f(cookie);
    int fdno;
    gzFile gzfile;

    if (fmode == NULL) return NULL;
    fdno = fdFileno(fd);
    fdSetFdno(fd, -1);		/* XXX skip the fdio close */
    if (fdno < 0) return NULL;
    gzfile = gzdopen(fdno, fmode);
    if (gzfile == NULL) return NULL;

    fdPush(fd, gzdio, gzfile, fdno);		/* Push gzdio onto stack */

    return fdLink(fd, "gzdFdopen");
}

static int gzdFlush(FD_t fd)
	/*@globals fileSystem @*/
	/*@modifies fileSystem @*/
{
    gzFile gzfile;
    gzfile = gzdFileno(fd);
    if (gzfile == NULL) return -2;
    return gzflush(gzfile, Z_SYNC_FLUSH);	/* XXX W2DO? */
}

/* =============================================================== */
static ssize_t gzdRead(void * cookie, /*@out@*/ char * buf, size_t count)
	/*@globals fileSystem, internalState @*/
	/*@modifies buf, fileSystem, internalState @*/
{
    FD_t fd = c2f(cookie);
    gzFile gzfile;
    ssize_t rc;

    if (fd == NULL || fd->bytesRemain == 0) return 0;	/* XXX simulate EOF */

    gzfile = gzdFileno(fd);
    if (gzfile == NULL) return -2;	/* XXX can't happen */

    fdstat_enter(fd, FDSTAT_READ);
    rc = gzread(gzfile, buf, count);
DBGIO(fd, (stderr, "==>\tgzdRead(%p,%p,%u) rc %lx %s\n", cookie, buf, (unsigned)count, (unsigned long)rc, fdbg(fd)));
    if (rc < 0) {
	int zerror = 0;
	fd->errcookie = gzerror(gzfile, &zerror);
	if (zerror == Z_ERRNO) {
	    fd->syserrno = errno;
	    fd->errcookie = strerror(fd->syserrno);
	}
    } else if (rc >= 0) {
	fdstat_exit(fd, FDSTAT_READ, rc);
	if (fd->ndigests && rc > 0) fdUpdateDigests(fd, buf, rc);
    }
    return rc;
}

static ssize_t gzdWrite(void * cookie, const char * buf, size_t count)
	/*@globals fileSystem, internalState @*/
	/*@modifies fileSystem, internalState @*/
{
    FD_t fd = c2f(cookie);
    gzFile gzfile;
    ssize_t rc;

    if (fd == NULL || fd->bytesRemain == 0) return 0;	/* XXX simulate EOF */

    if (fd->ndigests && count > 0) fdUpdateDigests(fd, buf, count);

    gzfile = gzdFileno(fd);
    if (gzfile == NULL) return -2;	/* XXX can't happen */

    fdstat_enter(fd, FDSTAT_WRITE);
    rc = gzwrite(gzfile, (void *)buf, count);
DBGIO(fd, (stderr, "==>\tgzdWrite(%p,%p,%u) rc %lx %s\n", cookie, buf, (unsigned)count, (unsigned long)rc, fdbg(fd)));
    if (rc < 0) {
	int zerror = 0;
	fd->errcookie = gzerror(gzfile, &zerror);
	if (zerror == Z_ERRNO) {
	    fd->syserrno = errno;
	    fd->errcookie = strerror(fd->syserrno);
	}
    } else if (rc > 0) {
	fdstat_exit(fd, FDSTAT_WRITE, rc);
    }
    return rc;
}

/* XXX zlib-1.0.4 has not */
static inline int gzdSeek(void * cookie, _libio_pos_t pos, int whence)
	/*@globals fileSystem, internalState @*/
	/*@modifies fileSystem, internalState @*/
{
#ifdef USE_COOKIE_SEEK_POINTER
    _IO_off64_t p = *pos;
#else
    off_t p = pos;
#endif
    int rc;
#if HAVE_GZSEEK
    FD_t fd = c2f(cookie);
    gzFile gzfile;

    if (fd == NULL) return -2;
    assert(fd->bytesRemain == -1);	/* XXX FIXME */

    gzfile = gzdFileno(fd);
    if (gzfile == NULL) return -2;	/* XXX can't happen */

    fdstat_enter(fd, FDSTAT_SEEK);
    rc = gzseek(gzfile, p, whence);
DBGIO(fd, (stderr, "==>\tgzdSeek(%p,%ld,%d) rc %lx %s\n", cookie, (long)p, whence, (unsigned long)rc, fdbg(fd)));
    if (rc < 0) {
	int zerror = 0;
	fd->errcookie = gzerror(gzfile, &zerror);
	if (zerror == Z_ERRNO) {
	    fd->syserrno = errno;
	    fd->errcookie = strerror(fd->syserrno);
	}
    } else if (rc >= 0) {
	fdstat_exit(fd, FDSTAT_SEEK, rc);
    }
#else
    rc = -2;
#endif
    return rc;
}

static int gzdClose( /*@only@*/ void * cookie)
	/*@globals fileSystem, internalState @*/
	/*@modifies fileSystem, internalState @*/
{
    FD_t fd = c2f(cookie);
    gzFile gzfile;
    int rc;

    gzfile = gzdFileno(fd);
    if (gzfile == NULL) return -2;	/* XXX can't happen */

    fdstat_enter(fd, FDSTAT_CLOSE);
    /*@-dependenttrans@*/
    rc = gzclose(gzfile);
    /*@=dependenttrans@*/

    /* XXX TODO: preserve fd if errors */

    if (fd) {
DBGIO(fd, (stderr, "==>\tgzdClose(%p) zerror %d %s\n", cookie, rc, fdbg(fd)));
	if (rc < 0) {
	    fd->errcookie = "gzclose error";
	    if (rc == Z_ERRNO) {
		fd->syserrno = errno;
		fd->errcookie = strerror(fd->syserrno);
	    }
	} else if (rc >= 0) {
	    fdstat_exit(fd, FDSTAT_CLOSE, rc);
	}
    }

DBGIO(fd, (stderr, "==>\tgzdClose(%p) rc %lx %s\n", cookie, (unsigned long)rc, fdbg(fd)));

    if (_rpmio_debug || rpmIsDebug()) fdstat_print(fd, "GZDIO", stderr);
    /*@-branchstate@*/
    if (rc == 0)
	fd = fdFree(fd, "open (gzdClose)");
    /*@=branchstate@*/
    return rc;
}

/*@-type@*/ /* LCL: function typedefs */
static struct FDIO_s gzdio_s = {
  gzdRead, gzdWrite, gzdSeek, gzdClose, XfdLink, XfdFree, XfdNew, fdFileno,
  NULL, gzdOpen, gzdFileno, gzdFlush,	NULL, NULL, NULL, NULL, NULL
};
/*@=type@*/
FDIO_t gzdio = /*@-compmempass@*/ &gzdio_s /*@=compmempass@*/ ;

/*@=moduncon@*/
#endif	/* HAVE_ZLIB_H */

/* =============================================================== */
/* Support for BZIP2 library.
 */
#if HAVE_BZLIB_H
/*@-moduncon@*/

#include <bzlib.h>

#ifdef HAVE_BZ2_1_0
# define bzopen  BZ2_bzopen
# define bzclose BZ2_bzclose
# define bzdopen BZ2_bzdopen
# define bzerror BZ2_bzerror
# define bzflush BZ2_bzflush
# define bzread  BZ2_bzread
# define bzwrite BZ2_bzwrite
#endif /* HAVE_BZ2_1_0 */

static inline /*@dependent@*/ void * bzdFileno(FD_t fd)
	/*@*/
{
    void * rc = NULL;
    int i;

    FDSANE(fd);
    for (i = fd->nfps; i >= 0; i--) {
/*@-boundsread@*/
	FDSTACK_t * fps = &fd->fps[i];
/*@=boundsread@*/
	if (fps->io != bzdio)
	    continue;
	rc = fps->fp;
	break;
    }
    
    return rc;
}

/*@-globuse@*/
static /*@null@*/ FD_t bzdOpen(const char * path, const char * mode)
	/*@globals fileSystem @*/
	/*@modifies fileSystem @*/
{
    FD_t fd;
    BZFILE *bzfile;;
    if ((bzfile = bzopen(path, mode)) == NULL)
	return NULL;
    fd = fdNew("open (bzdOpen)");
    fdPop(fd); fdPush(fd, bzdio, bzfile, -1);
    return fdLink(fd, "bzdOpen");
}
/*@=globuse@*/

/*@-globuse@*/
static /*@null@*/ FD_t bzdFdopen(void * cookie, const char * fmode)
	/*@globals fileSystem, internalState @*/
	/*@modifies fileSystem, internalState @*/
{
    FD_t fd = c2f(cookie);
    int fdno;
    BZFILE *bzfile;

    if (fmode == NULL) return NULL;
    fdno = fdFileno(fd);
    fdSetFdno(fd, -1);		/* XXX skip the fdio close */
    if (fdno < 0) return NULL;
    bzfile = bzdopen(fdno, fmode);
    if (bzfile == NULL) return NULL;

    fdPush(fd, bzdio, bzfile, fdno);		/* Push bzdio onto stack */

    return fdLink(fd, "bzdFdopen");
}
/*@=globuse@*/

/*@-globuse@*/
static int bzdFlush(FD_t fd)
	/*@globals fileSystem @*/
	/*@modifies fileSystem @*/
{
    return bzflush(bzdFileno(fd));
}
/*@=globuse@*/

/* =============================================================== */
/*@-globuse@*/
/*@-mustmod@*/		/* LCL: *buf is modified */
static ssize_t bzdRead(void * cookie, /*@out@*/ char * buf, size_t count)
	/*@globals fileSystem, internalState @*/
	/*@modifies *buf, fileSystem, internalState @*/
{
    FD_t fd = c2f(cookie);
    BZFILE *bzfile;
    ssize_t rc = 0;

    if (fd->bytesRemain == 0) return 0;	/* XXX simulate EOF */
    bzfile = bzdFileno(fd);
    fdstat_enter(fd, FDSTAT_READ);
    if (bzfile)
	/*@-compdef@*/
	rc = bzread(bzfile, buf, count);
	/*@=compdef@*/
    if (rc == -1) {
	int zerror = 0;
	if (bzfile)
	    fd->errcookie = bzerror(bzfile, &zerror);
    } else if (rc >= 0) {
	fdstat_exit(fd, FDSTAT_READ, rc);
	/*@-compdef@*/
	if (fd->ndigests && rc > 0) fdUpdateDigests(fd, buf, rc);
	/*@=compdef@*/
    }
    return rc;
}
/*@=mustmod@*/
/*@=globuse@*/

/*@-globuse@*/
static ssize_t bzdWrite(void * cookie, const char * buf, size_t count)
	/*@globals fileSystem, internalState @*/
	/*@modifies fileSystem, internalState @*/
{
    FD_t fd = c2f(cookie);
    BZFILE *bzfile;
    ssize_t rc;

    if (fd->bytesRemain == 0) return 0;	/* XXX simulate EOF */

    if (fd->ndigests && count > 0) fdUpdateDigests(fd, buf, count);

    bzfile = bzdFileno(fd);
    fdstat_enter(fd, FDSTAT_WRITE);
    rc = bzwrite(bzfile, (void *)buf, count);
    if (rc == -1) {
	int zerror = 0;
	fd->errcookie = bzerror(bzfile, &zerror);
    } else if (rc > 0) {
	fdstat_exit(fd, FDSTAT_WRITE, rc);
    }
    return rc;
}
/*@=globuse@*/

static inline int bzdSeek(void * cookie, /*@unused@*/ _libio_pos_t pos,
			/*@unused@*/ int whence)
	/*@*/
{
    FD_t fd = c2f(cookie);

    BZDONLY(fd);
    return -2;
}

static int bzdClose( /*@only@*/ void * cookie)
	/*@globals fileSystem, internalState @*/
	/*@modifies fileSystem, internalState @*/
{
    FD_t fd = c2f(cookie);
    BZFILE *bzfile;
    int rc;

    bzfile = bzdFileno(fd);

    if (bzfile == NULL) return -2;
    fdstat_enter(fd, FDSTAT_CLOSE);
    /*@-noeffectuncon@*/ /* FIX: check rc */
    bzclose(bzfile);
    /*@=noeffectuncon@*/
    rc = 0;	/* XXX FIXME */

    /* XXX TODO: preserve fd if errors */

    if (fd) {
	if (rc == -1) {
	    int zerror = 0;
	    fd->errcookie = bzerror(bzfile, &zerror);
	} else if (rc >= 0) {
	    fdstat_exit(fd, FDSTAT_CLOSE, rc);
	}
    }

DBGIO(fd, (stderr, "==>\tbzdClose(%p) rc %lx %s\n", cookie, (unsigned long)rc, fdbg(fd)));

    if (_rpmio_debug || rpmIsDebug()) fdstat_print(fd, "BZDIO", stderr);
    /*@-branchstate@*/
    if (rc == 0)
	fd = fdFree(fd, "open (bzdClose)");
    /*@=branchstate@*/
    return rc;
}

/*@-type@*/ /* LCL: function typedefs */
static struct FDIO_s bzdio_s = {
  bzdRead, bzdWrite, bzdSeek, bzdClose, XfdLink, XfdFree, XfdNew, fdFileno,
  NULL, bzdOpen, bzdFileno, bzdFlush,	NULL, NULL, NULL, NULL, NULL
};
/*@=type@*/
FDIO_t bzdio = /*@-compmempass@*/ &bzdio_s /*@=compmempass@*/ ;

/*@=moduncon@*/
#endif	/* HAVE_BZLIB_H */

/* =============================================================== */
/*@observer@*/
static const char * getFdErrstr (FD_t fd)
	/*@*/
{
    const char *errstr = NULL;

#ifdef	HAVE_ZLIB_H
    if (fdGetIo(fd) == gzdio) {
	errstr = fd->errcookie;
    } else
#endif	/* HAVE_ZLIB_H */

#ifdef	HAVE_BZLIB_H
    if (fdGetIo(fd) == bzdio) {
	errstr = fd->errcookie;
    } else
#endif	/* HAVE_BZLIB_H */

    {
	errstr = (fd->syserrno ? strerror(fd->syserrno) : "");
    }

    return errstr;
}

/* =============================================================== */

const char *Fstrerror(FD_t fd)
{
    if (fd == NULL)
	return (errno ? strerror(errno) : "");
    FDSANE(fd);
    return getFdErrstr(fd);
}

#define	FDIOVEC(_fd, _vec)	\
  ((fdGetIo(_fd) && fdGetIo(_fd)->_vec) ? fdGetIo(_fd)->_vec : NULL)

size_t Fread(void *buf, size_t size, size_t nmemb, FD_t fd) {
    fdio_read_function_t _read;
    int rc;

    FDSANE(fd);
DBGIO(fd, (stderr, "==> Fread(%p,%u,%u,%p) %s\n", buf, (unsigned)size, (unsigned)nmemb, (fd ? fd : NULL), fdbg(fd)));

    if (fdGetIo(fd) == fpio) {
	/*@+voidabstract -nullpass@*/
	rc = fread(buf, size, nmemb, fdGetFILE(fd));
	/*@=voidabstract =nullpass@*/
	return rc;
    }

    /*@-nullderef@*/
    _read = FDIOVEC(fd, read);
    /*@=nullderef@*/

    rc = (_read ? (*_read) (fd, buf, size * nmemb) : -2);
    return rc;
}

size_t Fwrite(const void *buf, size_t size, size_t nmemb, FD_t fd)
{
    fdio_write_function_t _write;
    int rc;

    FDSANE(fd);
DBGIO(fd, (stderr, "==> Fwrite(%p,%u,%u,%p) %s\n", buf, (unsigned)size, (unsigned)nmemb, (fd ? fd : NULL), fdbg(fd)));

    if (fdGetIo(fd) == fpio) {
/*@-boundsread@*/
	/*@+voidabstract -nullpass@*/
	rc = fwrite(buf, size, nmemb, fdGetFILE(fd));
	/*@=voidabstract =nullpass@*/
/*@=boundsread@*/
	return rc;
    }

    /*@-nullderef@*/
    _write = FDIOVEC(fd, write);
    /*@=nullderef@*/

    rc = (_write ? _write(fd, buf, size * nmemb) : -2);
    return rc;
}

int Fseek(FD_t fd, _libio_off_t offset, int whence) {
    fdio_seek_function_t _seek;
#ifdef USE_COOKIE_SEEK_POINTER
    _IO_off64_t o64 = offset;
    _libio_pos_t pos = &o64;
#else
    _libio_pos_t pos = offset;
#endif

    long int rc;

    FDSANE(fd);
DBGIO(fd, (stderr, "==> Fseek(%p,%ld,%d) %s\n", fd, (long)offset, whence, fdbg(fd)));

    if (fdGetIo(fd) == fpio) {
	FILE *fp;

	/*@+voidabstract -nullpass@*/
	fp = fdGetFILE(fd);
	rc = fseek(fp, offset, whence);
	/*@=voidabstract =nullpass@*/
	return rc;
    }

    /*@-nullderef@*/
    _seek = FDIOVEC(fd, seek);
    /*@=nullderef@*/

    rc = (_seek ? _seek(fd, pos, whence) : -2);
    return rc;
}

int Fclose(FD_t fd)
{
    int rc = 0, ec = 0;

    FDSANE(fd);
DBGIO(fd, (stderr, "==> Fclose(%p) %s\n", (fd ? fd : NULL), fdbg(fd)));

    fd = fdLink(fd, "Fclose");
    /*@-branchstate@*/
    while (fd->nfps >= 0) {
/*@-boundsread@*/
	FDSTACK_t * fps = &fd->fps[fd->nfps];
/*@=boundsread@*/
	
	if (fps->io == fpio) {
	    FILE *fp;
	    int fpno;

	    /*@+voidabstract -nullpass@*/
	    fp = fdGetFILE(fd);
	    fpno = fileno(fp);
	    /*@=voidabstract =nullpass@*/
	/* XXX persistent HTTP/1.1 returns the previously opened fp */
	    if (fd->nfps > 0 && fpno == -1 &&
		fd->fps[fd->nfps-1].io == ufdio &&
		fd->fps[fd->nfps-1].fp == fp &&
		(fd->fps[fd->nfps-1].fdno >= 0 || fd->req != NULL))
	    {
		int hadreqpersist = (fd->req != NULL);

		if (fp)
		    rc = fflush(fp);
		fd->nfps--;
		/*@-refcounttrans@*/
		rc = ufdClose(fd);
		/*@=refcounttrans@*/
/*@-usereleased@*/
		if (fdGetFdno(fd) >= 0)
		    break;
		if (!fd->persist)
		    hadreqpersist = 0;
		fdSetFp(fd, NULL);
		fd->nfps++;
		if (fp) {
		    /* HACK: flimsy Keepalive wiring. */
		    if (hadreqpersist) {
			fd->nfps--;
/*@-exposetrans@*/
			fdSetFp(fd, fp);
/*@=exposetrans@*/
/*@-refcounttrans@*/
			(void) fdClose(fd);
/*@=refcounttrans@*/
			fdSetFp(fd, NULL);
			fd->nfps++;
/*@-refcounttrans@*/
			(void) fdClose(fd);
/*@=refcounttrans@*/
		    } else
			rc = fclose(fp);
		}
		fdPop(fd);
		if (noLibio)
		    fdSetFp(fd, NULL);
	    } else {
		if (fp)
		    rc = fclose(fp);
		if (fpno == -1) {
		    fd = fdFree(fd, "fopencookie (Fclose)");
		    fdPop(fd);
		}
	    }
	} else {
	    /*@-nullderef@*/
	    fdio_close_function_t _close = FDIOVEC(fd, close);
	    /*@=nullderef@*/
	    rc = _close(fd);
	}
	if (fd->nfps == 0)
	    break;
	if (ec == 0 && rc)
	    ec = rc;
	fdPop(fd);
    }
    /*@=branchstate@*/
    fd = fdFree(fd, "Fclose");
    return ec;
/*@=usereleased@*/
}

/**
 * Convert stdio fmode to open(2) mode, filtering out zlib/bzlib flags.
 *	returns stdio[0] = NUL on error.
 *
 * - gzopen:	[0-9] is compession level
 * - gzopen:	'f' is filtered (Z_FILTERED)
 * - gzopen:	'h' is Huffman encoding (Z_HUFFMAN_ONLY)
 * - bzopen:	[1-9] is block size (modulo 100K)
 * - bzopen:	's' is smallmode
 * - HACK:	'.' terminates, rest is type of I/O
 */
/*@-boundswrite@*/
static inline void cvtfmode (const char *m,
				/*@out@*/ char *stdio, size_t nstdio,
				/*@out@*/ char *other, size_t nother,
				/*@out@*/ const char **end, /*@out@*/ int * f)
	/*@modifies *stdio, *other, *end, *f @*/
{
    int flags = 0;
    char c;

    switch (*m) {
    case 'a':
	flags |= O_WRONLY | O_CREAT | O_APPEND;
	if (--nstdio > 0) *stdio++ = *m;
	break;
    case 'w':
	flags |= O_WRONLY | O_CREAT | O_TRUNC;
	if (--nstdio > 0) *stdio++ = *m;
	break;
    case 'r':
	flags |= O_RDONLY;
	if (--nstdio > 0) *stdio++ = *m;
	break;
    default:
	*stdio = '\0';
	return;
	/*@notreached@*/ break;
    }
    m++;

    while ((c = *m++) != '\0') {
	switch (c) {
	case '.':
	    /*@switchbreak@*/ break;
	case '+':
	    flags &= ~(O_RDONLY|O_WRONLY);
	    flags |= O_RDWR;
	    if (--nstdio > 0) *stdio++ = c;
	    continue;
	    /*@notreached@*/ /*@switchbreak@*/ break;
	case 'b':
	    if (--nstdio > 0) *stdio++ = c;
	    continue;
	    /*@notreached@*/ /*@switchbreak@*/ break;
	case 'x':
	    flags |= O_EXCL;
	    if (--nstdio > 0) *stdio++ = c;
	    continue;
	    /*@notreached@*/ /*@switchbreak@*/ break;
	default:
	    if (--nother > 0) *other++ = c;
	    continue;
	    /*@notreached@*/ /*@switchbreak@*/ break;
	}
	break;
    }

    *stdio = *other = '\0';
    if (end != NULL)
	*end = (*m != '\0' ? m : NULL);
    if (f != NULL)
	*f = flags;
}
/*@=boundswrite@*/

#if _USE_LIBIO
#if defined(__GLIBC__) && __GLIBC__ == 2 && __GLIBC_MINOR__ == 0
/* XXX retrofit glibc-2.1.x typedef on glibc-2.0.x systems */
typedef _IO_cookie_io_functions_t cookie_io_functions_t;
#endif
#endif

/*@-boundswrite@*/
FD_t Fdopen(FD_t ofd, const char *fmode)
{
    char stdio[20], other[20], zstdio[20];
    const char *end = NULL;
    FDIO_t iof = NULL;
    FD_t fd = ofd;

if (_rpmio_debug)
fprintf(stderr, "*** Fdopen(%p,%s) %s\n", fd, fmode, fdbg(fd));
    FDSANE(fd);

    if (fmode == NULL)
	return NULL;

    cvtfmode(fmode, stdio, sizeof(stdio), other, sizeof(other), &end, NULL);
    if (stdio[0] == '\0')
	return NULL;
    zstdio[0] = '\0';
    strncat(zstdio, stdio, sizeof(zstdio) - strlen(zstdio));
    strncat(zstdio, other, sizeof(zstdio) - strlen(zstdio));

    if (end == NULL && other[0] == '\0')
	/*@-refcounttrans -retalias@*/ return fd; /*@=refcounttrans =retalias@*/

    /*@-branchstate@*/
    if (end && *end) {
	if (!strcmp(end, "fdio")) {
	    iof = fdio;
	} else if (!strcmp(end, "gzdio")) {
	    iof = gzdio;
	    /*@-internalglobs@*/
	    fd = gzdFdopen(fd, zstdio);
	    /*@=internalglobs@*/
#if HAVE_BZLIB_H
	} else if (!strcmp(end, "bzdio")) {
	    iof = bzdio;
	    /*@-internalglobs@*/
	    fd = bzdFdopen(fd, zstdio);
	    /*@=internalglobs@*/
#endif
	} else if (!strcmp(end, "ufdio")) {
	    iof = ufdio;
	} else if (!strcmp(end, "fpio")) {
	    iof = fpio;
	    if (noLibio) {
		int fdno = Fileno(fd);
		FILE * fp = fdopen(fdno, stdio);
/*@+voidabstract -nullpass@*/
if (_rpmio_debug)
fprintf(stderr, "*** Fdopen fpio fp %p\n", (void *)fp);
/*@=voidabstract =nullpass@*/
		if (fp == NULL)
		    return NULL;
		/* XXX gzdio/bzdio use fp for private data */
		/*@+voidabstract@*/
		if (fdGetFp(fd) == NULL)
		    fdSetFp(fd, fp);
		fdPush(fd, fpio, fp, fdno);	/* Push fpio onto stack */
		/*@=voidabstract@*/
	    }
	}
    } else if (other[0] != '\0') {
	for (end = other; *end && strchr("0123456789fh", *end); end++)
	    {};
	if (*end == '\0') {
	    iof = gzdio;
	    /*@-internalglobs@*/
	    fd = gzdFdopen(fd, zstdio);
	    /*@=internalglobs@*/
	}
    }
    /*@=branchstate@*/
    if (iof == NULL)
	/*@-refcounttrans -retalias@*/ return fd; /*@=refcounttrans =retalias@*/

    if (!noLibio) {
	FILE * fp = NULL;

#if _USE_LIBIO
	{   cookie_io_functions_t ciof;
	    ciof.read = iof->read;
	    ciof.write = iof->write;
	    ciof.seek = iof->seek;
	    ciof.close = iof->close;
	    fp = fopencookie(fd, stdio, ciof);
DBGIO(fd, (stderr, "==> fopencookie(%p,\"%s\",*%p) returns fp %p\n", fd, stdio, iof, fp));
	}
#endif

	/*@-branchstate@*/
	if (fp) {
	    /* XXX gzdio/bzdio use fp for private data */
	    /*@+voidabstract -nullpass@*/
	    if (fdGetFp(fd) == NULL)
		fdSetFp(fd, fp);
	    fdPush(fd, fpio, fp, fileno(fp));	/* Push fpio onto stack */
	    /*@=voidabstract =nullpass@*/
	    fd = fdLink(fd, "fopencookie");
	}
	/*@=branchstate@*/
    }

DBGIO(fd, (stderr, "==> Fdopen(%p,\"%s\") returns fd %p %s\n", ofd, fmode, (fd ? fd : NULL), fdbg(fd)));
    /*@-refcounttrans -retalias@*/ return fd; /*@=refcounttrans =retalias@*/
}
/*@=boundswrite@*/

FD_t Fopen(const char *path, const char *fmode)
{
    char stdio[20], other[20];
    const char *end = NULL;
    mode_t perms = 0666;
    int flags;
    FD_t fd;

    if (path == NULL || fmode == NULL)
	return NULL;

    stdio[0] = '\0';
    cvtfmode(fmode, stdio, sizeof(stdio), other, sizeof(other), &end, &flags);
    if (stdio[0] == '\0')
	return NULL;

    /*@-branchstate@*/
    if (end == NULL || !strcmp(end, "fdio")) {
if (_rpmio_debug)
fprintf(stderr, "*** Fopen fdio path %s fmode %s\n", path, fmode);
	fd = fdOpen(path, flags, perms);
	if (fdFileno(fd) < 0) {
	    if (fd) (void) fdClose(fd);
	    return NULL;
	}
    } else {
	FILE *fp;
	int fdno;
	int isHTTP = 0;

	/* XXX gzdio and bzdio here too */

	switch (urlIsURL(path)) {
	case URL_IS_HTTPS:
	case URL_IS_HTTP:
	case URL_IS_HKP:
	    isHTTP = 1;
	    /*@fallthrough@*/
	case URL_IS_PATH:
	case URL_IS_DASH:
	case URL_IS_FTP:
	case URL_IS_UNKNOWN:
if (_rpmio_debug)
fprintf(stderr, "*** Fopen ufdio path %s fmode %s\n", path, fmode);
	    fd = ufdOpen(path, flags, perms);
	    if (fd == NULL || !(fdFileno(fd) >= 0 || fd->req != NULL))
		return fd;
	    break;
	default:
if (_rpmio_debug)
fprintf(stderr, "*** Fopen WTFO path %s fmode %s\n", path, fmode);
	    return NULL;
	    /*@notreached@*/ break;
	}

	/* XXX persistent HTTP/1.1 returns the previously opened fp */
	if (isHTTP && ((fp = fdGetFp(fd)) != NULL) && ((fdno = fdGetFdno(fd)) >= 0 || fd->req != NULL))
	{
	    /*@+voidabstract@*/
	    fdPush(fd, fpio, fp, fileno(fp));	/* Push fpio onto stack */
	    /*@=voidabstract@*/
	    return fd;
	}
    }
    /*@=branchstate@*/

    /*@-branchstate@*/
    if (fd)
	fd = Fdopen(fd, fmode);
    /*@=branchstate@*/
    return fd;
}

int Fflush(FD_t fd)
{
    void * vh;
    if (fd == NULL) return -1;
    if (fdGetIo(fd) == fpio)
	/*@+voidabstract -nullpass@*/
	return fflush(fdGetFILE(fd));
	/*@=voidabstract =nullpass@*/

    vh = fdGetFp(fd);
    if (vh && fdGetIo(fd) == gzdio)
	return gzdFlush(vh);
#if HAVE_BZLIB_H
    if (vh && fdGetIo(fd) == bzdio)
	return bzdFlush(vh);
#endif

    return 0;
}

int Ferror(FD_t fd)
{
    int i, rc = 0;

    if (fd == NULL) return -1;
    if (fd->req != NULL) {
	/* HACK: flimsy wiring for neon errors. */
	rc = (fd->syserrno  || fd->errcookie != NULL) ? -1 : 0;
    } else
    for (i = fd->nfps; rc == 0 && i >= 0; i--) {
/*@-boundsread@*/
	FDSTACK_t * fps = &fd->fps[i];
/*@=boundsread@*/
	int ec;
	
	if (fps->io == fpio) {
	    /*@+voidabstract -nullpass@*/
	    ec = ferror(fdGetFILE(fd));
	    /*@=voidabstract =nullpass@*/
	} else if (fps->io == gzdio) {
	    ec = (fd->syserrno || fd->errcookie != NULL) ? -1 : 0;
	    i--;	/* XXX fdio under gzdio always has fdno == -1 */
#if HAVE_BZLIB_H
	} else if (fps->io == bzdio) {
	    ec = (fd->syserrno  || fd->errcookie != NULL) ? -1 : 0;
	    i--;	/* XXX fdio under bzdio always has fdno == -1 */
#endif
	} else {
	/* XXX need to check ufdio/gzdio/bzdio/fdio errors correctly. */
	    ec = (fdFileno(fd) < 0 ? -1 : 0);
	}

	if (rc == 0 && ec)
	    rc = ec;
    }
DBGIO(fd, (stderr, "==> Ferror(%p) rc %d %s\n", fd, rc, fdbg(fd)));
    return rc;
}

int Fileno(FD_t fd)
{
    int i, rc = -1;

    if (fd == NULL) return -1;
    if (fd->req != NULL)
	rc = 123456789;	/* HACK: https has no steenkin fileno. */
    else
    for (i = fd->nfps ; rc == -1 && i >= 0; i--) {
/*@-boundsread@*/
	rc = fd->fps[i].fdno;
/*@=boundsread@*/
    }
    
DBGIO(fd, (stderr, "==> Fileno(%p) rc %d %s\n", (fd ? fd : NULL), rc, fdbg(fd)));
    return rc;
}

/* XXX this is naive */
int Fcntl(FD_t fd, int op, void *lip)
{
    return fcntl(Fileno(fd), op, lip);
}

/* =============================================================== */
/* Helper routines that may be generally useful.
 */

char * rpmEscapeSpaces(const char * s)
{
    const char * se;
    char * t;
    char * te;
    size_t nb = 0;

    for (se = s; *se; se++) {
        if (isspace(*se))
            nb++;
        nb++;
    }
    nb++;

    t = te = xmalloc(nb);
    for (se = s; *se; se++) {
        if (isspace(*se))
            *te++ = '\\';
        *te++ = *se;
    }
    *te = '\0';
    return t;
}

/*@-bounds@*/
int rpmioMkpath(const char * path, mode_t mode, uid_t uid, gid_t gid)
{
    char * d, * de;
    int created = 0;
    int rc;

    if (path == NULL)
	return -1;
    d = alloca(strlen(path)+2);
    de = stpcpy(d, path);
    de[1] = '\0';
    for (de = d; *de != '\0'; de++) {
	struct stat st;
	char savec;

	while (*de && *de != '/') de++;
	savec = de[1];
	de[1] = '\0';

	rc = Stat(d, &st);
	if (rc) {
	    switch(errno) {
	    default:
		return errno;
		/*@notreached@*/ /*@switchbreak@*/ break;
	    case ENOENT:
		/*@switchbreak@*/ break;
	    }
	    rc = Mkdir(d, mode);
	    if (rc)
		return errno;
	    created = 1;
	    if (!(uid == (uid_t) -1 && gid == (gid_t) -1)) {
		rc = chown(d, uid, gid);
		if (rc)
		    return errno;
	    }
	} else if (!S_ISDIR(st.st_mode)) {
	    return ENOTDIR;
	}
	de[1] = savec;
    }
    rc = 0;
    if (created)
	rpmMessage(RPMMESS_DEBUG, "created directory(s) %s mode 0%o\n",
			path, mode);
    return rc;
}
/*@=bounds@*/

/*@-boundswrite@*/
int rpmioSlurp(const char * fn, const byte ** bp, ssize_t * blenp)
{
    static ssize_t blenmax = (32 * BUFSIZ);
    ssize_t blen = 0;
    byte * b = NULL;
    ssize_t size;
    FD_t fd;
    int rc = 0;

    fd = Fopen(fn, "r.ufdio");
    if (fd == NULL || Ferror(fd)) {
	rc = 2;
	goto exit;
    }

    size = fdSize(fd);
    blen = (size >= 0 ? size : blenmax);
    /*@-branchstate@*/
    if (blen) {
	int nb;
	b = xmalloc(blen+1);
	b[0] = '\0';
	nb = Fread(b, sizeof(*b), blen, fd);
	if (Ferror(fd) || (size > 0 && nb != blen)) {
	    rc = 1;
	    goto exit;
	}
	if (blen == blenmax && nb < blen) {
	    blen = nb;
	    b = xrealloc(b, blen+1);
	}
	b[blen] = '\0';
    }
    /*@=branchstate@*/

exit:
    if (fd) (void) Fclose(fd);
	
    if (rc) {
	if (b) free(b);
	b = NULL;
	blen = 0;
    }

    if (bp) *bp = b;
    else if (b) free(b);

    if (blenp) *blenp = blen;

    return rc;
}
/*@=boundswrite@*/

/*@-type@*/ /* LCL: function typedefs */
static struct FDIO_s fpio_s = {
  ufdRead, ufdWrite, fdSeek, ufdClose, XfdLink, XfdFree, XfdNew, fdFileno,
  ufdOpen, NULL, fdGetFp, NULL,	Mkdir, Chdir, Rmdir, Rename, Unlink
};
/*@=type@*/
FDIO_t fpio = /*@-compmempass@*/ &fpio_s /*@=compmempass@*/ ;
