chiark / gitweb /
635779c1b5de747cab1f52208669515f3a3dd3f3
[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
49         RawImportFinished on_finished;
50         void *userdata;
51
52         char *local;
53         bool force_local;
54
55         char *temp_path;
56         char *final_path;
57 };
58
59 RawImport* raw_import_unref(RawImport *i) {
60         if (!i)
61                 return NULL;
62
63         import_job_unref(i->raw_job);
64
65         curl_glue_unref(i->glue);
66         sd_event_unref(i->event);
67
68         if (i->temp_path) {
69                 (void) unlink(i->temp_path);
70                 free(i->temp_path);
71         }
72
73         free(i->final_path);
74         free(i->image_root);
75         free(i->local);
76         free(i);
77
78         return NULL;
79 }
80
81 int raw_import_new(RawImport **ret, sd_event *event, const char *image_root, RawImportFinished on_finished, void *userdata) {
82         _cleanup_(raw_import_unrefp) RawImport *i = NULL;
83         int r;
84
85         assert(ret);
86
87         i = new0(RawImport, 1);
88         if (!i)
89                 return -ENOMEM;
90
91         i->on_finished = on_finished;
92         i->userdata = userdata;
93
94         i->image_root = strdup(image_root ?: "/var/lib/machines");
95         if (!i->image_root)
96                 return -ENOMEM;
97
98         if (event)
99                 i->event = sd_event_ref(event);
100         else {
101                 r = sd_event_default(&i->event);
102                 if (r < 0)
103                         return r;
104         }
105
106         r = curl_glue_new(&i->glue, i->event);
107         if (r < 0)
108                 return r;
109
110         i->glue->on_finished = import_job_curl_on_finished;
111         i->glue->userdata = i;
112
113         *ret = i;
114         i = NULL;
115
116         return 0;
117 }
118
119 static int raw_import_maybe_convert_qcow2(RawImport *i) {
120         _cleanup_close_ int converted_fd = -1;
121         _cleanup_free_ char *t = NULL;
122         int r;
123
124         assert(i);
125         assert(i->raw_job);
126
127         r = qcow2_detect(i->raw_job->disk_fd);
128         if (r < 0)
129                 return log_error_errno(r, "Failed to detect whether this is a QCOW2 image: %m");
130         if (r == 0)
131                 return 0;
132
133         /* This is a QCOW2 image, let's convert it */
134         r = tempfn_random(i->final_path, &t);
135         if (r < 0)
136                 return log_oom();
137
138         converted_fd = open(t, O_RDWR|O_CREAT|O_EXCL|O_NOCTTY|O_CLOEXEC, 0644);
139         if (converted_fd < 0)
140                 return log_error_errno(errno, "Failed to create %s: %m", t);
141
142         r = chattr_fd(converted_fd, true, FS_NOCOW_FL);
143         if (r < 0)
144                 log_warning_errno(errno, "Failed to set file attributes on %s: %m", t);
145
146         log_info("Unpacking QCOW2 file.");
147
148         r = qcow2_convert(i->raw_job->disk_fd, converted_fd);
149         if (r < 0) {
150                 unlink(t);
151                 return log_error_errno(r, "Failed to convert qcow2 image: %m");
152         }
153
154         unlink(i->temp_path);
155         free(i->temp_path);
156
157         i->temp_path = t;
158         t = NULL;
159
160         safe_close(i->raw_job->disk_fd);
161         i->raw_job->disk_fd = converted_fd;
162         converted_fd = -1;
163
164         return 1;
165 }
166
167 static int raw_import_make_local_copy(RawImport *i) {
168         _cleanup_free_ char *tp = NULL;
169         _cleanup_close_ int dfd = -1;
170         const char *p;
171         int r;
172
173         assert(i);
174         assert(i->raw_job);
175
176         if (!i->local)
177                 return 0;
178
179         if (i->raw_job->disk_fd >= 0) {
180                 if (lseek(i->raw_job->disk_fd, SEEK_SET, 0) == (off_t) -1)
181                         return log_error_errno(errno, "Failed to seek to beginning of vendor image: %m");
182         } else {
183                 if (!i->final_path) {
184                         r = import_make_path(i->raw_job->url, i->raw_job->etag, i->image_root, ".raw-", ".raw", &i->final_path);
185                         if (r < 0)
186                                 return log_oom();
187                 }
188
189                 i->raw_job->disk_fd = open(i->final_path, O_RDONLY|O_NOCTTY|O_CLOEXEC);
190                 if (i->raw_job->disk_fd < 0)
191                         return log_error_errno(errno, "Failed to open vendor image: %m");
192         }
193
194         p = strappenda(i->image_root, "/", i->local, ".raw");
195
196         if (i->force_local) {
197                 (void) btrfs_subvol_remove(p);
198                 (void) rm_rf_dangerous(p, false, true, false);
199         }
200
201         r = tempfn_random(p, &tp);
202         if (r < 0)
203                 return log_oom();
204
205         dfd = open(tp, O_WRONLY|O_CREAT|O_EXCL|O_NOCTTY|O_CLOEXEC, 0664);
206         if (dfd < 0)
207                 return log_error_errno(errno, "Failed to create writable copy of image: %m");
208
209         /* Turn off COW writing. This should greatly improve
210          * performance on COW file systems like btrfs, since it
211          * reduces fragmentation caused by not allowing in-place
212          * writes. */
213         r = chattr_fd(dfd, true, FS_NOCOW_FL);
214         if (r < 0)
215                 log_warning_errno(errno, "Failed to set file attributes on %s: %m", tp);
216
217         r = copy_bytes(i->raw_job->disk_fd, dfd, (off_t) -1, true);
218         if (r < 0) {
219                 unlink(tp);
220                 return log_error_errno(r, "Failed to make writable copy of image: %m");
221         }
222
223         (void) copy_times(i->raw_job->disk_fd, dfd);
224         (void) copy_xattr(i->raw_job->disk_fd, dfd);
225
226         dfd = safe_close(dfd);
227
228         r = rename(tp, p);
229         if (r < 0)  {
230                 unlink(tp);
231                 return log_error_errno(errno, "Failed to move writable image into place: %m");
232         }
233
234         log_info("Created new local image '%s'.", i->local);
235         return 0;
236 }
237
238 static void raw_import_job_on_finished(ImportJob *j) {
239         RawImport *i;
240         int r;
241
242         assert(j);
243         assert(j->userdata);
244
245         i = j->userdata;
246         if (j->error != 0) {
247                 r = j->error;
248                 goto finish;
249         }
250
251         /* This is invoked if either the download completed
252          * successfully, or the download was skipped because we
253          * already have the etag. */
254
255         if (j->disk_fd >= 0) {
256                 r = raw_import_maybe_convert_qcow2(i);
257                 if (r < 0)
258                         goto finish;
259
260                 r = import_make_read_only_fd(j->disk_fd);
261                 if (r < 0)
262                         goto finish;
263
264                 r = rename(i->temp_path, i->final_path);
265                 if (r < 0) {
266                         r = log_error_errno(errno, "Failed to move RAW file into place: %m");
267                         goto finish;
268                 }
269
270                 free(i->temp_path);
271                 i->temp_path = NULL;
272         }
273
274         r = raw_import_make_local_copy(i);
275         if (r < 0)
276                 goto finish;
277
278         j->disk_fd = safe_close(j->disk_fd);
279
280         r = 0;
281
282 finish:
283         if (i->on_finished)
284                 i->on_finished(i, r, i->userdata);
285         else
286                 sd_event_exit(i->event, r);
287 }
288
289 static int raw_import_job_on_open_disk(ImportJob *j) {
290         RawImport *i;
291         int r;
292
293         assert(j);
294         assert(j->userdata);
295
296         i = j->userdata;
297
298         r = import_make_path(j->url, j->etag, i->image_root, ".raw-", ".raw", &i->final_path);
299         if (r < 0)
300                 return log_oom();
301
302         r = tempfn_random(i->final_path, &i->temp_path);
303         if (r <0)
304                 return log_oom();
305
306         mkdir_parents_label(i->temp_path, 0700);
307
308         j->disk_fd = open(i->temp_path, O_RDWR|O_CREAT|O_EXCL|O_NOCTTY|O_CLOEXEC, 0644);
309         if (j->disk_fd < 0)
310                 return log_error_errno(errno, "Failed to create %s: %m", i->temp_path);
311
312         r = chattr_fd(j->disk_fd, true, FS_NOCOW_FL);
313         if (r < 0)
314                 log_warning_errno(errno, "Failed to set file attributes on %s: %m", i->temp_path);
315
316         return 0;
317 }
318
319 int raw_import_pull(RawImport *i, const char *url, const char *local, bool force_local) {
320         int r;
321
322         assert(i);
323
324         if (i->raw_job)
325                 return -EBUSY;
326
327         if (!http_url_is_valid(url))
328                 return -EINVAL;
329
330         if (local && !machine_name_is_valid(local))
331                 return -EINVAL;
332
333         r = free_and_strdup(&i->local, local);
334         if (r < 0)
335                 return r;
336
337         i->force_local = force_local;
338
339         r = import_job_new(&i->raw_job, url, i->glue, i);
340         if (r < 0)
341                 return r;
342
343         i->raw_job->on_finished = raw_import_job_on_finished;
344         i->raw_job->on_open_disk = raw_import_job_on_open_disk;
345
346         r = import_find_old_etags(url, i->image_root, DT_REG, ".raw-", ".raw", &i->raw_job->old_etags);
347         if (r < 0)
348                 return r;
349
350         return import_job_begin(i->raw_job);
351 }