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
51 struct lister {
52 struct config *config;
53 struct stream *wr;
54 char *errmsg;
55 };
56
57 static int lister_batch(struct lister *);
58 static int lister_coll(struct lister *, struct coll *, struct status *);
59 static int lister_dodirdown(struct lister *, struct coll *,
60 struct statusrec *, struct attrstack *as);
61 static int lister_dodirup(struct lister *, struct coll *,
62 struct statusrec *, struct attrstack *as);
63 static int lister_dofile(struct lister *, struct coll *,
64 struct statusrec *);
65 static int lister_dodead(struct lister *, struct coll *,
66 struct statusrec *);
67 static int lister_dorcsfile(struct lister *, struct coll *,
68 struct statusrec *);
69 static int lister_dorcsdead(struct lister *, struct coll *,
70 struct statusrec *);
71
72 void *
lister(void * arg)73 lister(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
106 static int
lister_batch(struct lister * l)107 lister_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. */
145 static int
lister_coll(struct lister * l,struct coll * coll,struct status * st)146 lister_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);
228 bad:
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. */
238 static int
lister_dodirdown(struct lister * l,struct coll * coll,struct statusrec * sr,struct attrstack * as)239 lister_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. */
298 static int
lister_dodirup(struct lister * l,struct coll * coll,struct statusrec * sr,struct attrstack * as)299 lister_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. */
334 static int
lister_dofile(struct lister * l,struct coll * coll,struct statusrec * sr)335 lister_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);
400 send:
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. */
409 static int
lister_dorcsfile(struct lister * l,struct coll * coll,struct statusrec * sr)410 lister_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. */
463 static int
lister_dodead(struct lister * l,struct coll * coll,struct statusrec * sr)464 lister_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. */
518 static int
lister_dorcsdead(struct lister * l,struct coll * coll,struct statusrec * sr)519 lister_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