chiark / gitweb /
copy: teach copy_bytes() btrfs reflink magic
[elogind.git] / src / shared / copy.c
1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3 /***
4   This file is part of systemd.
5
6   Copyright 2014 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 "util.h"
25 #include "btrfs-util.h"
26 #include "copy.h"
27
28 int copy_bytes(int fdf, int fdt, off_t max_bytes) {
29         bool try_sendfile = true;
30         int r;
31
32         assert(fdf >= 0);
33         assert(fdt >= 0);
34
35         /* Try btrfs reflinks first. */
36         if (max_bytes == (off_t) -1) {
37                 r = btrfs_reflink(fdf, fdt);
38                 if (r >= 0)
39                         return 0;
40         }
41
42         for (;;) {
43                 size_t m = PIPE_BUF;
44                 ssize_t n;
45
46                 if (max_bytes != (off_t) -1) {
47
48                         if (max_bytes <= 0)
49                                 return -EFBIG;
50
51                         if ((off_t) m > max_bytes)
52                                 m = (size_t) max_bytes;
53                 }
54
55                 /* First try sendfile(), unless we already tried */
56                 if (try_sendfile) {
57
58                         n = sendfile(fdt, fdf, NULL, m);
59                         if (n < 0) {
60                                 if (errno != EINVAL && errno != ENOSYS)
61                                         return -errno;
62
63                                 try_sendfile = false;
64                                 /* use fallback below */
65                         } else if (n == 0) /* EOF */
66                                 break;
67                         else if (n > 0)
68                                 /* Succcess! */
69                                 goto next;
70                 }
71
72                 /* As a fallback just copy bits by hand */
73                 {
74                         char buf[m];
75
76                         n = read(fdf, buf, m);
77                         if (n < 0)
78                                 return -errno;
79                         if (n == 0) /* EOF */
80                                 break;
81
82                         r = loop_write(fdt, buf, (size_t) n, false);
83                         if (r < 0)
84                                 return r;
85
86                 }
87
88         next:
89                 if (max_bytes != (off_t) -1) {
90                         assert(max_bytes >= n);
91                         max_bytes -= n;
92                 }
93         }
94
95         return 0;
96 }
97
98 static int fd_copy_symlink(int df, const char *from, const struct stat *st, int dt, const char *to) {
99         _cleanup_free_ char *target = NULL;
100         int r;
101
102         assert(from);
103         assert(st);
104         assert(to);
105
106         r = readlinkat_malloc(df, from, &target);
107         if (r < 0)
108                 return r;
109
110         if (symlinkat(target, dt, to) < 0)
111                 return -errno;
112
113         if (fchownat(dt, to, st->st_uid, st->st_gid, AT_SYMLINK_NOFOLLOW) < 0)
114                 return -errno;
115
116         return 0;
117 }
118
119 static int fd_copy_regular(int df, const char *from, const struct stat *st, int dt, const char *to) {
120         _cleanup_close_ int fdf = -1, fdt = -1;
121         int r, q;
122
123         assert(from);
124         assert(st);
125         assert(to);
126
127         fdf = openat(df, from, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW);
128         if (fdf < 0)
129                 return -errno;
130
131         fdt = openat(dt, to, O_WRONLY|O_CREAT|O_EXCL|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW, st->st_mode & 07777);
132         if (fdt < 0)
133                 return -errno;
134
135         r = copy_bytes(fdf, fdt, (off_t) -1);
136         if (r < 0) {
137                 unlinkat(dt, to, 0);
138                 return r;
139         }
140
141         if (fchown(fdt, st->st_uid, st->st_gid) < 0)
142                 r = -errno;
143
144         if (fchmod(fdt, st->st_mode & 07777) < 0)
145                 r = -errno;
146
147         q = close(fdt);
148         fdt = -1;
149
150         if (q < 0) {
151                 r = -errno;
152                 unlinkat(dt, to, 0);
153         }
154
155         return r;
156 }
157
158 static int fd_copy_fifo(int df, const char *from, const struct stat *st, int dt, const char *to) {
159         int r;
160
161         assert(from);
162         assert(st);
163         assert(to);
164
165         r = mkfifoat(dt, to, st->st_mode & 07777);
166         if (r < 0)
167                 return -errno;
168
169         if (fchownat(dt, to, st->st_uid, st->st_gid, AT_SYMLINK_NOFOLLOW) < 0)
170                 r = -errno;
171
172         if (fchmodat(dt, to, st->st_mode & 07777, 0) < 0)
173                 r = -errno;
174
175         return r;
176 }
177
178 static int fd_copy_node(int df, const char *from, const struct stat *st, int dt, const char *to) {
179         int r;
180
181         assert(from);
182         assert(st);
183         assert(to);
184
185         r = mknodat(dt, to, st->st_mode, st->st_rdev);
186         if (r < 0)
187                 return -errno;
188
189         if (fchownat(dt, to, st->st_uid, st->st_gid, AT_SYMLINK_NOFOLLOW) < 0)
190                 r = -errno;
191
192         if (fchmodat(dt, to, st->st_mode & 07777, 0) < 0)
193                 r = -errno;
194
195         return r;
196 }
197
198 static int fd_copy_directory(
199                 int df,
200                 const char *from,
201                 const struct stat *st,
202                 int dt,
203                 const char *to,
204                 dev_t original_device,
205                 bool merge) {
206
207         _cleanup_close_ int fdf = -1, fdt = -1;
208         _cleanup_closedir_ DIR *d = NULL;
209         struct dirent *de;
210         bool created;
211         int r;
212
213         assert(st);
214         assert(to);
215
216         if (from)
217                 fdf = openat(df, from, O_RDONLY|O_DIRECTORY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW);
218         else
219                 fdf = fcntl(df, F_DUPFD_CLOEXEC, 3);
220
221         d = fdopendir(fdf);
222         if (!d)
223                 return -errno;
224         fdf = -1;
225
226         r = mkdirat(dt, to, st->st_mode & 07777);
227         if (r >= 0)
228                 created = true;
229         else if (errno == EEXIST && merge)
230                 created = false;
231         else
232                 return -errno;
233
234         fdt = openat(dt, to, O_RDONLY|O_DIRECTORY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW);
235         if (fdt < 0)
236                 return -errno;
237
238         r = 0;
239
240         if (created) {
241                 if (fchown(fdt, st->st_uid, st->st_gid) < 0)
242                         r = -errno;
243
244                 if (fchmod(fdt, st->st_mode & 07777) < 0)
245                         r = -errno;
246         }
247
248         FOREACH_DIRENT(de, d, return -errno) {
249                 struct stat buf;
250                 int q;
251
252                 if (fstatat(dirfd(d), de->d_name, &buf, AT_SYMLINK_NOFOLLOW) < 0) {
253                         r = -errno;
254                         continue;
255                 }
256
257                 if (buf.st_dev != original_device)
258                         continue;
259
260                 if (S_ISREG(buf.st_mode))
261                         q = fd_copy_regular(dirfd(d), de->d_name, &buf, fdt, de->d_name);
262                 else if (S_ISDIR(buf.st_mode))
263                         q = fd_copy_directory(dirfd(d), de->d_name, &buf, fdt, de->d_name, original_device, merge);
264                 else if (S_ISLNK(buf.st_mode))
265                         q = fd_copy_symlink(dirfd(d), de->d_name, &buf, fdt, de->d_name);
266                 else if (S_ISFIFO(buf.st_mode))
267                         q = fd_copy_fifo(dirfd(d), de->d_name, &buf, fdt, de->d_name);
268                 else if (S_ISBLK(buf.st_mode) || S_ISCHR(buf.st_mode))
269                         q = fd_copy_node(dirfd(d), de->d_name, &buf, fdt, de->d_name);
270                 else
271                         q = -ENOTSUP;
272
273                 if (q == -EEXIST && merge)
274                         q = 0;
275
276                 if (q < 0)
277                         r = q;
278         }
279
280         return r;
281 }
282
283 int copy_tree(const char *from, const char *to, bool merge) {
284         struct stat st;
285
286         assert(from);
287         assert(to);
288
289         if (lstat(from, &st) < 0)
290                 return -errno;
291
292         if (S_ISREG(st.st_mode))
293                 return fd_copy_regular(AT_FDCWD, from, &st, AT_FDCWD, to);
294         else if (S_ISDIR(st.st_mode))
295                 return fd_copy_directory(AT_FDCWD, from, &st, AT_FDCWD, to, st.st_dev, merge);
296         else if (S_ISLNK(st.st_mode))
297                 return fd_copy_symlink(AT_FDCWD, from, &st, AT_FDCWD, to);
298         else if (S_ISFIFO(st.st_mode))
299                 return fd_copy_fifo(AT_FDCWD, from, &st, AT_FDCWD, to);
300         else if (S_ISBLK(st.st_mode) || S_ISCHR(st.st_mode))
301                 return fd_copy_node(AT_FDCWD, from, &st, AT_FDCWD, to);
302         else
303                 return -ENOTSUP;
304 }
305
306 int copy_tree_fd(int dirfd, const char *to, bool merge) {
307
308         struct stat st;
309
310         assert(dirfd >= 0);
311         assert(to);
312
313         if (fstat(dirfd, &st) < 0)
314                 return -errno;
315
316         if (!S_ISDIR(st.st_mode))
317                 return -ENOTDIR;
318
319         return fd_copy_directory(dirfd, NULL, &st, AT_FDCWD, to, st.st_dev, merge);
320 }
321
322 int copy_file_fd(const char *from, int fdt) {
323         _cleanup_close_ int fdf = -1;
324
325         assert(from);
326         assert(fdt >= 0);
327
328         fdf = open(from, O_RDONLY|O_CLOEXEC|O_NOCTTY);
329         if (fdf < 0)
330                 return -errno;
331
332         return copy_bytes(fdf, fdt, (off_t) -1);
333 }
334
335 int copy_file(const char *from, const char *to, int flags, mode_t mode) {
336         int fdt, r;
337
338         assert(from);
339         assert(to);
340
341         fdt = open(to, flags|O_WRONLY|O_CREAT|O_CLOEXEC|O_NOCTTY, mode);
342         if (fdt < 0)
343                 return -errno;
344
345         r = copy_file_fd(from, fdt);
346         if (r < 0) {
347                 close(fdt);
348                 unlink(to);
349                 return r;
350         }
351
352         if (close(fdt) < 0) {
353                 unlink_noerrno(to);
354                 return -errno;
355         }
356
357         return 0;
358 }