1996-06-22 21:42:14 +00:00
|
|
|
|
/*-
|
|
|
|
|
* Copyright (c) 1996
|
1996-06-22 23:24:13 +00:00
|
|
|
|
* Jean-Marc Zucconi
|
1996-06-22 21:42:14 +00:00
|
|
|
|
*
|
|
|
|
|
* 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.
|
|
|
|
|
*
|
|
|
|
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``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 REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
|
|
|
|
|
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
|
|
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
|
|
|
|
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
|
|
|
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
|
|
|
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
|
|
|
|
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
|
|
|
|
* SUCH DAMAGE.
|
|
|
|
|
*/
|
|
|
|
|
|
1997-08-05 20:18:39 +00:00
|
|
|
|
/* $Id: main.c,v 1.41 1997/07/25 19:35:44 wollman Exp $ */
|
1996-06-19 09:32:11 +00:00
|
|
|
|
|
1996-09-19 17:31:34 +00:00
|
|
|
|
#include <sys/types.h>
|
|
|
|
|
|
|
|
|
|
#include <err.h>
|
|
|
|
|
#include <errno.h>
|
1997-01-30 21:43:44 +00:00
|
|
|
|
#include <limits.h> /* needed for INT_MAX */
|
|
|
|
|
#include <setjmp.h>
|
1996-06-19 09:32:11 +00:00
|
|
|
|
#include <signal.h>
|
1996-09-19 17:31:34 +00:00
|
|
|
|
#include <stdio.h>
|
|
|
|
|
#include <stdlib.h>
|
|
|
|
|
#include <string.h>
|
1997-01-30 21:43:44 +00:00
|
|
|
|
#include <sysexits.h>
|
1996-09-19 17:31:34 +00:00
|
|
|
|
#include <unistd.h>
|
1996-06-19 09:32:11 +00:00
|
|
|
|
|
1997-01-30 21:43:44 +00:00
|
|
|
|
#include <sys/param.h> /* for MAXHOSTNAMELEN */
|
|
|
|
|
#include <sys/time.h> /* for struct timeval, gettimeofday */
|
1996-06-19 09:32:11 +00:00
|
|
|
|
|
1997-01-30 21:43:44 +00:00
|
|
|
|
#include "fetch.h"
|
1996-06-19 09:32:11 +00:00
|
|
|
|
|
1997-01-30 21:43:44 +00:00
|
|
|
|
static struct fetch_state clean_fetch_state;
|
|
|
|
|
static sigjmp_buf sigbuf;
|
|
|
|
|
static int get(struct fetch_state *volatile fs);
|
1996-06-19 09:32:11 +00:00
|
|
|
|
|
1997-01-30 21:43:44 +00:00
|
|
|
|
static void
|
1997-07-01 06:37:34 +00:00
|
|
|
|
usage()
|
1996-06-19 09:32:11 +00:00
|
|
|
|
{
|
1997-07-01 06:37:34 +00:00
|
|
|
|
fprintf(stderr, "%s\n%s\n",
|
|
|
|
|
"usage: fetch [-DHILMNPRTValmnpqrv] [-o outputfile]",
|
1997-07-02 06:28:32 +00:00
|
|
|
|
" [-f file -h host [-c dir] | URL]");
|
1997-01-30 21:43:44 +00:00
|
|
|
|
exit(EX_USAGE);
|
1996-08-12 12:55:26 +00:00
|
|
|
|
}
|
|
|
|
|
|
1996-06-19 09:32:11 +00:00
|
|
|
|
|
|
|
|
|
int
|
1997-01-30 21:43:44 +00:00
|
|
|
|
main(int argc, char *const *argv)
|
1996-06-19 09:32:11 +00:00
|
|
|
|
{
|
1996-08-22 21:30:51 +00:00
|
|
|
|
int c;
|
1997-01-30 21:43:44 +00:00
|
|
|
|
char *ep;
|
|
|
|
|
struct fetch_state fs;
|
|
|
|
|
const char *change_to_dir, *file_to_get, *hostname;
|
|
|
|
|
int error, rv;
|
|
|
|
|
unsigned long l;
|
|
|
|
|
|
|
|
|
|
init_schemes();
|
|
|
|
|
fs = clean_fetch_state;
|
|
|
|
|
fs.fs_verbose = 1;
|
|
|
|
|
change_to_dir = file_to_get = hostname = 0;
|
|
|
|
|
|
1997-08-05 20:18:39 +00:00
|
|
|
|
while ((c = getopt(argc, argv, "abc:D:f:h:HilLmMnNo:pPqRrtT:vV:")) != -1) {
|
1997-01-30 21:43:44 +00:00
|
|
|
|
switch (c) {
|
|
|
|
|
case 'D': case 'H': case 'I': case 'N': case 'L': case 'V':
|
|
|
|
|
break; /* ncftp compatibility */
|
1996-08-22 21:30:51 +00:00
|
|
|
|
|
1997-01-31 19:55:51 +00:00
|
|
|
|
case 'a':
|
|
|
|
|
fs.fs_auto_retry = 1;
|
|
|
|
|
break;
|
1997-07-25 19:35:44 +00:00
|
|
|
|
|
|
|
|
|
case 'b':
|
|
|
|
|
fs.fs_linux_bug = 1;
|
|
|
|
|
break;
|
|
|
|
|
|
1997-01-30 21:43:44 +00:00
|
|
|
|
case 'c':
|
|
|
|
|
change_to_dir = optarg;
|
|
|
|
|
break;
|
1997-07-25 19:35:44 +00:00
|
|
|
|
|
1997-01-30 21:43:44 +00:00
|
|
|
|
case 'f':
|
|
|
|
|
file_to_get = optarg;
|
|
|
|
|
break;
|
1996-08-22 21:30:51 +00:00
|
|
|
|
|
1997-01-30 21:43:44 +00:00
|
|
|
|
case 'h':
|
|
|
|
|
hostname = optarg;
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case 'l':
|
|
|
|
|
fs.fs_linkfile = 1;
|
|
|
|
|
break;
|
|
|
|
|
|
1997-01-31 19:55:51 +00:00
|
|
|
|
case 'm': case 'M':
|
|
|
|
|
fs.fs_mirror = 1;
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case 'n':
|
|
|
|
|
fs.fs_newtime = 1;
|
|
|
|
|
break;
|
|
|
|
|
|
1997-01-30 21:43:44 +00:00
|
|
|
|
case 'o':
|
|
|
|
|
fs.fs_outputfile = optarg;
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case 'p': case 'P':
|
|
|
|
|
fs.fs_passive_mode = 1;
|
|
|
|
|
break;
|
1996-08-22 21:30:51 +00:00
|
|
|
|
|
1997-01-31 19:55:51 +00:00
|
|
|
|
case 'q':
|
|
|
|
|
fs.fs_verbose = 0;
|
1997-01-30 21:43:44 +00:00
|
|
|
|
break;
|
1997-01-31 19:55:51 +00:00
|
|
|
|
|
1997-01-30 21:43:44 +00:00
|
|
|
|
case 'r':
|
|
|
|
|
fs.fs_restart = 1;
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case 'R':
|
|
|
|
|
fs.fs_precious = 1;
|
|
|
|
|
break;
|
1996-08-22 21:30:51 +00:00
|
|
|
|
|
1997-08-05 20:18:39 +00:00
|
|
|
|
case 't':
|
|
|
|
|
fs.fs_use_connect = 1;
|
|
|
|
|
break;
|
|
|
|
|
|
1997-01-30 21:43:44 +00:00
|
|
|
|
case 'T':
|
|
|
|
|
/* strtol sets errno to ERANGE in the case of overflow */
|
|
|
|
|
errno = 0;
|
|
|
|
|
l = strtoul(optarg, &ep, 0);
|
|
|
|
|
if (!optarg[0] || *ep || errno != 0 || l > INT_MAX)
|
|
|
|
|
errx(EX_USAGE, "invalid timeout value: `%s'",
|
|
|
|
|
optarg);
|
|
|
|
|
fs.fs_timeout = l;
|
|
|
|
|
break;
|
|
|
|
|
|
1997-01-31 19:55:51 +00:00
|
|
|
|
case 'v':
|
|
|
|
|
if (fs.fs_verbose < 2)
|
|
|
|
|
fs.fs_verbose = 2;
|
|
|
|
|
else
|
|
|
|
|
fs.fs_verbose++;
|
|
|
|
|
break;
|
|
|
|
|
|
1997-01-30 21:43:44 +00:00
|
|
|
|
default:
|
|
|
|
|
case '?':
|
1997-07-01 06:37:34 +00:00
|
|
|
|
usage();
|
1997-01-30 21:43:44 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
1996-08-31 22:03:05 +00:00
|
|
|
|
|
1997-01-30 21:43:44 +00:00
|
|
|
|
clean_fetch_state = fs; /* preserve option settings */
|
1996-08-22 21:30:51 +00:00
|
|
|
|
|
1997-01-30 21:43:44 +00:00
|
|
|
|
if (argv[optind] && (hostname || change_to_dir || file_to_get)) {
|
|
|
|
|
warnx("cannot use -h, -c, or -f with a URI argument");
|
1997-07-01 06:37:34 +00:00
|
|
|
|
usage();
|
1996-08-22 21:30:51 +00:00
|
|
|
|
}
|
1997-01-30 21:43:44 +00:00
|
|
|
|
|
|
|
|
|
if (fs.fs_mirror && fs.fs_restart)
|
|
|
|
|
errx(EX_USAGE, "-m and -r are mutually exclusive.");
|
1996-08-22 21:30:51 +00:00
|
|
|
|
|
1997-01-30 21:43:44 +00:00
|
|
|
|
if (argv[optind] == 0) {
|
|
|
|
|
char *uri;
|
1996-08-31 22:03:05 +00:00
|
|
|
|
|
1997-01-30 21:43:44 +00:00
|
|
|
|
if (hostname == 0) hostname = "localhost";
|
|
|
|
|
if (change_to_dir == 0) change_to_dir = "";
|
|
|
|
|
if (file_to_get == 0) {
|
1997-07-01 06:37:34 +00:00
|
|
|
|
usage();
|
1997-01-30 21:43:44 +00:00
|
|
|
|
}
|
1996-06-19 09:32:11 +00:00
|
|
|
|
|
1997-01-30 21:43:44 +00:00
|
|
|
|
uri = alloca(sizeof("ftp://") + strlen(hostname) +
|
|
|
|
|
strlen(change_to_dir) + 2 + strlen(file_to_get));
|
|
|
|
|
strcpy(uri, "ftp://");
|
|
|
|
|
strcat(uri, hostname);
|
|
|
|
|
/*
|
|
|
|
|
* XXX - we should %-map a leading `/' into `%2f', but for
|
|
|
|
|
* anonymous FTP it is unlikely to matter. Still, it would
|
|
|
|
|
* be better to follow the spec.
|
|
|
|
|
*/
|
|
|
|
|
if (change_to_dir[0] != '/')
|
|
|
|
|
strcat(uri, "/");
|
|
|
|
|
strcat(uri, change_to_dir);
|
|
|
|
|
if (file_to_get[0] != '/' && uri[strlen(uri) - 1] != '/')
|
|
|
|
|
strcat(uri, "/");
|
|
|
|
|
strcat(uri, file_to_get);
|
|
|
|
|
|
|
|
|
|
error = parse_uri(&fs, uri);
|
|
|
|
|
if (error)
|
|
|
|
|
return error;
|
|
|
|
|
return get(&fs);
|
|
|
|
|
}
|
1996-06-19 09:32:11 +00:00
|
|
|
|
|
1997-01-30 21:43:44 +00:00
|
|
|
|
for (rv = 0; argv[optind] != 0; optind++) {
|
|
|
|
|
error = parse_uri(&fs, argv[optind]);
|
|
|
|
|
if (error) {
|
|
|
|
|
rv = error;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
1996-08-04 00:35:39 +00:00
|
|
|
|
|
1997-01-30 21:43:44 +00:00
|
|
|
|
error = get(&fs);
|
|
|
|
|
if (error) {
|
|
|
|
|
rv = error;
|
|
|
|
|
}
|
|
|
|
|
fs = clean_fetch_state;
|
1996-08-22 21:30:51 +00:00
|
|
|
|
}
|
1997-01-30 21:43:44 +00:00
|
|
|
|
return rv;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* The signal handling is probably more complex than it needs to be,
|
|
|
|
|
* but it doesn't cost a lot, so we'll be extra-careful. Using
|
|
|
|
|
* siglongjmp() to get out of the signal handler allows us to
|
|
|
|
|
* call rm() without having to store the state variable in some global
|
|
|
|
|
* spot where the signal handler can get at it. We also obviate the need
|
|
|
|
|
* for a separate timeout signal handler.
|
|
|
|
|
*/
|
|
|
|
|
static int
|
|
|
|
|
get(struct fetch_state *volatile fs)
|
|
|
|
|
{
|
|
|
|
|
volatile int error;
|
|
|
|
|
struct sigaction oldhup, oldint, oldquit, oldterm;
|
|
|
|
|
struct sigaction catch;
|
|
|
|
|
volatile sigset_t omask;
|
|
|
|
|
|
|
|
|
|
sigemptyset(&catch.sa_mask);
|
|
|
|
|
sigaddset(&catch.sa_mask, SIGHUP);
|
|
|
|
|
sigaddset(&catch.sa_mask, SIGINT);
|
|
|
|
|
sigaddset(&catch.sa_mask, SIGQUIT);
|
|
|
|
|
sigaddset(&catch.sa_mask, SIGTERM);
|
|
|
|
|
sigaddset(&catch.sa_mask, SIGALRM);
|
|
|
|
|
catch.sa_handler = catchsig;
|
|
|
|
|
catch.sa_flags = 0;
|
|
|
|
|
|
|
|
|
|
sigprocmask(SIG_BLOCK, &catch.sa_mask, (sigset_t *)&omask);
|
|
|
|
|
sigaction(SIGHUP, &catch, &oldhup);
|
|
|
|
|
sigaction(SIGINT, &catch, &oldint);
|
|
|
|
|
sigaction(SIGQUIT, &catch, &oldquit);
|
|
|
|
|
sigaction(SIGTERM, &catch, &oldterm);
|
|
|
|
|
|
|
|
|
|
error = sigsetjmp(sigbuf, 0);
|
|
|
|
|
if (error == SIGALRM) {
|
|
|
|
|
rm(fs);
|
|
|
|
|
unsetup_sigalrm();
|
|
|
|
|
fprintf(stderr, "\n"); /* just in case */
|
|
|
|
|
warnx("%s: %s: timed out", fs->fs_outputfile, fs->fs_status);
|
|
|
|
|
goto close;
|
|
|
|
|
} else if (error) {
|
|
|
|
|
rm(fs);
|
|
|
|
|
fprintf(stderr, "\n"); /* just in case */
|
|
|
|
|
warnx("%s: interrupted by signal: %s", fs->fs_status,
|
|
|
|
|
sys_signame[error]);
|
|
|
|
|
sigdelset(&omask, error);
|
|
|
|
|
signal(error, SIG_DFL);
|
|
|
|
|
sigprocmask(SIG_SETMASK, (sigset_t *)&omask, 0);
|
|
|
|
|
raise(error); /* so that it gets reported as such */
|
1996-06-19 09:32:11 +00:00
|
|
|
|
}
|
1996-08-22 21:30:51 +00:00
|
|
|
|
|
1997-01-30 21:43:44 +00:00
|
|
|
|
sigprocmask(SIG_SETMASK, (sigset_t *)&omask, 0);
|
|
|
|
|
error = fs->fs_retrieve(fs);
|
1996-06-19 09:32:11 +00:00
|
|
|
|
|
1997-01-30 21:43:44 +00:00
|
|
|
|
close:
|
|
|
|
|
sigaction(SIGHUP, &oldhup, 0);
|
|
|
|
|
sigaction(SIGINT, &oldint, 0);
|
|
|
|
|
sigaction(SIGQUIT, &oldquit, 0);
|
|
|
|
|
sigaction(SIGTERM, &oldterm, 0);
|
|
|
|
|
fs->fs_close(fs);
|
1997-01-17 12:52:12 +00:00
|
|
|
|
|
1997-01-30 21:43:44 +00:00
|
|
|
|
return error;
|
|
|
|
|
}
|
1996-09-19 18:07:24 +00:00
|
|
|
|
|
1997-01-30 21:43:44 +00:00
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Utility functions
|
|
|
|
|
*/
|
1996-09-19 18:07:24 +00:00
|
|
|
|
|
1997-01-30 21:43:44 +00:00
|
|
|
|
/*
|
|
|
|
|
* Handle all signals by jumping back into get().
|
|
|
|
|
*/
|
|
|
|
|
void
|
|
|
|
|
catchsig(int sig)
|
|
|
|
|
{
|
|
|
|
|
siglongjmp(sigbuf, sig);
|
1996-06-19 09:32:11 +00:00
|
|
|
|
}
|
|
|
|
|
|
1997-01-30 21:43:44 +00:00
|
|
|
|
/* Used to generate the progress display when not in quiet mode. */
|
1996-06-19 09:32:11 +00:00
|
|
|
|
void
|
1997-01-30 21:43:44 +00:00
|
|
|
|
display(struct fetch_state *fs, off_t size, ssize_t n)
|
1996-06-19 09:32:11 +00:00
|
|
|
|
{
|
1997-01-30 21:43:44 +00:00
|
|
|
|
static off_t bytes;
|
1997-02-10 18:49:42 +00:00
|
|
|
|
static off_t bytestart;
|
1997-02-14 19:08:18 +00:00
|
|
|
|
static int pr, stdoutatty, init = 0;
|
1996-08-22 21:30:51 +00:00
|
|
|
|
static struct timeval t0, t_start;
|
|
|
|
|
static char *s;
|
|
|
|
|
struct timezone tz;
|
|
|
|
|
struct timeval t;
|
|
|
|
|
float d;
|
|
|
|
|
|
1997-01-30 21:43:44 +00:00
|
|
|
|
if (!fs->fs_verbose)
|
1996-08-22 21:30:51 +00:00
|
|
|
|
return;
|
|
|
|
|
if (init == 0) {
|
|
|
|
|
init = 1;
|
|
|
|
|
gettimeofday(&t0, &tz);
|
|
|
|
|
t_start = t0;
|
|
|
|
|
bytes = pr = 0;
|
1997-02-14 19:08:18 +00:00
|
|
|
|
stdoutatty = isatty(STDOUT_FILENO);
|
1996-08-22 21:30:51 +00:00
|
|
|
|
if (size > 0)
|
1997-02-14 19:08:18 +00:00
|
|
|
|
asprintf (&s, "Receiving %s (%qd bytes)%s", fs->fs_outputfile,
|
1997-01-30 21:43:44 +00:00
|
|
|
|
(quad_t)size,
|
1996-08-22 21:30:51 +00:00
|
|
|
|
size ? "" : " [appending]");
|
|
|
|
|
else
|
1997-02-14 19:08:18 +00:00
|
|
|
|
asprintf (&s, "Receiving %s", fs->fs_outputfile);
|
1997-02-17 04:11:47 +00:00
|
|
|
|
fprintf (stderr, "%s", s);
|
1997-02-10 18:49:42 +00:00
|
|
|
|
bytestart = bytes = n;
|
1996-08-22 21:30:51 +00:00
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
gettimeofday(&t, &tz);
|
|
|
|
|
if (n == -1) {
|
1997-02-14 19:08:18 +00:00
|
|
|
|
if(stdoutatty) {
|
|
|
|
|
if (size > 0)
|
1997-02-17 04:11:47 +00:00
|
|
|
|
fprintf (stderr, "\r%s: 100%%", s);
|
1997-02-14 19:08:18 +00:00
|
|
|
|
else
|
1997-02-17 04:11:47 +00:00
|
|
|
|
fprintf (stderr, "\r%s: %qd Kbytes", s, (quad_t)bytes/1024);
|
1997-02-14 19:08:18 +00:00
|
|
|
|
}
|
1997-02-10 18:49:42 +00:00
|
|
|
|
bytes -= bytestart;
|
1996-08-22 21:30:51 +00:00
|
|
|
|
d = t.tv_sec + t.tv_usec/1.e6 - t_start.tv_sec - t_start.tv_usec/1.e6;
|
1997-02-17 04:11:47 +00:00
|
|
|
|
fprintf (stderr, "\n%qd bytes transfered in %.1f seconds",
|
|
|
|
|
(quad_t)bytes, d);
|
1996-08-22 21:30:51 +00:00
|
|
|
|
d = bytes/d;
|
|
|
|
|
if (d < 1000)
|
1997-02-17 04:11:47 +00:00
|
|
|
|
fprintf (stderr, " (%.0f bytes/s)\n", d);
|
1996-08-22 21:30:51 +00:00
|
|
|
|
else {
|
|
|
|
|
d /=1024;
|
1997-02-17 04:11:47 +00:00
|
|
|
|
fprintf (stderr, " (%.2f kB/s)\n", d);
|
1996-08-22 21:30:51 +00:00
|
|
|
|
}
|
1997-01-30 21:43:44 +00:00
|
|
|
|
free(s);
|
|
|
|
|
init = 0;
|
1996-08-22 21:30:51 +00:00
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
bytes += n;
|
|
|
|
|
d = t.tv_sec + t.tv_usec/1.e6 - t0.tv_sec - t0.tv_usec/1.e6;
|
|
|
|
|
if (d < 5) /* display every 5 sec. */
|
|
|
|
|
return;
|
|
|
|
|
t0 = t;
|
|
|
|
|
pr++;
|
1997-02-14 19:08:18 +00:00
|
|
|
|
if(stdoutatty) {
|
|
|
|
|
if (size > 1000000)
|
1997-02-17 04:11:47 +00:00
|
|
|
|
fprintf (stderr, "\r%s: %2qd%%", s, (quad_t)bytes/(size/100));
|
1997-02-14 19:08:18 +00:00
|
|
|
|
else if (size > 0)
|
1997-02-17 04:11:47 +00:00
|
|
|
|
fprintf (stderr, "\r%s: %2qd%%", s, (quad_t)100*bytes/size);
|
1997-02-14 19:08:18 +00:00
|
|
|
|
else
|
1997-02-17 04:11:47 +00:00
|
|
|
|
fprintf (stderr, "\r%s: %qd kB", s, (quad_t)bytes/1024);
|
1997-02-14 19:08:18 +00:00
|
|
|
|
}
|
1996-06-19 09:32:11 +00:00
|
|
|
|
}
|
|
|
|
|
|