]> git.ipfire.org Git - thirdparty/systemd.git/blame_incremental - src/import/import-tar.c
hwdb: add ProtoArc EM01 NL mouse configuration
[thirdparty/systemd.git] / src / import / import-tar.c
... / ...
CommitLineData
1/* SPDX-License-Identifier: LGPL-2.1-or-later */
2
3#include <sys/stat.h>
4
5#include "sd-daemon.h"
6#include "sd-event.h"
7
8#include "alloc-util.h"
9#include "btrfs-util.h"
10#include "dissect-image.h"
11#include "errno-util.h"
12#include "fd-util.h"
13#include "format-util.h"
14#include "import-common.h"
15#include "import-compress.h"
16#include "import-tar.h"
17#include "import-util.h"
18#include "install-file.h"
19#include "io-util.h"
20#include "log.h"
21#include "mkdir-label.h"
22#include "path-util.h"
23#include "pidref.h"
24#include "pretty-print.h"
25#include "process-util.h"
26#include "ratelimit.h"
27#include "rm-rf.h"
28#include "string-util.h"
29#include "terminal-util.h"
30#include "time-util.h"
31#include "tmpfile-util.h"
32
33typedef struct TarImport {
34 sd_event *event;
35
36 char *image_root;
37
38 TarImportFinished on_finished;
39 void *userdata;
40
41 char *local;
42 ImportFlags flags;
43
44 char *temp_path;
45 char *final_path;
46
47 int input_fd;
48 int tar_fd;
49 int tree_fd;
50 int userns_fd;
51
52 ImportCompress compress;
53
54 sd_event_source *input_event_source;
55
56 uint8_t buffer[IMPORT_BUFFER_SIZE];
57 size_t buffer_size;
58
59 uint64_t written_compressed;
60 uint64_t written_uncompressed;
61
62 struct stat input_stat;
63
64 PidRef tar_pid;
65
66 unsigned last_percent;
67 RateLimit progress_ratelimit;
68} TarImport;
69
70TarImport* tar_import_unref(TarImport *i) {
71 if (!i)
72 return NULL;
73
74 sd_event_source_unref(i->input_event_source);
75
76 pidref_done_sigkill_wait(&i->tar_pid);
77
78 if (i->temp_path) {
79 import_remove_tree(i->temp_path, &i->userns_fd, i->flags);
80 free(i->temp_path);
81 }
82
83 import_compress_free(&i->compress);
84
85 sd_event_unref(i->event);
86
87 safe_close(i->tar_fd);
88 safe_close(i->tree_fd);
89 safe_close(i->userns_fd);
90
91 free(i->final_path);
92 free(i->image_root);
93 free(i->local);
94 return mfree(i);
95}
96
97int tar_import_new(
98 TarImport **ret,
99 sd_event *event,
100 const char *image_root,
101 TarImportFinished on_finished,
102 void *userdata) {
103
104 _cleanup_(tar_import_unrefp) TarImport *i = NULL;
105 _cleanup_free_ char *root = NULL;
106 int r;
107
108 assert(ret);
109 assert(image_root);
110
111 root = strdup(image_root);
112 if (!root)
113 return -ENOMEM;
114
115 i = new(TarImport, 1);
116 if (!i)
117 return -ENOMEM;
118
119 *i = (TarImport) {
120 .input_fd = -EBADF,
121 .tar_fd = -EBADF,
122 .tree_fd = -EBADF,
123 .userns_fd = -EBADF,
124 .on_finished = on_finished,
125 .userdata = userdata,
126 .last_percent = UINT_MAX,
127 .image_root = TAKE_PTR(root),
128 .progress_ratelimit = { 100 * USEC_PER_MSEC, 1 },
129 .tar_pid = PIDREF_NULL,
130 };
131
132 if (event)
133 i->event = sd_event_ref(event);
134 else {
135 r = sd_event_default(&i->event);
136 if (r < 0)
137 return r;
138 }
139
140 *ret = TAKE_PTR(i);
141
142 return 0;
143}
144
145static void tar_import_report_progress(TarImport *i) {
146 unsigned percent;
147 assert(i);
148
149 /* We have no size information, unless the source is a regular file */
150 if (!S_ISREG(i->input_stat.st_mode))
151 return;
152
153 if (i->written_compressed >= (uint64_t) i->input_stat.st_size)
154 percent = 100;
155 else
156 percent = (unsigned) ((i->written_compressed * UINT64_C(100)) / (uint64_t) i->input_stat.st_size);
157
158 if (percent == i->last_percent)
159 return;
160
161 if (!ratelimit_below(&i->progress_ratelimit))
162 return;
163
164 sd_notifyf(false, "X_IMPORT_PROGRESS=%u%%", percent);
165
166 if (isatty_safe(STDERR_FILENO))
167 (void) draw_progress_barf(
168 percent,
169 "%s %s/%s",
170 glyph(GLYPH_ARROW_RIGHT),
171 FORMAT_BYTES(i->written_compressed),
172 FORMAT_BYTES(i->input_stat.st_size));
173 else
174 log_info("Imported %u%%.", percent);
175
176 i->last_percent = percent;
177}
178
179static int tar_import_finish(TarImport *i) {
180 const char *d;
181 int r;
182
183 assert(i);
184 assert(i->tar_fd >= 0);
185 assert(i->tree_fd >= 0);
186
187 i->tar_fd = safe_close(i->tar_fd);
188
189 if (pidref_is_set(&i->tar_pid)) {
190 r = pidref_wait_for_terminate_and_check("tar", &i->tar_pid, WAIT_LOG);
191 if (r < 0)
192 return r;
193
194 pidref_done(&i->tar_pid);
195
196 if (r != EXIT_SUCCESS)
197 return -EPROTO;
198 }
199
200 assert_se(d = i->temp_path ?: i->local);
201
202 r = import_mangle_os_tree_fd(i->tree_fd, i->userns_fd, i->flags);
203 if (r < 0)
204 return r;
205
206 r = install_file(
207 AT_FDCWD, d,
208 AT_FDCWD, i->final_path,
209 (i->flags & IMPORT_FORCE ? INSTALL_REPLACE : 0) |
210 (i->flags & IMPORT_READ_ONLY ? INSTALL_READ_ONLY|INSTALL_GRACEFUL : 0) |
211 (i->flags & IMPORT_SYNC ? INSTALL_SYNCFS|INSTALL_GRACEFUL : 0));
212 if (r < 0)
213 return log_error_errno(r, "Failed to move '%s' into place: %m", i->final_path ?: i->local);
214
215 i->temp_path = mfree(i->temp_path);
216
217 return 0;
218}
219
220static int tar_import_fork_tar(TarImport *i) {
221 const char *d, *root;
222 int r;
223
224 assert(i);
225 assert(i->local);
226 assert(!i->final_path);
227 assert(!i->temp_path);
228 assert(i->tar_fd < 0);
229 assert(i->tree_fd < 0);
230
231 if (i->flags & IMPORT_DIRECT) {
232 d = i->local;
233 root = NULL;
234 } else {
235 i->final_path = path_join(i->image_root, i->local);
236 if (!i->final_path)
237 return log_oom();
238
239 r = tempfn_random(i->final_path, NULL, &i->temp_path);
240 if (r < 0)
241 return log_oom();
242
243 d = i->temp_path;
244 root = i->image_root;
245 }
246
247 assert(d);
248
249 (void) mkdir_parents_label(d, 0700);
250
251 if (FLAGS_SET(i->flags, IMPORT_DIRECT|IMPORT_FORCE))
252 (void) rm_rf(d, REMOVE_ROOT|REMOVE_PHYSICAL|REMOVE_SUBVOLUME);
253
254 if (FLAGS_SET(i->flags, IMPORT_FOREIGN_UID)) {
255 r = import_make_foreign_userns(&i->userns_fd);
256 if (r < 0)
257 return r;
258
259 _cleanup_close_ int directory_fd = -EBADF;
260 r = mountfsd_make_directory(d, /* flags= */ 0, &directory_fd);
261 if (r < 0)
262 return r;
263
264 r = mountfsd_mount_directory_fd(directory_fd, i->userns_fd, DISSECT_IMAGE_FOREIGN_UID, &i->tree_fd);
265 if (r < 0)
266 return r;
267 } else {
268 if (i->flags & IMPORT_BTRFS_SUBVOL)
269 r = btrfs_subvol_make_fallback(AT_FDCWD, d, 0755);
270 else
271 r = RET_NERRNO(mkdir(d, 0755));
272 if (r == -EEXIST && (i->flags & IMPORT_DIRECT)) /* EEXIST is OK if in direct mode, but not otherwise,
273 * because in that case our temporary path collided */
274 r = 0;
275 if (r < 0)
276 return log_error_errno(r, "Failed to create directory/subvolume %s: %m", d);
277 if (r > 0 && (i->flags & IMPORT_BTRFS_QUOTA)) { /* actually btrfs subvol */
278 if (!(i->flags & IMPORT_DIRECT))
279 (void) import_assign_pool_quota_and_warn(root);
280 (void) import_assign_pool_quota_and_warn(d);
281 }
282
283 i->tree_fd = open(d, O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW);
284 if (i->tree_fd < 0)
285 return log_error_errno(errno, "Failed to open '%s': %m", d);
286 }
287
288 i->tar_fd = import_fork_tar_x(i->tree_fd, i->userns_fd, &i->tar_pid);
289 if (i->tar_fd < 0)
290 return i->tar_fd;
291
292 return 0;
293}
294
295static int tar_import_write(const void *p, size_t sz, void *userdata) {
296 TarImport *i = userdata;
297 int r;
298
299 r = loop_write(i->tar_fd, p, sz);
300 if (r < 0)
301 return r;
302
303 i->written_uncompressed += sz;
304
305 return 0;
306}
307
308static int tar_import_process(TarImport *i) {
309 ssize_t l;
310 int r;
311
312 assert(i);
313 assert(i->buffer_size < sizeof(i->buffer));
314
315 l = read(i->input_fd, i->buffer + i->buffer_size, sizeof(i->buffer) - i->buffer_size);
316 if (l < 0) {
317 if (errno == EAGAIN)
318 return 0;
319
320 r = log_error_errno(errno, "Failed to read input file: %m");
321 goto finish;
322 }
323
324 if ((size_t) l > sizeof(i->buffer) - i->buffer_size) {
325 r = log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Read input file exceeded maximum size.");
326 goto finish;
327 }
328
329 i->buffer_size += l;
330
331 if (i->compress.type == IMPORT_COMPRESS_UNKNOWN) {
332
333 if (l == 0) { /* EOF */
334 log_debug("File too short to be compressed, as no compression signature fits in, thus assuming uncompressed.");
335 import_uncompress_force_off(&i->compress);
336 } else {
337 r = import_uncompress_detect(&i->compress, i->buffer, i->buffer_size);
338 if (r < 0) {
339 log_error_errno(r, "Failed to detect file compression: %m");
340 goto finish;
341 }
342 if (r == 0) /* Need more data */
343 return 0;
344 }
345
346 r = tar_import_fork_tar(i);
347 if (r < 0)
348 goto finish;
349 }
350
351 r = import_uncompress(&i->compress, i->buffer, i->buffer_size, tar_import_write, i);
352 if (r < 0) {
353 log_error_errno(r, "Failed to decode and write: %m");
354 goto finish;
355 }
356
357 i->written_compressed += i->buffer_size;
358 i->buffer_size = 0;
359
360 if (l == 0) { /* EOF */
361 r = tar_import_finish(i);
362 goto finish;
363 }
364
365 tar_import_report_progress(i);
366
367 return 0;
368
369finish:
370 if (r >= 0 && isatty_safe(STDERR_FILENO))
371 clear_progress_bar(/* prefix= */ NULL);
372
373 if (i->on_finished)
374 i->on_finished(i, r, i->userdata);
375 else
376 sd_event_exit(i->event, r);
377
378 return 0;
379}
380
381static int tar_import_on_input(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
382 TarImport *i = userdata;
383
384 return tar_import_process(i);
385}
386
387static int tar_import_on_defer(sd_event_source *s, void *userdata) {
388 TarImport *i = userdata;
389
390 return tar_import_process(i);
391}
392
393int tar_import_start(TarImport *i, int fd, const char *local, ImportFlags flags) {
394 int r;
395
396 assert(i);
397 assert(fd >= 0);
398 assert(local);
399 assert(!(flags & ~IMPORT_FLAGS_MASK_TAR));
400
401 if (!import_validate_local(local, flags))
402 return -EINVAL;
403
404 if (i->input_fd >= 0)
405 return -EBUSY;
406
407 r = fd_nonblock(fd, true);
408 if (r < 0)
409 return r;
410
411 r = free_and_strdup(&i->local, local);
412 if (r < 0)
413 return r;
414
415 i->flags = flags;
416
417 if (fstat(fd, &i->input_stat) < 0)
418 return -errno;
419
420 r = sd_event_add_io(i->event, &i->input_event_source, fd, EPOLLIN, tar_import_on_input, i);
421 if (r == -EPERM) {
422 /* This fd does not support epoll, for example because it is a regular file. Busy read in that case */
423 r = sd_event_add_defer(i->event, &i->input_event_source, tar_import_on_defer, i);
424 if (r < 0)
425 return r;
426
427 r = sd_event_source_set_enabled(i->input_event_source, SD_EVENT_ON);
428 }
429 if (r < 0)
430 return r;
431
432 i->input_fd = fd;
433 return 0;
434}