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$ 27 */ 28 29#include <assert.h> 30#include <errno.h> 31#include <limits.h> 32#include <stdio.h> 33#include <stdlib.h> 34#include <string.h> 35 36#include "attrstack.h" 37#include "config.h" 38#include "fattr.h" 39#include "globtree.h" 40#include "lister.h" 41#include "misc.h" 42#include "mux.h" 43#include "proto.h" 44#include "status.h" 45#include "stream.h" 46 47/* Internal error codes. */ 48#define LISTER_ERR_WRITE (-1) /* Error writing to server. */ 49#define LISTER_ERR_STATUS (-2) /* Status file error in lstr->errmsg. */ 50 51struct lister { 52 struct config *config; 53 struct stream *wr; 54 char *errmsg; 55}; 56 57static int lister_batch(struct lister *); 58static int lister_coll(struct lister *, struct coll *, struct status *); 59static int lister_dodirdown(struct lister *, struct coll *, 60 struct statusrec *, struct attrstack *as); 61static int lister_dodirup(struct lister *, struct coll *, 62 struct statusrec *, struct attrstack *as); 63static int lister_dofile(struct lister *, struct coll *, 64 struct statusrec *); 65static int lister_dodead(struct lister *, struct coll *, 66 struct statusrec *); 67static int lister_dorcsfile(struct lister *, struct coll *, 68 struct statusrec *); 69static int lister_dorcsdead(struct lister *, struct coll *, 70 struct statusrec *); 71 72void * 73lister(void *arg) 74{ 75 struct thread_args *args; 76 struct lister lbuf, *l; 77 int error; 78 79 args = arg; 80 l = &lbuf; 81 l->config = args->config; 82 l->wr = args->wr; 83 l->errmsg = NULL; 84 error = lister_batch(l); 85 switch (error) { 86 case LISTER_ERR_WRITE: 87 xasprintf(&args->errmsg, 88 "TreeList failed: Network write failure: %s", 89 strerror(errno)); 90 args->status = STATUS_TRANSIENTFAILURE; 91 break; 92 case LISTER_ERR_STATUS: 93 xasprintf(&args->errmsg, 94 "TreeList failed: %s. Delete it and try again.", 95 l->errmsg); 96 free(l->errmsg); 97 args->status = STATUS_FAILURE; 98 break; 99 default: 100 assert(error == 0); 101 args->status = STATUS_SUCCESS; 102 }; 103 return (NULL); 104} 105 106static int 107lister_batch(struct lister *l) 108{ 109 struct config *config; 110 struct stream *wr; 111 struct status *st; 112 struct coll *coll; 113 int error; 114 115 config = l->config; 116 wr = l->wr; 117 STAILQ_FOREACH(coll, &config->colls, co_next) { 118 if (coll->co_options & CO_SKIP) 119 continue; 120 st = status_open(coll, -1, &l->errmsg); 121 if (st == NULL) 122 return (LISTER_ERR_STATUS); 123 error = proto_printf(wr, "COLL %s %s\n", coll->co_name, 124 coll->co_release); 125 if (error) 126 return (LISTER_ERR_WRITE); 127 stream_flush(wr); 128 if (coll->co_options & CO_COMPRESS) 129 stream_filter_start(wr, STREAM_FILTER_ZLIB, NULL); 130 error = lister_coll(l, coll, st); 131 status_close(st, NULL); 132 if (error) 133 return (error); 134 if (coll->co_options & CO_COMPRESS) 135 stream_filter_stop(wr); 136 stream_flush(wr); 137 } 138 error = proto_printf(wr, ".\n"); 139 if (error) 140 return (LISTER_ERR_WRITE); 141 return (0); 142} 143 144/* List a single collection based on the status file. */ 145static int 146lister_coll(struct lister *l, struct coll *coll, struct status *st) 147{ 148 struct stream *wr; 149 struct attrstack *as; 150 struct statusrec *sr; 151 struct fattr *fa; 152 size_t i; 153 int depth, error, ret, prunedepth; 154 155 wr = l->wr; 156 depth = 0; 157 prunedepth = INT_MAX; 158 as = attrstack_new(); 159 while ((ret = status_get(st, NULL, 0, 0, &sr)) == 1) { 160 switch (sr->sr_type) { 161 case SR_DIRDOWN: 162 depth++; 163 if (depth < prunedepth) { 164 error = lister_dodirdown(l, coll, sr, as); 165 if (error < 0) 166 goto bad; 167 if (error) 168 prunedepth = depth; 169 } 170 break; 171 case SR_DIRUP: 172 if (depth < prunedepth) { 173 error = lister_dodirup(l, coll, sr, as); 174 if (error) 175 goto bad; 176 } else if (depth == prunedepth) { 177 /* Finished pruning. */ 178 prunedepth = INT_MAX; 179 } 180 depth--; 181 continue; 182 case SR_CHECKOUTLIVE: 183 if (depth < prunedepth) { 184 error = lister_dofile(l, coll, sr); 185 if (error) 186 goto bad; 187 } 188 break; 189 case SR_CHECKOUTDEAD: 190 if (depth < prunedepth) { 191 error = lister_dodead(l, coll, sr); 192 if (error) 193 goto bad; 194 } 195 break; 196 case SR_FILEDEAD: 197 if (depth < prunedepth) { 198 if (!(coll->co_options & CO_CHECKOUTMODE)) { 199 error = lister_dorcsdead(l, coll, sr); 200 if (error) 201 goto bad; 202 } 203 } 204 break; 205 case SR_FILELIVE: 206 if (depth < prunedepth) { 207 if (!(coll->co_options & CO_CHECKOUTMODE)) { 208 error = lister_dorcsfile(l, coll, sr); 209 if (error) 210 goto bad; 211 } 212 } 213 break; 214 } 215 } 216 if (ret == -1) { 217 l->errmsg = status_errmsg(st); 218 error = LISTER_ERR_STATUS; 219 goto bad; 220 } 221 assert(status_eof(st)); 222 assert(depth == 0); 223 error = proto_printf(wr, ".\n"); 224 attrstack_free(as); 225 if (error) 226 return (LISTER_ERR_WRITE); 227 return (0); 228bad: 229 for (i = 0; i < attrstack_size(as); i++) { 230 fa = attrstack_pop(as); 231 fattr_free(fa); 232 } 233 attrstack_free(as); 234 return (error); 235} 236 237/* Handle a directory up entry found in the status file. */ 238static int 239lister_dodirdown(struct lister *l, struct coll *coll, struct statusrec *sr, 240 struct attrstack *as) 241{ 242 struct config *config; 243 struct stream *wr; 244 struct fattr *fa, *fa2; 245 char *path; 246 int error; 247 248 config = l->config; 249 wr = l->wr; 250 if (!globtree_test(coll->co_dirfilter, sr->sr_file)) 251 return (1); 252 if (coll->co_options & CO_TRUSTSTATUSFILE) { 253 fa = fattr_new(FT_DIRECTORY, -1); 254 } else { 255 xasprintf(&path, "%s/%s", coll->co_prefix, sr->sr_file); 256 fa = fattr_frompath(path, FATTR_NOFOLLOW); 257 if (fa == NULL) { 258 /* The directory doesn't exist, prune 259 * everything below it. */ 260 free(path); 261 return (1); 262 } 263 if (fattr_type(fa) == FT_SYMLINK) { 264 fa2 = fattr_frompath(path, FATTR_FOLLOW); 265 if (fa2 != NULL && fattr_type(fa2) == FT_DIRECTORY) { 266 /* XXX - When not in checkout mode, CVSup warns 267 * here about the file being a symlink to a 268 * directory instead of a directory. */ 269 fattr_free(fa); 270 fa = fa2; 271 } else { 272 fattr_free(fa2); 273 } 274 } 275 free(path); 276 } 277 278 if (fattr_type(fa) != FT_DIRECTORY) { 279 fattr_free(fa); 280 /* Report it as something bogus so 281 * that it will be replaced. */ 282 error = proto_printf(wr, "F %s %F\n", pathlast(sr->sr_file), 283 fattr_bogus, config->fasupport, coll->co_attrignore); 284 if (error) 285 return (LISTER_ERR_WRITE); 286 return (1); 287 } 288 289 /* It really is a directory. */ 290 attrstack_push(as, fa); 291 error = proto_printf(wr, "D %s\n", pathlast(sr->sr_file)); 292 if (error) 293 return (LISTER_ERR_WRITE); 294 return (0); 295} 296 297/* Handle a directory up entry found in the status file. */ 298static int 299lister_dodirup(struct lister *l, struct coll *coll, struct statusrec *sr, 300 struct attrstack *as) 301{ 302 struct config *config; 303 const struct fattr *sendattr; 304 struct stream *wr; 305 struct fattr *fa, *fa2; 306 int error; 307 308 config = l->config; 309 wr = l->wr; 310 fa = attrstack_pop(as); 311 if (coll->co_options & CO_TRUSTSTATUSFILE) { 312 fattr_free(fa); 313 fa = sr->sr_clientattr; 314 } 315 316 fa2 = sr->sr_clientattr; 317 if (fattr_equal(fa, fa2)) 318 sendattr = fa; 319 else 320 sendattr = fattr_bogus; 321 error = proto_printf(wr, "U %F\n", sendattr, config->fasupport, 322 coll->co_attrignore); 323 if (error) 324 return (LISTER_ERR_WRITE); 325 if (!(coll->co_options & CO_TRUSTSTATUSFILE)) 326 fattr_free(fa); 327 /* XXX CVSup flushes here for some reason with a comment saying 328 "Be smarter". We don't flush when listing other file types. */ 329 stream_flush(wr); 330 return (0); 331} 332 333/* Handle a checkout live entry found in the status file. */ 334static int 335lister_dofile(struct lister *l, struct coll *coll, struct statusrec *sr) 336{ 337 struct config *config; 338 struct stream *wr; 339 const struct fattr *sendattr, *fa; 340 struct fattr *fa2, *rfa; 341 char *path, *spath; 342 int error; 343 344 if (!globtree_test(coll->co_filefilter, sr->sr_file)) 345 return (0); 346 config = l->config; 347 wr = l->wr; 348 rfa = NULL; 349 sendattr = NULL; 350 error = 0; 351 if (!(coll->co_options & CO_TRUSTSTATUSFILE)) { 352 path = checkoutpath(coll->co_prefix, sr->sr_file); 353 if (path == NULL) { 354 spath = coll_statuspath(coll); 355 xasprintf(&l->errmsg, "Error in \"%s\": " 356 "Invalid filename \"%s\"", spath, sr->sr_file); 357 free(spath); 358 return (LISTER_ERR_STATUS); 359 } 360 rfa = fattr_frompath(path, FATTR_NOFOLLOW); 361 free(path); 362 if (rfa == NULL) { 363 /* 364 * According to the checkouts file we should have 365 * this file but we don't. Maybe the user deleted 366 * the file, or maybe the checkouts file is wrong. 367 * List the file with bogus attributes to cause the 368 * server to get things back in sync again. 369 */ 370 sendattr = fattr_bogus; 371 goto send; 372 } 373 fa = rfa; 374 } else { 375 fa = sr->sr_clientattr; 376 } 377 fa2 = fattr_forcheckout(sr->sr_serverattr, coll->co_umask); 378 if (!fattr_equal(fa, sr->sr_clientattr) || !fattr_equal(fa, fa2) || 379 strcmp(coll->co_tag, sr->sr_tag) != 0 || 380 strcmp(coll->co_date, sr->sr_date) != 0) { 381 /* 382 * The file corresponds to the information we have 383 * recorded about it, and its moded is correct for 384 * the requested umask setting. 385 */ 386 sendattr = fattr_bogus; 387 } else { 388 /* 389 * Either the file has been touched, or we are asking 390 * for a different revision than the one we recorded 391 * information about, or its mode isn't right (because 392 * it was last updated using a version of CVSup that 393 * wasn't so strict about modes). 394 */ 395 sendattr = sr->sr_serverattr; 396 } 397 fattr_free(fa2); 398 if (rfa != NULL) 399 fattr_free(rfa); 400send: 401 error = proto_printf(wr, "F %s %F\n", pathlast(sr->sr_file), sendattr, 402 config->fasupport, coll->co_attrignore); 403 if (error) 404 return (LISTER_ERR_WRITE); 405 return (0); 406} 407 408/* Handle a rcs file live entry found in the status file. */ 409static int 410lister_dorcsfile(struct lister *l, struct coll *coll, struct statusrec *sr) 411{ 412 struct config *config; 413 struct stream *wr; 414 const struct fattr *sendattr; 415 struct fattr *fa; 416 char *path, *spath; 417 size_t len; 418 int error; 419 420 if (!globtree_test(coll->co_filefilter, sr->sr_file)) 421 return (0); 422 config = l->config; 423 wr = l->wr; 424 if (!(coll->co_options & CO_TRUSTSTATUSFILE)) { 425 path = cvspath(coll->co_prefix, sr->sr_file, 0); 426 if (path == NULL) { 427 spath = coll_statuspath(coll); 428 xasprintf(&l->errmsg, "Error in \"%s\": " 429 "Invalid filename \"%s\"", spath, sr->sr_file); 430 free(spath); 431 return (LISTER_ERR_STATUS); 432 } 433 fa = fattr_frompath(path, FATTR_NOFOLLOW); 434 free(path); 435 } else 436 fa = sr->sr_clientattr; 437 if (fa != NULL && fattr_equal(fa, sr->sr_clientattr)) { 438 /* 439 * If the file is an RCS file, we use "loose" equality, so sizes 440 * may disagress because of differences in whitespace. 441 */ 442 if (isrcs(sr->sr_file, &len) && 443 !(coll->co_options & CO_NORCS) && 444 !(coll->co_options & CO_STRICTCHECKRCS)) { 445 fattr_maskout(fa, FA_SIZE); 446 } 447 sendattr = fa; 448 } else { 449 /* 450 * If different, the user may have changed it, so we report 451 * bogus attributes to force a full comparison. 452 */ 453 sendattr = fattr_bogus; 454 } 455 error = proto_printf(wr, "F %s %F\n", pathlast(sr->sr_file), sendattr, 456 config->fasupport, coll->co_attrignore); 457 if (error) 458 return (LISTER_ERR_WRITE); 459 return (0); 460} 461 462/* Handle a checkout dead entry found in the status file. */ 463static int 464lister_dodead(struct lister *l, struct coll *coll, struct statusrec *sr) 465{ 466 struct config *config; 467 struct stream *wr; 468 const struct fattr *sendattr; 469 struct fattr *fa; 470 char *path, *spath; 471 int error; 472 473 if (!globtree_test(coll->co_filefilter, sr->sr_file)) 474 return (0); 475 config = l->config; 476 wr = l->wr; 477 if (!(coll->co_options & CO_TRUSTSTATUSFILE)) { 478 path = checkoutpath(coll->co_prefix, sr->sr_file); 479 if (path == NULL) { 480 spath = coll_statuspath(coll); 481 xasprintf(&l->errmsg, "Error in \"%s\": " 482 "Invalid filename \"%s\"", spath, sr->sr_file); 483 free(spath); 484 return (LISTER_ERR_STATUS); 485 } 486 fa = fattr_frompath(path, FATTR_NOFOLLOW); 487 free(path); 488 if (fa != NULL && fattr_type(fa) != FT_DIRECTORY) { 489 /* 490 * We shouldn't have this file but we do. Report 491 * it to the server, which will either send a 492 * deletion request, of (if the file has come alive) 493 * sent the correct version. 494 */ 495 fattr_free(fa); 496 error = proto_printf(wr, "F %s %F\n", 497 pathlast(sr->sr_file), fattr_bogus, 498 config->fasupport, coll->co_attrignore); 499 if (error) 500 return (LISTER_ERR_WRITE); 501 return (0); 502 } 503 fattr_free(fa); 504 } 505 if (strcmp(coll->co_tag, sr->sr_tag) != 0 || 506 strcmp(coll->co_date, sr->sr_date) != 0) 507 sendattr = fattr_bogus; 508 else 509 sendattr = sr->sr_serverattr; 510 error = proto_printf(wr, "f %s %F\n", pathlast(sr->sr_file), sendattr, 511 config->fasupport, coll->co_attrignore); 512 if (error) 513 return (LISTER_ERR_WRITE); 514 return (0); 515} 516 517/* Handle a rcs file dead entry found in the status file. */ 518static int 519lister_dorcsdead(struct lister *l, struct coll *coll, struct statusrec *sr) 520{ 521 struct config *config; 522 struct stream *wr; 523 const struct fattr *sendattr; 524 struct fattr *fa; 525 char *path, *spath; 526 size_t len; 527 int error; 528 529 if (!globtree_test(coll->co_filefilter, sr->sr_file)) 530 return (0); 531 config = l->config; 532 wr = l->wr; 533 if (!(coll->co_options & CO_TRUSTSTATUSFILE)) { 534 path = cvspath(coll->co_prefix, sr->sr_file, 1); 535 if (path == NULL) { 536 spath = coll_statuspath(coll); 537 xasprintf(&l->errmsg, "Error in \"%s\": " 538 "Invalid filename \"%s\"", spath, sr->sr_file); 539 free(spath); 540 return (LISTER_ERR_STATUS); 541 } 542 fa = fattr_frompath(path, FATTR_NOFOLLOW); 543 free(path); 544 } else 545 fa = sr->sr_clientattr; 546 if (fattr_equal(fa, sr->sr_clientattr)) { 547 /* 548 * If the file is an RCS file, we use "loose" equality, so sizes 549 * may disagress because of differences in whitespace. 550 */ 551 if (isrcs(sr->sr_file, &len) && 552 !(coll->co_options & CO_NORCS) && 553 !(coll->co_options & CO_STRICTCHECKRCS)) { 554 fattr_maskout(fa, FA_SIZE); 555 } 556 sendattr = fa; 557 } else { 558 /* 559 * If different, the user may have changed it, so we report 560 * bogus attributes to force a full comparison. 561 */ 562 sendattr = fattr_bogus; 563 } 564 error = proto_printf(wr, "f %s %F\n", pathlast(sr->sr_file), sendattr, 565 config->fasupport, coll->co_attrignore); 566 if (error) 567 return (LISTER_ERR_WRITE); 568 return (0); 569} 570