]> CyberLeo.Net >> Repos - FreeBSD/releng/9.2.git/blob - usr.bin/csup/keyword.c
- Copy stable/9 to releng/9.2 as part of the 9.2-RELEASE cycle.
[FreeBSD/releng/9.2.git] / usr.bin / csup / keyword.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 <err.h>
31 #include <errno.h>
32 #include <stdio.h>
33 #include <stdlib.h>
34 #include <string.h>
35 #include <time.h>
36
37 #include "diff.h"
38 #include "keyword.h"
39 #include "misc.h"
40 #include "queue.h"
41 #include "stream.h"
42
43 /*
44  * The keyword API is used to expand the CVS/RCS keywords in files,
45  * such as $Id$, $Revision$, etc.  The server does it for us when it
46  * sends us entire files, but we need to handle the expansion when
47  * applying a diff update.
48  */
49
50 enum rcskey {
51         RCSKEY_AUTHOR,
52         RCSKEY_CVSHEADER,
53         RCSKEY_DATE,
54         RCSKEY_HEADER,
55         RCSKEY_ID,
56         RCSKEY_LOCKER,
57         RCSKEY_LOG,
58         RCSKEY_NAME,
59         RCSKEY_RCSFILE,
60         RCSKEY_REVISION,
61         RCSKEY_SOURCE,
62         RCSKEY_STATE
63 };
64
65 typedef enum rcskey rcskey_t;
66
67 struct tag {
68         char *ident;
69         rcskey_t key;
70         int enabled;
71         STAILQ_ENTRY(tag) next;
72 };
73
74 static struct tag       *tag_new(const char *, rcskey_t);
75 static char             *tag_expand(struct tag *, struct diffinfo *);
76 static void              tag_free(struct tag *);
77
78 struct keyword {
79         STAILQ_HEAD(, tag) keywords;            /* Enabled keywords. */
80         size_t minkeylen;
81         size_t maxkeylen;
82 };
83
84 /* Default CVS keywords. */
85 static struct {
86         const char *ident;
87         rcskey_t key;
88 } tag_defaults[] = {
89         { "Author",     RCSKEY_AUTHOR },
90         { "CVSHeader",  RCSKEY_CVSHEADER },
91         { "Date",       RCSKEY_DATE },
92         { "Header",     RCSKEY_HEADER },
93         { "Id",         RCSKEY_ID },
94         { "Locker",     RCSKEY_LOCKER },
95         { "Log",        RCSKEY_LOG },
96         { "Name",       RCSKEY_NAME },
97         { "RCSfile",    RCSKEY_RCSFILE },
98         { "Revision",   RCSKEY_REVISION },
99         { "Source",     RCSKEY_SOURCE },
100         { "State",      RCSKEY_STATE },
101         { NULL,         0, }
102 };
103
104 struct keyword *
105 keyword_new(void)
106 {
107         struct keyword *new;
108         struct tag *tag;
109         size_t len;
110         int i;
111
112         new = xmalloc(sizeof(struct keyword));
113         STAILQ_INIT(&new->keywords);
114         new->minkeylen = ~0;
115         new->maxkeylen = 0;
116         for (i = 0; tag_defaults[i].ident != NULL; i++) {
117                 tag = tag_new(tag_defaults[i].ident, tag_defaults[i].key);
118                 STAILQ_INSERT_TAIL(&new->keywords, tag, next);
119                 len = strlen(tag->ident);
120                 /*
121                  * These values are only computed here and not updated when
122                  * adding an alias.  This is a bug, but CVSup has it and we
123                  * need to be bug-to-bug compatible since the server will
124                  * expect us to do the same, and we will fail with an MD5
125                  * checksum mismatch if we don't.
126                  */
127                 new->minkeylen = min(new->minkeylen, len);
128                 new->maxkeylen = max(new->maxkeylen, len);
129         }
130         return (new);
131 }
132
133 int
134 keyword_decode_expand(const char *expand)
135 {
136
137         if (strcmp(expand, ".") == 0)
138                 return (EXPAND_DEFAULT);
139         else if (strcmp(expand, "kv") == 0)
140                 return (EXPAND_KEYVALUE);
141         else if (strcmp(expand, "kvl") == 0)
142                 return (EXPAND_KEYVALUELOCKER);
143         else if (strcmp(expand, "k") == 0)
144                 return (EXPAND_KEY);
145         else if (strcmp(expand, "o") == 0)
146                 return (EXPAND_OLD);
147         else if (strcmp(expand, "b") == 0)
148                 return (EXPAND_BINARY);
149         else if (strcmp(expand, "v") == 0)
150                 return (EXPAND_VALUE);
151         else
152                 return (-1);
153 }
154
155 const char *
156 keyword_encode_expand(int expand)
157 {
158
159         switch (expand) {
160                 case EXPAND_DEFAULT:
161                         return (".");
162                 case EXPAND_KEYVALUE:
163                         return ("kv");
164                 case EXPAND_KEYVALUELOCKER:
165                         return ("kvl");
166                 case EXPAND_KEY:
167                         return ("k");
168                 case EXPAND_OLD:
169                         return ("o");
170                 case EXPAND_BINARY:
171                         return ("b");
172                 case EXPAND_VALUE:
173                         return ("v");
174         }
175         return (NULL);
176 }
177
178 void
179 keyword_free(struct keyword *keyword)
180 {
181         struct tag *tag;
182
183         if (keyword == NULL)
184                 return;
185         while (!STAILQ_EMPTY(&keyword->keywords)) {
186                 tag = STAILQ_FIRST(&keyword->keywords);
187                 STAILQ_REMOVE_HEAD(&keyword->keywords, next);
188                 tag_free(tag);
189         }
190         free(keyword);
191 }
192
193 int
194 keyword_alias(struct keyword *keyword, const char *ident, const char *rcskey)
195 {
196         struct tag *new, *tag;
197
198         STAILQ_FOREACH(tag, &keyword->keywords, next) {
199                 if (strcmp(tag->ident, rcskey) == 0) {
200                         new = tag_new(ident, tag->key);
201                         STAILQ_INSERT_HEAD(&keyword->keywords, new, next);
202                         return (0);
203                 }
204         }
205         errno = ENOENT;
206         return (-1);
207 }
208
209 int
210 keyword_enable(struct keyword *keyword, const char *ident)
211 {
212         struct tag *tag;
213         int all;
214
215         all = 0;
216         if (strcmp(ident, ".") == 0)
217                 all = 1;
218
219         STAILQ_FOREACH(tag, &keyword->keywords, next) {
220                 if (!all && strcmp(tag->ident, ident) != 0)
221                         continue;
222                 tag->enabled = 1;
223                 if (!all)
224                         return (0);
225         }
226         if (!all) {
227                 errno = ENOENT;
228                 return (-1);
229         }
230         return (0);
231 }
232
233 int
234 keyword_disable(struct keyword *keyword, const char *ident)
235 {
236         struct tag *tag;
237         int all;
238
239         all = 0;
240         if (strcmp(ident, ".") == 0)
241                 all = 1;
242
243         STAILQ_FOREACH(tag, &keyword->keywords, next) {
244                 if (!all && strcmp(tag->ident, ident) != 0)
245                         continue;
246                 tag->enabled = 0;
247                 if (!all)
248                         return (0);
249         }
250
251         if (!all) {
252                 errno = ENOENT;
253                 return (-1);
254         }
255         return (0);
256 }
257
258 void
259 keyword_prepare(struct keyword *keyword)
260 {
261         struct tag *tag, *temp;
262
263         STAILQ_FOREACH_SAFE(tag, &keyword->keywords, next, temp) {
264                 if (!tag->enabled) {
265                         STAILQ_REMOVE(&keyword->keywords, tag, tag, next);
266                         tag_free(tag);
267                         continue;
268                 }
269         }
270 }
271
272 /*
273  * Expand appropriate RCS keywords.  If there's no tag to expand,
274  * keyword_expand() returns 0, otherwise it returns 1 and writes a
275  * pointer to the new line in *buf and the new len in *len.  The
276  * new line is allocated with malloc() and needs to be freed by the
277  * caller after use.
278  */
279 int
280 keyword_expand(struct keyword *keyword, struct diffinfo *di, char *line,
281     size_t size, char **buf, size_t *len)
282 {
283         struct tag *tag;
284         char *dollar, *keystart, *valstart, *vallim, *next;
285         char *linestart, *newline, *newval, *cp, *tmp;
286         size_t left, newsize, vallen;
287
288         if (di->di_expand == EXPAND_OLD || di->di_expand == EXPAND_BINARY)
289                 return (0);
290         newline = NULL;
291         newsize = 0;
292         left = size;
293         linestart = cp = line;
294 again:
295         dollar = memchr(cp, '$', left);
296         if (dollar == NULL) {
297                 if (newline != NULL) {
298                         *buf = newline;
299                         *len = newsize;
300                         return (1);
301                 }
302                 return (0);
303         }
304         keystart = dollar + 1;
305         left -= keystart - cp;
306         vallim = memchr(keystart, '$', left);
307         if (vallim == NULL) {
308                 if (newline != NULL) {
309                         *buf = newline;
310                         *len = newsize;
311                         return (1);
312                 }
313                 return (0);
314         }
315         if (vallim == keystart) {
316                 cp = keystart;
317                 goto again;
318         }
319         valstart = memchr(keystart, ':', left);
320         if (valstart == keystart) {
321                 cp = vallim;
322                 left -= vallim - keystart;
323                 goto again;
324         }
325         if (valstart == NULL || valstart > vallim)
326                 valstart = vallim;
327
328         if (valstart < keystart + keyword->minkeylen ||
329             valstart > keystart + keyword->maxkeylen) {
330                 cp = vallim;
331                 left -= vallim -keystart;
332                 goto again;
333         }
334         STAILQ_FOREACH(tag, &keyword->keywords, next) {
335                 if (strncmp(tag->ident, keystart, valstart - keystart) == 0 &&
336                     tag->ident[valstart - keystart] == '\0') {
337                         if (newline != NULL)
338                                 tmp = newline;
339                         else
340                                 tmp = NULL;
341                         newval = NULL;
342                         if (di->di_expand == EXPAND_KEY) {
343                                 newsize = dollar - linestart + 1 +
344                                     valstart - keystart + 1 +
345                                     size - (vallim + 1 - linestart);
346                                 newline = xmalloc(newsize);
347                                 cp = newline;
348                                 memcpy(cp, linestart, dollar - linestart);
349                                 cp += dollar - linestart;
350                                 *cp++ = '$';
351                                 memcpy(cp, keystart, valstart - keystart);
352                                 cp += valstart - keystart;
353                                 *cp++ = '$';
354                                 next = cp;
355                                 memcpy(cp, vallim + 1,
356                                     size - (vallim + 1 - linestart));
357                         } else if (di->di_expand == EXPAND_VALUE) {
358                                 newval = tag_expand(tag, di);
359                                 if (newval == NULL)
360                                         vallen = 0;
361                                 else
362                                         vallen = strlen(newval);
363                                 newsize = dollar - linestart +
364                                     vallen +
365                                     size - (vallim + 1 - linestart);
366                                 newline = xmalloc(newsize);
367                                 cp = newline;
368                                 memcpy(cp, linestart, dollar - linestart);
369                                 cp += dollar - linestart;
370                                 if (newval != NULL) {
371                                         memcpy(cp, newval, vallen);
372                                         cp += vallen;
373                                 }
374                                 next = cp;
375                                 memcpy(cp, vallim + 1,
376                                     size - (vallim + 1 - linestart));
377                         } else {
378                                 assert(di->di_expand == EXPAND_DEFAULT ||
379                                     di->di_expand == EXPAND_KEYVALUE ||
380                                     di->di_expand == EXPAND_KEYVALUELOCKER);
381                                 newval = tag_expand(tag, di);
382                                 if (newval == NULL)
383                                         vallen = 0;
384                                 else
385                                         vallen = strlen(newval);
386                                 newsize = dollar - linestart + 1 +
387                                     valstart - keystart + 2 +
388                                     vallen + 2 +
389                                     size - (vallim + 1 - linestart);
390                                 newline = xmalloc(newsize);
391                                 cp = newline;
392                                 memcpy(cp, linestart, dollar - linestart);
393                                 cp += dollar - linestart;
394                                 *cp++ = '$';
395                                 memcpy(cp, keystart, valstart - keystart);
396                                 cp += valstart - keystart;
397                                 *cp++ = ':';
398                                 *cp++ = ' ';
399                                 if (newval != NULL) {
400                                         memcpy(cp, newval, vallen);
401                                         cp += vallen;
402                                 }
403                                 *cp++ = ' ';
404                                 *cp++ = '$';
405                                 next = cp;
406                                 memcpy(cp, vallim + 1,
407                                     size - (vallim + 1 - linestart));
408                         }
409                         if (newval != NULL)
410                                 free(newval);
411                         if (tmp != NULL)
412                                 free(tmp);
413                         /*
414                          * Continue looking for tags in the rest of the line.
415                          */
416                         cp = next;
417                         size = newsize;
418                         left = size - (cp - newline);
419                         linestart = newline;
420                         goto again;
421                 }
422         }
423         cp = vallim;
424         left = size - (cp - linestart);
425         goto again;
426 }
427
428 static struct tag *
429 tag_new(const char *ident, rcskey_t key)
430 {
431         struct tag *new;
432
433         new = xmalloc(sizeof(struct tag));
434         new->ident = xstrdup(ident);
435         new->key = key;
436         new->enabled = 1;
437         return (new);
438 }
439
440 static void
441 tag_free(struct tag *tag)
442 {
443
444         free(tag->ident);
445         free(tag);
446 }
447
448 /*
449  * Expand a specific tag and return the new value.  If NULL
450  * is returned, the tag is empty.
451  */
452 static char *
453 tag_expand(struct tag *tag, struct diffinfo *di)
454 {
455         /*
456          * CVS formats dates as "XXXX/XX/XX XX:XX:XX".  32 bytes
457          * is big enough until year 10,000,000,000,000,000 :-).
458          */
459         char cvsdate[32];
460         struct tm tm;
461         char *filename, *val;
462         int error;
463
464         error = rcsdatetotm(di->di_revdate, &tm);
465         if (error)
466                 err(1, "strptime");
467         if (strftime(cvsdate, sizeof(cvsdate), "%Y/%m/%d %H:%M:%S", &tm) == 0)
468                 err(1, "strftime");
469         filename = strrchr(di->di_rcsfile, '/');
470         if (filename == NULL)
471                 filename = di->di_rcsfile;
472         else
473                 filename++;
474
475         switch (tag->key) {
476         case RCSKEY_AUTHOR:
477                 xasprintf(&val, "%s", di->di_author);
478                 break;
479         case RCSKEY_CVSHEADER:
480                 xasprintf(&val, "%s %s %s %s %s", di->di_rcsfile,
481                     di->di_revnum, cvsdate, di->di_author, di->di_state);
482                 break;
483         case RCSKEY_DATE:
484                 xasprintf(&val, "%s", cvsdate);
485                 break;
486         case RCSKEY_HEADER:
487                 xasprintf(&val, "%s/%s %s %s %s %s", di->di_cvsroot,
488                     di->di_rcsfile, di->di_revnum, cvsdate, di->di_author,
489                     di->di_state);
490                 break;
491         case RCSKEY_ID:
492                 xasprintf(&val, "%s %s %s %s %s", filename, di->di_revnum,
493                     cvsdate, di->di_author, di->di_state);
494                 break;
495         case RCSKEY_LOCKER:
496                 /*
497                  * Unimplemented even in CVSup sources.  It seems we don't
498                  * even have this information sent by the server.
499                  */
500                 return (NULL);
501         case RCSKEY_LOG:
502                 /* XXX */
503                 printf("%s: Implement Log keyword expansion\n", __func__);
504                 return (NULL);
505         case RCSKEY_NAME:
506                 if (di->di_tag != NULL)
507                         xasprintf(&val, "%s", di->di_tag);
508                 else
509                         return (NULL);
510                 break;
511         case RCSKEY_RCSFILE:
512                 xasprintf(&val, "%s", filename);
513                 break;
514         case RCSKEY_REVISION:
515                 xasprintf(&val, "%s", di->di_revnum);
516                 break;
517         case RCSKEY_SOURCE:
518                 xasprintf(&val, "%s/%s", di->di_cvsroot, di->di_rcsfile);
519                 break;
520         case RCSKEY_STATE:
521                 xasprintf(&val, "%s", di->di_state);
522                 break;
523         }
524         return (val);
525 }