1 /* $Id: uploader.c,v 1.4 2019/02/11 21:41:22 deraadt Exp $ */ 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/mman.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 <math.h> 25 #include <poll.h> 26 #include <stdio.h> 27 #include <stdlib.h> 28 #include <string.h> 29 #include <time.h> 30 #include <unistd.h> 31 32 #include "extern.h" 33 34 enum uploadst { 35 UPLOAD_FIND_NEXT = 0, /* find next to upload to sender */ 36 UPLOAD_WRITE_LOCAL, /* wait to write to sender */ 37 UPLOAD_READ_LOCAL, /* wait to read from local file */ 38 UPLOAD_FINISHED /* nothing more to do in phase */ 39 }; 40 41 /* 42 * Used to keep track of data flowing from the receiver to the sender. 43 * This is managed by the receiver process. 44 */ 45 struct upload { 46 enum uploadst state; 47 char *buf; /* if not NULL, pending upload */ 48 size_t bufsz; /* size of buf */ 49 size_t bufmax; /* maximum size of buf */ 50 size_t bufpos; /* position in buf */ 51 size_t idx; /* current transfer index */ 52 mode_t oumask; /* umask for creating files */ 53 int rootfd; /* destination directory */ 54 size_t csumlen; /* checksum length */ 55 int fdout; /* write descriptor to sender */ 56 const struct flist *fl; /* file list */ 57 size_t flsz; /* size of file list */ 58 int *newdir; /* non-zero if mkdir'd */ 59 }; 60 61 /* 62 * Log a directory by emitting the file and a trailing slash, just to 63 * show the operator that we're a directory. 64 */ 65 static void 66 log_dir(struct sess *sess, const struct flist *f) 67 { 68 size_t sz; 69 70 if (sess->opts->server) 71 return; 72 sz = strlen(f->path); 73 assert(sz > 0); 74 LOG1(sess, "%s%s", f->path, 75 '/' == f->path[sz - 1] ? "" : "/"); 76 } 77 78 /* 79 * Log a link by emitting the file and the target, just to show the 80 * operator that we're a link. 81 */ 82 static void 83 log_link(struct sess *sess, const struct flist *f) 84 { 85 86 if (!sess->opts->server) 87 LOG1(sess, "%s -> %s", f->path, f->link); 88 } 89 90 /* 91 * Simply log the filename. 92 */ 93 static void 94 log_file(struct sess *sess, const struct flist *f) 95 { 96 97 if (!sess->opts->server) 98 LOG1(sess, "%s", f->path); 99 } 100 101 /* 102 * Prepare the overall block set's metadata. 103 * We always have at least one block. 104 * The block size is an important part of the algorithm. 105 * I use the same heuristic as the reference rsync, but implemented in a 106 * bit more of a straightforward way. 107 * In general, the individual block length is the rounded square root of 108 * the total file size. 109 * The minimum block length is 700. 110 */ 111 static void 112 init_blkset(struct blkset *p, off_t sz) 113 { 114 double v; 115 116 if (sz >= (BLOCK_SIZE_MIN * BLOCK_SIZE_MIN)) { 117 /* Simple rounded-up integer square root. */ 118 119 v = sqrt(sz); 120 p->len = ceil(v); 121 122 /* 123 * Always be a multiple of eight. 124 * There's no reason to do this, but rsync does. 125 */ 126 127 if ((p->len % 8) > 0) 128 p->len += 8 - (p->len % 8); 129 } else 130 p->len = BLOCK_SIZE_MIN; 131 132 p->size = sz; 133 if ((p->blksz = sz / p->len) == 0) 134 p->rem = sz; 135 else 136 p->rem = sz % p->len; 137 138 /* If we have a remainder, then we need an extra block. */ 139 140 if (p->rem) 141 p->blksz++; 142 } 143 144 /* 145 * For each block, prepare the block's metadata. 146 * We use the mapped "map" file to set our checksums. 147 */ 148 static void 149 init_blk(struct blk *p, const struct blkset *set, off_t offs, 150 size_t idx, const void *map, const struct sess *sess) 151 { 152 153 assert(map != MAP_FAILED); 154 155 /* Block length inherits for all but the last. */ 156 157 p->idx = idx; 158 p->len = idx < set->blksz - 1 ? set->len : set->rem; 159 p->offs = offs; 160 161 p->chksum_short = hash_fast(map + offs, p->len); 162 hash_slow(map + offs, p->len, p->chksum_long, sess); 163 } 164 165 /* 166 * Return <0 on failure 0 on success. 167 */ 168 static int 169 pre_link(struct upload *p, struct sess *sess) 170 { 171 int rc, newlink = 0; 172 char *b; 173 struct stat st; 174 struct timespec tv[2]; 175 const struct flist *f; 176 177 f = &p->fl[p->idx]; 178 assert(S_ISLNK(f->st.mode)); 179 180 if (!sess->opts->preserve_links) { 181 WARNX(sess, "%s: ignoring symlink", f->path); 182 return 0; 183 } else if (sess->opts->dry_run) { 184 log_link(sess, f); 185 return 0; 186 } 187 188 /* See if the symlink already exists. */ 189 190 assert(p->rootfd != -1); 191 rc = fstatat(p->rootfd, f->path, &st, AT_SYMLINK_NOFOLLOW); 192 if (rc != -1 && !S_ISLNK(st.st_mode)) { 193 WARNX(sess, "%s: not a symlink", f->path); 194 return -1; 195 } else if (rc == -1 && errno != ENOENT) { 196 WARN(sess, "%s: fstatat", f->path); 197 return -1; 198 } 199 200 /* 201 * If the symbolic link already exists, then make sure that it 202 * points to the correct place. 203 * FIXME: does symlinkat() set permissions on the link using the 204 * destination file or the default umask? 205 * Do we need a fchmod in here as well? 206 */ 207 208 if (rc == -1) { 209 LOG3(sess, "%s: creating " 210 "symlink: %s", f->path, f->link); 211 if (symlinkat(f->link, p->rootfd, f->path) == -1) { 212 WARN(sess, "%s: symlinkat", f->path); 213 return -1; 214 } 215 newlink = 1; 216 } else { 217 b = symlinkat_read(sess, p->rootfd, f->path); 218 if (b == NULL) { 219 ERRX1(sess, "%s: symlinkat_read", f->path); 220 return -1; 221 } 222 if (strcmp(f->link, b)) { 223 free(b); 224 b = NULL; 225 LOG3(sess, "%s: updating " 226 "symlink: %s", f->path, f->link); 227 if (unlinkat(p->rootfd, f->path, 0) == -1) { 228 WARN(sess, "%s: unlinkat", f->path); 229 return -1; 230 } 231 if (symlinkat(f->link, p->rootfd, f->path) == -1) { 232 WARN(sess, "%s: symlinkat", f->path); 233 return -1; 234 } 235 newlink = 1; 236 } 237 free(b); 238 } 239 240 /* Optionally preserve times/perms on the symlink. */ 241 242 if (sess->opts->preserve_times) { 243 tv[0].tv_sec = time(NULL); 244 tv[0].tv_nsec = 0; 245 tv[1].tv_sec = f->st.mtime; 246 tv[1].tv_nsec = 0; 247 rc = utimensat(p->rootfd, 248 f->path, tv, AT_SYMLINK_NOFOLLOW); 249 if (rc == -1) { 250 ERR(sess, "%s: utimensat", f->path); 251 return -1; 252 } 253 LOG4(sess, "%s: updated symlink date", f->path); 254 } 255 256 /* 257 * FIXME: if newlink is set because we updated the symlink, we 258 * want to carry over the permissions from the last. 259 */ 260 261 if (newlink || sess->opts->preserve_perms) { 262 rc = fchmodat(p->rootfd, 263 f->path, f->st.mode, AT_SYMLINK_NOFOLLOW); 264 if (rc == -1) { 265 ERR(sess, "%s: fchmodat", f->path); 266 return -1; 267 } 268 LOG4(sess, "%s: updated symlink mode", f->path); 269 } 270 271 log_link(sess, f); 272 return 0; 273 } 274 275 /* 276 * If not found, create the destination directory in prefix order. 277 * Create directories using the existing umask. 278 * Return <0 on failure 0 on success. 279 */ 280 static int 281 pre_dir(const struct upload *p, struct sess *sess) 282 { 283 struct stat st; 284 int rc; 285 const struct flist *f; 286 287 f = &p->fl[p->idx]; 288 assert(S_ISDIR(f->st.mode)); 289 290 if (!sess->opts->recursive) { 291 WARNX(sess, "%s: ignoring directory", f->path); 292 return 0; 293 } else if (sess->opts->dry_run) { 294 log_dir(sess, f); 295 return 0; 296 } 297 298 assert(p->rootfd != -1); 299 rc = fstatat(p->rootfd, f->path, &st, AT_SYMLINK_NOFOLLOW); 300 if (rc == -1 && errno != ENOENT) { 301 WARN(sess, "%s: fstatat", f->path); 302 return -1; 303 } else if (rc != -1 && !S_ISDIR(st.st_mode)) { 304 WARNX(sess, "%s: not a directory", f->path); 305 return -1; 306 } else if (rc != -1) { 307 /* 308 * FIXME: we should fchmod the permissions here as well, 309 * as we may locally have shut down writing into the 310 * directory and that doesn't work. 311 */ 312 LOG3(sess, "%s: updating directory", f->path); 313 return 0; 314 } 315 316 /* 317 * We want to make the directory with default permissions (using 318 * our old umask, which we've since unset), then adjust 319 * permissions (assuming preserve_perms or new) afterward in 320 * case it's u-w or something. 321 */ 322 323 LOG3(sess, "%s: creating directory", f->path); 324 if (mkdirat(p->rootfd, f->path, 0777 & ~p->oumask) == -1) { 325 WARN(sess, "%s: mkdirat", f->path); 326 return -1; 327 } 328 329 p->newdir[p->idx] = 1; 330 log_dir(sess, f); 331 return 0; 332 } 333 334 /* 335 * Process the directory time and mode for "idx" in the file list. 336 * Returns zero on failure, non-zero on success. 337 */ 338 static int 339 post_dir(struct sess *sess, const struct upload *u, size_t idx) 340 { 341 struct timespec tv[2]; 342 int rc; 343 struct stat st; 344 const struct flist *f; 345 346 f = &u->fl[idx]; 347 assert(S_ISDIR(f->st.mode)); 348 349 /* We already warned about the directory in pre_process_dir(). */ 350 351 if (!sess->opts->recursive) 352 return 1; 353 else if (sess->opts->dry_run) 354 return 1; 355 356 if (fstatat(u->rootfd, f->path, &st, AT_SYMLINK_NOFOLLOW) == -1) { 357 ERR(sess, "%s: fstatat", f->path); 358 return 0; 359 } else if (!S_ISDIR(st.st_mode)) { 360 WARNX(sess, "%s: not a directory", f->path); 361 return 0; 362 } 363 364 /* 365 * Update the modification time if we're a new directory *or* if 366 * we're preserving times and the time has changed. 367 */ 368 369 if (u->newdir[idx] || 370 (sess->opts->preserve_times && 371 st.st_mtime != f->st.mtime)) { 372 tv[0].tv_sec = time(NULL); 373 tv[0].tv_nsec = 0; 374 tv[1].tv_sec = f->st.mtime; 375 tv[1].tv_nsec = 0; 376 rc = utimensat(u->rootfd, f->path, tv, 0); 377 if (rc == -1) { 378 ERR(sess, "%s: utimensat", f->path); 379 return 0; 380 } 381 LOG4(sess, "%s: updated date", f->path); 382 } 383 384 /* 385 * Update the mode if we're a new directory *or* if we're 386 * preserving modes and it has changed. 387 */ 388 389 if (u->newdir[idx] || 390 (sess->opts->preserve_perms && 391 st.st_mode != f->st.mode)) { 392 rc = fchmodat(u->rootfd, f->path, f->st.mode, 0); 393 if (rc == -1) { 394 ERR(sess, "%s: fchmodat", f->path); 395 return 0; 396 } 397 LOG4(sess, "%s: updated mode", f->path); 398 } 399 400 return 1; 401 } 402 403 /* 404 * Try to open the file at the current index. 405 * If the file does not exist, returns with success. 406 * Return <0 on failure, 0 on success w/nothing to be done, >0 on 407 * success and the file needs attention. 408 */ 409 static int 410 pre_file(const struct upload *p, int *filefd, struct sess *sess) 411 { 412 const struct flist *f; 413 414 f = &p->fl[p->idx]; 415 assert(S_ISREG(f->st.mode)); 416 417 if (sess->opts->dry_run) { 418 log_file(sess, f); 419 if (!io_write_int(sess, p->fdout, p->idx)) { 420 ERRX1(sess, "io_write_int"); 421 return -1; 422 } 423 return 0; 424 } 425 426 /* 427 * For non dry-run cases, we'll write the acknowledgement later 428 * in the rsync_uploader() function because we need to wait for 429 * the open() call to complete. 430 * If the call to openat() fails with ENOENT, there's a 431 * fast-path between here and the write function, so we won't do 432 * any blocking between now and then. 433 */ 434 435 *filefd = openat(p->rootfd, f->path, 436 O_RDONLY | O_NOFOLLOW | O_NONBLOCK, 0); 437 if (*filefd != -1 || errno == ENOENT) 438 return 1; 439 ERR(sess, "%s: openat", f->path); 440 return -1; 441 } 442 443 /* 444 * Allocate an uploader object in the correct state to start. 445 * Returns NULL on failure or the pointer otherwise. 446 * On success, upload_free() must be called with the allocated pointer. 447 */ 448 struct upload * 449 upload_alloc(struct sess *sess, int rootfd, int fdout, 450 size_t clen, const struct flist *fl, size_t flsz, mode_t msk) 451 { 452 struct upload *p; 453 454 if ((p = calloc(1, sizeof(struct upload))) == NULL) { 455 ERR(sess, "calloc"); 456 return NULL; 457 } 458 459 p->state = UPLOAD_FIND_NEXT; 460 p->oumask = msk; 461 p->rootfd = rootfd; 462 p->csumlen = clen; 463 p->fdout = fdout; 464 p->fl = fl; 465 p->flsz = flsz; 466 p->newdir = calloc(flsz, sizeof(int)); 467 if (p->newdir == NULL) { 468 ERR(sess, "calloc"); 469 free(p); 470 return NULL; 471 } 472 return p; 473 } 474 475 /* 476 * Perform all cleanups and free. 477 * Passing a NULL to this function is ok. 478 */ 479 void 480 upload_free(struct upload *p) 481 { 482 483 if (p == NULL) 484 return; 485 free(p->newdir); 486 free(p->buf); 487 free(p); 488 } 489 490 /* 491 * Iterates through all available files and conditionally gets the file 492 * ready for processing to check whether it's up to date. 493 * If not up to date or empty, sends file information to the sender. 494 * If returns 0, we've processed all files there are to process. 495 * If returns >0, we're waiting for POLLIN or POLLOUT data. 496 * Otherwise returns <0, which is an error. 497 */ 498 int 499 rsync_uploader(struct upload *u, int *fileinfd, 500 struct sess *sess, int *fileoutfd) 501 { 502 struct blkset blk; 503 struct stat st; 504 void *map, *bufp; 505 size_t i, mapsz, pos, sz; 506 off_t offs; 507 int c; 508 509 /* This should never get called. */ 510 511 assert(u->state != UPLOAD_FINISHED); 512 513 /* 514 * If we have an upload in progress, then keep writing until the 515 * buffer has been fully written. 516 * We must only have the output file descriptor working and also 517 * have a valid buffer to write. 518 */ 519 520 if (u->state == UPLOAD_WRITE_LOCAL) { 521 assert(NULL != u->buf); 522 assert(*fileoutfd != -1); 523 assert(*fileinfd == -1); 524 525 /* 526 * Unfortunately, we need to chunk these: if we're 527 * the server side of things, then we're multiplexing 528 * output and need to wrap this in chunks. 529 * This is a major deficiency of rsync. 530 * FIXME: add a "fast-path" mode that simply dumps out 531 * the buffer non-blocking if we're not mplexing. 532 */ 533 534 if (u->bufpos < u->bufsz) { 535 sz = MAX_CHUNK < (u->bufsz - u->bufpos) ? 536 MAX_CHUNK : (u->bufsz - u->bufpos); 537 c = io_write_buf(sess, u->fdout, 538 u->buf + u->bufpos, sz); 539 if (c == 0) { 540 ERRX1(sess, "io_write_nonblocking"); 541 return -1; 542 } 543 u->bufpos += sz; 544 if (u->bufpos < u->bufsz) 545 return 1; 546 } 547 548 /* 549 * Let the UPLOAD_FIND_NEXT state handle things if we 550 * finish, as we'll need to write a POLLOUT message and 551 * not have a writable descriptor yet. 552 */ 553 554 u->state = UPLOAD_FIND_NEXT; 555 u->idx++; 556 return 1; 557 } 558 559 /* 560 * If we invoke the uploader without a file currently open, then 561 * we iterate through til the next available regular file and 562 * start the opening process. 563 * This means we must have the output file descriptor working. 564 */ 565 566 if (u->state == UPLOAD_FIND_NEXT) { 567 assert(*fileinfd == -1); 568 assert(*fileoutfd != -1); 569 570 for ( ; u->idx < u->flsz; u->idx++) { 571 if (S_ISDIR(u->fl[u->idx].st.mode)) 572 c = pre_dir(u, sess); 573 else if (S_ISLNK(u->fl[u->idx].st.mode)) 574 c = pre_link(u, sess); 575 else if (S_ISREG(u->fl[u->idx].st.mode)) 576 c = pre_file(u, fileinfd, sess); 577 else 578 c = 0; 579 580 if (c < 0) 581 return -1; 582 else if (c > 0) 583 break; 584 } 585 586 /* 587 * Whether we've finished writing files or not, we 588 * disable polling on the output channel. 589 */ 590 591 *fileoutfd = -1; 592 if (u->idx == u->flsz) { 593 assert(*fileinfd == -1); 594 if (!io_write_int(sess, u->fdout, -1)) { 595 ERRX1(sess, "io_write_int"); 596 return -1; 597 } 598 u->state = UPLOAD_FINISHED; 599 LOG4(sess, "uploader: finished"); 600 return 0; 601 } 602 603 /* Go back to the event loop, if necessary. */ 604 605 u->state = -1 == *fileinfd ? 606 UPLOAD_WRITE_LOCAL : UPLOAD_READ_LOCAL; 607 if (u->state == UPLOAD_READ_LOCAL) 608 return 1; 609 } 610 611 /* 612 * If an input file is open, stat it and see if it's already up 613 * to date, in which case close it and go to the next one. 614 * Either way, we don't have a write channel open. 615 */ 616 617 if (u->state == UPLOAD_READ_LOCAL) { 618 assert(*fileinfd != -1); 619 assert(*fileoutfd == -1); 620 621 if (fstat(*fileinfd, &st) == -1) { 622 WARN(sess, "%s: fstat", u->fl[u->idx].path); 623 close(*fileinfd); 624 *fileinfd = -1; 625 return -1; 626 } else if (!S_ISREG(st.st_mode)) { 627 WARNX(sess, "%s: not regular", u->fl[u->idx].path); 628 close(*fileinfd); 629 *fileinfd = -1; 630 return -1; 631 } 632 633 if (st.st_size == u->fl[u->idx].st.size && 634 st.st_mtime == u->fl[u->idx].st.mtime) { 635 LOG3(sess, "%s: skipping: " 636 "up to date", u->fl[u->idx].path); 637 close(*fileinfd); 638 *fileinfd = -1; 639 *fileoutfd = u->fdout; 640 u->state = UPLOAD_FIND_NEXT; 641 u->idx++; 642 return 1; 643 } 644 645 /* Fallthrough... */ 646 647 u->state = UPLOAD_WRITE_LOCAL; 648 } 649 650 /* Initialies our blocks. */ 651 652 assert(u->state == UPLOAD_WRITE_LOCAL); 653 memset(&blk, 0, sizeof(struct blkset)); 654 blk.csum = u->csumlen; 655 656 if (*fileinfd != -1 && st.st_size > 0) { 657 mapsz = st.st_size; 658 map = mmap(NULL, mapsz, 659 PROT_READ, MAP_SHARED, *fileinfd, 0); 660 if (map == MAP_FAILED) { 661 WARN(sess, "%s: mmap", u->fl[u->idx].path); 662 close(*fileinfd); 663 *fileinfd = -1; 664 return -1; 665 } 666 667 init_blkset(&blk, st.st_size); 668 assert(blk.blksz); 669 670 blk.blks = calloc(blk.blksz, sizeof(struct blk)); 671 if (blk.blks == NULL) { 672 ERR(sess, "calloc"); 673 munmap(map, mapsz); 674 close(*fileinfd); 675 *fileinfd = -1; 676 return -1; 677 } 678 679 offs = 0; 680 for (i = 0; i < blk.blksz; i++) { 681 init_blk(&blk.blks[i], 682 &blk, offs, i, map, sess); 683 offs += blk.len; 684 } 685 686 munmap(map, mapsz); 687 close(*fileinfd); 688 *fileinfd = -1; 689 LOG3(sess, "%s: mapped %jd B with %zu blocks", 690 u->fl[u->idx].path, (intmax_t)blk.size, 691 blk.blksz); 692 } else { 693 if (*fileinfd != -1) { 694 close(*fileinfd); 695 *fileinfd = -1; 696 } 697 blk.len = MAX_CHUNK; /* Doesn't matter. */ 698 LOG3(sess, "%s: not mapped", u->fl[u->idx].path); 699 } 700 701 assert(*fileinfd == -1); 702 703 /* Make sure the block metadata buffer is big enough. */ 704 705 u->bufsz = 706 sizeof(int32_t) + /* identifier */ 707 sizeof(int32_t) + /* block count */ 708 sizeof(int32_t) + /* block length */ 709 sizeof(int32_t) + /* checksum length */ 710 sizeof(int32_t) + /* block remainder */ 711 blk.blksz * 712 (sizeof(int32_t) + /* short checksum */ 713 blk.csum); /* long checksum */ 714 715 if (u->bufsz > u->bufmax) { 716 if ((bufp = realloc(u->buf, u->bufsz)) == NULL) { 717 ERR(sess, "realloc"); 718 return -1; 719 } 720 u->buf = bufp; 721 u->bufmax = u->bufsz; 722 } 723 724 u->bufpos = pos = 0; 725 io_buffer_int(sess, u->buf, &pos, u->bufsz, u->idx); 726 io_buffer_int(sess, u->buf, &pos, u->bufsz, blk.blksz); 727 io_buffer_int(sess, u->buf, &pos, u->bufsz, blk.len); 728 io_buffer_int(sess, u->buf, &pos, u->bufsz, blk.csum); 729 io_buffer_int(sess, u->buf, &pos, u->bufsz, blk.rem); 730 for (i = 0; i < blk.blksz; i++) { 731 io_buffer_int(sess, u->buf, &pos, u->bufsz, 732 blk.blks[i].chksum_short); 733 io_buffer_buf(sess, u->buf, &pos, u->bufsz, 734 blk.blks[i].chksum_long, blk.csum); 735 } 736 assert(pos == u->bufsz); 737 738 /* Reenable the output poller and clean up. */ 739 740 *fileoutfd = u->fdout; 741 free(blk.blks); 742 return 1; 743 } 744 745 /* 746 * Fix up the directory permissions and times post-order. 747 * We can't fix up directory permissions in place because the server may 748 * want us to have overly-tight permissions---say, those that don't 749 * allow writing into the directory. 750 * We also need to do our directory times post-order because making 751 * files within the directory will change modification times. 752 * Returns zero on failure, non-zero on success. 753 */ 754 int 755 rsync_uploader_tail(struct upload *u, struct sess *sess) 756 { 757 size_t i; 758 759 760 if (!sess->opts->preserve_times && 761 !sess->opts->preserve_perms) 762 return 1; 763 764 LOG2(sess, "fixing up directory times and permissions"); 765 766 for (i = 0; i < u->flsz; i++) 767 if (S_ISDIR(u->fl[i].st.mode)) 768 if (!post_dir(sess, u, i)) 769 return 0; 770 771 return 1; 772 } 773