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