Logo ROOT   6.30.04
Reference Guide
 All Namespaces Files Pages
TFileMerger.cxx
Go to the documentation of this file.
1 // @(#)root/io:$Id$
2 // Author: Andreas Peters + Fons Rademakers + Rene Brun 26/5/2005
3 
4 /*************************************************************************
5  * Copyright (C) 1995-2005, Rene Brun and Fons Rademakers. *
6  * All rights reserved. *
7  * *
8  * For the licensing terms see $ROOTSYS/LICENSE. *
9  * For the list of contributors see $ROOTSYS/README/CREDITS. *
10  *************************************************************************/
11 
12 /**
13 \class TFileMerger TFileMerger.cxx
14 \ingroup IO
15 
16 This class provides file copy and merging services.
17 
18 It can be used to copy files (not only ROOT files), using TFile or
19 any of its remote file access plugins. It is therefore useful in
20 a Grid environment where the files might be accessible only remotely.
21 The merging interface allows files containing histograms and trees
22 to be merged, like the standalone hadd program.
23 */
24 
25 #include "TFileMerger.h"
26 #include "TDirectory.h"
27 #include "TUrl.h"
28 #include "TFile.h"
29 #include "TUUID.h"
30 #include "TSystem.h"
31 #include "TKey.h"
32 #include "THashList.h"
33 #include "TObjString.h"
34 #include "TClass.h"
35 #include "Riostream.h"
36 #include "TFileMergeInfo.h"
37 #include "TClassRef.h"
38 #include "TROOT.h"
39 #include "TMemFile.h"
40 #include "TVirtualMutex.h"
41 
42 #ifdef WIN32
43 // For _getmaxstdio
44 #include <stdio.h>
45 #else
46 // For getrlimit
47 #include <sys/time.h>
48 #include <sys/resource.h>
49 #endif
50 
51 #include <cstring>
52 
53 ClassImp(TFileMerger);
54 
55 TClassRef R__TH1_Class("TH1");
56 TClassRef R__TTree_Class("TTree");
57 
58 static const Int_t kCpProgress = BIT(14);
59 static const Int_t kCintFileNumber = 100;
60 ////////////////////////////////////////////////////////////////////////////////
61 /// Return the maximum number of allowed opened files minus some wiggle room
62 /// for CINT or at least of the standard library (stdio).
63 
64 static Int_t R__GetSystemMaxOpenedFiles()
65 {
66  int maxfiles;
67 #ifdef WIN32
68  maxfiles = _getmaxstdio();
69 #else
70  rlimit filelimit;
71  if (getrlimit(RLIMIT_NOFILE,&filelimit)==0) {
72  maxfiles = filelimit.rlim_cur;
73  } else {
74  // We could not get the value from getrlimit, let's return a reasonable default.
75  maxfiles = 512;
76  }
77 #endif
78  if (maxfiles > kCintFileNumber) {
79  return maxfiles - kCintFileNumber;
80  } else if (maxfiles > 5) {
81  return maxfiles - 5;
82  } else {
83  return maxfiles;
84  }
85 }
86 
87 ////////////////////////////////////////////////////////////////////////////////
88 /// Create file merger object.
89 
90 TFileMerger::TFileMerger(Bool_t isLocal, Bool_t histoOneGo)
91  : fMaxOpenedFiles( R__GetSystemMaxOpenedFiles() ),
92  fLocal(isLocal), fHistoOneGo(histoOneGo)
93 {
94  fMergeList.SetOwner(kTRUE);
95  fExcessFiles.SetOwner(kTRUE);
96 
97  R__LOCKGUARD(gROOTMutex);
98  gROOT->GetListOfCleanups()->Add(this);
99 }
100 
101 ////////////////////////////////////////////////////////////////////////////////
102 /// Cleanup.
103 
104 TFileMerger::~TFileMerger()
105 {
106  {
107  R__LOCKGUARD(gROOTMutex);
108  gROOT->GetListOfCleanups()->Remove(this);
109  }
110  SafeDelete(fOutputFile);
111 }
112 
113 ////////////////////////////////////////////////////////////////////////////////
114 /// Reset merger file list.
115 
116 void TFileMerger::Reset()
117 {
118  fFileList.Clear();
119  fMergeList.Clear();
120  fExcessFiles.Clear();
121  fObjectNames.Clear();
122 }
123 
124 ////////////////////////////////////////////////////////////////////////////////
125 /// Add file to file merger.
126 
127 Bool_t TFileMerger::AddFile(const char *url, Bool_t cpProgress)
128 {
129  if (fPrintLevel > 0) {
130  Printf("%s Source file %d: %s", fMsgPrefix.Data(), fFileList.GetEntries() + fExcessFiles.GetEntries() + 1, url);
131  }
132 
133  TFile *newfile = 0;
134  TString localcopy;
135 
136  if (fFileList.GetEntries() >= (fMaxOpenedFiles-1)) {
137 
138  TObjString *urlObj = new TObjString(url);
139  fMergeList.Add(urlObj);
140 
141  urlObj = new TObjString(url);
142  urlObj->SetBit(kCpProgress);
143  fExcessFiles.Add(urlObj);
144  return kTRUE;
145  }
146 
147  // We want gDirectory untouched by anything going on here
148  TDirectory::TContext ctxt;
149 
150  if (fLocal) {
151  TUUID uuid;
152  localcopy.Form("file:%s/ROOTMERGE-%s.root", gSystem->TempDirectory(), uuid.AsString());
153  if (!TFile::Cp(url, localcopy, cpProgress)) {
154  Error("AddFile", "cannot get a local copy of file %s", url);
155  return kFALSE;
156  }
157  newfile = TFile::Open(localcopy, "READ");
158  } else {
159  newfile = TFile::Open(url, "READ");
160  }
161 
162  // Zombie files should also be skipped
163  if (newfile && newfile->IsZombie()) {
164  delete newfile;
165  newfile = 0;
166  }
167 
168  if (!newfile) {
169  if (fLocal)
170  Error("AddFile", "cannot open local copy %s of URL %s",
171  localcopy.Data(), url);
172  else
173  Error("AddFile", "cannot open file %s", url);
174  return kFALSE;
175  } else {
176  if (fOutputFile && fOutputFile->GetCompressionLevel() != newfile->GetCompressionLevel()) fCompressionChange = kTRUE;
177 
178  newfile->SetBit(kCanDelete);
179  fFileList.Add(newfile);
180 
181  TObjString *urlObj = new TObjString(url);
182  fMergeList.Add(urlObj);
183 
184  return kTRUE;
185  }
186 }
187 
188 ////////////////////////////////////////////////////////////////////////////////
189 /// Add the TFile to this file merger and *do not* give ownership of the TFile to this
190 /// object.
191 ///
192 /// Return kTRUE if the addition was successful.
193 
194 Bool_t TFileMerger::AddFile(TFile *source, Bool_t cpProgress)
195 {
196  return AddFile(source,kFALSE,cpProgress);
197 }
198 
199 ////////////////////////////////////////////////////////////////////////////////
200 /// Add the TFile to this file merger and give ownership of the TFile to this
201 /// object (unless kFALSE is returned).
202 ///
203 /// Return kTRUE if the addition was successful.
204 
205 Bool_t TFileMerger::AddAdoptFile(TFile *source, Bool_t cpProgress)
206 {
207  return AddFile(source,kTRUE,cpProgress);
208 }
209 
210 ////////////////////////////////////////////////////////////////////////////////
211 /// Add the TFile to this file merger and give ownership of the TFile to this
212 /// object (unless kFALSE is returned).
213 ///
214 /// Return kTRUE if the addition was successful.
215 
216 Bool_t TFileMerger::AddFile(TFile *source, Bool_t own, Bool_t cpProgress)
217 {
218  if (source == 0 || source->IsZombie()) {
219  return kFALSE;
220  }
221 
222  if (fPrintLevel > 0) {
223  Printf("%s Source file %d: %s",fMsgPrefix.Data(),fFileList.GetEntries()+1,source->GetName());
224  }
225 
226  TFile *newfile = 0;
227  TString localcopy;
228 
229  // We want gDirectory untouched by anything going on here
230  TDirectory::TContext ctxt;
231  if (fLocal && !source->InheritsFrom(TMemFile::Class())) {
232  TUUID uuid;
233  localcopy.Form("file:%s/ROOTMERGE-%s.root", gSystem->TempDirectory(), uuid.AsString());
234  if (!source->Cp(localcopy, cpProgress)) {
235  Error("AddFile", "cannot get a local copy of file %s", source->GetName());
236  return kFALSE;
237  }
238  newfile = TFile::Open(localcopy, "READ");
239  // Zombie files should also be skipped
240  if (newfile && newfile->IsZombie()) {
241  delete newfile;
242  newfile = 0;
243  }
244  } else {
245  newfile = source;
246  }
247 
248  if (!newfile) {
249  if (fLocal)
250  Error("AddFile", "cannot open local copy %s of URL %s",
251  localcopy.Data(), source->GetName());
252  else
253  Error("AddFile", "cannot open file %s", source->GetName());
254  return kFALSE;
255  } else {
256  if (fOutputFile && fOutputFile->GetCompressionSettings() != newfile->GetCompressionSettings()) fCompressionChange = kTRUE;
257 
258  if (own || newfile != source) {
259  newfile->SetBit(kCanDelete);
260  } else {
261  newfile->ResetBit(kCanDelete);
262  }
263  fFileList.Add(newfile);
264 
265  TObjString *urlObj = new TObjString(source->GetName());
266  fMergeList.Add(urlObj);
267 
268  if (newfile != source && own) {
269  delete source;
270  }
271  return kTRUE;
272  }
273 }
274 
275 ////////////////////////////////////////////////////////////////////////////////
276 /// Open merger output file.
277 
278 Bool_t TFileMerger::OutputFile(const char *outputfile, Bool_t force, Int_t compressionLevel)
279 {
280  return OutputFile(outputfile,(force?"RECREATE":"CREATE"),compressionLevel);
281 }
282 
283 ////////////////////////////////////////////////////////////////////////////////
284 /// Open merger output file.
285 
286 Bool_t TFileMerger::OutputFile(const char *outputfile, Bool_t force)
287 {
288  Bool_t res = OutputFile(outputfile,(force?"RECREATE":"CREATE"),1); // 1 is the same as the default from the TFile constructor.
289  fExplicitCompLevel = kFALSE;
290  return res;
291 }
292 
293 ////////////////////////////////////////////////////////////////////////////////
294 /// Open merger output file.
295 ///
296 /// The 'mode' parameter is passed to the TFile constructor as the option, it
297 /// should be one of 'NEW','CREATE','RECREATE','UPDATE'
298 /// 'UPDATE' is usually used in conjunction with IncrementalMerge.
299 
300 Bool_t TFileMerger::OutputFile(const char *outputfile, const char *mode, Int_t compressionLevel)
301 {
302  // We want gDirectory untouched by anything going on here
303  TDirectory::TContext ctxt;
304  if (TFile *outputFile = TFile::Open(outputfile, mode, "", compressionLevel))
305  return OutputFile(std::unique_ptr<TFile>(outputFile));
306 
307  Error("OutputFile", "cannot open the MERGER output file %s", fOutputFilename.Data());
308  return kFALSE;
309 }
310 
311 ////////////////////////////////////////////////////////////////////////////////
312 /// Set an output file opened externally by the users
313 
314 Bool_t TFileMerger::OutputFile(std::unique_ptr<TFile> outputfile)
315 {
316  if (!outputfile || outputfile->IsZombie()) {
317  Error("OutputFile", "cannot open the MERGER output file %s", (outputfile) ? outputfile->GetName() : "");
318  return kFALSE;
319  }
320 
321  if (!outputfile->IsWritable()) {
322  Error("OutputFile", "output file %s is not writable", outputfile->GetName());
323  return kFALSE;
324  }
325 
326  fExplicitCompLevel = kTRUE;
327 
328  TFile *oldfile = fOutputFile;
329  fOutputFile = 0; // This avoids the complaint from RecursiveRemove about the file being deleted which is here
330  // spurrious. (see RecursiveRemove).
331  SafeDelete(oldfile);
332 
333  fOutputFilename = outputfile->GetName();
334  // We want gDirectory untouched by anything going on here
335  TDirectory::TContext ctxt;
336  fOutputFile = outputfile.release(); // Transfer the ownership of the file.
337 
338  return kTRUE;
339 }
340 
341 ////////////////////////////////////////////////////////////////////////////////
342 /// Open merger output file. 'mode' is passed to the TFile constructor as the option, it should
343 /// be one of 'NEW','CREATE','RECREATE','UPDATE'
344 /// 'UPDATE' is usually used in conjunction with IncrementalMerge.
345 
346 Bool_t TFileMerger::OutputFile(const char *outputfile, const char *mode /* = "RECREATE" */)
347 {
348  Bool_t res = OutputFile(outputfile,mode,1); // 1 is the same as the default from the TFile constructor.
349  fExplicitCompLevel = kFALSE;
350  return res;
351 }
352 
353 ////////////////////////////////////////////////////////////////////////////////
354 /// Print list of files being merged.
355 
356 void TFileMerger::PrintFiles(Option_t *options)
357 {
358  fFileList.Print(options);
359  fExcessFiles.Print(options);
360 }
361 
362 ////////////////////////////////////////////////////////////////////////////////
363 /// Merge the files.
364 ///
365 /// If no output file was specified it will write into
366 /// the file "FileMerger.root" in the working directory. Returns true
367 /// on success, false in case of error.
368 
369 Bool_t TFileMerger::Merge(Bool_t)
370 {
371  return PartialMerge(kAll | kRegular);
372 }
373 
374 ////////////////////////////////////////////////////////////////////////////////
375 /// Merge all objects in a directory
376 ///
377 /// The type is defined by the bit values in TFileMerger::EPartialMergeType.
378 
379 Bool_t TFileMerger::MergeRecursive(TDirectory *target, TList *sourcelist, Int_t type /* = kRegular | kAll */)
380 {
381  Bool_t status = kTRUE;
382  Bool_t onlyListed = kFALSE;
383  if (fPrintLevel > 0) {
384  Printf("%s Target path: %s",fMsgPrefix.Data(),target->GetPath());
385  }
386 
387  // Get the dir name
388  TString path(target->GetPath());
389  // coverity[unchecked_value] 'target' is from a file so GetPath always returns path starting with filename:
390  path.Remove(0, std::strlen(target->GetFile()->GetPath()));
391 
392  Int_t nguess = sourcelist->GetSize()+1000;
393  THashList allNames(nguess);
394  allNames.SetOwner(kTRUE);
395  // If the mode is set to skipping list objects, add names to the allNames list
396  if (type & kSkipListed) {
397  TObjArray *arr = fObjectNames.Tokenize(" ");
398  arr->SetOwner(kFALSE);
399  for (Int_t iname=0; iname<arr->GetEntriesFast(); iname++)
400  allNames.Add(arr->At(iname));
401  delete arr;
402  }
403  ((THashList*)target->GetList())->Rehash(nguess);
404  ((THashList*)target->GetListOfKeys())->Rehash(nguess);
405 
406  TFileMergeInfo info(target);
407  info.fIOFeatures = fIOFeatures;
408  info.fOptions = fMergeOptions;
409  if (fFastMethod && ((type&kKeepCompression) || !fCompressionChange) ) {
410  info.fOptions.Append(" fast");
411  }
412 
413  TFile *current_file;
414  TDirectory *current_sourcedir;
415  if (type & kIncremental) {
416  current_file = 0;
417  current_sourcedir = target;
418  } else {
419  current_file = (TFile*)sourcelist->First();
420  current_sourcedir = current_file->GetDirectory(path);
421  }
422  while (current_file || current_sourcedir) {
423  // When current_sourcedir != 0 and current_file == 0 we are going over the target
424  // for an incremental merge.
425  if (current_sourcedir && (current_file == 0 || current_sourcedir != target)) {
426 
427  // loop over all keys in this directory
428  TIter nextkey( current_sourcedir->GetListOfKeys() );
429  TKey *key;
430  TString oldkeyname;
431 
432  while ( (key = (TKey*)nextkey())) {
433 
434  // Keep only the highest cycle number for each key for mergeable objects. They are stored
435  // in the (hash) list consecutively and in decreasing order of cycles, so we can continue
436  // until the name changes. We flag the case here and we act consequently later.
437  Bool_t alreadyseen = (oldkeyname == key->GetName()) ? kTRUE : kFALSE;
438 
439  // Read in but do not copy directly the processIds.
440  if (strcmp(key->GetClassName(),"TProcessID") == 0) { key->ReadObj(); continue;}
441 
442  // If we have already seen this object [name], we already processed
443  // the whole list of files for this objects and we can just skip it
444  // and any related cycles.
445  if (allNames.FindObject(key->GetName())) {
446  oldkeyname = key->GetName();
447  continue;
448  }
449 
450  TClass *cl = TClass::GetClass(key->GetClassName());
451  if (!cl) {
452  Info("MergeRecursive", "cannot indentify object type (%s), name: %s title: %s",
453  key->GetClassName(), key->GetName(), key->GetTitle());
454  continue;
455  }
456  // For mergeable objects we add the names in a local hashlist handling them
457  // again (see above)
458  if (cl->GetMerge() || cl->InheritsFrom(TDirectory::Class()) ||
459  (cl->IsTObject() && !cl->IsLoaded() &&
460  /* If it has a dictionary and GetMerge() is nullptr then we already know the answer
461  to the next question is 'no, if we were to ask we would useless trigger
462  auto-parsing */
463  (cl->GetMethodWithPrototype("Merge", "TCollection*,TFileMergeInfo*") ||
464  cl->GetMethodWithPrototype("Merge", "TCollection*"))))
465  allNames.Add(new TObjString(key->GetName()));
466 
467  if (fNoTrees && cl->InheritsFrom(R__TTree_Class)) {
468  // Skip the TTree objects and any related cycles.
469  oldkeyname = key->GetName();
470  continue;
471  }
472  // Check if only the listed objects are to be merged
473  if (type & kOnlyListed) {
474  onlyListed = kFALSE;
475  oldkeyname = key->GetName();
476  oldkeyname += " ";
477  onlyListed = fObjectNames.Contains(oldkeyname);
478  oldkeyname = key->GetName();
479  if ((!onlyListed) && (!cl->InheritsFrom(TDirectory::Class()))) continue;
480  }
481 
482  if (!(type&kResetable && type&kNonResetable)) {
483  // If neither or both are requested at the same time, we merger both types.
484  if (!(type&kResetable)) {
485  if (cl->GetResetAfterMerge()) {
486  // Skip the object with a reset after merge routine (TTree and other incrementally mergeable objects)
487  oldkeyname = key->GetName();
488  continue;
489  }
490  }
491  if (!(type&kNonResetable)) {
492  if (!cl->GetResetAfterMerge()) {
493  // Skip the object without a reset after merge routine (Histograms and other non incrementally mergeable objects)
494  oldkeyname = key->GetName();
495  continue;
496  }
497  }
498  }
499  // read object from first source file
500  TObject *obj;
501  if (type & kIncremental) {
502  obj = current_sourcedir->GetList()->FindObject(key->GetName());
503  if (!obj) {
504  obj = key->ReadObj();
505  }
506  } else {
507  obj = key->ReadObj();
508  }
509  if (!obj) {
510  Info("MergeRecursive", "could not read object for key {%s, %s}",
511  key->GetName(), key->GetTitle());
512  continue;
513  }
514  // if (cl->IsTObject())
515  // obj->ResetBit(kMustCleanup);
516  if (cl->IsTObject() && cl != obj->IsA()) {
517  Error("MergeRecursive", "TKey and object retrieve disagree on type (%s vs %s). Continuing with %s.",
518  key->GetClassName(), obj->IsA()->GetName(), obj->IsA()->GetName());
519  cl = obj->IsA();
520  }
521  Bool_t canBeMerged = kTRUE;
522 
523  if ( cl->InheritsFrom( TDirectory::Class() ) ) {
524  // it's a subdirectory
525 
526  target->cd();
527  TDirectory *newdir;
528 
529  // For incremental or already seen we may have already a directory created
530  if (type & kIncremental || alreadyseen) {
531  newdir = target->GetDirectory(obj->GetName());
532  if (!newdir) {
533  newdir = target->mkdir( obj->GetName(), obj->GetTitle() );
534  // newdir->ResetBit(kMustCleanup);
535  }
536  } else {
537  newdir = target->mkdir( obj->GetName(), obj->GetTitle() );
538  // newdir->ResetBit(kMustCleanup);
539  }
540 
541  // newdir is now the starting point of another round of merging
542  // newdir still knows its depth within the target file via
543  // GetPath(), so we can still figure out where we are in the recursion
544 
545  // If this folder is a onlyListed object, merge everything inside.
546  if (onlyListed) type &= ~kOnlyListed;
547  status = MergeRecursive(newdir, sourcelist, type);
548  if (onlyListed) type |= kOnlyListed;
549  if (!status) return status;
550  } else if (cl->GetMerge()) {
551 
552  // Check if already treated
553  if (alreadyseen) continue;
554 
555  TList inputs;
556  Bool_t oneGo = fHistoOneGo && cl->InheritsFrom(R__TH1_Class);
557 
558  // Loop over all source files and merge same-name object
559  TFile *nextsource = current_file ? (TFile*)sourcelist->After( current_file ) : (TFile*)sourcelist->First();
560  if (nextsource == 0) {
561  // There is only one file in the list
562  ROOT::MergeFunc_t func = cl->GetMerge();
563  func(obj, &inputs, &info);
564  info.fIsFirst = kFALSE;
565  } else {
566  do {
567  // make sure we are at the correct directory level by cd'ing to path
568  TDirectory *ndir = nextsource->GetDirectory(path);
569  if (ndir) {
570  // For consistency (and persformance), we reset the MustCleanup be also for those
571  // 'key' retrieved indirectly.
572  // ndir->ResetBit(kMustCleanup);
573  ndir->cd();
574  TKey *key2 = (TKey*)ndir->GetListOfKeys()->FindObject(key->GetName());
575  if (key2) {
576  TObject *hobj = key2->ReadObj();
577  if (!hobj) {
578  Info("MergeRecursive", "could not read object for key {%s, %s}; skipping file %s",
579  key->GetName(), key->GetTitle(), nextsource->GetName());
580  nextsource = (TFile*)sourcelist->After(nextsource);
581  continue;
582  }
583  // Set ownership for collections
584  if (hobj->InheritsFrom(TCollection::Class())) {
585  ((TCollection*)hobj)->SetOwner();
586  }
587  hobj->ResetBit(kMustCleanup);
588  inputs.Add(hobj);
589  if (!oneGo) {
590  ROOT::MergeFunc_t func = cl->GetMerge();
591  Long64_t result = func(obj, &inputs, &info);
592  info.fIsFirst = kFALSE;
593  if (result < 0) {
594  Error("MergeRecursive", "calling Merge() on '%s' with the corresponding object in '%s'",
595  obj->GetName(), nextsource->GetName());
596  }
597  inputs.Delete();
598  }
599  }
600  }
601  nextsource = (TFile*)sourcelist->After( nextsource );
602  } while (nextsource);
603  // Merge the list, if still to be done
604  if (oneGo || info.fIsFirst) {
605  ROOT::MergeFunc_t func = cl->GetMerge();
606  func(obj, &inputs, &info);
607  info.fIsFirst = kFALSE;
608  inputs.Delete();
609  }
610  }
611  } else if (cl->IsTObject() &&
612  cl->GetMethodWithPrototype("Merge", "TCollection*,TFileMergeInfo*") ) {
613  // Object implements Merge(TCollection*,TFileMergeInfo*) and has a reflex dictionary ...
614 
615  // Check if already treated
616  if (alreadyseen) continue;
617 
618  TList listH;
619  TString listHargs;
620  listHargs.Form("(TCollection*)0x%lx,(TFileMergeInfo*)0x%lx", (ULong_t)&listH,(ULong_t)&info);
621 
622  // Loop over all source files and merge same-name object
623  TFile *nextsource = current_file ? (TFile*)sourcelist->After( current_file ) : (TFile*)sourcelist->First();
624  if (nextsource == 0) {
625  // There is only one file in the list
626  Int_t error = 0;
627  obj->Execute("Merge", listHargs.Data(), &error);
628  info.fIsFirst = kFALSE;
629  if (error) {
630  Error("MergeRecursive", "calling Merge() on '%s' with the corresponding object in '%s'",
631  obj->GetName(), key->GetName());
632  }
633  } else {
634  while (nextsource) {
635  // make sure we are at the correct directory level by cd'ing to path
636  TDirectory *ndir = nextsource->GetDirectory(path);
637  if (ndir) {
638  // For consistency (and persformance), we reset the MustCleanup be also for those
639  // 'key' retrieved indirectly.
640  //ndir->ResetBit(kMustCleanup);
641  ndir->cd();
642  TKey *key2 = (TKey*)ndir->GetListOfKeys()->FindObject(key->GetName());
643  if (key2) {
644  TObject *hobj = key2->ReadObj();
645  if (!hobj) {
646  Info("MergeRecursive", "could not read object for key {%s, %s}; skipping file %s",
647  key->GetName(), key->GetTitle(), nextsource->GetName());
648  nextsource = (TFile*)sourcelist->After(nextsource);
649  continue;
650  }
651  // Set ownership for collections
652  if (hobj->InheritsFrom(TCollection::Class())) {
653  ((TCollection*)hobj)->SetOwner();
654  }
655  hobj->ResetBit(kMustCleanup);
656  listH.Add(hobj);
657  Int_t error = 0;
658  obj->Execute("Merge", listHargs.Data(), &error);
659  info.fIsFirst = kFALSE;
660  if (error) {
661  Error("MergeRecursive", "calling Merge() on '%s' with the corresponding object in '%s'",
662  obj->GetName(), nextsource->GetName());
663  }
664  listH.Delete();
665  }
666  }
667  nextsource = (TFile*)sourcelist->After( nextsource );
668  }
669  // Merge the list, if still to be done
670  if (info.fIsFirst) {
671  Int_t error = 0;
672  obj->Execute("Merge", listHargs.Data(), &error);
673  info.fIsFirst = kFALSE;
674  listH.Delete();
675  }
676  }
677  } else if (cl->IsTObject() &&
678  cl->GetMethodWithPrototype("Merge", "TCollection*") ) {
679  // Object implements Merge(TCollection*) and has a reflex dictionary ...
680 
681  // Check if already treated
682  if (alreadyseen) continue;
683 
684  TList listH;
685  TString listHargs;
686  listHargs.Form("((TCollection*)0x%lx)", (ULong_t)&listH);
687 
688  // Loop over all source files and merge same-name object
689  TFile *nextsource = current_file ? (TFile*)sourcelist->After( current_file ) : (TFile*)sourcelist->First();
690  if (nextsource == 0) {
691  // There is only one file in the list
692  Int_t error = 0;
693  obj->Execute("Merge", listHargs.Data(), &error);
694  if (error) {
695  Error("MergeRecursive", "calling Merge() on '%s' with the corresponding object in '%s'",
696  obj->GetName(), key->GetName());
697  }
698  } else {
699  while (nextsource) {
700  // make sure we are at the correct directory level by cd'ing to path
701  TDirectory *ndir = nextsource->GetDirectory(path);
702  if (ndir) {
703  // For consistency (and persformance), we reset the MustCleanup be also for those
704  // 'key' retrieved indirectly.
705  //ndir->ResetBit(kMustCleanup);
706  ndir->cd();
707  TKey *key2 = (TKey*)ndir->GetListOfKeys()->FindObject(key->GetName());
708  if (key2) {
709  TObject *hobj = key2->ReadObj();
710  if (!hobj) {
711  Info("MergeRecursive", "could not read object for key {%s, %s}; skipping file %s",
712  key->GetName(), key->GetTitle(), nextsource->GetName());
713  nextsource = (TFile*)sourcelist->After(nextsource);
714  continue;
715  }
716  // Set ownership for collections
717  if (hobj->InheritsFrom(TCollection::Class())) {
718  ((TCollection*)hobj)->SetOwner();
719  }
720  hobj->ResetBit(kMustCleanup);
721  listH.Add(hobj);
722  Int_t error = 0;
723  obj->Execute("Merge", listHargs.Data(), &error);
724  info.fIsFirst = kFALSE;
725  if (error) {
726  Error("MergeRecursive", "calling Merge() on '%s' with the corresponding object in '%s'",
727  obj->GetName(), nextsource->GetName());
728  }
729  listH.Delete();
730  }
731  }
732  nextsource = (TFile*)sourcelist->After( nextsource );
733  }
734  // Merge the list, if still to be done
735  if (info.fIsFirst) {
736  Int_t error = 0;
737  obj->Execute("Merge", listHargs.Data(), &error);
738  info.fIsFirst = kFALSE;
739  listH.Delete();
740  }
741  }
742  } else {
743  // Object is of no type that we can merge
744  canBeMerged = kFALSE;
745  }
746 
747  // now write the merged histogram (which is "in" obj) to the target file
748  // note that this will just store obj in the current directory level,
749  // which is not persistent until the complete directory itself is stored
750  // by "target->SaveSelf()" below
751  target->cd();
752 
753  oldkeyname = key->GetName();
754  //!!if the object is a tree, it is stored in globChain...
755  if(cl->InheritsFrom( TDirectory::Class() )) {
756  //printf("cas d'une directory\n");
757 
758  auto dirobj = dynamic_cast<TDirectory*>(obj);
759  TString dirpath(dirobj->GetPath());
760  // coverity[unchecked_value] 'target' is from a file so GetPath always returns path starting with filename:
761  dirpath.Remove(0, std::strlen(dirobj->GetFile()->GetPath()));
762 
763  // Do not delete the directory if it is part of the output
764  // and we are in incremental mode (because it will be reuse
765  // and has not been written to disk (for performance reason).
766  // coverity[var_deref_model] the IsA()->InheritsFrom guarantees that the dynamic_cast will succeed.
767  if (!(type&kIncremental) || dirobj->GetFile() != target) {
768  dirobj->ResetBit(kMustCleanup);
769  delete dirobj;
770  }
771  // Let's also delete the directory from the other source (thanks to the 'allNames'
772  // mechanism above we will not process the directories when tranversing the next
773  // files).
774  TFile *nextsource = current_file ? (TFile*)sourcelist->After( current_file ) : (TFile*)sourcelist->First();
775  while (nextsource) {
776  TDirectory *ndir = nextsource->GetDirectory(dirpath);
777  // For consistency (and persformance), we reset the MustCleanup be also for those
778  // 'key' retrieved indirectly.
779  ndir->ResetBit(kMustCleanup);
780  delete ndir;
781  nextsource = (TFile*)sourcelist->After( nextsource );
782  }
783  } else if (cl->InheritsFrom( TCollection::Class() )) {
784  // Don't overwrite, if the object were not merged.
785  if ( obj->Write( oldkeyname, canBeMerged ? TObject::kSingleKey | TObject::kOverwrite : TObject::kSingleKey) <= 0 ) {
786  status = kFALSE;
787  }
788  ((TCollection*)obj)->SetOwner();
789  delete obj;
790  } else {
791  // Don't overwrite, if the object were not merged.
792  // NOTE: this is probably wrong for emulated objects.
793  if (cl->IsTObject()) {
794  if ( obj->Write( oldkeyname, canBeMerged ? TObject::kOverwrite : 0) <= 0) {
795  status = kFALSE;
796  }
797  obj->ResetBit(kMustCleanup);
798  } else {
799  if ( target->WriteObjectAny( (void*)obj, cl, oldkeyname, canBeMerged ? "OverWrite" : "" ) <= 0) {
800  status = kFALSE;
801  }
802  }
803  cl->Destructor(obj); // just in case the class is not loaded.
804  }
805  info.Reset();
806  } // while ( ( TKey *key = (TKey*)nextkey() ) )
807  }
808  current_file = current_file ? (TFile*)sourcelist->After(current_file) : (TFile*)sourcelist->First();
809  if (current_file) {
810  current_sourcedir = current_file->GetDirectory(path);
811  } else {
812  current_sourcedir = 0;
813  }
814  }
815  // save modifications to the target directory.
816  if (!(type&kIncremental)) {
817  // In case of incremental build, we will call Write on the top directory/file, so we do not need
818  // to call SaveSelf explicilty.
819  target->SaveSelf(kTRUE);
820  }
821 
822  return status;
823 }
824 
825 ////////////////////////////////////////////////////////////////////////////////
826 /// Merge the files. If no output file was specified it will write into
827 /// the file "FileMerger.root" in the working directory. Returns true
828 /// on success, false in case of error.
829 /// The type is defined by the bit values in EPartialMergeType:
830 /// kRegular : normal merge, overwritting the output file
831 /// kIncremental : merge the input file with the content of the output file (if already exising) (default)
832 /// kAll : merge all type of objects (default)
833 /// kResetable : merge only the objects with a MergeAfterReset member function.
834 /// kNonResetable : merge only the objects without a MergeAfterReset member function.
835 ///
836 /// If the type is set to kIncremental the output file is done deleted at the end of
837 /// this operation. If the type is not set to kIncremental, the output file is closed.
838 
839 Bool_t TFileMerger::PartialMerge(Int_t in_type)
840 {
841  if (!fOutputFile) {
842  TString outf(fOutputFilename);
843  if (outf.IsNull()) {
844  outf.Form("file:%s/FileMerger.root", gSystem->TempDirectory());
845  Info("PartialMerge", "will merge the results to the file %s\n"
846  "since you didn't specify a merge filename",
847  TUrl(outf).GetFile());
848  }
849  if (!OutputFile(outf.Data())) {
850  return kFALSE;
851  }
852  }
853 
854  // Special treament for the single file case ...
855  if ((fFileList.GetEntries() == 1) && !fExcessFiles.GetEntries() &&
856  !(in_type & kIncremental) && !fCompressionChange && !fExplicitCompLevel) {
857  fOutputFile->Close();
858  SafeDelete(fOutputFile);
859 
860  TFile *file = (TFile *) fFileList.First();
861  if (!file || (file && file->IsZombie())) {
862  Error("PartialMerge", "one-file case: problem attaching to file");
863  return kFALSE;
864  }
865  Bool_t result = kTRUE;
866  if (!(result = file->Cp(fOutputFilename))) {
867  Error("PartialMerge", "one-file case: could not copy '%s' to '%s'",
868  file->GetPath(), fOutputFilename.Data());
869  return kFALSE;
870  }
871  if (file->TestBit(kCanDelete)) file->Close();
872 
873  // Remove the temporary file
874  if (fLocal && !file->InheritsFrom(TMemFile::Class())) {
875  TUrl u(file->GetPath(), kTRUE);
876  if (gSystem->Unlink(u.GetFile()) != 0)
877  Warning("PartialMerge", "problems removing temporary local file '%s'", u.GetFile());
878  }
879  fFileList.Clear();
880  return result;
881  }
882 
883  fOutputFile->SetBit(kMustCleanup);
884 
885  TDirectory::TContext ctxt;
886 
887  Bool_t result = kTRUE;
888  Int_t type = in_type;
889  while (result && fFileList.GetEntries()>0) {
890  result = MergeRecursive(fOutputFile, &fFileList, type);
891 
892  // Remove local copies if there are any
893  TIter next(&fFileList);
894  TFile *file;
895  while ((file = (TFile*) next())) {
896  // close the files
897  if (file->TestBit(kCanDelete)) file->Close();
898  // remove the temporary files
899  if(fLocal && !file->InheritsFrom(TMemFile::Class())) {
900  TString p(file->GetPath());
901  // coverity[unchecked_value] Index is return a value with range or NPos to select the whole name.
902  p = p(0, p.Index(':',0));
903  gSystem->Unlink(p);
904  }
905  }
906  fFileList.Clear();
907  if (result && fExcessFiles.GetEntries() > 0) {
908  // We merge the first set of files in the output,
909  // we now need to open the next set and make
910  // sure we accumulate into the output, so we
911  // switch to incremental merging (if not already set)
912  type = type | kIncremental;
913  result = OpenExcessFiles();
914  }
915  }
916  if (!result) {
917  Error("Merge", "error during merge of your ROOT files");
918  } else {
919  // Close or write is required so the file is complete.
920  if (in_type & kIncremental) {
921  fOutputFile->Write("",TObject::kOverwrite);
922  } else {
923  gROOT->GetListOfFiles()->Remove(fOutputFile);
924  fOutputFile->Close();
925  }
926  }
927 
928  // Cleanup
929  if (in_type & kIncremental) {
930  Clear();
931  } else {
932  fOutputFile->ResetBit(kMustCleanup);
933  SafeDelete(fOutputFile);
934  }
935  return result;
936 }
937 
938 ////////////////////////////////////////////////////////////////////////////////
939 /// Open up to fMaxOpenedFiles of the excess files.
940 
941 Bool_t TFileMerger::OpenExcessFiles()
942 {
943  if (fPrintLevel > 0) {
944  Printf("%s Opening the next %d files", fMsgPrefix.Data(), TMath::Min(fExcessFiles.GetEntries(), fMaxOpenedFiles - 1));
945  }
946  Int_t nfiles = 0;
947  TIter next(&fExcessFiles);
948  TObjString *url = 0;
949  TString localcopy;
950  // We want gDirectory untouched by anything going on here
951  TDirectory::TContext ctxt;
952  while( nfiles < (fMaxOpenedFiles-1) && ( url = (TObjString*)next() ) ) {
953  TFile *newfile = 0;
954  if (fLocal) {
955  TUUID uuid;
956  localcopy.Form("file:%s/ROOTMERGE-%s.root", gSystem->TempDirectory(), uuid.AsString());
957  if (!TFile::Cp(url->GetName(), localcopy, url->TestBit(kCpProgress))) {
958  Error("OpenExcessFiles", "cannot get a local copy of file %s", url->GetName());
959  return kFALSE;
960  }
961  newfile = TFile::Open(localcopy, "READ");
962  } else {
963  newfile = TFile::Open(url->GetName(), "READ");
964  }
965 
966  if (!newfile) {
967  if (fLocal)
968  Error("OpenExcessFiles", "cannot open local copy %s of URL %s",
969  localcopy.Data(), url->GetName());
970  else
971  Error("OpenExcessFiles", "cannot open file %s", url->GetName());
972  return kFALSE;
973  } else {
974  if (fOutputFile && fOutputFile->GetCompressionLevel() != newfile->GetCompressionLevel()) fCompressionChange = kTRUE;
975 
976  newfile->SetBit(kCanDelete);
977  fFileList.Add(newfile);
978  ++nfiles;
979  fExcessFiles.Remove(url);
980  }
981  }
982  return kTRUE;
983 }
984 
985 ////////////////////////////////////////////////////////////////////////////////
986 /// Intercept the case where the output TFile is deleted!
987 
988 void TFileMerger::RecursiveRemove(TObject *obj)
989 {
990  if (obj == fOutputFile) {
991  Fatal("RecursiveRemove","Output file of the TFile Merger (targeting %s) has been deleted (likely due to a TTree larger than 100Gb)", fOutputFilename.Data());
992  }
993 
994 }
995 
996 ////////////////////////////////////////////////////////////////////////////////
997 /// Set a limit to the number of files that TFileMerger will open simultaneously.
998 ///
999 /// If the request is higher than the system limit, we reset it to the system limit.
1000 /// If the request is less than two, we reset it to 2 (one for the output file and one for the input file).
1001 
1002 void TFileMerger::SetMaxOpenedFiles(Int_t newmax)
1003 {
1004  Int_t sysmax = R__GetSystemMaxOpenedFiles();
1005  if (newmax < sysmax) {
1006  fMaxOpenedFiles = newmax;
1007  } else {
1008  fMaxOpenedFiles = sysmax;
1009  }
1010  if (fMaxOpenedFiles < 2) {
1011  fMaxOpenedFiles = 2;
1012  }
1013 }
1014 
1015 ////////////////////////////////////////////////////////////////////////////////
1016 /// Set the prefix to be used when printing informational message.
1017 
1018 void TFileMerger::SetMsgPrefix(const char *prefix)
1019 {
1020  fMsgPrefix = prefix;
1021 }
1022