• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

tueda / form / 15241916852

25 May 2025 08:59PM UTC coverage: 47.908% (-2.8%) from 50.743%
15241916852

push

github

tueda
ci: build arm64-windows binaries

39009 of 81425 relevant lines covered (47.91%)

1079780.1 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

3.5
/sources/extcmd.c
1
/** @file extcmd.c
2
 * 
3
 *  The system that takes care of communication with external programs.
4
 */
5
/* #[ License : */
6
/*
7
 *   Copyright (C) 1984-2023 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 copied from the DIANA project, which was
37
        written by M. Tentyukov and published under the GPL version 2 as
38
        published by the Free Software Foundation.
39

40
        This file is completely re-written by M.Tentyukov in May 2006.
41
        Since the interface was changed, the public function were changed,
42
        also. A new public functions were added: initPresetExternalChannels()
43
        (see comments just before this function in the present file) and
44
        setKillModeForExternalChannel (a pointer, not a function).
45

46
        If a macro WITHEXTERNALCHANNEL is not defined, all public functions
47
        are stubs returning failure.
48

49
        The idea is to start an external command  swallowing
50
        its stdin and stdout. This can be done by means of the function 
51
        int openExternalChannel(cmd,daemonize,shellname,stderrname), where
52
        cmd is a command to run,
53
        daemonize: if !=0 then start the command in the "daemon" mode,
54
        shellname: if !=NULL, execute the command in a subshell,
55
        stderrname: if != NULL, redirect stderr of the command to this file.
56
        The function returns some small positive integer number (the
57
        descriptor of a newly created external channel), or -1 on failure.
58

59
        After the command is started, it becomes a _current_ opened external
60
        channel. The buffer can be sent to its stdin by a function
61
        int writeBufToExtChannel(buf, n)
62
        (here buf is a pointer to the buffer, n is the length in bytes; the
63
        function returns 0 in success, or -1 on failure),
64
        or one character can be read from its stdout by
65
        means of the function 
66
        int getcFromExtChannel().
67

68
        The latter returns the
69
        character casted to integer, or something <0. This can be -2 (if there
70
        is no current external channel) or EOF, if the external program closes
71
        its stdout, or if the external program outputs a string coinciding
72
        with a _terminator_.
73

74
        By default, the terminator if an empty line. For the current external
75
        channel it can be set by means of the function
76
        int setTerminatorForExternalChannel(newterminator).
77
        The function returns 0 in success, or !0 if something is wrong (no
78
        current channel, too long terminator).
79

80
        After getcFromExtChannel() returns EOF, the current channel becomes
81
        undefined. Any further attempts to read information by
82
        getcFromExtChannel() result in -2. To set (re-set) a current channel,
83
        the function 
84
        int selectExternalChannel(n) 
85
        can be used. This function accepts the valid external channel
86
        descriptor (returned by openExternalChannel) and returns the
87
        descriptor of a previous current channel (0, if there was no current
88
        channel, or -1, if the external channel descriptor is invalid).
89
        If n == 0, the function undefine the current external channel.
90

91
        The function 
92
        int closeExternalChannel(n) 
93
        destroys the opened external channel with the descriptor n. It returns
94
        0 in success, or -1 on failure. If the corresponding external channel
95
        was the current one, the current channel becomes undefined. If n==0, 
96
        the function closes the current external channel.
97

98
        The function 
99
        int getCurrentExternalChannel(void)
100
        returns the descriptor if the current external channel, or 0 , if
101
        there is no current external channel.
102

103
        The function 
104
        void closeAllExternalChannels(void)
105

106
        destroys all opened external channels.
107

108
        List of all public functions:
109
        int openExternalChannel(UBYTE *cmd,int daemonize,UBYTE *shellname, UBYTE * stderrname);
110
        int initPresetExternalChannels(UBYTE *theline, int thetimeout);
111
        int setTerminatorForExternalChannel(char *newterminator);
112
        int setKillModeForExternalChannel(int signum, int sentToWholeGroup);
113
        int closeExternalChannel(int n);
114
        int selectExternalChannel(int n);
115
        int writeBufToExtChannel(char *buf,int n);
116
        int getcFromExtChannel(void);
117
        int getCurrentExternalChannel(void);
118
        void closeAllExternalChannels(void);
119

120
        ATTENTION!
121

122
        Four of them:
123
        1 setTerminatorForExternalChannel
124
        2 setKillModeForExternalChannel
125
        3 writeBufToExtChannel
126
        4 getcFromExtChannel
127

128
        are NOT functions, but variables (pointers) of a corrsponding type.
129
        They are initialised by proper values to avoid repeated error checking.
130

131
        All public functions are independent of realization hidden in this module.
132
        All other functions may have a returned type/parameters type local w.r.t. 
133
        this module; they are not declared outside of this file.
134

135
          #] Documentation : 
136
          #[ Selftest initializing:
137
*/
138

139
/*
140
Uncomment to get a self-consistent program:
141
#define SELFTEST 1
142
*/
143

144

145
#ifdef SELFTEST
146
#define WITHEXTERNALCHANNEL 1
147
#ifdef _MSC_VER
148
#define FORM_INLINE __inline
149
#else
150
#define FORM_INLINE inline
151
#endif
152
/*
153
        from declare.h:
154
*/
155
#define VOID void
156

157
/* 
158
        From form3.h:
159
*/
160
typedef unsigned char UBYTE;
161

162
/*The following variables should be defined in variable.h:*/
163
extern int (*writeBufToExtChannel)(char *buffer, size_t n);
164
extern int (*getcFromExtChannel)();
165
extern int (*setTerminatorForExternalChannel)(char *buffer);
166
extern int (*setKillModeForExternalChannel)(int signum, int sentToWholeGroup);
167

168
#else /*ifdef SELFTEST*/
169
#include "form3.h"
170
#endif /*ifdef SELFTEST ... else*/
171
/*
172
pid_t  getExternalChannelPid(VOID);
173
*/
174
/*
175
          #] Selftest initializing: 
176
          #[ Includes :
177
*/
178
#ifdef WITHEXTERNALCHANNEL
179
#include <stdio.h>
180
#ifndef _MSC_VER
181
#include <unistd.h>
182
#endif
183
#include <fcntl.h>
184
#include <sys/types.h>
185
#ifndef _MSC_VER
186
#include <sys/time.h>
187
#include <sys/wait.h>
188
#endif
189
#include <errno.h>
190
#include <signal.h>
191
#include <limits.h>
192
/*
193
          #] Includes : 
194
          #[ FailureFunctions:
195
*/
196

197
/*Non-initialized variant of public functions:*/
198
int writeBufToExtChannelFailure(char *buf, size_t count)
×
199
{
200
        DUMMYUSE(buf); DUMMYUSE(count);
×
201
        return(-1);
×
202
}/*writeBufToExtChannelFailure*/
203

204
int setTerminatorForExternalChannelFailure(char *newTerminator)
×
205
{
206
        DUMMYUSE(newTerminator);
×
207
        return(-1);
×
208
}/*setTerminatorForExternalChannelFailure*/
209

210
int setKillModeForExternalChannelFailure(int signum, int sentToWholeGroup)
×
211
{
212
        DUMMYUSE(signum); DUMMYUSE(sentToWholeGroup);
×
213
        return(-1);
×
214
}/*setKillModeForExternalChannelFailure*/
215

216
int getcFromExtChannelFailure(VOID)
×
217
{
218
        return(-2);
×
219
}/*getcFromExtChannelFailure*/
220

221
int (*writeBufToExtChannel)(char *buffer, size_t n) = &writeBufToExtChannelFailure;
222
int (*setTerminatorForExternalChannel)(char *buffer) =
223
        &setTerminatorForExternalChannelFailure;
224
int (*setKillModeForExternalChannel)(int signum, int sentToWholeGroup) =
225
        &setKillModeForExternalChannelFailure;
226
int (*getcFromExtChannel)(VOID) = &getcFromExtChannelFailure;
227
#endif
228
/*
229
          #] FailureFunctions: 
230
          #[ Stubs :
231
*/
232
#ifndef WITHEXTERNALCHANNEL
233
/*Stubs for public functions:*/
234
int openExternalChannel(UBYTE *cmd, int daemonize, UBYTE *shellname, UBYTE *stderrname)
235
{ DUMMYUSE(cmd); DUMMYUSE(daemonize); DUMMYUSE(shellname); DUMMYUSE(stderrname); return(-1); };
236
int initPresetExternalChannels(UBYTE *theline, int thetimeout) { DUMMYUSE(theline); DUMMYUSE(thetimeout); return(-1); };
237
int closeExternalChannel(int n) { DUMMYUSE(n); return(-1); };
238
int selectExternalChannel(int n) { DUMMYUSE(n); return(-1); };
239
int getCurrentExternalChannel(VOID) { return(0); };
240
void closeAllExternalChannels(VOID) {};
241
#else /*ifndef WITHEXTERNALCHANNEL*/
242
/*
243
          #] Stubs : 
244
          #[ Local types :
245
*/
246
/*First argument for the function signal:*/
247
#ifndef INTSIGHANDLER
248
typedef void (*mysighandler_t)(int);
249
#else
250
/* Sometimes, this nonsense may occurs:*/
251
/*typedef int (*mysighandler_t)(int);*/
252
#endif
253

254
/*Input IO buffer size increment -- each time the buffer 
255
is expired it will be increased by this value (in bytes):*/
256
#define DELTA_EXT_BUF 128
257

258
/*Re-allocatable array containing External Channel 
259
handlers increased each time by this value:*/
260
#define DELTA_EXT_LIST 8
261

262
/*How many times I/O routines may attempt to continue their work 
263
in some failures:*/
264
#define MAX_FAILS_IO 2
265

266
/*The external channel handler structure:*/
267
typedef struct ExternalChannel {
268
        pid_t pid;       /*PID of the external process*/
269
        pid_t gpid;       /*process  group ID of the external process.
270
                                                          If <=0, not used, if >0, the kill signals
271
                                                          is sent to the whole group */
272
        FILE *frec;      /*stdout of the external process*/
273
        char *INbuf;     /*External channel buffer*/
274
        char *IBfill;    /*Position in INbuf from which the next input character will be read*/
275
        char *IBfull;    /*End of read INbuf*/
276
        char *IBstop;    /*end of allocated space for INbuf*/
277
        char *terminator;/* Terminator - when extern. program outputs ONLY this string, 
278
                                                          it is assumed that the answer is ready, and getcFromExtChannel
279
                                                          returns EOF. Should not be longer then the minimal buffer!*/
280
        /*Info fields, not changable after creating a channel:*/
281
        char *cmd;       /*the command*/
282
        char *shellname;
283
        char *stderrname;/*filename to redirect stderr, or NULL*/
284
        int fsend;       /*stdin of the external process*/
285
        int killSignal; /*signal to kill*/
286
        int daemonize;/*0 --neither setsid nor daemonize, !=0 -- full daemonization*/
287
        PADPOINTER(0,3,0,0);
288
} EXTHANDLE;
289

290

291
static EXTHANDLE *externalChannelsList=0;
292
/*Here integers are better than pointers: */
293
static int externalChannelsListStop=0;
294
static int externalChannelsListFill=0;
295

296
/*"current" external channel:*/
297
static EXTHANDLE *externalChannelsListTop=0;
298
/*
299
          #] Local types : 
300
          #[ Selftest functions :
301
*/
302
#ifdef SELFTEST
303

304
/*For malloc prototype:*/
305
#include <stdlib.h>
306

307
/*StrLen, Malloc1, M_free and strDup1 are defined in tools.c -- here only emulation:*/
308
int StrLen(char *pattern)
309
{
310
register  char *p=(char*)pattern;
311
         while(*p)p++;
312
         return((int) ((p-(char*)pattern)) );
313
}/*StrLen*/
314
void *Malloc1(int l, char *c)
315
{
316
        return(malloc(l));
317
}
318
void M_free(void *p,char *c)
319
{
320
        return(free(p));
321
}
322

323
char *strDup1(UBYTE *instring, char *ifwrong)
324
{
325
        UBYTE *s = instring, *to;
326
        while ( *s ) s++;
327
        to = s = (UBYTE *)Malloc1((s-instring)+1,ifwrong);
328
        while ( *instring ) *to++ = *instring++;
329
        *to = 0;
330
        return(s);
331
}
332

333
/*PutPreVar from pre.c -- just ths stub:*/
334
int PutPreVar(UBYTE *a,UBYTE *b,UBYTE *c,int i)
335
{
336
        return(0);
337
}
338

339
#endif
340
/*
341
          #] Selftest functions : 
342
          #[ Local functions :
343
*/
344

345
/*Initialize one cell of handler:*/
346
static FORM_INLINE VOID extHandlerInit(EXTHANDLE *h)
×
347
{
348
        h->pid=-1;
×
349
        h->gpid=-1;
×
350
        h->fsend=0;
×
351
        h->killSignal=SIGKILL;
×
352
        h->daemonize=1;
×
353
        h->frec=NULL;
×
354
        h->INbuf=h->IBfill=h->IBfull=h->IBstop=
×
355
        h->terminator=h->cmd=h->shellname=h->stderrname=NULL;
×
356
}/*extHandlerInit*/
357

358
/* Copies each field of handler:*/
359
static FORM_INLINE VOID extHandlerSwallowCopy(EXTHANDLE *to, EXTHANDLE *from)
×
360
{
361
        to->pid=from->pid;
×
362
        to->gpid=from->gpid;
×
363
        to->fsend=from->fsend;
×
364
        to->killSignal=from->killSignal;
×
365
        to->daemonize=from->daemonize;
×
366
        to->frec=from->frec;
×
367
        to->INbuf=from->INbuf;
×
368
        to->IBfill=from->IBfill;
×
369
        to->IBfull=from->IBfull;
×
370
        to->IBstop=from->IBstop;
×
371
        to->terminator=from->terminator;
×
372
        to->cmd=from->cmd;
×
373
        to->shellname=from->shellname;
×
374
        to->stderrname=from->stderrname;
×
375
}/*extHandlerSwallow*/
×
376

377
/*Allocates memory for fields of handler which have no fixed
378
 storage size and initializes some fields:*/
379
static FORM_INLINE VOID 
380
extHandlerAlloc(EXTHANDLE *h, char *cmd, char *shellname, char *stderrname)
×
381
{
382
        h->IBfill=h->IBfull=h->INbuf=
×
383
                                Malloc1(DELTA_EXT_BUF,"External channel buffer");
×
384
        h->IBstop=h->INbuf+DELTA_EXT_BUF;
×
385
        /*Initialize a terminator:*/
386
        *(h->terminator=Malloc1(DELTA_EXT_BUF,"External channel terminator"))='\n';
×
387
        (h->terminator)[1]='\0';/*By default the terminator is '\n'*/
×
388
        /*Deep copy all strings:*/
389
        if(cmd!=NULL)
×
390
                h->cmd=(char *)strDup1((UBYTE *)cmd,"External channel command");
×
391
        else/*cmd cannot be NULL! If this is NULL then force it to be something special*/
392
                h->cmd=(char *)strDup1((UBYTE *)"/","External channel command");
×
393
        if(shellname!=NULL)
×
394
                h->shellname=
×
395
                         (char *)strDup1((UBYTE *)shellname,"External channel shell name");
×
396
        if(stderrname!=NULL)
×
397
                h->stderrname=
×
398
                         (char *)strDup1((UBYTE *)stderrname,"External channel stderr name");
×
399
}/*extHandlerAlloc*/
×
400

401
/*Disallocates dynamically allocated fields of a handler:*/
402
static FORM_INLINE VOID extHandlerFree(EXTHANDLE *h)
×
403
{
404
        if(h->stderrname) M_free(h->stderrname,"External channel stderr name");
×
405
        if(h->shellname) M_free(h->shellname,"External channel shell name");
×
406
        if(h->cmd) M_free(h->cmd,"External channel command");
×
407
        if(h->terminator)M_free(h->terminator,"External channel terminator");
×
408
        if(h->INbuf)M_free(h->INbuf,"External channel buffer");
×
409
        extHandlerInit(h);
×
410
}/*extHandlerFree*/
×
411
/* Closes all descriptors, kills the external process, frees all internal fields,
412
BUT does NOT free the main container:*/
413
static VOID destroyExternalChannel(EXTHANDLE *h)
×
414
{
415
        /*Note, this function works in parallel mode correctly, see comments below.*/
416

417
        /*Note, for slaves in a parallel mode h->pid == 0:*/
418
        if( (h->pid > 0) && (h->killSignal > 0) ){
×
419
                int chstatus;
×
420
                if( h->gpid > 0)
×
421
                        chstatus=kill(-h->gpid,h->killSignal);
×
422
                else
423
                        chstatus=kill(h->pid,h->killSignal);
×
424
                if(chstatus==0)
×
425
                /*If the process will not be killed by this signal, FORM hangs up here!:*/
426
                        waitpid(h->pid, &chstatus, 0);
×
427
        }/*if( (h->pid > 0) && (h->killSignal > 0) )*/
428

429
        /*Note, for slaves in a parallel mode h->frec == h->fsend == 0:*/
430
        if(h->frec) fclose(h->frec);
×
431
        if( h->fsend > 0) close(h->fsend);
×
432

433
        extHandlerFree(h);
×
434
        /*Does not do "free(h)"!*/
435
}/*destroyExternalChannel*/
×
436

437
/*Wrapper to the read() syscall, to handle possible interrupts by unblocked signals:*/
438
static FORM_INLINE ssize_t read2b(int fd, char *buf, size_t count)
×
439
{
440
ssize_t res;
×
441

442
        if( (res=read(fd,buf,count)) <1 )/*EOF or read is interrupted by a signal?:*/
×
443
                 while( (errno == EINTR)&&(res <1) )
×
444
                         /*The call was interrupted by  a  signal  before  any data was read, try again:*/
445
                         res=read(fd,buf,count);
×
446
        return (res);
×
447
}/*read2b*/
448

449
/*Wrapper to the write() syscall, to handle possible interrupts by unblocked signals:*/
450
static FORM_INLINE ssize_t writeFromb(int fd, char *buf, size_t count)
×
451
{
452
ssize_t res;
×
453
        if( (res=write(fd,buf,count)) <1 )/*Is write interrupted by a signal?:*/
×
454
                 while( (errno == EINTR)&&(res <1) )
×
455
                         /*The call was interrupted by a signal before any data was written, try again:*/
456
                         res=write(fd,buf,count);
×
457
        return (res);
×
458
}/*writeFromb*/
459

460
/* Read one (binary) PID from the file descriptor fd:*/
461
static FORM_INLINE pid_t readpid(int fd)
×
462
{
463
pid_t tmp;
×
464
        if(read2b(fd,(char*)&tmp,sizeof(pid_t))!=sizeof(pid_t))
×
465
          return (pid_t)-1;
466
        return tmp;
×
467
}/*readpid*/
468

469
/* Writeone (binary) PID to the file descriptor fd:*/
470
static FORM_INLINE pid_t writepid(int fd, pid_t thepid)
×
471
{
472
        if(writeFromb(fd,(char*)&thepid,sizeof(pid_t))!=sizeof(pid_t))
×
473
          return (pid_t)-1;
×
474
        return (pid_t)0;
475
}/*readpid*/
476

477
/*Wrtites exactly count bytes from the  buffer buf into the descriptor fd, independently on
478
  nonblocked signals and the MPU/buffer hits. Returns 0 or -1:
479
*/
480
static FORM_INLINE int writexactly(int fd, char *buf, size_t count)
×
481
{
482
ssize_t i;
×
483
int j=0,n=0;
×
484

485
        for(;;){
×
486
                if(  (i=writeFromb(fd, buf+j, count-j)) < 0 ) return(-1);
×
487
                j+=i;
×
488
                if ( ((size_t)j) == count ) break;
×
489
                if(i==0)n++;
×
490
                else n=0;
491
                if(n>MAX_FAILS_IO)return (-1);
×
492
        }/*for(;;)*/
493
        return (0);
494
}/*writexactly*/
495

496
/* Set the FD_CLOEXEC  flag of desc if value is nonzero,
497
 or clear the flag if value is 0.
498
 Return 0 on success, or -1 on error with errno  set. */
499
static int set_cloexec_flag(int desc, int value)
×
500
{
501
int oldflags = fcntl (desc, F_GETFD, 0);
×
502
        /* If reading the flags failed, return error indication now.*/
503
        if (oldflags < 0)
×
504
                return (oldflags);
505
        /* Set just the flag we want to set. */
506
        if (value != 0)
×
507
                oldflags |= FD_CLOEXEC;
×
508
        else
509
          oldflags &= ~FD_CLOEXEC;
×
510
        /* Store modified flag word in the descriptor. */
511
        return (fcntl(desc, F_SETFD, oldflags));
×
512
}/*set_cloexec_flag*/
513

514
/* Adds the integer fd to the array fifo of length top+1 so that 
515
the array is ascendantly ordered. It is supposed that all 0 -- top-1
516
elements in the array are already ordered:*/
517
static VOID pushDescriptor(int *fifo, int top, int fd)
×
518
{
519
        if ( top == 0 ) {
×
520
                fifo[top] = fd;
×
521
        } else {
522
                int ins=top-1;
×
523
                if( fifo[ins]<=fd )
×
524
                        fifo[top]=fd;
×
525
                else{
526
                        /*Find the position:*/
527
                        while( (ins>=0)&&(fifo[ins]>fd) )ins--;
×
528
                        /*Move all elements starting from the position to the right:*/
529
                        for(ins++;top>ins; top--)
×
530
                                fifo[top]=fifo[top-1];
×
531
                        /*Put the element:*/
532
                        fifo[ins]=fd;
×
533
                }
534
        }
535
}/*pushDescriptor*/
×
536

537
/*Close all descriptors greater or equal than startFrom except those
538
  listed in the ascendantly ordered array usedFd of length top:*/
539
static FORM_INLINE VOID closeAllDescriptors(int startFrom, int *usedFd, int top)
×
540
{
541
int n,maxfd;
×
542
        for(n=0;n<top; n++){
×
543
                maxfd=usedFd[n];
×
544
                for(;startFrom<maxfd;startFrom++)/*Close all less than maxfd*/
×
545
                        close(startFrom);
×
546
                startFrom++;/*skip maxfd*/
×
547
        }/*for(;startFrom<maxfd;startFrom++)*/
548
        /*Close all the rest:*/
549
        maxfd=sysconf(_SC_OPEN_MAX);
×
550
        for(;startFrom<maxfd;startFrom++)
×
551
                close(startFrom);
×
552
}/*closeAllDescriptors*/
×
553

554
typedef int L_APIPE[2];
555
/*Closes both pipe descriptors if not -1:*/
556
static VOID closepipe(L_APIPE *thepipe)
×
557
{
558
  if( (*thepipe)[0] != -1) close ((*thepipe)[0]);
×
559
  if( (*thepipe)[1] != -1) close ((*thepipe)[1]);
×
560
}/*closepipe*/
×
561

562
/*Parses the cmd line like "sh -c myprg", passes each option to the
563
  corresponding element of argv, ends agrv by NULL. Returns the 
564
  number of stored argv elements, or -1 if fails:*/
565
static FORM_INLINE int parseline(char **argv, char *cmd)
×
566
{
567
int n=0;
×
568
        while(*cmd != '\0'){
×
569
                for(; (*cmd <= ' ') && (*cmd != '\0') ;cmd++);
×
570
                if(*cmd != '\0'){
×
571
                        argv[n]=cmd;
×
572
                        while(*++cmd > ' ');
×
573
                        if(*cmd != '\0')
×
574
                                *cmd++ = '\0';
×
575
                        n++;
×
576
                }/*if(*cmd != '\0')*/
577
        }/*while(*cmd != '\0')*/
578
        argv[n]=NULL;
×
579
        if(n==0)return -1;
×
580
        return n;
581
}/*parseline*/
582

583
/*Reads positive decimal number (not bigger than maxnum)
584
  from the string and returns it;
585
  the pointer *b is set to the next non-converted character:*/
586
static LONG str2i(char *str, char **b, LONG maxnum)
×
587
{
588
        LONG n=0;
×
589
        /*Eat trailing spaces:*/
590
        while(*str<=' ')if(*str++ == '\0')return(-1);
×
591
        (*b)=str;
×
592
        while (*str>='0'&&*str<='9')
×
593
                if( (n=10*n + *str++ - '0')>maxnum )
×
594
                        return(-1);
595
        if((*b)==str)/*No single number!*/
×
596
                return(-1);
597
        (*b)=str;
×
598
        return(n);
×
599
}
600

601
/*Converts long integer to a decimal representation.
602
  For portability reasons we cannot use LongCopy from tools.c
603
  since theoretically LONG may be smaller than pid_t:*/
604
static char *l2s(LONG x, char *to)
×
605
{
606
        char *s;
×
607
        int i = 0, j;
×
608
        s = to;
×
609
        do { *s++ = (x % 10)+'0'; i++; } while ( ( x /= 10 ) != 0 );
×
610
        *s-- = '\0';
×
611
        j = ( i - 1 ) >> 1;
×
612
        while ( j >= 0 ) {
×
613
                i = to[j]; to[j] = s[-j]; s[-j] = (char)i; j--;
×
614
        }
615
        return(s+1);
×
616
}
617

618
/*like strcat() but returns the pointer to the end of the 
619
resulting string:*/
620
static  FORM_INLINE char *addStr(char *to, char *from)
×
621
{
622
        while(  (*to++ = *from++)!='\0' );
×
623
        return(to-1);
×
624
}/*addStr*/
625

626

627
/*Try to write (atomically) short buffer (of length count) to fd.
628
  timeout is a timeout in millisecs. Returns number of written bytes or -1:*/
629
static FORM_INLINE ssize_t writeSome(int fd, char *buf, size_t count, int timeout)
×
630
{
631
        ssize_t res = 0;
×
632
        fd_set wfds;
×
633
        struct timeval tv;
×
634
        int nrep=5;/*five attempts it interrupted by a non-blocking signal*/
×
635
        int flags = fcntl(fd, F_GETFL,0);
×
636

637
        /*Add O_NONBLOCK:*/
638
        fcntl(fd,F_SETFL, flags | O_NONBLOCK);
×
639
        /* important -- in order to avoid blocking of short receiver buffer*/
640

641
        do{
×
642
                FD_ZERO(&wfds);
×
643
                FD_SET(fd, &wfds);
×
644
                /* Wait up to timeout. */
645

646
                tv.tv_sec =timeout /1000;
×
647
                tv.tv_usec = (timeout % 1000)*1000;
×
648
                nrep--;
×
649

650
                switch(select(fd+1, NULL, &wfds, NULL, &tv)){
×
651
                        case -1:
×
652
                                if((nrep == 0)||( errno != EINTR) ){
×
653
                                        perror("select()");
×
654
                                        res=-1;
×
655
                                        nrep=0;
×
656
                                }/*else -- A non blocked signal was caught, just repeat*/
657
                                break;
658
                        case 0:/*timeout*/
659
                                res=-1;
660
                                nrep=0;
661
                                break;
662
                        default:
×
663
                                if( (res=write(fd,buf,count)) <0 )/*Signal?*/
×
664
                                        while( (errno == EINTR)&&(res <0) )
×
665
                                          res=write(fd,buf,count);
×
666
                                nrep=0;
667
                }/*switch*/
668
        }while(nrep);
×
669

670
        /*restore the flags:*/
671
        fcntl(fd,F_SETFL, flags);
×
672
        return (res);
×
673
}/*writeSome*/
674

675
/*Try to read short buffer (of length not more than count)
676
  from fd. timeout is a timeout in millisecs. Returns number 
677
  of written bytes or -1: */
678
static FORM_INLINE ssize_t readSome(int fd, char *buf, size_t count, int timeout)
×
679
{
680
        ssize_t res = 0;
×
681
        fd_set rfds;
×
682
        struct timeval tv;
×
683
        int nrep=5;/*five attempts it interrupted by a non-blocking signal*/
×
684

685
        do{
×
686
                FD_ZERO(&rfds);
×
687
                FD_SET(fd, &rfds);
×
688
                /* Wait up to timeout. */
689

690
                tv.tv_sec = timeout/1000;
×
691
                tv.tv_usec = (timeout % 1000)*1000;
×
692
                nrep--;
×
693

694
                switch(select(fd+1, &rfds, NULL, NULL, &tv)){
×
695
                        case -1:
×
696
                                if((nrep == 0)||( errno != EINTR) ){
×
697
                                        perror("select()");
×
698
                                        res=-1;
×
699
                                        nrep=0;
×
700
                                }/*else -- A non blocked signal was caught, just repeat*/
701
                                break;
702
                        case 0:/*timeout*/
703
                                res=-1;
704
                                nrep=0;
705
                                break;
706
                        default:
707
                                if( (res=read(fd,buf,count)) <0 )/*Signal?*/
×
708
                                        while( (errno == EINTR)&&(res <0) )
×
709
                                          res=read(fd,buf,count);
×
710
                                nrep=0;
711
                }/*switch*/
712
        }while(nrep);
×
713
        return (res);
×
714
}/*readSome*/
715

716
/*
717
          #] Local functions : 
718
          #[ Ok functions:
719
*/
720

721
/*Copies (deep copy) newTerminator to thehandler->terminator. Returns 0 if 
722
  newTerminator fits to the buffer, or !0 if it does not fit. ATT! In the 
723
  latter case thehandler->terminator is NOT '\0' terminated! */
724
int setTerminatorForExternalChannelOk(char *newTerminator)
×
725
{
726
int i=DELTA_EXT_BUF;
×
727
/*
728
        No problems with externalChannelsListTop are 
729
        possible since this function may be invoked only
730
        when the current channel is defined and externalChannelsListTop
731
  is set properly
732
*/
733
char *t=externalChannelsListTop->terminator;
×
734

735
        for(; i>1; i--)
×
736
                if( (*t++ = *newTerminator++)=='\0' )
×
737
                        break;
738
        /*Add trailing '\n', if absent:*/
739
        if(  (i == DELTA_EXT_BUF)/*newTerminator == '\0'*/
×
740
                ||(*(t-2)!='\n')       ){
×
741
          *(t-1)='\n';*t='\0'; 
×
742
        }
743
        return(i==1);
×
744
}/*setTerminatorForExternalChannelOk*/
745

746
/*Interface to change handler fields "killSignal" and "gpid"*/
747
int setKillModeForExternalChannelOk(int signum, int sentToWholeGroup)
×
748
{
749
        if(signum<0)
×
750
                return(-1);
751
        /*
752
                No problems with externalChannelsListTop are 
753
                possible since this function may be invoked only
754
                when the current channel is defined and externalChannelsListTop
755
                is set properly
756
        */
757
        externalChannelsListTop->killSignal=signum;
×
758
        if(sentToWholeGroup){/*gpid must be >0*/
×
759
                if(externalChannelsListTop->gpid <= 0)
×
760
                        externalChannelsListTop->gpid=-externalChannelsListTop->gpid;
×
761
        }else{/*gpid must be <=0*/
762
                if(externalChannelsListTop->gpid>0)
×
763
                        externalChannelsListTop->gpid=-externalChannelsListTop->gpid;
×
764
        }
765
        return(0);
766
}/*setKillModeForExternalChannelOk*/
767

768
/*
769
                 #[ getcFromExtChannelOk
770
*/
771
/*Returns one character from the external channel. It the input is expired, 
772
returns EOF. If the external process is finished completely, the function closes 
773
the channel (and returns EOF). If the external process was finished, the function
774
returns EOF:*/
775
int getcFromExtChannelOk(VOID)
×
776
{
777
mysighandler_t oldPIPE = 0;
×
778
EXTHANDLE *h;
×
779
int ret;
×
780

781
        if (externalChannelsListTop->IBfill < externalChannelsListTop->IBfull)
×
782
                /*in buffer*/
783
                return( *(externalChannelsListTop->IBfill++) );
×
784
        /*else -- the buffer is empty*/
785
        ret=EOF;
×
786
        h= externalChannelsListTop;
×
787
#ifdef WITHMPI
788
        if ( PF.me == MASTER ){
789
#endif
790
        /* Temporary ignore this signal:*/
791
        /* if compiler fails here, try to change the definition of
792
                mysighandler_t on the beginning of this file
793
                (just define INTSIGHANDLER).*/
794
        oldPIPE=signal(SIGPIPE,SIG_IGN);
×
795
#ifdef WITHMPI
796
        if(  fgets(h->INbuf,h->IBstop - h->INbuf, h->frec) == 0  )/*Fail! EOF?*/
797
                *(h->INbuf)='\0';/*Empty line may not appear here!*/
798
#else
799
        if(  (fgets(h->INbuf,h->IBstop - h->INbuf, h->frec) == 0)/*Fail! EOF?*/
×
800
                ||( *(h->INbuf) == '\0')/*Empty line? This shouldn't be!*/
×
801
          ){
802
                closeExternalChannel(externalChannelsListTop-externalChannelsList+1);
×
803
                /*Note, this code is only for the sequential mode! */
804
                goto getcFromExtChannelReady;
×
805
                /*Here we assume that fgets is never interrupted by singals*/
806
        }/*if( fgets(h->INbuf,h->IBstop - h->INbuf, h->frec) == 0 )*/
807
#endif
808
#ifdef WITHMPI
809
        }/*if ( PF.me == MASTER */
810

811
        /*Master broadcasts result to slaves, slaves read it from the master:*/
812
        if( PF_BroadcastString((UBYTE *)h->INbuf) ){/*Fail!*/
813
                  MesPrint("Fail broadcasting external channel results");
814
                  Terminate(-1);
815
        }/*if( PF_BroadcastString((UBYTE *)h->INbuf) )*/
816

817
        if( *(h->INbuf) == '\0'){/*Empty line? This shouldn't be!*/
818
                closeExternalChannel(externalChannelsListTop-externalChannelsList+1);
819
                goto getcFromExtChannelReady;
820
        }/*if( *(h->INbuf) == '\0')*/
821
#endif
822

823
        {/*Block*/
824
                char *t=h->terminator;
×
825
                /*Move IBfull to the end of read line and compare the line with the terminator.
826
                  Note, by construction the terminator fits to the first read line, see
827
                  the function setTerminatorForExternalChannel.*/
828
                for(h->IBfull=h->INbuf; *(h->IBfull)!='\0'; (h->IBfull)++)
×
829
                        if( *t== *(h->IBfull) )
×
830
                                t++;
×
831
                        else
832
                                break;/*not a terminator*/
833
                /*Continue moving IBfullto the end of read line:*/
834
                while(*(h->IBfull)!='\0')(h->IBfull)++;
×
835

836
                if( (t-h->terminator) == (h->IBfull-h->INbuf) ){
×
837
                        /*Terminator!*/
838
                        /*Reset the channel*/
839
                        h->IBfull=h->IBfill=h->INbuf;
×
840
                        externalChannelsListTop=0;/*Undefine the current channel*/
×
841
                        writeBufToExtChannel=&writeBufToExtChannelFailure;
×
842
                        getcFromExtChannel=&getcFromExtChannelFailure;
×
843
                        setTerminatorForExternalChannel=&setTerminatorForExternalChannelFailure;
×
844
                        setKillModeForExternalChannel=&setKillModeForExternalChannelFailure;
×
845
                        goto getcFromExtChannelReady;
×
846
                }/*if(t == (h->IBfull-h->INbuf) )*/
847
        }/*Block*/
848
        /*Does the buffer have enough capacity?*/
849
        while( *(h->IBfull - 1) != '\n' ){/*Buffer is not enough!*/
×
850
                /*Extend the buffer:*/
851
                int l= (h->IBstop - h->INbuf)+DELTA_EXT_BUF;
×
852
                char *newbuf=Malloc1(l,"External channel buffer");
×
853
                /*We wouldn't like to use realloc.*/
854
                /*Copy the buffer:*/
855
                char *n=newbuf,*o=h->INbuf;
×
856
                while(  (*n++ = *o++)!='\0' );
×
857
                /*Att! The order of the following operators is important!:*/
858
                h->IBfull= newbuf+(h->IBfull-h->INbuf);
×
859
                M_free(h->INbuf,"External channel buffer");
×
860
                h->INbuf = newbuf;
×
861
                h->IBstop = h->INbuf+l;
×
862
#ifdef WITHMPI
863
                if ( PF.me == MASTER ){
864
                        (h->IBfull)[1]='\0';/*Will mark (h->IBfull)[1] as '!' for failure*/
865
                        if(  fgets(h->IBfull,h->IBstop - h->IBfull, h->frec) == 0 ){
866
                                /*EOF! No trailing '\n'?*/
867
                                /*Mark:*/
868
                                (h->IBfull)[0]='\0';
869
                                (h->IBfull)[1]='!';
870
                                (h->IBfull)[2]='\0';
871
                                 /*The string "\0!\0" is used as an image of NULL.*/
872
                        }/*if(  fgets(h->IBfull,h->IBstop - h->IBfull, h->frec) == 0 )*/
873
                }/*if ( PF.me == MASTER )*/
874

875
                /*Master broadcasts results to slaves, slaves read it from the master:*/
876
                if( PF_BroadcastString((UBYTE *)h->IBfull) ){/*Fail!*/
877
                  MesPrint("Fail broadcasting external channel results");
878
                  Terminate(-1);
879
                }/*if( PF_BroadcastString(h->IBfull) )*/
880

881
                /*The string "\0!\0" is used as the image of NULL.*/
882
                if(
883
                          ( (h->IBfull)[0]=='\0' )
884
                        &&(  (h->IBfull)[1]=='!' )
885
                        &&(  (h->IBfull)[2]=='\0' )
886
                  )/*EOF! No trailing '\n'?*/
887
                        break;
888
#else
889
                if(  fgets(h->IBfull,h->IBstop - h->IBfull, h->frec) == 0 )
×
890
                        /*EOF! No trailing '\n'?*/
891
                        break;
892
#endif
893
                while( *(h->IBfull)!='\0' )(h->IBfull)++;
×
894
        }/*while( *(h->IBfull - 1) != '\n' )*/
895
        /*In h->INbuf we have a fresh string.*/
896
        ret=*(h->IBfill=h->INbuf);
×
897
        h->IBfill++;/*Next time a new, isn't it?*/
×
898

899
        getcFromExtChannelReady:
×
900
#ifdef WITHMPI
901
        if ( PF.me == MASTER ){
902
#endif
903
                        signal(SIGPIPE,oldPIPE);
×
904
#ifdef WITHMPI
905
        }/*if ( PF.me == MASTER )*/
906
#endif
907
                        return(ret);
×
908
}/*getcFromExtChannelOk*/
909
/*
910
                 #] getcFromExtChannelOk
911
*/
912
/*Writes exactly count bytes from the buffer buf to the external channel thehandler
913
  Returns 0 (on success) or -1:
914
*/
915
int writeBufToExtChannelOk(char *buf, size_t count)
×
916
{
917

918
int ret;
×
919
mysighandler_t oldPIPE;
×
920
#ifdef WITHMPI
921
        /*Only master communicates with the external program:*/
922
        if ( PF.me == MASTER ){
923
#endif
924
                /* Temporary ignore this signal:*/
925
                /* if compiler fails here, try to change the definition of
926
                        mysighandler_t on the beginning of this file 
927
                        (just define INTSIGHANDLER)*/
928
                oldPIPE=signal(SIGPIPE,SIG_IGN);
×
929
                ret=writexactly( externalChannelsListTop->fsend, buf, count);
×
930
                signal(SIGPIPE,oldPIPE);
×
931
#ifdef WITHMPI
932
        }else{
933
                /*Do not wait the master status: this would be too slow!*/
934
                ret=0;
935
        }
936
#endif
937
        return(ret);
×
938
}/*writeBufToExtChannel*/
939
/*
940
          #] Ok functions: 
941
          #[ do_run_cmd :
942
*/
943
/*The function returns PID of the started command*/
944
static FORM_INLINE pid_t do_run_cmd(
×
945
        int *fdsend,
946
        int *fdreceive,
947
        int *gpid, /*returns group process ID*/
948
        int ttymode,
949
                                         /*
950
                                          &8 - daemonizeing 
951
                                          &16 - setsid()*/
952
        char *cmd,
953
        char *argv[],
954
        char *stderrname
955
        )
956
{
957
int fdin[2]={-1,-1}, fdout[2]={-1,-1}, fdsig[2]={-1,-1};
×
958
/*initialised by -1 for possible rollback at failure, see closepipe() above*/
959

960
pid_t childpid,fatherchildpid = (pid_t)0;
×
961
mysighandler_t oldPIPE=NULL;
×
962

963
        if(
×
964
                (pipe(fdsig)!=0)/*This pipe will be used by a child to tell the father if fail.*/
×
965
                ||(pipe(fdin)!=0)
×
966
                ||(pipe(fdout)!=0)
×
967
        )goto fail_do_run_cmd;
×
968

969
        if((childpid = fork()) == -1){
×
970
                 perror("fork");
×
971
                 goto fail_do_run_cmd;
×
972
        }/*if((childpid = fork()) == -1)*/
973

974
        if(childpid == 0){/*Child.*/
×
975
                int fifo[3], top=0;
×
976
                /*
977
                  To be thread safely we can't rely on ascendant order of opened 
978
                  file descriptors. So we put each of descriptor we have to 
979
                  preserve into the array fifo.
980
                          Note, in _this_ process there are no any threads but descriptors
981
                  were created in frame of the parent process which may have 
982
                  multiple threads.
983
                */
984
                /*Mark descriptors which will NOT be closed:*/
985
                pushDescriptor(fifo,top++,fdsig[1]);
×
986
                pushDescriptor(fifo,top++,fdin[0]);
×
987
                pushDescriptor(fifo,top++,fdout[1]);
×
988
                /*Close all except stdin, stdout, stderr and placed into fifo:*/
989
                closeAllDescriptors(3,fifo, top);
×
990

991
                /*Now reopen stdin and stdout.*/
992
                /*thread-safety is not a problem here since there are no any threads up to now:*/
993
                if(
×
994
                        (close(0) == -1 )||/* Use fdin as stdin :*/
×
995
                        (dup(fdin[0]) == -1 )||
×
996
                        (close(1)==-1)||/* Use fdout as stdout:*/
×
997
                        (dup(fdout[1]) == -1 )
×
998
                  )
999
                {/*Fail!*/
1000
                        /*Signal to parent:*/
1001
                        writepid(fdsig[1],(pid_t)-2);         
×
1002
                        _exit(1);
×
1003
                }
1004

1005
                if(stderrname != NULL){
×
1006
                        if( 
×
1007
                                (close(2) != 0 )||
×
1008
                                (open(stderrname,O_WRONLY)<0)
×
1009
                          )
1010
                        {/*Fail!*/
1011
                                writepid(fdsig[1],(pid_t)-2);         
×
1012
                                _exit(1);
×
1013
                        }
1014
                }/*if(stderrname != NULL)*/
1015

1016
                if( ttymode & 16 )/* create a session and sets the process group ID */
×
1017
                        setsid();
×
1018

1019
                /*   */
1020
                if(set_cloexec_flag (fdsig[1], 1)!=0){/*Error?*/
×
1021
                        /*Signal to parent:*/
1022
                        writepid(fdsig[1],(pid_t)-2);
×
1023
                        _exit(1);
×
1024
                }/*if(set_cloexec_flag (fdsig[1], 1)!=0)*/
1025

1026
                if( ttymode & 8 ){/*Daemonize*/
×
1027
                        int fdsig2[2];/*To check exec() success*/
×
1028
                        if(
×
1029
                                pipe(fdsig2)||
×
1030
                                (set_cloexec_flag (fdsig2[1], 1)!=0)
×
1031
                          )
1032
                        {/*Error?*/
1033
                                /*Signal to parent:*/
1034
                                writepid(fdsig[1],(pid_t)-2);
×
1035
                                _exit(1);
×
1036
                        }
1037
                        set_cloexec_flag (fdsig2[0], 1);
×
1038
                        switch(childpid=fork()){
×
1039
                                case 0:/*grandchild*/
×
1040
                                  /*Execute external command:*/
1041
                                  execvp(cmd, argv);
×
1042
                                  /* Control can  reach this point only on error!*/
1043
                                  writepid(fdsig2[1],(pid_t)-2);
×
1044
                                  break;
×
1045
                                case -1:
×
1046
                                  /* Control can  reach this point only on error!*/
1047
                                  /*Inform the father about the failure*/
1048
                                  writepid(fdsig[1],(pid_t)-2);
×
1049
                                  _exit(1);/*The child, just exit, not return*/
×
1050
                          default:/*Son of his father*/
×
1051
                                  close(fdsig2[1]);
×
1052
                                  /*Ignore SIGPIPE (up to the end of the process):*/
1053
                                  signal(SIGPIPE,SIG_IGN);
×
1054

1055
                                  /*Wait on read() while the grandchild close the pipe 
1056
                                         (on success) or send -2 (if exec() fails).*/
1057
                                  /*There are two possibilities: 
1058
                                          -1 -- this is ok, the pipe was closed on exec,
1059
                                          the program was successfully executed;
1060
                                          -2 -- something is wrong, exec failed since the 
1061
                                          grandchild sends -2 after exec.
1062
                                  */
1063
                                  if( readpid(fdsig2[0]) != (pid_t)-1 )/*something is wrong*/
×
1064
                                          writepid(fdsig[1],(pid_t)-1);
×
1065
                                  else/*ok, send PID of the grandchild to the father:*/
1066
                                          writepid(fdsig[1],childpid);
×
1067
                                  /*Die and free the life space for the grandchild:*/
1068
                                  _exit(0);/*The child, just exit, not return*/
×
1069
                        }/*switch(childpid=fork())*/
1070
                }else{/*if( ttymode & 8 )*/
1071
                  execvp(cmd, argv);
×
1072
                  /* Control can  reach this point only on error!*/
1073
                  writepid(fdsig[1],(pid_t)-2);
×
1074
                  _exit(2);/*The child, just exit, not return*/
×
1075
                }/*if( ttymode & 8 )...else*/
1076
        }else{/* The (grand)father*/
1077
                close(fdsig[1]);
×
1078
                /*To prevent closing fdsig in rollback:*/
1079
                fdsig[1]=-1;
×
1080
                close(fdin[0]);
×
1081
                close(fdout[1]);
×
1082
                *fdsend    = fdin[1];
×
1083
                *fdreceive = fdout[0];
×
1084

1085
                /*Get the process group ID.*/
1086
                /*Avoid to use getpgid() which is non-standard.*/
1087
                if( ttymode & 16)/*setsid() was invoked, the child is a group leader:*/
×
1088
                        *gpid=childpid;
×
1089
                else/*the child belongs to the same process group as the this process:*/
1090
                        *gpid=getpgrp();/*if compiler fails here, try getpgrp(0) instead!*/
×
1091
                /*
1092
                  Rationale: getpgrp  conform  to POSIX.1 while 4.3BSD provides a 
1093
                  getpgrp() function that returns the process group ID for a 
1094
                  specified process.
1095
                */
1096

1097
                /* Temporary ignore this signal:*/
1098
                /* if compiler fails here, try to change the definition of
1099
                  mysighandler_t on the beginning of this file 
1100
                  (just define INTSIGHANDLER)*/
1101
                oldPIPE=signal(SIGPIPE,SIG_IGN);
×
1102

1103
                if( ttymode & 8 ){/*Daemonize*/
×
1104
                        /*Read the grandchild PID from the son.*/
1105
                        fatherchildpid=childpid;
×
1106
                        if(  (childpid=readpid(fdsig[0]))<0  ){
×
1107
                                /*Daemonization process fails for some reasons!*/
1108
                                childpid=fatherchildpid;/*for rollback*/
×
1109
                                goto fail_do_run_cmd;
×
1110
                        }
1111
                }else{
1112
                          /*fdsig[1] should be closed on exec and this read operation 
1113
                                 must fail on success:*/
1114
                          if( readpid(fdsig[0])!= (pid_t)-1 )
×
1115
                                  goto fail_do_run_cmd;
×
1116
                }/*if( ttymode & 8 ) ... else*/
1117
        }/*if(childpid == 0)...else*/
1118

1119
        /*Here can be ONLY the father*/
1120
        close(fdsig[0]);
×
1121
        /*To prevent closing fdsig in rollback after goto fail_flnk_do_runcmd:*/
1122
        fdsig[0]=-1;
×
1123

1124
        if( ttymode & 8 ){/*Daemonize*/
×
1125
                int i;
×
1126
                /*Wait while the father of a grandchild dies:*/
1127
                waitpid(fatherchildpid,&i,0);
×
1128
        }
1129

1130
        /*Restore the signal:*/
1131
        signal(SIGPIPE,oldPIPE);
×
1132

1133
        return(childpid);
×
1134

1135
        fail_do_run_cmd:
×
1136
                closepipe(&fdout);
×
1137
                closepipe(&fdin);
×
1138
                closepipe(&fdsig);
×
1139
                return((pid_t)-1);
×
1140
}/*do_run_cmd*/
1141
/*
1142
          #] do_run_cmd : 
1143
          #[ run_cmd :
1144
*/
1145
/*Starts the command cmd (directly, if shellpath is NULL, or in a subshell), 
1146
  swallowing its stdin and stdout; 
1147
  stderr will be re-directed to stderrname (if !=NULL). Returns PID of 
1148
  the started process. Stdin will be available as fdsend, and stdout 
1149
  will be available as fdreceive:*/
1150
static FORM_INLINE pid_t run_cmd(char *cmd, 
×
1151
                                                                 int *fdsend,
1152
                                                                 int *fdreceive,
1153
                                                                 int *gpid,
1154
                                                                 int daemonize,
1155
                                                                 char *shellpath,
1156
                                                                 char *stderrname )
1157
{
1158
char **argv;
×
1159
pid_t thepid;
×
1160

1161
        cmd=(char*)strDup1((UBYTE*)cmd, "run_cmd: cmd");/*detouch cmd*/
×
1162

1163
         
1164
        /* Prepare arguments for execvp:*/
1165
        if(shellpath != NULL){/*Run in a subshell.*/
×
1166
                int nopt;
×
1167
                /*Allocate space which is definitely enough:*/
1168
                argv=Malloc1(StrLen((UBYTE*)shellpath)*sizeof(char*)+2,"run_cmd:argv");
×
1169
                shellpath=(char*)strDup1((UBYTE*)shellpath, "run_cmd: shellpath");/*detouch shellpath*/
×
1170
                /*Parse a shell (e.g., "/bin/sh -c"):*/
1171
                nopt=parseline(argv, shellpath);
×
1172
                /* and add the command as a shell argument:*/
1173
                argv[nopt]=cmd;
×
1174
                argv[nopt+1]=NULL;
×
1175
        }else{/*Run the command directly:*/
1176
                /*Allocate space which is definitely enough:*/
1177
                argv=Malloc1(StrLen((UBYTE*)cmd)*sizeof(char*)+1,"run_cmd:argv");
×
1178
                parseline(argv, cmd);      
×
1179
        }
1180

1181
        thepid=do_run_cmd(
×
1182
                fdsend,
1183
                fdreceive,
1184
                gpid,
1185
                (daemonize)?(8|16):0,
1186
                argv[0],
1187
                argv,
1188
                stderrname
1189
        );
1190

1191
        M_free(argv,"run_cmd:argv");
×
1192
        if(shellpath)
×
1193
                M_free(shellpath,"run_cmd:argv");
×
1194
        M_free(cmd, "run_cmd: cmd");
×
1195

1196
        return(thepid);
×
1197
}/*run_cmd*/
1198
/*
1199
          #] run_cmd : 
1200
          #[ createExternalChannel :
1201
*/
1202
/*The structure to pass parameters to createExternalChannel
1203
  and openExternalChannel in case of preset channel (instead of
1204
  shellname):*/
1205
typedef struct{
1206
        int fdin;
1207
        int fdout;
1208
        pid_t theppid;
1209
}ECINFOSTRUCT;
1210

1211
/* Creates a new external channel starting the command cmd (if cmd !=NULL)
1212
        or using information from (ECINFOSTRUCT *)shellname, if cmd ==NULL:*/
1213
static FORM_INLINE void *createExternalChannel(
×
1214
                                          EXTHANDLE *h,
1215
                                          char *cmd, /*Command to run or NULL*/
1216
                                          /*0 --neither setsid nor daemonize, !=0 -- full daemonization:*/
1217
                                          int daemonize,
1218
                                          char *shellname,/* The shell (like "/bin/sh -c") or NULL*/
1219
                                          char *stderrname/*filename to redirect stderr or NULL*/
1220
                                                                                                                                )
1221
{
1222
        int fdreceive=0;
×
1223
        int gpid = 0;
×
1224
        ECINFOSTRUCT *psetInfo;
×
1225
#ifdef WITHMPI
1226
        char statusbuf[2]={'\0','\0'};/*'\0' if run_cmd retuns ok, '!' otherwise.*/
1227
#endif
1228
        extHandlerInit(h);
×
1229

1230
        h->pid=0;
×
1231

1232
        if( cmd==NULL ){/*Instead of starting a new command, use preset channel:*/
×
1233
                psetInfo=(ECINFOSTRUCT *)shellname;
×
1234
                shellname=NULL;
×
1235
                h->killSignal=0;
×
1236
                h->daemonize=0;
×
1237
        }
1238

1239
        /*Create a channel:*/
1240
#ifdef WITHMPI
1241
        if ( PF.me == MASTER ){
1242
#endif
1243
                if(cmd!=NULL)
×
1244
                        h->pid=run_cmd (cmd, &(h->fsend), 
×
1245
                                         &fdreceive,&gpid,daemonize,shellname,stderrname);
1246
                else{
1247
                        gpid=-psetInfo->theppid;
×
1248
                        h->pid=psetInfo->theppid;
×
1249
                        h->fsend=psetInfo->fdout;
×
1250
                        fdreceive=psetInfo->fdin;
×
1251
                }
1252
#ifdef WITHMPI
1253
                if(h->pid<0)
1254
                        statusbuf[0]='!';/*Broadcast fail to slaves*/
1255
        }
1256
         /*else: Keep h->pid = 0 and h->fsend = 0 for slaves in parallel mode!*/
1257

1258
        /*Master broadcasts status to slaves, slaves read it from the master:*/
1259
        if( PF_BroadcastString((UBYTE *)statusbuf) ){/*Fail!*/
1260
                h->pid=-1;
1261
        }else if( statusbuf[0]=='!')/*Master fails*/
1262
                h->pid=-1;
1263
#endif
1264

1265
        if(h->pid<0)goto createExternalChannelFails;
×
1266
#ifdef WITHMPI
1267
        if ( PF.me == MASTER ){
1268
#endif
1269
                h->gpid=gpid;
×
1270
        /*Open stdout of a newly created program as FILE* :*/
1271
                if( (h->frec=fdopen(fdreceive,"r")) == 0 )goto createExternalChannelFails;
×
1272

1273
#ifdef WITHMPI
1274
        }
1275
#endif      
1276
        /*Initialize buffers:*/
1277
        extHandlerAlloc(h,cmd,shellname,stderrname);
×
1278
        return(h);
×
1279
        /*Something is wrong?*/
1280
        createExternalChannelFails:
×
1281
          
1282
                destroyExternalChannel(h);
×
1283
                return(NULL);
×
1284
}/*createExternalChannel*/
1285

1286
/*
1287
          #] createExternalChannel : 
1288
          #[ openExternalChannel :
1289
*/
1290
int openExternalChannel(UBYTE *cmd, int daemonize, UBYTE *shellname, UBYTE *stderrname)
×
1291
{
1292
EXTHANDLE *h=externalChannelsListTop;
×
1293
int i=0;
×
1294

1295
        for(externalChannelsListTop=0;i<externalChannelsListFill;i++)
×
1296
                if(externalChannelsList[i].cmd==0){
×
1297
                        externalChannelsListTop=externalChannelsList+i;
×
1298
                        break;
×
1299
                }/*if(externalChannelsList[i].cmd!=0)*/
1300

1301
        if(externalChannelsListTop==0){
×
1302
          /*No free cells, create the new one:*/
1303
          if(externalChannelsListFill == externalChannelsListStop){
×
1304
                        /*The list is full, increase and reallocate it:*/
1305
                        EXTHANDLE *newbuf=Malloc1(
×
1306
                                                                                (externalChannelsListStop+=DELTA_EXT_LIST)*sizeof(EXTHANDLE),
×
1307
                                                                          "External channel list");
1308
                        for(i=0;i<externalChannelsListFill;i++)
×
1309
                                 extHandlerSwallowCopy(newbuf+i,externalChannelsList+i);
×
1310
                        if(externalChannelsList!=0)
×
1311
                                M_free(externalChannelsList,"External channel list");      
×
1312
                        for(;i<externalChannelsListStop;i++)
×
1313
                                extHandlerInit(newbuf+i);
×
1314
                        externalChannelsList=newbuf;
×
1315
          }/*if(externalChannelsListFill == externalChannelsListStop)*/
1316
          externalChannelsListTop=externalChannelsList+externalChannelsListFill;
×
1317
          externalChannelsListFill++;
×
1318
        }/*if(externalChannelsListTop==0)*/  
1319

1320
        if(createExternalChannel(
×
1321
                                                                          externalChannelsListTop,
1322
                                                                          (char*) cmd,
1323
                                                                          daemonize,
1324
                                                                          (char*)shellname,
1325
                                                                          (char*)stderrname            )!=NULL){
1326
                writeBufToExtChannel=&writeBufToExtChannelOk;
×
1327
                getcFromExtChannel=&getcFromExtChannelOk;
×
1328
                setTerminatorForExternalChannel=&setTerminatorForExternalChannelOk;
×
1329
                setKillModeForExternalChannel=&setKillModeForExternalChannelOk;
×
1330
                return(externalChannelsListTop-externalChannelsList+1);
×
1331
        }
1332
        /*else - failure!*/
1333
        externalChannelsListTop=h;
×
1334
        return(-1);
×
1335
}/*openExternalChannel*/
1336
/*
1337
          #] openExternalChannel : 
1338
          #[ initPresetExternalChannels :
1339
*/
1340
/*Just simple wrapper to invoke  openExternalChannel()
1341
        from initPresetExternalChannels():*/
1342
static FORM_INLINE int openPresetExternalChannel(int fdin, int fdout, pid_t theppid)
×
1343
{
1344
ECINFOSTRUCT inf;
×
1345
        inf.fdin=fdin; inf.fdout=fdout;inf.theppid=theppid;
×
1346
        return( openExternalChannel(NULL,0,(UBYTE *)&inf,NULL) );
×
1347
}/*openPresetExternalChannel*/
1348

1349
#define PIDTXTSIZE 23
1350
#define BOTHPIDSIZE 45
1351

1352
#ifndef LONG_MAX
1353
#define LONG_MAX 0x7FFFFFFFL
1354
#endif
1355

1356
/*There is a possibility to start FORM from another program providing
1357
one (or more) communication channels. These channels will be
1358
visible from a FORM program as ``pre-opened'' external channels existing
1359
after FORM starts.
1360
Before starting FORM, the parent application must create one or more
1361
pairs of pipes The read-only descriptor of the first pipe in the pair
1362
and the write-only descriptor of the second pipe must be passed to
1363
FORM as an argument of a command line option -pipe in ASCII decimal
1364
format. The argument of the option is a comma-separated list of pairs
1365
r#,w# where r# is a read-only descriptor and w# is a write-only
1366
descriptor. Alternatively, the environment variable FORM_PIPES can be
1367
used.
1368
        The following function expects as the first argument 
1369
this comma-separated list of the  descriptor pairs and tries to 
1370
initialize each of channel during thetimeout milliseconds:*/
1371

1372
int initPresetExternalChannels(UBYTE *theline, int thetimeout)
886✔
1373
{
1374
        int i, nchannels = 0;
886✔
1375
        int pidln;           /*The length of FORM PID in pidtxt*/
886✔
1376
        char pidtxt[PIDTXTSIZE],     /*64/Log_2[10] = 19.3, this is enough for any integer*/
886✔
1377
             chdescriptor[PIDTXTSIZE],
1378
             bothpidtxt[BOTHPIDSIZE],   /*"#,#\n\0"*/
1379
             *c,*b = 0;
886✔
1380
        int pip[2];
886✔
1381
        pid_t ppid;
886✔
1382
        if ( theline == NULL ) return(-1);
886✔
1383
        /*Put into pidtxt PID\n:*/
1384
        c = l2s((LONG)getpid(),pidtxt);
×
1385
        *c++='\n';
×
1386
        *c = '\0';
×
1387
        pidln = c-pidtxt;
×
1388

1389
        do {
×
1390
                pip[0] = (int)str2i((char*)theline,&c,0xFFFF);
×
1391
                if( ( pip[0] < 0 ) || ( *c != ',' ) ) goto presetFails;
×
1392
                
1393
                theline = (UBYTE*)c + 1;
×
1394
                pip[1] = (int)str2i((char*)theline,&c,0xFFFF);
×
1395
                if ( (pip[1] < 0 ) || ( ( *c != ',' ) && ( *c != '\0' ) ) ) goto presetFails; 
×
1396
                theline = (UBYTE *)c + 1;
×
1397
                /*Now we have two descriptors.
1398
                  According to the protocol, FORM must send to external channel 
1399
                  it's PID with added '\n' and read back two comma-separated
1400
                  decimals with added '\n'. The first must be repeated FORM PID,
1401
                  the second must be the parent PID
1402
                */
1403
                if ( writeSome(pip[1],pidtxt,pidln,thetimeout) != pidln ) goto presetFails;
×
1404
                i = readSome(pip[0],bothpidtxt,BOTHPIDSIZE,thetimeout);
×
1405
                if( ( i < 4 )                 /*at least 1,2\n*/
×
1406
                        || ( i == BOTHPIDSIZE )   /*No space for trailing '\0'*/
×
1407
                  ) goto presetFails;
×
1408
                /*read the FORM PID:*/
1409
                ppid = (pid_t)str2i(bothpidtxt,&b,getpid());
×
1410
                if( ( *b != ',' ) || ( ppid != getpid() ) )goto presetFails;
×
1411
                /*read the parent PID:*/
1412
                /*The problem is that we do not know the the real type of pid_t.
1413
                  But long should be enough. On obsolete systems (when LONG_MAX
1414
                  is not defined) we assume pid_t is 32-bit integer.
1415
                  This can lead to problem with portability: */
1416
                ppid = (pid_t)str2i(b+1,&b,LONG_MAX);
×
1417
                if ( (*b != '\n') || (ppid<2) ) goto presetFails;
×
1418
                i = openPresetExternalChannel(pip[0],pip[1],ppid);
×
1419
                if ( i < 0 ) goto presetFails;
×
1420
                nchannels++;
×
1421
                /*Now use bothpidtxt as a buffer for preprovar, the space is enough:*/
1422
                /*"PIPE#_" where # is ne order number of the channel:*/
1423
                b = l2s(nchannels,addStr(bothpidtxt,"PIPE"));
×
1424
                *b = '_';
×
1425
                b[1] = '\0';
×
1426
                *l2s(i,chdescriptor) = '\0';
×
1427
                PutPreVar((UBYTE*)bothpidtxt,(UBYTE*)chdescriptor,0,0);
×
1428
        } while ( *c != '\0' );
×
1429
        /*Export proprovar "PIPES_":*/
1430
        *l2s(nchannels,chdescriptor)='\0';
×
1431
        PutPreVar((UBYTE*)"PIPES_",(UBYTE*)chdescriptor,0,0);
×
1432

1433
        /*success:*/
1434
        return (nchannels);
×
1435

1436
presetFails:
1437
                /*Here we assume the descriptors the beginning of the list!*/
1438
                for(i=0; i<nchannels; i++)
×
1439
                        destroyExternalChannel(externalChannelsList+i);
×
1440
                return(-1);
1441
} /*initPresetExternalChannels*/
1442
/*
1443
          #] initPresetExternalChannels : 
1444
          #[ selectExternalChannel :
1445
*/
1446
/* 
1447
Accepts the valid external channel descriptor (returned by
1448
openExternalChannel) and returns the descriptor of a previous current
1449
channel (0, if there was no current channel, or -1, if the external
1450
channel descriptor is invalid).  If n == 0, the function undefine the
1451
current external channel:
1452
*/
1453
int selectExternalChannel(int n)
×
1454
{
1455
int ret=0;
×
1456
        if(externalChannelsListTop!=0)
×
1457
                ret=externalChannelsListTop-externalChannelsList+1;
×
1458

1459
        if(--n<0){
×
1460
                if(n!=-1)
×
1461
                        return(-1);
1462
                externalChannelsListTop=0;
×
1463
                writeBufToExtChannel=&writeBufToExtChannelFailure;
×
1464
                getcFromExtChannel=&getcFromExtChannelFailure;
×
1465
                setTerminatorForExternalChannel=&setTerminatorForExternalChannelFailure;
×
1466
                setKillModeForExternalChannel=&setKillModeForExternalChannelFailure;
×
1467
                return(ret);
×
1468
        }
1469
        if( 
×
1470
                (n>=externalChannelsListFill)||
×
1471
                (externalChannelsList[n].cmd==0)
×
1472
        )
1473
                return(-1);
1474
        
1475
        externalChannelsListTop=externalChannelsList+n;
×
1476
        writeBufToExtChannel=&writeBufToExtChannelOk;
×
1477
        getcFromExtChannel=&getcFromExtChannelOk;
×
1478
        setTerminatorForExternalChannel=&setTerminatorForExternalChannelOk;
×
1479
        setKillModeForExternalChannel=&setKillModeForExternalChannelOk;
×
1480
        return(ret);
×
1481
}/*selectExternalChannel*/
1482
/*
1483
          #] selectExternalChannel : 
1484
          #[ closeExternalChannel :
1485
*/
1486

1487
/*
1488
Destroys the opened external channel with the descriptor n. It returns
1489
0 in success, or -1 on failure. If the corresponding external channel
1490
was the current one, the current channel becomes undefined. If n==0,
1491
the function closes the current external channel.
1492
*/
1493
int closeExternalChannel(int n)
×
1494
{
1495
        if(n==0)
×
1496
                n=externalChannelsListTop-externalChannelsList;
×
1497
        else
1498
                n--;/*Count from 0*/
×
1499

1500
        if(
×
1501
                (n<0)||
×
1502
                (n>=externalChannelsListFill)||
×
1503
                (externalChannelsList[n].cmd==0)
×
1504
        )/*No shuch a channel*/
1505
                return(-1);
1506

1507
        destroyExternalChannel(externalChannelsList+n);
×
1508
        /*If the current external channel was destroyed, undefine current channel:*/
1509
        if(externalChannelsListTop==externalChannelsList+n){
×
1510
                externalChannelsListTop=NULL;
×
1511
                writeBufToExtChannel=&writeBufToExtChannelFailure;
×
1512
                getcFromExtChannel=&getcFromExtChannelFailure;
×
1513
                setTerminatorForExternalChannel=&setTerminatorForExternalChannelFailure;
×
1514
                setKillModeForExternalChannel=&setKillModeForExternalChannelFailure;
×
1515
        }/*if(externalChannelsListTop==externalChannelsList+n)*/
1516
        return(0);
1517
}/*closeExternalChannel*/
1518
/*
1519
          #] closeExternalChannel : 
1520
          #[ closeAllExternalChannels :
1521
*/
1522
void closeAllExternalChannels(VOID)
886✔
1523
{
1524
int i;
886✔
1525
        for(i=0; i<externalChannelsListFill; i++)
886✔
1526
                destroyExternalChannel(externalChannelsList+i);
×
1527
        externalChannelsListFill=externalChannelsListStop=0;
886✔
1528
        externalChannelsListTop=NULL;
886✔
1529

1530
        writeBufToExtChannel=&writeBufToExtChannelFailure;
886✔
1531
        getcFromExtChannel=&getcFromExtChannelFailure;
886✔
1532
        setTerminatorForExternalChannel=&setTerminatorForExternalChannelFailure;
886✔
1533
        setKillModeForExternalChannel=&setKillModeForExternalChannelFailure;
886✔
1534

1535
        if(externalChannelsList!=NULL){
886✔
1536
                M_free(externalChannelsList,"External channel list");
×
1537
                externalChannelsList=NULL;
×
1538
        }
1539
}/*closeAllExternalChannels*/
886✔
1540
/*
1541
          #] closeAllExternalChannels : 
1542
          #[ getExternalChannelPid :
1543
*/
1544
#ifdef SELFTEST
1545
pid_t getExternalChannelPid(VOID)
1546
{
1547
  if(externalChannelsListTop!=0)
1548
                return(externalChannelsListTop ->pid);
1549
  return(-1);
1550
}/*getExternalChannelPid*/
1551
#endif
1552
/*
1553
          #] getExternalChannelPid : 
1554
          #[ getCurrentExternalChannel :
1555
*/
1556

1557
int getCurrentExternalChannel(VOID)
×
1558
{
1559

1560
        if ( externalChannelsListTop != 0 )
×
1561
                return(externalChannelsListTop-externalChannelsList+1);
×
1562
        return(0);
1563
}/*getCurrentExternalChannel*/
1564
/*
1565
          #] getCurrentExternalChannel : 
1566
          #[ Selftest main :
1567
*/
1568

1569
#ifdef SELFTEST
1570

1571
/*
1572
        This is the example of how all these public functions may be used:
1573
*/
1574

1575
char buf[1024];
1576
char buf2[1024];
1577

1578
void help(void)
1579
{
1580
        printf("String started with a special letter is a command\n");
1581
        printf("Known commands are:\n");
1582
        printf("H or ? -- this help\n");
1583
        printf("Nn<command> -- start a new command\n");
1584
        printf("S<command> -- start a new command in a subshell,daemon,stderr>/dev/null\n");
1585
        printf("C# -- destroy channel #\n");
1586
        printf("R# -- set a new cahhel(number#) as a current one\n");
1587
        printf("K#1 #2 --  set signal for kill and kill mode (0 or !=0)\n");
1588
        printf("   ^d to quit\n");
1589
}/*help*/
1590

1591
int main (void)
1592
{
1593
        int i, j, k,last;
1594
        long long sum = 0;
1595

1596
        /*openExternalChannel(UBYTE *cmd, int daemonize, UBYTE *shellname, UBYTE *stderrname)*/
1597

1598
        help();
1599

1600
        printf("Initial channel:%d\n",last=openExternalChannel((UBYTE*)"cat",0,NULL,NULL));
1601

1602
        if( ( i = setTerminatorForExternalChannel("qu") ) != 0 ) return 1;
1603
        printf("Terminator is 'qu'\n");
1604

1605
        while ( fgets(buf, 1024, stdin) != NULL ) {
1606
                if ( *buf == 'N' ) {
1607
                        printf("New channel:%d\n",j=openExternalChannel((UBYTE*)buf+1,0,NULL,NULL));
1608
                        continue;
1609
                }
1610
                else if ( *buf == 'C' ) {
1611
                        int n;
1612
                        sscanf(buf+1,"%d",&n);
1613
                        printf("Destroy last channel:%d\n",closeExternalChannel(n));
1614
                        continue;
1615
                }
1616
                else if ( *buf == 'R' ) {
1617
                        int n = 0;
1618
                        sscanf(buf+1,"%d",&n);
1619
                        printf("Reopen channel %d:%d\n",n,selectExternalChannel(n));
1620
                        continue;
1621
                }else if( *buf == 'K' ) {
1622
                        int n=0,g = 0;
1623
                        sscanf(buf+1,"%d %d",&n,&g);
1624
                        printf("setKillMode %d\n",setKillModeForExternalChannel(n,g));
1625
                        continue;
1626
                }else if( *buf == 'S' ) {                        
1627
                        printf("New channel with sh&d&stderr:%d\n",
1628
                                         j=openExternalChannel((UBYTE*)buf+1,1,(UBYTE*)"/bin/sh -c",(UBYTE*)"/dev/null"));
1629
                        continue;
1630
                }
1631
                else if( ( *buf == 'H' )|| ( *buf == '?' )  ){
1632
                        help();
1633
                        continue;
1634
                }
1635

1636
                writeBufToExtChannel(buf,k=StrLen(buf));
1637
                sum += k;
1638
                for ( j = 0; ( i = getcFromExtChannel() ) != '\n'; j++) {
1639
                        if ( i == EOF ) {
1640
                                printf("EOF!\n");
1641
                                break;
1642
                        }
1643
                        buf2[j] = i;
1644
                }
1645
                buf2[j] = '\0';
1646
                printf("I got:'%s'; pid=%d\n",buf2,getExternalChannelPid());
1647
        }
1648
        printf("Total:%lld bytes\n",sum);
1649
        closeAllExternalChannels();
1650
        return 0;
1651
}
1652
#endif /*ifdef SELFTEST*/
1653
/*
1654
          #] Selftest main : 
1655
*/
1656

1657
#endif /*ifndef WITHEXTERNALCHANNEL ... else*/
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc