1 /*
2  *
3  *  Copyright (C) 2003-2019, OFFIS e.V.
4  *  All rights reserved.  See COPYRIGHT file for details.
5  *
6  *  This software and supporting documentation were developed by
7  *
8  *    OFFIS e.V.
9  *    R&D Division Health
10  *    Escherweg 2
11  *    D-26121 Oldenburg, Germany
12  *
13  *
14  *  Module:  dcmdata
15  *
16  *  Author:  Michael Onken
17  *
18  *  Purpose: Class for modifying DICOM files from commandline
19  *
20  */
21 
22 #include "dcmtk/config/osconfig.h"   // make sure OS specific configuration is included first
23 
24 #include "mdfconen.h"
25 #include "mdfdsman.h"
26 #include "dcmtk/ofstd/ofstd.h"
27 #include "dcmtk/ofstd/ofconapp.h"
28 #include "dcmtk/dcmdata/dctk.h"
29 #include "dcmtk/dcmdata/dcistrmz.h"    /* for dcmZlibExpectRFC1950Encoding */
30 
31 #define SHORTCOL 4
32 #define LONGCOL 21
33 
34 #ifdef WITH_ZLIB
35 BEGIN_EXTERN_C
36 #include <zlib.h>
37 END_EXTERN_C
38 #endif
39 
40 static OFLogger dcmodifyLogger = OFLog::getLogger("dcmtk.apps.dcmodify");
41 
MdfJob(const MdfJob & other)42 MdfJob::MdfJob(const MdfJob& other)
43 : option(other.option), path(other.path), value(other.value)
44 {
45 }
46 
operator ==(const MdfJob & j) const47 OFBool MdfJob::operator==(const MdfJob &j) const
48 {
49     return (option == j.option) && (path == j.path) && (value == j.value);
50 }
51 
operator =(const MdfJob & j)52 MdfJob &MdfJob::operator=(const MdfJob &j)
53 {
54     option = j.option;
55     path = j.path;
56     value = j.value;
57     return *this;
58 }
59 
60 
MdfConsoleEngine(int argc,char * argv[],const char * application_name)61 MdfConsoleEngine::MdfConsoleEngine(int argc, char *argv[],
62                                    const char *application_name)
63   : app(NULL), cmd(NULL), ds_man(NULL), ignore_errors_option(OFFalse),
64     update_metaheader_uids_option(OFTrue), no_backup_option(OFFalse),
65     read_mode_option(ERM_autoDetect), input_xfer_option(EXS_Unknown),
66     output_dataset_option(OFFalse), output_xfer_option(EXS_Unknown),
67     glenc_option(EGL_recalcGL), enctype_option(EET_ExplicitLength),
68     padenc_option(EPD_withoutPadding), filepad_option(0),
69     itempad_option(0), ignore_missing_tags_option(OFFalse),
70     no_reservation_checks(OFFalse), ignore_un_modifies(OFFalse),
71     create_if_necessary(OFFalse), was_created(OFFalse), jobs(NULL), files(NULL)
72 {
73     char rcsid[200];
74     // print application header
75     sprintf(rcsid, "$dcmtk: %s v%s %s $", application_name, OFFIS_DCMTK_VERSION, OFFIS_DCMTK_RELEASEDATE);
76 
77     // the next lines describe commandline arguments/options
78     app = new OFConsoleApplication(application_name, "Modify DICOM files", rcsid);
79     cmd = new OFCommandLine();
80 
81     cmd->setOptionColumns(LONGCOL, SHORTCOL);
82     cmd->setParamColumn(LONGCOL + SHORTCOL + 4);
83 
84     cmd->addParam("dcmfile-in", "DICOM input filename to be modified", OFCmdParam::PM_MultiMandatory);
85 
86     // add options to commandline application
87     cmd->addGroup("general options:", LONGCOL, SHORTCOL + 2);
88         cmd->addOption("--help",                    "-h",      "print this help text and exit", OFCommandLine::AF_Exclusive);
89         cmd->addOption("--version",                            "print version information and exit", OFCommandLine::AF_Exclusive);
90         OFLog::addOptions(*cmd);
91     cmd->addGroup("input options:");
92         cmd->addSubGroup("input file format:");
93             cmd->addOption("--read-file",           "+f",      "read file format or data set (default)");
94             cmd->addOption("--read-file-only",      "+fo",     "read file format only");
95             cmd->addOption("--read-dataset",        "-f",      "read data set without file meta information");
96             cmd->addOption("--create-file",         "+fc",     "create file format if file does not exist");
97         cmd->addSubGroup("input transfer syntax:");
98             cmd->addOption("--read-xfer-auto",      "-t=",     "use TS recognition (default)");
99             cmd->addOption("--read-xfer-detect",    "-td",     "ignore TS specified in the file meta header");
100             cmd->addOption("--read-xfer-little",    "-te",     "read with explicit VR little endian TS");
101             cmd->addOption("--read-xfer-big",       "-tb",     "read with explicit VR big endian TS");
102             cmd->addOption("--read-xfer-implicit",  "-ti",     "read with implicit VR little endian TS");
103         cmd->addSubGroup("parsing of odd-length attributes:");
104             cmd->addOption("--accept-odd-length",   "+ao",     "accept odd length attributes (default)");
105             cmd->addOption("--assume-even-length",  "+ae",     "assume real length is one byte larger");
106         cmd->addSubGroup("automatic data correction:");
107             cmd->addOption("--enable-correction",   "+dc",     "enable automatic data correction (default)");
108             cmd->addOption("--disable-correction",  "-dc",     "disable automatic data correction");
109 #ifdef WITH_ZLIB
110         cmd->addSubGroup("bitstream format of deflated input:");
111             cmd->addOption("--bitstream-deflated",  "+bd",     "expect deflated bitstream (default)");
112             cmd->addOption("--bitstream-zlib",      "+bz",     "expect deflated zlib bitstream");
113 #endif
114 
115     cmd->addGroup("processing options:");
116         cmd->addSubGroup("backup input files:");
117             cmd->addOption("--backup",                         "backup files before modifying (default)");
118             cmd->addOption("--no-backup",           "-nb",     "don't backup files (DANGEROUS)");
119         cmd->addSubGroup("insert mode:");
120             cmd->addOption("--insert",              "-i",   1, "\"[t]ag-path=[v]alue\"",
121                                                                "insert (or overwrite) path at position t\nwith value v", OFCommandLine::AF_NoWarning);
122             cmd->addOption("--insert-from-file",    "-if",  1, "\"[t]ag-path=[f]ilename\"",
123                                                                "insert (or overwrite) path at position t\nwith value from file f", OFCommandLine::AF_NoWarning);
124             cmd->addOption("--no-reserv-check",     "-nrc",    "do not check private reservations\nwhen inserting private tags");
125         cmd->addSubGroup("modify mode:");
126             cmd->addOption("--modify",              "-m",   1, "\"[t]ag-path=[v]alue\"",
127                                                                "modify tag at position t to value v", OFCommandLine::AF_NoWarning);
128             cmd->addOption("--modify-from-file",    "-mf",  1, "\"[t]ag-path=[f]ilename\"",
129                                                                "modify tag at position t to value from file f", OFCommandLine::AF_NoWarning);
130             cmd->addOption("--modify-all",          "-ma",  1, "\"[t]ag=[v]alue\"",
131                                                                "modify ALL matching tags t in file to value v", OFCommandLine::AF_NoWarning);
132         cmd->addSubGroup("erase mode:");
133             cmd->addOption("--erase",               "-e",   1, "\"[t]ag-path\"",
134                                                                "erase tag/item at position t", OFCommandLine::AF_NoWarning);
135             cmd->addOption("--erase-all",           "-ea",  1, "\"[t]ag\"",
136                                                                "erase ALL matching tags t in file", OFCommandLine::AF_NoWarning);
137             cmd->addOption("--erase-private",       "-ep",     "erase ALL private data from file", OFCommandLine::AF_NoWarning);
138         cmd->addSubGroup("unique identifier:");
139             cmd->addOption("--gen-stud-uid",        "-gst",    "generate new Study Instance UID", OFCommandLine::AF_NoWarning);
140             cmd->addOption("--gen-ser-uid",         "-gse",    "generate new Series Instance UID", OFCommandLine::AF_NoWarning);
141             cmd->addOption("--gen-inst-uid",        "-gin",    "generate new SOP Instance UID", OFCommandLine::AF_NoWarning);
142             cmd->addOption("--no-meta-uid",         "-nmu",    "do not update metaheader UIDs if related\nUIDs in the dataset are modified");
143         cmd->addSubGroup("error handling:");
144             cmd->addOption("--ignore-errors",       "-ie",     "continue with file, if modify error occurs");
145             cmd->addOption("--ignore-missing-tags", "-imt",    "treat 'tag not found' as success\nwhen modifying or erasing in datasets");
146             cmd->addOption("--ignore-un-values",    "-iun",    "do not try writing any values to elements\nhaving a VR of UN");
147     cmd->addGroup("output options:");
148         cmd->addSubGroup("output file format:");
149             cmd->addOption("--write-file",          "+F",      "write file format (default)");
150             cmd->addOption("--write-dataset",       "-F",      "write data set without file meta information");
151         cmd->addSubGroup("output transfer syntax:");
152             cmd->addOption("--write-xfer-same",     "+t=",     "write with same TS as input (default)");
153             cmd->addOption("--write-xfer-little",   "+te",     "write with explicit VR little endian TS");
154             cmd->addOption("--write-xfer-big",      "+tb",     "write with explicit VR big endian TS");
155             cmd->addOption("--write-xfer-implicit", "+ti",     "write with implicit VR little endian TS");
156         cmd->addSubGroup("post-1993 value representations:");
157             cmd->addOption("--enable-new-vr",       "+u",      "enable support for new VRs (UN/UT) (default)");
158             cmd->addOption("--disable-new-vr",      "-u",      "disable support for new VRs, convert to OB");
159         cmd->addSubGroup("group length encoding:");
160             cmd->addOption("--group-length-recalc", "+g=",     "recalculate group lengths if present (default)");
161             cmd->addOption("--group-length-create", "+g",      "always write with group length elements");
162             cmd->addOption("--group-length-remove", "-g",      "always write without group length elements");
163         cmd->addSubGroup("length encoding in sequences and items:");
164             cmd->addOption("--length-explicit",     "+le",     "write with explicit lengths (default)");
165             cmd->addOption("--length-undefined",    "-le",     "write with undefined lengths");
166         cmd->addSubGroup("data set trailing padding (not with --write-dataset):");
167             cmd->addOption("--padding-retain",      "-p=",     "do not change padding\n(default if not --write-dataset)");
168             cmd->addOption("--padding-off",         "-p",      "no padding (implicit if --write-dataset)");
169             cmd->addOption("--padding-create",      "+p",   2, "[f]ile-pad [i]tem-pad: integer",
170                                                                "align file on multiple of f bytes\nand items on multiple of i bytes");
171 
172     // evaluate commandline
173     prepareCmdLineArgs(argc, argv, application_name);
174     if (app->parseCommandLine(*cmd, argc, argv))
175     {
176         /* print help text and exit */
177         if (cmd->getArgCount() == 0)
178             app->printUsage();
179 
180         /* check exclusive options first */
181         if (cmd->hasExclusiveOption())
182         {
183             if (cmd->findOption("--version"))
184             {
185                 app->printHeader(OFTrue /*print host identifier*/);
186                 ofConsole.lockCout() << OFendl << "External libraries used:";
187 #ifdef WITH_ZLIB
188                 ofConsole.getCout() << OFendl << "- ZLIB, Version " << zlibVersion() << OFendl;
189 #else
190                 ofConsole.getCout() << " none" << OFendl;
191 #endif
192                 ofConsole.unlockCout();
193                 delete app;
194                 delete cmd;
195                 exit(0);
196             }
197         }
198 
199         // iterate the files (parameters) and save them in list
200         files = new OFList<OFString>;
201         OFString current_file;
202         for (int i = 1; i <= cmd->getParamCount(); i++)
203         {
204             cmd->getParam(i,current_file);
205             files->push_back(current_file);
206         }
207         // if no files are given: return with error message
208         if (files->empty())
209         {
210             OFLOG_ERROR(dcmodifyLogger, "no dicom files given!");
211             delete app;
212             delete cmd;
213             exit(1);
214         }
215 
216         // make sure data dictionary is loaded
217         if (!dcmDataDict.isDictionaryLoaded())
218             OFLOG_WARN(dcmodifyLogger, "no data dictionary loaded, "
219                 << "check environment variable: " << DCM_DICT_ENVIRONMENT_VARIABLE);
220     }
221 
222     /* print resource identifier */
223     OFLOG_DEBUG(dcmodifyLogger, rcsid << OFendl);
224 }
225 
226 
parseNonJobOptions()227 void MdfConsoleEngine::parseNonJobOptions()
228 {
229     // catch "general" options
230     OFLog::configureFromCommandLine(*cmd, *app);
231 
232     // input options
233     cmd->beginOptionBlock();
234     if (cmd->findOption("--read-file"))
235         read_mode_option = ERM_autoDetect;
236     if (cmd->findOption("--read-file-only"))
237         read_mode_option = ERM_fileOnly;
238     if (cmd->findOption("--read-dataset"))
239         read_mode_option = ERM_dataset;
240     cmd->endOptionBlock();
241 
242     if (cmd->findOption("--create-file"))
243         create_if_necessary = OFTrue;
244 
245     cmd->beginOptionBlock();
246     if (cmd->findOption("--read-xfer-auto"))
247         input_xfer_option = EXS_Unknown;
248     if (cmd->findOption("--read-xfer-detect"))
249         dcmAutoDetectDatasetXfer.set(OFTrue);
250     if (cmd->findOption("--read-xfer-little"))
251     {
252         app->checkDependence("--read-xfer-little", "--read-dataset", read_mode_option == ERM_dataset);
253         input_xfer_option = EXS_LittleEndianExplicit;
254     }
255     if (cmd->findOption("--read-xfer-big"))
256     {
257         app->checkDependence("--read-xfer-big", "--read-dataset", read_mode_option == ERM_dataset);
258         input_xfer_option = EXS_BigEndianExplicit;
259     }
260     if (cmd->findOption("--read-xfer-implicit"))
261     {
262         app->checkDependence("--read-xfer-implicit", "--read-dataset", read_mode_option == ERM_dataset);
263         input_xfer_option = EXS_LittleEndianImplicit;
264     }
265     cmd->endOptionBlock();
266 
267     cmd->beginOptionBlock();
268     if (cmd->findOption("--accept-odd-length"))
269         dcmAcceptOddAttributeLength.set(OFTrue);
270     if (cmd->findOption("--assume-even-length"))
271         dcmAcceptOddAttributeLength.set(OFFalse);
272     cmd->endOptionBlock();
273 
274     cmd->beginOptionBlock();
275     if (cmd->findOption("--enable-correction"))
276         dcmEnableAutomaticInputDataCorrection.set(OFTrue);
277     if (cmd->findOption("--disable-correction"))
278         dcmEnableAutomaticInputDataCorrection.set(OFFalse);
279     cmd->endOptionBlock();
280 
281 #ifdef WITH_ZLIB
282     cmd->beginOptionBlock();
283     if (cmd->findOption("--bitstream-deflated"))
284         dcmZlibExpectRFC1950Encoding.set(OFFalse);
285     if (cmd->findOption("--bitstream-zlib"))
286         dcmZlibExpectRFC1950Encoding.set(OFTrue);
287     cmd->endOptionBlock();
288 #endif
289 
290     // processing options
291     cmd->beginOptionBlock();
292     if (cmd->findOption("--backup"))
293         no_backup_option = OFFalse;
294     if (cmd->findOption("--no-backup"))
295         no_backup_option = OFTrue;
296     cmd->endOptionBlock();
297 
298     if (cmd->findOption("--no-reserv-check"))
299         no_reservation_checks = OFTrue;
300 
301     if (cmd->findOption("--no-meta-uid"))
302         update_metaheader_uids_option = OFFalse;
303 
304     if (cmd->findOption("--ignore-errors"))
305         ignore_errors_option = OFTrue;
306     if (cmd->findOption("--ignore-missing-tags"))
307         ignore_missing_tags_option = OFTrue;
308     if (cmd->findOption("--ignore-un-values"))
309         ignore_un_modifies = OFTrue;
310 
311     // output options
312     cmd->beginOptionBlock();
313     if (cmd->findOption("--write-file"))
314         output_dataset_option = OFFalse;
315     if (cmd->findOption("--write-dataset"))
316     {
317         output_dataset_option = OFTrue;
318         app->checkConflict("--write-dataset", "--create-file", create_if_necessary);
319     }
320     cmd->endOptionBlock();
321 
322     cmd->beginOptionBlock();
323     if (cmd->findOption("--write-xfer-same"))
324         output_xfer_option = EXS_Unknown;
325     if (cmd->findOption("--write-xfer-little"))
326         output_xfer_option = EXS_LittleEndianExplicit;
327     if (cmd->findOption("--write-xfer-big"))
328         output_xfer_option = EXS_BigEndianExplicit;
329     if (cmd->findOption("--write-xfer-implicit"))
330         output_xfer_option = EXS_LittleEndianImplicit;
331     cmd->endOptionBlock();
332 
333     cmd->beginOptionBlock();
334     if (cmd->findOption("--enable-new-vr"))
335         dcmEnableGenerationOfNewVRs();
336     if (cmd->findOption("--disable-new-vr"))
337         dcmDisableGenerationOfNewVRs();
338     cmd->endOptionBlock();
339 
340     cmd->beginOptionBlock();
341     if (cmd->findOption("--group-length-recalc"))
342         glenc_option = EGL_recalcGL;
343     if (cmd->findOption("--group-length-create"))
344         glenc_option = EGL_withGL;
345     if (cmd->findOption("--group-length-remove"))
346         glenc_option = EGL_withoutGL;
347     cmd->endOptionBlock();
348 
349     cmd->beginOptionBlock();
350     if (cmd->findOption("--length-explicit"))
351         enctype_option = EET_ExplicitLength;
352     if (cmd->findOption("--length-undefined"))
353         enctype_option = EET_UndefinedLength;
354     cmd->endOptionBlock();
355 
356     cmd->beginOptionBlock();
357     if (cmd->findOption("--padding-retain"))
358     {
359         app->checkConflict("--padding-retain", "--write-dataset", output_dataset_option);
360         padenc_option = EPD_noChange;
361     }
362     if (cmd->findOption("--padding-off"))
363         padenc_option = EPD_withoutPadding;
364     if (cmd->findOption("--padding-create"))
365     {
366         app->checkConflict("--padding-create", "--write-dataset", output_dataset_option);
367         app->checkValue(cmd->getValueAndCheckMin(filepad_option, 0));
368         app->checkValue(cmd->getValueAndCheckMin(itempad_option, 0));
369         padenc_option = EPD_withPadding;
370     }
371     cmd->endOptionBlock();
372 }
373 
374 
parseCommandLine()375 void MdfConsoleEngine::parseCommandLine()
376 {
377     jobs = new OFList<MdfJob>;
378     OFString option_string;
379     // check all options, that don't belong to a specific job
380     parseNonJobOptions();
381 
382     cmd->gotoFirstOption();
383     // iterate over commandline arguments from first to last
384     do {
385         if (cmd->getCurrentOption(option_string))
386         {
387             MdfJob aJob;
388             OFString option_value, tag_path, tag_value;
389             if (option_string == "--insert")
390                 aJob.option = "i";
391             else if (option_string == "--insert-from-file")
392                 aJob.option = "if";
393             else if (option_string == "--modify")
394                 aJob.option = "m";
395             else if (option_string == "--modify-from-file")
396                 aJob.option = "mf";
397             else if (option_string == "--modify-all")
398                 aJob.option = "ma";
399             else if (option_string == "--erase")
400                 aJob.option = "e";
401             else if (option_string == "--erase-all")
402                 aJob.option = "ea";
403             else if (option_string == "--erase-private")
404                 aJob.option = "ep";
405             else if (option_string == "--gen-stud-uid")
406                 aJob.option = "gst";
407             else if (option_string == "--gen-ser-uid")
408                 aJob.option = "gse";
409             else if (option_string == "--gen-inst-uid")
410                 aJob.option = "gin";
411             // else this is a non job option, e.g. -v, -d, -f, ...
412             else
413                 continue;
414             // get any parameters if job expects some
415             if (jobOptionExpectsParameters(aJob.option))
416             {
417                 cmd->getValue(option_value);
418                 splitPathAndValue(option_value, tag_path, tag_value);
419                 aJob.path = tag_path;
420                 aJob.value = tag_value;
421             }
422             // finally, and schedule job
423             jobs->push_back(aJob);
424         }
425     } while (cmd->gotoNextOption());
426 }
427 
428 
jobOptionExpectsParameters(const OFString & job)429 OFBool MdfConsoleEngine::jobOptionExpectsParameters(const OFString &job)
430 {
431     return (job != "ep") && (job != "gst") && (job != "gse") && (job != "gin");
432 }
433 
434 
splitPathAndValue(const OFString & whole,OFString & path,OFString & value)435 void MdfConsoleEngine::splitPathAndValue(const OFString &whole,
436                                          OFString &path,
437                                          OFString &value)
438 {
439     size_t pos = whole.find("=");
440     if (pos != OFString_npos)
441     {
442         path = whole.substr(0, pos);
443         value = whole.substr(pos + 1, value.length() - 1);
444     }
445     else path = whole;
446 }
447 
448 
executeJob(const MdfJob & job,const char * filename)449 int MdfConsoleEngine::executeJob(const MdfJob &job,
450                                  const char *filename)
451 {
452     OFCondition result;
453     int count = 0;
454     int error_count = 0;
455     OFLOG_INFO(dcmodifyLogger, "Executing (option|path|value): "
456         << job.option << "|" << job.path << "|" << job.value);
457     // start modify operation based on job option
458     if (job.option=="i")
459         result = ds_man->modifyOrInsertPath(job.path, job.value, OFFalse, update_metaheader_uids_option, ignore_missing_tags_option, no_reservation_checks);
460     else if (job.option == "if")
461         result = ds_man->modifyOrInsertFromFile(job.path, job.value /*filename*/, OFFalse, update_metaheader_uids_option, ignore_missing_tags_option, no_reservation_checks);
462     else if (job.option == "m")
463         result = ds_man->modifyOrInsertPath(job.path, job.value, OFTrue, update_metaheader_uids_option, ignore_missing_tags_option, no_reservation_checks);
464     else if (job.option == "mf")
465         result = ds_man->modifyOrInsertFromFile(job.path, job.value /*filename*/, OFTrue, update_metaheader_uids_option, ignore_missing_tags_option, no_reservation_checks);
466     else if (job.option == "ma")
467         result = ds_man->modifyAllTags(job.path, job.value, update_metaheader_uids_option, count, ignore_missing_tags_option);
468     else if (job.option == "e")
469         result = ds_man->deleteTag(job.path, OFFalse, ignore_missing_tags_option);
470     else if (job.option == "ea")
471         result = ds_man->deleteTag(job.path, OFTrue, ignore_missing_tags_option);
472     else if (job.option == "ep")
473         result = ds_man->deletePrivateData();
474     else if (job.option == "gst")
475         result = ds_man->generateAndInsertUID(DCM_StudyInstanceUID);
476     else if (job.option == "gse")
477         result = ds_man->generateAndInsertUID(DCM_SeriesInstanceUID);
478     else if (job.option == "gin")
479         result = ds_man->generateAndInsertUID(DCM_SOPInstanceUID);
480     // no valid job option found:
481     else
482     {
483         error_count++;
484         OFLOG_ERROR(dcmodifyLogger, "no valid option: " << job.option);
485     }
486     // if modify operation failed
487     if (result.bad() && error_count == 0)
488     {
489         if (filename != NULL)
490             OFLOG_ERROR(dcmodifyLogger, "modifying tag in file " << OFString(filename) << ": " << result.text());
491         else
492             OFLOG_ERROR(dcmodifyLogger, "modifying tag: " << result.text());
493         error_count++;
494     }
495     return error_count;
496 }
497 
498 
startProvidingService()499 int MdfConsoleEngine::startProvidingService()
500 {
501     OFCondition result;
502     const char *filename;
503     // return value of this function
504     int errors = 0;
505     // parse command line into file and job list
506     parseCommandLine();
507     // iterators for job and file loops
508     OFListIterator(MdfJob) job_it;
509     OFListIterator(MdfJob) job_last = jobs->end();;
510     OFListIterator(OFString) file_it = files->begin();
511     OFListIterator(OFString) file_last = files->end();;
512     // outer loop: iterate over all files
513     while (file_it != file_last)
514     {
515         filename = (*file_it).c_str();
516         result = loadFile(filename);
517 
518         // if file could be loaded:
519         if (result.good())
520         {
521             // for each file, set job iterator back to first entry
522             job_it = jobs->begin();
523             // inner loop: iterate over jobs, execute all jobs for current file
524             while (job_it != job_last)
525             {
526                 errors += executeJob(*job_it, filename);
527                 job_it++;
528             }
529             // if there were no errors or user wants to override them, save:
530             if (errors == 0 || ignore_errors_option)
531             {
532                 if (was_created && (output_xfer_option == EXS_Unknown))
533                 {
534                   output_xfer_option = EXS_LittleEndianExplicit;
535                 }
536                 result = ds_man->saveFile(filename, output_xfer_option,
537                                           enctype_option, glenc_option,
538                                           padenc_option, filepad_option,
539                                           itempad_option, output_dataset_option);
540                 if (result.bad())
541                 {
542                     OFLOG_ERROR(dcmodifyLogger, "couldn't save file: " << result.text());
543                     errors++;
544                     if (!no_backup_option && !was_created)
545                     {
546                         result = restoreFile(filename);
547                         if (result.bad())
548                         {
549                             OFLOG_ERROR(dcmodifyLogger, "couldn't restore file: " << result.text());
550                             errors++;
551                         }
552                     }
553                 }
554             }
555             // errors occurred and user doesn't want to ignore them:
556             else if (!no_backup_option && !was_created)
557             {
558                 result = restoreFile(filename);
559                 if (result.bad())
560                 {
561                     OFLOG_ERROR(dcmodifyLogger, "couldn't restore file!");
562                     errors++;
563                 }
564             }
565         }
566         // if loading fails:
567         else
568         {
569             errors++;
570             OFLOG_ERROR(dcmodifyLogger, "unable to load file " << filename <<": " << result.text());
571         }
572         file_it++;
573         // output separator line if required
574         if ((file_it != file_last) || (errors > 0))
575           OFLOG_INFO(dcmodifyLogger, "------------------------------------");
576     }
577     return errors;
578 }
579 
580 
loadFile(const char * filename)581 OFCondition MdfConsoleEngine::loadFile(const char *filename)
582 {
583     OFCondition result;
584     // free memory
585     delete ds_man;
586     ds_man = new MdfDatasetManager();
587     ds_man->setModifyUNValues(!ignore_un_modifies);
588     OFLOG_INFO(dcmodifyLogger, "Processing file: " << filename);
589     // load file into dataset manager
590     was_created = !OFStandard::fileExists(filename);
591     result = ds_man->loadFile(filename, read_mode_option, input_xfer_option, create_if_necessary);
592     if (result.good() && !no_backup_option && !was_created)
593         result = backupFile(filename);
594     return result;
595 }
596 
597 
backupFile(const char * filename)598 OFCondition MdfConsoleEngine::backupFile(const char *filename)
599 {
600     int result;
601     OFString backup = filename;
602     backup += ".bak";
603     OFLOG_INFO(dcmodifyLogger, "Creating backup of input file: " << backup);
604     // delete backup file, if it already exists
605     if (OFStandard::fileExists(backup.c_str()))
606     {
607         result = remove(backup.c_str());
608         if (result != 0)
609         {
610             OFLOG_ERROR(dcmodifyLogger, "couldn't delete previous backup file, unable to backup!");
611             return EC_IllegalCall;
612         }
613     }
614     // if backup file could be removed, backup original file
615     result = rename(filename, backup.c_str());
616     // set return value
617     if (result != 0)
618     {
619         OFLOG_ERROR(dcmodifyLogger, "unable to backup, no write permission?");
620         return EC_IllegalCall;
621     }
622 
623     return EC_Normal;
624 }
625 
626 
restoreFile(const char * filename)627 OFCondition MdfConsoleEngine::restoreFile(const char *filename)
628 {
629     int result;
630     OFString backup = filename;
631     backup += ".bak";
632     OFLOG_INFO(dcmodifyLogger, "Restoring original file from backup");
633     // delete the (original) file that dcmodify couldn't modify
634     if (OFStandard::fileExists(filename))
635     {
636         result = remove(filename);
637         if (result != 0)
638         {
639             OFLOG_ERROR(dcmodifyLogger, "unable to delete original file for restoring backup!");
640             return EC_IllegalCall;
641         }
642     }
643     // and rename backup file back to original filename
644     result = rename(backup.c_str(), filename);
645     // error renaming backup file
646     if (result != 0)
647     {
648         OFLOG_ERROR(dcmodifyLogger, "unable to rename backup file to original filename!");
649         return EC_IllegalCall;
650     }
651     // you only get to this point, if restoring was completely successful
652     return EC_Normal;
653 }
654 
655 
~MdfConsoleEngine()656 MdfConsoleEngine::~MdfConsoleEngine()
657 {
658     delete app;
659     delete cmd;
660     delete files;
661     delete jobs;
662     delete ds_man;
663 }
664