keyword.c revision 156230
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: vendor/csup/dist/contrib/csup/keyword.c 156230 2006-03-03 04:11:29Z mux $ 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 50enum 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 65typedef enum rcskey rcskey_t; 66 67struct tag { 68 char *ident; 69 rcskey_t key; 70 int enabled; 71 STAILQ_ENTRY(tag) next; 72}; 73 74static struct tag *tag_new(const char *, rcskey_t); 75static char *tag_expand(struct tag *, struct diffinfo *); 76static void tag_free(struct tag *); 77 78struct keyword { 79 STAILQ_HEAD(, tag) keywords; /* Enabled keywords. */ 80 size_t minkeylen; 81 size_t maxkeylen; 82}; 83 84/* Default CVS keywords. */ 85static 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 104struct keyword * 105keyword_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 133int 134keyword_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 155void 156keyword_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 170int 171keyword_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 186int 187keyword_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 210int 211keyword_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 235void 236keyword_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 */ 256int 257keyword_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; 271again: 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 405static struct tag * 406tag_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 417static void 418tag_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 */ 429static char * 430tag_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} 503