lister.c revision 156701
1177633Sdfr/*- 2177633Sdfr * Copyright (c) 2003-2006, Maxime Henrion <mux@FreeBSD.org> 3177633Sdfr * All rights reserved. 4177633Sdfr * 5177633Sdfr * Redistribution and use in source and binary forms, with or without 6177633Sdfr * modification, are permitted provided that the following conditions 7177633Sdfr * are met: 8177633Sdfr * 1. Redistributions of source code must retain the above copyright 9177633Sdfr * notice, this list of conditions and the following disclaimer. 10177633Sdfr * 2. Redistributions in binary form must reproduce the above copyright 11177633Sdfr * notice, this list of conditions and the following disclaimer in the 12177633Sdfr * documentation and/or other materials provided with the distribution. 13177633Sdfr * 14177633Sdfr * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 15177633Sdfr * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16177633Sdfr * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17177633Sdfr * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 18177633Sdfr * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19177633Sdfr * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 20177633Sdfr * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 21177633Sdfr * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 22177633Sdfr * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 23177633Sdfr * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 24177633Sdfr * SUCH DAMAGE. 25177633Sdfr * 26177633Sdfr * $FreeBSD: vendor/csup/dist/contrib/csup/lister.c 156701 2006-03-14 03:51:13Z mux $ 27177633Sdfr */ 28177633Sdfr 29177633Sdfr#include <assert.h> 30177633Sdfr#include <errno.h> 31177633Sdfr#include <limits.h> 32177633Sdfr#include <stdio.h> 33177633Sdfr#include <stdlib.h> 34177633Sdfr#include <string.h> 35177633Sdfr 36177633Sdfr#include "attrstack.h" 37177633Sdfr#include "config.h" 38177633Sdfr#include "fattr.h" 39177633Sdfr#include "globtree.h" 40177633Sdfr#include "lister.h" 41177633Sdfr#include "misc.h" 42177633Sdfr#include "mux.h" 43177633Sdfr#include "proto.h" 44177633Sdfr#include "status.h" 45177633Sdfr#include "stream.h" 46177633Sdfr 47177633Sdfr/* Internal error codes. */ 48177633Sdfr#define LISTER_ERR_WRITE (-1) /* Error writing to server. */ 49177633Sdfr#define LISTER_ERR_STATUS (-2) /* Status file error in lstr->errmsg. */ 50177633Sdfr 51177633Sdfrstruct lister { 52177633Sdfr struct config *config; 53177633Sdfr struct stream *wr; 54177633Sdfr char *errmsg; 55177633Sdfr}; 56177633Sdfr 57177633Sdfrstatic int lister_batch(struct lister *); 58177633Sdfrstatic int lister_coll(struct lister *, struct coll *, struct status *); 59177633Sdfrstatic int lister_dodirdown(struct lister *, struct coll *, 60177633Sdfr struct statusrec *, struct attrstack *as); 61177633Sdfrstatic int lister_dodirup(struct lister *, struct coll *, 62177633Sdfr struct statusrec *, struct attrstack *as); 63177633Sdfrstatic int lister_dofile(struct lister *, struct coll *, 64177633Sdfr struct statusrec *); 65177633Sdfrstatic int lister_dodead(struct lister *, struct coll *, 66177633Sdfr struct statusrec *); 67177633Sdfr 68177633Sdfrvoid * 69177633Sdfrlister(void *arg) 70177633Sdfr{ 71177633Sdfr struct thread_args *args; 72177633Sdfr struct lister lbuf, *l; 73177633Sdfr int error; 74177633Sdfr 75177633Sdfr args = arg; 76177633Sdfr l = &lbuf; 77177633Sdfr l->config = args->config; 78177633Sdfr l->wr = args->wr; 79177633Sdfr l->errmsg = NULL; 80177633Sdfr error = lister_batch(l); 81177633Sdfr switch (error) { 82177633Sdfr case LISTER_ERR_WRITE: 83177633Sdfr xasprintf(&args->errmsg, 84177633Sdfr "TreeList failed: Network write failure: %s", 85177633Sdfr strerror(errno)); 86177633Sdfr args->status = STATUS_TRANSIENTFAILURE; 87177633Sdfr break; 88177633Sdfr case LISTER_ERR_STATUS: 89177633Sdfr xasprintf(&args->errmsg, 90177633Sdfr "TreeList failed: %s. Delete it and try again.", 91177633Sdfr l->errmsg); 92177633Sdfr free(l->errmsg); 93177633Sdfr args->status = STATUS_FAILURE; 94177633Sdfr break; 95177633Sdfr default: 96177633Sdfr assert(error == 0); 97177633Sdfr args->status = STATUS_SUCCESS; 98177633Sdfr }; 99177633Sdfr return (NULL); 100177633Sdfr} 101177633Sdfr 102177633Sdfrstatic int 103177633Sdfrlister_batch(struct lister *l) 104177633Sdfr{ 105177633Sdfr struct config *config; 106177633Sdfr struct stream *wr; 107177633Sdfr struct status *st; 108177633Sdfr struct coll *coll; 109177633Sdfr int error; 110177633Sdfr 111177633Sdfr config = l->config; 112177633Sdfr wr = l->wr; 113177633Sdfr STAILQ_FOREACH(coll, &config->colls, co_next) { 114177633Sdfr if (coll->co_options & CO_SKIP) 115177633Sdfr continue; 116177633Sdfr st = status_open(coll, -1, &l->errmsg); 117177633Sdfr if (st == NULL) 118177633Sdfr return (LISTER_ERR_STATUS); 119177633Sdfr error = proto_printf(wr, "COLL %s %s\n", coll->co_name, 120177633Sdfr coll->co_release); 121177633Sdfr if (error) 122177633Sdfr return (LISTER_ERR_WRITE); 123177633Sdfr stream_flush(wr); 124177633Sdfr if (coll->co_options & CO_COMPRESS) 125177633Sdfr stream_filter_start(wr, STREAM_FILTER_ZLIB, NULL); 126177633Sdfr error = lister_coll(l, coll, st); 127177633Sdfr status_close(st, NULL); 128177633Sdfr if (error) 129177633Sdfr return (error); 130177633Sdfr if (coll->co_options & CO_COMPRESS) 131177633Sdfr stream_filter_stop(wr); 132177633Sdfr stream_flush(wr); 133177633Sdfr } 134177633Sdfr error = proto_printf(wr, ".\n"); 135177633Sdfr if (error) 136177633Sdfr return (LISTER_ERR_WRITE); 137177633Sdfr return (0); 138177633Sdfr} 139177633Sdfr 140177633Sdfr/* List a single collection based on the status file. */ 141177633Sdfrstatic int 142177633Sdfrlister_coll(struct lister *l, struct coll *coll, struct status *st) 143177633Sdfr{ 144177633Sdfr struct stream *wr; 145177633Sdfr struct attrstack *as; 146177633Sdfr struct statusrec *sr; 147177633Sdfr struct fattr *fa; 148177633Sdfr size_t i; 149177633Sdfr int depth, error, ret, prunedepth; 150177633Sdfr 151177633Sdfr wr = l->wr; 152177633Sdfr depth = 0; 153177633Sdfr prunedepth = INT_MAX; 154177633Sdfr as = attrstack_new(); 155177633Sdfr while ((ret = status_get(st, NULL, 0, 0, &sr)) == 1) { 156177633Sdfr switch (sr->sr_type) { 157177633Sdfr case SR_DIRDOWN: 158177633Sdfr depth++; 159177633Sdfr if (depth < prunedepth) { 160177633Sdfr error = lister_dodirdown(l, coll, sr, as); 161177633Sdfr if (error < 0) 162177633Sdfr goto bad; 163177633Sdfr if (error) 164177633Sdfr prunedepth = depth; 165177633Sdfr } 166177633Sdfr break; 167177633Sdfr case SR_DIRUP: 168177633Sdfr if (depth < prunedepth) { 169177633Sdfr error = lister_dodirup(l, coll, sr, as); 170177633Sdfr if (error) 171177633Sdfr goto bad; 172177633Sdfr } else if (depth == prunedepth) { 173177633Sdfr /* Finished pruning. */ 174177633Sdfr prunedepth = INT_MAX; 175177633Sdfr } 176177633Sdfr depth--; 177177633Sdfr continue; 178177633Sdfr case SR_CHECKOUTLIVE: 179177633Sdfr if (depth < prunedepth) { 180177633Sdfr error = lister_dofile(l, coll, sr); 181177633Sdfr if (error) 182177633Sdfr goto bad; 183177633Sdfr } 184177633Sdfr break; 185177633Sdfr case SR_CHECKOUTDEAD: 186177633Sdfr if (depth < prunedepth) { 187177633Sdfr error = lister_dodead(l, coll, sr); 188177633Sdfr if (error) 189177633Sdfr goto bad; 190177633Sdfr } 191177633Sdfr break; 192177633Sdfr } 193177633Sdfr } 194177633Sdfr if (ret == -1) { 195177633Sdfr l->errmsg = status_errmsg(st); 196177633Sdfr error = LISTER_ERR_STATUS; 197177633Sdfr goto bad; 198177633Sdfr } 199177633Sdfr assert(status_eof(st)); 200177633Sdfr assert(depth == 0); 201177633Sdfr error = proto_printf(wr, ".\n"); 202177633Sdfr attrstack_free(as); 203177633Sdfr if (error) 204177633Sdfr return (LISTER_ERR_WRITE); 205177633Sdfr return (0); 206177633Sdfrbad: 207177633Sdfr for (i = 0; i < attrstack_size(as); i++) { 208177633Sdfr fa = attrstack_pop(as); 209177633Sdfr fattr_free(fa); 210177633Sdfr } 211177633Sdfr attrstack_free(as); 212177633Sdfr return (error); 213177633Sdfr} 214177633Sdfr 215177633Sdfr/* Handle a directory up entry found in the status file. */ 216177633Sdfrstatic int 217177633Sdfrlister_dodirdown(struct lister *l, struct coll *coll, struct statusrec *sr, 218177633Sdfr struct attrstack *as) 219177633Sdfr{ 220177633Sdfr struct config *config; 221177633Sdfr struct stream *wr; 222177633Sdfr struct fattr *fa, *fa2; 223177633Sdfr char *path; 224177633Sdfr int error; 225177633Sdfr 226177633Sdfr config = l->config; 227177633Sdfr wr = l->wr; 228177633Sdfr if (!globtree_test(coll->co_dirfilter, sr->sr_file)) 229177633Sdfr return (1); 230177633Sdfr if (coll->co_options & CO_TRUSTSTATUSFILE) { 231177633Sdfr fa = fattr_new(FT_DIRECTORY, -1); 232177633Sdfr } else { 233177633Sdfr xasprintf(&path, "%s/%s", coll->co_prefix, sr->sr_file); 234177633Sdfr fa = fattr_frompath(path, FATTR_NOFOLLOW); 235177633Sdfr if (fa == NULL) { 236177633Sdfr /* The directory doesn't exist, prune 237177633Sdfr * everything below it. */ 238177633Sdfr free(path); 239177633Sdfr return (1); 240177633Sdfr } 241177633Sdfr if (fattr_type(fa) == FT_SYMLINK) { 242177633Sdfr fa2 = fattr_frompath(path, FATTR_FOLLOW); 243177633Sdfr if (fa2 != NULL && fattr_type(fa2) == FT_DIRECTORY) { 244177633Sdfr /* XXX - When not in checkout mode, CVSup warns 245177633Sdfr * here about the file being a symlink to a 246177633Sdfr * directory instead of a directory. */ 247177633Sdfr fattr_free(fa); 248177633Sdfr fa = fa2; 249177633Sdfr } else { 250177633Sdfr fattr_free(fa2); 251177633Sdfr } 252177633Sdfr } 253177633Sdfr free(path); 254177633Sdfr } 255177633Sdfr 256177633Sdfr if (fattr_type(fa) != FT_DIRECTORY) { 257177633Sdfr fattr_free(fa); 258177633Sdfr /* Report it as something bogus so 259177633Sdfr * that it will be replaced. */ 260177633Sdfr error = proto_printf(wr, "F %s %F\n", pathlast(sr->sr_file), 261177633Sdfr fattr_bogus, config->fasupport, coll->co_attrignore); 262177633Sdfr if (error) 263177633Sdfr return (LISTER_ERR_WRITE); 264177633Sdfr return (1); 265177633Sdfr } 266177633Sdfr 267177633Sdfr /* It really is a directory. */ 268177633Sdfr attrstack_push(as, fa); 269177633Sdfr error = proto_printf(wr, "D %s\n", pathlast(sr->sr_file)); 270177633Sdfr if (error) 271177633Sdfr return (LISTER_ERR_WRITE); 272177633Sdfr return (0); 273177633Sdfr} 274177633Sdfr 275177633Sdfr/* Handle a directory up entry found in the status file. */ 276177633Sdfrstatic int 277177633Sdfrlister_dodirup(struct lister *l, struct coll *coll, struct statusrec *sr, 278177633Sdfr struct attrstack *as) 279177633Sdfr{ 280177633Sdfr struct config *config; 281177633Sdfr const struct fattr *sendattr; 282177633Sdfr struct stream *wr; 283177633Sdfr struct fattr *fa, *fa2; 284177633Sdfr int error; 285177633Sdfr 286177633Sdfr config = l->config; 287177633Sdfr wr = l->wr; 288177633Sdfr fa = attrstack_pop(as); 289177633Sdfr if (coll->co_options & CO_TRUSTSTATUSFILE) { 290177633Sdfr fattr_free(fa); 291177633Sdfr fa = sr->sr_clientattr; 292177633Sdfr } 293177633Sdfr 294177633Sdfr fa2 = sr->sr_clientattr; 295177633Sdfr if (fattr_equal(fa, fa2)) 296177633Sdfr sendattr = fa; 297177633Sdfr else 298177633Sdfr sendattr = fattr_bogus; 299177633Sdfr error = proto_printf(wr, "U %F\n", sendattr, config->fasupport, 300177633Sdfr coll->co_attrignore); 301177633Sdfr if (error) 302177633Sdfr return (LISTER_ERR_WRITE); 303177633Sdfr if (!(coll->co_options & CO_TRUSTSTATUSFILE)) 304177633Sdfr fattr_free(fa); 305177633Sdfr /* XXX CVSup flushes here for some reason with a comment saying 306177633Sdfr "Be smarter". We don't flush when listing other file types. */ 307177633Sdfr stream_flush(wr); 308177633Sdfr return (0); 309177633Sdfr} 310177633Sdfr 311177633Sdfr/* Handle a checkout live entry found in the status file. */ 312177633Sdfrstatic int 313177633Sdfrlister_dofile(struct lister *l, struct coll *coll, struct statusrec *sr) 314177633Sdfr{ 315177633Sdfr struct config *config; 316177633Sdfr struct stream *wr; 317177633Sdfr const struct fattr *sendattr, *fa; 318177633Sdfr struct fattr *fa2, *rfa; 319177633Sdfr char *path, *spath; 320177633Sdfr int error; 321177633Sdfr 322177633Sdfr if (!globtree_test(coll->co_filefilter, sr->sr_file)) 323177633Sdfr return (0); 324177633Sdfr config = l->config; 325177633Sdfr wr = l->wr; 326177633Sdfr rfa = NULL; 327177633Sdfr sendattr = NULL; 328177633Sdfr error = 0; 329177633Sdfr if (!(coll->co_options & CO_TRUSTSTATUSFILE)) { 330177633Sdfr path = checkoutpath(coll->co_prefix, sr->sr_file); 331177633Sdfr if (path == NULL) { 332177633Sdfr spath = coll_statuspath(coll); 333177633Sdfr xasprintf(&l->errmsg, "Error in \"%s\": " 334177633Sdfr "Invalid filename \"%s\"", spath, sr->sr_file); 335177633Sdfr free(spath); 336177633Sdfr return (LISTER_ERR_STATUS); 337177633Sdfr } 338177633Sdfr rfa = fattr_frompath(path, FATTR_NOFOLLOW); 339177633Sdfr free(path); 340177633Sdfr if (rfa == NULL) { 341177633Sdfr /* 342177633Sdfr * According to the checkouts file we should have 343177633Sdfr * this file but we don't. Maybe the user deleted 344177633Sdfr * the file, or maybe the checkouts file is wrong. 345177633Sdfr * List the file with bogus attributes to cause the 346177633Sdfr * server to get things back in sync again. 347177633Sdfr */ 348177633Sdfr sendattr = fattr_bogus; 349177633Sdfr goto send; 350177633Sdfr } 351177633Sdfr fa = rfa; 352177633Sdfr } else { 353177633Sdfr fa = sr->sr_clientattr; 354177633Sdfr } 355177633Sdfr fa2 = fattr_forcheckout(sr->sr_serverattr, coll->co_umask); 356177633Sdfr if (!fattr_equal(fa, sr->sr_clientattr) || !fattr_equal(fa, fa2) || 357177633Sdfr strcmp(coll->co_tag, sr->sr_tag) != 0 || 358177633Sdfr strcmp(coll->co_date, sr->sr_date) != 0) { 359177633Sdfr /* 360177633Sdfr * The file corresponds to the information we have 361177633Sdfr * recorded about it, and its moded is correct for 362177633Sdfr * the requested umask setting. 363177633Sdfr */ 364177633Sdfr sendattr = fattr_bogus; 365177633Sdfr } else { 366177633Sdfr /* 367177633Sdfr * Either the file has been touched, or we are asking 368177633Sdfr * for a different revision than the one we recorded 369177633Sdfr * information about, or its mode isn't right (because 370177633Sdfr * it was last updated using a version of CVSup that 371177633Sdfr * wasn't so strict about modes). 372177633Sdfr */ 373177633Sdfr sendattr = sr->sr_serverattr; 374177633Sdfr } 375177633Sdfr fattr_free(fa2); 376177633Sdfr if (rfa != NULL) 377177633Sdfr fattr_free(rfa); 378177633Sdfrsend: 379177633Sdfr error = proto_printf(wr, "F %s %F\n", pathlast(sr->sr_file), sendattr, 380177633Sdfr config->fasupport, coll->co_attrignore); 381177633Sdfr if (error) 382177633Sdfr return (LISTER_ERR_WRITE); 383177633Sdfr return (0); 384177633Sdfr} 385177633Sdfr 386177633Sdfr/* Handle a checkout dead entry found in the status file. */ 387177633Sdfrstatic int 388177633Sdfrlister_dodead(struct lister *l, struct coll *coll, struct statusrec *sr) 389177633Sdfr{ 390177633Sdfr struct config *config; 391177633Sdfr struct stream *wr; 392177633Sdfr const struct fattr *sendattr; 393177633Sdfr struct fattr *fa; 394177633Sdfr char *path, *spath; 395177633Sdfr int error; 396177633Sdfr 397177633Sdfr if (!globtree_test(coll->co_filefilter, sr->sr_file)) 398177633Sdfr return (0); 399177633Sdfr config = l->config; 400177633Sdfr wr = l->wr; 401177633Sdfr if (!(coll->co_options & CO_TRUSTSTATUSFILE)) { 402177633Sdfr path = checkoutpath(coll->co_prefix, sr->sr_file); 403177633Sdfr if (path == NULL) { 404177633Sdfr spath = coll_statuspath(coll); 405177633Sdfr xasprintf(&l->errmsg, "Error in \"%s\": " 406177633Sdfr "Invalid filename \"%s\"", spath, sr->sr_file); 407177633Sdfr free(spath); 408177633Sdfr return (LISTER_ERR_STATUS); 409177633Sdfr } 410177633Sdfr fa = fattr_frompath(path, FATTR_NOFOLLOW); 411177633Sdfr free(path); 412177633Sdfr if (fa != NULL && fattr_type(fa) != FT_DIRECTORY) { 413177633Sdfr /* 414177633Sdfr * We shouldn't have this file but we do. Report 415177633Sdfr * it to the server, which will either send a 416177633Sdfr * deletion request, of (if the file has come alive) 417177633Sdfr * sent the correct version. 418177633Sdfr */ 419177633Sdfr fattr_free(fa); 420177633Sdfr error = proto_printf(wr, "F %s %F\n", 421177633Sdfr pathlast(sr->sr_file), fattr_bogus, 422177633Sdfr config->fasupport, coll->co_attrignore); 423177633Sdfr if (error) 424177633Sdfr return (LISTER_ERR_WRITE); 425177633Sdfr return (0); 426177633Sdfr } 427177633Sdfr fattr_free(fa); 428177633Sdfr } 429177633Sdfr if (strcmp(coll->co_tag, sr->sr_tag) != 0 || 430177633Sdfr strcmp(coll->co_date, sr->sr_date) != 0) 431177633Sdfr sendattr = fattr_bogus; 432177633Sdfr else 433177633Sdfr sendattr = sr->sr_serverattr; 434177633Sdfr error = proto_printf(wr, "f %s %F\n", pathlast(sr->sr_file), sendattr, 435177633Sdfr config->fasupport, coll->co_attrignore); 436177633Sdfr if (error) 437177633Sdfr return (LISTER_ERR_WRITE); 438177633Sdfr return (0); 439177633Sdfr} 440177633Sdfr