sender.c revision 1.3
1/* $Id: sender.c,v 1.3 2019/02/10 23:43:31 benno 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/stat.h> 18 19#include <assert.h> 20#include <inttypes.h> 21#include <stdlib.h> 22#include <string.h> 23#include <unistd.h> 24 25#include "extern.h" 26 27/* 28 * A client sender manages the read-only source files and sends data to 29 * the receiver as requested. 30 * First it sends its list of files, then it waits for the server to 31 * request updates to individual files. 32 * Returns zero on failure, non-zero on success. 33 * 34 * Pledges: stdio, rpath, unveil. 35 */ 36int 37rsync_sender(struct sess *sess, int fdin, 38 int fdout, size_t argc, char **argv) 39{ 40 struct flist *fl = NULL; 41 size_t flsz = 0, phase = 0, excl; 42 int rc = 0, c; 43 int32_t idx; 44 struct blkset *blks = NULL; 45 46 if (-1 == pledge("stdio rpath unveil", NULL)) { 47 ERR(sess, "pledge"); 48 return 0; 49 } 50 51 /* 52 * Generate the list of files we want to send from our 53 * command-line input. 54 * This will also remove all invalid files. 55 */ 56 57 if ( ! flist_gen(sess, argc, argv, &fl, &flsz)) { 58 ERRX1(sess, "flist_gen"); 59 goto out; 60 } 61 62 /* Client sends zero-length exclusions if deleting. */ 63 64 if ( ! sess->opts->server && sess->opts->del && 65 ! io_write_int(sess, fdout, 0)) { 66 ERRX1(sess, "io_write_int"); 67 goto out; 68 } 69 70 /* 71 * Then the file list in any mode. 72 * Finally, the IO error (always zero for us). 73 */ 74 75 if ( ! flist_send(sess, fdin, fdout, fl, flsz)) { 76 ERRX1(sess, "flist_send"); 77 goto out; 78 } else if ( ! io_write_int(sess, fdout, 0)) { 79 ERRX1(sess, "io_write_int"); 80 goto out; 81 } 82 83 /* Exit if we're the server with zero files. */ 84 85 if (0 == flsz && sess->opts->server) { 86 WARNX(sess, "sender has empty file list: exiting"); 87 rc = 1; 88 goto out; 89 } else if ( ! sess->opts->server) 90 LOG1(sess, "Transfer starting: %zu files", flsz); 91 92 /* 93 * If we're the server, read our exclusion list. 94 * This is always 0 for now. 95 */ 96 97 if (sess->opts->server) { 98 if ( ! io_read_size(sess, fdin, &excl)) { 99 ERRX1(sess, "io_read_size"); 100 goto out; 101 } else if (0 != excl) { 102 ERRX1(sess, "exclusion list is non-empty"); 103 goto out; 104 } 105 } 106 107 /* 108 * We have two phases: the first has a two-byte checksum, the 109 * second has a full 16-byte checksum. 110 */ 111 112 LOG2(sess, "sender transmitting phase 1 data"); 113 114 for (;;) { 115 if ( ! io_read_int(sess, fdin, &idx)) { 116 ERRX1(sess, "io_read_int"); 117 goto out; 118 } 119 120 /* 121 * If we receive an invalid index (-1), then we're 122 * either promoted to the second phase or it's time to 123 * exit, depending upon which phase we're in. 124 */ 125 126 if (-1 == idx) { 127 if ( ! io_write_int(sess, fdout, idx)) { 128 ERRX1(sess, "io_write_int"); 129 goto out; 130 } 131 132 /* FIXME: I don't understand this ack. */ 133 134 if (sess->opts->server && sess->rver > 27) 135 if ( ! io_write_int(sess, fdout, idx)) { 136 ERRX1(sess, "io_write_int"); 137 goto out; 138 } 139 140 if (phase++) 141 break; 142 LOG2(sess, "sender transmitting phase 2 data"); 143 continue; 144 } 145 146 /* Validate index and file type. */ 147 148 if (idx < 0 || (uint32_t)idx >= flsz) { 149 ERRX(sess, "file index out of bounds: " 150 "invalid %" PRId32 " out of %zu", 151 idx, flsz); 152 goto out; 153 } else if (S_ISDIR(fl[idx].st.mode)) { 154 ERRX(sess, "blocks requested for " 155 "directory: %s", fl[idx].path); 156 goto out; 157 } else if (S_ISLNK(fl[idx].st.mode)) { 158 ERRX(sess, "blocks requested for " 159 "symlink: %s", fl[idx].path); 160 goto out; 161 } else if ( ! S_ISREG(fl[idx].st.mode)) { 162 ERRX(sess, "blocks requested for " 163 "special: %s", fl[idx].path); 164 goto out; 165 } 166 167 if ( ! sess->opts->server) 168 LOG1(sess, "%s", fl[idx].wpath); 169 170 /* Dry-run doesn't do anything. */ 171 172 if (sess->opts->dry_run) { 173 if ( ! io_write_int(sess, fdout, idx)) { 174 ERRX1(sess, "io_write_int"); 175 goto out; 176 } 177 continue; 178 } 179 180 /* 181 * The server will now send us its view of the file. 182 * It does so by cutting a file into a series of blocks 183 * and checksumming each block. 184 * We can then compare the blocks in our file and those 185 * in theirs, and send them blocks they're missing or 186 * don't have. 187 */ 188 189 blks = blk_recv(sess, fdin, fl[idx].path); 190 if (NULL == blks) { 191 ERRX1(sess, "blk_recv"); 192 goto out; 193 } else if ( ! blk_recv_ack(sess, fdout, blks, idx)) { 194 ERRX1(sess, "blk_recv_ack"); 195 goto out; 196 } 197 198 c = blk_match(sess, fdout, blks, fl[idx].path); 199 blkset_free(blks); 200 201 if ( ! c) { 202 ERRX1(sess, "blk_match"); 203 goto out; 204 } 205 } 206 207 if ( ! sess_stats_send(sess, fdout)) { 208 ERRX1(sess, "sess_stats_end"); 209 goto out; 210 } 211 212 /* Final "goodbye" message. */ 213 214 if ( ! io_read_int(sess, fdin, &idx)) { 215 ERRX1(sess, "io_read_int"); 216 goto out; 217 } else if (-1 != idx) { 218 ERRX(sess, "read incorrect update complete ack"); 219 goto out; 220 } 221 222 LOG2(sess, "sender finished updating"); 223 rc = 1; 224out: 225 flist_free(fl, flsz); 226 return rc; 227} 228