xref: /openbsd-src/usr.bin/mg/region.c (revision f6aab3d83b51b91c24247ad2c2573574de475a82)
1 /*	$OpenBSD: region.c,v 1.44 2023/03/28 14:47:28 op Exp $	*/
2 
3 /* This file is in the public domain. */
4 
5 /*
6  *		Region based commands.
7  * The routines in this file deal with the region, that magic space between
8  * "." and mark.  Some functions are commands.  Some functions are just for
9  * internal use.
10  */
11 
12 #include <sys/queue.h>
13 #include <sys/socket.h>
14 #include <sys/types.h>
15 #include <sys/wait.h>
16 #include <errno.h>
17 #include <fcntl.h>
18 #include <paths.h>
19 #include <poll.h>
20 #include <signal.h>
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <string.h>
24 #include <unistd.h>
25 
26 #include "def.h"
27 
28 #define TIMEOUT 10000
29 
30 static char leftover[BUFSIZ];
31 
32 static	int	getregion(struct region *);
33 static	int	iomux(int, char * const, int, struct buffer *);
34 static	int	preadin(int, struct buffer *);
35 static	void	pwriteout(int, char **, int *);
36 static	int	setsize(struct region *, RSIZE);
37 static	int	shellcmdoutput(char * const, char * const, int);
38 
39 /*
40  * Kill the region.  Ask "getregion" to figure out the bounds of the region.
41  * Move "." to the start, and kill the characters. Mark is cleared afterwards.
42  */
43 int
44 killregion(int f, int n)
45 {
46 	int	s;
47 	struct region	region;
48 
49 	if ((s = getregion(&region)) != TRUE)
50 		return (s);
51 	/* This is a kill-type command, so do magic kill buffer stuff. */
52 	if ((lastflag & CFKILL) == 0)
53 		kdelete();
54 	thisflag |= CFKILL;
55 	curwp->w_dotp = region.r_linep;
56 	curwp->w_doto = region.r_offset;
57 	curwp->w_dotline = region.r_lineno;
58 	s = ldelete(region.r_size, KFORW | KREG);
59 	clearmark(FFARG, 0);
60 
61 	return (s);
62 }
63 
64 /*
65  * Copy all of the characters in the region to the kill buffer,
66  * clearing the mark afterwards.
67  * This is a bit like a kill region followed by a yank.
68  */
69 int
70 copyregion(int f, int n)
71 {
72 	struct line	*linep;
73 	struct region	 region;
74 	int	 loffs;
75 	int	 s;
76 
77 	if ((s = getregion(&region)) != TRUE)
78 		return (s);
79 
80 	/* kill type command */
81 	if ((lastflag & CFKILL) == 0)
82 		kdelete();
83 	thisflag |= CFKILL;
84 
85 	/* current line */
86 	linep = region.r_linep;
87 
88 	/* current offset */
89 	loffs = region.r_offset;
90 
91 	while (region.r_size--) {
92 		if (loffs == llength(linep)) {	/* End of line.		 */
93 			if ((s = kinsert(*curbp->b_nlchr, KFORW)) != TRUE)
94 				return (s);
95 			linep = lforw(linep);
96 			loffs = 0;
97 		} else {			/* Middle of line.	 */
98 			if ((s = kinsert(lgetc(linep, loffs), KFORW)) != TRUE)
99 				return (s);
100 			++loffs;
101 		}
102 	}
103 	clearmark(FFARG, 0);
104 
105 	return (TRUE);
106 }
107 
108 /*
109  * Lower case region.  Zap all of the upper case characters in the region to
110  * lower case. Use the region code to set the limits. Scan the buffer, doing
111  * the changes. Call "lchange" to ensure that redisplay is done in all
112  * buffers.
113  */
114 int
115 lowerregion(int f, int n)
116 {
117 	struct line	*linep;
118 	struct region	 region;
119 	int	 loffs, c, s;
120 
121 	if ((s = checkdirty(curbp)) != TRUE)
122 		return (s);
123 	if (curbp->b_flag & BFREADONLY) {
124 		dobeep();
125 		ewprintf("Buffer is read-only");
126 		return (FALSE);
127 	}
128 
129 	if ((s = getregion(&region)) != TRUE)
130 		return (s);
131 
132 	undo_add_change(region.r_linep, region.r_offset, region.r_size);
133 
134 	lchange(WFFULL);
135 	linep = region.r_linep;
136 	loffs = region.r_offset;
137 	while (region.r_size--) {
138 		if (loffs == llength(linep)) {
139 			linep = lforw(linep);
140 			loffs = 0;
141 		} else {
142 			c = lgetc(linep, loffs);
143 			if (ISUPPER(c) != FALSE)
144 				lputc(linep, loffs, TOLOWER(c));
145 			++loffs;
146 		}
147 	}
148 	return (TRUE);
149 }
150 
151 /*
152  * Upper case region.  Zap all of the lower case characters in the region to
153  * upper case.  Use the region code to set the limits.  Scan the buffer,
154  * doing the changes.  Call "lchange" to ensure that redisplay is done in all
155  * buffers.
156  */
157 int
158 upperregion(int f, int n)
159 {
160 	struct line	 *linep;
161 	struct region	  region;
162 	int	  loffs, c, s;
163 
164 	if ((s = checkdirty(curbp)) != TRUE)
165 		return (s);
166 	if (curbp->b_flag & BFREADONLY) {
167 		dobeep();
168 		ewprintf("Buffer is read-only");
169 		return (FALSE);
170 	}
171 	if ((s = getregion(&region)) != TRUE)
172 		return (s);
173 
174 	undo_add_change(region.r_linep, region.r_offset, region.r_size);
175 
176 	lchange(WFFULL);
177 	linep = region.r_linep;
178 	loffs = region.r_offset;
179 	while (region.r_size--) {
180 		if (loffs == llength(linep)) {
181 			linep = lforw(linep);
182 			loffs = 0;
183 		} else {
184 			c = lgetc(linep, loffs);
185 			if (ISLOWER(c) != FALSE)
186 				lputc(linep, loffs, TOUPPER(c));
187 			++loffs;
188 		}
189 	}
190 	return (TRUE);
191 }
192 
193 /*
194  * This routine figures out the bound of the region in the current window,
195  * and stores the results into the fields of the REGION structure. Dot and
196  * mark are usually close together, but I don't know the order, so I scan
197  * outward from dot, in both directions, looking for mark. The size is kept
198  * in a long. At the end, after the size is figured out, it is assigned to
199  * the size field of the region structure. If this assignment loses any bits,
200  * then we print an error. This is "type independent" overflow checking. All
201  * of the callers of this routine should be ready to get an ABORT status,
202  * because I might add a "if regions is big, ask before clobbering" flag.
203  */
204 static int
205 getregion(struct region *rp)
206 {
207 	struct line	*flp, *blp;
208 	long	 fsize, bsize;
209 
210 	if (curwp->w_markp == NULL) {
211 		dobeep();
212 		ewprintf("No mark set in this window");
213 		return (FALSE);
214 	}
215 
216 	/* "r_size" always ok */
217 	if (curwp->w_dotp == curwp->w_markp) {
218 		rp->r_linep = curwp->w_dotp;
219 		rp->r_lineno = curwp->w_dotline;
220 		if (curwp->w_doto < curwp->w_marko) {
221 			rp->r_offset = curwp->w_doto;
222 			rp->r_size = (RSIZE)(curwp->w_marko - curwp->w_doto);
223 		} else {
224 			rp->r_offset = curwp->w_marko;
225 			rp->r_size = (RSIZE)(curwp->w_doto - curwp->w_marko);
226 		}
227 		return (TRUE);
228 	}
229 	/* get region size */
230 	flp = blp = curwp->w_dotp;
231 	bsize = curwp->w_doto;
232 	fsize = llength(flp) - curwp->w_doto + 1;
233 	while (lforw(flp) != curbp->b_headp || lback(blp) != curbp->b_headp) {
234 		if (lforw(flp) != curbp->b_headp) {
235 			flp = lforw(flp);
236 			if (flp == curwp->w_markp) {
237 				rp->r_linep = curwp->w_dotp;
238 				rp->r_offset = curwp->w_doto;
239 				rp->r_lineno = curwp->w_dotline;
240 				return (setsize(rp,
241 				    (RSIZE)(fsize + curwp->w_marko)));
242 			}
243 			fsize += llength(flp) + 1;
244 		}
245 		if (lback(blp) != curbp->b_headp) {
246 			blp = lback(blp);
247 			bsize += llength(blp) + 1;
248 			if (blp == curwp->w_markp) {
249 				rp->r_linep = blp;
250 				rp->r_offset = curwp->w_marko;
251 				rp->r_lineno = curwp->w_markline;
252 				return (setsize(rp,
253 				    (RSIZE)(bsize - curwp->w_marko)));
254 			}
255 		}
256 	}
257 	dobeep();
258 	ewprintf("Bug: lost mark");
259 	return (FALSE);
260 }
261 
262 /*
263  * Set size, and check for overflow.
264  */
265 static int
266 setsize(struct region *rp, RSIZE size)
267 {
268 	rp->r_size = size;
269 	if (rp->r_size != size) {
270 		dobeep();
271 		ewprintf("Region is too large");
272 		return (FALSE);
273 	}
274 	return (TRUE);
275 }
276 
277 #define PREFIXLENGTH 40
278 static char	prefix_string[PREFIXLENGTH] = {'>', '\0'};
279 
280 /*
281  * Prefix the region with whatever is in prefix_string.  Leaves dot at the
282  * beginning of the line after the end of the region.  If an argument is
283  * given, prompts for the line prefix string.
284  */
285 int
286 prefixregion(int f, int n)
287 {
288 	struct line	*first, *last;
289 	struct region	 region;
290 	char	*prefix = prefix_string;
291 	int	 nline;
292 	int	 s;
293 
294 	if ((s = checkdirty(curbp)) != TRUE)
295 		return (s);
296 	if (curbp->b_flag & BFREADONLY) {
297 		dobeep();
298 		ewprintf("Buffer is read-only");
299 		return (FALSE);
300 	}
301 	if ((f == TRUE) && ((s = setprefix(FFRAND, 1)) != TRUE))
302 		return (s);
303 
304 	/* get # of lines to affect */
305 	if ((s = getregion(&region)) != TRUE)
306 		return (s);
307 	first = region.r_linep;
308 	last = (first == curwp->w_dotp) ? curwp->w_markp : curwp->w_dotp;
309 	for (nline = 1; first != last; nline++)
310 		first = lforw(first);
311 
312 	/* move to beginning of region */
313 	curwp->w_dotp = region.r_linep;
314 	curwp->w_doto = region.r_offset;
315 	curwp->w_dotline = region.r_lineno;
316 
317 	/* for each line, go to beginning and insert the prefix string */
318 	while (nline--) {
319 		(void)gotobol(FFRAND, 1);
320 		for (prefix = prefix_string; *prefix; prefix++)
321 			(void)linsert(1, *prefix);
322 		(void)forwline(FFRAND, 1);
323 	}
324 	(void)gotobol(FFRAND, 1);
325 	return (TRUE);
326 }
327 
328 /*
329  * Set line prefix string. Used by prefixregion.
330  */
331 int
332 setprefix(int f, int n)
333 {
334 	char	buf[PREFIXLENGTH], *rep;
335 	int	retval;
336 
337 	if (prefix_string[0] == '\0')
338 		rep = eread("Prefix string: ", buf, sizeof(buf),
339 		    EFNEW | EFCR);
340 	else
341 		rep = eread("Prefix string (default %s): ", buf, sizeof(buf),
342 		    EFNUL | EFNEW | EFCR, prefix_string);
343 	if (rep == NULL)
344 		return (ABORT);
345 	if (rep[0] != '\0') {
346 		(void)strlcpy(prefix_string, rep, sizeof(prefix_string));
347 		retval = TRUE;
348 	} else if (rep[0] == '\0' && prefix_string[0] != '\0') {
349 		/* CR -- use old one */
350 		retval = TRUE;
351 	} else
352 		retval = FALSE;
353 	return (retval);
354 }
355 
356 int
357 region_get_data(struct region *reg, char *buf, int len)
358 {
359 	int	 i, off;
360 	struct line	*lp;
361 
362 	off = reg->r_offset;
363 	lp = reg->r_linep;
364 	for (i = 0; i < len; i++) {
365 		if (off == llength(lp)) {
366 			lp = lforw(lp);
367 			if (lp == curbp->b_headp)
368 				break;
369 			off = 0;
370 			buf[i] = *curbp->b_nlchr;
371 		} else {
372 			buf[i] = lgetc(lp, off);
373 			off++;
374 		}
375 	}
376 	buf[i] = '\0';
377 	return (i);
378 }
379 
380 void
381 region_put_data(const char *buf, int len)
382 {
383 	int i;
384 
385 	for (i = 0; buf[i] != '\0' && i < len; i++) {
386 		if (buf[i] == *curbp->b_nlchr)
387 			lnewline();
388 		else
389 			linsert(1, buf[i]);
390 	}
391 }
392 
393 /*
394  * Mark whole buffer by first traversing to end-of-buffer
395  * and then to beginning-of-buffer. Mark, dot are implicitly
396  * set to eob, bob respectively during traversal.
397  */
398 int
399 markbuffer(int f, int n)
400 {
401 	if (gotoeob(f,n) == FALSE)
402 		return (FALSE);
403 	(void) clearmark(f, n);
404 	if (gotobob(f,n) == FALSE)
405 		return (FALSE);
406 	return (TRUE);
407 }
408 
409 /*
410  * Pipe text from current region to external command.
411  */
412 int
413 piperegion(int f, int n)
414 {
415 	struct region region;
416 	int len;
417 	char *cmd, cmdbuf[NFILEN], *text;
418 
419 	/* C-u M-| is not supported yet */
420 	if (n > 1)
421 		return (ABORT);
422 
423 	if (curwp->w_markp == NULL) {
424 		dobeep();
425 		ewprintf("The mark is not set now, so there is no region");
426 		return (FALSE);
427 	}
428 
429 	if ((cmd = eread("Shell command on region: ", cmdbuf, sizeof(cmdbuf),
430 	    EFNEW | EFCR)) == NULL || (cmd[0] == '\0'))
431 		return (ABORT);
432 
433 	if (getregion(&region) != TRUE)
434 		return (FALSE);
435 
436 	len = region.r_size;
437 
438 	if ((text = malloc(len + 1)) == NULL) {
439 		dobeep();
440 		ewprintf("Cannot allocate memory.");
441 		return (FALSE);
442 	}
443 
444 	region_get_data(&region, text, len);
445 
446 	return shellcmdoutput(cmd, text, len);
447 }
448 
449 /*
450  * Get command from mini-buffer and execute externally.
451  */
452 int
453 shellcommand(int f, int n)
454 {
455 	char *cmd, cmdbuf[NFILEN];
456 
457 	if (n > 1)
458 		return (ABORT);
459 
460 	if ((cmd = eread("Shell command: ", cmdbuf, sizeof(cmdbuf),
461 	    EFNEW | EFCR)) == NULL || (cmd[0] == '\0'))
462 		return (ABORT);
463 
464 	return shellcmdoutput(cmd, NULL, 0);
465 }
466 
467 int
468 shellcmdoutput(char* const cmd, char* const text, int len)
469 {
470 	struct buffer *bp;
471 	char	*argv[] = {NULL, "-c", cmd, NULL};
472 	char	*shellp;
473 	int	 ret;
474 
475 	bp = bfind("*Shell Command Output*", TRUE);
476 	bp->b_flag |= BFREADONLY;
477 	if (bclear(bp) != TRUE) {
478 		free(text);
479 		return (FALSE);
480 	}
481 
482 	if ((shellp = getenv("SHELL")) == NULL)
483 		shellp = _PATH_BSHELL;
484 
485 	if ((argv[0] = strrchr(shellp, '/')) != NULL)
486 		argv[0]++;
487 	else
488 		argv[0] = shellp;
489 
490 	ret = pipeio(shellp, argv, text, len, bp);
491 
492 	if (ret == TRUE) {
493 		eerase();
494 		if (lforw(bp->b_headp) == bp->b_headp)
495 			addline(bp, "(Shell command succeeded with no output)");
496 	}
497 
498 	free(text);
499 	return (ret);
500 }
501 
502 /*
503  * Create a socketpair, fork and execv path with argv.
504  * STDIN, STDOUT and STDERR of child process are redirected to socket.
505  * Parent writes len chars from text to socket.
506  */
507 int
508 pipeio(const char* const path, char* const argv[], char* const text, int len,
509     struct buffer *outbp)
510 {
511 	int s[2], ret;
512 	char *err;
513 	pid_t pid;
514 
515 	if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, s) == -1) {
516 		dobeep();
517 		ewprintf("socketpair error");
518 		return (FALSE);
519 	}
520 
521 	switch((pid = fork())) {
522 	case -1:
523 		dobeep();
524 		ewprintf("Can't fork");
525 		return (FALSE);
526 	case 0:
527 		/* Child process */
528 		close(s[0]);
529 		if (dup2(s[1], STDIN_FILENO) == -1)
530 			_exit(1);
531 		if (dup2(s[1], STDOUT_FILENO) == -1)
532 			_exit(1);
533 		if (dup2(s[1], STDERR_FILENO) == -1)
534 			_exit(1);
535 
536 		execv(path, argv);
537 		err = strerror(errno);
538 		write(s[1], err, strlen(err));
539 		_exit(1);
540 	default:
541 		/* Parent process */
542 		close(s[1]);
543 		ret = iomux(s[0], text, len, outbp);
544 		waitpid(pid, NULL, 0); /* Collect child to prevent zombies */
545 
546 		return (ret);
547 	}
548 	return (FALSE);
549 }
550 
551 /*
552  * Multiplex read, write on socket fd passed. Put output in outbp
553  * Poll on the fd for both read and write readiness.
554  */
555 int
556 iomux(int fd, char* const text, int len, struct buffer *outbp)
557 {
558 	struct pollfd pfd[1];
559 	int nfds;
560 	char *textcopy;
561 
562 	textcopy = text;
563 	fcntl(fd, F_SETFL, O_NONBLOCK);
564 	pfd[0].fd = fd;
565 
566 	/* There is nothing to write if len is zero
567 	 * but the cmd's output should be read so shutdown
568 	 * the socket for writing only and don't wait for POLLOUT
569 	 */
570 	if (len == 0) {
571 		shutdown(fd, SHUT_WR);
572 		pfd[0].events = POLLIN;
573 	} else
574 		pfd[0].events = POLLIN | POLLOUT;
575 
576 	while ((nfds = poll(pfd, 1, TIMEOUT)) != -1 ||
577 	    (pfd[0].revents & (POLLERR | POLLHUP | POLLNVAL))) {
578 		if (pfd[0].revents & POLLOUT && len > 0)
579 			pwriteout(fd, &textcopy, &len);
580 		else if (pfd[0].revents & POLLIN)
581 			if (preadin(fd, outbp) == FALSE)
582 				break;
583 		if (len == 0 && pfd[0].events & POLLOUT)
584 			pfd[0].events = POLLIN;
585 	}
586 	close(fd);
587 
588 	/* In case if last line doesn't have a '\n' add the leftover
589 	 * characters to buffer.
590 	 */
591 	if (leftover[0] != '\0') {
592 		addline(outbp, leftover);
593 		leftover[0] = '\0';
594 	}
595 	if (nfds == 0) {
596 		dobeep();
597 		ewprintf("poll timed out");
598 		return (FALSE);
599 	} else if (nfds == -1) {
600 		dobeep();
601 		ewprintf("poll error");
602 		return (FALSE);
603 	}
604 	return (popbuftop(outbp, WNONE));
605 }
606 
607 /*
608  * Write some text from region to fd. Once done shutdown the
609  * write end.
610  */
611 void
612 pwriteout(int fd, char **text, int *len)
613 {
614 	int w;
615 
616 	if (((w = send(fd, *text, *len, MSG_NOSIGNAL)) == -1)) {
617 		switch(errno) {
618 		case EPIPE:
619 			*len = -1;
620 			break;
621 		case EAGAIN:
622 			return;
623 		}
624 	} else
625 		*len -= w;
626 
627 	*text += w;
628 	if (*len <= 0)
629 		shutdown(fd, SHUT_WR);
630 }
631 
632 /*
633  * Read some data from socket fd, break on '\n' and add
634  * to buffer. If couldn't break on newline hold leftover
635  * characters and append in next iteration.
636  */
637 int
638 preadin(int fd, struct buffer *bp)
639 {
640 	int len;
641 	char buf[BUFSIZ], *p, *q;
642 
643 	if ((len = read(fd, buf, BUFSIZ - 1)) <= 0)
644 		return (FALSE);
645 
646 	buf[len] = '\0';
647 	p = q = buf;
648 	if (leftover[0] != '\0' && ((q = strchr(p, *bp->b_nlchr)) != NULL)) {
649 		*q++ = '\0';
650 		if (strlcat(leftover, p, sizeof(leftover)) >=
651 		    sizeof(leftover)) {
652 			dobeep();
653 			ewprintf("line too long");
654 			return (FALSE);
655 		}
656 		addline(bp, leftover);
657 		leftover[0] = '\0';
658 		p = q;
659 	}
660 	while ((q = strchr(p, *bp->b_nlchr)) != NULL) {
661 		*q++ = '\0';
662 		addline(bp, p);
663 		p = q;
664 	}
665 	if (strlcpy(leftover, p, sizeof(leftover)) >= sizeof(leftover)) {
666 		dobeep();
667 		ewprintf("line too long");
668 		return (FALSE);
669 	}
670 	return (TRUE);
671 }
672