FOSSology  4.4.0
Open Source License Compliance by Open Source Software
ununpack.c
Go to the documentation of this file.
1 /*
2  Ununpack: The universal unpacker.
3 
4  SPDX-FileCopyrightText: © 2007-2013 Hewlett-Packard Development Company, L.P.
5  SPDX-FileCopyrightText: © 2015,2023 Siemens AG
6 
7  SPDX-License-Identifier: GPL-2.0-only
8 */
99 #define _GNU_SOURCE
100 #include "ununpack.h"
101 #include "ununpack_globals.h"
102 #include <gcrypt.h>
103 
104 #ifdef COMMIT_HASH_S
105 char BuildVersion[]="ununpack build version: " VERSION_S " r(" COMMIT_HASH_S ").\n";
106 #else
107 char BuildVersion[]="ununpack build version: NULL.\n";
108 #endif
109 
110 /***********************************************************************/
111 int main(int argc, char *argv[])
112 {
113  int Pid;
114  int c;
115  int rvExist1=0, rvExist2=0;
116  PGresult *result;
117  char *NewDir=".";
118  char *AgentName = "ununpack";
119  char *AgentARSName = "ununpack_ars";
120  char *agent_desc = "Unpacks archives (iso, tar, etc)";
121  int Recurse=0;
122  int ars_pk = 0;
123  int user_pk = 0;
124  long Pfile_size = 0;
125  char *ListOutName=NULL;
126  char *Fname = NULL;
127  char *FnameCheck = NULL;
128  char *COMMIT_HASH;
129  char *VERSION;
130  char agent_rev[PATH_MAX];
131  struct stat Stat;
132 
133  /* connect to the scheduler */
134  fo_scheduler_connect(&argc, argv, &pgConn);
135 
136  while((c = getopt(argc,argv,"ACc:d:FfHhL:m:PQiIqRr:T:t:U:VvXx")) != -1)
137  {
138  switch(c)
139  {
140  case 'A': SetContainerArtifact=0; break;
141  case 'C': ForceContinue=1; break;
142  case 'c': break; /* handled by fo_scheduler_connect() */
143  case 'd':
144  /* if there is a %U in the path, substitute a unique ID */
145  NewDir=PathCheck(optarg);
146  break;
147  case 'F': UseRepository=1; break;
148  case 'f': ForceDuplicate=1; break;
149  case 'L': ListOutName=optarg; break;
150  case 'm':
151  MaxThread = atoi(optarg);
152  if (MaxThread < 1) MaxThread=1;
153  break;
154  case 'P': PruneFiles=1; break;
155  case 'R': Recurse=-1; break;
156  case 'r': Recurse=atoi(optarg); break;
157  case 'i':
158  if (!IsExe("dpkg-source",Quiet))
159  LOG_WARNING("dpkg-source is not available on this system. This means that debian source packages will NOT be unpacked.");
160  SafeExit(0);
161  break; /* never reached */
162  case 'I': IgnoreSCMData=1; break;
163  case 'Q':
164  UseRepository=1;
165 
166  user_pk = fo_scheduler_userID(); /* get user_pk for user who queued the agent */
167 
168  /* Get the upload_pk from the scheduler */
169  if((Upload_Pk = fo_scheduler_next()) == NULL) SafeExit(0);
170  break;
171  case 'q': Quiet=1; break;
172  case 'T':
173  memset(REP_GOLD,0,sizeof(REP_GOLD));
174  strncpy(REP_GOLD,optarg,sizeof(REP_GOLD)-1);
175  break;
176  case 't':
177  memset(REP_FILES,0,sizeof(REP_FILES));
178  strncpy(REP_FILES,optarg,sizeof(REP_FILES)-1);
179  break;
180  case 'U':
181  UseRepository = 1;
182  Recurse = -1;
183  Upload_Pk = optarg;
184  break;
185  case 'V': printf("%s", BuildVersion);SafeExit(0);
186  case 'v': Verbose++; break;
187  case 'X': UnlinkSource=1; break;
188  case 'x': UnlinkAll=1; break;
189  default:
190  Usage(argv[0], BuildVersion);
191  SafeExit(25);
192  }
193  }
194 
195  /* Initialize gcrypt and disable security memory */
196  gcry_check_version(NULL);
197  gcry_control (GCRYCTL_DISABLE_SECMEM, 0);
198  gcry_control (GCRYCTL_INITIALIZATION_FINISHED, 0);
199 
200  /* Open DB and Initialize CMD table */
201  if (UseRepository)
202  {
203  /* Check Permissions */
204  if (GetUploadPerm(pgConn, atoi(Upload_Pk), user_pk) < PERM_WRITE)
205  {
206  LOG_ERROR("You have no update permissions on upload %s", Upload_Pk);
207  SafeExit(100);
208  }
209 
210  COMMIT_HASH = fo_sysconfig(AgentName, "COMMIT_HASH");
211  VERSION = fo_sysconfig(AgentName, "VERSION");
212  sprintf(agent_rev, "%s.%s", VERSION, COMMIT_HASH);
213  /* Get the unpack agent key */
214  agent_pk = fo_GetAgentKey(pgConn, AgentName, atoi(Upload_Pk), agent_rev,agent_desc);
215 
216  InitCmd();
217 
218  /* Make sure ars table exists */
219  if (!fo_CreateARSTable(pgConn, AgentARSName)) SafeExit(0);
220 
221  /* Has this user previously unpacked this upload_pk successfully?
222  * In this case we are done. No new ars record is needed since no
223  * processing is initiated.
224  * The unpack version is ignored.
225  */
226  snprintf(SQL,MAXSQL,
227  "SELECT ars_pk from %s where upload_fk='%s' and ars_success=TRUE",
228  AgentARSName, Upload_Pk);
229  result = PQexec(pgConn, SQL);
230  if (fo_checkPQresult(pgConn, result, SQL, __FILE__, __LINE__)) SafeExit(101);
231 
232  if (PQntuples(result) > 0) /* if there is a value */
233  {
234  PQclear(result);
235  LOG_WARNING("Upload_pk %s, has already been unpacked. No further action required",
236  Upload_Pk)
237  SafeExit(0);
238  }
239  PQclear(result);
240 
241  /* write the unpack_ars start record */
242  ars_pk = fo_WriteARS(pgConn, ars_pk, atoi(Upload_Pk), agent_pk, AgentARSName, 0, 0);
243 
244  /* Get Pfile path and Pfile_Pk, from Upload_Pk */
245  snprintf(SQL,MAXSQL,
246  "SELECT pfile.pfile_sha1 || '.' || pfile.pfile_md5 || '.' || pfile.pfile_size AS pfile, pfile_fk, pfile_size FROM upload INNER JOIN pfile ON upload.pfile_fk = pfile.pfile_pk WHERE upload.upload_pk = '%s'",
247  Upload_Pk);
248  result = PQexec(pgConn, SQL);
249  if (fo_checkPQresult(pgConn, result, SQL, __FILE__, __LINE__)) SafeExit(102);
250 
251  if (PQntuples(result) > 0) /* if there is a value */
252  {
253  Pfile = strdup(PQgetvalue(result,0,0));
254  Pfile_Pk = strdup(PQgetvalue(result,0,1));
255  Pfile_size = atol(PQgetvalue(result, 0, 2));
256  if (Pfile_size == 0)
257  {
258  PQclear(result);
259  LOG_WARNING("Uploaded file (Upload_pk %s), is zero length. There is nothing to unpack.",
260  Upload_Pk)
261  SafeExit(0);
262  }
263 
264  PQclear(result);
265  }
266 
267  // Determine if uploadtree records should go into a separate table.
268  // If the input file size is > 500MB, then create a separate uploadtree_{upload_pk} table
269  // that inherits from the master uploadtree table.
270  // Save uploadtree_tablename, it will get written to upload.uploadtree_tablename later.
271  if (Pfile_size > 500000000)
272  {
273  sprintf(uploadtree_tablename, "uploadtree_%s", Upload_Pk);
275  {
276  snprintf(SQL,MAXSQL,"CREATE TABLE %s (LIKE uploadtree INCLUDING DEFAULTS INCLUDING CONSTRAINTS INCLUDING INDEXES); ALTER TABLE %s ADD CONSTRAINT %s CHECK (upload_fk=%s); ALTER TABLE %s INHERIT uploadtree",
278  PQsetNoticeProcessor(pgConn, SQLNoticeProcessor, SQL); // ignore notice about implicit primary key index creation
279  result = PQexec(pgConn, SQL);
280  // Ignore postgres notice about creating an implicit index
281  if (PQresultStatus(result) != PGRES_NONFATAL_ERROR)
282  if (fo_checkPQcommand(pgConn, result, SQL, __FILE__, __LINE__)) SafeExit(103);
283  PQclear(result);
284  }
285  }
286  else
287  strcpy(uploadtree_tablename, "uploadtree_a");
288  }
289 
291  if (NewDir) MkDir(NewDir);
292  if (Verbose) { fclose(stderr) ; stderr=stdout; } /* don't interlace! */
293  if (ListOutName != NULL)
294  {
295  if ((ListOutName[0]=='-') && (ListOutName[1]=='\0'))
296  ListOutFile=stdout;
297  else ListOutFile = fopen(ListOutName,"w");
298  if (!ListOutFile)
299  {
300  LOG_ERROR("pfile %s Unable to write to %s\n",Pfile_Pk,ListOutName)
301  SafeExit(104);
302  }
303  else
304  {
305  /* Start the file */
306  fputs("<xml tool=\"ununpack\" ",ListOutFile);
307  fputs("version=\"",ListOutFile);
308  fputs(Version,ListOutFile);
309  fputs("\" ",ListOutFile);
310  fputs("compiled_date=\"",ListOutFile);
311  fputs(__DATE__,ListOutFile);
312  fputs(" ",ListOutFile);
313  fputs(__TIME__,ListOutFile);
314  fputs("\"",ListOutFile);
315  fputs(">\n",ListOutFile);
316  }
317  /* Problem: When parallel processing, the XML may be generated out
318  of order. Solution? When using XML, only use 1 thread. */
319  MaxThread=1;
320  }
321 
322  // Set ReunpackSwitch if the uploadtree records are missing from the database.
324  {
325  snprintf(SQL,MAXSQL,"SELECT uploadtree_pk FROM uploadtree WHERE upload_fk=%s limit 1;",Upload_Pk);
326  result = PQexec(pgConn, SQL);
327  if (fo_checkPQresult(pgConn, result, SQL, __FILE__, __LINE__)) SafeExit(105);
328  if (PQntuples(result) == 0) ReunpackSwitch=1;
329  PQclear(result);
330  }
331 
332  /*** process files from command line ***/
333  for( ; optind<argc; optind++)
334  {
335  CksumFile *CF=NULL;
336  Cksum *Sum;
337  int i;
338  if (Fname) { free(Fname); Fname=NULL; }
339  if (ListOutName != NULL)
340  {
341  fprintf(ListOutFile,"<source source=\"%s\" ",argv[optind]);
342  if (UseRepository && !fo_RepExist(REP_FILES,argv[optind]))
343  {
344  /* make sure the source exists in the src repository */
345  if (fo_RepImport(argv[optind],REP_FILES,argv[optind],1) != 0)
346  {
347  LOG_ERROR("Failed to import '%s' as '%s' into the repository",argv[optind],argv[optind])
348  SafeExit(106);
349  }
350  }
351  }
352 
353  if (UseRepository)
354  {
355  if (fo_RepExist(REP_FILES,argv[optind]))
356  {
357  Fname=fo_RepMkPath(REP_FILES,argv[optind]);
358  }
359  else if (fo_RepExist(REP_GOLD,argv[optind]))
360  {
361  Fname=fo_RepMkPath(REP_GOLD,argv[optind]);
362  if (fo_RepImport(Fname,REP_FILES,argv[optind],1) != 0)
363  {
364  LOG_ERROR("Failed to import '%s' as '%s' into the repository",Fname,argv[optind])
365  SafeExit(107);
366  }
367  }
368 
369  if (Fname)
370  {
371  FnameCheck = Fname;
372  CF = SumOpenFile(Fname);
373  }
374  else
375  {
376  LOG_ERROR("NO file unpacked. File %s does not exist either in GOLD or FILES", Pfile);
377  SafeExit(108);
378  }
379  /* else: Fname is NULL and CF is NULL */
380  }
381  else
382  {
383  FnameCheck = argv[optind];
384  CF = SumOpenFile(argv[optind]);
385  }
386 
387  /* Check file to unpack. Does it exist? Is it zero length? */
388  if (stat(FnameCheck,&Stat))
389  {
390  LOG_ERROR("File to unpack is unavailable: %s, error: %s", Fname, strerror(errno));
391  SafeExit(109);
392  }
393  else
394  if (Stat.st_size < 1)
395  {
396  LOG_WARNING("File to unpack is empty: %s", Fname);
397  SafeExit(110);
398  }
399 
400  if (ListOutFile)
401  {
402  if (CF)
403  {
404  Sum = SumComputeBuff(CF);
405  SumCloseFile(CF);
406  if (Sum)
407  {
408  fputs("fuid=\"",ListOutFile);
409  for(i=0; i<20; i++)
410  { fprintf(ListOutFile,"%02X",Sum->SHA1digest[i]); }
411  fputs(".",ListOutFile);
412  for(i=0; i<16; i++)
413  { fprintf(ListOutFile,"%02X",Sum->MD5digest[i]); }
414  fputs(".",ListOutFile);
415  fprintf(ListOutFile,"%Lu",(long long unsigned int)Sum->DataLen);
416  fputs("\" ",ListOutFile);
417  free(Sum);
418  } /* if Sum */
419  } /* if CF */
420  else /* file too large to mmap (probably) */
421  {
422  FILE *Fin;
423  Fin = fopen(argv[optind],"rb");
424  if (Fin)
425  {
426  Sum = SumComputeFile(Fin);
427  if (Sum)
428  {
429  fputs("fuid=\"",ListOutFile);
430  for(i=0; i<20; i++)
431  { fprintf(ListOutFile,"%02X",Sum->SHA1digest[i]); }
432  fputs(".",ListOutFile);
433  for(i=0; i<16; i++)
434  { fprintf(ListOutFile,"%02X",Sum->MD5digest[i]); }
435  fputs(".",ListOutFile);
436  fprintf(ListOutFile,"%Lu",(long long unsigned int)Sum->DataLen);
437  fputs("\" ",ListOutFile);
438  free(Sum);
439  }
440  fclose(Fin);
441  }
442  } /* else no CF */
443  fprintf(ListOutFile,">\n"); /* end source XML */
444  }
445  if (Fname) TraverseStart(Fname,"called by main via args",NewDir,Recurse);
446  else TraverseStart(argv[optind],"called by main",NewDir,Recurse);
447  if (ListOutName != NULL) fprintf(ListOutFile,"</source>\n");
448  } /* end for */
449 
450  /* free memory */
451  if (Fname) { free(Fname); Fname=NULL; }
452 
453  /* process pfile from scheduler */
454  if (Pfile)
455  {
456  if (0 == (rvExist1 = fo_RepExist2(REP_FILES,Pfile)))
457  {
459  }
460  else if (0 == (rvExist2 = fo_RepExist2(REP_GOLD,Pfile)))
461  {
462  Fname=fo_RepMkPath(REP_GOLD,Pfile);
463  if (fo_RepImport(Fname,REP_FILES,Pfile,1) != 0)
464  {
465  LOG_ERROR("Failed to import '%s' as '%s' into the repository",Fname,Pfile)
466  SafeExit(111);
467  }
468  }
469  if (Fname)
470  {
471  TraverseStart(Fname,"called by main via env",NewDir,Recurse);
472  free(Fname);
473  Fname=NULL;
474  }
475  else
476  {
477  LOG_ERROR("NO file unpacked!");
478  if (rvExist1 > 0)
479  {
480  Fname=fo_RepMkPath(REP_FILES, Pfile);
481  LOG_ERROR("Error is %s for %s", strerror(rvExist1), Fname);
482  }
483  if (rvExist2 > 0)
484  {
485  Fname=fo_RepMkPath(REP_GOLD, Pfile);
486  LOG_ERROR("Error is %s for %s", strerror(rvExist2), Fname);
487  }
488  SafeExit(112);
489  }
490  }
491 
492  /* recurse on all the children */
493  if (Thread > 0) do
494  {
495  Pid = ParentWait();
496  Thread--;
497  if (Pid >= 0)
498  {
499  if (!Queue[Pid].ChildEnd)
500  {
501  /* copy over data */
502  if (Recurse > 0)
503  Traverse(Queue[Pid].ChildRecurse,NULL,"called by wait",NULL,Recurse-1,&Queue[Pid].PI);
504  else if (Recurse < 0)
505  Traverse(Queue[Pid].ChildRecurse,NULL,"called by wait",NULL,Recurse,&Queue[Pid].PI);
506  }
507  }
508  } while(Pid >= 0);
509 
510  if (MagicCookie) magic_close(MagicCookie);
511  if (ListOutFile)
512  {
513  fprintf(ListOutFile,"<summary files_regular=\"%d\" files_compressed=\"%d\" artifacts=\"%d\" directories=\"%d\" containers=\"%d\" />\n",
516  fputs("</xml>\n",ListOutFile);
517  }
518  if (pgConn)
519  {
520  /* If it completes, mark it! */
521  if (Upload_Pk)
522  {
523  snprintf(SQL,MAXSQL,"UPDATE upload SET upload_mode = (upload_mode | (1<<5)), uploadtree_tablename='%s' WHERE upload_pk = '%s';",uploadtree_tablename, Upload_Pk);
524  result = PQexec(pgConn, SQL); /* UPDATE upload */
525  if (fo_checkPQcommand(pgConn, result, SQL, __FILE__ ,__LINE__)) SafeExit(113);
526  PQclear(result);
527 
528  snprintf(SQL,MAXSQL,"UPDATE %s SET realparent = getItemParent(uploadtree_pk) WHERE upload_fk = '%s'",uploadtree_tablename, Upload_Pk);
529  result = PQexec(pgConn, SQL); /* UPDATE uploadtree */
530  if (fo_checkPQcommand(pgConn, result, SQL, __FILE__ ,__LINE__)) SafeExit(114);
531  PQclear(result);
532  }
533 
534  if (ars_pk) fo_WriteARS(pgConn, ars_pk, atoi(Upload_Pk), agent_pk, AgentARSName, 0, 1);
535  }
536  if (ListOutFile && (ListOutFile != stdout))
537  {
538  fclose(ListOutFile);
539  }
540 
541  if (UnlinkAll && MaxThread > 1)
542  {
543  /* Delete temporary files */
544  if (strcmp(NewDir, ".")) RemoveDir(NewDir);
545  }
546 
547  SafeExit(0);
548  return(0); // never executed but makes the compiler happy
549 }
char SQL[256]
SQL query to execute.
Definition: adj2nest.c:78
PGconn * pgConn
Database connection.
Definition: adj2nest.c:86
char * uploadtree_tablename
upload.uploadtree_tablename
Definition: adj2nest.c:100
int agent_pk
Definition: agent.h:74
char BuildVersion[]
Definition: buckets.c:68
Cksum * SumComputeBuff(CksumFile *CF)
Compute the checksum, allocate and return a Cksum containing the sum value.
Definition: checksum.c:182
Cksum * SumComputeFile(FILE *Fin)
Compute the checksum, allocate and return a string containing the sum value.
Definition: checksum.c:115
CksumFile * SumOpenFile(char *Fname)
Open and mmap a file.
Definition: checksum.c:26
void SumCloseFile(CksumFile *CF)
Close a file that was opened with SumOpenFile()
Definition: checksum.c:83
int Verbose
Verbose level.
Definition: util.c:19
int SetContainerArtifact
Should initial container be an artifact?
char * Pfile_Pk
Pfile pk in DB.
int ReunpackSwitch
Set if the uploadtree records are missing from db.
int UseRepository
Using files from the repository?
int MaxThread
Value between 1 and MAXCHILD.
int UnlinkSource
Remove recursive sources after unpacking?
char * Pfile
Pfile name (SHA1.MD5.Size)
int TotalDirectories
Number of directories.
unpackqueue Queue[MAXCHILD+1]
Manage children.
int Quiet
Run in quiet mode?
int Thread
Number of threads in execution.
int TotalFiles
Number of regular files.
int TotalArtifacts
Number of artifacts.
int PruneFiles
Remove links? >1 hard links, zero files, etc.
int TotalContainers
Number of containers.
char * Upload_Pk
Upload pk in DB.
int UnlinkAll
Remove ALL unpacked files when done (clean up)?
char REP_FILES[16]
Files repository name.
FILE * ListOutFile
File to store unpack list.
char REP_GOLD[16]
Gold repository name.
int IgnoreSCMData
1: Ignore SCM data, 0: dont ignore it.
int TotalCompressedFiles
Number of compressed files.
int ForceDuplicate
When using db, should it process duplicates?
int ForceContinue
Force continue when unpack tool fails?
magic_t MagicCookie
for Magic
Definition: finder.c:23
Usage()
Print Usage statement.
Definition: fo_dbcheck.php:63
FUNCTION int GetUploadPerm(PGconn *pgConn, long UploadPk, int user_pk)
Get users permission to this upload.
Definition: libfossagent.c:378
FUNCTION int fo_CreateARSTable(PGconn *pgConn, const char *tableName)
Create ars table if it doesn't already exist.
Definition: libfossagent.c:270
FUNCTION int fo_WriteARS(PGconn *pgConn, int ars_pk, int upload_pk, int agent_pk, const char *tableName, const char *ars_status, int ars_success)
Write ars record.
Definition: libfossagent.c:214
FUNCTION int fo_GetAgentKey(PGconn *pgConn, const char *agent_name, long Upload_pk, const char *rev, const char *agent_desc)
Get the latest enabled agent key (agent_pk) from the database.
Definition: libfossagent.c:158
int fo_checkPQresult(PGconn *pgConn, PGresult *result, char *sql, char *FileID, int LineNumb)
Check the result status of a postgres SELECT.
Definition: libfossdb.c:170
int fo_tableExists(PGconn *pgConn, const char *tableName)
Check if table exists. Note, this assumes the database name is 'fossology'.
Definition: libfossdb.c:232
int fo_checkPQcommand(PGconn *pgConn, PGresult *result, char *sql, char *FileID, int LineNumb)
Check the result status of a postgres commands (not select) If an error occured, write the error to s...
Definition: libfossdb.c:204
#define PERM_WRITE
Read-Write permission.
Definition: libfossology.h:33
char * fo_RepMkPath(const char *Type, char *Filename)
Given a filename, construct the full path to the file.
Definition: libfossrepo.c:352
int fo_RepExist2(char *Type, char *Filename)
Determine if a file exists.
Definition: libfossrepo.c:531
int fo_RepImport(char *Source, char *Type, char *Filename, int Link)
Import a file into the repository.
Definition: libfossrepo.c:812
int fo_RepExist(char *Type, char *Filename)
Determine if a file exists.
Definition: libfossrepo.c:486
char * fo_sysconfig(const char *sectionname, const char *variablename)
gets a system configuration variable from the configuration data.
int fo_scheduler_userID()
Gets the id of the user that created the job that the agent is running.
char * fo_scheduler_next()
Get the next data to process from the scheduler.
void fo_scheduler_connect(int *argc, char **argv, PGconn **db_conn)
Establish a connection between an agent and the scheduler.
Store file handler and mmap of a file.
Definition: checksum.h:44
Store check sum of a file.
Definition: checksum.h:33
uint8_t SHA1digest[20]
SHA1 digest of the file.
Definition: checksum.h:35
uint64_t DataLen
Size of the file.
Definition: checksum.h:36
uint8_t MD5digest[16]
MD5 digest of the file.
Definition: checksum.h:34
void TraverseStart(char *Filename, char *Label, char *NewDir, int Recurse)
Find all files (assuming a directory) and process (unpack) all of them.
Definition: traverse.c:23
int Traverse(char *Filename, char *Basename, char *Label, char *NewDir, int Recurse, ParentInfo *PI)
Find all files, traverse all directories. This is a depth-first search, in inode order!
Definition: traverse.c:275
int RemoveDir(char *dirpath)
Remove all files under dirpath (rm -rf)
Definition: utils.c:1641
void CheckCommands(int Show)
Make sure all commands are usable.
Definition: utils.c:567
int MkDir(char *Fname)
Smart mkdir.
Definition: utils.c:303
int ParentWait()
Wait for a child. Sets child status.
Definition: utils.c:509
void InitCmd()
Initialize the metahandler CMD table.
Definition: utils.c:108
int IsExe(char *Exe, int Quiet)
Check if the executable exists.
Definition: utils.c:393
void SafeExit(int rc)
Close scheduler and database connections, then exit.
Definition: utils.c:77
char * PathCheck(char *DirPath)
Check if path contains a "%U" or "%H". If so, substitute a unique ID for U.
Definition: utils.c:1662
void SQLNoticeProcessor(void *arg, const char *message)
Dummy postgresql notice processor. This prevents Notices from being written to stderr.
Definition: utils.c:1772
int Recurse
Level of unpack recursion. Default to infinite.
Definition: run_tests.c:19
char * NewDir
Test result directory.
Definition: run_tests.c:18
Contains global declaration of variables.