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