]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/import/pull-job.c
util-lib: split out allocation calls into alloc-util.[ch]
[thirdparty/systemd.git] / src / import / pull-job.c
CommitLineData
56ebfaf1
LP
1/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3/***
4 This file is part of systemd.
5
6 Copyright 2015 Lennart Poettering
7
8 systemd is free software; you can redistribute it and/or modify it
9 under the terms of the GNU Lesser General Public License as published by
10 the Free Software Foundation; either version 2.1 of the License, or
11 (at your option) any later version.
12
13 systemd is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 Lesser General Public License for more details.
17
18 You should have received a copy of the GNU Lesser General Public License
19 along with systemd; If not, see <http://www.gnu.org/licenses/>.
20***/
21
22#include <sys/xattr.h>
23
b5efdb8a 24#include "alloc-util.h"
3ffd4af2 25#include "fd-util.h"
89a5a90c 26#include "hexdecoct.h"
c004493c 27#include "io-util.h"
26166c88 28#include "machine-pool.h"
6bedfcbb 29#include "parse-util.h"
3ffd4af2 30#include "pull-job.h"
07630cea
LP
31#include "string-util.h"
32#include "strv.h"
89a5a90c 33#include "xattr-util.h"
56ebfaf1 34
dc2c282b 35PullJob* pull_job_unref(PullJob *j) {
56ebfaf1
LP
36 if (!j)
37 return NULL;
38
39 curl_glue_remove_and_free(j->glue, j->curl);
40 curl_slist_free_all(j->request_header);
41
42 safe_close(j->disk_fd);
43
3e2cda69 44 import_compress_free(&j->compress);
56ebfaf1 45
98c38001
LP
46 if (j->checksum_context)
47 gcry_md_close(j->checksum_context);
85dbc41d 48
56ebfaf1
LP
49 free(j->url);
50 free(j->etag);
51 strv_free(j->old_etags);
52 free(j->payload);
98c38001 53 free(j->checksum);
56ebfaf1
LP
54
55 free(j);
56
57 return NULL;
58}
59
dc2c282b 60static void pull_job_finish(PullJob *j, int ret) {
56ebfaf1
LP
61 assert(j);
62
dc2c282b
LP
63 if (j->state == PULL_JOB_DONE ||
64 j->state == PULL_JOB_FAILED)
56ebfaf1
LP
65 return;
66
68c913fd 67 if (ret == 0) {
dc2c282b 68 j->state = PULL_JOB_DONE;
7079cfef 69 j->progress_percent = 100;
68c913fd
LP
70 log_info("Download of %s complete.", j->url);
71 } else {
dc2c282b 72 j->state = PULL_JOB_FAILED;
56ebfaf1
LP
73 j->error = ret;
74 }
75
76 if (j->on_finished)
77 j->on_finished(j);
78}
79
dc2c282b
LP
80void pull_job_curl_on_finished(CurlGlue *g, CURL *curl, CURLcode result) {
81 PullJob *j = NULL;
56ebfaf1
LP
82 CURLcode code;
83 long status;
84 int r;
85
a7f7d1bd 86 if (curl_easy_getinfo(curl, CURLINFO_PRIVATE, (char **)&j) != CURLE_OK)
56ebfaf1
LP
87 return;
88
dc2c282b 89 if (!j || j->state == PULL_JOB_DONE || j->state == PULL_JOB_FAILED)
56ebfaf1
LP
90 return;
91
92 if (result != CURLE_OK) {
93 log_error("Transfer failed: %s", curl_easy_strerror(result));
94 r = -EIO;
95 goto finish;
96 }
97
98 code = curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &status);
99 if (code != CURLE_OK) {
100 log_error("Failed to retrieve response code: %s", curl_easy_strerror(code));
101 r = -EIO;
102 goto finish;
103 } else if (status == 304) {
104 log_info("Image already downloaded. Skipping download.");
85dbc41d 105 j->etag_exists = true;
56ebfaf1
LP
106 r = 0;
107 goto finish;
108 } else if (status >= 300) {
109 log_error("HTTP request to %s failed with code %li.", j->url, status);
110 r = -EIO;
111 goto finish;
112 } else if (status < 200) {
113 log_error("HTTP request to %s finished with unexpected code %li.", j->url, status);
114 r = -EIO;
115 goto finish;
116 }
117
dc2c282b 118 if (j->state != PULL_JOB_RUNNING) {
56ebfaf1
LP
119 log_error("Premature connection termination.");
120 r = -EIO;
121 goto finish;
122 }
123
124 if (j->content_length != (uint64_t) -1 &&
125 j->content_length != j->written_compressed) {
126 log_error("Download truncated.");
127 r = -EIO;
128 goto finish;
129 }
130
98c38001 131 if (j->checksum_context) {
85dbc41d
LP
132 uint8_t *k;
133
98c38001 134 k = gcry_md_read(j->checksum_context, GCRY_MD_SHA256);
85dbc41d
LP
135 if (!k) {
136 log_error("Failed to get checksum.");
137 r = -EIO;
138 goto finish;
139 }
140
98c38001
LP
141 j->checksum = hexmem(k, gcry_md_get_algo_dlen(GCRY_MD_SHA256));
142 if (!j->checksum) {
85dbc41d
LP
143 r = log_oom();
144 goto finish;
145 }
146
98c38001 147 log_debug("SHA256 of %s is %s.", j->url, j->checksum);
85dbc41d
LP
148 }
149
56ebfaf1
LP
150 if (j->disk_fd >= 0 && j->allow_sparse) {
151 /* Make sure the file size is right, in case the file was
152 * sparse and we just seeked for the last part */
153
154 if (ftruncate(j->disk_fd, j->written_uncompressed) < 0) {
b6e676ce 155 r = log_error_errno(errno, "Failed to truncate file: %m");
56ebfaf1
LP
156 goto finish;
157 }
158
159 if (j->etag)
160 (void) fsetxattr(j->disk_fd, "user.source_etag", j->etag, strlen(j->etag), 0);
161 if (j->url)
162 (void) fsetxattr(j->disk_fd, "user.source_url", j->url, strlen(j->url), 0);
163
164 if (j->mtime != 0) {
165 struct timespec ut[2];
166
167 timespec_store(&ut[0], j->mtime);
168 ut[1] = ut[0];
169 (void) futimens(j->disk_fd, ut);
170
171 (void) fd_setcrtime(j->disk_fd, j->mtime);
172 }
173 }
174
175 r = 0;
176
177finish:
dc2c282b 178 pull_job_finish(j, r);
56ebfaf1
LP
179}
180
3e2cda69
LP
181static int pull_job_write_uncompressed(const void *p, size_t sz, void *userdata) {
182 PullJob *j = userdata;
56ebfaf1
LP
183 ssize_t n;
184
185 assert(j);
186 assert(p);
8af3cf74
LP
187
188 if (sz <= 0)
189 return 0;
56ebfaf1
LP
190
191 if (j->written_uncompressed + sz < j->written_uncompressed) {
192 log_error("File too large, overflow");
193 return -EOVERFLOW;
194 }
195
196 if (j->written_uncompressed + sz > j->uncompressed_max) {
197 log_error("File overly large, refusing");
198 return -EFBIG;
199 }
200
201 if (j->disk_fd >= 0) {
202
b6e676ce 203 if (j->grow_machine_directory && j->written_since_last_grow >= GROW_INTERVAL_BYTES) {
26166c88
LP
204 j->written_since_last_grow = 0;
205 grow_machine_directory();
206 }
207
56ebfaf1
LP
208 if (j->allow_sparse)
209 n = sparse_write(j->disk_fd, p, sz, 64);
210 else
211 n = write(j->disk_fd, p, sz);
b6e676ce
LP
212 if (n < 0)
213 return log_error_errno(errno, "Failed to write file: %m");
56ebfaf1
LP
214 if ((size_t) n < sz) {
215 log_error("Short write");
216 return -EIO;
217 }
218 } else {
219
220 if (!GREEDY_REALLOC(j->payload, j->payload_allocated, j->payload_size + sz))
221 return log_oom();
222
8af3cf74 223 memcpy(j->payload + j->payload_size, p, sz);
56ebfaf1
LP
224 j->payload_size += sz;
225 }
226
227 j->written_uncompressed += sz;
26166c88 228 j->written_since_last_grow += sz;
56ebfaf1
LP
229
230 return 0;
231}
232
dc2c282b 233static int pull_job_write_compressed(PullJob *j, void *p, size_t sz) {
56ebfaf1
LP
234 int r;
235
236 assert(j);
237 assert(p);
8af3cf74
LP
238
239 if (sz <= 0)
240 return 0;
56ebfaf1
LP
241
242 if (j->written_compressed + sz < j->written_compressed) {
243 log_error("File too large, overflow");
244 return -EOVERFLOW;
245 }
246
247 if (j->written_compressed + sz > j->compressed_max) {
248 log_error("File overly large, refusing.");
249 return -EFBIG;
250 }
251
252 if (j->content_length != (uint64_t) -1 &&
253 j->written_compressed + sz > j->content_length) {
254 log_error("Content length incorrect.");
255 return -EFBIG;
256 }
257
98c38001
LP
258 if (j->checksum_context)
259 gcry_md_write(j->checksum_context, p, sz);
85dbc41d 260
3e2cda69
LP
261 r = import_uncompress(&j->compress, p, sz, pull_job_write_uncompressed, j);
262 if (r < 0)
263 return r;
56ebfaf1
LP
264
265 j->written_compressed += sz;
266
267 return 0;
268}
269
dc2c282b 270static int pull_job_open_disk(PullJob *j) {
56ebfaf1
LP
271 int r;
272
273 assert(j);
274
275 if (j->on_open_disk) {
276 r = j->on_open_disk(j);
277 if (r < 0)
278 return r;
279 }
280
281 if (j->disk_fd >= 0) {
282 /* Check if we can do sparse files */
283
284 if (lseek(j->disk_fd, SEEK_SET, 0) == 0)
285 j->allow_sparse = true;
286 else {
287 if (errno != ESPIPE)
288 return log_error_errno(errno, "Failed to seek on file descriptor: %m");
289
290 j->allow_sparse = false;
291 }
292 }
293
98c38001
LP
294 if (j->calc_checksum) {
295 if (gcry_md_open(&j->checksum_context, GCRY_MD_SHA256, 0) != 0) {
85dbc41d
LP
296 log_error("Failed to initialize hash context.");
297 return -EIO;
298 }
299 }
300
56ebfaf1
LP
301 return 0;
302}
303
dc2c282b 304static int pull_job_detect_compression(PullJob *j) {
56ebfaf1
LP
305 _cleanup_free_ uint8_t *stub = NULL;
306 size_t stub_size;
307
308 int r;
309
310 assert(j);
311
3e2cda69
LP
312 r = import_uncompress_detect(&j->compress, j->payload, j->payload_size);
313 if (r < 0)
314 return log_error_errno(r, "Failed to initialize compressor: %m");
315 if (r == 0)
56ebfaf1
LP
316 return 0;
317
3e2cda69 318 log_debug("Stream is compressed: %s", import_compress_type_to_string(j->compress.type));
56ebfaf1 319
dc2c282b 320 r = pull_job_open_disk(j);
56ebfaf1
LP
321 if (r < 0)
322 return r;
323
324 /* Now, take the payload we read so far, and decompress it */
325 stub = j->payload;
326 stub_size = j->payload_size;
327
328 j->payload = NULL;
329 j->payload_size = 0;
88a1aadc 330 j->payload_allocated = 0;
56ebfaf1 331
dc2c282b 332 j->state = PULL_JOB_RUNNING;
56ebfaf1 333
dc2c282b 334 r = pull_job_write_compressed(j, stub, stub_size);
56ebfaf1
LP
335 if (r < 0)
336 return r;
337
338 return 0;
339}
340
dc2c282b
LP
341static size_t pull_job_write_callback(void *contents, size_t size, size_t nmemb, void *userdata) {
342 PullJob *j = userdata;
56ebfaf1
LP
343 size_t sz = size * nmemb;
344 int r;
345
346 assert(contents);
347 assert(j);
348
349 switch (j->state) {
350
dc2c282b 351 case PULL_JOB_ANALYZING:
56ebfaf1
LP
352 /* Let's first check what it actually is */
353
354 if (!GREEDY_REALLOC(j->payload, j->payload_allocated, j->payload_size + sz)) {
355 r = log_oom();
356 goto fail;
357 }
358
8af3cf74 359 memcpy(j->payload + j->payload_size, contents, sz);
56ebfaf1
LP
360 j->payload_size += sz;
361
dc2c282b 362 r = pull_job_detect_compression(j);
56ebfaf1
LP
363 if (r < 0)
364 goto fail;
365
366 break;
367
dc2c282b 368 case PULL_JOB_RUNNING:
56ebfaf1 369
dc2c282b 370 r = pull_job_write_compressed(j, contents, sz);
56ebfaf1
LP
371 if (r < 0)
372 goto fail;
373
374 break;
375
dc2c282b
LP
376 case PULL_JOB_DONE:
377 case PULL_JOB_FAILED:
56ebfaf1
LP
378 r = -ESTALE;
379 goto fail;
380
381 default:
382 assert_not_reached("Impossible state.");
383 }
384
385 return sz;
386
387fail:
dc2c282b 388 pull_job_finish(j, r);
56ebfaf1
LP
389 return 0;
390}
391
dc2c282b
LP
392static size_t pull_job_header_callback(void *contents, size_t size, size_t nmemb, void *userdata) {
393 PullJob *j = userdata;
56ebfaf1
LP
394 size_t sz = size * nmemb;
395 _cleanup_free_ char *length = NULL, *last_modified = NULL;
396 char *etag;
397 int r;
398
399 assert(contents);
400 assert(j);
401
dc2c282b 402 if (j->state == PULL_JOB_DONE || j->state == PULL_JOB_FAILED) {
56ebfaf1
LP
403 r = -ESTALE;
404 goto fail;
405 }
406
dc2c282b 407 assert(j->state == PULL_JOB_ANALYZING);
56ebfaf1
LP
408
409 r = curl_header_strdup(contents, sz, "ETag:", &etag);
410 if (r < 0) {
411 log_oom();
412 goto fail;
413 }
414 if (r > 0) {
415 free(j->etag);
416 j->etag = etag;
417
418 if (strv_contains(j->old_etags, j->etag)) {
419 log_info("Image already downloaded. Skipping download.");
85dbc41d 420 j->etag_exists = true;
dc2c282b 421 pull_job_finish(j, 0);
56ebfaf1
LP
422 return sz;
423 }
424
425 return sz;
426 }
427
428 r = curl_header_strdup(contents, sz, "Content-Length:", &length);
429 if (r < 0) {
430 log_oom();
431 goto fail;
432 }
433 if (r > 0) {
434 (void) safe_atou64(length, &j->content_length);
435
436 if (j->content_length != (uint64_t) -1) {
437 char bytes[FORMAT_BYTES_MAX];
438
439 if (j->content_length > j->compressed_max) {
440 log_error("Content too large.");
441 r = -EFBIG;
442 goto fail;
443 }
444
68c913fd 445 log_info("Downloading %s for %s.", format_bytes(bytes, sizeof(bytes), j->content_length), j->url);
56ebfaf1
LP
446 }
447
448 return sz;
449 }
450
451 r = curl_header_strdup(contents, sz, "Last-Modified:", &last_modified);
452 if (r < 0) {
453 log_oom();
454 goto fail;
455 }
456 if (r > 0) {
457 (void) curl_parse_http_time(last_modified, &j->mtime);
458 return sz;
459 }
460
ff2670ad
LP
461 if (j->on_header) {
462 r = j->on_header(j, contents, sz);
463 if (r < 0)
464 goto fail;
465 }
466
56ebfaf1
LP
467 return sz;
468
469fail:
dc2c282b 470 pull_job_finish(j, r);
56ebfaf1
LP
471 return 0;
472}
473
dc2c282b
LP
474static int pull_job_progress_callback(void *userdata, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow) {
475 PullJob *j = userdata;
56ebfaf1
LP
476 unsigned percent;
477 usec_t n;
478
479 assert(j);
480
481 if (dltotal <= 0)
482 return 0;
483
484 percent = ((100 * dlnow) / dltotal);
485 n = now(CLOCK_MONOTONIC);
486
487 if (n > j->last_status_usec + USEC_PER_SEC &&
68c913fd
LP
488 percent != j->progress_percent &&
489 dlnow < dltotal) {
56ebfaf1
LP
490 char buf[FORMAT_TIMESPAN_MAX];
491
492 if (n - j->start_usec > USEC_PER_SEC && dlnow > 0) {
90bc083b 493 char y[FORMAT_BYTES_MAX];
56ebfaf1
LP
494 usec_t left, done;
495
496 done = n - j->start_usec;
497 left = (usec_t) (((double) done * (double) dltotal) / dlnow) - done;
498
90bc083b
LP
499 log_info("Got %u%% of %s. %s left at %s/s.",
500 percent,
501 j->url,
502 format_timespan(buf, sizeof(buf), left, USEC_PER_SEC),
503 format_bytes(y, sizeof(y), (uint64_t) ((double) dlnow / ((double) done / (double) USEC_PER_SEC))));
56ebfaf1
LP
504 } else
505 log_info("Got %u%% of %s.", percent, j->url);
506
507 j->progress_percent = percent;
508 j->last_status_usec = n;
7079cfef
LP
509
510 if (j->on_progress)
511 j->on_progress(j);
56ebfaf1
LP
512 }
513
514 return 0;
515}
516
dc2c282b
LP
517int pull_job_new(PullJob **ret, const char *url, CurlGlue *glue, void *userdata) {
518 _cleanup_(pull_job_unrefp) PullJob *j = NULL;
56ebfaf1
LP
519
520 assert(url);
521 assert(glue);
522 assert(ret);
523
dc2c282b 524 j = new0(PullJob, 1);
56ebfaf1
LP
525 if (!j)
526 return -ENOMEM;
527
dc2c282b 528 j->state = PULL_JOB_INIT;
56ebfaf1
LP
529 j->disk_fd = -1;
530 j->userdata = userdata;
531 j->glue = glue;
532 j->content_length = (uint64_t) -1;
533 j->start_usec = now(CLOCK_MONOTONIC);
534 j->compressed_max = j->uncompressed_max = 8LLU * 1024LLU * 1024LLU * 1024LLU; /* 8GB */
535
536 j->url = strdup(url);
537 if (!j->url)
538 return -ENOMEM;
539
540 *ret = j;
541 j = NULL;
542
543 return 0;
544}
545
dc2c282b 546int pull_job_begin(PullJob *j) {
56ebfaf1
LP
547 int r;
548
549 assert(j);
550
dc2c282b 551 if (j->state != PULL_JOB_INIT)
56ebfaf1
LP
552 return -EBUSY;
553
26166c88
LP
554 if (j->grow_machine_directory)
555 grow_machine_directory();
556
56ebfaf1
LP
557 r = curl_glue_make(&j->curl, j->url, j);
558 if (r < 0)
559 return r;
560
561 if (!strv_isempty(j->old_etags)) {
562 _cleanup_free_ char *cc = NULL, *hdr = NULL;
563
564 cc = strv_join(j->old_etags, ", ");
565 if (!cc)
566 return -ENOMEM;
567
568 hdr = strappend("If-None-Match: ", cc);
569 if (!hdr)
570 return -ENOMEM;
571
ff2670ad
LP
572 if (!j->request_header) {
573 j->request_header = curl_slist_new(hdr, NULL);
574 if (!j->request_header)
575 return -ENOMEM;
576 } else {
577 struct curl_slist *l;
578
579 l = curl_slist_append(j->request_header, hdr);
580 if (!l)
581 return -ENOMEM;
582
583 j->request_header = l;
584 }
585 }
56ebfaf1 586
ff2670ad 587 if (j->request_header) {
56ebfaf1
LP
588 if (curl_easy_setopt(j->curl, CURLOPT_HTTPHEADER, j->request_header) != CURLE_OK)
589 return -EIO;
590 }
591
dc2c282b 592 if (curl_easy_setopt(j->curl, CURLOPT_WRITEFUNCTION, pull_job_write_callback) != CURLE_OK)
56ebfaf1
LP
593 return -EIO;
594
595 if (curl_easy_setopt(j->curl, CURLOPT_WRITEDATA, j) != CURLE_OK)
596 return -EIO;
597
dc2c282b 598 if (curl_easy_setopt(j->curl, CURLOPT_HEADERFUNCTION, pull_job_header_callback) != CURLE_OK)
56ebfaf1
LP
599 return -EIO;
600
601 if (curl_easy_setopt(j->curl, CURLOPT_HEADERDATA, j) != CURLE_OK)
602 return -EIO;
603
dc2c282b 604 if (curl_easy_setopt(j->curl, CURLOPT_XFERINFOFUNCTION, pull_job_progress_callback) != CURLE_OK)
56ebfaf1
LP
605 return -EIO;
606
607 if (curl_easy_setopt(j->curl, CURLOPT_XFERINFODATA, j) != CURLE_OK)
608 return -EIO;
609
610 if (curl_easy_setopt(j->curl, CURLOPT_NOPROGRESS, 0) != CURLE_OK)
611 return -EIO;
612
613 r = curl_glue_add(j->glue, j->curl);
614 if (r < 0)
615 return r;
616
dc2c282b 617 j->state = PULL_JOB_ANALYZING;
56ebfaf1
LP
618
619 return 0;
620}