]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - contrib/csup/keyword.c
This commit was generated by cvs2svn to compensate for changes in r178382,
[FreeBSD/FreeBSD.git] / contrib / 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 void
156 keyword_free(struct keyword *keyword)
157 {
158         struct tag *tag;
159
160         if (keyword == NULL)
161                 return;
162         while (!STAILQ_EMPTY(&keyword->keywords)) {
163                 tag = STAILQ_FIRST(&keyword->keywords);
164                 STAILQ_REMOVE_HEAD(&keyword->keywords, next);
165                 tag_free(tag);
166         }
167         free(keyword);
168 }
169
170 int
171 keyword_alias(struct keyword *keyword, const char *ident, const char *rcskey)
172 {
173         struct tag *new, *tag;
174
175         STAILQ_FOREACH(tag, &keyword->keywords, next) {
176                 if (strcmp(tag->ident, rcskey) == 0) {
177                         new = tag_new(ident, tag->key);
178                         STAILQ_INSERT_HEAD(&keyword->keywords, new, next);
179                         return (0);
180                 }
181         }
182         errno = ENOENT;
183         return (-1);
184 }
185
186 int
187 keyword_enable(struct keyword *keyword, const char *ident)
188 {
189         struct tag *tag;
190         int all;
191
192         all = 0;
193         if (strcmp(ident, ".") == 0)
194                 all = 1;
195
196         STAILQ_FOREACH(tag, &keyword->keywords, next) {
197                 if (!all && strcmp(tag->ident, ident) != 0)
198                         continue;
199                 tag->enabled = 1;
200                 if (!all)
201                         return (0);
202         }
203         if (!all) {
204                 errno = ENOENT;
205                 return (-1);
206         }
207         return (0);
208 }
209
210 int
211 keyword_disable(struct keyword *keyword, const char *ident)
212 {
213         struct tag *tag;
214         int all;
215
216         all = 0;
217         if (strcmp(ident, ".") == 0)
218                 all = 1;
219
220         STAILQ_FOREACH(tag, &keyword->keywords, next) {
221                 if (!all && strcmp(tag->ident, ident) != 0)
222                         continue;
223                 tag->enabled = 0;
224                 if (!all)
225                         return (0);
226         }
227
228         if (!all) {
229                 errno = ENOENT;
230                 return (-1);
231         }
232         return (0);
233 }
234
235 void
236 keyword_prepare(struct keyword *keyword)
237 {
238         struct tag *tag, *temp;
239
240         STAILQ_FOREACH_SAFE(tag, &keyword->keywords, next, temp) {
241                 if (!tag->enabled) {
242                         STAILQ_REMOVE(&keyword->keywords, tag, tag, next);
243                         tag_free(tag);
244                         continue;
245                 }
246         }
247 }
248
249 /*
250  * Expand appropriate RCS keywords.  If there's no tag to expand,
251  * keyword_expand() returns 0, otherwise it returns 1 and writes a
252  * pointer to the new line in *buf and the new len in *len.  The
253  * new line is allocated with malloc() and needs to be freed by the
254  * caller after use.
255  */
256 int
257 keyword_expand(struct keyword *keyword, struct diffinfo *di, char *line,
258     size_t size, char **buf, size_t *len)
259 {
260         struct tag *tag;
261         char *dollar, *keystart, *valstart, *vallim, *next;
262         char *linestart, *newline, *newval, *cp, *tmp;
263         size_t left, newsize, vallen;
264
265         if (di->di_expand == EXPAND_OLD || di->di_expand == EXPAND_BINARY)
266                 return (0);
267         newline = NULL;
268         newsize = 0;
269         left = size;
270         linestart = cp = line;
271 again:
272         dollar = memchr(cp, '$', left);
273         if (dollar == NULL) {
274                 if (newline != NULL) {
275                         *buf = newline;
276                         *len = newsize;
277                         return (1);
278                 }
279                 return (0);
280         }
281         keystart = dollar + 1;
282         left -= keystart - cp;
283         vallim = memchr(keystart, '$', left);
284         if (vallim == NULL) {
285                 if (newline != NULL) {
286                         *buf = newline;
287                         *len = newsize;
288                         return (1);
289                 }
290                 return (0);
291         }
292         if (vallim == keystart) {
293                 cp = keystart;
294                 goto again;
295         }
296         valstart = memchr(keystart, ':', left);
297         if (valstart == keystart) {
298                 cp = vallim;
299                 left -= vallim - keystart;
300                 goto again;
301         }
302         if (valstart == NULL || valstart > vallim)
303                 valstart = vallim;
304
305         if (valstart < keystart + keyword->minkeylen ||
306             valstart > keystart + keyword->maxkeylen) {
307                 cp = vallim;
308                 left -= vallim -keystart;
309                 goto again;
310         }
311         STAILQ_FOREACH(tag, &keyword->keywords, next) {
312                 if (strncmp(tag->ident, keystart, valstart - keystart) == 0 &&
313                     tag->ident[valstart - keystart] == '\0') {
314                         if (newline != NULL)
315                                 tmp = newline;
316                         else
317                                 tmp = NULL;
318                         newval = NULL;
319                         if (di->di_expand == EXPAND_KEY) {
320                                 newsize = dollar - linestart + 1 +
321                                     valstart - keystart + 1 +
322                                     size - (vallim + 1 - linestart);
323                                 newline = xmalloc(newsize);
324                                 cp = newline;
325                                 memcpy(cp, linestart, dollar - linestart);
326                                 cp += dollar - linestart;
327                                 *cp++ = '$';
328                                 memcpy(cp, keystart, valstart - keystart);
329                                 cp += valstart - keystart;
330                                 *cp++ = '$';
331                                 next = cp;
332                                 memcpy(cp, vallim + 1,
333                                     size - (vallim + 1 - linestart));
334                         } else if (di->di_expand == EXPAND_VALUE) {
335                                 newval = tag_expand(tag, di);
336                                 if (newval == NULL)
337                                         vallen = 0;
338                                 else
339                                         vallen = strlen(newval);
340                                 newsize = dollar - linestart +
341                                     vallen +
342                                     size - (vallim + 1 - linestart);
343                                 newline = xmalloc(newsize);
344                                 cp = newline;
345                                 memcpy(cp, linestart, dollar - linestart);
346                                 cp += dollar - linestart;
347                                 if (newval != NULL) {
348                                         memcpy(cp, newval, vallen);
349                                         cp += vallen;
350                                 }
351                                 next = cp;
352                                 memcpy(cp, vallim + 1,
353                                     size - (vallim + 1 - linestart));
354                         } else {
355                                 assert(di->di_expand == EXPAND_DEFAULT ||
356                                     di->di_expand == EXPAND_KEYVALUE ||
357                                     di->di_expand == EXPAND_KEYVALUELOCKER);
358                                 newval = tag_expand(tag, di);
359                                 if (newval == NULL)
360                                         vallen = 0;
361                                 else
362                                         vallen = strlen(newval);
363                                 newsize = dollar - linestart + 1 +
364                                     valstart - keystart + 2 +
365                                     vallen + 2 +
366                                     size - (vallim + 1 - linestart);
367                                 newline = xmalloc(newsize);
368                                 cp = newline;
369                                 memcpy(cp, linestart, dollar - linestart);
370                                 cp += dollar - linestart;
371                                 *cp++ = '$';
372                                 memcpy(cp, keystart, valstart - keystart);
373                                 cp += valstart - keystart;
374                                 *cp++ = ':';
375                                 *cp++ = ' ';
376                                 if (newval != NULL) {
377                                         memcpy(cp, newval, vallen);
378                                         cp += vallen;
379                                 }
380                                 *cp++ = ' ';
381                                 *cp++ = '$';
382                                 next = cp;
383                                 memcpy(cp, vallim + 1,
384                                     size - (vallim + 1 - linestart));
385                         }
386                         if (newval != NULL)
387                                 free(newval);
388                         if (tmp != NULL)
389                                 free(tmp);
390                         /*
391                          * Continue looking for tags in the rest of the line.
392                          */
393                         cp = next;
394                         size = newsize;
395                         left = size - (cp - newline);
396                         linestart = newline;
397                         goto again;
398                 }
399         }
400         cp = vallim;
401         left = size - (cp - linestart);
402         goto again;
403 }
404
405 static struct tag *
406 tag_new(const char *ident, rcskey_t key)
407 {
408         struct tag *new;
409
410         new = xmalloc(sizeof(struct tag));
411         new->ident = xstrdup(ident);
412         new->key = key;
413         new->enabled = 1;
414         return (new);
415 }
416
417 static void
418 tag_free(struct tag *tag)
419 {
420
421         free(tag->ident);
422         free(tag);
423 }
424
425 /*
426  * Expand a specific tag and return the new value.  If NULL
427  * is returned, the tag is empty.
428  */
429 static char *
430 tag_expand(struct tag *tag, struct diffinfo *di)
431 {
432         /*
433          * CVS formats dates as "XXXX/XX/XX XX:XX:XX".  32 bytes
434          * is big enough until year 10,000,000,000,000,000 :-).
435          */
436         char cvsdate[32];
437         struct tm tm;
438         char *filename, *val;
439         int error;
440
441         error = rcsdatetotm(di->di_revdate, &tm);
442         if (error)
443                 err(1, "strptime");
444         if (strftime(cvsdate, sizeof(cvsdate), "%Y/%m/%d %H:%M:%S", &tm) == 0)
445                 err(1, "strftime");
446         filename = strrchr(di->di_rcsfile, '/');
447         if (filename == NULL)
448                 filename = di->di_rcsfile;
449         else
450                 filename++;
451
452         switch (tag->key) {
453         case RCSKEY_AUTHOR:
454                 xasprintf(&val, "%s", di->di_author);
455                 break;
456         case RCSKEY_CVSHEADER:
457                 xasprintf(&val, "%s %s %s %s %s", di->di_rcsfile,
458                     di->di_revnum, cvsdate, di->di_author, di->di_state);
459                 break;
460         case RCSKEY_DATE:
461                 xasprintf(&val, "%s", cvsdate);
462                 break;
463         case RCSKEY_HEADER:
464                 xasprintf(&val, "%s/%s %s %s %s %s", di->di_cvsroot,
465                     di->di_rcsfile, di->di_revnum, cvsdate, di->di_author,
466                     di->di_state);
467                 break;
468         case RCSKEY_ID:
469                 xasprintf(&val, "%s %s %s %s %s", filename, di->di_revnum,
470                     cvsdate, di->di_author, di->di_state);
471                 break;
472         case RCSKEY_LOCKER:
473                 /*
474                  * Unimplemented even in CVSup sources.  It seems we don't
475                  * even have this information sent by the server.
476                  */
477                 return (NULL);
478         case RCSKEY_LOG:
479                 /* XXX */
480                 printf("%s: Implement Log keyword expansion\n", __func__);
481                 return (NULL);
482         case RCSKEY_NAME:
483                 if (di->di_tag != NULL)
484                         xasprintf(&val, "%s", di->di_tag);
485                 else
486                         return (NULL);
487                 break;
488         case RCSKEY_RCSFILE:
489                 xasprintf(&val, "%s", filename);
490                 break;
491         case RCSKEY_REVISION:
492                 xasprintf(&val, "%s", di->di_revnum);
493                 break;
494         case RCSKEY_SOURCE:
495                 xasprintf(&val, "%s/%s", di->di_cvsroot, di->di_rcsfile);
496                 break;
497         case RCSKEY_STATE:
498                 xasprintf(&val, "%s", di->di_state);
499                 break;
500         }
501         return (val);
502 }