Annotation of embedaddon/curl/docs/examples/fopen.c, revision 1.1.1.1
1.1 misho 1: /*****************************************************************************
2: *
3: * This example source code introduces a c library buffered I/O interface to
4: * URL reads it supports fopen(), fread(), fgets(), feof(), fclose(),
5: * rewind(). Supported functions have identical prototypes to their normal c
6: * lib namesakes and are preceaded by url_ .
7: *
8: * Using this code you can replace your program's fopen() with url_fopen()
9: * and fread() with url_fread() and it become possible to read remote streams
10: * instead of (only) local files. Local files (ie those that can be directly
11: * fopened) will drop back to using the underlying clib implementations
12: *
13: * See the main() function at the bottom that shows an app that retrieves from
14: * a specified url using fgets() and fread() and saves as two output files.
15: *
16: * Copyright (c) 2003 - 2019 Simtec Electronics
17: *
18: * Re-implemented by Vincent Sanders <vince@kyllikki.org> with extensive
19: * reference to original curl example code
20: *
21: * Redistribution and use in source and binary forms, with or without
22: * modification, are permitted provided that the following conditions
23: * are met:
24: * 1. Redistributions of source code must retain the above copyright
25: * notice, this list of conditions and the following disclaimer.
26: * 2. Redistributions in binary form must reproduce the above copyright
27: * notice, this list of conditions and the following disclaimer in the
28: * documentation and/or other materials provided with the distribution.
29: * 3. The name of the author may not be used to endorse or promote products
30: * derived from this software without specific prior written permission.
31: *
32: * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
33: * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
34: * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
35: * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
36: * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
37: * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
38: * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
39: * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
40: * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
41: * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
42: *
43: * This example requires libcurl 7.9.7 or later.
44: */
45: /* <DESC>
46: * implements an fopen() abstraction allowing reading from URLs
47: * </DESC>
48: */
49:
50: #include <stdio.h>
51: #include <string.h>
52: #ifndef WIN32
53: # include <sys/time.h>
54: #endif
55: #include <stdlib.h>
56: #include <errno.h>
57:
58: #include <curl/curl.h>
59:
60: enum fcurl_type_e {
61: CFTYPE_NONE = 0,
62: CFTYPE_FILE = 1,
63: CFTYPE_CURL = 2
64: };
65:
66: struct fcurl_data
67: {
68: enum fcurl_type_e type; /* type of handle */
69: union {
70: CURL *curl;
71: FILE *file;
72: } handle; /* handle */
73:
74: char *buffer; /* buffer to store cached data*/
75: size_t buffer_len; /* currently allocated buffers length */
76: size_t buffer_pos; /* end of data in buffer*/
77: int still_running; /* Is background url fetch still in progress */
78: };
79:
80: typedef struct fcurl_data URL_FILE;
81:
82: /* exported functions */
83: URL_FILE *url_fopen(const char *url, const char *operation);
84: int url_fclose(URL_FILE *file);
85: int url_feof(URL_FILE *file);
86: size_t url_fread(void *ptr, size_t size, size_t nmemb, URL_FILE *file);
87: char *url_fgets(char *ptr, size_t size, URL_FILE *file);
88: void url_rewind(URL_FILE *file);
89:
90: /* we use a global one for convenience */
91: static CURLM *multi_handle;
92:
93: /* curl calls this routine to get more data */
94: static size_t write_callback(char *buffer,
95: size_t size,
96: size_t nitems,
97: void *userp)
98: {
99: char *newbuff;
100: size_t rembuff;
101:
102: URL_FILE *url = (URL_FILE *)userp;
103: size *= nitems;
104:
105: rembuff = url->buffer_len - url->buffer_pos; /* remaining space in buffer */
106:
107: if(size > rembuff) {
108: /* not enough space in buffer */
109: newbuff = realloc(url->buffer, url->buffer_len + (size - rembuff));
110: if(newbuff == NULL) {
111: fprintf(stderr, "callback buffer grow failed\n");
112: size = rembuff;
113: }
114: else {
115: /* realloc succeeded increase buffer size*/
116: url->buffer_len += size - rembuff;
117: url->buffer = newbuff;
118: }
119: }
120:
121: memcpy(&url->buffer[url->buffer_pos], buffer, size);
122: url->buffer_pos += size;
123:
124: return size;
125: }
126:
127: /* use to attempt to fill the read buffer up to requested number of bytes */
128: static int fill_buffer(URL_FILE *file, size_t want)
129: {
130: fd_set fdread;
131: fd_set fdwrite;
132: fd_set fdexcep;
133: struct timeval timeout;
134: int rc;
135: CURLMcode mc; /* curl_multi_fdset() return code */
136:
137: /* only attempt to fill buffer if transactions still running and buffer
138: * doesn't exceed required size already
139: */
140: if((!file->still_running) || (file->buffer_pos > want))
141: return 0;
142:
143: /* attempt to fill buffer */
144: do {
145: int maxfd = -1;
146: long curl_timeo = -1;
147:
148: FD_ZERO(&fdread);
149: FD_ZERO(&fdwrite);
150: FD_ZERO(&fdexcep);
151:
152: /* set a suitable timeout to fail on */
153: timeout.tv_sec = 60; /* 1 minute */
154: timeout.tv_usec = 0;
155:
156: curl_multi_timeout(multi_handle, &curl_timeo);
157: if(curl_timeo >= 0) {
158: timeout.tv_sec = curl_timeo / 1000;
159: if(timeout.tv_sec > 1)
160: timeout.tv_sec = 1;
161: else
162: timeout.tv_usec = (curl_timeo % 1000) * 1000;
163: }
164:
165: /* get file descriptors from the transfers */
166: mc = curl_multi_fdset(multi_handle, &fdread, &fdwrite, &fdexcep, &maxfd);
167:
168: if(mc != CURLM_OK) {
169: fprintf(stderr, "curl_multi_fdset() failed, code %d.\n", mc);
170: break;
171: }
172:
173: /* On success the value of maxfd is guaranteed to be >= -1. We call
174: select(maxfd + 1, ...); specially in case of (maxfd == -1) there are
175: no fds ready yet so we call select(0, ...) --or Sleep() on Windows--
176: to sleep 100ms, which is the minimum suggested value in the
177: curl_multi_fdset() doc. */
178:
179: if(maxfd == -1) {
180: #ifdef _WIN32
181: Sleep(100);
182: rc = 0;
183: #else
184: /* Portable sleep for platforms other than Windows. */
185: struct timeval wait = { 0, 100 * 1000 }; /* 100ms */
186: rc = select(0, NULL, NULL, NULL, &wait);
187: #endif
188: }
189: else {
190: /* Note that on some platforms 'timeout' may be modified by select().
191: If you need access to the original value save a copy beforehand. */
192: rc = select(maxfd + 1, &fdread, &fdwrite, &fdexcep, &timeout);
193: }
194:
195: switch(rc) {
196: case -1:
197: /* select error */
198: break;
199:
200: case 0:
201: default:
202: /* timeout or readable/writable sockets */
203: curl_multi_perform(multi_handle, &file->still_running);
204: break;
205: }
206: } while(file->still_running && (file->buffer_pos < want));
207: return 1;
208: }
209:
210: /* use to remove want bytes from the front of a files buffer */
211: static int use_buffer(URL_FILE *file, size_t want)
212: {
213: /* sort out buffer */
214: if(file->buffer_pos <= want) {
215: /* ditch buffer - write will recreate */
216: free(file->buffer);
217: file->buffer = NULL;
218: file->buffer_pos = 0;
219: file->buffer_len = 0;
220: }
221: else {
222: /* move rest down make it available for later */
223: memmove(file->buffer,
224: &file->buffer[want],
225: (file->buffer_pos - want));
226:
227: file->buffer_pos -= want;
228: }
229: return 0;
230: }
231:
232: URL_FILE *url_fopen(const char *url, const char *operation)
233: {
234: /* this code could check for URLs or types in the 'url' and
235: basically use the real fopen() for standard files */
236:
237: URL_FILE *file;
238: (void)operation;
239:
240: file = calloc(1, sizeof(URL_FILE));
241: if(!file)
242: return NULL;
243:
244: file->handle.file = fopen(url, operation);
245: if(file->handle.file)
246: file->type = CFTYPE_FILE; /* marked as URL */
247:
248: else {
249: file->type = CFTYPE_CURL; /* marked as URL */
250: file->handle.curl = curl_easy_init();
251:
252: curl_easy_setopt(file->handle.curl, CURLOPT_URL, url);
253: curl_easy_setopt(file->handle.curl, CURLOPT_WRITEDATA, file);
254: curl_easy_setopt(file->handle.curl, CURLOPT_VERBOSE, 0L);
255: curl_easy_setopt(file->handle.curl, CURLOPT_WRITEFUNCTION, write_callback);
256:
257: if(!multi_handle)
258: multi_handle = curl_multi_init();
259:
260: curl_multi_add_handle(multi_handle, file->handle.curl);
261:
262: /* lets start the fetch */
263: curl_multi_perform(multi_handle, &file->still_running);
264:
265: if((file->buffer_pos == 0) && (!file->still_running)) {
266: /* if still_running is 0 now, we should return NULL */
267:
268: /* make sure the easy handle is not in the multi handle anymore */
269: curl_multi_remove_handle(multi_handle, file->handle.curl);
270:
271: /* cleanup */
272: curl_easy_cleanup(file->handle.curl);
273:
274: free(file);
275:
276: file = NULL;
277: }
278: }
279: return file;
280: }
281:
282: int url_fclose(URL_FILE *file)
283: {
284: int ret = 0;/* default is good return */
285:
286: switch(file->type) {
287: case CFTYPE_FILE:
288: ret = fclose(file->handle.file); /* passthrough */
289: break;
290:
291: case CFTYPE_CURL:
292: /* make sure the easy handle is not in the multi handle anymore */
293: curl_multi_remove_handle(multi_handle, file->handle.curl);
294:
295: /* cleanup */
296: curl_easy_cleanup(file->handle.curl);
297: break;
298:
299: default: /* unknown or supported type - oh dear */
300: ret = EOF;
301: errno = EBADF;
302: break;
303: }
304:
305: free(file->buffer);/* free any allocated buffer space */
306: free(file);
307:
308: return ret;
309: }
310:
311: int url_feof(URL_FILE *file)
312: {
313: int ret = 0;
314:
315: switch(file->type) {
316: case CFTYPE_FILE:
317: ret = feof(file->handle.file);
318: break;
319:
320: case CFTYPE_CURL:
321: if((file->buffer_pos == 0) && (!file->still_running))
322: ret = 1;
323: break;
324:
325: default: /* unknown or supported type - oh dear */
326: ret = -1;
327: errno = EBADF;
328: break;
329: }
330: return ret;
331: }
332:
333: size_t url_fread(void *ptr, size_t size, size_t nmemb, URL_FILE *file)
334: {
335: size_t want;
336:
337: switch(file->type) {
338: case CFTYPE_FILE:
339: want = fread(ptr, size, nmemb, file->handle.file);
340: break;
341:
342: case CFTYPE_CURL:
343: want = nmemb * size;
344:
345: fill_buffer(file, want);
346:
347: /* check if there's data in the buffer - if not fill_buffer()
348: * either errored or EOF */
349: if(!file->buffer_pos)
350: return 0;
351:
352: /* ensure only available data is considered */
353: if(file->buffer_pos < want)
354: want = file->buffer_pos;
355:
356: /* xfer data to caller */
357: memcpy(ptr, file->buffer, want);
358:
359: use_buffer(file, want);
360:
361: want = want / size; /* number of items */
362: break;
363:
364: default: /* unknown or supported type - oh dear */
365: want = 0;
366: errno = EBADF;
367: break;
368:
369: }
370: return want;
371: }
372:
373: char *url_fgets(char *ptr, size_t size, URL_FILE *file)
374: {
375: size_t want = size - 1;/* always need to leave room for zero termination */
376: size_t loop;
377:
378: switch(file->type) {
379: case CFTYPE_FILE:
380: ptr = fgets(ptr, (int)size, file->handle.file);
381: break;
382:
383: case CFTYPE_CURL:
384: fill_buffer(file, want);
385:
386: /* check if there's data in the buffer - if not fill either errored or
387: * EOF */
388: if(!file->buffer_pos)
389: return NULL;
390:
391: /* ensure only available data is considered */
392: if(file->buffer_pos < want)
393: want = file->buffer_pos;
394:
395: /*buffer contains data */
396: /* look for newline or eof */
397: for(loop = 0; loop < want; loop++) {
398: if(file->buffer[loop] == '\n') {
399: want = loop + 1;/* include newline */
400: break;
401: }
402: }
403:
404: /* xfer data to caller */
405: memcpy(ptr, file->buffer, want);
406: ptr[want] = 0;/* always null terminate */
407:
408: use_buffer(file, want);
409:
410: break;
411:
412: default: /* unknown or supported type - oh dear */
413: ptr = NULL;
414: errno = EBADF;
415: break;
416: }
417:
418: return ptr;/*success */
419: }
420:
421: void url_rewind(URL_FILE *file)
422: {
423: switch(file->type) {
424: case CFTYPE_FILE:
425: rewind(file->handle.file); /* passthrough */
426: break;
427:
428: case CFTYPE_CURL:
429: /* halt transaction */
430: curl_multi_remove_handle(multi_handle, file->handle.curl);
431:
432: /* restart */
433: curl_multi_add_handle(multi_handle, file->handle.curl);
434:
435: /* ditch buffer - write will recreate - resets stream pos*/
436: free(file->buffer);
437: file->buffer = NULL;
438: file->buffer_pos = 0;
439: file->buffer_len = 0;
440:
441: break;
442:
443: default: /* unknown or supported type - oh dear */
444: break;
445: }
446: }
447:
448: #define FGETSFILE "fgets.test"
449: #define FREADFILE "fread.test"
450: #define REWINDFILE "rewind.test"
451:
452: /* Small main program to retrieve from a url using fgets and fread saving the
453: * output to two test files (note the fgets method will corrupt binary files if
454: * they contain 0 chars */
455: int main(int argc, char *argv[])
456: {
457: URL_FILE *handle;
458: FILE *outf;
459:
460: size_t nread;
461: char buffer[256];
462: const char *url;
463:
464: if(argc < 2)
465: url = "http://192.168.7.3/testfile";/* default to testurl */
466: else
467: url = argv[1];/* use passed url */
468:
469: /* copy from url line by line with fgets */
470: outf = fopen(FGETSFILE, "wb+");
471: if(!outf) {
472: perror("couldn't open fgets output file\n");
473: return 1;
474: }
475:
476: handle = url_fopen(url, "r");
477: if(!handle) {
478: printf("couldn't url_fopen() %s\n", url);
479: fclose(outf);
480: return 2;
481: }
482:
483: while(!url_feof(handle)) {
484: url_fgets(buffer, sizeof(buffer), handle);
485: fwrite(buffer, 1, strlen(buffer), outf);
486: }
487:
488: url_fclose(handle);
489:
490: fclose(outf);
491:
492:
493: /* Copy from url with fread */
494: outf = fopen(FREADFILE, "wb+");
495: if(!outf) {
496: perror("couldn't open fread output file\n");
497: return 1;
498: }
499:
500: handle = url_fopen("testfile", "r");
501: if(!handle) {
502: printf("couldn't url_fopen() testfile\n");
503: fclose(outf);
504: return 2;
505: }
506:
507: do {
508: nread = url_fread(buffer, 1, sizeof(buffer), handle);
509: fwrite(buffer, 1, nread, outf);
510: } while(nread);
511:
512: url_fclose(handle);
513:
514: fclose(outf);
515:
516:
517: /* Test rewind */
518: outf = fopen(REWINDFILE, "wb+");
519: if(!outf) {
520: perror("couldn't open fread output file\n");
521: return 1;
522: }
523:
524: handle = url_fopen("testfile", "r");
525: if(!handle) {
526: printf("couldn't url_fopen() testfile\n");
527: fclose(outf);
528: return 2;
529: }
530:
531: nread = url_fread(buffer, 1, sizeof(buffer), handle);
532: fwrite(buffer, 1, nread, outf);
533: url_rewind(handle);
534:
535: buffer[0]='\n';
536: fwrite(buffer, 1, 1, outf);
537:
538: nread = url_fread(buffer, 1, sizeof(buffer), handle);
539: fwrite(buffer, 1, nread, outf);
540:
541: url_fclose(handle);
542:
543: fclose(outf);
544:
545: return 0;/* all done */
546: }
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>