Commit | Line | Data |
---|---|---|
f5bcab4b WJ |
1 | /* system.c -- UNIX version */ |
2 | ||
3 | /* Author: | |
4 | * Steve Kirkendall | |
5 | * 14407 SW Teal Blvd. #C | |
6 | * Beaverton, OR 97005 | |
7 | * kirkenda@cs.pdx.edu | |
8 | */ | |
9 | ||
10 | ||
11 | /* This file contains a new version of the system() function and related stuff. | |
12 | * | |
13 | * Entry points are: | |
14 | * system(cmd) - run a single shell command | |
15 | * wildcard(names) - expand wildcard characters in filanames | |
16 | * filter(m,n,cmd,back) - run text lines through a filter program | |
17 | * | |
18 | * This is probably the single least portable file in the program. The code | |
19 | * shown here should work correctly if it links at all; it will work on UNIX | |
20 | * and any O.S./Compiler combination which adheres to UNIX forking conventions. | |
21 | */ | |
22 | ||
23 | #include "config.h" | |
24 | #include "vi.h" | |
25 | extern char **environ; | |
26 | ||
27 | #if ANY_UNIX | |
28 | ||
29 | /* This is a new version of the system() function. The only difference | |
30 | * between this one and the library one is: this one uses the o_shell option. | |
31 | */ | |
32 | int system(cmd) | |
33 | char *cmd; /* a command to run */ | |
34 | { | |
35 | int pid; /* process ID of child */ | |
36 | int died; | |
37 | int status; /* exit status of the command */ | |
38 | ||
39 | ||
40 | signal(SIGINT, SIG_IGN); | |
41 | pid = fork(); | |
42 | switch (pid) | |
43 | { | |
44 | case -1: /* error */ | |
45 | msg("fork() failed"); | |
46 | status = -1; | |
47 | break; | |
48 | ||
49 | case 0: /* child */ | |
50 | /* for the child, close all files except stdin/out/err */ | |
51 | for (status = 3; status < 60 && (close(status), errno != EINVAL); status++) | |
52 | { | |
53 | } | |
54 | ||
55 | signal(SIGINT, SIG_DFL); | |
56 | if (cmd == o_shell) | |
57 | { | |
58 | execle(o_shell, o_shell, (char *)0, environ); | |
59 | } | |
60 | else | |
61 | { | |
62 | execle(o_shell, o_shell, "-c", cmd, (char *)0, environ); | |
63 | } | |
64 | msg("execle(\"%s\", ...) failed", o_shell); | |
65 | exit(1); /* if we get here, the exec failed */ | |
66 | ||
67 | default: /* parent */ | |
68 | do | |
69 | { | |
70 | died = wait(&status); | |
71 | } while (died >= 0 && died != pid); | |
72 | if (died < 0) | |
73 | { | |
74 | status = -1; | |
75 | } | |
76 | #if __GNUC__ | |
77 | signal(SIGINT, (void (*)()) trapint); | |
78 | #else | |
79 | signal(SIGINT, trapint); | |
80 | #endif | |
81 | } | |
82 | ||
83 | return status; | |
84 | } | |
85 | ||
86 | /* This private function opens a pipe from a filter. It is similar to the | |
87 | * system() function above, and to popen(cmd, "r"). | |
88 | */ | |
89 | int rpipe(cmd, in) | |
90 | char *cmd; /* the filter command to use */ | |
91 | int in; /* the fd to use for stdin */ | |
92 | { | |
93 | int r0w1[2];/* the pipe fd's */ | |
94 | ||
95 | /* make the pipe */ | |
96 | if (pipe(r0w1) < 0) | |
97 | { | |
98 | return -1; /* pipe failed */ | |
99 | } | |
100 | ||
101 | /* The parent process (elvis) ignores signals while the filter runs. | |
102 | * The child (the filter program) will reset this, so that it can | |
103 | * catch the signal. | |
104 | */ | |
105 | signal(SIGINT, SIG_IGN); | |
106 | ||
107 | switch (fork()) | |
108 | { | |
109 | case -1: /* error */ | |
110 | return -1; | |
111 | ||
112 | case 0: /* child */ | |
113 | /* close the "read" end of the pipe */ | |
114 | close(r0w1[0]); | |
115 | ||
116 | /* redirect stdout to go to the "write" end of the pipe */ | |
117 | close(1); | |
118 | dup(r0w1[1]); | |
119 | close(2); | |
120 | dup(r0w1[1]); | |
121 | close(r0w1[1]); | |
122 | ||
123 | /* redirect stdin */ | |
124 | if (in != 0) | |
125 | { | |
126 | close(0); | |
127 | dup(in); | |
128 | close(in); | |
129 | } | |
130 | ||
131 | /* the filter should accept SIGINT signals */ | |
132 | signal(SIGINT, SIG_DFL); | |
133 | ||
134 | /* exec the shell to run the command */ | |
135 | execle(o_shell, o_shell, "-c", cmd, (char *)0, environ); | |
136 | exit(1); /* if we get here, exec failed */ | |
137 | ||
138 | default: /* parent */ | |
139 | /* close the "write" end of the pipe */ | |
140 | close(r0w1[1]); | |
141 | ||
142 | return r0w1[0]; | |
143 | } | |
144 | } | |
145 | ||
146 | #endif /* non-DOS */ | |
147 | ||
148 | #if OSK | |
149 | ||
150 | /* This private function opens a pipe from a filter. It is similar to the | |
151 | * system() function above, and to popen(cmd, "r"). | |
152 | */ | |
153 | int rpipe(cmd, in) | |
154 | char *cmd; /* the filter command to use */ | |
155 | int in; /* the fd to use for stdin */ | |
156 | { | |
157 | return osk_popen(cmd, "r", in, 0); | |
158 | } | |
159 | #endif | |
160 | ||
161 | #if ANY_UNIX || OSK | |
162 | ||
163 | /* This function closes the pipe opened by rpipe(), and returns 0 for success */ | |
164 | int rpclose(fd) | |
165 | int fd; | |
166 | { | |
167 | int status; | |
168 | ||
169 | close(fd); | |
170 | wait(&status); | |
171 | #if __GNUC__ | |
172 | signal(SIGINT, (void (*)()) trapint); | |
173 | #else | |
174 | signal(SIGINT, trapint); | |
175 | #endif | |
176 | return status; | |
177 | } | |
178 | ||
179 | #endif /* non-DOS */ | |
180 | ||
181 | /* This function expands wildcards in a filename or filenames. It does this | |
182 | * by running the "echo" command on the filenames via the shell; it is assumed | |
183 | * that the shell will expand the names for you. If for any reason it can't | |
184 | * run echo, then it returns the names unmodified. | |
185 | */ | |
186 | ||
187 | #if MSDOS || TOS | |
188 | #define PROG "wildcard " | |
189 | #define PROGLEN 9 | |
190 | #include <string.h> | |
191 | #else | |
192 | #define PROG "echo " | |
193 | #define PROGLEN 5 | |
194 | #endif | |
195 | ||
196 | #if !AMIGA | |
197 | char *wildcard(names) | |
198 | char *names; | |
199 | { | |
200 | ||
201 | # if VMS | |
202 | /* | |
203 | We could use expand() [vmswild.c], but what's the point on VMS? | |
204 | Anyway, echo is the wrong thing to do, it takes too long to build | |
205 | a subprocess on VMS and any "echo" program would have to be supplied | |
206 | by elvis. More importantly, many VMS utilities expand names | |
207 | themselves (the shell doesn't do any expansion) so the concept is | |
208 | non-native. jdc | |
209 | */ | |
210 | return names; | |
211 | # else | |
212 | ||
213 | int i, j, fd; | |
214 | REG char *s, *d; | |
215 | ||
216 | ||
217 | /* build the echo command */ | |
218 | if (names != tmpblk.c) | |
219 | { | |
220 | /* the names aren't in tmpblk.c, so we can do it the easy way */ | |
221 | strcpy(tmpblk.c, PROG); | |
222 | strcat(tmpblk.c, names); | |
223 | } | |
224 | else | |
225 | { | |
226 | /* the names are already in tmpblk.c, so shift them to make | |
227 | * room for the word "echo " | |
228 | */ | |
229 | for (s = names + strlen(names) + 1, d = s + PROGLEN; s > names; ) | |
230 | { | |
231 | *--d = *--s; | |
232 | } | |
233 | strncpy(names, PROG, PROGLEN); | |
234 | } | |
235 | ||
236 | /* run the command & read the resulting names */ | |
237 | fd = rpipe(tmpblk.c, 0); | |
238 | if (fd < 0) return names; | |
239 | i = 0; | |
240 | do | |
241 | { | |
242 | j = tread(fd, tmpblk.c + i, BLKSIZE - i); | |
243 | i += j; | |
244 | } while (j > 0); | |
245 | ||
246 | /* successful? */ | |
247 | if (rpclose(fd) == 0 && j == 0 && i < BLKSIZE && i > 0) | |
248 | { | |
249 | tmpblk.c[i-1] = '\0'; /* "i-1" so we clip off the newline */ | |
250 | return tmpblk.c; | |
251 | } | |
252 | else | |
253 | { | |
254 | return names; | |
255 | } | |
256 | # endif | |
257 | } | |
258 | #endif | |
259 | ||
260 | /* This function runs a range of lines through a filter program, and replaces | |
261 | * the original text with the filtered version. As a special case, if "to" | |
262 | * is MARK_UNSET, then it runs the filter program with stdin coming from | |
263 | * /dev/null, and inserts any output lines. | |
264 | */ | |
265 | int filter(from, to, cmd, back) | |
266 | MARK from, to; /* the range of lines to filter */ | |
267 | char *cmd; /* the filter command */ | |
268 | int back; /* boolean: will we read lines back? */ | |
269 | { | |
270 | int scratch; /* fd of the scratch file */ | |
271 | int fd; /* fd of the pipe from the filter */ | |
272 | char scrout[50]; /* name of the scratch out file */ | |
273 | MARK new; /* place where new text should go */ | |
274 | long sent, rcvd; /* number of lines sent/received */ | |
275 | int i, j; | |
276 | ||
277 | /* write the lines (if specified) to a temp file */ | |
278 | if (to) | |
279 | { | |
280 | /* we have lines */ | |
281 | #if MSDOS || TOS | |
282 | strcpy(scrout, o_directory); | |
283 | if ((i=strlen(scrout)) && !strchr("\\/:", scrout[i-1])) | |
284 | scrout[i++]=SLASH; | |
285 | strcpy(scrout+i, SCRATCHOUT+3); | |
286 | #else | |
287 | sprintf(scrout, SCRATCHOUT, o_directory); | |
288 | #endif | |
289 | mktemp(scrout); | |
290 | cmd_write(from, to, CMD_BANG, FALSE, scrout); | |
291 | sent = markline(to) - markline(from) + 1L; | |
292 | ||
293 | /* use those lines as stdin */ | |
294 | scratch = open(scrout, O_RDONLY); | |
295 | if (scratch < 0) | |
296 | { | |
297 | unlink(scrout); | |
298 | return -1; | |
299 | } | |
300 | } | |
301 | else | |
302 | { | |
303 | scratch = 0; | |
304 | sent = 0L; | |
305 | } | |
306 | ||
307 | /* start the filter program */ | |
308 | #if VMS | |
309 | /* | |
310 | VMS doesn't know a thing about file descriptor 0. The rpipe | |
311 | concept is non-portable. Hence we need a file name argument. | |
312 | */ | |
313 | fd = rpipe(cmd, scratch, scrout); | |
314 | #else | |
315 | fd = rpipe(cmd, scratch); | |
316 | #endif | |
317 | if (fd < 0) | |
318 | { | |
319 | if (to) | |
320 | { | |
321 | close(scratch); | |
322 | unlink(scrout); | |
323 | } | |
324 | return -1; | |
325 | } | |
326 | ||
327 | if (back) | |
328 | { | |
329 | ChangeText | |
330 | { | |
331 | /* adjust MARKs for whole lines, and set "new" */ | |
332 | from &= ~(BLKSIZE - 1); | |
333 | if (to) | |
334 | { | |
335 | to &= ~(BLKSIZE - 1); | |
336 | to += BLKSIZE; | |
337 | new = to; | |
338 | } | |
339 | else | |
340 | { | |
341 | new = from + BLKSIZE; | |
342 | } | |
343 | ||
344 | #if VMS | |
345 | /* Reading from a VMS mailbox (pipe) is record oriented... */ | |
346 | # define tread vms_pread | |
347 | #endif | |
348 | ||
349 | /* repeatedly read in new text and add it */ | |
350 | rcvd = 0L; | |
351 | while ((i = tread(fd, tmpblk.c, BLKSIZE - 1)) > 0) | |
352 | { | |
353 | tmpblk.c[i] = '\0'; | |
354 | add(new, tmpblk.c); | |
355 | #if VMS | |
356 | /* What! An advantage to record oriented reads? */ | |
357 | new += (i - 1); | |
358 | new = (new & ~(BLKSIZE - 1)) + BLKSIZE; | |
359 | rcvd++; | |
360 | #else | |
361 | for (i = 0; tmpblk.c[i]; i++) | |
362 | { | |
363 | if (tmpblk.c[i] == '\n') | |
364 | { | |
365 | new = (new & ~(BLKSIZE - 1)) + BLKSIZE; | |
366 | rcvd++; | |
367 | } | |
368 | else | |
369 | { | |
370 | new++; | |
371 | } | |
372 | } | |
373 | #endif | |
374 | } | |
375 | } | |
376 | ||
377 | /* delete old text, if any */ | |
378 | if (to) | |
379 | { | |
380 | cut(from, to); | |
381 | delete(from, to); | |
382 | } | |
383 | } | |
384 | else | |
385 | { | |
386 | /* read the command's output, and copy it to the screen */ | |
387 | while ((i = tread(fd, tmpblk.c, BLKSIZE - 1)) > 0) | |
388 | { | |
389 | for (j = 0; j < i; j++) | |
390 | { | |
391 | addch(tmpblk.c[j]); | |
392 | } | |
393 | } | |
394 | rcvd = 0; | |
395 | } | |
396 | ||
397 | /* Reporting... */ | |
398 | if (sent >= *o_report || rcvd >= *o_report) | |
399 | { | |
400 | if (sent > 0L && rcvd > 0L) | |
401 | { | |
402 | msg("%ld lines out, %ld lines back", sent, rcvd); | |
403 | } | |
404 | else if (sent > 0) | |
405 | { | |
406 | msg("%ld lines written to filter", sent); | |
407 | } | |
408 | else | |
409 | { | |
410 | msg("%ld lines read from filter", rcvd); | |
411 | } | |
412 | } | |
413 | rptlines = 0L; | |
414 | ||
415 | /* cleanup */ | |
416 | rpclose(fd); | |
417 | if (to) | |
418 | { | |
419 | close(scratch); | |
420 | unlink(scrout); | |
421 | } | |
422 | return 0; | |
423 | } |