xref: /openbsd/usr.bin/cvs/trigger.c (revision ed09211b)
1 /*	$OpenBSD: trigger.c,v 1.3 2008/06/10 02:08:49 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_append(buf, repo, strlen(repo));
75 		cvs_buf_putc(buf, ' ');
76 	}
77 
78 	if (*format == '\0')
79 		return 0;
80 
81 	for (;;) {
82 		for (p = format; *p != '\0';) {
83 			val = NULL;
84 
85 			switch (*p) {
86 			case '%':
87 				val = "%";
88 				break;
89 			case 'b':
90 				if (fi != NULL) {
91 					valbuf[0] = fi->tag_type;
92 					val = valbuf;
93 				}
94 				break;
95 			case 'o':
96 				if (fi != NULL)
97 					val = fi->tag_op;
98 				break;
99 			case 'p':
100 		    		val = current_cvsroot->cr_dir;
101 				break;
102 			case 'r':
103 				val = repo;
104 				break;
105 			case 'l':
106 			case 'S':
107 			case 's':
108 				if (fi != NULL)
109 					val = fi->file_path;
110 				break;
111 			case 't':
112 				if (fi != NULL)
113 					val = fi->tag_new;
114 				break;
115 			case 'V':
116 				if (fi != NULL)
117 					val = fi->crevstr;
118 				break;
119 			case 'v':
120 				if (fi != NULL)
121 					val = fi->nrevstr;
122 				break;
123 			default:
124 				return 1;
125 			}
126 
127 			if (val != NULL)
128 				cvs_buf_append(buf, val, strlen(val));
129 
130 			if (*(++p) != '\0')
131 				cvs_buf_putc(buf, ',');
132 		}
133 
134 		if (fi != NULL)
135 			fi = TAILQ_NEXT(fi, flist);
136 		if (fi == NULL)
137 			break;
138 
139 		if (strlen(format) == 1 && (*format == '%' || *format == 'o' ||
140 		    *format == 'p' || *format == 'r' || *format == 't'))
141 			break;
142 
143 		cvs_buf_putc(buf, ' ');
144 	}
145 
146 	if (quote)
147 		cvs_buf_putc(buf, '"');
148 
149 	return 0;
150 }
151 
152 static char *
153 parse_cmd(int type, char *cmd, const char *repo,
154     struct file_info_list *file_info)
155 {
156 	int expanded = 0;
157 	char argbuf[2] = { '\0', '\0' };
158 	char *allowed_args, *default_args, *args, *p, *q = NULL;
159 	size_t pos;
160 	BUF *buf;
161 
162 	switch (type) {
163 	case CVS_TRIGGER_COMMITINFO:
164 		allowed_args = "prsS{}";
165 		default_args = " %p/%r %S";
166 		break;
167 	case CVS_TRIGGER_LOGINFO:
168 		allowed_args = "prsSvV{}";
169 		default_args = NULL;
170 		break;
171 	case CVS_TRIGGER_VERIFYMSG:
172 		allowed_args = "l";
173 		default_args = " %l";
174 		break;
175 	case CVS_TRIGGER_TAGINFO:
176 		allowed_args = "btoprsSvV{}";
177 		default_args = " %t %o %p/%r %{sv}";
178 		break;
179 	default:
180 		return (NULL);
181 	}
182 
183 	/* XXX move this out of this function */
184 	/* before doing any stuff, check if the command starts with % */
185 	for (p = cmd; *p != '%' && !isspace(*p) && *p != '\0'; p++)
186 		;
187 	if (*p == '%')
188 		return (NULL);
189 
190 	buf = cvs_buf_alloc(1024);
191 
192 	p = cmd;
193 again:
194 	for (; *p != '\0'; p++) {
195 		if ((pos = strcspn(p, "%")) != 0) {
196 			cvs_buf_append(buf, p, pos);
197 			p += pos;
198 		}
199 
200 		if (*p++ == '\0')
201 			break;
202 
203 		q = NULL;
204 		switch (*p) {
205 		case '\0':
206 			goto bad;
207 		case '{':
208 			/* XXX do we realy have to check for { in there? */
209 			if (strchr(allowed_args, '{') == NULL)
210 				goto bad;
211 			pos = strcspn(++p, "}");
212 			if (p[pos] == '\0')
213 				goto bad;
214 			q = xmalloc(pos + 1);
215 			memcpy(q, p, pos);
216 			q[pos] = '\0';
217 			args = q;
218 			p += pos;
219 			break;
220 		default:
221 			argbuf[0] = *p;
222 			args = argbuf;
223 			break;
224 		}
225 
226 		if (expand_args(buf, file_info, repo, allowed_args, args))
227 			goto bad;
228 		expanded = 1;
229 
230 		if (q != NULL)
231 			xfree(q);
232 	}
233 
234 	if (!expanded && default_args != NULL) {
235 		p = default_args;
236 		expanded = 1;
237 		goto again;
238 	}
239 
240 	cvs_buf_putc(buf, '\0');
241 	return (cvs_buf_release(buf));
242 
243 bad:
244 	if (q != NULL)
245 		xfree(q);
246 	cvs_log(LP_NOTICE, "CVSROOT: malformed line %s", cmd);
247 	cvs_buf_free(buf);
248 	return (NULL);
249 }
250 
251 int
252 cvs_trigger_handle(int type, char *repo, char *in, struct trigger_list *list,
253     struct file_info_list *files)
254 {
255 	int r;
256 	char *cmd;
257 	struct trigger_line *line;
258 
259 	TAILQ_FOREACH(line, list, flist) {
260 		cmd = parse_cmd(type, line->line, repo, files);
261 		if (cmd != NULL) {
262 			switch(type) {
263 			case CVS_TRIGGER_COMMITINFO:
264 			case CVS_TRIGGER_TAGINFO:
265 			case CVS_TRIGGER_VERIFYMSG:
266 				if ((r = cvs_exec(cmd, NULL, 1)) != 0) {
267 					xfree(cmd);
268 					return r;
269 				}
270 				break;
271 			default:
272 				(void)cvs_exec(cmd, in, 1);
273 				break;
274 			}
275 			xfree(cmd);
276 		}
277 	}
278 
279 	return 0;
280 }
281 
282 struct trigger_list *
283 cvs_trigger_getlines(char * file, char * repo)
284 {
285 	FILE *fp;
286 	int allow_all, lineno, match = 0;
287 	size_t len;
288 	regex_t preg;
289 	struct trigger_list *list;
290 	struct trigger_line *tline;
291 	char fpath[MAXPATHLEN];
292 	char *currentline, *defaultline = NULL, *nline, *p, *q, *regex;
293 
294 	list = xmalloc(sizeof(*list));
295 	TAILQ_INIT(list);
296 
297 	if (strcmp(file, CVS_PATH_EDITINFO) == 0 ||
298 	    strcmp(file, CVS_PATH_VERIFYMSG) == 0)
299 		allow_all = 0;
300 	else
301 		allow_all = 1;
302 
303 	(void)xsnprintf(fpath, MAXPATHLEN, "%s/%s", current_cvsroot->cr_dir,
304 	    file);
305 
306 	if ((fp = fopen(fpath, "r")) == NULL) {
307 		if (errno != ENOENT)
308 			cvs_log(LP_ERRNO, "cvs_trigger_getlines: %s", file);
309 		return (NULL);
310 	}
311 
312 	lineno = 0;
313 	nline = NULL;
314 	while ((currentline = fgetln(fp, &len)) != NULL) {
315 		if (currentline[len - 1] == '\n') {
316 			currentline[len - 1] = '\0';
317 		} else {
318 			nline = xmalloc(len + 1);
319 			memcpy(nline, currentline, len);
320 			nline[len] = '\0';
321 			currentline = nline;
322 		}
323 
324 		lineno++;
325 
326 		for (p = currentline; isspace(*p); p++)
327 			;
328 
329 		if (*p == '\0' || *p == '#')
330 			continue;
331 
332 		for (q = p; !isspace(*q) && *q != '\0'; q++)
333 			;
334 
335 		if (*q == '\0')
336 			goto bad;
337 
338 		/* XXX why do you check *p (regex)? */
339 		if (*p == '%')
340 			goto bad;
341 
342 		*q++ = '\0';
343 		regex = p;
344 
345 		for (; isspace(*q); q++)
346 			;
347 
348 		if (*q == '\0')
349 			goto bad;
350 
351 		if (strcmp(regex, "ALL") == 0 && allow_all) {
352 			tline = xmalloc(sizeof(*tline));
353 			tline->line = xstrdup(q);
354 			TAILQ_INSERT_TAIL(list, tline, flist);
355 		} else if (defaultline == NULL && !match &&
356 		    strcmp(regex, "DEFAULT") == 0) {
357 			defaultline = xstrdup(q);
358 		} else if (!match) {
359 			if (regcomp(&preg, regex, REG_NOSUB|REG_EXTENDED))
360 				goto bad;
361 
362 			if (regexec(&preg, repo, 0, NULL, 0) != REG_NOMATCH) {
363 				match = 1;
364 
365 				tline = xmalloc(sizeof(*tline));
366 				tline->line = xstrdup(q);
367 				TAILQ_INSERT_HEAD(list, tline, flist);
368 			}
369 			regfree(&preg);
370 		}
371 	}
372 
373 	if (nline != NULL)
374 		xfree(nline);
375 
376 	if (defaultline != NULL) {
377 		if (!match) {
378 			tline = xmalloc(sizeof(*tline));
379 			tline->line = defaultline;
380 			TAILQ_INSERT_HEAD(list, tline, flist);
381 		} else
382 			xfree(defaultline);
383 	}
384 
385 	if (TAILQ_EMPTY(list)) {
386 		xfree(list);
387 		list = NULL;
388 	}
389 
390 	return (list);
391 
392 bad:
393 	cvs_log(LP_NOTICE, "%s: malformed line %d", file, lineno);
394 
395 	if (defaultline != NULL)
396 		xfree(defaultline);
397 	cvs_trigger_freelist(list);
398 
399 	return (NULL);
400 }
401 
402 void
403 cvs_trigger_freelist(struct trigger_list * list)
404 {
405 	struct trigger_line *line;
406 
407 	while ((line = TAILQ_FIRST(list)) != NULL) {
408 		TAILQ_REMOVE(list, line, flist);
409 		xfree(line->line);
410 		xfree(line);
411 	}
412 
413 	xfree(list);
414 }
415 
416 void
417 cvs_trigger_freeinfo(struct file_info_list * list)
418 {
419 	struct file_info * fi;
420 
421 	while ((fi = TAILQ_FIRST(list)) != NULL) {
422 		TAILQ_REMOVE(list, fi, flist);
423 
424 		if (fi->file_path != NULL)
425 			xfree(fi->file_path);
426 		if (fi->file_wd != NULL)
427 			xfree(fi->file_wd);
428 		if (fi->crevstr != NULL)
429 			xfree(fi->crevstr);
430 		if (fi->nrevstr != NULL)
431 			xfree(fi->nrevstr);
432 		if (fi->tag_new != NULL)
433 			xfree(fi->tag_new);
434 		if (fi->tag_old != NULL)
435 			xfree(fi->tag_old);
436 
437 		xfree(fi);
438 	}
439 }
440 
441 void
442 cvs_trigger_loginfo_header(BUF *buf, char *repo)
443 {
444 	char *dir, pwd[MAXPATHLEN];
445 	char hostname[MAXHOSTNAMELEN];
446 
447 	if (gethostname(hostname, sizeof(hostname)) == -1) {
448 		fatal("cvs_trigger_loginfo_header: gethostname failed %s",
449 		    strerror(errno));
450 	}
451 
452 	if (getcwd(pwd, sizeof(pwd)) == NULL)
453 		fatal("cvs_trigger_loginfo_header: Cannot get working "
454 		    "directory");
455 
456 	if ((dir = dirname(pwd)) == NULL) {
457 		fatal("cvs_trigger_loginfo_header: dirname failed %s",
458 		    strerror(errno));
459 	}
460 
461 	cvs_buf_puts(buf, "Update of ");
462 	cvs_buf_puts(buf, current_cvsroot->cr_dir);
463 	cvs_buf_putc(buf, '/');
464 	cvs_buf_puts(buf, repo);
465 	cvs_buf_putc(buf, '\n');
466 
467 	cvs_buf_puts(buf, "In directory ");
468 	cvs_buf_puts(buf, hostname);
469 	cvs_buf_puts(buf, ":");
470 	cvs_buf_puts(buf, dirname(pwd));
471 	cvs_buf_putc(buf, '/');
472 	cvs_buf_puts(buf, repo);
473 	cvs_buf_putc(buf, '\n');
474 	cvs_buf_putc(buf, '\n');
475 }
476 
477