Revision d5d76ab2

b/lib/utils/process.py
142 142

  
143 143
def RunCmd(cmd, env=None, output=None, cwd="/", reset_env=False,
144 144
           interactive=False, timeout=None, noclose_fds=None,
145
           _postfork_fn=None):
145
           input_fd=None, _postfork_fn=None):
146 146
  """Execute a (shell) command.
147 147

  
148 148
  The command should not read from its standard input, as it will be
......
170 170
  @type noclose_fds: list
171 171
  @param noclose_fds: list of additional (fd >=3) file descriptors to leave
172 172
                      open for the child process
173
  @type input_fd: C{file}-like object or numeric file descriptor
174
  @param input_fd: File descriptor for process' standard input
173 175
  @param _postfork_fn: Callback run after fork but before timeout (unittest)
174 176
  @rtype: L{RunResult}
175 177
  @return: RunResult instance
......
183 185
    raise errors.ProgrammerError("Parameters 'output' and 'interactive' can"
184 186
                                 " not be provided at the same time")
185 187

  
188
  if not (output is None or input_fd is None):
189
    # The current logic in "_RunCmdFile", which is used when output is defined,
190
    # does not support input files (not hard to implement, though)
191
    raise errors.ProgrammerError("Parameters 'output' and 'input_fd' can"
192
                                 " not be used at the same time")
193

  
186 194
  if isinstance(cmd, basestring):
187 195
    strcmd = cmd
188 196
    shell = True
......
202 210
    if output is None:
203 211
      out, err, status, timeout_action = _RunCmdPipe(cmd, cmd_env, shell, cwd,
204 212
                                                     interactive, timeout,
205
                                                     noclose_fds,
213
                                                     noclose_fds, input_fd,
206 214
                                                     _postfork_fn=_postfork_fn)
207 215
    else:
208 216
      assert _postfork_fn is None, \
209 217
          "_postfork_fn not supported if output provided"
218
      assert input_fd is None
210 219
      timeout_action = _TIMEOUT_NONE
211 220
      status = _RunCmdFile(cmd, cmd_env, shell, output, cwd, noclose_fds)
212 221
      out = err = ""
......
481 490

  
482 491

  
483 492
def _RunCmdPipe(cmd, env, via_shell, cwd, interactive, timeout, noclose_fds,
493
                input_fd,
484 494
                _linger_timeout=constants.CHILD_LINGER_TIMEOUT,
485 495
                _postfork_fn=None):
486 496
  """Run a command and return its output.
......
500 510
  @type noclose_fds: list
501 511
  @param noclose_fds: list of additional (fd >=3) file descriptors to leave
502 512
                      open for the child process
513
  @type input_fd: C{file}-like object or numeric file descriptor
514
  @param input_fd: File descriptor for process' standard input
503 515
  @param _postfork_fn: Function run after fork but before timeout (unittest)
504 516
  @rtype: tuple
505 517
  @return: (out, err, status)
......
507 519
  """
508 520
  poller = select.poll()
509 521

  
510
  stderr = subprocess.PIPE
511
  stdout = subprocess.PIPE
512
  stdin = subprocess.PIPE
513

  
514 522
  if interactive:
515
    stderr = stdout = stdin = None
523
    stderr = None
524
    stdout = None
525
  else:
526
    stderr = subprocess.PIPE
527
    stdout = subprocess.PIPE
528

  
529
  if input_fd:
530
    stdin = input_fd
531
  elif interactive:
532
    stdin = None
533
  else:
534
    stdin = subprocess.PIPE
516 535

  
517 536
  if noclose_fds:
518 537
    preexec_fn = lambda: CloseFDs(noclose_fds)
......
549 568

  
550 569
  timeout_action = _TIMEOUT_NONE
551 570

  
571
  # subprocess: "If the stdin argument is PIPE, this attribute is a file object
572
  # that provides input to the child process. Otherwise, it is None."
573
  assert (stdin == subprocess.PIPE) ^ (child.stdin is None), \
574
    "subprocess' stdin did not behave as documented"
575

  
552 576
  if not interactive:
553
    child.stdin.close()
577
    if child.stdin is not None:
578
      child.stdin.close()
554 579
    poller.register(child.stdout, select.POLLIN)
555 580
    poller.register(child.stderr, select.POLLIN)
556 581
    fdmap = {
b/test/ganeti.utils.process_unittest.py
274 274
    (out, err, status, ta) = \
275 275
      utils.process._RunCmdPipe(cmd, {}, False, "/", False,
276 276
                                timeout, [self.proc_ready_helper.write_fd],
277
                                None,
277 278
                                _linger_timeout=0.2,
278 279
                                _postfork_fn=self.proc_ready_helper.Ready)
279 280
    self.assert_(status < 0)
......
377 378
    finally:
378 379
      temp.close()
379 380

  
381
  def testNoInputRead(self):
382
    testfile = self._TestDataFilename("cert1.pem")
383

  
384
    result = utils.RunCmd(["cat"], timeout=10.0)
385
    self.assertFalse(result.failed)
386
    self.assertEqual(result.stderr, "")
387
    self.assertEqual(result.stdout, "")
388

  
389
  def testInputFileHandle(self):
390
    testfile = self._TestDataFilename("cert1.pem")
391

  
392
    result = utils.RunCmd(["cat"], input_fd=open(testfile, "r"))
393
    self.assertFalse(result.failed)
394
    self.assertEqual(result.stdout, utils.ReadFile(testfile))
395
    self.assertEqual(result.stderr, "")
396

  
397
  def testInputNumericFileDescriptor(self):
398
    testfile = self._TestDataFilename("cert2.pem")
399

  
400
    fh = open(testfile, "r")
401
    try:
402
      result = utils.RunCmd(["cat"], input_fd=fh.fileno())
403
    finally:
404
      fh.close()
405

  
406
    self.assertFalse(result.failed)
407
    self.assertEqual(result.stdout, utils.ReadFile(testfile))
408
    self.assertEqual(result.stderr, "")
409

  
410
  def testInputWithCloseFds(self):
411
    testfile = self._TestDataFilename("cert1.pem")
412

  
413
    temp = open(self.fname, "r+")
414
    try:
415
      temp.write("test283523367")
416
      temp.seek(0)
417

  
418
      result = utils.RunCmd(["/bin/bash", "-c",
419
                             ("cat && read -u %s; echo $REPLY" %
420
                              temp.fileno())],
421
                            input_fd=open(testfile, "r"),
422
                            noclose_fds=[temp.fileno()])
423
      self.assertFalse(result.failed)
424
      self.assertEqual(result.stdout.strip(),
425
                       utils.ReadFile(testfile) + "test283523367")
426
      self.assertEqual(result.stderr, "")
427
    finally:
428
      temp.close()
429

  
430
  def testOutputAndInteractive(self):
431
    self.assertRaises(errors.ProgrammerError, utils.RunCmd,
432
                      [], output=self.fname, interactive=True)
433

  
434
  def testOutputAndInput(self):
435
    self.assertRaises(errors.ProgrammerError, utils.RunCmd,
436
                      [], output=self.fname, input_fd=open(self.fname))
437

  
380 438

  
381 439
class TestRunParts(testutils.GanetiTestCase):
382 440
  """Testing case for the RunParts function"""

Also available in: Unified diff