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