blob: cc0687c7b64d671d4ca4f2db47c786d723da32ab [file] [log] [blame]
Chris Lattner24943d22010-06-08 16:52:24 +00001//===-- 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
27using namespace lldb;
28using namespace lldb_private;
29
30//-------------------------------------------------------------------------
31// CommandObjectBreakpointCommandAdd::CommandOptions
32//-------------------------------------------------------------------------
33
34CommandObjectBreakpointCommandAdd::CommandOptions::CommandOptions () :
35 Options ()
36{
Chris Lattner24943d22010-06-08 16:52:24 +000037}
38
39CommandObjectBreakpointCommandAdd::CommandOptions::~CommandOptions ()
40{
41}
42
43lldb::OptionDefinition
44CommandObjectBreakpointCommandAdd::CommandOptions::g_option_table[] =
45{
Jim Ingham34e9a982010-06-15 18:47:14 +000046 { LLDB_OPT_SET_1, true, "script", 's', no_argument, NULL, 0, NULL,
Chris Lattner24943d22010-06-08 16:52:24 +000047 "Write the breakpoint command script in the default scripting language."},
48
Jim Ingham34e9a982010-06-15 18:47:14 +000049 { LLDB_OPT_SET_2, true, "python", 'p', no_argument, NULL, 0, NULL,
Chris Lattner24943d22010-06-08 16:52:24 +000050 "Write the breakpoint command script in the Python scripting language."},
51
Jim Ingham34e9a982010-06-15 18:47:14 +000052 { LLDB_OPT_SET_3, true, "commands", 'c', no_argument, NULL, 0, NULL,
Chris Lattner24943d22010-06-08 16:52:24 +000053 "Write the breakpoint command script using the command line commands."},
54
55 { 0, false, NULL, 0, 0, NULL, 0, NULL, NULL }
56};
57
58const lldb::OptionDefinition*
59CommandObjectBreakpointCommandAdd::CommandOptions::GetDefinitions ()
60{
61 return g_option_table;
62}
63
64
65Error
66CommandObjectBreakpointCommandAdd::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
98void
99CommandObjectBreakpointCommandAdd::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
113CommandObjectBreakpointCommandAdd::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\
122This command will cause you to be prompted to enter the command or set \n\
123of commands you wish to be executed when the specified breakpoint is \n\
124hit. You will be told to enter your command(s), and will see a '> ' \n\
125prompt. Because you can enter one or many commands to be executed when \n\
126a breakpoint is hit, you will continue to be prompted after each \n\
127new-line that you enter, until you enter the word 'DONE', which will \n\
128cause the commands you have entered to be stored with the breakpoint \n\
129and executed when the breakpoint is hit. \n\
130 \n\
131Syntax checking is not necessarily done when breakpoint commands are \n\
132entered. An improperly written breakpoint command will attempt to get \n\
133executed when the breakpoint gets hit, and usually silently fail. If \n\
134your breakpoint command does not appear to be getting executed, go \n\
135back and check your syntax. \n\
136 \n\
137 \n\
138Special information about PYTHON breakpoint commands \n\
139---------------------------------------------------- \n\
140 \n\
141You 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\
143single 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\
145definitions, they will be passed to the Python interpreter as soon as \n\
146you finish entering the breakpoint command, and they can be called \n\
147later (don't forget to add calls to them, if you want them called when \n\
148the breakpoint is hit). If you enter multiple lines of Python that \n\
149are not function definitions, they will be collected into a new, \n\
150automatically generated Python function, and a call to the newly \n\
151generated function will be attached to the breakpoint. Important \n\
152Note: Because loose Python code gets collected into functions, if you \n\
153want to access global variables in the 'loose' code, you need to \n\
154specify that they are global, using the 'global' keyword. Be sure to \n\
155use correct Python syntax, including indentation, when entering Python \n\
156breakpoint commands. \n\
157 \n\
158Example Python one-line breakpoint command: \n\
159 \n\
160(lldb) breakpoint command add -p 1 \n\
161Enter your Python command(s). Type 'DONE' to end. \n\
162> print \"Hit this breakpoint!\" \n\
163> DONE \n\
164 \n\
165Example multiple line Python breakpoint command, using function definition: \n\
166 \n\
167(lldb) breakpoint command add -p 1 \n\
168Enter 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\
177Example multiple line Python breakpoint command, using 'loose' Python: \n\
178 \n\
179(lldb) breakpoint command add -p 1 \n\
180Enter 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\
186In 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\
188initialized: \n\
189 \n\
190(lldb) script \n\
191>>> bp_count = 0 \n\
192>>> quit() \n\
193 \n\
194(lldb) \n\
195 \n\
196Special information debugger command breakpoint commands \n\
197--------------------------------------------------------- \n\
198 \n\
199You may enter any debugger command, exactly as you would at the \n\
200debugger prompt. You may enter as many debugger commands as you like, \n\
201but do NOT enter more than one command per line. \n" );
202}
203
204CommandObjectBreakpointCommandAdd::~CommandObjectBreakpointCommandAdd ()
205{
206}
207
208bool
209CommandObjectBreakpointCommandAdd::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
289Options *
290CommandObjectBreakpointCommandAdd::GetOptions ()
291{
292 return &m_options;
293}
294
295const char *g_reader_instructions = "Enter your debugger command(s). Type 'DONE' to end.";
296
297void
298CommandObjectBreakpointCommandAdd::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
336size_t
337CommandObjectBreakpointCommandAdd::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
394CommandObjectBreakpointCommandRemove::CommandObjectBreakpointCommandRemove () :
395 CommandObject ("remove",
396 "Remove the set of commands from a breakpoint.",
397 "breakpoint command remove <breakpoint-id>")
398{
399}
400
401CommandObjectBreakpointCommandRemove::~CommandObjectBreakpointCommandRemove ()
402{
403}
404
405bool
406CommandObjectBreakpointCommandRemove::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
477CommandObjectBreakpointCommandList::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
484CommandObjectBreakpointCommandList::~CommandObjectBreakpointCommandList ()
485{
486}
487
488bool
489CommandObjectBreakpointCommandList::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 {
Jim Ingham3c7b5b92010-06-16 02:00:15 +0000534 const BreakpointOptions *bp_options = NULL;
Chris Lattner24943d22010-06-08 16:52:24 +0000535 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->GetOptionsNoCopy();
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());
Jim Ingham3c7b5b92010-06-16 02:00:15 +0000558 const Baton *baton = bp_options->GetBaton();
Chris Lattner24943d22010-06-08 16:52:24 +0000559 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
590CommandObjectBreakpointCommand::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
610CommandObjectBreakpointCommand::~CommandObjectBreakpointCommand ()
611{
612}
613
614bool
615CommandObjectBreakpointCommand::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