diff options
Diffstat (limited to 'curl.c')
-rw-r--r-- | curl.c | 348 |
1 files changed, 348 insertions, 0 deletions
@@ -0,0 +1,348 @@ +#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); +} |