1b39628e7SDag-Erling Smørgrav /*- 21de7b4b8SPedro F. Giffuni * SPDX-License-Identifier: BSD-3-Clause 31de7b4b8SPedro F. Giffuni * 42043423cSDag-Erling Smørgrav * Copyright (c) 2000-2014 Dag-Erling Smørgrav 5dcd47379SDag-Erling Smørgrav * Copyright (c) 2013 Michael Gmelin <freebsd@grem.de> 6b39628e7SDag-Erling Smørgrav * All rights reserved. 7b39628e7SDag-Erling Smørgrav * 8b39628e7SDag-Erling Smørgrav * Redistribution and use in source and binary forms, with or without 9b39628e7SDag-Erling Smørgrav * modification, are permitted provided that the following conditions 10b39628e7SDag-Erling Smørgrav * are met: 11b39628e7SDag-Erling Smørgrav * 1. Redistributions of source code must retain the above copyright 12b39628e7SDag-Erling Smørgrav * notice, this list of conditions and the following disclaimer 13b39628e7SDag-Erling Smørgrav * in this position and unchanged. 14b39628e7SDag-Erling Smørgrav * 2. Redistributions in binary form must reproduce the above copyright 15b39628e7SDag-Erling Smørgrav * notice, this list of conditions and the following disclaimer in the 16b39628e7SDag-Erling Smørgrav * documentation and/or other materials provided with the distribution. 17b39628e7SDag-Erling Smørgrav * 3. The name of the author may not be used to endorse or promote products 18b39628e7SDag-Erling Smørgrav * derived from this software without specific prior written permission 19b39628e7SDag-Erling Smørgrav * 20b39628e7SDag-Erling Smørgrav * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 21b39628e7SDag-Erling Smørgrav * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 22b39628e7SDag-Erling Smørgrav * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 23b39628e7SDag-Erling Smørgrav * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 24b39628e7SDag-Erling Smørgrav * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 25b39628e7SDag-Erling Smørgrav * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 26b39628e7SDag-Erling Smørgrav * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 27b39628e7SDag-Erling Smørgrav * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28b39628e7SDag-Erling Smørgrav * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 29b39628e7SDag-Erling Smørgrav * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30b39628e7SDag-Erling Smørgrav */ 31b39628e7SDag-Erling Smørgrav 32b39628e7SDag-Erling Smørgrav #include <sys/param.h> 33b39628e7SDag-Erling Smørgrav #include <sys/socket.h> 34469a4570SBruce Evans #include <sys/stat.h> 3505eb0358SBruce Evans #include <sys/time.h> 36b39628e7SDag-Erling Smørgrav 37821df508SXin LI #include <ctype.h> 38b39628e7SDag-Erling Smørgrav #include <err.h> 39b39628e7SDag-Erling Smørgrav #include <errno.h> 40dcd47379SDag-Erling Smørgrav #include <getopt.h> 4178394463SDag-Erling Smørgrav #include <signal.h> 42eae1afd9SDag-Erling Smørgrav #include <stdint.h> 43b39628e7SDag-Erling Smørgrav #include <stdio.h> 44b39628e7SDag-Erling Smørgrav #include <stdlib.h> 45b39628e7SDag-Erling Smørgrav #include <string.h> 460d60c709SDag-Erling Smørgrav #include <termios.h> 47b39628e7SDag-Erling Smørgrav #include <unistd.h> 48b39628e7SDag-Erling Smørgrav 49b39628e7SDag-Erling Smørgrav #include <fetch.h> 50b39628e7SDag-Erling Smørgrav 51c2ea176dSDag-Erling Smørgrav #define MINBUFSIZE 16384 5231871958SDag-Erling Smørgrav #define TIMEOUT 120 53b39628e7SDag-Erling Smørgrav 54b39628e7SDag-Erling Smørgrav /* Option flags */ 55bf70beceSEd Schouten static int A_flag; /* -A: do not follow 302 redirects */ 56bf70beceSEd Schouten static int a_flag; /* -a: auto retry */ 57bf70beceSEd Schouten static off_t B_size; /* -B: buffer size */ 58bf70beceSEd Schouten static int b_flag; /*! -b: workaround TCP bug */ 59bf70beceSEd Schouten static char *c_dirname; /* -c: remote directory */ 60bf70beceSEd Schouten static int d_flag; /* -d: direct connection */ 61bf70beceSEd Schouten static int F_flag; /* -F: restart without checking mtime */ 62bf70beceSEd Schouten static char *f_filename; /* -f: file to fetch */ 63bf70beceSEd Schouten static char *h_hostname; /* -h: host to fetch from */ 64bf70beceSEd Schouten static int i_flag; /* -i: specify file for mtime comparison */ 65bf70beceSEd Schouten static char *i_filename; /* name of input file */ 66bf70beceSEd Schouten static int l_flag; /* -l: link rather than copy file: URLs */ 67bf70beceSEd Schouten static int m_flag; /* -[Mm]: mirror mode */ 68bf70beceSEd Schouten static char *N_filename; /* -N: netrc file name */ 69bf70beceSEd Schouten static int n_flag; /* -n: do not preserve modification time */ 70bf70beceSEd Schouten static int o_flag; /* -o: specify output file */ 71bf70beceSEd Schouten static int o_directory; /* output file is a directory */ 72bf70beceSEd Schouten static char *o_filename; /* name of output file */ 73bf70beceSEd Schouten static int o_stdout; /* output file is stdout */ 74bf70beceSEd Schouten static int once_flag; /* -1: stop at first successful file */ 75bf70beceSEd Schouten static int p_flag; /* -[Pp]: use passive FTP */ 76bf70beceSEd Schouten static int R_flag; /* -R: don't delete partial files */ 77bf70beceSEd Schouten static int r_flag; /* -r: restart previous transfer */ 78bf70beceSEd Schouten static off_t S_size; /* -S: require size to match */ 79bf70beceSEd Schouten static int s_flag; /* -s: show size, don't fetch */ 80bf70beceSEd Schouten static long T_secs; /* -T: transfer timeout in seconds */ 81bf70beceSEd Schouten static int t_flag; /*! -t: workaround TCP bug */ 82bf70beceSEd Schouten static int U_flag; /* -U: do not use high ports */ 83bf70beceSEd Schouten static int v_level = 1; /* -v: verbosity level */ 84bf70beceSEd Schouten static int v_tty; /* stdout is a tty */ 856434dda6SDag-Erling Smørgrav static int v_progress; /* whether to display progress */ 86bf70beceSEd Schouten static pid_t pgrp; /* our process group */ 87bf70beceSEd Schouten static long w_secs; /* -w: retry delay */ 88bf70beceSEd Schouten static int family = PF_UNSPEC; /* -[46]: address family to use */ 89b39628e7SDag-Erling Smørgrav 90bf70beceSEd Schouten static int sigalrm; /* SIGALRM received */ 91bf70beceSEd Schouten static int siginfo; /* SIGINFO received */ 92bf70beceSEd Schouten static int sigint; /* SIGINT received */ 93b39628e7SDag-Erling Smørgrav 94bf70beceSEd Schouten static long ftp_timeout = TIMEOUT; /* default timeout for FTP transfers */ 95bf70beceSEd Schouten static long http_timeout = TIMEOUT;/* default timeout for HTTP transfers */ 96bf70beceSEd Schouten static char *buf; /* transfer buffer */ 97b39628e7SDag-Erling Smørgrav 98dcd47379SDag-Erling Smørgrav enum options 99dcd47379SDag-Erling Smørgrav { 100dcd47379SDag-Erling Smørgrav OPTION_BIND_ADDRESS, 101dcd47379SDag-Erling Smørgrav OPTION_NO_FTP_PASSIVE_MODE, 102dcd47379SDag-Erling Smørgrav OPTION_HTTP_REFERER, 103dcd47379SDag-Erling Smørgrav OPTION_HTTP_USER_AGENT, 104dcd47379SDag-Erling Smørgrav OPTION_NO_PROXY, 105dcd47379SDag-Erling Smørgrav OPTION_SSL_CA_CERT_FILE, 106dcd47379SDag-Erling Smørgrav OPTION_SSL_CA_CERT_PATH, 107dcd47379SDag-Erling Smørgrav OPTION_SSL_CLIENT_CERT_FILE, 108dcd47379SDag-Erling Smørgrav OPTION_SSL_CLIENT_KEY_FILE, 109dcd47379SDag-Erling Smørgrav OPTION_SSL_CRL_FILE, 110dcd47379SDag-Erling Smørgrav OPTION_SSL_NO_SSL3, 111dcd47379SDag-Erling Smørgrav OPTION_SSL_NO_TLS1, 112dcd47379SDag-Erling Smørgrav OPTION_SSL_NO_VERIFY_HOSTNAME, 113dcd47379SDag-Erling Smørgrav OPTION_SSL_NO_VERIFY_PEER 114dcd47379SDag-Erling Smørgrav }; 115dcd47379SDag-Erling Smørgrav 116dcd47379SDag-Erling Smørgrav 117dcd47379SDag-Erling Smørgrav static struct option longopts[] = 118dcd47379SDag-Erling Smørgrav { 119dcd47379SDag-Erling Smørgrav /* mapping to single character argument */ 120dcd47379SDag-Erling Smørgrav { "one-file", no_argument, NULL, '1' }, 121dcd47379SDag-Erling Smørgrav { "ipv4-only", no_argument, NULL, '4' }, 122dcd47379SDag-Erling Smørgrav { "ipv6-only", no_argument, NULL, '6' }, 123dcd47379SDag-Erling Smørgrav { "no-redirect", no_argument, NULL, 'A' }, 124dcd47379SDag-Erling Smørgrav { "retry", no_argument, NULL, 'a' }, 125dcd47379SDag-Erling Smørgrav { "buffer-size", required_argument, NULL, 'B' }, 126dcd47379SDag-Erling Smørgrav /* -c not mapped, since it's deprecated */ 127dcd47379SDag-Erling Smørgrav { "direct", no_argument, NULL, 'd' }, 128dcd47379SDag-Erling Smørgrav { "force-restart", no_argument, NULL, 'F' }, 129dcd47379SDag-Erling Smørgrav /* -f not mapped, since it's deprecated */ 130dcd47379SDag-Erling Smørgrav /* -h not mapped, since it's deprecated */ 131dcd47379SDag-Erling Smørgrav { "if-modified-since", required_argument, NULL, 'i' }, 132dcd47379SDag-Erling Smørgrav { "symlink", no_argument, NULL, 'l' }, 133dcd47379SDag-Erling Smørgrav /* -M not mapped since it's the same as -m */ 134dcd47379SDag-Erling Smørgrav { "mirror", no_argument, NULL, 'm' }, 135dcd47379SDag-Erling Smørgrav { "netrc", required_argument, NULL, 'N' }, 136dcd47379SDag-Erling Smørgrav { "no-mtime", no_argument, NULL, 'n' }, 137dcd47379SDag-Erling Smørgrav { "output", required_argument, NULL, 'o' }, 138dcd47379SDag-Erling Smørgrav /* -P not mapped since it's the same as -p */ 139dcd47379SDag-Erling Smørgrav { "passive", no_argument, NULL, 'p' }, 140dcd47379SDag-Erling Smørgrav { "quiet", no_argument, NULL, 'q' }, 141dcd47379SDag-Erling Smørgrav { "keep-output", no_argument, NULL, 'R' }, 142dcd47379SDag-Erling Smørgrav { "restart", no_argument, NULL, 'r' }, 143dcd47379SDag-Erling Smørgrav { "require-size", required_argument, NULL, 'S' }, 144dcd47379SDag-Erling Smørgrav { "print-size", no_argument, NULL, 's' }, 145dcd47379SDag-Erling Smørgrav { "timeout", required_argument, NULL, 'T' }, 146dcd47379SDag-Erling Smørgrav { "passive-portrange-default", no_argument, NULL, 'T' }, 147dcd47379SDag-Erling Smørgrav { "verbose", no_argument, NULL, 'v' }, 148dcd47379SDag-Erling Smørgrav { "retry-delay", required_argument, NULL, 'w' }, 149dcd47379SDag-Erling Smørgrav 150dcd47379SDag-Erling Smørgrav /* options without a single character equivalent */ 151dcd47379SDag-Erling Smørgrav { "bind-address", required_argument, NULL, OPTION_BIND_ADDRESS }, 152dcd47379SDag-Erling Smørgrav { "no-passive", no_argument, NULL, OPTION_NO_FTP_PASSIVE_MODE }, 153dcd47379SDag-Erling Smørgrav { "referer", required_argument, NULL, OPTION_HTTP_REFERER }, 154dcd47379SDag-Erling Smørgrav { "user-agent", required_argument, NULL, OPTION_HTTP_USER_AGENT }, 155dcd47379SDag-Erling Smørgrav { "no-proxy", required_argument, NULL, OPTION_NO_PROXY }, 156dcd47379SDag-Erling Smørgrav { "ca-cert", required_argument, NULL, OPTION_SSL_CA_CERT_FILE }, 157dcd47379SDag-Erling Smørgrav { "ca-path", required_argument, NULL, OPTION_SSL_CA_CERT_PATH }, 158dcd47379SDag-Erling Smørgrav { "cert", required_argument, NULL, OPTION_SSL_CLIENT_CERT_FILE }, 159dcd47379SDag-Erling Smørgrav { "key", required_argument, NULL, OPTION_SSL_CLIENT_KEY_FILE }, 160dcd47379SDag-Erling Smørgrav { "crl", required_argument, NULL, OPTION_SSL_CRL_FILE }, 161dcd47379SDag-Erling Smørgrav { "no-sslv3", no_argument, NULL, OPTION_SSL_NO_SSL3 }, 162dcd47379SDag-Erling Smørgrav { "no-tlsv1", no_argument, NULL, OPTION_SSL_NO_TLS1 }, 163dcd47379SDag-Erling Smørgrav { "no-verify-hostname", no_argument, NULL, OPTION_SSL_NO_VERIFY_HOSTNAME }, 164dcd47379SDag-Erling Smørgrav { "no-verify-peer", no_argument, NULL, OPTION_SSL_NO_VERIFY_PEER }, 165dcd47379SDag-Erling Smørgrav 166dcd47379SDag-Erling Smørgrav { NULL, 0, NULL, 0 } 167dcd47379SDag-Erling Smørgrav }; 168b39628e7SDag-Erling Smørgrav 169db695db7SDag-Erling Smørgrav /* 170db695db7SDag-Erling Smørgrav * Signal handler 171db695db7SDag-Erling Smørgrav */ 17291404f38SDag-Erling Smørgrav static void 173b39628e7SDag-Erling Smørgrav sig_handler(int sig) 174b39628e7SDag-Erling Smørgrav { 175ef50a72cSDag-Erling Smørgrav switch (sig) { 176ef50a72cSDag-Erling Smørgrav case SIGALRM: 177ef50a72cSDag-Erling Smørgrav sigalrm = 1; 178ef50a72cSDag-Erling Smørgrav break; 179cd400b67SDag-Erling Smørgrav case SIGINFO: 180cd400b67SDag-Erling Smørgrav siginfo = 1; 181cd400b67SDag-Erling Smørgrav break; 182ef50a72cSDag-Erling Smørgrav case SIGINT: 183ef50a72cSDag-Erling Smørgrav sigint = 1; 184ef50a72cSDag-Erling Smørgrav break; 185ef50a72cSDag-Erling Smørgrav } 186b39628e7SDag-Erling Smørgrav } 187b39628e7SDag-Erling Smørgrav 188b39628e7SDag-Erling Smørgrav struct xferstat { 1893f59deafSDag-Erling Smørgrav char name[64]; 190ae7dc19cSAndre Oppermann struct timeval start; /* start of transfer */ 191ae7dc19cSAndre Oppermann struct timeval last; /* time of last update */ 192ae7dc19cSAndre Oppermann struct timeval last2; /* time of previous last update */ 193ae7dc19cSAndre Oppermann off_t size; /* size of file per HTTP hdr */ 194ae7dc19cSAndre Oppermann off_t offset; /* starting offset in file */ 195ae7dc19cSAndre Oppermann off_t rcvd; /* bytes already received */ 196ae7dc19cSAndre Oppermann off_t lastrcvd; /* bytes received since last update */ 197b39628e7SDag-Erling Smørgrav }; 198b39628e7SDag-Erling Smørgrav 199db695db7SDag-Erling Smørgrav /* 2006434dda6SDag-Erling Smørgrav * Format a number of seconds as either XXdYYh, XXhYYm, XXmYYs, or XXs 2016434dda6SDag-Erling Smørgrav * depending on its magnitude 2026434dda6SDag-Erling Smørgrav */ 2036434dda6SDag-Erling Smørgrav static void 2046434dda6SDag-Erling Smørgrav stat_seconds(char *str, size_t strsz, long seconds) 2056434dda6SDag-Erling Smørgrav { 2066434dda6SDag-Erling Smørgrav 2076434dda6SDag-Erling Smørgrav if (seconds > 86400) 2086434dda6SDag-Erling Smørgrav snprintf(str, strsz, "%02ldd%02ldh", 2096434dda6SDag-Erling Smørgrav seconds / 86400, (seconds % 86400) / 3600); 2106434dda6SDag-Erling Smørgrav else if (seconds > 3600) 2116434dda6SDag-Erling Smørgrav snprintf(str, strsz, "%02ldh%02ldm", 2126434dda6SDag-Erling Smørgrav seconds / 3600, (seconds % 3600) / 60); 2136434dda6SDag-Erling Smørgrav else if (seconds > 60) 2146434dda6SDag-Erling Smørgrav snprintf(str, strsz, "%02ldm%02lds", 2156434dda6SDag-Erling Smørgrav seconds / 60, seconds % 60); 2166434dda6SDag-Erling Smørgrav else 2176434dda6SDag-Erling Smørgrav snprintf(str, strsz, " %02lds", 2186434dda6SDag-Erling Smørgrav seconds); 2196434dda6SDag-Erling Smørgrav } 2206434dda6SDag-Erling Smørgrav 2216434dda6SDag-Erling Smørgrav /* 22213da7d99SDag-Erling Smørgrav * Compute and display ETA 22313da7d99SDag-Erling Smørgrav */ 2246434dda6SDag-Erling Smørgrav static void 2256434dda6SDag-Erling Smørgrav stat_eta(char *str, size_t strsz, const struct xferstat *xs) 22613da7d99SDag-Erling Smørgrav { 227eae1afd9SDag-Erling Smørgrav long elapsed, eta; 228eae1afd9SDag-Erling Smørgrav off_t received, expected; 22913da7d99SDag-Erling Smørgrav 23013da7d99SDag-Erling Smørgrav elapsed = xs->last.tv_sec - xs->start.tv_sec; 23129568c01SDag-Erling Smørgrav received = xs->rcvd - xs->offset; 23229568c01SDag-Erling Smørgrav expected = xs->size - xs->rcvd; 233b12d1620SDag-Erling Smørgrav eta = (long)((double)elapsed * expected / received); 2346434dda6SDag-Erling Smørgrav if (eta > 0) 2356434dda6SDag-Erling Smørgrav stat_seconds(str, strsz, eta); 236ae7dc19cSAndre Oppermann else 2376434dda6SDag-Erling Smørgrav stat_seconds(str, strsz, elapsed); 23813da7d99SDag-Erling Smørgrav } 2393f59deafSDag-Erling Smørgrav 2403f59deafSDag-Erling Smørgrav /* 2413f59deafSDag-Erling Smørgrav * Format a number as "xxxx YB" where Y is ' ', 'k', 'M'... 2423f59deafSDag-Erling Smørgrav */ 2433f59deafSDag-Erling Smørgrav static const char *prefixes = " kMGTP"; 2446434dda6SDag-Erling Smørgrav static void 2456434dda6SDag-Erling Smørgrav stat_bytes(char *str, size_t strsz, off_t bytes) 2463f59deafSDag-Erling Smørgrav { 2473f59deafSDag-Erling Smørgrav const char *prefix = prefixes; 2483f59deafSDag-Erling Smørgrav 2493f59deafSDag-Erling Smørgrav while (bytes > 9999 && prefix[1] != '\0') { 2503f59deafSDag-Erling Smørgrav bytes /= 1024; 2513f59deafSDag-Erling Smørgrav prefix++; 2523f59deafSDag-Erling Smørgrav } 2536434dda6SDag-Erling Smørgrav snprintf(str, strsz, "%4ju %cB", (uintmax_t)bytes, *prefix); 25413da7d99SDag-Erling Smørgrav } 25513da7d99SDag-Erling Smørgrav 25613da7d99SDag-Erling Smørgrav /* 25713da7d99SDag-Erling Smørgrav * Compute and display transfer rate 25813da7d99SDag-Erling Smørgrav */ 2596434dda6SDag-Erling Smørgrav static void 2606434dda6SDag-Erling Smørgrav stat_bps(char *str, size_t strsz, struct xferstat *xs) 26113da7d99SDag-Erling Smørgrav { 2626434dda6SDag-Erling Smørgrav char bytes[16]; 26332c8cd29SDag-Erling Smørgrav double delta, bps; 26413da7d99SDag-Erling Smørgrav 2656434dda6SDag-Erling Smørgrav delta = ((double)xs->last.tv_sec + (xs->last.tv_usec / 1.e6)) 2666434dda6SDag-Erling Smørgrav - ((double)xs->last2.tv_sec + (xs->last2.tv_usec / 1.e6)); 267ae7dc19cSAndre Oppermann 26832c8cd29SDag-Erling Smørgrav if (delta == 0.0) { 2696434dda6SDag-Erling Smørgrav snprintf(str, strsz, "?? Bps"); 2703f59deafSDag-Erling Smørgrav } else { 271162361c1SAndre Oppermann bps = (xs->rcvd - xs->lastrcvd) / delta; 2726434dda6SDag-Erling Smørgrav stat_bytes(bytes, sizeof bytes, (off_t)bps); 2736434dda6SDag-Erling Smørgrav snprintf(str, strsz, "%sps", bytes); 2743f59deafSDag-Erling Smørgrav } 27513da7d99SDag-Erling Smørgrav } 27613da7d99SDag-Erling Smørgrav 27713da7d99SDag-Erling Smørgrav /* 278db695db7SDag-Erling Smørgrav * Update the stats display 279db695db7SDag-Erling Smørgrav */ 28091404f38SDag-Erling Smørgrav static void 28149e62d8fSDag-Erling Smørgrav stat_display(struct xferstat *xs, int force) 28249e62d8fSDag-Erling Smørgrav { 2836434dda6SDag-Erling Smørgrav char bytes[16], bps[16], eta[16]; 28449e62d8fSDag-Erling Smørgrav struct timeval now; 285dbcc1983SDag-Erling Smørgrav int ctty_pgrp; 28649e62d8fSDag-Erling Smørgrav 287dbcc1983SDag-Erling Smørgrav /* check if we're the foreground process */ 2886434dda6SDag-Erling Smørgrav if (ioctl(STDERR_FILENO, TIOCGPGRP, &ctty_pgrp) != 0 || 289dbcc1983SDag-Erling Smørgrav (pid_t)ctty_pgrp != pgrp) 290dbcc1983SDag-Erling Smørgrav return; 291dbcc1983SDag-Erling Smørgrav 29249e62d8fSDag-Erling Smørgrav gettimeofday(&now, NULL); 29349e62d8fSDag-Erling Smørgrav if (!force && now.tv_sec <= xs->last.tv_sec) 29449e62d8fSDag-Erling Smørgrav return; 295ae7dc19cSAndre Oppermann xs->last2 = xs->last; 29649e62d8fSDag-Erling Smørgrav xs->last = now; 29749e62d8fSDag-Erling Smørgrav 298c8488e24SDag-Erling Smørgrav fprintf(stderr, "\r%-46.46s", xs->name); 2996434dda6SDag-Erling Smørgrav if (xs->rcvd >= xs->size) { 3006434dda6SDag-Erling Smørgrav stat_bytes(bytes, sizeof bytes, xs->rcvd); 3016434dda6SDag-Erling Smørgrav setproctitle("%s [%s]", xs->name, bytes); 3026434dda6SDag-Erling Smørgrav fprintf(stderr, " %s", bytes); 303dc161d55SDag-Erling Smørgrav } else { 3046434dda6SDag-Erling Smørgrav stat_bytes(bytes, sizeof bytes, xs->size); 3050c878744SDag-Erling Smørgrav setproctitle("%s [%d%% of %s]", xs->name, 3060c878744SDag-Erling Smørgrav (int)((100.0 * xs->rcvd) / xs->size), 3076434dda6SDag-Erling Smørgrav bytes); 3083f59deafSDag-Erling Smørgrav fprintf(stderr, "%3d%% of %s", 3093f59deafSDag-Erling Smørgrav (int)((100.0 * xs->rcvd) / xs->size), 3106434dda6SDag-Erling Smørgrav bytes); 311dc161d55SDag-Erling Smørgrav } 312ae7dc19cSAndre Oppermann if (force == 2) { 313ae7dc19cSAndre Oppermann xs->lastrcvd = xs->offset; 314ae7dc19cSAndre Oppermann xs->last2 = xs->start; 315ae7dc19cSAndre Oppermann } 3166434dda6SDag-Erling Smørgrav stat_bps(bps, sizeof bps, xs); 3176434dda6SDag-Erling Smørgrav fprintf(stderr, " %s", bps); 318ae7dc19cSAndre Oppermann if ((xs->size > 0 && xs->rcvd > 0 && 319ae7dc19cSAndre Oppermann xs->last.tv_sec >= xs->start.tv_sec + 3) || 3206434dda6SDag-Erling Smørgrav force == 2) { 3216434dda6SDag-Erling Smørgrav stat_eta(eta, sizeof eta, xs); 3226434dda6SDag-Erling Smørgrav fprintf(stderr, " %s", eta); 3236434dda6SDag-Erling Smørgrav } 324ae7dc19cSAndre Oppermann xs->lastrcvd = xs->rcvd; 32549e62d8fSDag-Erling Smørgrav } 326aa4b3574SDag-Erling Smørgrav 327db695db7SDag-Erling Smørgrav /* 328db695db7SDag-Erling Smørgrav * Initialize the transfer statistics 329db695db7SDag-Erling Smørgrav */ 33091404f38SDag-Erling Smørgrav static void 33191404f38SDag-Erling Smørgrav stat_start(struct xferstat *xs, const char *name, off_t size, off_t offset) 332b39628e7SDag-Erling Smørgrav { 3336434dda6SDag-Erling Smørgrav 3346434dda6SDag-Erling Smørgrav memset(xs, 0, sizeof *xs); 335b39628e7SDag-Erling Smørgrav snprintf(xs->name, sizeof xs->name, "%s", name); 336aa4b3574SDag-Erling Smørgrav gettimeofday(&xs->start, NULL); 3376434dda6SDag-Erling Smørgrav xs->last2 = xs->last = xs->start; 338b39628e7SDag-Erling Smørgrav xs->size = size; 339b39628e7SDag-Erling Smørgrav xs->offset = offset; 340b3c141fdSDag-Erling Smørgrav xs->rcvd = offset; 341ae7dc19cSAndre Oppermann xs->lastrcvd = offset; 3426434dda6SDag-Erling Smørgrav if (v_progress) 34349e62d8fSDag-Erling Smørgrav stat_display(xs, 1); 3443f59deafSDag-Erling Smørgrav else if (v_level > 0) 3453f59deafSDag-Erling Smørgrav fprintf(stderr, "%-46s", xs->name); 346b39628e7SDag-Erling Smørgrav } 347b39628e7SDag-Erling Smørgrav 348db695db7SDag-Erling Smørgrav /* 349db695db7SDag-Erling Smørgrav * Update the transfer statistics 350db695db7SDag-Erling Smørgrav */ 35191404f38SDag-Erling Smørgrav static void 35291404f38SDag-Erling Smørgrav stat_update(struct xferstat *xs, off_t rcvd) 353b39628e7SDag-Erling Smørgrav { 3546434dda6SDag-Erling Smørgrav 355b39628e7SDag-Erling Smørgrav xs->rcvd = rcvd; 3566434dda6SDag-Erling Smørgrav if (v_progress) 35749e62d8fSDag-Erling Smørgrav stat_display(xs, 0); 358b39628e7SDag-Erling Smørgrav } 359b39628e7SDag-Erling Smørgrav 360db695db7SDag-Erling Smørgrav /* 361db695db7SDag-Erling Smørgrav * Finalize the transfer statistics 362db695db7SDag-Erling Smørgrav */ 36391404f38SDag-Erling Smørgrav static void 364b39628e7SDag-Erling Smørgrav stat_end(struct xferstat *xs) 365b39628e7SDag-Erling Smørgrav { 3666434dda6SDag-Erling Smørgrav char bytes[16], bps[16], eta[16]; 3676434dda6SDag-Erling Smørgrav 36832c8cd29SDag-Erling Smørgrav gettimeofday(&xs->last, NULL); 3696434dda6SDag-Erling Smørgrav if (v_progress) { 370ae7dc19cSAndre Oppermann stat_display(xs, 2); 3713f59deafSDag-Erling Smørgrav putc('\n', stderr); 3723f59deafSDag-Erling Smørgrav } else if (v_level > 0) { 3736434dda6SDag-Erling Smørgrav stat_bytes(bytes, sizeof bytes, xs->rcvd); 3746434dda6SDag-Erling Smørgrav stat_bps(bps, sizeof bps, xs); 3756434dda6SDag-Erling Smørgrav stat_eta(eta, sizeof eta, xs); 3766434dda6SDag-Erling Smørgrav fprintf(stderr, " %s %s %s\n", bytes, bps, eta); 3773f59deafSDag-Erling Smørgrav } 378b39628e7SDag-Erling Smørgrav } 379b39628e7SDag-Erling Smørgrav 380db695db7SDag-Erling Smørgrav /* 381db695db7SDag-Erling Smørgrav * Ask the user for authentication details 382db695db7SDag-Erling Smørgrav */ 38391404f38SDag-Erling Smørgrav static int 3840d60c709SDag-Erling Smørgrav query_auth(struct url *URL) 3850d60c709SDag-Erling Smørgrav { 3860d60c709SDag-Erling Smørgrav struct termios tios; 3870d60c709SDag-Erling Smørgrav tcflag_t saved_flags; 3880d60c709SDag-Erling Smørgrav int i, nopwd; 3890d60c709SDag-Erling Smørgrav 3900d60c709SDag-Erling Smørgrav fprintf(stderr, "Authentication required for <%s://%s:%d/>!\n", 391650b9e0eSIan Dowse URL->scheme, URL->host, URL->port); 3920d60c709SDag-Erling Smørgrav 3930d60c709SDag-Erling Smørgrav fprintf(stderr, "Login: "); 3940d60c709SDag-Erling Smørgrav if (fgets(URL->user, sizeof URL->user, stdin) == NULL) 395ebeb3babSDag-Erling Smørgrav return (-1); 396e9aa05f1SDag-Erling Smørgrav for (i = strlen(URL->user); i >= 0; --i) 397e9aa05f1SDag-Erling Smørgrav if (URL->user[i] == '\r' || URL->user[i] == '\n') 3980d60c709SDag-Erling Smørgrav URL->user[i] = '\0'; 3990d60c709SDag-Erling Smørgrav 4000d60c709SDag-Erling Smørgrav fprintf(stderr, "Password: "); 4010d60c709SDag-Erling Smørgrav if (tcgetattr(STDIN_FILENO, &tios) == 0) { 4020d60c709SDag-Erling Smørgrav saved_flags = tios.c_lflag; 4030d60c709SDag-Erling Smørgrav tios.c_lflag &= ~ECHO; 4040d60c709SDag-Erling Smørgrav tios.c_lflag |= ECHONL|ICANON; 4050d60c709SDag-Erling Smørgrav tcsetattr(STDIN_FILENO, TCSAFLUSH|TCSASOFT, &tios); 4060d60c709SDag-Erling Smørgrav nopwd = (fgets(URL->pwd, sizeof URL->pwd, stdin) == NULL); 4070d60c709SDag-Erling Smørgrav tios.c_lflag = saved_flags; 4080d60c709SDag-Erling Smørgrav tcsetattr(STDIN_FILENO, TCSANOW|TCSASOFT, &tios); 4090d60c709SDag-Erling Smørgrav } else { 4100d60c709SDag-Erling Smørgrav nopwd = (fgets(URL->pwd, sizeof URL->pwd, stdin) == NULL); 4110d60c709SDag-Erling Smørgrav } 4120d60c709SDag-Erling Smørgrav if (nopwd) 413ebeb3babSDag-Erling Smørgrav return (-1); 414e9aa05f1SDag-Erling Smørgrav for (i = strlen(URL->pwd); i >= 0; --i) 415e9aa05f1SDag-Erling Smørgrav if (URL->pwd[i] == '\r' || URL->pwd[i] == '\n') 4160d60c709SDag-Erling Smørgrav URL->pwd[i] = '\0'; 417e9aa05f1SDag-Erling Smørgrav 418ebeb3babSDag-Erling Smørgrav return (0); 4190d60c709SDag-Erling Smørgrav } 4200d60c709SDag-Erling Smørgrav 421db695db7SDag-Erling Smørgrav /* 422db695db7SDag-Erling Smørgrav * Fetch a file 423db695db7SDag-Erling Smørgrav */ 42491404f38SDag-Erling Smørgrav static int 425bf599c03SEugene Grosbein fetch(char *URL, const char *path, int *is_http) 426b39628e7SDag-Erling Smørgrav { 427b39628e7SDag-Erling Smørgrav struct url *url; 428b39628e7SDag-Erling Smørgrav struct url_stat us; 429e9dc4f3bSDag-Erling Smørgrav struct stat sb, nsb; 430b39628e7SDag-Erling Smørgrav struct xferstat xs; 431b39628e7SDag-Erling Smørgrav FILE *f, *of; 4322a7daafeSDag-Erling Smørgrav size_t size, readcnt, wr; 433e3bad5f7SEugene Grosbein off_t count, size_prev; 434b39628e7SDag-Erling Smørgrav char flags[8]; 435e9dc4f3bSDag-Erling Smørgrav const char *slash; 436e9dc4f3bSDag-Erling Smørgrav char *tmppath; 437e3bad5f7SEugene Grosbein int r, tries; 438eae1afd9SDag-Erling Smørgrav unsigned timeout; 439eae1afd9SDag-Erling Smørgrav char *ptr; 440b39628e7SDag-Erling Smørgrav 441b39628e7SDag-Erling Smørgrav f = of = NULL; 442e9dc4f3bSDag-Erling Smørgrav tmppath = NULL; 443b39628e7SDag-Erling Smørgrav 44413da7d99SDag-Erling Smørgrav timeout = 0; 44513da7d99SDag-Erling Smørgrav *flags = 0; 44613da7d99SDag-Erling Smørgrav count = 0; 44713da7d99SDag-Erling Smørgrav 44813da7d99SDag-Erling Smørgrav /* set verbosity level */ 44913da7d99SDag-Erling Smørgrav if (v_level > 1) 45013da7d99SDag-Erling Smørgrav strcat(flags, "v"); 45113da7d99SDag-Erling Smørgrav if (v_level > 2) 45213da7d99SDag-Erling Smørgrav fetchDebug = 1; 45313da7d99SDag-Erling Smørgrav 454b39628e7SDag-Erling Smørgrav /* parse URL */ 4558181961aSRuslan Ermilov url = NULL; 4568181961aSRuslan Ermilov if (*URL == '\0') { 4578181961aSRuslan Ermilov warnx("empty URL"); 4588181961aSRuslan Ermilov goto failure; 4598181961aSRuslan Ermilov } 460b39628e7SDag-Erling Smørgrav if ((url = fetchParseURL(URL)) == NULL) { 461b39628e7SDag-Erling Smørgrav warnx("%s: parse error", URL); 462b39628e7SDag-Erling Smørgrav goto failure; 463b39628e7SDag-Erling Smørgrav } 464b39628e7SDag-Erling Smørgrav 46564638f67SDag-Erling Smørgrav /* if no scheme was specified, take a guess */ 46664638f67SDag-Erling Smørgrav if (!*url->scheme) { 46764638f67SDag-Erling Smørgrav if (!*url->host) 46864638f67SDag-Erling Smørgrav strcpy(url->scheme, SCHEME_FILE); 46974bd3d76SDag-Erling Smørgrav else if (strncasecmp(url->host, "ftp.", 4) == 0) 47064638f67SDag-Erling Smørgrav strcpy(url->scheme, SCHEME_FTP); 47174bd3d76SDag-Erling Smørgrav else if (strncasecmp(url->host, "www.", 4) == 0) 47264638f67SDag-Erling Smørgrav strcpy(url->scheme, SCHEME_HTTP); 47364638f67SDag-Erling Smørgrav } 47464638f67SDag-Erling Smørgrav 475bf599c03SEugene Grosbein /* for both of http and https */ 47608a2504aSEugene Grosbein *is_http = strncmp(url->scheme, "http", 4) == 0; 477bf599c03SEugene Grosbein 478b39628e7SDag-Erling Smørgrav /* common flags */ 479b39628e7SDag-Erling Smørgrav switch (family) { 480b39628e7SDag-Erling Smørgrav case PF_INET: 481b39628e7SDag-Erling Smørgrav strcat(flags, "4"); 482b39628e7SDag-Erling Smørgrav break; 483b39628e7SDag-Erling Smørgrav case PF_INET6: 484b39628e7SDag-Erling Smørgrav strcat(flags, "6"); 485b39628e7SDag-Erling Smørgrav break; 486b39628e7SDag-Erling Smørgrav } 487b39628e7SDag-Erling Smørgrav 488b39628e7SDag-Erling Smørgrav /* FTP specific flags */ 4898acee1c0SDavid E. O'Brien if (strcmp(url->scheme, SCHEME_FTP) == 0) { 490b39628e7SDag-Erling Smørgrav if (p_flag) 491b39628e7SDag-Erling Smørgrav strcat(flags, "p"); 492b39628e7SDag-Erling Smørgrav if (d_flag) 493b39628e7SDag-Erling Smørgrav strcat(flags, "d"); 4947c480c6cSDag-Erling Smørgrav if (U_flag) 4957c480c6cSDag-Erling Smørgrav strcat(flags, "l"); 496b39628e7SDag-Erling Smørgrav timeout = T_secs ? T_secs : ftp_timeout; 497b39628e7SDag-Erling Smørgrav } 498b39628e7SDag-Erling Smørgrav 499b39628e7SDag-Erling Smørgrav /* HTTP specific flags */ 5002b8907e7SDag-Erling Smørgrav if (strcmp(url->scheme, SCHEME_HTTP) == 0 || 5012b8907e7SDag-Erling Smørgrav strcmp(url->scheme, SCHEME_HTTPS) == 0) { 502b39628e7SDag-Erling Smørgrav if (d_flag) 503b39628e7SDag-Erling Smørgrav strcat(flags, "d"); 504b39628e7SDag-Erling Smørgrav if (A_flag) 505b39628e7SDag-Erling Smørgrav strcat(flags, "A"); 506b39628e7SDag-Erling Smørgrav timeout = T_secs ? T_secs : http_timeout; 5077f92799fSMurray Stokely if (i_flag) { 5087f92799fSMurray Stokely if (stat(i_filename, &sb)) { 5097f92799fSMurray Stokely warn("%s: stat()", i_filename); 5107f92799fSMurray Stokely goto failure; 5117f92799fSMurray Stokely } 5127f92799fSMurray Stokely url->ims_time = sb.st_mtime; 5137f92799fSMurray Stokely strcat(flags, "i"); 5147f92799fSMurray Stokely } 515b39628e7SDag-Erling Smørgrav } 516b39628e7SDag-Erling Smørgrav 517ef50a72cSDag-Erling Smørgrav /* set the protocol timeout. */ 518b39628e7SDag-Erling Smørgrav fetchTimeout = timeout; 519b39628e7SDag-Erling Smørgrav 520b39628e7SDag-Erling Smørgrav /* just print size */ 521b39628e7SDag-Erling Smørgrav if (s_flag) { 522dc161d55SDag-Erling Smørgrav if (timeout) 523dc161d55SDag-Erling Smørgrav alarm(timeout); 524dc161d55SDag-Erling Smørgrav r = fetchStat(url, &us, flags); 525dc161d55SDag-Erling Smørgrav if (timeout) 526dc161d55SDag-Erling Smørgrav alarm(0); 527dc161d55SDag-Erling Smørgrav if (sigalrm || sigint) 528dc161d55SDag-Erling Smørgrav goto signal; 529dc161d55SDag-Erling Smørgrav if (r == -1) { 530dc161d55SDag-Erling Smørgrav warnx("%s", fetchLastErrString); 531e9a039c2SDag-Erling Smørgrav goto failure; 532dc161d55SDag-Erling Smørgrav } 533b39628e7SDag-Erling Smørgrav if (us.size == -1) 534b39628e7SDag-Erling Smørgrav printf("Unknown\n"); 535b39628e7SDag-Erling Smørgrav else 536eae1afd9SDag-Erling Smørgrav printf("%jd\n", (intmax_t)us.size); 537b39628e7SDag-Erling Smørgrav goto success; 538b39628e7SDag-Erling Smørgrav } 539b39628e7SDag-Erling Smørgrav 540e3bad5f7SEugene Grosbein tries = 1; 541e3bad5f7SEugene Grosbein again: 542e3bad5f7SEugene Grosbein r = 0; 543e9a039c2SDag-Erling Smørgrav /* 54491404f38SDag-Erling Smørgrav * If the -r flag was specified, we have to compare the local 54591404f38SDag-Erling Smørgrav * and remote files, so we should really do a fetchStat() 54691404f38SDag-Erling Smørgrav * first, but I know of at least one HTTP server that only 54791404f38SDag-Erling Smørgrav * sends the content size in response to GET requests, and 54891404f38SDag-Erling Smørgrav * leaves it out of replies to HEAD requests. Also, in the 54991404f38SDag-Erling Smørgrav * (frequent) case that the local and remote files match but 55091404f38SDag-Erling Smørgrav * the local file is truncated, we have sufficient information 55191404f38SDag-Erling Smørgrav * before the compare to issue a correct request. Therefore, 55291404f38SDag-Erling Smørgrav * we always issue a GET request as if we were sure the local 55391404f38SDag-Erling Smørgrav * file was a truncated copy of the remote file; we can drop 55491404f38SDag-Erling Smørgrav * the connection later if we change our minds. 555e9a039c2SDag-Erling Smørgrav */ 556a0c6ec97SDag-Erling Smørgrav sb.st_size = -1; 55713901dd2SDag-Erling Smørgrav if (!o_stdout) { 55813901dd2SDag-Erling Smørgrav r = stat(path, &sb); 559e3bad5f7SEugene Grosbein if (r == 0 && (r_flag || tries > 1) && S_ISREG(sb.st_mode)) { 56013901dd2SDag-Erling Smørgrav url->offset = sb.st_size; 56115e2d64eSDag-Erling Smørgrav } else if (r == -1 || !S_ISREG(sb.st_mode)) { 56213901dd2SDag-Erling Smørgrav /* 56313901dd2SDag-Erling Smørgrav * Whatever value sb.st_size has now is either 56413901dd2SDag-Erling Smørgrav * wrong (if stat(2) failed) or irrelevant (if the 56513901dd2SDag-Erling Smørgrav * path does not refer to a regular file) 56613901dd2SDag-Erling Smørgrav */ 56713901dd2SDag-Erling Smørgrav sb.st_size = -1; 56815e2d64eSDag-Erling Smørgrav } 56913901dd2SDag-Erling Smørgrav if (r == -1 && errno != ENOENT) { 570e9dc4f3bSDag-Erling Smørgrav warnx("%s: stat()", path); 571e9dc4f3bSDag-Erling Smørgrav goto failure; 572a0c6ec97SDag-Erling Smørgrav } 57313901dd2SDag-Erling Smørgrav } 574e3bad5f7SEugene Grosbein size_prev = sb.st_size; 575e9a039c2SDag-Erling Smørgrav 576e9a039c2SDag-Erling Smørgrav /* start the transfer */ 577dc161d55SDag-Erling Smørgrav if (timeout) 578dc161d55SDag-Erling Smørgrav alarm(timeout); 579dc161d55SDag-Erling Smørgrav f = fetchXGet(url, &us, flags); 5800c6d34afSDag-Erling Smørgrav if (timeout) 5810c6d34afSDag-Erling Smørgrav alarm(0); 582dc161d55SDag-Erling Smørgrav if (sigalrm || sigint) 583dc161d55SDag-Erling Smørgrav goto signal; 584dc161d55SDag-Erling Smørgrav if (f == NULL) { 5857a6309fdSEugene Grosbein if (i_flag && *is_http && fetchLastErrCode == FETCH_OK && 586deb1ff23SDag-Erling Smørgrav strcmp(fetchLastErrString, "Not Modified") == 0) { 5877f92799fSMurray Stokely /* HTTP Not Modified Response, return OK. */ 5887a6309fdSEugene Grosbein if (v_level > 0) 5897a6309fdSEugene Grosbein warnx("%s: %s", URL, fetchLastErrString); 5907f92799fSMurray Stokely r = 0; 5917f92799fSMurray Stokely goto done; 5927a6309fdSEugene Grosbein } else { 5937a6309fdSEugene Grosbein warnx("%s: %s", URL, fetchLastErrString); 594e9a039c2SDag-Erling Smørgrav goto failure; 595e9a039c2SDag-Erling Smørgrav } 5967a6309fdSEugene Grosbein } 597e9a039c2SDag-Erling Smørgrav if (sigint) 598e9a039c2SDag-Erling Smørgrav goto signal; 599e9a039c2SDag-Erling Smørgrav 600b39628e7SDag-Erling Smørgrav /* check that size is as expected */ 601e9a039c2SDag-Erling Smørgrav if (S_size) { 602e9a039c2SDag-Erling Smørgrav if (us.size == -1) { 6033d7c408cSDag-Erling Smørgrav warnx("%s: size unknown", URL); 604e9a039c2SDag-Erling Smørgrav } else if (us.size != S_size) { 605eae1afd9SDag-Erling Smørgrav warnx("%s: size mismatch: expected %jd, actual %jd", 606eae1afd9SDag-Erling Smørgrav URL, (intmax_t)S_size, (intmax_t)us.size); 607b39628e7SDag-Erling Smørgrav goto failure; 608b39628e7SDag-Erling Smørgrav } 609e9a039c2SDag-Erling Smørgrav } 610b39628e7SDag-Erling Smørgrav 611b39628e7SDag-Erling Smørgrav /* symlink instead of copy */ 612b39628e7SDag-Erling Smørgrav if (l_flag && strcmp(url->scheme, "file") == 0 && !o_stdout) { 613b39628e7SDag-Erling Smørgrav if (symlink(url->doc, path) == -1) { 614b39628e7SDag-Erling Smørgrav warn("%s: symlink()", path); 615b39628e7SDag-Erling Smørgrav goto failure; 616b39628e7SDag-Erling Smørgrav } 617b39628e7SDag-Erling Smørgrav goto success; 618b39628e7SDag-Erling Smørgrav } 619b39628e7SDag-Erling Smørgrav 620eb87c5a7SDag-Erling Smørgrav if (us.size == -1 && !o_stdout && v_level > 0) 6213d7c408cSDag-Erling Smørgrav warnx("%s: size of remote file is not known", URL); 622d939bf77SDag-Erling Smørgrav if (v_level > 1) { 623a0c6ec97SDag-Erling Smørgrav if (sb.st_size != -1) 624eae1afd9SDag-Erling Smørgrav fprintf(stderr, "local size / mtime: %jd / %ld\n", 625eae1afd9SDag-Erling Smørgrav (intmax_t)sb.st_size, (long)sb.st_mtime); 62689a70fbeSDag-Erling Smørgrav if (us.size != -1) 627eae1afd9SDag-Erling Smørgrav fprintf(stderr, "remote size / mtime: %jd / %ld\n", 628eae1afd9SDag-Erling Smørgrav (intmax_t)us.size, (long)us.mtime); 629d939bf77SDag-Erling Smørgrav } 630d939bf77SDag-Erling Smørgrav 631e9a039c2SDag-Erling Smørgrav /* open output file */ 632b39628e7SDag-Erling Smørgrav if (o_stdout) { 633b39628e7SDag-Erling Smørgrav /* output to stdout */ 634b39628e7SDag-Erling Smørgrav of = stdout; 635e9dc4f3bSDag-Erling Smørgrav } else if (r_flag && sb.st_size != -1) { 636e9a039c2SDag-Erling Smørgrav /* resume mode, local file exists */ 637e3bad5f7SEugene Grosbein if (!F_flag && us.mtime && sb.st_mtime != us.mtime && tries == 1) { 638e9a039c2SDag-Erling Smørgrav /* no match! have to refetch */ 639e9a039c2SDag-Erling Smørgrav fclose(f); 64089a70fbeSDag-Erling Smørgrav /* if precious, warn the user and give up */ 64189a70fbeSDag-Erling Smørgrav if (R_flag) { 64291404f38SDag-Erling Smørgrav warnx("%s: local modification time " 64391404f38SDag-Erling Smørgrav "does not match remote", path); 64489a70fbeSDag-Erling Smørgrav goto failure_keep; 64589a70fbeSDag-Erling Smørgrav } 646a17e51c1SDag-Erling Smørgrav } else if (url->offset > sb.st_size) { 647a17e51c1SDag-Erling Smørgrav /* gap between what we asked for and what we got */ 648a17e51c1SDag-Erling Smørgrav warnx("%s: gap in resume mode", URL); 649a17e51c1SDag-Erling Smørgrav fclose(of); 650a17e51c1SDag-Erling Smørgrav of = NULL; 651a17e51c1SDag-Erling Smørgrav /* picked up again later */ 6523d493842SDag-Erling Smørgrav } else if (us.size != -1) { 653e9a039c2SDag-Erling Smørgrav if (us.size == sb.st_size) 654e9a039c2SDag-Erling Smørgrav /* nothing to do */ 655e9a039c2SDag-Erling Smørgrav goto success; 656e9a039c2SDag-Erling Smørgrav if (sb.st_size > us.size) { 657e9a039c2SDag-Erling Smørgrav /* local file too long! */ 658eae1afd9SDag-Erling Smørgrav warnx("%s: local file (%jd bytes) is longer " 659eae1afd9SDag-Erling Smørgrav "than remote file (%jd bytes)", path, 660eae1afd9SDag-Erling Smørgrav (intmax_t)sb.st_size, (intmax_t)us.size); 661e9a039c2SDag-Erling Smørgrav goto failure; 662e9a039c2SDag-Erling Smørgrav } 663e9dc4f3bSDag-Erling Smørgrav /* we got it, open local file */ 66427274e81SDag-Erling Smørgrav if ((of = fopen(path, "r+")) == NULL) { 66510e3b1c7SDag-Erling Smørgrav warn("%s: fopen()", path); 666b39628e7SDag-Erling Smørgrav goto failure; 667b39628e7SDag-Erling Smørgrav } 668e9dc4f3bSDag-Erling Smørgrav /* check that it didn't move under our feet */ 669e9dc4f3bSDag-Erling Smørgrav if (fstat(fileno(of), &nsb) == -1) { 670e9dc4f3bSDag-Erling Smørgrav /* can't happen! */ 671e9dc4f3bSDag-Erling Smørgrav warn("%s: fstat()", path); 67210e3b1c7SDag-Erling Smørgrav goto failure; 67310e3b1c7SDag-Erling Smørgrav } 674e9dc4f3bSDag-Erling Smørgrav if (nsb.st_dev != sb.st_dev || 675a96469b7SEitan Adler nsb.st_ino != sb.st_ino || 676e9dc4f3bSDag-Erling Smørgrav nsb.st_size != sb.st_size) { 6773d7c408cSDag-Erling Smørgrav warnx("%s: file has changed", URL); 678e9dc4f3bSDag-Erling Smørgrav fclose(of); 679e9dc4f3bSDag-Erling Smørgrav of = NULL; 680e9dc4f3bSDag-Erling Smørgrav sb = nsb; 681a17e51c1SDag-Erling Smørgrav /* picked up again later */ 682a17e51c1SDag-Erling Smørgrav } 68327274e81SDag-Erling Smørgrav } 684a17e51c1SDag-Erling Smørgrav /* seek to where we left off */ 6855c5052ccSDag-Erling Smørgrav if (of != NULL && fseeko(of, url->offset, SEEK_SET) != 0) { 6865c5052ccSDag-Erling Smørgrav warn("%s: fseeko()", path); 687a17e51c1SDag-Erling Smørgrav fclose(of); 688a17e51c1SDag-Erling Smørgrav of = NULL; 689a17e51c1SDag-Erling Smørgrav /* picked up again later */ 69010e3b1c7SDag-Erling Smørgrav } 691e9dc4f3bSDag-Erling Smørgrav } else if (m_flag && sb.st_size != -1) { 692e9a039c2SDag-Erling Smørgrav /* mirror mode, local file exists */ 693e9a039c2SDag-Erling Smørgrav if (sb.st_size == us.size && sb.st_mtime == us.mtime) 694e9a039c2SDag-Erling Smørgrav goto success; 695e9a039c2SDag-Erling Smørgrav } 696e9dc4f3bSDag-Erling Smørgrav 697e9dc4f3bSDag-Erling Smørgrav if (of == NULL) { 698e9a039c2SDag-Erling Smørgrav /* 69991404f38SDag-Erling Smørgrav * We don't yet have an output file; either this is a 70091404f38SDag-Erling Smørgrav * vanilla run with no special flags, or the local and 70191404f38SDag-Erling Smørgrav * remote files didn't match. 702e9a039c2SDag-Erling Smørgrav */ 703e9dc4f3bSDag-Erling Smørgrav 70413da7d99SDag-Erling Smørgrav if (url->offset > 0) { 705e9dc4f3bSDag-Erling Smørgrav /* 706e9dc4f3bSDag-Erling Smørgrav * We tried to restart a transfer, but for 707e9dc4f3bSDag-Erling Smørgrav * some reason gave up - so we have to restart 708e9dc4f3bSDag-Erling Smørgrav * from scratch if we want the whole file 709e9dc4f3bSDag-Erling Smørgrav */ 710e9dc4f3bSDag-Erling Smørgrav url->offset = 0; 711e9dc4f3bSDag-Erling Smørgrav if ((f = fetchXGet(url, &us, flags)) == NULL) { 7123d7c408cSDag-Erling Smørgrav warnx("%s: %s", URL, fetchLastErrString); 713e9dc4f3bSDag-Erling Smørgrav goto failure; 714e9dc4f3bSDag-Erling Smørgrav } 715e9dc4f3bSDag-Erling Smørgrav if (sigint) 716e9dc4f3bSDag-Erling Smørgrav goto signal; 717e9dc4f3bSDag-Erling Smørgrav } 718e9dc4f3bSDag-Erling Smørgrav 719e9dc4f3bSDag-Erling Smørgrav /* construct a temp file name */ 720e9dc4f3bSDag-Erling Smørgrav if (sb.st_size != -1 && S_ISREG(sb.st_mode)) { 721e9dc4f3bSDag-Erling Smørgrav if ((slash = strrchr(path, '/')) == NULL) 722e9dc4f3bSDag-Erling Smørgrav slash = path; 723e9dc4f3bSDag-Erling Smørgrav else 724e9dc4f3bSDag-Erling Smørgrav ++slash; 725e3bad5f7SEugene Grosbein if(tmppath != NULL) 726e3bad5f7SEugene Grosbein free(tmppath); 727e9dc4f3bSDag-Erling Smørgrav asprintf(&tmppath, "%.*s.fetch.XXXXXX.%s", 7281c93d61fSMike Barcroft (int)(slash - path), path, slash); 729e9dc4f3bSDag-Erling Smørgrav if (tmppath != NULL) { 7302214137aSEitan Adler if (mkstemps(tmppath, strlen(slash) + 1) == -1) { 7312214137aSEitan Adler warn("%s: mkstemps()", path); 7322214137aSEitan Adler goto failure; 7332214137aSEitan Adler } 734e9dc4f3bSDag-Erling Smørgrav of = fopen(tmppath, "w"); 7351bac3abcSDag-Erling Smørgrav chown(tmppath, sb.st_uid, sb.st_gid); 7361bac3abcSDag-Erling Smørgrav chmod(tmppath, sb.st_mode & ALLPERMS); 737e9dc4f3bSDag-Erling Smørgrav } 73832411a1bSDag-Erling Smørgrav } 73932411a1bSDag-Erling Smørgrav if (of == NULL) 74032411a1bSDag-Erling Smørgrav of = fopen(path, "w"); 741e9dc4f3bSDag-Erling Smørgrav if (of == NULL) { 742b39628e7SDag-Erling Smørgrav warn("%s: open()", path); 743b39628e7SDag-Erling Smørgrav goto failure; 744b39628e7SDag-Erling Smørgrav } 745b39628e7SDag-Erling Smørgrav } 746b39628e7SDag-Erling Smørgrav count = url->offset; 747b39628e7SDag-Erling Smørgrav 748b39628e7SDag-Erling Smørgrav /* start the counter */ 749b39628e7SDag-Erling Smørgrav stat_start(&xs, path, us.size, count); 750b39628e7SDag-Erling Smørgrav 751cd400b67SDag-Erling Smørgrav sigalrm = siginfo = sigint = 0; 75249e62d8fSDag-Erling Smørgrav 75349e62d8fSDag-Erling Smørgrav /* suck in the data */ 754c2ea176dSDag-Erling Smørgrav setvbuf(f, NULL, _IOFBF, B_size); 755cd400b67SDag-Erling Smørgrav signal(SIGINFO, sig_handler); 756dc161d55SDag-Erling Smørgrav while (!sigint) { 7578c355b08SColin Percival if (us.size != -1 && us.size - count < B_size && 7588c355b08SColin Percival us.size - count >= 0) 759b39628e7SDag-Erling Smørgrav size = us.size - count; 76049e62d8fSDag-Erling Smørgrav else 76149e62d8fSDag-Erling Smørgrav size = B_size; 7620d60c709SDag-Erling Smørgrav if (siginfo) { 7630d60c709SDag-Erling Smørgrav stat_end(&xs); 7640d60c709SDag-Erling Smørgrav siginfo = 0; 7650d60c709SDag-Erling Smørgrav } 7662a7daafeSDag-Erling Smørgrav 7672a7daafeSDag-Erling Smørgrav if (size == 0) 7682a7daafeSDag-Erling Smørgrav break; 7692a7daafeSDag-Erling Smørgrav 7702a7daafeSDag-Erling Smørgrav if ((readcnt = fread(buf, 1, size, f)) < size) { 771dc161d55SDag-Erling Smørgrav if (ferror(f) && errno == EINTR && !sigint) 772cd400b67SDag-Erling Smørgrav clearerr(f); 7732a7daafeSDag-Erling Smørgrav else if (readcnt == 0) 774aa4b3574SDag-Erling Smørgrav break; 775b39628e7SDag-Erling Smørgrav } 7762a7daafeSDag-Erling Smørgrav 7772a7daafeSDag-Erling Smørgrav stat_update(&xs, count += readcnt); 7782a7daafeSDag-Erling Smørgrav for (ptr = buf; readcnt > 0; ptr += wr, readcnt -= wr) 7792a7daafeSDag-Erling Smørgrav if ((wr = fwrite(ptr, 1, readcnt, of)) < readcnt) { 780dc161d55SDag-Erling Smørgrav if (ferror(of) && errno == EINTR && !sigint) 781cd400b67SDag-Erling Smørgrav clearerr(of); 782cd400b67SDag-Erling Smørgrav else 783cd400b67SDag-Erling Smørgrav break; 784cd400b67SDag-Erling Smørgrav } 7852a7daafeSDag-Erling Smørgrav if (readcnt != 0) 786cd400b67SDag-Erling Smørgrav break; 787cd400b67SDag-Erling Smørgrav } 788dc161d55SDag-Erling Smørgrav if (!sigalrm) 789dc161d55SDag-Erling Smørgrav sigalrm = ferror(f) && errno == ETIMEDOUT; 790cd400b67SDag-Erling Smørgrav signal(SIGINFO, SIG_DFL); 791b39628e7SDag-Erling Smørgrav 792b39628e7SDag-Erling Smørgrav stat_end(&xs); 793b39628e7SDag-Erling Smørgrav 794dc161d55SDag-Erling Smørgrav /* 795dc161d55SDag-Erling Smørgrav * If the transfer timed out or was interrupted, we still want to 796dc161d55SDag-Erling Smørgrav * set the mtime in case the file is not removed (-r or -R) and 797dc161d55SDag-Erling Smørgrav * the user later restarts the transfer. 798dc161d55SDag-Erling Smørgrav */ 799dc161d55SDag-Erling Smørgrav signal: 80030204f98SDag-Erling Smørgrav /* set mtime of local file */ 801a6266f24SDag-Erling Smørgrav if (!n_flag && us.mtime && !o_stdout && of != NULL && 802a6266f24SDag-Erling Smørgrav (stat(path, &sb) != -1) && sb.st_mode & S_IFREG) { 803b39628e7SDag-Erling Smørgrav struct timeval tv[2]; 804b39628e7SDag-Erling Smørgrav 80549e62d8fSDag-Erling Smørgrav fflush(of); 80649e62d8fSDag-Erling Smørgrav tv[0].tv_sec = (long)(us.atime ? us.atime : us.mtime); 807b39628e7SDag-Erling Smørgrav tv[1].tv_sec = (long)us.mtime; 808b39628e7SDag-Erling Smørgrav tv[0].tv_usec = tv[1].tv_usec = 0; 8098e7cf3deSDag-Erling Smørgrav if (utimes(tmppath ? tmppath : path, tv)) 8108e7cf3deSDag-Erling Smørgrav warn("%s: utimes()", tmppath ? tmppath : path); 811b39628e7SDag-Erling Smørgrav } 812b39628e7SDag-Erling Smørgrav 81349e62d8fSDag-Erling Smørgrav /* timed out or interrupted? */ 814ef50a72cSDag-Erling Smørgrav if (sigalrm) 815ef50a72cSDag-Erling Smørgrav warnx("transfer timed out"); 8169516ffa7SDag-Erling Smørgrav if (sigint) { 817ef50a72cSDag-Erling Smørgrav warnx("transfer interrupted"); 8189516ffa7SDag-Erling Smørgrav goto failure; 8199516ffa7SDag-Erling Smørgrav } 82049e62d8fSDag-Erling Smørgrav 82105f45e0cSBill Fenner /* timeout / interrupt before connection completley established? */ 82205f45e0cSBill Fenner if (f == NULL) 82305f45e0cSBill Fenner goto failure; 82405f45e0cSBill Fenner 8259516ffa7SDag-Erling Smørgrav if (!sigalrm) { 82649e62d8fSDag-Erling Smørgrav /* check the status of our files */ 82749e62d8fSDag-Erling Smørgrav if (ferror(f)) 82849e62d8fSDag-Erling Smørgrav warn("%s", URL); 82949e62d8fSDag-Erling Smørgrav if (ferror(of)) 83049e62d8fSDag-Erling Smørgrav warn("%s", path); 83149e62d8fSDag-Erling Smørgrav if (ferror(f) || ferror(of)) 83249e62d8fSDag-Erling Smørgrav goto failure; 83378394463SDag-Erling Smørgrav } 83449e62d8fSDag-Erling Smørgrav 83549e62d8fSDag-Erling Smørgrav /* did the transfer complete normally? */ 83649e62d8fSDag-Erling Smørgrav if (us.size != -1 && count < us.size) { 837eae1afd9SDag-Erling Smørgrav warnx("%s appears to be truncated: %jd/%jd bytes", 838eae1afd9SDag-Erling Smørgrav path, (intmax_t)count, (intmax_t)us.size); 839a4efbe0dSEugene Grosbein if(!o_stdout && a_flag && count > size_prev) { 840e3bad5f7SEugene Grosbein fclose(f); 841e3bad5f7SEugene Grosbein if (w_secs) 842e3bad5f7SEugene Grosbein sleep(w_secs); 843e3bad5f7SEugene Grosbein tries++; 844e3bad5f7SEugene Grosbein goto again; 845e3bad5f7SEugene Grosbein } 84649e62d8fSDag-Erling Smørgrav goto failure_keep; 847bb11a878SDag-Erling Smørgrav } 848bb11a878SDag-Erling Smørgrav 84930204f98SDag-Erling Smørgrav /* 85030204f98SDag-Erling Smørgrav * If the transfer timed out and we didn't know how much to 85130204f98SDag-Erling Smørgrav * expect, assume the worst (i.e. we didn't get all of it) 85230204f98SDag-Erling Smørgrav */ 85330204f98SDag-Erling Smørgrav if (sigalrm && us.size == -1) { 85430204f98SDag-Erling Smørgrav warnx("%s may be truncated", path); 85530204f98SDag-Erling Smørgrav goto failure_keep; 85630204f98SDag-Erling Smørgrav } 85730204f98SDag-Erling Smørgrav 858b39628e7SDag-Erling Smørgrav success: 85949e62d8fSDag-Erling Smørgrav r = 0; 860e9dc4f3bSDag-Erling Smørgrav if (tmppath != NULL && rename(tmppath, path) == -1) { 861e9dc4f3bSDag-Erling Smørgrav warn("%s: rename()", path); 862e9dc4f3bSDag-Erling Smørgrav goto failure_keep; 863e9dc4f3bSDag-Erling Smørgrav } 864b39628e7SDag-Erling Smørgrav goto done; 865b39628e7SDag-Erling Smørgrav failure: 86649e62d8fSDag-Erling Smørgrav if (of && of != stdout && !R_flag && !r_flag) 867e2b41a62SDag-Erling Smørgrav if (stat(path, &sb) != -1 && (sb.st_mode & S_IFREG)) 868e9dc4f3bSDag-Erling Smørgrav unlink(tmppath ? tmppath : path); 869e9dc4f3bSDag-Erling Smørgrav if (R_flag && tmppath != NULL && sb.st_size == -1) 870e9dc4f3bSDag-Erling Smørgrav rename(tmppath, path); /* ignore errors here */ 87149e62d8fSDag-Erling Smørgrav failure_keep: 872b39628e7SDag-Erling Smørgrav r = -1; 873b39628e7SDag-Erling Smørgrav goto done; 874b39628e7SDag-Erling Smørgrav done: 875b39628e7SDag-Erling Smørgrav if (f) 876b39628e7SDag-Erling Smørgrav fclose(f); 877b39628e7SDag-Erling Smørgrav if (of && of != stdout) 878b39628e7SDag-Erling Smørgrav fclose(of); 879ec850e74SDag-Erling Smørgrav if (url) 880b39628e7SDag-Erling Smørgrav fetchFreeURL(url); 881e9dc4f3bSDag-Erling Smørgrav if (tmppath != NULL) 882e9dc4f3bSDag-Erling Smørgrav free(tmppath); 883ebeb3babSDag-Erling Smørgrav return (r); 884b39628e7SDag-Erling Smørgrav } 885b39628e7SDag-Erling Smørgrav 88691404f38SDag-Erling Smørgrav static void 887b39628e7SDag-Erling Smørgrav usage(void) 888b39628e7SDag-Erling Smørgrav { 889dcd47379SDag-Erling Smørgrav fprintf(stderr, "%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n", 89001ed3ca3SJung-uk Kim "usage: fetch [-146AadFlMmnPpqRrsUv] [-B bytes] [--bind-address=host]", 89101ed3ca3SJung-uk Kim " [--ca-cert=file] [--ca-path=dir] [--cert=file] [--crl=file]", 89201ed3ca3SJung-uk Kim " [-i file] [--key=file] [-N file] [--no-passive] [--no-proxy=list]", 89301ed3ca3SJung-uk Kim " [--no-sslv3] [--no-tlsv1] [--no-verify-hostname] [--no-verify-peer]", 89401ed3ca3SJung-uk Kim " [-o file] [--referer=URL] [-S bytes] [-T seconds]", 895dcd47379SDag-Erling Smørgrav " [--user-agent=agent-string] [-w seconds] URL ...", 89601ed3ca3SJung-uk Kim " fetch [-146AadFlMmnPpqRrsUv] [-B bytes] [--bind-address=host]", 89701ed3ca3SJung-uk Kim " [--ca-cert=file] [--ca-path=dir] [--cert=file] [--crl=file]", 89801ed3ca3SJung-uk Kim " [-i file] [--key=file] [-N file] [--no-passive] [--no-proxy=list]", 89901ed3ca3SJung-uk Kim " [--no-sslv3] [--no-tlsv1] [--no-verify-hostname] [--no-verify-peer]", 90001ed3ca3SJung-uk Kim " [-o file] [--referer=URL] [-S bytes] [-T seconds]", 901dcd47379SDag-Erling Smørgrav " [--user-agent=agent-string] [-w seconds] -h host -f file [-c dir]"); 902b39628e7SDag-Erling Smørgrav } 903b39628e7SDag-Erling Smørgrav 904b39628e7SDag-Erling Smørgrav 905db695db7SDag-Erling Smørgrav /* 906db695db7SDag-Erling Smørgrav * Entry point 907db695db7SDag-Erling Smørgrav */ 908b39628e7SDag-Erling Smørgrav int 909b39628e7SDag-Erling Smørgrav main(int argc, char *argv[]) 910b39628e7SDag-Erling Smørgrav { 911b39628e7SDag-Erling Smørgrav struct stat sb; 91278394463SDag-Erling Smørgrav struct sigaction sa; 91391404f38SDag-Erling Smørgrav const char *p, *s; 91432411a1bSDag-Erling Smørgrav char *end, *q; 915bf599c03SEugene Grosbein int c, e, is_http, r; 916b39628e7SDag-Erling Smørgrav 917dcd47379SDag-Erling Smørgrav 918dcd47379SDag-Erling Smørgrav while ((c = getopt_long(argc, argv, 919dcd47379SDag-Erling Smørgrav "146AaB:bc:dFf:Hh:i:lMmN:nPpo:qRrS:sT:tUvw:", 920dcd47379SDag-Erling Smørgrav longopts, NULL)) != -1) 921b39628e7SDag-Erling Smørgrav switch (c) { 922b39628e7SDag-Erling Smørgrav case '1': 923b39628e7SDag-Erling Smørgrav once_flag = 1; 924b39628e7SDag-Erling Smørgrav break; 925b39628e7SDag-Erling Smørgrav case '4': 926b39628e7SDag-Erling Smørgrav family = PF_INET; 927b39628e7SDag-Erling Smørgrav break; 928b39628e7SDag-Erling Smørgrav case '6': 929b39628e7SDag-Erling Smørgrav family = PF_INET6; 930b39628e7SDag-Erling Smørgrav break; 931b39628e7SDag-Erling Smørgrav case 'A': 932b39628e7SDag-Erling Smørgrav A_flag = 1; 933b39628e7SDag-Erling Smørgrav break; 934b39628e7SDag-Erling Smørgrav case 'a': 935b39628e7SDag-Erling Smørgrav a_flag = 1; 936b39628e7SDag-Erling Smørgrav break; 937b39628e7SDag-Erling Smørgrav case 'B': 93832411a1bSDag-Erling Smørgrav B_size = (off_t)strtol(optarg, &end, 10); 93932411a1bSDag-Erling Smørgrav if (*optarg == '\0' || *end != '\0') 9406d64e939SStefan Eßer errx(1, "invalid buffer size (%s)", optarg); 941b39628e7SDag-Erling Smørgrav break; 942b39628e7SDag-Erling Smørgrav case 'b': 943b39628e7SDag-Erling Smørgrav warnx("warning: the -b option is deprecated"); 944b39628e7SDag-Erling Smørgrav b_flag = 1; 945b39628e7SDag-Erling Smørgrav break; 946a8369cd9SDag-Erling Smørgrav case 'c': 947a8369cd9SDag-Erling Smørgrav c_dirname = optarg; 948a8369cd9SDag-Erling Smørgrav break; 949b39628e7SDag-Erling Smørgrav case 'd': 950b39628e7SDag-Erling Smørgrav d_flag = 1; 951b39628e7SDag-Erling Smørgrav break; 952b39628e7SDag-Erling Smørgrav case 'F': 953b39628e7SDag-Erling Smørgrav F_flag = 1; 954b39628e7SDag-Erling Smørgrav break; 955b39628e7SDag-Erling Smørgrav case 'f': 956b39628e7SDag-Erling Smørgrav f_filename = optarg; 957b39628e7SDag-Erling Smørgrav break; 958b39628e7SDag-Erling Smørgrav case 'H': 95915baa31aSPhilippe Charnier warnx("the -H option is now implicit, " 96091404f38SDag-Erling Smørgrav "use -U to disable"); 961b39628e7SDag-Erling Smørgrav break; 962b39628e7SDag-Erling Smørgrav case 'h': 963b39628e7SDag-Erling Smørgrav h_hostname = optarg; 964b39628e7SDag-Erling Smørgrav break; 9657f92799fSMurray Stokely case 'i': 9667f92799fSMurray Stokely i_flag = 1; 9677f92799fSMurray Stokely i_filename = optarg; 9687f92799fSMurray Stokely break; 969b39628e7SDag-Erling Smørgrav case 'l': 970b39628e7SDag-Erling Smørgrav l_flag = 1; 971b39628e7SDag-Erling Smørgrav break; 972b39628e7SDag-Erling Smørgrav case 'o': 973b39628e7SDag-Erling Smørgrav o_flag = 1; 974b39628e7SDag-Erling Smørgrav o_filename = optarg; 975b39628e7SDag-Erling Smørgrav break; 976b39628e7SDag-Erling Smørgrav case 'M': 977b39628e7SDag-Erling Smørgrav case 'm': 978e9a039c2SDag-Erling Smørgrav if (r_flag) 97991404f38SDag-Erling Smørgrav errx(1, "the -m and -r flags " 98091404f38SDag-Erling Smørgrav "are mutually exclusive"); 981b39628e7SDag-Erling Smørgrav m_flag = 1; 982b39628e7SDag-Erling Smørgrav break; 98313da7d99SDag-Erling Smørgrav case 'N': 98413da7d99SDag-Erling Smørgrav N_filename = optarg; 98513da7d99SDag-Erling Smørgrav break; 986b39628e7SDag-Erling Smørgrav case 'n': 987bb11a878SDag-Erling Smørgrav n_flag = 1; 988b39628e7SDag-Erling Smørgrav break; 989b39628e7SDag-Erling Smørgrav case 'P': 990b39628e7SDag-Erling Smørgrav case 'p': 991b39628e7SDag-Erling Smørgrav p_flag = 1; 992b39628e7SDag-Erling Smørgrav break; 993b39628e7SDag-Erling Smørgrav case 'q': 994b39628e7SDag-Erling Smørgrav v_level = 0; 995b39628e7SDag-Erling Smørgrav break; 996b39628e7SDag-Erling Smørgrav case 'R': 997b39628e7SDag-Erling Smørgrav R_flag = 1; 998b39628e7SDag-Erling Smørgrav break; 999b39628e7SDag-Erling Smørgrav case 'r': 1000e9a039c2SDag-Erling Smørgrav if (m_flag) 100191404f38SDag-Erling Smørgrav errx(1, "the -m and -r flags " 100291404f38SDag-Erling Smørgrav "are mutually exclusive"); 1003b39628e7SDag-Erling Smørgrav r_flag = 1; 1004b39628e7SDag-Erling Smørgrav break; 1005b39628e7SDag-Erling Smørgrav case 'S': 100632066c96SStefan Eßer S_size = strtoll(optarg, &end, 10); 100732411a1bSDag-Erling Smørgrav if (*optarg == '\0' || *end != '\0') 10086d64e939SStefan Eßer errx(1, "invalid size (%s)", optarg); 1009b39628e7SDag-Erling Smørgrav break; 1010b39628e7SDag-Erling Smørgrav case 's': 1011b39628e7SDag-Erling Smørgrav s_flag = 1; 1012b39628e7SDag-Erling Smørgrav break; 1013b39628e7SDag-Erling Smørgrav case 'T': 101432411a1bSDag-Erling Smørgrav T_secs = strtol(optarg, &end, 10); 101532411a1bSDag-Erling Smørgrav if (*optarg == '\0' || *end != '\0') 10166d64e939SStefan Eßer errx(1, "invalid timeout (%s)", optarg); 1017b39628e7SDag-Erling Smørgrav break; 1018b39628e7SDag-Erling Smørgrav case 't': 1019b39628e7SDag-Erling Smørgrav t_flag = 1; 1020b39628e7SDag-Erling Smørgrav warnx("warning: the -t option is deprecated"); 1021b39628e7SDag-Erling Smørgrav break; 10227c480c6cSDag-Erling Smørgrav case 'U': 10237c480c6cSDag-Erling Smørgrav U_flag = 1; 10247c480c6cSDag-Erling Smørgrav break; 1025b39628e7SDag-Erling Smørgrav case 'v': 1026b39628e7SDag-Erling Smørgrav v_level++; 1027b39628e7SDag-Erling Smørgrav break; 1028b39628e7SDag-Erling Smørgrav case 'w': 1029b39628e7SDag-Erling Smørgrav a_flag = 1; 103032411a1bSDag-Erling Smørgrav w_secs = strtol(optarg, &end, 10); 103132411a1bSDag-Erling Smørgrav if (*optarg == '\0' || *end != '\0') 10326d64e939SStefan Eßer errx(1, "invalid delay (%s)", optarg); 1033b39628e7SDag-Erling Smørgrav break; 1034dcd47379SDag-Erling Smørgrav case OPTION_BIND_ADDRESS: 1035dcd47379SDag-Erling Smørgrav setenv("FETCH_BIND_ADDRESS", optarg, 1); 1036dcd47379SDag-Erling Smørgrav break; 1037dcd47379SDag-Erling Smørgrav case OPTION_NO_FTP_PASSIVE_MODE: 1038dcd47379SDag-Erling Smørgrav setenv("FTP_PASSIVE_MODE", "no", 1); 1039dcd47379SDag-Erling Smørgrav break; 1040dcd47379SDag-Erling Smørgrav case OPTION_HTTP_REFERER: 1041dcd47379SDag-Erling Smørgrav setenv("HTTP_REFERER", optarg, 1); 1042dcd47379SDag-Erling Smørgrav break; 1043dcd47379SDag-Erling Smørgrav case OPTION_HTTP_USER_AGENT: 1044dcd47379SDag-Erling Smørgrav setenv("HTTP_USER_AGENT", optarg, 1); 1045dcd47379SDag-Erling Smørgrav break; 1046dcd47379SDag-Erling Smørgrav case OPTION_NO_PROXY: 1047dcd47379SDag-Erling Smørgrav setenv("NO_PROXY", optarg, 1); 1048dcd47379SDag-Erling Smørgrav break; 1049dcd47379SDag-Erling Smørgrav case OPTION_SSL_CA_CERT_FILE: 1050dcd47379SDag-Erling Smørgrav setenv("SSL_CA_CERT_FILE", optarg, 1); 1051dcd47379SDag-Erling Smørgrav break; 1052dcd47379SDag-Erling Smørgrav case OPTION_SSL_CA_CERT_PATH: 1053dcd47379SDag-Erling Smørgrav setenv("SSL_CA_CERT_PATH", optarg, 1); 1054dcd47379SDag-Erling Smørgrav break; 1055dcd47379SDag-Erling Smørgrav case OPTION_SSL_CLIENT_CERT_FILE: 1056dcd47379SDag-Erling Smørgrav setenv("SSL_CLIENT_CERT_FILE", optarg, 1); 1057dcd47379SDag-Erling Smørgrav break; 1058dcd47379SDag-Erling Smørgrav case OPTION_SSL_CLIENT_KEY_FILE: 1059dcd47379SDag-Erling Smørgrav setenv("SSL_CLIENT_KEY_FILE", optarg, 1); 1060dcd47379SDag-Erling Smørgrav break; 1061dcd47379SDag-Erling Smørgrav case OPTION_SSL_CRL_FILE: 1062*1d7a3388SFranco Fichtner setenv("SSL_CRL_FILE", optarg, 1); 1063dcd47379SDag-Erling Smørgrav break; 1064dcd47379SDag-Erling Smørgrav case OPTION_SSL_NO_SSL3: 1065dcd47379SDag-Erling Smørgrav setenv("SSL_NO_SSL3", "", 1); 1066dcd47379SDag-Erling Smørgrav break; 1067dcd47379SDag-Erling Smørgrav case OPTION_SSL_NO_TLS1: 1068dcd47379SDag-Erling Smørgrav setenv("SSL_NO_TLS1", "", 1); 1069dcd47379SDag-Erling Smørgrav break; 1070dcd47379SDag-Erling Smørgrav case OPTION_SSL_NO_VERIFY_HOSTNAME: 1071dcd47379SDag-Erling Smørgrav setenv("SSL_NO_VERIFY_HOSTNAME", "", 1); 1072dcd47379SDag-Erling Smørgrav break; 1073dcd47379SDag-Erling Smørgrav case OPTION_SSL_NO_VERIFY_PEER: 1074dcd47379SDag-Erling Smørgrav setenv("SSL_NO_VERIFY_PEER", "", 1); 1075dcd47379SDag-Erling Smørgrav break; 1076b39628e7SDag-Erling Smørgrav default: 1077b39628e7SDag-Erling Smørgrav usage(); 1078e8f612b6SMurray Stokely exit(1); 1079b39628e7SDag-Erling Smørgrav } 1080b39628e7SDag-Erling Smørgrav 1081b39628e7SDag-Erling Smørgrav argc -= optind; 1082b39628e7SDag-Erling Smørgrav argv += optind; 1083b39628e7SDag-Erling Smørgrav 1084a8369cd9SDag-Erling Smørgrav if (h_hostname || f_filename || c_dirname) { 1085b39628e7SDag-Erling Smørgrav if (!h_hostname || !f_filename || argc) { 1086b39628e7SDag-Erling Smørgrav usage(); 1087e8f612b6SMurray Stokely exit(1); 1088b39628e7SDag-Erling Smørgrav } 1089b39628e7SDag-Erling Smørgrav /* XXX this is a hack. */ 1090b39628e7SDag-Erling Smørgrav if (strcspn(h_hostname, "@:/") != strlen(h_hostname)) 1091b39628e7SDag-Erling Smørgrav errx(1, "invalid hostname"); 1092a8369cd9SDag-Erling Smørgrav if (asprintf(argv, "ftp://%s/%s/%s", h_hostname, 1093a8369cd9SDag-Erling Smørgrav c_dirname ? c_dirname : "", f_filename) == -1) 1094a6f339d2SKris Kennaway errx(1, "%s", strerror(ENOMEM)); 1095b39628e7SDag-Erling Smørgrav argc++; 1096b39628e7SDag-Erling Smørgrav } 1097b39628e7SDag-Erling Smørgrav 1098b39628e7SDag-Erling Smørgrav if (!argc) { 1099b39628e7SDag-Erling Smørgrav usage(); 1100e8f612b6SMurray Stokely exit(1); 1101b39628e7SDag-Erling Smørgrav } 1102b39628e7SDag-Erling Smørgrav 1103b39628e7SDag-Erling Smørgrav /* allocate buffer */ 1104b39628e7SDag-Erling Smørgrav if (B_size < MINBUFSIZE) 1105b39628e7SDag-Erling Smørgrav B_size = MINBUFSIZE; 1106b39628e7SDag-Erling Smørgrav if ((buf = malloc(B_size)) == NULL) 1107a6f339d2SKris Kennaway errx(1, "%s", strerror(ENOMEM)); 1108b39628e7SDag-Erling Smørgrav 110978394463SDag-Erling Smørgrav /* timeouts */ 1110b39628e7SDag-Erling Smørgrav if ((s = getenv("FTP_TIMEOUT")) != NULL) { 111132411a1bSDag-Erling Smørgrav ftp_timeout = strtol(s, &end, 10); 1112dacff752SDag-Erling Smørgrav if (*s == '\0' || *end != '\0' || ftp_timeout < 0) { 1113dacff752SDag-Erling Smørgrav warnx("FTP_TIMEOUT (%s) is not a positive integer", s); 1114b39628e7SDag-Erling Smørgrav ftp_timeout = 0; 1115b39628e7SDag-Erling Smørgrav } 1116b39628e7SDag-Erling Smørgrav } 1117b39628e7SDag-Erling Smørgrav if ((s = getenv("HTTP_TIMEOUT")) != NULL) { 111832411a1bSDag-Erling Smørgrav http_timeout = strtol(s, &end, 10); 1119dacff752SDag-Erling Smørgrav if (*s == '\0' || *end != '\0' || http_timeout < 0) { 1120dacff752SDag-Erling Smørgrav warnx("HTTP_TIMEOUT (%s) is not a positive integer", s); 1121b39628e7SDag-Erling Smørgrav http_timeout = 0; 1122b39628e7SDag-Erling Smørgrav } 1123b39628e7SDag-Erling Smørgrav } 1124b39628e7SDag-Erling Smørgrav 112578394463SDag-Erling Smørgrav /* signal handling */ 112678394463SDag-Erling Smørgrav sa.sa_flags = 0; 112778394463SDag-Erling Smørgrav sa.sa_handler = sig_handler; 112878394463SDag-Erling Smørgrav sigemptyset(&sa.sa_mask); 1129e9a039c2SDag-Erling Smørgrav sigaction(SIGALRM, &sa, NULL); 1130e9a039c2SDag-Erling Smørgrav sa.sa_flags = SA_RESETHAND; 1131e9a039c2SDag-Erling Smørgrav sigaction(SIGINT, &sa, NULL); 1132e9a039c2SDag-Erling Smørgrav fetchRestartCalls = 0; 1133ef50a72cSDag-Erling Smørgrav 1134b39628e7SDag-Erling Smørgrav /* output file */ 1135b39628e7SDag-Erling Smørgrav if (o_flag) { 1136b39628e7SDag-Erling Smørgrav if (strcmp(o_filename, "-") == 0) { 1137b39628e7SDag-Erling Smørgrav o_stdout = 1; 1138b39628e7SDag-Erling Smørgrav } else if (stat(o_filename, &sb) == -1) { 1139b39628e7SDag-Erling Smørgrav if (errno == ENOENT) { 1140b39628e7SDag-Erling Smørgrav if (argc > 1) 1141e8f612b6SMurray Stokely errx(1, "%s is not a directory", 114291404f38SDag-Erling Smørgrav o_filename); 1143b39628e7SDag-Erling Smørgrav } else { 1144e8f612b6SMurray Stokely err(1, "%s", o_filename); 1145b39628e7SDag-Erling Smørgrav } 1146b39628e7SDag-Erling Smørgrav } else { 1147b39628e7SDag-Erling Smørgrav if (sb.st_mode & S_IFDIR) 1148b39628e7SDag-Erling Smørgrav o_directory = 1; 1149b39628e7SDag-Erling Smørgrav } 1150b39628e7SDag-Erling Smørgrav } 1151b39628e7SDag-Erling Smørgrav 1152b39628e7SDag-Erling Smørgrav /* check if output is to a tty (for progress report) */ 1153bb11a878SDag-Erling Smørgrav v_tty = isatty(STDERR_FILENO); 11546434dda6SDag-Erling Smørgrav v_progress = v_tty && v_level > 0; 11556434dda6SDag-Erling Smørgrav if (v_progress) 1156dbcc1983SDag-Erling Smørgrav pgrp = getpgrp(); 1157dbcc1983SDag-Erling Smørgrav 1158b39628e7SDag-Erling Smørgrav r = 0; 1159b39628e7SDag-Erling Smørgrav 11600d60c709SDag-Erling Smørgrav /* authentication */ 11615ef824edSDag-Erling Smørgrav if (v_tty) 11620d60c709SDag-Erling Smørgrav fetchAuthMethod = query_auth; 116313da7d99SDag-Erling Smørgrav if (N_filename != NULL) 11642214137aSEitan Adler if (setenv("NETRC", N_filename, 1) == -1) 11652214137aSEitan Adler err(1, "setenv: cannot set NETRC=%s", N_filename); 11660d60c709SDag-Erling Smørgrav 1167b39628e7SDag-Erling Smørgrav while (argc) { 1168b39628e7SDag-Erling Smørgrav if ((p = strrchr(*argv, '/')) == NULL) 1169b39628e7SDag-Erling Smørgrav p = *argv; 1170b39628e7SDag-Erling Smørgrav else 1171b39628e7SDag-Erling Smørgrav p++; 1172b39628e7SDag-Erling Smørgrav 1173b39628e7SDag-Erling Smørgrav if (!*p) 1174b39628e7SDag-Erling Smørgrav p = "fetch.out"; 1175b39628e7SDag-Erling Smørgrav 1176b39628e7SDag-Erling Smørgrav fetchLastErrCode = 0; 1177b39628e7SDag-Erling Smørgrav 1178b39628e7SDag-Erling Smørgrav if (o_flag) { 1179b39628e7SDag-Erling Smørgrav if (o_stdout) { 1180bf599c03SEugene Grosbein e = fetch(*argv, "-", &is_http); 1181b39628e7SDag-Erling Smørgrav } else if (o_directory) { 1182b39628e7SDag-Erling Smørgrav asprintf(&q, "%s/%s", o_filename, p); 1183bf599c03SEugene Grosbein e = fetch(*argv, q, &is_http); 1184b39628e7SDag-Erling Smørgrav free(q); 1185b39628e7SDag-Erling Smørgrav } else { 1186bf599c03SEugene Grosbein e = fetch(*argv, o_filename, &is_http); 1187b39628e7SDag-Erling Smørgrav } 1188b39628e7SDag-Erling Smørgrav } else { 1189bf599c03SEugene Grosbein e = fetch(*argv, p, &is_http); 1190b39628e7SDag-Erling Smørgrav } 1191b39628e7SDag-Erling Smørgrav 1192ef50a72cSDag-Erling Smørgrav if (sigint) 1193e9a039c2SDag-Erling Smørgrav kill(getpid(), SIGINT); 1194ef50a72cSDag-Erling Smørgrav 1195b39628e7SDag-Erling Smørgrav if (e == 0 && once_flag) 1196b39628e7SDag-Erling Smørgrav exit(0); 1197b39628e7SDag-Erling Smørgrav 1198b39628e7SDag-Erling Smørgrav if (e) { 1199b39628e7SDag-Erling Smørgrav r = 1; 1200b39628e7SDag-Erling Smørgrav if ((fetchLastErrCode 120185f15576SEugene Grosbein && fetchLastErrCode != FETCH_AUTH 1202b39628e7SDag-Erling Smørgrav && fetchLastErrCode != FETCH_UNAVAIL 1203b39628e7SDag-Erling Smørgrav && fetchLastErrCode != FETCH_MOVED 1204b39628e7SDag-Erling Smørgrav && fetchLastErrCode != FETCH_URL 1205b39628e7SDag-Erling Smørgrav && fetchLastErrCode != FETCH_RESOLV 1206bf599c03SEugene Grosbein && fetchLastErrCode != FETCH_UNKNOWN 120785f15576SEugene Grosbein && (!is_http || ( 120885f15576SEugene Grosbein fetchLastErrCode != FETCH_PROTO 1209bf599c03SEugene Grosbein && fetchLastErrCode != FETCH_SERVER 1210bf599c03SEugene Grosbein && fetchLastErrCode != FETCH_TEMP 1211bf599c03SEugene Grosbein && fetchLastErrCode != FETCH_TIMEOUT 121285f15576SEugene Grosbein )))) { 121391404f38SDag-Erling Smørgrav if (w_secs && v_level) 121432411a1bSDag-Erling Smørgrav fprintf(stderr, "Waiting %ld seconds " 121591404f38SDag-Erling Smørgrav "before retrying\n", w_secs); 121691404f38SDag-Erling Smørgrav if (w_secs) 1217b39628e7SDag-Erling Smørgrav sleep(w_secs); 1218b39628e7SDag-Erling Smørgrav if (a_flag) 1219b39628e7SDag-Erling Smørgrav continue; 1220b39628e7SDag-Erling Smørgrav } 1221b39628e7SDag-Erling Smørgrav } 1222b39628e7SDag-Erling Smørgrav 1223b39628e7SDag-Erling Smørgrav argc--, argv++; 1224b39628e7SDag-Erling Smørgrav } 1225b39628e7SDag-Erling Smørgrav 1226b39628e7SDag-Erling Smørgrav exit(r); 1227b39628e7SDag-Erling Smørgrav } 1228