1 /* SPDX-License-Identifier: LGPL-2.1+ */
3 This file is part of systemd.
5 Copyright 2015 Lennart Poettering
7 systemd is free software; you can redistribute it and/or modify it
8 under the terms of the GNU Lesser General Public License as published by
9 the Free Software Foundation; either version 2.1 of the License, or
10 (at your option) any later version.
12 systemd is distributed in the hope that it will be useful, but
13 WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 Lesser General Public License for more details.
17 You should have received a copy of the GNU Lesser General Public License
18 along with systemd; If not, see <http://www.gnu.org/licenses/>.
21 #include <sys/xattr.h>
23 #include "alloc-util.h"
25 #include "hexdecoct.h"
26 #include "import-util.h"
28 #include "machine-pool.h"
29 #include "parse-util.h"
30 #include "pull-common.h"
32 #include "string-util.h"
34 #include "xattr-util.h"
36 PullJob
* pull_job_unref(PullJob
*j
) {
40 curl_glue_remove_and_free(j
->glue
, j
->curl
);
41 curl_slist_free_all(j
->request_header
);
43 safe_close(j
->disk_fd
);
45 import_compress_free(&j
->compress
);
47 if (j
->checksum_context
)
48 gcry_md_close(j
->checksum_context
);
52 strv_free(j
->old_etags
);
59 static void pull_job_finish(PullJob
*j
, int ret
) {
62 if (IN_SET(j
->state
, PULL_JOB_DONE
, PULL_JOB_FAILED
))
66 j
->state
= PULL_JOB_DONE
;
67 j
->progress_percent
= 100;
68 log_info("Download of %s complete.", j
->url
);
70 j
->state
= PULL_JOB_FAILED
;
78 static int pull_job_restart(PullJob
*j
) {
80 char *chksum_url
= NULL
;
82 r
= import_url_change_last_component(j
->url
, "SHA256SUMS", &chksum_url
);
88 j
->state
= PULL_JOB_INIT
;
89 j
->payload
= mfree(j
->payload
);
91 j
->payload_allocated
= 0;
92 j
->written_compressed
= 0;
93 j
->written_uncompressed
= 0;
94 j
->written_since_last_grow
= 0;
96 r
= pull_job_begin(j
);
103 void pull_job_curl_on_finished(CurlGlue
*g
, CURL
*curl
, CURLcode result
) {
109 if (curl_easy_getinfo(curl
, CURLINFO_PRIVATE
, (char **)&j
) != CURLE_OK
)
112 if (!j
|| IN_SET(j
->state
, PULL_JOB_DONE
, PULL_JOB_FAILED
))
115 if (result
!= CURLE_OK
) {
116 log_error("Transfer failed: %s", curl_easy_strerror(result
));
121 code
= curl_easy_getinfo(curl
, CURLINFO_RESPONSE_CODE
, &status
);
122 if (code
!= CURLE_OK
) {
123 log_error("Failed to retrieve response code: %s", curl_easy_strerror(code
));
126 } else if (status
== 304) {
127 log_info("Image already downloaded. Skipping download.");
128 j
->etag_exists
= true;
131 } else if (status
>= 300) {
132 if (status
== 404 && j
->style
== VERIFICATION_PER_FILE
) {
134 /* retry pull job with SHA256SUMS file */
135 r
= pull_job_restart(j
);
139 code
= curl_easy_getinfo(j
->curl
, CURLINFO_RESPONSE_CODE
, &status
);
140 if (code
!= CURLE_OK
) {
141 log_error("Failed to retrieve response code: %s", curl_easy_strerror(code
));
147 j
->style
= VERIFICATION_PER_DIRECTORY
;
152 log_error("HTTP request to %s failed with code %li.", j
->url
, status
);
155 } else if (status
< 200) {
156 log_error("HTTP request to %s finished with unexpected code %li.", j
->url
, status
);
161 if (j
->state
!= PULL_JOB_RUNNING
) {
162 log_error("Premature connection termination.");
167 if (j
->content_length
!= (uint64_t) -1 &&
168 j
->content_length
!= j
->written_compressed
) {
169 log_error("Download truncated.");
174 if (j
->checksum_context
) {
177 k
= gcry_md_read(j
->checksum_context
, GCRY_MD_SHA256
);
179 log_error("Failed to get checksum.");
184 j
->checksum
= hexmem(k
, gcry_md_get_algo_dlen(GCRY_MD_SHA256
));
190 log_debug("SHA256 of %s is %s.", j
->url
, j
->checksum
);
193 if (j
->disk_fd
>= 0 && j
->allow_sparse
) {
194 /* Make sure the file size is right, in case the file was
195 * sparse and we just seeked for the last part */
197 if (ftruncate(j
->disk_fd
, j
->written_uncompressed
) < 0) {
198 r
= log_error_errno(errno
, "Failed to truncate file: %m");
203 (void) fsetxattr(j
->disk_fd
, "user.source_etag", j
->etag
, strlen(j
->etag
), 0);
205 (void) fsetxattr(j
->disk_fd
, "user.source_url", j
->url
, strlen(j
->url
), 0);
208 struct timespec ut
[2];
210 timespec_store(&ut
[0], j
->mtime
);
212 (void) futimens(j
->disk_fd
, ut
);
214 (void) fd_setcrtime(j
->disk_fd
, j
->mtime
);
221 pull_job_finish(j
, r
);
224 static int pull_job_write_uncompressed(const void *p
, size_t sz
, void *userdata
) {
225 PullJob
*j
= userdata
;
234 if (j
->written_uncompressed
+ sz
< j
->written_uncompressed
) {
235 log_error("File too large, overflow");
239 if (j
->written_uncompressed
+ sz
> j
->uncompressed_max
) {
240 log_error("File overly large, refusing");
244 if (j
->disk_fd
>= 0) {
246 if (j
->grow_machine_directory
&& j
->written_since_last_grow
>= GROW_INTERVAL_BYTES
) {
247 j
->written_since_last_grow
= 0;
248 grow_machine_directory();
252 n
= sparse_write(j
->disk_fd
, p
, sz
, 64);
254 n
= write(j
->disk_fd
, p
, sz
);
256 return log_error_errno(errno
, "Failed to write file: %m");
257 if ((size_t) n
< sz
) {
258 log_error("Short write");
263 if (!GREEDY_REALLOC(j
->payload
, j
->payload_allocated
, j
->payload_size
+ sz
))
266 memcpy(j
->payload
+ j
->payload_size
, p
, sz
);
267 j
->payload_size
+= sz
;
270 j
->written_uncompressed
+= sz
;
271 j
->written_since_last_grow
+= sz
;
276 static int pull_job_write_compressed(PullJob
*j
, void *p
, size_t sz
) {
285 if (j
->written_compressed
+ sz
< j
->written_compressed
) {
286 log_error("File too large, overflow");
290 if (j
->written_compressed
+ sz
> j
->compressed_max
) {
291 log_error("File overly large, refusing.");
295 if (j
->content_length
!= (uint64_t) -1 &&
296 j
->written_compressed
+ sz
> j
->content_length
) {
297 log_error("Content length incorrect.");
301 if (j
->checksum_context
)
302 gcry_md_write(j
->checksum_context
, p
, sz
);
304 r
= import_uncompress(&j
->compress
, p
, sz
, pull_job_write_uncompressed
, j
);
308 j
->written_compressed
+= sz
;
313 static int pull_job_open_disk(PullJob
*j
) {
318 if (j
->on_open_disk
) {
319 r
= j
->on_open_disk(j
);
324 if (j
->disk_fd
>= 0) {
325 /* Check if we can do sparse files */
327 if (lseek(j
->disk_fd
, SEEK_SET
, 0) == 0)
328 j
->allow_sparse
= true;
331 return log_error_errno(errno
, "Failed to seek on file descriptor: %m");
333 j
->allow_sparse
= false;
337 if (j
->calc_checksum
) {
338 if (gcry_md_open(&j
->checksum_context
, GCRY_MD_SHA256
, 0) != 0) {
339 log_error("Failed to initialize hash context.");
347 static int pull_job_detect_compression(PullJob
*j
) {
348 _cleanup_free_
uint8_t *stub
= NULL
;
355 r
= import_uncompress_detect(&j
->compress
, j
->payload
, j
->payload_size
);
357 return log_error_errno(r
, "Failed to initialize compressor: %m");
361 log_debug("Stream is compressed: %s", import_compress_type_to_string(j
->compress
.type
));
363 r
= pull_job_open_disk(j
);
367 /* Now, take the payload we read so far, and decompress it */
369 stub_size
= j
->payload_size
;
373 j
->payload_allocated
= 0;
375 j
->state
= PULL_JOB_RUNNING
;
377 r
= pull_job_write_compressed(j
, stub
, stub_size
);
384 static size_t pull_job_write_callback(void *contents
, size_t size
, size_t nmemb
, void *userdata
) {
385 PullJob
*j
= userdata
;
386 size_t sz
= size
* nmemb
;
394 case PULL_JOB_ANALYZING
:
395 /* Let's first check what it actually is */
397 if (!GREEDY_REALLOC(j
->payload
, j
->payload_allocated
, j
->payload_size
+ sz
)) {
402 memcpy(j
->payload
+ j
->payload_size
, contents
, sz
);
403 j
->payload_size
+= sz
;
405 r
= pull_job_detect_compression(j
);
411 case PULL_JOB_RUNNING
:
413 r
= pull_job_write_compressed(j
, contents
, sz
);
420 case PULL_JOB_FAILED
:
425 assert_not_reached("Impossible state.");
431 pull_job_finish(j
, r
);
435 static size_t pull_job_header_callback(void *contents
, size_t size
, size_t nmemb
, void *userdata
) {
436 PullJob
*j
= userdata
;
437 size_t sz
= size
* nmemb
;
438 _cleanup_free_
char *length
= NULL
, *last_modified
= NULL
;
445 if (IN_SET(j
->state
, PULL_JOB_DONE
, PULL_JOB_FAILED
)) {
450 assert(j
->state
== PULL_JOB_ANALYZING
);
452 r
= curl_header_strdup(contents
, sz
, "ETag:", &etag
);
461 if (strv_contains(j
->old_etags
, j
->etag
)) {
462 log_info("Image already downloaded. Skipping download.");
463 j
->etag_exists
= true;
464 pull_job_finish(j
, 0);
471 r
= curl_header_strdup(contents
, sz
, "Content-Length:", &length
);
477 (void) safe_atou64(length
, &j
->content_length
);
479 if (j
->content_length
!= (uint64_t) -1) {
480 char bytes
[FORMAT_BYTES_MAX
];
482 if (j
->content_length
> j
->compressed_max
) {
483 log_error("Content too large.");
488 log_info("Downloading %s for %s.", format_bytes(bytes
, sizeof(bytes
), j
->content_length
), j
->url
);
494 r
= curl_header_strdup(contents
, sz
, "Last-Modified:", &last_modified
);
500 (void) curl_parse_http_time(last_modified
, &j
->mtime
);
505 r
= j
->on_header(j
, contents
, sz
);
513 pull_job_finish(j
, r
);
517 static int pull_job_progress_callback(void *userdata
, curl_off_t dltotal
, curl_off_t dlnow
, curl_off_t ultotal
, curl_off_t ulnow
) {
518 PullJob
*j
= userdata
;
527 percent
= ((100 * dlnow
) / dltotal
);
528 n
= now(CLOCK_MONOTONIC
);
530 if (n
> j
->last_status_usec
+ USEC_PER_SEC
&&
531 percent
!= j
->progress_percent
&&
533 char buf
[FORMAT_TIMESPAN_MAX
];
535 if (n
- j
->start_usec
> USEC_PER_SEC
&& dlnow
> 0) {
536 char y
[FORMAT_BYTES_MAX
];
539 done
= n
- j
->start_usec
;
540 left
= (usec_t
) (((double) done
* (double) dltotal
) / dlnow
) - done
;
542 log_info("Got %u%% of %s. %s left at %s/s.",
545 format_timespan(buf
, sizeof(buf
), left
, USEC_PER_SEC
),
546 format_bytes(y
, sizeof(y
), (uint64_t) ((double) dlnow
/ ((double) done
/ (double) USEC_PER_SEC
))));
548 log_info("Got %u%% of %s.", percent
, j
->url
);
550 j
->progress_percent
= percent
;
551 j
->last_status_usec
= n
;
560 int pull_job_new(PullJob
**ret
, const char *url
, CurlGlue
*glue
, void *userdata
) {
561 _cleanup_(pull_job_unrefp
) PullJob
*j
= NULL
;
567 j
= new0(PullJob
, 1);
571 j
->state
= PULL_JOB_INIT
;
573 j
->userdata
= userdata
;
575 j
->content_length
= (uint64_t) -1;
576 j
->start_usec
= now(CLOCK_MONOTONIC
);
577 j
->compressed_max
= j
->uncompressed_max
= 64LLU * 1024LLU * 1024LLU * 1024LLU; /* 64GB safety limit */
578 j
->style
= VERIFICATION_STYLE_UNSET
;
580 j
->url
= strdup(url
);
590 int pull_job_begin(PullJob
*j
) {
595 if (j
->state
!= PULL_JOB_INIT
)
598 if (j
->grow_machine_directory
)
599 grow_machine_directory();
601 r
= curl_glue_make(&j
->curl
, j
->url
, j
);
605 if (!strv_isempty(j
->old_etags
)) {
606 _cleanup_free_
char *cc
= NULL
, *hdr
= NULL
;
608 cc
= strv_join(j
->old_etags
, ", ");
612 hdr
= strappend("If-None-Match: ", cc
);
616 if (!j
->request_header
) {
617 j
->request_header
= curl_slist_new(hdr
, NULL
);
618 if (!j
->request_header
)
621 struct curl_slist
*l
;
623 l
= curl_slist_append(j
->request_header
, hdr
);
627 j
->request_header
= l
;
631 if (j
->request_header
) {
632 if (curl_easy_setopt(j
->curl
, CURLOPT_HTTPHEADER
, j
->request_header
) != CURLE_OK
)
636 if (curl_easy_setopt(j
->curl
, CURLOPT_WRITEFUNCTION
, pull_job_write_callback
) != CURLE_OK
)
639 if (curl_easy_setopt(j
->curl
, CURLOPT_WRITEDATA
, j
) != CURLE_OK
)
642 if (curl_easy_setopt(j
->curl
, CURLOPT_HEADERFUNCTION
, pull_job_header_callback
) != CURLE_OK
)
645 if (curl_easy_setopt(j
->curl
, CURLOPT_HEADERDATA
, j
) != CURLE_OK
)
648 if (curl_easy_setopt(j
->curl
, CURLOPT_XFERINFOFUNCTION
, pull_job_progress_callback
) != CURLE_OK
)
651 if (curl_easy_setopt(j
->curl
, CURLOPT_XFERINFODATA
, j
) != CURLE_OK
)
654 if (curl_easy_setopt(j
->curl
, CURLOPT_NOPROGRESS
, 0) != CURLE_OK
)
657 r
= curl_glue_add(j
->glue
, j
->curl
);
661 j
->state
= PULL_JOB_ANALYZING
;