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