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

#include    <mk.h>

#include    <sys/param.h>
#include    <sys/stat.h>
#include    <sys/time.h>
#include    <sys/wait.h>
#include    <sys/errno.h>

#include    <unistd.h>
#include    <stdlib.h>
#include    <signal.h>
#include    <stdio.h>
#include    <string.h>
#include    <assert.h>
#include    <sysexits.h>
#include    <err.h>

__RCSID("$Id: fetch-wrapper.c,v 1.1 2004/05/23 00:39:47 sethk Exp $");

#define FETCH_PROG  "fetch"
#define POLL_DELAY  250	    /* msec. */

static pid_t	_fetch_pid = -1;
char		_out_fname[PATH_MAX] = "";
struct timeval	_start_time;

static void
_trap_alarm(int sig)
{
#ifdef FETCH_BUG
    if (*_out_fname)
    {
	static u_long	dur = 0;
	static off_t	osize = 0;
	struct stat	st;

	if (stat(_out_fname, &st) == -1)
	{
#ifdef DEBUG
	    warn("stat %s", _out_fname);
#endif /* DEBUG */
	    return;
	}

	if (st.st_size > osize && dur)
	{
	    double  bps = (st.st_size - osize) / (POLL_DELAY / 1000.0);

	    fprintf(stderr, "%qu bytes transferred in %.1f seconds (",
		    st.st_size,
		    dur / 1000.0);
	    if (bps == 0.0)
		fprintf(stderr, "?? Bps");
	    else if (bps >= 1024 * 1024)
		fprintf(stderr, "%.2f MBps", bps / (1024 * 1024));
	    else if (bps >= 1024)
		fprintf(stderr, "%.2f kBps", bps / 1024);
	    else
		fprintf(stderr, "%.2f Bps", bps);
	    fputs(")\n", stderr);
	}

	osize = st.st_size;
	dur+= POLL_DELAY;
    }
#else /* !FETCH_BUG */
#ifdef DEBUG
    warnx("sending a SIGINFO to fetch");
#endif /* DEBUG */
    kill(_fetch_pid, SIGINFO);
#endif /* FETCH_BUG */
}

static void
_trap_fatal(int sig)
{
    if (_fetch_pid != -1)
    {
#ifdef DEBUG
	warnx("killing fetch with signal %d", sig); 
#endif /* DEBUG */
	kill(_fetch_pid, sig);
    }
    else
    {
#ifdef DEBUG
	warnx("trapped signal %d, but no fetch process", sig);
#endif /* DEBUG */
    }

    signal(sig, SIG_DFL);
    raise(sig);
}

int
main(int ac, char *av[])
{
    int			ch;
    extern int		opterr,
			optind;
    extern char		*optarg;
    char		*outdir = NULL;
    u_short		verbose = 0;
    char		**fetch_av;
    int			fsigs[] = {SIGHUP, SIGINT, SIGQUIT, SIGTERM};
    register u_int	i;
    struct itimerval	it;
    int			st;

    /* look for output filename and size in arguments. */
    opterr = 0;
    while ((ch = getopt(ac,
		    av,
		    "146AB:FMPN:RS:T:Uac:df:h:lmno:pqrsvw:")) != -1)
	switch (ch)
	{
	    struct stat	st;

	    case 'o':
		if (strcmp(optarg, "-"))
		{
		    if (stat(optarg, &st) == -1)
		    {
			if (errno == ENOENT)
			    strlcpy(_out_fname, optarg, sizeof(_out_fname));
			else
			{
#ifdef DEBUG
			    warn("stat %s", optarg);
#endif /* DEBUG */
			}
		    }
		    else
		    {
			if (S_ISDIR(st.st_mode))
			    outdir = optarg;
			else
			    strlcpy(_out_fname, optarg, sizeof(_out_fname));
		    }
		}
		break;

	    case 'v':
		++verbose;
		break;
	}
    if (!*_out_fname && optind < ac)
    {
	char	*cp;

	if ((cp = strrchr(av[optind], '/')))
	{
	    ++cp;

	    if (outdir)
		snprintf(_out_fname, sizeof(_out_fname), "%s/%s", outdir, cp);
	    else
		strlcpy(_out_fname, cp, sizeof(_out_fname));
	}
    }
    if (*_out_fname)
	warnx("output file is %s", _out_fname);

    fetch_av = mk_malloc(sizeof(*fetch_av) * ac + 2);
    fetch_av[0] = FETCH_PROG;
    if (!verbose)
    {
	fetch_av[1] = "-v";
	bcopy(av + 1, fetch_av + 2, sizeof(*av) * (ac + 1));
    }
    else
	bcopy(av + 1, fetch_av + 1, sizeof(*av) * (ac + 1));

    for (i = 0; i < sizeof(fsigs) / sizeof(fsigs[0]); ++i)
	signal(fsigs[i], _trap_fatal);

#ifdef DEBUG
    fprintf(stderr, "%s: spawning '%s'", getprogname(), fetch_av[0]);
    for (i = 1; fetch_av[i]; ++i)
	fprintf(stderr, " '%s'", fetch_av[i]);
    fputc('\n', stderr);
#endif /* DEBUG */

    switch ((_fetch_pid = vfork()))
    {
	case -1:
	    err(EX_OSERR, "fork");
	    /* NOTREACHED */

	case 0:
	    execvp(fetch_av[0], fetch_av);
    }

    mk_free(fetch_av);

    signal(SIGALRM, _trap_alarm);
    it.it_value.tv_sec = POLL_DELAY / 1000;
    it.it_value.tv_usec = (POLL_DELAY % 1000) * 1000;
    bcopy(&(it.it_value), &(it.it_interval), sizeof(it.it_value));
    setitimer(ITIMER_REAL, &it, NULL);

    if (waitpid(_fetch_pid, &st, 0) == -1)
	err(EX_OSERR, "reap child process");

    for (i = 0; i < sizeof(fsigs) / sizeof(fsigs[0]); ++i)
	signal(fsigs[i], SIG_DFL);

    timerclear(&(it.it_value));
    setitimer(ITIMER_REAL, &it, NULL);
    signal(SIGALRM, SIG_IGN);

    if (WIFEXITED(st))
	return WEXITSTATUS(st);
    else if (WIFSIGNALED(st))
    {
	raise(WTERMSIG(st));
	pause();
    }

    /* NOTREACHED */
    assert(0);
    return EX_SOFTWARE;
}

