xref: /openbsd/usr.bin/less/lsystem.c (revision 73471bf0)
1 /*
2  * Copyright (C) 1984-2012  Mark Nudelman
3  * Modified for use with illumos by Garrett D'Amore.
4  * Copyright 2014 Garrett D'Amore <garrett@damore.org>
5  *
6  * You may distribute under the terms of either the GNU General Public
7  * License or the Less License, as specified in the README file.
8  *
9  * For more information, see the README file.
10  */
11 
12 /*
13  * Routines to execute other programs.
14  * Necessarily very OS dependent.
15  */
16 
17 #include <signal.h>
18 
19 #include "less.h"
20 #include "position.h"
21 
22 extern int screen_trashed;
23 extern IFILE curr_ifile;
24 
25 static int pipe_data(char *cmd, off_t spos, off_t epos);
26 
27 /*
28  * Pass the specified command to a shell to be executed.
29  * Like plain "system()", but handles resetting terminal modes, etc.
30  */
31 void
32 lsystem(const char *cmd, const char *donemsg)
33 {
34 	int inp;
35 	char *shell;
36 	char *p;
37 	IFILE save_ifile;
38 
39 	/*
40 	 * Print the command which is to be executed,
41 	 * unless the command starts with a "-".
42 	 */
43 	if (cmd[0] == '-')
44 		cmd++;
45 	else {
46 		clear_bot();
47 		putstr("!");
48 		putstr(cmd);
49 		putstr("\n");
50 	}
51 
52 	/*
53 	 * Close the current input file.
54 	 */
55 	save_ifile = save_curr_ifile();
56 	(void) edit_ifile(NULL);
57 
58 	/*
59 	 * De-initialize the terminal and take out of raw mode.
60 	 */
61 	deinit();
62 	flush(0);	/* Make sure the deinit chars get out */
63 	raw_mode(0);
64 
65 	/*
66 	 * Restore signals to their defaults.
67 	 */
68 	init_signals(0);
69 
70 	/*
71 	 * Force standard input to be the user's terminal
72 	 * (the normal standard input), even if less's standard input
73 	 * is coming from a pipe.
74 	 */
75 	inp = dup(0);
76 	(void) close(0);
77 	if (open("/dev/tty", O_RDONLY) == -1)
78 		(void) dup(inp);
79 
80 	/*
81 	 * Pass the command to the system to be executed.
82 	 * If we have a SHELL environment variable, use
83 	 * <$SHELL -c "command"> instead of just <command>.
84 	 * If the command is empty, just invoke a shell.
85 	 */
86 	p = NULL;
87 	if ((shell = lgetenv("SHELL")) != NULL && *shell != '\0') {
88 		if (*cmd == '\0') {
89 			p = estrdup(shell);
90 		} else {
91 			char *esccmd = shell_quote(cmd);
92 			if (esccmd != NULL) {
93 				p = easprintf("%s -c %s", shell, esccmd);
94 				free(esccmd);
95 			}
96 		}
97 	}
98 	if (p == NULL) {
99 		if (*cmd == '\0')
100 			p = estrdup("sh");
101 		else
102 			p = estrdup(cmd);
103 	}
104 	(void) system(p);
105 	free(p);
106 
107 	/*
108 	 * Restore standard input, reset signals, raw mode, etc.
109 	 */
110 	(void) close(0);
111 	(void) dup(inp);
112 	(void) close(inp);
113 
114 	init_signals(1);
115 	raw_mode(1);
116 	if (donemsg != NULL) {
117 		putstr(donemsg);
118 		putstr("  (press RETURN)");
119 		get_return();
120 		(void) putchr('\n');
121 		flush(0);
122 	}
123 	init();
124 	screen_trashed = 1;
125 
126 	/*
127 	 * Reopen the current input file.
128 	 */
129 	reedit_ifile(save_ifile);
130 
131 	/*
132 	 * Since we were ignoring window change signals while we executed
133 	 * the system command, we must assume the window changed.
134 	 * Warning: this leaves a signal pending (in "signal_winch"),
135 	 * so psignals() should be called soon after lsystem().
136 	 */
137 	sigwinch(0);
138 }
139 
140 /*
141  * Pipe a section of the input file into the given shell command.
142  * The section to be piped is the section "between" the current
143  * position and the position marked by the given letter.
144  *
145  * If the mark is after the current screen, the section between
146  * the top line displayed and the mark is piped.
147  * If the mark is before the current screen, the section between
148  * the mark and the bottom line displayed is piped.
149  * If the mark is on the current screen, or if the mark is ".",
150  * the whole current screen is piped.
151  */
152 int
153 pipe_mark(int c, char *cmd)
154 {
155 	off_t mpos, tpos, bpos;
156 
157 	/*
158 	 * mpos = the marked position.
159 	 * tpos = top of screen.
160 	 * bpos = bottom of screen.
161 	 */
162 	mpos = markpos(c);
163 	if (mpos == -1)
164 		return (-1);
165 	tpos = position(TOP);
166 	if (tpos == -1)
167 		tpos = ch_zero();
168 	bpos = position(BOTTOM);
169 
170 	if (c == '.')
171 		return (pipe_data(cmd, tpos, bpos));
172 	else if (mpos <= tpos)
173 		return (pipe_data(cmd, mpos, bpos));
174 	else if (bpos == -1)
175 		return (pipe_data(cmd, tpos, bpos));
176 	else
177 		return (pipe_data(cmd, tpos, mpos));
178 }
179 
180 /*
181  * Create a pipe to the given shell command.
182  * Feed it the file contents between the positions spos and epos.
183  */
184 static int
185 pipe_data(char *cmd, off_t spos, off_t epos)
186 {
187 	FILE *f;
188 	int c;
189 
190 	/*
191 	 * This is structured much like lsystem().
192 	 * Since we're running a shell program, we must be careful
193 	 * to perform the necessary deinitialization before running
194 	 * the command, and reinitialization after it.
195 	 */
196 	if (ch_seek(spos) != 0) {
197 		error("Cannot seek to start position", NULL);
198 		return (-1);
199 	}
200 
201 	if ((f = popen(cmd, "w")) == NULL) {
202 		error("Cannot create pipe", NULL);
203 		return (-1);
204 	}
205 	clear_bot();
206 	putstr("!");
207 	putstr(cmd);
208 	putstr("\n");
209 
210 	deinit();
211 	flush(0);
212 	raw_mode(0);
213 	init_signals(0);
214 	lsignal(SIGPIPE, SIG_IGN);
215 
216 	c = EOI;
217 	while (epos == -1 || spos++ <= epos) {
218 		/*
219 		 * Read a character from the file and give it to the pipe.
220 		 */
221 		c = ch_forw_get();
222 		if (c == EOI)
223 			break;
224 		if (putc(c, f) == EOF)
225 			break;
226 	}
227 
228 	/*
229 	 * Finish up the last line.
230 	 */
231 	while (c != '\n' && c != EOI) {
232 		c = ch_forw_get();
233 		if (c == EOI)
234 			break;
235 		if (putc(c, f) == EOF)
236 			break;
237 	}
238 
239 	(void) pclose(f);
240 
241 	lsignal(SIGPIPE, SIG_DFL);
242 	init_signals(1);
243 	raw_mode(1);
244 	init();
245 	screen_trashed = 1;
246 	/* {{ Probably don't need this here. }} */
247 	sigwinch(0);
248 	return (0);
249 }
250