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"
27 #include "parse-util.h"
29 #include "string-util.h"
32 PullJob
* pull_job_unref(PullJob
*j
) {
36 curl_glue_remove_and_free(j
->glue
, j
->curl
);
37 curl_slist_free_all(j
->request_header
);
39 safe_close(j
->disk_fd
);
41 import_compress_free(&j
->compress
);
43 if (j
->checksum_context
)
44 gcry_md_close(j
->checksum_context
);
48 strv_free(j
->old_etags
);
57 static void pull_job_finish(PullJob
*j
, int ret
) {
60 if (j
->state
== PULL_JOB_DONE
||
61 j
->state
== PULL_JOB_FAILED
)
65 j
->state
= PULL_JOB_DONE
;
66 j
->progress_percent
= 100;
67 log_info("Download of %s complete.", j
->url
);
69 j
->state
= PULL_JOB_FAILED
;
77 void pull_job_curl_on_finished(CurlGlue
*g
, CURL
*curl
, CURLcode result
) {
83 if (curl_easy_getinfo(curl
, CURLINFO_PRIVATE
, (char **)&j
) != CURLE_OK
)
86 if (!j
|| j
->state
== PULL_JOB_DONE
|| j
->state
== PULL_JOB_FAILED
)
89 if (result
!= CURLE_OK
) {
90 log_error("Transfer failed: %s", curl_easy_strerror(result
));
95 code
= curl_easy_getinfo(curl
, CURLINFO_RESPONSE_CODE
, &status
);
96 if (code
!= CURLE_OK
) {
97 log_error("Failed to retrieve response code: %s", curl_easy_strerror(code
));
100 } else if (status
== 304) {
101 log_info("Image already downloaded. Skipping download.");
102 j
->etag_exists
= true;
105 } else if (status
>= 300) {
106 log_error("HTTP request to %s failed with code %li.", j
->url
, status
);
109 } else if (status
< 200) {
110 log_error("HTTP request to %s finished with unexpected code %li.", j
->url
, status
);
115 if (j
->state
!= PULL_JOB_RUNNING
) {
116 log_error("Premature connection termination.");
121 if (j
->content_length
!= (uint64_t) -1 &&
122 j
->content_length
!= j
->written_compressed
) {
123 log_error("Download truncated.");
128 if (j
->checksum_context
) {
131 k
= gcry_md_read(j
->checksum_context
, GCRY_MD_SHA256
);
133 log_error("Failed to get checksum.");
138 j
->checksum
= hexmem(k
, gcry_md_get_algo_dlen(GCRY_MD_SHA256
));
144 log_debug("SHA256 of %s is %s.", j
->url
, j
->checksum
);
147 if (j
->disk_fd
>= 0 && j
->allow_sparse
) {
148 /* Make sure the file size is right, in case the file was
149 * sparse and we just seeked for the last part */
151 if (ftruncate(j
->disk_fd
, j
->written_uncompressed
) < 0) {
152 r
= log_error_errno(errno
, "Failed to truncate file: %m");
157 (void) fsetxattr(j
->disk_fd
, "user.source_etag", j
->etag
, strlen(j
->etag
), 0);
159 (void) fsetxattr(j
->disk_fd
, "user.source_url", j
->url
, strlen(j
->url
), 0);
162 struct timespec ut
[2];
164 timespec_store(&ut
[0], j
->mtime
);
166 (void) futimens(j
->disk_fd
, ut
);
168 (void) fd_setcrtime(j
->disk_fd
, j
->mtime
);
175 pull_job_finish(j
, r
);
178 static int pull_job_write_uncompressed(const void *p
, size_t sz
, void *userdata
) {
179 PullJob
*j
= userdata
;
188 if (j
->written_uncompressed
+ sz
< j
->written_uncompressed
) {
189 log_error("File too large, overflow");
193 if (j
->written_uncompressed
+ sz
> j
->uncompressed_max
) {
194 log_error("File overly large, refusing");
198 if (j
->disk_fd
>= 0) {
200 if (j
->grow_machine_directory
&& j
->written_since_last_grow
>= GROW_INTERVAL_BYTES
) {
201 j
->written_since_last_grow
= 0;
202 grow_machine_directory();
206 n
= sparse_write(j
->disk_fd
, p
, sz
, 64);
208 n
= write(j
->disk_fd
, p
, sz
);
210 return log_error_errno(errno
, "Failed to write file: %m");
211 if ((size_t) n
< sz
) {
212 log_error("Short write");
217 if (!GREEDY_REALLOC(j
->payload
, j
->payload_allocated
, j
->payload_size
+ sz
))
220 memcpy(j
->payload
+ j
->payload_size
, p
, sz
);
221 j
->payload_size
+= sz
;
224 j
->written_uncompressed
+= sz
;
225 j
->written_since_last_grow
+= sz
;
230 static int pull_job_write_compressed(PullJob
*j
, void *p
, size_t sz
) {
239 if (j
->written_compressed
+ sz
< j
->written_compressed
) {
240 log_error("File too large, overflow");
244 if (j
->written_compressed
+ sz
> j
->compressed_max
) {
245 log_error("File overly large, refusing.");
249 if (j
->content_length
!= (uint64_t) -1 &&
250 j
->written_compressed
+ sz
> j
->content_length
) {
251 log_error("Content length incorrect.");
255 if (j
->checksum_context
)
256 gcry_md_write(j
->checksum_context
, p
, sz
);
258 r
= import_uncompress(&j
->compress
, p
, sz
, pull_job_write_uncompressed
, j
);
262 j
->written_compressed
+= sz
;
267 static int pull_job_open_disk(PullJob
*j
) {
272 if (j
->on_open_disk
) {
273 r
= j
->on_open_disk(j
);
278 if (j
->disk_fd
>= 0) {
279 /* Check if we can do sparse files */
281 if (lseek(j
->disk_fd
, SEEK_SET
, 0) == 0)
282 j
->allow_sparse
= true;
285 return log_error_errno(errno
, "Failed to seek on file descriptor: %m");
287 j
->allow_sparse
= false;
291 if (j
->calc_checksum
) {
292 if (gcry_md_open(&j
->checksum_context
, GCRY_MD_SHA256
, 0) != 0) {
293 log_error("Failed to initialize hash context.");
301 static int pull_job_detect_compression(PullJob
*j
) {
302 _cleanup_free_
uint8_t *stub
= NULL
;
309 r
= import_uncompress_detect(&j
->compress
, j
->payload
, j
->payload_size
);
311 return log_error_errno(r
, "Failed to initialize compressor: %m");
315 log_debug("Stream is compressed: %s", import_compress_type_to_string(j
->compress
.type
));
317 r
= pull_job_open_disk(j
);
321 /* Now, take the payload we read so far, and decompress it */
323 stub_size
= j
->payload_size
;
327 j
->payload_allocated
= 0;
329 j
->state
= PULL_JOB_RUNNING
;
331 r
= pull_job_write_compressed(j
, stub
, stub_size
);
338 static size_t pull_job_write_callback(void *contents
, size_t size
, size_t nmemb
, void *userdata
) {
339 PullJob
*j
= userdata
;
340 size_t sz
= size
* nmemb
;
348 case PULL_JOB_ANALYZING
:
349 /* Let's first check what it actually is */
351 if (!GREEDY_REALLOC(j
->payload
, j
->payload_allocated
, j
->payload_size
+ sz
)) {
356 memcpy(j
->payload
+ j
->payload_size
, contents
, sz
);
357 j
->payload_size
+= sz
;
359 r
= pull_job_detect_compression(j
);
365 case PULL_JOB_RUNNING
:
367 r
= pull_job_write_compressed(j
, contents
, sz
);
374 case PULL_JOB_FAILED
:
379 assert_not_reached("Impossible state.");
385 pull_job_finish(j
, r
);
389 static size_t pull_job_header_callback(void *contents
, size_t size
, size_t nmemb
, void *userdata
) {
390 PullJob
*j
= userdata
;
391 size_t sz
= size
* nmemb
;
392 _cleanup_free_
char *length
= NULL
, *last_modified
= NULL
;
399 if (j
->state
== PULL_JOB_DONE
|| j
->state
== PULL_JOB_FAILED
) {
404 assert(j
->state
== PULL_JOB_ANALYZING
);
406 r
= curl_header_strdup(contents
, sz
, "ETag:", &etag
);
415 if (strv_contains(j
->old_etags
, j
->etag
)) {
416 log_info("Image already downloaded. Skipping download.");
417 j
->etag_exists
= true;
418 pull_job_finish(j
, 0);
425 r
= curl_header_strdup(contents
, sz
, "Content-Length:", &length
);
431 (void) safe_atou64(length
, &j
->content_length
);
433 if (j
->content_length
!= (uint64_t) -1) {
434 char bytes
[FORMAT_BYTES_MAX
];
436 if (j
->content_length
> j
->compressed_max
) {
437 log_error("Content too large.");
442 log_info("Downloading %s for %s.", format_bytes(bytes
, sizeof(bytes
), j
->content_length
), j
->url
);
448 r
= curl_header_strdup(contents
, sz
, "Last-Modified:", &last_modified
);
454 (void) curl_parse_http_time(last_modified
, &j
->mtime
);
459 r
= j
->on_header(j
, contents
, sz
);
467 pull_job_finish(j
, r
);
471 static int pull_job_progress_callback(void *userdata
, curl_off_t dltotal
, curl_off_t dlnow
, curl_off_t ultotal
, curl_off_t ulnow
) {
472 PullJob
*j
= userdata
;
481 percent
= ((100 * dlnow
) / dltotal
);
482 n
= now(CLOCK_MONOTONIC
);
484 if (n
> j
->last_status_usec
+ USEC_PER_SEC
&&
485 percent
!= j
->progress_percent
&&
487 char buf
[FORMAT_TIMESPAN_MAX
];
489 if (n
- j
->start_usec
> USEC_PER_SEC
&& dlnow
> 0) {
490 char y
[FORMAT_BYTES_MAX
];
493 done
= n
- j
->start_usec
;
494 left
= (usec_t
) (((double) done
* (double) dltotal
) / dlnow
) - done
;
496 log_info("Got %u%% of %s. %s left at %s/s.",
499 format_timespan(buf
, sizeof(buf
), left
, USEC_PER_SEC
),
500 format_bytes(y
, sizeof(y
), (uint64_t) ((double) dlnow
/ ((double) done
/ (double) USEC_PER_SEC
))));
502 log_info("Got %u%% of %s.", percent
, j
->url
);
504 j
->progress_percent
= percent
;
505 j
->last_status_usec
= n
;
514 int pull_job_new(PullJob
**ret
, const char *url
, CurlGlue
*glue
, void *userdata
) {
515 _cleanup_(pull_job_unrefp
) PullJob
*j
= NULL
;
521 j
= new0(PullJob
, 1);
525 j
->state
= PULL_JOB_INIT
;
527 j
->userdata
= userdata
;
529 j
->content_length
= (uint64_t) -1;
530 j
->start_usec
= now(CLOCK_MONOTONIC
);
531 j
->compressed_max
= j
->uncompressed_max
= 8LLU * 1024LLU * 1024LLU * 1024LLU; /* 8GB */
533 j
->url
= strdup(url
);
543 int pull_job_begin(PullJob
*j
) {
548 if (j
->state
!= PULL_JOB_INIT
)
551 if (j
->grow_machine_directory
)
552 grow_machine_directory();
554 r
= curl_glue_make(&j
->curl
, j
->url
, j
);
558 if (!strv_isempty(j
->old_etags
)) {
559 _cleanup_free_
char *cc
= NULL
, *hdr
= NULL
;
561 cc
= strv_join(j
->old_etags
, ", ");
565 hdr
= strappend("If-None-Match: ", cc
);
569 if (!j
->request_header
) {
570 j
->request_header
= curl_slist_new(hdr
, NULL
);
571 if (!j
->request_header
)
574 struct curl_slist
*l
;
576 l
= curl_slist_append(j
->request_header
, hdr
);
580 j
->request_header
= l
;
584 if (j
->request_header
) {
585 if (curl_easy_setopt(j
->curl
, CURLOPT_HTTPHEADER
, j
->request_header
) != CURLE_OK
)
589 if (curl_easy_setopt(j
->curl
, CURLOPT_WRITEFUNCTION
, pull_job_write_callback
) != CURLE_OK
)
592 if (curl_easy_setopt(j
->curl
, CURLOPT_WRITEDATA
, j
) != CURLE_OK
)
595 if (curl_easy_setopt(j
->curl
, CURLOPT_HEADERFUNCTION
, pull_job_header_callback
) != CURLE_OK
)
598 if (curl_easy_setopt(j
->curl
, CURLOPT_HEADERDATA
, j
) != CURLE_OK
)
601 if (curl_easy_setopt(j
->curl
, CURLOPT_XFERINFOFUNCTION
, pull_job_progress_callback
) != CURLE_OK
)
604 if (curl_easy_setopt(j
->curl
, CURLOPT_XFERINFODATA
, j
) != CURLE_OK
)
607 if (curl_easy_setopt(j
->curl
, CURLOPT_NOPROGRESS
, 0) != CURLE_OK
)
610 r
= curl_glue_add(j
->glue
, j
->curl
);
614 j
->state
= PULL_JOB_ANALYZING
;