Systemd/src/import/curl-util.c

418 lines
11 KiB
C
Raw Normal View History

/* SPDX-License-Identifier: LGPL-2.1+ */
#include <fcntl.h>
#include "alloc-util.h"
#include "build.h"
#include "curl-util.h"
#include "fd-util.h"
#include "locale-util.h"
#include "string-util.h"
static void curl_glue_check_finished(CurlGlue *g) {
CURLMsg *msg;
int k = 0;
assert(g);
msg = curl_multi_info_read(g->curl, &k);
if (!msg)
return;
if (msg->msg != CURLMSG_DONE)
return;
if (g->on_finished)
g->on_finished(g, msg->easy_handle, msg->data.result);
}
static int curl_glue_on_io(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
CurlGlue *g = userdata;
int action, k = 0, translated_fd;
assert(s);
assert(g);
translated_fd = PTR_TO_FD(hashmap_get(g->translate_fds, FD_TO_PTR(fd)));
if (FLAGS_SET(revents, EPOLLIN | EPOLLOUT))
action = CURL_POLL_INOUT;
else if (revents & EPOLLIN)
action = CURL_POLL_IN;
else if (revents & EPOLLOUT)
action = CURL_POLL_OUT;
else
action = 0;
if (curl_multi_socket_action(g->curl, translated_fd, action, &k) != CURLM_OK)
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
"Failed to propagate IO event.");
curl_glue_check_finished(g);
return 0;
}
static int curl_glue_socket_callback(CURLM *curl, curl_socket_t s, int action, void *userdata, void *socketp) {
sd_event_source *io;
CurlGlue *g = userdata;
uint32_t events = 0;
int r;
assert(curl);
assert(g);
io = hashmap_get(g->ios, FD_TO_PTR(s));
if (action == CURL_POLL_REMOVE) {
if (io) {
int fd;
fd = sd_event_source_get_io_fd(io);
assert(fd >= 0);
2019-05-08 14:47:32 +02:00
sd_event_source_disable_unref(io);
hashmap_remove(g->ios, FD_TO_PTR(s));
hashmap_remove(g->translate_fds, FD_TO_PTR(fd));
safe_close(fd);
}
return 0;
}
r = hashmap_ensure_allocated(&g->ios, &trivial_hash_ops);
if (r < 0) {
log_oom();
return -1;
}
r = hashmap_ensure_allocated(&g->translate_fds, &trivial_hash_ops);
if (r < 0) {
log_oom();
return -1;
}
if (action == CURL_POLL_IN)
events = EPOLLIN;
else if (action == CURL_POLL_OUT)
events = EPOLLOUT;
else if (action == CURL_POLL_INOUT)
events = EPOLLIN|EPOLLOUT;
if (io) {
if (sd_event_source_set_io_events(io, events) < 0)
return -1;
if (sd_event_source_set_enabled(io, SD_EVENT_ON) < 0)
return -1;
} else {
_cleanup_close_ int fd = -1;
/* When curl needs to remove an fd from us it closes
* the fd first, and only then calls into us. This is
* nasty, since we cannot pass the fd on to epoll()
* anymore. Hence, duplicate the fds here, and keep a
* copy for epoll which we control after use. */
fd = fcntl(s, F_DUPFD_CLOEXEC, 3);
if (fd < 0)
return -1;
if (sd_event_add_io(g->event, &io, fd, events, curl_glue_on_io, g) < 0)
return -1;
(void) sd_event_source_set_description(io, "curl-io");
r = hashmap_put(g->ios, FD_TO_PTR(s), io);
if (r < 0) {
log_oom();
sd_event_source_unref(io);
return -1;
}
r = hashmap_put(g->translate_fds, FD_TO_PTR(fd), FD_TO_PTR(s));
if (r < 0) {
log_oom();
hashmap_remove(g->ios, FD_TO_PTR(s));
sd_event_source_unref(io);
return -1;
}
fd = -1;
}
return 0;
}
static int curl_glue_on_timer(sd_event_source *s, uint64_t usec, void *userdata) {
CurlGlue *g = userdata;
int k = 0;
assert(s);
assert(g);
if (curl_multi_socket_action(g->curl, CURL_SOCKET_TIMEOUT, 0, &k) != CURLM_OK)
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
"Failed to propagate timeout.");
curl_glue_check_finished(g);
return 0;
}
static int curl_glue_timer_callback(CURLM *curl, long timeout_ms, void *userdata) {
CurlGlue *g = userdata;
usec_t usec;
assert(curl);
assert(g);
if (timeout_ms < 0) {
if (g->timer) {
if (sd_event_source_set_enabled(g->timer, SD_EVENT_OFF) < 0)
return -1;
}
return 0;
}
usec = now(clock_boottime_or_monotonic()) + (usec_t) timeout_ms * USEC_PER_MSEC + USEC_PER_MSEC - 1;
if (g->timer) {
if (sd_event_source_set_time(g->timer, usec) < 0)
return -1;
if (sd_event_source_set_enabled(g->timer, SD_EVENT_ONESHOT) < 0)
return -1;
} else {
if (sd_event_add_time(g->event, &g->timer, clock_boottime_or_monotonic(), usec, 0, curl_glue_on_timer, g) < 0)
return -1;
(void) sd_event_source_set_description(g->timer, "curl-timer");
}
return 0;
}
CurlGlue *curl_glue_unref(CurlGlue *g) {
sd_event_source *io;
if (!g)
return NULL;
if (g->curl)
curl_multi_cleanup(g->curl);
while ((io = hashmap_steal_first(g->ios))) {
int fd;
fd = sd_event_source_get_io_fd(io);
assert(fd >= 0);
hashmap_remove(g->translate_fds, FD_TO_PTR(fd));
safe_close(fd);
sd_event_source_unref(io);
}
hashmap_free(g->ios);
sd_event_source_unref(g->timer);
sd_event_unref(g->event);
2016-10-17 00:28:30 +02:00
return mfree(g);
}
int curl_glue_new(CurlGlue **glue, sd_event *event) {
_cleanup_(curl_glue_unrefp) CurlGlue *g = NULL;
2018-11-23 19:52:35 +01:00
_cleanup_(curl_multi_cleanupp) CURL *c = NULL;
_cleanup_(sd_event_unrefp) sd_event *e = NULL;
int r;
if (event)
2018-11-23 19:52:35 +01:00
e = sd_event_ref(event);
else {
2018-11-23 19:52:35 +01:00
r = sd_event_default(&e);
if (r < 0)
return r;
}
2018-11-23 19:52:35 +01:00
c = curl_multi_init();
if (!c)
return -ENOMEM;
2018-11-23 19:52:35 +01:00
g = new(CurlGlue, 1);
if (!g)
return -ENOMEM;
*g = (CurlGlue) {
.event = TAKE_PTR(e),
.curl = TAKE_PTR(c),
};
if (curl_multi_setopt(g->curl, CURLMOPT_SOCKETDATA, g) != CURLM_OK)
return -EINVAL;
if (curl_multi_setopt(g->curl, CURLMOPT_SOCKETFUNCTION, curl_glue_socket_callback) != CURLM_OK)
return -EINVAL;
if (curl_multi_setopt(g->curl, CURLMOPT_TIMERDATA, g) != CURLM_OK)
return -EINVAL;
if (curl_multi_setopt(g->curl, CURLMOPT_TIMERFUNCTION, curl_glue_timer_callback) != CURLM_OK)
return -EINVAL;
*glue = TAKE_PTR(g);
return 0;
}
int curl_glue_make(CURL **ret, const char *url, void *userdata) {
_cleanup_(curl_easy_cleanupp) CURL *c = NULL;
const char *useragent;
assert(ret);
assert(url);
c = curl_easy_init();
if (!c)
return -ENOMEM;
/* curl_easy_setopt(c, CURLOPT_VERBOSE, 1L); */
if (curl_easy_setopt(c, CURLOPT_URL, url) != CURLE_OK)
return -EIO;
if (curl_easy_setopt(c, CURLOPT_PRIVATE, userdata) != CURLE_OK)
return -EIO;
useragent = strjoina(program_invocation_short_name, "/" GIT_VERSION);
if (curl_easy_setopt(c, CURLOPT_USERAGENT, useragent) != CURLE_OK)
return -EIO;
if (curl_easy_setopt(c, CURLOPT_FOLLOWLOCATION, 1L) != CURLE_OK)
return -EIO;
*ret = TAKE_PTR(c);
return 0;
}
int curl_glue_add(CurlGlue *g, CURL *c) {
assert(g);
assert(c);
if (curl_multi_add_handle(g->curl, c) != CURLM_OK)
return -EIO;
return 0;
}
void curl_glue_remove_and_free(CurlGlue *g, CURL *c) {
assert(g);
if (!c)
return;
if (g->curl)
curl_multi_remove_handle(g->curl, c);
curl_easy_cleanup(c);
}
struct curl_slist *curl_slist_new(const char *first, ...) {
struct curl_slist *l;
va_list ap;
if (!first)
return NULL;
l = curl_slist_append(NULL, first);
if (!l)
return NULL;
va_start(ap, first);
for (;;) {
struct curl_slist *n;
const char *i;
i = va_arg(ap, const char*);
if (!i)
break;
n = curl_slist_append(l, i);
if (!n) {
va_end(ap);
curl_slist_free_all(l);
return NULL;
}
l = n;
}
va_end(ap);
return l;
}
int curl_header_strdup(const void *contents, size_t sz, const char *field, char **value) {
const char *p;
char *s;
importd, basic/string-util: use case-insensitive comparison for HTTP headers According to RFC2616[1], HTTP header names are case-insensitive. So it's totally valid to have a header starting with either `Date:` or `date:`. However, when systemd-importd pulls an image from an HTTP server, it parses HTTP headers by comparing header names as-is, without any conversion. That causes failures when some HTTP servers return headers with different combinations of upper-/lower-cases. An example: https://alpha.release.flatcar-linux.net/amd64-usr/current/flatcar_developer_container.bin.bz2 returns `Etag: "pe89so9oir60"`, while https://alpha.release.core-os.net/amd64-usr/current/coreos_developer_container.bin.bz2 returns `ETag: "f03372edea9a1e7232e282c346099857"`. Since systemd-importd expects to see `ETag`, the etag for the Container Linux image is correctly interpreted as a part of the hidden file name. However, it cannot parse etag for Flatcar Linux, so the etag the Flatcar Linux image is not appended to the hidden file name. ``` $ sudo ls -al /var/lib/machines/ -r--r--r-- 1 root root 3303014400 Aug 21 20:07 '.raw-https:\x2f\x2falpha\x2erelease\x2ecore-os\x2enet\x2famd64-usr\x2fcurrent\x2fcoreos_developer_container\x2ebin\x2ebz2.\x22f03372edea9a1e7232e282c346099857\x22.raw' -r--r--r-- 1 root root 3303014400 Aug 17 06:15 '.raw-https:\x2f\x2falpha\x2erelease\x2eflatcar-linux\x2enet\x2famd64-usr\x2fcurrent\x2fflatcar_developer_container\x2ebin\x2ebz2.raw' ``` As a result, when the Flatcar image is removed and downloaded again, systemd-importd is not able to determine if the file has been already downloaded, so it always download it again. Then it fails to rename it to an expected name, because there's already a hidden file. To fix this issue, let's introduce a new helper function `memory_startswith_no_case()`, which compares memory regions in a case-insensitive way. Use this function in `curl_header_strdup()`. See also https://github.com/kinvolk/kube-spawn/issues/304 [1]: https://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2
2018-09-03 20:44:13 +02:00
p = memory_startswith_no_case(contents, sz, field);
if (!p)
return 0;
sz -= p - (const char*) contents;
if (memchr(p, 0, sz))
return 0;
2019-04-27 02:22:40 +02:00
/* Skip over preceding whitespace */
while (sz > 0 && strchr(WHITESPACE, p[0])) {
p++;
sz--;
}
2017-02-24 18:14:02 +01:00
/* Truncate trailing whitespace */
while (sz > 0 && strchr(WHITESPACE, p[sz-1]))
sz--;
s = strndup(p, sz);
if (!s)
return -ENOMEM;
*value = s;
return 1;
}
2014-12-25 03:14:09 +01:00
int curl_parse_http_time(const char *t, usec_t *ret) {
_cleanup_(freelocalep) locale_t loc = (locale_t) 0;
const char *e;
struct tm tm;
time_t v;
assert(t);
assert(ret);
loc = newlocale(LC_TIME_MASK, "C", (locale_t) 0);
if (loc == (locale_t) 0)
return -errno;
/* RFC822 */
e = strptime_l(t, "%a, %d %b %Y %H:%M:%S %Z", &tm, loc);
if (!e || *e != 0)
/* RFC 850 */
e = strptime_l(t, "%A, %d-%b-%y %H:%M:%S %Z", &tm, loc);
if (!e || *e != 0)
/* ANSI C */
e = strptime_l(t, "%a %b %d %H:%M:%S %Y", &tm, loc);
if (!e || *e != 0)
return -EINVAL;
v = timegm(&tm);
if (v == (time_t) -1)
return -EINVAL;
2014-12-25 03:14:09 +01:00
*ret = (usec_t) v * USEC_PER_SEC;
return 0;
}