]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/import/export-tar.c
tree-wide: use TAKE_PTR() and TAKE_FD() macros
[thirdparty/systemd.git] / src / import / export-tar.c
CommitLineData
53e1b683 1/* SPDX-License-Identifier: LGPL-2.1+ */
587fec42
LP
2/***
3 This file is part of systemd.
4
5 Copyright 2015 Lennart Poettering
6
7 systemd is free software; you can redistribute it and/or modify it
8 under the terms of the GNU Lesser General Public License as published by
9 the Free Software Foundation; either version 2.1 of the License, or
10 (at your option) any later version.
11
12 systemd is distributed in the hope that it will be useful, but
13 WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 Lesser General Public License for more details.
16
17 You should have received a copy of the GNU Lesser General Public License
18 along with systemd; If not, see <http://www.gnu.org/licenses/>.
19***/
20
587fec42 21#include "sd-daemon.h"
07630cea 22
b5efdb8a 23#include "alloc-util.h"
587fec42 24#include "btrfs-util.h"
3ffd4af2
LP
25#include "export-tar.h"
26#include "fd-util.h"
0d39fa9c 27#include "fileio.h"
587fec42 28#include "import-common.h"
0b452006 29#include "process-util.h"
07630cea
LP
30#include "ratelimit.h"
31#include "string-util.h"
32#include "util.h"
587fec42
LP
33
34#define COPY_BUFFER_SIZE (16*1024)
35
36struct TarExport {
37 sd_event *event;
38
39 TarExportFinished on_finished;
40 void *userdata;
41
42 char *path;
43 char *temp_path;
44
45 int output_fd;
46 int tar_fd;
47
48 ImportCompress compress;
49
50 sd_event_source *output_event_source;
51
52 void *buffer;
53 size_t buffer_size;
54 size_t buffer_allocated;
55
56 uint64_t written_compressed;
57 uint64_t written_uncompressed;
58
59 pid_t tar_pid;
60
61 struct stat st;
62 uint64_t quota_referenced;
63
64 unsigned last_percent;
65 RateLimit progress_rate_limit;
66
67 bool eof;
68 bool tried_splice;
69};
70
71TarExport *tar_export_unref(TarExport *e) {
72 if (!e)
73 return NULL;
74
75 sd_event_source_unref(e->output_event_source);
76
77 if (e->tar_pid > 1) {
78 (void) kill_and_sigcont(e->tar_pid, SIGKILL);
79 (void) wait_for_terminate(e->tar_pid, NULL);
80 }
81
82 if (e->temp_path) {
5bcd08db 83 (void) btrfs_subvol_remove(e->temp_path, BTRFS_REMOVE_QUOTA);
587fec42
LP
84 free(e->temp_path);
85 }
86
87 import_compress_free(&e->compress);
88
89 sd_event_unref(e->event);
90
91 safe_close(e->tar_fd);
92
93 free(e->buffer);
94 free(e->path);
6b430fdb 95 return mfree(e);
587fec42
LP
96}
97
98int tar_export_new(
99 TarExport **ret,
100 sd_event *event,
101 TarExportFinished on_finished,
102 void *userdata) {
103
104 _cleanup_(tar_export_unrefp) TarExport *e = NULL;
105 int r;
106
107 assert(ret);
108
109 e = new0(TarExport, 1);
110 if (!e)
111 return -ENOMEM;
112
113 e->output_fd = e->tar_fd = -1;
114 e->on_finished = on_finished;
115 e->userdata = userdata;
116 e->quota_referenced = (uint64_t) -1;
117
118 RATELIMIT_INIT(e->progress_rate_limit, 100 * USEC_PER_MSEC, 1);
119 e->last_percent = (unsigned) -1;
120
121 if (event)
122 e->event = sd_event_ref(event);
123 else {
124 r = sd_event_default(&e->event);
125 if (r < 0)
126 return r;
127 }
128
1cc6c93a 129 *ret = TAKE_PTR(e);
587fec42
LP
130
131 return 0;
132}
133
134static void tar_export_report_progress(TarExport *e) {
135 unsigned percent;
136 assert(e);
137
e5f270f5 138 /* Do we have any quota info? If not, we don't know anything about the progress */
587fec42
LP
139 if (e->quota_referenced == (uint64_t) -1)
140 return;
141
142 if (e->written_uncompressed >= e->quota_referenced)
143 percent = 100;
144 else
145 percent = (unsigned) ((e->written_uncompressed * UINT64_C(100)) / e->quota_referenced);
146
147 if (percent == e->last_percent)
148 return;
149
150 if (!ratelimit_test(&e->progress_rate_limit))
151 return;
152
153 sd_notifyf(false, "X_IMPORT_PROGRESS=%u", percent);
154 log_info("Exported %u%%.", percent);
155
156 e->last_percent = percent;
157}
158
159static int tar_export_process(TarExport *e) {
160 ssize_t l;
161 int r;
162
163 assert(e);
164
165 if (!e->tried_splice && e->compress.type == IMPORT_COMPRESS_UNCOMPRESSED) {
166
167 l = splice(e->tar_fd, NULL, e->output_fd, NULL, COPY_BUFFER_SIZE, 0);
168 if (l < 0) {
169 if (errno == EAGAIN)
170 return 0;
171
172 e->tried_splice = true;
173 } else if (l == 0) {
174 r = 0;
175 goto finish;
176 } else {
177 e->written_uncompressed += l;
178 e->written_compressed += l;
179
180 tar_export_report_progress(e);
181
182 return 0;
183 }
184 }
185
186 while (e->buffer_size <= 0) {
187 uint8_t input[COPY_BUFFER_SIZE];
188
189 if (e->eof) {
190 r = 0;
191 goto finish;
192 }
193
194 l = read(e->tar_fd, input, sizeof(input));
195 if (l < 0) {
196 r = log_error_errno(errno, "Failed to read tar file: %m");
197 goto finish;
198 }
199
200 if (l == 0) {
201 e->eof = true;
202 r = import_compress_finish(&e->compress, &e->buffer, &e->buffer_size, &e->buffer_allocated);
203 } else {
204 e->written_uncompressed += l;
205 r = import_compress(&e->compress, input, l, &e->buffer, &e->buffer_size, &e->buffer_allocated);
206 }
207 if (r < 0) {
208 r = log_error_errno(r, "Failed to encode: %m");
209 goto finish;
210 }
211 }
212
213 l = write(e->output_fd, e->buffer, e->buffer_size);
214 if (l < 0) {
215 if (errno == EAGAIN)
216 return 0;
217
218 r = log_error_errno(errno, "Failed to write output file: %m");
219 goto finish;
220 }
221
222 assert((size_t) l <= e->buffer_size);
223 memmove(e->buffer, (uint8_t*) e->buffer + l, e->buffer_size - l);
224 e->buffer_size -= l;
225 e->written_compressed += l;
226
227 tar_export_report_progress(e);
228
229 return 0;
230
231finish:
232 if (e->on_finished)
233 e->on_finished(e, r, e->userdata);
234 else
235 sd_event_exit(e->event, r);
236
237 return 0;
238}
239
240static int tar_export_on_output(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
241 TarExport *i = userdata;
242
243 return tar_export_process(i);
244}
245
246static int tar_export_on_defer(sd_event_source *s, void *userdata) {
247 TarExport *i = userdata;
248
249 return tar_export_process(i);
250}
251
252int tar_export_start(TarExport *e, const char *path, int fd, ImportCompressType compress) {
253 _cleanup_close_ int sfd = -1;
254 int r;
255
256 assert(e);
257 assert(path);
258 assert(fd >= 0);
259 assert(compress < _IMPORT_COMPRESS_TYPE_MAX);
260 assert(compress != IMPORT_COMPRESS_UNKNOWN);
261
262 if (e->output_fd >= 0)
263 return -EBUSY;
264
265 sfd = open(path, O_DIRECTORY|O_RDONLY|O_NOCTTY|O_CLOEXEC);
266 if (sfd < 0)
267 return -errno;
268
269 if (fstat(sfd, &e->st) < 0)
270 return -errno;
271
272 r = fd_nonblock(fd, true);
273 if (r < 0)
274 return r;
275
276 r = free_and_strdup(&e->path, path);
277 if (r < 0)
278 return r;
279
280 e->quota_referenced = (uint64_t) -1;
281
282 if (e->st.st_ino == 256) { /* might be a btrfs subvolume? */
283 BtrfsQuotaInfo q;
284
5bcd08db 285 r = btrfs_subvol_get_subtree_quota_fd(sfd, 0, &q);
587fec42 286 if (r >= 0)
cb81cd80 287 e->quota_referenced = q.referenced;
587fec42 288
a1e58e8e 289 e->temp_path = mfree(e->temp_path);
587fec42 290
14bcf25c 291 r = tempfn_random(path, NULL, &e->temp_path);
587fec42
LP
292 if (r < 0)
293 return r;
294
295 /* Let's try to make a snapshot, if we can, so that the export is atomic */
f70a17f8 296 r = btrfs_subvol_snapshot_fd(sfd, e->temp_path, BTRFS_SNAPSHOT_READ_ONLY|BTRFS_SNAPSHOT_RECURSIVE);
587fec42
LP
297 if (r < 0) {
298 log_debug_errno(r, "Couldn't create snapshot %s of %s, not exporting atomically: %m", e->temp_path, path);
a1e58e8e 299 e->temp_path = mfree(e->temp_path);
587fec42
LP
300 }
301 }
302
303 r = import_compress_init(&e->compress, compress);
304 if (r < 0)
305 return r;
306
307 r = sd_event_add_io(e->event, &e->output_event_source, fd, EPOLLOUT, tar_export_on_output, e);
308 if (r == -EPERM) {
309 r = sd_event_add_defer(e->event, &e->output_event_source, tar_export_on_defer, e);
310 if (r < 0)
311 return r;
312
313 r = sd_event_source_set_enabled(e->output_event_source, SD_EVENT_ON);
314 }
315 if (r < 0)
316 return r;
317
318 e->tar_fd = import_fork_tar_c(e->temp_path ?: e->path, &e->tar_pid);
319 if (e->tar_fd < 0) {
320 e->output_event_source = sd_event_source_unref(e->output_event_source);
321 return e->tar_fd;
322 }
323
324 e->output_fd = fd;
325 return r;
326}