Logo Search packages:      
Sourcecode: fbi version File versions  Download package

curl.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <time.h>
#include <errno.h>
#include <sys/select.h>

#include <curl/curl.h>
#include <curl/easy.h>

#include "curl.h"

/* curl globals */
static CURLM *curlm;
static fd_set rd, wr, ex;

/* my globals */
static int url_debug   = 0;
static int url_timeout = 30;

/* my structs */
struct iobuf {
    off_t   start;
    size_t  size;
    char    *data;
};

struct url_state {
    char           *path;
    CURL           *curl;
    char           errmsg[CURL_ERROR_SIZE];
    off_t          curl_pos;
    off_t          buf_pos;
    struct iobuf   buf;
    int            eof;
};

/* ---------------------------------------------------------------------- */
/* curl stuff                                                             */

static void __attribute__ ((constructor)) curl_init(void)
{
    curl_global_init(CURL_GLOBAL_ALL);
    curlm = curl_multi_init();
}

static void __attribute__ ((destructor)) curl_fini(void)
{
    curl_multi_cleanup(curlm);
    curl_global_cleanup();
}

static void curl_free_buffer(struct iobuf *buf)
{
    if (buf->data) {
      free(buf->data);
      memset(buf,0,sizeof(*buf));
    }
}

/* CURLOPT_WRITEFUNCTION */
static int curl_write(void *data,  size_t  size, size_t nmemb, void *handle)
{
    struct url_state *h = handle;

    curl_free_buffer(&h->buf);
    h->buf.start = h->curl_pos;
    h->buf.size  = size * nmemb;
    h->buf.data  = malloc(h->buf.size);
    memcpy(h->buf.data, data, h->buf.size);
    if (url_debug)
      fprintf(stderr,"  put %5d @ %5d\n",
            (int)h->buf.size, (int)h->buf.start);
    
    h->curl_pos += h->buf.size;
    return h->buf.size;
}

/* do transfers */
static int curl_xfer(struct url_state *h)
{
    CURLMcode rc;
    struct timeval tv;
    int count, maxfd;

    FD_ZERO(&rd);
    FD_ZERO(&wr);
    FD_ZERO(&ex);
    maxfd = -1;
    rc = curl_multi_fdset(curlm, &rd, &wr, &ex, &maxfd);
    if (CURLM_OK != rc) {
      fprintf(stderr,"curl_multi_fdset: %d %s\n",rc,h->errmsg);
      return -1;
    }
    if (-1 == maxfd) {
      /* wait 0.1 sec */
      if (url_debug)
          fprintf(stderr,"wait 0.01 sec\n");
      tv.tv_sec  = 0;
      tv.tv_usec = 100000;
    } else {
      /* wait for data */
      if (url_debug)
          fprintf(stderr,"select for data [maxfd=%d]\n",maxfd);
      tv.tv_sec  = url_timeout;
      tv.tv_usec = 0;
    }
    switch (select(maxfd+1, &rd, &wr, &ex, &tv)) {
    case -1:
      /* Huh? */
      perror("select");
      exit(1);
    case 0:
      /* timeout */
      return -1;
    }
    for (;;) {
      rc = curl_multi_perform(curlm,&count);
      if (CURLM_CALL_MULTI_PERFORM == rc)
          continue;
      if (CURLM_OK != rc) {
          fprintf(stderr,"curl_multi_perform: %d %s\n",rc,h->errmsg);
          return -1;
      }
      if (0 == count)
          h->eof = 1;
      break;
    }
    return 0;
}

/* curl setup */
static int curl_setup(struct url_state *h)
{
    if (h->curl) {
      curl_multi_remove_handle(curlm,h->curl);
      curl_easy_cleanup(h->curl);
    }

    h->curl = curl_easy_init();
    curl_easy_setopt(h->curl, CURLOPT_URL,           h->path);
    curl_easy_setopt(h->curl, CURLOPT_ERRORBUFFER,   h->errmsg);
    curl_easy_setopt(h->curl, CURLOPT_WRITEFUNCTION, curl_write);
    curl_easy_setopt(h->curl, CURLOPT_WRITEDATA,     h);
    curl_multi_add_handle(curlm, h->curl);

    h->buf_pos  = 0;
    h->curl_pos = 0;
    h->eof      = 0;
    return 0;
}

/* ---------------------------------------------------------------------- */
/* GNU glibc custom stream interface                                      */

static ssize_t url_read(void *handle, char *buf, size_t size)
{
    struct url_state *h = handle;
    size_t bytes, total;
    off_t off;
    int count;
    
    if (url_debug)
      fprintf(stderr,"url_read(size=%d)\n",(int)size);
    for (total = 0; size > 0;) {
      if (h->buf.start                <= h->buf_pos &&
          h->buf.start + h->buf.size  >  h->buf_pos) {
          /* can satisfy from current buffer */
          bytes = h->buf.start + h->buf.size - h->buf_pos;
          off   = h->buf_pos - h->buf.start;
          if (bytes > size)
            bytes = size;
          memcpy(buf+total, h->buf.data + off, bytes);
          if (url_debug)
            fprintf(stderr,"  get %5d @ %5d [%5d]\n",
                  (int)bytes, (int)h->buf_pos, (int)off);
          size       -= bytes;
          total      += bytes;
          h->buf_pos += bytes;
          continue;
      }
      if (h->buf_pos < h->buf.start) {
          /* seeking backwards -- restart transfer */
          if (url_debug)
            fprintf(stderr,"  rewind\n");
          curl_free_buffer(&h->buf);
          curl_setup(h);
          while (CURLM_CALL_MULTI_PERFORM == curl_multi_perform(curlm,&count))
            /* nothing */;
      }
      if (h->eof)
          /* stop on eof */
          break;
      /* fetch more data */
      if (-1 == curl_xfer(h)) {
          if (0 == total)
            return -1;
          break;
      }
    }
    return total;
}

#if 0
static ssize_t url_write(void *handle, const char *buf, size_t size)
{
    //struct url_state *h = handle;

    if (url_debug)
      fprintf(stderr,"url_write(size=%d)\n",(int)size);
    return -1;
}
#endif

static int url_seek(void *handle, off64_t *pos, int whence)
{
    struct url_state *h = handle;
    int rc = 0;

    if (url_debug)
      fprintf(stderr,"url_seek(pos=%d,whence=%d)\n", (int)(*pos), whence);
    switch (whence) {
    case SEEK_SET:
      h->buf_pos = *pos;
      break;
    case SEEK_CUR:
      h->buf_pos += *pos;
      break;
    case SEEK_END: 
      rc = -1;
    }
    *pos = h->buf_pos;
    return rc;
}

static int url_close(void *handle)
{
    struct url_state *h = handle;

    if (url_debug)
      fprintf(stderr,"url_close()\n");
    curl_multi_remove_handle(curlm,h->curl);
    curl_easy_cleanup(h->curl);
    if (h->buf.data)
      free(h->buf.data);
    free(h->path);
    free(h);
    return 0;
}

static cookie_io_functions_t url_hooks = {
    .read  = url_read,
#if 0
    .write = url_write,
#endif
    .seek  = url_seek,
    .close = url_close,
};

static FILE *url_open(const char *path, const char *mode)
{
    FILE *fp;
    struct url_state *h;
    int count;

    if (url_debug)
      fprintf(stderr,"url_open(%s,%s)\n",path,mode);

    h = malloc(sizeof(*h));
    if (NULL == h)
      goto err;
    memset(h,0,sizeof(*h));

    h->path = strdup(path);
    if (NULL == h->path)
      goto err;
    
    /* setup */
    curl_setup(h);
    fp = fopencookie(h, mode, url_hooks);
    if (NULL == fp)
      goto err;

    /* connect + start fetching */
    while (CURLM_CALL_MULTI_PERFORM == curl_multi_perform(curlm,&count))
      /* nothing */;

    /* check for errors */
    if (0 == count  &&  NULL == h->buf.data) {
      errno = ENOENT;
      goto fetch_err;
    }
      
    /* all done */
    return fp;


 fetch_err:
    curl_multi_remove_handle(curlm,h->curl);
 err:
    if (h->curl)
      curl_easy_cleanup(h->curl);
    if (h->path)
      free(h->path);
    if (h)
      free(h);
    return NULL;
}

/* ---------------------------------------------------------------------- */
/* hook into fopen using GNU ld's --wrap                                  */

int curl_is_url(const char *url)
{
    static char *protocols[] = {
      "ftp://",
      "http://",
      NULL,
    };
    int i;

    for (i = 0; protocols[i] != NULL; i++)
      if (0 == strncasecmp(url, protocols[i], strlen(protocols[i])))
          return 1;
    return 0;
}

FILE *__wrap_fopen(const char *path, const char *mode);
FILE *__real_fopen(const char *path, const char *mode);

FILE *__wrap_fopen(const char *path, const char *mode)
{
    if (url_debug)
      fprintf(stderr,"fopen(%s,%s)\n",path,mode);

    /* catch URLs */
    if (curl_is_url(path)) {
      if (strchr(mode,'w')) {
          fprintf(stderr,"write access over ftp/http is not supported, sorry\n");
          return NULL;
      }
      return url_open(path,mode);
    }
    
    /* files passed to the real fopen */
    return __real_fopen(path,mode);
}

Generated by  Doxygen 1.6.0   Back to index