]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/import/export-tar.c
tree-wide: use TAKE_PTR() and TAKE_FD() macros
[thirdparty/systemd.git] / src / import / export-tar.c
1 /* SPDX-License-Identifier: LGPL-2.1+ */
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
21 #include "sd-daemon.h"
22
23 #include "alloc-util.h"
24 #include "btrfs-util.h"
25 #include "export-tar.h"
26 #include "fd-util.h"
27 #include "fileio.h"
28 #include "import-common.h"
29 #include "process-util.h"
30 #include "ratelimit.h"
31 #include "string-util.h"
32 #include "util.h"
33
34 #define COPY_BUFFER_SIZE (16*1024)
35
36 struct 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
71 TarExport *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) {
83 (void) btrfs_subvol_remove(e->temp_path, BTRFS_REMOVE_QUOTA);
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);
95 return mfree(e);
96 }
97
98 int 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
129 *ret = TAKE_PTR(e);
130
131 return 0;
132 }
133
134 static void tar_export_report_progress(TarExport *e) {
135 unsigned percent;
136 assert(e);
137
138 /* Do we have any quota info? If not, we don't know anything about the progress */
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
159 static 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
231 finish:
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
240 static 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
246 static int tar_export_on_defer(sd_event_source *s, void *userdata) {
247 TarExport *i = userdata;
248
249 return tar_export_process(i);
250 }
251
252 int 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
285 r = btrfs_subvol_get_subtree_quota_fd(sfd, 0, &q);
286 if (r >= 0)
287 e->quota_referenced = q.referenced;
288
289 e->temp_path = mfree(e->temp_path);
290
291 r = tempfn_random(path, NULL, &e->temp_path);
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 */
296 r = btrfs_subvol_snapshot_fd(sfd, e->temp_path, BTRFS_SNAPSHOT_READ_ONLY|BTRFS_SNAPSHOT_RECURSIVE);
297 if (r < 0) {
298 log_debug_errno(r, "Couldn't create snapshot %s of %s, not exporting atomically: %m", e->temp_path, path);
299 e->temp_path = mfree(e->temp_path);
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 }