chiark / gitweb /
fsck: use _cleanup_close_pair_ where appropriate
[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, 100 * 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                 if (errno == EAGAIN)
347                         return 0;
348
349                 r = log_error_errno(errno, "Failed to read input file: %m");
350                 goto finish;
351         }
352         if (l == 0) {
353                 if (i->compress.type == IMPORT_COMPRESS_UNKNOWN) {
354                         log_error("Premature end of file: %m");
355                         r = -EIO;
356                         goto finish;
357                 }
358
359                 r = raw_import_finish(i);
360                 goto finish;
361         }
362
363         i->buffer_size += l;
364
365         if (i->compress.type == IMPORT_COMPRESS_UNKNOWN) {
366                 r = import_uncompress_detect(&i->compress, i->buffer, i->buffer_size);
367                 if (r < 0) {
368                         log_error("Failed to detect file compression: %m");
369                         goto finish;
370                 }
371                 if (r == 0) /* Need more data */
372                         return 0;
373
374                 r = raw_import_open_disk(i);
375                 if (r < 0)
376                         goto finish;
377
378                 r = raw_import_try_reflink(i);
379                 if (r < 0)
380                         goto finish;
381                 if (r > 0) {
382                         r = raw_import_finish(i);
383                         goto finish;
384                 }
385         }
386
387         r = import_uncompress(&i->compress, i->buffer, i->buffer_size, raw_import_write, i);
388         if (r < 0) {
389                 log_error_errno(r, "Failed to decode and write: %m");
390                 goto finish;
391         }
392
393         i->written_compressed += i->buffer_size;
394         i->buffer_size = 0;
395
396         raw_import_report_progress(i);
397
398         return 0;
399
400 finish:
401         if (i->on_finished)
402                 i->on_finished(i, r, i->userdata);
403         else
404                 sd_event_exit(i->event, r);
405
406         return 0;
407 }
408
409 static int raw_import_on_input(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
410         RawImport *i = userdata;
411
412         return raw_import_process(i);
413 }
414
415 static int raw_import_on_defer(sd_event_source *s, void *userdata) {
416         RawImport *i = userdata;
417
418         return raw_import_process(i);
419 }
420
421 int raw_import_start(RawImport *i, int fd, const char *local, bool force_local, bool read_only) {
422         int r;
423
424         assert(i);
425         assert(fd >= 0);
426         assert(local);
427
428         if (!machine_name_is_valid(local))
429                 return -EINVAL;
430
431         if (i->input_fd >= 0)
432                 return -EBUSY;
433
434         r = fd_nonblock(fd, true);
435         if (r < 0)
436                 return r;
437
438         r = free_and_strdup(&i->local, local);
439         if (r < 0)
440                 return r;
441         i->force_local = force_local;
442         i->read_only = read_only;
443
444         if (fstat(fd, &i->st) < 0)
445                 return -errno;
446
447         r = sd_event_add_io(i->event, &i->input_event_source, fd, EPOLLIN, raw_import_on_input, i);
448         if (r == -EPERM) {
449                 /* This fd does not support epoll, for example because it is a regular file. Busy read in that case */
450                 r = sd_event_add_defer(i->event, &i->input_event_source, raw_import_on_defer, i);
451                 if (r < 0)
452                         return r;
453
454                 r = sd_event_source_set_enabled(i->input_event_source, SD_EVENT_ON);
455         }
456         if (r < 0)
457                 return r;
458
459         i->input_fd = fd;
460         return r;
461 }