FreeFOAM The Cross-Platform CFD Toolkit
argList.C
Go to the documentation of this file.
1 /*---------------------------------------------------------------------------*\
2  ========= |
3  \\ / F ield | OpenFOAM: The Open Source CFD Toolbox
4  \\ / O peration |
5  \\ / A nd | Copyright (C) 1991-2010 OpenCFD Ltd.
6  \\/ M anipulation |
7 -------------------------------------------------------------------------------
8 License
9  This file is part of OpenFOAM.
10 
11  OpenFOAM is free software: you can redistribute it and/or modify it
12  under the terms of the GNU General Public License as published by
13  the Free Software Foundation, either version 3 of the License, or
14  (at your option) any later version.
15 
16  OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
17  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
18  FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
19  for more details.
20 
21  You should have received a copy of the GNU General Public License
22  along with OpenFOAM. If not, see <http://www.gnu.org/licenses/>.
23 
24 \*---------------------------------------------------------------------------*/
25 
26 #include "argList.H"
27 #include <OpenFOAM/OSspecific.H>
28 #include <OpenFOAM/clock.H>
29 #include <OpenFOAM/IFstream.H>
30 #include <OpenFOAM/dictionary.H>
31 #include <OpenFOAM/Switch.H>
32 #include <OpenFOAM/IOobject.H>
33 #include <OpenFOAM/JobInfo.H>
34 #include <OpenFOAM/labelList.H>
35 #include <OpenFOAM/ListOps.H>
36 
37 
38 // * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
39 
43 bool Foam::argList::bannerEnabled(true);
44 
45 
46 Foam::argList::initValidTables::initValidTables()
47 {
48  validOptions.set("case", "dir");
49  validOptions.set("parallel", "");
50  validParOptions.set("parallel", "");
51 
52  Pstream::addValidParOptions(validParOptions);
53 }
54 
55 
56 Foam::argList::initValidTables dummyInitValidTables;
57 
58 
59 // convert argv -> args_
60 // transform sequences with "(" ... ")" into string lists in the process
61 bool Foam::argList::regroupArgv(int& argc, char**& argv)
62 {
63  int nArgs = 0;
64  int listDepth = 0;
65  string tmpString;
66 
67  // note: we also re-write directly into args_
68  // and use a second pass to sort out args/options
69  for (int argI = 0; argI < argc; argI++)
70  {
71  if (strcmp(argv[argI], "(") == 0)
72  {
73  listDepth++;
74  tmpString += "(";
75  }
76  else if (strcmp(argv[argI], ")") == 0)
77  {
78  if (listDepth)
79  {
80  listDepth--;
81  tmpString += ")";
82  if (listDepth == 0)
83  {
84  args_[nArgs++] = tmpString;
85  tmpString.clear();
86  }
87  }
88  else
89  {
90  args_[nArgs++] = argv[argI];
91  }
92  }
93  else if (listDepth)
94  {
95  // quote each string element
96  tmpString += "\"";
97  tmpString += argv[argI];
98  tmpString += "\"";
99  }
100  else
101  {
102  args_[nArgs++] = argv[argI];
103  }
104  }
105 
106  if (tmpString.size())
107  {
108  args_[nArgs++] = tmpString;
109  }
110 
111  args_.setSize(nArgs);
112 
113  return nArgs < argc;
114 }
115 
116 
117 // get rootPath_ / globalCase_ from one of the following forms
118 // * [-case dir]
119 // * cwd
120 //
121 // Also export FOAM_CASE and FOAM_CASENAME environment variables
122 // so they can be used immediately (eg, in decomposeParDict)
123 //
124 void Foam::argList::getRootCase()
125 {
126  fileName casePath;
127 
128  // [-case dir] specified
129  HashTable<string>::iterator iter = options_.find("case");
130 
131  if (iter != options_.end())
132  {
133  casePath = iter();
134  casePath.clean();
135 
136  if (casePath.empty() || casePath == ".")
137  {
138  // handle degenerate form and '-case .' like no -case specified
139  casePath = cwd();
140  options_.erase("case");
141  }
142  else if (casePath[0] != '/' && casePath.name() == "..")
143  {
144  // avoid relative cases ending in '..' - makes for very ugly names
145  casePath = cwd()/casePath;
146  casePath.clean();
147  }
148  }
149  else
150  {
151  // nothing specified, use the current dir
152  casePath = cwd();
153  }
154 
155  rootPath_ = casePath.path();
156  globalCase_ = casePath.name();
157  case_ = globalCase_;
158 
159 
160  // Set the case and case-name as an environment variable
161  if (rootPath_[0] == '/')
162  {
163  // absolute path - use as-is
164  setEnv("FOAM_CASE", rootPath_/globalCase_, true);
165  setEnv("FOAM_CASENAME", globalCase_, true);
166  }
167  else
168  {
169  // qualify relative path
170  fileName casePath = cwd()/rootPath_/globalCase_;
171  casePath.clean();
172 
173  setEnv("FOAM_CASE", casePath, true);
174  setEnv("FOAM_CASENAME", casePath.name(), true);
175  }
176 
177 
178 }
179 
180 
182 {
183  return stringList::subList(args_, args_.size() - 1, 1);
184 }
185 
186 
187 // * * * * * * * * * * * * * * * * Constructors * * * * * * * * * * * * * * //
188 
190 (
191  int& argc,
192  char**& argv,
193  bool checkArgs,
194  bool checkOpts
195 )
196 :
197  args_(argc),
198  options_(argc)
199 {
200  // Check if this run is a parallel run by searching for any parallel option
201  // If found call runPar (might filter argv)
202  for (int argI = 0; argI < argc; argI++)
203  {
204  if (argv[argI][0] == '-')
205  {
206  const char *optionName = &argv[argI][1];
207 
208  if (validParOptions.found(optionName))
209  {
210  parRunControl_.runPar(argc, argv);
211  break;
212  }
213  }
214  }
215 
216  // convert argv -> args_ and capture ( ... ) lists
217  // for normal arguments and for options
218  regroupArgv(argc, argv);
219 
220  // Get executable name
221  args_[0] = fileName(argv[0]);
222  executable_ = fileName(argv[0]).name();
223 
224  // Check arguments and options, we already have argv[0]
225  int nArgs = 1;
226  string argListString = args_[0];
227 
228  for (int argI = 1; argI < args_.size(); argI++)
229  {
230  argListString += ' ';
231  argListString += args_[argI];
232 
233  if (args_[argI][0] == '-')
234  {
235  const char *optionName = &args_[argI][1];
236 
237  if
238  (
239  (
240  validOptions.found(optionName)
241  && validOptions[optionName] != ""
242  )
243  || (
244  validParOptions.found(optionName)
245  && validParOptions[optionName] != ""
246  )
247  )
248  {
249  argI++;
250  if (argI >= args_.size())
251  {
252  FatalError
253  << "option " << "'-" << optionName << '\''
254  << " requires an argument"
255  << exit(FatalError);
256  }
257 
258  argListString += ' ';
259  argListString += args_[argI];
260  options_.insert(optionName, args_[argI]);
261  }
262  else
263  {
264  options_.insert(optionName, "");
265  }
266  }
267  else
268  {
269  if (nArgs != argI)
270  {
271  args_[nArgs] = args_[argI];
272  }
273  nArgs++;
274  }
275  }
276 
277  args_.setSize(nArgs);
278 
279  // Help/documentation options:
280  // -help print the usage
281  // -doc display application documentation in browser
282  // -srcDoc display source code in browser
283  if
284  (
285  options_.found("help")
286  || options_.found("doc")
287  || options_.found("srcDoc")
288  )
289  {
290  if (options_.found("help"))
291  {
292  printUsage();
293  }
294 
295  // only display one or the other
296  if (options_.found("srcDoc"))
297  {
298  displayDoc(true);
299  }
300  else if (options_.found("doc"))
301  {
302  displayDoc(false);
303  }
304 
305  ::exit(0);
306  }
307 
308  // Print the usage message and exit if the number of arguments is incorrect
309  if (!check(checkArgs, checkOpts))
310  {
311  FatalError.exit();
312  }
313 
314 
315  string dateString = clock::date();
316  string timeString = clock::clockTime();
317 
318  // Print the banner once only for parallel runs
319  if (Pstream::master() && bannerEnabled)
320  {
322  << "Build : " << Foam::FOAMbuild << nl
323  << "Exec : " << argListString.c_str() << nl
324  << "Date : " << dateString.c_str() << nl
325  << "Time : " << timeString.c_str() << nl
326  << "Host : " << hostName() << nl
327  << "PID : " << pid() << endl;
328  }
329 
330  jobInfo.add("startDate", dateString);
331  jobInfo.add("startTime", timeString);
332  jobInfo.add("userName", userName());
333  jobInfo.add("foamVersion", word(FOAMfullVersion));
334  jobInfo.add("foamBuild", Foam::FOAMbuild);
335  jobInfo.add("foamUpstreamVersion", word(FOAMupstreamVersion));
336  jobInfo.add("code", executable_);
337  jobInfo.add("argList", argListString);
338  jobInfo.add("currentDir", cwd());
339  jobInfo.add("PPID", ppid());
340  jobInfo.add("PGID", pgid());
341 
342 
343  // Case is a single processor run unless it is running parallel
344  int nProcs = 1;
345 
346  // If this actually is a parallel run
347  if (parRunControl_.parRun())
348  {
349  // For the master
350  if (Pstream::master())
351  {
352  // establish rootPath_/globalCase_/case_ for master
353  getRootCase();
354 
355  IFstream decompDictStream
356  (
357  rootPath_/globalCase_/"system/decomposeParDict"
358  );
359 
360  if (!decompDictStream.good())
361  {
362  FatalError
363  << "Cannot read "
364  << decompDictStream.name()
365  << exit(FatalError);
366  }
367 
368  dictionary decompDict(decompDictStream);
369 
370  label dictNProcs
371  (
372  readLabel
373  (
374  decompDict.lookup("numberOfSubdomains")
375  )
376  );
377 
378  // Check number of processors.
379  // nProcs => number of actual procs
380  // dictNProcs => number of procs specified in decompositionDict
381  // nProcDirs => number of processor directories
382  // (n/a when running distributed)
383  //
384  // - normal running : nProcs = dictNProcs = nProcDirs
385  // - decomposition to more processors : nProcs = dictNProcs
386  // - decomposition to fewer processors : nProcs = nProcDirs
387  if (dictNProcs > Pstream::nProcs())
388  {
389  FatalError
390  << decompDictStream.name()
391  << " specifies " << dictNProcs
392  << " processors but job was started with "
393  << Pstream::nProcs() << " processors."
394  << exit(FatalError);
395  }
396 
397  // distributed data
398  if (decompDict.lookupOrDefault<Switch>("distributed", false))
399  {
400  fileNameList roots;
401  decompDict.lookup("roots") >> roots;
402 
403  if (roots.size() != Pstream::nProcs()-1)
404  {
405  FatalError
406  << "number of entries in decompositionDict::roots"
407  << " is not equal to the number of slaves "
408  << Pstream::nProcs()-1
409  << exit(FatalError);
410  }
411 
412  // Distribute the master's argument list (with new root)
413  bool hadCaseOpt = options_.found("case");
414  for
415  (
416  int slave=Pstream::firstSlave();
417  slave<=Pstream::lastSlave();
418  slave++
419  )
420  {
421  options_.set
422  (
423  "case",
424  fileName(roots[slave-1])/globalCase_
425  );
426 
427  OPstream toSlave(Pstream::scheduled, slave);
428  toSlave << args_ << options_;
429  }
430  options_.erase("case");
431 
432  // restore [-case dir]
433  if (hadCaseOpt)
434  {
435  options_.set("case", rootPath_/globalCase_);
436  }
437  }
438  else
439  {
440  // Possibly going to fewer processors.
441  // Check if all procDirs are there.
442  if (dictNProcs < Pstream::nProcs())
443  {
444  label nProcDirs = 0;
445  while
446  (
447  isDir
448  (
449  rootPath_/globalCase_/"processor"
450  + name(++nProcDirs)
451  )
452  )
453  {}
454 
455  if (nProcDirs != Pstream::nProcs())
456  {
457  FatalError
458  << "number of processor directories = "
459  << nProcDirs
460  << " is not equal to the number of processors = "
461  << Pstream::nProcs()
462  << exit(FatalError);
463  }
464  }
465 
466  // Distribute the master's argument list (unaltered)
467  for
468  (
469  int slave=Pstream::firstSlave();
470  slave<=Pstream::lastSlave();
471  slave++
472  )
473  {
474  OPstream toSlave(Pstream::scheduled, slave);
475  toSlave << args_ << options_;
476  }
477  }
478  }
479  else
480  {
481  // Collect the master's argument list
483  fromMaster >> args_ >> options_;
484 
485  // establish rootPath_/globalCase_/case_ for slave
486  getRootCase();
487  }
488 
489  nProcs = Pstream::nProcs();
490  case_ = globalCase_/(word("processor") + name(Pstream::myProcNo()));
491  }
492  else
493  {
494  // establish rootPath_/globalCase_/case_
495  getRootCase();
496  case_ = globalCase_;
497  }
498 
499 
500  wordList slaveProcs;
501 
502  // collect slave machine/pid
503  if (parRunControl_.parRun())
504  {
505  if (Pstream::master())
506  {
507  slaveProcs.setSize(Pstream::nProcs() - 1);
508  word slaveMachine;
509  label slavePid;
510 
511  label procI = 0;
512  for
513  (
514  int slave=Pstream::firstSlave();
515  slave<=Pstream::lastSlave();
516  slave++
517  )
518  {
519  IPstream fromSlave(Pstream::scheduled, slave);
520  fromSlave >> slaveMachine >> slavePid;
521 
522  slaveProcs[procI++] = slaveMachine + "." + name(slavePid);
523  }
524  }
525  else
526  {
528  toMaster << hostName() << pid();
529  }
530  }
531 
532 
533  if (Pstream::master() && bannerEnabled)
534  {
535  Info<< "Case : " << (rootPath_/globalCase_).c_str() << nl
536  << "nProcs : " << nProcs << endl;
537 
538  if (parRunControl_.parRun())
539  {
540  Info<< "Slaves : " << slaveProcs << nl
541  << "Pstream initialized with:" << nl
542  << " floatTransfer : " << Pstream::floatTransfer << nl
543  << " nProcsSimpleSum : " << Pstream::nProcsSimpleSum << nl
544  << " commsType : "
546  << endl;
547  }
548  }
549 
550  jobInfo.add("root", rootPath_);
551  jobInfo.add("case", globalCase_);
552  jobInfo.add("nProcs", nProcs);
553  if (slaveProcs.size())
554  {
555  jobInfo.add("slaves", slaveProcs);
556  }
557  jobInfo.write();
558 
559  // Switch on signal trapping. We have to wait until after Pstream::init
560  // since this sets up its own ones.
561  sigFpe_.set(bannerEnabled);
562  sigInt_.set(bannerEnabled);
563  sigQuit_.set(bannerEnabled);
564  sigSegv_.set(bannerEnabled);
565 
566  if (Pstream::master() && bannerEnabled)
567  {
568  Info<< endl;
570  }
571 }
572 
573 
574 // * * * * * * * * * * * * * * * * Destructors * * * * * * * * * * * * * * * //
575 
577 {
578  jobInfo.end();
579 }
580 
581 
582 // * * * * * * * * * * * * * * * Member Functions * * * * * * * * * * * * * //
583 
585 {
586  bannerEnabled = false;
587 }
588 
589 
591 {
592  validOptions.erase("parallel");
593 }
594 
595 
597 {
598  Info<< nl
599  << "Usage: " << executable_;
600 
601  for
602  (
603  SLList<string>::iterator iter = validArgs.begin();
604  iter != validArgs.end();
605  ++iter
606  )
607  {
608  Info<< " <" << iter().c_str() << '>';
609  }
610 
611  for
612  (
613  HashTable<string>::iterator iter = validOptions.begin();
614  iter != validOptions.end();
615  ++iter
616  )
617  {
618  Info<< " [-" << iter.key();
619 
620  if (iter().size())
621  {
622  Info<< ' ' << iter().c_str();
623  }
624 
625  Info<< ']';
626  }
627 
628  // place help/doc/srcDoc options of the way at the end,
629  // but with an extra space to separate it a little
630  Info<< " [-help] [-doc] [-srcDoc]\n" << endl;
631 }
632 
633 
634 void Foam::argList::displayDoc(bool source) const
635 {
636  const dictionary& docDict = debug::controlDict().subDict("Documentation");
637  // List of doxygen documentation file indices
638  List<fileName> docIndexFiles(docDict.lookup("doxyDocIndices"));
639 
640  bool found = false;
641  fileName docFile;
642 
643  forAll(docIndexFiles,idxI)
644  {
645  IFstream indexFile(docIndexFiles[idxI]);
646 
647  if (!indexFile.good())
648  {
649  WarningIn("Foam::argList::displayDoc(bool)")
650  << "Cannot open documentation index file " << docIndexFiles[idxI]
651  << endl;
652  }
653  else
654  {
655  // the dictionary
656  dictionary indexDict(indexFile);
657  if (indexDict.found("docDir") && indexDict.found("docFiles"))
658  {
659  fileName docDir;
660  dictionary docFiles;
661  // the documentation directory
662  indexDict.lookup("docDir") >> docDir;
663  // read the docFiles dictionary
664  docFiles = indexDict.subDict("docFiles");
665  // prefix docDir to all doc files
666  if (docFiles.found(executable_))
667  {
668  found = true;
669  docFile = docDir / FixedList<fileName,2>( docFiles.lookup(executable_) )[label(source)];
670  }
671  }
672  else
673  {
674  WarningIn("Foam::argList::displayDoc(bool)")
675  << "The file " << docIndexFiles[idxI]
676  << "does not contain one or both of the entries"
677  << "\"docDir\" and \"docFiles\"."
678  << endl;
679  }
680  }
681  }
682 
683  if (found)
684  {
685  string docBrowser(docDict.lookup("docBrowser"));
686  if (docBrowser != "ECHO")
687  {
688  docBrowser.replaceAll("%f", docFile);
689 
690  Info<< "Show documentation: " << docBrowser.c_str() << endl;
691 
692  system(docBrowser);
693  }
694  else
695  {
696  Info<< "Documentation available at: " << docFile << endl;
697  }
698  }
699  else
700  {
701  Info<< nl
702  << "No documentation found for " << executable_
703  << ", but you can use -help to display the usage\n" << endl;
704  }
705 }
706 
707 
708 bool Foam::argList::check(bool checkArgs, bool checkOpts) const
709 {
710  bool ok = true;
711 
712  if (Pstream::master())
713  {
714  if (checkArgs && args_.size() - 1 != validArgs.size())
715  {
716  FatalError
717  << "Wrong number of arguments, expected " << validArgs.size()
718  << " found " << args_.size() - 1 << endl;
719  ok = false;
720  }
721 
722  if (checkOpts)
723  {
724  forAllConstIter(HashTable<string>, options_, iter)
725  {
726  if
727  (
728  !validOptions.found(iter.key())
729  && !validParOptions.found(iter.key())
730  )
731  {
732  FatalError
733  << "Invalid option: -" << iter.key() << endl;
734  ok = false;
735  }
736  }
737  }
738 
739  if (!ok)
740  {
741  printUsage();
742  }
743  }
744 
745  return ok;
746 }
747 
748 
750 {
751  if (!isDir(rootPath()))
752  {
753  FatalError
754  << executable_
755  << ": cannot open root directory " << rootPath()
756  << endl;
757 
758  return false;
759  }
760 
761  if (!isDir(path()) && Pstream::master())
762  {
763  // Allow slaves on non-existing processor directories, created later
764  FatalError
765  << executable_
766  << ": cannot open case directory " << path()
767  << endl;
768 
769  return false;
770  }
771 
772  return true;
773 }
774 
775 
776 // ************************ vim: set sw=4 sts=4 et: ************************ //