]> CyberLeo.Net >> Repos - FreeBSD/releng/9.2.git/blob - usr.bin/csup/lister.c
- Copy stable/9 to releng/9.2 as part of the 9.2-RELEASE cycle.
[FreeBSD/releng/9.2.git] / usr.bin / csup / lister.c
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 *
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
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
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
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
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
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
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
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
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 }