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