]> sjero.net Git - wget/blobdiff - src/init.c
[svn] Use new macros xnew, xnew0, xnew_array, and xnew0_array in various places.
[wget] / src / init.c
index 62488fd20a4f03f190f1ce1eea65f89c678d3f2e..688b3cf00aeef4556125ac1d55bf4cb133490456 100644 (file)
@@ -1,5 +1,5 @@
 /* Reading/parsing the initialization file.
 /* Reading/parsing the initialization file.
-   Copyright (C) 1995, 1996, 1997, 1998, 2000, 2001
+   Copyright (C) 1995, 1996, 1997, 1998, 2000, 2001, 2003
    Free Software Foundation, Inc.
 
 This file is part of GNU Wget.
    Free Software Foundation, Inc.
 
 This file is part of GNU Wget.
@@ -62,7 +62,6 @@ so, delete this exception statement from your version.  */
 #include "utils.h"
 #include "init.h"
 #include "host.h"
 #include "utils.h"
 #include "init.h"
 #include "host.h"
-#include "recur.h"
 #include "netrc.h"
 #include "cookies.h"           /* for cookie_jar_delete */
 #include "progress.h"
 #include "netrc.h"
 #include "cookies.h"           /* for cookie_jar_delete */
 #include "progress.h"
@@ -85,6 +84,7 @@ static int enable_tilde_expansion;
 
 CMD_DECLARE (cmd_boolean);
 CMD_DECLARE (cmd_bytes);
 
 CMD_DECLARE (cmd_boolean);
 CMD_DECLARE (cmd_bytes);
+CMD_DECLARE (cmd_bytes_large);
 CMD_DECLARE (cmd_directory_vector);
 CMD_DECLARE (cmd_lockable_boolean);
 CMD_DECLARE (cmd_number);
 CMD_DECLARE (cmd_directory_vector);
 CMD_DECLARE (cmd_lockable_boolean);
 CMD_DECLARE (cmd_number);
@@ -102,6 +102,7 @@ CMD_DECLARE (cmd_spec_mirror);
 CMD_DECLARE (cmd_spec_progress);
 CMD_DECLARE (cmd_spec_recursive);
 CMD_DECLARE (cmd_spec_restrict_file_names);
 CMD_DECLARE (cmd_spec_progress);
 CMD_DECLARE (cmd_spec_recursive);
 CMD_DECLARE (cmd_spec_restrict_file_names);
+CMD_DECLARE (cmd_spec_timeout);
 CMD_DECLARE (cmd_spec_useragent);
 
 /* List of recognized commands, each consisting of name, closure and function.
 CMD_DECLARE (cmd_spec_useragent);
 
 /* List of recognized commands, each consisting of name, closure and function.
@@ -123,17 +124,19 @@ static struct {
   { "base",            &opt.base_href,         cmd_string },
   { "bindaddress",     &opt.bind_address,      cmd_string },
   { "cache",           &opt.allow_cache,       cmd_boolean },
   { "base",            &opt.base_href,         cmd_string },
   { "bindaddress",     &opt.bind_address,      cmd_string },
   { "cache",           &opt.allow_cache,       cmd_boolean },
+  { "connecttimeout",  &opt.connect_timeout,   cmd_time },
   { "continue",                &opt.always_rest,       cmd_boolean },
   { "convertlinks",    &opt.convert_links,     cmd_boolean },
   { "cookies",         &opt.cookies,           cmd_boolean },
   { "cutdirs",         &opt.cut_dirs,          cmd_number },
   { "continue",                &opt.always_rest,       cmd_boolean },
   { "convertlinks",    &opt.convert_links,     cmd_boolean },
   { "cookies",         &opt.cookies,           cmd_boolean },
   { "cutdirs",         &opt.cut_dirs,          cmd_number },
-#ifdef DEBUG
+#ifdef ENABLE_DEBUG
   { "debug",           &opt.debug,             cmd_boolean },
 #endif
   { "deleteafter",     &opt.delete_after,      cmd_boolean },
   { "dirprefix",       &opt.dir_prefix,        cmd_directory },
   { "dirstruct",       NULL,                   cmd_spec_dirstruct },
   { "dnscache",                &opt.dns_cache,         cmd_boolean },
   { "debug",           &opt.debug,             cmd_boolean },
 #endif
   { "deleteafter",     &opt.delete_after,      cmd_boolean },
   { "dirprefix",       &opt.dir_prefix,        cmd_directory },
   { "dirstruct",       NULL,                   cmd_spec_dirstruct },
   { "dnscache",                &opt.dns_cache,         cmd_boolean },
+  { "dnstimeout",      &opt.dns_timeout,       cmd_time },
   { "domains",         &opt.domains,           cmd_vector },
   { "dotbytes",                &opt.dot_bytes,         cmd_bytes },
   { "dotsinline",      &opt.dots_in_line,      cmd_number },
   { "domains",         &opt.domains,           cmd_vector },
   { "dotbytes",                &opt.dot_bytes,         cmd_bytes },
   { "dotsinline",      &opt.dots_in_line,      cmd_number },
@@ -182,8 +185,9 @@ static struct {
   { "proxypasswd",     &opt.proxy_passwd,      cmd_string },
   { "proxyuser",       &opt.proxy_user,        cmd_string },
   { "quiet",           &opt.quiet,             cmd_boolean },
   { "proxypasswd",     &opt.proxy_passwd,      cmd_string },
   { "proxyuser",       &opt.proxy_user,        cmd_string },
   { "quiet",           &opt.quiet,             cmd_boolean },
-  { "quota",           &opt.quota,             cmd_bytes },
+  { "quota",           &opt.quota,             cmd_bytes_large },
   { "randomwait",      &opt.random_wait,       cmd_boolean },
   { "randomwait",      &opt.random_wait,       cmd_boolean },
+  { "readtimeout",     &opt.read_timeout,      cmd_time },
   { "reclevel",                &opt.reclevel,          cmd_number_inf },
   { "recursive",       NULL,                   cmd_spec_recursive },
   { "referer",         &opt.referer,           cmd_string },
   { "reclevel",                &opt.reclevel,          cmd_number_inf },
   { "recursive",       NULL,                   cmd_spec_recursive },
   { "referer",         &opt.referer,           cmd_string },
@@ -209,7 +213,7 @@ static struct {
   { "sslprotocol",     &opt.sslprotocol,       cmd_number },
 #endif /* HAVE_SSL */
   { "strictcomments",  &opt.strict_comments,   cmd_boolean },
   { "sslprotocol",     &opt.sslprotocol,       cmd_number },
 #endif /* HAVE_SSL */
   { "strictcomments",  &opt.strict_comments,   cmd_boolean },
-  { "timeout",         &opt.timeout,           cmd_time },
+  { "timeout",         NULL,                   cmd_spec_timeout },
   { "timestamping",    &opt.timestamping,      cmd_boolean },
   { "tries",           &opt.ntry,              cmd_number_inf },
   { "useproxy",                &opt.use_proxy,         cmd_boolean },
   { "timestamping",    &opt.timestamping,      cmd_boolean },
   { "tries",           &opt.ntry,              cmd_number_inf },
   { "useproxy",                &opt.use_proxy,         cmd_boolean },
@@ -252,10 +256,9 @@ defaults (void)
      NULL this way is technically illegal, but porting Wget to a
      machine where NULL is not all-zero bit pattern will be the least
      of the implementors' worries.  */
      NULL this way is technically illegal, but porting Wget to a
      machine where NULL is not all-zero bit pattern will be the least
      of the implementors' worries.  */
-  memset (&opt, 0, sizeof (opt));
+  xzero (opt);
 
   opt.cookies = 1;
 
   opt.cookies = 1;
-
   opt.verbose = -1;
   opt.ntry = 20;
   opt.reclevel = 5;
   opt.verbose = -1;
   opt.ntry = 20;
   opt.reclevel = 5;
@@ -272,9 +275,7 @@ defaults (void)
     opt.no_proxy = sepstring (tmp);
   opt.allow_cache = 1;
 
     opt.no_proxy = sepstring (tmp);
   opt.allow_cache = 1;
 
-#ifdef HAVE_SELECT
-  opt.timeout = 900;
-#endif
+  opt.read_timeout = 900;
   opt.use_robots = 1;
 
   opt.remove_listing = 1;
   opt.use_robots = 1;
 
   opt.remove_listing = 1;
@@ -337,7 +338,8 @@ wgetrc_file_name (void)
     {
       if (!file_exists_p (env))
        {
     {
       if (!file_exists_p (env))
        {
-         fprintf (stderr, "%s: %s: %s.\n", exec_name, env, strerror (errno));
+         fprintf (stderr, _("%s: WGETRC points to %s, which doesn't exist.\n"),
+                  exec_name, env);
          exit (1);
        }
       return xstrdup (env);
          exit (1);
        }
       return xstrdup (env);
@@ -476,14 +478,18 @@ dehyphen (char *s)
 }
 
 /* Parse the line pointed by line, with the syntax:
 }
 
 /* Parse the line pointed by line, with the syntax:
-   <sp>* command <sp>* = <sp>* value <newline>
+   <sp>* command <sp>* = <sp>* value <sp>*
    Uses malloc to allocate space for command and value.
    If the line is invalid, data is freed and 0 is returned.
 
    Return values:
     1 - success
    Uses malloc to allocate space for command and value.
    If the line is invalid, data is freed and 0 is returned.
 
    Return values:
     1 - success
-    0 - failure
-   -1 - empty */
+    0 - error
+   -1 - empty
+
+   In case of success, *COM and *VAL point to freshly allocated
+   strings, and *COMIND points to com's index.  In case of error or
+   empty line, those values are unaffected.  */
 
 static int
 parse_line (const char *line, char **com, char **val, int *comind)
 
 static int
 parse_line (const char *line, char **com, char **val, int *comind)
@@ -553,20 +559,24 @@ setval_internal (int comind, const char *com, const char *val)
 /* Run command COM with value VAL.  If running the command produces an
    error, report the error and exit.
 
 /* Run command COM with value VAL.  If running the command produces an
    error, report the error and exit.
 
-   This is intended to be called from main() with commands not
-   provided by the user, therefore it aborts when an unknown command
-   is encountered.  Once the COMIND's are exported to init.h, this
-   function will be changed to accept COMIND directly.  */
+   This is intended to be called from main() to modify Wget's behavior
+   through command-line switches.  Since COM is hard-coded in main(),
+   it is not canonicalized, and this aborts when COM is not found.
+
+   If COMIND's are exported to init.h, this function will be changed
+   to accept COMIND directly.  */
 
 void
 setoptval (const char *com, const char *val)
 {
 
 void
 setoptval (const char *com, const char *val)
 {
-  int comind = findcmd (com);
-  assert (comind != -1);
-  if (!setval_internal (comind, com, val))
+  if (!setval_internal (findcmd (com), com, val))
     exit (2);
 }
 
     exit (2);
 }
 
+/* Parse OPT into command and value and run it.  For example,
+   run_command("foo=bar") is equivalent to setoptval("foo", "bar").
+   This is used by the `--execute' flag in main.c.  */
+
 void
 run_command (const char *opt)
 {
 void
 run_command (const char *opt)
 {
@@ -582,14 +592,25 @@ run_command (const char *opt)
     }
   else if (status == 0)
     {
     }
   else if (status == 0)
     {
-      fprintf (stderr, "Invalid command `%s'\n", opt);
+      fprintf (stderr, _("%s: Invalid --execute command `%s'\n"),
+              exec_name, opt);
       exit (2);
     }
 }
 \f
 /* Generic helper functions, for use with `commands'. */
 
       exit (2);
     }
 }
 \f
 /* Generic helper functions, for use with `commands'. */
 
-static int myatoi PARAMS ((const char *s));
+#define CMP1(p, c0) (TOLOWER((p)[0]) == (c0) && (p)[1] == '\0')
+
+#define CMP2(p, c0, c1) (TOLOWER((p)[0]) == (c0)       \
+                        && TOLOWER((p)[1]) == (c1)     \
+                        && (p)[2] == '\0')
+
+#define CMP3(p, c0, c1, c2) (TOLOWER((p)[0]) == (c0)   \
+                    && TOLOWER((p)[1]) == (c1)         \
+                    && TOLOWER((p)[2]) == (c2)         \
+                    && (p)[3] == '\0')
+
 
 /* Store the boolean value from VAL to CLOSURE.  COM is ignored,
    except for error messages.  */
 
 /* Store the boolean value from VAL to CLOSURE.  COM is ignored,
    except for error messages.  */
@@ -597,27 +618,18 @@ static int
 cmd_boolean (const char *com, const char *val, void *closure)
 {
   int bool_value;
 cmd_boolean (const char *com, const char *val, void *closure)
 {
   int bool_value;
-  const char *v = val;
-#define LC(x) TOLOWER(x)
-
-  if ((LC(v[0]) == 'o' && LC(v[1]) == 'n' && !v[2])
-      ||
-      (LC(v[0]) == 'y' && LC(v[1]) == 'e' && LC(v[2]) == 's' && !v[3])
-      ||
-      (v[0] == '1' && !v[1]))
+
+  if (CMP2 (val, 'o', 'n') || CMP3 (val, 'y', 'e', 's') || CMP1 (val, '1'))
     /* "on", "yes" and "1" mean true. */
     bool_value = 1;
     /* "on", "yes" and "1" mean true. */
     bool_value = 1;
-  else if ((LC(v[0]) == 'o' && LC(v[1]) == 'f' && LC(v[2]) == 'f' && !v[3])
-          ||
-          (LC(v[0]) == 'n' && LC(v[1]) == 'o' && !v[2])
-          ||
-          (v[0] == '0' && !v[1]))
+  else if (CMP3 (val, 'o', 'f', 'f') || CMP2 (val, 'n', 'o') || CMP1 (val, '0'))
     /* "off", "no" and "0" mean false. */
     bool_value = 0;
   else
     {
     /* "off", "no" and "0" mean false. */
     bool_value = 0;
   else
     {
-      fprintf (stderr, _("%s: %s: Please specify on or off.\n"),
-              exec_name, com);
+      fprintf (stderr,
+              _("%s: %s: Invalid boolean `%s', use `on' or `off'.\n"),
+              exec_name, com, val);
       return 0;
     }
 
       return 0;
     }
 
@@ -625,10 +637,10 @@ cmd_boolean (const char *com, const char *val, void *closure)
   return 1;
 }
 
   return 1;
 }
 
-/* Store the lockable_boolean {2, 1, 0, -1} value from VAL to CLOSURE.  COM is
-   ignored, except for error messages.  Values 2 and -1 indicate that once
-   defined, the value may not be changed by successive wgetrc files or
-   command-line arguments.
+/* Store the lockable_boolean {2, 1, 0, -1} value from VAL to CLOSURE.
+   COM is ignored, except for error messages.  Values 2 and -1
+   indicate that once defined, the value may not be changed by
+   successive wgetrc files or command-line arguments.
 
    Values: 2 - Enable a particular option for good ("always")
            1 - Enable an option ("on")
 
    Values: 2 - Enable a particular option for good ("always")
            1 - Enable an option ("on")
@@ -639,30 +651,28 @@ cmd_lockable_boolean (const char *com, const char *val, void *closure)
 {
   int lockable_boolean_value;
 
 {
   int lockable_boolean_value;
 
+  int oldval = *(int *)closure;
+
   /*
    * If a config file said "always" or "never", don't allow command line
    * arguments to override the config file.
    */
   /*
    * If a config file said "always" or "never", don't allow command line
    * arguments to override the config file.
    */
-  if (*(int *)closure == -1 || *(int *)closure == 2)
+  if (oldval == -1 || oldval == 2)
     return 1;
 
     return 1;
 
-  if (!strcasecmp (val, "always") || !strcmp (val, "2"))
+  if (0 == strcasecmp (val, "always") || CMP1 (val, '2'))
     lockable_boolean_value = 2;
     lockable_boolean_value = 2;
-  else if (!strcasecmp (val, "on")
-          || !strcasecmp (val, "yes")
-          || !strcmp (val, "1"))
+  else if (CMP2 (val, 'o', 'n') || CMP3 (val, 'y', 'e', 's') || CMP1 (val, '1'))
     lockable_boolean_value = 1;
     lockable_boolean_value = 1;
-  else if (!strcasecmp (val, "off")
-          || !strcasecmp (val, "no")
-          || !strcmp (val, "0"))
+  else if (CMP3 (val, 'o', 'f', 'f') || CMP2 (val, 'n', 'o') || CMP1 (val, '0'))
     lockable_boolean_value = 0;
     lockable_boolean_value = 0;
-  else if (!strcasecmp (val, "never") || !strcmp (val, "-1"))
+  else if (0 == strcasecmp (val, "never") || CMP2 (val, '-', '1'))
     lockable_boolean_value = -1;
   else
     {
     lockable_boolean_value = -1;
   else
     {
-      fprintf (stderr, _("%s: %s: Please specify always, on, off, "
-                        "or never.\n"),
-              exec_name, com);
+      fprintf (stderr,
+              _("%s: %s: Invalid boolean `%s', use always, on, off, or never.\n"),
+              exec_name, com, val);
       return 0;
     }
 
       return 0;
     }
 
@@ -670,20 +680,19 @@ cmd_lockable_boolean (const char *com, const char *val, void *closure)
   return 1;
 }
 
   return 1;
 }
 
+static int simple_atoi PARAMS ((const char *, const char *, int *));
+
 /* Set the non-negative integer value from VAL to CLOSURE.  With
    incorrect specification, the number remains unchanged.  */
 static int
 cmd_number (const char *com, const char *val, void *closure)
 {
 /* Set the non-negative integer value from VAL to CLOSURE.  With
    incorrect specification, the number remains unchanged.  */
 static int
 cmd_number (const char *com, const char *val, void *closure)
 {
-  int num = myatoi (val);
-
-  if (num == -1)
+  if (!simple_atoi (val, val + strlen (val), closure))
     {
     {
-      fprintf (stderr, _("%s: %s: Invalid specification `%s'.\n"),
+      fprintf (stderr, _("%s: %s: Invalid number `%s'.\n"),
               exec_name, com, val);
       return 0;
     }
               exec_name, com, val);
       return 0;
     }
-  *(int *)closure = num;
   return 1;
 }
 
   return 1;
 }
 
@@ -790,9 +799,10 @@ cmd_directory (const char *com, const char *val, void *closure)
   return 1;
 }
 
   return 1;
 }
 
-/* Merge the vector (array of strings separated with `,') in COM with
-   the vector (NULL-terminated array of strings) pointed to by
-   CLOSURE.  */
+/* Split VAL by space to a vector of values, and append those values
+   to vector pointed to by the CLOSURE argument.  If VAL is empty, the
+   CLOSURE vector is cleared instead.  */
+
 static int
 cmd_vector (const char *com, const char *val, void *closure)
 {
 static int
 cmd_vector (const char *com, const char *val, void *closure)
 {
@@ -839,100 +849,47 @@ cmd_directory_vector (const char *com, const char *val, void *closure)
   return 1;
 }
 
   return 1;
 }
 
-/* Poor man's atof: handles only <digits>.<digits>.  Returns 1 on
-   success, 0 on failure.  In case of success, stores its result to
-   *DEST.  */
+static int simple_atof PARAMS ((const char *, const char *, double *));
 
 
-static int
-simple_atof (const char *beg, const char *end, double *dest)
-{
-  double result = 0;
-
-  int seen_dot = 0;
-  int seen_digit = 0;
-  double divider = 1;
-
-  const char *p = beg;
-
-  while (p < end)
-    {
-      char ch = *p++;
-      if (ISDIGIT (ch))
-       {
-         if (!seen_dot)
-           result = (10 * result) + (ch - '0');
-         else
-           result += (ch - '0') / (divider *= 10);
-         seen_digit = 1;
-       }
-      else if (ch == '.')
-       {
-         if (!seen_dot)
-           seen_dot = 1;
-         else
-           return 0;
-       }
-    }
-  if (!seen_digit)
-    return 0;
-
-  *dest = result;
-  return 1;
-}
-
-/* Parse VAL as a number and set its value to CLOSURE (which should
-   point to a long int).
-
-   By default, the value is assumed to be in bytes.  If "K", "M", or
-   "G" are appended, the value is multiplied with 1<<10, 1<<20, or
-   1<<30, respectively.  Floating point values are allowed and are
-   cast to integer before use.  The idea is to be able to use things
-   like 1.5k instead of "1536".
-
-   The string "inf" is returned as 0.
-
-   In case of error, 0 is returned and memory pointed to by CLOSURE
-   remains unmodified.  */
+/* Enginge for cmd_bytes and cmd_bytes_large: converts a string such
+   as "100k" or "2.5G" to a floating point number.  */
 
 static int
 
 static int
-cmd_bytes (const char *com, const char *val, void *closure)
+parse_bytes_helper (const char *val, double *result)
 {
 {
-  long mult;
-  double number;
+  double number, mult;
   const char *end = val + strlen (val);
 
   /* Check for "inf".  */
   if (0 == strcmp (val, "inf"))
     {
   const char *end = val + strlen (val);
 
   /* Check for "inf".  */
   if (0 == strcmp (val, "inf"))
     {
-      *(long *)closure = 0;
+      *result = 0;
       return 1;
     }
 
   /* Strip trailing whitespace.  */
   while (val < end && ISSPACE (end[-1]))
     --end;
       return 1;
     }
 
   /* Strip trailing whitespace.  */
   while (val < end && ISSPACE (end[-1]))
     --end;
-
   if (val == end)
   if (val == end)
-    {
-    err:
-      fprintf (stderr, _("%s: Invalid byte value `%s'\n"), com, val);
-      return 0;
-    }
+    return 0;
 
   switch (TOLOWER (end[-1]))
     {
     case 'k':
 
   switch (TOLOWER (end[-1]))
     {
     case 'k':
-      --end, mult = 1L<<10;
+      --end, mult = 1024.0;
       break;
     case 'm':
       break;
     case 'm':
-      --end, mult = 1L<<20;
+      --end, mult = 1048576.0;
       break;
     case 'g':
       break;
     case 'g':
-      --end, mult = 1L<<30;
+      --end, mult = 1073741824.0;
+      break;
+    case 't':
+      --end, mult = 1099511627776.0;
       break;
     default:
       break;
     default:
-      /* Not a recognized suffix: assume it belongs to the number.
-        (If not, atof simple_atof will raise an error.)  */
+      /* Not a recognized suffix: assume it's a digit.  (If not,
+        simple_atof will raise an error.)  */
       mult = 1;
     }
 
       mult = 1;
     }
 
@@ -942,12 +899,59 @@ cmd_bytes (const char *com, const char *val, void *closure)
   while (val < end && ISSPACE (end[-1]))
     --end;
   if (val == end)
   while (val < end && ISSPACE (end[-1]))
     --end;
   if (val == end)
-    goto err;
+    return 0;
 
   if (!simple_atof (val, end, &number))
 
   if (!simple_atof (val, end, &number))
-    goto err;
+    return 0;
+
+  *result = number * mult;
+  return 1;
+}
+
+/* Parse VAL as a number and set its value to CLOSURE (which should
+   point to a long int).
+
+   By default, the value is assumed to be in bytes.  If "K", "M", or
+   "G" are appended, the value is multiplied with 1<<10, 1<<20, or
+   1<<30, respectively.  Floating point values are allowed and are
+   cast to integer before use.  The idea is to be able to use things
+   like 1.5k instead of "1536".
+
+   The string "inf" is returned as 0.
 
 
-  *(long *)closure = (long)(number * mult);
+   In case of error, 0 is returned and memory pointed to by CLOSURE
+   remains unmodified.  */
+
+static int
+cmd_bytes (const char *com, const char *val, void *closure)
+{
+  double byte_value;
+  if (!parse_bytes_helper (val, &byte_value))
+    {
+      fprintf (stderr, _("%s: %s: Invalid byte value `%s'\n"),
+              exec_name, com, val);
+      return 0;
+    }
+  *(long *)closure = (long)byte_value;
+  return 1;
+}
+
+/* Like cmd_bytes, but CLOSURE is interpreted as a pointer to
+   LARGE_INT.  It works by converting the string to double, therefore
+   working with values up to 2^53-1 without loss of precision.  This
+   value (8192 TB) is large enough to serve for a while.  */
+
+static int
+cmd_bytes_large (const char *com, const char *val, void *closure)
+{
+  double byte_value;
+  if (!parse_bytes_helper (val, &byte_value))
+    {
+      fprintf (stderr, _("%s: %s: Invalid byte value `%s'\n"),
+              exec_name, com, val);
+      return 0;
+    }
+  *(LARGE_INT *)closure = (LARGE_INT)byte_value;
   return 1;
 }
 
   return 1;
 }
 
@@ -968,7 +972,8 @@ cmd_time (const char *com, const char *val, void *closure)
   if (val == end)
     {
     err:
   if (val == end)
     {
     err:
-      fprintf (stderr, _("%s: Invalid time specification `%s'\n"), com, val);
+      fprintf (stderr, _("%s: %s: Invalid time period `%s'\n"),
+              exec_name, com, val);
       return 0;
     }
 
       return 0;
     }
 
@@ -1044,7 +1049,7 @@ cmd_spec_header (const char *com, const char *val, void *closure)
 
       if (!check_user_specified_header (val))
        {
 
       if (!check_user_specified_header (val))
        {
-         fprintf (stderr, _("%s: %s: Invalid specification `%s'.\n"),
+         fprintf (stderr, _("%s: %s: Invalid header `%s'.\n"),
                   exec_name, com, val);
          return 0;
        }
                   exec_name, com, val);
          return 0;
        }
@@ -1069,6 +1074,9 @@ cmd_spec_htmlify (const char *com, const char *val, void *closure)
   return flag;
 }
 
   return flag;
 }
 
+/* Set the "mirror" mode.  It means: recursive download, timestamping,
+   no limit on max. recursion depth, and don't remove listings.  */
+
 static int
 cmd_spec_mirror (const char *com, const char *val, void *closure)
 {
 static int
 cmd_spec_mirror (const char *com, const char *val, void *closure)
 {
@@ -1088,6 +1096,9 @@ cmd_spec_mirror (const char *com, const char *val, void *closure)
   return 1;
 }
 
   return 1;
 }
 
+/* Set progress.type to VAL, but verify that it's a valid progress
+   implementation before that.  */
+
 static int
 cmd_spec_progress (const char *com, const char *val, void *closure)
 {
 static int
 cmd_spec_progress (const char *com, const char *val, void *closure)
 {
@@ -1105,6 +1116,10 @@ cmd_spec_progress (const char *com, const char *val, void *closure)
   return 1;
 }
 
   return 1;
 }
 
+/* Set opt.recursive to VAL as with cmd_boolean.  If opt.recursive is
+   set to true, also set opt.dirstruct to 1, unless opt.no_dirstruct
+   is specified.  */
+
 static int
 cmd_spec_recursive (const char *com, const char *val, void *closure)
 {
 static int
 cmd_spec_recursive (const char *com, const char *val, void *closure)
 {
@@ -1139,7 +1154,8 @@ cmd_spec_restrict_file_names (const char *com, const char *val, void *closure)
   else
     {
     err:
   else
     {
     err:
-      fprintf (stderr, _("%s: %s: Invalid specification `%s'.\n"),
+      fprintf (stderr,
+              _("%s: %s: Invalid restriction `%s', use `unix' or `windows'.\n"),
               exec_name, com, val);
       return 0;
     }
               exec_name, com, val);
       return 0;
     }
@@ -1159,6 +1175,20 @@ cmd_spec_restrict_file_names (const char *com, const char *val, void *closure)
   return 1;
 }
 
   return 1;
 }
 
+/* Set all three timeout values. */
+
+static int
+cmd_spec_timeout (const char *com, const char *val, void *closure)
+{
+  double value;
+  if (!cmd_time (com, val, &value))
+    return 0;
+  opt.read_timeout = value;
+  opt.connect_timeout = value;
+  opt.dns_timeout = value;
+  return 1;
+}
+
 static int
 cmd_spec_useragent (const char *com, const char *val, void *closure)
 {
 static int
 cmd_spec_useragent (const char *com, const char *val, void *closure)
 {
@@ -1166,7 +1196,7 @@ cmd_spec_useragent (const char *com, const char *val, void *closure)
      junk to the server.  */
   if (!*val || strchr (val, '\n'))
     {
      junk to the server.  */
   if (!*val || strchr (val, '\n'))
     {
-      fprintf (stderr, _("%s: %s: Invalid specification `%s'.\n"),
+      fprintf (stderr, _("%s: %s: Invalid value `%s'.\n"),
               exec_name, com, val);
       return 0;
     }
               exec_name, com, val);
       return 0;
     }
@@ -1176,20 +1206,70 @@ cmd_spec_useragent (const char *com, const char *val, void *closure)
 \f
 /* Miscellaneous useful routines.  */
 
 \f
 /* Miscellaneous useful routines.  */
 
-/* Return the integer value of a positive integer written in S, or -1
-   if an error was encountered.  */
+/* A very simple atoi clone, more portable than strtol and friends,
+   but reports errors, unlike atoi.  Returns 1 on success, 0 on
+   failure.  In case of success, stores result to *DEST.  */
+
 static int
 static int
-myatoi (const char *s)
+simple_atoi (const char *beg, const char *end, int *dest)
 {
 {
-  int res;
-  const char *orig = s;
+  int result = 0;
+  const char *p;
 
 
-  for (res = 0; *s && ISDIGIT (*s); s++)
-    res = 10 * res + (*s - '0');
-  if (*s || orig == s)
-    return -1;
-  else
-    return res;
+  if (beg == end)
+    return 0;
+
+  for (p = beg; p < end && ISDIGIT (*p); p++)
+    result = (10 * result) + (*p - '0');
+
+  if (p != end)
+    return 0;
+
+  *dest = result;
+  return 1;
+}
+
+/* Trivial atof, with error reporting.  Handles "<digits>[.<digits>]",
+   doesn't handle exponential notation.  Returns 1 on success, 0 on
+   failure.  In case of success, stores its result to *DEST.  */
+
+static int
+simple_atof (const char *beg, const char *end, double *dest)
+{
+  double result = 0;
+
+  int seen_dot = 0;
+  int seen_digit = 0;
+  double divider = 1;
+
+  const char *p;
+
+  for (p = beg; p < end; p++)
+    {
+      char ch = *p;
+      if (ISDIGIT (ch))
+       {
+         if (!seen_dot)
+           result = (10 * result) + (ch - '0');
+         else
+           result += (ch - '0') / (divider *= 10);
+         seen_digit = 1;
+       }
+      else if (ch == '.')
+       {
+         if (!seen_dot)
+           seen_dot = 1;
+         else
+           return 0;
+       }
+      else
+       return 0;
+    }
+  if (!seen_digit)
+    return 0;
+
+  *dest = result;
+  return 1;
 }
 
 static int
 }
 
 static int
@@ -1232,7 +1312,7 @@ cleanup (void)
      memory which grows with the size of the program.  */
 
 #ifdef DEBUG_MALLOC
      memory which grows with the size of the program.  */
 
 #ifdef DEBUG_MALLOC
-  recursive_cleanup ();
+  convert_cleanup ();
   res_cleanup ();
   http_cleanup ();
   cleanup_html_url ();
   res_cleanup ();
   http_cleanup ();
   cleanup_html_url ();