chiark / gitweb /
import: make image verification optional
[elogind.git] / src / import / import-raw.c
1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3 /***
4   This file is part of systemd.
5
6   Copyright 2014 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 #include <linux/fs.h>
24 #include <curl/curl.h>
25 #include <gcrypt.h>
26
27 #include "utf8.h"
28 #include "strv.h"
29 #include "copy.h"
30 #include "btrfs-util.h"
31 #include "util.h"
32 #include "macro.h"
33 #include "mkdir.h"
34 #include "curl-util.h"
35 #include "qcow2-util.h"
36 #include "import-job.h"
37 #include "import-util.h"
38 #include "import-raw.h"
39
40 typedef struct RawImportFile RawImportFile;
41
42 struct RawImport {
43         sd_event *event;
44         CurlGlue *glue;
45
46         char *image_root;
47
48         ImportJob *raw_job;
49         ImportJob *sha256sums_job;
50
51         RawImportFinished on_finished;
52         void *userdata;
53
54         char *local;
55         bool force_local;
56
57         char *temp_path;
58         char *final_path;
59
60         ImportVerify verify;
61 };
62
63 RawImport* raw_import_unref(RawImport *i) {
64         if (!i)
65                 return NULL;
66
67         import_job_unref(i->raw_job);
68
69         curl_glue_unref(i->glue);
70         sd_event_unref(i->event);
71
72         if (i->temp_path) {
73                 (void) unlink(i->temp_path);
74                 free(i->temp_path);
75         }
76
77         free(i->final_path);
78         free(i->image_root);
79         free(i->local);
80         free(i);
81
82         return NULL;
83 }
84
85 int raw_import_new(RawImport **ret, sd_event *event, const char *image_root, RawImportFinished on_finished, void *userdata) {
86         _cleanup_(raw_import_unrefp) RawImport *i = NULL;
87         int r;
88
89         assert(ret);
90
91         i = new0(RawImport, 1);
92         if (!i)
93                 return -ENOMEM;
94
95         i->on_finished = on_finished;
96         i->userdata = userdata;
97
98         i->image_root = strdup(image_root ?: "/var/lib/machines");
99         if (!i->image_root)
100                 return -ENOMEM;
101
102         if (event)
103                 i->event = sd_event_ref(event);
104         else {
105                 r = sd_event_default(&i->event);
106                 if (r < 0)
107                         return r;
108         }
109
110         r = curl_glue_new(&i->glue, i->event);
111         if (r < 0)
112                 return r;
113
114         i->glue->on_finished = import_job_curl_on_finished;
115         i->glue->userdata = i;
116
117         *ret = i;
118         i = NULL;
119
120         return 0;
121 }
122
123 static int raw_import_maybe_convert_qcow2(RawImport *i) {
124         _cleanup_close_ int converted_fd = -1;
125         _cleanup_free_ char *t = NULL;
126         int r;
127
128         assert(i);
129         assert(i->raw_job);
130
131         r = qcow2_detect(i->raw_job->disk_fd);
132         if (r < 0)
133                 return log_error_errno(r, "Failed to detect whether this is a QCOW2 image: %m");
134         if (r == 0)
135                 return 0;
136
137         /* This is a QCOW2 image, let's convert it */
138         r = tempfn_random(i->final_path, &t);
139         if (r < 0)
140                 return log_oom();
141
142         converted_fd = open(t, O_RDWR|O_CREAT|O_EXCL|O_NOCTTY|O_CLOEXEC, 0644);
143         if (converted_fd < 0)
144                 return log_error_errno(errno, "Failed to create %s: %m", t);
145
146         r = chattr_fd(converted_fd, true, FS_NOCOW_FL);
147         if (r < 0)
148                 log_warning_errno(errno, "Failed to set file attributes on %s: %m", t);
149
150         log_info("Unpacking QCOW2 file.");
151
152         r = qcow2_convert(i->raw_job->disk_fd, converted_fd);
153         if (r < 0) {
154                 unlink(t);
155                 return log_error_errno(r, "Failed to convert qcow2 image: %m");
156         }
157
158         unlink(i->temp_path);
159         free(i->temp_path);
160
161         i->temp_path = t;
162         t = NULL;
163
164         safe_close(i->raw_job->disk_fd);
165         i->raw_job->disk_fd = converted_fd;
166         converted_fd = -1;
167
168         return 1;
169 }
170
171 static int raw_import_make_local_copy(RawImport *i) {
172         _cleanup_free_ char *tp = NULL;
173         _cleanup_close_ int dfd = -1;
174         const char *p;
175         int r;
176
177         assert(i);
178         assert(i->raw_job);
179
180         if (!i->local)
181                 return 0;
182
183         if (i->raw_job->etag_exists) {
184                 /* We have downloaded this one previously, reopen it */
185
186                 assert(i->raw_job->disk_fd < 0);
187
188                 if (!i->final_path) {
189                         r = import_make_path(i->raw_job->url, i->raw_job->etag, i->image_root, ".raw-", ".raw", &i->final_path);
190                         if (r < 0)
191                                 return log_oom();
192                 }
193
194                 i->raw_job->disk_fd = open(i->final_path, O_RDONLY|O_NOCTTY|O_CLOEXEC);
195                 if (i->raw_job->disk_fd < 0)
196                         return log_error_errno(errno, "Failed to open vendor image: %m");
197         } else {
198                 /* We freshly downloaded the image, use it */
199
200                 assert(i->raw_job->disk_fd >= 0);
201
202                 if (lseek(i->raw_job->disk_fd, SEEK_SET, 0) == (off_t) -1)
203                         return log_error_errno(errno, "Failed to seek to beginning of vendor image: %m");
204         }
205
206         p = strappenda(i->image_root, "/", i->local, ".raw");
207
208         if (i->force_local) {
209                 (void) btrfs_subvol_remove(p);
210                 (void) rm_rf_dangerous(p, false, true, false);
211         }
212
213         r = tempfn_random(p, &tp);
214         if (r < 0)
215                 return log_oom();
216
217         dfd = open(tp, O_WRONLY|O_CREAT|O_EXCL|O_NOCTTY|O_CLOEXEC, 0664);
218         if (dfd < 0)
219                 return log_error_errno(errno, "Failed to create writable copy of image: %m");
220
221         /* Turn off COW writing. This should greatly improve
222          * performance on COW file systems like btrfs, since it
223          * reduces fragmentation caused by not allowing in-place
224          * writes. */
225         r = chattr_fd(dfd, true, FS_NOCOW_FL);
226         if (r < 0)
227                 log_warning_errno(errno, "Failed to set file attributes on %s: %m", tp);
228
229         r = copy_bytes(i->raw_job->disk_fd, dfd, (off_t) -1, true);
230         if (r < 0) {
231                 unlink(tp);
232                 return log_error_errno(r, "Failed to make writable copy of image: %m");
233         }
234
235         (void) copy_times(i->raw_job->disk_fd, dfd);
236         (void) copy_xattr(i->raw_job->disk_fd, dfd);
237
238         dfd = safe_close(dfd);
239
240         r = rename(tp, p);
241         if (r < 0)  {
242                 unlink(tp);
243                 return log_error_errno(errno, "Failed to move writable image into place: %m");
244         }
245
246         log_info("Created new local image '%s'.", i->local);
247         return 0;
248 }
249
250 static int raw_import_verify_sha256sum(RawImport *i) {
251         _cleanup_free_ char *fn = NULL;
252         const char *p, *line;
253         int r;
254
255         assert(i);
256         assert(i->verify != IMPORT_VERIFY_NO);
257
258         assert(i->raw_job);
259         assert(i->raw_job->sha256);
260
261         assert(i->sha256sums_job);
262         assert(i->sha256sums_job->payload);
263         assert(i->sha256sums_job->payload_size > 0);
264
265         r = import_url_last_component(i->raw_job->url, &fn);
266         if (r < 0)
267                 return log_oom();
268
269         if (!filename_is_valid(fn)) {
270                 log_error("Cannot verify checksum, could not determine valid server-side file name.");
271                 return -EBADMSG;
272         }
273
274         line = strappenda(i->raw_job->sha256, " *", fn, "\n");
275
276         p = memmem(i->sha256sums_job->payload,
277                    i->sha256sums_job->payload_size,
278                    line,
279                    strlen(line));
280
281         if (!p || (p != (char*) i->sha256sums_job->payload && p[-1] != '\n')) {
282                 log_error("Checksum did not check out, payload has been tempered with.");
283                 return -EBADMSG;
284         }
285
286         log_info("SHA256 checksum of %s is valid.", i->raw_job->url);
287
288         return 0;
289 }
290
291 static int raw_import_finalize(RawImport *i) {
292         int r;
293
294         assert(i);
295
296         if (!IMPORT_JOB_STATE_IS_COMPLETE(i->raw_job) ||
297             (i->verify != IMPORT_VERIFY_NO && !IMPORT_JOB_STATE_IS_COMPLETE(i->sha256sums_job)))
298                 return 0;
299
300         if (i->verify != IMPORT_VERIFY_NO &&
301             i->raw_job->etag_exists) {
302
303                 assert(i->temp_path);
304                 assert(i->final_path);
305                 assert(i->raw_job->disk_fd >= 0);
306
307                 r = raw_import_verify_sha256sum(i);
308                 if (r < 0)
309                         return r;
310
311                 r = rename(i->temp_path, i->final_path);
312                 if (r < 0)
313                         return log_error_errno(errno, "Failed to move RAW file into place: %m");
314
315                 free(i->temp_path);
316                 i->temp_path = NULL;
317         }
318
319         r = raw_import_make_local_copy(i);
320         if (r < 0)
321                 return r;
322
323         i->raw_job->disk_fd = safe_close(i->raw_job->disk_fd);
324
325         return 1;
326 }
327
328 static void raw_import_invoke_finished(RawImport *i, int r) {
329         assert(i);
330
331         if (i->on_finished)
332                 i->on_finished(i, r, i->userdata);
333         else
334                 sd_event_exit(i->event, r);
335 }
336
337 static void raw_import_raw_job_on_finished(ImportJob *j) {
338         RawImport *i;
339         int r;
340
341         assert(j);
342         assert(j->userdata);
343
344         i = j->userdata;
345         if (j->error != 0) {
346                 r = j->error;
347                 goto finish;
348         }
349
350         /* This is invoked if either the download completed
351          * successfully, or the download was skipped because we
352          * already have the etag. In this case ->etag_exists is
353          * true. */
354
355         if (!j->etag_exists) {
356                 assert(j->disk_fd >= 0);
357
358                 r = raw_import_maybe_convert_qcow2(i);
359                 if (r < 0)
360                         goto finish;
361
362                 r = import_make_read_only_fd(j->disk_fd);
363                 if (r < 0)
364                         goto finish;
365         }
366
367         r = raw_import_finalize(i);
368         if (r < 0)
369                 goto finish;
370         if (r == 0)
371                 return;
372
373         r = 0;
374
375 finish:
376         raw_import_invoke_finished(i, r);
377 }
378
379 static void raw_import_sha256sums_job_on_finished(ImportJob *j) {
380         RawImport *i;
381         int r;
382
383         assert(j);
384         assert(j->userdata);
385
386         i = j->userdata;
387         assert(i->verify != IMPORT_VERIFY_NO);
388
389         if (j->error != 0) {
390                 log_error_errno(j->error, "Failed to retrieve SHA256 checksum, cannot verify.");
391                 r = j->error;
392                 goto finish;
393         }
394
395         r = raw_import_finalize(i);
396         if (r < 0)
397                 goto finish;
398         if (r == 0)
399                 return;
400
401         r = 0;
402 finish:
403         raw_import_invoke_finished(i, r);
404 }
405
406 static int raw_import_raw_job_on_open_disk(ImportJob *j) {
407         RawImport *i;
408         int r;
409
410         assert(j);
411         assert(j->userdata);
412
413         i = j->userdata;
414
415         r = import_make_path(j->url, j->etag, i->image_root, ".raw-", ".raw", &i->final_path);
416         if (r < 0)
417                 return log_oom();
418
419         r = tempfn_random(i->final_path, &i->temp_path);
420         if (r <0)
421                 return log_oom();
422
423         mkdir_parents_label(i->temp_path, 0700);
424
425         j->disk_fd = open(i->temp_path, O_RDWR|O_CREAT|O_EXCL|O_NOCTTY|O_CLOEXEC, 0644);
426         if (j->disk_fd < 0)
427                 return log_error_errno(errno, "Failed to create %s: %m", i->temp_path);
428
429         r = chattr_fd(j->disk_fd, true, FS_NOCOW_FL);
430         if (r < 0)
431                 log_warning_errno(errno, "Failed to set file attributes on %s: %m", i->temp_path);
432
433         return 0;
434 }
435
436 int raw_import_pull(RawImport *i, const char *url, const char *local, bool force_local, ImportVerify verify) {
437         _cleanup_free_ char *sha256sums_url = NULL;
438         int r;
439
440         assert(i);
441         assert(verify < _IMPORT_VERIFY_MAX);
442         assert(verify >= 0);
443
444         if (i->raw_job)
445                 return -EBUSY;
446
447         if (!http_url_is_valid(url))
448                 return -EINVAL;
449
450         if (local && !machine_name_is_valid(local))
451                 return -EINVAL;
452
453         r = free_and_strdup(&i->local, local);
454         if (r < 0)
455                 return r;
456         i->force_local = force_local;
457         i->verify = verify;
458
459         /* Queue job for the image itself */
460         r = import_job_new(&i->raw_job, url, i->glue, i);
461         if (r < 0)
462                 return r;
463
464         i->raw_job->on_finished = raw_import_raw_job_on_finished;
465         i->raw_job->on_open_disk = raw_import_raw_job_on_open_disk;
466         i->raw_job->calc_hash = true;
467
468         r = import_find_old_etags(url, i->image_root, DT_REG, ".raw-", ".raw", &i->raw_job->old_etags);
469         if (r < 0)
470                 return r;
471
472         if (verify != IMPORT_VERIFY_NO) {
473                 /* Queue job for the SHA256SUMS file for the image */
474                 r = import_url_change_last_component(url, "SHA256SUMS", &sha256sums_url);
475                 if (r < 0)
476                         return r;
477
478                 r = import_job_new(&i->sha256sums_job, sha256sums_url, i->glue, i);
479                 if (r < 0)
480                         return r;
481
482                 i->sha256sums_job->on_finished = raw_import_sha256sums_job_on_finished;
483                 i->sha256sums_job->uncompressed_max = i->sha256sums_job->compressed_max = 1ULL * 1024ULL * 1024ULL;
484
485                 r = import_job_begin(i->sha256sums_job);
486                 if (r < 0)
487                         return r;
488         }
489
490         r = import_job_begin(i->raw_job);
491         if (r < 0)
492                 return r;
493
494         return 0;
495 }