UserInterface.C 13.7 KB
Newer Older
Francois Gygi committed
1 2
////////////////////////////////////////////////////////////////////////////////
//
Francois Gygi committed
3 4 5 6
// Copyright (c) 2008 The Regents of the University of California
//
// This file is part of Qbox
//
Francois Gygi committed
7 8
// Qbox is distributed under the terms of the GNU General Public License
// as published by the Free Software Foundation, either version 2 of
Francois Gygi committed
9 10 11 12 13 14
// the License, or (at your option) any later version.
// See the file COPYING in the root directory of this distribution
// or <http://www.gnu.org/licenses/>.
//
////////////////////////////////////////////////////////////////////////////////
//
Francois Gygi committed
15 16 17 18 19
// UserInterface.C: definition of readCmd and processCmds
//
////////////////////////////////////////////////////////////////////////////////

#include "UserInterface.h"
20
#include "qbox_xmlns.h"
Francois Gygi committed
21 22 23
#include <string>
#include <list>
#include <fstream>
Francois Gygi committed
24
#include <unistd.h> // sync()
25 26
#include <sys/types.h>
#include <sys/stat.h>
Francois Gygi committed
27
#include <mpi.h>
28 29 30
using namespace std;

////////////////////////////////////////////////////////////////////////////////
31
void wait_for_no_file(const string& lockfilename)
32
{
33
  cerr << " waiting for no " << lockfilename << endl;
34 35 36 37 38
  struct stat statbuf;
  int status;
  do
  {
    // stat returns 0 if the file exists
39 40 41
    status = stat(lockfilename.c_str(),&statbuf);
    usleep(100000);
    // cerr << ".";
42 43 44
  }
  while ( status == 0 );
}
Francois Gygi committed
45 46 47 48

////////////////////////////////////////////////////////////////////////////////
UserInterface::UserInterface(void) : terminate_(false)
{
49
  int mype = 0;
Francois Gygi committed
50 51 52 53
  MPI_Comm_rank(MPI_COMM_WORLD,&mype);
  onpe0_ = ( mype == 0 );
}

Francois Gygi committed
54
////////////////////////////////////////////////////////////////////////////////
Francois Gygi committed
55
UserInterface::~UserInterface(void)
Francois Gygi committed
56 57 58 59 60 61 62 63 64
{
  std::list<Cmd*>::iterator cmd;
  for ( cmd = cmdlist.begin(); cmd != cmdlist.end(); cmd++ )
    delete *cmd;
  std::list<Var*>::iterator var;
  for ( var = varlist.begin(); var != varlist.end(); var++ )
    delete *var;
}

Francois Gygi committed
65
////////////////////////////////////////////////////////////////////////////////
66
int UserInterface::readCmd(char *s, int max, istream &fp, bool echo)
Francois Gygi committed
67 68
{
  int ch, i = 0;
Francois Gygi committed
69
  while ( (ch = fp.get()) != EOF &&  !( ch == '\n' || ch ==';' || ch == '#') )
70
  {
Francois Gygi committed
71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87
    if ( ch == '\\' ) // line continuation character
    {
      // check if backslash is followed by a newline
      ch = fp.get();
      if ( ch == '\n' )
      {
        // backslash followed by newline, do nothing
      }
      else
      {
        // backslash not followed by newline
        if ( i < max - 1 )
          s[i++] = '\\';
        if ( i < max - 1 )
          s[i++] = ch;
      }
    }
Francois Gygi committed
88 89
    if (i < max - 1)
      s[i++] = ch;
Francois Gygi committed
90
  }
91

Francois Gygi committed
92 93
  if (max > 0) s[i] = '\0';  /* add terminating NULL */

94
  if ( fp.eof() )
95
    return 0;             /* return 0 for end of file */
96

Francois Gygi committed
97
  // output command line if reading from a script
Francois Gygi committed
98
  if ( echo && i > 0 ) cout << "<cmd>" << s << "</cmd>" << endl;
99

Francois Gygi committed
100 101
  if ( ch == '#' )
  {
102
    if ( echo ) cout << "<cmd>#";
Francois Gygi committed
103 104 105 106
    while ( (ch = fp.get()) != EOF && !( ch == '\n' ) )
    {
      if ( echo ) cout << (char) ch;
    }
Francois Gygi committed
107
    if ( echo && ch=='\n' ) cout << "</cmd>" << endl;
Francois Gygi committed
108
    if ( !(ch == '\n') )
109
      return 0;             /* return 0 for end of file */
Francois Gygi committed
110
  }
111

112
  return 1; // a command was read
Francois Gygi committed
113 114 115
}

////////////////////////////////////////////////////////////////////////////////
116 117
void UserInterface::processCmds ( istream &cmdstream, const char *prompt,
  bool echo)
Francois Gygi committed
118
{
119 120 121 122
#if DEBUG
  cout << "UserInterface::processCmds: prompt="
       << prompt << " echo=" << echo << endl;
#endif
123
  // read and process commands from cmdstream until done
Francois Gygi committed
124 125
  const int cmdlinemax = 1024;
  char cmdline[cmdlinemax];
Francois Gygi committed
126 127
  list<Cmd*>::iterator cmd;
  const char *separators = " ;\t";
Francois Gygi committed
128
  int done=0,cmd_read,status;
129

Francois Gygi committed
130
  if ( onpe0_ )
131
    cout << prompt << " ";
132

133 134 135 136
  while ( !done )
  {
    if ( onpe0_ )
    {
Francois Gygi committed
137 138
      for ( int i = 0; i < cmdlinemax; i++ )
        cmdline[i] = '\0';
139
      // readCmd returns 1 if a command is read, 0 if at EOF
Francois Gygi committed
140
      cmd_read = readCmd(cmdline, cmdlinemax, cmdstream, echo );
141 142
      done = !cmd_read;
    }
Francois Gygi committed
143
    MPI_Bcast(&cmdline[0],cmdlinemax,MPI_CHAR,0,MPI_COMM_WORLD);
144 145 146 147 148 149 150 151
    MPI_Bcast(&cmd_read,1,MPI_INT,0,MPI_COMM_WORLD);

    if ( cmd_read )
    {
      // cmdline contains a string of tokens terminated by '\0'
      // cout << " command line is: " << cmdline << endl;

      // comment lines: start with '#'
Francois Gygi committed
152
      int i;
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
      if ( cmdline[i=strspn(cmdline," ")] == '#' )
      {
        // cout << " comment line" << endl;
        // do nothing, not even write prompt
      }
      else if ( cmdline[i=strspn(cmdline," ")] == '!' )
      {
        // shell escape commands start with '!'
        // cout << " shell escape" << endl;
        if ( onpe0_ )
        {
          system ( &cmdline[i+1] );
          cout << prompt << " ";
        }
      }
      else
      {
        // cout << " command split in the following tokens:" << endl;

        // scan tokens and build argument list
        list<char*> arglist;
        int ntok = 0;
        char* tok = strtok(cmdline, separators);
        while ( tok != 0 )
        {
          arglist.push_back(tok);
          ntok++;
          // cout << "\"" << tok << "\"" << endl;
          tok = strtok(0,separators);
        }
        // cout << " total of " << ntok << " tokens" << endl;
        // arglist.dump();

        // build ac and av
        int ac = ntok;
        char **av = new char *[ntok+1];
        i = 0;
        list<char*>::iterator iarg = arglist.begin();
        while ( iarg != arglist.end() )
        {
          av[i++] = *iarg++;
        }
        av[ntok] = 0;

        // write arguments
        // for ( i = 0; i < ac; i++ )
        // {
        //   cout << av[i] << endl;
        // }

        // search cmdlist for command

        tok = av[0];

        // check for empty command line
        if ( tok != 0 )
        {
          Cmd *cmdptr = findCmd(tok);

          if ( cmdptr )
          {
            MPI_Barrier(MPI_COMM_WORLD);
#if DEBUG
            cout << " execute command " << cmdptr->name() << endl;
#endif
            cmdptr->action(ac,av);
            MPI_Barrier(MPI_COMM_WORLD);
#if DEBUG
221
            cout << " command completed " << cmdptr->name() << endl;
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 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292
#endif
          }
          else
          {
            // command is not in the command list, check for script files
            ifstream cmdstr;
            if ( onpe0_ )
            {
              cmdstr.open(av[0],ios::in);
              status = !cmdstr;
            }
            MPI_Bcast(&status,1,MPI_INT,0,MPI_COMM_WORLD);
            if ( !status )
            {
              // create new prompt in the form: prompt<filename>
              char *newprompt=0;
              if ( onpe0_ )
              {
                newprompt = new char[strlen(prompt)+strlen(av[0])+4];
                newprompt = strcpy(newprompt,prompt);
                newprompt = strcat(newprompt,"[");
                newprompt = strcat(newprompt,av[0]);
                newprompt = strcat(newprompt,"]");
              }
                // MPI: process commands on all processes.
                // Note: newprompt is 0 on all processes > 0
                // Note: echo == true for scripts
              processCmds (cmdstr, newprompt, true);
              if ( onpe0_ )
                delete newprompt;
            }
            else
            {
              if ( onpe0_ )
                cout << " No such command or file name: " << tok << endl;
            }
          }
        }
        delete [] av;
        if ( onpe0_ )
          cout << prompt << " ";
      }
    }
    else // if cmd_read
    {
      // found eof
      done = true;
    }

    if ( onpe0_ )
      done |= terminate_;
    MPI_Bcast(&done,1,MPI_INT,0,MPI_COMM_WORLD);
  }

  if ( onpe0_ )
    cout << " End of command stream " << endl;
}

////////////////////////////////////////////////////////////////////////////////
void UserInterface::processCmdsServer ( string inputfilename,
  string outputfilename, const char *prompt, bool echo)
{
  // process commands in server mode
  // read commands from inputfilename
  // redirect cout to outputfilename

  char cmdline[256];
  list<Cmd*>::iterator cmd;
  const char *separators = " ;\t";
  int i,done=0,cmd_read,status;

293
  string lockfilename = inputfilename + ".lock";
294 295 296 297 298 299 300 301

  // in server mode, redirect output to stream qbout
  streambuf *qbout_buf;
  streambuf *cout_buf;
  ofstream qbout;
  ifstream qbin;

  ofstream tstfile;
302

Francois Gygi committed
303 304
  while ( !done )
  {
305
    if ( onpe0_ )
Francois Gygi committed
306
    {
307 308
      // create file to signal that Qbox is waiting for a command on qbin
      tstfile.open(lockfilename.c_str());
309 310 311
      tstfile << "1" << endl;
      tstfile.close();
      sync();
312
      // wait for tstfile to be removed by the driver
313
      usleep(100000);
314
      wait_for_no_file(lockfilename.c_str());
315 316 317 318

      qbin.open(inputfilename.c_str());
      qbin.sync();
      qbin.clear();
319 320 321 322 323 324 325 326 327 328 329

      // save copy of cout streambuf
      cout_buf = cout.rdbuf();
      qbout.open(outputfilename.c_str(),ios_base::trunc);
      qbout_buf = qbout.rdbuf();
      // redirect cout
      cout.rdbuf(qbout_buf);
      cerr << " processCmdsServer: cout streambuf redirected" << endl;

      cout << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" << endl;
      cout << "<fpmd:simulation xmlns:fpmd=\"" << qbox_xmlns() << "\">" << endl;
Francois Gygi committed
330
    }
331

332
    do
Francois Gygi committed
333 334 335
    {
      if ( onpe0_ )
      {
336 337
        // readCmd returns 1 if a command is read, 0 if at EOF
        cmd_read = readCmd(cmdline, 256, qbin, echo );
338
        cout << prompt << " " << cmdline << endl;
Francois Gygi committed
339
      }
340 341
      MPI_Bcast(&cmdline[0],256,MPI_CHAR,0,MPI_COMM_WORLD);
      MPI_Bcast(&cmd_read,1,MPI_INT,0,MPI_COMM_WORLD);
Francois Gygi committed
342

343
      if ( cmd_read )
Francois Gygi committed
344
      {
345 346
        if ( onpe0_ )
        {
347 348
          cerr << " read cmd: " << cmdline << endl;
          cerr << " executing ... ";
349 350
        }

351 352 353 354 355
        // cmdline contains a string of tokens terminated by '\0'
        // cout << " command line is: " << cmdline << endl;

        // comment lines: start with '#'
        if ( cmdline[i=strspn(cmdline," ")] == '#' )
356
        {
357 358
          // cout << " comment line" << endl;
          // do nothing, not even write prompt
359
        }
360
        else if ( cmdline[i=strspn(cmdline," ")] == '!' )
361
        {
362 363 364 365 366 367 368
          // shell escape commands start with '!'
          // cout << " shell escape" << endl;
          if ( onpe0_ )
          {
            system ( &cmdline[i+1] );
            // cout << prompt << " ";
          }
369
        }
370 371 372
        else
        {
          // cout << " command split in the following tokens:" << endl;
Francois Gygi committed
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
          // scan tokens and build argument list
          list<char*> arglist;
          int ntok = 0;
          char* tok = strtok(cmdline, separators);
          while ( tok != 0 )
          {
            arglist.push_back(tok);
            ntok++;
            // cout << "\"" << tok << "\"" << endl;
            tok = strtok(0,separators);
          }
          // cout << " total of " << ntok << " tokens" << endl;
          // arglist.dump();

          // build ac and av
          int ac = ntok;
          char **av = new char *[ntok+1];
          i = 0;
          list<char*>::iterator iarg = arglist.begin();
          while ( iarg != arglist.end() )
          {
            av[i++] = *iarg++;
          }
          av[ntok] = 0;
Francois Gygi committed
398

399 400 401 402 403
          // write arguments
          // for ( i = 0; i < ac; i++ )
          // {
          //   cout << av[i] << endl;
          // }
Francois Gygi committed
404

405
          // search cmdlist for command
Francois Gygi committed
406

407
          tok = av[0];
408

409 410
          // check for empty command line
          if ( tok != 0 )
411
          {
412 413 414 415 416
            Cmd *cmdptr = findCmd(tok);

            if ( cmdptr )
            {
              MPI_Barrier(MPI_COMM_WORLD);
Francois Gygi committed
417
#if DEBUG
418
              cerr << " execute command " << cmdptr->name() << endl;
Francois Gygi committed
419
#endif
420 421
              cmdptr->action(ac,av);
              MPI_Barrier(MPI_COMM_WORLD);
Francois Gygi committed
422
#if DEBUG
423
              cerr << " command completed " << cmdptr->name() << endl;
Francois Gygi committed
424
#endif
425
            }
426
            else
427
            {
428 429
              // command is not in the command list, check for script files
              ifstream cmdstr;
430 431
              if ( onpe0_ )
              {
432 433
                cmdstr.open(av[0],ios::in);
                status = !cmdstr;
434
              }
435 436 437 438 439 440 441 442 443 444 445 446 447
              MPI_Bcast(&status,1,MPI_INT,0,MPI_COMM_WORLD);
              if ( !status )
              {
                // create new prompt in the form: prompt<filename>
                char *newprompt=0;
                if ( onpe0_ )
                {
                  newprompt = new char[strlen(prompt)+strlen(av[0])+4];
                  newprompt = strcpy(newprompt,prompt);
                  newprompt = strcat(newprompt,"[");
                  newprompt = strcat(newprompt,av[0]);
                  newprompt = strcat(newprompt,"]");
                }
448 449 450
                // MPI: process commands on all processes.
                // Note: newprompt is 0 on all processes > 0
                // Note: echo == true for scripts
451 452 453 454 455 456 457 458 459
                processCmds (cmdstr, newprompt, true);
                if ( onpe0_ )
                  delete newprompt;
              }
              else
              {
                if ( onpe0_ )
                  cout << " No such command or file name: " << tok << endl;
              }
Francois Gygi committed
460 461
            }
          }
462
          delete [] av;
Francois Gygi committed
463
        }
464

465
        if ( onpe0_ )
466 467 468 469 470 471 472 473 474 475 476 477
          cerr << "done" << endl;

        // check if terminate_ flag was set during command execution
        if ( onpe0_ )
          done = terminate_;
        MPI_Bcast(&done,1,MPI_INT,0,MPI_COMM_WORLD);

      } // if cmd_read

    } while ( !done && cmd_read );

    if ( onpe0_ )
Francois Gygi committed
478
    {
479 480 481 482 483 484 485
      qbin.close();
      cout << " End of command stream " << endl;
      cout << "</fpmd:simulation>" << endl;
      cout.flush();
      qbout.close();
      cout.rdbuf(cout_buf);
      cerr << " processCmdsServer: cout streambuf reassigned" << endl;
Francois Gygi committed
486 487
    }

488 489
    // wait before retrying
    usleep(200000);
490

491
  } // while !done
492 493 494

  if ( onpe0_ )
  {
495 496
    // remove lock file
    remove(lockfilename.c_str());
497 498
    sync();
  }
Francois Gygi committed
499
}