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