Blame src/fopen.c

Packit 762fc5
/*****************************************************************************
Packit 762fc5
 *
Packit 762fc5
 * This example source code introduces a c library buffered I/O interface to
Packit 762fc5
 * URL reads it supports fopen(), fread(), fgets(), feof(), fclose(),
Packit 762fc5
 * rewind(). Supported functions have identical prototypes to their normal c
Packit 762fc5
 * lib namesakes and are preceaded by url_ .
Packit 762fc5
 *
Packit 762fc5
 * Using this code you can replace your program's fopen() with url_fopen()
Packit 762fc5
 * and fread() with url_fread() and it become possible to read remote streams
Packit 762fc5
 * instead of (only) local files. Local files (ie those that can be directly
Packit 762fc5
 * fopened) will drop back to using the underlying clib implementations
Packit 762fc5
 *
Packit 762fc5
 * See the main() function at the bottom that shows an app that retrives from a
Packit 762fc5
 * specified url using fgets() and fread() and saves as two output files.
Packit 762fc5
 *
Packit 762fc5
 * Copyright (c) 2003 Simtec Electronics
Packit 762fc5
 *
Packit 762fc5
 * Re-implemented by Vincent Sanders <vince@kyllikki.org> with extensive
Packit 762fc5
 * reference to original curl example code
Packit 762fc5
 *
Packit 762fc5
 * Redistribution and use in source and binary forms, with or without
Packit 762fc5
 * modification, are permitted provided that the following conditions
Packit 762fc5
 * are met:
Packit 762fc5
 * 1. Redistributions of source code must retain the above copyright
Packit 762fc5
 *    notice, this list of conditions and the following disclaimer.
Packit 762fc5
 * 2. Redistributions in binary form must reproduce the above copyright
Packit 762fc5
 *    notice, this list of conditions and the following disclaimer in the
Packit 762fc5
 *    documentation and/or other materials provided with the distribution.
Packit 762fc5
 * 3. The name of the author may not be used to endorse or promote products
Packit 762fc5
 *    derived from this software without specific prior written permission.
Packit 762fc5
 *
Packit 762fc5
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
Packit 762fc5
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
Packit 762fc5
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
Packit 762fc5
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
Packit 762fc5
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
Packit 762fc5
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
Packit 762fc5
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
Packit 762fc5
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
Packit 762fc5
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
Packit 762fc5
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Packit 762fc5
 *
Packit 762fc5
 * This example requires libcurl 7.9.7 or later.
Packit 762fc5
 *
Packit 762fc5
 * Modified for aide by Hannes von Haugwitz <hannes@vonhaugwitz.com>
Packit 762fc5
 * based on modifications of previous version by Pablo Virolainen
Packit 762fc5
 * (pablo@ipi.fi):
Packit 762fc5
 *      2013-05-14: - moved declarations to 'include/fopen.h'
Packit 762fc5
 *                  - removed (unneeded) main method
Packit 762fc5
 */
Packit 762fc5
Packit 762fc5
#include "fopen.h"
Packit 762fc5
Packit 762fc5
/* we use a global one for convenience */
Packit 762fc5
CURLM *multi_handle;
Packit 762fc5
Packit 762fc5
/* curl calls this routine to get more data */
Packit 762fc5
static size_t write_callback(char *buffer,
Packit 762fc5
                             size_t size,
Packit 762fc5
                             size_t nitems,
Packit 762fc5
                             void *userp)
Packit 762fc5
{
Packit 762fc5
  char *newbuff;
Packit 762fc5
  size_t rembuff;
Packit 762fc5
Packit 762fc5
  URL_FILE *url = (URL_FILE *)userp;
Packit 762fc5
  size *= nitems;
Packit 762fc5
Packit 762fc5
  rembuff=url->buffer_len - url->buffer_pos; /* remaining space in buffer */
Packit 762fc5
Packit 762fc5
  if(size > rembuff) {
Packit 762fc5
    /* not enough space in buffer */
Packit 762fc5
    newbuff=realloc(url->buffer,url->buffer_len + (size - rembuff));
Packit 762fc5
    if(newbuff==NULL) {
Packit 762fc5
      fprintf(stderr,"callback buffer grow failed\n");
Packit 762fc5
      size=rembuff;
Packit 762fc5
    }
Packit 762fc5
    else {
Packit 762fc5
      /* realloc suceeded increase buffer size*/
Packit 762fc5
      url->buffer_len+=size - rembuff;
Packit 762fc5
      url->buffer=newbuff;
Packit 762fc5
    }
Packit 762fc5
  }
Packit 762fc5
Packit 762fc5
  memcpy(&url->buffer[url->buffer_pos], buffer, size);
Packit 762fc5
  url->buffer_pos += size;
Packit 762fc5
Packit 762fc5
  return size;
Packit 762fc5
}
Packit 762fc5
Packit 762fc5
/* use to attempt to fill the read buffer up to requested number of bytes */
Packit 762fc5
static int fill_buffer(URL_FILE *file, size_t want)
Packit 762fc5
{
Packit 762fc5
  fd_set fdread;
Packit 762fc5
  fd_set fdwrite;
Packit 762fc5
  fd_set fdexcep;
Packit 762fc5
  struct timeval timeout;
Packit 762fc5
  int rc;
Packit 762fc5
Packit 762fc5
  /* only attempt to fill buffer if transactions still running and buffer
Packit 762fc5
   * doesnt exceed required size already
Packit 762fc5
   */
Packit 762fc5
  if((!file->still_running) || (file->buffer_pos > want))
Packit 762fc5
    return 0;
Packit 762fc5
Packit 762fc5
  /* attempt to fill buffer */
Packit 762fc5
  do {
Packit 762fc5
    int maxfd = -1;
Packit 762fc5
    long curl_timeo = -1;
Packit 762fc5
Packit 762fc5
    FD_ZERO(&fdread);
Packit 762fc5
    FD_ZERO(&fdwrite);
Packit 762fc5
    FD_ZERO(&fdexcep);
Packit 762fc5
Packit 762fc5
    /* set a suitable timeout to fail on */
Packit 762fc5
    timeout.tv_sec = 60; /* 1 minute */
Packit 762fc5
    timeout.tv_usec = 0;
Packit 762fc5
Packit 762fc5
    curl_multi_timeout(multi_handle, &curl_timeo);
Packit 762fc5
    if(curl_timeo >= 0) {
Packit 762fc5
      timeout.tv_sec = curl_timeo / 1000;
Packit 762fc5
      if(timeout.tv_sec > 1)
Packit 762fc5
        timeout.tv_sec = 1;
Packit 762fc5
      else
Packit 762fc5
        timeout.tv_usec = (curl_timeo % 1000) * 1000;
Packit 762fc5
    }
Packit 762fc5
Packit 762fc5
    /* get file descriptors from the transfers */
Packit 762fc5
    curl_multi_fdset(multi_handle, &fdread, &fdwrite, &fdexcep, &maxfd);
Packit 762fc5
Packit 762fc5
    /* In a real-world program you OF COURSE check the return code of the
Packit 762fc5
       function calls.  On success, the value of maxfd is guaranteed to be
Packit 762fc5
       greater or equal than -1.  We call select(maxfd + 1, ...), specially
Packit 762fc5
       in case of (maxfd == -1), we call select(0, ...), which is basically
Packit 762fc5
       equal to sleep. */
Packit 762fc5
Packit 762fc5
    rc = select(maxfd+1, &fdread, &fdwrite, &fdexcep, &timeout);
Packit 762fc5
Packit 762fc5
    switch(rc) {
Packit 762fc5
    case -1:
Packit 762fc5
      /* select error */
Packit 762fc5
      break;
Packit 762fc5
Packit 762fc5
    case 0:
Packit 762fc5
    default:
Packit 762fc5
      /* timeout or readable/writable sockets */
Packit 762fc5
      curl_multi_perform(multi_handle, &file->still_running);
Packit 762fc5
      break;
Packit 762fc5
    }
Packit 762fc5
  } while(file->still_running && (file->buffer_pos < want));
Packit 762fc5
  return 1;
Packit 762fc5
}
Packit 762fc5
Packit 762fc5
/* use to remove want bytes from the front of a files buffer */
Packit 762fc5
static int use_buffer(URL_FILE *file,int want)
Packit 762fc5
{
Packit 762fc5
  /* sort out buffer */
Packit 762fc5
  if((file->buffer_pos - want) <=0) {
Packit 762fc5
    /* ditch buffer - write will recreate */
Packit 762fc5
    if(file->buffer)
Packit 762fc5
      free(file->buffer);
Packit 762fc5
Packit 762fc5
    file->buffer=NULL;
Packit 762fc5
    file->buffer_pos=0;
Packit 762fc5
    file->buffer_len=0;
Packit 762fc5
  }
Packit 762fc5
  else {
Packit 762fc5
    /* move rest down make it available for later */
Packit 762fc5
    memmove(file->buffer,
Packit 762fc5
            &file->buffer[want],
Packit 762fc5
            (file->buffer_pos - want));
Packit 762fc5
Packit 762fc5
    file->buffer_pos -= want;
Packit 762fc5
  }
Packit 762fc5
  return 0;
Packit 762fc5
}
Packit 762fc5
Packit 762fc5
URL_FILE *url_fopen(const char *url,const char *operation)
Packit 762fc5
{
Packit 762fc5
  /* this code could check for URLs or types in the 'url' and
Packit 762fc5
     basicly use the real fopen() for standard files */
Packit 762fc5
Packit 762fc5
  URL_FILE *file;
Packit 762fc5
  (void)operation;
Packit 762fc5
Packit 762fc5
  file = malloc(sizeof(URL_FILE));
Packit 762fc5
  if(!file)
Packit 762fc5
    return NULL;
Packit 762fc5
Packit 762fc5
  memset(file, 0, sizeof(URL_FILE));
Packit 762fc5
Packit 762fc5
  if((file->handle.file=fopen(url,operation)))
Packit 762fc5
    file->type = CFTYPE_FILE; /* marked as URL */
Packit 762fc5
Packit 762fc5
  else {
Packit 762fc5
    file->type = CFTYPE_CURL; /* marked as URL */
Packit 762fc5
    file->handle.curl = curl_easy_init();
Packit 762fc5
Packit 762fc5
    curl_easy_setopt(file->handle.curl, CURLOPT_URL, url);
Packit 762fc5
    curl_easy_setopt(file->handle.curl, CURLOPT_WRITEDATA, file);
Packit 762fc5
    curl_easy_setopt(file->handle.curl, CURLOPT_VERBOSE, 0L);
Packit 762fc5
    curl_easy_setopt(file->handle.curl, CURLOPT_WRITEFUNCTION, write_callback);
Packit 762fc5
Packit 762fc5
    if(!multi_handle)
Packit 762fc5
      multi_handle = curl_multi_init();
Packit 762fc5
Packit 762fc5
    curl_multi_add_handle(multi_handle, file->handle.curl);
Packit 762fc5
Packit 762fc5
    /* lets start the fetch */
Packit 762fc5
    curl_multi_perform(multi_handle, &file->still_running);
Packit 762fc5
Packit 762fc5
    if((file->buffer_pos == 0) && (!file->still_running)) {
Packit 762fc5
      /* if still_running is 0 now, we should return NULL */
Packit 762fc5
Packit 762fc5
      /* make sure the easy handle is not in the multi handle anymore */
Packit 762fc5
      curl_multi_remove_handle(multi_handle, file->handle.curl);
Packit 762fc5
Packit 762fc5
      /* cleanup */
Packit 762fc5
      curl_easy_cleanup(file->handle.curl);
Packit 762fc5
Packit 762fc5
      free(file);
Packit 762fc5
Packit 762fc5
      file = NULL;
Packit 762fc5
    }
Packit 762fc5
  }
Packit 762fc5
  return file;
Packit 762fc5
}
Packit 762fc5
Packit 762fc5
int url_fclose(URL_FILE *file)
Packit 762fc5
{
Packit 762fc5
  int ret=0;/* default is good return */
Packit 762fc5
Packit 762fc5
  switch(file->type) {
Packit 762fc5
  case CFTYPE_FILE:
Packit 762fc5
    ret=fclose(file->handle.file); /* passthrough */
Packit 762fc5
    break;
Packit 762fc5
Packit 762fc5
  case CFTYPE_CURL:
Packit 762fc5
    /* make sure the easy handle is not in the multi handle anymore */
Packit 762fc5
    curl_multi_remove_handle(multi_handle, file->handle.curl);
Packit 762fc5
Packit 762fc5
    /* cleanup */
Packit 762fc5
    curl_easy_cleanup(file->handle.curl);
Packit 762fc5
    break;
Packit 762fc5
Packit 762fc5
  default: /* unknown or supported type - oh dear */
Packit 762fc5
    ret=EOF;
Packit 762fc5
    errno=EBADF;
Packit 762fc5
    break;
Packit 762fc5
  }
Packit 762fc5
Packit 762fc5
  if(file->buffer)
Packit 762fc5
    free(file->buffer);/* free any allocated buffer space */
Packit 762fc5
Packit 762fc5
  free(file);
Packit 762fc5
Packit 762fc5
  return ret;
Packit 762fc5
}
Packit 762fc5
Packit 762fc5
int url_feof(URL_FILE *file)
Packit 762fc5
{
Packit 762fc5
  int ret=0;
Packit 762fc5
Packit 762fc5
  switch(file->type) {
Packit 762fc5
  case CFTYPE_FILE:
Packit 762fc5
    ret=feof(file->handle.file);
Packit 762fc5
    break;
Packit 762fc5
Packit 762fc5
  case CFTYPE_CURL:
Packit 762fc5
    if((file->buffer_pos == 0) && (!file->still_running))
Packit 762fc5
      ret = 1;
Packit 762fc5
    break;
Packit 762fc5
Packit 762fc5
  default: /* unknown or supported type - oh dear */
Packit 762fc5
    ret=-1;
Packit 762fc5
    errno=EBADF;
Packit 762fc5
    break;
Packit 762fc5
  }
Packit 762fc5
  return ret;
Packit 762fc5
}
Packit 762fc5
Packit 762fc5
size_t url_fread(void *ptr, size_t size, size_t nmemb, URL_FILE *file)
Packit 762fc5
{
Packit 762fc5
  size_t want;
Packit 762fc5
Packit 762fc5
  switch(file->type) {
Packit 762fc5
  case CFTYPE_FILE:
Packit 762fc5
    want=fread(ptr,size,nmemb,file->handle.file);
Packit 762fc5
    break;
Packit 762fc5
Packit 762fc5
  case CFTYPE_CURL:
Packit 762fc5
    want = nmemb * size;
Packit 762fc5
Packit 762fc5
    fill_buffer(file,want);
Packit 762fc5
Packit 762fc5
    /* check if theres data in the buffer - if not fill_buffer()
Packit 762fc5
     * either errored or EOF */
Packit 762fc5
    if(!file->buffer_pos)
Packit 762fc5
      return 0;
Packit 762fc5
Packit 762fc5
    /* ensure only available data is considered */
Packit 762fc5
    if(file->buffer_pos < want)
Packit 762fc5
      want = file->buffer_pos;
Packit 762fc5
Packit 762fc5
    /* xfer data to caller */
Packit 762fc5
    memcpy(ptr, file->buffer, want);
Packit 762fc5
Packit 762fc5
    use_buffer(file,want);
Packit 762fc5
Packit 762fc5
    want = want / size;     /* number of items */
Packit 762fc5
    break;
Packit 762fc5
Packit 762fc5
  default: /* unknown or supported type - oh dear */
Packit 762fc5
    want=0;
Packit 762fc5
    errno=EBADF;
Packit 762fc5
    break;
Packit 762fc5
Packit 762fc5
  }
Packit 762fc5
  return want;
Packit 762fc5
}
Packit 762fc5
Packit 762fc5
char *url_fgets(char *ptr, size_t size, URL_FILE *file)
Packit 762fc5
{
Packit 762fc5
  size_t want = size - 1;/* always need to leave room for zero termination */
Packit 762fc5
  size_t loop;
Packit 762fc5
Packit 762fc5
  switch(file->type) {
Packit 762fc5
  case CFTYPE_FILE:
Packit 762fc5
    ptr = fgets(ptr,size,file->handle.file);
Packit 762fc5
    break;
Packit 762fc5
Packit 762fc5
  case CFTYPE_CURL:
Packit 762fc5
    fill_buffer(file,want);
Packit 762fc5
Packit 762fc5
    /* check if theres data in the buffer - if not fill either errored or
Packit 762fc5
     * EOF */
Packit 762fc5
    if(!file->buffer_pos)
Packit 762fc5
      return NULL;
Packit 762fc5
Packit 762fc5
    /* ensure only available data is considered */
Packit 762fc5
    if(file->buffer_pos < want)
Packit 762fc5
      want = file->buffer_pos;
Packit 762fc5
Packit 762fc5
    /*buffer contains data */
Packit 762fc5
    /* look for newline or eof */
Packit 762fc5
    for(loop=0;loop < want;loop++) {
Packit 762fc5
      if(file->buffer[loop] == '\n') {
Packit 762fc5
        want=loop+1;/* include newline */
Packit 762fc5
        break;
Packit 762fc5
      }
Packit 762fc5
    }
Packit 762fc5
Packit 762fc5
    /* xfer data to caller */
Packit 762fc5
    memcpy(ptr, file->buffer, want);
Packit 762fc5
    ptr[want]=0;/* allways null terminate */
Packit 762fc5
Packit 762fc5
    use_buffer(file,want);
Packit 762fc5
Packit 762fc5
    break;
Packit 762fc5
Packit 762fc5
  default: /* unknown or supported type - oh dear */
Packit 762fc5
    ptr=NULL;
Packit 762fc5
    errno=EBADF;
Packit 762fc5
    break;
Packit 762fc5
  }
Packit 762fc5
Packit 762fc5
  return ptr;/*success */
Packit 762fc5
}
Packit 762fc5
Packit 762fc5
void url_rewind(URL_FILE *file)
Packit 762fc5
{
Packit 762fc5
  switch(file->type) {
Packit 762fc5
  case CFTYPE_FILE:
Packit 762fc5
    rewind(file->handle.file); /* passthrough */
Packit 762fc5
    break;
Packit 762fc5
Packit 762fc5
  case CFTYPE_CURL:
Packit 762fc5
    /* halt transaction */
Packit 762fc5
    curl_multi_remove_handle(multi_handle, file->handle.curl);
Packit 762fc5
Packit 762fc5
    /* restart */
Packit 762fc5
    curl_multi_add_handle(multi_handle, file->handle.curl);
Packit 762fc5
Packit 762fc5
    /* ditch buffer - write will recreate - resets stream pos*/
Packit 762fc5
    if(file->buffer)
Packit 762fc5
      free(file->buffer);
Packit 762fc5
Packit 762fc5
    file->buffer=NULL;
Packit 762fc5
    file->buffer_pos=0;
Packit 762fc5
    file->buffer_len=0;
Packit 762fc5
Packit 762fc5
    break;
Packit 762fc5
Packit 762fc5
  default: /* unknown or supported type - oh dear */
Packit 762fc5
    break;
Packit 762fc5
  }
Packit 762fc5
}