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