chiark / gitweb /
fsck: use _cleanup_close_pair_ where appropriate
[elogind.git] / src / import / export-raw.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 #include <libgen.h>
24 #undef basename
25
26 #include "sd-daemon.h"
27 #include "util.h"
28 #include "ratelimit.h"
29 #include "btrfs-util.h"
30 #include "copy.h"
31 #include "import-common.h"
32 #include "export-raw.h"
33
34 #define COPY_BUFFER_SIZE (16*1024)
35
36 struct RawExport {
37         sd_event *event;
38
39         RawExportFinished on_finished;
40         void *userdata;
41
42         char *path;
43
44         int input_fd;
45         int output_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         unsigned last_percent;
59         RateLimit progress_rate_limit;
60
61         struct stat st;
62
63         bool eof;
64         bool tried_reflink;
65         bool tried_sendfile;
66 };
67
68 RawExport *raw_export_unref(RawExport *e) {
69         if (!e)
70                 return NULL;
71
72         sd_event_source_unref(e->output_event_source);
73
74         import_compress_free(&e->compress);
75
76         sd_event_unref(e->event);
77
78         safe_close(e->input_fd);
79
80         free(e->buffer);
81         free(e->path);
82         free(e);
83
84         return NULL;
85 }
86
87 int raw_export_new(
88                 RawExport **ret,
89                 sd_event *event,
90                 RawExportFinished on_finished,
91                 void *userdata) {
92
93         _cleanup_(raw_export_unrefp) RawExport *e = NULL;
94         int r;
95
96         assert(ret);
97
98         e = new0(RawExport, 1);
99         if (!e)
100                 return -ENOMEM;
101
102         e->output_fd = e->input_fd = -1;
103         e->on_finished = on_finished;
104         e->userdata = userdata;
105
106         RATELIMIT_INIT(e->progress_rate_limit, 100 * USEC_PER_MSEC, 1);
107         e->last_percent = (unsigned) -1;
108
109         if (event)
110                 e->event = sd_event_ref(event);
111         else {
112                 r = sd_event_default(&e->event);
113                 if (r < 0)
114                         return r;
115         }
116
117         *ret = e;
118         e = NULL;
119
120         return 0;
121 }
122
123 static void raw_export_report_progress(RawExport *e) {
124         unsigned percent;
125         assert(e);
126
127         if (e->written_uncompressed >= (uint64_t) e->st.st_size)
128                 percent = 100;
129         else
130                 percent = (unsigned) ((e->written_uncompressed * UINT64_C(100)) / (uint64_t) e->st.st_size);
131
132         if (percent == e->last_percent)
133                 return;
134
135         if (!ratelimit_test(&e->progress_rate_limit))
136                 return;
137
138         sd_notifyf(false, "X_IMPORT_PROGRESS=%u", percent);
139         log_info("Exported %u%%.", percent);
140
141         e->last_percent = percent;
142 }
143
144 static int raw_export_process(RawExport *e) {
145         ssize_t l;
146         int r;
147
148         assert(e);
149
150         if (!e->tried_reflink && e->compress.type == IMPORT_COMPRESS_UNCOMPRESSED) {
151
152                 /* If we shall take an uncompressed snapshot we can
153                  * reflink source to destination directly. Let's see
154                  * if this works. */
155
156                 r = btrfs_reflink(e->input_fd, e->output_fd);
157                 if (r >= 0) {
158                         r = 0;
159                         goto finish;
160                 }
161
162                 e->tried_reflink = true;
163         }
164
165         if (!e->tried_sendfile && e->compress.type == IMPORT_COMPRESS_UNCOMPRESSED) {
166
167                 l = sendfile(e->output_fd, e->input_fd, NULL, COPY_BUFFER_SIZE);
168                 if (l < 0) {
169                         if (errno == EAGAIN)
170                                 return 0;
171
172                         e->tried_sendfile = 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                         raw_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->input_fd, input, sizeof(input));
195                 if (l < 0) {
196                         r = log_error_errno(errno, "Failed to read raw 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         raw_export_report_progress(e);
228
229         return 0;
230
231 finish:
232         if (r >= 0) {
233                 (void) copy_times(e->input_fd, e->output_fd);
234                 (void) copy_xattr(e->input_fd, e->output_fd);
235         }
236
237         if (e->on_finished)
238                 e->on_finished(e, r, e->userdata);
239         else
240                 sd_event_exit(e->event, r);
241
242         return 0;
243 }
244
245 static int raw_export_on_output(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
246         RawExport *i = userdata;
247
248         return raw_export_process(i);
249 }
250
251 static int raw_export_on_defer(sd_event_source *s, void *userdata) {
252         RawExport *i = userdata;
253
254         return raw_export_process(i);
255 }
256
257 static int reflink_snapshot(int fd, const char *path) {
258         char *p, *d;
259         int new_fd, r;
260
261         p = strdupa(path);
262         d = dirname(p);
263
264         new_fd = open(d, O_TMPFILE|O_CLOEXEC|O_NOCTTY|O_RDWR, 0600);
265         if (new_fd < 0) {
266                 _cleanup_free_ char *t = NULL;
267
268                 r = tempfn_random(path, &t);
269                 if (r < 0)
270                         return r;
271
272                 new_fd = open(t, O_CLOEXEC|O_CREAT|O_NOCTTY|O_RDWR, 0600);
273                 if (new_fd < 0)
274                         return -errno;
275
276                 (void) unlink(t);
277         }
278
279         r = btrfs_reflink(fd, new_fd);
280         if (r < 0) {
281                 safe_close(new_fd);
282                 return r;
283         }
284
285         return new_fd;
286 }
287
288 int raw_export_start(RawExport *e, const char *path, int fd, ImportCompressType compress) {
289         _cleanup_close_ int sfd = -1, tfd = -1;
290         int r;
291
292         assert(e);
293         assert(path);
294         assert(fd >= 0);
295         assert(compress < _IMPORT_COMPRESS_TYPE_MAX);
296         assert(compress != IMPORT_COMPRESS_UNKNOWN);
297
298         if (e->output_fd >= 0)
299                 return -EBUSY;
300
301         r = fd_nonblock(fd, true);
302         if (r < 0)
303                 return r;
304
305         r = free_and_strdup(&e->path, path);
306         if (r < 0)
307                 return r;
308
309         sfd = open(path, O_RDONLY|O_CLOEXEC|O_NOCTTY);
310         if (sfd < 0)
311                 return -errno;
312
313         if (fstat(sfd, &e->st) < 0)
314                 return -errno;
315         if (!S_ISREG(e->st.st_mode))
316                 return -ENOTTY;
317
318         /* Try to take a reflink snapshot of the file, if we can t make the export atomic */
319         tfd = reflink_snapshot(sfd, path);
320         if (tfd >= 0) {
321                 e->input_fd = tfd;
322                 tfd = -1;
323         } else {
324                 e->input_fd = sfd;
325                 sfd = -1;
326         }
327
328         r = import_compress_init(&e->compress, compress);
329         if (r < 0)
330                 return r;
331
332         r = sd_event_add_io(e->event, &e->output_event_source, fd, EPOLLOUT, raw_export_on_output, e);
333         if (r == -EPERM) {
334                 r = sd_event_add_defer(e->event, &e->output_event_source, raw_export_on_defer, e);
335                 if (r < 0)
336                         return r;
337
338                 r = sd_event_source_set_enabled(e->output_event_source, SD_EVENT_ON);
339         }
340         if (r < 0)
341                 return r;
342
343         e->output_fd = fd;
344         return r;
345 }