FreeBSD 10 clang port
[oweals/cde.git] / cde / lib / DtTerm / TermPrim / TermPrimSubproc.c
1 /*
2  * CDE - Common Desktop Environment
3  *
4  * Copyright (c) 1993-2012, The Open Group. All rights reserved.
5  *
6  * These libraries and programs are free software; you can
7  * redistribute them and/or modify them under the terms of the GNU
8  * Lesser General Public License as published by the Free Software
9  * Foundation; either version 2 of the License, or (at your option)
10  * any later version.
11  *
12  * These libraries and programs are distributed in the hope that
13  * they will be useful, but WITHOUT ANY WARRANTY; without even the
14  * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
15  * PURPOSE. See the GNU Lesser General Public License for more
16  * details.
17  *
18  * You should have received a copy of the GNU Lesser General Public
19  * License along with these librararies and programs; if not, write
20  * to the Free Software Foundation, Inc., 51 Franklin Street, Fifth
21  * Floor, Boston, MA 02110-1301 USA
22  */
23 #ifndef lint
24 #ifdef  VERBOSE_REV_INFO
25 static char rcs_id[] = "$TOG: TermPrimSubproc.c /main/11 1998/04/20 12:45:57 mgreess $";
26 #endif  /* VERBOSE_REV_INFO */
27 #endif  /* lint */
28
29 /*                                                                      *
30  * (c) Copyright 1993, 1994, 1996 Hewlett-Packard Company               *
31  * (c) Copyright 1993, 1994, 1996 International Business Machines Corp. *
32  * (c) Copyright 1993, 1994, 1996 Sun Microsystems, Inc.                *
33  * (c) Copyright 1993, 1994, 1996 Novell, Inc.                          *
34  * (c) Copyright 1996 Digital Equipment Corporation.                    *
35  * (c) Copyright 1996 FUJITSU LIMITED.                                  *
36  * (c) Copyright 1996 Hitachi.                                          *
37  */
38
39 #include "TermHeader.h"
40 #include <fcntl.h>
41 #if defined(ALPHA_ARCHITECTURE) || defined(CSRG_BASED) || defined(LINUX_ARCHITECTURE)
42 /* For TIOCSTTY definitions */
43 #include <sys/ioctl.h>
44 #endif /* ALPHA_ARCHITECTURE */
45 #include <sys/wait.h>
46
47 #include <signal.h>
48 #include <errno.h>
49
50 #define X_INCLUDE_PWD_H
51 #define X_INCLUDE_UNISTD_H
52 #define XOS_USE_XT_LOCKING
53 #include <X11/Xos_r.h>
54
55 #include <Xm/Xm.h>
56 #if defined(HPVUE)
57 #include <Xv/EnvControl.h>
58 #else    /* HPVUE */
59 #include <Dt/EnvControlP.h>
60 #endif   /* HPVUE */
61
62 #include "TermPrimP.h"
63 #include "TermPrimI.h"
64 #include "TermPrimGetPty.h"
65 #include "TermPrimSetPty.h"
66 #include "TermPrimSubproc.h"
67 #include "TermPrimDebug.h"
68 #include "TermPrimSetUtmp.h"
69 #include "TermPrimUtil.h"
70
71 typedef struct _subprocInfo {
72     pid_t               pid;
73     int                 stat_loc;
74     Widget              w;
75     _termSubprocProc    proc;
76     XtPointer           client_data;
77     XtSignalId          signal_id;
78     struct _subprocInfo *next;
79     struct _subprocInfo *prev;
80 } subprocInfo;
81
82 static subprocInfo _subprocHead;
83 static subprocInfo *subprocHead = &_subprocHead;
84
85 static pid_t
86 FakeFork (void)
87 {
88     static Boolean debugInit = True;
89     static int debugForkFailures = 0;
90     char *c;
91
92     if (isDebugFSet('f', 10)) {
93 #ifdef  BBA
94 #pragma BBA_IGNORE
95 #endif  /*BBA*/
96         _DtTermProcessLock();
97         if (debugInit) {
98             if ((c = getenv("dttermDebugForkFailures"))) {
99                 debugForkFailures = strtol(c, (char **) 0, 0);
100                 debugInit = 0;
101             }
102         }
103         if (debugForkFailures > 0) {
104             /* decrement the number of failures... */
105             (void) debugForkFailures--;
106
107             /* set our error return... */
108             errno = EAGAIN;
109
110             /* and error out... */
111             _DtTermProcessUnlock();
112             return(-1);
113         }
114         _DtTermProcessUnlock();
115     }
116
117     /* just do a fork()... */
118     return(fork());
119 }
120
121 /*ARGSUSED*/
122 static void
123 InvokeCallbacks(XtPointer client_data, XtSignalId *id)
124 {
125     subprocInfo *subprocTmp = (subprocInfo *) client_data;
126
127     if (subprocTmp->w && subprocTmp->proc)
128       (subprocTmp->proc)(subprocTmp->w, subprocTmp->pid, &subprocTmp->stat_loc);
129 }
130
131 _termSubprocId
132 _DtTermPrimAddSubproc(Widget            w, 
133                       pid_t             pid, 
134                       _termSubprocProc  proc,
135                       XtPointer         client_data)
136 {
137     subprocInfo *subprocTmp;
138
139     /* malloc a new entry... */
140     subprocTmp = (subprocInfo *) XtCalloc(1, sizeof(subprocInfo));
141
142     /* fill in the structures... */
143     subprocTmp->pid = pid;
144     subprocTmp->w = w;
145     subprocTmp->proc = proc;
146     subprocTmp->client_data = client_data;
147     subprocTmp->signal_id = XtAppAddSignal(XtWidgetToApplicationContext(w), 
148                                            InvokeCallbacks, subprocTmp);
149
150     /* insert it after the head of the list... */
151     _DtTermProcessLock();
152     subprocTmp->prev = subprocHead;
153     subprocTmp->next = subprocHead->next;
154     subprocHead->next = subprocTmp;
155     if (subprocTmp->next) {
156         subprocTmp->next->prev = subprocTmp;
157     }
158     _DtTermProcessUnlock();
159
160     /* return the pointer... */
161     return((_termSubprocId) subprocTmp);
162 }
163
164 void
165 _DtTermPrimSubprocRemoveSubproc(Widget w, _termSubprocId id)
166 {
167     subprocInfo *subprocTmp = (subprocInfo *) id;
168
169     /* remove the entry from the linked list...
170      */
171     /* there will always be a head, so we can always update it... */
172     _DtTermProcessLock();
173     subprocTmp->w = NULL;
174     subprocTmp->prev->next = subprocTmp->next;
175     if (subprocTmp->next) {
176         subprocTmp->next->prev = subprocTmp->prev;
177     }
178     _DtTermProcessUnlock();
179
180     XtRemoveSignal(subprocTmp->signal_id);
181
182     /* free our storage... */
183     (void) XtFree((char *) subprocTmp);
184 }
185
186 /*ARGSUSED*/
187 static void
188 ReapChild(int sig)
189 {
190     pid_t pid;
191     int   stat_loc;
192     int   err = errno;
193
194     /* There may be several children waiting. */
195     while ((pid = waitpid(-1, &stat_loc, WNOHANG)) > 0)
196       DtTermSubprocReap(pid, &stat_loc);
197
198     /*
199      * Because our signal handler was installed with sigaction()
200      * instead of signal() it should remain installed after it is
201      * invoked, even on SVR4 machines.  Otherwise we would need to
202      * reinstall it each time, creating a race condition in which
203      * signals could be lost. 
204      */
205
206     /* Preserve errno, like all good signal handlers should. */
207     errno = err;
208 }
209
210 void
211 _DtTermPrimSetChildSignalHandler(void)
212 {
213     struct sigaction new_action;
214
215     new_action.sa_handler = ReapChild;
216     sigemptyset(&new_action.sa_mask);
217     new_action.sa_flags = 0;
218 #ifdef SA_RESTART 
219     new_action.sa_flags |= SA_RESTART;
220 #endif
221
222     /* Use new sigaction() signal handling semantics, not signal(). */
223     (void) sigaction(SIGCHLD, &new_action, (struct sigaction*) NULL);
224 }
225
226 void
227 DtTermSubprocReap(pid_t pid, int *stat_loc)
228 {
229     /*
230      * This procedure has special constraints, since it may be invoked
231      * inside a signal handler.  That means it (and anything it calls)
232      * can only use POSIX async-signal safe library routines.  A notable
233      * omission from the list of reentrant routines is pthread_mutex_lock(),
234      * which means we cannot call XtProcessLock() or XtAppLock().
235      *
236      * That makes it challenging to transfer the pid and stat_loc
237      * information out of the signal handler to a routine where it is
238      * safe to invoke callbacks.  Storing them in static globals will not
239      * work, because overlapping signals may arrive.  The approach used
240      * here is imperfect, but the best I could contrive.  We block signals
241      * and then search the global data structures without using any locks. 
242      * The routines that update the subprocHead list try not to leave it
243      * in a transient inconsistent state, but that cannot be guaranteed.
244      */
245
246     subprocInfo *subprocTmp;
247     sigset_t new_sigs;
248     sigset_t old_sigs;
249
250     /* 
251      * Block additional SIGCHLD signals temporarily.  This is not
252      * necessary if the handler was installed with sigaction(), but we
253      * may be called from an application's signal handler, and it may
254      * have been installed with signal().
255      */
256     (void) sigemptyset(&new_sigs);
257     (void) sigaddset(&new_sigs, SIGCHLD);
258     (void) sigprocmask(SIG_BLOCK, &new_sigs, &old_sigs);
259
260     if (pid > 0) {
261         /* find the subprocInfo structure for this subprocess... */
262         for (subprocTmp = subprocHead->next; 
263              subprocTmp;
264              subprocTmp = subprocTmp->next) {
265             if (subprocTmp->pid == pid) {
266                 if (subprocTmp->w && !subprocTmp->w->core.being_destroyed) {
267                     subprocTmp->stat_loc = *stat_loc;
268                     XtNoticeSignal(subprocTmp->signal_id);
269                 }
270                 break;
271             }
272         }
273     }
274
275     /* Restore SIGCHLD handling to its original state. */
276     (void) sigprocmask(SIG_SETMASK, &old_sigs, NULL);
277 }
278
279 pid_t
280 _DtTermPrimSubprocExec(Widget             w,
281                        char              *ptyName,
282                        Boolean            consoleMode,
283                        char              *cwd,
284                        char              *cmd,
285                        char             **argv,
286                        Boolean            loginShell)
287 {
288     DtTermPrimitiveWidget tw = (DtTermPrimitiveWidget) w;
289     static char *defaultCmd = (char *) 0;
290     int i;
291     int pty;
292     pid_t pid;
293     char *c;
294     int err;
295 #ifdef  MOVE_FDS
296     int saveFd[3];
297 #else   /* MOVE_FDS */
298     int savedStderr;
299 #endif  /* MOVE_FDS */
300     Boolean argvFree = False;
301     struct sigaction sa;
302     sigset_t ss;
303     char buffer[BUFSIZ];
304     Widget parent;
305     char *namebuf;
306     struct passwd * pw;
307     _Xgetpwparams pw_buf;
308     _Xgetloginparams login_buf;
309
310 #ifdef  ALPHA_ARCHITECTURE
311     /* merge code from xterm, ignore so that TIOCSWINSZ doesn't block */
312     signal(SIGTTOU, SIG_IGN);
313 #endif /* ALPHA_ARCHITECTURE */
314
315     /* build a default exec command and argv list if one wasn't supplied...
316      */
317     /* cmd... */
318     /* the command will be taken as follows:
319      *      - from the passed in cmd,
320      *      - from $SHELL,
321      *      - from the /etc/passwd entry for the /etc/utmp entry for this
322      *        terminal,
323      *      - from the /etc/passwd entry for this userid, or
324      *      - /bin/sh.
325      */
326     if (!cmd || !*cmd) {
327         if (!defaultCmd) {
328             /* from $SHELL... */
329             c = getenv("SHELL");
330
331             /* if not valid, try the /etc/passwd entry for the username
332              * associated with the /etc/utmp entry for this tty...
333              */
334             if (!c || !*c) {
335                 /* get the /etc/passwd entry for the username associated with
336                  * /etc/utmp...
337                  */
338                 if ((namebuf = _XGetlogin(login_buf)) != NULL) {
339                     /* get the user's passwd entry... */
340                     pw = _XGetpwnam(namebuf, pw_buf);
341                     /* if we weren't able to come up with one for the
342                      * username...
343                      */
344                     if (pw != NULL)
345                         c = pw->pw_shell;
346                 }
347             }
348
349             /* if not valid, try the /etc/passwd entry for the username
350              * associate with the real uid...
351              */
352             if (!c || !*c) {
353                 /* if we weren't able to come up with one for the userid... */
354                 pw = _XGetpwuid(getuid(), pw_buf);
355                 if (pw != NULL) {
356                     c = pw->pw_shell;
357                 }
358             }
359
360             /* if not valid, use /bin/sh... */
361             if (!c || !*c) {
362                 c = DEFAULT_SHELL;
363             }
364
365             /* malloc space for this string.  It will be free'ed in the
366              * destroy function...
367              */
368             defaultCmd = XtMalloc(strlen(c) + 1);
369             (void) strcpy(defaultCmd, c);
370         }
371
372         cmd = defaultCmd;
373     }
374
375     if (!argv) {
376         /* base it on cmd... */
377         argv = (char **) XtMalloc(2 * sizeof(char *));
378         /* if loginShell is set, then pre-pend a '-' to argv[0].  That's
379          * also why we allocate an extra byte in argv[0]...
380          */
381         argv[0] = XtMalloc(strlen(cmd) + 2);
382         *argv[0] = '\0';
383         if (loginShell) {
384             /* pre-pend an '-' for loginShell... */
385             (void) strcat(argv[0], "-");
386             if ((c = strrchr(cmd, '/'))) {
387                 strcat(argv[0], ++c);
388             } else {
389                 strcat(argv[0], cmd);
390             }
391         } else {
392             (void) strcat(argv[0], cmd);
393         }
394         /* null term the list... */
395         argv[1] = (char *) 0;
396
397         /* we will need to free it up later... */
398         argvFree = True;
399     }
400
401 #ifdef  OLDCODE
402     /* this is left around from when we were using vfork().... */
403     /* open the pty slave so that we can set the modes.
404      *
405      * NOTE: this code depends on support for the O_NOCTTY ioctl.  This
406      *     ioctl allows us to open the device without becoming the
407      *     session group leader for it.  If that can't be done, it may
408      *     be necessary to rethink the way we open the pty slave...
409      */
410     if ((pty = open(ptyName, O_RDWR | O_NOCTTY, 0)) < 0) {
411         (void) perror(ptyName);
412         return((pid_t) -1);
413     }
414 #endif  /* OLDCODE */
415
416 #ifdef  MOVE_FDS
417     /* move fd[0:2] out of the way for now... */
418     for (i = 0; i <= 2; i++) {
419         saveFd[i] = fcntl(i, F_DUPFD, 3);
420         (void) close(i);
421     }
422 #else   /* MOVE_FDS */
423     savedStderr = fcntl(2, F_DUPFD, 3);
424 #endif  /* MOVE_FDS */
425
426     /* set close on exec flags on all files... */
427     for (i = 0; i < _NFILE; i++) {
428         (void) fcntl(i, F_SETFD, 1);
429     }
430
431     /* fork.  We can't use vfork() since we need to do lots of stuff
432      * below...
433      */
434     if (isDebugSet('T')) {
435 #ifdef  BBA
436 #pragma BBA_IGNORE
437 #endif  /*BBA*/
438         (void) timeStamp("about to fork()");
439     }
440
441     _DtTermProcessLock();
442     for (i = 0; ((pid = FakeFork()) < 0) && (i < 10); i++) {
443         /* if we are out of process slots, then let's sleep a bit and
444          * try again...
445          */
446         if (errno != EAGAIN) {
447             break;
448         }
449
450         /* give it a chance to clear up... */
451         (void) sleep((unsigned long) 2);
452     }
453
454     if (pid < 0) {
455         (void) perror("fork()");
456 #ifdef  OLDCODE
457         /* this is left around from when we were using vfork().... */
458         (void) close(pty);
459 #endif  /* OLDCODE */
460         return((pid_t) - 1);
461     } else if (pid == 0) {
462         /* child...
463          */
464         _DtTermProcessUnlock();
465 #if defined(ALPHA_ARCHITECTURE) || defined(CSRG_BASED) || defined(LINUX_ARCHITECTURE)
466         /* establish a new session for child */
467         setsid();
468 #else
469         /* do a setpgrp() so that we can... */
470         (void) setpgrp();
471 #endif /* ALPHA_ARCHITECTURE */
472
473 #if defined(LINUX_ARCHITECTURE)
474         /* set the ownership and mode of the pty... */
475         (void) _DtTermPrimSetupPty(ptyName, pty);
476 #endif
477
478         /* open the pty slave as our controlling terminal... */
479         pty = open(ptyName, O_RDWR, 0);
480
481         if (pty < 0) {
482             (void) perror(ptyName);
483             (void) _exit(1);
484         }
485
486 #if defined(ALPHA_ARCHITECTURE) || defined(CSRG_BASED) || defined(LINUX_ARCHITECTURE)
487         /* BSD needs to do this to acquire pty as controlling terminal */
488         if (ioctl(pty, TIOCSCTTY, (char *)NULL) < 0) {
489             (void) close(pty);
490             (void) perror("Error acquiring pty slave as controlling terminal");
491             /* exit the subprocess */
492             _exit(1);
493         }
494
495         /* Do it when no controlling terminal doesn't work for OSF/1 */
496         _DtTermPrimPtyGetDefaultModes();
497 #endif /* ALPHA_ARCHITECTURE */
498
499 #if !defined(LINUX_ARCHITECTURE)
500         /* set the ownership and mode of the pty... */
501         (void) _DtTermPrimSetupPty(ptyName, pty);
502 #endif /* LINUX_ARCHITECTURE */
503
504         /* apply the ttyModes... */
505         _DtTermPrimPtyInit(pty, tw->term.ttyModes, tw->term.csWidth);
506         /* set the window size... */
507         _DtTermPrimPtySetWindowSize(pty,
508                 tw->term.columns * tw->term.widthInc +
509                 (2 * (tw->primitive.shadow_thickness +
510                       tw->primitive.highlight_thickness +
511                       tw->term.marginWidth)),
512                 tw->term.rows * tw->term.heightInc +
513                 (2 * (tw->primitive.shadow_thickness +
514                       tw->primitive.highlight_thickness +
515                       tw->term.marginHeight)),
516                 tw->term.rows, tw->term.columns);
517
518         /* if we are in console mode, turn it on... */
519         if (consoleMode) {
520             _DtTermPrimPtyConsoleModeEnable(pty);
521         }
522
523 #ifdef  MOVE_FDS
524         /* that should have open'ed into fd 0.  Dup it into fd's 1 and 2... */
525         (void) dup(pty);
526         (void) dup(pty);
527 #else   /* MOVE_FDS */
528         /* dup pty into fd's 0, 1, and 2... */
529         for (i = 0; i < 3; i++) {
530             if (i != pty) {
531                 (void) close(i);
532                 (void) dup(pty);
533             }
534         }
535         if (pty >= 3) {
536             (void) close(pty);
537         }
538 #endif  /* MOVE_FDS */
539
540         /* reset any alarms... */
541         (void) alarm(0);
542
543         /* reset all signal handlers... */
544         sa.sa_handler = SIG_DFL;
545         (void) sigemptyset(&sa.sa_mask);
546         sa.sa_flags = 0;
547         for (i = 1; i < NSIG; i++) {
548             (void) sigaction(i, &sa, (struct sigaction *) 0);
549         }
550
551         /* unblock all signals... */
552         (void) sigemptyset(&ss);
553         (void) sigprocmask(SIG_SETMASK, &ss, (sigset_t *) 0);
554
555         /*
556         ** Restore the original (pre-DT) environment, removing any
557         ** DT-specific environment variables that were added before
558         ** we...
559         */
560 #if defined(HPVUE)
561 #if       (OSMINORVERSION > 01)
562         (void) VuEnvControl(VUE_ENV_RESTORE_PRE_VUE);
563 #endif /* (OSMINORVERSION > 01) */
564 #else   /* (HPVUE) */  
565         (void) _DtEnvControl(DT_ENV_RESTORE_PRE_DT);
566 #endif  /* (HPVUE) */  
567             
568         /*
569         ** set a few environment variables of our own...
570         */
571         for (parent = w; !XtIsShell(parent); parent = XtParent(parent))
572             ;
573         (void) sprintf(buffer, "%ld", XtWindow(parent));
574         _DtTermPrimPutEnv("WINDOWID=", buffer);
575         _DtTermPrimPutEnv("DISPLAY=", XDisplayString(XtDisplay(w)));
576         if (((DtTermPrimitiveWidget)w)->term.emulationId) {
577             _DtTermPrimPutEnv("TERMINAL_EMULATOR=",
578                               ((DtTermPrimitiveWidget)w)->term.emulationId);
579         }
580                  
581         /* set our utmp entry... */
582         (void) _DtTermPrimUtmpEntryCreate(w, getpid(),
583                 ((DtTermPrimitiveWidget)w)->term.tpd->utmpId);
584
585         if (isDebugSet('T')) {
586 #ifdef  BBA
587 #pragma BBA_IGNORE
588 #endif  /*BBA*/
589             (void) timeStamp("about to execvp()");
590         }
591
592         /* turn off suid forever...
593          */
594         _DtTermPrimRemoveSuidRoot();
595
596         /* change to the requested directory... */
597         if (cwd && *cwd) {
598             (void) chdir(cwd);
599         }
600
601 #ifdef  BBA
602         _bA_dump();
603 #endif  /* BBA */
604         _DtEnvControl(DT_ENV_RESTORE_PRE_DT);
605         (void) execvp(cmd, argv);
606         /* if we got to this point we error'ed out.  Let's write out the
607          * error...
608          */
609         err = errno;
610         /* restore stderr... */
611         (void) close(2);
612         (void) dup(savedStderr);
613         /* restore errno... */
614         errno = err;
615         (void) perror(cmd);
616         /* and we need to exit the subprocess... */
617         _exit(1);
618     }
619
620     /* parent...
621      */
622     _DtTermProcessUnlock();
623     if (isDebugSet('T')) {
624 #ifdef  BBA
625 #pragma BBA_IGNORE
626 #endif  /*BBA*/
627         (void) timeStamp("parent resuming");
628     }
629 #ifdef  MOVE_FDS
630     /* DKS: we should check this out and see if it is necessary... */
631     (void) close(0);
632     (void) close(1);
633     (void) close(2);
634     /* move fd[0:2] back in place... */
635     for (i = 0; i <= 2; i++) {
636         if (saveFd[i] >= 0) { 
637             (void) fcntl(saveFd[i], F_DUPFD, i);
638             (void) close(saveFd[i]);
639         }
640     }
641 #else   /* MOVE_FDS */
642     (void) close(savedStderr);
643 #endif  /* MOVE_FDS */
644
645     /* clean up malloc'ed memory... */
646     if (argvFree) {
647         (void) XtFree(argv[0]);
648         (void) XtFree((char *) argv);
649     }
650
651 #ifdef  OLDCODE
652     /* since we no longer open it in the parent, we probably don't want
653      * to close it either...
654      */
655     (void) close(pty);
656 #endif  /* OLDCODE */
657
658     /* assume that our child set up a utmp entry (since we have no way
659      * for it to report to us) and add it to the list to cleanup)...
660      */
661     _DtTermPrimUtmpAddEntry(((DtTermPrimitiveWidget)w)->term.tpd->utmpId);
662
663     return(pid);
664 }