xref: /freebsd/contrib/less/lsystem.c (revision 4523eebc6c1828d4fd41d7f1ab943cf079043bc8)
1 /*
2  * Copyright (C) 1984-2026  Mark Nudelman
3  *
4  * You may distribute under the terms of either the GNU General Public
5  * License or the Less License, as specified in the README file.
6  *
7  * For more information, see the README file.
8  */
9 
10 
11 /*
12  * Routines to execute other programs.
13  * Necessarily very OS dependent.
14  */
15 
16 #include "less.h"
17 #include <signal.h>
18 #include "position.h"
19 
20 #if MSDOS_COMPILER
21 #include <dos.h>
22 #if MSDOS_COMPILER==WIN32C && defined(__MINGW32__)
23 #include <direct.h>
24 #define setdisk(n) _chdrive((n)+1)
25 #else
26 #ifdef _MSC_VER
27 #include <direct.h>
28 #define setdisk(n) _chdrive((n)+1)
29 #else
30 #include <dir.h>
31 #endif
32 #endif
33 #endif
34 
35 extern int sigs;
36 extern IFILE curr_ifile;
37 
38 
39 #if HAVE_SYSTEM
40 
41 /*
42  * Pass the specified command to a shell to be executed.
43  * Like plain "system()", but handles resetting terminal modes, etc.
44  */
45 public void lsystem(constant char *cmd, constant char *donemsg)
46 {
47 	int inp;
48 #if HAVE_SHELL
49 	constant char *shell;
50 	char *p;
51 #endif
52 	IFILE save_ifile;
53 #if MSDOS_COMPILER && MSDOS_COMPILER!=WIN32C
54 	char cwd[FILENAME_MAX+1];
55 #endif
56 
57 	/*
58 	 * Print the command which is to be executed,
59 	 * unless the command starts with a "-".
60 	 */
61 	if (cmd[0] == '-')
62 		cmd++;
63 	else
64 	{
65 		clear_bot();
66 		putstr("!");
67 		putstr(cmd);
68 		putstr("\n");
69 	}
70 
71 #if MSDOS_COMPILER
72 #if MSDOS_COMPILER==WIN32C
73 	if (*cmd == '\0')
74 		cmd = getenv("COMSPEC");
75 #else
76 	/*
77 	 * Working directory is global on MSDOS.
78 	 * The child might change the working directory, so we
79 	 * must save and restore CWD across calls to "system",
80 	 * or else we won't find our file when we return and
81 	 * try to "reedit_ifile" it.
82 	 */
83 	getcwd(cwd, FILENAME_MAX);
84 #endif
85 #endif
86 
87 	/*
88 	 * Close the current input file.
89 	 */
90 	save_ifile = save_curr_ifile();
91 	(void) edit_ifile(NULL_IFILE);
92 
93 	/*
94 	 * De-initialize the terminal and take out of raw mode.
95 	 */
96 	term_deinit();
97 	flush();         /* Make sure the deinit chars get out */
98 	raw_mode(FALSE);
99 #if MSDOS_COMPILER==WIN32C
100 	close_getchr();
101 #endif
102 
103 #if !MSDOS_COMPILER
104 	/*
105 	 * Restore signals to their defaults.
106 	 * But not on Windows, since signals are received by background processes
107 	 * and a ctrl-C while running the child would kill this instance of less.
108 	 */
109 	init_signals(FALSE);
110 #endif
111 
112 #if HAVE_DUP
113 	/*
114 	 * Force standard input to be the user's terminal
115 	 * (the normal standard input), even if less's standard input
116 	 * is coming from a pipe.
117 	 */
118 	inp = dup(0);
119 	close(0);
120 #if !MSDOS_COMPILER
121 	if (open_tty() < 0)
122 #endif
123 		dup(inp);
124 #endif
125 
126 	/*
127 	 * Pass the command to the system to be executed.
128 	 * If we have a SHELL environment variable, use
129 	 * <$SHELL -c "command"> instead of just <command>.
130 	 * If the command is empty, just invoke a shell.
131 	 */
132 #if HAVE_SHELL
133 	p = NULL;
134 	if ((shell = lgetenv("SHELL")) != NULL && *shell != '\0')
135 	{
136 		if (*cmd == '\0')
137 			p = save(shell);
138 		else
139 		{
140 			constant char *copt = shell_coption();
141 			if (copt == NULL)
142 				p = save(cmd);
143 			else
144 			{
145 				char *esccmd = shell_quote(cmd);
146 				if (esccmd != NULL)
147 				{
148 					size_t len = strlen(shell) + strlen(esccmd) + strlen(copt) + 3;
149 					p = (char *) ecalloc(len, sizeof(char));
150 					SNPRINTF3(p, len, "%s %s %s", shell, copt, esccmd);
151 					free(esccmd);
152 				}
153 			}
154 		}
155 	}
156 	if (p == NULL)
157 	{
158 		if (*cmd == '\0')
159 			p = save("sh");
160 		else
161 			p = save(cmd);
162 	}
163 	system(p);
164 	free(p);
165 #else
166 #if MSDOS_COMPILER==DJGPPC
167 	/*
168 	 * Make stdin of the child be in cooked mode.
169 	 */
170 	setmode(0, O_TEXT);
171 	/*
172 	 * We don't need to catch signals of the child (it
173 	 * also makes trouble with some DPMI servers).
174 	 */
175 	__djgpp_exception_toggle();
176 	system(cmd);
177 	__djgpp_exception_toggle();
178 #else
179 	system(cmd);
180 #endif
181 #endif
182 
183 #if HAVE_DUP
184 	/*
185 	 * Restore standard input, reset signals, raw mode, etc.
186 	 */
187 	close(0);
188 	dup(inp);
189 	close(inp);
190 #endif
191 
192 #if MSDOS_COMPILER==WIN32C
193 	open_getchr();
194 #endif
195 #if !MSDOS_COMPILER
196 	init_signals(TRUE);
197 #endif
198 	raw_mode(TRUE);
199 	if (donemsg != NULL)
200 	{
201 		putstr(donemsg);
202 		putstr("  ");
203 		putstr(LM(press_RETURN));
204 		get_return();
205 		putchr('\n');
206 		flush();
207 	}
208 	term_init();
209 	screen_trashed();
210 
211 #if MSDOS_COMPILER && MSDOS_COMPILER!=WIN32C
212 	/*
213 	 * Restore the previous directory (possibly
214 	 * changed by the child program we just ran).
215 	 */
216 	chdir(cwd);
217 #if MSDOS_COMPILER != DJGPPC
218 	/*
219 	 * Some versions of chdir() don't change to the drive
220 	 * which is part of CWD.  (DJGPP does this in chdir.)
221 	 */
222 	if (cwd[1] == ':')
223 	{
224 		if (cwd[0] >= 'a' && cwd[0] <= 'z')
225 			setdisk(cwd[0] - 'a');
226 		else if (cwd[0] >= 'A' && cwd[0] <= 'Z')
227 			setdisk(cwd[0] - 'A');
228 	}
229 #endif
230 #endif
231 
232 	/*
233 	 * Reopen the current input file.
234 	 */
235 	reedit_ifile(save_ifile);
236 
237 	/*
238 	 * Since we were ignoring window change signals while we executed
239 	 * the system command, we must assume the window changed.
240 	 * Warning: this leaves a signal pending (in "sigs"),
241 	 * so psignals() should be called soon after lsystem().
242 	 */
243 	sigs |= S_WINCH;
244 }
245 
246 #endif
247 
248 #if PIPEC
249 /*
250  * Create a pipe to the given shell command.
251  * Feed it the file contents between the positions spos and epos.
252  */
253 static int pipe_data(constant char *cmd, POSITION spos, POSITION epos)
254 {
255 	FILE *f;
256 	int c;
257 
258 	/*
259 	 * This is structured much like lsystem().
260 	 * Since we're running a shell program, we must be careful
261 	 * to perform the necessary deinitialization before running
262 	 * the command, and reinitialization after it.
263 	 */
264 	if (ch_seek(spos) != 0)
265 	{
266 		error(LM(Cannot_seek_to_start_position), NULL_PARG);
267 		return (-1);
268 	}
269 
270 	if ((f = popen(cmd, "w")) == NULL)
271 	{
272 		error(LM(Cannot_create_pipe), NULL_PARG);
273 		return (-1);
274 	}
275 	clear_bot();
276 	putstr("!");
277 	putstr(cmd);
278 	putstr("\n");
279 
280 	term_deinit();
281 	flush();
282 	raw_mode(FALSE);
283 #if !MSDOS_COMPILER
284 	init_signals(FALSE);
285 #endif
286 #if MSDOS_COMPILER==WIN32C
287 	close_getchr();
288 #endif
289 #ifdef SIGPIPE
290 	LSIGNAL(SIGPIPE, SIG_IGN);
291 #endif
292 
293 	c = EOI;
294 	while (epos == NULL_POSITION || spos++ <= epos)
295 	{
296 		/*
297 		 * Read a character from the file and give it to the pipe.
298 		 */
299 		c = ch_forw_get();
300 		if (c == EOI)
301 			break;
302 		if (putc(c, f) == EOF)
303 			break;
304 	}
305 
306 	/*
307 	 * Finish up the last line.
308 	 */
309 	while (c != '\n' && c != EOI )
310 	{
311 		c = ch_forw_get();
312 		if (c == EOI)
313 			break;
314 		if (putc(c, f) == EOF)
315 			break;
316 	}
317 
318 	pclose(f);
319 
320 #ifdef SIGPIPE
321 	LSIGNAL(SIGPIPE, SIG_DFL);
322 #endif
323 #if MSDOS_COMPILER==WIN32C
324 	open_getchr();
325 #endif
326 #if !MSDOS_COMPILER
327 	init_signals(TRUE);
328 #endif
329 	raw_mode(TRUE);
330 	term_init();
331 	screen_trashed();
332 #if defined(SIGWINCH) || defined(SIGWIND)
333 	/* {{ Probably don't need this here. }} */
334 	lwinch(0);
335 #endif
336 	return (0);
337 }
338 
339 /*
340  * Pipe a section of the input file into the given shell command.
341  * If mpos2 is not NULL_POSITION, the section between
342  * mpos1 and mpos2 is piped.
343  * Otherwise, if mpos1 is before the current screen,
344  * the section between mpos1 and the bottom line displayed is piped.
345  * Otherwise, the section between the top line displayed and
346  * mpos1 is piped.
347  */
348 public int pipe_pos(constant char *cmd, POSITION mpos1, POSITION mpos2)
349 {
350 	POSITION tpos, bpos;
351 
352 	tpos = position(TOP);
353 	if (tpos == NULL_POSITION)
354 		tpos = ch_zero();
355 	bpos = position(BOTTOM);
356 
357 	if (mpos2 != NULL_POSITION)
358 	{
359 		if (mpos1 < mpos2)
360 			return pipe_data(cmd, mpos1, mpos2);
361 		else
362 			return pipe_data(cmd, mpos2, mpos1);
363 	} else if (mpos1 < tpos)
364 		return pipe_data(cmd, mpos1, bpos);
365 	else
366 		return pipe_data(cmd, tpos, mpos1);
367 }
368 
369 #endif
370