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