1156230Smux/*- 2156230Smux * Copyright (c) 2003-2006, Maxime Henrion <mux@FreeBSD.org> 3156230Smux * All rights reserved. 4156230Smux * 5156230Smux * Redistribution and use in source and binary forms, with or without 6156230Smux * modification, are permitted provided that the following conditions 7156230Smux * are met: 8156230Smux * 1. Redistributions of source code must retain the above copyright 9156230Smux * notice, this list of conditions and the following disclaimer. 10156230Smux * 2. Redistributions in binary form must reproduce the above copyright 11156230Smux * notice, this list of conditions and the following disclaimer in the 12156230Smux * documentation and/or other materials provided with the distribution. 13156230Smux * 14156230Smux * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 15156230Smux * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16156230Smux * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17156230Smux * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 18156230Smux * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19156230Smux * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 20156230Smux * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 21156230Smux * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 22156230Smux * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 23156230Smux * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 24156230Smux * SUCH DAMAGE. 25156230Smux * 26156230Smux * $FreeBSD$ 27156230Smux */ 28156230Smux 29156230Smux#include <assert.h> 30156230Smux#include <errno.h> 31156230Smux#include <limits.h> 32156230Smux#include <stdio.h> 33156230Smux#include <stdlib.h> 34156230Smux#include <string.h> 35156230Smux 36156230Smux#include "attrstack.h" 37156230Smux#include "config.h" 38156230Smux#include "fattr.h" 39156230Smux#include "globtree.h" 40156230Smux#include "lister.h" 41156230Smux#include "misc.h" 42156230Smux#include "mux.h" 43156230Smux#include "proto.h" 44156230Smux#include "status.h" 45156230Smux#include "stream.h" 46156230Smux 47156230Smux/* Internal error codes. */ 48156230Smux#define LISTER_ERR_WRITE (-1) /* Error writing to server. */ 49156230Smux#define LISTER_ERR_STATUS (-2) /* Status file error in lstr->errmsg. */ 50156230Smux 51156230Smuxstruct lister { 52156230Smux struct config *config; 53156230Smux struct stream *wr; 54156230Smux char *errmsg; 55156230Smux}; 56156230Smux 57156230Smuxstatic int lister_batch(struct lister *); 58156230Smuxstatic int lister_coll(struct lister *, struct coll *, struct status *); 59156230Smuxstatic int lister_dodirdown(struct lister *, struct coll *, 60156230Smux struct statusrec *, struct attrstack *as); 61156230Smuxstatic int lister_dodirup(struct lister *, struct coll *, 62156230Smux struct statusrec *, struct attrstack *as); 63156230Smuxstatic int lister_dofile(struct lister *, struct coll *, 64156230Smux struct statusrec *); 65156230Smuxstatic int lister_dodead(struct lister *, struct coll *, 66156230Smux struct statusrec *); 67186781Slulfstatic int lister_dorcsfile(struct lister *, struct coll *, 68186781Slulf struct statusrec *); 69186781Slulfstatic int lister_dorcsdead(struct lister *, struct coll *, 70186781Slulf struct statusrec *); 71156230Smux 72156230Smuxvoid * 73156230Smuxlister(void *arg) 74156230Smux{ 75156230Smux struct thread_args *args; 76156230Smux struct lister lbuf, *l; 77156230Smux int error; 78156230Smux 79156230Smux args = arg; 80156230Smux l = &lbuf; 81156230Smux l->config = args->config; 82156230Smux l->wr = args->wr; 83156230Smux l->errmsg = NULL; 84156230Smux error = lister_batch(l); 85156230Smux switch (error) { 86156230Smux case LISTER_ERR_WRITE: 87156230Smux xasprintf(&args->errmsg, 88156230Smux "TreeList failed: Network write failure: %s", 89156230Smux strerror(errno)); 90156230Smux args->status = STATUS_TRANSIENTFAILURE; 91156230Smux break; 92156230Smux case LISTER_ERR_STATUS: 93156230Smux xasprintf(&args->errmsg, 94156230Smux "TreeList failed: %s. Delete it and try again.", 95156230Smux l->errmsg); 96156230Smux free(l->errmsg); 97156230Smux args->status = STATUS_FAILURE; 98156230Smux break; 99156230Smux default: 100156230Smux assert(error == 0); 101156230Smux args->status = STATUS_SUCCESS; 102156230Smux }; 103156230Smux return (NULL); 104156230Smux} 105156230Smux 106156230Smuxstatic int 107156230Smuxlister_batch(struct lister *l) 108156230Smux{ 109156230Smux struct config *config; 110156230Smux struct stream *wr; 111156230Smux struct status *st; 112156230Smux struct coll *coll; 113156230Smux int error; 114156230Smux 115156230Smux config = l->config; 116156230Smux wr = l->wr; 117156230Smux STAILQ_FOREACH(coll, &config->colls, co_next) { 118156230Smux if (coll->co_options & CO_SKIP) 119156230Smux continue; 120156230Smux st = status_open(coll, -1, &l->errmsg); 121156230Smux if (st == NULL) 122156230Smux return (LISTER_ERR_STATUS); 123156230Smux error = proto_printf(wr, "COLL %s %s\n", coll->co_name, 124156230Smux coll->co_release); 125156230Smux if (error) 126156230Smux return (LISTER_ERR_WRITE); 127156230Smux stream_flush(wr); 128156230Smux if (coll->co_options & CO_COMPRESS) 129156230Smux stream_filter_start(wr, STREAM_FILTER_ZLIB, NULL); 130156230Smux error = lister_coll(l, coll, st); 131156230Smux status_close(st, NULL); 132156230Smux if (error) 133156230Smux return (error); 134156230Smux if (coll->co_options & CO_COMPRESS) 135156230Smux stream_filter_stop(wr); 136156230Smux stream_flush(wr); 137156230Smux } 138156230Smux error = proto_printf(wr, ".\n"); 139156230Smux if (error) 140156230Smux return (LISTER_ERR_WRITE); 141156230Smux return (0); 142156230Smux} 143156230Smux 144156230Smux/* List a single collection based on the status file. */ 145156230Smuxstatic int 146156230Smuxlister_coll(struct lister *l, struct coll *coll, struct status *st) 147156230Smux{ 148156230Smux struct stream *wr; 149156230Smux struct attrstack *as; 150156230Smux struct statusrec *sr; 151156230Smux struct fattr *fa; 152156701Smux size_t i; 153156230Smux int depth, error, ret, prunedepth; 154156230Smux 155156230Smux wr = l->wr; 156156230Smux depth = 0; 157156230Smux prunedepth = INT_MAX; 158156230Smux as = attrstack_new(); 159156230Smux while ((ret = status_get(st, NULL, 0, 0, &sr)) == 1) { 160156230Smux switch (sr->sr_type) { 161156230Smux case SR_DIRDOWN: 162156230Smux depth++; 163156230Smux if (depth < prunedepth) { 164156230Smux error = lister_dodirdown(l, coll, sr, as); 165156230Smux if (error < 0) 166156230Smux goto bad; 167156230Smux if (error) 168156230Smux prunedepth = depth; 169156230Smux } 170156230Smux break; 171156230Smux case SR_DIRUP: 172156230Smux if (depth < prunedepth) { 173156230Smux error = lister_dodirup(l, coll, sr, as); 174156230Smux if (error) 175156230Smux goto bad; 176156230Smux } else if (depth == prunedepth) { 177156230Smux /* Finished pruning. */ 178156230Smux prunedepth = INT_MAX; 179156230Smux } 180156230Smux depth--; 181156230Smux continue; 182156230Smux case SR_CHECKOUTLIVE: 183156230Smux if (depth < prunedepth) { 184156230Smux error = lister_dofile(l, coll, sr); 185156230Smux if (error) 186156230Smux goto bad; 187156230Smux } 188156230Smux break; 189156230Smux case SR_CHECKOUTDEAD: 190156230Smux if (depth < prunedepth) { 191156230Smux error = lister_dodead(l, coll, sr); 192156230Smux if (error) 193156230Smux goto bad; 194156230Smux } 195156230Smux break; 196186781Slulf case SR_FILEDEAD: 197186781Slulf if (depth < prunedepth) { 198186781Slulf if (!(coll->co_options & CO_CHECKOUTMODE)) { 199186781Slulf error = lister_dorcsdead(l, coll, sr); 200186781Slulf if (error) 201186781Slulf goto bad; 202186781Slulf } 203186781Slulf } 204186781Slulf break; 205186781Slulf case SR_FILELIVE: 206186781Slulf if (depth < prunedepth) { 207186781Slulf if (!(coll->co_options & CO_CHECKOUTMODE)) { 208186781Slulf error = lister_dorcsfile(l, coll, sr); 209186781Slulf if (error) 210186781Slulf goto bad; 211186781Slulf } 212186781Slulf } 213186781Slulf break; 214156230Smux } 215156230Smux } 216156230Smux if (ret == -1) { 217156230Smux l->errmsg = status_errmsg(st); 218156230Smux error = LISTER_ERR_STATUS; 219156230Smux goto bad; 220156230Smux } 221156230Smux assert(status_eof(st)); 222156230Smux assert(depth == 0); 223156230Smux error = proto_printf(wr, ".\n"); 224156230Smux attrstack_free(as); 225156230Smux if (error) 226156230Smux return (LISTER_ERR_WRITE); 227156230Smux return (0); 228156230Smuxbad: 229156701Smux for (i = 0; i < attrstack_size(as); i++) { 230156230Smux fa = attrstack_pop(as); 231156230Smux fattr_free(fa); 232156230Smux } 233156230Smux attrstack_free(as); 234156230Smux return (error); 235156230Smux} 236156230Smux 237156230Smux/* Handle a directory up entry found in the status file. */ 238156230Smuxstatic int 239156230Smuxlister_dodirdown(struct lister *l, struct coll *coll, struct statusrec *sr, 240156230Smux struct attrstack *as) 241156230Smux{ 242156230Smux struct config *config; 243156230Smux struct stream *wr; 244156230Smux struct fattr *fa, *fa2; 245156230Smux char *path; 246156230Smux int error; 247156230Smux 248156230Smux config = l->config; 249156230Smux wr = l->wr; 250156230Smux if (!globtree_test(coll->co_dirfilter, sr->sr_file)) 251156230Smux return (1); 252156230Smux if (coll->co_options & CO_TRUSTSTATUSFILE) { 253156230Smux fa = fattr_new(FT_DIRECTORY, -1); 254156230Smux } else { 255156230Smux xasprintf(&path, "%s/%s", coll->co_prefix, sr->sr_file); 256156230Smux fa = fattr_frompath(path, FATTR_NOFOLLOW); 257156230Smux if (fa == NULL) { 258156230Smux /* The directory doesn't exist, prune 259156230Smux * everything below it. */ 260156230Smux free(path); 261156230Smux return (1); 262156230Smux } 263156230Smux if (fattr_type(fa) == FT_SYMLINK) { 264156230Smux fa2 = fattr_frompath(path, FATTR_FOLLOW); 265156230Smux if (fa2 != NULL && fattr_type(fa2) == FT_DIRECTORY) { 266156230Smux /* XXX - When not in checkout mode, CVSup warns 267156230Smux * here about the file being a symlink to a 268156230Smux * directory instead of a directory. */ 269156230Smux fattr_free(fa); 270156230Smux fa = fa2; 271156230Smux } else { 272156230Smux fattr_free(fa2); 273156230Smux } 274156230Smux } 275156230Smux free(path); 276156230Smux } 277156230Smux 278156230Smux if (fattr_type(fa) != FT_DIRECTORY) { 279156230Smux fattr_free(fa); 280156230Smux /* Report it as something bogus so 281156230Smux * that it will be replaced. */ 282156230Smux error = proto_printf(wr, "F %s %F\n", pathlast(sr->sr_file), 283156230Smux fattr_bogus, config->fasupport, coll->co_attrignore); 284156230Smux if (error) 285156230Smux return (LISTER_ERR_WRITE); 286156230Smux return (1); 287156230Smux } 288156230Smux 289156230Smux /* It really is a directory. */ 290156230Smux attrstack_push(as, fa); 291156230Smux error = proto_printf(wr, "D %s\n", pathlast(sr->sr_file)); 292156230Smux if (error) 293156230Smux return (LISTER_ERR_WRITE); 294156230Smux return (0); 295156230Smux} 296156230Smux 297156230Smux/* Handle a directory up entry found in the status file. */ 298156230Smuxstatic int 299156230Smuxlister_dodirup(struct lister *l, struct coll *coll, struct statusrec *sr, 300156230Smux struct attrstack *as) 301156230Smux{ 302156230Smux struct config *config; 303156230Smux const struct fattr *sendattr; 304156230Smux struct stream *wr; 305156230Smux struct fattr *fa, *fa2; 306156230Smux int error; 307156230Smux 308156230Smux config = l->config; 309156230Smux wr = l->wr; 310156230Smux fa = attrstack_pop(as); 311156230Smux if (coll->co_options & CO_TRUSTSTATUSFILE) { 312156230Smux fattr_free(fa); 313156230Smux fa = sr->sr_clientattr; 314156230Smux } 315156230Smux 316156230Smux fa2 = sr->sr_clientattr; 317156230Smux if (fattr_equal(fa, fa2)) 318156230Smux sendattr = fa; 319156230Smux else 320156230Smux sendattr = fattr_bogus; 321156230Smux error = proto_printf(wr, "U %F\n", sendattr, config->fasupport, 322156230Smux coll->co_attrignore); 323156230Smux if (error) 324156230Smux return (LISTER_ERR_WRITE); 325156230Smux if (!(coll->co_options & CO_TRUSTSTATUSFILE)) 326156230Smux fattr_free(fa); 327156230Smux /* XXX CVSup flushes here for some reason with a comment saying 328156230Smux "Be smarter". We don't flush when listing other file types. */ 329156230Smux stream_flush(wr); 330156230Smux return (0); 331156230Smux} 332156230Smux 333156230Smux/* Handle a checkout live entry found in the status file. */ 334156230Smuxstatic int 335156230Smuxlister_dofile(struct lister *l, struct coll *coll, struct statusrec *sr) 336156230Smux{ 337156230Smux struct config *config; 338156230Smux struct stream *wr; 339156230Smux const struct fattr *sendattr, *fa; 340156230Smux struct fattr *fa2, *rfa; 341156230Smux char *path, *spath; 342156230Smux int error; 343156230Smux 344156230Smux if (!globtree_test(coll->co_filefilter, sr->sr_file)) 345156230Smux return (0); 346156230Smux config = l->config; 347156230Smux wr = l->wr; 348156230Smux rfa = NULL; 349156230Smux sendattr = NULL; 350156230Smux error = 0; 351156230Smux if (!(coll->co_options & CO_TRUSTSTATUSFILE)) { 352156230Smux path = checkoutpath(coll->co_prefix, sr->sr_file); 353156230Smux if (path == NULL) { 354156230Smux spath = coll_statuspath(coll); 355156230Smux xasprintf(&l->errmsg, "Error in \"%s\": " 356156230Smux "Invalid filename \"%s\"", spath, sr->sr_file); 357156230Smux free(spath); 358156230Smux return (LISTER_ERR_STATUS); 359156230Smux } 360156230Smux rfa = fattr_frompath(path, FATTR_NOFOLLOW); 361156230Smux free(path); 362156230Smux if (rfa == NULL) { 363156230Smux /* 364156230Smux * According to the checkouts file we should have 365156230Smux * this file but we don't. Maybe the user deleted 366156230Smux * the file, or maybe the checkouts file is wrong. 367156230Smux * List the file with bogus attributes to cause the 368156230Smux * server to get things back in sync again. 369156230Smux */ 370156230Smux sendattr = fattr_bogus; 371156230Smux goto send; 372156230Smux } 373156230Smux fa = rfa; 374156230Smux } else { 375156230Smux fa = sr->sr_clientattr; 376156230Smux } 377156230Smux fa2 = fattr_forcheckout(sr->sr_serverattr, coll->co_umask); 378156230Smux if (!fattr_equal(fa, sr->sr_clientattr) || !fattr_equal(fa, fa2) || 379156230Smux strcmp(coll->co_tag, sr->sr_tag) != 0 || 380156230Smux strcmp(coll->co_date, sr->sr_date) != 0) { 381156230Smux /* 382156230Smux * The file corresponds to the information we have 383156230Smux * recorded about it, and its moded is correct for 384156230Smux * the requested umask setting. 385156230Smux */ 386156230Smux sendattr = fattr_bogus; 387156230Smux } else { 388156230Smux /* 389156230Smux * Either the file has been touched, or we are asking 390156230Smux * for a different revision than the one we recorded 391156230Smux * information about, or its mode isn't right (because 392156230Smux * it was last updated using a version of CVSup that 393156230Smux * wasn't so strict about modes). 394156230Smux */ 395156230Smux sendattr = sr->sr_serverattr; 396156230Smux } 397156230Smux fattr_free(fa2); 398156230Smux if (rfa != NULL) 399156230Smux fattr_free(rfa); 400156230Smuxsend: 401156230Smux error = proto_printf(wr, "F %s %F\n", pathlast(sr->sr_file), sendattr, 402156230Smux config->fasupport, coll->co_attrignore); 403156230Smux if (error) 404156230Smux return (LISTER_ERR_WRITE); 405156230Smux return (0); 406156230Smux} 407156230Smux 408186781Slulf/* Handle a rcs file live entry found in the status file. */ 409186781Slulfstatic int 410186781Slulflister_dorcsfile(struct lister *l, struct coll *coll, struct statusrec *sr) 411186781Slulf{ 412186781Slulf struct config *config; 413186781Slulf struct stream *wr; 414186781Slulf const struct fattr *sendattr; 415186781Slulf struct fattr *fa; 416186781Slulf char *path, *spath; 417186781Slulf size_t len; 418186781Slulf int error; 419186781Slulf 420186781Slulf if (!globtree_test(coll->co_filefilter, sr->sr_file)) 421186781Slulf return (0); 422186781Slulf config = l->config; 423186781Slulf wr = l->wr; 424186781Slulf if (!(coll->co_options & CO_TRUSTSTATUSFILE)) { 425186781Slulf path = cvspath(coll->co_prefix, sr->sr_file, 0); 426186781Slulf if (path == NULL) { 427186781Slulf spath = coll_statuspath(coll); 428186781Slulf xasprintf(&l->errmsg, "Error in \"%s\": " 429186781Slulf "Invalid filename \"%s\"", spath, sr->sr_file); 430186781Slulf free(spath); 431186781Slulf return (LISTER_ERR_STATUS); 432186781Slulf } 433186781Slulf fa = fattr_frompath(path, FATTR_NOFOLLOW); 434186781Slulf free(path); 435186781Slulf } else 436186781Slulf fa = sr->sr_clientattr; 437186781Slulf if (fa != NULL && fattr_equal(fa, sr->sr_clientattr)) { 438186781Slulf /* 439186781Slulf * If the file is an RCS file, we use "loose" equality, so sizes 440186781Slulf * may disagress because of differences in whitespace. 441186781Slulf */ 442186781Slulf if (isrcs(sr->sr_file, &len) && 443186781Slulf !(coll->co_options & CO_NORCS) && 444186781Slulf !(coll->co_options & CO_STRICTCHECKRCS)) { 445186781Slulf fattr_maskout(fa, FA_SIZE); 446186781Slulf } 447186781Slulf sendattr = fa; 448186781Slulf } else { 449186781Slulf /* 450186781Slulf * If different, the user may have changed it, so we report 451186781Slulf * bogus attributes to force a full comparison. 452186781Slulf */ 453186781Slulf sendattr = fattr_bogus; 454186781Slulf } 455186781Slulf error = proto_printf(wr, "F %s %F\n", pathlast(sr->sr_file), sendattr, 456186781Slulf config->fasupport, coll->co_attrignore); 457186781Slulf if (error) 458186781Slulf return (LISTER_ERR_WRITE); 459186781Slulf return (0); 460186781Slulf} 461186781Slulf 462156230Smux/* Handle a checkout dead entry found in the status file. */ 463156230Smuxstatic int 464156230Smuxlister_dodead(struct lister *l, struct coll *coll, struct statusrec *sr) 465156230Smux{ 466156230Smux struct config *config; 467156230Smux struct stream *wr; 468156230Smux const struct fattr *sendattr; 469156230Smux struct fattr *fa; 470156230Smux char *path, *spath; 471156230Smux int error; 472156230Smux 473156230Smux if (!globtree_test(coll->co_filefilter, sr->sr_file)) 474156230Smux return (0); 475156230Smux config = l->config; 476156230Smux wr = l->wr; 477156230Smux if (!(coll->co_options & CO_TRUSTSTATUSFILE)) { 478156230Smux path = checkoutpath(coll->co_prefix, sr->sr_file); 479156230Smux if (path == NULL) { 480156230Smux spath = coll_statuspath(coll); 481156230Smux xasprintf(&l->errmsg, "Error in \"%s\": " 482156230Smux "Invalid filename \"%s\"", spath, sr->sr_file); 483156230Smux free(spath); 484156230Smux return (LISTER_ERR_STATUS); 485156230Smux } 486156230Smux fa = fattr_frompath(path, FATTR_NOFOLLOW); 487156230Smux free(path); 488156230Smux if (fa != NULL && fattr_type(fa) != FT_DIRECTORY) { 489156230Smux /* 490156230Smux * We shouldn't have this file but we do. Report 491156230Smux * it to the server, which will either send a 492156230Smux * deletion request, of (if the file has come alive) 493156230Smux * sent the correct version. 494156230Smux */ 495156230Smux fattr_free(fa); 496156230Smux error = proto_printf(wr, "F %s %F\n", 497156230Smux pathlast(sr->sr_file), fattr_bogus, 498156230Smux config->fasupport, coll->co_attrignore); 499156230Smux if (error) 500156230Smux return (LISTER_ERR_WRITE); 501156230Smux return (0); 502156230Smux } 503156230Smux fattr_free(fa); 504156230Smux } 505156230Smux if (strcmp(coll->co_tag, sr->sr_tag) != 0 || 506156230Smux strcmp(coll->co_date, sr->sr_date) != 0) 507156230Smux sendattr = fattr_bogus; 508156230Smux else 509156230Smux sendattr = sr->sr_serverattr; 510156230Smux error = proto_printf(wr, "f %s %F\n", pathlast(sr->sr_file), sendattr, 511156230Smux config->fasupport, coll->co_attrignore); 512156230Smux if (error) 513156230Smux return (LISTER_ERR_WRITE); 514156230Smux return (0); 515156230Smux} 516186781Slulf 517186781Slulf/* Handle a rcs file dead entry found in the status file. */ 518186781Slulfstatic int 519186781Slulflister_dorcsdead(struct lister *l, struct coll *coll, struct statusrec *sr) 520186781Slulf{ 521186781Slulf struct config *config; 522186781Slulf struct stream *wr; 523186781Slulf const struct fattr *sendattr; 524186781Slulf struct fattr *fa; 525186781Slulf char *path, *spath; 526186781Slulf size_t len; 527186781Slulf int error; 528186781Slulf 529186781Slulf if (!globtree_test(coll->co_filefilter, sr->sr_file)) 530186781Slulf return (0); 531186781Slulf config = l->config; 532186781Slulf wr = l->wr; 533241835Seadler if (!(coll->co_options & CO_TRUSTSTATUSFILE)) { 534186781Slulf path = cvspath(coll->co_prefix, sr->sr_file, 1); 535186781Slulf if (path == NULL) { 536186781Slulf spath = coll_statuspath(coll); 537186781Slulf xasprintf(&l->errmsg, "Error in \"%s\": " 538186781Slulf "Invalid filename \"%s\"", spath, sr->sr_file); 539186781Slulf free(spath); 540186781Slulf return (LISTER_ERR_STATUS); 541186781Slulf } 542186781Slulf fa = fattr_frompath(path, FATTR_NOFOLLOW); 543186781Slulf free(path); 544186781Slulf } else 545186781Slulf fa = sr->sr_clientattr; 546186781Slulf if (fattr_equal(fa, sr->sr_clientattr)) { 547186781Slulf /* 548186781Slulf * If the file is an RCS file, we use "loose" equality, so sizes 549186781Slulf * may disagress because of differences in whitespace. 550186781Slulf */ 551186781Slulf if (isrcs(sr->sr_file, &len) && 552186781Slulf !(coll->co_options & CO_NORCS) && 553186781Slulf !(coll->co_options & CO_STRICTCHECKRCS)) { 554186781Slulf fattr_maskout(fa, FA_SIZE); 555186781Slulf } 556186781Slulf sendattr = fa; 557186781Slulf } else { 558186781Slulf /* 559186781Slulf * If different, the user may have changed it, so we report 560186781Slulf * bogus attributes to force a full comparison. 561186781Slulf */ 562186781Slulf sendattr = fattr_bogus; 563186781Slulf } 564186781Slulf error = proto_printf(wr, "f %s %F\n", pathlast(sr->sr_file), sendattr, 565186781Slulf config->fasupport, coll->co_attrignore); 566186781Slulf if (error) 567186781Slulf return (LISTER_ERR_WRITE); 568186781Slulf return (0); 569186781Slulf} 570