chiark / gitweb /
tmpfiles: make sure "C" doesn't copy anything if the destination already exists
[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         if (created) {
183                 if (fchown(fdt, st->st_uid, st->st_gid) < 0)
184                         r = -errno;
185
186                 if (fchmod(fdt, st->st_mode & 07777) < 0)
187                         r = -errno;
188         }
189
190         r = 0;
191         FOREACH_DIRENT(de, d, return -errno) {
192                 struct stat buf;
193                 int q;
194
195                 if (fstatat(dirfd(d), de->d_name, &buf, AT_SYMLINK_NOFOLLOW) < 0) {
196                         r = -errno;
197                         continue;
198                 }
199
200                 if (buf.st_dev != original_device)
201                         continue;
202
203                 if (S_ISREG(buf.st_mode))
204                         q = fd_copy_regular(dirfd(d), de->d_name, &buf, fdt, de->d_name);
205                 else if (S_ISDIR(buf.st_mode))
206                         q = fd_copy_directory(dirfd(d), de->d_name, &buf, fdt, de->d_name, original_device, merge);
207                 else if (S_ISLNK(buf.st_mode))
208                         q = fd_copy_symlink(dirfd(d), de->d_name, &buf, fdt, de->d_name);
209                 else if (S_ISFIFO(buf.st_mode))
210                         q = fd_copy_fifo(dirfd(d), de->d_name, &buf, fdt, de->d_name);
211                 else if (S_ISBLK(buf.st_mode) || S_ISCHR(buf.st_mode))
212                         q = fd_copy_node(dirfd(d), de->d_name, &buf, fdt, de->d_name);
213                 else
214                         q = -ENOTSUP;
215
216                 if (q == -EEXIST && merge)
217                         q = 0;
218
219                 if (q < 0)
220                         r = q;
221         }
222
223         return r;
224 }
225
226 int copy_tree(const char *from, const char *to, bool merge) {
227         struct stat st;
228
229         assert(from);
230         assert(to);
231
232         if (lstat(from, &st) < 0)
233                 return -errno;
234
235         if (S_ISREG(st.st_mode))
236                 return fd_copy_regular(AT_FDCWD, from, &st, AT_FDCWD, to);
237         else if (S_ISDIR(st.st_mode))
238                 return fd_copy_directory(AT_FDCWD, from, &st, AT_FDCWD, to, st.st_dev, merge);
239         else if (S_ISLNK(st.st_mode))
240                 return fd_copy_symlink(AT_FDCWD, from, &st, AT_FDCWD, to);
241         else if (S_ISFIFO(st.st_mode))
242                 return fd_copy_fifo(AT_FDCWD, from, &st, AT_FDCWD, to);
243         else if (S_ISBLK(st.st_mode) || S_ISCHR(st.st_mode))
244                 return fd_copy_node(AT_FDCWD, from, &st, AT_FDCWD, to);
245         else
246                 return -ENOTSUP;
247 }
248
249 int copy_file(const char *from, const char *to, int flags, mode_t mode) {
250         _cleanup_close_ int fdf = -1, fdt = -1;
251         int r;
252
253         assert(from);
254         assert(to);
255
256         fdf = open(from, O_RDONLY|O_CLOEXEC|O_NOCTTY);
257         if (fdf < 0)
258                 return -errno;
259
260         fdt = open(to, flags|O_WRONLY|O_CREAT|O_CLOEXEC|O_NOCTTY, mode);
261         if (fdt < 0)
262                 return -errno;
263
264         r = copy_bytes(fdf, fdt);
265         if (r < 0) {
266                 unlink(to);
267                 return r;
268         }
269
270         r = close(fdt);
271         fdt = -1;
272
273         if (r < 0) {
274                 r = -errno;
275                 unlink(to);
276                 return r;
277         }
278
279         return 0;
280 }