Build with debug symbols enabled.
[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 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 /*
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 <errno.h>
37 #include <signal.h>
38 #include <sys/wait.h>
39 #include <sys/param.h>
40 #include <limits.h>
41 #include <stdlib.h>
42 #include <locale.h>
43
44 #include <SPC/spcP.h>
45 #include <bms/MemoryMgr.h> 
46
47 #include <SPC/spc-proto.h>
48
49 #ifdef SVR4
50 #include <unistd.h>
51 #endif
52
53 #include <Tt/tt_c.h>
54 #include "DtSvcLock.h"
55
56 /* Global vars. */
57
58 SPC_Connection_Ptr write_terminator=NULL, read_terminator=NULL;
59
60 /*
61  * Forward declarations
62  */
63 static char *get_path_from_context ( 
64         char            *context );
65 static int remove_variable(
66         char            *string );
67 static void resolve_variable_reference(
68         char            **string );
69
70 /*
71  * This array contains the process id's of the sub-processes
72  * started by the daemon.  When a sub-process terminates, its
73  * entry will be set to SPCD_DEAD_PROCESS.  This list of pid's
74  * is kept beause when the exit timer expires, if the daemon
75  * has no sub-processes running, it will exit.
76  */
77 pid_t *SPC_pid_list = NULL;
78
79 /*
80  * This global variable is set by the daemon when the client
81  * connects.
82  */
83 int SPC_client_version_number = SPC_PROTOCOL_VERSION;
84
85 /*
86  * If this variable is not NULL, it will contain the name of 
87  * the mount point environment variable plus its value thus it
88  * ready for 'putenv'.
89  */
90 char *SPC_mount_point_env_var = NULL;
91
92 /* External definitions */
93
94 extern XeChar spc_logging;
95 extern XeString *environ;
96
97 /*
98  * Routines for handling Sub-Processes
99  */
100
101 /*
102  **
103  ** Initialize synchronous terminators.
104  **
105 */
106
107 /*----------------------------------------------------------------------+*/
108 SPC_Setup_Synchronous_Terminator(void)
109 /*----------------------------------------------------------------------+*/
110 {
111   int pipes[2];
112
113   _DtSvcProcessLock();
114   if(write_terminator) {
115     _DtSvcProcessUnlock();
116     return(TRUE);
117   }
118   
119   if(pipe(pipes)<0) {
120     SPC_Error(SPC_No_Pipe);
121     _DtSvcProcessUnlock();
122     return(SPC_ERROR);
123   }
124
125   if((write_terminator=SPC_Alloc_Connection())==SPC_ERROR) {
126     _DtSvcProcessUnlock();
127     return(SPC_ERROR);
128   }
129   SPC_Add_Connection(write_terminator);
130   
131   if((read_terminator=SPC_Alloc_Connection())==SPC_ERROR) {
132     _DtSvcProcessUnlock();
133     return(SPC_ERROR);
134   }
135   SPC_Add_Connection(read_terminator);
136
137   write_terminator->sid=pipes[WRITE_SIDE];
138   write_terminator->connected=TRUE;
139
140   read_terminator->sid=pipes[READ_SIDE];
141   read_terminator->connected=TRUE;
142   SPC_XtAddInput(NULL, &read_terminator->termination_id, read_terminator->sid,
143                  SPC_Conditional_Packet_Handler, SPC_Terminator);
144   
145   _DtSvcProcessUnlock();
146   return(TRUE);
147 }
148
149 /*----------------------------------------------------------------------+*/
150 SPC_Connection_Ptr SPC_Channel_Terminator_Connection(SPC_Channel_Ptr channel)
151 /*----------------------------------------------------------------------+*/
152 {
153   if(IS_REMOTE(channel))
154     return(channel->connection);
155   else
156     return(read_terminator);
157 }
158   
159 /*----------------------------------------------------------------------+*/
160 void SPC_Close_Unused(void)
161 /*----------------------------------------------------------------------+*/
162 {
163   /* Close any and all unused file descriptors */
164   int fd;
165  
166   for (fd = STDERR + 1; fd < max_fds; fd++) spc_close(fd);
167 }
168
169 /*----------------------------------------------------------------------+*/
170 SPC_MakeSystemCommand(SPC_Channel_Ptr channel)
171 /*----------------------------------------------------------------------+*/
172 {
173
174   XeString shell;
175   XeString *argv;
176   XeString *tmp_argv;
177   XeChar newargtwo[_POSIX_ARG_MAX];
178   int argtwolen=0, tmplen=0;
179   
180   /* Allocate our memory up front */
181
182   argv=Alloc_Argv(4);
183
184   newargtwo[argtwolen]=0;
185
186   /* copy path into newargtwo */
187   
188   strncat(newargtwo, channel->path, _POSIX_ARG_MAX-1);
189   strcat(newargtwo, (XeString)" ");
190   argtwolen=strlen(newargtwo);
191   
192   /* copy argv into newargtwo */
193   for(tmp_argv=channel->argv; tmp_argv && *tmp_argv; tmp_argv++) {
194     tmplen=strlen(*tmp_argv)+1;              /* Room for extra space */
195     if((tmplen+argtwolen)<_POSIX_ARG_MAX-1) {
196       strcat(newargtwo, *tmp_argv);
197       strcat(newargtwo, (XeString)" ");
198       argtwolen += tmplen;
199     } else {
200       XeChar *errbuf;
201
202       errbuf = malloc(sizeof(XeChar) * 100);
203       if (errbuf)
204       {
205         SPC_Free_Envp(argv);
206         sprintf(errbuf,"(%d chars), max. length is %d",tmplen,_POSIX_ARG_MAX);
207         SPC_Error(SPC_Arg_Too_Long, tmp_argv, _POSIX_ARG_MAX);
208         free(errbuf);
209       }
210       return(SPC_ERROR);
211     }
212   }
213        
214   /* get a shell --
215      First use the value of $SB_SHELL (if any), 
216      then try $SHELL,
217      then use DEFAULT_SHELL
218      */
219
220   if(!(shell=getenv((XeString)"SB_SHELL")))
221     if(!(shell=getenv((XeString)"SHELL")))
222       shell = DEFAULT_SHELL;
223   
224   /* setup argv properly */
225   
226   argv[0]=SPC_copy_string(shell);
227   argv[1]=SPC_copy_string((XeString)"-c");
228   argv[2]=SPC_copy_string(newargtwo);
229   argv[3]=NULL;
230   channel->argv = argv;
231   channel->IOMode |= SPCIO_DEALLOC_ARGV;
232   
233   /* Now set this shell as the path */
234
235   channel->path = shell;
236   
237   return(TRUE);
238 }
239
240 /*
241  * Routines for handling child process termination
242  */
243
244 /*----------------------------------------------------------------------+*/
245 /* This is the right way according to the Spec 1170 */
246 void SPC_Child_Terminated(int i)
247 /*----------------------------------------------------------------------+*/
248 {
249   /* This catches signals for sub-process termination */
250   int type, cause, status;
251   pid_t wait_pid, pid;
252   SPC_Channel_Ptr channel;
253   protocol_request     req, *prot;
254   buffered_data        data, *pdata;
255   int length;
256   int indx;
257   int saved_errno = errno;
258
259   prot  = (&req);
260   pdata = (&data);
261
262   prot->dataptr=pdata;
263
264   wait_pid = -1;
265   while(pid = waitpid(wait_pid, &status, WNOHANG)) {
266     if((pid == -1 && errno == ECHILD) || pid == 0) {
267       /* no more children.  Return */
268       errno = saved_errno;
269       return;
270     }
271     
272     /* Okay, we got the process ID of a terminated child.  Find the
273        channel associated with this PID. */
274     channel=SPC_Find_PID(pid);
275 #ifdef DEBUG
276     fprintf(stderr, (XeString)"got SIGCHLD, pid: %d, channel: %p\n", pid, channel);
277 #endif
278
279     if(!channel) {
280       continue;
281     }
282
283     _DtSvcProcessLock();
284     /*
285      * Look for this process in the pid list.  If found, mark it
286      * as done.
287      */
288     if (SPC_pid_list != NULL) {
289       for (indx=0; SPC_pid_list[indx] != NULL; indx++)
290         if (SPC_pid_list[indx] == pid) {
291            SPC_pid_list[indx] = SPCD_DEAD_PROCESS;
292            break;
293         }
294     }
295     _DtSvcProcessUnlock();
296
297     /* We have the channel.  Mark it as being closed. */
298
299     channel->status     = status;
300
301     /* If we this channel is set up for synchronous termination,
302        write the protocol request to record that this guy died.
303        Otherwise, call the termination handler directly. */
304     
305     if(IS_SPCIO_SYNC_TERM(channel->IOMode)) {
306
307       /* This code is basically what SPC_Write_Protocol_Request does.
308          It is replicated here because a call to SPC_W_P_R would have
309          to be re-enterant if we called it here, and SPC_W_P_R is not
310          re-enterant at this time. */
311       
312       SPC_Reset_Protocol_Ptr(prot, channel, APPLICATION_DIED, 0);
313       pdata->len=WRITE_APPLICATION_DIED(pdata, status);
314       length=WRITE_HEADER(pdata, channel->cid,
315                           prot->request_type,
316                           pdata->len,
317                           0);
318       pdata->data[length]=(XeChar)' ';
319       length=pdata->len+REQUEST_HEADER_LENGTH;
320       if(write(write_terminator->sid, pdata->data, length)==ERROR)
321         SPC_Error(SPC_Internal_Error);
322       pdata->offset=REQUEST_HEADER_LENGTH;
323       print_protocol_request((XeString) (XeString)"  <-- INTERNAL APPLICATION_DIED", prot);
324     }
325     else {
326       SPC_Change_State(channel, NULL, -1, 0);
327       if(channel->Terminate_Handler) {
328         XeSPCGetProcessStatus(channel, &type, &cause);
329         (* channel->Terminate_Handler)
330           (channel, channel->pid, type, cause, channel->Terminate_Data);
331       }
332     }
333     /* Loop around & get another PID */
334   }
335   errno = saved_errno;
336 }
337
338 /*
339  ***
340  *** Check to see if a given path names an executable file.  Thus, we
341  *** need to know if the file exists.  If it is a directory, we want
342  *** to fail.  Otherwise, we want to us the system rules for checking
343  *** on the file, and thus the 'access' call.
344  ***
345 */
346
347 static Boolean executable_predicate(XeString path, XeString dir, XeString file)
348 {
349
350   struct stat file_status;
351   
352   dir=dir;
353   file=file;
354
355   if(stat(path, &file_status) != 0)
356     return(FALSE);
357   
358   if(S_ISDIR(file_status.st_mode))
359     return(FALSE);
360
361   return(access(path, X_OK | F_OK) == 0);
362 }
363
364 /*----------------------------------------------------------------------+*/
365 int exec_proc_local_channel_object(SPC_Channel_Ptr channel)
366 /*----------------------------------------------------------------------+*/
367 {
368     sigset_t newsigmask, oldsigmask;
369     pid_t pid;
370     int result;
371     XeString *envp;
372     XeString dir = XeString_NULL;
373     int retval;
374     int i, reuse_pid = 0;
375     
376     call_parent_method(channel, exec_proc, (channel), result);
377     
378     if(result==SPC_ERROR)
379         return(SPC_ERROR);
380
381     /* Check to see if the channel pathname points to a valid executable.
382        We do this by using the _path_search function.  If the channel
383        has a PATH variable set in its local environment, use it,
384        otherwise use the "global" environment.  We can accomplish this
385        by using the spc_getenv call in the _path_search call.  If the
386        channel doesn't have a PATH variable, then spc_getenv will
387        return NULL, which indicates use of the global environment.
388    */
389
390     if(!_path_search(SPC_Getenv("PATH", channel->envp),
391                     channel->path,
392                     executable_predicate)) {
393       SPC_Error(SPC_Cannot_Exec, channel->path);
394       return(SPC_ERROR);
395     }
396        
397     /* If we were passed a host:dir to cd to, make sure it exists. */
398     /* We want to do this before we fork the child.                */
399
400     if((channel->context_dir) && (channel->context_dir[0])) {
401         struct stat     stat_info;
402         Boolean ok = FALSE;
403         
404         _DtSvcProcessLock();
405         if (SPC_client_version_number < SPC_PROTOCOL_VERSION_CDE_BASE)
406            dir = get_path_from_context(channel->context_dir);
407         else {
408            /*
409             * context_dir is actually a "netfile" so it needs to
410             * be converted to a "real" path.
411             */
412            dir = (char *) tt_netfile_file (channel->context_dir);
413            if (tt_ptr_error (dir) != TT_OK) 
414               dir = NULL;
415         }
416         _DtSvcProcessUnlock();
417
418         if (dir == NULL)
419             /* can't make connection ... */;
420         else if (stat(dir,&stat_info) != 0)     
421             /* directory not there */;
422
423         else if ((stat_info.st_mode & S_IFDIR) == 0)
424             /* path is not a directory ... */;
425         else
426             ok = TRUE;
427         
428         if (!ok && IS_SPCIO_FORCE_CONTEXT(channel->IOMode)) {
429             if (dir != NULL && (strcmp (dir, channel->context_dir) != 0))
430                SPC_Error(SPC_cannot_Chdir, dir);
431             SPC_Error(SPC_cannot_Chdir, channel->context_dir);
432             XeFree(dir);
433             dir = XeString_NULL;
434             return(SPC_ERROR);
435         }
436     }
437
438     if(mempf0(channel, pre_fork)==SPC_ERROR)
439         return(SPC_ERROR);
440     
441     /* When using HP NLIO (xj0input) we have a problem.  Namely,  */
442     /* the xj0 processs uses signal() to deal with SIGCLD which   */    
443     /* is incompatible with sigaction/sigprogmask/etc.  Even      */
444     /* though xj0 resets the signal handler, since the signal     */
445     /* routines are incompatible, our original handler gets lost. */
446     /* Hence, we need to reset it.  We do it here everytime we    */
447     /* fork a child just to be on the safe side.                  */
448
449     SPC_ResetTerminator();
450     
451     sigemptyset(&newsigmask);
452     sigemptyset(&oldsigmask);
453     sigaddset(&newsigmask, SIGCHLD);
454     
455     if (sigprocmask(SIG_BLOCK, &newsigmask, &oldsigmask) == ERROR)
456         return(SPC_ERROR);
457     
458     pid = channel->pid = fork();
459     
460     /*
461      * Must save this pid so that when the daemon's timer goes off,
462      * if there has been no activity and there are no sub-processes
463      * running, the daemon can exit.
464      */
465     i = 0;
466     _DtSvcProcessLock();
467     if (SPC_pid_list == NULL)
468       /*
469        * Create the first block plus the NULL terminator.
470        */
471       SPC_pid_list = (pid_t *) malloc (2 * sizeof (pid_t));
472     else {
473       /*
474        * If a dead pid entry exists, reuse it; otherwise, must create 
475        * room for the new pid.
476        */
477       for (i = 0; SPC_pid_list[i] != NULL; i++)
478         if (SPC_pid_list[i] == SPCD_DEAD_PROCESS) {
479           SPC_pid_list[i] = pid;
480           reuse_pid = 1;
481           break;
482         }
483       if (!reuse_pid)
484         SPC_pid_list = (pid_t *) realloc (SPC_pid_list, 
485                                            (i+2) * sizeof (pid_t));
486     }
487     if (!reuse_pid) {
488       SPC_pid_list[i] = pid;
489       SPC_pid_list[i+1] = NULL;
490     }
491     _DtSvcProcessUnlock();
492
493     if (pid) {
494         XeFree(dir);
495         
496         /* Did we really fork? */
497         if (pid == ERROR) {
498           SPC_Error(SPC_Cannot_Fork);
499           retval = SPC_ERROR;
500         } else {
501           /* Do any set up for the parent process here */
502           mempf1(channel, post_fork, pid);
503           retval = TRUE;
504         }
505         
506         /* Reinstate the old signal mask (unblock SIGCLD). */
507         
508         sigprocmask(SIG_SETMASK, &oldsigmask, (sigset_t *)NULL);
509         return(retval);
510     }
511     else {
512         /* Child process: connect wires, make environment and exec sub-process */
513         
514         sigprocmask(SIG_SETMASK, &oldsigmask, (sigset_t *)NULL);
515         
516         /* Make sure the child is the process group leader.  In the case of
517            ptys, we also want to break the current terminal affiliation.
518            We want to be the process group leader so XeSPCKillProcess (which
519            does a kill(-pid, 9)) will kill all processes associated with us.
520            
521            For PTY's, we need to break the terminal affiliation so the next
522            open (which will be a pty) will cause us to become affiliated with
523            the pty.  We do this so when the parent process closes the master
524            side of the pty, the slave side processes get SIGHUP.  If they
525            ignore SIGHUP, they will never die.  So it goes...
526            */
527
528         if(IS_SPCIO_PTY(channel->IOMode))
529             setsid();
530         else {
531             pid_t tmppid = getpid();
532             if(setpgid(tmppid, tmppid) == -1) 
533                 fprintf(stderr, (XeString)"setpgid failed, errno: %d\n", errno);
534         }
535         
536         /* Connect wires to sub-process standard files */
537         result=mempf1(channel, post_fork, pid);
538         
539         if(result!=SPC_ERROR) {
540             int         indx = -1;
541             int         i;
542             char        **ppch;
543
544             /*
545              * Before adding in the list of environment variables 
546              * from the environment variable files, must search 
547              * the list for LANG definitions.  If found, the
548              * last definition must be putenv'ed to assure the 
549              * multi-byte parsing code is using the correct locale.
550              */
551             for (i = 0, ppch = channel->envp; *ppch; *ppch++, i++)
552                if (!strncmp (*ppch, "LANG=", 5))
553                   indx = i;
554
555             if (indx != -1)
556                resolve_variable_reference (&channel->envp[indx]);
557
558             _DtSvcProcessLock();
559             if (!setlocale (LC_CTYPE, ""))
560                /*
561                 * setlocale failed - log a message but execute 
562                 * the command anyway.
563                 */ 
564                if (SPC_Print_Protocol != NULL)
565                   (void) fprintf(SPC_Print_Protocol,
566                       "+++> Failed to 'setlocale'; LANG = %s\n",
567                       getenv("LANG"));
568             /* Mix in the stashed environment */
569             
570             for(envp=channel->envp; *envp; envp++)
571                 resolve_variable_reference(&*envp);
572
573             if (SPC_mount_point_env_var != NULL)
574                 /*
575                  * The mount point environment variable was 
576                  * inherited by the daemon or was given to the
577                  * daemon via the command line.  In either case
578                  * this subprocess must inherit the daemon's
579                  * value.
580                  */
581                 (void) putenv (SPC_mount_point_env_var);
582             _DtSvcProcessUnlock();
583
584             /* Connect to the context directory */
585             /* We have already validated this guy exists */
586
587             if(dir) 
588                 Xechdir(dir);
589         }
590         
591         XeFree(dir);
592
593         if(result!=SPC_ERROR) {
594             /* Execute */
595             /* Compiler barfs without cast ? */
596 #if defined(__hpux_8_0) || defined(__aix)
597             result=execvp(channel->path, channel->argv);
598 #else
599             result=execvp(channel->path, channel->argv);
600 #endif
601             /* If we return from exec, it failed */
602             SPC_Error(SPC_Cannot_Exec, channel->path);
603         }
604         
605         /* We want to get rid of this child image (carefully) */
606         _exit(42);
607         
608     }
609 }
610
611
612 /****************************************************************************
613  *
614  * get_path_from_context - given a 'context' string in the following form:
615  * 
616  *     [host:]path
617  *
618  * return the path component.
619  *
620  * NOTE - the caller must free the returned string.
621  * 
622  * Parameters:
623  *
624  *    char      *context        - the context string to parse
625  *
626  * Return Value:
627  *
628  *    A NULL if a pathname cannot be constructed.
629  *
630  ****************************************************************************/
631
632 static char *get_path_from_context (
633         char            *context)
634 {
635    char                 *host = NULL;
636    char                 *file = NULL;
637    char                 *netfile = NULL;
638    char                 *path = NULL;
639    char                 tmp[MAXPATHLEN];
640    char                 *pch;
641
642    /*
643     * Break context into its host and file parts.
644     */
645    if (context == NULL)
646       return (NULL);
647
648    (void) strcpy (tmp, context);
649    file = tmp;
650
651    if ((pch = (char *) strchr (tmp, ':')) != NULL) {
652       host = tmp;
653       file = pch + 1;
654       *pch = '\000';
655    }
656
657    if (!host)
658       return (strdup (file));
659
660    netfile = (char *) tt_host_file_netfile (host, file);
661    if (tt_ptr_error (netfile) != TT_OK) {
662       SPC_Error (SPC_Cannot_Create_Netfilename, context, host);
663       return (NULL);
664    }
665
666    path = (char *) tt_netfile_file (netfile);
667    tt_free (netfile);
668    if (tt_ptr_error (path) != TT_OK) {
669       SPC_Error (SPC_Cannot_Create_Netfilename, context, host);
670       return (NULL);
671    }
672
673    return (path);
674 }
675
676
677 /**************************************************************************
678  *
679  * remove_variable ()
680  *
681  * This takes a string of the format:
682  *
683  *    var_name=some_value | <remove_variable_keyword> var_name
684  *  
685  * and if the second form is found, 'var_name' is removed from
686  * the environment.
687  *
688  * Parameters:
689  *
690  *    char      *string         see the format above.
691  *
692  * Return Value: 
693  *
694  *    int       0 if 'string' contains a 'remove variable' command
695  *              1 if string does not contain a 'remove variable' command
696  *
697  * Modified:
698  *
699  *    char      **environ       'var_name' is removed from the environment
700  *
701  **************************************************************************/
702
703 static int
704 remove_variable(
705         char            *string)
706 {
707    char         *pch;
708    char         **ppch;
709    char         **ppch2;
710    char         *tmp_var;
711    int          tmp_len;
712
713    /*
714     * If string contains some white space before the variable
715     * or keyword, skip the white space.
716     */
717    pch = string;
718    while (
719 #ifdef NLS16
720           ((mblen (pch, MB_CUR_MAX) == 1)) &&
721 #endif  /* NLS16 */
722           isspace ((u_char)*pch))
723              pch++;
724
725    if (*pch == '\000') 
726       return (1);
727    if (strncmp (pch, SPC_REMOVE_VAR, strlen (SPC_REMOVE_VAR)))
728       return (1);
729
730    /*
731     * Skip the white space after the keyword and move to the
732     * beginning of the variable.
733     */
734    pch = pch + strlen (SPC_REMOVE_VAR);
735    while (
736 #ifdef NLS16
737           ((mblen (pch, MB_CUR_MAX) == 1)) &&
738 #endif  /* NLS16 */
739           isspace ((u_char)*pch))
740              pch++;
741
742    if (*pch == '\000') 
743       return (1);
744
745    /*
746     * pch should now point to the variable to be removed.
747     *
748     * tmp_var will be equal to the variable with a trailing '='.
749     * This is added so the future comparison will not match
750     * against variables that start with 'tmp_var' but then
751     * have something else.
752     */
753    tmp_var = malloc ((strlen (pch) + 2) * sizeof (char));
754    (void) sprintf (tmp_var, "%s=", pch);
755    tmp_len = strlen (tmp_var);
756
757    /*
758     * Scan 'environ' for 'tmp_var'
759     */
760    for (ppch = environ; *ppch; *ppch++) {
761       if (!strncmp (tmp_var, *ppch, tmp_len)) {
762          /*
763           * Found the variable so remove it by moving all
764           * variables after *ppch up.
765           */
766          for (ppch2 = ppch; *ppch2; *ppch2++) {
767             *ppch++;
768             *ppch2 = *ppch;
769          }
770          break;
771       }
772    }
773
774    free ((char *) tmp_var);
775    return (0);
776 }
777
778
779 /**************************************************************************
780  *
781  * resolve_variable_reference ()
782  *
783  * This function takes a string of the format:
784  *
785  *    var_name=some_value
786  *  
787  * and if 'some_value' contains a reference to an environment
788  * variable, the reference is replaced with the value of the
789  * variable.
790  *
791  *    For example, if 'string' is
792  *
793  *            FOO=$VFA_TOP/foo
794  *
795  *    and $VFA_TOP is '/some_tree', then 'string' will be changed to:
796  *
797  *            FOO=/some_tree/foo
798  *
799  * Notes:
800  *
801  *    o 'putenv' will be invoked with argument 'string', even if 'string' 
802  *      was not modified.
803  *
804  *    o A valid variable name consists of alphanumerics and underscore
805  *
806  * Parameters:
807  *
808  *    char      **string        MODIFIED - the environment variable 
809  *                              definition to be parsed
810  *
811  * Return Value: 
812  *
813  *    void
814  *
815  **************************************************************************/
816
817 static void
818 resolve_variable_reference(
819         char            **string )              /* MODIFIED */
820 {
821    char         *string_end;
822    char         *var_start;
823    char         *pch;
824    char         *value;
825    int          n;                      /* number of bytes in a string */
826    int          len;
827    int          found_brace;
828    char         variable[256];
829    char         *buf = NULL;
830    char         *pbeg = *string;
831    char         *string_start = *string;
832
833    if (!remove_variable (*string))
834       return;
835
836    pch = pbeg;
837    
838    buf = malloc(2048);
839
840    while (*pbeg) {
841
842       /*
843        * Look for '$' - the beginning of a variable reference.
844        * If a '$' character is not found, exit the loop
845        */
846 #ifdef NLS16
847       while (*pch) {
848
849          if (((len = mblen (pch, MB_CUR_MAX)) == 1) && (*pch == '$'))
850             break;
851
852          /*
853           * Move past this char
854           */
855          if (len > 0)
856             pch += len;
857          else
858             /*
859              * pch is null or it points to an invalid mulit-byte
860              * character.
861              */
862             break;
863       }
864 #else
865       pch = strchr (pch, '$');
866 #endif /* NLS16 */
867
868       if (pch == NULL || *pch == '\000')
869          /*
870           * The string doesn't contain any (more) variables
871           */
872          break;
873
874       string_start = *string;
875
876       /*
877        * Found a '$' - the beginning of an environment variable so
878        * skip it and check the next char for '{' and if it is found
879        * skip it and move to the real beginning of an env variable.
880        */
881       string_end = pch;
882       found_brace = 0;
883       pch++;
884
885 #ifdef NLS16
886       if ((mblen (pch, MB_CUR_MAX) == 1) && (*pch == '{')) {
887 #else
888       if (*pch == '{') {
889 #endif /* NLS16 */
890          pch++;                         /* input = ${ */
891          found_brace = 1;
892       }
893       if (*pch == '\0')                 /* input = $\0 or ${\0 */
894          break;
895
896       /*
897        * Find the end of the variable name - it is assumed to 
898        * be the first character that is not an alpha-numeric
899        * or '_'.
900        */
901       var_start = pch;
902       n = 0;
903
904       while (*pch) {
905 #ifdef NLS16
906          if ((mblen (pch, MB_CUR_MAX) > 1) || 
907              ((mblen (pch, MB_CUR_MAX) == 1) && 
908              ((*pch == '_') || (isalnum (*pch))))) {
909 #else
910          if (isalnum (*pch) || *pch == '_') {
911 #endif /* NLS16 */
912          
913 #ifdef NLS16
914             len = mblen (pch, MB_CUR_MAX);
915 #else
916             len = 1;
917 #endif /* NLS16 */
918             n += len;
919             pch += len;
920             continue;
921          }
922
923 #ifdef NLS16
924          if (found_brace && (mblen (pch, MB_CUR_MAX) == 1) && (*pch == '}'))
925 #else
926          if (found_brace && *pch == '}')
927 #endif /* NLS16 */
928             /*
929              * Move past the closing brace
930              */
931             pch++;
932          break;
933       }
934
935       if (n == 0) {
936          /*
937           * Nothing 'recognizable' follows the $ or ${
938           */
939          pbeg = pch;
940          continue;
941       }
942
943       /*
944        * Stuff the environment variable name in 'variable' and then
945        * get its value.  If the variable doesn't exist, leave its
946        * name in the string.
947        */
948       (void) strncpy (variable, var_start, n);
949       variable[n] = '\0';
950
951       if ((value = getenv (variable)) == NULL) {
952          /*
953           * Leave what looks like an environment variable in place.
954           */
955          pbeg = pch;
956          continue;
957       }
958
959       if (strlen (value) == 0) {
960          pbeg = pch;
961          continue;
962       }
963
964       /*
965        * Need to replace the variable definition with the string
966        * pointed to by 'value'.  So create a string that contains the 
967        * characters before the environment variable, then the contents 
968        * of 'value' and finally, the characters after the environment 
969        * variable.
970        */
971       if (string_end == string_start)
972          /*
973           * There is nothing to prepend before 'value'.
974           */
975          buf[0] = '\0';
976       else {
977          (void) strncpy (buf, string_start, (string_end - string_start));
978          buf[(string_end - string_start)] = '\0';
979       }
980       (void) strcat (buf, value);
981       len = strlen (buf);
982       if (*pch != '\0')
983          (void) strcat (buf, pch);
984        
985       /*
986        * Now put 'buf' into 'string'.
987        */
988       *string = realloc (*string, strlen (buf) + 1);
989       (void) strcpy (*string, buf);
990       pch = *string + len;
991       pbeg = pch;
992    }
993
994    /*
995     * Even if no substitutions were made, the variable must
996     * be put in the environment.
997     */
998    (void) putenv (*string);
999    if (buf) free(buf);
1000 }