xref: /openbsd/usr.bin/cvs/trigger.c (revision c723dd1d)
1 /*	$OpenBSD: trigger.c,v 1.11 2008/06/10 14:40:54 joris Exp $	*/
2 /*
3  * Copyright (c) 2008 Tobias Stoeckmann <tobias@openbsd.org>
4  * Copyright (c) 2008 Jonathan Armani <dbd@asystant.net>
5  *
6  * Permission to use, copy, modify, and distribute this software for any
7  * purpose with or without fee is hereby granted, provided that the above
8  * copyright notice and this permission notice appear in all copies.
9  *
10  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17  */
18 
19 #include <sys/queue.h>
20 #include <sys/types.h>
21 
22 #include <ctype.h>
23 #include <errno.h>
24 #include <libgen.h>
25 #include <regex.h>
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include <string.h>
29 #include <unistd.h>
30 
31 #include "config.h"
32 #include "cvs.h"
33 
34 static int	 expand_args(BUF *, struct file_info_list *, const char *,
35     const char *, char *);
36 static char	*parse_cmd(int, char *, const char *, struct file_info_list *);
37 
38 static int
39 expand_args(BUF *buf, struct file_info_list *file_info, const char *repo,
40     const char *allowed_args, char *format)
41 {
42 	int oldstyle, quote;
43 	struct file_info *fi = NULL;
44 	char *p, valbuf[2] = { '\0', '\0' };
45 	const char *val;
46 
47 	if (file_info != NULL && !TAILQ_EMPTY(file_info))
48 		fi = TAILQ_FIRST(file_info);
49 
50 	quote = oldstyle = 0;
51 
52 	/* Why does GNU cvs print something if it encounters %{}? */
53 	if (*format == '\0')
54 		oldstyle = 1;
55 
56 	for (p = format; *p != '\0'; p++) {
57 		if (*p != '%' && strchr(allowed_args, *p) == NULL)
58 			return 1;
59 
60 		switch (*p) {
61 		case 's':
62 		case 'V':
63 		case 'v':
64 			quote = 1;
65 			oldstyle = 1;
66 			break;
67 		default:
68 			break;
69 		}
70 	}
71 	if (quote)
72 		cvs_buf_putc(buf, '"');
73 	if (oldstyle) {
74 		cvs_buf_puts(buf, repo);
75 		cvs_buf_putc(buf, ' ');
76 	}
77 
78 	if (*format == '\0')
79 		return 0;
80 
81 	/*
82 	 * check like this, add only uses loginfo for directories anyway
83 	 */
84 	if (cvs_cmdop == CVS_OP_ADD) {
85 		cvs_buf_puts(buf, "- New directory");
86 		if (quote)
87 			cvs_buf_putc(buf, '"');
88 		return (0);
89 	}
90 
91 	if (cvs_cmdop == CVS_OP_IMPORT) {
92 		cvs_buf_puts(buf, "- Imported sources");
93 		if (quote)
94 			cvs_buf_putc(buf, '"');
95 		return (0);
96 	}
97 
98 	for (;;) {
99 		for (p = format; *p != '\0';) {
100 			val = NULL;
101 
102 			switch (*p) {
103 			case '%':
104 				val = "%";
105 				break;
106 			case 'b':
107 				if (fi != NULL) {
108 					valbuf[0] = fi->tag_type;
109 					val = valbuf;
110 				}
111 				break;
112 			case 'o':
113 				if (fi != NULL)
114 					val = fi->tag_op;
115 				break;
116 			case 'p':
117 		    		val = current_cvsroot->cr_dir;
118 				break;
119 			case 'r':
120 				val = repo;
121 				break;
122 			case 'l':
123 			case 'S':
124 			case 's':
125 				if (fi != NULL)
126 					val = fi->file_path;
127 				break;
128 			case 't':
129 				if (fi != NULL)
130 					val = fi->tag_new;
131 				break;
132 			case 'V':
133 				if (fi != NULL) {
134 					if (fi->crevstr != NULL &&
135 					    !strcmp(fi->crevstr,
136 					    "Non-existent"))
137 						val = "NONE";
138 					else
139 						val = fi->crevstr;
140 				}
141 				break;
142 			case 'v':
143 				if (fi != NULL) {
144 					if (fi->nrevstr != NULL &&
145 					    !strcmp(fi->nrevstr, "Removed"))
146 						val = "NONE";
147 					else
148 						val = fi->nrevstr;
149 				}
150 				break;
151 			default:
152 				return 1;
153 			}
154 
155 			if (val != NULL)
156 				cvs_buf_puts(buf, val);
157 
158 			if (*(++p) != '\0')
159 				cvs_buf_putc(buf, ',');
160 		}
161 
162 		if (fi != NULL)
163 			fi = TAILQ_NEXT(fi, flist);
164 		if (fi == NULL)
165 			break;
166 
167 		if (strlen(format) == 1 && (*format == '%' || *format == 'o' ||
168 		    *format == 'p' || *format == 'r' || *format == 't'))
169 			break;
170 
171 		cvs_buf_putc(buf, ' ');
172 	}
173 
174 	if (quote)
175 		cvs_buf_putc(buf, '"');
176 
177 	return 0;
178 }
179 
180 static char *
181 parse_cmd(int type, char *cmd, const char *repo,
182     struct file_info_list *file_info)
183 {
184 	int expanded = 0;
185 	char argbuf[2] = { '\0', '\0' };
186 	char *allowed_args, *default_args, *args, *file, *p, *q = NULL;
187 	size_t pos;
188 	BUF *buf;
189 
190 	switch (type) {
191 	case CVS_TRIGGER_COMMITINFO:
192 		allowed_args = "prsS{}";
193 		default_args = " %p/%r %S";
194 		file = CVS_PATH_COMMITINFO;
195 		break;
196 	case CVS_TRIGGER_LOGINFO:
197 		allowed_args = "prsSvVt{}";
198 		default_args = NULL;
199 		file = CVS_PATH_LOGINFO;
200 		break;
201 	case CVS_TRIGGER_VERIFYMSG:
202 		allowed_args = "l";
203 		default_args = " %l";
204 		file = CVS_PATH_VERIFYMSG;
205 		break;
206 	case CVS_TRIGGER_TAGINFO:
207 		allowed_args = "btoprsSvV{}";
208 		default_args = " %t %o %p/%r %{sv}";
209 		file = CVS_PATH_TAGINFO;
210 		break;
211 	default:
212 		return (NULL);
213 	}
214 
215 	/* XXX move this out of this function */
216 	/* before doing any stuff, check if the command starts with % */
217 	for (p = cmd; *p != '%' && !isspace(*p) && *p != '\0'; p++)
218 		;
219 	if (*p == '%')
220 		return (NULL);
221 
222 	buf = cvs_buf_alloc(1024);
223 
224 	p = cmd;
225 again:
226 	for (; *p != '\0'; p++) {
227 		if ((pos = strcspn(p, "%")) != 0) {
228 			cvs_buf_append(buf, p, pos);
229 			p += pos;
230 		}
231 
232 		if (*p++ == '\0')
233 			break;
234 
235 		q = NULL;
236 		switch (*p) {
237 		case '\0':
238 			goto bad;
239 		case '{':
240 			if (strchr(allowed_args, '{') == NULL)
241 				goto bad;
242 			pos = strcspn(++p, "}");
243 			if (p[pos] == '\0')
244 				goto bad;
245 			q = xmalloc(pos + 1);
246 			memcpy(q, p, pos);
247 			q[pos] = '\0';
248 			args = q;
249 			p += pos;
250 			break;
251 		default:
252 			argbuf[0] = *p;
253 			args = argbuf;
254 			break;
255 		}
256 
257 		if (expand_args(buf, file_info, repo, allowed_args, args))
258 			goto bad;
259 		expanded = 1;
260 
261 		if (q != NULL)
262 			xfree(q);
263 	}
264 
265 	if (!expanded && default_args != NULL) {
266 		p = default_args;
267 		expanded = 1;
268 		goto again;
269 	}
270 
271 	cvs_buf_putc(buf, '\0');
272 	return (cvs_buf_release(buf));
273 
274 bad:
275 	if (q != NULL)
276 		xfree(q);
277 	cvs_log(LP_NOTICE, "%s contains malformed command '%s'", file, cmd);
278 	cvs_buf_free(buf);
279 	return (NULL);
280 }
281 
282 int
283 cvs_trigger_handle(int type, char *repo, char *in, struct trigger_list *list,
284     struct file_info_list *files)
285 {
286 	int r;
287 	char *cmd;
288 	struct trigger_line *line;
289 
290 	TAILQ_FOREACH(line, list, flist) {
291 		cmd = parse_cmd(type, line->line, repo, files);
292 		if (cmd != NULL) {
293 			switch(type) {
294 			case CVS_TRIGGER_COMMITINFO:
295 			case CVS_TRIGGER_TAGINFO:
296 			case CVS_TRIGGER_VERIFYMSG:
297 				if ((r = cvs_exec(cmd, NULL, 1)) != 0) {
298 					xfree(cmd);
299 					return r;
300 				}
301 				break;
302 			default:
303 				(void)cvs_exec(cmd, in, 1);
304 				break;
305 			}
306 			xfree(cmd);
307 		}
308 	}
309 
310 	return 0;
311 }
312 
313 struct trigger_list *
314 cvs_trigger_getlines(char * file, char * repo)
315 {
316 	FILE *fp;
317 	int allow_all, lineno, match = 0;
318 	size_t len;
319 	regex_t preg;
320 	struct trigger_list *list;
321 	struct trigger_line *tline;
322 	char fpath[MAXPATHLEN];
323 	char *currentline, *defaultline = NULL, *nline, *p, *q, *regex;
324 
325 	list = xmalloc(sizeof(*list));
326 	TAILQ_INIT(list);
327 
328 	if (strcmp(file, CVS_PATH_EDITINFO) == 0 ||
329 	    strcmp(file, CVS_PATH_VERIFYMSG) == 0)
330 		allow_all = 0;
331 	else
332 		allow_all = 1;
333 
334 	(void)xsnprintf(fpath, MAXPATHLEN, "%s/%s", current_cvsroot->cr_dir,
335 	    file);
336 
337 	if ((fp = fopen(fpath, "r")) == NULL) {
338 		if (errno != ENOENT)
339 			cvs_log(LP_ERRNO, "cvs_trigger_getlines: %s", file);
340 		return (NULL);
341 	}
342 
343 	lineno = 0;
344 	nline = NULL;
345 	while ((currentline = fgetln(fp, &len)) != NULL) {
346 		if (currentline[len - 1] == '\n') {
347 			currentline[len - 1] = '\0';
348 		} else {
349 			nline = xmalloc(len + 1);
350 			memcpy(nline, currentline, len);
351 			nline[len] = '\0';
352 			currentline = nline;
353 		}
354 
355 		lineno++;
356 
357 		for (p = currentline; isspace(*p); p++)
358 			;
359 
360 		if (*p == '\0' || *p == '#')
361 			continue;
362 
363 		for (q = p; !isspace(*q) && *q != '\0'; q++)
364 			;
365 
366 		if (*q == '\0')
367 			goto bad;
368 
369 		/* XXX why do you check *p (regex)? */
370 		if (*p == '%')
371 			goto bad;
372 
373 		*q++ = '\0';
374 		regex = p;
375 
376 		for (; isspace(*q); q++)
377 			;
378 
379 		if (*q == '\0')
380 			goto bad;
381 
382 		if (strcmp(regex, "ALL") == 0 && allow_all) {
383 			tline = xmalloc(sizeof(*tline));
384 			tline->line = xstrdup(q);
385 			TAILQ_INSERT_TAIL(list, tline, flist);
386 		} else if (defaultline == NULL && !match &&
387 		    strcmp(regex, "DEFAULT") == 0) {
388 			defaultline = xstrdup(q);
389 		} else if (!match) {
390 			if (regcomp(&preg, regex, REG_NOSUB|REG_EXTENDED))
391 				goto bad;
392 
393 			if (regexec(&preg, repo, 0, NULL, 0) != REG_NOMATCH) {
394 				match = 1;
395 
396 				tline = xmalloc(sizeof(*tline));
397 				tline->line = xstrdup(q);
398 				TAILQ_INSERT_HEAD(list, tline, flist);
399 			}
400 			regfree(&preg);
401 		}
402 	}
403 
404 	if (nline != NULL)
405 		xfree(nline);
406 
407 	if (defaultline != NULL) {
408 		if (!match) {
409 			tline = xmalloc(sizeof(*tline));
410 			tline->line = defaultline;
411 			TAILQ_INSERT_HEAD(list, tline, flist);
412 		} else
413 			xfree(defaultline);
414 	}
415 
416 	if (TAILQ_EMPTY(list)) {
417 		xfree(list);
418 		list = NULL;
419 	}
420 
421 	return (list);
422 
423 bad:
424 	cvs_log(LP_NOTICE, "%s: malformed line %d", file, lineno);
425 
426 	if (defaultline != NULL)
427 		xfree(defaultline);
428 	cvs_trigger_freelist(list);
429 
430 	return (NULL);
431 }
432 
433 void
434 cvs_trigger_freelist(struct trigger_list * list)
435 {
436 	struct trigger_line *line;
437 
438 	while ((line = TAILQ_FIRST(list)) != NULL) {
439 		TAILQ_REMOVE(list, line, flist);
440 		xfree(line->line);
441 		xfree(line);
442 	}
443 
444 	xfree(list);
445 }
446 
447 void
448 cvs_trigger_freeinfo(struct file_info_list * list)
449 {
450 	struct file_info * fi;
451 
452 	while ((fi = TAILQ_FIRST(list)) != NULL) {
453 		TAILQ_REMOVE(list, fi, flist);
454 
455 		if (fi->file_path != NULL)
456 			xfree(fi->file_path);
457 		if (fi->file_wd != NULL)
458 			xfree(fi->file_wd);
459 		if (fi->crevstr != NULL)
460 			xfree(fi->crevstr);
461 		if (fi->nrevstr != NULL)
462 			xfree(fi->nrevstr);
463 		if (fi->tag_new != NULL)
464 			xfree(fi->tag_new);
465 		if (fi->tag_old != NULL)
466 			xfree(fi->tag_old);
467 
468 		xfree(fi);
469 	}
470 }
471 
472 void
473 cvs_trigger_loginfo_header(BUF *buf, char *repo)
474 {
475 	char *dir, pwd[MAXPATHLEN];
476 	char hostname[MAXHOSTNAMELEN];
477 
478 	if (gethostname(hostname, sizeof(hostname)) == -1) {
479 		fatal("cvs_trigger_loginfo_header: gethostname failed %s",
480 		    strerror(errno));
481 	}
482 
483 	if (getcwd(pwd, sizeof(pwd)) == NULL)
484 		fatal("cvs_trigger_loginfo_header: Cannot get working "
485 		    "directory");
486 
487 	if ((dir = dirname(pwd)) == NULL) {
488 		fatal("cvs_trigger_loginfo_header: dirname failed %s",
489 		    strerror(errno));
490 	}
491 
492 	cvs_buf_puts(buf, "Update of ");
493 	cvs_buf_puts(buf, current_cvsroot->cr_dir);
494 	cvs_buf_putc(buf, '/');
495 	cvs_buf_puts(buf, repo);
496 	cvs_buf_putc(buf, '\n');
497 
498 	cvs_buf_puts(buf, "In directory ");
499 	cvs_buf_puts(buf, hostname);
500 	cvs_buf_puts(buf, ":");
501 	cvs_buf_puts(buf, dirname(pwd));
502 	cvs_buf_putc(buf, '/');
503 	cvs_buf_puts(buf, repo);
504 	cvs_buf_putc(buf, '\n');
505 	cvs_buf_putc(buf, '\n');
506 }
507 
508