1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
4 This file is part of systemd.
6 Copyright 2015 Lennart Poettering
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.
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.
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/>.
22 #include <sys/xattr.h>
26 #include "machine-pool.h"
28 #include "string-util.h"
31 PullJob
* pull_job_unref(PullJob
*j
) {
35 curl_glue_remove_and_free(j
->glue
, j
->curl
);
36 curl_slist_free_all(j
->request_header
);
38 safe_close(j
->disk_fd
);
40 import_compress_free(&j
->compress
);
42 if (j
->checksum_context
)
43 gcry_md_close(j
->checksum_context
);
47 strv_free(j
->old_etags
);
56 static void pull_job_finish(PullJob
*j
, int ret
) {
59 if (j
->state
== PULL_JOB_DONE
||
60 j
->state
== PULL_JOB_FAILED
)
64 j
->state
= PULL_JOB_DONE
;
65 j
->progress_percent
= 100;
66 log_info("Download of %s complete.", j
->url
);
68 j
->state
= PULL_JOB_FAILED
;
76 void pull_job_curl_on_finished(CurlGlue
*g
, CURL
*curl
, CURLcode result
) {
82 if (curl_easy_getinfo(curl
, CURLINFO_PRIVATE
, (char **)&j
) != CURLE_OK
)
85 if (!j
|| j
->state
== PULL_JOB_DONE
|| j
->state
== PULL_JOB_FAILED
)
88 if (result
!= CURLE_OK
) {
89 log_error("Transfer failed: %s", curl_easy_strerror(result
));
94 code
= curl_easy_getinfo(curl
, CURLINFO_RESPONSE_CODE
, &status
);
95 if (code
!= CURLE_OK
) {
96 log_error("Failed to retrieve response code: %s", curl_easy_strerror(code
));
99 } else if (status
== 304) {
100 log_info("Image already downloaded. Skipping download.");
101 j
->etag_exists
= true;
104 } else if (status
>= 300) {
105 log_error("HTTP request to %s failed with code %li.", j
->url
, status
);
108 } else if (status
< 200) {
109 log_error("HTTP request to %s finished with unexpected code %li.", j
->url
, status
);
114 if (j
->state
!= PULL_JOB_RUNNING
) {
115 log_error("Premature connection termination.");
120 if (j
->content_length
!= (uint64_t) -1 &&
121 j
->content_length
!= j
->written_compressed
) {
122 log_error("Download truncated.");
127 if (j
->checksum_context
) {
130 k
= gcry_md_read(j
->checksum_context
, GCRY_MD_SHA256
);
132 log_error("Failed to get checksum.");
137 j
->checksum
= hexmem(k
, gcry_md_get_algo_dlen(GCRY_MD_SHA256
));
143 log_debug("SHA256 of %s is %s.", j
->url
, j
->checksum
);
146 if (j
->disk_fd
>= 0 && j
->allow_sparse
) {
147 /* Make sure the file size is right, in case the file was
148 * sparse and we just seeked for the last part */
150 if (ftruncate(j
->disk_fd
, j
->written_uncompressed
) < 0) {
151 r
= log_error_errno(errno
, "Failed to truncate file: %m");
156 (void) fsetxattr(j
->disk_fd
, "user.source_etag", j
->etag
, strlen(j
->etag
), 0);
158 (void) fsetxattr(j
->disk_fd
, "user.source_url", j
->url
, strlen(j
->url
), 0);
161 struct timespec ut
[2];
163 timespec_store(&ut
[0], j
->mtime
);
165 (void) futimens(j
->disk_fd
, ut
);
167 (void) fd_setcrtime(j
->disk_fd
, j
->mtime
);
174 pull_job_finish(j
, r
);
177 static int pull_job_write_uncompressed(const void *p
, size_t sz
, void *userdata
) {
178 PullJob
*j
= userdata
;
187 if (j
->written_uncompressed
+ sz
< j
->written_uncompressed
) {
188 log_error("File too large, overflow");
192 if (j
->written_uncompressed
+ sz
> j
->uncompressed_max
) {
193 log_error("File overly large, refusing");
197 if (j
->disk_fd
>= 0) {
199 if (j
->grow_machine_directory
&& j
->written_since_last_grow
>= GROW_INTERVAL_BYTES
) {
200 j
->written_since_last_grow
= 0;
201 grow_machine_directory();
205 n
= sparse_write(j
->disk_fd
, p
, sz
, 64);
207 n
= write(j
->disk_fd
, p
, sz
);
209 return log_error_errno(errno
, "Failed to write file: %m");
210 if ((size_t) n
< sz
) {
211 log_error("Short write");
216 if (!GREEDY_REALLOC(j
->payload
, j
->payload_allocated
, j
->payload_size
+ sz
))
219 memcpy(j
->payload
+ j
->payload_size
, p
, sz
);
220 j
->payload_size
+= sz
;
223 j
->written_uncompressed
+= sz
;
224 j
->written_since_last_grow
+= sz
;
229 static int pull_job_write_compressed(PullJob
*j
, void *p
, size_t sz
) {
238 if (j
->written_compressed
+ sz
< j
->written_compressed
) {
239 log_error("File too large, overflow");
243 if (j
->written_compressed
+ sz
> j
->compressed_max
) {
244 log_error("File overly large, refusing.");
248 if (j
->content_length
!= (uint64_t) -1 &&
249 j
->written_compressed
+ sz
> j
->content_length
) {
250 log_error("Content length incorrect.");
254 if (j
->checksum_context
)
255 gcry_md_write(j
->checksum_context
, p
, sz
);
257 r
= import_uncompress(&j
->compress
, p
, sz
, pull_job_write_uncompressed
, j
);
261 j
->written_compressed
+= sz
;
266 static int pull_job_open_disk(PullJob
*j
) {
271 if (j
->on_open_disk
) {
272 r
= j
->on_open_disk(j
);
277 if (j
->disk_fd
>= 0) {
278 /* Check if we can do sparse files */
280 if (lseek(j
->disk_fd
, SEEK_SET
, 0) == 0)
281 j
->allow_sparse
= true;
284 return log_error_errno(errno
, "Failed to seek on file descriptor: %m");
286 j
->allow_sparse
= false;
290 if (j
->calc_checksum
) {
291 if (gcry_md_open(&j
->checksum_context
, GCRY_MD_SHA256
, 0) != 0) {
292 log_error("Failed to initialize hash context.");
300 static int pull_job_detect_compression(PullJob
*j
) {
301 _cleanup_free_
uint8_t *stub
= NULL
;
308 r
= import_uncompress_detect(&j
->compress
, j
->payload
, j
->payload_size
);
310 return log_error_errno(r
, "Failed to initialize compressor: %m");
314 log_debug("Stream is compressed: %s", import_compress_type_to_string(j
->compress
.type
));
316 r
= pull_job_open_disk(j
);
320 /* Now, take the payload we read so far, and decompress it */
322 stub_size
= j
->payload_size
;
326 j
->payload_allocated
= 0;
328 j
->state
= PULL_JOB_RUNNING
;
330 r
= pull_job_write_compressed(j
, stub
, stub_size
);
337 static size_t pull_job_write_callback(void *contents
, size_t size
, size_t nmemb
, void *userdata
) {
338 PullJob
*j
= userdata
;
339 size_t sz
= size
* nmemb
;
347 case PULL_JOB_ANALYZING
:
348 /* Let's first check what it actually is */
350 if (!GREEDY_REALLOC(j
->payload
, j
->payload_allocated
, j
->payload_size
+ sz
)) {
355 memcpy(j
->payload
+ j
->payload_size
, contents
, sz
);
356 j
->payload_size
+= sz
;
358 r
= pull_job_detect_compression(j
);
364 case PULL_JOB_RUNNING
:
366 r
= pull_job_write_compressed(j
, contents
, sz
);
373 case PULL_JOB_FAILED
:
378 assert_not_reached("Impossible state.");
384 pull_job_finish(j
, r
);
388 static size_t pull_job_header_callback(void *contents
, size_t size
, size_t nmemb
, void *userdata
) {
389 PullJob
*j
= userdata
;
390 size_t sz
= size
* nmemb
;
391 _cleanup_free_
char *length
= NULL
, *last_modified
= NULL
;
398 if (j
->state
== PULL_JOB_DONE
|| j
->state
== PULL_JOB_FAILED
) {
403 assert(j
->state
== PULL_JOB_ANALYZING
);
405 r
= curl_header_strdup(contents
, sz
, "ETag:", &etag
);
414 if (strv_contains(j
->old_etags
, j
->etag
)) {
415 log_info("Image already downloaded. Skipping download.");
416 j
->etag_exists
= true;
417 pull_job_finish(j
, 0);
424 r
= curl_header_strdup(contents
, sz
, "Content-Length:", &length
);
430 (void) safe_atou64(length
, &j
->content_length
);
432 if (j
->content_length
!= (uint64_t) -1) {
433 char bytes
[FORMAT_BYTES_MAX
];
435 if (j
->content_length
> j
->compressed_max
) {
436 log_error("Content too large.");
441 log_info("Downloading %s for %s.", format_bytes(bytes
, sizeof(bytes
), j
->content_length
), j
->url
);
447 r
= curl_header_strdup(contents
, sz
, "Last-Modified:", &last_modified
);
453 (void) curl_parse_http_time(last_modified
, &j
->mtime
);
458 r
= j
->on_header(j
, contents
, sz
);
466 pull_job_finish(j
, r
);
470 static int pull_job_progress_callback(void *userdata
, curl_off_t dltotal
, curl_off_t dlnow
, curl_off_t ultotal
, curl_off_t ulnow
) {
471 PullJob
*j
= userdata
;
480 percent
= ((100 * dlnow
) / dltotal
);
481 n
= now(CLOCK_MONOTONIC
);
483 if (n
> j
->last_status_usec
+ USEC_PER_SEC
&&
484 percent
!= j
->progress_percent
&&
486 char buf
[FORMAT_TIMESPAN_MAX
];
488 if (n
- j
->start_usec
> USEC_PER_SEC
&& dlnow
> 0) {
489 char y
[FORMAT_BYTES_MAX
];
492 done
= n
- j
->start_usec
;
493 left
= (usec_t
) (((double) done
* (double) dltotal
) / dlnow
) - done
;
495 log_info("Got %u%% of %s. %s left at %s/s.",
498 format_timespan(buf
, sizeof(buf
), left
, USEC_PER_SEC
),
499 format_bytes(y
, sizeof(y
), (uint64_t) ((double) dlnow
/ ((double) done
/ (double) USEC_PER_SEC
))));
501 log_info("Got %u%% of %s.", percent
, j
->url
);
503 j
->progress_percent
= percent
;
504 j
->last_status_usec
= n
;
513 int pull_job_new(PullJob
**ret
, const char *url
, CurlGlue
*glue
, void *userdata
) {
514 _cleanup_(pull_job_unrefp
) PullJob
*j
= NULL
;
520 j
= new0(PullJob
, 1);
524 j
->state
= PULL_JOB_INIT
;
526 j
->userdata
= userdata
;
528 j
->content_length
= (uint64_t) -1;
529 j
->start_usec
= now(CLOCK_MONOTONIC
);
530 j
->compressed_max
= j
->uncompressed_max
= 8LLU * 1024LLU * 1024LLU * 1024LLU; /* 8GB */
532 j
->url
= strdup(url
);
542 int pull_job_begin(PullJob
*j
) {
547 if (j
->state
!= PULL_JOB_INIT
)
550 if (j
->grow_machine_directory
)
551 grow_machine_directory();
553 r
= curl_glue_make(&j
->curl
, j
->url
, j
);
557 if (!strv_isempty(j
->old_etags
)) {
558 _cleanup_free_
char *cc
= NULL
, *hdr
= NULL
;
560 cc
= strv_join(j
->old_etags
, ", ");
564 hdr
= strappend("If-None-Match: ", cc
);
568 if (!j
->request_header
) {
569 j
->request_header
= curl_slist_new(hdr
, NULL
);
570 if (!j
->request_header
)
573 struct curl_slist
*l
;
575 l
= curl_slist_append(j
->request_header
, hdr
);
579 j
->request_header
= l
;
583 if (j
->request_header
) {
584 if (curl_easy_setopt(j
->curl
, CURLOPT_HTTPHEADER
, j
->request_header
) != CURLE_OK
)
588 if (curl_easy_setopt(j
->curl
, CURLOPT_WRITEFUNCTION
, pull_job_write_callback
) != CURLE_OK
)
591 if (curl_easy_setopt(j
->curl
, CURLOPT_WRITEDATA
, j
) != CURLE_OK
)
594 if (curl_easy_setopt(j
->curl
, CURLOPT_HEADERFUNCTION
, pull_job_header_callback
) != CURLE_OK
)
597 if (curl_easy_setopt(j
->curl
, CURLOPT_HEADERDATA
, j
) != CURLE_OK
)
600 if (curl_easy_setopt(j
->curl
, CURLOPT_XFERINFOFUNCTION
, pull_job_progress_callback
) != CURLE_OK
)
603 if (curl_easy_setopt(j
->curl
, CURLOPT_XFERINFODATA
, j
) != CURLE_OK
)
606 if (curl_easy_setopt(j
->curl
, CURLOPT_NOPROGRESS
, 0) != CURLE_OK
)
609 r
= curl_glue_add(j
->glue
, j
->curl
);
613 j
->state
= PULL_JOB_ANALYZING
;