]> git.ipfire.org Git - thirdparty/grub.git/commitdiff
HTTP seek support. Various bugfixes.
authorVladimir 'phcoder' Serbinenko <phcoder@gmail.com>
Wed, 5 Oct 2011 15:39:13 +0000 (17:39 +0200)
committerVladimir 'phcoder' Serbinenko <phcoder@gmail.com>
Wed, 5 Oct 2011 15:39:13 +0000 (17:39 +0200)
grub-core/net/http.c
grub-core/net/net.c
grub-core/net/tcp.c
include/grub/net.h
include/grub/net/tcp.h

index b5de4c5c6e706967351e76b7ba5c56ae5e8e0293..e4664757c1d6d63df22377d8544b83450c716323 100644 (file)
@@ -42,7 +42,11 @@ typedef struct http_data
   grub_size_t current_line_len;
   int headers_recv;
   int first_line_recv;
+  int size_recv;
   grub_net_tcp_socket_t sock;
+  char *filename;
+  grub_err_t err;
+  char *errmsg;
 } *http_data_t;
 
 static grub_err_t
@@ -72,19 +76,25 @@ parse_line (http_data_t data, char *ptr, grub_size_t len)
        {
        case 200:
          break;
+       case 404:
+         data->err = GRUB_ERR_FILE_NOT_FOUND;
+         data->errmsg = grub_xasprintf ("file `%s' not found", data->filename);
+         return GRUB_ERR_NONE;
        default:
-         return grub_error (GRUB_ERR_NET_UNKNOWN_ERROR,
-                            "unsupported HTTP error %d: %s",
-                            code, ptr);
+         data->err = GRUB_ERR_NET_UNKNOWN_ERROR;
+         data->errmsg = grub_xasprintf ("unsupported HTTP error %d: %s",
+                                        code, ptr);
+         return GRUB_ERR_NONE;
        }
       data->first_line_recv = 1;
       return GRUB_ERR_NONE;
     }
   if (grub_memcmp (ptr, "Content-Length: ", sizeof ("Content-Length: ") - 1)
-      == 0)
+      == 0 && !data->size_recv)
     {
       ptr += sizeof ("Content-Length: ") - 1;
       data->file_size = grub_strtoull (ptr, &ptr, 10);
+      data->size_recv = 1;
       return GRUB_ERR_NONE;
     }
   return GRUB_ERR_NONE;  
@@ -98,10 +108,10 @@ http_err (grub_net_tcp_socket_t sock __attribute__ ((unused)),
   http_data_t data = file->data;
 
   if (data->sock)
-    grub_net_tcp_close (data->sock);
-  grub_free (data);
+    grub_net_tcp_close (data->sock, GRUB_NET_TCP_ABORT);
   if (data->current_line)
     grub_free (data->current_line);
+  grub_free (data);
   file->device->net->eof = 1;
 }
 
@@ -132,7 +142,7 @@ http_receive (grub_net_tcp_socket_t sock __attribute__ ((unused)),
       if (!t)
        {
          grub_netbuff_free (nb);
-         grub_net_tcp_close (data->sock);
+         grub_net_tcp_close (data->sock, GRUB_NET_TCP_ABORT);
          return grub_errno;
        }
              
@@ -151,7 +161,7 @@ http_receive (grub_net_tcp_socket_t sock __attribute__ ((unused)),
       data->current_line_len = 0;
       if (err)
        {
-         grub_net_tcp_close (data->sock);
+         grub_net_tcp_close (data->sock, GRUB_NET_TCP_ABORT);
          grub_netbuff_free (nb);
          return err;
        }
@@ -167,7 +177,7 @@ http_receive (grub_net_tcp_socket_t sock __attribute__ ((unused)),
          if (!data->current_line)
            {
              grub_netbuff_free (nb);
-             grub_net_tcp_close (data->sock);
+             grub_net_tcp_close (data->sock, GRUB_NET_TCP_ABORT);
              return grub_errno;
            }
          data->current_line_len = (char *) nb->tail - ptr;
@@ -178,7 +188,7 @@ http_receive (grub_net_tcp_socket_t sock __attribute__ ((unused)),
       err = parse_line (data, ptr, ptr2 - ptr);
       if (err)
        {
-         grub_net_tcp_close (data->sock);
+         grub_net_tcp_close (data->sock, GRUB_NET_TCP_ABORT);
          grub_netbuff_free (nb);
          return err;
        }
@@ -191,7 +201,7 @@ http_receive (grub_net_tcp_socket_t sock __attribute__ ((unused)),
       err = grub_netbuff_pull (nb, ptr - (char *) nb->data);
       if (err)
        {
-         grub_net_tcp_close (data->sock);
+         grub_net_tcp_close (data->sock, GRUB_NET_TCP_ABORT);
          grub_netbuff_free (nb);
          return err;
        }      
@@ -203,57 +213,51 @@ http_receive (grub_net_tcp_socket_t sock __attribute__ ((unused)),
 }
 
 static grub_err_t
-http_open (struct grub_file *file, const char *filename)
+http_establish (struct grub_file *file, grub_off_t offset, int initial)
 {
-  struct grub_net_buff *nb;
-  http_data_t data;
-  grub_err_t err;
+  http_data_t data = file->data;
   grub_uint8_t *ptr;
   int i;
-
-  data = grub_zalloc (sizeof (*data));
-  if (!data)
-    return grub_errno;
+  struct grub_net_buff *nb;
+  grub_err_t err;
 
   nb = grub_netbuff_alloc (GRUB_NET_TCP_RESERVE_SIZE
                           + sizeof ("GET ") - 1
-                          + grub_strlen (filename)
+                          + grub_strlen (data->filename)
                           + sizeof (" HTTP/1.1\r\nHost: ") - 1
                           + grub_strlen (file->device->net->server)
                           + sizeof ("\r\nUser-Agent: " PACKAGE_STRING
-                                    "\r\n\r\n") - 1);
+                                    "\r\n") - 1
+                          + sizeof ("Content-Range: bytes XXXXXXXXXXXXXXXXXXXX"
+                                    "-XXXXXXXXXXXXXXXXXXXX/"
+                                    "XXXXXXXXXXXXXXXXXXXX\r\n\r\n"));
   if (!nb)
-    {
-      grub_free (data);
-      return grub_errno;
-    }
+    return grub_errno;
 
   grub_netbuff_reserve (nb, GRUB_NET_TCP_RESERVE_SIZE);
   ptr = nb->tail;
   err = grub_netbuff_put (nb, sizeof ("GET ") - 1);
   if (err)
     {
-      grub_free (data);
       grub_netbuff_free (nb);
       return err;
     }
   grub_memcpy (ptr, "GET ", sizeof ("GET ") - 1);
 
   ptr = nb->tail;
-  err = grub_netbuff_put (nb, grub_strlen (filename));
+
+  err = grub_netbuff_put (nb, grub_strlen (data->filename));
   if (err)
     {
-      grub_free (data);
       grub_netbuff_free (nb);
       return err;
     }
-  grub_memcpy (ptr, filename, grub_strlen (filename));
+  grub_memcpy (ptr, data->filename, grub_strlen (data->filename));
 
   ptr = nb->tail;
   err = grub_netbuff_put (nb, sizeof (" HTTP/1.1\r\nHost: ") - 1);
   if (err)
     {
-      grub_free (data);
       grub_netbuff_free (nb);
       return err;
     }
@@ -264,7 +268,6 @@ http_open (struct grub_file *file, const char *filename)
   err = grub_netbuff_put (nb, grub_strlen (file->device->net->server));
   if (err)
     {
-      grub_free (data);
       grub_netbuff_free (nb);
       return err;
     }
@@ -273,19 +276,30 @@ http_open (struct grub_file *file, const char *filename)
 
   ptr = nb->tail;
   err = grub_netbuff_put (nb, 
-                         sizeof ("\r\nUser-Agent: " PACKAGE_STRING "\r\n\r\n")
+                         sizeof ("\r\nUser-Agent: " PACKAGE_STRING "\r\n")
                          - 1);
   if (err)
     {
-      grub_free (data);
       grub_netbuff_free (nb);
       return err;
     }
-  grub_memcpy (ptr, "\r\nUser-Agent: " PACKAGE_STRING "\r\n\r\n",
-              sizeof ("\r\nUser-Agent: " PACKAGE_STRING "\r\n\r\n") - 1);
-
-  file->not_easily_seekable = 1;
-  file->data = data;
+  grub_memcpy (ptr, "\r\nUser-Agent: " PACKAGE_STRING "\r\n",
+              sizeof ("\r\nUser-Agent: " PACKAGE_STRING "\r\n") - 1);
+  if (!initial)
+    {
+      ptr = nb->tail;
+      grub_snprintf ((char *) ptr,
+                    sizeof ("Content-Range: bytes XXXXXXXXXXXXXXXXXXXX-"
+                            "XXXXXXXXXXXXXXXXXXXX/XXXXXXXXXXXXXXXXXXXX\r\n"
+                            "\r\n"),
+                    "Content-Range: bytes %" PRIuGRUB_UINT64_T "-%"
+                    PRIuGRUB_UINT64_T "/%" PRIuGRUB_UINT64_T "\r\n\r\n",
+                    offset, data->file_size - 1, data->file_size);
+      grub_netbuff_put (nb, grub_strlen ((char *) ptr));
+    }
+  ptr = nb->tail;
+  grub_netbuff_put (nb, 2);
+  grub_memcpy (ptr, "\r\n", 2);
 
   data->sock = grub_net_tcp_open (file->device->net->server,
                                  HTTP_PORT, http_receive,
@@ -293,7 +307,6 @@ http_open (struct grub_file *file, const char *filename)
                                  file);
   if (!data->sock)
     {
-      grub_free (data);
       grub_netbuff_free (nb);
       return grub_errno;
     }
@@ -303,8 +316,7 @@ http_open (struct grub_file *file, const char *filename)
   err = grub_net_send_tcp_packet (data->sock, nb, 1);
   if (err)
     {
-      grub_free (data);
-      grub_net_tcp_close (data->sock);
+      grub_net_tcp_close (data->sock, GRUB_NET_TCP_ABORT);
       return err;
     }
 
@@ -316,9 +328,81 @@ http_open (struct grub_file *file, const char *filename)
 
   if (!data->headers_recv)
     {
-      grub_net_tcp_close (data->sock);
+      grub_net_tcp_close (data->sock, GRUB_NET_TCP_ABORT);
+      if (data->err)
+       {
+         char *str = data->errmsg;
+         err = grub_error (data->err, "%s", str);
+         grub_free (str);
+         return data->err;
+       }
+      return grub_error (GRUB_ERR_TIMEOUT, "timeout opening http");
+    }
+  return GRUB_ERR_NONE;
+}
+
+static grub_err_t
+http_seek (struct grub_file *file, grub_off_t off)
+{
+  struct http_data *old_data, *data;
+  grub_err_t err;
+  old_data = file->data;
+  /* FIXME: Reuse socket?  */
+  grub_net_tcp_close (old_data->sock, GRUB_NET_TCP_ABORT);
+
+  while (file->device->net->packs.first)
+    grub_net_remove_packet (file->device->net->packs.first);
+
+  data = grub_zalloc (sizeof (*data));
+  if (!data)
+    return grub_errno;
+
+  data->file_size = old_data->file_size;
+  data->size_recv = 1;
+  data->filename = old_data->filename;
+  if (!data->filename)
+    {
       grub_free (data);
-      return grub_error (GRUB_ERR_TIMEOUT, "Time out opening http.");
+      return grub_errno;
+    }
+  grub_free (old_data);
+
+  err = http_establish (file, off, 0);
+  if (err)
+    {
+      grub_free (data->filename);
+      grub_free (data);
+      return err;
+    }
+  return GRUB_ERR_NONE;
+}
+
+static grub_err_t
+http_open (struct grub_file *file, const char *filename)
+{
+  grub_err_t err;
+  struct http_data *data;
+
+  data = grub_zalloc (sizeof (*data));
+  if (!data)
+    return grub_errno;
+
+  data->filename = grub_strdup (filename);
+  if (!data->filename)
+    {
+      grub_free (data);
+      return grub_errno;
+    }
+
+  file->not_easily_seekable = 0;
+  file->data = data;
+
+  err = http_establish (file, 0, 1);
+  if (err)
+    {
+      grub_free (data->filename);
+      grub_free (data);
+      return err;
     }
   file->size = data->file_size;
 
@@ -331,10 +415,10 @@ http_close (struct grub_file *file)
   http_data_t data = file->data;
 
   if (data->sock)
-    grub_net_tcp_close (data->sock);
-  grub_free (data);
+    grub_net_tcp_close (data->sock, GRUB_NET_TCP_ABORT);
   if (data->current_line)
     grub_free (data->current_line);
+  grub_free (data);
   return GRUB_ERR_NONE;
 }
 
@@ -342,7 +426,8 @@ static struct grub_net_app_protocol grub_http_protocol =
   {
     .name = "http",
     .open = http_open,
-    .close = http_close
+    .close = http_close,
+    .seek = http_seek
   };
 
 GRUB_MOD_INIT (http)
index 0ff8d01ff829e2b0708ad99ba46411ef99322b7d..0bde49322856df014d8ee9818f2eafcf57da54e0 100644 (file)
@@ -815,17 +815,17 @@ grub_net_poll_cards_idle_real (void)
 static grub_ssize_t
 grub_net_fs_read_real (grub_file_t file, char *buf, grub_size_t len)
 {
-  grub_net_t sock = file->device->net;
+  grub_net_t net = file->device->net;
   struct grub_net_buff *nb;
   char *ptr = buf;
   grub_size_t amount, total = 0;
   int try = 0;
   while (try <= 3)
     {
-      while (sock->packs.first)
+      while (net->packs.first)
        {
          try = 0;
-         nb = sock->packs.first->nb;
+         nb = net->packs.first->nb;
          amount = nb->tail - nb->data;
          if (amount > len)
            amount = len;
@@ -840,7 +840,7 @@ grub_net_fs_read_real (grub_file_t file, char *buf, grub_size_t len)
          if (amount == (grub_size_t) (nb->tail - nb->data))
            {
              grub_netbuff_free (nb);
-             grub_net_remove_packet (sock->packs.first);
+             grub_net_remove_packet (net->packs.first);
            }
          else
            nb->data += amount;
@@ -848,7 +848,7 @@ grub_net_fs_read_real (grub_file_t file, char *buf, grub_size_t len)
          if (!len)
            return total;
        }
-      if (!sock->eof)
+      if (!net->eof)
        {
          try++;
          grub_net_poll_cards (200);
@@ -856,39 +856,59 @@ grub_net_fs_read_real (grub_file_t file, char *buf, grub_size_t len)
       else
        return total;
     }
-    return total;
+  grub_error (GRUB_ERR_TIMEOUT, "timeout reading '%s'", net->name);
+  return -1;
+}
+
+static grub_off_t
+have_ahead (struct grub_file *file)
+{
+  grub_net_t net = file->device->net;
+  grub_off_t ret = net->offset;
+  struct grub_net_packet *pack;
+  for (pack = net->packs.first; pack; pack = pack->next)
+    ret += pack->nb->tail - pack->nb->data;
+  return ret;
 }
 
 static grub_err_t 
 grub_net_seek_real (struct grub_file *file, grub_off_t offset)
 {
-  grub_size_t len = offset - file->device->net->offset;
-
-  if (!len)
+  if (offset == file->device->net->offset)
     return GRUB_ERR_NONE;
 
-  if (file->device->net->offset > offset)
+  if (offset > file->device->net->offset)
     {
-      grub_err_t err;
-      while (file->device->net->packs.first)
+      if (!file->device->net->protocol->seek || have_ahead (file) >= offset)
        {
-         grub_netbuff_free (file->device->net->packs.first->nb);
-         grub_net_remove_packet (file->device->net->packs.first);
+         grub_net_fs_read_real (file, NULL,
+                                offset - file->device->net->offset);
+         return grub_errno;
        }
-      file->device->net->protocol->close (file);
-
-      file->device->net->packs.first = NULL;
-      file->device->net->packs.last = NULL;
-      file->device->net->offset = 0;
-      file->device->net->eof = 0;
-      err = file->device->net->protocol->open (file, file->device->net->name);
-      if (err)
-       return err;
-      len = offset;
+      return file->device->net->protocol->seek (file, offset);
     }
 
-  grub_net_fs_read_real (file, NULL, len);
-  return GRUB_ERR_NONE;
+  {
+    grub_err_t err;
+    if (file->device->net->protocol->seek)
+      return file->device->net->protocol->seek (file, offset);
+    while (file->device->net->packs.first)
+      {
+       grub_netbuff_free (file->device->net->packs.first->nb);
+       grub_net_remove_packet (file->device->net->packs.first);
+      }
+    file->device->net->protocol->close (file);
+
+    file->device->net->packs.first = NULL;
+    file->device->net->packs.last = NULL;
+    file->device->net->offset = 0;
+    file->device->net->eof = 0;
+    err = file->device->net->protocol->open (file, file->device->net->name);
+    if (err)
+      return err;
+    grub_net_fs_read_real (file, NULL, offset);
+    return grub_errno;
+  }
 }
 
 static grub_ssize_t
index 25e8ab4ed9c97dea2436ab9d19f9a7032b16f935..7f120ac1637d760d074a831d4b1290ae308a8c28 100644 (file)
@@ -56,7 +56,8 @@ struct grub_net_tcp_socket
   int in_port;
   int out_port;
   int errors;
-  int reseted;
+  int they_reseted;
+  int i_reseted;
   grub_uint32_t my_start_seq;
   grub_uint32_t my_cur_seq;
   grub_uint32_t their_start_seq;
@@ -153,7 +154,7 @@ error (grub_net_tcp_socket_t sock)
 {
   struct unacked *unack, *next;
 
-  if (sock->established && sock->error_hook)
+  if (sock->error_hook)
     sock->error_hook (sock, sock->hook_data);
 
   for (unack = sock->unack_first; unack; unack = next)
@@ -217,7 +218,8 @@ tcp_send (struct grub_net_buff *nb, grub_net_tcp_socket_t socket)
 }
 
 void
-grub_net_tcp_close (grub_net_tcp_socket_t sock)
+grub_net_tcp_close (grub_net_tcp_socket_t sock,
+                   int discard_received)
 {
   struct grub_net_buff *nb_fin;
   struct tcphdr *tcph_fin;
@@ -225,6 +227,9 @@ grub_net_tcp_close (grub_net_tcp_socket_t sock)
 
   sock->i_closed = 1;
 
+  if (discard_received != GRUB_NET_TCP_CONTINUE_RECEIVING)
+    sock->recv_hook = NULL;
+
   nb_fin = grub_netbuff_alloc (sizeof (*tcph_fin)
                               + GRUB_NET_OUR_IPV4_HEADER_SIZE
                               + GRUB_NET_MAX_LINK_HEADER_SIZE);
@@ -254,6 +259,8 @@ grub_net_tcp_close (grub_net_tcp_socket_t sock)
   tcph_fin->window = grub_cpu_to_be16 (0);
   tcph_fin->urgent = 0;
   err = tcp_send (nb_fin, sock);
+  if (discard_received == GRUB_NET_TCP_ABORT)
+    sock->i_reseted = 1;
   if (err)
     {
       grub_netbuff_free (nb_fin);
@@ -264,7 +271,7 @@ grub_net_tcp_close (grub_net_tcp_socket_t sock)
 }
 
 static void
-ack (grub_net_tcp_socket_t sock)
+ack_real (grub_net_tcp_socket_t sock, int res)
 {
   struct grub_net_buff *nb_ack;
   struct tcphdr *tcph_ack;
@@ -291,9 +298,18 @@ ack (grub_net_tcp_socket_t sock)
       return;
     }
   tcph_ack = (void *) nb_ack->data;
-  tcph_ack->ack = grub_cpu_to_be32 (sock->their_cur_seq);
-  tcph_ack->flags = grub_cpu_to_be16 ((5 << 12) | TCP_ACK);
-  tcph_ack->window = grub_cpu_to_be16 (sock->my_window);
+  if (res)
+    {
+      tcph_ack->ack = grub_cpu_to_be32 (0);
+      tcph_ack->flags = grub_cpu_to_be16 ((5 << 12) | TCP_RST);
+      tcph_ack->window = grub_cpu_to_be16 (0);
+    }
+  else
+    {
+      tcph_ack->ack = grub_cpu_to_be32 (sock->their_cur_seq);
+      tcph_ack->flags = grub_cpu_to_be16 ((5 << 12) | TCP_ACK);
+      tcph_ack->window = grub_cpu_to_be16 (sock->my_window);
+    }
   tcph_ack->urgent = 0;
   tcph_ack->src = grub_cpu_to_be16 (sock->in_port);
   tcph_ack->dst = grub_cpu_to_be16 (sock->out_port);
@@ -305,6 +321,18 @@ ack (grub_net_tcp_socket_t sock)
     }
 }
 
+static void
+ack (grub_net_tcp_socket_t sock)
+{
+  ack_real (sock, 0);
+}
+
+static void
+reset (grub_net_tcp_socket_t sock)
+{
+  ack_real (sock, 1);
+}
+
 void
 grub_net_tcp_retransmit (void)
 {
@@ -434,6 +462,7 @@ grub_net_tcp_accept (grub_net_tcp_socket_t sock,
   tcph->flags = grub_cpu_to_be16 ((5 << 12) | TCP_SYN | TCP_ACK);
   tcph->window = grub_cpu_to_be16 (sock->my_window);
   tcph->urgent = 0;
+  sock->established = 1;
   tcp_socket_register (sock);
   err = tcp_send (nb_ack, sock);
   if (err)
@@ -555,7 +584,7 @@ grub_net_tcp_open (char *server,
     {
       grub_list_remove (GRUB_AS_LIST_P (&tcp_sockets),
                        GRUB_AS_LIST (socket));
-      if (socket->reseted)
+      if (socket->they_reseted)
        grub_error (GRUB_ERR_NET_PORT_CLOSED, "port closed");
       else
        grub_error (GRUB_ERR_NET_NO_ANSWER, "no answer");
@@ -693,7 +722,7 @@ grub_net_recv_tcp_packet (struct grub_net_buff *nb,
 
     if (grub_be_to_cpu16 (tcph->flags) & TCP_RST)
       {
-       sock->reseted = 1;
+       sock->they_reseted = 1;
 
        error (sock);
 
@@ -725,7 +754,7 @@ grub_net_recv_tcp_packet (struct grub_net_buff *nb,
            grub_free (unack);
          }
        sock->unack_first = unack;
-       if (!sock->unack_last)
+       if (!sock->unack_first)
          sock->unack_last = NULL;
       }
 
@@ -735,6 +764,12 @@ grub_net_recv_tcp_packet (struct grub_net_buff *nb,
        grub_netbuff_free (nb);
        return GRUB_ERR_NONE;
       }
+    if (sock->i_reseted)
+      {
+       reset (sock);
+       grub_netbuff_free (nb);
+       return GRUB_ERR_NONE;
+      }
 
     err = grub_priority_queue_push (sock->pq, &nb);
     if (err)
index 25578431527a20f871c6e1b230fd52bb8801d4ed..ac011520a38849f8e8c1f5745c8c490a7ff39ba4 100644 (file)
@@ -204,6 +204,7 @@ struct grub_net_app_protocol
                     int (*hook) (const char *filename,
                                  const struct grub_dirhook_info *info));
   grub_err_t (*open) (struct grub_file *file, const char *filename);
+  grub_err_t (*seek) (struct grub_file *file, grub_off_t off);
   grub_err_t (*close) (struct grub_file *file);
 };
 
index 0ac7819bc8c81c9c1677d71f77b23528ba6e2014..b25ceff1bf53d5aba402aca9ac2e571891e0cdbd 100644 (file)
@@ -53,8 +53,15 @@ grub_net_send_tcp_packet (const grub_net_tcp_socket_t socket,
                          struct grub_net_buff *nb,
                          int push);
 
+enum
+  {
+    GRUB_NET_TCP_CONTINUE_RECEIVING,
+    GRUB_NET_TCP_DISCARD,
+    GRUB_NET_TCP_ABORT
+  };
+
 void
-grub_net_tcp_close (grub_net_tcp_socket_t sock);
+grub_net_tcp_close (grub_net_tcp_socket_t sock, int discard_received);
 
 grub_err_t
 grub_net_tcp_accept (grub_net_tcp_socket_t sock,