]> git.donarmstrong.com Git - samtools.git/blob - knetfile.c
* open a file at FTP
[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]) && ftp->response[3] != '-') break;
26                         n = 0;
27                         continue;
28                 }
29         }
30         if (n < 2) return -1;
31         ftp->response[n-2] = 0;
32         return strtol(ftp->response, &p, 0);
33 }
34
35 static int kftp_send_cmd(knetFile *ftp, const char *cmd, int is_get)
36 {
37         write(ftp->ctrl_fd, cmd, strlen(cmd));
38         return is_get? kftp_get_response(ftp) : 0;
39 }
40
41 static int kftp_pasv_prep(knetFile *ftp)
42 {
43         char *p;
44         int v[6];
45         kftp_send_cmd(ftp, "PASV\r\n", 1);
46         for (p = ftp->response; *p && *p != '('; ++p);
47         if (*p != '(') return -1;
48         ++p;
49         sscanf(p, "%d,%d,%d,%d,%d,%d", &v[0], &v[1], &v[2], &v[3], &v[4], &v[5]);
50         memcpy(ftp->pasv_ip, v, 4 * sizeof(int));
51         ftp->pasv_port = (v[4]<<8&0xff00) + v[5];
52         return 0;
53 }
54
55 static int kftp_pasv_connect(knetFile *ftp)
56 {
57 #define __err_pasv_connect(func) do { perror(func); freeaddrinfo(res); return -1; } while (0)
58
59         struct addrinfo hints, *res;
60         struct linger lng = { 0, 0 };
61         int on;
62         char host[80], port[10];
63
64         if (ftp->pasv_port == 0) {
65                 fprintf(stderr, "[kftp_pasv_connect] kftp_pasv_prep() is not called before hand.\n");
66                 return -1;
67         }
68         memset(&hints, 0, sizeof(struct addrinfo));
69         hints.ai_family = AF_UNSPEC;
70         hints.ai_socktype = SOCK_STREAM;
71         sprintf(host, "%d.%d.%d.%d", ftp->pasv_ip[0], ftp->pasv_ip[1], ftp->pasv_ip[2], ftp->pasv_ip[3]);
72         sprintf(port, "%d", ftp->pasv_port);
73         if (getaddrinfo(host, port, &hints, &res) != 0) { perror("getaddrinfo"); return -1; }
74         if ((ftp->fd = socket(res->ai_family, res->ai_socktype, res->ai_protocol)) == -1) __err_pasv_connect("socket");
75         if (setsockopt(ftp->fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1) __err_pasv_connect("setsockopt");
76         if (setsockopt(ftp->fd, SOL_SOCKET, SO_LINGER, &lng, sizeof(lng)) == -1) __err_pasv_connect("setsockopt");
77         if (connect(ftp->fd, res->ai_addr, res->ai_addrlen) != 0) __err_pasv_connect("connect");
78         freeaddrinfo(res);
79         return 0;
80 }
81
82 int kftp_connect(knetFile *ftp)
83 {
84 #define __err_connect(func) do { perror(func); return -1; } while (0)
85
86         int on;
87         { // open socket
88                 struct addrinfo hints, *res;
89                 memset(&hints, 0, sizeof(struct addrinfo));
90                 hints.ai_family = AF_UNSPEC;
91                 hints.ai_socktype = SOCK_STREAM;
92                 if (getaddrinfo(ftp->host, "21", &hints, &res) != 0) __err_connect("getaddrinfo");
93                 if ((ftp->ctrl_fd = socket(res->ai_family, res->ai_socktype, res->ai_protocol)) == -1) __err_connect("socket");
94                 if (setsockopt(ftp->ctrl_fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1) __err_connect("setsockopt");
95                 if (connect(ftp->ctrl_fd, res->ai_addr, res->ai_addrlen) != 0) __err_connect("connect");
96                 freeaddrinfo(res);
97                 kftp_get_response(ftp);
98         }
99         { // login
100                 kftp_send_cmd(ftp, "USER anonymous\r\n", 1);
101                 kftp_send_cmd(ftp, "PASS kftp@\r\n", 1);
102                 kftp_send_cmd(ftp, "TYPE I\r\n", 1);
103         }
104         return 0;
105 }
106
107 int kftp_reconnect(knetFile *ftp)
108 {
109         if (ftp->ctrl_fd) {
110                 close(ftp->ctrl_fd);
111                 ftp->ctrl_fd = 0;
112         }
113         close(ftp->fd);
114         return kftp_connect(ftp);
115 }
116
117 // initialize ->type, ->host and ->retr
118 knetFile *kftp_prep(const char *fn, const char *mode)
119 {
120         knetFile *fp;
121         char *p;
122         int l;
123         if (strstr(fn, "ftp://") != fn) return 0;
124         for (p = (char*)fn + 6; *p && *p != '/'; ++p);
125         if (*p != '/') return 0;
126         l = p - fn - 6;
127         fp = calloc(1, sizeof(knetFile));
128         fp->type = KNF_TYPE_FTP;
129         fp->host = calloc(l + 1, 1);
130         if (strchr(mode, 'c')) fp->no_reconnect = 1;
131         strncpy(fp->host, fn + 6, l);
132         fp->retr = calloc(strlen(p) + 7, 1);
133         sprintf(fp->retr, "RETR %s\r\n", p);
134         return fp;
135 }
136 // place ->fd at offset off
137 int kftp_connect_file(knetFile *fp, off_t off)
138 {
139         if (fp->fd) {
140                 close(fp->fd);
141                 if (fp->no_reconnect) kftp_get_response(fp);
142         }
143         kftp_pasv_prep(fp);
144         if (off) {
145                 char tmp[32];
146                 sprintf(tmp, "REST %lld\r\n", (long long)off);
147                 kftp_send_cmd(fp, tmp, 1);
148         }
149         kftp_send_cmd(fp, fp->retr, 0);
150         kftp_pasv_connect(fp);
151         kftp_get_response(fp);
152         fp->offset = off;
153         return 0;
154 }
155
156 knetFile *knet_open(const char *fn, const char *mode)
157 {
158         knetFile *fp = 0;
159         if (mode[0] != 'r') {
160                 fprintf(stderr, "[kftp_open] only mode \"r\" is supported.\n");
161                 return 0;
162         }
163         if (strstr(fn, "ftp://") == fn) {
164                 fp = kftp_prep(fn, mode);
165                 if (fp == 0) return 0;
166                 if (kftp_connect(fp) == -1) {
167                         knet_close(fp);
168                         return 0;
169                 }
170                 kftp_connect_file(fp, 0);
171         } else {
172                 int fd = open(fn, O_RDONLY);
173                 if (fd == -1) {
174                         perror("open");
175                         return 0;
176                 }
177                 fp = (knetFile*)calloc(1, sizeof(knetFile));
178                 fp->type = KNF_TYPE_LOCAL;
179                 fp->fd = fd;
180         }
181         return fp;
182 }
183
184 off_t knet_read(knetFile *fp, void *buf, off_t len)
185 {
186         off_t l = read(fp->fd, buf, len);
187         fp->offset += l;
188         return l;
189 }
190
191 off_t knet_seek(knetFile *fp, off_t off, int whence)
192 {
193         if (fp->type == KNF_TYPE_LOCAL) {
194                 fp->offset = lseek(fp->fd, off, whence);
195                 return fp->offset;
196         }
197         if (fp->type == KNF_TYPE_FTP) {
198                 if (whence != SEEK_SET) {
199                         fprintf(stderr, "[knet_seek] only SEEK_SET is supported for FTP. Offset is unchanged.\n");
200                         return -1;
201                 }
202                 if (!fp->no_reconnect) kftp_reconnect(fp);
203                 kftp_connect_file(fp, off);
204                 return fp->offset;
205         }
206         return 0;
207 }
208
209 int knet_close(knetFile *fp)
210 {
211         if (fp == 0) return 0;
212         if (fp->ctrl_fd > 0) close(fp->ctrl_fd);
213         if (fp->fd > 0) close(fp->fd);
214         free(fp->response); free(fp->retr); free(fp->host);
215         free(fp);
216         return 0;
217 }
218
219 #ifdef KNETFILE_MAIN
220 int main(void)
221 {
222         char buf[256];
223         knetFile *fp;
224 //      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);
225 //      fp = knet_open("ftp://ftp.sanger.ac.uk/pub4/treefam/tmp/index.shtml", "r"); knet_seek(fp, 2000, SEEK_SET);
226         fp = knet_open("knetfile.c", "r"); knet_seek(fp, 2000, SEEK_SET);
227         knet_read(fp, buf, 255);
228         buf[255] = 0;
229         printf("%s\n", buf);
230         knet_close(fp);
231         return 0;
232 }
233 #endif