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>