]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/import/import-raw.c
util: rework strappenda(), and rename it strjoina()
[thirdparty/systemd.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 }