#include #include #include #include #include #include #include #include #include #include #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); }