FORM 4.3
extcmd.c
Go to the documentation of this file.
1
5/* #[ License : */
6/*
7 * Copyright (C) 1984-2022 J.A.M. Vermaseren
8 * When using this file you are requested to refer to the publication
9 * J.A.M.Vermaseren "New features of FORM" math-ph/0010025
10 * This is considered a matter of courtesy as the development was paid
11 * for by FOM the Dutch physics granting agency and we would like to
12 * be able to track its scientific use to convince FOM of its value
13 * for the community.
14 *
15 * This file is part of FORM.
16 *
17 * FORM is free software: you can redistribute it and/or modify it under the
18 * terms of the GNU General Public License as published by the Free Software
19 * Foundation, either version 3 of the License, or (at your option) any later
20 * version.
21 *
22 * FORM is distributed in the hope that it will be useful, but WITHOUT ANY
23 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
24 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
25 * details.
26 *
27 * You should have received a copy of the GNU General Public License along
28 * with FORM. If not, see <http://www.gnu.org/licenses/>.
29 */
30/* #] License : */
31/*
32 #[ Documentation :
33
34 This module is written by M.Tentyukov as a part of implementation of
35 interaction between FORM and external processes, first release
36 09.04.2004. A part of this code is copyied from the DIANA project
37 written by M. Tentyukov and published under the GPL version 2 as
38 published by the Free Software Foundation. The code of this module
39 is NOT covered by GPL; it can be used under the terms of the FORM
40 License http://www.nikhef.nl/~form/license.html
41
42 This file is completely re-written by M.Tentyukov in May 2006.
43 Since the interface was changed, the public function were changed,
44 also. A new publc functions were added: initPresetExternalChannels()
45 (see comments just before this function in the present file) and
46 setKillModeForExternalChannel (a pointer, not a function).
47
48 If a macro WITHEXTERNALCHANNEL is not defined, all public punctions
49 are stubs returning failure.
50
51 The idea is to start an external command swallowing
52 its stdin and stdout. This can be done by means of the function
53 int openExternalChannel(cmd,daemonize,shellname,stderrname), where
54 cmd is a command to run,
55 daemonize: if !=0 then start the command in the "daemon" mode,
56 shellname: if !=NULL, execute the command in a subshell,
57 stderrname: if != NULL, redirect stderr of the command to this file.
58 The function returns some small positive integer number (the
59 descriptor of a newly created external channel), or -1 on failure.
60
61 After the command is started, it becomes a _current_ opened external
62 channel. The buffer can be sent to its stdin by a function
63 int writeBufToExtChannel(buf, n)
64 (here buf is a pointer to the buffer, n is the length in bytes; the
65 function returns 0 in success, or -1 on failure),
66 or one character can be read from its stdout by
67 means of the function
68 int getcFromExtChannel().
69
70 The latter returns the
71 character casted to integer, or something <0. This can be -2 (if there
72 is no current external channel) or EOF, if the external program closes
73 its stdout, or if the external program outputs a string coinciding
74 with a _terminator_.
75
76 By default, the terminator if an empty line. For the current external
77 channel it can be set by means of the function
78 int setTerminatorForExternalChannel(newterminaror).
79 The function returns 0 in success, or !0 if something is wrong (no
80 current channel, too long terminator).
81
82 After getcFromExtChannel() returns EOF, the current channel becomes
83 undefined. Any further attempts to read information by
84 getcFromExtChannel() result in -2. To set (re-set) a current channel,
85 the function
86 int selectExternalChannel(n)
87 can be used. This function accepts the valid external channel
88 descriptor (returned by openExternalChannel) and returns the
89 descriptor of a previous current channel (0, if there was no current
90 channel, or -1, if the external channel descriptor is invalid).
91 If n == 0, the function undefine the current external channel.
92
93 The function
94 int closeExternalChannel(n)
95 destroys the opened external channel with the descriptor n. It returns
96 0 in success, or -1 on failure. If the corresponding external channel
97 was the current one, the current channel becomes undefined. If n==0,
98 the function closes the current external channel.
99
100 The function
101 int getCurrentExternalChannel(void)
102 returns the descriptor if the current external channel, or 0 , if
103 there is no current external channel.
104
105 The function
106 void closeAllExternalChannels(void)
107
108 destroys all opened external channels.
109
110 List of all public functions:
111 int openExternalChannel(UBYTE *cmd,int daemonize,UBYTE *shellname, UBYTE * stderrname);
112 int initPresetExternalChannels(UBYTE *theline, int thetimeout);
113 int setTerminatorForExternalChannel(char *newterminaror);
114 int setKillModeForExternalChannel(int signum, int sentToWholeGroup);
115 int closeExternalChannel(int n);
116 int selectExternalChannel(int n);
117 int writeBufToExtChannel(char *buf,int n);
118 int getcFromExtChannel(void);
119 int getCurrentExternalChannel(void);
120 void closeAllExternalChannels(void);
121
122 ATTENTION!
123
124 Four of them:
125 1 setTerminatorForExternalChannel
126 2 setKillModeForExternalChannel
127 3 writeBufToExtChannel
128 4 getcFromExtChannel
129
130 are NOT functions, but variables (pointers) of a corrsponding type.
131 They are initialised by proper values to avoid repeated error checking.
132
133 All public functions are independent of realization hidden in this module.
134 All other functions may have a returned type/parameters type local w.r.t.
135 this module; they are not declared outside of this file.
136
137 #] Documentation :
138 #[ Selftest initializing:
139*/
140
141/*
142Uncomment to get a self-consistent program:
143#define SELFTEST 1
144*/
145
146
147#ifdef SELFTEST
148#define WITHEXTERNALCHANNEL 1
149#ifdef _MSC_VER
150#define FORM_INLINE __inline
151#else
152#define FORM_INLINE inline
153#endif
154/*
155 from declare.h:
156*/
157#define VOID void
158
159/*
160 From form3.h:
161*/
162typedef unsigned char UBYTE;
163
164/*The following variables should be defined in variable.h:*/
165extern int (*writeBufToExtChannel)(char *buffer, size_t n);
166extern int (*getcFromExtChannel)();
167extern int (*setTerminatorForExternalChannel)(char *buffer);
168extern int (*setKillModeForExternalChannel)(int signum, int sentToWholeGroup);
169
170#else /*ifdef SELFTEST*/
171#include "form3.h"
172#endif /*ifdef SELFTEST ... else*/
173/*
174pid_t getExternalChannelPid(VOID);
175*/
176/*
177 #] Selftest initializing:
178 #[ Includes :
179*/
180#ifdef WITHEXTERNALCHANNEL
181#include <stdio.h>
182#ifndef _MSC_VER
183#include <unistd.h>
184#endif
185#include <fcntl.h>
186#include <sys/types.h>
187#ifndef _MSC_VER
188#include <sys/time.h>
189#include <sys/wait.h>
190#endif
191#include <errno.h>
192#include <signal.h>
193#include <limits.h>
194/*
195 #] Includes :
196 #[ FailureFunctions:
197*/
198
199/*Non-initialized variant of public functions:*/
200int writeBufToExtChannelFailure(char *buf, size_t count)
201{
202 DUMMYUSE(buf); DUMMYUSE(count);
203 return(-1);
204}/*writeBufToExtChannelFailure*/
205
206int setTerminatorForExternalChannelFailure(char *newTerminator)
207{
208 DUMMYUSE(newTerminator);
209 return(-1);
210}/*setTerminatorForExternalChannelFailure*/
211
212int setKillModeForExternalChannelFailure(int signum, int sentToWholeGroup)
213{
214 DUMMYUSE(signum); DUMMYUSE(sentToWholeGroup);
215 return(-1);
216}/*setKillModeForExternalChannelFailure*/
217
218int getcFromExtChannelFailure()
219{
220 return(-2);
221}/*getcFromExtChannelFailure*/
222
223int (*writeBufToExtChannel)(char *buffer, size_t n) = &writeBufToExtChannelFailure;
224int (*setTerminatorForExternalChannel)(char *buffer) =
225 &setTerminatorForExternalChannelFailure;
226int (*setKillModeForExternalChannel)(int signum, int sentToWholeGroup) =
227 &setKillModeForExternalChannelFailure;
228int (*getcFromExtChannel)() = &getcFromExtChannelFailure;
229#endif
230/*
231 #] FailureFunctions:
232 #[ Stubs :
233*/
234#ifndef WITHEXTERNALCHANNEL
235/*Stubs for public functions:*/
236int openExternalChannel(UBYTE *cmd, int daemonize, UBYTE *shellname, UBYTE *stderrname)
237{ DUMMYUSE(cmd); DUMMYUSE(daemonize); DUMMYUSE(shellname); DUMMYUSE(stderrname); return(-1); };
238int initPresetExternalChannels(UBYTE *theline, int thetimeout) { DUMMYUSE(theline); DUMMYUSE(thetimeout); return(-1); };
239int closeExternalChannel(int n) { DUMMYUSE(n); return(-1); };
240int selectExternalChannel(int n) { DUMMYUSE(n); return(-1); };
241int getCurrentExternalChannel() { return(0); };
242void closeAllExternalChannels() {};
243#else /*ifndef WITHEXTERNALCHANNEL*/
244/*
245 #] Stubs :
246 #[ Local types :
247*/
248/*First argument for the function signal:*/
249#ifndef INTSIGHANDLER
250typedef void (*mysighandler_t)(int);
251#else
252/* Sometimes, this nonsense may occurs:*/
253/*typedef int (*mysighandler_t)(int);*/
254#endif
255
256/*Input IO buffer size increment -- each time the buffer
257is expired it will be increased by this value (in bytes):*/
258#define DELTA_EXT_BUF 128
259
260/*Re-allocatable array containing External Channel
261handlers increased each time by this value:*/
262#define DELTA_EXT_LIST 8
263
264/*How many times I/O routines may attempt to continue their work
265in some failures:*/
266#define MAX_FAILS_IO 2
267
268/*The external channel handler structure:*/
269typedef struct ExternalChannel {
270 pid_t pid; /*PID of the external process*/
271 pid_t gpid; /*process group ID of the external process.
272 If <=0, not used, if >0, the kill signals
273 is sent to the whole group */
274 FILE *frec; /*stdout of the external process*/
275 char *INbuf; /*External channel buffer*/
276 char *IBfill; /*Position in INbuf from which the next input character will be read*/
277 char *IBfull; /*End of read INbuf*/
278 char *IBstop; /*end of allocated space for INbuf*/
279 char *terminator;/* Terminator - when extern. program outputs ONLY this string,
280 it is assumed that the answer is ready, and getcFromExtChannel
281 returns EOF. Should not be longer then the minimal buffer!*/
282 /*Info fields, not changable after creating a channel:*/
283 char *cmd; /*the command*/
284 char *shellname;
285 char *stderrname;/*filename to redirect stderr, or NULL*/
286 int fsend; /*stdin of the external process*/
287 int killSignal; /*signal to kill*/
288 int daemonize;/*0 --neither setsid nor daemonize, !=0 -- full daemonization*/
289 PADPOINTER(0,3,0,0);
290} EXTHANDLE;
291
292
293static EXTHANDLE *externalChannelsList=0;
294/*Here integers are better than pointers: */
295static int externalChannelsListStop=0;
296static int externalChannelsListFill=0;
297
298/*"current" external channel:*/
299static EXTHANDLE *externalChannelsListTop=0;
300/*
301 #] Local types :
302 #[ Selftest functions :
303*/
304#ifdef SELFTEST
305
306/*For malloc prototype:*/
307#include <stdlib.h>
308
309/*StrLen, Malloc1, M_free and strDup1 are defined in tools.c -- here only emulation:*/
310int StrLen(char *pattern)
311{
312register char *p=(char*)pattern;
313 while(*p)p++;
314 return((int) ((p-(char*)pattern)) );
315}/*StrLen*/
316void *Malloc1(int l, char *c)
317{
318 return(malloc(l));
319}
320void M_free(void *p,char *c)
321{
322 return(free(p));
323}
324
325char *strDup1(UBYTE *instring, char *ifwrong)
326{
327 UBYTE *s = instring, *to;
328 while ( *s ) s++;
329 to = s = (UBYTE *)Malloc1((s-instring)+1,ifwrong);
330 while ( *instring ) *to++ = *instring++;
331 *to = 0;
332 return(s);
333}
334
335/*PutPreVar from pre.c -- just ths stub:*/
336int PutPreVar(UBYTE *a,UBYTE *b,UBYTE *c,int i)
337{
338 return(0);
339}
340
341#endif
342/*
343 #] Selftest functions :
344 #[ Local functions :
345*/
346
347/*Initialize one cell of handler:*/
348static FORM_INLINE VOID extHandlerInit(EXTHANDLE *h)
349{
350 h->pid=-1;
351 h->gpid=-1;
352 h->fsend=0;
353 h->killSignal=SIGKILL;
354 h->daemonize=1;
355 h->frec=NULL;
356 h->INbuf=h->IBfill=h->IBfull=h->IBstop=
357 h->terminator=h->cmd=h->shellname=h->stderrname=NULL;
358}/*extHandlerInit*/
359
360/* Copies each field of handler:*/
361static FORM_INLINE VOID extHandlerSwallowCopy(EXTHANDLE *to, EXTHANDLE *from)
362{
363 to->pid=from->pid;
364 to->gpid=from->gpid;
365 to->fsend=from->fsend;
366 to->killSignal=from->killSignal;
367 to->daemonize=from->daemonize;
368 to->frec=from->frec;
369 to->INbuf=from->INbuf;
370 to->IBfill=from->IBfill;
371 to->IBfull=from->IBfull;
372 to->IBstop=from->IBstop;
373 to->terminator=from->terminator;
374 to->cmd=from->cmd;
375 to->shellname=from->shellname;
376 to->stderrname=from->stderrname;
377}/*extHandlerSwallow*/
378
379/*Allocates memory for fields of handler which have no fixed
380 storage size and initializes some fields:*/
381static FORM_INLINE VOID
382extHandlerAlloc(EXTHANDLE *h, char *cmd, char *shellname, char *stderrname)
383{
384 h->IBfill=h->IBfull=h->INbuf=
385 Malloc1(DELTA_EXT_BUF,"External channel buffer");
386 h->IBstop=h->INbuf+DELTA_EXT_BUF;
387 /*Initialize a terminator:*/
388 *(h->terminator=Malloc1(DELTA_EXT_BUF,"External channel terminator"))='\n';
389 (h->terminator)[1]='\0';/*By default the terminator is '\n'*/
390 /*Deep copy all strings:*/
391 if(cmd!=NULL)
392 h->cmd=(char *)strDup1((UBYTE *)cmd,"External channel command");
393 else/*cmd cannot be NULL! If this is NULL then force it to be something special*/
394 h->cmd=(char *)strDup1((UBYTE *)"/","External channel command");
395 if(shellname!=NULL)
396 h->shellname=
397 (char *)strDup1((UBYTE *)shellname,"External channel shell name");
398 if(stderrname!=NULL)
399 h->stderrname=
400 (char *)strDup1((UBYTE *)stderrname,"External channel stderr name");
401}/*extHandlerAlloc*/
402
403/*Disallocates dynamically allocated fields of a handler:*/
404static FORM_INLINE VOID extHandlerFree(EXTHANDLE *h)
405{
406 if(h->stderrname) M_free(h->stderrname,"External channel stderr name");
407 if(h->shellname) M_free(h->shellname,"External channel shell name");
408 if(h->cmd) M_free(h->cmd,"External channel command");
409 if(h->terminator)M_free(h->terminator,"External channel terminator");
410 if(h->INbuf)M_free(h->INbuf,"External channel buffer");
411 extHandlerInit(h);
412}/*extHandlerFree*/
413/* Closes all descriptors, kills the external process, frees all internal fields,
414BUT does NOT free the main container:*/
415static VOID destroyExternalChannel(EXTHANDLE *h)
416{
417 /*Note, this function works in parallel mode correctly, see comments below.*/
418
419 /*Note, for slaves in a parallel mode h->pid == 0:*/
420 if( (h->pid > 0) && (h->killSignal > 0) ){
421 int chstatus;
422 if( h->gpid > 0)
423 chstatus=kill(-h->gpid,h->killSignal);
424 else
425 chstatus=kill(h->pid,h->killSignal);
426 if(chstatus==0)
427 /*If the process will not be killed by this signal, FORM hangs up here!:*/
428 waitpid(h->pid, &chstatus, 0);
429 }/*if( (h->pid > 0) && (h->killSignal > 0) )*/
430
431 /*Note, for slaves in a parallel mode h->frec == h->fsend == 0:*/
432 if(h->frec) fclose(h->frec);
433 if( h->fsend > 0) close(h->fsend);
434
435 extHandlerFree(h);
436 /*Does not do "free(h)"!*/
437}/*destroyExternalChannel*/
438
439/*Wrapper to the read() syscall, to handle possible interrupts by unblocked signals:*/
440static FORM_INLINE ssize_t read2b(int fd, char *buf, size_t count)
441{
442ssize_t res;
443
444 if( (res=read(fd,buf,count)) <1 )/*EOF or read is interrupted by a signal?:*/
445 while( (errno == EINTR)&&(res <1) )
446 /*The call was interrupted by a signal before any data was read, try again:*/
447 res=read(fd,buf,count);
448 return (res);
449}/*read2b*/
450
451/*Wrapper to the write() syscall, to handle possible interrupts by unblocked signals:*/
452static FORM_INLINE ssize_t writeFromb(int fd, char *buf, size_t count)
453{
454ssize_t res;
455 if( (res=write(fd,buf,count)) <1 )/*Is write interrupted by a signal?:*/
456 while( (errno == EINTR)&&(res <1) )
457 /*The call was interrupted by a signal before any data was written, try again:*/
458 res=write(fd,buf,count);
459 return (res);
460}/*writeFromb*/
461
462/* Read one (binary) PID from the file descriptor fd:*/
463static FORM_INLINE pid_t readpid(int fd)
464{
465pid_t tmp;
466 if(read2b(fd,(char*)&tmp,sizeof(pid_t))!=sizeof(pid_t))
467 return (pid_t)-1;
468 return tmp;
469}/*readpid*/
470
471/* Writeone (binary) PID to the file descriptor fd:*/
472static FORM_INLINE pid_t writepid(int fd, pid_t thepid)
473{
474 if(writeFromb(fd,(char*)&thepid,sizeof(pid_t))!=sizeof(pid_t))
475 return (pid_t)-1;
476 return (pid_t)0;
477}/*readpid*/
478
479/*Wrtites exactly count bytes from the buffer buf into the descriptor fd, independently on
480 nonblocked signals and the MPU/buffer hits. Returns 0 or -1:
481*/
482static FORM_INLINE int writexactly(int fd, char *buf, size_t count)
483{
484ssize_t i;
485int j=0,n=0;
486
487 for(;;){
488 if( (i=writeFromb(fd, buf+j, count-j)) < 0 ) return(-1);
489 j+=i;
490 if ( ((size_t)j) == count ) break;
491 if(i==0)n++;
492 else n=0;
493 if(n>MAX_FAILS_IO)return (-1);
494 }/*for(;;)*/
495 return (0);
496}/*writexactly*/
497
498/* Set the FD_CLOEXEC flag of desc if value is nonzero,
499 or clear the flag if value is 0.
500 Return 0 on success, or -1 on error with errno set. */
501static int set_cloexec_flag(int desc, int value)
502{
503int oldflags = fcntl (desc, F_GETFD, 0);
504 /* If reading the flags failed, return error indication now.*/
505 if (oldflags < 0)
506 return (oldflags);
507 /* Set just the flag we want to set. */
508 if (value != 0)
509 oldflags |= FD_CLOEXEC;
510 else
511 oldflags &= ~FD_CLOEXEC;
512 /* Store modified flag word in the descriptor. */
513 return (fcntl(desc, F_SETFD, oldflags));
514}/*set_cloexec_flag*/
515
516/* Adds the integer fd to the array fifo of length top+1 so that
517the array is ascendantly ordered. It is supposed that all 0 -- top-1
518elements in the array are already ordered:*/
519static VOID pushDescriptor(int *fifo, int top, int fd)
520{
521 if ( top == 0 ) {
522 fifo[top] = fd;
523 } else {
524 int ins=top-1;
525 if( fifo[ins]<=fd )
526 fifo[top]=fd;
527 else{
528 /*Find the position:*/
529 while( (ins>=0)&&(fifo[ins]>fd) )ins--;
530 /*Move all elements starting from the position to the right:*/
531 for(ins++;top>ins; top--)
532 fifo[top]=fifo[top-1];
533 /*Put the element:*/
534 fifo[ins]=fd;
535 }
536 }
537}/*pushDescriptor*/
538
539/*Close all descriptors greate or equal than startFrom except those
540 listed in the ascendantly ordered array usedFd of length top:*/
541static FORM_INLINE VOID closeAllDescriptors(int startFrom, int *usedFd, int top)
542{
543int n,maxfd;
544 for(n=0;n<top; n++){
545 maxfd=usedFd[n];
546 for(;startFrom<maxfd;startFrom++)/*Close all less than maxfd*/
547 close(startFrom);
548 startFrom++;/*skip maxfd*/
549 }/*for(;startFrom<maxfd;startFrom++)*/
550 /*Close all the rest:*/
551 maxfd=sysconf(_SC_OPEN_MAX);
552 for(;startFrom<maxfd;startFrom++)
553 close(startFrom);
554}/*closeAllDescriptors*/
555
556typedef int L_APIPE[2];
557/*Closes both pipe descriptors if not -1:*/
558static VOID closepipe(L_APIPE *thepipe)
559{
560 if( (*thepipe)[0] != -1) close ((*thepipe)[0]);
561 if( (*thepipe)[1] != -1) close ((*thepipe)[1]);
562}/*closepipe*/
563
564/*Parses the cmd line like "sh -c myprg", passes each option to the
565 correspondinig element of argv, ends agrv by NULL. Returns the
566 number of stored argv elements, or -1 if fails:*/
567static FORM_INLINE int parseline(char **argv, char *cmd)
568{
569int n=0;
570 while(*cmd != '\0'){
571 for(; (*cmd <= ' ') && (*cmd != '\0') ;cmd++);
572 if(*cmd != '\0'){
573 argv[n]=cmd;
574 while(*++cmd > ' ');
575 if(*cmd != '\0')
576 *cmd++ = '\0';
577 n++;
578 }/*if(*cmd != '\0')*/
579 }/*while(*cmd != '\0')*/
580 argv[n]=NULL;
581 if(n==0)return -1;
582 return n;
583}/*parseline*/
584
585/*Reads positive decimal number (not bigger than maxnum)
586 from the string and returns it;
587 the pointer *b is set to the next non-converted character:*/
588static LONG str2i(char *str, char **b, LONG maxnum)
589{
590 LONG n=0;
591 /*Eat trailing spaces:*/
592 while(*str<=' ')if(*str++ == '\0')return(-1);
593 (*b)=str;
594 while (*str>='0'&&*str<='9')
595 if( (n=10*n + *str++ - '0')>maxnum )
596 return(-1);
597 if((*b)==str)/*No single number!*/
598 return(-1);
599 (*b)=str;
600 return(n);
601}
602
603/*Converts long integer to a decimal representation.
604 For portability reasons we cannot use LongCopy from tools.c
605 since theoretically LONG may be smaller than pid_t:*/
606static char *l2s(LONG x, char *to)
607{
608 char *s;
609 int i = 0, j;
610 s = to;
611 do { *s++ = (x % 10)+'0'; i++; } while ( ( x /= 10 ) != 0 );
612 *s-- = '\0';
613 j = ( i - 1 ) >> 1;
614 while ( j >= 0 ) {
615 i = to[j]; to[j] = s[-j]; s[-j] = (char)i; j--;
616 }
617 return(s+1);
618}
619
620/*like strcat() but returns the pointer to the end of the
621resulting string:*/
622static FORM_INLINE char *addStr(char *to, char *from)
623{
624 while( (*to++ = *from++)!='\0' );
625 return(to-1);
626}/*addStr*/
627
628
629/*Try to write (atomically) short buffer (of length count) to fd.
630 timeout is a timeout in millisecs. Returns number of writen bytes or -1:*/
631static FORM_INLINE ssize_t writeSome(int fd, char *buf, size_t count, int timeout)
632{
633 ssize_t res = 0;
634 fd_set wfds;
635 struct timeval tv;
636 int nrep=5;/*five attempts it interrupted by a non-blocking signal*/
637 int flags = fcntl(fd, F_GETFL,0);
638
639 /*Add O_NONBLOCK:*/
640 fcntl(fd,F_SETFL, flags | O_NONBLOCK);
641 /* important -- in order to avoid blocking of short rceiver buffer*/
642
643 do{
644 FD_ZERO(&wfds);
645 FD_SET(fd, &wfds);
646 /* Wait up to timeout. */
647
648 tv.tv_sec =timeout /1000;
649 tv.tv_usec = (timeout % 1000)*1000;
650 nrep--;
651
652 switch(select(fd+1, NULL, &wfds, NULL, &tv)){
653 case -1:
654 if((nrep == 0)||( errno != EINTR) ){
655 perror("select()");
656 res=-1;
657 nrep=0;
658 }/*else -- A non blocked signal was caught, just repeat*/
659 break;
660 case 0:/*timeout*/
661 res=-1;
662 nrep=0;
663 break;
664 default:
665 if( (res=write(fd,buf,count)) <0 )/*Signal?*/
666 while( (errno == EINTR)&&(res <0) )
667 res=write(fd,buf,count);
668 nrep=0;
669 }/*switch*/
670 }while(nrep);
671
672 /*restore the flags:*/
673 fcntl(fd,F_SETFL, flags);
674 return (res);
675}/*writeSome*/
676
677/*Try to read short buffer (of length not more than count)
678 from fd. timeout is a timeout in millisecs. Returns number
679 of writen bytes or -1: */
680static FORM_INLINE ssize_t readSome(int fd, char *buf, size_t count, int timeout)
681{
682 ssize_t res = 0;
683 fd_set rfds;
684 struct timeval tv;
685 int nrep=5;/*five attempts it interrupted by a non-blocking signal*/
686
687 do{
688 FD_ZERO(&rfds);
689 FD_SET(fd, &rfds);
690 /* Wait up to timeout. */
691
692 tv.tv_sec = timeout/1000;
693 tv.tv_usec = (timeout % 1000)*1000;
694 nrep--;
695
696 switch(select(fd+1, &rfds, NULL, NULL, &tv)){
697 case -1:
698 if((nrep == 0)||( errno != EINTR) ){
699 perror("select()");
700 res=-1;
701 nrep=0;
702 }/*else -- A non blocked signal was caught, just repeat*/
703 break;
704 case 0:/*timeout*/
705 res=-1;
706 nrep=0;
707 break;
708 default:
709 if( (res=read(fd,buf,count)) <0 )/*Signal?*/
710 while( (errno == EINTR)&&(res <0) )
711 res=read(fd,buf,count);
712 nrep=0;
713 }/*switch*/
714 }while(nrep);
715 return (res);
716}/*readSome*/
717
718/*
719 #] Local functions :
720 #[ Ok functions:
721*/
722
723/*Copies (deep copy) newTerminator to thehandler->terminator. Returns 0 if
724 newTerminator fits to the buffer, or !0 if it does not fit. ATT! In the
725 latter case thehandler->terminator is NOT '\0' terminated! */
726int setTerminatorForExternalChannelOk(char *newTerminator)
727{
728int i=DELTA_EXT_BUF;
729/*
730 No problems with externalChannelsListTop are
731 possible since this function may be invoked only
732 when the current channel is defined and externalChannelsListTop
733 is set properly
734*/
735char *t=externalChannelsListTop->terminator;
736
737 for(; i>1; i--)
738 if( (*t++ = *newTerminator++)=='\0' )
739 break;
740 /*Add trailing '\n', if absent:*/
741 if( (i == DELTA_EXT_BUF)/*newTerminator == '\0'*/
742 ||(*(t-2)!='\n') ){
743 *(t-1)='\n';*t='\0';
744 }
745 return(i==1);
746}/*setTerminatorForExternalChannelOk*/
747
748/*Interface to change handler fields "killSignal" and "gpid"*/
749int setKillModeForExternalChannelOk(int signum, int sentToWholeGroup)
750{
751 if(signum<0)
752 return(-1);
753 /*
754 No problems with externalChannelsListTop are
755 possible since this function may be invoked only
756 when the current channel is defined and externalChannelsListTop
757 is set properly
758 */
759 externalChannelsListTop->killSignal=signum;
760 if(sentToWholeGroup){/*gpid must be >0*/
761 if(externalChannelsListTop->gpid <= 0)
762 externalChannelsListTop->gpid=-externalChannelsListTop->gpid;
763 }else{/*gpid must be <=0*/
764 if(externalChannelsListTop->gpid>0)
765 externalChannelsListTop->gpid=-externalChannelsListTop->gpid;
766 }
767 return(0);
768}/*setKillModeForExternalChannelOk*/
769
770/*
771 #[ getcFromExtChannelOk
772*/
773/*Returns one character from the external channel. It the input is expired,
774returns EOF. If the external process is finished completely, the function closes
775the channel (and returns EOF). If the external process was finished, the function
776returns EOF:*/
777int getcFromExtChannelOk()
778{
779mysighandler_t oldPIPE = 0;
780EXTHANDLE *h;
781int ret;
782
783 if (externalChannelsListTop->IBfill < externalChannelsListTop->IBfull)
784 /*in buffer*/
785 return( *(externalChannelsListTop->IBfill++) );
786 /*else -- the buffer is empty*/
787 ret=EOF;
788 h= externalChannelsListTop;
789#ifdef WITHMPI
790 if ( PF.me == MASTER ){
791#endif
792 /* Temporary ignore this signal:*/
793 /* if compiler fails here, try to change the definition of
794 mysighandler_t on the beginning of this file
795 (just define INTSIGHANDLER).*/
796 oldPIPE=signal(SIGPIPE,SIG_IGN);
797#ifdef WITHMPI
798 if( fgets(h->INbuf,h->IBstop - h->INbuf, h->frec) == 0 )/*Fail! EOF?*/
799 *(h->INbuf)='\0';/*Empty line may not appear here!*/
800#else
801 if( (fgets(h->INbuf,h->IBstop - h->INbuf, h->frec) == 0)/*Fail! EOF?*/
802 ||( *(h->INbuf) == '\0')/*Empty line? This shouldn't be!*/
803 ){
804 closeExternalChannel(externalChannelsListTop-externalChannelsList+1);
805 /*Note, this code is only for the sequential mode! */
806 goto getcFromExtChannelReady;
807 /*Here we assume that fgets is never interrupted by singals*/
808 }/*if( fgets(h->INbuf,h->IBstop - h->INbuf, h->frec) == 0 )*/
809#endif
810#ifdef WITHMPI
811 }/*if ( PF.me == MASTER */
812
813 /*Master broadcasts result to slaves, slaves read it from the master:*/
814 if( PF_BroadcastString((UBYTE *)h->INbuf) ){/*Fail!*/
815 MesPrint("Fail broadcasting external channel results");
816 Terminate(-1);
817 }/*if( PF_BroadcastString((UBYTE *)h->INbuf) )*/
818
819 if( *(h->INbuf) == '\0'){/*Empty line? This shouldn't be!*/
820 closeExternalChannel(externalChannelsListTop-externalChannelsList+1);
821 goto getcFromExtChannelReady;
822 }/*if( *(h->INbuf) == '\0')*/
823#endif
824
825 {/*Block*/
826 char *t=h->terminator;
827 /*Move IBfull to the end of read line and compare the line with the terminator.
828 Note, by construction the terminator fits to the first read line, see
829 the function setTerminatorForExternalChannel.*/
830 for(h->IBfull=h->INbuf; *(h->IBfull)!='\0'; (h->IBfull)++)
831 if( *t== *(h->IBfull) )
832 t++;
833 else
834 break;/*not a terminator*/
835 /*Continue moving IBfullto the end of read line:*/
836 while(*(h->IBfull)!='\0')(h->IBfull)++;
837
838 if( (t-h->terminator) == (h->IBfull-h->INbuf) ){
839 /*Terminator!*/
840 /*Reset the channel*/
841 h->IBfull=h->IBfill=h->INbuf;
842 externalChannelsListTop=0;/*Undefine the current channel*/
843 writeBufToExtChannel=&writeBufToExtChannelFailure;
844 getcFromExtChannel=&getcFromExtChannelFailure;
845 setTerminatorForExternalChannel=&setTerminatorForExternalChannelFailure;
846 setKillModeForExternalChannel=&setKillModeForExternalChannelFailure;
847 goto getcFromExtChannelReady;
848 }/*if(t == (h->IBfull-h->INbuf) )*/
849 }/*Block*/
850 /*Does the buffer have enough capacity?*/
851 while( *(h->IBfull - 1) != '\n' ){/*Buffer is not enough!*/
852 /*Extend the buffer:*/
853 int l= (h->IBstop - h->INbuf)+DELTA_EXT_BUF;
854 char *newbuf=Malloc1(l,"External channel buffer");
855 /*We wouldn't like to use realloc.*/
856 /*Copy the buffer:*/
857 char *n=newbuf,*o=h->INbuf;
858 while( (*n++ = *o++)!='\0' );
859 /*Att! The order of the following operators is important!:*/
860 h->IBfull= newbuf+(h->IBfull-h->INbuf);
861 M_free(h->INbuf,"External channel buffer");
862 h->INbuf = newbuf;
863 h->IBstop = h->INbuf+l;
864#ifdef WITHMPI
865 if ( PF.me == MASTER ){
866 (h->IBfull)[1]='\0';/*Will mark (h->IBfull)[1] as '!' for failure*/
867 if( fgets(h->IBfull,h->IBstop - h->IBfull, h->frec) == 0 ){
868 /*EOF! No trailing '\n'?*/
869 /*Mark:*/
870 (h->IBfull)[0]='\0';
871 (h->IBfull)[1]='!';
872 (h->IBfull)[2]='\0';
873 /*The string "\0!\0" is used as an image of NULL.*/
874 }/*if( fgets(h->IBfull,h->IBstop - h->IBfull, h->frec) == 0 )*/
875 }/*if ( PF.me == MASTER )*/
876
877 /*Master broadcasts results to slaves, slaves read it from the master:*/
878 if( PF_BroadcastString((UBYTE *)h->IBfull) ){/*Fail!*/
879 MesPrint("Fail broadcasting external channel results");
880 Terminate(-1);
881 }/*if( PF_BroadcastString(h->IBfull) )*/
882
883 /*The string "\0!\0" is used as the image of NULL.*/
884 if(
885 ( (h->IBfull)[0]=='\0' )
886 &&( (h->IBfull)[1]=='!' )
887 &&( (h->IBfull)[2]=='\0' )
888 )/*EOF! No trailing '\n'?*/
889 break;
890#else
891 if( fgets(h->IBfull,h->IBstop - h->IBfull, h->frec) == 0 )
892 /*EOF! No trailing '\n'?*/
893 break;
894#endif
895 while( *(h->IBfull)!='\0' )(h->IBfull)++;
896 }/*while( *(h->IBfull - 1) != '\n' )*/
897 /*In h->INbuf we have a fresh string.*/
898 ret=*(h->IBfill=h->INbuf);
899 h->IBfill++;/*Next time a new, isn't it?*/
900
901 getcFromExtChannelReady:
902#ifdef WITHMPI
903 if ( PF.me == MASTER ){
904#endif
905 signal(SIGPIPE,oldPIPE);
906#ifdef WITHMPI
907 }/*if ( PF.me == MASTER )*/
908#endif
909 return(ret);
910}/*getcFromExtChannelOk*/
911/*
912 #] getcFromExtChannelOk
913*/
914/*Writes exactly count bytes from the buffer buf to the external channel thehandler
915 Returns 0 (on success) or -1:
916*/
917int writeBufToExtChannelOk(char *buf, size_t count)
918{
919
920int ret;
921mysighandler_t oldPIPE;
922#ifdef WITHMPI
923 /*Only master communicates with the external program:*/
924 if ( PF.me == MASTER ){
925#endif
926 /* Temporary ignore this signal:*/
927 /* if compiler fails here, try to change the definition of
928 mysighandler_t on the beginning of this file
929 (just define INTSIGHANDLER)*/
930 oldPIPE=signal(SIGPIPE,SIG_IGN);
931 ret=writexactly( externalChannelsListTop->fsend, buf, count);
932 signal(SIGPIPE,oldPIPE);
933#ifdef WITHMPI
934 }else{
935 /*Do not wait the master status: this would be too slow!*/
936 ret=0;
937 }
938#endif
939 return(ret);
940}/*writeBufToExtChannel*/
941/*
942 #] Ok functions:
943 #[ do_run_cmd :
944*/
945/*The function returns PID of the started command*/
946static FORM_INLINE pid_t do_run_cmd(
947 int *fdsend,
948 int *fdreceive,
949 int *gpid, /*returns group process ID*/
950 int ttymode,
951 /*
952 &8 - daemonizeing
953 &16 - setsid()*/
954 char *cmd,
955 char *argv[],
956 char *stderrname
957 )
958{
959int fdin[2]={-1,-1}, fdout[2]={-1,-1}, fdsig[2]={-1,-1};
960/*initialised by -1 for possible rollback at failure, see closepipe() above*/
961
962pid_t childpid,fatherchildpid = (pid_t)0;
963mysighandler_t oldPIPE=NULL;
964
965 if(
966 (pipe(fdsig)!=0)/*This pipe will be used by a child to tell the father if fail.*/
967 ||(pipe(fdin)!=0)
968 ||(pipe(fdout)!=0)
969 )goto fail_do_run_cmd;
970
971 if((childpid = fork()) == -1){
972 perror("fork");
973 goto fail_do_run_cmd;
974 }/*if((childpid = fork()) == -1)*/
975
976 if(childpid == 0){/*Child.*/
977 int fifo[3], top=0;
978 /*
979 To be thread safely we can't rely on ascendant order of opened
980 file descriptors. So we put each of descriptor we have to
981 preserve into the array fifo.
982 Note, in _this_ process there are no any threads but descriptors
983 were created in frame of the parent process which may have
984 multiple threads.
985 */
986 /*Mark descriptors which will NOT be closed:*/
987 pushDescriptor(fifo,top++,fdsig[1]);
988 pushDescriptor(fifo,top++,fdin[0]);
989 pushDescriptor(fifo,top++,fdout[1]);
990 /*Close all except stdin, stdout, stderr and placed into fifo:*/
991 closeAllDescriptors(3,fifo, top);
992
993 /*Now reopen stdin and stdout.*/
994 /*thread-safety is not a problem here since there are no any threads up to now:*/
995 if(
996 (close(0) == -1 )||/* Use fdin as stdin :*/
997 (dup(fdin[0]) == -1 )||
998 (close(1)==-1)||/* Use fdout as stdout:*/
999 (dup(fdout[1]) == -1 )
1000 )
1001 {/*Fail!*/
1002 /*Signal to parent:*/
1003 writepid(fdsig[1],(pid_t)-2);
1004 _exit(1);
1005 }
1006
1007 if(stderrname != NULL){
1008 if(
1009 (close(2) != 0 )||
1010 (open(stderrname,O_WRONLY)<0)
1011 )
1012 {/*Fail!*/
1013 writepid(fdsig[1],(pid_t)-2);
1014 _exit(1);
1015 }
1016 }/*if(stderrname != NULL)*/
1017
1018 if( ttymode & 16 )/* create a session and sets the process group ID */
1019 setsid();
1020
1021 /* */
1022 if(set_cloexec_flag (fdsig[1], 1)!=0){/*Error?*/
1023 /*Signal to parent:*/
1024 writepid(fdsig[1],(pid_t)-2);
1025 _exit(1);
1026 }/*if(set_cloexec_flag (fdsig[1], 1)!=0)*/
1027
1028 if( ttymode & 8 ){/*Daemonize*/
1029 int fdsig2[2];/*To check exec() success*/
1030 if(
1031 pipe(fdsig2)||
1032 (set_cloexec_flag (fdsig2[1], 1)!=0)
1033 )
1034 {/*Error?*/
1035 /*Signal to parent:*/
1036 writepid(fdsig[1],(pid_t)-2);
1037 _exit(1);
1038 }
1039 set_cloexec_flag (fdsig2[0], 1);
1040 switch(childpid=fork()){
1041 case 0:/*grandchild*/
1042 /*Execute external command:*/
1043 execvp(cmd, argv);
1044 /* Control can reach this point only on error!*/
1045 writepid(fdsig2[1],(pid_t)-2);
1046 break;
1047 case -1:
1048 /* Control can reach this point only on error!*/
1049 /*Inform the father about the failure*/
1050 writepid(fdsig[1],(pid_t)-2);
1051 _exit(1);/*The child, just exit, not return*/
1052 default:/*Son of his father*/
1053 close(fdsig2[1]);
1054 /*Ignore SIGPIPE (up to the end of the process):*/
1055 signal(SIGPIPE,SIG_IGN);
1056
1057 /*Wait on read() while the granchild close the pipe
1058 (on success) or send -2 (if exec() fails).*/
1059 /*There are two possibilities:
1060 -1 -- this is ok, the pipe was closed on exec,
1061 the program was successfully executed;
1062 -2 -- something is wrong, exec failed since the
1063 grandchild sends -2 after exec.
1064 */
1065 if( readpid(fdsig2[0]) != (pid_t)-1 )/*something is wrong*/
1066 writepid(fdsig[1],(pid_t)-1);
1067 else/*ok, send PID of the granchild to the father:*/
1068 writepid(fdsig[1],childpid);
1069 /*Die and free the life space for the grandchild:*/
1070 _exit(0);/*The child, just exit, not return*/
1071 }/*switch(childpid=fork())*/
1072 }else{/*if( ttymode & 8 )*/
1073 execvp(cmd, argv);
1074 /* Control can reach this point only on error!*/
1075 writepid(fdsig[1],(pid_t)-2);
1076 _exit(2);/*The child, just exit, not return*/
1077 }/*if( ttymode & 8 )...else*/
1078 }else{/* The (grand)father*/
1079 close(fdsig[1]);
1080 /*To prevent closing fdsig in rollback:*/
1081 fdsig[1]=-1;
1082 close(fdin[0]);
1083 close(fdout[1]);
1084 *fdsend = fdin[1];
1085 *fdreceive = fdout[0];
1086
1087 /*Get the process group ID.*/
1088 /*Avoid to use getpgid() which is non-standard.*/
1089 if( ttymode & 16)/*setsid() was invoked, the child is a group leader:*/
1090 *gpid=childpid;
1091 else/*the child belongs to the same process group as the this process:*/
1092 *gpid=getpgrp();/*if compiler fails here, try getpgrp(0) instead!*/
1093 /*
1094 Rationale: getpgrp conform to POSIX.1 while 4.3BSD provides a
1095 getpgrp() function that returns the process group ID for a
1096 specified process.
1097 */
1098
1099 /* Temporary ignore this signal:*/
1100 /* if compiler fails here, try to change the definition of
1101 mysighandler_t on the beginning of this file
1102 (just define INTSIGHANDLER)*/
1103 oldPIPE=signal(SIGPIPE,SIG_IGN);
1104
1105 if( ttymode & 8 ){/*Daemonize*/
1106 /*Read the grandchild PID from the son.*/
1107 fatherchildpid=childpid;
1108 if( (childpid=readpid(fdsig[0]))<0 ){
1109 /*Daemonization process fails for some reasons!*/
1110 childpid=fatherchildpid;/*for rollback*/
1111 goto fail_do_run_cmd;
1112 }
1113 }else{
1114 /*fdsig[1] should be closed on exec and this read operation
1115 must fail on success:*/
1116 if( readpid(fdsig[0])!= (pid_t)-1 )
1117 goto fail_do_run_cmd;
1118 }/*if( ttymode & 8 ) ... else*/
1119 }/*if(childpid == 0)...else*/
1120
1121 /*Here can be ONLY the father*/
1122 close(fdsig[0]);
1123 /*To prevent closing fdsig in rollback after goto fail_flnk_do_runcmd:*/
1124 fdsig[0]=-1;
1125
1126 if( ttymode & 8 ){/*Daemonize*/
1127 int i;
1128 /*Wait while the father of a grandchild dies:*/
1129 waitpid(fatherchildpid,&i,0);
1130 }
1131
1132 /*Restore the signal:*/
1133 signal(SIGPIPE,oldPIPE);
1134
1135 return(childpid);
1136
1137 fail_do_run_cmd:
1138 closepipe(&fdout);
1139 closepipe(&fdin);
1140 closepipe(&fdsig);
1141 return((pid_t)-1);
1142}/*do_run_cmd*/
1143/*
1144 #] do_run_cmd :
1145 #[ run_cmd :
1146*/
1147/*Starts the command cmd (directly, if shellpath is NULL, or in a subshell),
1148 swallowing its stdin and stdout;
1149 stderr will be re-directed to stderrname (if !=NULL). Returns PID of
1150 the started process. Stdin will be available as fdsend, and stdout
1151 will be available as fdreceive:*/
1152static FORM_INLINE pid_t run_cmd(char *cmd,
1153 int *fdsend,
1154 int *fdreceive,
1155 int *gpid,
1156 int daemonize,
1157 char *shellpath,
1158 char *stderrname )
1159{
1160char **argv;
1161pid_t thepid;
1162
1163 cmd=(char*)strDup1((UBYTE*)cmd, "run_cmd: cmd");/*detouch cmd*/
1164
1165
1166 /* Prepare arguments for execvp:*/
1167 if(shellpath != NULL){/*Run in a subshell.*/
1168 int nopt;
1169 /*Allocate space which is definitely enough:*/
1170 argv=Malloc1(StrLen((UBYTE*)shellpath)*sizeof(char*)+2,"run_cmd:argv");
1171 shellpath=(char*)strDup1((UBYTE*)shellpath, "run_cmd: shellpath");/*detouch shellpath*/
1172 /*Parse a shell (e.g., "/bin/sh -c"):*/
1173 nopt=parseline(argv, shellpath);
1174 /* and add the command as a shell argument:*/
1175 argv[nopt]=cmd;
1176 argv[nopt+1]=NULL;
1177 }else{/*Run the command directly:*/
1178 /*Allocate space which is definitely enough:*/
1179 argv=Malloc1(StrLen((UBYTE*)cmd)*sizeof(char*)+1,"run_cmd:argv");
1180 parseline(argv, cmd);
1181 }
1182
1183 thepid=do_run_cmd(
1184 fdsend,
1185 fdreceive,
1186 gpid,
1187 (daemonize)?(8|16):0,
1188 argv[0],
1189 argv,
1190 stderrname
1191 );
1192
1193 M_free(argv,"run_cmd:argv");
1194 if(shellpath)
1195 M_free(shellpath,"run_cmd:argv");
1196 M_free(cmd, "run_cmd: cmd");
1197
1198 return(thepid);
1199}/*run_cmd*/
1200/*
1201 #] run_cmd :
1202 #[ createExternalChannel :
1203*/
1204/*The structure to pass parameters to createExternalChannel
1205 and openExternalChannel in case of preset channel (instead of
1206 shellname):*/
1207typedef struct{
1208 int fdin;
1209 int fdout;
1210 pid_t theppid;
1211}ECINFOSTRUCT;
1212
1213/* Creates a new external channel starting the command cmd (if cmd !=NULL)
1214 or using informaion from (ECINFOSTRUCT *)shellname, if cmd ==NULL:*/
1215static FORM_INLINE void *createExternalChannel(
1216 EXTHANDLE *h,
1217 char *cmd, /*Command to run or NULL*/
1218 /*0 --neither setsid nor daemonize, !=0 -- full daemonization:*/
1219 int daemonize,
1220 char *shellname,/* The shell (like "/bin/sh -c") or NULL*/
1221 char *stderrname/*filename to redirect stderr or NULL*/
1222 )
1223{
1224 int fdreceive=0;
1225 int gpid = 0;
1226 ECINFOSTRUCT *psetInfo;
1227#ifdef WITHMPI
1228 char statusbuf[2]={'\0','\0'};/*'\0' if run_cmd retuns ok, '!' othervise.*/
1229#endif
1230 extHandlerInit(h);
1231
1232 h->pid=0;
1233
1234 if( cmd==NULL ){/*Instead of strting a new command, use preset channel:*/
1235 psetInfo=(ECINFOSTRUCT *)shellname;
1236 shellname=NULL;
1237 h->killSignal=0;
1238 h->daemonize=0;
1239 }
1240
1241 /*Create a channel:*/
1242#ifdef WITHMPI
1243 if ( PF.me == MASTER ){
1244#endif
1245 if(cmd!=NULL)
1246 h->pid=run_cmd (cmd, &(h->fsend),
1247 &fdreceive,&gpid,daemonize,shellname,stderrname);
1248 else{
1249 gpid=-psetInfo->theppid;
1250 h->pid=psetInfo->theppid;
1251 h->fsend=psetInfo->fdout;
1252 fdreceive=psetInfo->fdin;
1253 }
1254#ifdef WITHMPI
1255 if(h->pid<0)
1256 statusbuf[0]='!';/*Brodcast fail to slaves*/
1257 }
1258 /*else: Keep h->pid = 0 and h->fsend = 0 for slaves in parallel mode!*/
1259
1260 /*Master broadcasts status to slaves, slaves read it from the master:*/
1261 if( PF_BroadcastString((UBYTE *)statusbuf) ){/*Fail!*/
1262 h->pid=-1;
1263 }else if( statusbuf[0]=='!')/*Master fails*/
1264 h->pid=-1;
1265#endif
1266
1267 if(h->pid<0)goto createExternalChannelFails;
1268#ifdef WITHMPI
1269 if ( PF.me == MASTER ){
1270#endif
1271 h->gpid=gpid;
1272 /*Open stdout of a newly created program as FILE* :*/
1273 if( (h->frec=fdopen(fdreceive,"r")) == 0 )goto createExternalChannelFails;
1274
1275#ifdef WITHMPI
1276 }
1277#endif
1278 /*Initialize buffers:*/
1279 extHandlerAlloc(h,cmd,shellname,stderrname);
1280 return(h);
1281 /*Something is wrong?*/
1282 createExternalChannelFails:
1283
1284 destroyExternalChannel(h);
1285 return(NULL);
1286}/*createExternalChannel*/
1287
1288/*
1289 #] createExternalChannel :
1290 #[ openExternalChannel :
1291*/
1292int openExternalChannel(UBYTE *cmd, int daemonize, UBYTE *shellname, UBYTE *stderrname)
1293{
1294EXTHANDLE *h=externalChannelsListTop;
1295int i=0;
1296
1297 for(externalChannelsListTop=0;i<externalChannelsListFill;i++)
1298 if(externalChannelsList[i].cmd==0){
1299 externalChannelsListTop=externalChannelsList+i;
1300 break;
1301 }/*if(externalChannelsList[i].cmd!=0)*/
1302
1303 if(externalChannelsListTop==0){
1304 /*No free cells, create the new one:*/
1305 if(externalChannelsListFill == externalChannelsListStop){
1306 /*The list is full, increase and reallocate it:*/
1307 EXTHANDLE *newbuf=Malloc1(
1308 (externalChannelsListStop+=DELTA_EXT_LIST)*sizeof(EXTHANDLE),
1309 "External channel list");
1310 for(i=0;i<externalChannelsListFill;i++)
1311 extHandlerSwallowCopy(newbuf+i,externalChannelsList+i);
1312 if(externalChannelsList!=0)
1313 M_free(externalChannelsList,"External channel list");
1314 for(;i<externalChannelsListStop;i++)
1315 extHandlerInit(newbuf+i);
1316 externalChannelsList=newbuf;
1317 }/*if(externalChannelsListFill == externalChannelsListStop)*/
1318 externalChannelsListTop=externalChannelsList+externalChannelsListFill;
1319 externalChannelsListFill++;
1320 }/*if(externalChannelsListTop==0)*/
1321
1322 if(createExternalChannel(
1323 externalChannelsListTop,
1324 (char*) cmd,
1325 daemonize,
1326 (char*)shellname,
1327 (char*)stderrname )!=NULL){
1328 writeBufToExtChannel=&writeBufToExtChannelOk;
1329 getcFromExtChannel=&getcFromExtChannelOk;
1330 setTerminatorForExternalChannel=&setTerminatorForExternalChannelOk;
1331 setKillModeForExternalChannel=&setKillModeForExternalChannelOk;
1332 return(externalChannelsListTop-externalChannelsList+1);
1333 }
1334 /*else - failure!*/
1335 externalChannelsListTop=h;
1336 return(-1);
1337}/*openExternalChannel*/
1338/*
1339 #] openExternalChannel :
1340 #[ initPresetExternalChannels :
1341*/
1342/*Just simpe wrapper to invoke openExternalChannel()
1343 from initPresetExternalChannels():*/
1344static FORM_INLINE int openPresetExternalChannel(int fdin, int fdout, pid_t theppid)
1345{
1346ECINFOSTRUCT inf;
1347 inf.fdin=fdin; inf.fdout=fdout;inf.theppid=theppid;
1348 return( openExternalChannel(NULL,0,(UBYTE *)&inf,NULL) );
1349}/*openPresetExternalChannel*/
1350
1351#define PIDTXTSIZE 23
1352#define BOTHPIDSIZE 45
1353
1354#ifndef LONG_MAX
1355#define LONG_MAX 0x7FFFFFFFL
1356#endif
1357
1358/*There is a possibility to start FORM from another program providing
1359one (or more) communication channels. These channels will be
1360visible from a FORM program as ``pre-opened'' external channels existing
1361after FORM starts.
1362Before starting FORM, the parent application must create one or more
1363pairs of pipes The read-only descriptor of the first pipe in the pair
1364and the write-only descriptor of the second pipe must be passed to
1365FORM as an argument of a command line option -pipe in ASCII decimal
1366format. The argument of the option is a comma-separated list of pairs
1367r#,w# where r# is a read-only descriptor and w# is a write-only
1368descriptor. Alternatively, the environment variable FORM_PIPES can be
1369used.
1370 The following function expects as the first argument
1371this comma-separated list of the desctiptor pairs and tries to
1372initialize each of channel during thetimeout milliseconds:*/
1373
1374int initPresetExternalChannels(UBYTE *theline, int thetimeout)
1375{
1376 int i, nchannels = 0;
1377 int pidln; /*The length of FORM PID in pidtxt*/
1378 char pidtxt[PIDTXTSIZE], /*64/Log_2[10] = 19.3, this is enough for any integer*/
1379 chdescriptor[PIDTXTSIZE],
1380 bothpidtxt[BOTHPIDSIZE], /*"#,#\n\0"*/
1381 *c,*b = 0;
1382 int pip[2];
1383 pid_t ppid;
1384 if ( theline == NULL ) return(-1);
1385 /*Put into pidtxt PID\n:*/
1386 c = l2s((LONG)getpid(),pidtxt);
1387 *c++='\n';
1388 *c = '\0';
1389 pidln = c-pidtxt;
1390
1391 do {
1392 pip[0] = (int)str2i((char*)theline,&c,0xFFFF);
1393 if( ( pip[0] < 0 ) || ( *c != ',' ) ) goto presetFails;
1394
1395 theline = (UBYTE*)c + 1;
1396 pip[1] = (int)str2i((char*)theline,&c,0xFFFF);
1397 if ( (pip[1] < 0 ) || ( ( *c != ',' ) && ( *c != '\0' ) ) ) goto presetFails;
1398 theline = (UBYTE *)c + 1;
1399 /*Now we have two descriptors.
1400 According to the protocol, FORM must send to external channel
1401 it's PID with added '\n' and read back two comma-separaetd
1402 decimals with added '\n'. The first must be repeated FORM PID,
1403 the second must be the parent PID
1404 */
1405 if ( writeSome(pip[1],pidtxt,pidln,thetimeout) != pidln ) goto presetFails;
1406 i = readSome(pip[0],bothpidtxt,BOTHPIDSIZE,thetimeout);
1407 if( ( i < 4 ) /*at least 1,2\n*/
1408 || ( i == BOTHPIDSIZE ) /*No space for trailing '\0'*/
1409 ) goto presetFails;
1410 /*read the FORM PID:*/
1411 ppid = (pid_t)str2i(bothpidtxt,&b,getpid());
1412 if( ( *b != ',' ) || ( ppid != getpid() ) )goto presetFails;
1413 /*read the parent PID:*/
1414 /*The problem is that we do not know the the real type of pid_t.
1415 But long should be ehough. On obsolete systems (when LONG_MAX
1416 is not defined) we assume pid_t is 32-bit integer.
1417 This can lead to problem with portability: */
1418 ppid = (pid_t)str2i(b+1,&b,LONG_MAX);
1419 if ( (*b != '\n') || (ppid<2) ) goto presetFails;
1420 i = openPresetExternalChannel(pip[0],pip[1],ppid);
1421 if ( i < 0 ) goto presetFails;
1422 nchannels++;
1423 /*Now use bothpidtxt as a buffer for preprovar, the space is enough:*/
1424 /*"PIPE#_" where # is ne order number of the channel:*/
1425 b = l2s(nchannels,addStr(bothpidtxt,"PIPE"));
1426 *b = '_';
1427 b[1] = '\0';
1428 *l2s(i,chdescriptor) = '\0';
1429 PutPreVar((UBYTE*)bothpidtxt,(UBYTE*)chdescriptor,0,0);
1430 } while ( *c != '\0' );
1431 /*Export proprovar "PIPES_":*/
1432 *l2s(nchannels,chdescriptor)='\0';
1433 PutPreVar((UBYTE*)"PIPES_",(UBYTE*)chdescriptor,0,0);
1434
1435 /*success:*/
1436 return (nchannels);
1437
1438presetFails:
1439 /*Here we assume the descriptors the beginning of the list!*/
1440 for(i=0; i<nchannels; i++)
1441 destroyExternalChannel(externalChannelsList+i);
1442 return(-1);
1443} /*initPresetExternalChannels*/
1444/*
1445 #] initPresetExternalChannels :
1446 #[ selectExternalChannel :
1447*/
1448/*
1449Accepts the valid external channel descriptor (returned by
1450openExternalChannel) and returns the descriptor of a previous current
1451channel (0, if there was no current channel, or -1, if the external
1452channel descriptor is invalid). If n == 0, the function undefine the
1453current external channel:
1454*/
1455int selectExternalChannel(int n)
1456{
1457int ret=0;
1458 if(externalChannelsListTop!=0)
1459 ret=externalChannelsListTop-externalChannelsList+1;
1460
1461 if(--n<0){
1462 if(n!=-1)
1463 return(-1);
1464 externalChannelsListTop=0;
1465 writeBufToExtChannel=&writeBufToExtChannelFailure;
1466 getcFromExtChannel=&getcFromExtChannelFailure;
1467 setTerminatorForExternalChannel=&setTerminatorForExternalChannelFailure;
1468 setKillModeForExternalChannel=&setKillModeForExternalChannelFailure;
1469 return(ret);
1470 }
1471 if(
1472 (n>=externalChannelsListFill)||
1473 (externalChannelsList[n].cmd==0)
1474 )
1475 return(-1);
1476
1477 externalChannelsListTop=externalChannelsList+n;
1478 writeBufToExtChannel=&writeBufToExtChannelOk;
1479 getcFromExtChannel=&getcFromExtChannelOk;
1480 setTerminatorForExternalChannel=&setTerminatorForExternalChannelOk;
1481 setKillModeForExternalChannel=&setKillModeForExternalChannelOk;
1482 return(ret);
1483}/*selectExternalChannel*/
1484/*
1485 #] selectExternalChannel :
1486 #[ closeExternalChannel :
1487*/
1488
1489/*
1490Destroys the opened external channel with the descriptor n. It returns
14910 in success, or -1 on failure. If the corresponding external channel
1492was the current one, the current channel becomes undefined. If n==0,
1493the function closes the current external channel.
1494*/
1495int closeExternalChannel(int n)
1496{
1497 if(n==0)
1498 n=externalChannelsListTop-externalChannelsList;
1499 else
1500 n--;/*Count from 0*/
1501
1502 if(
1503 (n<0)||
1504 (n>=externalChannelsListFill)||
1505 (externalChannelsList[n].cmd==0)
1506 )/*No shuch a channel*/
1507 return(-1);
1508
1509 destroyExternalChannel(externalChannelsList+n);
1510 /*If the current external channel was destroyed, undefine current channel:*/
1511 if(externalChannelsListTop==externalChannelsList+n){
1512 externalChannelsListTop=NULL;
1513 writeBufToExtChannel=&writeBufToExtChannelFailure;
1514 getcFromExtChannel=&getcFromExtChannelFailure;
1515 setTerminatorForExternalChannel=&setTerminatorForExternalChannelFailure;
1516 setKillModeForExternalChannel=&setKillModeForExternalChannelFailure;
1517 }/*if(externalChannelsListTop==externalChannelsList+n)*/
1518 return(0);
1519}/*closeExternalChannel*/
1520/*
1521 #] closeExternalChannel :
1522 #[ closeAllExternalChannels :
1523*/
1524void closeAllExternalChannels()
1525{
1526int i;
1527 for(i=0; i<externalChannelsListFill; i++)
1528 destroyExternalChannel(externalChannelsList+i);
1529 externalChannelsListFill=externalChannelsListStop=0;
1530 externalChannelsListTop=NULL;
1531
1532 writeBufToExtChannel=&writeBufToExtChannelFailure;
1533 getcFromExtChannel=&getcFromExtChannelFailure;
1534 setTerminatorForExternalChannel=&setTerminatorForExternalChannelFailure;
1535 setKillModeForExternalChannel=&setKillModeForExternalChannelFailure;
1536
1537 if(externalChannelsList!=NULL){
1538 M_free(externalChannelsList,"External channel list");
1539 externalChannelsList=NULL;
1540 }
1541}/*closeAllExternalChannels*/
1542/*
1543 #] closeAllExternalChannels :
1544 #[ getExternalChannelPid :
1545*/
1546#ifdef SELFTEST
1547pid_t getExternalChannelPid()
1548{
1549 if(externalChannelsListTop!=0)
1550 return(externalChannelsListTop ->pid);
1551 return(-1);
1552}/*getExternalChannelPid*/
1553#endif
1554/*
1555 #] getExternalChannelPid :
1556 #[ getCurrentExternalChannel :
1557*/
1558
1559int getCurrentExternalChannel()
1560{
1561
1562 if ( externalChannelsListTop != 0 )
1563 return(externalChannelsListTop-externalChannelsList+1);
1564 return(0);
1565}/*getCurrentExternalChannel*/
1566/*
1567 #] getCurrentExternalChannel :
1568 #[ Selftest main :
1569*/
1570
1571#ifdef SELFTEST
1572
1573/*
1574 This is the example of how all these public functions may be used:
1575*/
1576
1577char buf[1024];
1578char buf2[1024];
1579
1580void help(void)
1581{
1582 printf("String started with a special letter is a command\n");
1583 printf("Known commands are:\n");
1584 printf("H or ? -- this help\n");
1585 printf("Nn<command> -- start a new command\n");
1586 printf("S<command> -- start a new command in a subshell,daemon,stderr>/dev/null\n");
1587 printf("C# -- destroy channel #\n");
1588 printf("R# -- set a new cahhel(number#) as a current one\n");
1589 printf("K#1 #2 -- set signal for kill and kill mode (0 or !=0)\n");
1590 printf(" ^d to quit\n");
1591}/*help*/
1592
1593int main (void)
1594{
1595 int i, j, k,last;
1596 long long sum = 0;
1597
1598 /*openExternalChannel(UBYTE *cmd, int daemonize, UBYTE *shellname, UBYTE *stderrname)*/
1599
1600 help();
1601
1602 printf("Initial channel:%d\n",last=openExternalChannel((UBYTE*)"cat",0,NULL,NULL));
1603
1604 if( ( i = setTerminatorForExternalChannel("qu") ) != 0 ) return 1;
1605 printf("Terminaror is 'qu'\n");
1606
1607 while ( fgets(buf, 1024, stdin) != NULL ) {
1608 if ( *buf == 'N' ) {
1609 printf("New channel:%d\n",j=openExternalChannel((UBYTE*)buf+1,0,NULL,NULL));
1610 continue;
1611 }
1612 else if ( *buf == 'C' ) {
1613 int n;
1614 sscanf(buf+1,"%d",&n);
1615 printf("Destroy last channel:%d\n",closeExternalChannel(n));
1616 continue;
1617 }
1618 else if ( *buf == 'R' ) {
1619 int n = 0;
1620 sscanf(buf+1,"%d",&n);
1621 printf("Reopen channel %d:%d\n",n,selectExternalChannel(n));
1622 continue;
1623 }else if( *buf == 'K' ) {
1624 int n=0,g = 0;
1625 sscanf(buf+1,"%d %d",&n,&g);
1626 printf("setKillMode %d\n",setKillModeForExternalChannel(n,g));
1627 continue;
1628 }else if( *buf == 'S' ) {
1629 printf("New channel with sh&d&stderr:%d\n",
1630 j=openExternalChannel((UBYTE*)buf+1,1,(UBYTE*)"/bin/sh -c",(UBYTE*)"/dev/null"));
1631 continue;
1632 }
1633 else if( ( *buf == 'H' )|| ( *buf == '?' ) ){
1634 help();
1635 continue;
1636 }
1637
1638 writeBufToExtChannel(buf,k=StrLen(buf));
1639 sum += k;
1640 for ( j = 0; ( i = getcFromExtChannel() ) != '\n'; j++) {
1641 if ( i == EOF ) {
1642 printf("EOF!\n");
1643 break;
1644 }
1645 buf2[j] = i;
1646 }
1647 buf2[j] = '\0';
1648 printf("I got:'%s'; pid=%d\n",buf2,getExternalChannelPid());
1649 }
1650 printf("Total:%lld bytes\n",sum);
1651 closeAllExternalChannels();
1652 return 0;
1653}
1654#endif /*ifdef SELFTEST*/
1655/*
1656 #] Selftest main :
1657*/
1658
1659#endif /*ifndef WITHEXTERNALCHANNEL ... else*/
int PutPreVar(UBYTE *, UBYTE *, UBYTE *, int)
Definition: pre.c:642
int PF_BroadcastString(UBYTE *str)
Definition: parallel.c:2152