detailer.c revision 156231
1/*- 2 * Copyright (c) 2003-2006, Maxime Henrion <mux@FreeBSD.org> 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions 7 * are met: 8 * 1. Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * 2. Redistributions in binary form must reproduce the above copyright 11 * notice, this list of conditions and the following disclaimer in the 12 * documentation and/or other materials provided with the distribution. 13 * 14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 15 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 18 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 20 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 21 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 22 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 23 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 24 * SUCH DAMAGE. 25 * 26 * $FreeBSD: head/contrib/csup/detailer.c 156231 2006-03-03 04:11:29Z mux $ 27 */ 28 29#include <assert.h> 30#include <errno.h> 31#include <stdlib.h> 32#include <string.h> 33 34#include "config.h" 35#include "detailer.h" 36#include "fixups.h" 37#include "misc.h" 38#include "mux.h" 39#include "proto.h" 40#include "status.h" 41#include "stream.h" 42 43/* Internal error codes. */ 44#define DETAILER_ERR_PROTO (-1) /* Protocol error. */ 45#define DETAILER_ERR_MSG (-2) /* Error is in detailer->errmsg. */ 46#define DETAILER_ERR_READ (-3) /* Error reading from server. */ 47#define DETAILER_ERR_WRITE (-4) /* Error writing to server. */ 48 49struct detailer { 50 struct config *config; 51 struct stream *rd; 52 struct stream *wr; 53 char *errmsg; 54}; 55 56static int detailer_batch(struct detailer *); 57static int detailer_coll(struct detailer *, struct coll *, 58 struct status *); 59static int detailer_dofile(struct detailer *, struct coll *, 60 struct status *, char *); 61 62void * 63detailer(void *arg) 64{ 65 struct thread_args *args; 66 struct detailer dbuf, *d; 67 int error; 68 69 args = arg; 70 71 d = &dbuf; 72 d->config = args->config; 73 d->rd = args->rd; 74 d->wr = args->wr; 75 d->errmsg = NULL; 76 77 error = detailer_batch(d); 78 switch (error) { 79 case DETAILER_ERR_PROTO: 80 xasprintf(&args->errmsg, "Detailer failed: Protocol error"); 81 args->status = STATUS_FAILURE; 82 break; 83 case DETAILER_ERR_MSG: 84 xasprintf(&args->errmsg, "Detailer failed: %s", d->errmsg); 85 free(d->errmsg); 86 args->status = STATUS_FAILURE; 87 break; 88 case DETAILER_ERR_READ: 89 if (stream_eof(d->rd)) { 90 xasprintf(&args->errmsg, "Detailer failed: " 91 "Premature EOF from server"); 92 } else { 93 xasprintf(&args->errmsg, "Detailer failed: " 94 "Network read failure: %s", strerror(errno)); 95 } 96 args->status = STATUS_TRANSIENTFAILURE; 97 break; 98 case DETAILER_ERR_WRITE: 99 xasprintf(&args->errmsg, "Detailer failed: " 100 "Network write failure: %s", strerror(errno)); 101 args->status = STATUS_TRANSIENTFAILURE; 102 break; 103 default: 104 assert(error == 0); 105 args->status = STATUS_SUCCESS; 106 } 107 return (NULL); 108} 109 110static int 111detailer_batch(struct detailer *d) 112{ 113 struct config *config; 114 struct stream *rd, *wr; 115 struct coll *coll; 116 struct status *st; 117 struct fixup *fixup; 118 char *cmd, *collname, *line, *release; 119 int error, fixupseof; 120 121 config = d->config; 122 rd = d->rd; 123 wr = d->wr; 124 STAILQ_FOREACH(coll, &config->colls, co_next) { 125 if (coll->co_options & CO_SKIP) 126 continue; 127 line = stream_getln(rd, NULL); 128 cmd = proto_get_ascii(&line); 129 collname = proto_get_ascii(&line); 130 release = proto_get_ascii(&line); 131 error = proto_get_time(&line, &coll->co_scantime); 132 if (error || line != NULL || strcmp(cmd, "COLL") != 0 || 133 strcmp(collname, coll->co_name) != 0 || 134 strcmp(release, coll->co_release) != 0) 135 return (DETAILER_ERR_PROTO); 136 error = proto_printf(wr, "COLL %s %s\n", coll->co_name, 137 coll->co_release); 138 if (error) 139 return (DETAILER_ERR_WRITE); 140 stream_flush(wr); 141 if (coll->co_options & CO_COMPRESS) { 142 stream_filter_start(rd, STREAM_FILTER_ZLIB, NULL); 143 stream_filter_start(wr, STREAM_FILTER_ZLIB, NULL); 144 } 145 st = status_open(coll, -1, &d->errmsg); 146 if (st == NULL) 147 return (DETAILER_ERR_MSG); 148 error = detailer_coll(d, coll, st); 149 status_close(st, NULL); 150 if (error) 151 return (error); 152 if (coll->co_options & CO_COMPRESS) { 153 stream_filter_stop(rd); 154 stream_filter_stop(wr); 155 } 156 stream_flush(wr); 157 } 158 line = stream_getln(rd, NULL); 159 if (line == NULL) 160 return (DETAILER_ERR_READ); 161 if (strcmp(line, ".") != 0) 162 return (DETAILER_ERR_PROTO); 163 error = proto_printf(wr, ".\n"); 164 if (error) 165 return (DETAILER_ERR_WRITE); 166 stream_flush(wr); 167 168 /* Now send fixups if needed. */ 169 fixup = NULL; 170 fixupseof = 0; 171 STAILQ_FOREACH(coll, &config->colls, co_next) { 172 if (coll->co_options & CO_SKIP) 173 continue; 174 error = proto_printf(wr, "COLL %s %s\n", coll->co_name, 175 coll->co_release); 176 if (error) 177 return (DETAILER_ERR_WRITE); 178 if (coll->co_options & CO_COMPRESS) 179 stream_filter_start(wr, STREAM_FILTER_ZLIB, NULL); 180 while (!fixupseof) { 181 if (fixup == NULL) 182 fixup = fixups_get(config->fixups); 183 if (fixup == NULL) { 184 fixupseof = 1; 185 break; 186 } 187 if (fixup->f_coll != coll) 188 break; 189 error = proto_printf(wr, "Y %s %s %s\n", fixup->f_name, 190 coll->co_tag, coll->co_date); 191 if (error) 192 return (DETAILER_ERR_WRITE); 193 fixup = NULL; 194 } 195 error = proto_printf(wr, ".\n"); 196 if (error) 197 return (DETAILER_ERR_WRITE); 198 if (coll->co_options & CO_COMPRESS) 199 stream_filter_stop(wr); 200 stream_flush(wr); 201 } 202 error = proto_printf(wr, ".\n"); 203 if (error) 204 return (DETAILER_ERR_WRITE); 205 return (0); 206} 207 208static int 209detailer_coll(struct detailer *d, struct coll *coll, struct status *st) 210{ 211 struct stream *rd, *wr; 212 char *cmd, *file, *line, *msg; 213 int error; 214 215 rd = d->rd; 216 wr = d->wr; 217 line = stream_getln(rd, NULL); 218 if (line == NULL) 219 return (DETAILER_ERR_READ); 220 while (strcmp(line, ".") != 0) { 221 cmd = proto_get_ascii(&line); 222 if (cmd == NULL || strlen(cmd) != 1) 223 return (DETAILER_ERR_PROTO); 224 switch (cmd[0]) { 225 case 'D': 226 /* Delete file. */ 227 file = proto_get_ascii(&line); 228 if (file == NULL || line != NULL) 229 return (DETAILER_ERR_PROTO); 230 error = proto_printf(wr, "D %s\n", file); 231 if (error) 232 return (DETAILER_ERR_WRITE); 233 break; 234 case 'U': 235 /* Add or update file. */ 236 file = proto_get_ascii(&line); 237 if (file == NULL || line != NULL) 238 return (DETAILER_ERR_PROTO); 239 error = detailer_dofile(d, coll, st, file); 240 if (error) 241 return (error); 242 break; 243 case '!': 244 /* Warning from server. */ 245 msg = proto_get_rest(&line); 246 if (msg == NULL) 247 return (DETAILER_ERR_PROTO); 248 lprintf(-1, "Server warning: %s\n", msg); 249 break; 250 default: 251 return (DETAILER_ERR_PROTO); 252 } 253 stream_flush(wr); 254 line = stream_getln(rd, NULL); 255 if (line == NULL) 256 return (DETAILER_ERR_READ); 257 } 258 error = proto_printf(wr, ".\n"); 259 if (error) 260 return (DETAILER_ERR_WRITE); 261 return (0); 262} 263 264static int 265detailer_dofile(struct detailer *d, struct coll *coll, struct status *st, 266 char *file) 267{ 268 char md5[MD5_DIGEST_SIZE]; 269 struct stream *wr; 270 struct fattr *fa; 271 struct statusrec *sr; 272 char *path; 273 int error, ret; 274 275 wr = d->wr; 276 path = checkoutpath(coll->co_prefix, file); 277 if (path == NULL) 278 return (DETAILER_ERR_PROTO); 279 fa = fattr_frompath(path, FATTR_NOFOLLOW); 280 if (fa == NULL) { 281 /* We don't have the file, so the only option at this 282 point is to tell the server to send it. The server 283 may figure out that the file is dead, in which case 284 it will tell us. */ 285 error = proto_printf(wr, "C %s %s %s\n", 286 file, coll->co_tag, coll->co_date); 287 free(path); 288 if (error) 289 return (DETAILER_ERR_WRITE); 290 return (0); 291 } 292 ret = status_get(st, file, 0, 0, &sr); 293 if (ret == -1) { 294 d->errmsg = status_errmsg(st); 295 free(path); 296 return (DETAILER_ERR_MSG); 297 } 298 if (ret == 0) 299 sr = NULL; 300 301 /* If our recorded information doesn't match the file that the 302 client has, then ignore the recorded information. */ 303 if (sr != NULL && (sr->sr_type != SR_CHECKOUTLIVE || 304 !fattr_equal(sr->sr_clientattr, fa))) 305 sr = NULL; 306 fattr_free(fa); 307 if (sr != NULL && strcmp(sr->sr_revdate, ".") != 0) { 308 error = proto_printf(wr, "U %s %s %s %s %s\n", file, 309 coll->co_tag, coll->co_date, sr->sr_revnum, sr->sr_revdate); 310 free(path); 311 if (error) 312 return (DETAILER_ERR_WRITE); 313 return (0); 314 } 315 316 /* 317 * We don't have complete and/or accurate recorded information 318 * about what version of the file we have. Compute the file's 319 * checksum as an aid toward identifying which version it is. 320 */ 321 error = MD5_File(path, md5); 322 if (error) { 323 xasprintf(&d->errmsg, 324 "Cannot calculate checksum for \"%s\": %s", path, 325 strerror(errno)); 326 return (DETAILER_ERR_MSG); 327 } 328 free(path); 329 if (sr == NULL) { 330 error = proto_printf(wr, "S %s %s %s %s\n", file, 331 coll->co_tag, coll->co_date, md5); 332 } else { 333 error = proto_printf(wr, "s %s %s %s %s %s\n", file, 334 coll->co_tag, coll->co_date, sr->sr_revnum, md5); 335 } 336 if (error) 337 return (DETAILER_ERR_WRITE); 338 return (0); 339} 340