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