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