1/*	$OpenBSD: log.c,v 1.13 2022/12/26 19:16:02 jmc Exp $	*/
2
3/*
4 * This file is in the public domain.
5 *
6 * Author: Mark Lumsden <mark@showcomplex.com>
7 *
8 */
9
10/*
11 * Record a history of an mg session for temporal debugging.
12 * Sometimes pressing a key will set the scene for a bug only visible
13 * dozens of keystrokes later. gdb has its limitations in this scenario.
14 *
15 * Note this file is not compiled into mg by default, you will need to
16 * amend the 'Makefile' for that to happen. Because of this, the code
17 * is subject to bit-rot. However, I know myself and others have
18 * written similar functionally often enough, that recording the below
19 * in a code repository could aid the development efforts of mg, even
20 * if it requires a bit of effort to get working. The current code is
21 * written in the spirit of debugging (quickly and perhaps not ideal,
22 * but it does what is required well enough). Should debugging become
23 * more formalised within mg, then I would expect that to change.
24 *
25 * If you open a file with long lines to run through this debugging
26 * code, you may run into problems with the 1st fprintf statement in
27 * in the mglog_lines() function. mg sometimes segvs at a strlen call
28 * in fprintf - possibly something to do with the format string?
29 * 	"%s%p b^%p f.%p %d %d\t%c|%s\n"
30 * When I get time I will look into it. But since my debugging
31 * generally revolves around a file like:
32 *
33 * abc
34 * def
35 * ghk
36 *
37 * I don't experience this bug. Just note it for future investigation.
38 */
39
40#include <sys/queue.h>
41#include <sys/stat.h>
42#include <ctype.h>
43#include <fcntl.h>
44#include <signal.h>
45#include <stdio.h>
46#include <stdlib.h>
47#include <string.h>
48#include <unistd.h>
49#include <stdarg.h>
50
51#include "def.h"
52#include "key.h"
53#include "kbd.h"
54#include "funmap.h"
55#include "chrdef.h"
56
57#include "log.h"
58
59static char	*mglogfiles_create(FILE **, char *);
60static int	 mglog_lines(PF);
61static int	 mglog_undo(void);
62static int	 mglog_window(void);
63static int	 mglog_key(KEYMAP *map);
64
65const char	*mglogdir;
66const char	*mglogpath_lines;
67const char	*mglogpath_undo;
68const char	*mglogpath_window;
69const char	*mglogpath_key;
70const char    	*mglogpath_interpreter;
71const char	*mglogpath_misc;
72int		 mgloglevel;
73
74FILE		*fd_lines;
75FILE		*fd_undo;
76FILE		*fd_window;
77FILE		*fd_key;
78FILE		*fd_interpreter;
79FILE		*fd_misc;
80
81int
82mglog(PF funct, void *map)
83{
84	if(!mglog_lines(funct))
85		ewprintf("Problem logging lines");
86	if(!mglog_undo())
87		ewprintf("Problem logging undo");
88	if(!mglog_window())
89		ewprintf("Problem logging window");
90	if(!mglog_key(map))
91		ewprintf("Problem logging key");
92
93	return (TRUE);
94}
95
96
97static int
98mglog_key(KEYMAP *map)
99{
100	PF		*pfp;
101
102	if (ISWORD(*key.k_chars)) {
103		fprintf(fd_key, "k_count:%d k_chars:%hd\tchr:%c\t", key.k_count,
104		    *key.k_chars, CHARMASK(*key.k_chars));
105	} else {
106		fprintf(fd_key, "k_count:%d k_chars:%hd\t\t", key.k_count,
107		    *key.k_chars);
108	}
109	fprintf(fd_key, "map:%p %d %d %p %hd %hd\n",
110	    map,
111	    map->map_num,
112	    map->map_max,
113	    map->map_default,
114	    map->map_element->k_base,
115	    map->map_element->k_num
116	    );
117	for (pfp = map->map_element->k_funcp; *pfp != NULL; pfp++)
118		fprintf(fd_key, "%s ", function_name(*pfp));
119
120	fprintf(fd_key, "\n\n");
121	fflush(fd_key);
122	return (TRUE);
123}
124
125static int
126mglog_window(void)
127{
128	struct mgwin	*wp;
129	int		 i;
130
131	for (wp = wheadp, i = 0; wp != NULL; wp = wp->w_wndp, ++i) {
132		fprintf(fd_window,
133		    "%d wh%p wlst%p wbfp%p wlp%p wdtp%p wmkp%p wdto%d wmko%d" \
134		    " wtpr%d wntr%d wfrm%d wrfl%c wflg%c wwrl%p wdtl%d" \
135		    " wmkl%d\n",
136		    i,
137		    wp,
138		    &wp->w_list,
139		    wp->w_bufp,
140		    wp->w_linep,
141		    wp->w_dotp,
142		    wp->w_markp,
143		    wp->w_doto,
144		    wp->w_marko,
145		    wp->w_toprow,
146		    wp->w_ntrows,
147		    wp->w_frame,
148		    wp->w_rflag,
149		    wp->w_flag,
150		    wp->w_wrapline,
151		    wp->w_dotline,
152		    wp->w_markline
153		    );
154	}
155	fflush(fd_window);
156	return (TRUE);
157}
158
159static int
160mglog_undo(void)
161{
162	struct undo_rec	*rec;
163	char		 buf[4096], tmp[1024];
164	int      	 num;
165	char		*jptr;
166
167	jptr = "^J"; /* :) */
168	/*
169	 * From undo_dump()
170	 */
171	num = 0;
172	TAILQ_FOREACH(rec, &curbp->b_undo, next) {
173		num++;
174		fprintf(fd_undo, "%d:\t %s at %d ", num,
175		    (rec->type == DELETE) ? "DELETE":
176		    (rec->type == DELREG) ? "DELREGION":
177		    (rec->type == INSERT) ? "INSERT":
178		    (rec->type == BOUNDARY) ? "----" :
179		    (rec->type == MODIFIED) ? "MODIFIED": "UNKNOWN",
180		    rec->pos
181		    );
182		if (rec->content) {
183			(void)strlcat(buf, "\"", sizeof(buf));
184			snprintf(tmp, sizeof(tmp), "%.*s",
185			    *rec->content == '\n' ? 2 : rec->region.r_size,
186			    *rec->content == '\n' ? jptr : rec->content);
187			(void)strlcat(buf, tmp, sizeof(buf));
188			(void)strlcat(buf, "\"", sizeof(buf));
189		}
190		snprintf(tmp, sizeof(tmp), " [%d]", rec->region.r_size);
191		if (strlcat(buf, tmp, sizeof(buf)) >= sizeof(buf)) {
192			dobeep();
193			ewprintf("Undo record too large. Aborted.");
194			return (FALSE);
195		}
196		fprintf(fd_undo, "%s\n", buf);
197		tmp[0] = buf[0] = '\0';
198	}
199	fprintf(fd_undo, "\t [end-of-undo]\n\n");
200	fflush(fd_undo);
201
202	return (TRUE);
203}
204
205static int
206mglog_lines(PF funct)
207{
208	struct line     *lp;
209	char		*curline, *tmp, o;
210	int		 i;
211
212	i = 0;
213
214	fprintf(fd_lines, "%s\n", function_name(funct));
215	lp = bfirstlp(curbp);
216
217	for(;;) {
218		i++;
219		curline = " ";
220		o = ' ';
221		if (i == curwp->w_dotline) {
222			curline = ">";
223			if (lp->l_used > 0 && curwp->w_doto < lp->l_used)
224				o = lp->l_text[curwp->w_doto];
225			else
226				o = '-';
227		}
228		if (lp->l_size == 0)
229			tmp = " ";
230		else
231			tmp = lp->l_text;
232
233		/* segv on fprintf below with long lines */
234		fprintf(fd_lines, "%s%p b^%p f.%p %d %d\t%c|%s\n", curline,
235		    lp, lp->l_bp, lp->l_fp,
236		    lp->l_size, lp->l_used, o, tmp);
237
238		lp = lforw(lp);
239		if (lp == curbp->b_headp) {
240			fprintf(fd_lines, " %p b^%p f.%p [bhead]\n(EOB)\n",
241			    lp, lp->l_bp, lp->l_fp);
242
243			fprintf(fd_lines, "lines:raw:%d buf:%d wdot:%d\n\n",
244			    i, curbp->b_lines, curwp->w_dotline);
245
246			break;
247		}
248	}
249	fflush(fd_lines);
250
251	return (TRUE);
252}
253
254/*
255 * See what the eval variable code is up to.
256 */
257int
258mglog_isvar(
259	const char* const argbuf,
260	const char* const argp,
261	const int 	  sizof
262)
263{
264
265	fprintf(fd_interpreter, " argbuf:%s,argp:%s,sizof:%d<\n",
266	    argbuf,
267	    argp,
268	    sizof);
269
270	fflush(fd_interpreter);
271	return (TRUE);
272}
273
274/*
275 * See what the eval line code is up to.
276 */
277int
278mglog_execbuf(
279	const char* const pre,
280	const char* const excbuf,
281	const char* const argbuf,
282    	const char* const argp,
283	const int 	  last,
284	const int	  inlist,
285    	const char* const cmdp,
286	const char* const p,
287	const char* const contbuf
288)
289{
290	fprintf(fd_interpreter, "%sexcbuf:%s,argbuf:%s,argp:%s,last:%d,inlist:%d,"\
291	    "cmdp:%s,p:%s,contbuf:%s<\n",
292	    pre,
293	    excbuf,
294	    argbuf,
295	    argp,
296	    last,
297	    inlist,
298	    cmdp,
299	    p,
300	    contbuf
301	    );
302	fflush(fd_interpreter);
303	return (TRUE);
304}
305
306/*
307 * Misc. logging for various subsystems
308 */
309int
310mglog_misc(
311	const char *fmt,
312	...
313)
314{
315	va_list		ap;
316	int		rc;
317
318	va_start(ap, fmt);
319	rc = vfprintf(fd_misc, fmt, ap);
320	va_end(ap);
321	fflush(fd_misc);
322
323	if (rc < 0)
324		return (FALSE);
325
326	return (TRUE);
327}
328
329
330
331/*
332 * Make sure logging to log files can happen.
333 */
334int
335mgloginit(void)
336{
337	struct stat	 sb;
338	mode_t           dir_mode, f_mode, oumask;
339	char		*mglogfile_lines, *mglogfile_undo, *mglogfile_window;
340	char		*mglogfile_key, *mglogfile_interpreter, *mglogfile_misc;
341
342	mglogdir = "./log/";
343	mglogfile_lines = "line.log";
344	mglogfile_undo = "undo.log";
345	mglogfile_window = "window.log";
346	mglogfile_key = "key.log";
347	mglogfile_interpreter = "interpreter.log";
348	mglogfile_misc = "misc.log";
349
350	/*
351	 * Change mgloglevel for desired level of logging.
352	 * log.h has relevant level info.
353	 */
354	mgloglevel = 1;
355
356	oumask = umask(0);
357	f_mode = 0777& ~oumask;
358	dir_mode = f_mode | S_IWUSR | S_IXUSR;
359
360	if(stat(mglogdir, &sb)) {
361		if (mkdir(mglogdir, dir_mode) != 0)
362			return (FALSE);
363		if (chmod(mglogdir, f_mode) == -1)
364			return (FALSE);
365	}
366	mglogpath_lines = mglogfiles_create(&fd_lines, mglogfile_lines);
367	if (mglogpath_lines == NULL)
368		return (FALSE);
369	mglogpath_undo = mglogfiles_create(&fd_undo, mglogfile_undo);
370	if (mglogpath_undo == NULL)
371		return (FALSE);
372	mglogpath_window = mglogfiles_create(&fd_window, mglogfile_window);
373	if (mglogpath_window == NULL)
374		return (FALSE);
375	mglogpath_key = mglogfiles_create(&fd_key, mglogfile_key);
376	if (mglogpath_key == NULL)
377		return (FALSE);
378	mglogpath_interpreter = mglogfiles_create(&fd_interpreter,
379	    mglogfile_interpreter);
380	if (mglogpath_interpreter == NULL)
381		return (FALSE);
382	mglogpath_misc = mglogfiles_create(&fd_misc, mglogfile_misc);
383	if (mglogpath_misc == NULL)
384		return (FALSE);
385
386	return (TRUE);
387}
388
389
390static char *
391mglogfiles_create(FILE ** fd, char *mglogfile)
392{
393	char		 tmp[NFILEN], *tmp2;
394
395	if (strlcpy(tmp, mglogdir, sizeof(tmp)) >
396	    sizeof(tmp))
397		return (NULL);
398	if (strlcat(tmp, mglogfile, sizeof(tmp)) >
399	    sizeof(tmp))
400		return (NULL);
401	if ((tmp2 = strndup(tmp, NFILEN)) == NULL)
402		return (NULL);
403
404	if ((*fd = fopen(tmp2, "w")) == NULL)
405		return (NULL);
406
407	return (tmp2);
408}
409