Annotation of src/usr.bin/rsync/flist.c, Revision 1.1
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: {
! 68: size_t i, j;
! 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: {
! 147: size_t i;
! 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, '/');
! 158: if (NULL != cp && '\0' != cp[1])
! 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
! 241: flist_send(struct sess *sess, int fdin,
! 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:
! 259: /*
! 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
! 954: flist_gen_dels(struct sess *sess, const char *root,
! 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;
! 966:
! 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:
! 975: /*
! 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, "."));
! 1016: c = asprintf(&cargv[j],
! 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:
! 1063: /*
! 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: }