RemoteProcess.C 71.3 KB
Newer Older
hrchilds's avatar
hrchilds committed
1
2
/*****************************************************************************
*
brugger's avatar
   
brugger committed
3
* Copyright (c) 2000 - 2008, Lawrence Livermore National Security, LLC
hrchilds's avatar
hrchilds committed
4
* Produced at the Lawrence Livermore National Laboratory
brugger's avatar
   
brugger committed
5
* LLNL-CODE-400142
hrchilds's avatar
hrchilds committed
6
7
* All rights reserved.
*
brugger's avatar
   
brugger committed
8
* This file is  part of VisIt. For  details, see https://visit.llnl.gov/.  The
hrchilds's avatar
hrchilds committed
9
10
11
12
13
14
15
16
17
18
* full copyright notice is contained in the file COPYRIGHT located at the root
* of the VisIt distribution or at http://www.llnl.gov/visit/copyright.html.
*
* Redistribution  and  use  in  source  and  binary  forms,  with  or  without
* modification, are permitted provided that the following conditions are met:
*
*  - Redistributions of  source code must  retain the above  copyright notice,
*    this list of conditions and the disclaimer below.
*  - Redistributions in binary form must reproduce the above copyright notice,
*    this  list of  conditions  and  the  disclaimer (as noted below)  in  the
brugger's avatar
   
brugger committed
19
20
21
*    documentation and/or other materials provided with the distribution.
*  - Neither the name of  the LLNS/LLNL nor the names of  its contributors may
*    be used to endorse or promote products derived from this software without
hrchilds's avatar
hrchilds committed
22
23
24
25
26
*    specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT  HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR  IMPLIED WARRANTIES, INCLUDING,  BUT NOT  LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND  FITNESS FOR A PARTICULAR  PURPOSE
brugger's avatar
   
brugger committed
27
28
29
* ARE  DISCLAIMED. IN  NO EVENT  SHALL LAWRENCE  LIVERMORE NATIONAL  SECURITY,
* LLC, THE  U.S.  DEPARTMENT OF  ENERGY  OR  CONTRIBUTORS BE  LIABLE  FOR  ANY
* DIRECT,  INDIRECT,   INCIDENTAL,   SPECIAL,   EXEMPLARY,  OR   CONSEQUENTIAL
hrchilds's avatar
hrchilds committed
30
31
32
33
34
35
36
37
38
* DAMAGES (INCLUDING, BUT NOT  LIMITED TO, PROCUREMENT OF  SUBSTITUTE GOODS OR
* SERVICES; LOSS OF  USE, DATA, OR PROFITS; OR  BUSINESS INTERRUPTION) HOWEVER
* CAUSED  AND  ON  ANY  THEORY  OF  LIABILITY,  WHETHER  IN  CONTRACT,  STRICT
* LIABILITY, OR TORT  (INCLUDING NEGLIGENCE OR OTHERWISE)  ARISING IN ANY  WAY
* OUT OF THE  USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
* DAMAGE.
*
*****************************************************************************/

fogal1's avatar
fogal1 committed
39
40
41
42
43
44
#include <cerrno>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <map>
#include <set>
hrchilds's avatar
hrchilds committed
45
46
47

#if defined(_WIN32)
#include <process.h>
hrchilds's avatar
hrchilds committed
48
#include <win32commhelpers.h>
hrchilds's avatar
hrchilds committed
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
#else
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <pwd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <unistd.h>
#include <PTY.h>
#include <signal.h>
#endif

#include <visit-config.h>

#include <RemoteProcess.h>
#include <BadHostException.h>
#include <SocketConnection.h>
#include <CommunicationHeader.h>
#include <IncompatibleVersionException.h>
#include <IncompatibleSecurityTokenException.h>
#include <CancelledConnectException.h>
#include <CouldNotConnectException.h>
71
#include <InstallationFunctions.h>
hrchilds's avatar
hrchilds committed
72
73

#include <DebugStream.h>
hrchilds's avatar
hrchilds committed
74
#include <snprintf.h>
hrchilds's avatar
hrchilds committed
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115

#ifdef HAVE_THREADS
#if !defined(_WIN32)
#include <pthread.h>
// Variables required for pthreads.
static pthread_attr_t thread_atts;
static bool init_thread_atts = false;
#define MUTEX_CREATE(mutex)  pthread_mutex_init(&(mutex), NULL)
#define MUTEX_DESTROY(mutex) pthread_mutex_destroy(&(mutex))
#define MUTEX_LOCK(mutex)    pthread_mutex_lock(&(mutex))
#define MUTEX_UNLOCK(mutex)  pthread_mutex_unlock(&(mutex))
#define MUTEX_TYPE           pthread_mutex_t
#else
// Windows threads
#define MUTEX_CREATE(mutex)  InitializeCriticalSection(&(mutex))
#define MUTEX_DESTROY(mutex) DeleteCriticalSection(&(mutex))
#define MUTEX_LOCK(mutex)    EnterCriticalSection(&(mutex))
#define MUTEX_UNLOCK(mutex)  LeaveCriticalSection(&(mutex))
#define MUTEX_TYPE           CRITICAL_SECTION
#endif

// Data structure for the thread callback.
struct ThreadCallbackDataStruct
{
    int                 pid;
    int                 desc;
    DESCRIPTOR          listenSocketNum;
    struct sockaddr_in *sin;
    int                 Errno;
    bool                alive;
    MUTEX_TYPE          mutex;
};
#endif

// The port that we try to get for listening for remote connections.
#define INITIAL_PORT_NUMBER 5600

//
// Static data
//
void (*RemoteProcess::getAuthentication)(const char *, const char *, int) = NULL;
hrchilds's avatar
hrchilds committed
116
bool RemoteProcess::disablePTY = false;
hrchilds's avatar
hrchilds committed
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272

using std::map;
static map<int, bool> childDied;

#if !defined(_WIN32)
// ****************************************************************************
//  Function:  catch_dead_child
//
//  Purpose:
//    Signal handler for a SIGCHLD even while waiting for remote connections.
//    Catch a child that died and mark it's success or failure in the
//    childDied array.
//
//  Programmer:  Jeremy Meredith
//  Creation:    July 10, 2002
//
// ****************************************************************************
static void
catch_dead_child(int sig)
{
    // assert (sig == SIGCHLD);
    int status;
    int pid;
    pid = wait(&status);

    childDied[pid] = (status == 0 ? false : true);

    signal(SIGCHLD, catch_dead_child);
}
#endif


// ****************************************************************************
// Method: RemoteProcess::RemoteProcess
//
// Purpose: 
//   Constructor for the RemoteProcess class.
//
// Arguments:
//    rProgram : The name of the remote program to execute.
//
// Programmer: Brad Whitlock
// Creation:   Fri Jul 14 09:11:08 PDT 2000
//
// Modifications:
//    Jeremy Meredith, Tue Aug  8 13:49:42 PDT 2000
//    Changed it to allow more than one read/write socket.
//
//    Brad Whitlock, Fri Oct 20 12:46:49 PDT 2000
//    Added code to initialize the SocketConnection pointers.
//
//    Brad Whitlock, Mon Nov 20 17:03:34 PST 2000
//    Added initializer for localHost.
//
//    Brad Whitlock, Thu Feb 21 10:04:53 PDT 2002
//    Added initializer for localUserName.
//
//    Brad Whitlock, Thu Sep 26 16:50:04 PST 2002
//    Initialized progressCallback and progressCallbackData.
//
//    Brad Whitlock, Mon Dec 16 14:28:53 PST 2002
//    I added securityKey.
//
// ****************************************************************************

RemoteProcess::RemoteProcess(const std::string &rProgram) : localHost("notset"),
    localUserName(), securityKey(), remoteHost("localhost"),
    remoteProgram(rProgram), remoteUserName("notset"), argList()
{
    remoteProgramPid = -1;
    listenSocketNum = -1;

    // Zero out the SocketConnection pointers.
    readConnections = 0;
    writeConnections = 0;
    nReadConnections = 0;
    nWriteConnections = 0;

    // Set the callback information.
    progressCallback = 0;
    progressCallbackData = 0;
}

// ****************************************************************************
// Method: RemoteProcess::~RemoteProcess
//
// Purpose: 
//   Destructor for the RemoteProcess class. It closes any socket
//   file descriptors that were opened.
//
// Programmer: Brad Whitlock
// Creation:   Fri Jul 14 09:11:51 PDT 2000
//
// Modifications:
//    Jeremy Meredith, Tue Aug  8 13:49:42 PDT 2000
//    Changed it to allow more than one read/write socket.
//
//    Brad Whitlock, Thu Oct 5 18:05:09 PST 2000
//    Added code to delete the SocketConnections.
//
//    Brad Whitlock, Tue Mar 19 15:55:08 PST 2002
//    Abstracted the deletion of the connections.
//
//    Brad Whitlock, Tue Oct 1 15:04:03 PST 2002
//    I closed the listen socket.
//
//    Brad Whitlock, Fri Jan 3 15:29:54 PST 2003
//    I moved the code to close the listen socket into another method.
//
// ****************************************************************************

RemoteProcess::~RemoteProcess()
{
    // Delete the read SocketConnections
    if(nReadConnections > 0)
    {
        for(int i = 0; i < nReadConnections; ++i)
        {
            // Delete the connection
            delete readConnections[i];
        }
        delete [] readConnections;
        readConnections = 0;
        nReadConnections = 0;
    }

    if(readConnections != 0)
    {
        delete [] readConnections;
        readConnections = 0;
        nReadConnections = 0;
    }

    // Delete the write SocketConnections
    if(nWriteConnections > 0)
    {
        for(int i = 0; i < nWriteConnections; ++i)
        {
            // Delete the connection
            delete writeConnections[i];
        }
    }

    if(writeConnections != 0)
    {
        delete [] writeConnections;
        writeConnections = 0;
        nWriteConnections = 0;
    }

    //
    // Close the listening socket so we don't waste file descriptors.
    //
    CloseListenSocket();
}

hrchilds's avatar
hrchilds committed
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
// ****************************************************************************
//  Method:  RemoteProcess::DisablePTY
//
//  Purpose:
//    Disables usage of PTYs.
//
//  Programmer:  Jeremy Meredith
//  Creation:    July  3, 2003
//
// ****************************************************************************

void
RemoteProcess::DisablePTY()
{
    disablePTY = true;
}

hrchilds's avatar
hrchilds committed
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
// ****************************************************************************
// Method: RemoteProcess::AddArgument
//
// Purpose: 
//   Adds an argument to the RemoteProcess's argument list.
//
// Arguments:
//   arg : The argument to add.
//
// Returns:    
//
// Note:       
//   This method must be called prior to RemoteProcess::Open to have
//   any useful effect.
//
// Programmer: Brad Whitlock
// Creation:   Fri Jul 14 09:12:35 PDT 2000
//
// Modifications:
//   
// ****************************************************************************

void
RemoteProcess::AddArgument(const std::string &arg)
{
    argList.push_back(arg);
}

// ****************************************************************************
// Method: RemoteProcess::SetRemoteUserName
//
// Purpose: 
//   Sets the username to use when launching a process on a remote
//   machine.
//
// Arguments:
//   rUserName : A string containing the user's login name on the
//               remote machine.
//
// Programmer: Brad Whitlock
// Creation:   Fri Oct 20 12:39:59 PDT 2000
//
// Modifications:
//   
// ****************************************************************************

void
RemoteProcess::SetRemoteUserName(const std::string &rUserName)
{
    remoteUserName = rUserName;
}

// ****************************************************************************
// Method: RemoteProcess::GetReadConnection
//
// Purpose: 
//   Gets a pointer to the i'th read Connection.
//
// Arguments:
//   i : The index of the Connection we want.
//
// Returns:    A pointer to the i'th read Connection, or 0.
//
// Note:       
//
// Programmer: Brad Whitlock
// Creation:   Thu Oct 5 18:36:52 PST 2000
//
// Modifications:
//   
// ****************************************************************************

Connection *
RemoteProcess::GetReadConnection(int i) const
{
    return (i < nReadConnections) ? readConnections[i] : 0;
}

// ****************************************************************************
// Method: RemoteProcess::GetWriteConnection
//
// Purpose: 
//   Gets a pointer to the i'th write Connection.
//
// Arguments:
//   i : The index of the Connection we want.
//
// Returns:    A pointer to the i'th write Connection, or 0.
//
// Note:       
//
// Programmer: Brad Whitlock
// Creation:   Thu Oct 5 18:36:52 PST 2000
//
// Modifications:
//   
// ****************************************************************************

Connection *
RemoteProcess::GetWriteConnection(int i) const
{
    return (i < nWriteConnections) ? writeConnections[i] : 0;
}

// ****************************************************************************
// Method: RemoteProcess::GetLocalHostName
//
// Purpose: 
//   Returns the name of the localhost machine.
//
// Returns:    The name of the localhost machine.
//
// Notes:      This method only returns the correct name if the Open method
//             has been called.
//
// Programmer: Brad Whitlock
// Creation:   Mon Sep 24 11:26:57 PDT 2001
//
// Modifications:
//   
// ****************************************************************************

const std::string &
RemoteProcess::GetLocalHostName() const
{
    return localHost;
}

// ****************************************************************************
// Method: RemoteProcess::GetLocalUserName
//
// Purpose: 
//   Returns the name of the local user.
//
// Returns:    The local user name
//
// Notes:      This method only returns the correct name if the Open method
//             has been called.
//
// Programmer: Brad Whitlock
// Creation:   Thu Feb 21 10:04:05 PDT 2002
//
// Modifications:
//   
// ****************************************************************************

const std::string &
RemoteProcess::GetLocalUserName() const
{
    return localUserName;
}

// ****************************************************************************
// Method: RemoteProcess::HostIsLocal
//
// Purpose: 
//   Returns whether or not a hostname is local.
//
// Arguments:
//   rHost : The name of the host to check.
//
// Programmer: Brad Whitlock
// Creation:   Mon May 5 13:03:32 PST 2003
//
// Modifications:
//   
// ****************************************************************************

bool
RemoteProcess::HostIsLocal(const std::string &rHost) const
{
    return (rHost == localHost || rHost == "localhost");
}

// ****************************************************************************
// Method: RemoteProcess::GetPid
//
// Purpose: 
//   Returns the process id of the remote process.
//
// Programmer: Brad Whitlock
// Creation:   Fri Jul 21 13:29:36 PST 2000
//
// Modifications:
//   
// ****************************************************************************

int
RemoteProcess::GetProcessId() const
{
    return remoteProgramPid;
}

// ****************************************************************************
// Method: RemoteProcess::GetSocketAndPort
//
// Purpose: 
//   Creates a socket and gets a port to use.
//
// Returns:    
//    true if it worked, false if it did not.
//
// Note:       
//
// Programmer: Brad Whitlock
// Creation:   Fri Jul 14 09:16:22 PDT 2000
//
// Modifications:
//    Jeremy Meredith, Tue Aug  8 13:49:42 PDT 2000
//    Changed it to allow more than one read/write socket.
//    It now also only listens on a single port.
//
//    Brad Whitlock, Tue Mar 19 15:56:46 PST 2002
//    Made it work on MS Windows. We don't want to re-use addresses on Windows
//    because it causes the ports to not get incremented.
//
//    Brad Whitlock, Mon Mar 17 08:51:59 PDT 2003
//    I made it use a different starting port.
//
hrchilds's avatar
hrchilds committed
509
510
511
//    Brad Whitlock, Tue Jan 17 13:37:17 PST 2006
//    Added debug logging.
//
hrchilds's avatar
hrchilds committed
512
513
514
//    Brad Whitlock, Fri May 12 11:56:59 PDT 2006
//    Added more Windows error logging.
//
hrchilds's avatar
hrchilds committed
515
516
517
518
519
// ****************************************************************************

bool
RemoteProcess::GetSocketAndPort()
{
hrchilds's avatar
hrchilds committed
520
    const char *mName = "RemoteProcess::GetSocketAndPort: ";
hrchilds's avatar
hrchilds committed
521
522
523
524
525
526
527
528
    int  on = 1;
    bool portFound = false;

    // Open a socket.
    listenSocketNum = socket(AF_INET, SOCK_STREAM, 0);
    if (listenSocketNum < 0)
    {
        // Cannot open a socket.
hrchilds's avatar
hrchilds committed
529
        debug5 << mName << "Can't open a socket." << endl;
hrchilds's avatar
hrchilds committed
530
531
532
#if defined(_WIN32)
        LogWindowsSocketError(mName, "socket");
#endif
hrchilds's avatar
hrchilds committed
533
534
        return false;
    }
hrchilds's avatar
hrchilds committed
535
    debug5 << mName << "Opened listen socket: " << listenSocketNum << endl;
hrchilds's avatar
hrchilds committed
536
537
538
539
540
541
542

    //
    // Look for a port that can be used.
    //
    sin.sin_family = AF_INET;
    sin.sin_addr.s_addr = htonl(INADDR_ANY);
    listenPortNum = INITIAL_PORT_NUMBER;
hrchilds's avatar
hrchilds committed
543
544
    debug5 << mName << "Looking for available port starting with: "
           << listenPortNum << endl;
hrchilds's avatar
hrchilds committed
545
546
547
548
549
550
551
552
553
    while (!portFound && listenPortNum < 32767)
    {
        sin.sin_port = htons(listenPortNum);
#if !defined(_WIN32)
        setsockopt(listenSocketNum, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
#endif
        if (bind(listenSocketNum, (struct sockaddr *)&sin, sizeof(sin)) < 0)
        {
            listenPortNum++;
hrchilds's avatar
hrchilds committed
554
555
556
#if defined(_WIN32)
            LogWindowsSocketError(mName, "bind");
#endif
hrchilds's avatar
hrchilds committed
557
558
559
560
561
562
        }
        else
        {
            portFound = true;
        }
    }
hrchilds's avatar
hrchilds committed
563

hrchilds's avatar
hrchilds committed
564
565
566
    if (!portFound)
    {
        // Cannot find unused port.
hrchilds's avatar
hrchilds committed
567
        debug5 << mName << "Can't find an unused port." << endl;
hrchilds's avatar
hrchilds committed
568
569
570
        return false;
    }

hrchilds's avatar
hrchilds committed
571
572
    debug5 << mName << "Bind socket to port: " << listenPortNum << endl;

hrchilds's avatar
hrchilds committed
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
    return true;
}

// ****************************************************************************
// Method: RemoteProcess::CloseListenSocket
//
// Purpose: 
//   Closes the listen socket so the port that we've reserved gets released.
//
// Programmer: Brad Whitlock
// Creation:   Fri Jan 3 15:28:48 PST 2003
//
// Modifications:
//   
// ****************************************************************************

void
RemoteProcess::CloseListenSocket()
{
    //
    // Close the listening socket so the port gets released.
    //
    if(listenSocketNum != -1)
    {
hrchilds's avatar
hrchilds committed
597
        debug5 << "RemoteProcess::CloseListenSocket: closing listen socket" << endl;
hrchilds's avatar
hrchilds committed
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
#if defined(_WIN32)
        closesocket(listenSocketNum);
#else
        close(listenSocketNum);
#endif
        listenSocketNum = -1;
    }
}

// ****************************************************************************
// Method: RemoteProcess::CallProgressCallback
//
// Purpose: 
//   Calls the launch progress callback function.
//
// Arguments:
//   stage : The reason we're calling the function.
//
// Returns:    The callback's return value.
//
// Note:       
//
// Programmer: Brad Whitlock
// Creation:   Mon Sep 30 07:32:38 PDT 2002
//
// Modifications:
//   
// ****************************************************************************

bool
RemoteProcess::CallProgressCallback(int stage)
{
    bool retval = true;
#ifdef HAVE_THREADS
    // Call the progress callback.
    if(progressCallback != 0)
        retval = (*progressCallback)(progressCallbackData, stage);
#endif
    return retval;
}

// ****************************************************************************
// Method: RemoteProcess::AcceptSocket
//
// Purpose: 
//   Accepts a socket connection from the remote process. Must be
//   called after GetSocketAndPort and the remote process has been
//   launched.
//
// Returns:
//   The socket file descriptor or -1.
//
// Programmer: Brad Whitlock
// Creation:   Fri Jul 14 09:17:36 PDT 2000
//
// Modifications:
//    Jeremy Meredith, Tue Aug  8 13:49:42 PDT 2000
//    Changed it to allow more than one read/write socket.
//
//    Brad Whitlock, Thu Oct 5 18:02:19 PST 2000
//    I changed the code to use the class's global sockaddr_in struct
//    and to return the socket descriptor.
//
//    Brad Whitlock, Wed Nov 8 18:03:51 PST 2000
//    I added conditional compilation for gnu compilers since their
//    prototype for the accept call is a little different.
//
//    Jeremy Meredith, Wed Jun  6 21:43:16 PDT 2001
//    Changed the check for len to use socklen_t.  Added initialization of len.
//
//    Jeremy Meredith, Wed Jul 10 12:55:58 PDT 2002
//    Added a check to see if the child process died with an error status.
//    This signifies that we could not possibly accept successfully.
//
//    Brad Whitlock, Fri Sep 27 11:29:07 PDT 2002
//    I rewrote the routine so it makes use of subroutines and exception
//    handling so we can support cancelled connections.
//
hrchilds's avatar
hrchilds committed
676
677
678
//    Brad Whitlock, Tue Jan 17 14:21:27 PST 2006
//    Added debug logging.
//
hrchilds's avatar
hrchilds committed
679
680
681
682
683
// ****************************************************************************

int
RemoteProcess::AcceptSocket()
{
hrchilds's avatar
hrchilds committed
684
    const char *mName = "RemoteProcess::AcceptSocket: ";
hrchilds's avatar
hrchilds committed
685
686
687
688
689
    int desc = -1;
    int opt = 1;

#ifdef HAVE_THREADS
    if(progressCallback == 0)
hrchilds's avatar
hrchilds committed
690
691
    {
        debug5 << mName << "0: Calling SingleThreadedAcceptSocket." << endl;
hrchilds's avatar
hrchilds committed
692
        desc = SingleThreadedAcceptSocket();
hrchilds's avatar
hrchilds committed
693
    }
hrchilds's avatar
hrchilds committed
694
    else
hrchilds's avatar
hrchilds committed
695
696
    {
        debug5 << mName << "Calling MultiThreadedAcceptSocket." << endl;
hrchilds's avatar
hrchilds committed
697
        desc = MultiThreadedAcceptSocket();
hrchilds's avatar
hrchilds committed
698
    }
hrchilds's avatar
hrchilds committed
699
#else
hrchilds's avatar
hrchilds committed
700
    debug5 << mName << "1: Calling SingleThreadedAcceptSocket." << endl;
hrchilds's avatar
hrchilds committed
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
    desc = SingleThreadedAcceptSocket();
#endif

    // If the descriptor is -1, we could not connect to the remote process.
    if(desc == -1)
    {
        EXCEPTION0(CouldNotConnectException);
    }

    // If the descriptor is -2, we cancelled the connection to the
    // remote process.
    if(desc == -2)
    {
        EXCEPTION0(CancelledConnectException);
    }

hrchilds's avatar
hrchilds committed
717
718
    debug4 << mName << "Setting socket options." << endl;

hrchilds's avatar
hrchilds committed
719
720
    // Disable Nagle algorithm.
#if defined(_WIN32)
hrchilds's avatar
hrchilds committed
721
722
723
724
725
    if(setsockopt(desc, IPPROTO_TCP, TCP_NODELAY, (const char FAR*)&opt, sizeof(int))
       == SOCKET_ERROR)
    {
        LogWindowsSocketError(mName, "setsockopt");
    }
hrchilds's avatar
hrchilds committed
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
#else
    setsockopt(desc, IPPROTO_TCP, TCP_NODELAY, &opt, sizeof(int));
#endif

    return desc;
}

// ****************************************************************************
// Method: RemoteProcess::SingleThreadedAcceptSocket
//
// Purpose: 
//   Accepts a socket connection from the remote process. Must be
//   called after GetSocketAndPort and the remote process has been
//   launched.
//
// Returns:
//   The socket file descriptor or -1.
//
// Note:       This is a single-threaded version that blocks the application
//             until the completion of the accept call.
//
// Programmer: Brad Whitlock
// Creation:   Thu Sep 26 16:42:28 PST 2002
//
// Modifications:
hrchilds's avatar
hrchilds committed
751
752
753
//   Brad Whitlock, Tue Jan 17 13:41:47 PST 2006
//   Added debug info.
//
hrchilds's avatar
hrchilds committed
754
755
756
//   Brad Whitlock, Fri May 12 12:02:10 PDT 2006
//   Added more debug info for Windows.
//
hrchilds's avatar
hrchilds committed
757
758
759
760
761
// ****************************************************************************

int
RemoteProcess::SingleThreadedAcceptSocket()
{
hrchilds's avatar
hrchilds committed
762
    const char *mName = "RemoteProcess::SingleThreadedAcceptSocket: ";
hrchilds's avatar
hrchilds committed
763
764
765
766
767
768
769
770
771
772
773
    int desc = -1;

    // Wait for the socket to become available on the other side.
    do
    {
#ifdef HAVE_SOCKLEN_T
        socklen_t len;
#else
        int len;
#endif
        len = sizeof(struct sockaddr);
hrchilds's avatar
hrchilds committed
774
        debug5 << mName << "waiting for accept() to return" << endl;
hrchilds's avatar
hrchilds committed
775
        desc = accept(listenSocketNum, (struct sockaddr *)&sin, &len);
hrchilds's avatar
hrchilds committed
776
777
778
779
#if defined(_WIN32)
        if(desc == INVALID_SOCKET)
            LogWindowsSocketError(mName, "accept");
#endif
hrchilds's avatar
hrchilds committed
780
781
782
    }
    while (desc == -1 && errno == EINTR && childDied[GetProcessId()] == false);

hrchilds's avatar
hrchilds committed
783
784
    debug5 << mName << "accept returned descriptor: " << desc << endl;

hrchilds's avatar
hrchilds committed
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
    return desc;
}

#ifdef HAVE_THREADS
// ****************************************************************************
// Function: threaded_accept_callback
//
// Purpose:
//   This is a thread callback function that performs the accept network
//   function call. We do the accept on another thread so we can process other
//   events, etc while we do the blocking accept.
//
// Notes:      
//
// Programmer: Brad Whitlock
// Creation:   Thu Sep 26 16:54:30 PST 2002
//
// Modifications:
//   Brad Whitlock, Fri Dec 6 12:13:27 PDT 2002
//   I put a loop around the accept function call so that if we catch a
//   signal during the accept, we have an opportunity to try it again.
//
hrchilds's avatar
hrchilds committed
807
808
809
//   Brad Whitlock, Fri May 12 12:02:38 PDT 2006
//   Log Windows error messages.
//
hrchilds's avatar
hrchilds committed
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
// ****************************************************************************

#if defined(WIN32)
DWORD WINAPI threaded_accept_callback(LPVOID data)
#else
static void *threaded_accept_callback(void *data)
#endif
{
    ThreadCallbackDataStruct *cb = (ThreadCallbackDataStruct *)data;

#if !defined(_WIN32)
    // Set the thread cancellation policy so the main thread can terminate
    // this thread immediately if there is a problem.
    pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
    pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL);
#endif

    // Wait for the socket to connect back.
    int desc = -1;
    do
    {
#ifdef HAVE_SOCKLEN_T
    socklen_t len;
#else
    int len;
#endif
        len = sizeof(struct sockaddr);
        desc = accept(cb->listenSocketNum, (struct sockaddr *)cb->sin, &len);
hrchilds's avatar
hrchilds committed
838
839
840
841
#if defined(_WIN32)
        if(desc == INVALID_SOCKET)
            LogWindowsSocketError("threaded_accept_callback", "accept");
#endif
hrchilds's avatar
hrchilds committed
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
    } while(desc == -1 && errno == EINTR && childDied[cb->pid] == false);

    // Set the results into the callback struct that the other thread is polling.
    MUTEX_LOCK(cb->mutex);
    cb->alive = false;
    cb->Errno = (desc == -1) ? errno : 0;
    cb->desc = desc;
    MUTEX_UNLOCK(cb->mutex);

    return 0;
}
#endif

// ****************************************************************************
// Method: RemoteProcess::MultiThreadedAcceptSocket
//
// Purpose: 
//   Accepts a socket connection from the remote process. Must be
//   called after GetSocketAndPort and the remote process has been
//   launched.
//
// Returns:    The socket descriptor or -1.
//
// Note:       
//
// Programmer: Brad Whitlock
// Creation:   Thu Sep 26 16:56:14 PST 2002
//
// Modifications:
//   Brad Whitlock, Fri Dec 6 12:20:32 PDT 2002
//   I fixed some bugs that prevented the routine from working with
//   engines that were submitted to the batch system.
//
hrchilds's avatar
hrchilds committed
875
876
877
878
879
880
//   Hank Childs, Fri Sep 24 16:23:05 PDT 2004
//   If the stacksize is too large on some machines, the pthread library will
//   start behaving erratically -- specifically: it will report that it has
//   successfully started a thread, but never call its start function.  So
//   we will curb large sizes.  ['5422]
//
hrchilds's avatar
hrchilds committed
881
882
883
//   Brad Whitlock, Tue Jan 17 13:43:05 PST 2006
//   Added some debug logging.
//
884
885
886
//   Kathleen Bonnell, Wed Aug 22 18:00:57 PDT 2007 
//   Added 'Sleep' command to while-loop to speed up launch on Windows. 
//
hrchilds's avatar
hrchilds committed
887
888
889
890
891
// ****************************************************************************

int
RemoteProcess::MultiThreadedAcceptSocket()
{
hrchilds's avatar
hrchilds committed
892
    const char *mName = "RemoteProcess::MultiThreadedAcceptSocket: ";
hrchilds's avatar
hrchilds committed
893
894
895
#ifdef HAVE_THREADS
    int desc = -1;
 
hrchilds's avatar
hrchilds committed
896
    debug5 << mName << "Initializing thread callback data" << endl;
hrchilds's avatar
hrchilds committed
897
898
899
900
901
902
903
904
905
906
907
908
    // Set up some callback data for the thread callback.
    ThreadCallbackDataStruct cb;
    cb.pid = GetProcessId();
    cb.desc = -1;
    cb.listenSocketNum = listenSocketNum;
    cb.sin = &sin;
    cb.Errno = 0;
    cb.alive = true;
    // Create the mutex. It has to be done before we create the thread because
    // the thread callback needs the mutex.
    MUTEX_CREATE(cb.mutex);

hrchilds's avatar
hrchilds committed
909
    debug5 << mName << "Creating new accept thread" << endl;
hrchilds's avatar
hrchilds committed
910
911
912
913
914
915
916
917
918
919
920
    bool validThread = true;
#if defined(WIN32)
    // Create the thread Windows style.
    DWORD  Id;
    HANDLE tid;
    tid = CreateThread(0, 0, threaded_accept_callback,(LPVOID)&cb, 0, &Id);
    validThread = (tid != INVALID_HANDLE_VALUE);
#else
    if(!init_thread_atts)
    {
        pthread_attr_init(&thread_atts);
hrchilds's avatar
hrchilds committed
921
922
923
924
925
926
927
928
929
930
931
932
933

        size_t stack_size;
        int sixty_four_MB = 67108864;
        pthread_attr_getstacksize(&thread_atts, &stack_size);
        if (stack_size > sixty_four_MB)
        {
            debug1 << "Users stack size set bigger than 64MB, which can "
                   << "cause problems on some systems." << endl;
            debug1 << "Resetting limit to 64MB" << endl;
            debug1 << "Old stack size was " << stack_size << endl;
            pthread_attr_setstacksize(&thread_atts, sixty_four_MB);
        }

hrchilds's avatar
hrchilds committed
934
935
936
937
938
939
940
941
942
943
944
945
946
        init_thread_atts = true;
    }
    // Create the thread pthread style.
    pthread_t tid;
    if(pthread_create(&tid, &thread_atts, threaded_accept_callback,
       (void *)&cb) == -1)
    {
        validThread = false;
    }
#endif

    if(validThread)
    {
hrchilds's avatar
hrchilds committed
947
948
        debug5 << mName << "New accept thread created" << endl;

hrchilds's avatar
hrchilds committed
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
        //
        // If the accept callback thread is still alive, then loop until it is
        // done or the user cancels the operation or there is a problem
        // launching the program.
        //
        bool alive;
        MUTEX_LOCK(cb.mutex);
        alive = cb.alive;
        MUTEX_UNLOCK(cb.mutex);
        if(alive)
        {
            //
            // Wait for the socket to become available on the other side.
            //
            bool noDescriptor, noProcessError, noCancel;
hrchilds's avatar
hrchilds committed
964
            debug5 << mName << "Calling progress callback(1) ";
hrchilds's avatar
hrchilds committed
965
966
967
            do
            {
                // Call the progress callback.
hrchilds's avatar
hrchilds committed
968
                debug5 << ".";
hrchilds's avatar
hrchilds committed
969
970
971
972
973
974
975
976
977
                noCancel = CallProgressCallback(1);

                // Determine if we should keep looping. We have to access the
                // information through a mutex since there is more than one flag
                // that we're checking.
                MUTEX_LOCK(cb.mutex);
                noDescriptor = (cb.desc == -1);
                noProcessError = (cb.Errno == 0 && childDied[GetProcessId()] == false);
                MUTEX_UNLOCK(cb.mutex);
978
979
980
#ifdef WIN32
                Sleep(1);
#endif
hrchilds's avatar
hrchilds committed
981
982
            }
            while(noDescriptor && noProcessError && noCancel);
hrchilds's avatar
hrchilds committed
983
            debug5 << endl;
hrchilds's avatar
hrchilds committed
984
985
986
987
988
989

            // If the thread is still alive, we encountered an error or we cancelled
            // the process launch. If the thread is still alive, cancel it.
            MUTEX_LOCK(cb.mutex);
            if(cb.alive)
            {
hrchilds's avatar
hrchilds committed
990
                debug5 << mName << "Terminating the accept thread." << endl;
hrchilds's avatar
hrchilds committed
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
#if defined(WIN32)
                TerminateThread(tid, 0);
                CloseHandle(tid);
#else
                pthread_cancel(tid);
#endif
                // Only return -2 (CancelledConnection) if we did not cancel.
                // Otherwise, we probably got here because the desired executable
                // could not be run (CouldNotConnect).
                cb.desc = noCancel ? -1 : -2;
            }
            MUTEX_UNLOCK(cb.mutex);
        }

        // Set the return value.
        desc = cb.desc;
    }
    else
    {
        // We could not create the thread so do the single-threaded version.
hrchilds's avatar
hrchilds committed
1011
1012
        debug5 << mName << "New accept thread was not created. Do the single "
            "threaded version" << endl;
hrchilds's avatar
hrchilds committed
1013
1014
1015
1016
1017
1018
        desc = SingleThreadedAcceptSocket();
    }

    // Destroy the mutex.
    MUTEX_DESTROY(cb.mutex);

hrchilds's avatar
hrchilds committed
1019
1020
    debug5 << mName << "Returning: " << desc << endl;

hrchilds's avatar
hrchilds committed
1021
1022
1023
1024
1025
1026
1027
1028
    // Return the descriptor.
    return desc;
#else
    // No threads implementation.
    return -1;
#endif
}

1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
// ****************************************************************************
// Method: RemoteProcess::Launch
//
// Purpose: 
//   Launches the remote process.
//
// Arguments:
//   rHost               : The host where the program will be launched.
//   createAsThoughLocal : Whether to create the program as local.
//   commandLine         : 
//
// Returns:    
//
// Note:       I moved this out from the Open method.
//
// Programmer: Brad Whitlock
// Creation:   Thu Apr  9 10:25:24 PDT 2009
//
// Modifications:
//   
// ****************************************************************************

void
RemoteProcess::Launch(const std::string &rHost, bool createAsThoughLocal,
    const stringVector &commandLine)
{
    const char *mName = "RemoteProcess::Launch: ";

    if (HostIsLocal(rHost) || createAsThoughLocal)
    {
        debug5 << mName << "Calling LaunchLocal" << endl;
        LaunchLocal(commandLine);
    }
    else
    {
        debug5 << mName << "Calling LaunchRemote" << endl;
        LaunchRemote(commandLine);
    }
}

hrchilds's avatar
hrchilds committed
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
// ****************************************************************************
// Method: RemoteProcess::Open
//
// Purpose: 
//   Opens sockets and launches a remote process using ssh.
//
// Arguments:
//   rHost    : The remote host to run on.
//   numRead  : The number of read sockets to create to the remote process.
//   numWrite : The number of write sockets to create to the remote process.
//   createAsThoughLocal : Creates the process as though it was a local process
//                         regardless of the hostname.
//
// Returns:    
//   true if it worked, false if it did not.
//
// Note:       
//   numRead and numWrite cannot both be 0, otherwise the method
//   will return without creating the remote process.
//
// Programmer: Brad Whitlock
// Creation:   Fri Jul 14 09:19:03 PDT 2000
//
// Modifications:
//    Jeremy Meredith, Tue Aug  8 13:49:42 PDT 2000
//    Changed it to allow more than one read/write socket.
//    It now also only listens on a single port.
//
//    Brad Whitlock, Fri Aug 25 11:04:59 PDT 2000
//    Changed it to launch a local process if the rHost is
//    "localhost" or if the rHost is the same as the localHost name
//    that is looked up.
//
//    Brad Whitlock, Mon Nov 20 16:55:53 PST 2000
//    Moved the guts to smaller methods.
//
//    Jeremy Meredith, Wed Jul 10 12:57:14 PDT 2002
//    Added code to set up a handler to watch for the remote process to die.
//    It is allowed to close with a zero exit status, but it gets marked
//    as "dead" if it exits with a nonzero exit code while we are waiting
//    for it.
//
//    Jeremy Meredith, Tue Oct 22 15:02:50 PDT 2002
//    Moved the initialization of the signal handler into LaunchLocal and
//    LaunchRemote.  It must be the last thing called before fork(), since
//    if we are using PTYs, the call to grantpt may hang if there is a
//    signal handler for SIGCHLD that calls wait() -- grantpt gets the
//    status and wait() never returns.
//
//    Brad Whitlock, Mon May 5 13:00:59 PST 2003
//    I moved the code that creates the command line into CreateCommandLine
//    instead of having it inside of the methods to launch processes.
//
hrchilds's avatar
hrchilds committed
1122
1123
1124
1125
1126
//    Jeremy Meredith, Thu Oct  9 14:02:22 PDT 2003
//    Added ability to manually specify a client host name or to have it
//    parsed from the SSH_CLIENT (or related) environment variables.  Added
//    ability to specify an SSH port.
//
hrchilds's avatar
hrchilds committed
1127
1128
1129
//    Brad Whitlock, Tue Jan 17 13:49:43 PST 2006
//    Adding debug logging.
//
1130
1131
1132
//    Jeremy Meredith, Thu May 24 11:10:15 EDT 2007
//    Added SSH tunneling argument; pass it along to CreateCommandLine.
//
1133
1134
1135
//    Brad Whitlock, Thu Apr  9 10:40:08 PDT 2009
//    I moved some code to the new Launch method.
//
hrchilds's avatar
hrchilds committed
1136
1137
1138
// ****************************************************************************

bool
hrchilds's avatar
hrchilds committed
1139
1140
1141
1142
1143
RemoteProcess::Open(const std::string &rHost,
                    HostProfile::ClientHostDetermination chd,
                    const std::string &clientHostName,
                    bool manualSSHPort,
                    int sshPort,
1144
                    bool useTunneling,
hrchilds's avatar
hrchilds committed
1145
1146
                    int numRead, int numWrite,
                    bool createAsThoughLocal)
hrchilds's avatar
hrchilds committed
1147
{
hrchilds's avatar
hrchilds committed
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
    // Write the arguments to the debug log.
    const char *mName = "RemoteProcess::Open: ";
    debug5 << mName << "Called with (rHost=" << rHost.c_str();
    int i_chd = int(chd);
    debug5 << ", chd=";
    const char *chd_t[] = {"MachineName", "ManuallySpecified", "ParsedFromSSHCLIENT"};
    if(i_chd >= 0 && i_chd <= 2)
        debug5 << chd_t[i_chd];
    else
        debug5 << i_chd;
    debug5 << ", manualSSHPort=" << (manualSSHPort?"true":"false");
    debug5 << ", sshPort=" << sshPort;
1160
    debug5 << ", useTunneling=" << useTunneling;
hrchilds's avatar
hrchilds committed
1161
1162
1163
1164
1165
    debug5 << ", numRead=" << numRead;
    debug5 << ", numWrite=" << numWrite;
    debug5 << ", createAsThoughLocal=" << (createAsThoughLocal?"true":"false");
    debug5 << ")" << endl;

hrchilds's avatar
hrchilds committed
1166
1167
    // Start making the connections and start listening.
    if(!StartMakingConnection(rHost, numRead, numWrite))
hrchilds's avatar
hrchilds committed
1168
1169
1170
    {
        debug5 << "StartMakingConnection(" << rHost.c_str() << ", " << numRead
               << ", " << numWrite << ") failed. Returning." << endl;
hrchilds's avatar
hrchilds committed
1171
        return false;
hrchilds's avatar
hrchilds committed
1172
    }
hrchilds's avatar
hrchilds committed
1173
1174
1175

    // Add all of the relevant command line arguments to a vector of strings.
    stringVector commandLine;
hrchilds's avatar
hrchilds committed
1176
    CreateCommandLine(commandLine, rHost,
1177
                      chd, clientHostName, manualSSHPort, sshPort,useTunneling,
hrchilds's avatar
hrchilds committed
1178
                      numRead, numWrite,
hrchilds's avatar
hrchilds committed
1179
                      createAsThoughLocal);
1180
    debug5 << mName << "Creating the command line to use: (";
1181
    for(size_t i = 0; i < commandLine.size(); ++i)
hrchilds's avatar
hrchilds committed
1182
    {
hrchilds's avatar
hrchilds committed
1183
        debug5 << commandLine[i].c_str();
hrchilds's avatar
hrchilds committed
1184
1185
1186
1187
        if(i < commandLine.size()-1)
            debug5 << ", ";
    } 
    debug5 << ")" << endl;
hrchilds's avatar
hrchilds committed
1188
1189
1190
1191

    //
    // Launch the remote process.
    //
1192
    Launch(rHost, createAsThoughLocal, commandLine);
hrchilds's avatar
hrchilds committed
1193
1194
1195
1196

    childDied[GetProcessId()] = false;

    // Finish the connections.
hrchilds's avatar
hrchilds committed
1197
    debug5 << mName << "Calling FinishMakingConnection" << endl;
hrchilds's avatar
hrchilds committed
1198
1199
1200
1201
1202
1203
1204
    FinishMakingConnection(numRead, numWrite);

#if !defined(_WIN32)
    // Stop watching for dead children
    signal(SIGCHLD, SIG_DFL);
#endif

hrchilds's avatar
hrchilds committed
1205
1206
    debug5 << mName << "Returning true" << endl;

hrchilds's avatar
hrchilds committed
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
    return true;
}

// ****************************************************************************
// Method: RemoteProcess::WaitForTermination
//
// Purpose: 
//   Waits for the remote process to quit.
//
// Programmer: Brad Whitlock
// Creation:   Wed Apr 3 12:03:11 PDT 2002
//
// Modifications:
hrchilds's avatar
hrchilds committed
1220
1221
1222
//   Brad Whitlock, Tue Jan 17 14:01:46 PST 2006
//   Added debug logging.
//
hrchilds's avatar
hrchilds committed
1223
1224
1225
1226
1227
1228
1229
// ****************************************************************************

void
RemoteProcess::WaitForTermination()
{
    if(remoteProgramPid != -1)
    {
hrchilds's avatar
hrchilds committed
1230
        debug5 << "RemoteProcess::WaitForTermination: Waiting for process to quit" << endl;
hrchilds's avatar
hrchilds committed
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
        int result;
#if defined(_WIN32)
        _cwait(&result, remoteProgramPid, _WAIT_CHILD);
#else
        waitpid(remoteProgramPid, &result, 0);
#endif
    }
}

// ****************************************************************************
// Method: RemoteProcess::StartMakingConnection
//
// Purpose: 
//   Starts making the connection to the remote process so we're ready to
//   go on this end before the remote process is launched.
//
// Arguments:
//   rHost    : The remote host to run on.
//   numRead  : The number of read sockets to create to the remote process.
//   numWrite : The number of write sockets to create to the remote process.
//
// Returns:    
//   true if it worked, false if it did not.
//   
// Note:       
//
// Programmer: Brad Whitlock
// Creation:   Mon Nov 20 16:53:53 PST 2000
//
// Modifications:
//   Jeremy Meredith, Thu Apr 19 09:43:07 PDT 2001
//   Added code to get the full local host name.
//
//   Brad Whitlock, Thu Feb 21 10:14:35 PDT 2002
//   Added code to get the local user name.
//
//   Brad Whitlock, Thu Sep 12 11:34:58 PDT 2002
//   I added an extra lookup on Windows that gets the actual local hostname
//   so it does not use any bad names returned by Windows.
//
//   Brad Whitlock, Thu Dec 19 11:32:43 PDT 2002
//   I made the code create a security key.
//
hrchilds's avatar
hrchilds committed
1274
1275
1276
//   Brad Whitlock, Tue Jan 17 14:03:43 PST 2006
//   Added debug logging.
//
1277
1278
1279
1280
//   Mark C. Miller, Mon Nov  5 17:15:45 PST 2007
//   Added code to attempt 'localhost' after using name returned by
//   gethostname() failes.
//
1281
1282
1283
//   Kathleen Bonnell, Tue Sep 9 15:16:47 PDT 2008 
//   Fixed windows extra-lookup loop to correctly use hostent members.
//
hrchilds's avatar
hrchilds committed
1284
1285
1286
1287
1288
1289
// ****************************************************************************

bool
RemoteProcess::StartMakingConnection(const std::string &rHost, int numRead,
    int numWrite)
{
hrchilds's avatar
hrchilds committed
1290
1291
1292
1293
1294
1295
    const char *mName = "RemoteProcess::StartMakingConnection: ";
    debug5 << mName << "Called with args (";
    debug5 << "rHost=" << rHost.c_str();
    debug5 << ", numRead=" << numRead;
    debug5 << ", numWrite=" << numWrite << ")" << endl;

hrchilds's avatar
hrchilds committed
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
    //
    // If there are no sockets to be opened, get out since that seems
    // like a usedless thing to do.
    //
    if(numRead + numWrite == 0)
        return false;

    //
    // Set the remote host name and make sure that it is valid. If it
    // is not valid, throw a BadHostException. Do not bother checking
    // whether or not remoteHost is valid if it equals "localhost" since
    // in that case we'll be spawning a local process.
    //
hrchilds's avatar
hrchilds committed
1309
    remoteHost = rHost;
hrchilds's avatar
hrchilds committed
1310
1311
    debug5 << mName << "Calling gethostbyname(\"" << remoteHost.c_str()
           << "\") to look up the name of the remote host" << endl;
hrchilds's avatar
hrchilds committed
1312
1313
1314
    bool remote = (remoteHost != std::string("localhost"));
    if(remote && (gethostbyname(remoteHost.c_str()) == NULL))
    {
hrchilds's avatar
hrchilds committed
1315
1316
1317
#if defined(_WIN32)
        LogWindowsSocketError(mName, "gethostbyname,1");
#endif
hrchilds's avatar
hrchilds committed
1318
1319
1320
1321
1322
1323
1324
        EXCEPTION1(BadHostException, remoteHost);
    }

    //
    // Get the local host name since it will be a command line option
    // for the remote process.
    //
hrchilds's avatar
hrchilds committed
1325
    debug5 << mName << "Looking up the name of the local host: ";
hrchilds's avatar
hrchilds committed
1326
1327
1328
    char localHostStr[256];
    if (gethostname(localHostStr, 256) == -1)
    {
hrchilds's avatar
hrchilds committed
1329
1330
1331
#if defined(_WIN32)
        LogWindowsSocketError(mName, "gethostname");
#endif
hrchilds's avatar
hrchilds committed
1332
1333
1334
1335
        // Couldn't get the hostname, it's probably invalid so
        // throw a BadHostException.
        EXCEPTION1(BadHostException, localHostStr);
    }
hrchilds's avatar
hrchilds committed
1336
1337
1338
    debug5 << localHostStr << endl;
    debug5 << mName << "Looking up the host using gethostbyname(\""
           << localHostStr << "\"): ";
hrchilds's avatar
hrchilds committed
1339
1340
1341
    struct hostent *localHostEnt = gethostbyname(localHostStr);
    if (localHostEnt == NULL)
    {
fogal1's avatar
fogal1 committed
1342
1343
        // Ok, using the host's name returned from gethostname()
        // did not work. So, lets fall back to 'localhost'
1344
1345
        strcpy(localHostStr,"localhost");
        localHostEnt = gethostbyname(localHostStr);
fogal1's avatar
fogal1 committed
1346
1347
        if (localHostEnt == NULL)
        {
hrchilds's avatar
hrchilds committed
1348
#if defined(_WIN32)
1349
            LogWindowsSocketError(mName, "gethostbyname,2");
hrchilds's avatar
hrchilds committed
1350
#endif
1351
1352
1353
1354
            // Couldn't get the full host entry; it's probably invalid so
            // throw a BadHostException.
            EXCEPTION1(BadHostException, localHostStr);
        }
hrchilds's avatar
hrchilds committed
1355
1356
    }
    localHost = std::string(localHostEnt->h_name);
hrchilds's avatar
hrchilds committed
1357
    debug5 << localHost.c_str() << endl;
hrchilds's avatar
hrchilds committed
1358
1359
1360
1361
1362
1363
1364
1365
#if defined(_WIN32)
    // On some Windows systems, particularly those hooked up to dial up
    // connections where the actual network hostname may not match the
    // computer's hostname, we need to do another lookup to ensure that
    // we get the actual local hostname so the remote process can connect
    // back successfully.
    if(remote)
    {
hrchilds's avatar
hrchilds committed
1366
        debug5 << mName << "We're on Win32 and the host is remote. "
1367
1368
1369
1370
               << "Make sure that we have the correct name for localhost by "
               << "iterating through the localHostEnt and calling "
               << "gethostbyaddr." << endl;
        for(int i = 0; localHostEnt->h_addr_list[i] != 0; ++i)
hrchilds's avatar
hrchilds committed
1371
        {
1372
1373
1374
1375
1376
            struct hostent *h = NULL;
            h = gethostbyaddr(localHostEnt->h_addr_list[i], 
                              localHostEnt->h_length, 
                              localHostEnt->h_addrtype);
            if(h)
hrchilds's avatar
hrchilds committed
1377
            {
1378
1379
1380
                localHost = std::string(h->h_name);
                debug5 << mName << "gethostbyaddr returned: " 
                       << localHost.c_str() << endl;
hrchilds's avatar
hrchilds committed
1381
            }
1382
1383
            else
                LogWindowsSocketError(mName, "gethostbyaddr");
hrchilds's avatar
hrchilds committed
1384
1385
1386
1387
1388
1389
1390
        }
    }
#endif

    //
    // Get the local user name.
    //
hrchilds's avatar
hrchilds committed
1391
    debug5 << mName << "Get the local user's login name: ";
hrchilds's avatar
hrchilds committed
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
#if defined(_WIN32)
    char username[100];
    DWORD maxLen = 100;
    GetUserName((LPTSTR)username, (LPDWORD)&maxLen);
    localUserName = std::string(username);
#else
    struct passwd *users_passwd_entry = NULL;
    if((users_passwd_entry = getpwuid(getuid())) != NULL)
        localUserName = std::string(users_passwd_entry->pw_name);
#endif
hrchilds's avatar
hrchilds committed
1402
    debug5 << localUserName.c_str() << endl;
hrchilds's avatar
hrchilds committed
1403
1404
1405
1406
1407
1408
1409
1410
1411

    //
    // Create a security key
    //
    securityKey = CommunicationHeader::CreateRandomKey();

    //
    // Open the socket for listening
    //
hrchilds's avatar
hrchilds committed
1412
    debug5 << mName << "Calling GetSocketAndPort" << endl;
hrchilds's avatar
hrchilds committed
1413
1414
1415
    if(!GetSocketAndPort())
    {
        // Could not open socket and port
hrchilds's avatar
hrchilds committed
1416
        debug5 << mName << "GetSocketAndPort returned false" << endl;
hrchilds's avatar
hrchilds committed
1417
1418
1419
1420
1421
1422
        return false;
    }

    //
    // Start listening for connections.
    //
hrchilds's avatar
hrchilds committed
1423
    debug5 << mName << "Start listening for connections." << endl;
hrchilds's avatar
hrchilds committed
1424
1425
1426
1427
#if defined(_WIN32)
    if(listen(listenSocketNum, 5) == SOCKET_ERROR)
        LogWindowsSocketError(mName, "listen");
#else
hrchilds's avatar
hrchilds committed
1428
    listen(listenSocketNum, 5);
hrchilds's avatar
hrchilds committed
1429
#endif
hrchilds's avatar
hrchilds committed
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
    return true;
}

// ****************************************************************************
// Method: RemoteProcess::FinishMakingConnection
//
// Purpose: 
//   Finishes making the connections needed to talk to the remote process.
//
// Arguments:
//   numRead  : The number of read sockets.
//   numWrite : The number of write sockets.
//
// Returns:    
//
// Note:       
//
// Programmer: Brad Whitlock
// Creation:   Mon Nov 20 16:50:57 PST 2000
//
// Modifications:
//   Brad Whitlock, Mon Mar 25 15:13:41 PST 2002
//   Made it use Connection objects instead of SocketConnection.
//
//   Jeremy Meredith, Wed Jul 10 12:58:14 PDT 2002
//   Made it throw an exception if it gets a -1 descriptor for any socket.
//   This should be an error when trying to launch a remote process.
//
//   Brad Whitlock, Thu Sep 26 17:08:01 PST 2002
//   I made it call the progress callback.
//
//   Brad Whitlock, Fri Jan 3 15:27:54 PST 2003
//   I added code to close the listen socket.
//
hrchilds's avatar
hrchilds committed
1464
1465
1466
//   Brad Whitlock, Tue Jan 17 14:15:14 PST 2006
//   Added debug logging.
//
hrchilds's avatar
hrchilds committed
1467
1468
1469
1470
1471
// ****************************************************************************

void
RemoteProcess::FinishMakingConnection(int numRead, int numWrite)
{
hrchilds's avatar
hrchilds committed
1472
1473
1474
1475
    const char *mName = "RemoteProcess::FinishMakingConnection: ";

    debug5 << mName << "Called with (" << numRead << ", " << numWrite << ")\n";

hrchilds's avatar
hrchilds committed
1476
    // Call the progress callback.
hrchilds's avatar
hrchilds committed
1477
    debug5 << mName << "Call the progress callback(0)" << endl;
hrchilds's avatar
hrchilds committed
1478
1479
1480
1481
1482
1483
1484
1485
    CallProgressCallback(0);

    TRY
    {
        //
        // Accept the sockets that were created and create SocketConnection
        // objects for them.
        //
hrchilds's avatar
hrchilds committed
1486
        debug5 << mName << "Creating read connections" << endl;
hrchilds's avatar
hrchilds committed
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
        nReadConnections = 0;
        if(numRead > 0)
        {
            readConnections = new Connection*[numRead];
            for(int i = 0; i < numRead; ++i)
            {
                int descriptor = AcceptSocket();
                readConnections[nReadConnections] = new SocketConnection(descriptor);
                ++nReadConnections;
            }
        }

hrchilds's avatar
hrchilds committed
1499
        debug5 << mName << "Creating write connections" << endl;
hrchilds's avatar
hrchilds committed
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
        nWriteConnections = 0;
        if(numWrite > 0)
        {
            writeConnections = new Connection*[numWrite];
            for(int i = 0; i < numWrite; ++i)
            {
                int descriptor = AcceptSocket();
                writeConnections[nWriteConnections] = new SocketConnection(descriptor);
                ++nWriteConnections;
            }
        }

        //
        // Now that the sockets are open, exchange type representation info
        // and set that info in the socket connections.
        //
hrchilds's avatar
hrchilds committed
1516
        debug5 << mName << "Exchanging type representations" << endl;
hrchilds's avatar
hrchilds committed
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
        ExchangeTypeRepresentations();
    }
    CATCHALL(...)
    {
        // Call the progress callback and tell it to end.
        CallProgressCallback(2);
        CloseListenSocket();

        RETHROW;
    }
    ENDTRY

    // Call the progress callback.
hrchilds's avatar
hrchilds committed
1530
    debug5 << mName << "Call the progress callback(2)" << endl;
hrchilds's avatar
hrchilds committed
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
    CallProgressCallback(2);
    CloseListenSocket();
}

// ****************************************************************************
// Method: RemoteProcess::ExchangeTypeRepresentations
//
// Purpose: 
//   Exchanges the machine's type representation with that of the
//   machine on the other end of the sockets. It then puts that
//   type representation into the write sockets. Conversion is disabled
//   if the type representations are the same.
//
// Programmer: Brad Whitlock
// Creation:   Thu Oct 5 18:25:19 PST 2000
//
// Modifications:
//   Brad Whitlock, Wed Apr 25 11:33:58 PDT 2001
//   Moved the specifics of the message header into the CommunicationHeader
//   class.
//
//   Jeremy Meredith, Fri Apr 27 15:30:29 PDT 2001
//   Added CouldNotConnectException.
//
//   Brad Whitlock, Mon Mar 25 14:13:34 PST 2002
//   Made it pass a connection to the communication header object.
//
// ****************************************************************************

void
RemoteProcess::ExchangeTypeRepresentations()
{
    // We can only exchange type representations if we opened
    // a read and a write socket.
    if(nReadConnections > 0 && nWriteConnections > 0)
    {
        TypeRepresentation  local;
        CommunicationHeader header;

        // Create a second key that will pass over the socket. The remote
        // process must pass this key back too.
        std::string socketKey(header.CreateRandomKey());

        // Send the local component and platform information.
        header.WriteHeader(writeConnections[0], VERSION,
                           "", socketKey);

        bool throwVersionException = false;
        bool throwConnectException = false;
        bool throwSecurityTokenException = false;
        TRY
        {
            // Set the security key that must be matched by the remote
            // process.
            header.SetSecurityKey(securityKey);

            // Read the remote component and platform information.
            header.ReadHeader(readConnections[0], VERSION);
        }
        CATCH(IncompatibleVersionException)
        {
            throwVersionException = true;
        }
        CATCH(CouldNotConnectException)
        {
            throwConnectException = true;
        }
        CATCH(IncompatibleSecurityTokenException)
        {
            throwSecurityTokenException = true;
        }
        ENDTRY

        // Now that we have the type representation for the remote machine,
        // if it is the same as the local type representation, turn off 
        // conversion in the write connections. Otherwise, set it into all
        // of the write sockets.
        if(local == header.GetTypeRepresentation())
        {
            for(int i = 0; i < nReadConnections; ++i)
                readConnections[i]->EnableConversion(false);
        }
        else
        {
            for(int i = 0; i < nReadConnections; ++i)
            {
                readConnections[i]->SetDestinationFormat(
                    header.GetTypeRepresentation());
            }
        }

        // If an IncompatibleVersionException was thrown, we caught it so
        // we could send this side's communication header. Now that we've done
        // that, we can re-throw the exception.
        if(throwVersionException)
        {
            EXCEPTION0(IncompatibleVersionException);
        }
        if(throwConnectException)
        {
            EXCEPTION0(CouldNotConnectException);
        }
        if(throwSecurityTokenException)
        {
            EXCEPTION0(IncompatibleSecurityTokenException);
        }
    }
}

// ****************************************************************************
// Method: RemoteProcess::SecureShell
//
// Purpose: 
//   Returns the name of the SSH program being used to launch remote components.
//
// Returns:    A string containing the name of the SSH program used to
//             launch remote components.
//
// Programmer: Brad Whitlock
// Creation:   Mon Aug 26 12:49:52 PDT 2002
//
// Modifications:
//   
// ****************************************************************************

const char *
RemoteProcess::SecureShell() const
{
    const char *ssh =  (const char *)getenv("VISITSSH");
    if(ssh == 0)
#if defined(_WIN32)
        ssh = "qtssh.exe";
#else
        ssh = "ssh";
#endif
    return ssh;
}

// ****************************************************************************
// Method: RemoteProcess::SecureShellArgs
//
// Purpose: 
//   Returns a string that contains any arguments that should be passed to
//   the SSH program.
//
// Returns:    NULL or a string containing arguments to pass to SSH.
//
// Programmer: Brad Whitlock
// Creation:   Mon Aug 26 12:49:00 PDT 2002
//
// Modifications:
//   
// ****************************************************************************

const char *
RemoteProcess::SecureShellArgs() const
{
    return (const char *)getenv("VISITSSHARGS");
}

// ****************************************************************************
// Method: RemoteProcess::CreateCommandLine
//
// Purpose: 
//   Creates the command line for the process that we want to launch.
//
// Arguments:
//   args     : The return string vector.
//   rHost    : The name of the host that we want to launch the process on.
//   numRead  : The number of read sockets to create.
//   numWrite : The number of write sockets to create.
//   local    : Prevents ssh arguments from being added.
//
// Returns:   The command line in the args vector. 
//
// Programmer: Brad Whitlock
// Creation:   Mon May 5 13:05:24 PST 2003
//
// Modifications:
hrchilds's avatar
hrchilds committed
1710
1711
1712
//   Brad Whitlock, Tue Jul 29 10:51:42 PDT 2003
//   I removed -nread and -nwrite from the command line.
//
hrchilds's avatar
hrchilds committed
1713
1714
1715
1716
1717
//    Jeremy Meredith, Thu Oct  9 14:02:47 PDT 2003
//    Added ability to manually specify a client host name or to have it
//    parsed from the SSH_CLIENT (or related) environment variables.  Added
//    ability to specify an SSH port.
//
1718
1719
1720
1721
1722
1723
1724
1725
1726
//    Jeremy Meredith, Thu May 24 11:11:22 EDT 2007
//    Added support for SSH tunneling.  The initial implementation here
//    will be to forward N remote ports to local ports 5600 .. 5600+N-1.
//    We save off this map, and when launching remote processes we will
//    convert client:originalport to remote:tunneledport using this map.
//    If N is too low, we will run out of forwards.  If N is too high
//    then picking arbitrary random ports on the remote machine is
//    liable to run into collisions with other users more quickly.
//
1727
1728
1729
1730
1731
1732
//    Jeremy Meredith, Tue Sep  4 16:40:32 EDT 2007
//    Changed "localhost" in -R port forwarding specification to
//    "127.0.0.1".  Still investigating, but apparently OS X seems to
//    fail with localhost, but works with 127.0.0.1.  Since other platforms
//    still work with 127.0.0.1, this should be a safe change.
//
1733
1734
1735
//    Thomas R. Treadway, Mon Oct  8 13:27:42 PDT 2007
//    Backing out SSH tunneling on Panther (MacOS X 10.3)
//
1736
1737
1738
1739
1740
1741
//    Jeremy Meredith, Mon Dec 10 14:58:59 EST 2007
//    If we got a fully numeric X.Y.Z version number, request just
//    X.Y; from this point on, we have designated that all patch
//    levels of the same Major.Minor version will be compatible with
//    each other, so we always want the latest in the X.Y series.
//
1742
1743
1744
1745
1746
1747
//    Jeremy Meredith, Wed Apr 30 11:46:20 EDT 2008
//    If it's not a local launch, add the "-noloopback" flag.  The new
//    behavior is to always use the loopback device (127.0.0.1) unless this
//    flag is given, and this is the most direct place to correctly add
//    that flag.
//
1748
1749
1750
1751
1752
//    Jeremy Meredith, Tue Jun  3 16:16:16 EDT 2008
//    Increased the number of local ports for tunneling from 5 to either
//    7 (on UNIX) or 10 (on Windows).  Particularly on Windows, users were
//    still having problems.
//
hrchilds's avatar
hrchilds committed
1753
1754
1755
1756
// ****************************************************************************

void
RemoteProcess::CreateCommandLine(stringVector &args, const std::string &rHost,
hrchilds's avatar
hrchilds committed
1757
1758
1759
1760
                                 HostProfile::ClientHostDetermination chd,
                                 const std::string &clientHostName,
                                 bool manualSSHPort,
                                 int sshPort,
1761
1762
                                 bool useTunneling,
                                 int numRead, int numWrite, bool local)
hrchilds's avatar
hrchilds committed
1763
1764
1765
1766
1767
1768
1769
1770
1771
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787
1788
1789
1790
{
    //
    // If the host is not local, then add some ssh arguments to the
    // front of the command line argument list.
    //
    if(!HostIsLocal(rHost) && !local)
    {
#if defined(_WIN32)
        // If the path to secure shell contains spaces, enclose the program
        // name in quotes. This is mostly so the ssh program runs correctly
        //  on Windows when VisIt is installed into a path containing spaces.
        std::string ssh(SecureShell());
        if(ssh.find(" ") != std::string::npos)
        {
            std::string q("\"");
            args.push_back(q + ssh + q);
        }
        else
            args.push_back(SecureShell());
#else
        args.push_back(SecureShell());
#endif

        // Set any optional ssh arguments into the command line.
        const char *sshArgs = SecureShellArgs();
        if(sshArgs)
            args.push_back(sshArgs);

hrchilds's avatar
hrchilds committed
1791
1792
1793
1794
1795
1796
1797
1798
        if (manualSSHPort)
        {
            char portStr[256];
            SNPRINTF(portStr, 256, "%d", sshPort);
            args.push_back("-p");
            args.push_back(portStr);
        }

hrchilds's avatar
hrchilds committed
1799
1800
1801
1802
1803
1804
        // Set the username.
        if(remoteUserName != std::string("notset"))
        {
             args.push_back("-l");
             args.push_back(remoteUserName);
        }
1805
1806
1807

        // If we're tunneling, add the arguments to SSH to 
        // forward a bunch of remote ports to our local ports.
1808
#if defined(PANTHERHACK)
1809
1810
// Broken on Panther
#else
1811
1812
1813
        if (useTunneling)
        {
            int numRemotePortsPerLocalPort = 1;
1814
1815
1816
1817
1818
#if defined(_WIN32)
            int numLocalPorts              = 10;
#else
            int numLocalPorts              = 7;
#endif
1819
1820
1821
1822
1823
1824
1825
1826
1827
1828
1829
1830
1831
1832
1833
1834
1835
1836
1837
1838
1839
1840
1841
1842
1843
1844
1845
1846
1847
1848
1849
1850
1851
            int firstLocalPort             = INITIAL_PORT_NUMBER;
            int lowerRemotePort            = 10000;
            int upperRemotePort            = 40000;
            int remotePortRange            = 1+upperRemotePort-lowerRemotePort;
            portTunnelMap.clear();
            std::set<int> remotePortSet;
            for (int i = 0; i < numLocalPorts ; i++)
            {
                int localPort = firstLocalPort + i;
                for (int j = 0; j < numRemotePortsPerLocalPort; j++)
                {
                    int remotePort;
                    do
                    {
#if defined(_WIN32)
                        remotePort = lowerRemotePort+(rand()%remotePortRange);
#else
                        remotePort = lowerRemotePort+(lrand48()%remotePortRange);
#endif
                    }
                    while (remotePortSet.count(remotePort) != 0);

                    remotePortSet.insert(remotePort);

                    // NOTE: using a (non-multi) map only works
                    // when there is one remote port for each local port.
                    // If we change the implementation so that we try to
                    // map more than one remote port to each local port,
                    // we much change this!
                    portTunnelMap[localPort] = remotePort;

                    char forwardSpec[256];
                    sprintf(forwardSpec, "%d:%s:%d",
1852
                            remotePort, "127.0.0.1", localPort);
1853
1854
1855
1856
1857
1858
1859
                    debug5 << "RemoteProcess::CreateCommandLine -- "
                           << "forwarding ("<< forwardSpec << ")" << endl;
                    args.push_back("-R");
                    args.push_back(forwardSpec);
                }
            }
        }
1860
#endif
hrchilds's avatar
hrchilds committed
1861
1862
1863
1864
1865
1866
1867
1868
1869
1870
1871
1872
1873
1874
1875
    
        // Set the name of the host to run on.
        args.push_back(remoteHost.c_str());
    }

    //
    // Add the name of the remote program to run 
    //
    args.push_back(remoteProgram);

    //
    // Add the version so that we try to launch a version that is compatible
    // with this version.
    //
    args.push_back("-v");
1876
1877
1878
1879
1880
1881
1882
1883
1884
1885
1886
1887
1888
1889
1890
    int major=0, minor=0, patch=0;
    if (GetVisItVersionFromString(VERSION, major, minor, patch) >= 2)
    {
        // Note: we didn't wrap GetVisItVersionFromString with abs() because
        // a negative value implies a beta version, and we only want to
        // attempt to request a compatible version within this major
        // release period if we're not a beta version.
        char majorVersionOnly[100];
        sprintf(majorVersionOnly, "%d.%d", major, minor);
        args.push_back(majorVersionOnly);
    }
    else
    {
        args.push_back(VERSION);
    }
hrchilds's avatar
hrchilds committed
1891
1892
1893
1894

    //
    // Add the program's additional arguments.
    //
1895
    for(size_t i = 0; i < argList.size(); ++i)
hrchilds's avatar
hrchilds committed
1896
1897
        args.push_back(argList[i]);

1898
1899
1900
1901
1902
1903
1904
1905
1906
1907
1908

    //
    // If it's not going to be a local launch, set an argument
    // disabling automatic usage of the loopback network device.
    //
    if (!HostIsLocal(rHost))
    {
        args.push_back("-noloopback");
    }


hrchilds's avatar
hrchilds committed
1909
1910
1911
    //
    // Add the local hostname and the ports we'll be talking on.
    //
1912
#if defined(PANTHERHACK)
1913
1914
// Broken on Panther
#else
1915
    if (useTunneling)
hrchilds's avatar
hrchilds committed
1916
    {
1917
1918
1919
1920
        // If we're tunneling, we know that the VCL must attempt to
        // connect to localhost on the forwarded port.
        args.push_back("-sshtunneling");

hrchilds's avatar
hrchilds committed
1921
        args.push_back("-host");
1922
1923
1924
1925
1926
1927
1928
1929
1930
1931
1932
1933
1934
1935
        args.push_back("localhost");

        if (portTunnelMap.count(listenPortNum)<=0)
        {
            debug5 << "Error finding tunnel for port "<<listenPortNum<<endl;
            EXCEPTION1(VisItException, "Launcher needed to tunnel to a local "
                       "port that wasn't in the port map.  The number of "
                       "tunneled ports may need to be increased.");
        }

        args.push_back("-port");
        char tmp[20];
        sprintf(tmp, "%d", portTunnelMap[listenPortNum]);
        args.push_back(tmp);
hrchilds's avatar
hrchilds committed
1936
    }
1937
    else
1938
#endif
1939
1940
1941
1942
1943
1944
1945
1946
1947
1948
1949
1950
1951
1952
1953
1954
1955
    {
        // If we're not tunneling, we must choose a method of determining
        // the host name, and use the actual listen port number.
        switch (chd)
        {
          case HostProfile::MachineName:
            args.push_back("-host");
            args.push_back(localHost.c_str());
            break;
          case HostProfile::ManuallySpecified:
            args.push_back("-host");
            args.push_back(clientHostName);
            break;
          case HostProfile::ParsedFromSSHCLIENT:
            args.push_back("-guesshost");
            break;
        }
hrchilds's avatar
hrchilds committed
1956

1957
1958
1959
1960
1961
        args.push_back("-port");
        char tmp[20];
        sprintf(tmp, "%d", listenPortNum);
        args.push_back(tmp);
    }
hrchilds's avatar
hrchilds committed
1962
1963
1964
1965
1966
1967
1968
1969
1970
1971
1972
1973
1974
1975
1976
1977
1978
1979
1980
1981
1982
1983
1984
1985
1986
1987
1988
1989
1990
1991
1992
1993
1994
1995
1996
1997
1998
1999
2000
2001
2002
2003
2004
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016

    //
    // Add the security key
    //
    args.push_back("-key");
    args.push_back(securityKey);
}

// ****************************************************************************
// Method: RemoteProcess::LaunchRemote
//
// Purpose: 
//   Launches a process on a remote machine using ssh.
//
// Arguments:
//   args : The command line arguments of the process to launch.
//    
// Programmer: Brad Whitlock
// Creation:   Fri Jul 21 13:24:29 PST 2000
//
// Modifications:
//    Jeremy Meredith, Tue Aug  8 13:49:42 PDT 2000
//    Changed it to allow more than one read/write socket.   
//
//    Hank Childs, Mon Oct 16 10:59:22 PDT 2000
//    Fixed memory leak.
//
//    Jeremy Meredith, Fri Apr 27 15:31:06 PDT 2001
//    Switched to using PTY's when available.  Added callback for
//    authentication.  Added CouldNotConnectException for failure condition.
//    Removed the -f and -n flags from ssh as well as the close(0).  These
//    were preventing us reading status back from the remote process.
//
//    Brad Whitlock, Tue Mar 19 15:59:33 PST 2002
//    Made it work on MS Windows.
//
//    Brad Whitlock, Mon Aug 26 12:32:03 PDT 2002
//    It didn't really work on MS Windows - now it does.
//
//    Jeremy Meredith, Tue Oct 22 15:02:50 PDT 2002
//    Moved the initialization of the signal handler into pty_fork or
//    just above fork. It must be the last thing called before fork(),
//    or at least after grantpt, due to some grantpt vileness.
//
//    Brad Whitlock, Mon Dec 16 11:54:29 PDT 2002
//    I added code to send a random number token as an argument to the
//    new process. I also added code to close the listen socket in the case
//    that a CouldNotConnectException is thrown.
//
//    Brad Whitlock, Thu Apr 24 07:22:24 PDT 2003
//    I made it pass the version as a command line argument.
//
//    Brad Whitlock, Mon May 5 13:10:52 PST 2003
//    I moved large portions of the command line creation code elsewhere.
//
hrchilds's avatar
hrchilds committed
2017
2018
2019
//    Jeremy Meredith, Thu Jul  3 15:01:25 PDT 2003
//    Allowed disabling of PTYs even when they are available.
//
hrchilds's avatar
hrchilds committed
2020
2021
2022
2023
//    Jeremy Meredith, Tue Dec  9 15:24:42 PST 2003
//    Added code to close the PTY if we could not connect.  Also attempt to 
//    kill the child process with a TERM.
//
hrchilds's avatar
hrchilds committed
2024
2025
2026
2027
2028
// ****************************************************************************

void
RemoteProcess::LaunchRemote(const stringVector &args)
{
hrchilds's avatar
hrchilds committed
2029
2030
    const char *mName = "RemoteProcess::LaunchRemote: ";

hrchilds's avatar
hrchilds committed
2031
2032
2033
2034
2035
2036
2037
2038
2039
2040
    // 
    // Create the parameters for the exec
    //
    int  argc = 0;
    char **argv = CreateSplitCommandLine(args, argc);

    //
    // Start the program on the remote host.
    // 
#if defined(_WIN32)
hrchilds's avatar
hrchilds committed
2041
    debug5 << mName << "Starting child process using _spawnvp" << endl;
hrchilds's avatar
hrchilds committed
2042
2043
2044
2045
    // Start the program using the WIN32 _spawnvp function.
    remoteProgramPid = _spawnvp(_P_NOWAIT, SecureShell(), argv);
#else
    // Start the program in UNIX
2046
#ifdef VISIT_USE_PTY
hrchilds's avatar
hrchilds committed
2047
    debug5 << mName << "Starting child process using pty_fork" << endl;
hrchilds's avatar
hrchilds committed
2048
    int ptyFileDescriptor;
hrchilds's avatar
hrchilds committed
2049
2050
2051
2052
2053
2054
2055
2056
2057
2058
2059
    if (!disablePTY)
    {
        // we will tell pty_fork to set up the signal handler for us, because
        // this call must come after the grantpt call inside pty_fork()
        remoteProgramPid = pty_fork(ptyFileDescriptor, catch_dead_child);
    }
    else
    {
        signal(SIGCHLD, catch_dead_child);
        remoteProgramPid = fork();
    }
hrchilds's avatar
hrchilds committed
2060
#else
hrchilds's avatar
hrchilds committed
2061
    debug5 << mName << "Starting child process using fork" << endl;
hrchilds's avatar
hrchilds committed
2062
    signal(SIGCHLD, catch_dead_child);
hrchilds's avatar
hrchilds committed
2063
    remoteProgramPid = fork();
hrchilds's avatar
hrchilds committed
2064
#endif
hrchilds's avatar
hrchilds committed
2065
    switch (remoteProgramPid)
hrchilds's avatar
hrchilds committed
2066
2067
2068
2069
2070
2071
2072
2073
2074
2075
2076
2077
2078
2079
2080
2081
2082
2083
2084
2085
2086
    {
    case -1:
        // Could not fork.
        exit(-1);
        break;
    case 0:
        // Do not close stdout, stderr, because ssh will fail if
        // not set up exactly right and we want to see the error
        // messages.
        for (int k = 3 ; k < 32 ; ++k)
        {
            close(k);
        }
        execvp(SecureShell(), argv);
        close(0); close(1); close(2);
        exit(-1);
        break;   // OCD
    default:
        break;
    }

2087
#ifdef VISIT_USE_PTY
hrchilds's avatar
hrchilds committed
2088
    if (!disablePTY && getAuthentication)