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