Blame src/fopen.c

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