1/* Copyright (C) 2021 Free Software Foundation, Inc.
2   Contributed by Oracle.
3
4   This file is part of GNU Binutils.
5
6   This program is free software; you can redistribute it and/or modify
7   it under the terms of the GNU General Public License as published by
8   the Free Software Foundation; either version 3, or (at your option)
9   any later version.
10
11   This program is distributed in the hope that it will be useful,
12   but WITHOUT ANY WARRANTY; without even the implied warranty of
13   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14   GNU General Public License for more details.
15
16   You should have received a copy of the GNU General Public License
17   along with this program; if not, write to the Free Software
18   Foundation, 51 Franklin Street - Fifth Floor, Boston,
19   MA 02110-1301, USA.  */
20
21#include "config.h"
22#include <ctype.h>
23#include <errno.h>
24#include <unistd.h>
25#include <getopt.h>
26
27#include "util.h"
28#include "StringMap.h"
29#include "LoadObject.h"
30#include "DbeSession.h"
31#include "DbeFile.h"
32#include "SourceFile.h"
33#include "Elf.h"
34#include "gp-archive.h"
35#include "ArchiveExp.h"
36#include "Print.h"
37#include "Module.h"
38
39er_archive::er_archive (int argc, char *argv[]) : DbeApplication (argc, argv)
40{
41  force = 0;
42  common_archive_dir = NULL;
43  quiet = 0;
44  descendant = 1;
45  use_relative_path = 0;
46  s_option = ARCH_EXE_ONLY;
47  mask = NULL;
48}
49
50er_archive::~er_archive ()
51{
52  if (mask)
53    {
54      for (long i = 0, sz = mask->size (); i < sz; i++)
55	{
56	  regex_t *regex_desc = mask->get (i);
57	  regfree (regex_desc);
58	  delete regex_desc;
59	}
60      delete mask;
61    }
62  delete common_archive_dir;
63}
64
65int
66er_archive::mask_is_on (const char *str)
67{
68  if (mask == NULL)
69    return 1;
70  for (long i = 0, sz = mask->size (); i < sz; i++)
71    {
72      regex_t *regex_desc = mask->get (i);
73      if (regexec (regex_desc, str, 0, NULL, 0) == 0)
74	return 1;
75    }
76  return 0;
77}
78
79void
80er_archive::usage ()
81{
82/*
83  fprintf (stderr, GTXT ("Usage: %s [-nqFV] [-a on|ldobjects|src|usedldobjects|usedsrc|off] [-m regexp] experiment\n"), whoami);
84*/
85
86/*
87  Ruud - Isolate this line because it has an argument.  Otherwise it would be at the
88  end of this long list.
89*/
90  printf ( GTXT (
91    "Usage: gprofng archive [OPTION(S)] EXPERIMENT\n"));
92
93  printf ( GTXT (
94    "\n"
95    "Archive the associated application binaries and source files in a gprofng\n"
96    "experiment to make it self contained and portable.\n"
97    "\n"
98    "Options:\n"
99    "\n"
100    " --version           print the version number and exit.\n"
101    " --help              print usage information and exit.\n"
102    " --verbose {on|off}  enable (on) or disable (off) verbose mode; the default is \"off\".\n"
103    "\n"
104    " -a {off|on|ldobjects|src|usedldobjects|usedsrc}  specify archiving of binaries and other files;\n"
105    "                    in addition to disable this feature (off), or enable archiving off all\n"
106    "                    loadobjects and sources (on), the other options support a more\n"
107    "                    refined selection. All of these options enable archiving, but the\n"
108    "                    keyword controls what exactly is selected: all load objects (ldobjects),\n"
109    "                    all source files (src), the loadobjects asscoiated with a program counter\n"
110    "                    (usedldobjects), or the source files associated with a program counter\n"
111    "                    (usedsrc); the default is \"-a ldobjects\".\n"
112    "\n"
113    " -n                 archive the named experiment only, not any of its descendants.\n"
114    "\n"
115    " -q                 do not write any warnings to stderr; messages are archived and\n"
116    "                    can be retrieved later.\n"
117    "\n"
118    " -F                 force writing or rewriting of the archive; ignored with the -n\n"
119    "                    or -m options, or if this is a subexperiment.\n"
120    "\n"
121    " -d <path>          specifies the location of a common archive; this is a directory that\n"
122    "                    contains archived files.\n"
123    "\n"
124    " -m <regex>         archive only those source, object, and debug info files whose full\n"
125    "                    path name matches the given POSIX compliant regular expression.\n"
126    "\n"
127    "Limitations:\n"
128    "\n"
129    "Default archiving does not occur in case the application profiled terminates prematurely,\n"
130    "or if archiving is disabled when collecting the performance data. In such cases, this\n"
131    "tool can be used to afterwards archive the information, but it has to run on the same \n"
132    "system where the profiling data was recorded.\n"
133    "\n"
134    "Documentation:\n"
135    "\n"
136    "A getting started guide for gprofng is maintained as a Texinfo manual. If the info and\n"
137    "gprofng programs are properly installed at your site, the command \"info gprofng\"\n"
138    "should give you access to this document.\n"
139    "\n"
140    "See also:\n"
141    "\n"
142    "gprofng(1), gp-collect-app(1), gp-display-html(1), gp-display-src(1), gp-display-text(1)\n"));
143// Ruud
144/*
145  fprintf (stderr, GTXT ("GNU %s version %s\n"), get_basename (prog_name), VERSION);
146*/
147  exit (1);
148}
149
150Vector <LoadObject*> *
151er_archive::get_loadObjs ()
152{
153  Vector <LoadObject*> *objs = new Vector<LoadObject*>();
154  Vector <LoadObject*> *loadObjs = dbeSession->get_text_segments ();
155  if (s_option != ARCH_NOTHING)
156    {
157      for (long i = 0, sz = VecSize(loadObjs); i < sz; i++)
158	{
159	  LoadObject *lo = loadObjs->get (i);
160	  if ((lo->flags & SEG_FLAG_DYNAMIC) != 0)
161	    continue;
162	  DbeFile *df = lo->dbeFile;
163	  if (df && ((df->filetype & DbeFile::F_FICTION) != 0))
164	    continue;
165	  if (!lo->isUsed && ((s_option & (ARCH_USED_EXE_ONLY | ARCH_USED_SRC_ONLY)) != 0))
166	    continue;
167	  objs->append (lo);
168	}
169    }
170  if (DEBUG_ARCHIVE)
171    {
172      Dprintf (DEBUG_ARCHIVE, NTXT ("get_text_segments(): %d\n"),
173	       (int) (loadObjs ? loadObjs->size () : -1));
174      for (long i = 0, sz = loadObjs ? loadObjs->size () : 0; i < sz; i++)
175	{
176	  LoadObject *lo = loadObjs->get (i);
177	  Dprintf (DEBUG_ARCHIVE, NTXT ("%s:%d  [%2ld] %s\n"),
178		   get_basename (__FILE__), (int) __LINE__, i, STR (lo->dump ()));
179	}
180      Dprintf (DEBUG_ARCHIVE, NTXT ("\nget_loadObjs(): %d\n"),
181	       (int) (objs ? objs->size () : -1));
182      for (long i = 0, sz = VecSize(objs); i < sz; i++)
183	{
184	  LoadObject *lo = objs->get (i);
185	  Dprintf (DEBUG_ARCHIVE, NTXT ("%s:%d  [%2ld] %s\n"),
186		   get_basename (__FILE__), (int) __LINE__, i, STR (lo->dump ()));
187	}
188    }
189  delete loadObjs;
190  return objs;
191}
192
193/**
194 * Clean old archive
195 * Except the following cases:
196 * 1. Founder experiment is an MPI experiment
197 * 2. "-n" option is passed (do not archive descendants)
198 * 3. "-m" option is passed (partial archiving)
199 * 4. Experiment name is not the founder experiment (it is a sub-experiment)
200 * @param expname
201 * @param founder_exp
202 * @return 0 - success
203 */
204int
205er_archive::clean_old_archive (char *expname, ArchiveExp *founder_exp)
206{
207  if (0 == descendant)
208    { // do not archive descendants
209      fprintf (stderr, GTXT ("Warning: Option -F is ignored because -n option is specified (do not archive descendants)\n"));
210      return 1;
211    }
212  if (NULL != mask)
213    { // partial archiving
214      fprintf (stderr, GTXT ("Warning: Option -F is ignored because -m option is specified\n"));
215      return 1;
216    }
217  // Check if the experiment is the founder
218  char *s1 = dbe_strdup (expname);
219  char *s2 = dbe_strdup (founder_exp->get_expt_name ());
220  if (!s1 || !s2)
221    {
222      fprintf (stderr, GTXT ("Cannot allocate memory\n"));
223      exit (1);
224    }
225  // remove trailing slashes
226  for (int n = strlen (s1); n > 0; n--)
227    {
228      if ('/' != s1[n - 1])
229	break;
230      s1[n - 1] = 0;
231    }
232  for (int n = strlen (s2); n > 0; n--)
233    {
234      if ('/' != s2[n - 1])
235	break;
236      s2[n - 1] = 0;
237    }
238  if (strcmp (s1, s2) != 0)
239    { // not founder
240      fprintf (stderr, GTXT ("Warning: Option -F is ignored because specified experiment name %s does not match founder experiment name %s\n"), s1, s2);
241      free (s1);
242      free (s2);
243      return 1;
244    }
245  // Remove old "archives"
246  char *arch = founder_exp->get_arch_name ();
247  fprintf (stderr, GTXT ("INFO: removing existing archive: %s\n"), arch);
248  if (dbe_stat (arch, NULL) == 0)
249    {
250      char *cmd = dbe_sprintf ("/bin/rm -rf %s", arch);
251      system (cmd);
252      free (cmd);
253      if (dbe_stat (arch, NULL) != 0)
254	{ // create "archives"
255	  if (!founder_exp->create_dir (founder_exp->get_arch_name ()))
256	    {
257	      fprintf (stderr, GTXT ("Unable to create directory `%s'\n"), founder_exp->get_arch_name ());
258	      exit (1);
259	    }
260	}
261    }
262  free (s1);
263  free (s2);
264  return 0;
265} // clean_old_archive_if_necessary
266
267void
268er_archive::start (int argc, char *argv[])
269{
270  int last = argc - 1;
271  if (check_args (argc, argv) != last)
272    usage ();
273  check_env_var ();
274  if (s_option == ARCH_NOTHING)
275    return;
276
277  ArchiveExp *founder_exp = new ArchiveExp (argv[last]);
278  if (founder_exp->get_status () == Experiment::FAILURE)
279    {
280      if (!quiet)
281	fprintf (stderr, GTXT ("er_archive: %s: %s\n"), argv[last],
282		 pr_mesgs (founder_exp->fetch_errors (), NTXT (""), NTXT ("")));
283      exit (1);
284    }
285  if (!founder_exp->create_dir (founder_exp->get_arch_name ()))
286    {
287      fprintf (stderr, GTXT ("Unable to create directory `%s'\n"), founder_exp->get_arch_name ());
288      exit (1);
289    }
290  if (!common_archive_dir)
291    common_archive_dir = dbe_strdup (getenv ("GPROFNG_ARCHIVE_COMMON_DIR"));
292  if (common_archive_dir)
293    {
294      if (!founder_exp->create_dir (common_archive_dir))
295	if (dbe_stat (common_archive_dir, NULL) != 0)
296	  {
297	    fprintf (stderr, GTXT ("Unable to create directory for common archive `%s'\n"), common_archive_dir);
298	    exit (1);
299	  }
300    }
301  // Clean old archives if necessary
302  if (force)
303    clean_old_archive (argv[last], founder_exp);
304  Vector<ArchiveExp*> *exps = new Vector<ArchiveExp*>();
305  exps->append (founder_exp);
306  if (descendant)
307    {
308      Vector<char*> *exp_names = founder_exp->get_descendants_names ();
309      if (exp_names)
310	{
311	  for (long i = 0, sz = exp_names->size (); i < sz; i++)
312	    {
313	      char *exp_path = exp_names->get (i);
314	      ArchiveExp *exp = new ArchiveExp (exp_path);
315	      if (exp->get_status () == Experiment::FAILURE)
316		{
317		  if (!quiet)
318		    fprintf (stderr, GTXT ("er_archive: %s: %s\n"), exp_path,
319			     pr_mesgs (exp->fetch_errors (), NTXT (""), NTXT ("")));
320		  delete exp;
321		  continue;
322		}
323	      exps->append (exp);
324	    }
325	  exp_names->destroy ();
326	  delete exp_names;
327	}
328    }
329  for (long i = 0, sz = exps->size (); i < sz; i++)
330    {
331      ArchiveExp *exp = exps->get (i);
332      exp->read_data (s_option);
333    }
334
335  Vector <DbeFile*> *copy_files = new Vector<DbeFile*>();
336  Vector <LoadObject*> *loadObjs = get_loadObjs ();
337  for (long i = 0, sz = VecSize(loadObjs); i < sz; i++)
338    {
339      LoadObject *lo = loadObjs->get (i);
340      if (strcmp (lo->get_pathname (), "LinuxKernel") == 0)
341	continue;
342      DbeFile *df = lo->dbeFile;
343      if ((df->filetype & DbeFile::F_FICTION) != 0)
344	continue;
345      if (df->get_location () == NULL)
346	{
347	  copy_files->append (df);
348	  continue;
349	}
350      if ((df->filetype & DbeFile::F_JAVACLASS) != 0)
351	{
352	  if (df->container)
353	    { // Found in .jar file
354	      copy_files->append (df->container);
355	    }
356	  copy_files->append (df);
357	  if ((s_option & ARCH_EXE_ONLY) != 0)
358	    continue;
359	}
360      lo->sync_read_stabs ();
361      Elf *elf = lo->get_elf ();
362      if (elf && (lo->checksum != 0) && (lo->checksum != elf->elf_checksum ()))
363	{
364	  if (!quiet)
365	    fprintf (stderr, GTXT ("er_archive: '%s' has an unexpected checksum value; perhaps it was rebuilt. File ignored\n"),
366		       df->get_location ());
367	  continue;
368	}
369      copy_files->append (df);
370      if (elf)
371	{
372	  Elf *f = elf->find_ancillary_files (lo->get_pathname ());
373	  if (f)
374	    copy_files->append (f->dbeFile);
375	  for (long i1 = 0, sz1 = VecSize(elf->ancillary_files); i1 < sz1; i1++)
376	    {
377	      Elf *ancElf = elf->ancillary_files->get (i1);
378	      copy_files->append (ancElf->dbeFile);
379	    }
380	}
381      Vector<Module*> *modules = lo->seg_modules;
382      for (long i1 = 0, sz1 = VecSize(modules); i1 < sz1; i1++)
383	{
384	  Module *mod = modules->get (i1);
385	  if ((mod->flags & MOD_FLAG_UNKNOWN) != 0)
386	    continue;
387	  else if ((s_option & (ARCH_USED_EXE_ONLY | ARCH_USED_SRC_ONLY)) != 0 &&
388		   !mod->isUsed)
389	    continue;
390	  if ((s_option & ARCH_ALL) != 0)
391	    mod->read_stabs (false); // Find all Sources
392	  if (mod->dot_o_file && mod->dot_o_file->dbeFile)
393	    copy_files->append (mod->dot_o_file->dbeFile);
394	}
395    }
396  delete loadObjs;
397
398  int bmask = DbeFile::F_LOADOBJ | DbeFile::F_JAVACLASS | DbeFile::F_JAR_FILE |
399	  DbeFile::F_DOT_O | DbeFile::F_DEBUG_FILE;
400  if ((s_option & (ARCH_USED_SRC_ONLY | ARCH_ALL)) != 0)
401    {
402      bmask |= DbeFile::F_JAVA_SOURCE | DbeFile::F_SOURCE;
403      Vector<SourceFile*> *sources = dbeSession->get_sources ();
404      for (long i = 0, sz = VecSize(sources); i < sz; i++)
405	{
406	  SourceFile *src = sources->get (i);
407	  if ((src->flags & SOURCE_FLAG_UNKNOWN) != 0)
408	    continue;
409	  else if ((s_option & ARCH_USED_SRC_ONLY) != 0)
410	    {
411	      if ((src->dbeFile->filetype & DbeFile::F_JAVA_SOURCE) != 0 &&
412		  !src->isUsed)
413		continue;
414	    }
415	  if (src->dbeFile)
416	    copy_files->append (src->dbeFile);
417	}
418    }
419
420  Vector <DbeFile*> *notfound_files = new Vector<DbeFile*>();
421  for (long i = 0, sz = VecSize(copy_files); i < sz; i++)
422    {
423      DbeFile *df = copy_files->get (i);
424      char *fnm = df->get_location ();
425      char *nm = df->get_name ();
426      Dprintf (DEBUG_ARCHIVE,
427	       "%s::%d copy_files[%ld] filetype=%4d inArchive=%d '%s' --> '%s'\n",
428	       get_basename (__FILE__), (int) __LINE__, i,
429	       df->filetype, df->inArchive ? 1 : 0, STR (nm), STR (fnm));
430      Dprintf (DEBUG_ARCHIVE && df->container,
431	       "    copy_files[%ld]: Found '%s' in '%s'\n",
432	       i, STR (nm), STR (df->container->get_name ()));
433      if (fnm == NULL)
434	{
435	  if (!quiet)
436	    notfound_files->append (df);
437	  continue;
438	}
439      else if (df->inArchive)
440	{
441	  Dprintf (DEBUG_ARCHIVE,
442		   "  NOT COPIED: copy_files[%ld]: inArchive=1 '%s'\n",
443		   i, STR (nm));
444	  continue;
445	}
446      else if ((df->filetype & bmask) == 0)
447	{
448	  Dprintf (DEBUG_ARCHIVE,
449		   "  NOT COPIED: copy_files[%ld]: container=%p filetype=%d bmask=%d '%s'\n",
450		   i, df->container, df->filetype, bmask, STR (nm));
451	  continue;
452	}
453      else if (df->container &&
454	       (df->filetype & (DbeFile::F_JAVA_SOURCE | DbeFile::F_SOURCE)) == 0)
455	{
456	  Dprintf (DEBUG_ARCHIVE,
457		   "  NOT COPIED: copy_files[%ld]: container=%p filetype=%d bmask=%d '%s'\n",
458		   i, df->container, df->filetype, bmask, STR (nm));
459	  continue;
460	}
461      else if (!mask_is_on (df->get_name ()))
462	{
463	  Dprintf (DEBUG_ARCHIVE,
464		   "  NOT COPIED: copy_files[%ld]: mask is off for '%s'\n",
465		   i, STR (nm));
466	  continue;
467	}
468      char *anm = founder_exp->getNameInArchive (nm, false);
469      if (force)
470	unlink (anm);
471      int res = founder_exp->copy_file (fnm, anm, quiet, common_archive_dir, use_relative_path);
472      if (0 == res)  // file successfully archived
473	df->inArchive = 1;
474      delete anm;
475    }
476  delete copy_files;
477
478  if (notfound_files->size () > 0)
479    {
480      for (long i = 0, sz = notfound_files->size (); i < sz; i++)
481	{
482	  DbeFile *df = notfound_files->get (i);
483	  fprintf (stderr, GTXT ("er_archive: Cannot find file: `%s'\n"), df->get_name ());
484	}
485      fprintf (stderr, GTXT ("\n If you know the correct location of the missing file(s)"
486			     " you can help %s to find them by manually editing the .gprofng.rc file."
487			     " See %s man pages for more details.\n"),
488	       whoami, whoami);
489    }
490  delete notfound_files;
491}
492
493int
494er_archive::check_args (int argc, char *argv[])
495{
496  int opt;
497  int rseen = 0;
498  int dseen = 0;
499  // Parsing the command line
500  opterr = 0;
501  optind = 1;
502  static struct option long_options[] = {
503    {"help",    no_argument,        0, 'h'},
504    {"version", no_argument,        0, 'V'},
505    {"whoami",  required_argument,  0, 'w'},
506    {"outfile", required_argument,  0, 'O'},
507    {NULL, 0, 0, 0}
508  };
509  while (1)
510    {
511      int option_index = 0;
512      opt = getopt_long (argc, argv, NTXT (":VFa:d:qnr:m:"),
513			 long_options, &option_index);
514      if (opt == EOF)
515	break;
516      switch (opt)
517	{
518	case 'F':
519	  force = 1;
520	  break;
521	case 'd': // Common archive directory (absolute path)
522	  if (rseen)
523	    {
524	      fprintf (stderr, GTXT ("Error: invalid combination of options: -r and -d are in conflict.\n"));
525	      return -1;
526	    }
527	  if (dseen)
528	    fprintf (stderr, GTXT ("Warning: option -d was specified several times. Last value is used.\n"));
529	  free (common_archive_dir);
530	  common_archive_dir = strdup (optarg);
531	  dseen = 1;
532	  break;
533	case 'q':
534	  quiet = 1;
535	  break;
536	case 'n':
537	  descendant = 0;
538	  break;
539	case 'r': // Common archive directory (relative path)
540	  if (dseen)
541	    {
542	      fprintf (stderr, GTXT ("Error: invalid combination of options: -d and -r are in conflict.\n"));
543	      return -1;
544	    }
545	  if (rseen)
546	    fprintf (stderr, GTXT ("Warning: option -r was specified several times. Last value is used.\n"));
547	  free (common_archive_dir);
548	  common_archive_dir = strdup (optarg);
549	  use_relative_path = 1;
550	  rseen = 1;
551	  break;
552	case 'a':
553	  if (strcmp (optarg, "off") == 0)
554	    s_option = ARCH_NOTHING;
555	  else if (strcmp (optarg, "on") == 0 ||
556		   strcmp (optarg, "ldobjects") == 0)
557	    s_option = ARCH_EXE_ONLY;
558	  else if (strcmp (optarg, "usedldobjects") == 0)
559	    s_option = ARCH_USED_EXE_ONLY;
560	  else if (strcmp (optarg, "usedsrc") == 0)
561	    s_option = ARCH_USED_EXE_ONLY | ARCH_USED_SRC_ONLY;
562	  else if (strcmp (optarg, "all") == 0 || strcmp (optarg, "src") == 0)
563	    s_option = ARCH_ALL;
564	  else
565	    {
566	      fprintf (stderr, GTXT ("Error: invalid option: `-%c %s'\n"),
567		       optopt, optarg);
568	      return -1;
569	    }
570	  break;
571	case 'm':
572	  {
573	    regex_t *regex_desc = new regex_t ();
574	    if (regcomp (regex_desc, optarg, REG_EXTENDED | REG_NOSUB | REG_NEWLINE))
575	      {
576		delete regex_desc;
577		fprintf (stderr, GTXT ("Error: invalid option: `-%c %s'\n"),
578			 optopt, optarg);
579		return -1;
580	      }
581	    if (mask == NULL)
582	      mask = new Vector<regex_t *>();
583	    mask->append (regex_desc);
584	    break;
585	  }
586	case 'O':
587	  {
588	    int fd = open (optarg, O_WRONLY | O_CREAT | O_APPEND, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
589	    if (fd == -1)
590	      {
591		fprintf (stderr, GTXT ("er_archive: Can't open %s: %s\n"),
592			 optarg, strerror (errno));
593		break;
594	      }
595	    if (dup2 (fd, 2) == -1)
596	      {
597		close (fd);
598		fprintf (stderr, GTXT ("er_archive: Can't divert stderr: %s\n"),
599			 strerror (errno));
600		break;
601	      }
602	    if (dup2 (fd, 1) == -1)
603	      {
604		close (fd);
605		fprintf (stderr, GTXT ("er_archive: Can't divert stdout: %s\n"),
606			 strerror (errno));
607		break;
608	      }
609	    close (fd);
610	    struct timeval tp;
611	    gettimeofday (&tp, NULL);
612	    fprintf (stderr, "### Start %s#", ctime (&tp.tv_sec));
613	    for (int i = 0; i < argc; i++)
614	      fprintf (stderr, " %s", argv[i]);
615	    fprintf (stderr, "\n");
616	    break;
617	  }
618	case 'V':
619// Ruud
620	  Application::print_version_info ();
621/*
622	  printf (GTXT ("GNU %s version %s\n"), get_basename (prog_name), VERSION);
623*/
624	  exit (0);
625	case 'w':
626	  whoami = optarg;
627	  break;
628	case 'h':
629	  usage ();
630	  exit (0);
631	case ':': // -s -m without operand
632	  fprintf (stderr, GTXT ("Option -%c requires an operand\n"), optopt);
633	  return -1;
634	case '?':
635	default:
636	  fprintf (stderr, GTXT ("Unrecognized option: -%c\n"), optopt);
637	  return -1;
638	}
639    }
640  return optind;
641}
642
643void
644er_archive::check_env_var ()
645{
646  char *ename = NTXT ("GPROFNG_ARCHIVE");
647  char *var = getenv (ename);
648  if (var == NULL)
649    return;
650  var = dbe_strdup (var);
651  Vector<char*> *opts = new Vector<char*>();
652  opts->append (ename);
653  for (char *s = var;;)
654    {
655      while (*s && isblank (*s))
656	s++;
657      if (*s == 0)
658	break;
659      opts->append (s);
660      while (*s && !isblank (*s))
661	s++;
662      if (*s == 0)
663	break;
664      *s = 0;
665      s++;
666    }
667  if (opts->size () > 0)
668    {
669      char **arr = (char **) malloc (sizeof (char *) *opts->size ());
670      for (long i = 0; i < opts->size (); i++)
671	arr[i] = opts->get (i);
672      if (-1 == check_args (opts->size (), arr))
673	fprintf (stderr, GTXT ("Error: Wrong SP_ER_ARCHIVE: '%s'\n"), var);
674      free (arr);
675    }
676  delete opts;
677  free (var);
678}
679
680static int
681real_main (int argc, char *argv[])
682{
683  er_archive *archive = new er_archive (argc, argv);
684  dbeSession->archive_mode = 1;
685  archive->start (argc, argv);
686  dbeSession->unlink_tmp_files ();
687  return 0;
688}
689
690/**
691 * Call catch_out_of_memory(int (*real_main)(int, char*[]), int argc, char *argv[]) which will call real_main()
692 * @param argc
693 * @param argv
694 * @return
695 */
696int
697main (int argc, char *argv[])
698{
699  return catch_out_of_memory (real_main, argc, argv);
700}
701