]> sjero.net Git - wget/blobdiff - src/utils.c
[svn] Generalize connect_with_timeout into run_with_timeout.
[wget] / src / utils.c
index cf54f316ac86525a6ff3b104d00946a3521eb838..cd1e645d42e7968ec4eb5d8dfdf5d9400b818e5a 100644 (file)
@@ -59,6 +59,27 @@ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.  */
 # include <termios.h>
 #endif
 
+/* Needed for run_with_timeout. */
+#undef USE_SIGNAL_TIMEOUT
+#ifdef HAVE_SIGNAL_H
+# include <signal.h>
+#endif
+#ifdef HAVE_SETJMP_H
+# include <setjmp.h>
+#endif
+/* If sigsetjmp is a macro, configure won't pick it up. */
+#ifdef sigsetjmp
+# define HAVE_SIGSETJMP
+#endif
+#ifdef HAVE_SIGNAL
+# ifdef HAVE_SIGSETJMP
+#  define USE_SIGNAL_TIMEOUT
+# endif
+# ifdef HAVE_SIGBLOCK
+#  define USE_SIGNAL_TIMEOUT
+# endif
+#endif
+
 #include "wget.h"
 #include "utils.h"
 #include "fnmatch.h"
@@ -84,21 +105,9 @@ extern int errno;
 static void
 memfatal (const char *what)
 {
-  /* HACK: expose save_log_p from log.c, so we can turn it off in
-     order to prevent saving the log.  Saving the log is dangerous
-     because logprintf() and logputs() can call malloc(), so this
-     could infloop.  When logging is turned off, infloop can no longer
-     happen.
-
-     #### This is no longer really necessary because the new routines
-     in log.c cons only if the line exceeds eighty characters.  But
-     this can come at the end of a line, so it's OK to be careful.
-
-     On a more serious note, it would be good to have a
-     log_forced_shutdown() routine that exposes this cleanly.  */
-  extern int save_log_p;
-
-  save_log_p = 0;
+  /* Make sure we don't try to store part of the log line, and thus
+     call malloc.  */
+  log_set_save_context (0);
   logprintf (LOG_ALWAYS, _("%s: %s: Not enough memory.\n"), exec_name, what);
   exit (1);
 }
@@ -464,134 +473,19 @@ fork_to_background (void)
   else if (pid != 0)
     {
       /* parent, no error */
-      printf (_("Continuing in background.\n"));
+      printf (_("Continuing in background, pid %d.\n"), (int)pid);
       if (changedp)
        printf (_("Output will be written to `%s'.\n"), opt.lfilename);
-      exit (0);
+      exit (0);                        /* #### should we use _exit()? */
     }
-  /* child: keep running */
-}
-#endif /* not WINDOWS */
-\f
-/* Resolve "." and ".." elements of PATH by destructively modifying
-   PATH.  "." is resolved by removing that path element, and ".." is
-   resolved by removing the preceding path element.  Leading and
-   trailing slashes are preserved.
-
-   Return non-zero if any changes have been made.
-
-   For example, "a/b/c/./../d/.." will yield "a/b/".  More exhaustive
-   test examples are provided below.  If you change anything in this
-   function, run test_path_simplify to make sure you haven't broken a
-   test case.
 
-   A previous version of this function was based on path_simplify()
-   from GNU Bash, but it has been rewritten for Wget 1.8.1.  */
-
-int
-path_simplify (char *path)
-{
-  int change = 0;
-  char *p, *end;
-
-  if (path[0] == '/')
-    ++path;                    /* preserve the leading '/'. */
-
-  p = path;
-  end = p + strlen (p) + 1;    /* position past the terminating zero. */
-
-  while (1)
-    {
-    again:
-      /* P should point to the beginning of a path element. */
-
-      if (*p == '.' && (*(p + 1) == '/' || *(p + 1) == '\0'))
-       {
-         /* Handle "./foo" by moving "foo" two characters to the
-            left. */
-         if (*(p + 1) == '/')
-           {
-             change = 1;
-             memmove (p, p + 2, end - p);
-             end -= 2;
-             goto again;
-           }
-         else
-           {
-             change = 1;
-             *p = '\0';
-             break;
-           }
-       }
-      else if (*p == '.' && *(p + 1) == '.'
-              && (*(p + 2) == '/' || *(p + 2) == '\0'))
-       {
-         /* Handle "../foo" by moving "foo" one path element to the
-            left.  */
-         char *b = p;          /* not p-1 because P can equal PATH */
-
-         /* Backtrack by one path element, but not past the beginning
-            of PATH. */
-
-         /* foo/bar/../baz */
-         /*         ^ p    */
-         /*     ^ b        */
-
-         if (b > path)
-           {
-             /* Move backwards until B hits the beginning of the
-                previous path element or the beginning of path. */
-             for (--b; b > path && *(b - 1) != '/'; b--)
-               ;
-           }
-
-         change = 1;
-         if (*(p + 2) == '/')
-           {
-             memmove (b, p + 3, end - (p + 3));
-             end -= (p + 3) - b;
-             p = b;
-           }
-         else
-           {
-             *b = '\0';
-             break;
-           }
-
-         goto again;
-       }
-      else if (*p == '/')
-       {
-         /* Remove empty path elements.  Not mandated by rfc1808 et
-            al, but empty path elements are not all that useful, and
-            the rest of Wget might not deal with them well. */
-         char *q = p;
-         while (*q == '/')
-           ++q;
-         change = 1;
-         if (*q == '\0')
-           {
-             *p = '\0';
-             break;
-           }
-         memmove (p, q, end - q);
-         end -= q - p;
-         goto again;
-       }
-
-      /* Skip to the next path element. */
-      while (*p && *p != '/')
-       ++p;
-      if (*p == '\0')
-       break;
-
-      /* Make sure P points to the beginning of the next path element,
-        which is location after the slash. */
-      ++p;
-    }
-
-  return change;
+  /* child: give up the privileges and keep running. */
+  setsid ();
+  freopen ("/dev/null", "r", stdin);
+  freopen ("/dev/null", "w", stdout);
+  freopen ("/dev/null", "w", stderr);
 }
+#endif /* not WINDOWS */
 \f
 /* "Touch" FILE, i.e. make its atime and mtime equal to the time
    specified with TM.  */
@@ -706,6 +600,7 @@ make_directory (const char *directory)
 {
   int quit = 0;
   int i;
+  int ret = 0;
   char *dir;
 
   /* Make a copy of dir, to be able to write to it.  Otherwise, the
@@ -721,18 +616,19 @@ make_directory (const char *directory)
       if (!dir[i])
        quit = 1;
       dir[i] = '\0';
-      /* Check whether the directory already exists.  */
+      /* Check whether the directory already exists.  Allow creation of
+        of intermediate directories to fail, as the initial path components
+        are not necessarily directories!  */
       if (!file_exists_p (dir))
-       {
-         if (mkdir (dir, 0777) < 0)
-           return -1;
-       }
+       ret = mkdir (dir, 0777);
+      else
+       ret = 0;
       if (quit)
        break;
       else
        dir[i] = '/';
     }
-  return 0;
+  return ret;
 }
 
 /* Merge BASE with FILE.  BASE can be a directory or a file name, FILE
@@ -917,25 +813,48 @@ suffix (const char *str)
     return NULL;
 }
 
-/* Read a line from FP.  The function reallocs the storage as needed
-   to accomodate for any length of the line.  Reallocs are done
-   exponentially, doubling the storage after each overflow to minimize
-   the number of calls to realloc() and fgets().  The newline
-   character at the end of line is retained.
+/* Return non-zero if FNAME ends with a typical HTML suffix.  The
+   following (case-insensitive) suffixes are presumed to be HTML files:
+   
+     html
+     htm
+     ?html (`?' matches one character)
+
+   #### CAVEAT.  This is not necessarily a good indication that FNAME
+   refers to a file that contains HTML!  */
+int
+has_html_suffix_p (const char *fname)
+{
+  char *suf;
+
+  if ((suf = suffix (fname)) == NULL)
+    return 0;
+  if (!strcasecmp (suf, "html"))
+    return 1;
+  if (!strcasecmp (suf, "htm"))
+    return 1;
+  if (suf[0] && !strcasecmp (suf + 1, "html"))
+    return 1;
+  return 0;
+}
+
+/* Read a line from FP and return the pointer to freshly allocated
+   storage.  The stoarage space is obtained through malloc() and
+   should be freed with free() when it is no longer needed.
+
+   The length of the line is not limited, except by available memory.
+   The newline character at the end of line is retained.  The line is
+   terminated with a zero character.
 
    After end-of-file is encountered without anything being read, NULL
    is returned.  NULL is also returned on error.  To distinguish
-   between these two cases, use the stdio function ferror().
-
-   A future version of this function will be rewritten to use fread()
-   instead of fgets(), and to return the length of the line, which
-   will make the function usable on files with binary content.  */
+   between these two cases, use the stdio function ferror().  */
 
 char *
 read_whole_line (FILE *fp)
 {
   int length = 0;
-  int bufsize = 81;
+  int bufsize = 82;
   char *line = (char *)xmalloc (bufsize);
 
   while (fgets (line + length, bufsize - length, fp))
@@ -1783,6 +1702,49 @@ determine_screen_width (void)
 #endif /* TIOCGWINSZ */
 }
 
+/* Return a random number between 0 and MAX-1, inclusive.
+
+   If MAX is greater than the value of RAND_MAX+1 on the system, the
+   returned value will be in the range [0, RAND_MAX].  This may be
+   fixed in a future release.
+
+   The random number generator is seeded automatically the first time
+   it is called.
+
+   This uses rand() for portability.  It has been suggested that
+   random() offers better randomness, but this is not required for
+   Wget, so I chose to go for simplicity and use rand
+   unconditionally.  */
+
+int
+random_number (int max)
+{
+  static int seeded;
+  double bounded;
+  int rnd;
+
+  if (!seeded)
+    {
+      srand (time (NULL));
+      seeded = 1;
+    }
+  rnd = rand ();
+
+  /* On systems that don't define RAND_MAX, assume it to be 2**15 - 1,
+     and enforce that assumption by masking other bits.  */
+#ifndef RAND_MAX
+# define RAND_MAX 32767
+  rnd &= RAND_MAX;
+#endif
+
+  /* This is equivalent to rand() % max, but uses the high-order bits
+     for better randomness on architecture where rand() is implemented
+     using a simple congruential generator.  */
+
+  bounded = (double)max * rnd / (RAND_MAX + 1.0);
+  return (int)bounded;
+}
+
 #if 0
 /* A debugging function for checking whether an MD5 library works. */
 
@@ -1816,96 +1778,73 @@ debug_test_md5 (char *buf)
   return res;
 }
 #endif
+\f
+/* Implementation of run_with_timeout, a generic timeout handler for
+   systems with Unix-like signal handling.  */
+#ifdef HAVE_SIGSETJMP
+#define SETJMP(env) sigsetjmp (env, 1)
 
-#if 0
-/* Debugging and testing support for path_simplify. */
+static sigjmp_buf run_with_timeout_env;
 
-/* Debug: run path_simplify on PATH and return the result in a new
-   string.  Useful for calling from the debugger.  */
-static char *
-ps (char *path)
+static RETSIGTYPE
+abort_run_with_timeout (int sig)
 {
-  char *copy = xstrdup (path);
-  path_simplify (copy);
-  return copy;
+  assert (sig == SIGALRM);
+  siglongjmp (run_with_timeout_env, -1);
 }
+#else  /* not HAVE_SIGSETJMP */
+#define SETJMP(env) setjmp (env)
 
-static void
-run_test (char *test, char *expected_result, int expected_change)
+static jmp_buf run_with_timeout_env;
+
+static RETSIGTYPE
+abort_run_with_timeout (int sig)
 {
-  char *test_copy = xstrdup (test);
-  int modified = path_simplify (test_copy);
+  assert (sig == SIGALRM);
+  /* We don't have siglongjmp to preserve the set of blocked signals;
+     if we longjumped out of the handler at this point, SIGALRM would
+     remain blocked.  We must unblock it manually. */
+  int mask = siggetmask ();
+  mask &= ~sigmask(SIGALRM);
+  sigsetmask (mask);
 
-  if (0 != strcmp (test_copy, expected_result))
-    {
-      printf ("Failed path_simplify(\"%s\"): expected \"%s\", got \"%s\".\n",
-             test, expected_result, test_copy);
-    }
-  if (modified != expected_change)
-    {
-      if (expected_change == 1)
-       printf ("Expected no modification with path_simplify(\"%s\").\n",
-               test);
-      else
-       printf ("Expected modification with path_simplify(\"%s\").\n",
-               test);
-    }
-  xfree (test_copy);
+  /* Now it's safe to longjump. */
+  longjmp (run_with_timeout_env, -1);
 }
+#endif /* not HAVE_SIGSETJMP */
 
-static void
-test_path_simplify (void)
-{
-  static struct {
-    char *test, *result;
-    int should_modify;
-  } tests[] = {
-    { "",              "",             0 },
-    { ".",             "",             1 },
-    { "..",            "",             1 },
-    { "foo",           "foo",          0 },
-    { "foo/bar",       "foo/bar",      0 },
-    { "foo///bar",     "foo/bar",      1 },
-    { "foo/.",         "foo/",         1 },
-    { "foo/./",                "foo/",         1 },
-    { "foo./",         "foo./",        0 },
-    { "foo/../bar",    "bar",          1 },
-    { "foo/../bar/",   "bar/",         1 },
-    { "foo/bar/..",    "foo/",         1 },
-    { "foo/bar/../x",  "foo/x",        1 },
-    { "foo/bar/../x/", "foo/x/",       1 },
-    { "foo/..",                "",             1 },
-    { "foo/../..",     "",             1 },
-    { "a/b/../../c",   "c",            1 },
-    { "./a/../b",      "b",            1 }
-  };
-  int i;
+int
+run_with_timeout (long timeout, void (*fun) (void *), void *arg)
+{
+#ifndef USE_SIGNAL_TIMEOUT
+  fun (arg);
+  return 0;
+#else
+  int saved_errno;
 
-  for (i = 0; i < ARRAY_SIZE (tests); i++)
+  if (timeout == 0)
     {
-      char *test = tests[i].test;
-      char *expected_result = tests[i].result;
-      int   expected_change = tests[i].should_modify;
-      run_test (test, expected_result, expected_change);
+      fun (arg);
+      return 0;
     }
 
-  /* Now run all the tests with a leading slash before the test case,
-     to prove that the slash is being preserved.  */
-  for (i = 0; i < ARRAY_SIZE (tests); i++)
+  signal (SIGALRM, abort_run_with_timeout);
+  if (SETJMP (run_with_timeout_env) != 0)
     {
-      char *test, *expected_result;
-      int expected_change = tests[i].should_modify;
-
-      test = xmalloc (1 + strlen (tests[i].test) + 1);
-      sprintf (test, "/%s", tests[i].test);
-
-      expected_result = xmalloc (1 + strlen (tests[i].result) + 1);
-      sprintf (expected_result, "/%s", tests[i].result);
+      /* Longjumped out of FUN with a timeout. */
+      signal (SIGALRM, SIG_DFL);
+      return 1;
+    }
+  alarm (timeout);
+  fun (arg);
 
-      run_test (test, expected_result, expected_change);
+  /* Preserve errno in case alarm() or signal() modifies it. */
+  saved_errno = errno;
+  alarm (0);
+  signal (SIGALRM, SIG_DFL);
+  errno = saved_errno;
 
-      xfree (test);
-      xfree (expected_result);
-    }
-}
+  return 0;
 #endif
+}
+