A clone of btpd with my configuration changes.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

230 line
5.8 KiB

  1. #include <pthread.h>
  2. #include <stdarg.h>
  3. #include <string.h>
  4. #include <unistd.h>
  5. #include <curl/curl.h>
  6. #include "btpd.h"
  7. #include "http.h"
  8. #define MAX_DOWNLOAD (1 << 18) // 256kB
  9. #define CURL_SELECT_TIME (& (struct timeval) { 1, 0 })
  10. enum http_state {
  11. HS_ADD,
  12. HS_ACTIVE,
  13. HS_DONE,
  14. HS_NOADD,
  15. HS_CANCEL
  16. };
  17. struct http {
  18. enum http_state state;
  19. char *url;
  20. CURL *curlh;
  21. struct http_res res;
  22. BTPDQ_ENTRY(http) entry;
  23. void (*cb)(struct http *, struct http_res *, void *);
  24. void *cb_arg;
  25. };
  26. BTPDQ_HEAD(http_tq, http);
  27. static struct http_tq m_httpq = BTPDQ_HEAD_INITIALIZER(m_httpq);
  28. static pthread_mutex_t m_httpq_lock;
  29. static pthread_cond_t m_httpq_cond;
  30. static CURLM *m_curlh;
  31. static size_t
  32. http_write_cb(void *ptr, size_t size, size_t nmemb, void *arg)
  33. {
  34. char *mem;
  35. struct http_res *res = arg;
  36. size_t nbytes = size * nmemb;
  37. size_t nlength = res->length + nbytes;
  38. if (nlength > MAX_DOWNLOAD)
  39. return 0;
  40. if ((mem = realloc(res->content, nlength)) == NULL)
  41. return 0;
  42. res->content = mem;
  43. bcopy(ptr, res->content + res->length, nbytes);
  44. res->length = nlength;
  45. return nbytes;
  46. }
  47. int
  48. http_get(struct http **ret,
  49. void (*cb)(struct http *, struct http_res *, void *), void *arg,
  50. const char *fmt, ...)
  51. {
  52. struct http *h = btpd_calloc(1, sizeof(*h));
  53. h->state = HS_ADD;
  54. h->cb = cb;
  55. h->cb_arg = arg;
  56. if ((h->curlh = curl_easy_init()) == NULL)
  57. btpd_err("Fatal error in curl.\n");
  58. va_list ap;
  59. va_start(ap, fmt);
  60. if (vasprintf(&h->url, fmt, ap) == -1)
  61. btpd_err("Out of memory.\n");
  62. va_end(ap);
  63. curl_easy_setopt(h->curlh, CURLOPT_URL, h->url);
  64. curl_easy_setopt(h->curlh, CURLOPT_USERAGENT, BTPD_VERSION);
  65. curl_easy_setopt(h->curlh, CURLOPT_WRITEFUNCTION, http_write_cb);
  66. curl_easy_setopt(h->curlh, CURLOPT_WRITEDATA, &h->res);
  67. curl_easy_setopt(h->curlh, CURLOPT_FOLLOWLOCATION, 1);
  68. pthread_mutex_lock(&m_httpq_lock);
  69. BTPDQ_INSERT_TAIL(&m_httpq, h, entry);
  70. pthread_mutex_unlock(&m_httpq_lock);
  71. pthread_cond_signal(&m_httpq_cond);
  72. if (ret != NULL)
  73. *ret = h;
  74. return 0;
  75. }
  76. void
  77. http_cancel(struct http *http)
  78. {
  79. pthread_mutex_lock(&m_httpq_lock);
  80. if (http->state == HS_ADD)
  81. http->state = HS_NOADD;
  82. else
  83. http->state = HS_CANCEL;
  84. pthread_mutex_unlock(&m_httpq_lock);
  85. }
  86. int
  87. http_succeeded(struct http_res *res)
  88. {
  89. return res->res == HRES_OK && res->code >= 200 && res->code < 300;
  90. }
  91. static void
  92. http_td_cb(void *arg)
  93. {
  94. struct http *h = arg;
  95. if (h->res.res == HRES_OK)
  96. curl_easy_getinfo(h->curlh, CURLINFO_RESPONSE_CODE, &h->res.code);
  97. if (h->res.res == HRES_FAIL)
  98. h->res.errmsg = curl_easy_strerror(h->res.code);
  99. if (h->state != HS_CANCEL)
  100. h->cb(h, &h->res, h->cb_arg);
  101. curl_easy_cleanup(h->curlh);
  102. if (h->res.content != NULL)
  103. free(h->res.content);
  104. free(h->url);
  105. free(h);
  106. }
  107. static void
  108. http_td_actions(void)
  109. {
  110. int nmsgs;
  111. struct http *http, *next;
  112. struct http_tq postq;
  113. CURLMsg *cmsg;
  114. pthread_mutex_lock(&m_httpq_lock);
  115. do {
  116. while (BTPDQ_EMPTY(&m_httpq))
  117. pthread_cond_wait(&m_httpq_cond, &m_httpq_lock);
  118. BTPDQ_INIT(&postq);
  119. BTPDQ_FOREACH_MUTABLE(http, &m_httpq, entry, next) {
  120. switch (http->state) {
  121. case HS_ADD:
  122. curl_multi_add_handle(m_curlh, http->curlh);
  123. http->state = HS_ACTIVE;
  124. break;
  125. case HS_CANCEL:
  126. curl_multi_remove_handle(m_curlh, http->curlh);
  127. case HS_NOADD:
  128. BTPDQ_REMOVE(&m_httpq, http, entry);
  129. BTPDQ_INSERT_TAIL(&postq, http, entry);
  130. http->state = HS_CANCEL;
  131. http->res.res = HRES_CANCEL;
  132. break;
  133. case HS_DONE:
  134. abort();
  135. default:
  136. break;
  137. }
  138. }
  139. while ((cmsg = curl_multi_info_read(m_curlh, &nmsgs)) != NULL) {
  140. BTPDQ_FOREACH(http, &m_httpq, entry) {
  141. if (http->curlh == cmsg->easy_handle)
  142. break;
  143. }
  144. assert(http != NULL);
  145. BTPDQ_REMOVE(&m_httpq, http, entry);
  146. BTPDQ_INSERT_TAIL(&postq, http, entry);
  147. http->state = HS_DONE;
  148. if (cmsg->data.result == 0)
  149. http->res.res = HRES_OK;
  150. else {
  151. http->res.res = HRES_FAIL;
  152. http->res.code = cmsg->data.result;
  153. }
  154. curl_multi_remove_handle(m_curlh, http->curlh);
  155. }
  156. if (!BTPDQ_EMPTY(&postq)) {
  157. pthread_mutex_unlock(&m_httpq_lock);
  158. td_post_begin();
  159. BTPDQ_FOREACH(http, &postq, entry)
  160. td_post(http_td_cb, http);
  161. td_post_end();
  162. pthread_mutex_lock(&m_httpq_lock);
  163. }
  164. } while (BTPDQ_EMPTY(&m_httpq));
  165. pthread_mutex_unlock(&m_httpq_lock);
  166. }
  167. static void
  168. http_td(void *arg)
  169. {
  170. fd_set rset, wset, eset;
  171. int maxfd, nbusy;
  172. for (;;) {
  173. http_td_actions();
  174. while (CURLM_CALL_MULTI_PERFORM == curl_multi_perform(m_curlh, &nbusy))
  175. ;
  176. if (nbusy > 0) {
  177. FD_ZERO(&rset);
  178. FD_ZERO(&wset);
  179. FD_ZERO(&eset);
  180. curl_multi_fdset(m_curlh, &rset, &wset, &eset, &maxfd);
  181. select(maxfd + 1, &rset, &wset, &eset, CURL_SELECT_TIME);
  182. }
  183. }
  184. }
  185. static void
  186. errdie(int err)
  187. {
  188. if (err != 0)
  189. btpd_err("Fatal error in http_init.\n");
  190. }
  191. void
  192. http_init(void)
  193. {
  194. pthread_t ret;
  195. errdie(curl_global_init(0));
  196. errdie((m_curlh = curl_multi_init()) == NULL);
  197. errdie(pthread_mutex_init(&m_httpq_lock, NULL));
  198. errdie(pthread_cond_init(&m_httpq_cond, NULL));
  199. errdie(pthread_create(&ret, NULL, (void *(*)(void *))http_td, NULL));
  200. }