]> sjero.net Git - wget/blobdiff - src/utils.c
[svn] A bunch of new features:
[wget] / src / utils.c
index 31aab9c13efb6d875027864983da1d8b6046cbc6..a6a08e2a567f6b3268757c635a00ff3449cc656a 100644 (file)
@@ -1,5 +1,5 @@
 /* Various functions of utilitarian nature.
-   Copyright (C) 1995, 1996, 1997, 1998 Free Software Foundation, Inc.
+   Copyright (C) 1995, 1996, 1997, 1998, 2000 Free Software Foundation, Inc.
 
 This file is part of Wget.
 
@@ -31,6 +31,9 @@ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.  */
 #ifdef HAVE_UNISTD_H
 # include <unistd.h>
 #endif
+#ifdef HAVE_MMAP
+# include <sys/mman.h>
+#endif
 #ifdef HAVE_PWD_H
 # include <pwd.h>
 #endif
@@ -45,10 +48,13 @@ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.  */
 #ifdef NeXT
 # include <libc.h>             /* for access() */
 #endif
+#include <fcntl.h>
+#include <assert.h>
 
 #include "wget.h"
 #include "utils.h"
 #include "fnmatch.h"
+#include "hash.h"
 
 #ifndef errno
 extern int errno;
@@ -411,7 +417,7 @@ touch (const char *file, time_t tm)
 #endif
 
   if (utime (file, &times) == -1)
-    logprintf (LOG_NOTQUIET, "utime: %s\n", strerror (errno));
+    logprintf (LOG_NOTQUIET, "utime(%s): %s\n", file, strerror (errno));
 }
 
 /* Checks if FILE is a symbolic link, and removes it if it is.  Does
@@ -696,61 +702,188 @@ suffix (const char *str)
 /* Read a line from FP.  The function reallocs the storage as needed
    to accomodate for any length of the line.  Reallocs are done
    storage exponentially, doubling the storage after each overflow to
-   minimize the number of calls to realloc().
+   minimize the number of calls to realloc() and fgets().  The newline
+   character at the end of line is retained.
+
+   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().  */
 
-   It is not an exemplary of correctness, since it kills off the
-   newline (and no, there is no way to know if there was a newline at
-   EOF).  */
 char *
 read_whole_line (FILE *fp)
 {
-  char *line;
-  int i, bufsize, c;
+  int length = 0;
+  int bufsize = 81;
+  char *line = (char *)xmalloc (bufsize);
 
-  i = 0;
-  bufsize = 40;
-  line = (char *)xmalloc (bufsize);
-  /* Construct the line.  */
-  while ((c = getc (fp)) != EOF && c != '\n')
+  while (fgets (line + length, bufsize - length, fp))
     {
-      if (i > bufsize - 1)
-       line = (char *)xrealloc (line, (bufsize <<= 1));
-      line[i++] = c;
+      length += strlen (line + length);
+      assert (length > 0);
+      if (line[length - 1] == '\n')
+       break;
+      /* fgets() guarantees to read the whole line, or to use up the
+         space we've given it.  We can double the buffer
+         unconditionally.  */
+      bufsize <<= 1;
+      line = xrealloc (line, bufsize);
     }
-  if (c == EOF && !i)
+  if (length == 0 || ferror (fp))
     {
       free (line);
       return NULL;
     }
-  /* Check for overflow at zero-termination (no need to double the
-     buffer in this case.  */
-  if (i == bufsize)
-    line = (char *)xrealloc (line, i + 1);
-  line[i] = '\0';
+  if (length + 1 < bufsize)
+    /* Relieve the memory from our exponential greediness.  We say
+       `length + 1' because the terminating \0 is not included in
+       LENGTH.  We don't need to zero-terminate the string ourselves,
+       though, because fgets() does that.  */
+    line = xrealloc (line, length + 1);
   return line;
 }
+\f
+/* Read FILE into memory.  A pointer to `struct file_memory' are
+   returned; use struct element `content' to access file contents, and
+   the element `length' to know the file length.  `content' is *not*
+   zero-terminated, and you should *not* read or write beyond the [0,
+   length) range of characters.
 
-/* Load file pointed to by FP to memory and return the malloc-ed
-   buffer with the contents.  *NREAD will contain the number of read
-   bytes.  The file is loaded in chunks, allocated exponentially,
-   starting with FILE_BUFFER_SIZE bytes.  */
-void
-load_file (FILE *fp, char **buf, long *nread)
-{
-  long bufsize;
+   After you are done with the file contents, call read_file_free to
+   release the memory.
+
+   Depending on the operating system and the type of file that is
+   being read, read_file() either mmap's the file into memory, or
+   reads the file into the core using read().
+
+   If file is named "-", fileno(stdin) is used for reading instead.
+   If you want to read from a real file named "-", use "./-" instead.  */
 
-  bufsize = 512;
-  *nread = 0;
-  *buf = NULL;
-  while (!feof (fp) && !ferror (fp))
+struct file_memory *
+read_file (const char *file)
+{
+  int fd;
+  struct file_memory *fm;
+  long size;
+  int inhibit_close = 0;
+
+  /* Some magic in the finest tradition of Perl and its kin: if FILE
+     is "-", just use stdin.  */
+  if (HYPHENP (file))
     {
-      *buf = (char *)xrealloc (*buf, bufsize + *nread);
-      *nread += fread (*buf + *nread, sizeof (char), bufsize, fp);
-      bufsize <<= 1;
+      fd = fileno (stdin);
+      inhibit_close = 1;
+      /* Note that we don't inhibit mmap() in this case.  If stdin is
+         redirected from a regular file, mmap() will still work.  */
+    }
+  else
+    fd = open (file, O_RDONLY);
+  if (fd < 0)
+    return NULL;
+  fm = xmalloc (sizeof (struct file_memory));
+
+#ifdef HAVE_MMAP
+  {
+    struct stat buf;
+    if (fstat (fd, &buf) < 0)
+      goto mmap_lose;
+    fm->length = buf.st_size;
+    /* NOTE: As far as I know, the callers of this function never
+       modify the file text.  Relying on this would enable us to
+       specify PROT_READ and MAP_SHARED for a marginal gain in
+       efficiency, but at some cost to generality.  */
+    fm->content = mmap (NULL, fm->length, PROT_READ | PROT_WRITE,
+                       MAP_PRIVATE, fd, 0);
+    if (fm->content == MAP_FAILED)
+      goto mmap_lose;
+    if (!inhibit_close)
+      close (fd);
+
+    fm->mmap_p = 1;
+    return fm;
+  }
+
+ mmap_lose:
+  /* The most common reason why mmap() fails is that FD does not point
+     to a plain file.  However, it's also possible that mmap() doesn't
+     work for a particular type of file.  Therefore, whenever mmap()
+     fails, we just fall back to the regular method.  */
+#endif /* HAVE_MMAP */
+
+  fm->length = 0;
+  size = 512;                  /* number of bytes fm->contents can
+                                   hold at any given time. */
+  fm->content = xmalloc (size);
+  while (1)
+    {
+      long nread;
+      if (fm->length > size / 2)
+       {
+         /* #### I'm not sure whether the whole exponential-growth
+             thing makes sense with kernel read.  On Linux at least,
+             read() refuses to read more than 4K from a file at a
+             single chunk anyway.  But other Unixes might optimize it
+             better, and it doesn't *hurt* anything, so I'm leaving
+             it.  */
+
+         /* Normally, we grow SIZE exponentially to make the number
+             of calls to read() and realloc() logarithmic in relation
+             to file size.  However, read() can read an amount of data
+             smaller than requested, and it would be unreasonably to
+             double SIZE every time *something* was read.  Therefore,
+             we double SIZE only when the length exceeds half of the
+             entire allocated size.  */
+         size <<= 1;
+         fm->content = xrealloc (fm->content, size);
+       }
+      nread = read (fd, fm->content + fm->length, size - fm->length);
+      if (nread > 0)
+       /* Successful read. */
+       fm->length += nread;
+      else if (nread < 0)
+       /* Error. */
+       goto lose;
+      else
+       /* EOF */
+       break;
     }
-  /* #### No indication of encountered error??  */
+  if (!inhibit_close)
+    close (fd);
+  if (size > fm->length && fm->length != 0)
+    /* Due to exponential growth of fm->content, the allocated region
+       might be much larger than what is actually needed.  */
+    fm->content = xrealloc (fm->content, fm->length);
+  fm->mmap_p = 0;
+  return fm;
+
+ lose:
+  if (!inhibit_close)
+    close (fd);
+  free (fm->content);
+  free (fm);
+  return NULL;
 }
 
+/* Release the resources held by FM.  Specifically, this calls
+   munmap() or free() on fm->content, depending whether mmap or
+   malloc/read were used to read in the file.  It also frees the
+   memory needed to hold the FM structure itself.  */
+
+void
+read_file_free (struct file_memory *fm)
+{
+#ifdef HAVE_MMAP
+  if (fm->mmap_p)
+    {
+      munmap (fm->content, fm->length);
+    }
+  else
+#endif
+    {
+      free (fm->content);
+    }
+  free (fm);
+}
+\f
 /* Free the pointers in a NULL-terminated vector of pointers, then
    free the pointer itself.  */
 void
@@ -794,97 +927,42 @@ merge_vecs (char **v1, char **v2)
   return v1;
 }
 
-/* A set of simple-minded routines to store and search for strings in
-   a linked list.  You may add a string to the slist, and peek whether
-   it's still in there at any time later.  */
+/* A set of simple-minded routines to store strings in a linked list.
+   This used to also be used for searching, but now we have hash
+   tables for that.  */
 
-/* Add an element to the list.  If flags is NOSORT, the list will not
-   be sorted.  */
+/* Append an element to the list.  */
 slist *
-add_slist (slist *l, const char *s, int flags)
+slist_append (slist *l, const char *s)
 {
-  slist *t, *old, *beg;
-  int cmp;
-
-  if (flags & NOSORT)
-    {
-      if (!l)
-       {
-         t = (slist *)xmalloc (sizeof (slist));
-         t->string = xstrdup (s);
-         t->next = NULL;
-         return t;
-       }
-      beg = l;
-      /* Find the last element.  */
-      while (l->next)
-       l = l->next;
-      t = (slist *)xmalloc (sizeof (slist));
-      l->next = t;
-      t->string = xstrdup (s);
-      t->next = NULL;
-      return beg;
-    }
-  /* Empty list or changing the first element.  */
-  if (!l || (cmp = strcmp (l->string, s)) > 0)
-    {
-      t = (slist *)xmalloc (sizeof (slist));
-      t->string = xstrdup (s);
-      t->next = l;
-      return t;
-    }
+  slist *newel = (slist *)xmalloc (sizeof (slist));
+  slist *beg = l;
 
-  beg = l;
-  if (cmp == 0)
-    return beg;
+  newel->string = xstrdup (s);
+  newel->next = NULL;
 
-  /* Second two one-before-the-last element.  */
+  if (!l)
+    return newel;
+  /* Find the last element.  */
   while (l->next)
-    {
-      old = l;
-      l = l->next;
-      cmp = strcmp (s, l->string);
-      if (cmp == 0)             /* no repeating in the list */
-       return beg;
-      else if (cmp > 0)
-       continue;
-      /* If the next list element is greater than s, put s between the
-        current and the next list element.  */
-      t = (slist *)xmalloc (sizeof (slist));
-      old->next = t;
-      t->next = l;
-      t->string = xstrdup (s);
-      return beg;
-    }
-  t = (slist *)xmalloc (sizeof (slist));
-  t->string = xstrdup (s);
-  /* Insert the new element after the last element.  */
-  l->next = t;
-  t->next = NULL;
+    l = l->next;
+  l->next = newel;
   return beg;
 }
 
 /* Is there a specific entry in the list?  */
 int
-in_slist (slist *l, const char *s)
+slist_contains (slist *l, const char *s)
 {
-  int cmp;
-
-  while (l)
-    {
-      cmp = strcmp (l->string, s);
-      if (cmp == 0)
-       return 1;
-      else if (cmp > 0)         /* the list is ordered!  */
-       return 0;
-      l = l->next;
-    }
+  for (; l; l = l->next)
+    if (!strcmp (l->string, s))
+      return 1;
   return 0;
 }
 
 /* Free the whole slist.  */
 void
-free_slist (slist *l)
+slist_free (slist *l)
 {
   slist *n;
 
@@ -896,21 +974,73 @@ free_slist (slist *l)
       l = n;
     }
 }
+\f
+/* Sometimes it's useful to create "sets" of strings, i.e. special
+   hash tables where you want to store strings as keys and merely
+   query for their existence.  Here is a set of utility routines that
+   makes that transparent.  */
 
-/* Legible -- return a static pointer to the legibly printed long.  */
-char *
-legible (long l)
+void
+string_set_add (struct hash_table *ht, const char *s)
 {
-  static char outbuf[20];
-  char inbuf[20];
+  /* We use "1" as value.  It provides us a useful and clear arbitrary
+     value, and it consumes no memory -- the pointers to the same
+     string "1" will be shared by all the key-value pairs in the hash
+     table.  */
+  hash_table_put (ht, xstrdup (s), "1");
+}
+
+int
+string_set_exists (struct hash_table *ht, const char *s)
+{
+  return hash_table_exists (ht, s);
+}
+
+static int
+string_set_free_mapper (void *key, void *value_ignored, void *arg_ignored)
+{
+  free (key);
+  return 0;
+}
+
+void
+string_set_free (struct hash_table *ht)
+{
+  hash_table_map (ht, string_set_free_mapper, NULL);
+  hash_table_destroy (ht);
+}
+
+static int
+free_keys_and_values_mapper (void *key, void *value, void *arg_ignored)
+{
+  free (key);
+  free (value);
+  return 0;
+}
+
+/* Another utility function: call free() on all keys and values of HT.  */
+
+void
+free_keys_and_values (struct hash_table *ht)
+{
+  hash_table_map (ht, free_keys_and_values_mapper, NULL);
+}
+
+\f
+/* Engine for legible and legible_long_long; this function works on
+   strings.  */
+
+static char *
+legible_1 (const char *repr)
+{
+  static char outbuf[128];
   int i, i1, mod;
-  char *outptr, *inptr;
+  char *outptr;
+  const char *inptr;
 
-  /* Print the number into the buffer.  */
-  long_to_string (inbuf, l);
   /* Reset the pointers.  */
   outptr = outbuf;
-  inptr = inbuf;
+  inptr = repr;
   /* If the number is negative, shift the pointers.  */
   if (*inptr == '-')
     {
@@ -935,6 +1065,26 @@ legible (long l)
   return outbuf;
 }
 
+/* Legible -- return a static pointer to the legibly printed long.  */
+char *
+legible (long l)
+{
+  char inbuf[24];
+  /* Print the number into the buffer.  */
+  long_to_string (inbuf, l);
+  return legible_1 (inbuf);
+}
+
+/* The same as legible(), but works on VERY_LONG_TYPE.  See sysdep.h.  */
+char *
+legible_very_long (VERY_LONG_TYPE l)
+{
+  char inbuf[128];
+  /* Print the number into the buffer.  */
+  sprintf (inbuf, VERY_LONG_FORMAT, l);
+  return legible_1 (inbuf);
+}
+
 /* Count the digits in a (long) integer.  */
 int
 numdigit (long a)
@@ -945,34 +1095,54 @@ numdigit (long a)
   return res;
 }
 
-/* Print NUMBER to BUFFER.  The digits are first written in reverse
-   order (the least significant digit first), and are then reversed.  */
+/* Print NUMBER to BUFFER.  This is equivalent to sprintf(buffer,
+   "%ld", number), only much faster.
+
+   BUFFER should accept 24 bytes.  This should suffice for the longest
+   numbers on 64-bit machines, including the `-' sign and the trailing
+   \0.  */
 void
 long_to_string (char *buffer, long number)
 {
-  char *p;
-  int i, l;
+#if (SIZEOF_LONG != 4) && (SIZEOF_LONG != 8)
+  /* Huh? */
+  sprintf (buffer, "%ld", number);
+#else /* (SIZEOF_LONG == 4) || (SIZEOF_LONG == 8) */
+  char *p = buffer;
+  int force = 0;
 
   if (number < 0)
     {
-      *buffer++ = '-';
+      *p++ = '-';
       number = -number;
     }
-  p = buffer;
-  /* Print the digits to the string.  */
-  do
-    {
-      *p++ = number % 10 + '0';
-      number /= 10;
-    }
-  while (number);
-  /* And reverse them.  */
-  l = p - buffer - 1;
-  for (i = l/2; i >= 0; i--)
-    {
-      char c = buffer[i];
-      buffer[i] = buffer[l - i];
-      buffer[l - i] = c;
-    }
-  buffer[l + 1] = '\0';
+
+#define FROB(figure) do {                                              \
+    if (force || number >= figure)                                     \
+      *p++ = number / figure + '0', number %= figure, force = 1;       \
+    } while (0)
+#if SIZEOF_LONG == 8
+  FROB (1000000000000000000L);
+  FROB (100000000000000000L);
+  FROB (10000000000000000L);
+  FROB (1000000000000000L);
+  FROB (100000000000000L);
+  FROB (10000000000000L);
+  FROB (1000000000000L);
+  FROB (100000000000L);
+  FROB (10000000000L);
+#endif /* SIZEOF_LONG == 8 */
+  FROB (1000000000);
+  FROB (100000000);
+  FROB (10000000);
+  FROB (1000000);
+  FROB (100000);
+  FROB (10000);
+  FROB (1000);
+  FROB (100);
+  FROB (10);
+#undef FROB
+  *p++ = number + '0';
+  *p = '\0';
+#endif /* (SIZEOF_LONG == 4) || (SIZEOF_LONG == 8) */
 }