chiark / gitweb /
15e5eb2ca274177adf214b27251de5680fcfc224
[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 2015 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 <linux/fs.h>
23
24 #include "sd-daemon.h"
25 #include "sd-event.h"
26 #include "util.h"
27 #include "path-util.h"
28 #include "btrfs-util.h"
29 #include "copy.h"
30 #include "mkdir.h"
31 #include "ratelimit.h"
32 #include "machine-pool.h"
33 #include "qcow2-util.h"
34 #include "import-compress.h"
35 #include "import-common.h"
36 #include "import-raw.h"
37
38 struct RawImport {
39         sd_event *event;
40
41         char *image_root;
42
43         RawImportFinished on_finished;
44         void *userdata;
45
46         char *local;
47         bool force_local;
48         bool read_only;
49         bool grow_machine_directory;
50
51         char *temp_path;
52         char *final_path;
53
54         int input_fd;
55         int output_fd;
56
57         ImportCompress compress;
58
59         uint64_t written_since_last_grow;
60
61         sd_event_source *input_event_source;
62
63         uint8_t buffer[16*1024];
64         size_t buffer_size;
65
66         uint64_t written_compressed;
67         uint64_t written_uncompressed;
68
69         struct stat st;
70
71         unsigned last_percent;
72         RateLimit progress_rate_limit;
73 };
74
75 RawImport* raw_import_unref(RawImport *i) {
76         if (!i)
77                 return NULL;
78
79         sd_event_unref(i->event);
80
81         if (i->temp_path) {
82                 (void) unlink(i->temp_path);
83                 free(i->temp_path);
84         }
85
86         import_compress_free(&i->compress);
87
88         sd_event_source_unref(i->input_event_source);
89
90         safe_close(i->output_fd);
91
92         free(i->final_path);
93         free(i->image_root);
94         free(i->local);
95         free(i);
96
97         return NULL;
98 }
99
100 int raw_import_new(
101                 RawImport **ret,
102                 sd_event *event,
103                 const char *image_root,
104                 RawImportFinished on_finished,
105                 void *userdata) {
106
107         _cleanup_(raw_import_unrefp) RawImport *i = NULL;
108         int r;
109
110         assert(ret);
111
112         i = new0(RawImport, 1);
113         if (!i)
114                 return -ENOMEM;
115
116         i->input_fd = i->output_fd = -1;
117         i->on_finished = on_finished;
118         i->userdata = userdata;
119
120         RATELIMIT_INIT(i->progress_rate_limit, 500 * USEC_PER_MSEC, 1);
121         i->last_percent = (unsigned) -1;
122
123         i->image_root = strdup(image_root ?: "/var/lib/machines");
124         if (!i->image_root)
125                 return -ENOMEM;
126
127         i->grow_machine_directory = path_startswith(i->image_root, "/var/lib/machines");
128
129         if (event)
130                 i->event = sd_event_ref(event);
131         else {
132                 r = sd_event_default(&i->event);
133                 if (r < 0)
134                         return r;
135         }
136
137         *ret = i;
138         i = NULL;
139
140         return 0;
141 }
142
143 static void raw_import_report_progress(RawImport *i) {
144         unsigned percent;
145         assert(i);
146
147         /* We have no size information, unless the source is a regular file */
148         if (!S_ISREG(i->st.st_mode))
149                 return;
150
151         if (i->written_compressed >= (uint64_t) i->st.st_size)
152                 percent = 100;
153         else
154                 percent = (unsigned) ((i->written_compressed * UINT64_C(100)) / (uint64_t) i->st.st_size);
155
156         if (percent == i->last_percent)
157                 return;
158
159         if (!ratelimit_test(&i->progress_rate_limit))
160                 return;
161
162         sd_notifyf(false, "X_IMPORT_PROGRESS=%u", percent);
163         log_info("Imported %u%%.", percent);
164
165         i->last_percent = percent;
166 }
167
168 static int raw_import_maybe_convert_qcow2(RawImport *i) {
169         _cleanup_close_ int converted_fd = -1;
170         _cleanup_free_ char *t = NULL;
171         int r;
172
173         assert(i);
174
175         r = qcow2_detect(i->output_fd);
176         if (r < 0)
177                 return log_error_errno(r, "Failed to detect whether this is a QCOW2 image: %m");
178         if (r == 0)
179                 return 0;
180
181         /* This is a QCOW2 image, let's convert it */
182         r = tempfn_random(i->final_path, &t);
183         if (r < 0)
184                 return log_oom();
185
186         converted_fd = open(t, O_RDWR|O_CREAT|O_EXCL|O_NOCTTY|O_CLOEXEC, 0664);
187         if (converted_fd < 0)
188                 return log_error_errno(errno, "Failed to create %s: %m", t);
189
190         r = chattr_fd(converted_fd, true, FS_NOCOW_FL);
191         if (r < 0)
192                 log_warning_errno(errno, "Failed to set file attributes on %s: %m", t);
193
194         log_info("Unpacking QCOW2 file.");
195
196         r = qcow2_convert(i->output_fd, converted_fd);
197         if (r < 0) {
198                 unlink(t);
199                 return log_error_errno(r, "Failed to convert qcow2 image: %m");
200         }
201
202         (void) unlink(i->temp_path);
203         free(i->temp_path);
204         i->temp_path = t;
205         t = NULL;
206
207         safe_close(i->output_fd);
208         i->output_fd = converted_fd;
209         converted_fd = -1;
210
211         return 1;
212 }
213
214 static int raw_import_finish(RawImport *i) {
215         int r;
216
217         assert(i);
218         assert(i->output_fd >= 0);
219         assert(i->temp_path);
220         assert(i->final_path);
221
222         /* In case this was a sparse file, make sure the file system is right */
223         if (i->written_uncompressed > 0) {
224                 if (ftruncate(i->output_fd, i->written_uncompressed) < 0)
225                         return log_error_errno(errno, "Failed to truncate file: %m");
226         }
227
228         r = raw_import_maybe_convert_qcow2(i);
229         if (r < 0)
230                 return r;
231
232         if (S_ISREG(i->st.st_mode)) {
233                 (void) copy_times(i->input_fd, i->output_fd);
234                 (void) copy_xattr(i->input_fd, i->output_fd);
235         }
236
237         if (i->read_only) {
238                 r = import_make_read_only_fd(i->output_fd);
239                 if (r < 0)
240                         return r;
241         }
242
243         if (i->force_local) {
244                 (void) btrfs_subvol_remove(i->final_path);
245                 (void) rm_rf_dangerous(i->final_path, false, true, false);
246         }
247
248         if (renameat2(AT_FDCWD, i->temp_path, AT_FDCWD, i->final_path, RENAME_NOREPLACE) < 0)
249                 return log_error_errno(errno, "Failed to move image into place: %m");
250
251         free(i->temp_path);
252         i->temp_path = NULL;
253
254         return 0;
255 }
256
257 static int raw_import_open_disk(RawImport *i) {
258         int r;
259
260         assert(i);
261
262         assert(!i->final_path);
263         assert(!i->temp_path);
264         assert(i->output_fd < 0);
265
266         i->final_path = strjoin(i->image_root, "/", i->local, ".raw", NULL);
267         if (!i->final_path)
268                 return log_oom();
269
270         r = tempfn_random(i->final_path, &i->temp_path);
271         if (r < 0)
272                 return log_oom();
273
274         (void) mkdir_parents_label(i->temp_path, 0700);
275
276         i->output_fd = open(i->temp_path, O_RDWR|O_CREAT|O_EXCL|O_NOCTTY|O_CLOEXEC, 0664);
277         if (i->output_fd < 0)
278                 return log_error_errno(errno, "Failed to open destination %s: %m", i->temp_path);
279
280         r = chattr_fd(i->output_fd, true, FS_NOCOW_FL);
281         if (r < 0)
282                 log_warning_errno(errno, "Failed to set file attributes on %s: %m", i->temp_path);
283
284         return 0;
285 }
286
287 static int raw_import_try_reflink(RawImport *i) {
288         off_t p;
289         int r;
290
291         assert(i);
292         assert(i->input_fd >= 0);
293         assert(i->output_fd >= 0);
294
295         if (i->compress.type != IMPORT_COMPRESS_UNCOMPRESSED)
296                 return 0;
297
298         if (!S_ISREG(i->st.st_mode))
299                 return 0;
300
301         p = lseek(i->input_fd, 0, SEEK_CUR);
302         if (p == (off_t) -1)
303                 return log_error_errno(errno, "Failed to read file offset of input file: %m");
304
305         /* Let's only try a btrfs reflink, if we are reading from the beginning of the file */
306         if ((uint64_t) p != (uint64_t) i->buffer_size)
307                 return 0;
308
309         r = btrfs_reflink(i->input_fd, i->output_fd);
310         if (r >= 0)
311                 return 1;
312
313         return 0;
314 }
315
316 static int raw_import_write(const void *p, size_t sz, void *userdata) {
317         RawImport *i = userdata;
318         ssize_t n;
319
320         if (i->grow_machine_directory && i->written_since_last_grow >= GROW_INTERVAL_BYTES) {
321                 i->written_since_last_grow = 0;
322                 grow_machine_directory();
323         }
324
325         n = sparse_write(i->output_fd, p, sz, 64);
326         if (n < 0)
327                 return -errno;
328         if ((size_t) n < sz)
329                 return -EIO;
330
331         i->written_uncompressed += sz;
332         i->written_since_last_grow += sz;
333
334         return 0;
335 }
336
337 static int raw_import_process(RawImport *i) {
338         ssize_t l;
339         int r;
340
341         assert(i);
342         assert(i->buffer_size < sizeof(i->buffer));
343
344         l = read(i->input_fd, i->buffer + i->buffer_size, sizeof(i->buffer) - i->buffer_size);
345         if (l < 0) {
346                 r = log_error_errno(errno, "Failed to read input file: %m");
347                 goto finish;
348         }
349         if (l == 0) {
350                 if (i->compress.type == IMPORT_COMPRESS_UNKNOWN) {
351                         log_error("Premature end of file: %m");
352                         r = -EIO;
353                         goto finish;
354                 }
355
356                 r = raw_import_finish(i);
357                 goto finish;
358         }
359
360         i->buffer_size += l;
361
362         if (i->compress.type == IMPORT_COMPRESS_UNKNOWN) {
363                 r = import_uncompress_detect(&i->compress, i->buffer, i->buffer_size);
364                 if (r < 0) {
365                         log_error("Failed to detect file compression: %m");
366                         goto finish;
367                 }
368                 if (r == 0) /* Need more data */
369                         return 0;
370
371                 r = raw_import_open_disk(i);
372                 if (r < 0)
373                         goto finish;
374
375                 r = raw_import_try_reflink(i);
376                 if (r < 0)
377                         goto finish;
378                 if (r > 0) {
379                         r = raw_import_finish(i);
380                         goto finish;
381                 }
382         }
383
384         r = import_uncompress(&i->compress, i->buffer, i->buffer_size, raw_import_write, i);
385         if (r < 0) {
386                 log_error_errno(r, "Failed to decode and write: %m");
387                 goto finish;
388         }
389
390         i->written_compressed += i->buffer_size;
391         i->buffer_size = 0;
392
393         raw_import_report_progress(i);
394
395         return 0;
396
397 finish:
398         if (i->on_finished)
399                 i->on_finished(i, r, i->userdata);
400         else
401                 sd_event_exit(i->event, r);
402
403         return 0;
404 }
405
406 static int raw_import_on_input(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
407         RawImport *i = userdata;
408
409         return raw_import_process(i);
410 }
411
412 static int raw_import_on_defer(sd_event_source *s, void *userdata) {
413         RawImport *i = userdata;
414
415         return raw_import_process(i);
416 }
417
418 int raw_import_start(RawImport *i, int fd, const char *local, bool force_local, bool read_only) {
419         int r;
420
421         assert(i);
422         assert(fd >= 0);
423         assert(local);
424
425         if (!machine_name_is_valid(local))
426                 return -EINVAL;
427
428         if (i->input_fd >= 0)
429                 return -EBUSY;
430
431         r = free_and_strdup(&i->local, local);
432         if (r < 0)
433                 return r;
434         i->force_local = force_local;
435         i->read_only = read_only;
436
437         if (fstat(fd, &i->st) < 0)
438                 return -errno;
439
440         r = sd_event_add_io(i->event, &i->input_event_source, fd, EPOLLIN, raw_import_on_input, i);
441         if (r == -EPERM) {
442                 /* This fd does not support epoll, for example because it is a regular file. Busy read in that case */
443                 r = sd_event_add_defer(i->event, &i->input_event_source, raw_import_on_defer, i);
444                 if (r < 0)
445                         return r;
446
447                 r = sd_event_source_set_enabled(i->input_event_source, SD_EVENT_ON);
448         }
449         if (r < 0)
450                 return r;
451
452         i->input_fd = fd;
453         return r;
454 }