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