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