Mention ‘SAMtools’ in libbam-dev's description, to make it easier to find with apt...
[samtools.git] / knetfile.c
index e110aa720f6577e7f8dee34fddc32cb6220d7154..af091465862c238aefeb3d0562b03f75320b6c1d 100644 (file)
@@ -1,6 +1,7 @@
 /* The MIT License
 
-   Copyright (c) 2008 Genome Research Ltd (GRL).
+   Copyright (c) 2008 by Genome Research Ltd (GRL).
+                 2010 by Attractive Chaos <attractor@live.co.uk>
 
    Permission is hereby granted, free of charge, to any person obtaining
    a copy of this software and associated documentation files (the
    SOFTWARE.
 */
 
-/* Contact: Heng Li <lh3@sanger.ac.uk> */
-
 /* Probably I will not do socket programming in the next few years and
    therefore I decide to heavily annotate this file, for Linux and
-   Windows as well.  -lh3 */
+   Windows as well.  -ac */
 
 #include <time.h>
 #include <stdio.h>
 #include <ctype.h>
 #include <stdlib.h>
 #include <string.h>
+#include <errno.h>
 #include <unistd.h>
 #include <sys/types.h>
 
-#ifdef _WIN32
-#include <winsock.h>
-#else
+#ifndef _WIN32
 #include <netdb.h>
 #include <arpa/inet.h>
 #include <sys/socket.h>
@@ -70,7 +68,14 @@ static int socket_wait(int fd, int is_read)
        if (is_read) fdr = &fds;
        else fdw = &fds;
        ret = select(fd+1, fdr, fdw, 0, &tv);
+#ifndef _WIN32
        if (ret == -1) perror("select");
+#else
+       if (ret == 0)
+               fprintf(stderr, "select time-out\n");
+       else if (ret == SOCKET_ERROR)
+               fprintf(stderr, "select: %d\n", WSAGetLastError());
+#endif
        return ret;
 }
 
@@ -84,7 +89,7 @@ static int socket_connect(const char *host, const char *port)
 
        int on = 1, fd;
        struct linger lng = { 0, 0 };
-       struct addrinfo hints, *res;
+       struct addrinfo hints, *res = 0;
        memset(&hints, 0, sizeof(struct addrinfo));
        hints.ai_family = AF_UNSPEC;
        hints.ai_socktype = SOCK_STREAM;
@@ -103,16 +108,28 @@ static int socket_connect(const char *host, const char *port)
 }
 #else
 /* MinGW's printf has problem with "%lld" */
-char *uint64tostr(char *buf, uint64_t x)
+char *int64tostr(char *buf, int64_t x)
 {
-       int i, cnt;
-       for (i = 0; x; x /= 10) buf[i++] = '0' + x%10;
+       int cnt;
+       int i = 0;
+       do {
+               buf[i++] = '0' + x % 10;
+               x /= 10;
+       } while (x);
        buf[i] = 0;
        for (cnt = i, i = 0; i < cnt/2; ++i) {
                int c = buf[i]; buf[i] = buf[cnt-i-1]; buf[cnt-i-1] = c;
        }
        return buf;
 }
+
+int64_t strtoint64(const char *buf)
+{
+       int64_t x;
+       for (x = 0; *buf != '\0'; ++buf)
+               x = x * 10 + ((int64_t) *buf - 48);
+       return x;
+}
 /* In windows, the first thing is to establish the TCP connection. */
 int knet_win32_init()
 {
@@ -129,7 +146,11 @@ void knet_win32_destroy()
  * non-Windows OS, I do not use this one. */
 static SOCKET socket_connect(const char *host, const char *port)
 {
-#define __err_connect(func) do { perror(func); return -1; } while (0)
+#define __err_connect(func)                                                                            \
+       do {                                                                                                            \
+               fprintf(stderr, "%s: %d\n", func, WSAGetLastError());   \
+               return -1;                                                                                              \
+       } while (0)
 
        int on = 1;
        SOCKET fd;
@@ -182,7 +203,11 @@ static off_t my_netread(int fd, void *buf, off_t len)
 
 static int kftp_get_response(knetFile *ftp)
 {
+#ifndef _WIN32
        unsigned char c;
+#else
+       char c;
+#endif
        int n = 0;
        char *p;
        if (socket_wait(ftp->ctrl_fd, 1) <= 0) return 0;
@@ -259,10 +284,11 @@ int kftp_reconnect(knetFile *ftp)
                ftp->ctrl_fd = -1;
        }
        netclose(ftp->fd);
+       ftp->fd = -1;
        return kftp_connect(ftp);
 }
 
-// initialize ->type, ->host and ->retr
+// initialize ->type, ->host, ->retr and ->size
 knetFile *kftp_parse_url(const char *fn, const char *mode)
 {
        knetFile *fp;
@@ -283,25 +309,42 @@ knetFile *kftp_parse_url(const char *fn, const char *mode)
        strncpy(fp->host, fn + 6, l);
        fp->retr = calloc(strlen(p) + 8, 1);
        sprintf(fp->retr, "RETR %s\r\n", p);
-       fp->seek_offset = -1;
+    fp->size_cmd = calloc(strlen(p) + 8, 1);
+    sprintf(fp->size_cmd, "SIZE %s\r\n", p);
+       fp->seek_offset = 0;
        return fp;
 }
 // place ->fd at offset off
 int kftp_connect_file(knetFile *fp)
 {
        int ret;
+       long long file_size;
        if (fp->fd != -1) {
                netclose(fp->fd);
                if (fp->no_reconnect) kftp_get_response(fp);
        }
        kftp_pasv_prep(fp);
-       if (fp->offset) {
+    kftp_send_cmd(fp, fp->size_cmd, 1);
+#ifndef _WIN32
+    if ( sscanf(fp->response,"%*d %lld", &file_size) != 1 )
+    {
+        fprintf(stderr,"[kftp_connect_file] %s\n", fp->response);
+        return -1;
+    }
+#else
+       const char *p = fp->response;
+       while (*p != ' ') ++p;
+       while (*p < '0' || *p > '9') ++p;
+       file_size = strtoint64(p);
+#endif
+       fp->file_size = file_size;
+       if (fp->offset>=0) {
                char tmp[32];
 #ifndef _WIN32
                sprintf(tmp, "REST %lld\r\n", (long long)fp->offset);
 #else
                strcpy(tmp, "REST ");
-               uint64tostr(tmp + 5, fp->offset);
+               int64tostr(tmp + 5, fp->offset);
                strcat(tmp, "\r\n");
 #endif
                kftp_send_cmd(fp, tmp, 1);
@@ -319,6 +362,7 @@ int kftp_connect_file(knetFile *fp)
        return 0;
 }
 
+
 /**************************
  * HTTP specific routines *
  **************************/
@@ -354,7 +398,7 @@ knetFile *khttp_parse_url(const char *fn, const char *mode)
        }
        fp->type = KNF_TYPE_HTTP;
        fp->ctrl_fd = fp->fd = -1;
-       fp->seek_offset = -1;
+       fp->seek_offset = 0;
        return fp;
 }
 
@@ -366,8 +410,7 @@ int khttp_connect_file(knetFile *fp)
        fp->fd = socket_connect(fp->host, fp->port);
        buf = calloc(0x10000, 1); // FIXME: I am lazy... But in principle, 64KB should be large enough.
        l += sprintf(buf + l, "GET %s HTTP/1.0\r\nHost: %s\r\n", fp->path, fp->http_host);
-       if (fp->offset)
-               l += sprintf(buf + l, "Range: bytes=%lld-\r\n", (long long)fp->offset);
+    l += sprintf(buf + l, "Range: bytes=%lld-\r\n", (long long)fp->offset);
        l += sprintf(buf + l, "\r\n");
        netwrite(fp->fd, buf, l);
        l = 0;
@@ -383,7 +426,7 @@ int khttp_connect_file(knetFile *fp)
                return -1;
        }
        ret = strtol(buf + 8, &p, 0); // HTTP return code
-       if (ret == 200 && fp->offset) { // 200 (complete result); then skip beginning of the file
+       if (ret == 200 && fp->offset>0) { // 200 (complete result); then skip beginning of the file
                off_t rest = fp->offset;
                while (rest) {
                        off_t l = rest < 0x10000? rest : 0x10000;
@@ -473,7 +516,10 @@ off_t knet_read(knetFile *fp, void *buf, off_t len)
        if (fp->type == KNF_TYPE_LOCAL) { // on Windows, the following block is necessary; not on UNIX
                off_t rest = len, curr;
                while (rest) {
-                       curr = read(fp->fd, buf + l, rest);
+                       do {
+                               curr = read(fp->fd, buf + l, rest);
+                       } while (curr < 0 && EINTR == errno);
+                       if (curr < 0) return -1;
                        if (curr == 0) break;
                        l += curr; rest -= curr;
                }
@@ -482,7 +528,7 @@ off_t knet_read(knetFile *fp, void *buf, off_t len)
        return l;
 }
 
-int knet_seek(knetFile *fp, off_t off, int whence)
+off_t knet_seek(knetFile *fp, int64_t off, int whence)
 {
        if (whence == SEEK_SET && off == fp->offset) return 0;
        if (fp->type == KNF_TYPE_LOCAL) {
@@ -490,20 +536,40 @@ int knet_seek(knetFile *fp, off_t off, int whence)
                 * while fseek() returns zero on success. */
                off_t offset = lseek(fp->fd, off, whence);
                if (offset == -1) {
-                       perror("lseek");
+            // Be silent, it is OK for knet_seek to fail when the file is streamed
+            // fprintf(stderr,"[knet_seek] %s\n", strerror(errno));
                        return -1;
                }
                fp->offset = offset;
                return 0;
-       } else if (fp->type == KNF_TYPE_FTP || fp->type == KNF_TYPE_HTTP) {
-               if (whence != SEEK_SET) { // FIXME: we can surely allow SEEK_CUR and SEEK_END in future
-                       fprintf(stderr, "[knet_seek] only SEEK_SET is supported for FTP/HTTP. Offset is unchanged.\n");
+       }
+    else if (fp->type == KNF_TYPE_FTP) 
+    {
+        if (whence==SEEK_CUR)
+            fp->offset += off;
+        else if (whence==SEEK_SET)
+            fp->offset = off;
+        else if ( whence==SEEK_END)
+            fp->offset = fp->file_size+off;
+               fp->is_ready = 0;
+               return 0;
+       } 
+    else if (fp->type == KNF_TYPE_HTTP) 
+    {
+               if (whence == SEEK_END) { // FIXME: can we allow SEEK_END in future?
+                       fprintf(stderr, "[knet_seek] SEEK_END is not supported for HTTP. Offset is unchanged.\n");
+                       errno = ESPIPE;
                        return -1;
                }
-               fp->offset = off;
+        if (whence==SEEK_CUR)
+            fp->offset += off;
+        else if (whence==SEEK_SET)
+            fp->offset = off;
                fp->is_ready = 0;
                return 0;
        }
+       errno = EINVAL;
+    fprintf(stderr,"[knet_seek] %s\n", strerror(errno));
        return -1;
 }