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