lister.c revision 156701
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: vendor/csup/dist/contrib/csup/lister.c 156701 2006-03-14 03:51:13Z mux $ 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 *); 67 68void * 69lister(void *arg) 70{ 71 struct thread_args *args; 72 struct lister lbuf, *l; 73 int error; 74 75 args = arg; 76 l = &lbuf; 77 l->config = args->config; 78 l->wr = args->wr; 79 l->errmsg = NULL; 80 error = lister_batch(l); 81 switch (error) { 82 case LISTER_ERR_WRITE: 83 xasprintf(&args->errmsg, 84 "TreeList failed: Network write failure: %s", 85 strerror(errno)); 86 args->status = STATUS_TRANSIENTFAILURE; 87 break; 88 case LISTER_ERR_STATUS: 89 xasprintf(&args->errmsg, 90 "TreeList failed: %s. Delete it and try again.", 91 l->errmsg); 92 free(l->errmsg); 93 args->status = STATUS_FAILURE; 94 break; 95 default: 96 assert(error == 0); 97 args->status = STATUS_SUCCESS; 98 }; 99 return (NULL); 100} 101 102static int 103lister_batch(struct lister *l) 104{ 105 struct config *config; 106 struct stream *wr; 107 struct status *st; 108 struct coll *coll; 109 int error; 110 111 config = l->config; 112 wr = l->wr; 113 STAILQ_FOREACH(coll, &config->colls, co_next) { 114 if (coll->co_options & CO_SKIP) 115 continue; 116 st = status_open(coll, -1, &l->errmsg); 117 if (st == NULL) 118 return (LISTER_ERR_STATUS); 119 error = proto_printf(wr, "COLL %s %s\n", coll->co_name, 120 coll->co_release); 121 if (error) 122 return (LISTER_ERR_WRITE); 123 stream_flush(wr); 124 if (coll->co_options & CO_COMPRESS) 125 stream_filter_start(wr, STREAM_FILTER_ZLIB, NULL); 126 error = lister_coll(l, coll, st); 127 status_close(st, NULL); 128 if (error) 129 return (error); 130 if (coll->co_options & CO_COMPRESS) 131 stream_filter_stop(wr); 132 stream_flush(wr); 133 } 134 error = proto_printf(wr, ".\n"); 135 if (error) 136 return (LISTER_ERR_WRITE); 137 return (0); 138} 139 140/* List a single collection based on the status file. */ 141static int 142lister_coll(struct lister *l, struct coll *coll, struct status *st) 143{ 144 struct stream *wr; 145 struct attrstack *as; 146 struct statusrec *sr; 147 struct fattr *fa; 148 size_t i; 149 int depth, error, ret, prunedepth; 150 151 wr = l->wr; 152 depth = 0; 153 prunedepth = INT_MAX; 154 as = attrstack_new(); 155 while ((ret = status_get(st, NULL, 0, 0, &sr)) == 1) { 156 switch (sr->sr_type) { 157 case SR_DIRDOWN: 158 depth++; 159 if (depth < prunedepth) { 160 error = lister_dodirdown(l, coll, sr, as); 161 if (error < 0) 162 goto bad; 163 if (error) 164 prunedepth = depth; 165 } 166 break; 167 case SR_DIRUP: 168 if (depth < prunedepth) { 169 error = lister_dodirup(l, coll, sr, as); 170 if (error) 171 goto bad; 172 } else if (depth == prunedepth) { 173 /* Finished pruning. */ 174 prunedepth = INT_MAX; 175 } 176 depth--; 177 continue; 178 case SR_CHECKOUTLIVE: 179 if (depth < prunedepth) { 180 error = lister_dofile(l, coll, sr); 181 if (error) 182 goto bad; 183 } 184 break; 185 case SR_CHECKOUTDEAD: 186 if (depth < prunedepth) { 187 error = lister_dodead(l, coll, sr); 188 if (error) 189 goto bad; 190 } 191 break; 192 } 193 } 194 if (ret == -1) { 195 l->errmsg = status_errmsg(st); 196 error = LISTER_ERR_STATUS; 197 goto bad; 198 } 199 assert(status_eof(st)); 200 assert(depth == 0); 201 error = proto_printf(wr, ".\n"); 202 attrstack_free(as); 203 if (error) 204 return (LISTER_ERR_WRITE); 205 return (0); 206bad: 207 for (i = 0; i < attrstack_size(as); i++) { 208 fa = attrstack_pop(as); 209 fattr_free(fa); 210 } 211 attrstack_free(as); 212 return (error); 213} 214 215/* Handle a directory up entry found in the status file. */ 216static int 217lister_dodirdown(struct lister *l, struct coll *coll, struct statusrec *sr, 218 struct attrstack *as) 219{ 220 struct config *config; 221 struct stream *wr; 222 struct fattr *fa, *fa2; 223 char *path; 224 int error; 225 226 config = l->config; 227 wr = l->wr; 228 if (!globtree_test(coll->co_dirfilter, sr->sr_file)) 229 return (1); 230 if (coll->co_options & CO_TRUSTSTATUSFILE) { 231 fa = fattr_new(FT_DIRECTORY, -1); 232 } else { 233 xasprintf(&path, "%s/%s", coll->co_prefix, sr->sr_file); 234 fa = fattr_frompath(path, FATTR_NOFOLLOW); 235 if (fa == NULL) { 236 /* The directory doesn't exist, prune 237 * everything below it. */ 238 free(path); 239 return (1); 240 } 241 if (fattr_type(fa) == FT_SYMLINK) { 242 fa2 = fattr_frompath(path, FATTR_FOLLOW); 243 if (fa2 != NULL && fattr_type(fa2) == FT_DIRECTORY) { 244 /* XXX - When not in checkout mode, CVSup warns 245 * here about the file being a symlink to a 246 * directory instead of a directory. */ 247 fattr_free(fa); 248 fa = fa2; 249 } else { 250 fattr_free(fa2); 251 } 252 } 253 free(path); 254 } 255 256 if (fattr_type(fa) != FT_DIRECTORY) { 257 fattr_free(fa); 258 /* Report it as something bogus so 259 * that it will be replaced. */ 260 error = proto_printf(wr, "F %s %F\n", pathlast(sr->sr_file), 261 fattr_bogus, config->fasupport, coll->co_attrignore); 262 if (error) 263 return (LISTER_ERR_WRITE); 264 return (1); 265 } 266 267 /* It really is a directory. */ 268 attrstack_push(as, fa); 269 error = proto_printf(wr, "D %s\n", pathlast(sr->sr_file)); 270 if (error) 271 return (LISTER_ERR_WRITE); 272 return (0); 273} 274 275/* Handle a directory up entry found in the status file. */ 276static int 277lister_dodirup(struct lister *l, struct coll *coll, struct statusrec *sr, 278 struct attrstack *as) 279{ 280 struct config *config; 281 const struct fattr *sendattr; 282 struct stream *wr; 283 struct fattr *fa, *fa2; 284 int error; 285 286 config = l->config; 287 wr = l->wr; 288 fa = attrstack_pop(as); 289 if (coll->co_options & CO_TRUSTSTATUSFILE) { 290 fattr_free(fa); 291 fa = sr->sr_clientattr; 292 } 293 294 fa2 = sr->sr_clientattr; 295 if (fattr_equal(fa, fa2)) 296 sendattr = fa; 297 else 298 sendattr = fattr_bogus; 299 error = proto_printf(wr, "U %F\n", sendattr, config->fasupport, 300 coll->co_attrignore); 301 if (error) 302 return (LISTER_ERR_WRITE); 303 if (!(coll->co_options & CO_TRUSTSTATUSFILE)) 304 fattr_free(fa); 305 /* XXX CVSup flushes here for some reason with a comment saying 306 "Be smarter". We don't flush when listing other file types. */ 307 stream_flush(wr); 308 return (0); 309} 310 311/* Handle a checkout live entry found in the status file. */ 312static int 313lister_dofile(struct lister *l, struct coll *coll, struct statusrec *sr) 314{ 315 struct config *config; 316 struct stream *wr; 317 const struct fattr *sendattr, *fa; 318 struct fattr *fa2, *rfa; 319 char *path, *spath; 320 int error; 321 322 if (!globtree_test(coll->co_filefilter, sr->sr_file)) 323 return (0); 324 config = l->config; 325 wr = l->wr; 326 rfa = NULL; 327 sendattr = NULL; 328 error = 0; 329 if (!(coll->co_options & CO_TRUSTSTATUSFILE)) { 330 path = checkoutpath(coll->co_prefix, sr->sr_file); 331 if (path == NULL) { 332 spath = coll_statuspath(coll); 333 xasprintf(&l->errmsg, "Error in \"%s\": " 334 "Invalid filename \"%s\"", spath, sr->sr_file); 335 free(spath); 336 return (LISTER_ERR_STATUS); 337 } 338 rfa = fattr_frompath(path, FATTR_NOFOLLOW); 339 free(path); 340 if (rfa == NULL) { 341 /* 342 * According to the checkouts file we should have 343 * this file but we don't. Maybe the user deleted 344 * the file, or maybe the checkouts file is wrong. 345 * List the file with bogus attributes to cause the 346 * server to get things back in sync again. 347 */ 348 sendattr = fattr_bogus; 349 goto send; 350 } 351 fa = rfa; 352 } else { 353 fa = sr->sr_clientattr; 354 } 355 fa2 = fattr_forcheckout(sr->sr_serverattr, coll->co_umask); 356 if (!fattr_equal(fa, sr->sr_clientattr) || !fattr_equal(fa, fa2) || 357 strcmp(coll->co_tag, sr->sr_tag) != 0 || 358 strcmp(coll->co_date, sr->sr_date) != 0) { 359 /* 360 * The file corresponds to the information we have 361 * recorded about it, and its moded is correct for 362 * the requested umask setting. 363 */ 364 sendattr = fattr_bogus; 365 } else { 366 /* 367 * Either the file has been touched, or we are asking 368 * for a different revision than the one we recorded 369 * information about, or its mode isn't right (because 370 * it was last updated using a version of CVSup that 371 * wasn't so strict about modes). 372 */ 373 sendattr = sr->sr_serverattr; 374 } 375 fattr_free(fa2); 376 if (rfa != NULL) 377 fattr_free(rfa); 378send: 379 error = proto_printf(wr, "F %s %F\n", pathlast(sr->sr_file), sendattr, 380 config->fasupport, coll->co_attrignore); 381 if (error) 382 return (LISTER_ERR_WRITE); 383 return (0); 384} 385 386/* Handle a checkout dead entry found in the status file. */ 387static int 388lister_dodead(struct lister *l, struct coll *coll, struct statusrec *sr) 389{ 390 struct config *config; 391 struct stream *wr; 392 const struct fattr *sendattr; 393 struct fattr *fa; 394 char *path, *spath; 395 int error; 396 397 if (!globtree_test(coll->co_filefilter, sr->sr_file)) 398 return (0); 399 config = l->config; 400 wr = l->wr; 401 if (!(coll->co_options & CO_TRUSTSTATUSFILE)) { 402 path = checkoutpath(coll->co_prefix, sr->sr_file); 403 if (path == NULL) { 404 spath = coll_statuspath(coll); 405 xasprintf(&l->errmsg, "Error in \"%s\": " 406 "Invalid filename \"%s\"", spath, sr->sr_file); 407 free(spath); 408 return (LISTER_ERR_STATUS); 409 } 410 fa = fattr_frompath(path, FATTR_NOFOLLOW); 411 free(path); 412 if (fa != NULL && fattr_type(fa) != FT_DIRECTORY) { 413 /* 414 * We shouldn't have this file but we do. Report 415 * it to the server, which will either send a 416 * deletion request, of (if the file has come alive) 417 * sent the correct version. 418 */ 419 fattr_free(fa); 420 error = proto_printf(wr, "F %s %F\n", 421 pathlast(sr->sr_file), fattr_bogus, 422 config->fasupport, coll->co_attrignore); 423 if (error) 424 return (LISTER_ERR_WRITE); 425 return (0); 426 } 427 fattr_free(fa); 428 } 429 if (strcmp(coll->co_tag, sr->sr_tag) != 0 || 430 strcmp(coll->co_date, sr->sr_date) != 0) 431 sendattr = fattr_bogus; 432 else 433 sendattr = sr->sr_serverattr; 434 error = proto_printf(wr, "f %s %F\n", pathlast(sr->sr_file), sendattr, 435 config->fasupport, coll->co_attrignore); 436 if (error) 437 return (LISTER_ERR_WRITE); 438 return (0); 439} 440