Imported Debian patch 0.1.5c-2
[samtools.git] / knetfile.c
1 #include <time.h>
2 #include <stdio.h>
3 #include <netdb.h>
4 #include <ctype.h>
5 #include <stdlib.h>
6 #include <string.h>
7 #include <unistd.h>
8 #include <sys/types.h>
9 #include <arpa/inet.h>
10 #include <sys/socket.h>
11 #include "knetfile.h"
12
13 static int socket_wait(int fd, int is_read)
14 {
15         fd_set fds, *fdr = 0, *fdw = 0;
16         struct timeval tv;
17         int ret;
18         tv.tv_sec = 5; tv.tv_usec = 0; // 5 seconds time out
19         FD_ZERO(&fds);
20         FD_SET(fd, &fds);
21         if (is_read) fdr = &fds;
22         else fdw = &fds;
23         ret = select(fd+1, fdr, fdw, 0, &tv);
24         if (ret == -1) perror("select");
25         return ret;
26 }
27
28 static int kftp_get_response(knetFile *ftp)
29 {
30         unsigned char c;
31         int n = 0;
32         char *p;
33         if (socket_wait(ftp->ctrl_fd, 1) <= 0) return 0;
34         while (read(ftp->ctrl_fd, &c, 1)) { // FIXME: this is *VERY BAD* for unbuffered I/O
35                 //fputc(c, stderr);
36                 if (n >= ftp->max_response) {
37                         ftp->max_response = ftp->max_response? ftp->max_response<<1 : 256;
38                         ftp->response = realloc(ftp->response, ftp->max_response);
39                 }
40                 ftp->response[n++] = c;
41                 if (c == '\n') {
42                         if (n >= 4 && isdigit(ftp->response[0]) && isdigit(ftp->response[1]) && isdigit(ftp->response[2])
43                                 && ftp->response[3] != '-') break;
44                         n = 0;
45                         continue;
46                 }
47         }
48         if (n < 2) return -1;
49         ftp->response[n-2] = 0;
50         return strtol(ftp->response, &p, 0);
51 }
52
53 static int kftp_send_cmd(knetFile *ftp, const char *cmd, int is_get)
54 {
55         if (socket_wait(ftp->ctrl_fd, 0) <= 0) return -1; // socket is not ready for writing
56         write(ftp->ctrl_fd, cmd, strlen(cmd));
57         return is_get? kftp_get_response(ftp) : 0;
58 }
59
60 static int kftp_pasv_prep(knetFile *ftp)
61 {
62         char *p;
63         int v[6];
64         kftp_send_cmd(ftp, "PASV\r\n", 1);
65         for (p = ftp->response; *p && *p != '('; ++p);
66         if (*p != '(') return -1;
67         ++p;
68         sscanf(p, "%d,%d,%d,%d,%d,%d", &v[0], &v[1], &v[2], &v[3], &v[4], &v[5]);
69         memcpy(ftp->pasv_ip, v, 4 * sizeof(int));
70         ftp->pasv_port = (v[4]<<8&0xff00) + v[5];
71         return 0;
72 }
73
74 static int kftp_pasv_connect(knetFile *ftp)
75 {
76 #define __err_pasv_connect(func) do { perror(func); freeaddrinfo(res); return -1; } while (0)
77
78         struct addrinfo hints, *res;
79         struct linger lng = { 0, 0 };
80         int on = 1;
81         char host[80], port[10];
82
83         if (ftp->pasv_port == 0) {
84                 fprintf(stderr, "[kftp_pasv_connect] kftp_pasv_prep() is not called before hand.\n");
85                 return -1;
86         }
87         memset(&hints, 0, sizeof(struct addrinfo));
88         hints.ai_family = AF_UNSPEC;
89         hints.ai_socktype = SOCK_STREAM;
90         sprintf(host, "%d.%d.%d.%d", ftp->pasv_ip[0], ftp->pasv_ip[1], ftp->pasv_ip[2], ftp->pasv_ip[3]);
91         sprintf(port, "%d", ftp->pasv_port);
92         if (getaddrinfo(host, port, &hints, &res) != 0) { perror("getaddrinfo"); return -1; }
93         if ((ftp->fd = socket(res->ai_family, res->ai_socktype, res->ai_protocol)) == -1) __err_pasv_connect("socket");
94         if (setsockopt(ftp->fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1) __err_pasv_connect("setsockopt");
95         if (setsockopt(ftp->fd, SOL_SOCKET, SO_LINGER, &lng, sizeof(lng)) == -1) __err_pasv_connect("setsockopt");
96         if (connect(ftp->fd, res->ai_addr, res->ai_addrlen) != 0) __err_pasv_connect("connect");
97         freeaddrinfo(res);
98         return 0;
99 }
100
101 int kftp_connect(knetFile *ftp)
102 {
103 #define __err_connect(func) do { perror(func); return -1; } while (0)
104
105         int on = 1;
106         { // open socket
107                 struct addrinfo hints, *res;
108                 memset(&hints, 0, sizeof(struct addrinfo));
109                 hints.ai_family = AF_UNSPEC;
110                 hints.ai_socktype = SOCK_STREAM;
111                 if (getaddrinfo(ftp->host, "21", &hints, &res) != 0) __err_connect("getaddrinfo");
112                 if ((ftp->ctrl_fd = socket(res->ai_family, res->ai_socktype, res->ai_protocol)) == -1) __err_connect("socket");
113                 if (setsockopt(ftp->ctrl_fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1) __err_connect("setsockopt");
114                 if (connect(ftp->ctrl_fd, res->ai_addr, res->ai_addrlen) != 0) __err_connect("connect");
115                 freeaddrinfo(res);
116                 kftp_get_response(ftp);
117         }
118         { // login
119                 kftp_send_cmd(ftp, "USER anonymous\r\n", 1);
120                 kftp_send_cmd(ftp, "PASS kftp@\r\n", 1);
121                 kftp_send_cmd(ftp, "TYPE I\r\n", 1);
122         }
123         return 0;
124 }
125
126 int kftp_reconnect(knetFile *ftp)
127 {
128         if (ftp->ctrl_fd >= 0) {
129                 close(ftp->ctrl_fd);
130                 ftp->ctrl_fd = -1;
131         }
132         close(ftp->fd);
133         return kftp_connect(ftp);
134 }
135
136 // initialize ->type, ->host and ->retr
137 knetFile *kftp_parse_url(const char *fn, const char *mode)
138 {
139         knetFile *fp;
140         char *p;
141         int l;
142         if (strstr(fn, "ftp://") != fn) return 0;
143         for (p = (char*)fn + 6; *p && *p != '/'; ++p);
144         if (*p != '/') return 0;
145         l = p - fn - 6;
146         fp = calloc(1, sizeof(knetFile));
147         fp->type = KNF_TYPE_FTP;
148         fp->fd = -1;
149         fp->host = calloc(l + 1, 1);
150         if (strchr(mode, 'c')) fp->no_reconnect = 1;
151         strncpy(fp->host, fn + 6, l);
152         fp->retr = calloc(strlen(p) + 8, 1);
153         sprintf(fp->retr, "RETR %s\r\n", p);
154         fp->seek_offset = -1;
155         return fp;
156 }
157 // place ->fd at offset off
158 int kftp_connect_file(knetFile *fp)
159 {
160         int ret;
161         if (fp->fd >= 0) {
162                 close(fp->fd);
163                 if (fp->no_reconnect) kftp_get_response(fp);
164         }
165         kftp_pasv_prep(fp);
166         if (fp->offset) {
167                 char tmp[32];
168                 sprintf(tmp, "REST %lld\r\n", (long long)fp->offset);
169                 kftp_send_cmd(fp, tmp, 1);
170         }
171         kftp_send_cmd(fp, fp->retr, 0);
172         kftp_pasv_connect(fp);
173         ret = kftp_get_response(fp);
174         if (ret != 150) {
175                 fprintf(stderr, "[kftp_connect_file] %s\n", fp->response);
176                 close(fp->fd);
177                 fp->fd = -1;
178                 return -1;
179         }
180         fp->is_ready = 1;
181         return 0;
182 }
183
184 knetFile *knet_open(const char *fn, const char *mode)
185 {
186         knetFile *fp = 0;
187         if (mode[0] != 'r') {
188                 fprintf(stderr, "[kftp_open] only mode \"r\" is supported.\n");
189                 return 0;
190         }
191         if (strstr(fn, "ftp://") == fn) {
192                 fp = kftp_parse_url(fn, mode);
193                 if (fp == 0) return 0;
194                 if (kftp_connect(fp) == -1) {
195                         knet_close(fp);
196                         return 0;
197                 }
198                 kftp_connect_file(fp);
199                 if (fp->fd < 0) {
200                         knet_close(fp);
201                         return 0;
202                 }
203         } else {
204                 int fd = open(fn, O_RDONLY);
205                 if (fd == -1) {
206                         perror("open");
207                         return 0;
208                 }
209                 fp = (knetFile*)calloc(1, sizeof(knetFile));
210                 fp->type = KNF_TYPE_LOCAL;
211                 fp->fd = fd;
212         }
213         return fp;
214 }
215
216 knetFile *knet_dopen(int fd, const char *mode)
217 {
218         knetFile *fp = (knetFile*)calloc(1, sizeof(knetFile));
219         fp->type = KNF_TYPE_LOCAL;
220         fp->fd = fd;
221         return fp;
222 }
223
224 off_t knet_read(knetFile *fp, void *buf, off_t len)
225 {
226         off_t l = 0;
227         if (fp->fd < 0) return 0;
228         if (fp->type == KNF_TYPE_LOCAL) {
229                 off_t rest = len, curr;
230                 while (rest) {
231                         curr = read(fp->fd, buf + l, rest);
232                         if (curr == 0) break;
233                         l += curr; rest -= curr;
234                 }
235                 fp->offset += l;
236         } else {
237                 off_t rest = len, curr;
238                 if (fp->is_ready == 0) {
239                         if (!fp->no_reconnect) kftp_reconnect(fp);
240                         kftp_connect_file(fp);
241                         fp->is_ready = 1;
242                 }
243                 while (rest) {
244                         if (socket_wait(fp->fd, 1) <= 0) break; // socket is not ready for reading
245                         curr = read(fp->fd, buf + l, rest);
246                         if (curr == 0) break; // FIXME: end of file or bad network? I do not know...
247                         l += curr; rest -= curr;
248                 }
249                 fp->offset += l;
250         }
251         return l;
252 }
253
254 int knet_seek(knetFile *fp, off_t off, int whence)
255 {
256         if (fp->type == KNF_TYPE_LOCAL) {
257                 if (lseek(fp->fd, off, whence) == -1) {
258                         perror("lseek");
259                         return -1;
260                 }
261                 fp->offset = off;
262                 return 0;
263         }
264         if (fp->type == KNF_TYPE_FTP) {
265                 if (whence != SEEK_SET) { // FIXME: we can surely allow SEEK_CUR and SEEK_END in future
266                         fprintf(stderr, "[knet_seek] only SEEK_SET is supported for FTP. Offset is unchanged.\n");
267                         return -1;
268                 }
269                 fp->offset = off;
270                 fp->is_ready = 0;
271                 return 0;
272         }
273         return -1;
274 }
275
276 int knet_close(knetFile *fp)
277 {
278         if (fp == 0) return 0;
279         if (fp->ctrl_fd >= 0) close(fp->ctrl_fd);
280         if (fp->fd >= 0) close(fp->fd);
281         free(fp->response); free(fp->retr); free(fp->host);
282         free(fp);
283         return 0;
284 }
285
286 #ifdef KNETFILE_MAIN
287 int main(void)
288 {
289         char buf[256];
290         knetFile *fp;
291 //      fp = knet_open("ftp://ftp.ncbi.nih.gov/1000genomes/ftp/data/NA12878/alignment/NA12878.chrom6.SLX.SRP000032.2009_06.bam", "r"); knet_seek(fp, 2500000000ll, SEEK_SET);
292         fp = knet_open("ftp://ftp.sanger.ac.uk/pub4/treefam/tmp/index.shtml", "r"); knet_seek(fp, 2000, SEEK_SET);
293 //      fp = knet_open("knetfile.c", "r"); knet_seek(fp, 2000, SEEK_SET);
294         knet_read(fp, buf, 255);
295         buf[255] = 0;
296         printf("%s\n", buf);
297         knet_close(fp);
298         return 0;
299 }
300 #endif