aboutsummaryrefslogtreecommitdiffstats
path: root/curl.c
diff options
context:
space:
mode:
Diffstat (limited to 'curl.c')
-rw-r--r--curl.c348
1 files changed, 348 insertions, 0 deletions
diff --git a/curl.c b/curl.c
new file mode 100644
index 0000000..498b093
--- /dev/null
+++ b/curl.c
@@ -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);
+}