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