GCC Code Coverage Report
Directory: ./ Exec Total Coverage
File: usr.bin/cvs/logmsg.c Lines: 27 179 15.1 %
Date: 2017-11-07 Branches: 10 106 9.4 %

Line Branch Exec Source
1
/*	$OpenBSD: logmsg.c,v 1.60 2017/05/28 16:57:01 joris Exp $	*/
2
/*
3
 * Copyright (c) 2007 Joris Vink <joris@openbsd.org>
4
 *
5
 * Permission to use, copy, modify, and distribute this software for any
6
 * purpose with or without fee is hereby granted, provided that the above
7
 * copyright notice and this permission notice appear in all copies.
8
 *
9
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16
 */
17
18
#include <sys/stat.h>
19
#include <sys/types.h>
20
#include <sys/wait.h>
21
22
#include <errno.h>
23
#include <fcntl.h>
24
#include <libgen.h>
25
#include <paths.h>
26
#include <signal.h>
27
#include <stdint.h>
28
#include <stdlib.h>
29
#include <string.h>
30
#include <unistd.h>
31
32
#include "cvs.h"
33
34
#define CVS_LOGMSG_PREFIX		"CVS:"
35
#define CVS_LOGMSG_LINE		\
36
"----------------------------------------------------------------------"
37
38
int	cvs_logmsg_edit(const char *);
39
40
char *
41
cvs_logmsg_read(const char *path)
42
{
43
	int fd;
44
	BUF *bp;
45
	FILE *fp;
46
2
	size_t len;
47
1
	struct stat st;
48
	char *buf, *lbuf;
49
50
1
	if ((fd = open(path, O_RDONLY)) == -1)
51
		fatal("cvs_logmsg_read: open %s", strerror(errno));
52
53
1
	if (fstat(fd, &st) == -1)
54
		fatal("cvs_logmsg_read: fstat %s", strerror(errno));
55
56
1
	if (!S_ISREG(st.st_mode))
57
		fatal("cvs_logmsg_read: file is not a regular file");
58
59
1
	if ((fp = fdopen(fd, "r")) == NULL)
60
		fatal("cvs_logmsg_read: fdopen %s", strerror(errno));
61
62
1
	if ((uintmax_t)st.st_size > SIZE_MAX)
63
		fatal("cvs_logmsg_read: %s: file size too big", path);
64
65
	lbuf = NULL;
66
1
	bp = buf_alloc(st.st_size);
67
4
	while ((buf = fgetln(fp, &len))) {
68
1
		if (buf[len - 1] == '\n') {
69
1
			buf[len - 1] = '\0';
70
1
			--len;
71
1
		} else {
72
			lbuf = xmalloc(len + 1);
73
			memcpy(lbuf, buf, len);
74
			lbuf[len] = '\0';
75
			buf = lbuf;
76
		}
77
78
1
		if (!strncmp(buf, CVS_LOGMSG_PREFIX,
79
		    sizeof(CVS_LOGMSG_PREFIX) - 1))
80
			continue;
81
82
1
		buf_append(bp, buf, len);
83
1
		buf_putc(bp, '\n');
84
	}
85
86
1
	free(lbuf);
87
88
1
	(void)fclose(fp);
89
90
1
	buf_putc(bp, '\0');
91
2
	return (buf_release(bp));
92
1
}
93
94
char *
95
cvs_logmsg_create(char *dir, struct cvs_flisthead *added,
96
    struct cvs_flisthead *removed, struct cvs_flisthead *modified)
97
{
98
	FILE *fp, *rp;
99
	int c, fd, rd, saved_errno;
100
	struct cvs_filelist *cf;
101
	struct stat st1, st2;
102
	char *fpath, *logmsg, repo[PATH_MAX];
103
	struct stat st;
104
	struct trigger_list *line_list;
105
	struct trigger_line *line;
106
	static int reuse = 0;
107
	static char *prevmsg = NULL;
108
109
	if (reuse)
110
		return xstrdup(prevmsg);
111
112
	(void)xasprintf(&fpath, "%s/cvsXXXXXXXXXX", cvs_tmpdir);
113
114
	if ((fd = mkstemp(fpath)) == -1)
115
		fatal("cvs_logmsg_create: mkstemp %s", strerror(errno));
116
117
	worklist_add(fpath, &temp_files);
118
119
	if ((fp = fdopen(fd, "w")) == NULL) {
120
		saved_errno = errno;
121
		(void)unlink(fpath);
122
		fatal("cvs_logmsg_create: fdopen %s", strerror(saved_errno));
123
	}
124
125
	if (prevmsg != NULL && prevmsg[0] != '\0')
126
		fprintf(fp, "%s", prevmsg);
127
	else
128
		fputc('\n', fp);
129
130
	line_list = cvs_trigger_getlines(CVS_PATH_RCSINFO, repo);
131
	if (line_list != NULL) {
132
		TAILQ_FOREACH(line, line_list, flist) {
133
			if ((rd = open(line->line, O_RDONLY)) == -1)
134
				fatal("cvs_logmsg_create: open %s",
135
				    strerror(errno));
136
			if (fstat(rd, &st) == -1)
137
				fatal("cvs_logmsg_create: fstat %s",
138
				    strerror(errno));
139
			if (!S_ISREG(st.st_mode))
140
				fatal("cvs_logmsg_create: file is not a "
141
				    "regular file");
142
			if ((rp = fdopen(rd, "r")) == NULL)
143
				fatal("cvs_logmsg_create: fdopen %s",
144
				    strerror(errno));
145
			if ((uintmax_t)st.st_size > SIZE_MAX)
146
				fatal("cvs_logmsg_create: %s: file size "
147
				    "too big", line->line);
148
			logmsg = xmalloc(st.st_size);
149
			fread(logmsg, st.st_size, 1, rp);
150
			fwrite(logmsg, st.st_size, 1, fp);
151
			free(logmsg);
152
			(void)fclose(rp);
153
		}
154
		cvs_trigger_freelist(line_list);
155
	}
156
157
	fprintf(fp, "%s %s\n%s Enter Log.  Lines beginning with `%s' are "
158
	    "removed automatically\n%s \n", CVS_LOGMSG_PREFIX, CVS_LOGMSG_LINE,
159
	    CVS_LOGMSG_PREFIX, CVS_LOGMSG_PREFIX, CVS_LOGMSG_PREFIX);
160
161
	if (cvs_cmdop == CVS_OP_COMMIT) {
162
		fprintf(fp, "%s Committing in %s\n%s\n", CVS_LOGMSG_PREFIX,
163
		    dir != NULL ? dir : ".", CVS_LOGMSG_PREFIX);
164
	}
165
166
	if (added != NULL && !RB_EMPTY(added)) {
167
		fprintf(fp, "%s Added Files:", CVS_LOGMSG_PREFIX);
168
		RB_FOREACH(cf, cvs_flisthead, added)
169
			fprintf(fp, "\n%s \t%s ", CVS_LOGMSG_PREFIX,
170
			    dir != NULL ? basename(cf->file_path) :
171
			    cf->file_path);
172
		fputs("\n", fp);
173
	}
174
175
	if (removed != NULL && !RB_EMPTY(removed)) {
176
		fprintf(fp, "%s Removed Files:", CVS_LOGMSG_PREFIX);
177
		RB_FOREACH(cf, cvs_flisthead, removed)
178
			fprintf(fp, "\n%s \t%s ", CVS_LOGMSG_PREFIX,
179
			    dir != NULL ? basename(cf->file_path) :
180
			    cf->file_path);
181
		fputs("\n", fp);
182
	}
183
184
	if (modified != NULL && !RB_EMPTY(modified)) {
185
		fprintf(fp, "%s Modified Files:", CVS_LOGMSG_PREFIX);
186
		RB_FOREACH(cf, cvs_flisthead, modified)
187
			fprintf(fp, "\n%s \t%s ", CVS_LOGMSG_PREFIX,
188
			    dir != NULL ? basename(cf->file_path) :
189
			    cf->file_path);
190
		fputs("\n", fp);
191
	}
192
193
	fprintf(fp, "%s %s\n", CVS_LOGMSG_PREFIX, CVS_LOGMSG_LINE);
194
	(void)fflush(fp);
195
196
	if (fstat(fd, &st1) == -1) {
197
		saved_errno = errno;
198
		(void)unlink(fpath);
199
		fatal("cvs_logmsg_create: fstat %s", strerror(saved_errno));
200
	}
201
202
	logmsg = NULL;
203
204
	for (;;) {
205
		if (cvs_logmsg_edit(fpath) == -1)
206
			break;
207
208
		if (fstat(fd, &st2) == -1) {
209
			saved_errno = errno;
210
			(void)unlink(fpath);
211
			fatal("cvs_logmsg_create: fstat %s",
212
			    strerror(saved_errno));
213
		}
214
215
		if (st1.st_mtime != st2.st_mtime) {
216
			logmsg = cvs_logmsg_read(fpath);
217
			free(prevmsg);
218
			prevmsg = xstrdup(logmsg);
219
			break;
220
		}
221
222
		printf("\nLog message unchanged or not specified\n"
223
		    "a)bort, c)ontinue, e)dit, !)reuse this message "
224
		    "unchanged for remaining dirs\nAction: (abort) ");
225
		(void)fflush(stdout);
226
227
		c = getc(stdin);
228
		if (c == EOF || c == '\n' || c == 'a' || c == 'A') {
229
			fatal("Aborted by user");
230
		} else if (c == 'c' || c == 'C') {
231
			if (prevmsg == NULL)
232
				prevmsg = xstrdup("");
233
			logmsg = xstrdup(prevmsg);
234
			break;
235
		} else if (c == 'e' || c == 'E') {
236
			continue;
237
		} else if (c == '!') {
238
			reuse = 1;
239
			if (prevmsg == NULL)
240
				prevmsg = xstrdup("");
241
			logmsg = xstrdup(prevmsg);
242
			break;
243
		} else {
244
			cvs_log(LP_ERR, "invalid input");
245
			continue;
246
		}
247
	}
248
249
	(void)fclose(fp);
250
	(void)unlink(fpath);
251
	free(fpath);
252
253
	return (logmsg);
254
}
255
256
/*
257
 * Execute an editor on the specified pathname, which is interpreted
258
 * from the shell.  This means flags may be included.
259
 *
260
 * Returns -1 on error, or the exit value on success.
261
 */
262
int
263
cvs_logmsg_edit(const char *pathname)
264
{
265
	char *argp[] = {"sh", "-c", NULL, NULL}, *p;
266
	sig_t sighup, sigint, sigquit;
267
	pid_t pid;
268
	int saved_errno, st;
269
270
	(void)xasprintf(&p, "%s %s", cvs_editor, pathname);
271
	argp[2] = p;
272
273
	sighup = signal(SIGHUP, SIG_IGN);
274
	sigint = signal(SIGINT, SIG_IGN);
275
	sigquit = signal(SIGQUIT, SIG_IGN);
276
	if ((pid = fork()) == -1)
277
		goto fail;
278
	if (pid == 0) {
279
		execv(_PATH_BSHELL, argp);
280
		_exit(127);
281
	}
282
	while (waitpid(pid, &st, 0) == -1)
283
		if (errno != EINTR)
284
			goto fail;
285
	free(p);
286
	(void)signal(SIGHUP, sighup);
287
	(void)signal(SIGINT, sigint);
288
	(void)signal(SIGQUIT, sigquit);
289
	if (!WIFEXITED(st)) {
290
		errno = EINTR;
291
		return (-1);
292
	}
293
	return (WEXITSTATUS(st));
294
295
 fail:
296
	saved_errno = errno;
297
	(void)signal(SIGHUP, sighup);
298
	(void)signal(SIGINT, sigint);
299
	(void)signal(SIGQUIT, sigquit);
300
	free(p);
301
	errno = saved_errno;
302
	return (-1);
303
}
304
305
int
306
cvs_logmsg_verify(char *logmsg)
307
{
308
	int fd, ret = 0;
309
8
	char *fpath;
310
	struct trigger_list *line_list;
311
4
	struct file_info_list files_info;
312
	struct file_info *fi;
313
314
4
	line_list = cvs_trigger_getlines(CVS_PATH_VERIFYMSG, "DEFAULT");
315
4
	if (line_list != NULL) {
316
		TAILQ_INIT(&files_info);
317
318
		(void)xasprintf(&fpath, "%s/cvsXXXXXXXXXX", cvs_tmpdir);
319
		if ((fd = mkstemp(fpath)) == -1)
320
			fatal("cvs_logmsg_verify: mkstemp %s", strerror(errno));
321
322
		fi = xcalloc(1, sizeof(*fi));
323
		fi->file_path = xstrdup(fpath);
324
		TAILQ_INSERT_TAIL(&files_info, fi, flist);
325
326
		if (cvs_trigger_handle(CVS_TRIGGER_VERIFYMSG, NULL, NULL,
327
		    line_list, &files_info)) {
328
			cvs_log(LP_ERR, "Log message check failed");
329
			ret = 1;
330
		}
331
332
		cvs_trigger_freeinfo(&files_info);
333
		(void)close(fd);
334
		(void)unlink(fpath);
335
		free(fpath);
336
		cvs_trigger_freelist(line_list);
337
	}
338
339
4
	return ret;
340
4
}
341