Annotation of src/usr.bin/rsync/flist.c, Revision 1.2
1.1 benno 1: /* $Id$ */
2: /*
3: * Copyright (c) 2019 Kristaps Dzonsons <kristaps@bsd.lv>
4: *
5: * Permission to use, copy, modify, and distribute this software for any
6: * purpose with or without fee is hereby granted, provided that the above
7: * copyright notice and this permission notice appear in all copies.
8: *
9: * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10: * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11: * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12: * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13: * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14: * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15: * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16: */
17: #include <sys/param.h>
18: #include <sys/stat.h>
19:
20: #include <assert.h>
21: #include <errno.h>
22: #include <fcntl.h>
23: #include <inttypes.h>
24: #include <fts.h>
25: #include <search.h>
26: #include <stdio.h>
27: #include <stdlib.h>
28: #include <string.h>
29: #include <unistd.h>
30:
31: #include "extern.h"
32:
33: /*
34: * We allocate our file list in chunk sizes so as not to do it one by
35: * one.
36: * Preferrably we get one or two allocation.
37: */
38: #define FLIST_CHUNK_SIZE (1024)
39:
40: /*
41: * These flags are part of the rsync protocol.
42: * They are sent as the first byte for a file transmission and encode
43: * information that affects subsequent transmissions.
44: */
45: #define FLIST_MODE_SAME 0x0002 /* mode is repeat */
46: #define FLIST_NAME_SAME 0x0020 /* name is repeat */
47: #define FLIST_NAME_LONG 0x0040 /* name >255 bytes */
48: #define FLIST_TIME_SAME 0x0080 /* time is repeat */
49:
50: /*
51: * Requied way to sort a filename list.
52: */
53: static int
54: flist_cmp(const void *p1, const void *p2)
55: {
56: const struct flist *f1 = p1, *f2 = p2;
57:
58: return strcmp(f1->wpath, f2->wpath);
59: }
60:
61: /*
62: * Deduplicate our file list (which may be zero-length).
63: * Returns zero on failure, non-zero on success.
64: */
65: static int
66: flist_dedupe(struct sess *sess, struct flist **fl, size_t *sz)
67: {
1.2 ! benno 68: size_t i, j;
1.1 benno 69: struct flist *new;
70: struct flist *f, *fnext;
71:
72: if (0 == *sz)
73: return 1;
74:
75: /* Create a new buffer, "new", and copy. */
76:
77: new = calloc(*sz, sizeof(struct flist));
78: if (NULL == new) {
79: ERR(sess, "calloc");
80: return 0;
81: }
82:
83: for (i = j = 0; i < *sz - 1; i++) {
84: f = &(*fl)[i];
85: fnext = &(*fl)[i + 1];
86:
87: if (strcmp(f->wpath, fnext->wpath)) {
88: new[j++] = *f;
89: continue;
90: }
91:
92: /*
93: * Our working (destination) paths are the same.
94: * If the actual file is the same (as given on the
95: * command-line), then we can just discard the first.
96: * Otherwise, we need to bail out: it means we have two
97: * different files with the relative path on the
98: * destination side.
99: */
100:
101: if (0 == strcmp(f->path, fnext->path)) {
102: new[j++] = *f;
103: i++;
104: WARNX(sess, "%s: duplicate path: %s",
105: f->wpath, f->path);
106: free(fnext->path);
107: free(fnext->link);
108: fnext->path = fnext->link = NULL;
109: continue;
110: }
111:
112: ERRX(sess, "%s: duplicate working path for "
113: "possibly different file: %s, %s",
114: f->wpath, f->path, fnext->path);
115: free(new);
116: return 0;
117: }
118:
119: /* Don't forget the last entry. */
120:
121: if (i == *sz - 1)
122: new[j++] = (*fl)[i];
123:
124: /*
125: * Reassign to the deduplicated array.
126: * If we started out with *sz > 0, which we check for at the
127: * beginning, then we'll always continue having *sz > 0.
128: */
129:
130: free(*fl);
131: *fl = new;
132: *sz = j;
133: assert(*sz);
134: return 1;
135: }
136:
137: /*
138: * We're now going to find our top-level directories.
139: * This only applies to recursive mode.
140: * If we have the first element as the ".", then that's the "top
141: * directory" of our transfer.
142: * Otherwise, mark up all top-level directories in the set.
143: */
144: static void
145: flist_topdirs(struct sess *sess, struct flist *fl, size_t flsz)
146: {
1.2 ! benno 147: size_t i;
1.1 benno 148: const char *cp;
149:
150: if ( ! sess->opts->recursive)
151: return;
152:
153: if (flsz && strcmp(fl[0].wpath, ".")) {
154: for (i = 0; i < flsz; i++) {
155: if ( ! S_ISDIR(fl[i].st.mode))
156: continue;
157: cp = strchr(fl[i].wpath, '/');
1.2 ! benno 158: if (NULL != cp && '\0' != cp[1])
1.1 benno 159: continue;
160: fl[i].st.flags |= FLSTAT_TOP_DIR;
161: LOG4(sess, "%s: top-level", fl[i].wpath);
162: }
163: } else if (flsz) {
164: fl[0].st.flags |= FLSTAT_TOP_DIR;
165: LOG4(sess, "%s: top-level", fl[0].wpath);
166: }
167: }
168:
169: /*
170: * Filter through the fts() file information.
171: * We want directories (pre-order), regular files, and symlinks.
172: * Everything else is skipped and possibly warned about.
173: * Return zero to skip, non-zero to examine.
174: */
175: static int
176: flist_fts_check(struct sess *sess, FTSENT *ent)
177: {
178:
179: if (FTS_F == ent->fts_info ||
180: FTS_D == ent->fts_info ||
181: FTS_SL == ent->fts_info ||
182: FTS_SLNONE == ent->fts_info)
183: return 1;
184:
185: if (FTS_DC == ent->fts_info) {
186: WARNX(sess, "%s: directory cycle", ent->fts_path);
187: } else if (FTS_DNR == ent->fts_info) {
188: errno = ent->fts_errno;
189: WARN(sess, "%s: unreadable directory", ent->fts_path);
190: } else if (FTS_DOT == ent->fts_info) {
191: WARNX(sess, "%s: skipping dot-file", ent->fts_path);
192: } else if (FTS_ERR == ent->fts_info) {
193: errno = ent->fts_errno;
194: WARN(sess, "%s", ent->fts_path);
195: } else if (FTS_DEFAULT == ent->fts_info) {
196: WARNX(sess, "%s: skipping special", ent->fts_path);
197: } else if (FTS_NS == ent->fts_info) {
198: errno = ent->fts_errno;
199: WARN(sess, "%s: could not stat", ent->fts_path);
200: }
201:
202: return 0;
203: }
204:
205: /*
206: * Copy necessary elements in "st" into the fields of "f".
207: */
208: static void
209: flist_copy_stat(struct flist *f, const struct stat *st)
210: {
211:
212: f->st.mode = st->st_mode;
213: f->st.uid = st->st_uid;
214: f->st.gid = st->st_gid;
215: f->st.size = st->st_size;
216: f->st.mtime = st->st_mtime;
217: }
218:
219: void
220: flist_free(struct flist *f, size_t sz)
221: {
222: size_t i;
223:
224: if (NULL == f)
225: return;
226:
227: for (i = 0; i < sz; i++) {
228: free(f[i].path);
229: free(f[i].link);
230: }
231: free(f);
232: }
233:
234: /*
235: * Serialise our file list (which may be zero-length) to the wire.
236: * Makes sure that the receiver isn't going to block on sending us
237: * return messages on the log channel.
238: * Return zero on failure, non-zero on success.
239: */
240: int
1.2 ! benno 241: flist_send(struct sess *sess, int fdin,
1.1 benno 242: int fdout, const struct flist *fl, size_t flsz)
243: {
244: size_t i, fnlen;
245: uint8_t flag;
246: const struct flist *f;
247: const char *fn;
248:
249: /* Double-check that we've no pending multiplexed data. */
250:
251: LOG2(sess, "sending file metadata list: %zu", flsz);
252:
253: for (i = 0; i < flsz; i++) {
254: f = &fl[i];
255: fn = f->wpath;
256: fnlen = strlen(f->wpath);
257: assert(fnlen > 0);
258:
1.2 ! benno 259: /*
1.1 benno 260: * If applicable, unclog the read buffer.
261: * This happens when the receiver has a lot of log
262: * messages and all we're doing is sending our file list
263: * without checking for messages.
264: */
265:
266: if (sess->mplex_reads &&
267: io_read_check(sess, fdin) &&
268: ! io_read_flush(sess, fdin)) {
269: ERRX1(sess, "io_read_flush");
270: return 0;
271: }
272:
273: /*
274: * For ease, make all of our filenames be "long"
275: * regardless their actual length.
276: * This also makes sure that we don't transmit a zero
277: * byte unintentionally.
278: */
279:
280: flag = FLIST_NAME_LONG;
281:
282: LOG3(sess, "%s: sending file metadata: "
283: "size %jd, mtime %jd, mode %o",
284: fn, (intmax_t)f->st.size,
285: (intmax_t)f->st.mtime, f->st.mode);
286:
287: /* Now write to the wire. */
288: /* FIXME: buffer this. */
289:
290: if ( ! io_write_byte(sess, fdout, flag)) {
291: ERRX1(sess, "io_write_byte");
292: return 0;
293: } else if ( ! io_write_int(sess, fdout, fnlen)) {
294: ERRX1(sess, "io_write_int");
295: return 0;
296: } else if ( ! io_write_buf(sess, fdout, fn, fnlen)) {
297: ERRX1(sess, "io_write_buf");
298: return 0;
299: } else if ( ! io_write_long(sess, fdout, f->st.size)) {
300: ERRX1(sess, "io_write_long");
301: return 0;
302: } else if ( ! io_write_int(sess, fdout, f->st.mtime)) {
303: ERRX1(sess, "io_write_int");
304: return 0;
305: } else if ( ! io_write_int(sess, fdout, f->st.mode)) {
306: ERRX1(sess, "io_write_int");
307: return 0;
308: }
309:
310: /* Optional link information. */
311:
312: if (S_ISLNK(f->st.mode) &&
313: sess->opts->preserve_links) {
314: fn = f->link;
315: fnlen = strlen(f->link);
316: if ( ! io_write_int(sess, fdout, fnlen)) {
317: ERRX1(sess, "io_write_int");
318: return 0;
319: }
320: if ( ! io_write_buf(sess, fdout, fn, fnlen)) {
321: ERRX1(sess, "io_write_int");
322: return 0;
323: }
324: }
325:
326: if (S_ISREG(f->st.mode))
327: sess->total_size += f->st.size;
328: }
329:
330: if ( ! io_write_byte(sess, fdout, 0)) {
331: ERRX1(sess, "io_write_byte");
332: return 0;
333: }
334:
335: return 1;
336: }
337:
338: /*
339: * Read the filename of a file list.
340: * This is the most expensive part of the file list transfer, so a lot
341: * of attention has gone into transmitting as little as possible.
342: * Micro-optimisation, but whatever.
343: * Fills in "f" with the full path on success.
344: * Returns zero on failure, non-zero on success.
345: */
346: static int
347: flist_recv_name(struct sess *sess, int fd,
348: struct flist *f, uint8_t flags, char last[MAXPATHLEN])
349: {
350: uint8_t bval;
351: size_t partial = 0;
352: size_t pathlen = 0, len;
353:
354: /*
355: * Read our filename.
356: * If we have FLIST_NAME_SAME, we inherit some of the last
357: * transmitted name.
358: * If we have FLIST_NAME_LONG, then the string length is greater
359: * than byte-size.
360: */
361:
362: if (FLIST_NAME_SAME & flags) {
363: if ( ! io_read_byte(sess, fd, &bval)) {
364: ERRX1(sess, "io_read_byte");
365: return 0;
366: }
367: partial = bval;
368: }
369:
370: /* Get the (possibly-remaining) filename length. */
371:
372: if (FLIST_NAME_LONG & flags) {
373: if ( ! io_read_size(sess, fd, &pathlen)) {
374: ERRX1(sess, "io_read_size");
375: return 0;
376: }
377: } else {
378: if ( ! io_read_byte(sess, fd, &bval)) {
379: ERRX1(sess, "io_read_byte");
380: return 0;
381: }
382: pathlen = bval;
383: }
384:
385: /* Allocate our full filename length. */
386: /* FIXME: maximum pathname length. */
387:
388: if (0 == (len = pathlen + partial)) {
389: ERRX(sess, "security violation: "
390: "zero-length pathname");
391: return 0;
392: }
393:
394: if (NULL == (f->path = malloc(len + 1))) {
395: ERR(sess, "malloc");
396: return 0;
397: }
398: f->path[len] = '\0';
399:
400: if (FLIST_NAME_SAME & flags)
401: memcpy(f->path, last, partial);
402:
403: if ( ! io_read_buf(sess, fd, f->path + partial, pathlen)) {
404: ERRX1(sess, "io_read_buf");
405: return 0;
406: }
407:
408: if ('/' == f->path[0]) {
409: ERRX(sess, "security violation: "
410: "absolute pathname: %s", f->path);
411: return 0;
412: }
413:
414: if (NULL != strstr(f->path, "/../") ||
415: (len > 2 && 0 == strcmp(f->path + len - 3, "/..")) ||
416: (len > 2 && 0 == strncmp(f->path, "../", 3)) ||
417: 0 == strcmp(f->path, "..")) {
418: ERRX(sess, "%s: security violation: "
419: "backtracking pathname", f->path);
420: return 0;
421: }
422:
423: /* Record our last path and construct our filename. */
424:
425: strlcpy(last, f->path, MAXPATHLEN);
426: f->wpath = f->path;
427: return 1;
428: }
429:
430: /*
431: * Reallocate a file list in chunks of FLIST_CHUNK_SIZE;
432: * Returns zero on failure, non-zero on success.
433: */
434: static int
435: flist_realloc(struct sess *sess,
436: struct flist **fl, size_t *sz, size_t *max)
437: {
438: void *pp;
439:
440: if (*sz + 1 <= *max) {
441: (*sz)++;
442: return 1;
443: }
444:
445: pp = recallocarray(*fl, *max,
446: *max + FLIST_CHUNK_SIZE, sizeof(struct flist));
447: if (NULL == pp) {
448: ERR(sess, "recallocarray");
449: return 0;
450: }
451: *fl = pp;
452: *max += FLIST_CHUNK_SIZE;
453: (*sz)++;
454: return 1;
455: }
456:
457: /*
458: * Copy a regular or symbolic link file "path" into "f".
459: * This handles the correct path creation and symbolic linking.
460: * Returns zero on failure, non-zero on success.
461: */
462: static int
463: flist_append(struct sess *sess, struct flist *f,
464: struct stat *st, const char *path)
465: {
466:
467: /*
468: * Copy the full path for local addressing and transmit
469: * only the filename part for the receiver.
470: */
471:
472: if (NULL == (f->path = strdup(path))) {
473: ERR(sess, "strdup");
474: return 0;
475: }
476:
477: if (NULL == (f->wpath = strrchr(f->path, '/')))
478: f->wpath = f->path;
479: else
480: f->wpath++;
481:
482: /*
483: * On the receiving end, we'll strip out all bits on the
484: * mode except for the file permissions.
485: * No need to warn about it here.
486: */
487:
488: flist_copy_stat(f, st);
489:
490: /* Optionally copy link information. */
491:
492: if (S_ISLNK(st->st_mode)) {
493: f->link = symlink_read(sess, f->path);
494: if (NULL == f->link) {
495: ERRX1(sess, "symlink_read");
496: return 0;
497: }
498: }
499:
500: return 1;
501: }
502:
503: /*
504: * Receive a file list from the wire, filling in length "sz" (which may
505: * possibly be zero) and list "flp" on success.
506: * Return zero on failure, non-zero on success.
507: */
508: int
509: flist_recv(struct sess *sess, int fd, struct flist **flp, size_t *sz)
510: {
511: struct flist *fl = NULL;
512: struct flist *ff;
513: const struct flist *fflast = NULL;
514: size_t flsz = 0, flmax = 0, lsz;
515: uint8_t flag;
516: char last[MAXPATHLEN];
517: uint64_t lval; /* temporary values... */
518: int32_t ival;
519:
520: last[0] = '\0';
521:
522: for (;;) {
523: if ( ! io_read_byte(sess, fd, &flag)) {
524: ERRX1(sess, "io_read_byte");
525: goto out;
526: } else if (0 == flag)
527: break;
528:
529: if ( ! flist_realloc(sess, &fl, &flsz, &flmax)) {
530: ERRX1(sess, "flist_realloc");
531: goto out;
532: }
533:
534: ff = &fl[flsz - 1];
535: fflast = flsz > 1 ? &fl[flsz - 2] : NULL;
536:
537: /* Filename first. */
538:
539: if ( ! flist_recv_name(sess, fd, ff, flag, last)) {
540: ERRX1(sess, "flist_recv_name");
541: goto out;
542: }
543:
544: /* Read the file size. */
545:
546: if ( ! io_read_ulong(sess, fd, &lval)) {
547: ERRX1(sess, "io_read_ulong");
548: goto out;
549: }
550: ff->st.size = lval;
551:
552: /* Read the modification time. */
553:
554: if ( ! (FLIST_TIME_SAME & flag)) {
555: if ( ! io_read_int(sess, fd, &ival)) {
556: ERRX1(sess, "io_read_int");
557: goto out;
558: }
559: ff->st.mtime = ival;
560: } else if (NULL == fflast) {
561: ERRX(sess, "same time without last entry");
562: goto out;
563: } else
564: ff->st.mtime = fflast->st.mtime;
565:
566: /* Read the file mode. */
567:
568: if ( ! (FLIST_MODE_SAME & flag)) {
569: if ( ! io_read_int(sess, fd, &ival)) {
570: ERRX1(sess, "io_read_int");
571: goto out;
572: }
573: ff->st.mode = ival;
574: } else if (NULL == fflast) {
575: ERRX(sess, "same mode without last entry");
576: goto out;
577: } else
578: ff->st.mode = fflast->st.mode;
579:
580: /* Optionally read the link information. */
581:
582: if (S_ISLNK(ff->st.mode) &&
583: sess->opts->preserve_links) {
584: if ( ! io_read_size(sess, fd, &lsz)) {
585: ERRX1(sess, "io_read_size");
586: goto out;
587: } else if (0 == lsz) {
588: ERRX(sess, "empty link name");
589: goto out;
590: }
591: ff->link = calloc(lsz + 1, 1);
592: if (NULL == ff->link) {
593: ERR(sess, "calloc");
594: goto out;
595: }
596: if ( ! io_read_buf(sess, fd, ff->link, lsz)) {
597: ERRX1(sess, "io_read_buf");
598: goto out;
599: }
600: }
601:
602: LOG3(sess, "%s: received file metadata: "
603: "size %jd, mtime %jd, mode %o",
604: ff->path, (intmax_t)ff->st.size,
605: (intmax_t)ff->st.mtime, ff->st.mode);
606:
607: if (S_ISREG(ff->st.mode))
608: sess->total_size += ff->st.size;
609: }
610:
611: /* Remember to order the received list. */
612:
613: LOG2(sess, "received file metadata list: %zu", flsz);
614: qsort(fl, flsz, sizeof(struct flist), flist_cmp);
615: flist_topdirs(sess, fl, flsz);
616: *sz = flsz;
617: *flp = fl;
618: return 1;
619: out:
620: flist_free(fl, flsz);
621: *sz = 0;
622: *flp = NULL;
623: return 0;
624: }
625:
626: /*
627: * Generate a flist possibly-recursively given a file root, which may
628: * also be a regular file or symlink.
629: * On success, augments the generated list in "flp" of length "sz".
630: * Returns zero on failure, non-zero on success.
631: */
632: static int
633: flist_gen_dirent(struct sess *sess, char *root,
634: struct flist **fl, size_t *sz, size_t *max)
635: {
636: char *cargv[2], *cp;
637: int rc = 0;
638: FTS *fts;
639: FTSENT *ent;
640: struct flist *f;
641: size_t flsz = 0, stripdir;
642: struct stat st;
643:
644: cargv[0] = root;
645: cargv[1] = NULL;
646:
647: /*
648: * If we're a file, then revert to the same actions we use for
649: * the non-recursive scan.
650: */
651:
652: if (-1 == lstat(root, &st)) {
653: ERR(sess, "%s: lstat", root);
654: return 0;
655: } else if (S_ISREG(st.st_mode)) {
656: if ( ! flist_realloc(sess, fl, sz, max)) {
657: ERRX1(sess, "flist_realloc");
658: return 0;
659: }
660: f = &(*fl)[(*sz) - 1];
661: assert(NULL != f);
662:
663: if ( ! flist_append(sess, f, &st, root)) {
664: ERRX1(sess, "flist_append");
665: return 0;
666: } else if (-1 == unveil(root, "r")) {
667: ERR(sess, "%s: unveil", root);
668: return 0;
669: }
670: return 1;
671: } else if (S_ISLNK(st.st_mode)) {
672: if ( ! sess->opts->preserve_links) {
673: WARNX(sess, "%s: skipping symlink", root);
674: return 1;
675: } else if ( ! flist_realloc(sess, fl, sz, max)) {
676: ERRX1(sess, "flist_realloc");
677: return 0;
678: }
679: f = &(*fl)[(*sz) - 1];
680: assert(NULL != f);
681:
682: if ( ! flist_append(sess, f, &st, root)) {
683: ERRX1(sess, "flist_append");
684: return 0;
685: } else if (-1 == unveil(root, "r")) {
686: ERR(sess, "%s: unveil", root);
687: return 0;
688: }
689: return 1;
690: } else if ( ! S_ISDIR(st.st_mode)) {
691: WARNX(sess, "%s: skipping special", root);
692: return 1;
693: }
694:
695: /*
696: * If we end with a slash, it means that we're not supposed to
697: * copy the directory part itself---only the contents.
698: * So set "stripdir" to be what we take out.
699: */
700:
701: stripdir = strlen(root);
702: assert(stripdir > 0);
703: if ('/' != root[stripdir - 1])
704: stripdir = 0;
705:
706: /*
707: * If we're not stripping anything, then see if we need to strip
708: * out the leading material in the path up to and including the
709: * last directory component.
710: */
711:
712: if (0 == stripdir)
713: if (NULL != (cp = strrchr(root, '/')))
714: stripdir = cp - root + 1;
715:
716: /*
717: * If we're recursive, then we need to take down all of the
718: * files and directory components, so use fts(3).
719: * Copying the information file-by-file into the flstat.
720: * We'll make sense of it in flist_send.
721: */
722:
723: if (NULL == (fts = fts_open(cargv, FTS_PHYSICAL, NULL))) {
724: ERR(sess, "fts_open");
725: return 0;
726: }
727:
728: errno = 0;
729: while (NULL != (ent = fts_read(fts))) {
730: if ( ! flist_fts_check(sess, ent)) {
731: errno = 0;
732: continue;
733: }
734:
735: /* We don't allow symlinks without -l. */
736:
737: assert(NULL != ent->fts_statp);
738: if (S_ISLNK(ent->fts_statp->st_mode) &&
739: ! sess->opts->preserve_links) {
740: WARNX(sess, "%s: skipping "
741: "symlink", ent->fts_path);
742: continue;
743: }
744:
745: /* Allocate a new file entry. */
746:
747: if ( ! flist_realloc(sess, fl, sz, max)) {
748: ERRX1(sess, "flist_realloc");
749: goto out;
750: }
751: flsz++;
752: f = &(*fl)[*sz - 1];
753:
754: /* Our path defaults to "." for the root. */
755:
756: if ('\0' == ent->fts_path[stripdir]) {
757: if (asprintf(&f->path, "%s.", ent->fts_path) < 0) {
758: ERR(sess, "asprintf");
759: f->path = NULL;
760: goto out;
761: }
762: } else {
763: if (NULL == (f->path = strdup(ent->fts_path))) {
764: ERR(sess, "strdup");
765: goto out;
766: }
767: }
768:
769: f->wpath = f->path + stripdir;
770: flist_copy_stat(f, ent->fts_statp);
771:
772: /* Optionally copy link information. */
773:
774: if (S_ISLNK(ent->fts_statp->st_mode)) {
775: f->link = symlink_read(sess, f->path);
776: if (NULL == f->link) {
777: ERRX1(sess, "symlink_read");
778: goto out;
779: }
780: }
781:
782: /* Reset errno for next fts_read() call. */
783: errno = 0;
784: }
785: if (errno) {
786: ERR(sess, "fts_read");
787: goto out;
788: } else if (-1 == unveil(root, "r")) {
789: ERR(sess, "%s: unveil", root);
790: goto out;
791: }
792:
793: LOG3(sess, "generated %zu filenames: %s", flsz, root);
794: rc = 1;
795: out:
796: fts_close(fts);
797: return rc;
798: }
799:
800: /*
801: * Generate a flist recursively given the array of directories (or
802: * files, symlinks, doesn't matter) specified in argv (argc >0).
803: * On success, stores the generated list in "flp" with length "sz",
804: * which may be zero.
805: * Returns zero on failure, non-zero on success.
806: */
807: static int
808: flist_gen_dirs(struct sess *sess, size_t argc,
809: char **argv, struct flist **flp, size_t *sz)
810: {
811: size_t i, max = 0;
812:
813: for (i = 0; i < argc; i++)
814: if ( ! flist_gen_dirent(sess, argv[i], flp, sz, &max))
815: break;
816:
817: if (i == argc) {
818: LOG2(sess, "recursively generated %zu filenames", *sz);
819: return 1;
820: }
821:
822: ERRX1(sess, "flist_gen_dirent");
823: flist_free(*flp, max);
824: *flp = NULL;
825: *sz = 0;
826: return 0;
827: }
828:
829: /*
830: * Generate list of files from the command-line argc (>0) and argv.
831: * On success, stores the generated list in "flp" with length "sz",
832: * which may be zero.
833: * Returns zero on failure, non-zero on success.
834: */
835: static int
836: flist_gen_files(struct sess *sess, size_t argc,
837: char **argv, struct flist **flp, size_t *sz)
838: {
839: struct flist *fl = NULL, *f;
840: size_t i, flsz = 0;
841: struct stat st;
842:
843: assert(argc);
844:
845: if (NULL == (fl = calloc(argc, sizeof(struct flist)))) {
846: ERR(sess, "calloc");
847: return 0;
848: }
849:
850: for (i = 0; i < argc; i++) {
851: if ('\0' == argv[i][0])
852: continue;
853: if (-1 == lstat(argv[i], &st)) {
854: ERR(sess, "%s: lstat", argv[i]);
855: goto out;
856: }
857:
858: /*
859: * File type checks.
860: * In non-recursive mode, we don't accept directories.
861: * We also skip symbolic links without -l.
862: * Beyond that, we only accept regular files.
863: */
864:
865: if (S_ISDIR(st.st_mode)) {
866: WARNX(sess, "%s: skipping directory", argv[i]);
867: continue;
868: } else if (S_ISLNK(st.st_mode)) {
869: if ( ! sess->opts->preserve_links) {
870: WARNX(sess, "%s: skipping "
871: "symlink", argv[i]);
872: continue;
873: }
874: } else if ( ! S_ISREG(st.st_mode)) {
875: WARNX(sess, "%s: skipping special", argv[i]);
876: continue;
877: }
878:
879: f = &fl[flsz++];
880: assert(NULL != f);
881:
882: /* Add this file to our file-system worldview. */
883:
884: if (-1 == unveil(argv[i], "r")) {
885: ERR(sess, "%s: unveil", argv[i]);
886: goto out;
887: } else if ( ! flist_append(sess, f, &st, argv[i])) {
888: ERRX1(sess, "flist_append");
889: goto out;
890: }
891: }
892:
893: LOG2(sess, "non-recursively generated %zu filenames", flsz);
894: *sz = flsz;
895: *flp = fl;
896: return 1;
897: out:
898: flist_free(fl, argc);
899: *sz = 0;
900: *flp = NULL;
901: return 0;
902: }
903:
904: /*
905: * Generate a sorted, de-duplicated list of file metadata.
906: * In non-recursive mode (the default), we use only the files we're
907: * given.
908: * Otherwise, directories are recursively examined.
909: * Returns zero on failure, non-zero on success.
910: * On success, "fl" will need to be freed with flist_free().
911: */
912: int
913: flist_gen(struct sess *sess, size_t argc,
914: char **argv, struct flist **flp, size_t *sz)
915: {
916: int rc;
917:
918: assert(argc > 0);
919: rc = sess->opts->recursive ?
920: flist_gen_dirs(sess, argc, argv, flp, sz) :
921: flist_gen_files(sess, argc, argv, flp, sz);
922:
923: /* After scanning, lock our file-system view. */
924:
925: if (-1 == unveil(NULL, NULL)) {
926: ERR(sess, "unveil");
927: return 0;
928: } else if ( ! rc)
929: return 0;
930:
931: qsort(*flp, *sz, sizeof(struct flist), flist_cmp);
932:
933: if (flist_dedupe(sess, flp, sz)) {
934: flist_topdirs(sess, *flp, *sz);
935: return 1;
936: }
937:
938: ERRX1(sess, "flist_dedupe");
939: flist_free(*flp, *sz);
940: *flp = NULL;
941: *sz = 0;
942: return 0;
943: }
944:
945: /*
946: * Generate a list of files in root to delete that are within the
947: * top-level directories stipulated by "wfl".
948: * Only handles symbolic links, directories, and regular files.
949: * Returns zero on failure (fl and flsz will be NULL and zero), non-zero
950: * on success.
951: * On success, "fl" will need to be freed with flist_free().
952: */
953: int
1.2 ! benno 954: flist_gen_dels(struct sess *sess, const char *root,
1.1 benno 955: struct flist **fl, size_t *sz,
956: const struct flist *wfl, size_t wflsz)
957: {
958: char **cargv = NULL;
959: int rc = 0, c;
960: FTS *fts = NULL;
961: FTSENT *ent;
962: struct flist *f;
963: size_t cargvs = 0, i, j, max = 0, stripdir;
964: ENTRY hent;
965: ENTRY *hentp;
1.2 ! benno 966:
1.1 benno 967: *fl = NULL;
968: *sz = 0;
969:
970: /* Only run this code when we're recursive. */
971:
972: if ( ! sess->opts->recursive)
973: return 1;
974:
1.2 ! benno 975: /*
1.1 benno 976: * Gather up all top-level directories for scanning.
977: * This is stipulated by rsync's --delete behaviour, where we
978: * only delete things in the top-level directories given on the
979: * command line.
980: */
981:
982: assert(wflsz > 0);
983: for (i = 0; i < wflsz; i++)
984: if (FLSTAT_TOP_DIR & wfl[i].st.flags)
985: cargvs++;
986: if (0 == cargvs)
987: return 1;
988:
989: if (NULL == (cargv = calloc(cargvs + 1, sizeof(char *)))) {
990: ERR(sess, "calloc");
991: return 0;
992: }
993:
994: /*
995: * If we're given just a "." as the first entry, that means
996: * we're doing a relative copy with a trailing slash.
997: * Special-case this just for the sake of simplicity.
998: * Otherwise, look through all top-levels.
999: */
1000:
1001: if (wflsz && 0 == strcmp(wfl[0].wpath, ".")) {
1002: assert(1 == cargvs);
1003: assert(S_ISDIR(wfl[0].st.mode));
1004: if (asprintf(&cargv[0], "%s/", root) < 0) {
1005: ERR(sess, "asprintf");
1006: cargv[0] = NULL;
1007: goto out;
1008: }
1009: cargv[1] = NULL;
1010: } else {
1011: for (i = j = 0; i < wflsz; i++) {
1012: if ( ! (FLSTAT_TOP_DIR & wfl[i].st.flags))
1013: continue;
1014: assert(S_ISDIR(wfl[i].st.mode));
1015: assert(strcmp(wfl[i].wpath, "."));
1.2 ! benno 1016: c = asprintf(&cargv[j],
1.1 benno 1017: "%s/%s", root, wfl[i].wpath);
1018: if (c < 0) {
1019: ERR(sess, "asprintf");
1020: cargv[j] = NULL;
1021: goto out;
1022: }
1023: LOG4(sess, "%s: will scan "
1024: "for deletions", cargv[j]);
1025: j++;
1026: }
1027: assert(j == cargvs);
1028: cargv[j] = NULL;
1029: }
1030:
1031: LOG2(sess, "delete from %zu directories", cargvs);
1032:
1033: /*
1034: * Next, use the standard hcreate(3) hashtable interface to hash
1035: * all of the files that we want to synchronise.
1036: * This way, we'll be able to determine which files we want to
1037: * delete in O(n) time instead of O(n * search) time.
1038: * Plus, we can do the scan in-band and only allocate the files
1039: * we want to delete.
1040: */
1041:
1042: if ( ! hcreate(wflsz)) {
1043: ERR(sess, "hcreate");
1044: goto out;
1045: }
1046:
1047: for (i = 0; i < wflsz; i++) {
1048: memset(&hent, 0, sizeof(ENTRY));
1049: if (NULL == (hent.key = strdup(wfl[i].wpath))) {
1050: ERR(sess, "strdup");
1051: goto out;
1052: }
1053: if (NULL == (hentp = hsearch(hent, ENTER))) {
1054: ERR(sess, "hsearch");
1055: goto out;
1056: } else if (hentp->key != hent.key) {
1057: ERRX(sess, "%s: duplicate", wfl[i].wpath);
1058: free(hent.key);
1059: goto out;
1060: }
1061: }
1062:
1.2 ! benno 1063: /*
1.1 benno 1064: * Now we're going to try to descend into all of the top-level
1065: * directories stipulated by the file list.
1066: * If the directories don't exist, it's ok.
1067: */
1068:
1069: if (NULL == (fts = fts_open(cargv, FTS_PHYSICAL, NULL))) {
1070: ERR(sess, "fts_open");
1071: goto out;
1072: }
1073:
1074: stripdir = strlen(root) + 1;
1075: errno = 0;
1076: while (NULL != (ent = fts_read(fts))) {
1077: if (FTS_NS == ent->fts_info)
1078: continue;
1079: if ( ! flist_fts_check(sess, ent)) {
1080: errno = 0;
1081: continue;
1082: } else if (stripdir >= ent->fts_pathlen)
1083: continue;
1084:
1085: /* Look up in hashtable. */
1086:
1087: memset(&hent, 0, sizeof(ENTRY));
1088: hent.key = ent->fts_path + stripdir;
1089: if (NULL != hsearch(hent, FIND))
1090: continue;
1091:
1092: /* Not found: we'll delete it. */
1093:
1094: if ( ! flist_realloc(sess, fl, sz, &max)) {
1095: ERRX1(sess, "flist_realloc");
1096: goto out;
1097: }
1098: f = &(*fl)[*sz - 1];
1099:
1100: if (NULL == (f->path = strdup(ent->fts_path))) {
1101: ERR(sess, "strdup");
1102: goto out;
1103: }
1104: f->wpath = f->path + stripdir;
1105: assert(NULL != ent->fts_statp);
1106: flist_copy_stat(f, ent->fts_statp);
1107: errno = 0;
1108: }
1109:
1110: if (errno) {
1111: ERR(sess, "fts_read");
1112: goto out;
1113: }
1114:
1115: qsort(*fl, *sz, sizeof(struct flist), flist_cmp);
1116: rc = 1;
1117: out:
1118: if (NULL != fts)
1119: fts_close(fts);
1120: for (i = 0; i < cargvs; i++)
1121: free(cargv[i]);
1122: free(cargv);
1123: hdestroy();
1124: return rc;
1125: }
1126:
1127: /*
1128: * Delete all files and directories in "fl".
1129: * If called with a zero-length "fl", does nothing.
1130: * If dry_run is specified, simply write what would be done.
1131: * Return zero on failure, non-zero on success.
1132: */
1133: int
1134: flist_del(struct sess *sess, int root,
1135: const struct flist *fl, size_t flsz)
1136: {
1137: ssize_t i;
1138: int flag;
1139:
1140: if (0 == flsz)
1141: return 1;
1142:
1143: assert(sess->opts->del);
1144: assert(sess->opts->recursive);
1145:
1146: for (i = flsz - 1; i >= 0; i--) {
1147: LOG1(sess, "%s: deleting", fl[i].wpath);
1148: if (sess->opts->dry_run)
1149: continue;
1150: assert(-1 != root);
1151: flag = S_ISDIR(fl[i].st.mode) ? AT_REMOVEDIR : 0;
1152: if (-1 == unlinkat(root, fl[i].wpath, flag) &&
1153: ENOENT != errno) {
1154: ERR(sess, "%s: unlinkat", fl[i].wpath);
1155: return 0;
1156: }
1157: }
1158:
1159: return 1;
1160: }