1 //===-- CommandObjectBreakpointCommand.cpp ----------------------*- C++ -*-===//
2 //
3 //                     The LLVM Compiler Infrastructure
4 //
5 // This file is distributed under the University of Illinois Open Source
6 // License. See LICENSE.TXT for details.
7 //
8 //===----------------------------------------------------------------------===//
9 
10 // C Includes
11 // C++ Includes
12 
13 
14 #include "CommandObjectBreakpointCommand.h"
15 #include "CommandObjectBreakpoint.h"
16 
17 #include "lldb/Interpreter/CommandInterpreter.h"
18 #include "lldb/Interpreter/CommandReturnObject.h"
19 #include "lldb/Target/Target.h"
20 #include "lldb/Target/Thread.h"
21 #include "lldb/Breakpoint/BreakpointIDList.h"
22 #include "lldb/Breakpoint/Breakpoint.h"
23 #include "lldb/Breakpoint/BreakpointLocation.h"
24 #include "lldb/Breakpoint/StoppointCallbackContext.h"
25 #include "lldb/Core/State.h"
26 
27 using namespace lldb;
28 using namespace lldb_private;
29 
30 //-------------------------------------------------------------------------
31 // CommandObjectBreakpointCommandAdd::CommandOptions
32 //-------------------------------------------------------------------------
33 
34 CommandObjectBreakpointCommandAdd::CommandOptions::CommandOptions () :
35     Options ()
36 {
37     BuildValidOptionSets();
38 }
39 
40 CommandObjectBreakpointCommandAdd::CommandOptions::~CommandOptions ()
41 {
42 }
43 
44 lldb::OptionDefinition
45 CommandObjectBreakpointCommandAdd::CommandOptions::g_option_table[] =
46 {
47     { 0, true, "script",    's', no_argument, NULL, 0, NULL,
48         "Write the breakpoint command script in the default scripting language."},
49 
50     { 1, true, "python",    'p', no_argument, NULL, 0, NULL,
51         "Write the breakpoint command script in the Python scripting language."},
52 
53     { 2, true, "commands",  'c', no_argument, NULL, 0, NULL,
54         "Write the breakpoint command script using the command line commands."},
55 
56     { 0, false, NULL, 0, 0, NULL, 0, NULL, NULL }
57 };
58 
59 const lldb::OptionDefinition*
60 CommandObjectBreakpointCommandAdd::CommandOptions::GetDefinitions ()
61 {
62     return g_option_table;
63 }
64 
65 
66 Error
67 CommandObjectBreakpointCommandAdd::CommandOptions::SetOptionValue
68 (
69     int option_idx,
70     const char *option_arg
71 )
72 {
73     Error error;
74     char short_option = (char) m_getopt_table[option_idx].val;
75 
76     switch (short_option)
77       {
78       case 's':
79         m_use_commands = false;
80         m_use_script_language = true;
81         m_script_language = eScriptLanguageDefault;
82         break;
83       case 'p':
84         m_use_commands = false;
85         m_use_script_language = true;
86         m_script_language = eScriptLanguagePython;
87         break;
88       case 'c':
89         m_use_commands = true;
90         m_use_script_language = false;
91         m_script_language = eScriptLanguageNone;
92         break;
93       default:
94         break;
95       }
96     return error;
97 }
98 
99 void
100 CommandObjectBreakpointCommandAdd::CommandOptions::ResetOptionValues ()
101 {
102     Options::ResetOptionValues();
103 
104     m_use_commands = false;
105     m_use_script_language = false;
106     m_script_language = eScriptLanguageNone;
107 }
108 
109 //-------------------------------------------------------------------------
110 // CommandObjectBreakpointCommandAdd
111 //-------------------------------------------------------------------------
112 
113 
114 CommandObjectBreakpointCommandAdd::CommandObjectBreakpointCommandAdd () :
115     CommandObject ("add",
116                    "Adds a set of commands to a breakpoint to be executed whenever a breakpoint is hit.",
117                    "breakpoint command add <cmd-options> <breakpoint-id>")
118 {
119     SetHelpLong (
120 "\nGeneral information about entering breakpoint commands \n\
121 ------------------------------------------------------ \n\
122  \n\
123 This command will cause you to be prompted to enter the command or set \n\
124 of commands you wish to be executed when the specified breakpoint is \n\
125 hit.  You will be told to enter your command(s), and will see a '> ' \n\
126 prompt. Because you can enter one or many commands to be executed when \n\
127 a breakpoint is hit, you will continue to be prompted after each \n\
128 new-line that you enter, until you enter the word 'DONE', which will \n\
129 cause the commands you have entered to be stored with the breakpoint \n\
130 and executed when the breakpoint is hit. \n\
131  \n\
132 Syntax checking is not necessarily done when breakpoint commands are \n\
133 entered.  An improperly written breakpoint command will attempt to get \n\
134 executed when the breakpoint gets hit, and usually silently fail.  If \n\
135 your breakpoint command does not appear to be getting executed, go \n\
136 back and check your syntax. \n\
137  \n\
138  \n\
139 Special information about PYTHON breakpoint commands \n\
140 ---------------------------------------------------- \n\
141  \n\
142 You may enter either one line of Python or multiple lines of Python \n\
143 (including defining whole functions, if desired).  If you enter a \n\
144 single line of Python, that will be passed to the Python interpreter \n\
145 'as is' when the breakpoint gets hit.  If you enter function \n\
146 definitions, they will be passed to the Python interpreter as soon as \n\
147 you finish entering the breakpoint command, and they can be called \n\
148 later (don't forget to add calls to them, if you want them called when \n\
149 the breakpoint is hit).  If you enter multiple lines of Python that \n\
150 are not function definitions, they will be collected into a new, \n\
151 automatically generated Python function, and a call to the newly \n\
152 generated function will be attached to the breakpoint.  Important \n\
153 Note: Because loose Python code gets collected into functions, if you \n\
154 want to access global variables in the 'loose' code, you need to \n\
155 specify that they are global, using the 'global' keyword.  Be sure to \n\
156 use correct Python syntax, including indentation, when entering Python \n\
157 breakpoint commands. \n\
158  \n\
159 Example Python one-line breakpoint command: \n\
160  \n\
161 (lldb) breakpoint command add -p 1 \n\
162 Enter your Python command(s). Type 'DONE' to end. \n\
163 > print \"Hit this breakpoint!\" \n\
164 > DONE \n\
165  \n\
166 Example multiple line Python breakpoint command, using function definition: \n\
167  \n\
168 (lldb) breakpoint command add -p 1 \n\
169 Enter your Python command(s). Type 'DONE' to end. \n\
170 > def breakpoint_output (bp_no): \n\
171 >     out_string = \"Hit breakpoint number \" + repr (bp_no) \n\
172 >     print out_string \n\
173 >     return True \n\
174 > breakpoint_output (1) \n\
175 > DONE \n\
176  \n\
177  \n\
178 Example multiple line Python breakpoint command, using 'loose' Python: \n\
179  \n\
180 (lldb) breakpoint command add -p 1 \n\
181 Enter your Python command(s). Type 'DONE' to end. \n\
182 > global bp_count \n\
183 > bp_count = bp_count + 1 \n\
184 > print \"Hit this breakpoint \" + repr(bp_count) + \" times!\" \n\
185 > DONE \n\
186  \n\
187 In this case, since there is a reference to a global variable, \n\
188 'bp_count', you will also need to make sure 'bp_count' exists and is \n\
189 initialized: \n\
190  \n\
191 (lldb) script \n\
192 >>> bp_count = 0 \n\
193 >>> quit() \n\
194  \n\
195 (lldb)  \n\
196  \n\
197 Special information  debugger command breakpoint commands \n\
198 --------------------------------------------------------- \n\
199  \n\
200 You may enter any debugger command, exactly as you would at the \n\
201 debugger prompt.  You may enter as many debugger commands as you like, \n\
202 but do NOT enter more than one command per line. \n" );
203 }
204 
205 CommandObjectBreakpointCommandAdd::~CommandObjectBreakpointCommandAdd ()
206 {
207 }
208 
209 bool
210 CommandObjectBreakpointCommandAdd::Execute
211 (
212     Args& command,
213     CommandContext *context,
214     CommandInterpreter *interpreter,
215     CommandReturnObject &result
216 )
217 {
218     Target *target = context->GetTarget();
219 
220     if (target == NULL)
221     {
222         result.AppendError ("There is not a current executable; there are no breakpoints to which to add commands");
223         result.SetStatus (eReturnStatusFailed);
224         return false;
225     }
226 
227     const BreakpointList &breakpoints = target->GetBreakpointList();
228     size_t num_breakpoints = breakpoints.GetSize();
229 
230     if (num_breakpoints == 0)
231     {
232         result.AppendError ("No breakpoints exist to have commands added");
233         result.SetStatus (eReturnStatusFailed);
234         return false;
235     }
236 
237     if (command.GetArgumentCount() == 0)
238     {
239         result.AppendError ("No breakpoint specified to which to add the commands");
240         result.SetStatus (eReturnStatusFailed);
241         return false;
242     }
243 
244     BreakpointIDList valid_bp_ids;
245     CommandObjectMultiwordBreakpoint::VerifyBreakpointIDs (command, target, result, &valid_bp_ids);
246 
247     if (result.Succeeded())
248     {
249         for (int i = 0; i < valid_bp_ids.Size(); ++i)
250         {
251             BreakpointID cur_bp_id = valid_bp_ids.GetBreakpointIDAtIndex (i);
252             if (cur_bp_id.GetBreakpointID() != LLDB_INVALID_BREAK_ID)
253             {
254                 Breakpoint *bp = target->GetBreakpointByID (cur_bp_id.GetBreakpointID()).get();
255                 if (cur_bp_id.GetLocationID() != LLDB_INVALID_BREAK_ID)
256                 {
257                     BreakpointLocationSP bp_loc_sp(bp->FindLocationByID (cur_bp_id.GetLocationID()));
258                     if (bp_loc_sp)
259                     {
260                         if (m_options.m_use_script_language)
261                         {
262                             interpreter->GetScriptInterpreter()->CollectDataForBreakpointCommandCallback (bp_loc_sp->GetLocationOptions(),
263                                                                                                           result);
264                         }
265                         else
266                         {
267                             CollectDataForBreakpointCommandCallback (bp_loc_sp->GetLocationOptions(), result);
268                         }
269                     }
270                 }
271                 else
272                 {
273                     if (m_options.m_use_script_language)
274                     {
275                         interpreter->GetScriptInterpreter()->CollectDataForBreakpointCommandCallback (bp->GetOptions(),
276                                                                                                       result);
277                     }
278                     else
279                     {
280                         CollectDataForBreakpointCommandCallback (bp->GetOptions(), result);
281                     }
282                 }
283             }
284         }
285     }
286 
287     return result.Succeeded();
288 }
289 
290 Options *
291 CommandObjectBreakpointCommandAdd::GetOptions ()
292 {
293     return &m_options;
294 }
295 
296 const char *g_reader_instructions = "Enter your debugger command(s).  Type 'DONE' to end.";
297 
298 void
299 CommandObjectBreakpointCommandAdd::CollectDataForBreakpointCommandCallback
300 (
301     BreakpointOptions *bp_options,
302     CommandReturnObject &result
303 )
304 {
305     InputReaderSP reader_sp (new InputReader());
306     std::auto_ptr<BreakpointOptions::CommandData> data_ap(new BreakpointOptions::CommandData());
307     if (reader_sp && data_ap.get())
308     {
309         BatonSP baton_sp (new BreakpointOptions::CommandBaton (data_ap.release()));
310         bp_options->SetCallback (CommandObjectBreakpointCommand::BreakpointOptionsCallbackFunction, baton_sp);
311 
312         Error err (reader_sp->Initialize (CommandObjectBreakpointCommandAdd::GenerateBreakpointCommandCallback,
313                                           bp_options,                   // baton
314                                           eInputReaderGranularityLine,  // token size, to pass to callback function
315                                           "DONE",                       // end token
316                                           "> ",                         // prompt
317                                           true));                       // echo input
318         if (err.Success())
319         {
320             Debugger::GetSharedInstance().PushInputReader (reader_sp);
321             result.SetStatus (eReturnStatusSuccessFinishNoResult);
322         }
323         else
324         {
325             result.AppendError (err.AsCString());
326             result.SetStatus (eReturnStatusFailed);
327         }
328     }
329     else
330     {
331         result.AppendError("out of memory");
332         result.SetStatus (eReturnStatusFailed);
333     }
334 
335 }
336 
337 size_t
338 CommandObjectBreakpointCommandAdd::GenerateBreakpointCommandCallback
339 (
340     void *baton,
341     InputReader *reader,
342     lldb::InputReaderAction notification,
343     const char *bytes,
344     size_t bytes_len
345 )
346 {
347     FILE *out_fh = Debugger::GetSharedInstance().GetOutputFileHandle();
348 
349     switch (notification)
350     {
351     case eInputReaderActivate:
352         if (out_fh)
353         {
354             ::fprintf (out_fh, "%s\n", g_reader_instructions);
355             if (reader->GetPrompt())
356                 ::fprintf (out_fh, "%s", reader->GetPrompt());
357         }
358         break;
359 
360     case eInputReaderDeactivate:
361         break;
362 
363     case eInputReaderReactivate:
364         if (out_fh && reader->GetPrompt())
365             ::fprintf (out_fh, "%s", reader->GetPrompt());
366         break;
367 
368     case eInputReaderGotToken:
369         if (bytes && bytes_len && baton)
370         {
371             BreakpointOptions *bp_options = (BreakpointOptions *) baton;
372             if (bp_options)
373             {
374                 Baton *bp_options_baton = bp_options->GetBaton();
375                 if (bp_options_baton)
376                     ((BreakpointOptions::CommandData *)bp_options_baton->m_data)->user_source.AppendString (bytes, bytes_len);
377             }
378         }
379         if (out_fh && !reader->IsDone() && reader->GetPrompt())
380             ::fprintf (out_fh, "%s", reader->GetPrompt());
381         break;
382 
383     case eInputReaderDone:
384         break;
385     }
386 
387     return bytes_len;
388 }
389 
390 
391 //-------------------------------------------------------------------------
392 // CommandObjectBreakpointCommandRemove
393 //-------------------------------------------------------------------------
394 
395 CommandObjectBreakpointCommandRemove::CommandObjectBreakpointCommandRemove () :
396     CommandObject ("remove",
397                    "Remove the set of commands from a breakpoint.",
398                    "breakpoint command remove <breakpoint-id>")
399 {
400 }
401 
402 CommandObjectBreakpointCommandRemove::~CommandObjectBreakpointCommandRemove ()
403 {
404 }
405 
406 bool
407 CommandObjectBreakpointCommandRemove::Execute (Args& command,
408                                                CommandContext *context,
409                                                CommandInterpreter *interpreter,
410                                                CommandReturnObject &result)
411 {
412     Target *target = context->GetTarget();
413 
414     if (target == NULL)
415     {
416         result.AppendError ("There is not a current executable; there are no breakpoints from which to remove commands");
417         result.SetStatus (eReturnStatusFailed);
418         return false;
419     }
420 
421     const BreakpointList &breakpoints = target->GetBreakpointList();
422     size_t num_breakpoints = breakpoints.GetSize();
423 
424     if (num_breakpoints == 0)
425     {
426         result.AppendError ("No breakpoints exist to have commands removed");
427         result.SetStatus (eReturnStatusFailed);
428         return false;
429     }
430 
431     if (command.GetArgumentCount() == 0)
432     {
433         result.AppendError ("No breakpoint specified from which to remove the commands");
434         result.SetStatus (eReturnStatusFailed);
435         return false;
436     }
437 
438     BreakpointIDList valid_bp_ids;
439     CommandObjectMultiwordBreakpoint::VerifyBreakpointIDs (command, target, result, &valid_bp_ids);
440 
441     if (result.Succeeded())
442     {
443         for (int i = 0; i < valid_bp_ids.Size(); ++i)
444         {
445             BreakpointID cur_bp_id = valid_bp_ids.GetBreakpointIDAtIndex (i);
446             if (cur_bp_id.GetBreakpointID() != LLDB_INVALID_BREAK_ID)
447             {
448                 Breakpoint *bp = target->GetBreakpointByID (cur_bp_id.GetBreakpointID()).get();
449                 if (cur_bp_id.GetLocationID() != LLDB_INVALID_BREAK_ID)
450                 {
451                     BreakpointLocationSP bp_loc_sp (bp->FindLocationByID (cur_bp_id.GetLocationID()));
452                     if (bp_loc_sp)
453                         bp_loc_sp->ClearCallback();
454                     else
455                     {
456                         result.AppendErrorWithFormat("Invalid breakpoint ID: %u.%u.\n",
457                                                      cur_bp_id.GetBreakpointID(),
458                                                      cur_bp_id.GetLocationID());
459                         result.SetStatus (eReturnStatusFailed);
460                         return false;
461                     }
462                 }
463                 else
464                 {
465                     bp->ClearCallback();
466                 }
467             }
468         }
469     }
470     return result.Succeeded();
471 }
472 
473 
474 //-------------------------------------------------------------------------
475 // CommandObjectBreakpointCommandList
476 //-------------------------------------------------------------------------
477 
478 CommandObjectBreakpointCommandList::CommandObjectBreakpointCommandList () :
479     CommandObject ("List",
480                    "List the script or set of commands to be executed when the breakpoint is hit.",
481                    "breakpoint command list <breakpoint-id>")
482 {
483 }
484 
485 CommandObjectBreakpointCommandList::~CommandObjectBreakpointCommandList ()
486 {
487 }
488 
489 bool
490 CommandObjectBreakpointCommandList::Execute (Args& command,
491                                              CommandContext *context,
492                                              CommandInterpreter *interpreter,
493                                              CommandReturnObject &result)
494 {
495     Target *target = context->GetTarget();
496 
497     if (target == NULL)
498     {
499         result.AppendError ("There is not a current executable; there are no breakpoints for which to list commands");
500         result.SetStatus (eReturnStatusFailed);
501         return false;
502     }
503 
504     const BreakpointList &breakpoints = target->GetBreakpointList();
505     size_t num_breakpoints = breakpoints.GetSize();
506 
507     if (num_breakpoints == 0)
508     {
509         result.AppendError ("No breakpoints exist for which to list commands");
510         result.SetStatus (eReturnStatusFailed);
511         return false;
512     }
513 
514     if (command.GetArgumentCount() == 0)
515     {
516         result.AppendError ("No breakpoint specified for which to list the commands");
517         result.SetStatus (eReturnStatusFailed);
518         return false;
519     }
520 
521     BreakpointIDList valid_bp_ids;
522     CommandObjectMultiwordBreakpoint::VerifyBreakpointIDs (command, target, result, &valid_bp_ids);
523 
524     if (result.Succeeded())
525     {
526         for (int i = 0; i < valid_bp_ids.Size(); ++i)
527         {
528             BreakpointID cur_bp_id = valid_bp_ids.GetBreakpointIDAtIndex (i);
529             if (cur_bp_id.GetBreakpointID() != LLDB_INVALID_BREAK_ID)
530             {
531                 Breakpoint *bp = target->GetBreakpointByID (cur_bp_id.GetBreakpointID()).get();
532 
533                 if (bp)
534                 {
535                     BreakpointOptions *bp_options = NULL;
536                     if (cur_bp_id.GetLocationID() != LLDB_INVALID_BREAK_ID)
537                     {
538                         BreakpointLocationSP bp_loc_sp(bp->FindLocationByID (cur_bp_id.GetLocationID()));
539                         if (bp_loc_sp)
540                             bp_options = bp_loc_sp->GetOptionsNoCopy();
541                         else
542                         {
543                             result.AppendErrorWithFormat("Invalid breakpoint ID: %u.%u.\n",
544                                                          cur_bp_id.GetBreakpointID(),
545                                                          cur_bp_id.GetLocationID());
546                             result.SetStatus (eReturnStatusFailed);
547                             return false;
548                         }
549                     }
550                     else
551                     {
552                         bp_options = bp->GetOptions();
553                     }
554 
555                     if (bp_options)
556                     {
557                         StreamString id_str;
558                         BreakpointID::GetCanonicalReference (&id_str, cur_bp_id.GetBreakpointID(), cur_bp_id.GetLocationID());
559                         Baton *baton = bp_options->GetBaton();
560                         if (baton)
561                         {
562                             result.GetOutputStream().Printf ("Breakpoint %s:\n", id_str.GetData());
563                             result.GetOutputStream().IndentMore ();
564                             baton->GetDescription(&result.GetOutputStream(), eDescriptionLevelFull);
565                             result.GetOutputStream().IndentLess ();
566                         }
567                         else
568                         {
569                             result.AppendMessageWithFormat ("Breakpoint %s does not have an associated command.\n", id_str.GetData());
570                         }
571                     }
572                     result.SetStatus (eReturnStatusSuccessFinishResult);
573                 }
574                 else
575                 {
576                     result.AppendErrorWithFormat("Invalid breakpoint ID: %u.\n", cur_bp_id.GetBreakpointID());
577                     result.SetStatus (eReturnStatusFailed);
578                 }
579 
580             }
581         }
582     }
583 
584     return result.Succeeded();
585 }
586 
587 //-------------------------------------------------------------------------
588 // CommandObjectBreakpointCommand
589 //-------------------------------------------------------------------------
590 
591 CommandObjectBreakpointCommand::CommandObjectBreakpointCommand (CommandInterpreter *interpreter) :
592     CommandObjectMultiword ("command",
593                             "A set of commands for adding, removing and examining bits of code to be executed when the breakpoint is hit (breakpoint 'commmands').",
594                             "command <sub-command> [<sub-command-options>] <breakpoint-id>")
595 {
596     bool status;
597     CommandObjectSP add_command_object (new CommandObjectBreakpointCommandAdd ());
598     CommandObjectSP remove_command_object (new CommandObjectBreakpointCommandRemove ());
599     CommandObjectSP list_command_object (new CommandObjectBreakpointCommandList ());
600 
601     add_command_object->SetCommandName ("breakpoint command add");
602     remove_command_object->SetCommandName ("breakpoint command remove");
603     list_command_object->SetCommandName ("breakpoint command list");
604 
605     status = LoadSubCommand (add_command_object, "add", interpreter);
606     status = LoadSubCommand (remove_command_object, "remove", interpreter);
607     status = LoadSubCommand (list_command_object, "list", interpreter);
608 }
609 
610 
611 CommandObjectBreakpointCommand::~CommandObjectBreakpointCommand ()
612 {
613 }
614 
615 bool
616 CommandObjectBreakpointCommand::BreakpointOptionsCallbackFunction
617 (
618     void *baton,
619     StoppointCallbackContext *context,
620     lldb::user_id_t break_id,
621     lldb::user_id_t break_loc_id
622 )
623 {
624     bool ret_value = true;
625     if (baton == NULL)
626         return true;
627 
628 
629     BreakpointOptions::CommandData *data = (BreakpointOptions::CommandData *) baton;
630     StringList &commands = data->user_source;
631 
632     if (commands.GetSize() > 0)
633     {
634         uint32_t num_commands = commands.GetSize();
635         CommandInterpreter &interpreter = Debugger::GetSharedInstance().GetCommandInterpreter();
636         CommandReturnObject result;
637         ExecutionContext exe_ctx = context->context;
638 
639         FILE *out_fh = Debugger::GetSharedInstance().GetOutputFileHandle();
640         FILE *err_fh = Debugger::GetSharedInstance().GetErrorFileHandle();
641 
642 
643         uint32_t i;
644         for (i = 0; i < num_commands; ++i)
645         {
646 
647             // First time through we use the context from the stoppoint, after that we use whatever
648             // has been set by the previous command.
649 
650             if (!interpreter.HandleCommand (commands.GetStringAtIndex(i), false, result, &exe_ctx))
651                 break;
652 
653             // FIXME: This isn't really the right way to do this.  We should be able to peek at the public
654             // to see if there is any new events, but that is racey, since the internal process thread has to run and
655             // deliver the event to the public queue before a run will show up.  So for now we check
656             // the internal thread state.
657 
658             lldb::StateType internal_state = exe_ctx.process->GetPrivateState();
659             if (internal_state != eStateStopped)
660             {
661                 if (i < num_commands - 1)
662                 {
663                     if (out_fh)
664                         ::fprintf (out_fh, "Short-circuiting command execution because target state changed to %s."
665                                            " last command: \"%s\"\n", StateAsCString(internal_state),
666                                            commands.GetStringAtIndex(i));
667                 }
668                 break;
669             }
670 
671             // First time through we use the context from the stoppoint, after that we use whatever
672             // has been set by the previous command.
673             exe_ctx = Debugger::GetSharedInstance().GetCurrentExecutionContext();
674 
675 
676             if (out_fh)
677                 ::fprintf (out_fh, "%s", result.GetErrorStream().GetData());
678             if (err_fh)
679                 ::fprintf (err_fh, "%s", result.GetOutputStream().GetData());
680             result.Clear();
681             result.SetStatus (eReturnStatusSuccessFinishNoResult);
682         }
683 
684         if (err_fh && !result.Succeeded() && i < num_commands)
685             ::fprintf (err_fh, "Attempt to execute '%s' failed.\n", commands.GetStringAtIndex(i));
686 
687         if (out_fh)
688             ::fprintf (out_fh, "%s", result.GetErrorStream().GetData());
689 
690         if (err_fh)
691             ::fprintf (err_fh, "%s", result.GetOutputStream().GetData());
692     }
693     return ret_value;
694 }
695 
696