ttserver: fixup forward (vexing) fucntion decl's in main, get rid of **environ
[oweals/cde.git] / cde / lib / DtSvc / DtUtil1 / Action.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 /* $TOG: Action.c /main/28 1999/09/16 14:55:25 mgreess $ */
24 /* 
25  * (c) Copyright 1997, The Open Group 
26  */
27 /*************************************<+>*************************************
28  *****************************************************************************
29  **
30  **   File:         Action.c
31  **
32  **   Project:      DT
33  **
34  **   Description:  This file contains the action library source code.
35  **               
36  **
37  ** (c) Copyright 1993, 1994 Hewlett-Packard Company
38  ** (c) Copyright 1993, 1994 International Business Machines Corp.
39  ** (c) Copyright 1993, 1994 Sun Microsystems, Inc.
40  ** (c) Copyright 1993, 1994 Novell, Inc. 
41  **
42  **
43  ****************************************************************************
44  ************************************<+>*************************************/
45
46 /*LINTLIBRARY*/
47 #include <stdio.h>
48 #include <errno.h>
49 #include <sys/types.h>
50 #include <sys/stat.h>
51 #include <sys/param.h>
52
53 #ifdef _SUN_OS /* Need this for the strtod () call */
54 #include <floatingpoint.h>
55 #endif /* _SUN_OS */
56
57 #define X_INCLUDE_STRING_H
58 #define XOS_USE_XT_LOCKING
59 #include <X11/Xos_r.h>
60
61 #include <stdlib.h>
62 #include <limits.h>
63
64 #include <X11/Intrinsic.h>
65
66 #include <Dt/CmdInv.h>
67 #include <Dt/DtP.h>
68 #include <Dt/Dts.h>
69 #include <Dt/Help.h>
70 #include <Dt/Message.h>
71 #include <Dt/Connect.h>
72 #include <Dt/Indicator.h>
73 #include <Dt/DtNlUtils.h>
74 #include <Dt/CommandM.h>
75 #include <Dt/Utility.h>
76 #include <Dt/Service.h>
77 #include <Dt/UserMsg.h>
78
79 #include <Xm/Xm.h>
80 #include <Xm/BulletinB.h>
81 #include <Xm/DialogS.h>
82 #include <Xm/Frame.h>
83 #include <Xm/Form.h>
84 #include <Xm/LabelG.h>
85 #include <Xm/TextF.h>
86 #include <Xm/SeparatoG.h>
87 #include <Xm/PushBG.h>
88 #include <Xm/MessageB.h>
89 #include <Xm/MwmUtil.h>
90 #include <Xm/Protocols.h>
91
92 #include <Dt/ActionP.h>
93 #include <Dt/ActionUtilP.h>
94 #include <Dt/ActionDb.h>
95 #include <Dt/ActionFind.h>
96 #include <Tt/tttk.h>
97
98 #include <Dt/Action.h>
99
100 #include "myassertP.h"
101 #include "DtSvcLock.h"
102
103 #ifndef CDE_INSTALLATION_TOP
104 #define CDE_INSTALLATION_TOP "/opt/dt"
105 #endif
106
107 extern char * _DtStripSpaces( 
108                         char * string) ;
109 extern char * _DtDbPathIdToString( DtDbPathId pathId) ;
110
111 #define _MAX_MAP_ATTEMPTS       100     /* Maximum nuber of "MAPS" that will
112                                            be done. */
113 #define _DT_ACTION_MAX_CLOSE_TRIES      5
114
115 /********    Public Function Declarations    ********/
116
117 void _DtCreateErrorDialog( 
118                         Widget w,
119                         char * actionName,
120                         XmString msg) ;
121 Boolean _DtCompileMessagePiece(
122                         Widget w,
123                         ActionRequest *request,
124                         char * relPathHost,
125                         char * relPathDir,
126                         parsedMsg * piece,
127                         Boolean initialize,
128                         unsigned long processingMask,
129                         Boolean ** paramUsed,
130                         int * promptDataIndex ) ;
131 ActionRequest * _DtCloneRequest (
132                         ActionRequest * request) ;
133 void _DtFreeRequest( 
134                         ActionRequest *request) ;
135
136 char * _DtFindCwd( void ) ;
137
138 char * _DtActMapFileName(
139                         const char * curHost,
140                         const char * dir,
141                         const char * file,
142                         const char * newHost ) ;
143
144 extern void _DtProcessTtRequest(
145                         Widget w,
146                         ActionRequest *request,
147                         char * relPathHost,
148                         char * relPathDir ) ;
149 extern Tt_status _DtInitializeToolTalk(Widget w);
150
151 extern Boolean _DtEmptyString(
152                         String str) ;
153
154 /********    End Public Function Declarations    ********/
155
156
157 /********    Static Function Declarations    ********/
158
159
160 static void FreeErrorDialog( 
161                         Widget w,
162                         XtPointer user_data,
163                         XtPointer call_data) ;
164 static void InvalidFilename( 
165                         Widget w,
166                         char * actionName,
167                         char * filename) ;
168 static void HostAccessError( 
169                         Widget w,
170                         char * actionName,
171                         char * hostName) ;
172 static void MultiHostAccessError(
173                         Widget w,
174                         char * actionName,
175                         char * hostList) ;
176 static void NoActionError( 
177                         Widget w,
178                         DtShmBoson  origNameQuark,
179                         char * actionName,
180                         char * type,
181                         char * host,
182                         char * dir,
183                         char * file) ;
184 static void MapError( 
185                         Widget w,
186                         char * actionName ) ;
187 static void CommandInvokerError( 
188                         Widget w,
189                         char * actionName,
190                         char * errorString) ;
191 static void NoToolTalkConnectionError(
192                         Widget w,
193                         String actionName,
194                         Tt_status status) ;
195 static void TmpFileCreateError(
196                         Widget w,
197                         char *actionName,
198                         char *dirName) ;
199 static void TmpFileOpenError(
200                         Widget w,
201                         char *actionName,
202                         char *fileName) ;
203 static void TmpFileWriteError(
204                         Widget w,
205                         char *actionName,
206                         char *fileName) ;
207 static void UnSupportedObject(
208                         Widget w,
209                         char *actionName,
210                         int  objClass);
211 static void SetExecHost(
212                         ActionRequest * request) ;
213 static void ParseHostList (
214                         char * hostString,
215                         char *** hostListPtr,
216                         int * hostListSizePtr,
217                         int * hostCountPtr) ;
218 static void RemoveDuplicateHostNames (
219                         char ** hostList,
220                         int   * hostCountPtr ) ;
221 static void AddFailedHostToList (
222                         ActionRequest * request,
223                         char * badHost) ;
224 static int _DtAddEntry( 
225                         char * string,
226                         char * **arrayPtr,
227                         int *sizePtr) ;
228 static void TryToTypeFile( 
229                         ObjectData *obj,
230                         char * host,
231                         char * dir,
232                         char * file,
233                         char ** resolvedPath);
234 static ActionRequest * CreateActionRequest( 
235                         Widget w,
236                         char * actionName,
237                         DtActionArg *aap,
238                         int numArgs,
239                         char * termOpts,
240                         char * execHost,
241                         char * cwdHost,
242                         char * cwdDir,
243                         _DtActInvRecT *invp);
244 static _DtActInvRecT   *CreateInvocationRecord(
245                         char            *actionName,
246                         Widget          w,
247                         DtActionArg     *aap,
248                         int             numArgs);
249 static Boolean ParseFileArgument(
250                         Widget w,
251                         ActionRequest * request,
252                         ObjectData * objectData,
253                         char * hostname,
254                         char * filename,
255                         char * filetype,
256                         Boolean typeFile) ;
257 static void AddPrompt( 
258                         int argNum,
259                         char * prompt,
260                         int *numPrompts,
261                         PromptEntry **prompts) ;
262 static int MatchParamsToAction( 
263                         ActionRequest *request,
264                         int *numPrompts,
265                         PromptEntry **prompts) ;
266 static void ProcessOneSegment(
267                         ActionRequest * request,
268                         parsedMsg * msg,
269                         PromptEntry **prompts,
270                         int *numPrompts,
271                         Boolean * argsOptionFound,
272                         int * lastArgReferenced,
273                         int * unused,
274                         Boolean * paramUsed) ;
275 static ActionPtr CloneActionDBEntry( 
276                         ActionPtr action) ;
277 static void CloneParsedMessage(
278                         parsedMsg * old_pmsg,
279                         parsedMsg * new_pmsg ) ;
280 static void FreeParsedMessage(
281                         parsedMsg * parsedMessage) ;
282 static parsedMsg * CloneParsedMessageArray(
283                         parsedMsg * pmsgArray,
284                         int count ) ;
285 static void FreeParsedMessageArray(
286                         parsedMsg * parsedMessageArray,
287                         int count ) ;
288 static Boolean InsertArgumentString(
289                         Widget w,
290                         char **bufPtr,
291                         int * bufSizePtr,
292                         ActionRequest *request,
293                         ObjectData *object,
294                         unsigned long mask,
295                         char * relPathHost,
296                         char * relPathDir,
297                         Boolean addLeadingSpace,
298                         unsigned long processingMask ) ;
299 static void InsertUnmappedArgumentString(
300                         char **bufPtr,
301                         int * bufSizePtr,
302                         ObjectData *object,
303                         Boolean addLeadingSpace ) ;
304 static char * GrowMsgBuffer( 
305                         char * buffer,
306                         int *size,
307                         int count) ;
308 static void CmdInvSuccessfulRequest( 
309                         char *message,
310                         void *data2) ;
311 static void CmdInvFailedRequest( 
312                         char *message,
313                         void *data2) ;
314 static void InitiateDtRequest(
315                         Widget w,
316                         ActionRequest *request) ;
317 static Boolean ResolveDtNotifyMessagePieces(
318                         Widget w,
319                         ActionRequest *request,
320                         char * relPathHost,
321                         char * relPathDir ) ;
322 static void InitiateDtNotifyMessage(
323                         Widget w,
324                         ActionRequest *request ) ;
325 static void PrepareAndExecuteAction( 
326                         Widget w,
327                         ActionRequest *request);
328 static void __ExtractCWD(
329                         ActionRequest *request,
330                         char ** hostPtr,
331                         char ** dirPtr,
332                         Boolean useObjectInfo) ;
333 static void ContinueRequest( 
334                         Widget widget,
335                         XtPointer user_data,
336                         XtPointer call_data) ;
337 static void CancelRequest( 
338                         Widget widget,
339                         XtPointer user_data,
340                         XtPointer call_data) ;
341 static void CreateContinueDialog( 
342                         Widget w,
343                         ActionRequest *request,
344                         int numPrompts,
345                         PromptEntry *prompts) ;
346 static void CancelPromptDialog( 
347                         Widget widget,
348                         PromptDialog *dialog,
349                         XtPointer call_data) ;
350 static void ProcessPromptDialog( 
351                         Widget widget,
352                         PromptDialog *dialog,
353                         XtPointer call_data) ;
354 static void ChangePromptTraversal( 
355                         Widget widget,
356                         PromptDialog *dialog,
357                         XtPointer call_data) ;
358 static void CreatePromptDialog( 
359                         Widget w,
360                         ActionRequest *request,
361                         int numPrompts,
362                         PromptEntry *prompts) ;
363 static Boolean MoreArgumentsToProcess( 
364                         ActionRequest *request) ;
365 static Boolean ProcessRequest( 
366                         Widget w,
367                         ActionRequest *request) ;
368 static void InitLocalizedStrings( void ) ;
369 static int LinkToTypeQuark(
370                         char * host,
371                         char * dir,
372                         char * file,
373                         char **resolvedPath) ;
374 static void CancelOut(
375                         Widget w,
376                         XEvent *event,
377                         XtPointer params,
378                         XtPointer num_params);
379 static void InitiateCommandInvokerRequest(
380                         Widget w,
381                         ActionRequest *request,
382                         char * host,
383                         char * dir) ;
384 static void ProcessCommandInvokerRequest(
385                         Widget w,
386                         ActionRequest *request,
387                         char * relPathHost,
388                         char * relPathDir) ;
389 static Boolean ResolveCommandInvokerMessagePieces(
390                         Widget w,
391                         ActionRequest *request,
392                         char * relPathHost,
393                         char * relPathDir) ;
394 static Tt_callback_action _DbReloadCB(Tt_message m, Tt_pattern p);
395 static void _DtActTimerCB( XtPointer clientData, 
396                 XtIntervalId timerId);
397 static void _DtActIndicatorCB( XtPointer clientData, 
398                 XtIntervalId timerId);
399
400 /********    End Static Function Declarations    ********/
401
402
403 /* Pointers to localizable strings */
404 static String PromptDialogTitle;
405 static String ErrorPostfix;
406 static String PromptDialogLabel;
407 static String ContinueMessage;
408 static String HostErrorMsg;
409 static String HostErrorMsg2;
410 static String NoActionMsg;
411 static String NoActionMsg2;
412 static String NoActionMsg3;
413 static String MapErrorMsg;
414 static String InvalidFileMsg;
415 static String MultiHostErrorMsg;
416 static String IcccmReqErrorMsg;
417 static String NoToolTalkConnMsg;
418 static String UnSupportedObjMsg;
419 static String TmpFileCreateErrorMsg;
420 static String TmpFileOpenErrorMsg;
421 static String TmpFileWriteErrorMsg;
422
423 /*
424  * RWV:
425  * These error messages are used in the ActionTt.c file
426  * but were declared static to this file.  -- For the
427  * time being I made them global to get things to work.
428  */
429 String ToolTalkErrorMsg;
430 String ToolTalkErrorMsg2;
431 String TtFileArgMapErr;
432
433
434 /*
435  * Variables needed to make the "Escape" key remove the prompt dialog.
436  */
437 static XtActionsRec actionTable [] = {
438    {"Escape", (XtActionProc) CancelOut},
439 };
440 static char translations_escape[] = "<Key>osfCancel:Escape()";
441
442
443 /* Help files */
444 #define PROMPT_HELP     "vg_act"
445
446
447 /* Maximum Indicator activation duration (in milliseconds) */
448
449 #define INDICATOR_TIME          (120 * 1000)
450 #define MIN_INDICATOR_TIME        (5 * 1000)
451
452
453
454 /******************************************************************************
455  ******************************************************************************
456  *
457  *      Public API Functions
458  *
459  ******************************************************************************
460  *****************************************************************************/
461
462 /*******************************************************************************
463  * DtActionInvoke       -- invoke an action
464  *      Widget          w;              ( widget for UI needs)
465  *      char            *action;        ( action name )
466  *      int             aac;            ( action arg count )
467  *      ActionArgp      aap;            ( action argument pointer )
468  *      char            *termOpts;      ( (opt) terminal options)
469  *      char            *execHost;      ( (opt) execution host )
470  *      char            *cwd;           ( (opt) cwd for this action )
471  *      int             useIndicator;   ( 1 ==> use indicator, 0 ==> not )
472  *      DtActionCallbackProc statusUpdateCb;  (user supplied fcn)
473  *      XtPointer       client_data     (user supplied client data)
474  *****************************************************************************/
475 DtActionInvocationID
476 DtActionInvoke(
477         Widget          w,      
478         char            *action,
479         DtActionArg     *aap,
480         int             aac,   
481         char            *termOpts,
482         char            *execHost,
483         char            *cwd,
484         int             useIndicator,
485         DtActionCallbackProc statusUpdateCb,
486         XtPointer       client_data)
487 {
488     int i;
489     ActionRequest       *request;
490     char                *contextHost= NULL;/* dummy to replace old parameter */
491     _DtActInvRecT       *invp;          /* pointer to invocation record */
492     Tt_status            status = TT_OK;
493     static Boolean initialized = False;
494     extern XtAppContext *_DtInitAppContextp;
495     _DtSvcWidgetToAppContext(w);
496
497     _DtSvcAppLock(app);
498     _DtSvcAppLock(*_DtInitAppContextp);
499
500     /* We can't handle gadgets; use the parent, if necessary */
501     if (XmIsGadget(w))
502       w = XtParent(w);
503     
504     _DtSvcProcessLock();
505     if ( !initialized )
506     {
507       mode_t mode;
508       char *tmpDir;
509
510      InitLocalizedStrings();
511
512      /*
513       * Make sure Tooltalk is initialized
514       */
515       status = _DtInitializeToolTalk(w);
516       if (TT_OK != status)
517       {
518           NoToolTalkConnectionError(w, action, status);
519           _DtSvcProcessUnlock();    
520           return 0;
521       }
522
523      /*
524       * Create the DtTmp directory, if necessary.
525       */
526       tmpDir = _DtGetDtTmpDir();
527       /* mode == 0755 */
528       mode = (S_IRUSR|S_IWUSR|S_IXUSR|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH );
529       if ( mkdir(tmpDir,mode) )
530       {
531           /* 
532            * Tmp directory creation failure.
533            *
534            * Make one attempt to create the parent directory if the error
535            * was because of a missing path component -- It may be because
536            * the "$HOME/.dt" directory hasn't yet been created.
537            */
538           if ( errno == ENOENT )
539           {
540               char *parentDir = _DtDirname(tmpDir);
541               if (parentDir && 0 == mkdir(parentDir,mode))
542                   mkdir(tmpDir,mode);
543               if (parentDir) XtFree(parentDir);
544           }
545       }
546       XtFree(tmpDir);
547
548       _DtInitializeCommandInvoker( XtDisplay(w),
549         NULL,   /* bms tool class -- ignored */
550         NULL,   /* application class -- ignored */
551         (DtSvcMsgContext)NULL, /* reloadDBHandler -- none here */
552         (_DtInitAppContextp ? 
553             *_DtInitAppContextp : XtWidgetToApplicationContext(w)));
554     }
555     initialized = True;
556     _DtSvcProcessUnlock();    
557
558     /* Start the activity indicator */
559     if ( useIndicator ) {
560         _DtSendActivityNotification(INDICATOR_TIME);
561     }
562  
563     if ( (invp = CreateInvocationRecord(action,w,aap,aac)) == NULL)
564     {
565         myassert( 0 );  /* no request structure --should never happen */
566         /* give up -- cannot allocate record */
567
568         if ( useIndicator ) _DtSendActivityDoneNotification();
569         _DtSvcAppUnlock(*_DtInitAppContextp);
570         _DtSvcAppUnlock(app);
571         return 0;
572     }
573     
574     myassert(invp->id);
575
576     if ( useIndicator ) {
577       /* Start timer for minimum blink time */
578         XtAppAddTimeOut(XtWidgetToApplicationContext(w),
579             MIN_INDICATOR_TIME,  
580             (XtTimerCallbackProc) _DtActIndicatorCB,
581             (XtPointer) invp->id );
582     }
583
584     /*
585      * Add user callback info to the new invocation record.
586      */
587     invp->client_data = client_data;
588     invp->cb = statusUpdateCb;
589
590     /* Create and fill in the request structure */
591     if ( !IS_INV_FINISHED(invp->state) && (request = CreateActionRequest ( 
592            w,action,aap,aac,termOpts,execHost,contextHost,cwd,invp)) != NULL)
593     {
594         if (ProcessRequest(w, request))
595         {
596              /* all done invoking ? */
597              RESET_INV_PENDING(invp->state);
598
599               /* We should only get here if all requests have been honored */
600              SET_INV_COMPLETE(invp->state);
601
602              /*
603               * Evaluate whether we are done with this invocation. 
604               * We may have to return  values  to the caller.  
605               */
606              _DtActExecutionLeafNodeCleanup(invp->id,NULL,0,True);
607              _DtFreeRequest(request);
608         }
609         /* Otherwise, a dialog was posted; request will be freed later */
610     }
611
612     /*
613      * Set the indicator that the invocation Id has been returned
614      * add a timer so that we can return status info once the
615      * caller has gotten the invocation Id.
616      */
617     SET_INV_ID_RETURNED(invp->state);
618     XtAppAddTimeOut(XtWidgetToApplicationContext(w),
619         0 /* call back immediately */, 
620         (XtTimerCallbackProc) _DtActTimerCB,
621         (XtPointer) invp->id );
622
623     _DtSvcAppUnlock(*_DtInitAppContextp);
624     _DtSvcAppUnlock(app);
625     return invp->id;
626 }
627
628
629 void
630 DtDbReloadNotify( DtDbReloadCallbackProc proc, XtPointer client_data)
631 {
632     Tt_status   status;
633     Tt_pattern  pattern;
634     char *      sessId;
635     extern XtAppContext *_DtInitAppContextp;
636
637
638     if (NULL == proc) return;
639
640     _DtSvcAppLock(*_DtInitAppContextp);
641
642     /* 
643      *  Check if we need to initialize tooltalk
644      */
645     status = _DtInitializeToolTalk(NULL);
646     if (TT_OK != status) {
647           _DtSvcAppUnlock(*_DtInitAppContextp);
648           return;
649     }
650
651     /*
652      * This function register a ToolTalk pattern for every
653      * callback added.
654      */
655     pattern = tt_pattern_create();
656     if (tt_ptr_error(pattern) != TT_OK) {
657         _DtSvcAppUnlock(*_DtInitAppContextp);
658         return;
659     }
660
661     if (tt_pattern_scope_add(pattern, TT_SESSION) != TT_OK) {
662         _DtSvcAppUnlock(*_DtInitAppContextp);
663         return;
664     }
665     if (tt_pattern_category_set(pattern, TT_OBSERVE) != TT_OK) {
666         _DtSvcAppUnlock(*_DtInitAppContextp);
667         return;
668     }
669     if (tt_pattern_class_add(pattern, TT_NOTICE) != TT_OK) {
670         _DtSvcAppUnlock(*_DtInitAppContextp);
671         return;
672     }
673     if (tt_pattern_state_add(pattern, TT_SENT) != TT_OK) {
674         _DtSvcAppUnlock(*_DtInitAppContextp);
675         return;
676     }
677     sessId = tt_default_session();
678     if (tt_pattern_session_add(pattern, sessId) != TT_OK) {
679         _DtSvcAppUnlock(*_DtInitAppContextp);
680         return;
681     }
682     tt_free( sessId );
683     if (tt_pattern_op_add(pattern, "DtTypes_Reloaded") != TT_OK) {
684         _DtSvcAppUnlock(*_DtInitAppContextp);
685         return;
686     }
687
688     /*
689      * Store information needed by the callback in the user data
690      * fields of the pattern.
691      */
692     status = tt_pattern_user_set(pattern, 0, (void *)proc);
693     if (status != TT_OK) {
694         _DtSvcAppUnlock(*_DtInitAppContextp);
695         return;
696     }
697     status = tt_pattern_user_set(pattern, 1, (void *)client_data);
698     if (status != TT_OK) {
699         _DtSvcAppUnlock(*_DtInitAppContextp);
700         return;
701     }
702
703     /*
704      * _DbReloadCB is the ToolTalk callback which will call
705      * the user callback.
706      */
707     if (tt_pattern_callback_add(pattern, _DbReloadCB) != TT_OK) {
708         _DtSvcAppUnlock(*_DtInitAppContextp);
709         return;
710     }
711
712     if (tt_pattern_register(pattern) != TT_OK) {
713         _DtSvcAppUnlock(*_DtInitAppContextp);
714         return;
715     }
716
717     _DtSvcAppUnlock(*_DtInitAppContextp);
718 }
719
720
721 /******************************************************************************
722  ******************************************************************************
723  *
724  *      Private API Functions
725  *
726  ******************************************************************************
727  *****************************************************************************/
728
729 static void
730 _DtActTimerCB( XtPointer clientData, XtIntervalId IntId)
731 {
732         _DtActExecutionLeafNodeCleanup((unsigned long)clientData,
733            NULL,0,True);
734 }
735
736 static void
737 _DtActIndicatorCB( XtPointer clientData, XtIntervalId IntId )
738 {
739         unsigned long invocId  = (unsigned long) clientData;
740         _DtActInvRecT *invRecP = _DtActFindInvRec( invocId );
741
742         if ( !invRecP || IS_INV_FINISHED(invRecP->state) )
743         {
744             /* Turn off the activity indicator */
745             _DtSendActivityDoneNotification();
746         }
747         else
748         {
749             /* 
750              * Let the action turn off the indicator when invocation
751              * is complete.
752              */
753             SET_INV_INDICATOR_ON(invRecP->state);
754         }
755 }
756
757 /***************************************************************************
758 *
759 *  Routines and static data to support DtDbReloadNotify which supplies
760 *  the user with transparent access to the messaging system for 
761 *  notification of action/datatypes database changes.
762 *
763 ****************************************************************************/
764
765 /*
766  * _DbReloadCB 
767  * A ToolTalk callback function used to map callback arguments to
768  * the callback function specified by the user.  This function invokes the
769  * user-defined DtReloadNotifyProc callback with the desired client_data.
770  */
771
772 static Tt_callback_action
773 _DbReloadCB(Tt_message m, Tt_pattern p)
774 {
775     DtDbReloadCallbackProc      proc;
776     XtPointer                   client_data;
777
778     /*
779      * user data 0: DtDbReloadCallbackProc      proc;
780      * user data 1: XtPointer                   client_data;
781      */
782     proc = (DtDbReloadCallbackProc)tt_pattern_user(p, 0);
783     client_data = (XtPointer)tt_pattern_user(p, 1);
784
785     /*
786      * Call registered callback function.
787      */
788     if (proc) (*proc)(client_data);
789
790     return TT_CALLBACK_PROCESSED;
791 }
792
793
794 /***************************************************************************/
795 /***************************************************************************/
796 /*                        Error Dialog Code                                */
797 /***************************************************************************/
798 /***************************************************************************/
799
800 /*
801  * 'Ok' callback for the generic error dialogs.  It will simply destroy
802  * the dialog.
803  */
804
805 static void 
806 FreeErrorDialog(
807         Widget w,
808         XtPointer user_data,
809         XtPointer call_data )
810
811 {
812    XtDestroyWidget(XtParent(w));
813 }
814
815
816 /*
817  * Generic function used to create an error dialog.
818  */
819
820 void 
821 _DtCreateErrorDialog(
822         Widget w,
823         String actionName,
824         XmString msg )
825
826 {
827    String title;
828    int n;
829    Arg args[10];
830    Widget dialog;
831    XmString ok;
832    XWindowAttributes xwa;
833    Status status;
834    Boolean is_mapped = False;
835    char *fmt;
836
837    fmt = XtNewString((char *)Dt11GETMESSAGE(2, 1, "%1$s%2$s%3$s"));
838
839    /* Create the title string for the dialog */
840    title = (char *)XtMalloc((Cardinal)(strlen(PromptDialogTitle) + 
841                                        strlen(actionName) +
842                                        strlen(ErrorPostfix) +
843                                        strlen(fmt) + 1));
844
845    (void)sprintf(title, fmt, PromptDialogTitle, actionName, ErrorPostfix);
846
847    XtFree(fmt);
848
849    ok = XmStringCreateLocalized((String)_DtOkString);
850
851    if (XtIsRealized(w))
852    {
853      status = XGetWindowAttributes (XtDisplay (w), XtWindow (w), &xwa);
854      if (status && (xwa.map_state == IsViewable))
855        is_mapped = True;
856    }
857
858    /* Create the error dialog */
859    n = 0;
860    XtSetArg(args[n], XmNmessageString, msg); n++;
861    XtSetArg(args[n], XmNtitle, title); n++;
862    XtSetArg(args[n], XmNokLabelString, ok); n++;
863    XtSetArg(args[n], XmNuseAsyncGeometry, True); n++;
864    if (!is_mapped) 
865    {
866       XtSetArg (args[n], XmNdefaultPosition, False);
867       n++;
868    }
869    dialog = XmCreateErrorDialog(w, "errorDialog", args, n);
870    XmStringFree(ok);
871    XtFree(title);
872
873    /* Set up callbacks */
874    XtUnmanageChild(XmMessageBoxGetChild(dialog, XmDIALOG_HELP_BUTTON));
875    XtUnmanageChild(XmMessageBoxGetChild(dialog, XmDIALOG_CANCEL_BUTTON));
876    XtAddCallback(dialog, XmNokCallback, FreeErrorDialog,
877                  (XtPointer)NULL);
878
879    /*
880     * If the widget is not mapped, center this dialog.
881     */
882    if (!is_mapped) 
883    {
884       Dimension dialogWd, dialogHt;
885       Widget dialogShell = XtParent(dialog);
886
887       XtSetArg(args[0], XmNmappedWhenManaged, False);
888       XtSetValues(dialogShell, args, 1);
889
890       XtManageChild(dialog);
891       XtRealizeWidget (dialogShell);
892
893       XtSetArg(args[0], XmNwidth, &dialogWd);
894       XtSetArg(args[1], XmNheight, &dialogHt);
895       XtGetValues(dialog, args, 2);
896
897       XtSetArg (args[0], XmNx,
898                 (WidthOfScreen(XtScreen(dialog)) - dialogWd) / 2U);
899       XtSetArg (args[1], XmNy,
900                 (HeightOfScreen(XtScreen(dialog)) - dialogHt) / 2U);
901       XtSetValues (dialog, args, 2);
902
903       XtSetArg(args[0], XmNmappedWhenManaged, True);
904       XtSetValues(dialogShell, args, 1);
905    }
906
907    /* Display the dialog */
908    XtManageChild(dialog);
909 }
910
911
912 /*
913  * Error handler for when the user supplied a file name which cannot
914  * be accessed.  Displays an error dialog.  Most often, this is caused
915  * when a filename with an embedded space is received in the object list.
916  */
917
918 static void 
919 InvalidFilename(
920         Widget w,
921         String actionName,
922         String filename )
923
924 {
925    XmString pt1, pt2, msg;
926
927    /* Construct the error message */
928    pt1 = XmStringCreateLocalized(InvalidFileMsg);
929    pt2 = XmStringCreateLocalized(filename);
930    msg = XmStringConcat(pt1, pt2);
931
932    _DtCreateErrorDialog(w, actionName, msg);
933    XmStringFree(pt1);
934    XmStringFree(pt2);
935    XmStringFree(msg);
936 }
937
938
939 /*
940  * Error handler for when the user supplied a host name which cannot
941  * be accessed.  Displays an error dialog.
942  */
943
944 static void 
945 HostAccessError(
946         Widget w,
947         String actionName,
948         String hostName )
949
950 {
951    XmString pt1, pt2, pt3, msg, msg2;
952
953    /* Construct the error message */
954    pt1 = XmStringCreateLocalized(HostErrorMsg);
955    pt2 = XmStringCreateLocalized(hostName);
956    pt3 = XmStringCreateLocalized(HostErrorMsg2);
957
958    msg  = XmStringConcat(pt1, pt2);
959    msg2 = XmStringConcat(msg, pt3);
960
961    _DtCreateErrorDialog(w, actionName, msg2);
962
963    XmStringFree(pt1);
964    XmStringFree(pt2);
965    XmStringFree(pt3);
966    XmStringFree(msg);
967    XmStringFree(msg2);
968 }
969
970
971 /*
972  * Error handler for when the user supplied a collection of host names 
973  * which cannot be accessed.  Displays an error dialog.
974  */
975
976 static void 
977 MultiHostAccessError(
978         Widget w,
979         String actionName,
980         String hostList )
981
982 {
983    XmString msg;
984    char * buf = XtMalloc(strlen(MultiHostErrorMsg) + strlen(hostList) + 10);
985
986    sprintf(buf, MultiHostErrorMsg, hostList);
987    msg = XmStringCreateLocalized(buf);
988    _DtCreateErrorDialog(w, actionName, msg);
989
990    XmStringFree(msg);
991    XtFree(buf);
992 }
993
994 /***************************************************************************
995  * 
996  * MapError - this function creates an error message when an action
997  *   cannot be executed because of too many "MAPs". 
998  *
999  * PARAMETERS:
1000  *
1001  *   Widget w;          - Widget needed for posting the error dialog.
1002  *
1003  *   String actionName; - The name of the action.
1004  *
1005  * RETURN:      void
1006  *
1007  ****************************************************************************/
1008
1009 static void 
1010 MapError(
1011         Widget w,
1012         String actionName )
1013 {
1014
1015    XmString msg = XmStringCreateLocalized(MapErrorMsg);
1016    _DtCreateErrorDialog(w, actionName, msg);
1017    XmStringFree(msg);
1018
1019 }
1020
1021 /*
1022  * Error handler for when an action definition cannot be found to
1023  * match an object of a particular type.  Displays an error dialog.
1024  * A different message is displayed when no objects is supplied.
1025  */
1026
1027 /* 
1028 * RWV: 
1029 * Since we use tmp files for buffers and do not support
1030 * strings; we need not add special code for buffer and
1031 * string support.
1032 */
1033 /* fdt: Will need to also handle a string or a buffer ... eventually */
1034
1035 static void 
1036 NoActionError(
1037         Widget w,
1038         DtShmBoson origNameQuark,
1039         char * actionName,
1040         char * type,
1041         char * host,
1042         char * dir,
1043         char * file )
1044
1045 {
1046    char     *msgbuf = XtMalloc(2*MAXPATHLEN);
1047    XmString msg;
1048    char *   name = NULL;
1049
1050    /* Construct the error message */
1051    if ((host == NULL) && (dir == NULL) && (file == NULL) && (type == NULL) )
1052    {
1053       (void)sprintf(msgbuf,NoActionMsg2,actionName);
1054    }
1055    else if ( (type != NULL ) && (file == NULL) && (dir == NULL))
1056    {
1057       /*
1058        * We are dealing with a buffer object for which an action couldn't 
1059        * be located.
1060        */
1061       (void)sprintf(msgbuf,NoActionMsg3,actionName,type);
1062    }
1063    else
1064    {
1065       name = (char *)XtMalloc((Cardinal)((host ? strlen(host) : 0) +
1066                                          (dir ? strlen(dir) : 0) +
1067                                          (file ? strlen(file) : 0) + 10));
1068       name[0] = '\0';
1069       
1070       /* Construct the file name */
1071       if (host)
1072       {
1073          (void)strcat(name, host);
1074          (void)strcat(name, ":");
1075       }
1076
1077       if (dir)
1078       {
1079          (void)strcat(name, dir);
1080          if (strcmp(dir, "/") != 0)
1081             (void)strcat(name, "/");
1082       }
1083
1084       if (file)
1085          (void)strcat(name, file);
1086
1087       (void)sprintf(msgbuf,NoActionMsg,actionName,name,type);
1088
1089    }
1090    msg  = XmStringCreateLocalized(msgbuf);
1091
1092    _DtCreateErrorDialog(w, actionName, msg);
1093    XmStringFree(msg);
1094    XtFree(msgbuf);
1095    XtFree(name);
1096 }
1097
1098
1099 /*
1100  * Error handler for when the Command Invoker detects an error, and
1101  * send us a failure response to our action request.
1102  * Display an error dialog.
1103  */
1104
1105 static void 
1106 CommandInvokerError(
1107         Widget w,
1108         String actionName,
1109         String errorString )
1110
1111 {
1112    XmString msg;
1113
1114    msg = XmStringCreateLocalized(errorString);
1115    _DtCreateErrorDialog(w, actionName, msg);
1116    XmStringFree(msg);
1117 }
1118
1119
1120 /*
1121  * If an action requires a ToolTalk connection, and we were unable to get
1122  * one, then we will fail and post an error dialog.
1123  */
1124
1125 static void 
1126 NoToolTalkConnectionError(
1127         Widget w,
1128         String actionName,
1129         Tt_status status)
1130
1131 {
1132         XmString msg;
1133         char *errmsg, *statmsg;
1134                    
1135         if (TT_OK == status)
1136           statmsg = "";
1137         else
1138           statmsg = tt_status_message(status);
1139         errmsg = XtMalloc(strlen(NoToolTalkConnMsg) + strlen(statmsg) + 2);
1140         sprintf(errmsg, NoToolTalkConnMsg, statmsg);
1141
1142         msg = XmStringCreateLocalized(errmsg);
1143         _DtCreateErrorDialog(w, actionName, msg);
1144
1145         XtFree(errmsg);
1146         XmStringFree(msg);
1147 }
1148
1149 static void
1150 TmpFileCreateError( Widget w, char *actionName, char *dirName)
1151 {
1152         XmString    msg;
1153         char        *msgbuf = XtMalloc(_DtAct_MAX_BUF_SIZE);
1154         
1155         sprintf(msgbuf,TmpFileCreateErrorMsg,_DtActNULL_GUARD(dirName),
1156              actionName);
1157
1158         msg  = XmStringCreateLocalized(msgbuf);
1159         _DtCreateErrorDialog(w, actionName, msg);
1160         XmStringFree(msg);
1161         XtFree(msgbuf);
1162 }
1163
1164 static void
1165 TmpFileOpenError( Widget w, char *actionName, char *fileName)
1166 {
1167         XmString    msg;
1168         char        *msgbuf = XtMalloc(_DtAct_MAX_BUF_SIZE);
1169         
1170         sprintf(msgbuf,TmpFileOpenErrorMsg,_DtActNULL_GUARD(fileName),
1171              actionName);
1172
1173         msg  = XmStringCreateLocalized(msgbuf);
1174         _DtCreateErrorDialog(w, actionName, msg);
1175         XmStringFree(msg);
1176         XtFree(msgbuf);
1177 }
1178
1179 static void
1180 TmpFileWriteError( Widget w, char *actionName, char *fileName)
1181 {
1182         XmString    msg;
1183         char        *msgbuf = XtMalloc(_DtAct_MAX_BUF_SIZE);
1184         
1185         sprintf(msgbuf,TmpFileWriteErrorMsg,_DtActNULL_GUARD(fileName),
1186             actionName);
1187
1188         msg  = XmStringCreateLocalized(msgbuf);
1189         _DtCreateErrorDialog(w, actionName, msg);
1190         XmStringFree(msg);
1191         XtFree(msgbuf);
1192 }
1193
1194 static void
1195 UnSupportedObject( Widget w, char *actionName, int objClass)
1196 {
1197         XmString    msg;
1198         char        *msgbuf = XtMalloc(_DtAct_MAX_BUF_SIZE);
1199         
1200         sprintf(msgbuf,UnSupportedObjMsg,objClass,actionName);
1201
1202         msg  = XmStringCreateLocalized(msgbuf);
1203         _DtCreateErrorDialog(w, actionName, msg);
1204         XmStringFree(msg);
1205         XtFree(msgbuf);
1206 }
1207
1208
1209
1210
1211 /***************************************************************************/
1212 /***************************************************************************/
1213 /*              Main Work Functions For _DtActionInvoke()                   */
1214 /***************************************************************************/
1215 /***************************************************************************/
1216
1217
1218 /*
1219  * Load the globals pointing to any localizable strings.
1220  */
1221
1222 static void 
1223 InitLocalizedStrings( void )
1224
1225 {
1226    PromptDialogTitle = XtNewString(((char *)Dt11GETMESSAGE(2, 3, "Action:  ")));
1227    ErrorPostfix = XtNewString(((char *)Dt11GETMESSAGE(2, 4, "   [Error]")));
1228    PromptDialogLabel = XtNewString(
1229          ((char *)Dt11GETMESSAGE(2, 5, "Please enter the following information:")));
1230    ContinueMessage = XtNewString(
1231     ((char *)Dt11GETMESSAGE(2, 6, "You have supplied more parameters than the selected action requires.\n\nSelect 'Ok' to ignore extra parameters.\n\nSelect 'Cancel' to terminate the action.")));
1232    HostErrorMsg =XtNewString(((char *)Dt11GETMESSAGE(2, 7, "The following host was not accessible:\n\n        ")));
1233
1234 #ifdef _SUN_OS
1235    HostErrorMsg2 =XtNewString(((char *)Dt11GETMESSAGE(2, 8, "\n\nThis may be because the remote host's file\nsystem is not properly mounted.\n\n")));
1236 #else
1237    HostErrorMsg2 =XtNewString(((char *)Dt11GETMESSAGE(2, 8, "\n\nCheck that the appropriate remote data access connection\nhas been made.\n\n(See \"The Common Desktop Environment User's Guide\"\nfor more information.)\n")));
1238 #endif
1239    NoActionMsg =XtNewString(
1240         ((char *)Dt11GETMESSAGE(2, 9, "Either action \"%s\" was not found\n         or\nthis action does not apply to the file:\n    \"%s\"\nwith data attribute:  \"%s%\"\n\n")));
1241    NoActionMsg2 = XtNewString(((char *)Dt11GETMESSAGE(2, 10, "Action \"%s\" was not found.\n")));
1242    InvalidFileMsg = XtNewString(((char *)Dt11GETMESSAGE(2, 11, "The following file was not found:\n\n       ")));
1243    MapErrorMsg = XtNewString(((char *)Dt11GETMESSAGE(2, 12, "This action cannot be executed because it contains too\nmany levels of MAPs, or the mapping is \"circular\".")));
1244    MultiHostErrorMsg =XtNewString(((char *)Dt11GETMESSAGE(2,13, "Unable to invoke the requested action.\n\nAre the following hosts accessible?\n     (%s)\nDoes the corresponding program exist?\n(Run " CDE_INSTALLATION_TOP "/bin/dttypes to match actions and programs.)\n\nHas your system run out of room to execute new processes?")));
1245    IcccmReqErrorMsg = XtNewString(((char *)Dt11GETMESSAGE(2,14,"The request to service this action has failed")));
1246    NoToolTalkConnMsg = XtNewString(((char *)Dt11GETMESSAGE(2,15,"The request to service this action has failed.\nA ToolTalk connection could not be established:\n\n%s")));
1247    ToolTalkErrorMsg = XtNewString(((char *)Dt11GETMESSAGE(2,16, "The request to service this action has failed")));
1248    ToolTalkErrorMsg2 = XtNewString(((char *)Dt11GETMESSAGE(2,17, "The request to service this action has failed for the following reason:\n\n    %s")));
1249    TtFileArgMapErr = XtNewString((char *)Dt11GETMESSAGE(2,18,"An error occurred while attempting to map one of\nthe file arguments."));
1250    NoActionMsg3 =XtNewString(
1251         ((char *)Dt11GETMESSAGE(2, 19, "Either action \"%s\" was not found\n         or\nthis action does not apply to buffers of type:\n  \"%s\"\n\n")));
1252    UnSupportedObjMsg = XtNewString(
1253         ((char *)Dt11GETMESSAGE(2, 21, "Unsupported input object class: \"%d\"\nfor action: \"%s\".")));
1254    TmpFileCreateErrorMsg = XtNewString(
1255         ((char *)Dt11GETMESSAGE(2, 22, "Unable to create a temporary file in directory: \"%s\"\nfor the action named: \"%s\"")));
1256    TmpFileOpenErrorMsg = XtNewString(
1257         ((char *)Dt11GETMESSAGE(2, 23, "Unable to open a temporary file: \"%s\"\nfor the action named: \"%s\"")));
1258    TmpFileWriteErrorMsg = XtNewString(
1259         ((char *)Dt11GETMESSAGE(2, 24, "Unable to write a temporary file: \"%s\"\nfor the action named: \"%s\"")));
1260 }
1261
1262 /*
1263  * This function takes the information supplied by the caller of
1264  * _DtActionInvoke(), and turns it into an internal format.  This
1265  * includes parsing out each of the file names, and converting the
1266  * type from a string to an integer.
1267  *
1268  * The structure returned must be freed up eventually.
1269  */
1270
1271 static ActionRequest * 
1272 CreateActionRequest(
1273         Widget w,
1274         String actionName,
1275         DtActionArg  *aap,
1276         int aac,
1277         String termOpts,
1278         String execHost,
1279         String cwdHost,
1280         String cwdDir,
1281         _DtActInvRecT *invp )
1282
1283 {
1284    int i, j;
1285    int numObjects = 0;
1286    ObjectData * objectDataArray;
1287    ObjectData objectData;
1288    ActionRequest * request;
1289
1290
1291    /* Allocate a new request structure -- zero filled */
1292    request = (ActionRequest *) XtCalloc(1,(Cardinal)sizeof(ActionRequest));
1293
1294    request->actionName = XtNewString(actionName);
1295
1296    if (termOpts)
1297       request->termOpts = XtNewString(termOpts);
1298
1299    if (execHost) 
1300       request->execHost = XtNewString(execHost);
1301
1302    if (cwdHost)
1303       request->cwdHost = XtNewString(cwdHost);
1304
1305    if (cwdDir)
1306       request->cwdDir = XtNewString(cwdDir);
1307
1308    request->objsUsed = -1;      /* -1 => not yet determined */
1309
1310    if ( invp )
1311        request->invocId = invp->id;
1312
1313    /* If there are no objects, then there's no reason to continue */
1314    if ((aac <= 0) || (aap == NULL))
1315       return(request);
1316
1317    /*
1318     * Allocate space for all the object data at once
1319     */
1320    objectDataArray = (ObjectData *) XtCalloc(aac,(sizeof(ObjectData)));
1321
1322    /*
1323     * process object names -- assume all file names are of the form
1324     *    /path/file  (do NOT allow host:/path/file)
1325     */
1326    for ( i = 0; i < aac ; i++ ) 
1327    {
1328        memset((void *)&objectData,0,sizeof(ObjectData));
1329        if ( (aap+i)->argClass == DtACTION_FILE ) 
1330        {
1331           if (ParseFileArgument(w, request, &objectData, NULL ,
1332                             aap[i].u.file.name, NULL , True))
1333           {
1334              XtFree((char *)objectDataArray);
1335              return(NULL);
1336           }
1337        } 
1338        else if ( (aap+i)->argClass == DtACTION_BUFFER )
1339        {
1340            /*
1341             * Check if we've already created a tmp file for this buffer
1342             * if so fill in the request structure as if this were a file
1343             * object.
1344             */
1345            if ( invp->info[i].name )
1346            {
1347               /*
1348                * Use the tmp file name and type stored in the invocation rec.
1349                * The FILE bit will be set in the object mask -- we will also
1350                * set the BUFFER bit to indicate that this is a tmp file 
1351                * representing a buffer.
1352                */
1353               if (ParseFileArgument(w, request, &objectData, NULL ,
1354                                 invp->info[i].name, invp->info[i].type , True))
1355               {
1356                  XtFree((char *)objectDataArray);
1357                  return(NULL);
1358               }
1359               /*
1360                * Set the buffer object bit as well -- and check whether
1361                * this buffer is intended to be writable, if not reset the
1362                * writable bit set in ParseFileArgument().
1363                */
1364               SET_BUFFER_OBJ(objectData.mask);
1365                if ( !(aap[i].u.buffer.writable) )
1366                     RESET_WRITE_OBJ(objectData.mask);
1367                /*
1368                 * Save the buffer type info if we have it
1369                 */
1370                if ( aap[i].u.buffer.type )
1371                     objectData.type = _DtDtsMMStringToBoson(aap[i].u.buffer.type);
1372
1373                /*
1374                 * Save the original buffer pointer and size for this
1375                 * pseudo-file.
1376                 */
1377                if ( aap[i].u.buffer.bp )
1378                {
1379                    objectData.u.file.bp = aap[i].u.buffer.bp;
1380                    objectData.u.file.sizebp = aap[i].u.buffer.size;
1381                }
1382               
1383            }
1384            else
1385            {
1386                /* Do buffer stuff here */
1387                SET_BUFFER_OBJ(objectData.mask);
1388                if ( aap[i].u.buffer.writable )
1389                     SET_WRITE_OBJ(objectData.mask);
1390
1391                /*
1392                 * If the buffer type has been passed in to us save its quark 
1393                 * in the object structure now.  When/if the type is determined
1394                 * later this object record should be filled in with the
1395                 * necessary quark.
1396                 */
1397                if ( aap[i].u.buffer.type )
1398                     objectData.type = _DtDtsMMStringToBoson(aap[i].u.buffer.type);
1399                else
1400                {
1401                     /*
1402                      * We have already determined the buffer type when creating
1403                      * the invocation record. So get the type string
1404                      * from the invocation record.
1405                      */
1406                      myassert(invp->info[i].type);
1407                      if (invp->info[i].type)
1408                      {
1409                         objectData.type = _DtDtsMMStringToBoson(invp->info[i].type); 
1410                      }
1411                 }
1412                 
1413                /*
1414                 * Save buffer contents
1415                 */
1416                if ( aap[i].u.buffer.bp )
1417                {
1418                    objectData.u.buffer.size = aap[i].u.buffer.size;
1419                    objectData.u.buffer.bp = aap[i].u.buffer.bp;
1420                } else
1421                {
1422                    myassert(0 /* null buffer pointer */ );
1423                    objectData.u.buffer.bp = NULL;
1424                    objectData.u.buffer.size = 0;
1425                }
1426            }
1427        }
1428
1429         /* structure assignment */
1430         objectDataArray[i] = objectData;
1431         numObjects++;
1432    }
1433
1434    request->numObjects = numObjects;
1435    request->objects    = objectDataArray;
1436
1437    return(request);
1438 }
1439
1440
1441 /******************************************************************************
1442  *
1443  * static _DtActInvRecT *
1444  * CreateInvocationRecord(actionName,w,aap,aac)
1445  *      Create an invocation record and fill in argument information
1446  *      return a pointer to the newly allocated invocation record.
1447  *
1448  *****************************************************************************/
1449 static _DtActInvRecT *
1450 CreateInvocationRecord(
1451         char            *actionName,
1452         Widget          w,
1453         DtActionArg     *aap,
1454         int             aac)
1455 {
1456     int i;
1457     _DtActInvRecT       *invp;  /* pointer to invocation record */
1458     char                *tmp;
1459
1460         /*
1461          * allocate invocation record and ID to return to caller
1462          */
1463       invp =  _DtActAllocInvRec();
1464       if ( !invp )
1465       {
1466                 /*
1467                  * RWV --> ideally we would need error message here 
1468                  *         but if we are unable to allocate the record we
1469                  *         would in all likelyhood be unable to allocate the
1470                  *         error message as well.  We will essentially assume
1471                  *         allocation does not fail as is done throughout the
1472                  *         library.
1473                  */
1474                 return NULL;
1475       }
1476       myassert(invp->id != 0);
1477
1478       SET_INV_PENDING(invp->state);
1479       invp->w = w;
1480       invp->numChildren = 0;
1481       invp->childRec = NULL;
1482
1483       /*
1484        * Fill in argument information
1485        */
1486       invp->ac = aac;
1487
1488       if (aac == 0) {
1489          invp->info = NULL;
1490
1491          return invp;
1492       }
1493
1494       invp->info = (_DtActArgInfo *)XtCalloc(aac,sizeof(_DtActArgInfo));
1495
1496       for ( i=0; i < aac; i++ )
1497       {
1498          if ( aap[i].argClass == DtACTION_BUFFER )
1499          {
1500             int fd;             /* tmp file descriptor */
1501             char *format;       /* name template (printf format) */
1502             char *is_executable;        /* IS_EXECUTABLE attribute */
1503             mode_t mode; 
1504             int bytesToWrite, bytesWritten;
1505             int closeAttempts;
1506
1507             SET_BUFFER_OBJ(invp->info[i].mask);
1508             if ( aap[i].u.buffer.writable )
1509                 SET_WRITE_OBJ(invp->info[i].mask);
1510
1511             /* save original buffer size */
1512             invp->info[i].size = aap[i].u.buffer.size;
1513
1514             /*
1515              * Determine the type of the buffer object.
1516              * Typing based on the object name takes precedence
1517              * over the type "hint".
1518              */
1519             if ( aap[i].u.buffer.name 
1520                  || (aap[i].u.buffer.type == NULL) ) 
1521             {
1522                 tmp = DtDtsBufferToDataType(
1523                           aap[i].u.buffer.bp,aap[i].u.buffer.size,
1524                           aap[i].u.buffer.name);
1525                 /*
1526                  * Malloc our own copy of the type string so we won't
1527                  * have to worry about when to call DtDtsFreeDataType() later.
1528                  */
1529                 invp->info[i].type = XtNewString(tmp);
1530                 DtDtsFreeDataType(tmp);
1531             } else
1532             {
1533                 invp->info[i].type = XtNewString(aap[i].u.buffer.type);
1534             }
1535         
1536             /* 
1537              * Simply create tmp files for ALL buffers.
1538              * 
1539              * This allows us to work around problems related to client
1540              * programs making subsequent changes to or freeing the memory
1541              * associated with the buffer before we are through with it.
1542              *
1543              * For actions of type CMD we need to have files anyway.
1544              *
1545              * Be sure to create tmp files with
1546              * a suffix proper for the buffer type. 
1547              */
1548
1549            /* first determine the permissions for the new tmp file */
1550            is_executable = 
1551                 DtDtsDataTypeToAttributeValue(invp->info[i].type,
1552                      _DtActIS_EXECUTABLE,NULL);
1553            /*
1554             * The tmp file should at LEAST be readable
1555             */
1556            mode=( S_IRUSR  |  S_IRGRP | S_IROTH );
1557            if ( aap[i].u.buffer.writable )
1558                mode |= ( S_IWUSR | S_IWGRP | S_IWOTH );
1559            if ( is_executable 
1560                    && DtDtsIsTrue(is_executable) )
1561                 mode |= ( S_IXUSR | S_IXGRP | S_IXOTH );
1562
1563            DtDtsFreeAttributeValue(is_executable);
1564
1565
1566            if ( aap[i].u.buffer.name )
1567            {
1568                /*
1569                 * Attempt to use the name supplied for the buffer.
1570                 */
1571                invp->info[i].name = _DtActGenerateTmpFile(NULL,
1572                         aap[i].u.buffer.name,mode,&fd);
1573            }
1574            if ( !invp->info[i].name )
1575            { 
1576                /*
1577                 * Generate tmp file based on format supplied for the
1578                 * file type.
1579                 */
1580                format = DtDtsDataTypeToAttributeValue(invp->info[i].type,
1581                          _DtActNAME_TEMPLATE,NULL);
1582
1583                invp->info[i].name = _DtActGenerateTmpFile(NULL,format,mode,&fd);
1584                DtDtsFreeAttributeValue(format);
1585            }
1586            if ( !invp->info[i].name )
1587            {
1588                 /*
1589                  * Unable to generate usable tmp file name.
1590                  */
1591                 /* 
1592                  * Error message makes assertion message redundant.
1593                  * myassert(invp->info[i].name);
1594                  */
1595                 TmpFileCreateError(w,actionName,_DtGetDtTmpDir());
1596
1597                 RESET_INV_PENDING(invp->state);
1598                 SET_INV_ERROR(invp->state);
1599                 SET_INV_CANCEL(invp->state);
1600
1601                 close(fd);
1602                 return invp;
1603            }    
1604
1605            /*
1606             * Write contents of buffer to temp file
1607             */
1608            myassert( fd >= 0 );
1609            for ( bytesToWrite = aap[i].u.buffer.size, bytesWritten = 0;
1610                   bytesToWrite > 0;
1611                   bytesToWrite -= bytesWritten)
1612            {
1613                 bytesWritten = write(fd,aap[i].u.buffer.bp,bytesToWrite);
1614                 if ( bytesWritten < 0 ) 
1615                 {
1616                     if (errno == EINTR )
1617                     {
1618                             bytesWritten = 0;
1619                             continue;
1620                     } 
1621                     else
1622                     {
1623                         myassert(0  /* Unrecoverable Write Error */);
1624                         TmpFileWriteError(w,actionName,invp->info[i].name);
1625
1626                         close(fd);
1627                         (void) unlink(invp->info[i].name);
1628
1629                         RESET_INV_PENDING(invp->state);
1630                         SET_INV_ERROR(invp->state);
1631                         SET_INV_CANCEL(invp->state);
1632
1633                         return invp;
1634                     }
1635                 }
1636             }
1637             
1638             closeAttempts = 0;
1639             while ( close(fd) )
1640             {
1641              /* error closing fd */
1642                 if ( closeAttempts > _DT_ACTION_MAX_CLOSE_TRIES )
1643                     break;
1644
1645                 switch ( errno )
1646                 {
1647                     case EBADF:  /* invalid fd */
1648                         myassert( 0 );
1649                         break;
1650                     case EINTR:  /* interrupted sys call */
1651                         closeAttempts++;
1652                         continue;       /* try again */
1653                     case ENOSPC: /* Not enough space on NFS-mounted dev */
1654                         TmpFileWriteError(w,actionName,
1655                                        invp->info[i].name);
1656
1657                         unlink(invp->info[i].name);
1658                         RESET_INV_PENDING(invp->state);
1659                         SET_INV_ERROR(invp->state);
1660                         SET_INV_CANCEL(invp->state);
1661
1662                         /* try another close */
1663                         if ( close(fd) ) 
1664                         {
1665                             /* It should have worked this time */
1666                             myassert(0);
1667                         }
1668                         return invp;
1669                     default: /* anything else */
1670                         myassert(0);
1671                         break;
1672                 }
1673                 break;  /* only try again for conditions with continue */
1674             }
1675             
1676
1677            /*
1678             * Now that we have created a tmp file for this buffer
1679             * object set the FILE_OBJ flag as well as the buffer flag.
1680             * Objects with both the BUFFER and FILE flags set will be
1681             * recognized as buffers which have been written to tmp files.
1682             */
1683             
1684             SET_FILE_OBJ(invp->info[i].mask);
1685
1686          }
1687          else if ( aap[i].argClass == DtACTION_FILE )
1688          {
1689             invp->info[i].name = XtNewString(aap[i].u.file.name);
1690
1691             SET_FILE_OBJ(invp->info[i].mask);
1692             SET_WRITE_OBJ(invp->info[i].mask);
1693          }
1694          else
1695          {
1696             myassert( 0 /* unsupported object */ );
1697             UnSupportedObject(w, actionName, aap[i].argClass);
1698
1699             RESET_INV_PENDING(invp->state);
1700             SET_INV_ERROR(invp->state);
1701             SET_INV_CANCEL(invp->state);
1702
1703             return invp;
1704          }
1705       }
1706
1707       return invp;
1708 }
1709
1710
1711
1712 static Boolean 
1713 ParseFileArgument(
1714         Widget w,
1715         ActionRequest * request,
1716         ObjectData * objectData,
1717         String hostname,
1718         String filename,
1719         String filetype,
1720         Boolean typeFile )
1721
1722 {
1723    int i, j;
1724    String dirName;
1725    String host;
1726    String dir;
1727    int hostId;
1728    char *resolvedPath=NULL;
1729
1730 /********************************************************************
1731         WE NO LONGER ACCEPT host:/path FORMAT
1732    if (host = _DtHostString(filename))
1733    {
1734       hostId = _DtAddEntry(host, &request->hostNames, &request->numHostNames);
1735       XtFree(host);
1736    }
1737    else 
1738 ********************************************************************/
1739    if ( hostname )
1740    {
1741       hostId = _DtAddEntry(hostname, &request->hostNames, 
1742                            &request->numHostNames);
1743    }
1744    else
1745    {
1746       if ( request->cwdHost != NULL ) 
1747       {
1748          hostId = _DtAddEntry(request->cwdHost, &request->hostNames, 
1749                               &request->numHostNames);
1750       }
1751       else
1752       {
1753          /* if all else fails use local host */
1754          host = _DtGetLocalHostName();
1755          hostId = _DtAddEntry(host, &request->hostNames, 
1756                               &request->numHostNames);
1757          XtFree(host);
1758       }
1759    }
1760
1761    objectData->u.file.origFilename = XtNewString(filename);
1762    objectData->u.file.origHostname = XtNewString(hostname);
1763    objectData->u.file.hostIndex = hostId;
1764    objectData->u.file.baseFilename = _DtBasename(filename);
1765    objectData->type = -1;
1766
1767    /* Hash the directory name */
1768    if ( (dirName  = _DtDirname(filename)) == NULL )
1769    {
1770       if ( request->cwdDir )
1771          dirName = XtNewString(request->cwdDir);
1772       else
1773       {
1774          /* Default to current directory */
1775          dirName = _DtFindCwd();
1776       }
1777    }
1778    else if ( dirName[0] != '/' )
1779    {
1780       /*
1781        * We have been provided with a relative path name
1782        * interpret it relative to the context directory.
1783        */
1784       String tmpName;
1785
1786       if ( request->cwdDir )
1787          tmpName=XtNewString(request->cwdDir);
1788       else
1789          tmpName=_DtFindCwd();
1790                 
1791       tmpName=XtRealloc(tmpName,strlen(tmpName)+strlen(dirName) +2);
1792       (void)strcat(tmpName,"/");
1793       (void)strcat(tmpName,dirName);
1794       XtFree(dirName);
1795       dirName=tmpName;
1796    }
1797           
1798    if ( objectData->u.file.baseFilename == NULL || dirName == NULL )
1799    {
1800       /* Invalidly formed file name */
1801       InvalidFilename(w, request->clonedAction->label, filename);
1802       _DtFreeRequest (request);
1803       XtFree(dirName);
1804       XtFree(objectData->u.file.origFilename);
1805       XtFree(objectData->u.file.origHostname);
1806       XtFree(objectData->u.file.baseFilename);
1807       return(True);
1808    }
1809
1810    objectData->u.file.dirIndex = _DtAddEntry(dirName, &request->dirNames,
1811                                              &request->numDirNames);
1812    SET_UNKNOWN_IF_DIR(objectData->mask);
1813    SET_FILE_OBJ(objectData->mask);
1814    /*
1815     * default file objects are treated as writable/returned objects.
1816     */
1817    SET_WRITE_OBJ(objectData->mask);
1818
1819    /*
1820     * If a type has been provided for this file -- use it.
1821     * otherwise -- look up the type.
1822     */
1823    if (typeFile)
1824    {
1825       if ( filetype && *filetype )
1826          objectData->type = _DtDtsMMStringToBoson(filetype);
1827       else
1828       {
1829          TryToTypeFile(objectData,request->hostNames[hostId],
1830                       dirName, objectData->u.file.baseFilename,
1831                       &resolvedPath);
1832
1833          if ( resolvedPath )
1834          {
1835                 struct stat sbuf;
1836                 if ( !stat(resolvedPath,&sbuf) )
1837                 {
1838                         /* successful stat of file -- check permissions */
1839                       if ( !( sbuf.st_mode&S_IWOTH 
1840                            || sbuf.st_mode&S_IWGRP 
1841                            || sbuf.st_mode&S_IWUSR) )
1842                       {
1843                              RESET_WRITE_OBJ(objectData->mask);
1844                       }
1845                 }
1846          }
1847         
1848       }
1849    }
1850    XtFree(resolvedPath);
1851    XtFree(dirName);
1852    return(False);
1853 }
1854
1855
1856 /*
1857  * Returns a string representing the current working directory
1858  * for this process. This string must be freed up by the caller.
1859  * NOTE:This function does not replace sym_links with real paths.
1860  *      This may be useful on networks where nfs mounts and symbolic
1861  *      consistently named symbolic links are used to give the
1862  *      impression of a single large network file system.
1863  */
1864
1865 String 
1866 _DtFindCwd( void )
1867
1868 {
1869         String tmp = 0;
1870         char buf[MAXPATHLEN + 1];
1871
1872         if ((tmp = getcwd(buf, MAXPATHLEN)) == NULL)
1873         {
1874                 _DtSimpleError(
1875                         DtProgName, DtError, NULL,
1876                         "getcwd(): unable to get current directory", NULL);
1877                 tmp = "/";
1878         }
1879         return (XtNewString(tmp));
1880 }
1881
1882
1883 /*
1884  * Generic function which checks to see if the specified string is
1885  * already entered in the passed-in array; if so, then it will return
1886  * the index of the existing entry within the array; if not, then it
1887  * will grow the array, add the string into it, and then return the
1888  * new index.
1889  */
1890
1891 static int 
1892 _DtAddEntry(
1893         String string,
1894         String **arrayPtr,
1895         int *sizePtr )
1896
1897 {
1898    int i;
1899
1900    /* See if the string is already in the array */
1901    for (i = 0; i < *sizePtr; i++)
1902    {
1903       if (strcmp(string, (*arrayPtr)[i]) == 0)
1904          return(i);
1905    }
1906
1907    /* Add the string */
1908    i = *sizePtr;
1909    (*sizePtr)++;
1910    (*arrayPtr) = (String *)XtRealloc((String)*arrayPtr, 
1911                            (Cardinal)(sizeof(String) * (*sizePtr)));
1912    (*arrayPtr)[i] = XtNewString(string);
1913    return(i);
1914 }
1915
1916
1917 /*
1918  * This function will type the indicated file, only if it is the first
1919  * parameter file; this is to improve performance, since in many cases,
1920  * only the first argument is used to 'type' the action, and the others
1921  * never need to be 'typed'.
1922  */
1923
1924 static void 
1925 TryToTypeFile(
1926         ObjectData *obj,
1927         char * host,
1928         char * dir,
1929         char * file,
1930         char **resolvedPath )
1931
1932 {
1933    /* Follow the link when typing files */
1934    obj->type = LinkToTypeQuark(host, dir, file, resolvedPath);
1935 }
1936
1937
1938 /*
1939  * Given a file, follow any links, and base the filetype off of the
1940  * final file, not the link we are passed.
1941  */
1942
1943 static int 
1944 LinkToTypeQuark(
1945         char * host,
1946         char * dir,
1947         char * file,
1948         char **resolvedPath )
1949
1950 {
1951    char * path;
1952    char link_path[MAXPATHLEN + 1];
1953    char file_name[MAXPATHLEN + 1];
1954    int link_len;
1955    char * end;
1956    int history_count;
1957    int history_size;
1958    char ** history;
1959    int i;
1960    char * dtype;
1961    DtShmBoson dquark;
1962
1963    /* Used to check for symbolic link loops */
1964    history_count = 0;
1965    history_size = 100;
1966    history = (char **)XtMalloc(sizeof(char *) * history_size);
1967
1968    path = _DtActMapFileName(host, dir, file, NULL);
1969    if (path == NULL)
1970    {
1971       *resolvedPath=NULL;
1972       return(-1);
1973    }
1974    strcpy(file_name, path);
1975    XtFree(path);
1976
1977    while ((link_len = readlink(file_name, link_path, MAXPATHLEN)) > 0)
1978    {
1979       link_path[link_len] = '\0';
1980
1981       /* Force the link to be an absolute path, if necessary */
1982       if (link_path[0] != '/')
1983       {
1984          /* Relative paths are relative to the current directory */
1985          end = DtStrrchr(file_name, '/') + 1;
1986          *end = '\0';
1987          strcat(file_name, link_path);
1988       }
1989       else
1990          strcpy(file_name, link_path);
1991
1992       /* Check for a recursive loop; abort if found */
1993       for (i = 0; i < history_count; i++)
1994       {
1995          if (strcmp(file_name, history[i]) == 0)
1996          {
1997             /* Drop back to last non-recursive portion of the path */
1998             strcpy(file_name, history[history_count-1]);
1999             for (i = 0; i < history_count; i++)
2000                XtFree(history[i]);
2001             XtFree((char *)history);
2002             dtype = DtDtsFileToDataType(file_name);
2003             dquark = _DtDtsMMStringToBoson(dtype);
2004             DtDtsFreeDataType(dtype);
2005             *resolvedPath = XtNewString(file_name);
2006             return(dquark);
2007          }
2008       }
2009
2010       /* Add to the history list */
2011       if (history_count >= history_size)
2012       {
2013          history_size += 100;
2014          history = (char **)XtRealloc((char *)history, 
2015                                       sizeof(char *) * history_size);
2016       }
2017       history[history_count++] = XtNewString(file_name);
2018    }
2019
2020    /* Free up the history list */
2021    for (i = 0; i < history_count; i++)
2022       XtFree(history[i]);
2023    XtFree((char *)history);
2024
2025    dtype = DtDtsFileToDataType(file_name);
2026    dquark = _DtDtsMMStringToBoson(dtype);
2027    DtDtsFreeDataType(dtype);
2028    *resolvedPath = XtNewString(file_name);
2029    return(dquark);
2030 }
2031
2032
2033 /*
2034  * Given a request, find the action to which it maps, and see if enough
2035  * parameters were supplied to allow the action to be started.  It's
2036  * possible we may need to bring up a dialog to collect more data, or
2037  * we may need to invoke multiple actions.
2038  *
2039  * The first time an action request is processed, we will check the
2040  * parameter situation, and will prompt the user, if necessary.  The
2041  * second time the action request is processed (typically when the
2042  * user closes the parameter collecting dialog), we will simply invoke
2043  * the action with whatever we have; the user will not be prompted a
2044  * second time for any missing parameters.
2045  *
2046  * If the request is processed (True is returned), then it is up to the
2047  * caller to free up the request structure.
2048  */
2049
2050 static Boolean 
2051 ProcessRequest(
2052         Widget w,
2053         ActionRequest *request )
2054
2055 {
2056    int unused;
2057    ActionPtr action;
2058    int numPrompts;
2059    PromptEntry * prompts;
2060    DtShmBoson actionQuark;
2061    Tt_status status = TT_OK;
2062
2063    /* See if this is the first pass for the request */
2064    if (request->clonedAction == NULL)
2065    {
2066
2067       /* Always start with the first host, when processing a request */
2068       request->hostIndex = 0;
2069
2070       /* Find the action DB entry which we map to */
2071       actionQuark = _DtDtsMMStringToBoson(request->actionName);
2072       RESET_TOO_MANY_MAPS(request->mask);
2073
2074       if (actionQuark == -1 || (action = _DtActionFindDBEntry(request, actionQuark)) == NULL) 
2075       {
2076           /*
2077            * No action label is available here for error dialogs
2078            */
2079          if (IS_TOO_MANY_MAPS(request->mask))
2080          {
2081             MapError (w, request->actionName);
2082
2083          }
2084          else if (request->numObjects > 0)
2085          {
2086             if (IS_FILE_OBJ(request->objects[0].mask))
2087             {
2088                NoActionError(w, actionQuark, 
2089                  request->actionName, 
2090                  (char *)_DtDtsMMBosonToString(request->objects[0].type),
2091                  request->hostNames[request->objects[0].u.file.hostIndex],
2092                  request->dirNames[request->objects[0].u.file.dirIndex],
2093                  request->objects[0].u.file.baseFilename);
2094             }
2095             else if ( IS_BUFFER_OBJ(request->objects[0].mask) )
2096             {
2097                /*
2098                 * RWV -- may have to modify this call to generate a 
2099                 * message more suitable for buffer objects.
2100                 */
2101                NoActionError(w, actionQuark,
2102                  request->actionName, 
2103                  (char *)_DtDtsMMBosonToString(request->objects[0].type),
2104                  NULL,  /* host */
2105                  NULL,  /* dir */
2106                  "Memory Object"   /* filename */);
2107             } else
2108                 myassert(0 /* should never get here */ );
2109
2110             /* fdt: add code for  strings
2111              * else if (IS_STRING_OBJ(request->objects[0].mask))
2112             */
2113          }
2114          else
2115             NoActionError(w, actionQuark, request->actionName, 
2116                 NULL, NULL, NULL, NULL);
2117
2118          /*
2119           * If we are in the middle of reprocessing a single argument
2120           * action, then continue with the next parameter.  Otherwise,
2121           * this error terminates the request, so return.
2122           */
2123          if (IS_REPROCESSING(request->mask) && MoreArgumentsToProcess(request))
2124             return(ProcessRequest(w, request));
2125
2126          /*
2127           * We were never able to start this action.
2128           */
2129          {
2130             _DtActInvRecT *invRecP = _DtActFindInvRec(request->invocId);
2131             if (invRecP) SET_INV_ERROR(invRecP->state);
2132          }
2133          return(True);
2134       }
2135
2136       request->clonedAction = action;
2137
2138       /*
2139        * If this is a ToolTalk message, then before proceeding any further,
2140        * make sure we can get connected to a ToolTalk session.  If we can't,
2141        * then we need to bail out.
2142        */
2143       if (IS_TT_MSG(action->mask) &&
2144           (status = _DtInitializeToolTalk(NULL)) != TT_OK)
2145       {
2146          NoToolTalkConnectionError(w, request->clonedAction->label, status);
2147          {
2148             _DtActInvRecT *invRecP = _DtActFindInvRec(request->invocId);
2149             if (invRecP) SET_INV_ERROR(invRecP->state);
2150          }
2151          return(True);
2152       }
2153
2154       /* Determine how we are sitting with parameters */
2155       unused = MatchParamsToAction(request, &numPrompts, &prompts);
2156       request->objsUsed = request->numObjects - unused;
2157       myassert(request->objsUsed >= 0);
2158
2159       /* 
2160        * Do we need to create a prompt dialog? 
2161        * NOTE: if the action requires the user to be prompted, but the
2162        *       user has supplied extra parameters, so he will be asked
2163        *       to abort or continue, do the abort/continue dialog BEFORE
2164        *       the prompt dialog; there's little sense in collecting
2165        *       additional input if the user is going to abort the action!
2166        */
2167       if ((prompts != NULL) &&
2168           ((unused == 0) || IS_ARG_SINGLE_ARG(action->mask) || 
2169             IS_ARG_NONE_FOUND(action->mask)))
2170       {
2171          CreatePromptDialog(w, request, numPrompts, prompts);
2172          XtFree((char *)prompts);
2173          return(False);
2174       }
2175
2176       /* Were too many parameters supplied? */
2177       else if (unused > 0)
2178       {
2179          /* 
2180           * If the action only needs a single parameter, then we need
2181           * to fire off multiple instances of the action; otherwise,
2182           * prompt the user to continue or abort.  An action requiring
2183           * no parameters is also treated like a single parameter action.
2184           */
2185          if (IS_ARG_SINGLE_ARG(action->mask) || IS_ARG_NONE_FOUND(action->mask))
2186          {
2187
2188             PrepareAndExecuteAction(w, request);
2189
2190             /* See if there are still more parameters to be processed */
2191             if (MoreArgumentsToProcess(request))
2192                return(ProcessRequest(w, request));
2193          }
2194          else
2195          {
2196             /* 
2197              * Postpone any further processing until the user either
2198              * tells us to continue, or abort.
2199              */
2200             CreateContinueDialog(w, request, numPrompts, prompts);
2201             XtFree((char *)prompts);
2202             return(False);
2203          }
2204       }
2205       else
2206       {
2207            PrepareAndExecuteAction(w, request);
2208        }
2209    }
2210    else
2211    {
2212
2213       PrepareAndExecuteAction(w, request);
2214       action = request->clonedAction;
2215
2216       /* 
2217        * If this is a single argument action, and we have more parameters
2218        * waiting to be processed, then continue processing them.
2219        */
2220       if ((IS_ARG_SINGLE_ARG(action->mask) || IS_ARG_NONE_FOUND(action->mask))
2221           && (MoreArgumentsToProcess(request)))
2222       {
2223          return(ProcessRequest(w, request));
2224       }
2225    }
2226
2227    return(True);
2228 }
2229
2230
2231 /*
2232  * This function is called at the point where we have collected all of the
2233  * information needed to actually initiate the action.  We will use the
2234  * set of arguments passed into _DtActionInvoke(), along with any values
2235  * supplied through the prompt dialog.  It is also at this point that
2236  * the thread of control will split, dependent upon the type of action
2237  * being executed (Command Invoker, Tooltalk).
2238  */
2239 static void 
2240 PrepareAndExecuteAction(
2241         Widget w,
2242         ActionRequest *request )
2243
2244 {
2245    char * relPathHost;
2246    char * relPathDir;
2247    int i;
2248    ActionPtr action = request->clonedAction;
2249    int     argNum;
2250    _DtActInvRecT *invp; /* pointer to invocation record */
2251    _DtActChildRecT *childrecp;  /* pointer to child record */
2252
2253   /*
2254    * We have gathered all the information necessary to invoke
2255    * this action  all dialogs have been posted and processed.
2256    * Now  create the action invocation record -- unless we are
2257    * in the midst of reprocessing an already invoked action.
2258    */
2259    invp = _DtActFindInvRec(request->invocId);
2260    myassert(invp);
2261    SET_INV_WORKING(invp->state);
2262
2263    /*
2264     * Allocate a child rec -- fill it in
2265     */
2266    if ( (childrecp = _DtActAllocChildRec(invp)) != NULL )
2267    {
2268         request->childId = childrecp->childId;
2269
2270         childrecp->childState = _DtActCHILD_PENDING_START;
2271         childrecp->mask = action->mask;
2272
2273         childrecp->numObjects = request->objsUsed;
2274    } 
2275    else
2276        myassert( 0 /* Unable to allocate childRec */ );
2277
2278
2279    /*
2280     * Before proceeding, we need to determine what host and directory
2281     * will be used when resolving relative pathnames.
2282     */
2283    __ExtractCWD(request, &relPathHost, &relPathDir, False);
2284    if (IS_CMD(action->mask))
2285    {
2286       /*
2287        * All buffer objects must be placed into temporary files for
2288        * command actions. This has already been done when the
2289        * request structure was created.
2290        */
2291       if (childrecp && childrecp->numObjects > 0)
2292       {
2293            childrecp->argMap =
2294                (_DtActArgMap *)XtCalloc(childrecp->numObjects,
2295                      sizeof(_DtActArgMap));
2296
2297            for ( i = 0; i < childrecp->numObjects && i < invp->ac; i++ )
2298            {
2299                 childrecp->argMap[i].argN = i+1; /*  ignored for CMD actions */
2300                 childrecp->argMap[i].argIdx = 
2301                     i + request->objOffset;      /* idx into invp->info[] */
2302            }
2303       }
2304       ProcessCommandInvokerRequest(w, request, relPathHost, relPathDir);
2305    }
2306    else if (IS_TT_MSG(action->mask))
2307    {
2308       if (childrecp)
2309       {
2310          /*
2311           * create argmap for returnable arguments -- 
2312           * i.e. those appearing in TT_ARGn_VALUE fields.
2313           *
2314           * The requirement is that one and only one action argument may
2315           * appear in a TT_ARGn_VALUE field.
2316           *
2317           * argMap is a sparse array which maps TT_ARGn_VALUEs to input
2318           * parameters.  If a TT_ARGn_VALUE does not have an input parameter
2319           * as a value then the sentinel value "-1" is provided as the index.
2320           * Allocate enough space for all the TT_ARGn_VALUEs plus one for
2321           * TT_FILE.
2322           *
2323           * The elements of the argMap array then represent:
2324           * argMap[ TT_ARG0, TT_ARG1, ...,TT_ARGN, TT_FILE]
2325           */
2326
2327           childrecp->argMap =
2328               (_DtActArgMap *)XtCalloc( action->u.tt_msg.value_count + 1,
2329                    sizeof(_DtActArgMap));
2330    
2331           for ( i = 0; i < action->u.tt_msg.value_count; i++)
2332           {
2333               /*
2334                * Set index value to "-1".  This value will indicate 
2335                * TT_ARGn_VALUES which are NOT associated with input
2336                * parameters (action arguments).  If there is an action
2337                * argument associated with this TT_ARGn_VALUE we will set
2338                * it below.
2339                */
2340               childrecp->argMap[i].argIdx = -1; 
2341               childrecp->argMap[i].argN = i; 
2342
2343                 /* null argn value is valid -- so check MsgParts*/
2344               if (!action->u.tt_msg.tt_argn_value[i].numMsgParts)
2345                   continue;
2346
2347               if (action->
2348                       u.tt_msg.tt_argn_value[i].parsedMessage[0].keyword 
2349                           != ARG)
2350                   continue;
2351
2352               /*
2353                * TT_ARGn_VALUE fields should have only one arg keyword.
2354                */
2355               myassert(action->u.tt_msg.tt_argn_value[i].numMsgParts == 1);
2356               argNum =action->
2357                          u.tt_msg.tt_argn_value[i].parsedMessage[0].argNum;
2358
2359               if ( ( argNum > 0 ) && ( argNum <= invp->ac ) )
2360               {
2361                   /* The ith message part must be returned */
2362                   childrecp->argMap[i].argIdx = 
2363                         argNum + request->objOffset - 1;
2364                   myassert( childrecp->argMap[i].argIdx >= 0 );
2365               }
2366            }
2367            /*
2368             * Add an argMap entry for the value of the TT_FILE field.
2369             * Tooltalk (e.g. media messages) sometimes uses this field
2370             * to pass values such as file names to the message receipient.
2371             * If the TT_FILE field has a single ARG keyword
2372             * then record that parameter number otherwise record "-1" as
2373             * was done for the value arguments above.
2374             */
2375            childrecp->argMap[i].argIdx = -1; 
2376            childrecp->argMap[i].argN = -1;   /* Use "-1" as TT_FILE entry idx */
2377            if (action->u.tt_msg.tt_file.numMsgParts
2378                   && action->u.tt_msg.tt_file.parsedMessage[0].keyword == ARG )
2379            {
2380               argNum =action->
2381                          u.tt_msg.tt_file.parsedMessage[0].argNum;
2382               if ( ( argNum > 0 ) && ( argNum <= invp->ac ) )
2383               {
2384                   /* The ith message part should be the last argMap entry */
2385                   childrecp->argMap[i].argIdx = 
2386                         argNum + request->objOffset - 1;
2387                   myassert( childrecp->argMap[i].argIdx >= 0 );
2388               }
2389            }
2390
2391       }
2392       _DtProcessTtRequest(w, request, relPathHost, relPathDir);
2393    }
2394
2395    /*
2396     * For now we are through invoking this child.
2397     * There may still be more children to invoke or we may have to
2398     * re-invoke this child (e.g. multi-host processing for commands).
2399     */
2400    SET_INV_DONE(invp->state);
2401
2402    /* Free up the path information */
2403    XtFree(relPathHost);
2404    XtFree(relPathDir);
2405 }
2406
2407
2408 /*
2409  * Determine the CWD to use; this information can be used to both
2410  * resolve relative filepaths, and to set the CWD used when executing
2411  * a command invoker request.  When resolving relative paths, the
2412  * information specified for the first argument is not used (see case 2
2413  * below).  It is determined using the following algorithm:
2414  *
2415  *     1) Use the CWD specified in the action (if a cmd invoker action).
2416  *     2) If told to use the objects, then use the directory where the
2417  *        object lives (if a regular file), or the object itself (if
2418  *        it's a directory.
2419  *     3) Use the CWD passed into _) by the application.
2420  *     4) Use the physical CWD of the application.
2421  *
2422  * Both the host and directory paths must by freed by the caller.
2423  */
2424 static void 
2425 __ExtractCWD(
2426         ActionRequest *request,
2427         char ** hostPtr,
2428         char ** dirPtr,
2429         Boolean useObjectInfo )
2430
2431 {
2432    String msg;
2433    String lastCh;
2434    int lastChLen;
2435    ActionPtr action = request->clonedAction;
2436
2437    /* Only dropped objects will have been 'typed' at this point */
2438    if (useObjectInfo && (IS_CMD(action->mask)) && (request->numObjects > 0) && 
2439        (request->objects[0].type >= 0) && 
2440         IS_FILE_OBJ(request->objects[0].mask))
2441    {
2442       if (action->u.cmd.contextHost != NULL)
2443          *hostPtr = XtNewString(action->u.cmd.contextHost);
2444       else
2445       {
2446          *hostPtr = XtNewString(
2447                      request->hostNames[request->objects[0].u.file.hostIndex]);
2448       }
2449
2450       if (IS_UNKNOWN_IF_DIR(request->objects[0].mask))
2451       {
2452          String nfsPath;
2453          char *theHost, *theDir;
2454          struct stat statInfo;
2455
2456          RESET_UNKNOWN_IF_DIR(request->objects[0].mask);
2457
2458          /* 
2459           * The file may not have been checked yet, if it was never
2460           * referenced in the execution string; so .. we'll check 
2461           * here.
2462           */
2463          theHost = request->hostNames[request->objects[0].u.file.hostIndex];
2464          theDir = request->dirNames[request->objects[0].u.file.dirIndex];
2465          nfsPath = _DtActMapFileName(theHost, theDir, 
2466                                request->objects[0].u.file.baseFilename, NULL);
2467
2468          if (nfsPath && (stat(nfsPath, &statInfo) == 0) &&
2469              ((statInfo.st_mode & S_IFMT) == S_IFDIR))
2470          {
2471             SET_DIR_OBJ(request->objects[0].mask);
2472          }
2473          XtFree(nfsPath);
2474       }
2475
2476       if (IS_DIR_OBJ(request->objects[0].mask))
2477       {
2478          if (action->u.cmd.contextDir != NULL)
2479             *dirPtr = XtNewString(action->u.cmd.contextDir);
2480          else 
2481          {
2482             *dirPtr = XtMalloc((Cardinal)
2483                (strlen(request->dirNames[request->objects[0].u.file.dirIndex]) +
2484                 strlen(request->objects[0].u.file.baseFilename) + 2));
2485             strcpy(*dirPtr, 
2486                    request->dirNames[request->objects[0].u.file.dirIndex]);
2487
2488             DtLastChar(*dirPtr, &lastCh, &lastChLen);
2489             if ((lastChLen != 1) || (*lastCh != '/'))
2490                (void)strcat(*dirPtr, "/");
2491
2492             (void)strcat(*dirPtr, request->objects[0].u.file.baseFilename);
2493          }
2494       }
2495       else
2496       {
2497          if (action->u.cmd.contextDir != NULL)
2498             *dirPtr = XtNewString(action->u.cmd.contextDir);
2499          else
2500          {
2501             *dirPtr = XtNewString(
2502                         request->dirNames[request->objects[0].u.file.dirIndex]);
2503          }
2504       }
2505    }
2506    else 
2507    {
2508       /* Use specified context, or get process context, if necessary */
2509       if (IS_CMD(action->mask) && (action->u.cmd.contextHost != NULL))
2510          *hostPtr = XtNewString(action->u.cmd.contextHost);
2511       else if (request->cwdHost)
2512          *hostPtr = XtNewString(request->cwdHost);
2513       else
2514       {
2515          *hostPtr = _DtGetLocalHostName();
2516       }
2517
2518       if (IS_CMD(action->mask) && (action->u.cmd.contextDir != NULL))
2519          *dirPtr = XtNewString(action->u.cmd.contextDir);
2520       else if (request->cwdDir)
2521          *dirPtr = XtNewString(request->cwdDir);
2522       else
2523          *dirPtr = _DtFindCwd();
2524    }
2525 }
2526
2527
2528 /*
2529  * This function is used to prepare for the continued processing of
2530  * the parameters, when the action is a single argument action.  It
2531  * free up any data which was associated with the previous parameter,
2532  * and cascades up any remaining parameters in the object array.
2533  */
2534
2535 static Boolean 
2536 MoreArgumentsToProcess(
2537         ActionRequest *request )
2538
2539 {
2540    int i;
2541    char * path;
2542    char * dtype;
2543
2544    if (request->numObjects <= 1)
2545    {
2546       return(False);
2547    }
2548    else
2549    {
2550       /* Repeat processing for the next argument */
2551
2552       /* Cascade up the remaining, unprocess parameters */
2553       if (IS_FILE_OBJ(request->objects[0].mask))
2554       {
2555          XtFree(request->objects[0].u.file.origFilename);
2556          XtFree(request->objects[0].u.file.origHostname);
2557          XtFree(request->objects[0].u.file.baseFilename);
2558       }
2559      /* 
2560       * RWV: 
2561       * Since we use tmp files for buffers and do not support
2562       * strings; we need not add special code for buffer and
2563       * string support.
2564       */
2565       /* fdt: Add support for strings and buffers here
2566        * else if (IS_BUFFER_OBJ(request->objects[0].mask))
2567        *    XtFree(request->objects[0].u.buffer.buffer);
2568        * else if (IS_STRING_OBJ(request->objects[0].mask))
2569        *    XtFree(request->objects[0].u.string.string);
2570        */
2571       for (i = 0; i < (request->numObjects - 1); i++)
2572       {
2573          request->objects[i] = request->objects[i+1];
2574       }
2575       request->numObjects--;
2576       request->objOffset++; 
2577       request->objsUsed = 0;
2578       request->childId = 0;
2579
2580       /* Free up our previously cloned action */
2581       _DtFreeActionStruct(request->clonedAction);
2582       request->clonedAction = NULL;
2583
2584       /* Free up any leftover prompt strings */
2585       for (i = 0; i < request->numPromptInputs; i++)
2586          XtFree(request->promptInputs[i]);
2587       XtFree((char *)request->promptInputs);
2588       request->promptInputs = NULL;
2589       request->numPromptInputs = 0;
2590       SET_REPROCESSING(request->mask);
2591       XtFree(request->badHostList);
2592       request->badHostList = NULL;
2593       XtFree(request->currentHost);
2594       request->currentHost = NULL;
2595       request->hostIndex = 0;
2596
2597       /* Type the object, if possible */
2598       if (IS_FILE_OBJ(request->objects[0].mask))
2599       {
2600          if ((request->objects[0].u.file.hostIndex >= 0) &&
2601              (request->objects[0].u.file.dirIndex >= 0))
2602          {
2603             path = _DtActMapFileName(
2604                  request->hostNames[request->objects[0].u.file.hostIndex], 
2605                  request->dirNames[request->objects[0].u.file.dirIndex], 
2606                  request->objects[0].u.file.baseFilename, NULL);
2607             dtype = DtDtsFileToDataType(path);
2608             request->objects[0].type = _DtDtsMMStringToBoson(dtype);
2609             DtDtsFreeDataType(dtype);
2610             XtFree(path);
2611          }
2612       }
2613      /* 
2614       * RWV: 
2615       * Since we use tmp files for buffers and do not support
2616       * strings; we need not add special code for buffer and
2617       * string support.
2618       */
2619       /* fdt: add support for buffers and strings here
2620        * else if (IS_BUFFER_OBJ(request->objects[0].mask))
2621        * {
2622        * }
2623        * else if (IS_STRING_OBJ(request->objects[0].mask))
2624        * {
2625        * }
2626        */
2627
2628       return(True);
2629    }
2630 }
2631
2632
2633 /***************************************************************************/
2634 /***************************************************************************/
2635 /*              Functions For Cloning And Free Structures                  */
2636 /***************************************************************************/
2637 /***************************************************************************/
2638
2639
2640 /*
2641  * At the point that a request is sent, we need to save a copy of the 
2642  * request, for future reference.  We need to clone the request, since 
2643  * the original request structure may get modified between the time we 
2644  * send the message, and the time we need to reference it in the future.
2645  */
2646
2647 ActionRequest * 
2648 _DtCloneRequest (
2649    ActionRequest * request)
2650
2651 {
2652    ActionRequest * newRequest;
2653    int i;
2654
2655    newRequest = (ActionRequest *)XtMalloc((Cardinal)sizeof(ActionRequest));
2656
2657    /*  
2658     * Structure assignment to clone all scalar values 
2659     * If a value is not explicitly set then it defaults to the same value
2660     * as in the original request.  Pointers to malloc-ed memory should all
2661     * be replaced with pointers to a copy of the region.
2662     */
2663    (*newRequest) = (*request);
2664
2665    newRequest->actionName = XtNewString(request->actionName);
2666
2667    if (request->numObjects > 0) {
2668      newRequest->objects = (ObjectData *)XtMalloc(sizeof(ObjectData) *
2669                                                   request->numObjects);
2670
2671      for (i = 0; i < request->numObjects; i++)
2672      {
2673        newRequest->objects[i] = request->objects[i];
2674        if (IS_FILE_OBJ(request->objects[i].mask))
2675        {
2676          newRequest->objects[i].u.file.origFilename = 
2677            XtNewString(request->objects[i].u.file.origFilename);
2678          newRequest->objects[i].u.file.origHostname = 
2679            XtNewString(request->objects[i].u.file.origHostname);
2680          newRequest->objects[i].u.file.baseFilename = 
2681            XtNewString(request->objects[i].u.file.baseFilename);
2682        } 
2683        else if ( IS_BUFFER_OBJ(request->objects[i].mask) )
2684        {
2685          /* 
2686           * RWV:
2687           * Since we are creating tmp files for all buffers
2688           * we should never have to copy a buffer's contents.
2689           *
2690           * We should never reach this code because the FILE_OBJ
2691           * bit is set when we create tmp files for buffers.
2692           */
2693          myassert(0);
2694          /*  
2695           * RWV:
2696           * Can we get by without copying buffer object contents?
2697           * if so  -- how do we avoid freeing it twice OR
2698           * not freeing it at all?
2699           */
2700          /* make a copy of the buffer */
2701          if ( request->objects[i].u.buffer.bp )
2702          {
2703            myassert(newRequest->objects[i].u.buffer.size == request->objects[i].u.buffer.size);
2704            newRequest->objects[i].u.buffer.bp =
2705              XtMalloc( request->objects[i].u.buffer.size );
2706            memcpy(newRequest->objects[i].u.buffer.bp,
2707                   request->objects[i].u.buffer.bp,
2708                   newRequest->objects[i].u.buffer.size);
2709          }
2710        }
2711        else
2712          myassert(0 /* no other object types supported */ );
2713      }
2714    }
2715
2716    newRequest->numPromptInputs = request->numPromptInputs;
2717    if (request->numPromptInputs > 0) {
2718      newRequest->promptInputs = (char **)XtMalloc(sizeof(char *) * 
2719                                                   request->numPromptInputs);
2720      for (i = 0; i < request->numPromptInputs; i++)
2721        newRequest->promptInputs[i] = XtNewString(request->promptInputs[i]);
2722    }
2723
2724    newRequest->numHostNames = request->numHostNames;
2725    if (request->numHostNames > 0) {
2726      newRequest->hostNames = (char **)XtMalloc(sizeof(char *) * 
2727                                                request->numHostNames);
2728      for (i = 0; i < request->numHostNames; i++)
2729        newRequest->hostNames[i] = XtNewString(request->hostNames[i]);
2730    }
2731
2732    newRequest->numDirNames = request->numDirNames;
2733    if (request->numDirNames > 0) {
2734      newRequest->dirNames = (char **)XtMalloc(sizeof(char *) * 
2735                                               request->numDirNames);
2736      for (i = 0; i < request->numDirNames; i++)
2737        newRequest->dirNames[i] = XtNewString(request->dirNames[i]);
2738    }
2739
2740    newRequest->termOpts = XtNewString(request->termOpts);
2741    newRequest->cwdHost = XtNewString(request->cwdHost);
2742    newRequest->cwdDir = XtNewString(request->cwdDir);
2743
2744    if (request->clonedAction)
2745       newRequest->clonedAction = CloneActionDBEntry(request->clonedAction);
2746    else
2747       newRequest->clonedAction = NULL;
2748
2749    newRequest->badHostList = XtNewString(request->badHostList);
2750    newRequest->currentHost = XtNewString(request->currentHost);
2751    newRequest->execHost = XtNewString(request->execHost);
2752
2753
2754    return(newRequest);
2755 }
2756
2757
2758 /*
2759  * Free up the contents of a request structure
2760  */
2761
2762 void 
2763 _DtFreeRequest(
2764         ActionRequest *request )
2765
2766 {
2767    int i;
2768
2769    XtFree(request->actionName);
2770
2771    for (i = 0; i < request->numObjects; i++)
2772    {
2773       if (IS_FILE_OBJ(request->objects[i].mask))
2774       {
2775          XtFree(request->objects[i].u.file.origFilename);
2776          XtFree(request->objects[i].u.file.origHostname);
2777          XtFree(request->objects[i].u.file.baseFilename);
2778       }
2779      /* 
2780       * RWV: 
2781       * Since we use tmp files for buffers and do not support
2782       * strings; we need not add special code for buffer and
2783       * string support.
2784       */
2785       /* fdt: Add support for buffers and strings here
2786        * else if (IS_BUFFER_OBJ(request->objects[i].mask)
2787        *   XtFree(request->objects[i].u.buffer.buffer);
2788        * else if (IS_STRING_OBJ(request->objects[i].mask)
2789        *   XtFree(request->objects[i].u.string.string);
2790        */
2791    }
2792
2793    /*
2794     * Since the objectDataArray was malloced at once
2795     * we can free it at once.
2796     */
2797    if (request->objects) XtFree((char *)request->objects);
2798
2799    for (i = 0; i < request->numPromptInputs; i++)
2800       XtFree(request->promptInputs[i]);
2801    if (request->promptInputs) XtFree((char *)request->promptInputs);
2802
2803    for (i = 0; i < request->numHostNames; i++)
2804       XtFree(request->hostNames[i]);
2805    if (request->hostNames) XtFree((char *)request->hostNames);
2806
2807    for (i = 0; i < request->numDirNames; i++)
2808       XtFree(request->dirNames[i]);
2809    if (request->dirNames) XtFree((char *)request->dirNames);
2810
2811    XtFree(request->termOpts);
2812    XtFree(request->cwdHost);
2813    XtFree(request->cwdDir);
2814    _DtFreeActionStruct(request->clonedAction);
2815    XtFree(request->badHostList);
2816    XtFree(request->currentHost);
2817    XtFree(request->execHost);
2818
2819    XtFree ((char *)request);
2820 }
2821
2822
2823 /*
2824  * Create a clone of an action DB entry
2825  */
2826
2827 static ActionPtr 
2828 CloneActionDBEntry(
2829         ActionPtr action )
2830
2831 {
2832    ActionPtr newAction = (ActionPtr)XtMalloc((Cardinal)sizeof(Action));
2833    int i;
2834
2835    /* Clone each field */
2836    newAction->action = action->action;
2837    newAction->file_name_id = action->file_name_id;
2838    newAction->label = XtNewString(action->label);
2839    newAction->description = XtNewString(action->description);
2840
2841    newAction->type_count = action->type_count;
2842    if (action->type_count > 0) {
2843      newAction->arg_types = (DtShmBoson *)XtMalloc(sizeof(DtShmBoson) *
2844                                                    newAction->type_count);
2845      for (i = 0; i < newAction->type_count; i++)
2846        newAction->arg_types[i] = action->arg_types[i];
2847    }
2848    else {
2849      newAction->arg_types = NULL;
2850    }
2851
2852    newAction->arg_count = action->arg_count;
2853    newAction->mask = action->mask;
2854
2855    if (IS_CMD(action->mask))
2856    {
2857       cmdAttr * newCmd = &(newAction->u.cmd);
2858       cmdAttr * oldCmd = &(action->u.cmd);
2859
2860       CloneParsedMessage(&(oldCmd->execString), &(newCmd->execString));
2861       CloneParsedMessage(&(oldCmd->termOpts), &(newCmd->termOpts));
2862       newCmd->contextDir = XtNewString(oldCmd->contextDir);
2863       newCmd->contextHost = XtNewString(oldCmd->contextHost);
2864       CloneParsedMessage(&(oldCmd->execHosts), &(newCmd->execHosts));
2865       newCmd->execHostCount = oldCmd->execHostCount;
2866       if (oldCmd->execHostCount > 0) {
2867         newCmd->execHostArray = (char **)XtMalloc(sizeof(char *) *
2868                                                   newCmd->execHostCount);
2869         for (i = 0; i < newCmd->execHostCount; i++)
2870           newCmd->execHostArray[i] = XtNewString(oldCmd->execHostArray[i]);
2871       }
2872       else {
2873         newCmd->execHostArray = NULL;
2874       }
2875    }
2876    else if (IS_MAP(action->mask))
2877    {
2878       newAction->u.map.map_action = action->u.map.map_action;
2879    }
2880    else if (IS_TT_MSG(action->mask))
2881    {
2882       tt_msgAttr * newMsg = &(newAction->u.tt_msg);
2883       tt_msgAttr * oldMsg = &(action->u.tt_msg);
2884
2885       newMsg->tt_class = oldMsg->tt_class;
2886       newMsg->tt_scope = oldMsg->tt_scope;
2887       CloneParsedMessage(&(oldMsg->tt_op), &(newMsg->tt_op));
2888       CloneParsedMessage(&(oldMsg->tt_file), &(newMsg->tt_file));
2889
2890       newMsg->mode_count = oldMsg->mode_count;
2891       if (oldMsg->mode_count > 0) {
2892         newMsg->tt_argn_mode =
2893           (int *)XtMalloc(sizeof(int) * newMsg->mode_count);
2894         for (i = 0; i < newMsg->mode_count; i++)
2895           newMsg->tt_argn_mode[i] = oldMsg->tt_argn_mode[i];
2896       }
2897       else {
2898         newMsg->tt_argn_mode = NULL;
2899       }
2900
2901       newMsg->vtype_count = oldMsg->vtype_count;
2902       newMsg->tt_argn_vtype = CloneParsedMessageArray(oldMsg->tt_argn_vtype,
2903                                                       oldMsg->vtype_count);
2904
2905       newMsg->value_count = oldMsg->value_count;
2906       newMsg->tt_argn_value = CloneParsedMessageArray(oldMsg->tt_argn_value,
2907                                                       oldMsg->value_count);
2908
2909       newMsg->rep_type_count = oldMsg->rep_type_count;
2910       if (oldMsg->rep_type_count > 0) {
2911         newMsg->tt_argn_rep_type = (int *)XtMalloc(sizeof(int) * 
2912                                                    newMsg->rep_type_count);
2913         for (i = 0; i < newMsg->rep_type_count; i++)
2914           newMsg->tt_argn_rep_type[i] = oldMsg->tt_argn_rep_type[i];
2915       }
2916       else {
2917         newMsg->tt_argn_rep_type = NULL;
2918       }
2919    }
2920
2921    return(newAction);
2922 }
2923
2924
2925 /*
2926  * Free up the contents of a request structure 
2927  */
2928
2929 void 
2930 _DtFreeActionStruct(
2931         ActionPtr action )
2932
2933 {
2934    int i;
2935
2936    if (action == NULL)
2937       return;
2938
2939    XtFree(action->label);
2940    XtFree(action->description);
2941    if (action->arg_types) XtFree((char *)action->arg_types);
2942
2943    if (IS_CMD(action->mask))
2944    {
2945       FreeParsedMessage(&(action->u.cmd.execString));
2946       FreeParsedMessage(&(action->u.cmd.termOpts));
2947       XtFree(action->u.cmd.contextDir);
2948       XtFree(action->u.cmd.contextHost);
2949       FreeParsedMessage(&(action->u.cmd.execHosts));
2950       for (i = 0; i < action->u.cmd.execHostCount; i++)
2951          XtFree(action->u.cmd.execHostArray[i]);
2952       if (action->u.cmd.execHostArray) {
2953         XtFree((char *)action->u.cmd.execHostArray);
2954       }
2955    }
2956    else if (IS_TT_MSG(action->mask))
2957    {
2958       FreeParsedMessage(&(action->u.tt_msg.tt_op));
2959       FreeParsedMessage(&(action->u.tt_msg.tt_file));
2960       if (action->u.tt_msg.tt_argn_mode) {
2961          XtFree((char *)action->u.tt_msg.tt_argn_mode);
2962       }
2963       FreeParsedMessageArray(action->u.tt_msg.tt_argn_vtype,
2964                              action->u.tt_msg.vtype_count);
2965       FreeParsedMessageArray(action->u.tt_msg.tt_argn_value,
2966                              action->u.tt_msg.value_count);
2967       if (action->u.tt_msg.tt_argn_rep_type) {
2968          XtFree((char *)action->u.tt_msg.tt_argn_rep_type);
2969       }
2970    }
2971
2972    XtFree((char *)action);
2973 }
2974
2975
2976 static void
2977 CloneParsedMessage(
2978         parsedMsg * old_pmsg,
2979         parsedMsg * new_pmsg )
2980
2981 {
2982    int i;
2983    MsgComponent * piece;
2984    MsgComponent * newPiece;
2985
2986    new_pmsg->numMsgParts = old_pmsg->numMsgParts;
2987    if (old_pmsg->compiledMessage)
2988    {
2989       /*
2990        * Some day these may not always be null-terminated strings
2991        */
2992       new_pmsg->compiledMessage = (char *)XtMalloc(old_pmsg->msgLen);
2993       memcpy(new_pmsg->compiledMessage,
2994           old_pmsg->compiledMessage,
2995           old_pmsg->msgLen);
2996       new_pmsg->msgLen = old_pmsg->msgLen;
2997    }
2998    else
2999    {
3000       new_pmsg->compiledMessage = NULL;
3001       new_pmsg->msgLen = 0;
3002    }
3003
3004
3005    /* Clone the message components */
3006    if (old_pmsg->numMsgParts > 0)
3007    {
3008       new_pmsg->parsedMessage = (MsgComponent *)
3009            XtMalloc((Cardinal)(sizeof(MsgComponent) * old_pmsg->numMsgParts));
3010
3011       for (i = 0; i < old_pmsg->numMsgParts; i++)
3012       {
3013          piece = &(old_pmsg->parsedMessage[i]);
3014          newPiece = &(new_pmsg->parsedMessage[i]);
3015
3016          /* Clone each subcomponent of this message */
3017          if (piece->precedingText)
3018             newPiece->precedingText = XtNewString(piece->precedingText);
3019          else
3020             newPiece->precedingText = NULL;
3021
3022          if (piece->prompt)
3023             newPiece->prompt = XtNewString(piece->prompt);
3024          else
3025             newPiece->prompt = NULL;
3026
3027          newPiece->keyword = piece->keyword;
3028          newPiece->argNum = piece->argNum;
3029          newPiece->mask = piece->mask;
3030       }
3031    }
3032    else
3033       new_pmsg->parsedMessage = NULL;
3034 }
3035
3036
3037 /*
3038  * Free up the contents of a parsedMsg structure, but not the structure
3039  * itself (since many of our structures contain in-line instances of
3040  * the parsedMsg structure).
3041  */
3042 static void 
3043 FreeParsedMessage(
3044         parsedMsg * parsedMessage )
3045
3046 {
3047    int i;
3048
3049    /* Free up the message components */
3050    if (parsedMessage->numMsgParts > 0)
3051    {
3052       for (i = 0; i < parsedMessage->numMsgParts; i++)
3053       {
3054          XtFree(parsedMessage->parsedMessage[i].precedingText);
3055          XtFree(parsedMessage->parsedMessage[i].prompt);
3056       }
3057       XtFree((char *)parsedMessage->parsedMessage);
3058    }
3059
3060    XtFree(parsedMessage->compiledMessage);
3061 }
3062
3063
3064 /*
3065  * Allocate an array to hold a copy of all of the parsedMsg structures.
3066  * This array must be freed eventually by the caller.
3067  */
3068 static parsedMsg *
3069 CloneParsedMessageArray(
3070         parsedMsg * pmsgArray,
3071         int count )
3072
3073 {
3074    parsedMsg * newArray;
3075    int i;
3076
3077    if (count == 0)
3078       return(NULL);
3079
3080    newArray = (parsedMsg *)XtMalloc(sizeof(parsedMsg) * count);
3081
3082    for (i = 0; i < count; i++)
3083       CloneParsedMessage(pmsgArray + i, newArray + i);
3084
3085    return(newArray);
3086 }
3087
3088
3089 /*
3090  * Free up the counted array of parsedMsg structures.
3091  * The array pointing to them also needs to be freed.
3092  */
3093 static void 
3094 FreeParsedMessageArray(
3095         parsedMsg * parsedMessageArray,
3096         int count )
3097
3098 {
3099    int i;
3100
3101    for (i = 0; i < count; i++)
3102       FreeParsedMessage(parsedMessageArray + i);
3103
3104    XtFree((char *)parsedMessageArray);
3105 }
3106
3107
3108 /***************************************************************************/
3109 /***************************************************************************/
3110 /*         Functions For Placing Arguments Into A Message String           */
3111 /***************************************************************************/
3112 /***************************************************************************/
3113
3114
3115 /*
3116  * This function takes a 'parsedMsg' structure, and compiles all of its
3117  * pieces into a single string, replacing keywords as they are encountered.
3118  * Since a given action request can be made up of multiple pieces, this
3119  * function uses some static variables to maintain state information between
3120  * calls for the same action request; passing in 'True' for the 'initialize'
3121  * parameter for the first call for a given action request will clear out
3122  * any old static values.
3123  */
3124
3125 Boolean 
3126 _DtCompileMessagePiece(
3127         Widget w,
3128         ActionRequest *request,
3129         char * relPathHost,
3130         char * relPathDir,
3131         parsedMsg * piece,
3132         Boolean initialize,
3133         unsigned long processingMask,
3134         Boolean ** paramUsed,
3135         int * promptDataIndex )
3136
3137 {
3138    int i, j;
3139    Boolean firstParmUsed;
3140    MsgComponent * segment;
3141    char * compiledMsg = NULL;
3142    int    compiledMsgSize = 0;
3143    ObjectData tmpObjData;
3144    static char *sessionHostName= NULL;
3145    static char *displayHostName = NULL;
3146    static char *localHostName = NULL;
3147
3148    XtFree(piece->compiledMessage);
3149    piece->compiledMessage = NULL;
3150    piece->msgLen = 0;
3151
3152    if (initialize)
3153    {
3154       /* 
3155        * Keep track of which parameters have been used, so that when
3156        * a %Args% keyword is encountered, we know which parameters
3157        * should be substituted.
3158        */
3159       *promptDataIndex = 0;
3160
3161       if (request->numObjects > 0) {
3162          *paramUsed = (Boolean *)XtMalloc((Cardinal)(sizeof(Boolean) *
3163                                                      request->numObjects));
3164          for (i = 0; i < request->numObjects; i++)
3165            (*paramUsed)[i] = False;
3166       }
3167    }
3168
3169    _DtSvcProcessLock();
3170    /* We need to query our hostname the first time only */
3171    if ( ! localHostName )
3172       localHostName = _DtGetLocalHostName();
3173
3174    /*
3175     * Determine the display host name -- default to localHostName for
3176     * degenerate display names (i.e. :0, unix:0, local:0, ...)
3177     */
3178     if ( ! displayHostName )
3179         displayHostName = _DtGetDisplayHostName(XtDisplay(w));
3180
3181     if ( ! sessionHostName )
3182         sessionHostName = _DtGetSessionHostName();
3183    _DtSvcProcessUnlock();
3184
3185    /*
3186     * The message is constructed by taking each of the
3187     * action segments, replacing any keywords, and then adding the
3188     * information to the end of the buffer.
3189     */
3190    for (i = 0; i < piece->numMsgParts; i++)
3191    {
3192       segment = piece->parsedMessage + i;
3193
3194       /* Add any text preceding the keyword */
3195       if (segment->precedingText)
3196       {
3197          compiledMsg = GrowMsgBuffer(compiledMsg, &compiledMsgSize, 
3198                                  (int)strlen(segment->precedingText));
3199          (void)strcat(compiledMsg, segment->precedingText);
3200       }
3201
3202       /* Process the keyword */
3203       switch (segment->keyword)
3204       {
3205          case LOCAL_HOST:
3206          {
3207             /* Add in the local host name */
3208             compiledMsg = GrowMsgBuffer(compiledMsg, &compiledMsgSize, 
3209                                       (int)strlen(localHostName));
3210             (void)strcat(compiledMsg, localHostName);
3211             break;
3212          }
3213
3214          case DATABASE_HOST:
3215          {
3216             /* 
3217              * Add in the host associated with the DB file from which this
3218              * action was loaded.
3219              */
3220             char * fullPath;
3221             char * host;
3222
3223             fullPath = _DtDbPathIdToString(request->clonedAction->file_name_id);
3224             host = _DtHostString(fullPath);
3225             if (host)
3226             {
3227               compiledMsg = GrowMsgBuffer(compiledMsg, &compiledMsgSize,
3228                                           host ? (int)strlen(host) : 0);
3229               (void)strcat(compiledMsg, host);
3230               XtFree(host);
3231             }
3232             XtFree(fullPath);
3233             break;
3234          }
3235
3236          case DISPLAY_HOST:
3237          {
3238             /*
3239              * Use the displayHostName determined the first time thru
3240              */
3241             compiledMsg = GrowMsgBuffer(compiledMsg, &compiledMsgSize, 
3242                                   (int)strlen(displayHostName));
3243             (void)strcat(compiledMsg, displayHostName);
3244             break;
3245          }
3246
3247          case SESSION_HOST:
3248          {
3249             /* 
3250              * Add in the session server host where providing the
3251              * display management.  (i.e. the host where the login client
3252              * is running.) 
3253              */
3254             compiledMsg = GrowMsgBuffer(compiledMsg, &compiledMsgSize, 
3255                                   (int)strlen(sessionHostName));
3256             (void)strcat(compiledMsg, sessionHostName);
3257             break;
3258
3259          }
3260
3261          case NO_KEYWORD:
3262          {
3263             /* 
3264              * If this is an entry which simply collected some user input,
3265              * then add the user's input to the message buffer.
3266              * This corresponds to the keywords: 
3267              *
3268              *         %"prompt"% 
3269              *         %(String)"prompt"%
3270              */
3271             if (segment->prompt)
3272             {
3273                /* Create dummy object; makes processing easier */
3274                if (ParseFileArgument(w, request, &tmpObjData,
3275                              NULL, request->promptInputs[*promptDataIndex],
3276                              NULL, False))
3277                {
3278                   XtFree(compiledMsg);
3279                   return(False);
3280                }
3281
3282                if (!InsertArgumentString(w, &compiledMsg, &compiledMsgSize, 
3283                           request, &tmpObjData, segment->mask, relPathHost, 
3284                           relPathDir, False, 0))
3285                {
3286                   XtFree(compiledMsg);
3287                   return(False);
3288                }
3289
3290                /* Signal that this prompt has been used */
3291                (*promptDataIndex)++;
3292             }
3293             break;
3294          }
3295
3296          case ARG:
3297          {
3298             if (segment->argNum == ALL_ARGS)
3299             {
3300                /* Insert all currently unused parameters */
3301                for (j = 0, firstParmUsed = False; j < request->numObjects; j++)
3302                {
3303                   /* Used or empty objects are skipped */
3304                   if ((*paramUsed)[j] == False)
3305                   {
3306                      if (IS_FILE_OBJ(request->objects[j].mask) &&
3307                          request->objects[j].u.file.origFilename)
3308                      {
3309                         if (!InsertArgumentString(w, &compiledMsg, 
3310                                       &compiledMsgSize, 
3311                                       request, request->objects+j,
3312                                       segment->mask, relPathHost, relPathDir,
3313                                       firstParmUsed, processingMask))
3314                         {
3315                            XtFree(compiledMsg);
3316                            return(False);
3317                         }
3318                         firstParmUsed = True;
3319                      }
3320                      /* 
3321                       * RWV: 
3322                       * Since we use tmp files for buffers and do not support
3323                       * strings; we need not add special code for buffer and
3324                       * string support.
3325                       */
3326                      /* fdt: add support for buffers and strings
3327                       * else if (IS_BUFFER_OBJ(request->objects[i].mask) &&
3328                       *          request->objects[i].u.buffer.buffer)
3329                       * else if (IS_STRING_OBJ(request->objects[i].mask) &&
3330                       *          request->objects[i].u.string.string)
3331                       */
3332                   }
3333                }
3334             }
3335             else if (segment->argNum <= request->numObjects)
3336             {
3337                if (IS_FILE_OBJ(request->objects[segment->argNum-1].mask) &&
3338                    request->objects[segment->argNum-1].u.file.origFilename)
3339                {
3340                     /* Replace only with the specified argument */
3341                    (*paramUsed)[segment->argNum-1] = True;
3342                    /*
3343                     * All buffer objects have been written to tmp files.
3344                     * This code replaces a reference to an object with its
3345                     * (tmp) file name.
3346                     *      Tooltalk processing code elsewhere
3347                     * (ActionTt.c) detects the conditions under which a buffer
3348                     * object reference should be replaced by the buffer contents
3349                     * instead of the tmp file name.  (i.e. a value field with a
3350                     * single argument reference with no additional text).  In such
3351                     * cases the compiled message string will be ignored.
3352                     */
3353                    if (!InsertArgumentString(w, &compiledMsg, &compiledMsgSize, 
3354                              request, request->objects + segment->argNum - 1, 
3355                              segment->mask, relPathHost, relPathDir, False,
3356                              processingMask))
3357                   {
3358                      XtFree(compiledMsg);
3359                      return(False);
3360                   }
3361                }
3362              /* 
3363               * RWV: 
3364               * Since we use tmp files for buffers and do not support
3365               * strings; we need not add special code for buffer and
3366               * string support.
3367               */
3368                /* fdt: add support for buffers and strings
3369                 * else if (IS_BUFFER_OBJ(request->objects[i].mask) &&
3370                 *          request->objects[i].u.buffer.buffer)
3371                 * else if (IS_STRING_OBJ(request->objects[i].mask) &&
3372                 *          request->objects[i].u.string.string)
3373                 */
3374             }
3375             break;
3376          }
3377       }
3378    }
3379
3380    if ((piece->compiledMessage = compiledMsg) == NULL)
3381         piece->msgLen = 0;
3382    else
3383        piece->msgLen = compiledMsg ? strlen(compiledMsg) + 1: 0;
3384    return(True);
3385 }
3386
3387 /*
3388  * Given an object, add it to the end of the message buffer.  The
3389  * object may refer to a file, thus possibly requiring that it be
3390  * converted to another format.
3391  */
3392
3393 static Boolean 
3394 InsertArgumentString(
3395         Widget w,
3396         char **bufPtr,
3397         int * bufSizePtr,
3398         ActionRequest *request,
3399         ObjectData *object,
3400         unsigned long mask,
3401         char * relPathHost,
3402         char * relPathDir,
3403         Boolean addLeadingSpace,
3404         unsigned long processingMask )
3405
3406 {
3407    int len;
3408    String lastCh;
3409    int lastChLen;
3410    char * path;
3411    char * value;
3412    char * dataType;
3413    char * mediaAttr;
3414
3415    if (processingMask & _DTAct_TT_VTYPE)
3416       SET_TREAT_AS_FILE(mask);
3417
3418    if (IS_TREAT_AS_FILE(mask))
3419    {
3420       if (object->type == -1)
3421       {
3422          /* Object still needs to be typed */
3423          if (IS_FILE_OBJ(object->mask))
3424          {
3425             char * origInfo = object->u.file.origFilename;
3426
3427             ParseFileArgument(w, request, object, NULL, origInfo, NULL, True);
3428             XtFree(origInfo);
3429          }
3430          /* 
3431           * RWV: 
3432           * Since we use tmp files for buffers and do not support
3433           * strings; we need not add special code for buffer and
3434           * string support.
3435           */
3436          /* fdt: add support for buffers and strings
3437           * else if (IS_BUFFER_OBJ(object->mask))
3438           * else if (IS_STRING_OBJ(object->mask))
3439           */
3440       }
3441
3442       if (IS_FILE_OBJ(object->mask))
3443       {
3444          if (processingMask & _DTAct_TT_VTYPE)
3445          {
3446             /* 
3447              * Instead of inserting the object referred to by "Arg_n",
3448              * we need to instead insert the MEDIA attribute for the
3449              * object.  If the MEDIA attribute is not defined for the
3450              * datatype associated with this object, then use the
3451              * datatype name itself.  If the thing can't be defined, then
3452              * do nothing.
3453              */
3454             if (object->type != (-1))
3455             {
3456                dataType = (char *)_DtDtsMMBosonToString(object->type);
3457
3458                if ((path = _DtActMapFileName(
3459                   request->hostNames[object->u.file.hostIndex], 
3460                   request->dirNames[object->u.file.dirIndex], 
3461                   object->u.file.baseFilename, 
3462                   NULL)) == NULL)
3463                {
3464                   path = NULL;
3465                }
3466
3467                mediaAttr = DtDtsDataTypeToAttributeValue(dataType, "MEDIA", 
3468                                                          path);
3469                XtFree(path);
3470
3471                if (mediaAttr)
3472                {
3473                   value = XtNewString(mediaAttr);
3474                   DtDtsFreeAttributeValue(mediaAttr);
3475                }
3476                else
3477                   value = XtNewString(dataType);
3478
3479                *bufPtr = GrowMsgBuffer(*bufPtr, bufSizePtr, strlen(value) + 1);
3480                if (addLeadingSpace)
3481                   strcat(*bufPtr, " ");
3482                strcat(*bufPtr, value);
3483                XtFree(value);
3484             }
3485             return(True);
3486          }
3487
3488          if (IS_CMD(request->clonedAction->mask))
3489          {
3490             /* Map into a real path, relative to the execution host */
3491             if ((path = _DtActMapFileName(
3492                request->hostNames[object->u.file.hostIndex], 
3493                request->dirNames[object->u.file.dirIndex], 
3494                object->u.file.baseFilename, 
3495                request->currentHost)) == NULL)
3496             {
3497                AddFailedHostToList(request, request->currentHost);
3498                return(False);
3499             }
3500
3501             *bufPtr = GrowMsgBuffer(*bufPtr, bufSizePtr, strlen(path) + 1);
3502             if (addLeadingSpace)
3503                (void)strcat(*bufPtr, " ");
3504             strcat(*bufPtr, path);
3505             XtFree(path);
3506          }
3507          else if (IS_TT_MSG(request->clonedAction->mask))
3508          {
3509             /*
3510              * ToolTalk automatically translates the 'filename' field within
3511              * a message, and expects the incoming name to be relative to
3512              * the local host.  So ... we simply need to map the name to
3513              * be relative to the local host.  However, if this is not the
3514              * filename, but is instead one of the 'args', then we must
3515              * insert it in a 'neutral' form.
3516              */
3517             if (processingMask & _DTAct_TT_ARG)
3518             {
3519                /* Map into "host:/path" */
3520                /* fdt: May need to instead map into 'network indep' form */
3521                InsertUnmappedArgumentString(bufPtr, bufSizePtr, object, 
3522                                             addLeadingSpace);
3523             }
3524             else
3525             {
3526                if ((path = _DtActMapFileName(
3527                   request->hostNames[object->u.file.hostIndex], 
3528                   request->dirNames[object->u.file.dirIndex], 
3529                   object->u.file.baseFilename, NULL)) == NULL)
3530                {
3531                   return(False);
3532                }
3533
3534                *bufPtr = GrowMsgBuffer(*bufPtr, bufSizePtr, strlen(path) + 1);
3535                if (addLeadingSpace)
3536                   (void)strcat(*bufPtr, " ");
3537                strcat(*bufPtr, path);
3538                XtFree(path);
3539             }
3540          }
3541       }
3542      /* 
3543       * RWV: 
3544       * Since we use tmp files for buffers and do not support
3545       * strings; we need not add special code for buffer and
3546       * string support.
3547       */
3548       /* fdt: add support for buffers and strings
3549        * else if (IS_BUFFER_OBJ(object->mask))
3550        * else if (IS_STRING_OBJ(object->mask))
3551        */
3552    }
3553    else
3554       InsertUnmappedArgumentString(bufPtr, bufSizePtr, object, addLeadingSpace);
3555    return(True);
3556 }
3557
3558
3559 /*
3560  * This function knows how to insert a string in "host:/path" format;
3561  * this is essentually an 'unmapped' filename.  File arguments which
3562  * have been preceded by the "(String)" qualifier will be saved in
3563  * this fashion.  Likewise, any filenames (either in "String" or "File"
3564  * form) for an message will be saved in this format, due to the
3565  * fact that we don't know the execution host, and thus cannot properly
3566  * map the filename using the ToolTalk filename mapping functions.
3567  */
3568
3569 static void 
3570 InsertUnmappedArgumentString(
3571         char **bufPtr,
3572         int * bufSizePtr,
3573         ObjectData *object,
3574         Boolean addLeadingSpace )
3575
3576 {
3577    char * host = NULL;
3578    int size;
3579
3580    /* No mapping is necessary here. */
3581    if (IS_FILE_OBJ(object->mask))
3582    {
3583       size = strlen(object->u.file.origFilename) + 4;
3584       *bufPtr = GrowMsgBuffer(*bufPtr, bufSizePtr, size);
3585       if (addLeadingSpace)
3586           (void)strcat(*bufPtr, " ");
3587       strcat(*bufPtr, object->u.file.origFilename);
3588    }
3589  /* 
3590   * RWV: 
3591   * Since we use tmp files for buffers and do not support
3592   * strings; we need not add special code for buffer and
3593   * string support.
3594   */
3595    /* fdt: add support for buffers and strings
3596     * else if (IS_BUFFER_OBJ(object->mask))
3597     * else if (IS_STRING_OBJ(object->mask))
3598     */
3599 }
3600
3601
3602 /*
3603  * This function checks to see if the message buffer is large enough
3604  * to hold the current contents + 'count' more bytes.  If it is not
3605  * large enough, then the buffer will be grown.  The buffer MUST BE
3606  * NULL terminated.
3607  */
3608
3609 static String 
3610 GrowMsgBuffer(
3611         String buffer,
3612         int *size,
3613         int count )
3614
3615 {
3616    int currentBufUsed = buffer ? strlen(buffer) : 0;
3617
3618    if ((currentBufUsed + count + 1) >= *size)
3619    {
3620       (*size) += (count+1 > 1024) ? count + 1 : 1024;
3621       buffer = (char *)XtRealloc(buffer, (Cardinal)*size);
3622
3623       /* If this is the first alloc for the buffer, then terminate the buffer */
3624       if(currentBufUsed == 0)
3625          buffer[0] = '\0';
3626    }
3627
3628    return(buffer);
3629 }
3630
3631
3632
3633 /***************************************************************************/
3634 /***************************************************************************/
3635 /*           Functions For Matching Arguments To A Message String          */
3636 /***************************************************************************/
3637 /***************************************************************************/
3638
3639
3640 /*
3641  * If the specified prompt has not already been added to the array of
3642  * prompt strings, then add it.  The exception is for stand-alone
3643  * prompt strings, which always are added.
3644  */
3645
3646 static void 
3647 AddPrompt(
3648         int argNum,
3649         String prompt,
3650         int *numPrompts,
3651         PromptEntry **prompts )
3652
3653 {
3654    int i;
3655
3656    /*
3657     * Standard arguments only want their prompts entered once.
3658     * Stand-alone prompts all have argNum == NO_ARG, and each one
3659     * must be saved.  It's a special case.
3660     */
3661    if (argNum != NO_ARG)
3662    {
3663       for (i = 0; i < *numPrompts; i++)
3664       {
3665          if ((*prompts)[i].argIndex == argNum)
3666             return;
3667       }
3668    }
3669
3670    (*numPrompts)++;
3671    *prompts = (PromptEntry *)XtRealloc((char *)*prompts, 
3672               (Cardinal)(sizeof(PromptEntry) * *numPrompts));
3673    (*prompts)[(*numPrompts) - 1].argIndex = argNum;
3674    (*prompts)[(*numPrompts) - 1].prompt = prompt;
3675 }
3676
3677
3678 /*
3679  * This function takes an action DB entry and an action request, and
3680  * determines if enough information was supplied to create the message
3681  * needed to get the work done.  If information was missing, then this
3682  * function will return an array of prompt strings, which can be used
3683  * to create a dialog for collecting the missing information.  This 
3684  * function also returns an indication of how many of the parameters
3685  * were left unused.
3686  *
3687  * The caller is responsible for freeing up the prompt array, but the
3688  * entries in the array MUST NOT be freed up.
3689  */
3690
3691 static int 
3692 MatchParamsToAction(
3693         ActionRequest *request,
3694         int *numPrompts,
3695         PromptEntry **prompts )
3696 {
3697    Boolean * paramUsed = NULL;
3698    int unused;
3699    Boolean argsOptionFound;
3700    int i;
3701    int lastArgReferenced;
3702    ActionPtr action = request->clonedAction;
3703
3704    /* Initialize things */
3705    *numPrompts = 0;
3706    *prompts = NULL;
3707    argsOptionFound = False;
3708    lastArgReferenced = -1;
3709
3710    /* 
3711     * This array lets us know which parameters can be used when we
3712     * encounter the %Args% keyword.
3713     */
3714    unused = request->numObjects;
3715    if (unused > 0) {
3716      paramUsed = (Boolean *)XtMalloc((Cardinal)(sizeof(Boolean) * unused));
3717      for (i = 0; i < unused; i++)
3718        paramUsed[i] = False;
3719    }
3720
3721    if (IS_CMD(action->mask))
3722    {
3723         /*
3724          * NOTE: The current implementation of prompt strings requires that
3725          *       the segments be evaluated in the same order in which the
3726          *       message fields were parsed.
3727          *      (See ResolveCommandInvokerMessagePieces() )
3728          *       This order is currently "execHost", "execString" and
3729          *       "termOpts".  This situation arises because
3730          *       the existing prompt data structures do NOT identify the
3731          *       location of the prompt and hence where to put the
3732          *       user-supplied value; except by order of occurrence.
3733          */
3734       ProcessOneSegment(request, &(action->u.cmd.execHosts), prompts, 
3735                         numPrompts, &argsOptionFound, &lastArgReferenced,
3736                         &unused, paramUsed);
3737       ProcessOneSegment(request, &(action->u.cmd.execString), prompts, 
3738                         numPrompts, &argsOptionFound, &lastArgReferenced, 
3739                         &unused, paramUsed);
3740       ProcessOneSegment(request, &(action->u.cmd.termOpts), prompts, 
3741                         numPrompts, &argsOptionFound, &lastArgReferenced, 
3742                         &unused, paramUsed);
3743    }
3744    else if (IS_TT_MSG(action->mask))
3745    {
3746       ProcessOneSegment(request, &(action->u.tt_msg.tt_op), prompts, 
3747                         numPrompts, &argsOptionFound, &lastArgReferenced, 
3748                         &unused, paramUsed);
3749       ProcessOneSegment(request, &(action->u.tt_msg.tt_file), prompts, 
3750                         numPrompts, &argsOptionFound, &lastArgReferenced, 
3751                         &unused, paramUsed);
3752
3753       for (i = 0; i < action->u.tt_msg.vtype_count; i++)
3754       {
3755          ProcessOneSegment(request, &(action->u.tt_msg.tt_argn_vtype[i]), 
3756                            prompts, numPrompts, &argsOptionFound, 
3757                            &lastArgReferenced, &unused, paramUsed);
3758       }
3759
3760       for (i = 0; i < action->u.tt_msg.value_count; i++)
3761       {
3762         /*
3763          * We require that at most ONE argument be consumed by a
3764          * tt_argn_value field.
3765          */
3766          ProcessOneSegment(request, &(action->u.tt_msg.tt_argn_value[i]), 
3767                            prompts, numPrompts, &argsOptionFound, 
3768                            &lastArgReferenced, &unused, paramUsed);
3769          
3770       }
3771    }
3772
3773    /*
3774     * Now that we have processed all of the pieces which will ultimately
3775     * used to construct our message, determine if any of the arguments
3776     * passed to _DtActionInvoke were not used; this allows us to tell
3777     * the user that there were unused arguments, so they can choose
3778     * to continue or abort the request.
3779     * If we ever encountered a %Args% keyword, then ultimately all of
3780     * the parameters will be used.
3781     */
3782    if (argsOptionFound)
3783       unused = 0;
3784    else
3785    {
3786       /*
3787        * Determine how many arguments were actually unused; only count
3788        * those arguments AFTER the last referenced one.  i.e. if arg2
3789        * is referenced, but arg1 and arg3 are not, then only count arg3
3790        * as an unused (and thus extra) parameter.
3791        */
3792       for (i = 0; ((i < lastArgReferenced - 1) && (i < request->numObjects));
3793            i++)
3794       {
3795          if (!paramUsed[i])
3796             unused--;
3797       }
3798
3799       /* This should never happen, but ... */
3800       if (unused < 0)
3801          unused = 0;
3802    }
3803
3804    if (paramUsed) XtFree(paramUsed);
3805
3806    return(unused);
3807 }
3808
3809
3810 static void 
3811 ProcessOneSegment(
3812         ActionRequest * request,
3813         parsedMsg * msg,
3814         PromptEntry **prompts,
3815         int *numPrompts,
3816         Boolean * argsOptionFound,
3817         int * lastArgReferenced,
3818         int * unused,
3819         Boolean * paramUsed )
3820 {
3821    MsgComponent * piece;
3822    int i;
3823
3824    /* 
3825     * Check each piece of this message component, to see if the parameter
3826     * it expects has been supplied.  If the parameter is missing, and
3827     * a prompt was supplied, then add the prompt to the prompt array.
3828     */
3829    for (i = 0; i < msg->numMsgParts; i++)
3830    {
3831       piece = msg->parsedMessage + i;
3832
3833       /* 
3834        * We only care about %Args% and %Arg_<n>% keywords, and
3835        * entries which have no keyword, but do have a prompt.
3836        */ 
3837       if (piece->keyword == ARG)
3838       {
3839          if (piece->argNum == ALL_ARGS)
3840          {
3841             /*
3842              * When a %Args% keyword is found, this implies that there
3843              * will ultimately be no unused parameters, because this
3844              * keyword is replaced by all unused parameters.
3845              */
3846             *argsOptionFound = True;
3847          }
3848          else if (piece->argNum > 0)
3849          {
3850             /* Keep track of the largest arg index referenced */
3851             if (piece->argNum > *lastArgReferenced)
3852                *lastArgReferenced = piece->argNum;
3853
3854             /* See if a parameter was supplied for this argNum */
3855             if (piece->argNum > request->numObjects)
3856             {
3857                /* Parameter is missing; see if a prompt was given */
3858                if (piece->prompt)
3859                   AddPrompt(piece->argNum, piece->prompt, numPrompts, prompts);
3860             }
3861             else
3862             {
3863                /* Mark this parameter as having been used */
3864                if (!paramUsed[piece->argNum - 1])
3865                {
3866                   paramUsed[piece->argNum - 1] = True;
3867                   (*unused)--;
3868                }
3869             }
3870          }
3871       }
3872       else if ((piece->keyword == NO_KEYWORD) && (piece->prompt))
3873       {
3874          /* Entries may be nothing but a prompt */
3875          AddPrompt(NO_ARG, piece->prompt, numPrompts, prompts);
3876       }
3877    }
3878 }
3879
3880 /***************************************************************************/
3881 /***************************************************************************/
3882 /*                          Prompt Dialog Support                          */
3883 /***************************************************************************/
3884 /***************************************************************************/
3885
3886
3887 /*
3888  * This is the event handler which catches the 'escape' key when typed
3889  * into the prompt.  It will unpost the dialog.
3890  */
3891
3892 static void
3893 CancelOut(
3894         Widget w,
3895         XEvent *event,
3896         XtPointer params,
3897         XtPointer num_params)
3898 {
3899    Arg args[10];
3900    Widget cancel;
3901
3902    /* Get the cancel button widget id */
3903    XtSetArg(args[0], XmNuserData, &cancel);
3904    XtGetValues(w, args, 1);
3905
3906    /* Unpost the text annotation dialog */
3907    XtCallCallbacks(cancel, XmNactivateCallback, NULL);
3908 }
3909
3910
3911 /*
3912  * 'Cancel' callback for the dialog used to collect missing parameters
3913  * from the user.  It will free up the memory holding the cancelled
3914  * request and will destroy the dialog.
3915  */
3916
3917 static void 
3918 CancelPromptDialog(
3919         Widget widget,
3920         PromptDialog *dialog,
3921         XtPointer call_data )
3922
3923 {
3924    unsigned long evalStatus;
3925    unsigned long userStatus;
3926    _DtActInvRecT *invp;
3927
3928    /* Destroy the dialog */
3929    XtDestroyWidget(XtParent(dialog->topLevel));
3930
3931    /* Free up the prompt sub-structure */
3932    XtFree((char *)dialog->prompts);
3933
3934    
3935    invp = _DtActFindInvRec(dialog->request->invocId);
3936    myassert(invp);  /* There should always be an invocation record */
3937
3938    /* Free up the original request structure */
3939    _DtFreeRequest(dialog->request);
3940
3941    /* Free up the callback structure */
3942    XtFree((char *)dialog);
3943
3944    if ( !invp )
3945        return;  /* This should never happen */
3946
3947    SET_INV_CANCEL(invp->state);
3948
3949    /*
3950     * Evaluate whether we are done with this invocation -- are there
3951     * uncompleted children? There should not be any subsequent invocations
3952     * to worry about since this cancel effectively aborts further processing.
3953     *
3954     * We may have to return  values  to the caller.  
3955     */
3956    
3957    _DtActExecutionLeafNodeCleanup(invp->id,NULL,0,True);
3958
3959 }
3960
3961 /*
3962  * This function changes the focus from the given "widget's" 
3963  * tab group to the next tab group.
3964  */
3965
3966 static void 
3967 ChangePromptTraversal(
3968         Widget widget,
3969         PromptDialog *dialog,
3970         XtPointer call_data )
3971 {
3972    XmProcessTraversal (widget, XmTRAVERSE_NEXT_TAB_GROUP);
3973 }
3974
3975 /*
3976  * 'Ok' callback for the dialog used to collect missing parameters
3977  * from the user.  It will redo the array of parameter strings, and
3978  * then execute the command, given whatever the user has supplied.
3979  * It will also destroy the dialog box.
3980  */
3981
3982 static void 
3983 ProcessPromptDialog(
3984         Widget widget,
3985         PromptDialog *dialog,
3986         XtPointer call_data )
3987
3988 {
3989    int i, j;
3990    String value;
3991
3992    /* Unpost the dialog */
3993    XtUnmanageChild(dialog->topLevel);
3994
3995    /*
3996     * Given the set of strings supplied by the user, update the
3997     * object array which is part of the original request.
3998     */
3999    for (i = 0; i < dialog->numPrompts; i++)
4000    {
4001       value = XmTextFieldGetString(dialog->prompts[i].promptWidget);
4002
4003       /* Do we need to grow the object array? */
4004       if (dialog->prompts[i].argIndex > 0)
4005       {
4006          if (_DtEmptyString(value))
4007          {
4008             XtFree(value);
4009             continue;
4010          }
4011
4012          if (dialog->prompts[i].argIndex > dialog->request->numObjects)
4013          {
4014             dialog->request->objects = (ObjectData *)
4015                 XtRealloc((char *)dialog->request->objects, 
4016                 (Cardinal)(sizeof(ObjectData) * (dialog->prompts[i].argIndex)));
4017
4018             /* Initialize the new array entries */
4019             for (j = dialog->request->numObjects; 
4020                  j < dialog->prompts[i].argIndex; 
4021                  j++)
4022             {
4023                dialog->request->objects[j].mask = 0;
4024                SET_FILE_OBJ(dialog->request->objects[j].mask);
4025                dialog->request->objects[j].type = -1;
4026                dialog->request->objects[j].u.file.hostIndex = -1;
4027                dialog->request->objects[j].u.file.dirIndex = -1;
4028                dialog->request->objects[j].u.file.origFilename = NULL;
4029                dialog->request->objects[j].u.file.origHostname = NULL;
4030                dialog->request->objects[j].u.file.baseFilename = NULL;
4031                dialog->request->objects[j].u.file.bp = 0;
4032                dialog->request->objects[j].u.file.sizebp = 0;
4033                dialog->request->objects[j].u.buffer.bp = 0;
4034                dialog->request->objects[j].u.buffer.size = 0;
4035                dialog->request->objects[j].u.string.string = 0;
4036                SET_UNKNOWN_IF_DIR(dialog->request->objects[j].mask);
4037             }
4038             dialog->request->numObjects = dialog->prompts[i].argIndex;
4039          }
4040          /*
4041           * These values cannot be broken up into host/dir/file components,
4042           * nor can they be typed, until we know it they refer to a file.
4043           * This can't be determined until we construct the action message.
4044           */
4045          dialog->request->objects[dialog->prompts[i].argIndex-1].u.file.
4046                   origFilename = value;
4047       }
4048       else /* Prompt-only input */
4049       {
4050          /* 
4051           * Prompt-only input can't fit in our ordered object array,
4052           * since they don't have a unique argIndex which can be used
4053           * as the index into the object array.
4054           */
4055          dialog->request->numPromptInputs++;
4056          dialog->request->promptInputs = (String *)
4057             XtRealloc((char *)dialog->request->promptInputs,
4058               (Cardinal)(dialog->request->numPromptInputs * sizeof(String)));
4059
4060          dialog->request->promptInputs[dialog->request->numPromptInputs-1] =
4061               value;
4062       }
4063    }
4064
4065    /* Destroy the dialog */
4066    XtDestroyWidget(XtParent(dialog->topLevel));
4067    XmUpdateDisplay(widget);
4068    
4069    /* 
4070     * Invoke the action using the information we've collected.
4071     * If this was a single argument action, then the reprocessing
4072     * of it may have generated another dialog, so we can only free
4073     * up the request, when all processing is done.
4074     */
4075    if (ProcessRequest(dialog->associatedWidget, dialog->request))
4076    {
4077        _DtActInvRecT    *invp;
4078        unsigned long    evalStatus;
4079        unsigned long    userStatus;
4080
4081        if ( (invp = _DtActFindInvRec(dialog->request->invocId)) )
4082        {
4083            /* all done invoking ? */
4084            RESET_INV_PENDING(invp->state);
4085
4086            /* We should only get here if all requests have been honored */
4087            SET_INV_COMPLETE(invp->state);
4088
4089            /*
4090             * evaluate whether all child actions have been completed
4091             * and if its time to call the user callback.
4092             */
4093            _DtActExecutionLeafNodeCleanup(invp->id,NULL,0,True);
4094        }
4095        myassert(invp); /* there should always be one to find */
4096        _DtFreeRequest(dialog->request);
4097    }
4098
4099    /* Free up the prompt sub-structure */
4100    XtFree((char *)dialog->prompts);
4101
4102    /* Free up the callback structure */
4103    XtFree((char *)dialog);
4104 }
4105
4106
4107 /*
4108  * This function takes the array of prompt strings, and creates a
4109  * dialog box, using these prompt strings as the labels for a set
4110  * of text widgets.
4111  */
4112
4113 static void 
4114 CreatePromptDialog(
4115         Widget w,
4116         ActionRequest *request,
4117         int numPrompts,
4118         PromptEntry *prompts )
4119
4120 {
4121    PromptDialog * dialog;
4122    DialogPromptEntry * promptDes;
4123    XmString pt1;
4124    String title;
4125    Widget shell, bboard, frame, form, label;
4126    Widget promptLabel, topAttach;
4127    Widget separator, ok, cancel;
4128    int count;
4129    int n, i;
4130    Arg args[20];
4131    XmString labelString;
4132    XWindowAttributes xwa;
4133    Status status;
4134    Boolean is_mapped = False;
4135    static XtTranslations trans_table;
4136    static Boolean first = True;
4137    Atom xa_WM_DELETE_WINDOW;
4138
4139    /*
4140     * Want to set up the Escape key so that it will unpost the dialog.
4141     */
4142    _DtSvcProcessLock();
4143    if (first)
4144    {
4145       XtAppAddActions(XtWidgetToApplicationContext(w), actionTable, 1);
4146       trans_table = XtParseTranslationTable(translations_escape);
4147       first = False;
4148    }
4149    _DtSvcProcessUnlock();
4150
4151    /* Allocate the structures we'll be needing */
4152    dialog = (PromptDialog *)XtMalloc((Cardinal)sizeof(PromptDialog));
4153    promptDes = (DialogPromptEntry *)XtMalloc((Cardinal)
4154               (sizeof(DialogPromptEntry) * numPrompts));
4155
4156
4157    /*  Create the shell, frame and form used for the dialog.  */
4158
4159    title = (char *)XtMalloc((Cardinal)
4160            (strlen(PromptDialogTitle)+ strlen(request->clonedAction->label) + 1));
4161    (void)sprintf(title, "%1$s%2$s", PromptDialogTitle, request->clonedAction->label);
4162    n = 0;
4163    XtSetArg (args[n], XmNallowShellResize, True);               n++;
4164    XtSetArg (args[n], XmNtitle, title);         n++;
4165    shell = XmCreateDialogShell (w, "promptDialog", args, n);
4166    XtFree(title);
4167
4168    if (XtIsRealized(w))
4169    {
4170      status = XGetWindowAttributes (XtDisplay (w), XtWindow (w), &xwa);
4171      if (status && (xwa.map_state == IsViewable))
4172        is_mapped = True;
4173    }
4174
4175    n = 0;
4176    XtSetArg (args[n], XmNmarginWidth, 0);               n++;
4177    XtSetArg (args[n], XmNmarginHeight, 0);              n++;
4178    if (!is_mapped)
4179    {
4180       XtSetArg (args[n], XmNdefaultPosition, False);    
4181       n++;
4182    }
4183    bboard = XmCreateBulletinBoard (shell, "bboard", args, n);
4184
4185    n = 0;
4186    XtSetArg (args[n], XmNshadowThickness, 1);           n++;
4187    XtSetArg (args[n], XmNshadowType, XmSHADOW_OUT);     n++;
4188    frame = XmCreateFrame (bboard, "frame", args, n);
4189    XtManageChild (frame);
4190
4191    n = 0;
4192    XtSetArg (args[n], XmNautoUnmanage, False);                  n++;
4193    XtSetArg (args[n], XmNtextTranslations, trans_table);        n++;
4194    form = XmCreateForm (frame, "form", args, n);
4195    XtManageChild (form);
4196
4197    /* Create the dialog description label */
4198
4199    pt1 = XmStringCreateLocalized(PromptDialogLabel);
4200    n = 0;
4201    XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM);       n++;
4202    XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
4203    XtSetArg(args[n], XmNleftOffset, 20);                       n++;
4204    XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM);       n++;
4205    XtSetArg(args[n], XmNrightOffset, 20);                       n++;
4206    XtSetArg(args[n], XmNtopOffset, 15);                        n++;
4207    XtSetArg(args[n], XmNlabelString, pt1);                        n++;
4208    label = XmCreateLabelGadget(form, "label", args, n);
4209    XtManageChild (label);
4210    XmStringFree(pt1);
4211
4212    /* Create each of the needed prompts */
4213    topAttach = label;
4214    for (count = 0; count < numPrompts; count++)
4215    {
4216       promptDes[count].argIndex = prompts[count].argIndex;
4217
4218       pt1 = XmStringCreateLocalized(prompts[count].prompt);
4219       n = 0;
4220       XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET);       n++;
4221       XtSetArg(args[n], XmNtopWidget, topAttach);       n++;
4222       XtSetArg(args[n], XmNtopOffset, 10);                        n++;
4223       XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
4224       XtSetArg(args[n], XmNleftOffset, 30);                       n++;
4225       XtSetArg(args[n], XmNlabelString, pt1);                       n++;
4226       promptLabel = XmCreateLabelGadget(form, "promptLabel", args, n);
4227       XtManageChild(promptLabel);
4228       XmStringFree(pt1);
4229
4230       n = 0;
4231       XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET);       n++;
4232       XtSetArg(args[n], XmNtopWidget, topAttach);       n++;
4233       XtSetArg(args[n], XmNtopOffset, 8);                        n++;
4234       XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
4235       XtSetArg(args[n], XmNrightOffset, 30);                       n++;
4236       XtSetArg(args[n], XmNtraversalOn, True);                       n++;
4237       XtSetArg(args[n], XmNleftAttachment,XmATTACH_WIDGET );    n++;
4238       XtSetArg(args[n], XmNleftWidget, promptLabel);        n++;
4239       XtSetArg(args[n], XmNleftOffset, 15);                     n++;
4240       promptDes[count].promptWidget = XmCreateTextField(form, "text", args, n);
4241
4242       XtManageChild(promptDes[count].promptWidget);
4243
4244       XmAddTabGroup(promptDes[count].promptWidget);
4245       topAttach = promptDes[count].promptWidget;
4246    }
4247
4248
4249    /*  Create a separator between the buttons  */
4250
4251    n = 0;
4252    XtSetArg (args[n], XmNleftAttachment, XmATTACH_FORM);        n++;
4253    XtSetArg (args[n], XmNrightAttachment, XmATTACH_FORM);       n++;
4254    XtSetArg (args[n], XmNtopAttachment, XmATTACH_WIDGET);       n++;
4255    XtSetArg (args[n], XmNtopWidget, topAttach); n++;
4256    XtSetArg (args[n], XmNtopOffset, 20);                        n++;
4257    separator =  XmCreateSeparatorGadget (form, "separator", args, n);
4258    XtManageChild (separator);
4259
4260
4261    /*  Create the ok and cancel buttons  */
4262
4263    n = 0;
4264    labelString = XmStringCreateLocalized((String)_DtOkString);
4265    XtSetArg (args[n], XmNleftAttachment, XmATTACH_POSITION);    n++;
4266    XtSetArg (args[n], XmNleftPosition, 5 + 10);                 n++;
4267    XtSetArg (args[n], XmNrightAttachment, XmATTACH_POSITION);   n++;
4268    XtSetArg (args[n], XmNrightPosition, 31 + 10);               n++;
4269    XtSetArg (args[n], XmNtopAttachment, XmATTACH_WIDGET);       n++;
4270    XtSetArg (args[n], XmNtopWidget, separator);                 n++;
4271    XtSetArg (args[n], XmNtopOffset, 16);                        n++;
4272    XtSetArg (args[n], XmNbottomOffset, 16);                     n++;
4273    XtSetArg (args[n], XmNmarginHeight, 4);                      n++;
4274    XtSetArg (args[n], XmNshowAsDefault, True);                  n++;
4275    XtSetArg (args[n], XmNlabelString, labelString);             n++;
4276    ok = XmCreatePushButtonGadget (form, "ok", args, n);
4277    XtManageChild(ok);
4278    XtAddCallback(ok, XmNactivateCallback, (XtCallbackProc)ProcessPromptDialog, 
4279                  (XtPointer)dialog);
4280    XmStringFree(labelString);
4281
4282    /* Set the default action */
4283    
4284    if (numPrompts <= 1) 
4285    {
4286       n = 0;
4287       XtSetArg (args[n], XmNdefaultButton, ok);    n++;
4288       XtSetValues(bboard, args, n);
4289    }
4290    else 
4291    {
4292       int i;
4293       /*
4294        * Want to set the traversal so that if "return" is hit in the
4295        * last prompt, the "ProcessPromptDialog" callback is invoked.  
4296        * Otherwise, the "return" should move the focus to the next 
4297        * which is in a different (tab group).
4298        */
4299       for (i = 0; i < numPrompts; i++) 
4300       {
4301          if (i <= (numPrompts - 2))
4302             XtAddCallback(promptDes[i].promptWidget, XmNactivateCallback, 
4303                           (XtCallbackProc)ChangePromptTraversal, 
4304                           (XtPointer)dialog);
4305          else
4306             XtAddCallback(promptDes[i].promptWidget, XmNactivateCallback, 
4307                           (XtCallbackProc)ProcessPromptDialog, 
4308                           (XtPointer)dialog);
4309       }
4310    }
4311
4312    n = 0;
4313    labelString = XmStringCreateLocalized((String)_DtCancelString);
4314    XtSetArg (args[n], XmNleftAttachment, XmATTACH_POSITION);    n++;
4315    XtSetArg (args[n], XmNleftPosition, 37 + 22);                n++;
4316    XtSetArg (args[n], XmNrightAttachment, XmATTACH_POSITION);   n++;
4317    XtSetArg (args[n], XmNrightPosition, 63 + 22);               n++;
4318    XtSetArg (args[n], XmNtopAttachment, XmATTACH_WIDGET);       n++;
4319    XtSetArg (args[n], XmNtopWidget, separator);                 n++;
4320    XtSetArg (args[n], XmNtopOffset, 21);                        n++;
4321    XtSetArg (args[n], XmNbottomOffset, 21);                     n++;
4322    XtSetArg (args[n], XmNmarginHeight, 4);                      n++;
4323    XtSetArg (args[n], XmNlabelString, labelString);             n++;
4324    cancel = XmCreatePushButtonGadget (form, "cancel", args, n);
4325    XtManageChild(cancel);
4326    XtAddCallback(cancel, XmNactivateCallback, (XtCallbackProc)CancelPromptDialog, 
4327                  (XtPointer)dialog);
4328    XmStringFree(labelString);
4329
4330    /*
4331     * For each prompt, must set up the Escape key to be equivalent
4332     * to the "Cancel button.
4333     */
4334    for (i = 0; i < numPrompts; i++)  {
4335       n = 0;
4336       XtSetArg(args[n], XmNuserData, cancel);  n++;
4337       XtSetValues(promptDes[i].promptWidget, args, n);
4338    }
4339
4340    /*
4341     * If the widget is not mapped, center this dialog.
4342     */
4343    if (!is_mapped) 
4344    {
4345       Dimension dialogWd, dialogHt;
4346
4347       XtSetArg(args[0], XmNmappedWhenManaged, False);
4348       XtSetValues(shell, args, 1);
4349
4350       XtManageChild(bboard);
4351       XtRealizeWidget(shell);
4352
4353       XtSetArg(args[0], XmNwidth, &dialogWd);
4354       XtSetArg(args[1], XmNheight, &dialogHt);
4355       XtGetValues(bboard, args, 2);
4356
4357       XtSetArg (args[0], XmNx,
4358                 (WidthOfScreen(XtScreen(bboard)) - dialogWd) / 2U);
4359       XtSetArg (args[1], XmNy,
4360                 (HeightOfScreen(XtScreen(bboard)) - dialogHt) / 2U);
4361       XtSetValues (bboard, args, 2); 
4362    }
4363
4364    /*  Adjust the decorations for the dialog shell of the dialog  */
4365
4366    n = 0;
4367    XtSetArg (args[n], XmNmwmDecorations, 
4368              MWM_DECOR_BORDER | MWM_DECOR_MENU | MWM_DECOR_TITLE);      n++;
4369    XtSetValues(shell, args, n);
4370
4371    xa_WM_DELETE_WINDOW = 
4372      XInternAtom(XtDisplay(shell), "WM_DELETE_WINDOW", False);
4373    XmAddWMProtocolCallback(
4374                 shell, xa_WM_DELETE_WINDOW,
4375                 (XtCallbackProc) CancelPromptDialog, (XtPointer) dialog);
4376
4377    /* Fill in our instance structure */
4378    dialog->request = request;
4379    dialog->topLevel = bboard;
4380    dialog->numPrompts = count;
4381    dialog->prompts = promptDes;
4382    dialog->associatedWidget = w;
4383
4384    /* Post the dialog */
4385    XtSetArg(args[0], XmNmappedWhenManaged, True);
4386    XtSetValues(shell, args, 1);
4387    XtManageChild(bboard);
4388
4389    /* Make the first prompt automatically get the focus. */
4390    if (numPrompts >= 0)
4391       XmProcessTraversal(promptDes[0].promptWidget, XmTRAVERSE_CURRENT);
4392 }
4393
4394
4395 /***************************************************************************/
4396 /***************************************************************************/
4397 /*                         Continue Dialog Support                         */
4398 /***************************************************************************/
4399 /***************************************************************************/
4400
4401
4402 /*
4403  * 'Ok' callback for the abort/continue dialog.  It will continue with the
4404  * processing of the request, ignoring any unused parameters.
4405  */
4406
4407 static void 
4408 ContinueRequest(
4409         Widget widget,
4410         XtPointer user_data,
4411         XtPointer call_data )
4412
4413 {
4414    int i;
4415    ContinueDialog *dialog = (ContinueDialog *)user_data;
4416
4417    /* Destroy the dialog */
4418    XtDestroyWidget(XtParent(dialog->topLevel));
4419    XmUpdateDisplay(widget);
4420
4421    /*
4422     * If we need to collect some prompt input from the user, then
4423     * post the prompt dialog; otherwise, send the action request.
4424     */
4425    if (dialog->numPrompts == 0)
4426    {
4427       if (ProcessRequest(dialog->associatedWidget, dialog->request))
4428       {
4429            _DtActInvRecT        *invp;
4430
4431            if((invp=_DtActFindInvRec(dialog->request->invocId))!=NULL)
4432            {
4433                /* all done invoking ? */
4434                RESET_INV_PENDING(invp->state);
4435
4436                /* We should only get here if all requests have been honored */
4437                SET_INV_COMPLETE(invp->state);
4438
4439                /*
4440                 * evaluate whether all child actions have been completed
4441                 * and if its time to call the user callback.
4442                 */
4443                _DtActExecutionLeafNodeCleanup(invp->id,NULL,0,True);
4444            }
4445
4446            myassert(invp); /* there should always be one to find */
4447          _DtFreeRequest(dialog->request);
4448       }
4449    }
4450    else
4451    {
4452       CreatePromptDialog(dialog->associatedWidget, dialog->request,
4453                          dialog->numPrompts, 
4454                          dialog->prompts);
4455    }
4456
4457
4458    /* Free up the prompt sub-structure */
4459    for (i = 0; i < dialog->numPrompts; i++)
4460       XtFree(dialog->prompts[i].prompt);
4461    XtFree((char *)dialog->prompts);
4462
4463    /* Free up the callback structure */
4464    XtFree((char *)dialog);
4465 }
4466
4467
4468 /*
4469  * 'Cancel' callback for the dialog which prompts the user to continue
4470  * or abort, when too many parameters have been supplied.  This will
4471  * free up the dialog data and the request and will destroy the dialog.
4472  */
4473
4474 static void 
4475 CancelRequest(
4476         Widget widget,
4477         XtPointer user_data,
4478         XtPointer call_data )
4479
4480 {
4481    int i;
4482    ContinueDialog *dialog = (ContinueDialog *)user_data;
4483    unsigned long evalStatus;
4484    unsigned long userStatus;
4485    _DtActInvRecT *invp;
4486
4487    /* Destroy the dialog */
4488    XtDestroyWidget(XtParent(dialog->topLevel));
4489
4490    /* Free up the prompt sub-structure */
4491    for (i = 0; i < dialog->numPrompts; i++)
4492       XtFree(dialog->prompts[i].prompt);
4493    XtFree((char *)dialog->prompts);
4494
4495    /* get the invocation record */
4496    invp = _DtActFindInvRec(dialog->request->invocId);
4497    myassert(invp);      /* There should always be one available */
4498    
4499    /* Free up the original request structure */
4500    _DtFreeRequest(dialog->request);
4501
4502    /* Free up the callback structure */
4503    XtFree((char *)dialog);
4504
4505    if ( !invp )
4506        return;   /* should never happen */
4507
4508    SET_INV_CANCEL(invp->state);
4509    /*
4510     * Evaluate whether we are done with this invocation -- are there
4511     * uncompleted children? There should not be any subsequent invocations
4512     * to worry about since this cancel effectively aborts further processing.
4513     *
4514     * We may have to return  values  to the caller.  
4515     */
4516    _DtActExecutionLeafNodeCleanup(invp->id,NULL,0,True);
4517 }
4518
4519 /*
4520  * When an action is requested, and more parameters than are needed
4521  * are supplied, the user will be prompted to continue with the
4522  * operation (ignoring the extra parameters), or to abort the request.
4523  *
4524  * This function builds the dialog which will collect the user's response.
4525  */
4526
4527 static void 
4528 CreateContinueDialog(
4529         Widget w,
4530         ActionRequest *request,
4531         int numPrompts,
4532         PromptEntry *prompts )
4533
4534 {
4535    ContinueDialog * dialog;
4536    String title;
4537    XmString label;
4538    int i;
4539    int n;
4540    Arg args[10];
4541    XmString ok, cancel;
4542    char *fmt;
4543
4544    /* Allocate the structures we'll be needing */
4545    dialog = (ContinueDialog *)XtMalloc((Cardinal)sizeof(ContinueDialog));
4546    dialog->request = request;
4547    dialog->associatedWidget = w;
4548    dialog->numPrompts = numPrompts;
4549
4550    /*
4551     * We need to make a clone of the prompt array, since the strings
4552     * it contains are not ones we can guarantee will be around when
4553     * the user finally responds to this dialog.
4554     */
4555    if (prompts)
4556    {
4557       dialog->prompts = (PromptEntry *)
4558               XtMalloc((Cardinal)(sizeof(PromptEntry) * numPrompts));
4559       for (i = 0; i < numPrompts; i++)
4560       {
4561          dialog->prompts[i].argIndex = prompts[i].argIndex;
4562          dialog->prompts[i].prompt = XtNewString(prompts[i].prompt);
4563       }
4564    }
4565    else
4566       dialog->prompts = NULL;
4567
4568    ok = XmStringCreateLocalized((String)_DtOkString);
4569    cancel = XmStringCreateLocalized((String)_DtCancelString);
4570
4571    /* Create the error dialog */
4572    fmt = XtNewString((char *)Dt11GETMESSAGE(2, 2, "%1$s%2$s"));
4573    title = (char *)XtMalloc((Cardinal)
4574              (strlen(PromptDialogTitle) +
4575               strlen(request->clonedAction->label) +
4576               strlen(fmt) + 1));
4577    (void)sprintf(title, fmt, PromptDialogTitle, request->clonedAction->label);
4578    label = XmStringCreateLocalized(ContinueMessage);
4579    XtFree(fmt);
4580
4581    n = 0;
4582    XtSetArg(args[n], XmNmessageString, label); n++;
4583    XtSetArg(args[n], XmNtitle, title); n++;
4584    XtSetArg(args[n], XmNokLabelString, ok); n++;
4585    XtSetArg(args[n], XmNcancelLabelString, cancel); n++;
4586    dialog->topLevel = XmCreateWarningDialog(w, "continueDialog", args, n);
4587    XtFree(title);
4588    XmStringFree(ok);
4589    XmStringFree(cancel);
4590    XmStringFree(label);
4591
4592    XtUnmanageChild(XmMessageBoxGetChild(dialog->topLevel,
4593                                         XmDIALOG_HELP_BUTTON));
4594    XtAddCallback(dialog->topLevel, XmNokCallback, ContinueRequest,
4595                  (XtPointer)dialog);
4596    XtAddCallback(dialog->topLevel, XmNcancelCallback, CancelRequest,
4597                  (XtPointer)dialog);
4598    XtManageChild(dialog->topLevel);
4599 }
4600
4601
4602 /***************************************************************************/
4603 /***************************************************************************/
4604 /*                  Command Invoker Specific Functions                     */
4605 /***************************************************************************/
4606 /***************************************************************************/
4607
4608 /*
4609  * This is the entry point into the command-specific world.  All of the code
4610  * before this has been written to handle any of the different transport
4611  * types.  The code from this point on will know specifically how to
4612  * interact with the command invoker layer.  We will start by taking each
4613  * of the pieces of information making up the command invoker request, and
4614  * resolving any of the keywords, by replacing them with the appropriate
4615  * information.  If this fails (which it only should do if we try to map
4616  * a file to a host which cannot be accessed), then we will either continue,
4617  * using the next exec host, or we will terminate, if no more hosts are left.
4618  */
4619
4620 static void 
4621 ProcessCommandInvokerRequest(
4622         Widget w,
4623         ActionRequest *request,
4624         char * relPathHost,
4625         char * relPathDir )
4626
4627 {
4628    char * cwdHost;
4629    char * cwdDir;
4630    ActionPtr action = request->clonedAction;
4631    _DtActInvRecT *invp = NULL;
4632    _DtActChildRecT *childp = NULL;
4633
4634    if (ResolveCommandInvokerMessagePieces(w, request, relPathHost, relPathDir))
4635    {
4636       /* 
4637        * Issue the request; the success/failure notification comes 
4638        * asynchronously; that's when everything gets cleaned up, or
4639        * tried again, for the next exec host.
4640        */
4641       __ExtractCWD(request, &cwdHost, &cwdDir, True);
4642       InitiateCommandInvokerRequest( w, request, cwdHost, cwdDir);
4643       XtFree(cwdHost);
4644       XtFree(cwdDir);
4645    }
4646    else
4647    {
4648        if (  !(invp = _DtActFindInvRec(request->invocId) ) )
4649             myassert( 0 /* could not find invocation record */ );
4650
4651        if ( !(childp=_DtActFindChildRec(request->invocId,request->childId)))
4652             myassert( 0 /* could not find child record */ );
4653
4654       /*
4655        * The only way we could have reached here is if the execution host
4656        * was not accessible, and we tried to map one of the data files to
4657        * be relative to this host.  If there are other hosts to be tried,
4658        * then we will retry the request on the next host; otherwise, we
4659        * will post an error dialog, and bail out.
4660        */
4661
4662       request->hostIndex++;
4663
4664       if (request->hostIndex >= action->u.cmd.execHostCount)
4665       {
4666          /* No more hosts to try; report an error, and bail out */
4667
4668            if ( invp && childp )
4669            {
4670              SET_INV_ERROR(invp->state); 
4671              childp->childState = _DtActCHILD_FAILED;
4672            }
4673
4674          /* 
4675           * Cleanup should happen later when we return up the stack.
4676           */
4677
4678          if (action->u.cmd.execHostCount <= 1)
4679          {
4680             /* Display error dialog listing just the one failed exec host */
4681             HostAccessError(w, request->clonedAction->label, request->badHostList);
4682          }
4683          else
4684          {
4685             /* Display error dialog listing all failed exec hosts */
4686             MultiHostAccessError(w, request->clonedAction->label, request->badHostList);
4687          }
4688       }
4689       else
4690       {
4691            if ( invp && childp )
4692            {
4693                 /* 
4694                  * Delete child record for failed exec on this host
4695                  */
4696                 _DtActDeleteChildRec(invp,childp);
4697                 SET_INV_PENDING(invp->state);
4698                 if ( ! invp->numChildren )
4699                     RESET_INV_WORKING(invp->state);
4700            }
4701
4702          /* Retry the request, using the next exec host */
4703          PrepareAndExecuteAction(w, request);
4704          return;
4705       }
4706    }
4707 }
4708
4709
4710 /*
4711  * This function takes all of the pieces making up a command invoker request,
4712  * and resolves any references to keywords, using both the passed-in
4713  * arguments, and any information collected from the prompt dialog.
4714  */
4715
4716 static Boolean 
4717 ResolveCommandInvokerMessagePieces(
4718         Widget w,
4719         ActionRequest *request,
4720         char * relPathHost,
4721         char * relPathDir )
4722
4723 {
4724    ActionPtr action = request->clonedAction;
4725    cmdAttr * cmd = &(action->u.cmd);
4726    char * termOpts;
4727    Boolean * paramUsed = NULL;
4728    int promptDataIndex = 0;
4729         /*
4730          * NOTE: The current implementation of prompt strings requires that
4731          *       the segments be evaluated in the same order in which the
4732          *       action fields were parsed. (See MatchParamsToAction() )
4733          *       This order is currently "execHost", "execString" and
4734          *       "termOpts".  This situation arises because
4735          *       the existing prompt data structures do NOT identify the
4736          *       location of the prompt and hence where to put the
4737          *       user-supplied value; except by order of occurrence.
4738          */
4739
4740    /* Set up the next host to execute on */
4741    _DtCompileMessagePiece(w, request, relPathHost, relPathDir,
4742                            &(action->u.cmd.execHosts), True, 0, &paramUsed,
4743                            &promptDataIndex);
4744    SetExecHost(request);
4745
4746    if ((_DtCompileMessagePiece(w, request, relPathHost, relPathDir,
4747                            &(cmd->execString), False, 0, &paramUsed,
4748                            &promptDataIndex) == False) ||
4749        (_DtCompileMessagePiece(w, request, relPathHost, relPathDir,
4750                            &(cmd->termOpts), False, 0, &paramUsed,
4751                            &promptDataIndex) == False))
4752    {
4753       /* Free up any intermediate work we've done here */
4754       XtFree(cmd->execString.compiledMessage);
4755       XtFree(cmd->termOpts.compiledMessage);
4756       XtFree(cmd->execHosts.compiledMessage);
4757       cmd->execString.compiledMessage = NULL;
4758       cmd->termOpts.compiledMessage = NULL;
4759       cmd->execHosts.compiledMessage = NULL;
4760       XtFree(paramUsed);
4761       return(False);
4762    }
4763
4764    /* 
4765     * If term_opts were passed in to the _DtActionInvoke() function, then
4766     * append them to the term_opts derived from the action definition and
4767     * internal defaults.  This should give precedence to the last defined
4768     * options.
4769     */
4770    if ( request->termOpts )
4771    {
4772          termOpts = XtMalloc( strlen(cmd->termOpts.compiledMessage) + 
4773                         strlen(request->termOpts) + 2 );
4774         strcpy(termOpts,cmd->termOpts.compiledMessage);
4775         strcat(termOpts," ");
4776         strcat(termOpts,request->termOpts);
4777         XtFree(cmd->termOpts.compiledMessage);
4778         cmd->termOpts.compiledMessage = termOpts;
4779    }
4780
4781    XtFree(paramUsed);
4782    return(True);
4783 }
4784
4785
4786 /*
4787  * Process a command-invoker request.
4788  */
4789
4790 static void 
4791 InitiateCommandInvokerRequest(
4792         Widget w,
4793         ActionRequest *request,
4794         String host,
4795         String dir)
4796
4797 {
4798    char procIdBuf[_DtAct_MAX_BUF_SIZE];
4799    char tmpFileBuf[_DtAct_MAX_BUF_SIZE];
4800    char *procId;                /* for dtexec command line */
4801    char *tmpFiles = NULL;       /* for dtexec command line */
4802    _DtActInvRecT *invp;
4803    _DtActChildRecT *childp;
4804    CallbackData *data=(CallbackData *)XtMalloc((Cardinal)sizeof(CallbackData));
4805    ActionPtr action = request->clonedAction;
4806
4807    tmpFileBuf[0]='\0';  /* seed the buffer with a null string */
4808
4809    /*
4810     * Generate the procId option string for dtexec
4811     */
4812
4813    /* Get the default procId from toolTalk */
4814    switch ( tt_ptr_error(procId = tt_default_procid()) )
4815    {
4816        case TT_ERR_NOMP:
4817            ;    /* fall through */
4818        case TT_ERR_PROCID: /* Try to establish a connection */
4819            tt_free(procId) ;
4820            if ( !_DtInitializeToolTalk(NULL) )
4821                procId=NULL;
4822            else if ( tt_ptr_error(procId = tt_default_procid()) != TT_OK )
4823            {
4824                myassert( 0 ); /* we should never get here */
4825                procId = NULL;
4826            }
4827            break;
4828        case TT_OK:
4829            break;
4830        default:
4831            tt_free(procId);
4832            procId = NULL;
4833            break;
4834    }
4835    /*
4836     * The string generated for procId should never exceed the procId buf size. 
4837     */
4838    sprintf(procIdBuf,"%s_%d_%lu",
4839        _DtActNULL_GUARD(procId),
4840        (int) request->invocId,
4841        request->childId );
4842
4843    myassert( strlen(procIdBuf) < sizeof(procIdBuf) );
4844
4845    if (procId)
4846        tt_free(procId);
4847    procId = procIdBuf;  /* no need to malloc */
4848    
4849    /*
4850     * Generate string of tmp file args  for dtexec.
4851     */
4852     
4853     if ( (invp = _DtActFindInvRec(request->invocId)) != NULL )
4854     {
4855         if ( (childp =
4856              _DtActFindChildRec(request->invocId,request->childId)) != NULL )
4857         {
4858             int i;
4859             char *p;
4860             int len = 0;
4861
4862             for(i = 0; i < childp->numObjects; i++)
4863             {
4864
4865                 if(!(IS_BUFFER_OBJ(invp->info[childp->argMap[i].argIdx].mask)))
4866                     continue;   /* not a buffer object */
4867
4868                 if ( !(p = invp->info[childp->argMap[i].argIdx].name) )
4869                     continue;   /* no tmp file name */
4870  
4871                 /* Add up the string length of the file name */
4872                 if((len += strlen(" -tmp ") + strlen(p)) < sizeof(tmpFileBuf))
4873                 {
4874                     /* 
4875                      * Use the automatic tmpFileBuf if possible
4876                      */
4877                     strcat(tmpFileBuf," -tmp ");
4878                     strcat(tmpFileBuf,p);
4879                 } 
4880                 else
4881                 {
4882                    /*
4883                     * Malloc more space if necessary
4884                     */
4885                     XtFree(tmpFiles);
4886                     tmpFiles = XtMalloc(len + 1);
4887                     strcpy(tmpFiles,tmpFileBuf);
4888                     strcpy(tmpFiles," -tmp ");
4889                     strcpy(tmpFiles,p);
4890                 }
4891                 
4892             }
4893             if ( len > 0 && len <  sizeof(tmpFileBuf) )
4894                 tmpFiles = tmpFileBuf;
4895         }
4896         else
4897         {
4898             myassert( 0 /* could not find child rec */ );
4899             tmpFiles = NULL;
4900         }
4901     }
4902     else
4903         tmpFiles = NULL;
4904
4905    /* Fill out the callback structure */
4906    data->actionLabel = XtNewString(request->clonedAction->label);
4907    data->associatedWidget = w;
4908    data->offset = 0;
4909    data->actionPtr = action;
4910    data->requestPtr = _DtCloneRequest(request);
4911
4912
4913    if ( _DtActionCommandInvoke(action->mask & _DtAct_WINTYPE_BITS, host, dir, 
4914                             action->u.cmd.execString.compiledMessage,
4915                             action->u.cmd.termOpts.compiledMessage,
4916                             request->currentHost,
4917                             procId,
4918                             tmpFiles,
4919                             CmdInvSuccessfulRequest, (XtPointer)data,
4920                             CmdInvFailedRequest, (XtPointer)data) ) 
4921         if (invp)
4922                 SET_INV_CMD_QUEUED(invp->state);
4923
4924
4925    if ( tmpFiles != tmpFileBuf )
4926        XtFree(tmpFiles);
4927 }
4928
4929
4930 /*
4931  * Sets the 'currentHost' field within the request structure to
4932  * the name of the next exec host to try.  Before any of the exec hosts 
4933  * in the action definition are tried, we will try the host passed in as 
4934  * part of the request, if one was specified.
4935  */
4936
4937 static void
4938 SetExecHost (
4939              ActionRequest * request )
4940
4941 {
4942    ActionPtr action = request->clonedAction;
4943    int hostCount = 0;
4944    int hostListSize = 0;
4945    char ** hostList = NULL;
4946
4947    XtFree(request->currentHost);
4948
4949    /* If this is the first call, we may need to parse the host list */
4950    if (action->u.cmd.execHostArray == NULL)
4951    {
4952       /* Explicitly specified execHost overrides action definition execHost */
4953       if (request->execHost)
4954          ParseHostList(request->execHost, &hostList, &hostListSize, &hostCount);
4955       else if (action->u.cmd.execHosts.compiledMessage)
4956       {
4957          ParseHostList(action->u.cmd.execHosts.compiledMessage, &hostList, 
4958                        &hostListSize, &hostCount);
4959       }
4960
4961       RemoveDuplicateHostNames(hostList, &hostCount);
4962
4963       action->u.cmd.execHostArray = hostList;
4964       action->u.cmd.execHostCount = hostCount;
4965    }
4966
4967    if (action->u.cmd.execHostCount == 0)
4968    {
4969       /* 
4970        * Oh boy ... someone is trying to be nasty!  The only way we could
4971        * have gotten here was to have the action's 'EXEC_HOST' field set
4972        * to nothing but a prompt string, and then the user left the string
4973        * empty!  We'll default the local host, and hope it works.
4974        */
4975       /* 
4976        * fdt: we really should default to whatever has been configured
4977        *      as the default execution host, followed by the LocalHost,
4978        *      if they are not the same.
4979        */
4980       request->currentHost = _DtGetLocalHostName();
4981    }
4982    else
4983    {
4984       request->currentHost = 
4985            XtNewString(action->u.cmd.execHostArray[request->hostIndex]);
4986    }
4987 }
4988
4989
4990 /*
4991  * This function takes a string of comma-separated host names, and adds them
4992  * to the passed-in string array.
4993  */
4994
4995 static void
4996 ParseHostList (
4997    char * hostString,
4998    char *** hostListPtr,
4999    int * hostListSizePtr,
5000    int * hostCountPtr )
5001
5002 {
5003    char * workString;
5004    char * nextHost;
5005    _Xstrtokparams       strtok_buf;
5006
5007    workString = XtNewString(hostString);
5008    nextHost = _XStrtok(workString, ",", strtok_buf);
5009
5010    while(nextHost)
5011    {
5012       nextHost = _DtStripSpaces(nextHost);
5013
5014       if (strlen(nextHost) > 0)
5015       {
5016          if (*hostCountPtr >= *hostListSizePtr)
5017          {
5018             (*hostListSizePtr) += 5;
5019             (*hostListPtr) = (char **)XtRealloc((char *)(*hostListPtr),
5020                                          sizeof(char *) * (*hostListSizePtr));
5021          }
5022
5023          (*hostListPtr)[*hostCountPtr] = XtNewString(nextHost);
5024          (*hostCountPtr)++;
5025       }
5026
5027       nextHost = _XStrtok(NULL, ",", strtok_buf);
5028    }
5029
5030    XtFree(workString);
5031 }
5032
5033
5034 /*
5035  * This function goes through the compiled list of exec hosts, and removes
5036  * any duplicate entries.  It is not very useful to attempt to execute on
5037  * a given host, more than once.
5038  */
5039 static void
5040 RemoveDuplicateHostNames (
5041              char ** hostList,
5042              int   * hostCountPtr )
5043
5044 {
5045    int i,j,k;
5046
5047    for (i = 0; i < *hostCountPtr; i++)
5048    {
5049       for (j = i+1; j < *hostCountPtr; )
5050       {
5051          if (strcmp(hostList[i], hostList[j]) == 0)
5052          {
5053             /* Remove the second entry */
5054             XtFree(hostList[j]);
5055             for (k = j; k < (*hostCountPtr) - 1; k++)
5056                hostList[k] = hostList[k+1];
5057             (*hostCountPtr)--;
5058          }
5059          else
5060             j++;
5061       }
5062    }
5063 }
5064
5065
5066 /*
5067  * When one of the exec hosts fails, we add it to the list of failed
5068  * hostnames, so that if ultimately all of the hosts fail, we have a
5069  * list we can display within the error dialog.
5070  */
5071
5072 static void 
5073 AddFailedHostToList (
5074    ActionRequest * request,
5075    String badHost )
5076
5077 {
5078    int curLen;
5079
5080    if (request->badHostList)
5081       curLen = strlen(request->badHostList);
5082    else
5083       curLen = 0;
5084
5085    request->badHostList = XtRealloc(request->badHostList,
5086                                     curLen + 10 + strlen(badHost));
5087
5088    if (curLen > 0)
5089    {
5090       strcat(request->badHostList, ", ");
5091       strcat(request->badHostList, badHost);
5092    }
5093    else
5094       strcpy(request->badHostList, badHost);
5095 }
5096
5097
5098 /*
5099  *
5100  * This callback is invoked when the Command Invoker library has successfully 
5101  * exectued an action.  We need to free up everything associated with this
5102  * request.
5103  */
5104
5105 static void 
5106 CmdInvSuccessfulRequest(
5107         char *message,
5108         void *data2)
5109
5110 {
5111    _DtActInvRecT *invp = NULL;
5112    _DtActChildRecT *childrecp = NULL;
5113
5114    CallbackData *data = (CallbackData *) data2;
5115
5116    /*
5117     * Mark this invocation step as done
5118     * The child process itself may not be done.
5119     */
5120    if ((invp = _DtActFindInvRec(data->requestPtr->invocId)) != NULL )
5121    {
5122        extern void *_DtCmdCheckQForId(DtActionInvocationID id);
5123        
5124        SET_INV_DONE(invp->state);
5125        RESET_INV_CMD_QUEUED(invp->state);
5126        /*
5127         * Are there still more commands queued for this request ?
5128         */
5129        if ( _DtCmdCheckQForId(invp->id) )
5130        {
5131                 /*
5132                  * If so; set the command queued bit
5133                  */
5134                 SET_INV_CMD_QUEUED(invp->state);
5135        }
5136         /*
5137          * RWV:
5138          * This may not be the right place to set the child state for
5139          * command actions.  The child process may already have communicated
5140          * its status via TT messaging OR it may already have exited.
5141          * For now we set the state here -- till we find a better place.
5142          */
5143        if ((childrecp = _DtActFindChildRec(invp->id,data->requestPtr->childId)))
5144            childrecp->childState = _DtActCHILD_ALIVE_UNKNOWN;
5145        else
5146            myassert(0 /* could not find child record */ );
5147        
5148        _DtActExecutionLeafNodeCleanup(invp->id,NULL,0,True);
5149    }
5150    else
5151         myassert( 0 /* Couldn't find an invocation record */);
5152
5153    _DtFreeRequest(data->requestPtr);
5154    XtFree(data->actionLabel);
5155    XtFree((char *)data);
5156 }
5157
5158
5159
5160 /*
5161  * This callback is invoked when the Command Invoker library has failed 
5162  * to exectue an action.  It there are additional execHosts to be processed,
5163  * then try the command again, using the next host.  If there are no more
5164  * hosts, then post an error dialog, and give up (freeing all data 
5165  * associated with this request).
5166  */
5167
5168 static void 
5169 CmdInvFailedRequest(
5170         char *error_message,
5171         void *data2)
5172
5173 {
5174    CallbackData * data = (CallbackData *) data2;
5175    String msg = error_message;
5176    ActionPtr action;
5177    ActionRequest * request;
5178    _DtActChildRecT *childp = NULL;
5179    _DtActInvRecT *invp = NULL;
5180
5181
5182    /*
5183     * If this was not the last host in the execHost list, then retry
5184     * the request, using the next host; if this was the last host,
5185     * then we failed, and it is time to post an error dialog.  If the
5186     * host list had only one item, then to be backwards compatible,
5187     * we will display the message returned by the command invoker.
5188     * Otherwise, we will simple display the list of execHosts, along
5189     * with a message saying they could not be accessed.
5190     */
5191    request = data->requestPtr;
5192
5193    if (request->clonedAction)
5194       action  = request->clonedAction;
5195    else
5196       action  = data->actionPtr;
5197    request->hostIndex++;
5198    AddFailedHostToList(request, request->currentHost);
5199
5200    if (  !(invp = _DtActFindInvRec(request->invocId) ) )
5201         myassert( 0 /* could not find invocation record */ );
5202
5203    if ( !(childp=_DtActFindChildRec(request->invocId,request->childId)))
5204         myassert( 0 /* could not find child record */ );
5205
5206    /*
5207     * Make sure the CMD_QUEUED bit is set correctly
5208     */
5209    if ( invp  )
5210    {
5211        extern void *_DtCmdCheckQForId(DtActionInvocationID id);
5212        
5213        SET_INV_DONE(invp->state);
5214        RESET_INV_CMD_QUEUED(invp->state);
5215        /*
5216         * Are there still more commands queued for this request ?
5217         */
5218        if ( _DtCmdCheckQForId(invp->id) )
5219        {
5220                 /*
5221                  * If so; set the command queued bit
5222                  */
5223                 SET_INV_CMD_QUEUED(invp->state);
5224        }
5225    }
5226
5227    if (request->hostIndex < action->u.cmd.execHostCount)
5228    {
5229        /* 
5230         * Free up the child structure for the failed command request
5231         * We may be trying again on another host but a new child rec
5232         * will be allocated in PrepareAndExecute().
5233         */
5234
5235        if ( invp && childp )
5236        {
5237             /* 
5238              * Delete child record for failed exec on this host
5239              */
5240             _DtActDeleteChildRec(invp,childp);
5241             SET_INV_PENDING(invp->state);
5242             if ( ! invp->numChildren )
5243                 RESET_INV_WORKING(invp->state);
5244        }
5245
5246       /* Retry, using the next host */
5247       PrepareAndExecuteAction(data->associatedWidget, request);
5248    }
5249    else
5250    {
5251
5252        if ( invp && childp )
5253        {
5254          /* 
5255           * RWV ---  
5256           * How can we tell if the Invocation COMPLETE bit
5257           * needs to be set here?
5258           * How about if no invocation is pending or working?
5259           */
5260           SET_INV_ERROR(invp->state); 
5261           childp->childState = _DtActCHILD_FAILED;
5262        }
5263
5264
5265       /* No more hosts (they all failed); put up error dialog */
5266       if (action->u.cmd.execHostCount <= 1)
5267       {
5268          /* Be backwards compatible */
5269          CommandInvokerError(data->associatedWidget,
5270                              action->label,
5271                              msg + data->offset);
5272       }
5273       else
5274       {
5275          MultiHostAccessError(data->associatedWidget, request->clonedAction->label, 
5276                               request->badHostList);
5277       }
5278
5279
5280       /* Cleanup */
5281       _DtActExecutionLeafNodeCleanup(invp->id,NULL,0,True);
5282       _DtFreeRequest(request);
5283       XtFree(data->actionLabel);
5284    }
5285    XtFree((char *)data);
5286 }
5287
5288
5289
5290 /*
5291  * This function maps a filename relative to 'host' to be relative to
5292  * 'newHost'.  If newHost is NULL, then the local host is assumed.
5293  *
5294  * The returned string must be freed by the caller.
5295  */
5296
5297 char * 
5298 _DtActMapFileName(
5299         const char * curHost,
5300         const char * dir,
5301         const char * file,
5302         const char * newHost )
5303 {
5304    char buf[MAXPATHLEN];
5305    char *chp = NULL;
5306    int   clen = 0;
5307    char *netpath = NULL;
5308    char *path = NULL;
5309
5310    /*
5311     * Create the full path name relative to curHost
5312     */
5313    
5314    buf[0]='\0'; /* empty string to start with */
5315
5316    if ( dir )
5317            strcpy(buf,dir);
5318    if ( file )
5319    {
5320         /* check if there is already a '/' separator */
5321        if ( *file != '/' ) 
5322        {
5323            DtLastChar(buf,&chp,&clen);
5324            if ( !( (clen == 1) && (*chp == '/')) )
5325                strcat(buf,"/");
5326        }
5327        strcat(buf,file);
5328    }
5329
5330    /* We should have constructed a file name string now */
5331    myassert(buf[0] != '\0');
5332
5333    if (newHost)
5334    {
5335       if ( _DtIsSameHost(curHost,newHost) )
5336       {
5337          /*
5338           * The current host is the same as the new host 
5339           * so no file name translation is necessary
5340           */
5341          return XtNewString(buf);
5342       }
5343       /*
5344        * The current host is not the same as the new host -- find the
5345        * cannonical netfile name then reinterpret it on the new host.
5346        */
5347        switch ( tt_ptr_error(netpath = tt_host_file_netfile(curHost,buf)) )
5348        {
5349          case TT_OK:
5350             break;
5351          case TT_ERR_PATH:
5352             netpath = NULL;
5353             break;
5354          case TT_ERR_DBAVAIL:
5355             netpath = NULL;
5356             break;
5357          case TT_ERR_DBEXIST:
5358             netpath = NULL;
5359             break;
5360          case TT_ERR_INTERNAL:
5361             netpath = NULL;
5362             break;
5363          default:
5364             netpath = NULL;
5365             break;
5366        }
5367        if ( netpath )
5368        {
5369           switch ( tt_ptr_error(path = tt_host_netfile_file(newHost,netpath)) )
5370           {
5371             case TT_OK:
5372                break;
5373             case TT_ERR_PATH:
5374                path = NULL;
5375                break;
5376             case TT_ERR_DBAVAIL:
5377                path = NULL;
5378                break;
5379             case TT_ERR_DBEXIST:
5380                path = NULL;
5381                break;
5382             case TT_ERR_INTERNAL:
5383                path = NULL;
5384                break;
5385             default:
5386                path = NULL;
5387                break;
5388           }
5389         }
5390    } else
5391    {
5392       /* 
5393        * Convert the file path which is relative to curHost to be
5394        * relative to the local host.
5395        */
5396       if ( _DtIsSameHost(curHost,NULL) )
5397       {
5398          /*
5399           * The current host is the same as the  local host 
5400           * so no file name translation is necessary
5401           */
5402          return XtNewString(buf);
5403       }
5404       /*
5405        * The current host is not the same as the local host -- find the
5406        * cannonical netfile name then reinterpret it on the local host.
5407        */
5408        switch ( tt_ptr_error(netpath = tt_host_file_netfile(curHost,buf)) )
5409        {
5410          case TT_OK:
5411             break;
5412          case TT_ERR_PATH:
5413             netpath = NULL;
5414             break;
5415          case TT_ERR_DBAVAIL:
5416             netpath = NULL;
5417             break;
5418          case TT_ERR_DBEXIST:
5419             netpath = NULL;
5420             break;
5421          case TT_ERR_INTERNAL:
5422             netpath = NULL;
5423             break;
5424          default:
5425             netpath = NULL;
5426             break;
5427        }
5428        if ( netpath )
5429        {
5430           switch ( tt_ptr_error(path = tt_netfile_file(netpath)) ) 
5431           {
5432             case TT_OK:
5433                break;
5434             case TT_ERR_PATH:
5435                path = NULL;
5436                break;
5437             case TT_ERR_DBAVAIL:
5438                path = NULL;
5439                break;
5440             case TT_ERR_DBEXIST:
5441                path = NULL;
5442                break;
5443             case TT_ERR_INTERNAL:
5444                path = NULL;
5445                break;
5446             default:
5447                path = NULL;
5448                break;
5449           }
5450        }
5451    }
5452         
5453    /*
5454     * Free up the memory allocated by tooltalk filenaming code here so
5455     * downstream code need not worry about it.
5456     */
5457    if ( netpath )
5458         tt_free(netpath);
5459    if ( path )
5460    {
5461       char *s = path;
5462       path = XtNewString(s);
5463       tt_free(s);
5464    }
5465         
5466    return path;
5467 }