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