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