]> sjero.net Git - wget/blobdiff - src/retr.c
[svn] * retr.c (fd_read_body): Report the amount of data *written* as
[wget] / src / retr.c
index 5d9795ad67ac7177163f96d1dff28f2eaa6bb9c7..73425cba8359e4b3d7ce596e3e036e896012d34b 100644 (file)
@@ -63,12 +63,16 @@ so, delete this exception statement from your version.  */
 extern int errno;
 #endif
 
-/* See the comment in gethttp() why this is needed. */
-int global_download_count;
-
 /* Total size of downloaded files.  Used to enforce quota.  */
 LARGE_INT total_downloaded_bytes;
 
+/* If non-NULL, the stream to which output should be written.  This
+   stream is initialized when `-O' is used.  */
+FILE *output_stream;
+
+/* Whether output_document is a regular file we can manipulate,
+   i.e. not `-' or a device file. */
+int output_stream_regular;
 \f
 static struct {
   long chunk_bytes;
@@ -129,35 +133,72 @@ limit_bandwidth (long bytes, struct wget_timer *timer)
   limit_data.chunk_start = wtimer_read (timer);
 }
 
-#define MIN(i, j) ((i) <= (j) ? (i) : (j))
+#ifndef MIN
+# define MIN(i, j) ((i) <= (j) ? (i) : (j))
+#endif
 
-/* Reads the contents of file descriptor FD, until it is closed, or a
-   read error occurs.  The data is read in 8K chunks, and stored to
-   stream fp, which should have been open for writing.
+/* Write data in BUF to OUT.  However, if *SKIP is non-zero, skip that
+   amount of data and decrease SKIP.  Increment *TOTAL by the amount
+   of data written.  */
 
-   The EXPECTED argument is passed to show_progress() unchanged, but
-   otherwise ignored.
+static int
+write_data (FILE *out, const char *buf, int bufsize, long *skip,
+           long *transferred)
+{
+  if (!out)
+    return 1;
+  if (*skip > bufsize)
+    {
+      *skip -= bufsize;
+      return 1;
+    }
+  if (*skip)
+    {
+      buf += *skip;
+      bufsize -= *skip;
+      *skip = 0;
+      if (bufsize == 0)
+       return 1;
+    }
+  *transferred += bufsize;
+  fwrite (buf, 1, bufsize, out);
+
+  /* Immediately flush the downloaded data.  This should not hinder
+     performance: fast downloads will arrive in large 16K chunks
+     (which stdio would write out immediately anyway), and slow
+     downloads wouldn't be limited by disk speed.  */
+  fflush (out);
+  return !ferror (out);
+}
+
+/* Read the contents of file descriptor FD until it the connection
+   terminates or a read error occurs.  The data is read in portions of
+   up to 16K and written to OUT as it arrives.  If opt.verbose is set,
+   the progress is shown.
+
+   TOREAD is the amount of data expected to arrive, normally only used
+   by the progress gauge.
 
-   If opt.verbose is set, the progress is also shown.  RESTVAL
-   represents a value from which to start downloading (which will be
-   shown accordingly).  If RESTVAL is non-zero, the stream should have
-   been open for appending.
+   STARTPOS is the position from which the download starts, used by
+   the progress gauge.  The amount of data read gets stored to
+   *TRANSFERRED.  The time it took to download the data (in
+   milliseconds) is stored to *ELAPSED.
 
-   The function exits and returns codes of 0, -1 and -2 if the
-   connection was closed, there was a read error, or if it could not
-   write to the output stream, respectively.  */
+   The function exits and returns the amount of data read.  In case of
+   error while reading data, -1 is returned.  In case of error while
+   writing data, -2 is returned.  */
 
 int
-fd_read_body (int fd, FILE *out, long *len, long restval, long expected,
-             int use_expected, double *elapsed)
+fd_read_body (int fd, FILE *out, long toread, long startpos,
+             long *transferred, double *elapsed, int flags)
 {
-  int res = 0;
+  int ret = 0;
 
   static char dlbuf[16384];
   int dlbufsize = sizeof (dlbuf);
 
-  struct wget_timer *timer = wtimer_allocate ();
-  double last_successful_read_tm;
+  struct wget_timer *timer = NULL;
+  double last_successful_read_tm = 0;
 
   /* The progress gauge, set according to the user preferences. */
   void *progress = NULL;
@@ -168,18 +209,36 @@ fd_read_body (int fd, FILE *out, long *len, long restval, long expected,
      data arrives slowly. */
   int progress_interactive = 0;
 
-  *len = restval;
+  int exact = flags & rb_read_exactly;
+  long skip = 0;
+
+  /* How much data we've read.  This is used internally and is
+     unaffected by skipping STARTPOS.  */
+  long total_read = 0;
+
+  *transferred = 0;
+  if (flags & rb_skip_startpos)
+    skip = startpos;
 
   if (opt.verbose)
     {
-      progress = progress_create (restval, expected);
+      /* If we're skipping STARTPOS bytes, hide it from
+        progress_create because the indicator can't deal with it.  */
+      progress = progress_create (skip ? 0 : startpos, toread);
       progress_interactive = progress_interactive_p (progress);
     }
 
   if (opt.limit_rate)
     limit_bandwidth_reset ();
-  wtimer_reset (timer);
-  last_successful_read_tm = 0;
+
+  /* A timer is needed for tracking progress, for throttling, and for
+     tracking elapsed time.  If either of these are requested, start
+     the timer.  */
+  if (progress || opt.limit_rate || elapsed)
+    {
+      timer = wtimer_new ();
+      last_successful_read_tm = 0;
+    }
 
   /* Use a smaller buffer for low requested bandwidths.  For example,
      with --limit-rate=2k, it doesn't make sense to slurp in 16K of
@@ -188,15 +247,13 @@ fd_read_body (int fd, FILE *out, long *len, long restval, long expected,
   if (opt.limit_rate && opt.limit_rate < dlbufsize)
     dlbufsize = opt.limit_rate;
 
-  /* Read from fd while there is available data.
-
-     Normally, if expected is 0, it means that it is not known how
-     much data is expected.  However, if use_expected is specified,
-     then expected being zero means exactly that.  */
-  while (!use_expected || (*len < expected))
+  /* Read from FD while there is data to read.  Normally toread==0
+     means that it is unknown how much data is to arrive.  However, if
+     EXACT is set, then toread==0 means what it says: that no data
+     should be read.  */
+  while (!exact || (total_read < toread))
     {
-      int amount_to_read = (use_expected
-                           ? MIN (expected - *len, dlbufsize) : dlbufsize);
+      int rdsize = exact ? MIN (toread - total_read, dlbufsize) : dlbufsize;
       double tmout = opt.read_timeout;
       if (progress_interactive)
        {
@@ -213,72 +270,101 @@ fd_read_body (int fd, FILE *out, long *len, long restval, long expected,
              if (tmout < 0)
                {
                  /* We've already exceeded the timeout. */
-                 res = -1, errno = ETIMEDOUT;
+                 ret = -1, errno = ETIMEDOUT;
                  break;
                }
            }
        }
-      res = fd_read (fd, dlbuf, amount_to_read, tmout);
+      ret = fd_read (fd, dlbuf, rdsize, tmout);
 
-      if (res == 0 || (res < 0 && errno != ETIMEDOUT))
+      if (ret == 0 || (ret < 0 && errno != ETIMEDOUT))
        break;
-      else if (res < 0)
-       res = 0;                /* timeout */
+      else if (ret < 0)
+       ret = 0;                /* timeout */
 
-      wtimer_update (timer);
-      if (res > 0)
+      if (progress || opt.limit_rate)
+       {
+         wtimer_update (timer);
+         if (ret > 0)
+           last_successful_read_tm = wtimer_read (timer);
+       }
+
+      if (ret > 0)
        {
-         fwrite (dlbuf, 1, res, out);
-         /* Always flush the contents of the network packet.  This
-            should not hinder performance: fast downloads will be
-            received in 16K chunks (which stdio would write out
-            anyway), and slow downloads won't be limited by disk
-            performance.  */
-         fflush (out);
-         if (ferror (out))
+         total_read += ret;
+         if (!write_data (out, dlbuf, ret, &skip, transferred))
            {
-             res = -2;
+             ret = -2;
              goto out;
            }
-         last_successful_read_tm = wtimer_read (timer);
        }
 
       if (opt.limit_rate)
-       limit_bandwidth (res, timer);
+       limit_bandwidth (ret, timer);
 
-      *len += res;
       if (progress)
-       progress_update (progress, res, wtimer_read (timer));
+       progress_update (progress, ret, wtimer_read (timer));
 #ifdef WINDOWS
-      if (use_expected && expected > 0)
-       ws_percenttitle (100.0 * (double)(*len) / (double)expected);
+      if (toread > 0)
+       ws_percenttitle (100.0 *
+                        (startpos + total_read) / (startpos + toread));
 #endif
     }
-  if (res < -1)
-    res = -1;
+  if (ret < -1)
+    ret = -1;
 
  out:
   if (progress)
     progress_finish (progress, wtimer_read (timer));
   if (elapsed)
     *elapsed = wtimer_read (timer);
-  wtimer_delete (timer);
+  if (timer)
+    wtimer_delete (timer);
 
-  return res;
+  return ret;
 }
 \f
-typedef const char *(*finder_t) PARAMS ((const char *, int, int));
+/* Read a hunk of data from FD, up until a terminator.  The terminator
+   is whatever the TERMINATOR function determines it to be; for
+   example, it can be a line of data, or the head of an HTTP response.
+   The function returns the data read allocated with malloc.
 
-/* Driver for fd_read_line and fd_read_head: keeps reading data until
-   a terminator (as decided by FINDER) occurs in the data.  The trick
-   is that the data is first peeked at, and only then actually read.
-   That way the data after the terminator is never read.  */
+   In case of error, NULL is returned.  In case of EOF and no data
+   read, NULL is returned and errno set to 0.  In case of EOF with
+   data having been read, the data is returned, but it will
+   (obviously) not contain the terminator.
 
-static char *
-fd_read_until (int fd, finder_t finder, int bufsize)
+   The idea is to be able to read a line of input, or otherwise a hunk
+   of text, such as the head of an HTTP request, without crossing the
+   boundary, so that the next call to fd_read etc. reads the data
+   after the hunk.  To achieve that, this function does the following:
+
+   1. Peek at available data.
+
+   2. Determine whether the peeked data, along with the previously
+      read data, includes the terminator.
+
+      2a. If yes, read the data until the end of the terminator, and
+          exit.
+
+      2b. If no, read the peeked data and goto 1.
+
+   The function is careful to assume as little as possible about the
+   implementation of peeking.  For example, every peek is followed by
+   a read.  If the read returns a different amount of data, the
+   process is retried until all data arrives safely.
+
+   BUFSIZE is the size of the initial buffer expected to read all the
+   data in the typical case.
+
+   This function should be used as a building block for other
+   functions -- see fd_read_line as a simple example.  */
+
+char *
+fd_read_hunk (int fd, hunk_terminator_t hunk_terminator, int bufsize)
 {
-  int size = bufsize, tail = 0;
-  char *buf = xmalloc (size);
+  char *hunk = xmalloc (bufsize);
+  int tail = 0;                        /* tail position in HUNK */
 
   while (1)
     {
@@ -287,23 +373,28 @@ fd_read_until (int fd, finder_t finder, int bufsize)
 
       /* First, peek at the available data. */
 
-      pklen = fd_peek (fd, buf + tail, size - tail, -1);
+      pklen = fd_peek (fd, hunk + tail, bufsize - 1 - tail, -1);
       if (pklen < 0)
        {
-         xfree (buf);
+         xfree (hunk);
          return NULL;
        }
-      end = finder (buf, tail, pklen);
+      end = hunk_terminator (hunk, tail, pklen);
       if (end)
        {
-         /* The data contains the terminator: we'll read the data up
+         /* The data contains the terminator: we'll drain the data up
             to the end of the terminator.  */
-         remain = end - (buf + tail);
-         /* Note +1 for trailing \0. */
-         if (size < tail + remain + 1)
+         remain = end - (hunk + tail);
+         if (remain == 0)
            {
-             size = tail + remain + 1;
-             buf = xrealloc (buf, size);
+             /* No more data needs to be read. */
+             hunk[tail] = '\0';
+             return hunk;
+           }
+         if (bufsize - 1 < tail + remain)
+           {
+             bufsize = tail + remain + 1;
+             hunk = xrealloc (hunk, bufsize);
            }
        }
       else
@@ -315,54 +406,47 @@ fd_read_until (int fd, finder_t finder, int bufsize)
         how much data we'll get.  (Some TCP stacks are notorious for
         read returning less data than the previous MSG_PEEK.)  */
 
-      rdlen = fd_read (fd, buf + tail, remain, 0);
+      rdlen = fd_read (fd, hunk + tail, remain, 0);
       if (rdlen < 0)
        {
-         xfree_null (buf);
+         xfree_null (hunk);
          return NULL;
        }
+      tail += rdlen;
+      hunk[tail] = '\0';
+
       if (rdlen == 0)
        {
          if (tail == 0)
            {
              /* EOF without anything having been read */
-             xfree (buf);
+             xfree (hunk);
              errno = 0;
              return NULL;
            }
-         /* Return what we received so far. */
-         if (size < tail + 1)
-           {
-             size = tail + 1;  /* expand the buffer to receive the
-                                  terminating \0 */
-             buf = xrealloc (buf, size);
-           }
-         buf[tail] = '\0';
-         return buf;
+         else
+           /* EOF seen: return the data we've read. */
+           return hunk;
        }
-      tail += rdlen;
       if (end && rdlen == remain)
-       {
-         /* The end was seen and the data read -- we got what we came
-            for.  */
-         buf[tail] = '\0';
-         return buf;
-       }
+       /* The terminator was seen and the remaining data drained --
+          we got what we came for.  */
+       return hunk;
 
       /* Keep looping until all the data arrives. */
 
-      if (tail == size)
+      if (tail == bufsize - 1)
        {
-         size <<= 1;
-         buf = xrealloc (buf, size);
+         bufsize <<= 1;
+         hunk = xrealloc (hunk, bufsize);
        }
     }
 }
 
 static const char *
-line_terminator (const char *buf, int tail, int peeklen)
+line_terminator (const char *hunk, int oldlen, int peeklen)
 {
-  const char *p = memchr (buf + tail, '\n', peeklen);
+  const char *p = memchr (hunk + oldlen, '\n', peeklen);
   if (p)
     /* p+1 because we want the line to include '\n' */
     return p + 1;
@@ -379,43 +463,7 @@ line_terminator (const char *buf, int tail, int peeklen)
 char *
 fd_read_line (int fd)
 {
-  return fd_read_until (fd, line_terminator, 128);
-}
-
-static const char *
-head_terminator (const char *buf, int tail, int peeklen)
-{
-  const char *start, *end;
-  if (tail < 4)
-    start = buf;
-  else
-    start = buf + tail - 4;
-  end = buf + tail + peeklen;
-
-  for (; start < end - 1; start++)
-    if (*start == '\n')
-      {
-       if (start < end - 2
-           && start[1] == '\r'
-           && start[2] == '\n')
-         return start + 3;
-       if (start[1] == '\n')
-         return start + 2;
-      }
-  return NULL;
-}
-
-/* Read the request head from FD and return it.  The chunk of data is
-   allocated using malloc.
-
-   If an error occurs, or if no data can be read, NULL is returned.
-   In the former case errno indicates the error condition, and in the
-   latter case, errno is NULL.  */
-
-char *
-fd_read_head (int fd)
-{
-  return fd_read_until (fd, head_terminator, 512);
+  return fd_read_hunk (fd, line_terminator, 128);
 }
 \f
 /* Return a printed representation of the download rate, as
@@ -707,7 +755,6 @@ retrieve_url (const char *origurl, char **file, char **newloc,
       xfree (url);
     }
 
-  ++global_download_count;
   RESTORE_POST_DATA;
 
   return result;
@@ -904,7 +951,7 @@ getproxy (struct url *u)
   rewritten_url = rewrite_shorthand_url (proxy);
   if (rewritten_url)
     {
-      strncpy (rewritten_storage, rewritten_url, sizeof(rewritten_storage));
+      strncpy (rewritten_storage, rewritten_url, sizeof (rewritten_storage));
       rewritten_storage[sizeof (rewritten_storage) - 1] = '\0';
       proxy = rewritten_storage;
     }