diff options
Diffstat (limited to 'src/fcopy')
-rw-r--r-- | src/fcopy/main.c | 530 |
1 files changed, 272 insertions, 258 deletions
diff --git a/src/fcopy/main.c b/src/fcopy/main.c index 43fc8fc99..089152efc 100644 --- a/src/fcopy/main.c +++ b/src/fcopy/main.c | |||
@@ -21,6 +21,7 @@ | |||
21 | #include "../include/common.h" | 21 | #include "../include/common.h" |
22 | #include <fcntl.h> | 22 | #include <fcntl.h> |
23 | #include <ftw.h> | 23 | #include <ftw.h> |
24 | #include <errno.h> | ||
24 | 25 | ||
25 | 26 | ||
26 | #define COPY_LIMIT (500 * 1024 *1024) | 27 | #define COPY_LIMIT (500 * 1024 *1024) |
@@ -34,245 +35,244 @@ static char *inpath = NULL; | |||
34 | 35 | ||
35 | // modified version of the function from util.c | 36 | // modified version of the function from util.c |
36 | static void copy_file(const char *srcname, const char *destname, mode_t mode, uid_t uid, gid_t gid) { | 37 | static void copy_file(const char *srcname, const char *destname, mode_t mode, uid_t uid, gid_t gid) { |
37 | assert(srcname); | 38 | assert(srcname); |
38 | assert(destname); | 39 | assert(destname); |
39 | mode &= 07777; | 40 | mode &= 07777; |
40 | 41 | ||
41 | // open source | 42 | // open source |
42 | int src = open(srcname, O_RDONLY); | 43 | int src = open(srcname, O_RDONLY); |
43 | if (src < 0) { | 44 | if (src < 0) { |
44 | fprintf(stderr, "Warning fcopy: cannot open %s, file not copied\n", srcname); | 45 | fprintf(stderr, "Warning fcopy: cannot open %s, file not copied\n", srcname); |
45 | return; | 46 | return; |
46 | } | 47 | } |
47 | 48 | ||
48 | // open destination | 49 | // open destination |
49 | int dst = open(destname, O_CREAT|O_WRONLY|O_TRUNC, 0755); | 50 | int dst = open(destname, O_CREAT|O_WRONLY|O_TRUNC, 0755); |
50 | if (dst < 0) { | 51 | if (dst < 0) { |
51 | fprintf(stderr, "Warning fcopy: cannot open %s, file not copied\n", destname); | 52 | fprintf(stderr, "Warning fcopy: cannot open %s, file not copied\n", destname); |
52 | close(src); | 53 | close(src); |
53 | return; | 54 | return; |
54 | } | 55 | } |
55 | 56 | ||
56 | // copy | 57 | // copy |
57 | ssize_t len; | 58 | ssize_t len; |
58 | static const int BUFLEN = 1024; | 59 | static const int BUFLEN = 1024; |
59 | unsigned char buf[BUFLEN]; | 60 | unsigned char buf[BUFLEN]; |
60 | while ((len = read(src, buf, BUFLEN)) > 0) { | 61 | while ((len = read(src, buf, BUFLEN)) > 0) { |
61 | int done = 0; | 62 | int done = 0; |
62 | while (done != len) { | 63 | while (done != len) { |
63 | int rv = write(dst, buf + done, len - done); | 64 | int rv = write(dst, buf + done, len - done); |
64 | if (rv == -1) | 65 | if (rv == -1) |
65 | goto errexit; | 66 | goto errexit; |
66 | done += rv; | 67 | done += rv; |
67 | } | 68 | } |
68 | } | 69 | } |
69 | fflush(0); | 70 | fflush(0); |
70 | 71 | ||
71 | if (fchown(dst, uid, gid) == -1) | 72 | if (fchown(dst, uid, gid) == -1) |
72 | goto errexit; | 73 | goto errexit; |
73 | if (fchmod(dst, mode) == -1) | 74 | if (fchmod(dst, mode) == -1) |
74 | goto errexit; | 75 | goto errexit; |
75 | 76 | ||
76 | close(src); | 77 | close(src); |
77 | close(dst); | 78 | close(dst); |
78 | 79 | ||
79 | return; | 80 | return; |
80 | 81 | ||
81 | errexit: | 82 | errexit: |
82 | close(src); | 83 | close(src); |
83 | close(dst); | 84 | close(dst); |
84 | unlink(destname); | 85 | unlink(destname); |
85 | fprintf(stderr, "Warning fcopy: cannot copy %s\n", destname); | 86 | fprintf(stderr, "Warning fcopy: cannot copy %s\n", destname); |
86 | } | 87 | } |
87 | 88 | ||
88 | 89 | ||
89 | 90 | ||
90 | // modified version of the function in firejail/util.c | 91 | // modified version of the function in firejail/util.c |
91 | static void mkdir_attr(const char *fname, mode_t mode, uid_t uid, gid_t gid) { | 92 | static void mkdir_attr(const char *fname, mode_t mode, uid_t uid, gid_t gid) { |
92 | assert(fname); | 93 | assert(fname); |
93 | mode &= 07777; | 94 | mode &= 07777; |
94 | 95 | ||
95 | if (mkdir(fname, mode) == -1 || | 96 | if (mkdir(fname, mode) == -1 || |
96 | chmod(fname, mode) == -1) { | 97 | chmod(fname, mode) == -1) { |
97 | fprintf(stderr, "Error fcopy: failed to create %s directory\n", fname); | 98 | fprintf(stderr, "Error fcopy: failed to create %s directory\n", fname); |
98 | errExit("mkdir/chmod"); | 99 | errExit("mkdir/chmod"); |
99 | } | 100 | } |
100 | if (chown(fname, uid, gid)) | 101 | if (chown(fname, uid, gid)) |
101 | fprintf(stderr, "Warning fcopy: failed to change ownership of %s\n", fname); | 102 | fprintf(stderr, "Warning fcopy: failed to change ownership of %s\n", fname); |
102 | } | 103 | } |
103 | 104 | ||
104 | void copy_link(const char *target, const char *linkpath, mode_t mode, uid_t uid, gid_t gid) { | 105 | void copy_link(const char *target, const char *linkpath, mode_t mode, uid_t uid, gid_t gid) { |
105 | (void) mode; | 106 | (void) mode; |
106 | (void) uid; | 107 | (void) uid; |
107 | (void) gid; | 108 | (void) gid; |
108 | char *rp = realpath(target, NULL); | 109 | char *rp = realpath(target, NULL); |
109 | if (rp) { | 110 | if (rp) { |
110 | if (symlink(rp, linkpath) == -1) | 111 | if (symlink(rp, linkpath) == -1) |
111 | goto errout; | 112 | goto errout; |
112 | free(rp); | 113 | free(rp); |
113 | } | 114 | } |
114 | else | 115 | else |
115 | goto errout; | 116 | goto errout; |
116 | 117 | ||
117 | return; | 118 | return; |
118 | errout: | 119 | errout: |
119 | fprintf(stderr, "Warning fcopy: cannot create symbolic link %s\n", target); | 120 | fprintf(stderr, "Warning fcopy: cannot create symbolic link %s\n", target); |
120 | } | 121 | } |
121 | 122 | ||
122 | static int first = 1; | 123 | static int first = 1; |
123 | static int fs_copydir(const char *infname, const struct stat *st, int ftype, struct FTW *sftw) { | 124 | static int fs_copydir(const char *infname, const struct stat *st, int ftype, struct FTW *sftw) { |
124 | (void) st; | 125 | (void) st; |
125 | (void) sftw; | 126 | (void) sftw; |
126 | assert(infname); | 127 | assert(infname); |
127 | assert(*infname != '\0'); | 128 | assert(*infname != '\0'); |
128 | assert(outpath); | 129 | assert(outpath); |
129 | assert(*outpath != '\0'); | 130 | assert(*outpath != '\0'); |
130 | assert(inpath); | 131 | assert(inpath); |
131 | 132 | ||
132 | // check size limit | 133 | // check size limit |
133 | if (size_limit_reached) | 134 | if (size_limit_reached) |
134 | return 0; | 135 | return 0; |
135 | 136 | ||
136 | 137 | ||
137 | char *outfname; | 138 | char *outfname; |
138 | if (asprintf(&outfname, "%s%s", outpath, infname + strlen(inpath)) == -1) | 139 | if (asprintf(&outfname, "%s%s", outpath, infname + strlen(inpath)) == -1) |
139 | errExit("asprintf"); | 140 | errExit("asprintf"); |
140 | 141 | ||
141 | //printf("outpaht %s\n", outpath); | 142 | // don't copy it if we already have the file |
142 | //printf("inpath %s\n", inpath); | 143 | struct stat s; |
143 | //printf("infname %s\n", infname); | 144 | if (stat(outfname, &s) == 0) { |
144 | //printf("outfname %s\n\n", outfname); | 145 | if (first) |
145 | 146 | first = 0; | |
146 | // don't copy it if we already have the file | 147 | else |
147 | struct stat s; | 148 | fprintf(stderr, "Warning fcopy: skipping %s, file already present\n", infname); |
148 | if (stat(outfname, &s) == 0) { | 149 | free(outfname); |
149 | if (first) | 150 | return 0; |
150 | first = 0; | 151 | } |
151 | else | 152 | |
152 | fprintf(stderr, "Warning fcopy: skipping %s, file already present\n", infname); | 153 | // extract mode and ownership |
153 | free(outfname); | 154 | if (stat(infname, &s) != 0) { |
154 | return 0; | 155 | fprintf(stderr, "Warning fcopy: skipping %s, cannot find inode\n", infname); |
155 | } | 156 | free(outfname); |
156 | 157 | return 0; | |
157 | // extract mode and ownership | 158 | } |
158 | if (stat(infname, &s) != 0) { | 159 | uid_t uid = s.st_uid; |
159 | fprintf(stderr, "Warning fcopy: skipping %s, cannot find inode\n", infname); | 160 | gid_t gid = s.st_gid; |
160 | free(outfname); | 161 | mode_t mode = s.st_mode; |
161 | return 0; | 162 | |
162 | } | 163 | // recalculate size |
163 | uid_t uid = s.st_uid; | 164 | if ((s.st_size + size_cnt) > COPY_LIMIT) { |
164 | gid_t gid = s.st_gid; | 165 | fprintf(stderr, "Error fcopy: size limit of %dMB reached\n", (COPY_LIMIT / 1024) / 1024); |
165 | mode_t mode = s.st_mode; | 166 | size_limit_reached = 1; |
166 | 167 | free(outfname); | |
167 | // recalculate size | 168 | return 0; |
168 | if ((s.st_size + size_cnt) > COPY_LIMIT) { | 169 | } |
169 | fprintf(stderr, "Error fcopy: size limit of %dMB reached\n", (COPY_LIMIT / 1024) / 1024); | 170 | |
170 | size_limit_reached = 1; | 171 | file_cnt++; |
171 | free(outfname); | 172 | size_cnt += s.st_size; |
172 | return 0; | 173 | |
173 | } | 174 | if(ftype == FTW_F) { |
174 | 175 | copy_file(infname, outfname, mode, uid, gid); | |
175 | file_cnt++; | 176 | } |
176 | size_cnt += s.st_size; | 177 | else if (ftype == FTW_D) { |
177 | 178 | mkdir_attr(outfname, mode, uid, gid); | |
178 | if(ftype == FTW_F) { | 179 | } |
179 | copy_file(infname, outfname, mode, uid, gid); | 180 | else if (ftype == FTW_SL) { |
180 | } | 181 | copy_link(infname, outfname, mode, uid, gid); |
181 | else if (ftype == FTW_D) { | 182 | } |
182 | mkdir_attr(outfname, mode, uid, gid); | 183 | |
183 | } | 184 | return(0); |
184 | else if (ftype == FTW_SL) { | ||
185 | copy_link(infname, outfname, mode, uid, gid); | ||
186 | } | ||
187 | |||
188 | return(0); | ||
189 | } | 185 | } |
190 | 186 | ||
191 | static char *check(const char *src) { | 187 | static char *check(const char *src) { |
192 | struct stat s; | 188 | struct stat s; |
193 | char *rsrc = realpath(src, NULL); | 189 | char *rsrc = realpath(src, NULL); |
194 | if (!rsrc || stat(rsrc, &s) == -1) | 190 | if (!rsrc || stat(rsrc, &s) == -1) |
195 | goto errexit; | 191 | goto errexit; |
196 | 192 | ||
197 | // check uid | 193 | // check uid |
198 | if (s.st_uid != getuid() || s.st_gid != getgid()) | 194 | if (s.st_uid != getuid() || s.st_gid != getgid()) |
199 | goto errexit; | 195 | goto errexit; |
200 | 196 | ||
201 | // dir, link, regular file | 197 | // dir, link, regular file |
202 | if (S_ISDIR(s.st_mode) || S_ISREG(s.st_mode) || S_ISLNK(s.st_mode)) | 198 | if (S_ISDIR(s.st_mode) || S_ISREG(s.st_mode) || S_ISLNK(s.st_mode)) |
203 | return rsrc; // normal exit from the function | 199 | return rsrc; // normal exit from the function |
204 | 200 | ||
205 | errexit: | 201 | errexit: |
206 | fprintf(stderr, "Error fcopy: invalid file %s\n", src); | 202 | fprintf(stderr, "Error fcopy: invalid file %s\n", src); |
207 | exit(1); | 203 | exit(1); |
208 | } | 204 | } |
209 | 205 | ||
210 | static void duplicate_dir(const char *src, const char *dest, struct stat *s) { | 206 | static void duplicate_dir(const char *src, const char *dest, struct stat *s) { |
211 | (void) s; | 207 | (void) s; |
212 | char *rsrc = check(src); | 208 | char *rsrc = check(src); |
213 | char *rdest = check(dest); | 209 | char *rdest = check(dest); |
214 | inpath = rsrc; | 210 | inpath = rsrc; |
215 | outpath = rdest; | 211 | outpath = rdest; |
216 | 212 | ||
217 | // walk | 213 | // walk |
218 | if(nftw(rsrc, fs_copydir, 1, FTW_PHYS) != 0) { | 214 | if(nftw(rsrc, fs_copydir, 1, FTW_PHYS) != 0) { |
219 | fprintf(stderr, "Error: unable to copy file\n"); | 215 | fprintf(stderr, "Error: unable to copy file\n"); |
220 | exit(1); | 216 | exit(1); |
221 | } | 217 | } |
222 | 218 | ||
223 | free(rsrc); | 219 | free(rsrc); |
224 | free(rdest); | 220 | free(rdest); |
225 | } | 221 | } |
226 | 222 | ||
227 | static void duplicate_file(const char *src, const char *dest, struct stat *s) { | 223 | static void duplicate_file(const char *src, const char *dest, struct stat *s) { |
228 | char *rsrc = check(src); | 224 | char *rsrc = check(src); // we drop the result and use the original name |
229 | char *rdest = check(dest); | 225 | char *rdest = check(dest); |
230 | uid_t uid = s->st_uid; | 226 | uid_t uid = s->st_uid; |
231 | gid_t gid = s->st_gid; | 227 | gid_t gid = s->st_gid; |
232 | mode_t mode = s->st_mode; | 228 | mode_t mode = s->st_mode; |
233 | 229 | ||
234 | // build destination file name | 230 | // build destination file name |
235 | char *name; | 231 | char *name; |
236 | char *ptr = strrchr(rsrc, '/'); | 232 | char *ptr = strrchr(src, '/'); |
237 | ptr++; | 233 | ptr++; |
238 | if (asprintf(&name, "%s/%s", rdest, ptr) == -1) | 234 | if (asprintf(&name, "%s/%s", rdest, ptr) == -1) |
239 | errExit("asprintf"); | 235 | errExit("asprintf"); |
240 | 236 | ||
241 | // copy | 237 | // copy |
242 | copy_file(rsrc, name, mode, uid, gid); | 238 | copy_file(rsrc, name, mode, uid, gid); |
243 | 239 | ||
244 | free(name); | 240 | free(name); |
245 | free(rsrc); | 241 | free(rsrc); |
246 | free(rdest); | 242 | free(rdest); |
247 | } | 243 | } |
248 | 244 | ||
249 | static void duplicate_link(const char *src, const char *dest, struct stat *s) { | 245 | static void duplicate_link(const char *src, const char *dest, struct stat *s) { |
250 | char *rsrc = check(src); // we drop the result and use the original name | 246 | char *rsrc = check(src); // we drop the result and use the original name |
251 | char *rdest = check(dest); | 247 | char *rdest = check(dest); |
252 | uid_t uid = s->st_uid; | 248 | uid_t uid = s->st_uid; |
253 | gid_t gid = s->st_gid; | 249 | gid_t gid = s->st_gid; |
254 | mode_t mode = s->st_mode; | 250 | mode_t mode = s->st_mode; |
255 | 251 | ||
256 | // build destination file name | 252 | // build destination file name |
257 | char *name; | 253 | char *name; |
258 | // char *ptr = strrchr(rsrc, '/'); | 254 | // char *ptr = strrchr(rsrc, '/'); |
259 | char *ptr = strrchr(src, '/'); | 255 | char *ptr = strrchr(src, '/'); |
260 | ptr++; | 256 | ptr++; |
261 | if (asprintf(&name, "%s/%s", rdest, ptr) == -1) | 257 | if (asprintf(&name, "%s/%s", rdest, ptr) == -1) |
262 | errExit("asprintf"); | 258 | errExit("asprintf"); |
263 | 259 | ||
264 | // copy | 260 | // copy |
265 | copy_link(rsrc, name, mode, uid, gid); | 261 | copy_link(rsrc, name, mode, uid, gid); |
266 | 262 | ||
267 | free(name); | 263 | free(name); |
268 | free(rsrc); | 264 | free(rsrc); |
269 | free(rdest); | 265 | free(rdest); |
270 | } | 266 | } |
271 | 267 | ||
272 | static void usage(void) { | 268 | static void usage(void) { |
273 | printf("Usage: fcopy src dest\n"); | 269 | fputs("Usage: fcopy [--follow-link] src dest\n" |
274 | printf("Copy src file in dest directory. If src is a directory, copy all the files in\n"); | 270 | "\n" |
275 | printf("src recoursively. If the destination directory does not exist, it will be created.\n"); | 271 | "Copy SRC to DEST/SRC. SRC may be a file, directory, or symbolic link.\n" |
272 | "If SRC is a directory it is copied recursively. If it is a symlink,\n" | ||
273 | "the link itself is duplicated, unless --follow-link is given,\n" | ||
274 | "in which case the destination of the link is copied.\n" | ||
275 | "DEST must already exist and must be a directory.\n", stderr); | ||
276 | } | 276 | } |
277 | 277 | ||
278 | int main(int argc, char **argv) { | 278 | int main(int argc, char **argv) { |
@@ -285,56 +285,70 @@ for (i = 0; i < argc; i++) | |||
285 | printf("\n"); | 285 | printf("\n"); |
286 | } | 286 | } |
287 | #endif | 287 | #endif |
288 | if (argc != 3) { | 288 | char *src; |
289 | fprintf(stderr, "Error fcopy: files missing\n"); | 289 | char *dest; |
290 | usage(); | 290 | int follow_link; |
291 | exit(1); | 291 | |
292 | } | 292 | if (argc == 3) { |
293 | 293 | src = argv[1]; | |
294 | // check the two files; remove ending / | 294 | dest = argv[2]; |
295 | char *src = argv[1]; | 295 | follow_link = 0; |
296 | int len = strlen(src); | 296 | } |
297 | if (src[len - 1] == '/') | 297 | else if (argc == 4 && !strcmp(argv[1], "--follow-link")) { |
298 | src[len - 1] = '\0'; | 298 | src = argv[2]; |
299 | if (strcspn(src, "\\*&!?\"'<>%^(){}[];,") != (size_t)len) { | 299 | dest = argv[3]; |
300 | fprintf(stderr, "Error fcopy: invalid file name %s\n", src); | 300 | follow_link = 1; |
301 | exit(1); | 301 | } |
302 | } | 302 | else { |
303 | 303 | usage(); | |
304 | char *dest = argv[2]; | 304 | exit(1); |
305 | len = strlen(dest); | 305 | } |
306 | if (dest[len - 1] == '/') | 306 | |
307 | dest[len - 1] = '\0'; | 307 | // check the two files; remove ending / |
308 | if (strcspn(dest, "\\*&!?\"'<>%^(){}[];,~") != (size_t)len) { | 308 | int len = strlen(src); |
309 | fprintf(stderr, "Error fcopy: invalid file name %s\n", dest); | 309 | if (src[len - 1] == '/') |
310 | exit(1); | 310 | src[len - 1] = '\0'; |
311 | } | 311 | if (strcspn(src, "\\*&!?\"'<>%^(){}[];,") != (size_t)len) { |
312 | 312 | fprintf(stderr, "Error fcopy: invalid source file name %s\n", src); | |
313 | 313 | exit(1); | |
314 | // the destination should be a directory; | 314 | } |
315 | struct stat s; | 315 | |
316 | if (stat(dest, &s) == -1 || | 316 | len = strlen(dest); |
317 | !S_ISDIR(s.st_mode)) { | 317 | if (dest[len - 1] == '/') |
318 | fprintf(stderr, "Error fcopy: invalid destination directory\n"); | 318 | dest[len - 1] = '\0'; |
319 | exit(1); | 319 | if (strcspn(dest, "\\*&!?\"'<>%^(){}[];,~") != (size_t)len) { |
320 | } | 320 | fprintf(stderr, "Error fcopy: invalid dest file name %s\n", dest); |
321 | 321 | exit(1); | |
322 | // copy files | 322 | } |
323 | if (lstat(src, &s) == -1) { | 323 | |
324 | fprintf(stderr, "Error fcopy: cannot find source file\n"); | 324 | |
325 | exit(1); | 325 | // the destination should be a directory; |
326 | } | 326 | struct stat s; |
327 | 327 | if (stat(dest, &s) == -1) { | |
328 | if (S_ISDIR(s.st_mode)) | 328 | fprintf(stderr, "Error fcopy: dest dir %s: %s\n", dest, strerror(errno)); |
329 | duplicate_dir(src, dest, &s); | 329 | exit(1); |
330 | else if (S_ISREG(s.st_mode)) | 330 | } |
331 | duplicate_file(src, dest, &s); | 331 | if (!S_ISDIR(s.st_mode)) { |
332 | else if (S_ISLNK(s.st_mode)) | 332 | fprintf(stderr, "Error fcopy: dest %s is not a directory\n", dest); |
333 | duplicate_link(src, dest, &s); | 333 | exit(1); |
334 | else { | 334 | } |
335 | fprintf(stderr, "Error fcopy: source file unsupported\n"); | 335 | |
336 | exit(1); | 336 | // copy files |
337 | } | 337 | if ((follow_link ? stat : lstat)(src, &s) == -1) { |
338 | 338 | fprintf(stderr, "Error fcopy: src %s: %s\n", src, strerror(errno)); | |
339 | return 0; | 339 | exit(1); |
340 | } | ||
341 | |||
342 | if (S_ISDIR(s.st_mode)) | ||
343 | duplicate_dir(src, dest, &s); | ||
344 | else if (S_ISREG(s.st_mode)) | ||
345 | duplicate_file(src, dest, &s); | ||
346 | else if (S_ISLNK(s.st_mode)) | ||
347 | duplicate_link(src, dest, &s); | ||
348 | else { | ||
349 | fprintf(stderr, "Error fcopy: src %s is an unsupported type of file\n", src); | ||
350 | exit(1); | ||
351 | } | ||
352 | |||
353 | return 0; | ||
340 | } | 354 | } |