2 * CDE - Common Desktop Environment
4 * Copyright (c) 1993-2012, The Open Group. All rights reserved.
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)
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
18 * You should have received a copy of the GNU Lesser General Public
19 * License along with these libraries and programs; if not, write
20 * to the Free Software Foundation, Inc., 51 Franklin Street, Fifth
21 * Floor, Boston, MA 02110-1301 USA
24 * File: spc-exec.c $TOG: spc-exec.c /main/9 1998/10/26 17:22:38 mgreess $
27 * (c) Copyright 1988, Hewlett-Packard Company, all rights reserved.
29 * (c) Copyright 1993, 1994 Hewlett-Packard Company *
30 * (c) Copyright 1993, 1994 International Business Machines Corp. *
31 * (c) Copyright 1993, 1994 Sun Microsystems, Inc. *
32 * (c) Copyright 1993, 1994 Novell, Inc. *
35 #include <bms/sbport.h> /* NOTE: sbport.h must be the first include. */
40 #include <sys/param.h>
46 #include <bms/MemoryMgr.h>
48 #include <SPC/spc-proto.h>
55 #include "DtSvcLock.h"
59 SPC_Connection_Ptr write_terminator=NULL, read_terminator=NULL;
62 * Forward declarations
64 static char *get_path_from_context (
66 static int remove_variable(
68 static void resolve_variable_reference(
71 Boolean _path_search (
72 XeString path, XeString filename, path_search_predicate p); /* XXX */
75 * This array contains the process id's of the sub-processes
76 * started by the daemon. When a sub-process terminates, its
77 * entry will be set to SPCD_DEAD_PROCESS. This list of pid's
78 * is kept beause when the exit timer expires, if the daemon
79 * has no sub-processes running, it will exit.
81 pid_t *SPC_pid_list = NULL;
84 * This global variable is set by the daemon when the client
87 int SPC_client_version_number = SPC_PROTOCOL_VERSION;
90 * If this variable is not NULL, it will contain the name of
91 * the mount point environment variable plus its value thus it
94 char *SPC_mount_point_env_var = NULL;
96 /* External definitions */
98 extern XeChar spc_logging;
99 extern XeString *environ;
102 * Routines for handling Sub-Processes
107 ** Initialize synchronous terminators.
111 /*----------------------------------------------------------------------+*/
113 SPC_Setup_Synchronous_Terminator(void)
114 /*----------------------------------------------------------------------+*/
119 if(write_terminator) {
120 _DtSvcProcessUnlock();
125 SPC_Error(SPC_No_Pipe);
126 _DtSvcProcessUnlock();
130 if((write_terminator=SPC_Alloc_Connection())==SPC_ERROR) {
131 _DtSvcProcessUnlock();
134 SPC_Add_Connection(write_terminator);
136 if((read_terminator=SPC_Alloc_Connection())==SPC_ERROR) {
137 _DtSvcProcessUnlock();
140 SPC_Add_Connection(read_terminator);
142 write_terminator->sid=pipes[WRITE_SIDE];
143 write_terminator->connected=TRUE;
145 read_terminator->sid=pipes[READ_SIDE];
146 read_terminator->connected=TRUE;
147 SPC_XtAddInput(NULL, &read_terminator->termination_id, read_terminator->sid,
148 SPC_Conditional_Packet_Handler, SPC_Terminator);
150 _DtSvcProcessUnlock();
154 /*----------------------------------------------------------------------+*/
155 SPC_Connection_Ptr SPC_Channel_Terminator_Connection(SPC_Channel_Ptr channel)
156 /*----------------------------------------------------------------------+*/
158 if(IS_REMOTE(channel))
159 return(channel->connection);
161 return(read_terminator);
164 /*----------------------------------------------------------------------+*/
165 void SPC_Close_Unused(void)
166 /*----------------------------------------------------------------------+*/
168 /* Close any and all unused file descriptors */
171 for (fd = STDERR + 1; fd < max_fds; fd++) spc_close(fd);
174 /*----------------------------------------------------------------------+*/
176 SPC_MakeSystemCommand(SPC_Channel_Ptr channel)
177 /*----------------------------------------------------------------------+*/
183 XeChar newargtwo[_POSIX_ARG_MAX];
184 int argtwolen=0, tmplen=0;
186 /* Allocate our memory up front */
190 newargtwo[argtwolen]=0;
192 /* copy path into newargtwo */
194 strncat(newargtwo, channel->path, _POSIX_ARG_MAX-1);
195 strcat(newargtwo, (XeString)" ");
196 argtwolen=strlen(newargtwo);
198 /* copy argv into newargtwo */
199 for(tmp_argv=channel->argv; tmp_argv && *tmp_argv; tmp_argv++) {
200 tmplen=strlen(*tmp_argv)+1; /* Room for extra space */
201 if((tmplen+argtwolen)<_POSIX_ARG_MAX-1) {
202 strcat(newargtwo, *tmp_argv);
203 strcat(newargtwo, (XeString)" ");
208 errbuf = malloc(sizeof(XeChar) * 100);
212 sprintf(errbuf,"(%d chars), max. length is %d",tmplen,_POSIX_ARG_MAX);
213 SPC_Error(SPC_Arg_Too_Long, tmp_argv, _POSIX_ARG_MAX);
221 First use the value of $SB_SHELL (if any),
223 then use DEFAULT_SHELL
226 if(!(shell=getenv((XeString)"SB_SHELL")))
227 if(!(shell=getenv((XeString)"SHELL")))
228 shell = DEFAULT_SHELL;
230 /* setup argv properly */
232 argv[0]=SPC_copy_string(shell);
233 argv[1]=SPC_copy_string((XeString)"-c");
234 argv[2]=SPC_copy_string(newargtwo);
236 channel->argv = argv;
237 channel->IOMode |= SPCIO_DEALLOC_ARGV;
239 /* Now set this shell as the path */
241 channel->path = shell;
247 * Routines for handling child process termination
250 /*----------------------------------------------------------------------+*/
251 /* This is the right way according to the Spec 1170 */
252 void SPC_Child_Terminated(int i)
253 /*----------------------------------------------------------------------+*/
255 /* This catches signals for sub-process termination */
256 int type, cause, status;
258 SPC_Channel_Ptr channel;
259 protocol_request req, *prot;
260 buffered_data data, *pdata;
263 int saved_errno = errno;
271 while((pid = waitpid(wait_pid, &status, WNOHANG))) {
272 if((pid == -1 && errno == ECHILD) || pid == 0) {
273 /* no more children. Return */
278 /* Okay, we got the process ID of a terminated child. Find the
279 channel associated with this PID. */
280 channel=SPC_Find_PID(pid);
282 fprintf(stderr, (XeString)"got SIGCHLD, pid: %d, channel: %p\n", pid, channel);
291 * Look for this process in the pid list. If found, mark it
294 if (SPC_pid_list != NULL) {
295 for (indx=0; SPC_pid_list[indx] != 0; indx++)
296 if (SPC_pid_list[indx] == pid) {
297 SPC_pid_list[indx] = SPCD_DEAD_PROCESS;
301 _DtSvcProcessUnlock();
303 /* We have the channel. Mark it as being closed. */
305 channel->status = status;
307 /* If we this channel is set up for synchronous termination,
308 write the protocol request to record that this guy died.
309 Otherwise, call the termination handler directly. */
311 if(IS_SPCIO_SYNC_TERM(channel->IOMode)) {
313 /* This code is basically what SPC_Write_Protocol_Request does.
314 It is replicated here because a call to SPC_W_P_R would have
315 to be re-enterant if we called it here, and SPC_W_P_R is not
316 re-enterant at this time. */
318 SPC_Reset_Protocol_Ptr(prot, channel, APPLICATION_DIED, 0);
319 pdata->len=WRITE_APPLICATION_DIED(pdata, status);
320 length=WRITE_HEADER(pdata, channel->cid,
324 pdata->data[length]=(XeChar)' ';
325 length=pdata->len+REQUEST_HEADER_LENGTH;
326 if(write(write_terminator->sid, pdata->data, length)==ERROR)
327 SPC_Error(SPC_Internal_Error);
328 pdata->offset=REQUEST_HEADER_LENGTH;
329 print_protocol_request((XeString) (XeString)" <-- INTERNAL APPLICATION_DIED", prot);
332 SPC_Change_State(channel, 0, -1, 0);
333 if(channel->Terminate_Handler) {
334 XeSPCGetProcessStatus(channel, &type, &cause);
335 (* channel->Terminate_Handler)
336 (channel, channel->pid, type, cause, channel->Terminate_Data);
339 /* Loop around & get another PID */
346 *** Check to see if a given path names an executable file. Thus, we
347 *** need to know if the file exists. If it is a directory, we want
348 *** to fail. Otherwise, we want to us the system rules for checking
349 *** on the file, and thus the 'access' call.
353 static Boolean executable_predicate(XeString path, XeString dir, XeString file)
356 struct stat file_status;
361 if(stat(path, &file_status) != 0)
364 if(S_ISDIR(file_status.st_mode))
367 return(access(path, X_OK | F_OK) == 0);
370 /*----------------------------------------------------------------------+*/
371 int exec_proc_local_channel_object(SPC_Channel_Ptr channel)
372 /*----------------------------------------------------------------------+*/
374 sigset_t newsigmask, oldsigmask;
378 XeString dir = XeString_NULL;
380 int i, reuse_pid = 0;
382 call_parent_method(channel, exec_proc, (channel), result);
384 if(result==SPC_ERROR)
387 /* Check to see if the channel pathname points to a valid executable.
388 We do this by using the _path_search function. If the channel
389 has a PATH variable set in its local environment, use it,
390 otherwise use the "global" environment. We can accomplish this
391 by using the spc_getenv call in the _path_search call. If the
392 channel doesn't have a PATH variable, then spc_getenv will
393 return NULL, which indicates use of the global environment.
396 if(!_path_search(SPC_Getenv("PATH", channel->envp),
398 executable_predicate)) {
399 SPC_Error(SPC_Cannot_Exec, channel->path);
403 /* If we were passed a host:dir to cd to, make sure it exists. */
404 /* We want to do this before we fork the child. */
406 if((channel->context_dir) && (channel->context_dir[0])) {
407 struct stat stat_info;
411 if (SPC_client_version_number < SPC_PROTOCOL_VERSION_CDE_BASE)
412 dir = get_path_from_context(channel->context_dir);
415 * context_dir is actually a "netfile" so it needs to
416 * be converted to a "real" path.
418 dir = (char *) tt_netfile_file (channel->context_dir);
419 if (tt_ptr_error (dir) != TT_OK)
422 _DtSvcProcessUnlock();
425 /* can't make connection ... */;
426 else if (stat(dir,&stat_info) != 0)
427 /* directory not there */;
429 else if ((stat_info.st_mode & S_IFDIR) == 0)
430 /* path is not a directory ... */;
434 if (!ok && IS_SPCIO_FORCE_CONTEXT(channel->IOMode)) {
435 if (dir != NULL && (strcmp (dir, channel->context_dir) != 0))
436 SPC_Error(SPC_cannot_Chdir, dir);
437 SPC_Error(SPC_cannot_Chdir, channel->context_dir);
444 if(mempf0(channel, pre_fork)==SPC_ERROR) {
449 /* When using HP NLIO (xj0input) we have a problem. Namely, */
450 /* the xj0 processs uses signal() to deal with SIGCLD which */
451 /* is incompatible with sigaction/sigprogmask/etc. Even */
452 /* though xj0 resets the signal handler, since the signal */
453 /* routines are incompatible, our original handler gets lost. */
454 /* Hence, we need to reset it. We do it here everytime we */
455 /* fork a child just to be on the safe side. */
457 SPC_ResetTerminator();
459 sigemptyset(&newsigmask);
460 sigemptyset(&oldsigmask);
461 sigaddset(&newsigmask, SIGCHLD);
463 if (sigprocmask(SIG_BLOCK, &newsigmask, &oldsigmask) == ERROR) {
468 pid = channel->pid = fork();
471 * Must save this pid so that when the daemon's timer goes off,
472 * if there has been no activity and there are no sub-processes
473 * running, the daemon can exit.
477 if (SPC_pid_list == NULL)
479 * Create the first block plus the NULL terminator.
481 SPC_pid_list = (pid_t *) malloc (2 * sizeof (pid_t));
484 * If a dead pid entry exists, reuse it; otherwise, must create
485 * room for the new pid.
487 for (i = 0; SPC_pid_list[i] != 0; i++)
488 if (SPC_pid_list[i] == SPCD_DEAD_PROCESS) {
489 SPC_pid_list[i] = pid;
494 SPC_pid_list = (pid_t *) realloc (SPC_pid_list,
495 (i+2) * sizeof (pid_t));
498 SPC_pid_list[i] = pid;
499 SPC_pid_list[i+1] = 0;
501 _DtSvcProcessUnlock();
506 /* Did we really fork? */
508 SPC_Error(SPC_Cannot_Fork);
511 /* Do any set up for the parent process here */
512 mempf1(channel, post_fork, pid);
516 /* Reinstate the old signal mask (unblock SIGCLD). */
518 sigprocmask(SIG_SETMASK, &oldsigmask, (sigset_t *)NULL);
522 /* Child process: connect wires, make environment and exec sub-process */
524 sigprocmask(SIG_SETMASK, &oldsigmask, (sigset_t *)NULL);
526 /* Make sure the child is the process group leader. In the case of
527 ptys, we also want to break the current terminal affiliation.
528 We want to be the process group leader so XeSPCKillProcess (which
529 does a kill(-pid, 9)) will kill all processes associated with us.
531 For PTY's, we need to break the terminal affiliation so the next
532 open (which will be a pty) will cause us to become affiliated with
533 the pty. We do this so when the parent process closes the master
534 side of the pty, the slave side processes get SIGHUP. If they
535 ignore SIGHUP, they will never die. So it goes...
538 if(IS_SPCIO_PTY(channel->IOMode))
541 pid_t tmppid = getpid();
542 if(setpgid(tmppid, tmppid) == -1)
543 fprintf(stderr, (XeString)"setpgid failed, errno: %d\n", errno);
546 /* Connect wires to sub-process standard files */
547 result=mempf1(channel, post_fork, pid);
549 if(result!=SPC_ERROR) {
555 * Before adding in the list of environment variables
556 * from the environment variable files, must search
557 * the list for LANG definitions. If found, the
558 * last definition must be putenv'ed to assure the
559 * multi-byte parsing code is using the correct locale.
561 for (i = 0, ppch = channel->envp; *ppch; *ppch++, i++)
562 if (!strncmp (*ppch, "LANG=", 5))
566 resolve_variable_reference (&channel->envp[indx]);
569 if (!setlocale (LC_CTYPE, ""))
571 * setlocale failed - log a message but execute
572 * the command anyway.
574 if (SPC_Print_Protocol != NULL)
575 (void) fprintf(SPC_Print_Protocol,
576 "+++> Failed to 'setlocale'; LANG = %s\n",
578 /* Mix in the stashed environment */
580 for(envp=channel->envp; *envp; envp++)
581 resolve_variable_reference(&*envp);
583 if (SPC_mount_point_env_var != NULL)
585 * The mount point environment variable was
586 * inherited by the daemon or was given to the
587 * daemon via the command line. In either case
588 * this subprocess must inherit the daemon's
591 (void) putenv (SPC_mount_point_env_var);
592 _DtSvcProcessUnlock();
594 /* Connect to the context directory */
595 /* We have already validated this guy exists */
603 if(result!=SPC_ERROR) {
605 /* Compiler barfs without cast ? */
606 #if defined(__hpux_8_0) || defined(__aix)
607 result=execvp(channel->path, channel->argv);
609 result=execvp(channel->path, channel->argv);
611 /* If we return from exec, it failed */
612 SPC_Error(SPC_Cannot_Exec, channel->path);
615 /* We want to get rid of this child image (carefully) */
622 /****************************************************************************
624 * get_path_from_context - given a 'context' string in the following form:
628 * return the path component.
630 * NOTE - the caller must free the returned string.
634 * char *context - the context string to parse
638 * A NULL if a pathname cannot be constructed.
640 ****************************************************************************/
642 static char *get_path_from_context (
647 char *netfile = NULL;
649 char tmp[MAXPATHLEN];
653 * Break context into its host and file parts.
658 (void) strcpy (tmp, context);
661 if ((pch = (char *) strchr (tmp, ':')) != NULL) {
668 return (strdup (file));
670 netfile = (char *) tt_host_file_netfile (host, file);
671 if (tt_ptr_error (netfile) != TT_OK) {
672 SPC_Error (SPC_Cannot_Create_Netfilename, context, host);
676 path = (char *) tt_netfile_file (netfile);
678 if (tt_ptr_error (path) != TT_OK) {
679 SPC_Error (SPC_Cannot_Create_Netfilename, context, host);
687 /**************************************************************************
691 * This takes a string of the format:
693 * var_name=some_value | <remove_variable_keyword> var_name
695 * and if the second form is found, 'var_name' is removed from
700 * char *string see the format above.
704 * int 0 if 'string' contains a 'remove variable' command
705 * 1 if string does not contain a 'remove variable' command
709 * char **environ 'var_name' is removed from the environment
711 **************************************************************************/
724 * If string contains some white space before the variable
725 * or keyword, skip the white space.
730 ((mblen (pch, MB_CUR_MAX) == 1)) &&
732 isspace ((u_char)*pch))
737 if (strncmp (pch, SPC_REMOVE_VAR, strlen (SPC_REMOVE_VAR)))
741 * Skip the white space after the keyword and move to the
742 * beginning of the variable.
744 pch = pch + strlen (SPC_REMOVE_VAR);
747 ((mblen (pch, MB_CUR_MAX) == 1)) &&
749 isspace ((u_char)*pch))
756 * pch should now point to the variable to be removed.
758 * tmp_var will be equal to the variable with a trailing '='.
759 * This is added so the future comparison will not match
760 * against variables that start with 'tmp_var' but then
761 * have something else.
763 tmp_var = malloc ((strlen (pch) + 2) * sizeof (char));
764 (void) sprintf (tmp_var, "%s=", pch);
765 tmp_len = strlen (tmp_var);
768 * Scan 'environ' for 'tmp_var'
770 for (ppch = environ; *ppch; *ppch++) {
771 if (!strncmp (tmp_var, *ppch, tmp_len)) {
773 * Found the variable so remove it by moving all
774 * variables after *ppch up.
776 for (ppch2 = ppch; *ppch2; *ppch2++) {
784 free ((char *) tmp_var);
789 /**************************************************************************
791 * resolve_variable_reference ()
793 * This function takes a string of the format:
795 * var_name=some_value
797 * and if 'some_value' contains a reference to an environment
798 * variable, the reference is replaced with the value of the
801 * For example, if 'string' is
805 * and $VFA_TOP is '/some_tree', then 'string' will be changed to:
811 * o 'putenv' will be invoked with argument 'string', even if 'string'
814 * o A valid variable name consists of alphanumerics and underscore
818 * char **string MODIFIED - the environment variable
819 * definition to be parsed
825 **************************************************************************/
828 resolve_variable_reference(
829 char **string ) /* MODIFIED */
835 int n; /* number of bytes in a string */
840 char *pbeg = *string;
841 char *string_start = *string;
843 if (!remove_variable (*string))
853 * Look for '$' - the beginning of a variable reference.
854 * If a '$' character is not found, exit the loop
859 if (((len = mblen (pch, MB_CUR_MAX)) == 1) && (*pch == '$'))
863 * Move past this char
869 * pch is null or it points to an invalid mulit-byte
875 pch = strchr (pch, '$');
878 if (pch == NULL || *pch == '\000')
880 * The string doesn't contain any (more) variables
884 string_start = *string;
887 * Found a '$' - the beginning of an environment variable so
888 * skip it and check the next char for '{' and if it is found
889 * skip it and move to the real beginning of an env variable.
896 if ((mblen (pch, MB_CUR_MAX) == 1) && (*pch == '{')) {
900 pch++; /* input = ${ */
903 if (*pch == '\0') /* input = $\0 or ${\0 */
907 * Find the end of the variable name - it is assumed to
908 * be the first character that is not an alpha-numeric
916 if ((mblen (pch, MB_CUR_MAX) > 1) ||
917 ((mblen (pch, MB_CUR_MAX) == 1) &&
918 ((*pch == '_') || (isalnum (*pch))))) {
920 if (isalnum (*pch) || *pch == '_') {
924 len = mblen (pch, MB_CUR_MAX);
934 if (found_brace && (mblen (pch, MB_CUR_MAX) == 1) && (*pch == '}'))
936 if (found_brace && *pch == '}')
939 * Move past the closing brace
947 * Nothing 'recognizable' follows the $ or ${
954 * Stuff the environment variable name in 'variable' and then
955 * get its value. If the variable doesn't exist, leave its
956 * name in the string.
958 (void) strncpy (variable, var_start, n);
961 if ((value = getenv (variable)) == NULL) {
963 * Leave what looks like an environment variable in place.
969 if (strlen (value) == 0) {
975 * Need to replace the variable definition with the string
976 * pointed to by 'value'. So create a string that contains the
977 * characters before the environment variable, then the contents
978 * of 'value' and finally, the characters after the environment
981 if (string_end == string_start)
983 * There is nothing to prepend before 'value'.
987 (void) strncpy (buf, string_start, (string_end - string_start));
988 buf[(string_end - string_start)] = '\0';
990 (void) strcat (buf, value);
993 (void) strcat (buf, pch);
996 * Now put 'buf' into 'string'.
998 *string = realloc (*string, strlen (buf) + 1);
999 (void) strcpy (*string, buf);
1000 pch = *string + len;
1005 * Even if no substitutions were made, the variable must
1006 * be put in the environment.
1008 (void) putenv (*string);