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