1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <stddef.h>
4 #include <unistd.h>
5 #include <string.h>
6 #include <time.h>
7 #include <errno.h>
8 #include <sys/select.h>
9
10 #include <curl/curl.h>
11 #include <curl/easy.h>
12
13 #include "curl.h"
14
15 /* curl globals */
16 static CURLM *curlm;
17 static fd_set rd, wr, ex;
18
19 /* my globals */
20 static int url_debug = 0;
21 static int url_timeout = 30;
22
23 /* my structs */
24 struct iobuf {
25 off_t start;
26 size_t size;
27 char *data;
28 };
29
30 struct url_state {
31 char *path;
32 CURL *curl;
33 char errmsg[CURL_ERROR_SIZE];
34 off_t curl_pos;
35 off_t buf_pos;
36 struct iobuf buf;
37 int eof;
38 };
39
40 /* ---------------------------------------------------------------------- */
41 /* curl stuff */
42
curl_init(void)43 static void __attribute__ ((constructor)) curl_init(void)
44 {
45 curl_global_init(CURL_GLOBAL_ALL);
46 curlm = curl_multi_init();
47 }
48
curl_fini(void)49 static void __attribute__ ((destructor)) curl_fini(void)
50 {
51 curl_multi_cleanup(curlm);
52 curl_global_cleanup();
53 }
54
curl_free_buffer(struct iobuf * buf)55 static void curl_free_buffer(struct iobuf *buf)
56 {
57 if (buf->data) {
58 free(buf->data);
59 memset(buf,0,sizeof(*buf));
60 }
61 }
62
63 /* CURLOPT_WRITEFUNCTION */
curl_write(void * data,size_t size,size_t nmemb,void * handle)64 static int curl_write(void *data, size_t size, size_t nmemb, void *handle)
65 {
66 struct url_state *h = handle;
67
68 curl_free_buffer(&h->buf);
69 h->buf.start = h->curl_pos;
70 h->buf.size = size * nmemb;
71 h->buf.data = malloc(h->buf.size);
72 memcpy(h->buf.data, data, h->buf.size);
73 if (url_debug)
74 fprintf(stderr," put %5d @ %5d\n",
75 (int)h->buf.size, (int)h->buf.start);
76
77 h->curl_pos += h->buf.size;
78 return h->buf.size;
79 }
80
81 /* do transfers */
curl_xfer(struct url_state * h)82 static int curl_xfer(struct url_state *h)
83 {
84 CURLMcode rc;
85 struct timeval tv;
86 int count, maxfd;
87
88 FD_ZERO(&rd);
89 FD_ZERO(&wr);
90 FD_ZERO(&ex);
91 maxfd = -1;
92 rc = curl_multi_fdset(curlm, &rd, &wr, &ex, &maxfd);
93 if (CURLM_OK != rc) {
94 fprintf(stderr,"curl_multi_fdset: %d %s\n",rc,h->errmsg);
95 return -1;
96 }
97 if (-1 == maxfd) {
98 /* wait 0.1 sec */
99 if (url_debug)
100 fprintf(stderr,"wait 0.01 sec\n");
101 tv.tv_sec = 0;
102 tv.tv_usec = 100000;
103 } else {
104 /* wait for data */
105 if (url_debug)
106 fprintf(stderr,"select for data [maxfd=%d]\n",maxfd);
107 tv.tv_sec = url_timeout;
108 tv.tv_usec = 0;
109 }
110 switch (select(maxfd+1, &rd, &wr, &ex, &tv)) {
111 case -1:
112 /* Huh? */
113 perror("select");
114 exit(1);
115 case 0:
116 /* timeout */
117 return -1;
118 }
119 for (;;) {
120 rc = curl_multi_perform(curlm,&count);
121 if (CURLM_CALL_MULTI_PERFORM == rc)
122 continue;
123 if (CURLM_OK != rc) {
124 fprintf(stderr,"curl_multi_perform: %d %s\n",rc,h->errmsg);
125 return -1;
126 }
127 if (0 == count)
128 h->eof = 1;
129 break;
130 }
131 return 0;
132 }
133
134 /* curl setup */
curl_setup(struct url_state * h)135 static int curl_setup(struct url_state *h)
136 {
137 if (h->curl) {
138 curl_multi_remove_handle(curlm,h->curl);
139 curl_easy_cleanup(h->curl);
140 }
141
142 h->curl = curl_easy_init();
143 curl_easy_setopt(h->curl, CURLOPT_URL, h->path);
144 curl_easy_setopt(h->curl, CURLOPT_ERRORBUFFER, h->errmsg);
145 curl_easy_setopt(h->curl, CURLOPT_WRITEFUNCTION, curl_write);
146 curl_easy_setopt(h->curl, CURLOPT_WRITEDATA, h);
147 curl_multi_add_handle(curlm, h->curl);
148
149 h->buf_pos = 0;
150 h->curl_pos = 0;
151 h->eof = 0;
152 return 0;
153 }
154
155 /* ---------------------------------------------------------------------- */
156 /* GNU glibc custom stream interface */
157
url_read(void * handle,char * buf,size_t size)158 static ssize_t url_read(void *handle, char *buf, size_t size)
159 {
160 struct url_state *h = handle;
161 size_t bytes, total;
162 off_t off;
163 int count;
164
165 if (url_debug)
166 fprintf(stderr,"url_read(size=%d)\n",(int)size);
167 for (total = 0; size > 0;) {
168 if (h->buf.start <= h->buf_pos &&
169 h->buf.start + h->buf.size > h->buf_pos) {
170 /* can satisfy from current buffer */
171 bytes = h->buf.start + h->buf.size - h->buf_pos;
172 off = h->buf_pos - h->buf.start;
173 if (bytes > size)
174 bytes = size;
175 memcpy(buf+total, h->buf.data + off, bytes);
176 if (url_debug)
177 fprintf(stderr," get %5d @ %5d [%5d]\n",
178 (int)bytes, (int)h->buf_pos, (int)off);
179 size -= bytes;
180 total += bytes;
181 h->buf_pos += bytes;
182 continue;
183 }
184 if (h->buf_pos < h->buf.start) {
185 /* seeking backwards -- restart transfer */
186 if (url_debug)
187 fprintf(stderr," rewind\n");
188 curl_free_buffer(&h->buf);
189 curl_setup(h);
190 while (CURLM_CALL_MULTI_PERFORM == curl_multi_perform(curlm,&count))
191 /* nothing */;
192 }
193 if (h->eof)
194 /* stop on eof */
195 break;
196 /* fetch more data */
197 if (-1 == curl_xfer(h)) {
198 if (0 == total)
199 return -1;
200 break;
201 }
202 }
203 return total;
204 }
205
206 #if 0
207 static ssize_t url_write(void *handle, const char *buf, size_t size)
208 {
209 //struct url_state *h = handle;
210
211 if (url_debug)
212 fprintf(stderr,"url_write(size=%d)\n",(int)size);
213 return -1;
214 }
215 #endif
216
url_seek(void * handle,off64_t * pos,int whence)217 static int url_seek(void *handle, off64_t *pos, int whence)
218 {
219 struct url_state *h = handle;
220 int rc = 0;
221
222 if (url_debug)
223 fprintf(stderr,"url_seek(pos=%d,whence=%d)\n", (int)(*pos), whence);
224 switch (whence) {
225 case SEEK_SET:
226 h->buf_pos = *pos;
227 break;
228 case SEEK_CUR:
229 h->buf_pos += *pos;
230 break;
231 case SEEK_END:
232 rc = -1;
233 }
234 *pos = h->buf_pos;
235 return rc;
236 }
237
url_close(void * handle)238 static int url_close(void *handle)
239 {
240 struct url_state *h = handle;
241
242 if (url_debug)
243 fprintf(stderr,"url_close()\n");
244 curl_multi_remove_handle(curlm,h->curl);
245 curl_easy_cleanup(h->curl);
246 if (h->buf.data)
247 free(h->buf.data);
248 free(h->path);
249 free(h);
250 return 0;
251 }
252
253 static cookie_io_functions_t url_hooks = {
254 .read = url_read,
255 #if 0
256 .write = url_write,
257 #endif
258 .seek = url_seek,
259 .close = url_close,
260 };
261
url_open(const char * path,const char * mode)262 static FILE *url_open(const char *path, const char *mode)
263 {
264 FILE *fp;
265 struct url_state *h;
266 int count;
267
268 if (url_debug)
269 fprintf(stderr,"url_open(%s,%s)\n",path,mode);
270
271 h = malloc(sizeof(*h));
272 if (NULL == h)
273 goto err;
274 memset(h,0,sizeof(*h));
275
276 h->path = strdup(path);
277 if (NULL == h->path)
278 goto err;
279
280 /* setup */
281 curl_setup(h);
282 fp = fopencookie(h, mode, url_hooks);
283 if (NULL == fp)
284 goto err;
285
286 /* connect + start fetching */
287 while (CURLM_CALL_MULTI_PERFORM == curl_multi_perform(curlm,&count))
288 /* nothing */;
289
290 /* check for errors */
291 if (0 == count && NULL == h->buf.data) {
292 errno = ENOENT;
293 goto fetch_err;
294 }
295
296 /* all done */
297 return fp;
298
299
300 fetch_err:
301 curl_multi_remove_handle(curlm,h->curl);
302 err:
303 if (h->curl)
304 curl_easy_cleanup(h->curl);
305 if (h->path)
306 free(h->path);
307 if (h)
308 free(h);
309 return NULL;
310 }
311
312 /* ---------------------------------------------------------------------- */
313 /* hook into fopen using GNU ld's --wrap */
314
curl_is_url(const char * url)315 int curl_is_url(const char *url)
316 {
317 static char *protocols[] = {
318 "ftp://",
319 "http://",
320 NULL,
321 };
322 int i;
323
324 for (i = 0; protocols[i] != NULL; i++)
325 if (0 == strncasecmp(url, protocols[i], strlen(protocols[i])))
326 return 1;
327 return 0;
328 }
329
330 FILE *__wrap_fopen(const char *path, const char *mode);
331 FILE *__real_fopen(const char *path, const char *mode);
332
__wrap_fopen(const char * path,const char * mode)333 FILE *__wrap_fopen(const char *path, const char *mode)
334 {
335 if (url_debug)
336 fprintf(stderr,"fopen(%s,%s)\n",path,mode);
337
338 /* catch URLs */
339 if (curl_is_url(path)) {
340 if (strchr(mode,'w')) {
341 fprintf(stderr,"write access over ftp/http is not supported, sorry\n");
342 return NULL;
343 }
344 return url_open(path,mode);
345 }
346
347 /* files passed to the real fopen */
348 return __real_fopen(path,mode);
349 }
350