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