]> CyberLeo.Net >> Repos - FreeBSD/releng/10.0.git/blob - usr.bin/csup/detailer.c
- Copy stable/10 (r259064) to releng/10.0 as part of the
[FreeBSD/releng/10.0.git] / usr.bin / csup / detailer.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 <stdlib.h>
32 #include <string.h>
33 #include <stdio.h>
34
35 #include <sys/types.h>
36 #include <sys/stat.h>
37 #include <unistd.h>
38
39 #include "config.h"
40 #include "detailer.h"
41 #include "fixups.h"
42 #include "globtree.h"
43 #include "misc.h"
44 #include "mux.h"
45 #include "proto.h"
46 #include "rcsfile.h"
47 #include "rsyncfile.h"
48 #include "status.h"
49 #include "stream.h"
50
51 /* Internal error codes. */
52 #define DETAILER_ERR_PROTO      (-1)    /* Protocol error. */
53 #define DETAILER_ERR_MSG        (-2)    /* Error is in detailer->errmsg. */
54 #define DETAILER_ERR_READ       (-3)    /* Error reading from server. */
55 #define DETAILER_ERR_WRITE      (-4)    /* Error writing to server. */
56
57 struct detailer {
58         struct config *config;
59         struct stream *rd;
60         struct stream *wr;
61         char *errmsg;
62 };
63
64 static int      detailer_batch(struct detailer *);
65 static int      detailer_coll(struct detailer *, struct coll *,
66                     struct status *);
67 static int      detailer_dofile_co(struct detailer *, struct coll *,
68                     struct status *, char *);
69 static int      detailer_dofile_rcs(struct detailer *, struct coll *, 
70                     char *, char *);
71 static int      detailer_dofile_regular(struct detailer *, char *, char *);
72 static int      detailer_dofile_rsync(struct detailer *, char *, char *);
73 static int      detailer_checkrcsattr(struct detailer *, struct coll *, char *,
74                     struct fattr *, int);
75 int             detailer_send_details(struct detailer *, struct coll *, char *,
76                     char *, struct fattr *);
77
78 void *
79 detailer(void *arg)
80 {
81         struct thread_args *args;
82         struct detailer dbuf, *d;
83         int error;
84
85         args = arg;
86
87         d = &dbuf;
88         d->config = args->config;
89         d->rd = args->rd;
90         d->wr = args->wr;
91         d->errmsg = NULL;
92
93         error = detailer_batch(d);
94         switch (error) {
95         case DETAILER_ERR_PROTO:
96                 xasprintf(&args->errmsg, "Detailer failed: Protocol error");
97                 args->status = STATUS_FAILURE;
98                 break;
99         case DETAILER_ERR_MSG:
100                 xasprintf(&args->errmsg, "Detailer failed: %s", d->errmsg);
101                 free(d->errmsg);
102                 args->status = STATUS_FAILURE;
103                 break;
104         case DETAILER_ERR_READ:
105                 if (stream_eof(d->rd)) {
106                         xasprintf(&args->errmsg, "Detailer failed: "
107                             "Premature EOF from server");
108                 } else {
109                         xasprintf(&args->errmsg, "Detailer failed: "
110                             "Network read failure: %s", strerror(errno));
111                 }
112                 args->status = STATUS_TRANSIENTFAILURE;
113                 break;
114         case DETAILER_ERR_WRITE:
115                 xasprintf(&args->errmsg, "Detailer failed: "
116                     "Network write failure: %s", strerror(errno));
117                 args->status = STATUS_TRANSIENTFAILURE;
118                 break;
119         default:
120                 assert(error == 0);
121                 args->status = STATUS_SUCCESS;
122         }
123         return (NULL);
124 }
125
126 static int
127 detailer_batch(struct detailer *d)
128 {
129         struct config *config;
130         struct stream *rd, *wr;
131         struct coll *coll;
132         struct status *st;
133         struct fixup *fixup;
134         char *cmd, *collname, *line, *release;
135         int error, fixupseof;
136
137         config = d->config;
138         rd = d->rd;
139         wr = d->wr;
140         STAILQ_FOREACH(coll, &config->colls, co_next) {
141                 if (coll->co_options & CO_SKIP)
142                         continue;
143                 line = stream_getln(rd, NULL);
144                 cmd = proto_get_ascii(&line);
145                 collname = proto_get_ascii(&line);
146                 release = proto_get_ascii(&line);
147                 error = proto_get_time(&line, &coll->co_scantime);
148                 if (error || line != NULL || strcmp(cmd, "COLL") != 0 ||
149                     strcmp(collname, coll->co_name) != 0 ||
150                     strcmp(release, coll->co_release) != 0)
151                         return (DETAILER_ERR_PROTO);
152                 error = proto_printf(wr, "COLL %s %s\n", coll->co_name,
153                     coll->co_release);
154                 if (error)
155                         return (DETAILER_ERR_WRITE);
156                 stream_flush(wr);
157                 if (coll->co_options & CO_COMPRESS) {
158                         stream_filter_start(rd, STREAM_FILTER_ZLIB, NULL);
159                         stream_filter_start(wr, STREAM_FILTER_ZLIB, NULL);
160                 }
161                 st = status_open(coll, -1, &d->errmsg);
162                 if (st == NULL)
163                         return (DETAILER_ERR_MSG);
164                 error = detailer_coll(d, coll, st);
165                 status_close(st, NULL);
166                 if (error)
167                         return (error);
168                 if (coll->co_options & CO_COMPRESS) {
169                         stream_filter_stop(rd);
170                         stream_filter_stop(wr);
171                 }
172                 stream_flush(wr);
173         }
174         line = stream_getln(rd, NULL);
175         if (line == NULL)
176                 return (DETAILER_ERR_READ);
177         if (strcmp(line, ".") != 0)
178                 return (DETAILER_ERR_PROTO);
179         error = proto_printf(wr, ".\n");
180         if (error)
181                 return (DETAILER_ERR_WRITE);
182         stream_flush(wr);
183
184         /* Now send fixups if needed. */
185         fixup = NULL;
186         fixupseof = 0;
187         STAILQ_FOREACH(coll, &config->colls, co_next) {
188                 if (coll->co_options & CO_SKIP)
189                         continue;
190                 error = proto_printf(wr, "COLL %s %s\n", coll->co_name,
191                     coll->co_release);
192                 if (error)
193                         return (DETAILER_ERR_WRITE);
194                 if (coll->co_options & CO_COMPRESS)
195                         stream_filter_start(wr, STREAM_FILTER_ZLIB, NULL);
196                 while (!fixupseof) {
197                         if (fixup == NULL)
198                                 fixup = fixups_get(config->fixups);
199                         if (fixup == NULL) {
200                                 fixupseof = 1;
201                                 break;
202                         }
203                         if (fixup->f_coll != coll)
204                                 break;
205                         if (coll->co_options & CO_CHECKOUTMODE)
206                                 error = proto_printf(wr, "Y %s %s %s\n",
207                                     fixup->f_name, coll->co_tag, coll->co_date);
208                         else {
209                                 error = proto_printf(wr, "A %s\n",
210                                     fixup->f_name);
211                         }
212                         if (error)
213                                 return (DETAILER_ERR_WRITE);
214                         fixup = NULL;
215                 }
216                 error = proto_printf(wr, ".\n");
217                 if (error)
218                         return (DETAILER_ERR_WRITE);
219                 if (coll->co_options & CO_COMPRESS)
220                         stream_filter_stop(wr);
221                 stream_flush(wr);
222         }
223         error = proto_printf(wr, ".\n");
224         if (error)
225                 return (DETAILER_ERR_WRITE);
226         return (0);
227 }
228
229 static int
230 detailer_coll(struct detailer *d, struct coll *coll, struct status *st)
231 {
232         struct fattr *rcsattr;
233         struct stream *rd, *wr;
234         char *attr, *cmd, *file, *line, *msg, *path, *target;
235         int error, attic;
236
237         rd = d->rd;
238         wr = d->wr;
239         attic = 0;
240         line = stream_getln(rd, NULL);
241         if (line == NULL)
242                 return (DETAILER_ERR_READ);
243         while (strcmp(line, ".") != 0) {
244                 cmd = proto_get_ascii(&line);
245                 if (cmd == NULL || strlen(cmd) != 1)
246                         return (DETAILER_ERR_PROTO);
247                 switch (cmd[0]) {
248                 case 'D':
249                         /* Delete file. */
250                         file = proto_get_ascii(&line);
251                         if (file == NULL || line != NULL)
252                                 return (DETAILER_ERR_PROTO); 
253                         error = proto_printf(wr, "D %s\n", file);
254                         if (error)
255                                 return (DETAILER_ERR_WRITE);
256                         break;
257                 case 'I':
258                 case 'i':
259                 case 'j':
260                         /* Directory operations. */
261                         file = proto_get_ascii(&line);
262                         if (file == NULL || line != NULL)
263                                 return (DETAILER_ERR_PROTO);
264                         error = proto_printf(wr, "%s %s\n", cmd, file);
265                         if (error)
266                                 return (DETAILER_ERR_WRITE);
267                         break;
268                 case 'J':
269                         /* Set directory attributes. */
270                         file = proto_get_ascii(&line);
271                         attr = proto_get_ascii(&line);
272                         if (file == NULL || line != NULL || attr == NULL)
273                                 return (DETAILER_ERR_PROTO);
274                         error = proto_printf(wr, "%s %s %s\n", cmd, file, attr);
275                         if (error)
276                                 return (DETAILER_ERR_WRITE);
277                         break;
278                 case 'H':
279                 case 'h':
280                         /* Create a hard link. */
281                         file = proto_get_ascii(&line);
282                         target = proto_get_ascii(&line);
283                         if (file == NULL || target == NULL)
284                                 return (DETAILER_ERR_PROTO);
285                         error = proto_printf(wr, "%s %s %s\n", cmd, file,
286                             target);
287                         break;
288                 case 't':
289                         file = proto_get_ascii(&line);
290                         attr = proto_get_ascii(&line);
291                         if (file == NULL || attr == NULL || line != NULL) {
292                                 return (DETAILER_ERR_PROTO);
293                         }
294                         rcsattr = fattr_decode(attr);
295                         if (rcsattr == NULL) {
296                                 return (DETAILER_ERR_PROTO);
297                         }
298                         error = detailer_checkrcsattr(d, coll, file, rcsattr,
299                             1);
300                         break;
301
302                 case 'T':
303                         file = proto_get_ascii(&line);
304                         attr = proto_get_ascii(&line);
305                         if (file == NULL || attr == NULL || line != NULL)
306                                 return (DETAILER_ERR_PROTO);
307                         rcsattr = fattr_decode(attr);
308                         if (rcsattr == NULL)
309                                 return (DETAILER_ERR_PROTO);
310                         error = detailer_checkrcsattr(d, coll, file, rcsattr,
311                             0);
312                         break;
313
314                 case 'U':
315                         /* Add or update file. */
316                         file = proto_get_ascii(&line);
317                         if (file == NULL || line != NULL)
318                                 return (DETAILER_ERR_PROTO);
319                         if (coll->co_options & CO_CHECKOUTMODE) {
320                                 error = detailer_dofile_co(d, coll, st, file);
321                         } else {
322                                 path = cvspath(coll->co_prefix, file, 0);
323                                 rcsattr = fattr_frompath(path, FATTR_NOFOLLOW);
324                                 error = detailer_send_details(d, coll, file,
325                                     path, rcsattr);
326                                 if (rcsattr != NULL)
327                                         fattr_free(rcsattr);
328                                 free(path);
329                         }
330                         if (error)
331                                 return (error);
332                         break;
333                 case '!':
334                         /* Warning from server. */
335                         msg = proto_get_rest(&line);
336                         if (msg == NULL)
337                                 return (DETAILER_ERR_PROTO);
338                         lprintf(-1, "Server warning: %s\n", msg);
339                         break;
340                 default:
341                         return (DETAILER_ERR_PROTO);
342                 }
343                 stream_flush(wr);
344                 line = stream_getln(rd, NULL);
345                 if (line == NULL)
346                         return (DETAILER_ERR_READ);
347         }
348         error = proto_printf(wr, ".\n");
349         if (error)
350                 return (DETAILER_ERR_WRITE);
351         return (0);
352 }
353
354 /*
355  * Tell the server to update a regular file.
356  */
357 static int
358 detailer_dofile_regular(struct detailer *d, char *name, char *path)
359 {
360         struct stream *wr;
361         struct stat st;
362         char md5[MD5_DIGEST_SIZE];
363         int error;
364         
365         wr = d->wr;
366         error = stat(path, &st);
367         /* If we don't have it or it's unaccessible, we want it again. */
368         if (error) {
369                 proto_printf(wr, "A %s\n", name);
370                 return (0);
371         }
372
373         /* If not, we want the file to be updated. */
374         error = MD5_File(path, md5);
375         if (error) {
376                 lprintf(-1, "Error reading \"%s\"\n", name);
377                 return (error);
378         }
379         error = proto_printf(wr, "R %s %O %s\n", name, st.st_size, md5);
380         if (error)
381                 return (DETAILER_ERR_WRITE);
382         return (0);
383 }
384
385 /*
386  * Tell the server to update a file with the rsync algorithm.
387  */
388 static int
389 detailer_dofile_rsync(struct detailer *d, char *name, char *path)
390 {
391         struct stream *wr;
392         struct rsyncfile *rf;
393
394         wr = d->wr;
395         rf = rsync_open(path, 0, 1);
396         if (rf == NULL) {
397                 /* Fallback if we fail in opening it. */
398                 proto_printf(wr, "A %s\n", name);
399                 return (0);
400         }
401         proto_printf(wr, "r %s %z %z\n", name, rsync_filesize(rf),
402             rsync_blocksize(rf));
403         /* Detail the blocks. */
404         while (rsync_nextblock(rf) != 0)
405                 proto_printf(wr, "%s %s\n", rsync_rsum(rf), rsync_blockmd5(rf));
406         proto_printf(wr, ".\n");
407         rsync_close(rf);
408         return (0);
409 }
410
411 /*
412  * Tell the server to update an RCS file that we have, or send it if we don't.
413  */
414 static int
415 detailer_dofile_rcs(struct detailer *d, struct coll *coll, char *name,
416     char *path)
417 {
418         struct stream *wr;
419         struct fattr *fa;
420         struct rcsfile *rf;
421         int error;
422
423         wr = d->wr;
424         path = atticpath(coll->co_prefix, name);
425         fa = fattr_frompath(path, FATTR_NOFOLLOW);
426         if (fa == NULL) {
427                 /* We don't have it, so send request to get it. */
428                 error = proto_printf(wr, "A %s\n", name);
429                 if (error)
430                         return (DETAILER_ERR_WRITE);
431                 free(path);
432                 return (0);
433         }
434
435         rf = rcsfile_frompath(path, name, coll->co_cvsroot, coll->co_tag, 1);
436         free(path);
437         if (rf == NULL) {
438                 error = proto_printf(wr, "A %s\n", name);
439                 if (error)
440                         return (DETAILER_ERR_WRITE);
441                 return (0);
442         }
443         /* Tell to update the RCS file. The client version details follow. */
444         rcsfile_send_details(rf, wr);
445         rcsfile_free(rf);
446         fattr_free(fa);
447         return (0);
448 }
449
450 static int
451 detailer_dofile_co(struct detailer *d, struct coll *coll, struct status *st,
452     char *file)
453 {
454         struct stream *wr;
455         struct fattr *fa;
456         struct statusrec *sr;
457         char md5[MD5_DIGEST_SIZE];
458         char *path;
459         int error, ret;
460
461         wr = d->wr;
462         path = checkoutpath(coll->co_prefix, file);
463         if (path == NULL)
464                 return (DETAILER_ERR_PROTO);
465         fa = fattr_frompath(path, FATTR_NOFOLLOW);
466         if (fa == NULL) {
467                 /* We don't have the file, so the only option at this
468                    point is to tell the server to send it.  The server
469                    may figure out that the file is dead, in which case
470                    it will tell us. */
471                 error = proto_printf(wr, "C %s %s %s\n",
472                     file, coll->co_tag, coll->co_date);
473                 free(path);
474                 if (error)
475                         return (DETAILER_ERR_WRITE);
476                 return (0);
477         }
478         ret = status_get(st, file, 0, 0, &sr);
479         if (ret == -1) {
480                 d->errmsg = status_errmsg(st);
481                 free(path);
482                 return (DETAILER_ERR_MSG);
483         }
484         if (ret == 0)
485                 sr = NULL;
486
487         /* If our recorded information doesn't match the file that the
488            client has, then ignore the recorded information. */
489         if (sr != NULL && (sr->sr_type != SR_CHECKOUTLIVE ||
490             !fattr_equal(sr->sr_clientattr, fa)))
491                 sr = NULL;
492         fattr_free(fa);
493         if (sr != NULL && strcmp(sr->sr_revdate, ".") != 0) {
494                 error = proto_printf(wr, "U %s %s %s %s %s\n", file,
495                     coll->co_tag, coll->co_date, sr->sr_revnum, sr->sr_revdate);
496                 free(path);
497                 if (error)
498                         return (DETAILER_ERR_WRITE);
499                 return (0);
500         }
501
502         /*
503          * We don't have complete and/or accurate recorded information
504          * about what version of the file we have.  Compute the file's
505          * checksum as an aid toward identifying which version it is.
506          */
507         error = MD5_File(path, md5);
508         if (error) {
509                 xasprintf(&d->errmsg,
510                     "Cannot calculate checksum for \"%s\": %s", path,
511                     strerror(errno));
512                 return (DETAILER_ERR_MSG);
513         }
514         free(path);
515         if (sr == NULL) {
516                 error = proto_printf(wr, "S %s %s %s %s\n", file,
517                     coll->co_tag, coll->co_date, md5);
518         } else {
519                 error = proto_printf(wr, "s %s %s %s %s %s\n", file,
520                     coll->co_tag, coll->co_date, sr->sr_revnum, md5);
521         }
522         if (error)
523                 return (DETAILER_ERR_WRITE);
524         return (0);
525 }
526
527 int
528 detailer_checkrcsattr(struct detailer *d, struct coll *coll, char *name,
529     struct fattr *server_attr, int attic)
530 {
531         struct fattr *client_attr;
532         char *attr, *path;
533         int error;
534
535         /*
536          * I don't think we can use the status file, since it only records file
537          * attributes in cvsmode.
538          */
539         client_attr = NULL;
540         path = cvspath(coll->co_prefix, name, attic);
541         if (path == NULL) {
542                 return (DETAILER_ERR_PROTO);
543         }
544
545         if (access(path, F_OK) == 0 && 
546             ((client_attr = fattr_frompath(path, FATTR_NOFOLLOW)) != NULL) &&
547             fattr_equal(client_attr, server_attr)) {
548                 attr = fattr_encode(client_attr, NULL, 0);
549                 if (attic) {
550                         error = proto_printf(d->wr, "l %s %s\n", name, attr);
551                 } else {
552                         error = proto_printf(d->wr, "L %s %s\n", name, attr);
553                 }
554                 free(attr);
555                 free(path);
556                 fattr_free(client_attr);
557                 if (error)
558                         return (DETAILER_ERR_WRITE);
559                 return (0);
560         }
561         /* We don't have it, so tell the server to send it. */
562         error = detailer_send_details(d, coll, name, path, client_attr);
563         fattr_free(client_attr);
564         free(path);
565         return (error);
566 }
567
568 int
569 detailer_send_details(struct detailer *d, struct coll *coll, char *name,
570     char *path, struct fattr *fa)
571 {
572         int error;
573         size_t len;
574
575        /*
576         * Try to check if the file exists either live or dead to see if we can
577         * edit it and put it live or dead, rather than receiving the entire
578         * file.
579         */
580         if (fa == NULL) {
581                 path = atticpath(coll->co_prefix, name); 
582                 fa = fattr_frompath(path, FATTR_NOFOLLOW);
583         }
584         if (fa == NULL) {
585                 error = proto_printf(d->wr, "A %s\n", name);
586                 if (error)
587                         return (DETAILER_ERR_WRITE);
588         } else if (fattr_type(fa) == FT_FILE) {
589                 if (isrcs(name, &len) && !(coll->co_options & CO_NORCS)) {
590                         detailer_dofile_rcs(d, coll, name, path);
591                 } else if (!(coll->co_options & CO_NORSYNC) &&
592                     !globtree_test(coll->co_norsync, name)) {
593                         detailer_dofile_rsync(d, name, path);
594                 } else {
595                         detailer_dofile_regular(d, name, path);
596                 }
597         } else {
598                 error = proto_printf(d->wr, "N %s\n", name);
599                 if (error)
600                         return (DETAILER_ERR_WRITE);
601         }
602         return (0);
603 }