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