]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/import/export-tar.c
Add SPDX license identifiers to source files under the LGPL
[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 = e;
130 e = NULL;
131
132 return 0;
133 }
134
135 static void tar_export_report_progress(TarExport *e) {
136 unsigned percent;
137 assert(e);
138
139 /* Do we have any quota info? If not, we don't know anything about the progress */
140 if (e->quota_referenced == (uint64_t) -1)
141 return;
142
143 if (e->written_uncompressed >= e->quota_referenced)
144 percent = 100;
145 else
146 percent = (unsigned) ((e->written_uncompressed * UINT64_C(100)) / e->quota_referenced);
147
148 if (percent == e->last_percent)
149 return;
150
151 if (!ratelimit_test(&e->progress_rate_limit))
152 return;
153
154 sd_notifyf(false, "X_IMPORT_PROGRESS=%u", percent);
155 log_info("Exported %u%%.", percent);
156
157 e->last_percent = percent;
158 }
159
160 static int tar_export_process(TarExport *e) {
161 ssize_t l;
162 int r;
163
164 assert(e);
165
166 if (!e->tried_splice && e->compress.type == IMPORT_COMPRESS_UNCOMPRESSED) {
167
168 l = splice(e->tar_fd, NULL, e->output_fd, NULL, COPY_BUFFER_SIZE, 0);
169 if (l < 0) {
170 if (errno == EAGAIN)
171 return 0;
172
173 e->tried_splice = true;
174 } else if (l == 0) {
175 r = 0;
176 goto finish;
177 } else {
178 e->written_uncompressed += l;
179 e->written_compressed += l;
180
181 tar_export_report_progress(e);
182
183 return 0;
184 }
185 }
186
187 while (e->buffer_size <= 0) {
188 uint8_t input[COPY_BUFFER_SIZE];
189
190 if (e->eof) {
191 r = 0;
192 goto finish;
193 }
194
195 l = read(e->tar_fd, input, sizeof(input));
196 if (l < 0) {
197 r = log_error_errno(errno, "Failed to read tar file: %m");
198 goto finish;
199 }
200
201 if (l == 0) {
202 e->eof = true;
203 r = import_compress_finish(&e->compress, &e->buffer, &e->buffer_size, &e->buffer_allocated);
204 } else {
205 e->written_uncompressed += l;
206 r = import_compress(&e->compress, input, l, &e->buffer, &e->buffer_size, &e->buffer_allocated);
207 }
208 if (r < 0) {
209 r = log_error_errno(r, "Failed to encode: %m");
210 goto finish;
211 }
212 }
213
214 l = write(e->output_fd, e->buffer, e->buffer_size);
215 if (l < 0) {
216 if (errno == EAGAIN)
217 return 0;
218
219 r = log_error_errno(errno, "Failed to write output file: %m");
220 goto finish;
221 }
222
223 assert((size_t) l <= e->buffer_size);
224 memmove(e->buffer, (uint8_t*) e->buffer + l, e->buffer_size - l);
225 e->buffer_size -= l;
226 e->written_compressed += l;
227
228 tar_export_report_progress(e);
229
230 return 0;
231
232 finish:
233 if (e->on_finished)
234 e->on_finished(e, r, e->userdata);
235 else
236 sd_event_exit(e->event, r);
237
238 return 0;
239 }
240
241 static int tar_export_on_output(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
242 TarExport *i = userdata;
243
244 return tar_export_process(i);
245 }
246
247 static int tar_export_on_defer(sd_event_source *s, void *userdata) {
248 TarExport *i = userdata;
249
250 return tar_export_process(i);
251 }
252
253 int tar_export_start(TarExport *e, const char *path, int fd, ImportCompressType compress) {
254 _cleanup_close_ int sfd = -1;
255 int r;
256
257 assert(e);
258 assert(path);
259 assert(fd >= 0);
260 assert(compress < _IMPORT_COMPRESS_TYPE_MAX);
261 assert(compress != IMPORT_COMPRESS_UNKNOWN);
262
263 if (e->output_fd >= 0)
264 return -EBUSY;
265
266 sfd = open(path, O_DIRECTORY|O_RDONLY|O_NOCTTY|O_CLOEXEC);
267 if (sfd < 0)
268 return -errno;
269
270 if (fstat(sfd, &e->st) < 0)
271 return -errno;
272
273 r = fd_nonblock(fd, true);
274 if (r < 0)
275 return r;
276
277 r = free_and_strdup(&e->path, path);
278 if (r < 0)
279 return r;
280
281 e->quota_referenced = (uint64_t) -1;
282
283 if (e->st.st_ino == 256) { /* might be a btrfs subvolume? */
284 BtrfsQuotaInfo q;
285
286 r = btrfs_subvol_get_subtree_quota_fd(sfd, 0, &q);
287 if (r >= 0)
288 e->quota_referenced = q.referenced;
289
290 e->temp_path = mfree(e->temp_path);
291
292 r = tempfn_random(path, NULL, &e->temp_path);
293 if (r < 0)
294 return r;
295
296 /* Let's try to make a snapshot, if we can, so that the export is atomic */
297 r = btrfs_subvol_snapshot_fd(sfd, e->temp_path, BTRFS_SNAPSHOT_READ_ONLY|BTRFS_SNAPSHOT_RECURSIVE);
298 if (r < 0) {
299 log_debug_errno(r, "Couldn't create snapshot %s of %s, not exporting atomically: %m", e->temp_path, path);
300 e->temp_path = mfree(e->temp_path);
301 }
302 }
303
304 r = import_compress_init(&e->compress, compress);
305 if (r < 0)
306 return r;
307
308 r = sd_event_add_io(e->event, &e->output_event_source, fd, EPOLLOUT, tar_export_on_output, e);
309 if (r == -EPERM) {
310 r = sd_event_add_defer(e->event, &e->output_event_source, tar_export_on_defer, e);
311 if (r < 0)
312 return r;
313
314 r = sd_event_source_set_enabled(e->output_event_source, SD_EVENT_ON);
315 }
316 if (r < 0)
317 return r;
318
319 e->tar_fd = import_fork_tar_c(e->temp_path ?: e->path, &e->tar_pid);
320 if (e->tar_fd < 0) {
321 e->output_event_source = sd_event_source_unref(e->output_event_source);
322 return e->tar_fd;
323 }
324
325 e->output_fd = fd;
326 return r;
327 }