Convert uses of XKeycodeToKeysym (deprecated) to XkbKeycodeToKeysym
[oweals/cde.git] / cde / lib / DtSvc / DtEncap / spc-exec.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 libraries 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 /*
24  * File:         spc-exec.c $TOG: spc-exec.c /main/9 1998/10/26 17:22:38 mgreess $
25  * Language:     C
26  *
27  * (c) Copyright 1988, Hewlett-Packard Company, all rights reserved.
28  *
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.                                *
33  */
34
35 #include <bms/sbport.h> /* NOTE: sbport.h must be the first include. */
36 #include <ctype.h>
37 #include <errno.h>
38 #include <signal.h>
39 #include <sys/wait.h>
40 #include <sys/param.h>
41 #include <limits.h>
42 #include <stdlib.h>
43 #include <locale.h>
44
45 #include <SPC/spcP.h>
46 #include <bms/MemoryMgr.h> 
47
48 #include <SPC/spc-proto.h>
49
50 #ifdef SVR4
51 #include <unistd.h>
52 #endif
53
54 #include <Tt/tt_c.h>
55 #include "DtSvcLock.h"
56
57 /* Global vars. */
58
59 SPC_Connection_Ptr write_terminator=NULL, read_terminator=NULL;
60
61 /*
62  * Forward declarations
63  */
64 static char *get_path_from_context ( 
65         char            *context );
66 static int remove_variable(
67         char            *string );
68 static void resolve_variable_reference(
69         char            **string );
70
71 Boolean _path_search (
72         XeString path, XeString filename, path_search_predicate p); /* XXX */
73
74 /*
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.
80  */
81 pid_t *SPC_pid_list = NULL;
82
83 /*
84  * This global variable is set by the daemon when the client
85  * connects.
86  */
87 int SPC_client_version_number = SPC_PROTOCOL_VERSION;
88
89 /*
90  * If this variable is not NULL, it will contain the name of 
91  * the mount point environment variable plus its value thus it
92  * ready for 'putenv'.
93  */
94 char *SPC_mount_point_env_var = NULL;
95
96 /* External definitions */
97
98 extern XeChar spc_logging;
99 extern XeString *environ;
100
101 /*
102  * Routines for handling Sub-Processes
103  */
104
105 /*
106  **
107  ** Initialize synchronous terminators.
108  **
109 */
110
111 /*----------------------------------------------------------------------+*/
112 int
113 SPC_Setup_Synchronous_Terminator(void)
114 /*----------------------------------------------------------------------+*/
115 {
116   int pipes[2];
117
118   _DtSvcProcessLock();
119   if(write_terminator) {
120     _DtSvcProcessUnlock();
121     return(TRUE);
122   }
123   
124   if(pipe(pipes)<0) {
125     SPC_Error(SPC_No_Pipe);
126     _DtSvcProcessUnlock();
127     return(SPC_ERROR);
128   }
129
130   if((write_terminator=SPC_Alloc_Connection())==SPC_ERROR) {
131     _DtSvcProcessUnlock();
132     return(SPC_ERROR);
133   }
134   SPC_Add_Connection(write_terminator);
135   
136   if((read_terminator=SPC_Alloc_Connection())==SPC_ERROR) {
137     _DtSvcProcessUnlock();
138     return(SPC_ERROR);
139   }
140   SPC_Add_Connection(read_terminator);
141
142   write_terminator->sid=pipes[WRITE_SIDE];
143   write_terminator->connected=TRUE;
144
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);
149   
150   _DtSvcProcessUnlock();
151   return(TRUE);
152 }
153
154 /*----------------------------------------------------------------------+*/
155 SPC_Connection_Ptr SPC_Channel_Terminator_Connection(SPC_Channel_Ptr channel)
156 /*----------------------------------------------------------------------+*/
157 {
158   if(IS_REMOTE(channel))
159     return(channel->connection);
160   else
161     return(read_terminator);
162 }
163   
164 /*----------------------------------------------------------------------+*/
165 void SPC_Close_Unused(void)
166 /*----------------------------------------------------------------------+*/
167 {
168   /* Close any and all unused file descriptors */
169   int fd;
170  
171   for (fd = STDERR + 1; fd < max_fds; fd++) spc_close(fd);
172 }
173
174 /*----------------------------------------------------------------------+*/
175 int
176 SPC_MakeSystemCommand(SPC_Channel_Ptr channel)
177 /*----------------------------------------------------------------------+*/
178 {
179
180   XeString shell;
181   XeString *argv;
182   XeString *tmp_argv;
183   XeChar newargtwo[_POSIX_ARG_MAX];
184   int argtwolen=0, tmplen=0;
185   
186   /* Allocate our memory up front */
187
188   argv=Alloc_Argv(4);
189
190   newargtwo[argtwolen]=0;
191
192   /* copy path into newargtwo */
193   
194   strncat(newargtwo, channel->path, _POSIX_ARG_MAX-1);
195   strcat(newargtwo, (XeString)" ");
196   argtwolen=strlen(newargtwo);
197   
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)" ");
204       argtwolen += tmplen;
205     } else {
206       XeChar *errbuf;
207
208       errbuf = malloc(sizeof(XeChar) * 100);
209       if (errbuf)
210       {
211         SPC_Free_Envp(argv);
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);
214         free(errbuf);
215       }
216       return(SPC_ERROR);
217     }
218   }
219        
220   /* get a shell --
221      First use the value of $SB_SHELL (if any), 
222      then try $SHELL,
223      then use DEFAULT_SHELL
224      */
225
226   if(!(shell=getenv((XeString)"SB_SHELL")))
227     if(!(shell=getenv((XeString)"SHELL")))
228       shell = DEFAULT_SHELL;
229   
230   /* setup argv properly */
231   
232   argv[0]=SPC_copy_string(shell);
233   argv[1]=SPC_copy_string((XeString)"-c");
234   argv[2]=SPC_copy_string(newargtwo);
235   argv[3]=NULL;
236   channel->argv = argv;
237   channel->IOMode |= SPCIO_DEALLOC_ARGV;
238   
239   /* Now set this shell as the path */
240
241   channel->path = shell;
242   
243   return(TRUE);
244 }
245
246 /*
247  * Routines for handling child process termination
248  */
249
250 /*----------------------------------------------------------------------+*/
251 /* This is the right way according to the Spec 1170 */
252 void SPC_Child_Terminated(int i)
253 /*----------------------------------------------------------------------+*/
254 {
255   /* This catches signals for sub-process termination */
256   int type, cause, status;
257   pid_t wait_pid, pid;
258   SPC_Channel_Ptr channel;
259   protocol_request     req, *prot;
260   buffered_data        data, *pdata;
261   int length;
262   int indx;
263   int saved_errno = errno;
264
265   prot  = (&req);
266   pdata = (&data);
267
268   prot->dataptr=pdata;
269
270   wait_pid = -1;
271   while((pid = waitpid(wait_pid, &status, WNOHANG))) {
272     if((pid == -1 && errno == ECHILD) || pid == 0) {
273       /* no more children.  Return */
274       errno = saved_errno;
275       return;
276     }
277     
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);
281 #ifdef DEBUG
282     fprintf(stderr, (XeString)"got SIGCHLD, pid: %d, channel: %p\n", pid, channel);
283 #endif
284
285     if(!channel) {
286       continue;
287     }
288
289     _DtSvcProcessLock();
290     /*
291      * Look for this process in the pid list.  If found, mark it
292      * as done.
293      */
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;
298            break;
299         }
300     }
301     _DtSvcProcessUnlock();
302
303     /* We have the channel.  Mark it as being closed. */
304
305     channel->status     = status;
306
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. */
310     
311     if(IS_SPCIO_SYNC_TERM(channel->IOMode)) {
312
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. */
317       
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,
321                           prot->request_type,
322                           pdata->len,
323                           0);
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);
330     }
331     else {
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);
337       }
338     }
339     /* Loop around & get another PID */
340   }
341   errno = saved_errno;
342 }
343
344 /*
345  ***
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.
350  ***
351 */
352
353 static Boolean executable_predicate(XeString path, XeString dir, XeString file)
354 {
355
356   struct stat file_status;
357   
358   dir=dir;
359   file=file;
360
361   if(stat(path, &file_status) != 0)
362     return(FALSE);
363   
364   if(S_ISDIR(file_status.st_mode))
365     return(FALSE);
366
367   return(access(path, X_OK | F_OK) == 0);
368 }
369
370 /*----------------------------------------------------------------------+*/
371 int exec_proc_local_channel_object(SPC_Channel_Ptr channel)
372 /*----------------------------------------------------------------------+*/
373 {
374     sigset_t newsigmask, oldsigmask;
375     pid_t pid;
376     int result;
377     XeString *envp;
378     XeString dir = XeString_NULL;
379     int retval;
380     int i, reuse_pid = 0;
381     
382     call_parent_method(channel, exec_proc, (channel), result);
383     
384     if(result==SPC_ERROR)
385         return(SPC_ERROR);
386
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.
394    */
395
396     if(!_path_search(SPC_Getenv("PATH", channel->envp),
397                     channel->path,
398                     executable_predicate)) {
399       SPC_Error(SPC_Cannot_Exec, channel->path);
400       return(SPC_ERROR);
401     }
402        
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.                */
405
406     if((channel->context_dir) && (channel->context_dir[0])) {
407         struct stat     stat_info;
408         Boolean ok = FALSE;
409         
410         _DtSvcProcessLock();
411         if (SPC_client_version_number < SPC_PROTOCOL_VERSION_CDE_BASE)
412            dir = get_path_from_context(channel->context_dir);
413         else {
414            /*
415             * context_dir is actually a "netfile" so it needs to
416             * be converted to a "real" path.
417             */
418            dir = (char *) tt_netfile_file (channel->context_dir);
419            if (tt_ptr_error (dir) != TT_OK) 
420               dir = NULL;
421         }
422         _DtSvcProcessUnlock();
423
424         if (dir == NULL)
425             /* can't make connection ... */;
426         else if (stat(dir,&stat_info) != 0)     
427             /* directory not there */;
428
429         else if ((stat_info.st_mode & S_IFDIR) == 0)
430             /* path is not a directory ... */;
431         else
432             ok = TRUE;
433         
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);
438             XeFree(dir);
439             dir = XeString_NULL;
440             return(SPC_ERROR);
441         }
442     }
443
444     if(mempf0(channel, pre_fork)==SPC_ERROR) {
445         XeFree(dir);
446         return(SPC_ERROR);
447     }
448
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.                  */
456
457     SPC_ResetTerminator();
458     
459     sigemptyset(&newsigmask);
460     sigemptyset(&oldsigmask);
461     sigaddset(&newsigmask, SIGCHLD);
462     
463     if (sigprocmask(SIG_BLOCK, &newsigmask, &oldsigmask) == ERROR) {
464         XeFree(dir);
465         return(SPC_ERROR);
466     }
467
468     pid = channel->pid = fork();
469     
470     /*
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.
474      */
475     i = 0;
476     _DtSvcProcessLock();
477     if (SPC_pid_list == NULL)
478       /*
479        * Create the first block plus the NULL terminator.
480        */
481       SPC_pid_list = (pid_t *) malloc (2 * sizeof (pid_t));
482     else {
483       /*
484        * If a dead pid entry exists, reuse it; otherwise, must create 
485        * room for the new pid.
486        */
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;
490           reuse_pid = 1;
491           break;
492         }
493       if (!reuse_pid)
494         SPC_pid_list = (pid_t *) realloc (SPC_pid_list, 
495                                            (i+2) * sizeof (pid_t));
496     }
497     if (!reuse_pid) {
498       SPC_pid_list[i] = pid;
499       SPC_pid_list[i+1] = 0;
500     }
501     _DtSvcProcessUnlock();
502
503     if (pid) {
504         XeFree(dir);
505         
506         /* Did we really fork? */
507         if (pid == ERROR) {
508           SPC_Error(SPC_Cannot_Fork);
509           retval = SPC_ERROR;
510         } else {
511           /* Do any set up for the parent process here */
512           mempf1(channel, post_fork, pid);
513           retval = TRUE;
514         }
515         
516         /* Reinstate the old signal mask (unblock SIGCLD). */
517         
518         sigprocmask(SIG_SETMASK, &oldsigmask, (sigset_t *)NULL);
519         return(retval);
520     }
521     else {
522         /* Child process: connect wires, make environment and exec sub-process */
523         
524         sigprocmask(SIG_SETMASK, &oldsigmask, (sigset_t *)NULL);
525         
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.
530            
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...
536            */
537
538         if(IS_SPCIO_PTY(channel->IOMode))
539             setsid();
540         else {
541             pid_t tmppid = getpid();
542             if(setpgid(tmppid, tmppid) == -1) 
543                 fprintf(stderr, (XeString)"setpgid failed, errno: %d\n", errno);
544         }
545         
546         /* Connect wires to sub-process standard files */
547         result=mempf1(channel, post_fork, pid);
548         
549         if(result!=SPC_ERROR) {
550             int         indx = -1;
551             int         i;
552             char        **ppch;
553
554             /*
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.
560              */
561             for (i = 0, ppch = channel->envp; *ppch; *ppch++, i++)
562                if (!strncmp (*ppch, "LANG=", 5))
563                   indx = i;
564
565             if (indx != -1)
566                resolve_variable_reference (&channel->envp[indx]);
567
568             _DtSvcProcessLock();
569             if (!setlocale (LC_CTYPE, ""))
570                /*
571                 * setlocale failed - log a message but execute 
572                 * the command anyway.
573                 */ 
574                if (SPC_Print_Protocol != NULL)
575                   (void) fprintf(SPC_Print_Protocol,
576                       "+++> Failed to 'setlocale'; LANG = %s\n",
577                       getenv("LANG"));
578             /* Mix in the stashed environment */
579             
580             for(envp=channel->envp; *envp; envp++)
581                 resolve_variable_reference(&*envp);
582
583             if (SPC_mount_point_env_var != NULL)
584                 /*
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
589                  * value.
590                  */
591                 (void) putenv (SPC_mount_point_env_var);
592             _DtSvcProcessUnlock();
593
594             /* Connect to the context directory */
595             /* We have already validated this guy exists */
596
597             if(dir) 
598                 Xechdir(dir);
599         }
600         
601         XeFree(dir);
602
603         if(result!=SPC_ERROR) {
604             /* Execute */
605             /* Compiler barfs without cast ? */
606 #if defined(__hpux_8_0) || defined(__aix)
607             result=execvp(channel->path, channel->argv);
608 #else
609             result=execvp(channel->path, channel->argv);
610 #endif
611             /* If we return from exec, it failed */
612             SPC_Error(SPC_Cannot_Exec, channel->path);
613         }
614         
615         /* We want to get rid of this child image (carefully) */
616         _exit(42);
617         
618     }
619 }
620
621
622 /****************************************************************************
623  *
624  * get_path_from_context - given a 'context' string in the following form:
625  * 
626  *     [host:]path
627  *
628  * return the path component.
629  *
630  * NOTE - the caller must free the returned string.
631  * 
632  * Parameters:
633  *
634  *    char      *context        - the context string to parse
635  *
636  * Return Value:
637  *
638  *    A NULL if a pathname cannot be constructed.
639  *
640  ****************************************************************************/
641
642 static char *get_path_from_context (
643         char            *context)
644 {
645    char                 *host = NULL;
646    char                 *file = NULL;
647    char                 *netfile = NULL;
648    char                 *path = NULL;
649    char                 tmp[MAXPATHLEN];
650    char                 *pch;
651
652    /*
653     * Break context into its host and file parts.
654     */
655    if (context == NULL)
656       return (NULL);
657
658    (void) strcpy (tmp, context);
659    file = tmp;
660
661    if ((pch = (char *) strchr (tmp, ':')) != NULL) {
662       host = tmp;
663       file = pch + 1;
664       *pch = '\000';
665    }
666
667    if (!host)
668       return (strdup (file));
669
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);
673       return (NULL);
674    }
675
676    path = (char *) tt_netfile_file (netfile);
677    tt_free (netfile);
678    if (tt_ptr_error (path) != TT_OK) {
679       SPC_Error (SPC_Cannot_Create_Netfilename, context, host);
680       return (NULL);
681    }
682
683    return (path);
684 }
685
686
687 /**************************************************************************
688  *
689  * remove_variable ()
690  *
691  * This takes a string of the format:
692  *
693  *    var_name=some_value | <remove_variable_keyword> var_name
694  *  
695  * and if the second form is found, 'var_name' is removed from
696  * the environment.
697  *
698  * Parameters:
699  *
700  *    char      *string         see the format above.
701  *
702  * Return Value: 
703  *
704  *    int       0 if 'string' contains a 'remove variable' command
705  *              1 if string does not contain a 'remove variable' command
706  *
707  * Modified:
708  *
709  *    char      **environ       'var_name' is removed from the environment
710  *
711  **************************************************************************/
712
713 static int
714 remove_variable(
715         char            *string)
716 {
717    char         *pch;
718    char         **ppch;
719    char         **ppch2;
720    char         *tmp_var;
721    int          tmp_len;
722
723    /*
724     * If string contains some white space before the variable
725     * or keyword, skip the white space.
726     */
727    pch = string;
728    while (
729 #ifdef NLS16
730           ((mblen (pch, MB_CUR_MAX) == 1)) &&
731 #endif  /* NLS16 */
732           isspace ((u_char)*pch))
733              pch++;
734
735    if (*pch == '\000') 
736       return (1);
737    if (strncmp (pch, SPC_REMOVE_VAR, strlen (SPC_REMOVE_VAR)))
738       return (1);
739
740    /*
741     * Skip the white space after the keyword and move to the
742     * beginning of the variable.
743     */
744    pch = pch + strlen (SPC_REMOVE_VAR);
745    while (
746 #ifdef NLS16
747           ((mblen (pch, MB_CUR_MAX) == 1)) &&
748 #endif  /* NLS16 */
749           isspace ((u_char)*pch))
750              pch++;
751
752    if (*pch == '\000') 
753       return (1);
754
755    /*
756     * pch should now point to the variable to be removed.
757     *
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.
762     */
763    tmp_var = malloc ((strlen (pch) + 2) * sizeof (char));
764    (void) sprintf (tmp_var, "%s=", pch);
765    tmp_len = strlen (tmp_var);
766
767    /*
768     * Scan 'environ' for 'tmp_var'
769     */
770    for (ppch = environ; *ppch; *ppch++) {
771       if (!strncmp (tmp_var, *ppch, tmp_len)) {
772          /*
773           * Found the variable so remove it by moving all
774           * variables after *ppch up.
775           */
776          for (ppch2 = ppch; *ppch2; *ppch2++) {
777             *ppch++;
778             *ppch2 = *ppch;
779          }
780          break;
781       }
782    }
783
784    free ((char *) tmp_var);
785    return (0);
786 }
787
788
789 /**************************************************************************
790  *
791  * resolve_variable_reference ()
792  *
793  * This function takes a string of the format:
794  *
795  *    var_name=some_value
796  *  
797  * and if 'some_value' contains a reference to an environment
798  * variable, the reference is replaced with the value of the
799  * variable.
800  *
801  *    For example, if 'string' is
802  *
803  *            FOO=$VFA_TOP/foo
804  *
805  *    and $VFA_TOP is '/some_tree', then 'string' will be changed to:
806  *
807  *            FOO=/some_tree/foo
808  *
809  * Notes:
810  *
811  *    o 'putenv' will be invoked with argument 'string', even if 'string' 
812  *      was not modified.
813  *
814  *    o A valid variable name consists of alphanumerics and underscore
815  *
816  * Parameters:
817  *
818  *    char      **string        MODIFIED - the environment variable 
819  *                              definition to be parsed
820  *
821  * Return Value: 
822  *
823  *    void
824  *
825  **************************************************************************/
826
827 static void
828 resolve_variable_reference(
829         char            **string )              /* MODIFIED */
830 {
831    char         *string_end;
832    char         *var_start;
833    char         *pch;
834    char         *value;
835    int          n;                      /* number of bytes in a string */
836    int          len;
837    int          found_brace;
838    char         variable[256];
839    char         *buf = NULL;
840    char         *pbeg = *string;
841    char         *string_start = *string;
842
843    if (!remove_variable (*string))
844       return;
845
846    pch = pbeg;
847    
848    buf = malloc(2048);
849
850    while (*pbeg) {
851
852       /*
853        * Look for '$' - the beginning of a variable reference.
854        * If a '$' character is not found, exit the loop
855        */
856 #ifdef NLS16
857       while (*pch) {
858
859          if (((len = mblen (pch, MB_CUR_MAX)) == 1) && (*pch == '$'))
860             break;
861
862          /*
863           * Move past this char
864           */
865          if (len > 0)
866             pch += len;
867          else
868             /*
869              * pch is null or it points to an invalid mulit-byte
870              * character.
871              */
872             break;
873       }
874 #else
875       pch = strchr (pch, '$');
876 #endif /* NLS16 */
877
878       if (pch == NULL || *pch == '\000')
879          /*
880           * The string doesn't contain any (more) variables
881           */
882          break;
883
884       string_start = *string;
885
886       /*
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.
890        */
891       string_end = pch;
892       found_brace = 0;
893       pch++;
894
895 #ifdef NLS16
896       if ((mblen (pch, MB_CUR_MAX) == 1) && (*pch == '{')) {
897 #else
898       if (*pch == '{') {
899 #endif /* NLS16 */
900          pch++;                         /* input = ${ */
901          found_brace = 1;
902       }
903       if (*pch == '\0')                 /* input = $\0 or ${\0 */
904          break;
905
906       /*
907        * Find the end of the variable name - it is assumed to 
908        * be the first character that is not an alpha-numeric
909        * or '_'.
910        */
911       var_start = pch;
912       n = 0;
913
914       while (*pch) {
915 #ifdef NLS16
916          if ((mblen (pch, MB_CUR_MAX) > 1) || 
917              ((mblen (pch, MB_CUR_MAX) == 1) && 
918              ((*pch == '_') || (isalnum (*pch))))) {
919 #else
920          if (isalnum (*pch) || *pch == '_') {
921 #endif /* NLS16 */
922          
923 #ifdef NLS16
924             len = mblen (pch, MB_CUR_MAX);
925 #else
926             len = 1;
927 #endif /* NLS16 */
928             n += len;
929             pch += len;
930             continue;
931          }
932
933 #ifdef NLS16
934          if (found_brace && (mblen (pch, MB_CUR_MAX) == 1) && (*pch == '}'))
935 #else
936          if (found_brace && *pch == '}')
937 #endif /* NLS16 */
938             /*
939              * Move past the closing brace
940              */
941             pch++;
942          break;
943       }
944
945       if (n == 0) {
946          /*
947           * Nothing 'recognizable' follows the $ or ${
948           */
949          pbeg = pch;
950          continue;
951       }
952
953       /*
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.
957        */
958       (void) strncpy (variable, var_start, n);
959       variable[n] = '\0';
960
961       if ((value = getenv (variable)) == NULL) {
962          /*
963           * Leave what looks like an environment variable in place.
964           */
965          pbeg = pch;
966          continue;
967       }
968
969       if (strlen (value) == 0) {
970          pbeg = pch;
971          continue;
972       }
973
974       /*
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 
979        * variable.
980        */
981       if (string_end == string_start)
982          /*
983           * There is nothing to prepend before 'value'.
984           */
985          buf[0] = '\0';
986       else {
987          (void) strncpy (buf, string_start, (string_end - string_start));
988          buf[(string_end - string_start)] = '\0';
989       }
990       (void) strcat (buf, value);
991       len = strlen (buf);
992       if (*pch != '\0')
993          (void) strcat (buf, pch);
994        
995       /*
996        * Now put 'buf' into 'string'.
997        */
998       *string = realloc (*string, strlen (buf) + 1);
999       (void) strcpy (*string, buf);
1000       pch = *string + len;
1001       pbeg = pch;
1002    }
1003
1004    /*
1005     * Even if no substitutions were made, the variable must
1006     * be put in the environment.
1007     */
1008    (void) putenv (*string);
1009    if (buf) free(buf);
1010 }