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