chiark / gitweb /
shared: the btrfs quota field is called "referenced" not "referred"
[elogind.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 <sys/sendfile.h>
23
24 #include "sd-daemon.h"
25 #include "util.h"
26 #include "ratelimit.h"
27 #include "btrfs-util.h"
28 #include "import-common.h"
29 #include "export-tar.h"
30
31 #define COPY_BUFFER_SIZE (16*1024)
32
33 struct TarExport {
34         sd_event *event;
35
36         TarExportFinished on_finished;
37         void *userdata;
38
39         char *path;
40         char *temp_path;
41
42         int output_fd;
43         int tar_fd;
44
45         ImportCompress compress;
46
47         sd_event_source *output_event_source;
48
49         void *buffer;
50         size_t buffer_size;
51         size_t buffer_allocated;
52
53         uint64_t written_compressed;
54         uint64_t written_uncompressed;
55
56         pid_t tar_pid;
57
58         struct stat st;
59         uint64_t quota_referenced;
60
61         unsigned last_percent;
62         RateLimit progress_rate_limit;
63
64         bool eof;
65         bool tried_splice;
66 };
67
68 TarExport *tar_export_unref(TarExport *e) {
69         if (!e)
70                 return NULL;
71
72         sd_event_source_unref(e->output_event_source);
73
74         if (e->tar_pid > 1) {
75                 (void) kill_and_sigcont(e->tar_pid, SIGKILL);
76                 (void) wait_for_terminate(e->tar_pid, NULL);
77         }
78
79         if (e->temp_path) {
80                 (void) btrfs_subvol_remove(e->temp_path);
81                 free(e->temp_path);
82         }
83
84         import_compress_free(&e->compress);
85
86         sd_event_unref(e->event);
87
88         safe_close(e->tar_fd);
89
90         free(e->buffer);
91         free(e->path);
92         free(e);
93
94         return NULL;
95 }
96
97 int tar_export_new(
98                 TarExport **ret,
99                 sd_event *event,
100                 TarExportFinished on_finished,
101                 void *userdata) {
102
103         _cleanup_(tar_export_unrefp) TarExport *e = NULL;
104         int r;
105
106         assert(ret);
107
108         e = new0(TarExport, 1);
109         if (!e)
110                 return -ENOMEM;
111
112         e->output_fd = e->tar_fd = -1;
113         e->on_finished = on_finished;
114         e->userdata = userdata;
115         e->quota_referenced = (uint64_t) -1;
116
117         RATELIMIT_INIT(e->progress_rate_limit, 100 * USEC_PER_MSEC, 1);
118         e->last_percent = (unsigned) -1;
119
120         if (event)
121                 e->event = sd_event_ref(event);
122         else {
123                 r = sd_event_default(&e->event);
124                 if (r < 0)
125                         return r;
126         }
127
128         *ret = e;
129         e = NULL;
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? I fnot, 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_quota_fd(sfd, &q);
286                 if (r >= 0)
287                         e->quota_referenced = q.referenced;
288
289                 free(e->temp_path);
290                 e->temp_path = NULL;
291
292                 r = tempfn_random(path, &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, true, false);
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                         free(e->temp_path);
301                         e->temp_path = NULL;
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 }