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