main (int argc, char **argv)
{
  char **url, **t, *p;
  int i, ret, longindex;
  int nurl;
  int retconf;
  int argstring_length;
  bool use_userconfig = false;
  bool noconfig = false;
  bool append_to_log = false;

  cleaned_up = 0; /* do cleanup later */

  timer = ptimer_new ();
  double start_time = ptimer_measure (timer);

  total_downloaded_bytes = 0;
  program_name = argv[0];

  i18n_initialize ();

  /* Construct the name of the executable, without the directory part.  */
#ifdef __VMS
  /* On VMS, lose the "dev:[dir]" prefix and the ".EXE;nnn" suffix. */
  exec_name = vms_basename (argv[0]);
#else /* def __VMS */
  exec_name = base_name (argv[0]);
#endif /* def __VMS [else] */

#ifdef WINDOWS
  /* Drop extension (typically .EXE) from executable filename. */
  windows_main ((char **) &exec_name);
#endif

  /* Construct the arguments string. */
  for (argstring_length = 1, i = 1; i < argc; i++)
    argstring_length += strlen (argv[i]) + 3 + 1;
  program_argstring = p = malloc (argstring_length);
  if (p == NULL)
    {
      fprintf (stderr, _("Memory allocation problem\n"));
      exit (WGET_EXIT_PARSE_ERROR);
    }
  for (i = 1; i < argc; i++)
    {
      int arglen;

      *p++ = '"';
      arglen = strlen (argv[i]);
      memcpy (p, argv[i], arglen);
      p += arglen;
      *p++ = '"';
      *p++ = ' ';
    }
  *p = '\0';

  /* Load the hard-coded defaults.  */
  defaults ();
  opt.homedir = home_dir();

  init_switches ();

  /* This separate getopt_long is needed to find the user config file
     option ("--config") and parse it before the other user options. */
  longindex = -1;

  while ((retconf = getopt_long (argc, argv,
                                short_options, long_options, &longindex)) != -1)
    {
      int confval;
      struct cmdline_option *config_opt;

      /* There is no short option for "--config". */
      if (longindex >= 0)
        {
          confval = long_options[longindex].val;
          config_opt = &option_data[confval & ~BOOLEAN_NEG_MARKER];
          if (strcmp (config_opt->long_name, "no-config") == 0)
            {
              noconfig = true;
              break;
            }
          else if (strcmp (config_opt->long_name, "config") == 0)
            {
              file_stats_t flstats;
              use_userconfig = true;
              memset(&flstats, 0, sizeof(flstats));
              if (file_exists_p(optarg, &flstats) && run_wgetrc (optarg, &flstats))
                break;
              else
                {
                  fprintf (stderr, _("Exiting due to error in %s\n"), optarg);
                  exit (WGET_EXIT_PARSE_ERROR);
                }
            }
        }
    }

  /* If the user did not specify a config, read the system wgetrc and ~/.wgetrc. */
  if (noconfig == false && use_userconfig == false)
    if ((ret = initialize ()))
      return ret;

  opterr = 0;
  optind = 0;

  longindex = -1;
  while ((ret = getopt_long (argc, argv,
                             short_options, long_options, &longindex)) != -1)
    {
      int val;
      struct cmdline_option *cmdopt;

      /* If LONGINDEX is unchanged, it means RET is referring a short
         option.  */
      if (longindex == -1)
        {
          if (ret == '?')
            {
              print_usage (1);
              fprintf (stderr, "\n");
              fprintf (stderr, _("Try `%s --help' for more options.\n"),
                       exec_name);
              exit (WGET_EXIT_PARSE_ERROR);
            }
          /* Find the short option character in the mapping.  */
          longindex = optmap[ret - 32];
        }
      val = long_options[longindex].val;

      /* Use the retrieved value to locate the option in the
         option_data array, and to see if we're dealing with the
         negated "--no-FOO" variant of the boolean option "--foo".  */
      cmdopt = &option_data[val & ~BOOLEAN_NEG_MARKER];
      switch (cmdopt->type)
        {
        case OPT_VALUE:
          setoptval (cmdopt->data, optarg, cmdopt->long_name);
          break;
        case OPT_BOOLEAN:
          if (optarg)
            /* The user has specified a value -- use it. */
            setoptval (cmdopt->data, optarg, cmdopt->long_name);
          else
            {
              /* NEG is true for `--no-FOO' style boolean options. */
              bool neg = !!(val & BOOLEAN_NEG_MARKER);
              setoptval (cmdopt->data, neg ? "0" : "1", cmdopt->long_name);
            }
          break;
        case OPT_FUNCALL:
          {
            void (*func) (void) = (void (*) (void)) cmdopt->data;
            func ();
          }
          break;
        case OPT__APPEND_OUTPUT:
          setoptval ("logfile", optarg, cmdopt->long_name);
          append_to_log = true;
          break;
        case OPT__EXECUTE:
          if (optarg) /* check silences static analyzer */
            run_command (optarg);
          break;
        case OPT__NO:
          {
            /* We support real --no-FOO flags now, but keep these
               short options for convenience and backward
               compatibility.  */
            for (p = optarg; p && *p; p++)
              switch (*p)
                {
                case 'v':
                  setoptval ("verbose", "0", cmdopt->long_name);
                  break;
                case 'H':
                  setoptval ("addhostdir", "0", cmdopt->long_name);
                  break;
                case 'd':
                  setoptval ("dirstruct", "0", cmdopt->long_name);
                  break;
                case 'c':
                  setoptval ("noclobber", "1", cmdopt->long_name);
                  break;
                case 'p':
                  setoptval ("noparent", "1", cmdopt->long_name);
                  break;
                default:
                  fprintf (stderr, _("%s: illegal option -- `-n%c'\n"),
                           exec_name, *p);
                  print_usage (1);
                  fprintf (stderr, "\n");
                  fprintf (stderr, _("Try `%s --help' for more options.\n"),
                           exec_name);
                  exit (WGET_EXIT_GENERIC_ERROR);
                }
            break;
          }
        case OPT__PARENT:
        case OPT__CLOBBER:
          {
            /* The wgetrc commands are named noparent and noclobber,
               so we must revert the meaning of the cmdline options
               before passing the value to setoptval.  */
            bool flag = true;
            if (optarg)
              flag = (*optarg == '1' || c_tolower (*optarg) == 'y'
                      || (c_tolower (optarg[0]) == 'o'
                          && c_tolower (optarg[1]) == 'n'));
            setoptval (cmdopt->type == OPT__PARENT ? "noparent" : "noclobber",
                       flag ? "0" : "1", cmdopt->long_name);
            break;
          }
        case OPT__DONT_REMOVE_LISTING:
          setoptval ("removelisting", "0", cmdopt->long_name);
          break;
        }

      longindex = -1;
    }

  nurl = argc - optind;

  /* Initialize logging ASAP.  */
  log_init (opt.lfilename, append_to_log);

  /* If we do not have Debug support compiled in AND Wget is invoked with the
   * --debug switch, instead of failing, we silently turn it into a no-op. For
   *  this no-op, we explicitly set opt.debug to false and hence none of the
   *  Debug output messages will be printed.
   */
#ifndef ENABLE_DEBUG
  if (opt.debug)
    {
      fprintf (stderr, _("Debugging support not compiled in. "
                         "Ignoring --debug flag.\n"));
      opt.debug = false;
    }
#endif

  /* All user options have now been processed, so it's now safe to do
     interoption dependency checks. */

  if (opt.noclobber && (opt.convert_links || opt.convert_file_only))
    {
      fprintf (stderr,
               opt.convert_links ?
                   _("Both --no-clobber and --convert-links were specified,"
                     " only --convert-links will be used.\n") :
                    _("Both --no-clobber and --convert-file-only were specified,"
                      " only --convert-file-only will be used.\n"));
      opt.noclobber = false;
    }

  if (opt.reclevel == 0)
      opt.reclevel = INFINITE_RECURSION; /* see recur.h for commentary */

  if (opt.spider || opt.delete_after)
      opt.no_dirstruct = true;

  if (opt.page_requisites && !opt.recursive)
    {
      /* Don't set opt.recursive here because it would confuse the FTP
         code.  Instead, call retrieve_tree below when either
         page_requisites or recursive is requested.  */
      opt.reclevel = 0;
      if (!opt.no_dirstruct)
        opt.dirstruct = 1;      /* normally handled by cmd_spec_recursive() */
    }

  if (opt.verbose == -1)
    opt.verbose = !opt.quiet;

  if (!opt.verbose && opt.show_progress == -1)
    opt.show_progress = false;

  if (opt.quiet && opt.show_progress == -1)
    opt.show_progress = false;

  /* Sanity checks.  */
  if (opt.verbose && opt.quiet)
    {
      fprintf (stderr, _("Can't be verbose and quiet at the same time.\n"));
      print_usage (1);
      exit (WGET_EXIT_GENERIC_ERROR);
    }
  if (opt.timestamping && opt.noclobber)
    {
      fprintf (stderr, _("\
Can't timestamp and not clobber old files at the same time.\n"));
      print_usage (1);
      exit (WGET_EXIT_GENERIC_ERROR);
    }
#ifdef ENABLE_IPV6
  if (opt.ipv4_only && opt.ipv6_only)
    {
      fprintf (stderr,
               _("Cannot specify both --inet4-only and --inet6-only.\n"));
      print_usage (1);
      exit (WGET_EXIT_GENERIC_ERROR);
    }
#endif
  if (opt.output_document)
    {
      if ((opt.convert_links || opt.convert_file_only)
          && (nurl > 1 || opt.page_requisites || opt.recursive))
        {
          fputs (_("\
Cannot specify both -k or --convert-file-only and -O if multiple URLs are given, or in combination\n\
with -p or -r. See the manual for details.\n\n"), stderr);
          print_usage (1);
          exit (WGET_EXIT_GENERIC_ERROR);
        }
      if (opt.page_requisites
          || opt.recursive)
        {
          logprintf (LOG_NOTQUIET, "%s", _("\
WARNING: combining -O with -r or -p will mean that all downloaded content\n\
will be placed in the single file you specified.\n\n"));
        }
      if (opt.timestamping)
        {
          logprintf (LOG_NOTQUIET, "%s", _("\
WARNING: timestamping does nothing in combination with -O. See the manual\n\
for details.\n\n"));
          opt.timestamping = false;
        }
      if (opt.noclobber && file_exists_p(opt.output_document, NULL))
           {
              /* Check if output file exists; if it does, exit. */
              logprintf (LOG_VERBOSE,
                         _("File %s already there; not retrieving.\n"),
                         quote (opt.output_document));
              exit (WGET_EXIT_GENERIC_ERROR);
           }
    }

  if (opt.warc_filename != 0)
    {
      if (opt.noclobber)
        {
          fprintf (stderr,
                   _("WARC output does not work with --no-clobber, "
                     "--no-clobber will be disabled.\n"));
          opt.noclobber = false;
        }
      if (opt.timestamping)
        {
          fprintf (stderr,
                   _("WARC output does not work with timestamping, "
                     "timestamping will be disabled.\n"));
          opt.timestamping = false;
        }
      if (opt.spider)
        {
          fprintf (stderr,
                   _("WARC output does not work with --spider.\n"));
          exit (WGET_EXIT_GENERIC_ERROR);
        }
      if (opt.always_rest || opt.start_pos >= 0)
        {
          fprintf (stderr,
                   _("WARC output does not work with --continue or"
                     " --start-pos, they will be disabled.\n"));
          opt.always_rest = false;
          opt.start_pos = -1;
        }
      if (opt.warc_cdx_dedup_filename != 0 && !opt.warc_digests_enabled)
        {
          fprintf (stderr,
                   _("Digests are disabled; WARC deduplication will "
                     "not find duplicate records.\n"));
        }
      if (opt.warc_keep_log)
        {
          opt.progress_type = xstrdup ("dot");
        }
    }

#ifdef HAVE_LIBZ
  if (opt.always_rest || opt.start_pos >= 0)
    {
      if (opt.compression == compression_auto)
        {
          /* Compression does not work with --continue or --start-pos.
             Since compression was not explicitly set, it will be disabled. */
          opt.compression = compression_none;
        }
      else if (opt.compression != compression_none)
        {
          fprintf (stderr,
                   _("Compression does not work with --continue or"
                     " --start-pos, they will be disabled.\n"));
          opt.always_rest = false;
          opt.start_pos = -1;
        }
    }
#endif

  if (opt.ask_passwd && opt.passwd)
    {
      fprintf (stderr,
               _("Cannot specify both --ask-password and --password.\n"));
      print_usage (1);
      exit (WGET_EXIT_GENERIC_ERROR);
    }

  if (opt.start_pos >= 0 && opt.always_rest)
    {
      fprintf (stderr,
               _("Specifying both --start-pos and --continue is not "
                 "recommended; --continue will be disabled.\n"));
      opt.always_rest = false;
    }

  if (!nurl && !opt.input_filename
#ifdef HAVE_METALINK
      && !opt.input_metalink
#endif
      )
    {
      /* No URL specified.  */
#ifndef TESTING
      fprintf (stderr, _("%s: missing URL\n"), exec_name);
      print_usage (1);
      fprintf (stderr, "\n");
      /* #### Something nicer should be printed here -- similar to the
         pre-1.5 `--help' page.  */
      fprintf (stderr, _("Try `%s --help' for more options.\n"), exec_name);
#endif
      exit (WGET_EXIT_GENERIC_ERROR);
    }

  /* Compile the regular expressions.  */
  switch (opt.regex_type)
    {
#ifdef HAVE_LIBPCRE2
      case regex_type_pcre:
        opt.regex_compile_fun = compile_pcre2_regex;
        opt.regex_match_fun = match_pcre2_regex;
        break;
#endif
#ifdef HAVE_LIBPCRE
      case regex_type_pcre:
        opt.regex_compile_fun = compile_pcre_regex;
        opt.regex_match_fun = match_pcre_regex;
        break;
#endif

      case regex_type_posix:
      default:
        opt.regex_compile_fun = compile_posix_regex;
        opt.regex_match_fun = match_posix_regex;
        break;
    }
  if (opt.acceptregex_s)
    {
      opt.acceptregex = opt.regex_compile_fun (opt.acceptregex_s);
      if (!opt.acceptregex)
        exit (WGET_EXIT_GENERIC_ERROR);
    }
  if (opt.rejectregex_s)
    {
      opt.rejectregex = opt.regex_compile_fun (opt.rejectregex_s);
      if (!opt.rejectregex)
        exit (WGET_EXIT_GENERIC_ERROR);
    }
  if (opt.post_data || opt.post_file_name)
    {
      if (opt.post_data && opt.post_file_name)
        {
          fprintf (stderr, _("You cannot specify both --post-data and --post-file.\n"));
          exit (WGET_EXIT_GENERIC_ERROR);
        }
      else if (opt.method)
        {
          fprintf (stderr, _("You cannot use --post-data or --post-file along with --method. "
                             "--method expects data through --body-data and --body-file options\n"));
          exit (WGET_EXIT_GENERIC_ERROR);
        }
    }
  if (opt.body_data || opt.body_file)
    {
      if (!opt.method)
        {
          fprintf (stderr, _("You must specify a method through --method=HTTPMethod "
                              "to use with --body-data or --body-file.\n"));
          exit (WGET_EXIT_GENERIC_ERROR);
        }
      else if (opt.body_data && opt.body_file)
        {
          fprintf (stderr, _("You cannot specify both --body-data and --body-file.\n"));
          exit (WGET_EXIT_GENERIC_ERROR);
        }
    }

  /* Set various options as required for opt.method.  */

  /* When user specifies HEAD as the method, we do not wish to download any
     files. Hence, set wget to run in spider mode.  */
  if (opt.method && c_strcasecmp (opt.method, "HEAD") == 0)
    setoptval ("spider", "1", "spider");

  /* Convert post_data to body-data and post_file_name to body-file options.
     This is required so as to remove redundant code later on in gethttp().
     The --post-data and --post-file options may also be removed in
     the future hence it makes sense to convert them to aliases for
     the more generic --method options.
     This MUST occur only after the sanity checks so as to prevent the
     user from setting both post and body options simultaneously.
  */
  if (opt.post_data || opt.post_file_name)
    {
        setoptval ("method", "POST", "method");
        if (opt.post_data)
          {
            setoptval ("bodydata", opt.post_data, "body-data");
            xfree(opt.post_data);
          }
        else
          {
            setoptval ("bodyfile", opt.post_file_name, "body-file");
            xfree(opt.post_file_name);
          }
    }

#ifdef ENABLE_IRI
  if (opt.enable_iri)
    {
      if (opt.locale && !check_encoding_name (opt.locale))
        xfree (opt.locale);

      if (!opt.locale)
        opt.locale = find_locale ();

      if (opt.encoding_remote && !check_encoding_name (opt.encoding_remote))
        xfree (opt.encoding_remote);
    }
#else
  memset (&dummy_iri, 0, sizeof (dummy_iri));
  if (opt.enable_iri || opt.locale || opt.encoding_remote)
    {
      /* sXXXav : be more specific... */
      fprintf (stderr, _("This version does not have support for IRIs\n"));
      exit (WGET_EXIT_GENERIC_ERROR);
    }
#endif

  if (opt.ask_passwd)
    {
      opt.passwd = prompt_for_password ();

      if (opt.passwd == NULL || opt.passwd[0] == '\0')
        exit (WGET_EXIT_GENERIC_ERROR);
    }

  if (opt.use_askpass)
  {
    if (opt.use_askpass[0] == '\0')
      {
        fprintf (stderr,
                 _("use-askpass requires a string or either environment variable WGET_ASKPASS or SSH_ASKPASS to be set.\n"));
        exit (WGET_EXIT_GENERIC_ERROR);
      }
  }

#ifdef USE_WATT32
  if (opt.wdebug)
     dbug_init();
  sock_init();
#elif ! defined TESTING
  if (opt.background)
    {
      bool logfile_changed = fork_to_background ();

      if (logfile_changed)
        log_init (opt.lfilename, append_to_log);
    }
#endif

  /* Initialize progress.  Have to do this after the options are
     processed so we know where the log file is.  */
  if (opt.show_progress)
    set_progress_implementation (opt.progress_type);

  /* Fill in the arguments.  */
  url = alloca_array (char *, nurl + 1);
  if (url == NULL)
    {
      fprintf (stderr, _("Memory allocation problem\n"));
      exit (WGET_EXIT_PARSE_ERROR);
    }
  for (i = 0; i < nurl; i++, optind++)
    {
      char *rewritten = rewrite_shorthand_url (argv[optind]);
      if (rewritten)
        url[i] = rewritten;
      else
        url[i] = xstrdup (argv[optind]);
    }
  url[i] = NULL;

  /* Open WARC file. */
  if (opt.warc_filename != 0)
    warc_init ();

  DEBUGP (("DEBUG output created by Wget %s on %s.\n\n",
           version_string, OS_TYPE));

  /* Open the output filename if necessary.  */

/* 2005-04-17 SMS.
   Note that having the output_stream ("-O") file opened here for an FTP
   URL rather than in getftp() (ftp.c) (and the http equivalent) rather
   limits the ability in VMS to open the file differently for ASCII
   versus binary FTP there.  (Of course, doing it here allows a open
   failure to be detected immediately, without first connecting to the
   server.)
*/
  if (opt.output_document)
    {
      if (HYPHENP (opt.output_document))
        {
#ifdef WINDOWS
          _setmode (_fileno (stdout), _O_BINARY);
#endif
          output_stream = stdout;
        }
      else
        {
          struct stat st;

#ifdef __VMS
/* Common fopen() optional arguments:
   sequential access only, access callback function.
*/
# define FOPEN_OPT_ARGS , "fop=sqo", "acc", acc_cb, &open_id
          int open_id = 7;
#else /* def __VMS */
# define FOPEN_OPT_ARGS
#endif /* def __VMS [else] */

          output_stream = fopen (opt.output_document,
                                 opt.always_rest ? "ab" : "wb"
                                 FOPEN_OPT_ARGS);
          if (output_stream == NULL)
            {
              perror (opt.output_document);
              exit (WGET_EXIT_GENERIC_ERROR);
            }
          if (fstat (fileno (output_stream), &st) == 0 && S_ISREG (st.st_mode))
            output_stream_regular = true;
        }
      if (!output_stream_regular && (opt.convert_links || opt.recursive))
        {
          fprintf (stderr, _("-k or -r can be used together with -O only if \
outputting to a regular file.\n"));
          exit (WGET_EXIT_GENERIC_ERROR);
        }
      if (!output_stream_regular && (opt.convert_links || opt.convert_file_only))
        {
          fprintf (stderr, _("--convert-links or --convert-file-only can be used together \
only if outputting to a regular file.\n"));
          exit (WGET_EXIT_GENERIC_ERROR);
        }
    }

#ifdef HAVE_LIBCARES
  if (opt.bind_dns_address || opt.dns_servers)
    {
      if (ares_library_init (ARES_LIB_INIT_ALL))
        {
          fprintf (stderr, _("Failed to init libcares\n"));
          exit (WGET_EXIT_GENERIC_ERROR);
        }

      if (ares_init (&ares) != ARES_SUCCESS)
        {
          fprintf (stderr, _("Failed to init c-ares channel\n"));
          exit (WGET_EXIT_GENERIC_ERROR);
        }

      if (opt.bind_dns_address)
        {
          struct in_addr a4;
#ifdef ENABLE_IPV6
          struct in6_addr a6;
#endif

          if (inet_pton (AF_INET, opt.bind_dns_address, &a4) == 1)
            {
              ares_set_local_ip4 (ares, ntohl (a4.s_addr));
            }
#ifdef ENABLE_IPV6
          else if (inet_pton (AF_INET6, opt.bind_dns_address, &a6) == 1)
            {
              ares_set_local_ip6 (ares, (unsigned char *) &a6);
            }
#endif
          else
            {
              fprintf (stderr, _("Failed to parse IP address '%s'\n"), opt.bind_dns_address);
              exit (WGET_EXIT_GENERIC_ERROR);
            }
        }

      if (opt.dns_servers)
        {
          int result;

          if ((result = ares_set_servers_csv (ares, opt.dns_servers)) != ARES_SUCCESS)
            {
              fprintf (stderr, _("Failed to set DNS server(s) '%s' (%d)\n"), opt.dns_servers, result);
              exit (WGET_EXIT_GENERIC_ERROR);
            }
        }
    }
#endif

#ifdef __VMS
  /* Set global ODS5 flag according to the specified destination (if
     any), otherwise according to the current default device.
  */
  if (output_stream == NULL)
    set_ods5_dest( "SYS$DISK");
  else if (output_stream != stdout)
    set_ods5_dest( opt.output_document);
#endif /* def __VMS */

#ifdef WINDOWS
  ws_startup ();
#endif

#ifdef SIGHUP
  /* Setup the signal handler to redirect output when hangup is
     received.  */
  if (signal(SIGHUP, SIG_IGN) != SIG_IGN)
    signal(SIGHUP, redirect_output_signal);
#endif
  /* ...and do the same for SIGUSR1.  */
#ifdef SIGUSR1
  signal (SIGUSR1, redirect_output_signal);
#endif
#ifdef SIGPIPE
  /* Writing to a closed socket normally signals SIGPIPE, and the
     process exits.  What we want is to ignore SIGPIPE and just check
     for the return value of write().  */
  signal (SIGPIPE, SIG_IGN);
#endif
#ifdef SIGWINCH
  signal (SIGWINCH, progress_handle_sigwinch);
#endif

#ifdef HAVE_HSTS
  /* Load the HSTS database.
     Maybe all the URLs are FTP(S), in which case HSTS would not be needed,
     but this is the best place to do it, and it shouldn't be a critical
     performance hit.
   */
  if (opt.hsts)
    load_hsts ();
#endif

  /* Retrieve the URLs from argument list.  */
  for (t = url; *t; t++)
    {
      char *filename = NULL, *redirected_URL = NULL;
      int dt, url_err;
      /* Need to do a new struct iri every time, because
       * retrieve_url may modify it in some circumstances,
       * currently. */
      struct iri *iri = iri_new ();
      struct url *url_parsed;

      set_uri_encoding (iri, opt.locale, true);
      url_parsed = url_parse (*t, &url_err, iri, true);

      if (!url_parsed)
        {
          char *error = url_error (*t, url_err);
          logprintf (LOG_NOTQUIET, "%s: %s.\n",*t, error);
          xfree (error);
          inform_exit_status (URLERROR);
        }
      else
        {
          /* Request credentials if use_askpass is set. */
          if (opt.use_askpass)
            use_askpass (url_parsed);

          if ((opt.recursive || opt.page_requisites)
              && ((url_scheme (*t) != SCHEME_FTP
#ifdef HAVE_SSL
              && url_scheme (*t) != SCHEME_FTPS
#endif
              )
                  || url_uses_proxy (url_parsed)))
            {
              int old_follow_ftp = opt.follow_ftp;

              /* Turn opt.follow_ftp on in case of recursive FTP retrieval */
              if (url_scheme (*t) == SCHEME_FTP
#ifdef HAVE_SSL
                  || url_scheme (*t) == SCHEME_FTPS
#endif
                  )
                opt.follow_ftp = 1;

              retrieve_tree (url_parsed, NULL);

              opt.follow_ftp = old_follow_ftp;
            }
          else
            {
              retrieve_url (url_parsed, *t, &filename, &redirected_URL, NULL,
                            &dt, opt.recursive, iri, true);
            }

          if (opt.delete_after && filename != NULL && file_exists_p (filename, NULL))
            {
              DEBUGP (("Removing file due to --delete-after in main():\n"));
              logprintf (LOG_VERBOSE, _("Removing %s.\n"), filename);
              if (unlink (filename))
                logprintf (LOG_NOTQUIET, "unlink: %s\n", strerror (errno));
            }
          xfree (redirected_URL);
          xfree (filename);
          url_free (url_parsed);
        }
      iri_free (iri);
    }

  /* And then from the input file, if any.  */
  if (opt.input_filename)
    {
      int count;
      int status;
      status = retrieve_from_file (opt.input_filename, opt.force_html, &count);
      inform_exit_status (status);
      if (!count)
        logprintf (LOG_NOTQUIET, _("No URLs found in %s.\n"),
                   opt.input_filename);
    }

#ifdef HAVE_METALINK
  /* Finally, from metlink file, if any.  */
  if (opt.input_metalink)
    {
      metalink_error_t meta_err;
      uerr_t retr_err;
      metalink_t *metalink;

      meta_err = metalink_parse_file (opt.input_metalink, &metalink);

      if (meta_err)
        {
          logprintf (LOG_NOTQUIET, _("Unable to parse metalink file %s.\n"),
                     opt.input_metalink);
          retr_err = METALINK_PARSE_ERROR;
        }
      else
        {
          /* We need to sort the resources if preferred location
             was specified by the user.  */
          if (opt.preferred_location && opt.preferred_location[0])
            {
              metalink_file_t **mfile_ptr;
              for (mfile_ptr = metalink->files; *mfile_ptr; mfile_ptr++)
                {
                  metalink_resource_t **mres_ptr;
                  metalink_file_t *mfile = *mfile_ptr;
                  size_t mres_count = 0;

                  for (mres_ptr = mfile->resources; *mres_ptr; mres_ptr++)
                    mres_count++;

                  stable_sort (mfile->resources,
                               mres_count,
                               sizeof (metalink_resource_t *),
                               metalink_res_cmp);
                }
            }
          retr_err = retrieve_from_metalink (metalink);
          if (retr_err != RETROK)
            {
              logprintf (LOG_NOTQUIET,
                         _("Could not download all resources from %s.\n"),
                         quote (opt.input_metalink));
            }
          metalink_delete (metalink);
        }
      inform_exit_status (retr_err);
    }
#endif /* HAVE_METALINK */

  /* Print broken links. */
  if (opt.recursive && opt.spider)
    print_broken_links ();

  /* Print the downloaded sum.  */
  if ((opt.recursive || opt.page_requisites
       || nurl > 1
       || (opt.input_filename && total_downloaded_bytes != 0))
      &&
      total_downloaded_bytes != 0)
    {
      double end_time = ptimer_measure (timer);
      char *wall_time = xstrdup (secs_to_human_time (end_time - start_time));
      char *download_time = xstrdup (secs_to_human_time (total_download_time));

      ptimer_destroy (timer); timer = NULL;

      logprintf (LOG_NOTQUIET,
                 _("FINISHED --%s--\nTotal wall clock time: %s\n"
                   "Downloaded: %d files, %s in %s (%s)\n"),
                 datetime_str (time (NULL)),
                 wall_time,
                 numurls,
                 human_readable (total_downloaded_bytes, 10, 1),
                 download_time,
                 retr_rate (total_downloaded_bytes, total_download_time));
      xfree (wall_time);
      xfree (download_time);

      /* Print quota warning, if exceeded.  */
      if (opt.quota && total_downloaded_bytes > opt.quota)
        logprintf (LOG_NOTQUIET,
                   _("Download quota of %s EXCEEDED!\n"),
                   human_readable (opt.quota, 10, 1));
    }

  if (opt.cookies_output)
    save_cookies ();

#ifdef HAVE_HSTS
  if (opt.hsts && hsts_store)
    save_hsts ();
#endif

  if ((opt.convert_links || opt.convert_file_only) && !opt.delete_after)
    convert_all_links ();

  cleanup ();

  exit (get_exit_status ());
}