]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - contrib/csup/lister.c
This commit was generated by cvs2svn to compensate for changes in r175261,
[FreeBSD/FreeBSD.git] / contrib / 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
68 void *
69 lister(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
102 static int
103 lister_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. */
141 static int
142 lister_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);
206 bad:
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. */
216 static int
217 lister_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. */
276 static int
277 lister_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. */
312 static int
313 lister_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);
378 send:
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. */
387 static int
388 lister_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 }