ó
.„^c           @   s>  d  Z  d d l Z d d l Z d d l Z d d l Z d d l Z d d l Z d d l m Z e j d Z	 d „  Z
 d „  Z d e f d „  ƒ  YZ d	 e f d
 „  ƒ  YZ e ƒ  Z e j Z e j Z e j Z e j Z e j Z d e f d „  ƒ  YZ d e j f d „  ƒ  YZ d e f d „  ƒ  YZ d e f d „  ƒ  YZ d S(   s{   
Wrappers around subprocess functionality that simulate an actual shell.

.. testsetup:: *

    from wizard.shell import *
iÿÿÿÿN(   t   utili    c         C   s   |  d d k p |  d d k S(   sA   Detects whether or not an argument list invokes a Python program.i    t   pythont   wizard(    (   t   args(    (    s:   /afs/athena.mit.edu/contrib/scripts/wizard/wizard/shell.pyt	   is_python   s    c         C   sá   t  j ƒ  r d St j |  ƒ } | s) d Sg  } xR t  j j ƒ  D]A \ } } | j d ƒ sf | d k r? | j d | | f ƒ q? q? W| t j	 7} t
 j d ƒ | rº t  j | | d ƒ n  t  j d d d d	 t | ƒ | Œ d S(
   sd  
    Checks if we are running as root.  If we are, attempt to drop
    priviledges to the user who owns ``dir``, by re-calling
    itself using sudo with exec, such that the new process subsumes our
    current one.  If ``log_file`` is passed, the file is chown'ed
    to the user we are dropping priviledges to, so the subprocess
    can write to it.
    Nt   WIZARD_t   SSH_GSSAPI_NAMEs   %s=%ss   Dropping priviledgesiÿÿÿÿt   sudos   -ut   #(   t   ost   getuidR    t   get_dir_uidt   environt   itemst
   startswitht   appendt   syst   argvt   loggingt   debugt   chownt   execlpt   str(   t   dirt   log_filet   uidR   t   kt   v(    (    s:   /afs/athena.mit.edu/contrib/scripts/wizard/wizard/shell.pyt   drop_priviledges   s    	 t   Shellc           B   sk   e  Z d  Z e d „ Z d „  Z d „  Z d „  Z d „  Z d „  Z	 d „  Z
 d „  Z d	 „  Z d
 „  Z RS(   sm   
    An advanced shell that performs logging.  If ``dry`` is ``True``,
    no commands are actually run.
    c         C   s   | |  _  d  |  _ d  S(   N(   t   dryt   Nonet   cwd(   t   selfR   (    (    s:   /afs/athena.mit.edu/contrib/scripts/wizard/wizard/shell.pyt   __init__8   s    	c         O   s4  |  j  ƒ  | j d t ƒ | j d t ƒ | j d d ƒ | j d d ƒ | j d t j ƒ | j d t j ƒ | j d t j ƒ | j d d ƒ | j d	 t ƒ d
 d j | ƒ d } | d rÔ | d t k	 sä | d t k rô t j	 | ƒ n t j
 | ƒ |  j r| d rd Sd S| d d k rEt | ƒ rEt | d <n  | d d k rnt | ƒ } t | d <n  | j d d ƒ | d r¦t j } t j } t j } n | d } | d } | d } d } | d rút t j j ƒ  | d j ƒ  ƒ } n  | d	 } t j | d | d | d | d |  j d | d	 | ƒ}	 |  j |	 | |  rS|	 S|	 j | d ƒ \ } } | d k rd } n  | d k r–d } n  | d sÐ| d r½|  j d | ƒ qÐ|  j | | ƒ n  |	 j r| d rìt }
 n t }
 |
 |	 j | | | ƒ ‚ n  | d r*t | ƒ j  d ƒ S| | f S(   sŽ  
        Performs a system call.  The actual executable and options should
        be passed as arguments to this function.  It will magically
        ensure that 'wizard' as a command works. Several keyword arguments
        are also supported:

        :param python: explicitly marks the subprocess as Python or not Python
            for improved error reporting.  By default, we use
            :func:`is_python` to autodetect this.
        :param input: input to feed the subprocess on standard input.
        :param interactive: whether or not directly hook up all pipes
            to the controlling terminal, to allow interaction with subprocess.
        :param strip: if ``True``, instead of returning a tuple,
            return the string stdout output of the command with trailing newlines
            removed.  This emulates the behavior of backticks and ``$()`` in Bash.
            Prefer to use :meth:`eval` instead (you should only need to explicitly
            specify this if you are using another wrapper around this function).
        :param log: if True, we log the call as INFO, if False, we log the call
            as DEBUG, otherwise, we detect based on ``strip``.
        :param addenv: mapping of environment variables *to add*
        :param stdout:
        :param stderr:
        :param stdin: a file-type object that will be written to or read from as a pipe.
        :returns: a tuple of strings ``(stdout, stderr)``, or a string ``stdout``
            if ``strip`` is specified.

        >>> sh = Shell()
        >>> sh.call("echo", "Foobar")
        ('Foobar\n', '')
        >>> sh.call("cat", input='Foobar')
        ('Foobar', '')
        t   interactivet   stripR   t   logt   stdoutt   stdint   stderrt   addenvt	   close_fdss	   Running `t    t   `t    i    R   t   inputR    t   envs   
N(   NN(!   t   _waitt
   setdefaultt   FalseR   t
   subprocesst   PIPEt   Truet   joinR   R   t   infoR   R   t   listt
   wizard_binR   R&   R'   R(   t   dictR	   R   R   t   PopenR    t   _asynct   communicatet   _logt
   returncodet   PythonCallErrort	   CallErrorR   t   rstrip(   R!   R   t   kwargst   msgR&   R'   R(   R/   R*   t   proct   eclass(    (    s:   /afs/athena.mit.edu/contrib/scripts/wizard/wizard/shell.pyt   call;   sn    !
*	

		



&
6		

	
 	
c         C   s8   | r t  j d | ƒ n  | r4 t  j d | ƒ n  d S(   s;   Logs the standard output and standard input from a command.s   STDOUT:
s   STDERR:
N(   R   R   (   R!   R&   R(   (    (    s:   /afs/athena.mit.edu/contrib/scripts/wizard/wizard/shell.pyR>   £   s    c         C   s   d  S(   N(    (   R!   (    (    s:   /afs/athena.mit.edu/contrib/scripts/wizard/wizard/shell.pyR0   ©   s    c         O   s   t  S(   N(   R2   (   R!   R   RC   (    (    s:   /afs/athena.mit.edu/contrib/scripts/wizard/wizard/shell.pyR<   «   s    c         O   sß   | j  d d
 ƒ } | j  d d
 ƒ } | j d t | ƒ ƒ | rX | rX |  j | | Ž  St j d ƒ r“ t | ƒ } | j d d t j d ƒ ƒ n  | r¼ |  j d d d	 t	 | ƒ | | Ž S| rÛ |  j d d | | | Ž Sd
 S(   s  
        Performs a system call as a different user.  This is only possible
        if you are running as root.  Keyword arguments
        are the same as :meth:`call` with the following additions:

        :param user: name of the user to run command as.
        :param uid: uid of the user to run command as.

        .. note::

            The resulting system call internally uses :command:`sudo`,
            and as such environment variables will get scrubbed.  We
            manually preserve :envvar:`SSH_GSSAPI_NAME`.
        t   userR   R   R   i    s   SSH_GSSAPI_NAME=R   s   -uR   N(
   t   popR   R1   R   RG   R	   t   getenvR8   t   insertR   (   R!   R   RC   RH   R   (    (    s:   /afs/athena.mit.edu/contrib/scripts/wizard/wizard/shell.pyt
   callAsUser­   s       # c         O   st   t  j ƒ  r |  j | | Ž  St  j t  j ƒ  ƒ j } | t  j ƒ  k r` | | d <|  j | | Ž  S|  j | | Ž  Sd S(   sÃ  
        Checks if the owner of the current working directory is the same
        as the current user, and if it isn't, attempts to sudo to be
        that user.  The intended use case is for calling Git commands
        when running as root, but this method should be used when
        interfacing with any moderately complex program that depends
        on working directory context.  Keyword arguments are the
        same as :meth:`call`.
        R   N(   R	   R
   RG   t   statt   getcwdt   st_uidt   geteuidRL   (   R!   R   RC   R   (    (    s:   /afs/athena.mit.edu/contrib/scripts/wizard/wizard/shell.pyt   safeCallÆ   s    

c         O   s   t  | d <|  j | | Ž  S(   s,  
        Evaluates a command and returns its output, with trailing newlines
        stripped (like backticks in Bash).  This is a convenience method for
        calling :meth:`call` with ``strip``.

            >>> sh = Shell()
            >>> sh.eval("echo", "Foobar") 
            'Foobar'
        R$   (   R5   RG   (   R!   R   RC   (    (    s:   /afs/athena.mit.edu/contrib/scripts/wizard/wizard/shell.pyt   evalÙ   s    

c         C   s   | |  _  d S(   s—   
        Sets the directory processes are executed in. This sets a value
        to be passed as the ``cwd`` argument to ``subprocess.Popen``.
        N(   R    (   R!   R    (    (    s:   /afs/athena.mit.edu/contrib/scripts/wizard/wizard/shell.pyt   setcwdå   s    c          C   s~   t  j d ƒ }  |  s d }  n  |  d k r3 d }  n  y t j |  d d t ƒWn* t j k
 ry } t j d | j	 ƒ n Xd  S(   Nt   SHELLs	   /bin/bashs   /usr/local/bin/mbashs   -iR#   s$   Shell returned non-zero exit code %d(
   R	   RJ   R!   RG   R5   t   shellRA   R   t   warningt   code(   t
   user_shellt   e(    (    s:   /afs/athena.mit.edu/contrib/scripts/wizard/wizard/shell.pyR#   ë   s     	 	(   t   __name__t
   __module__t   __doc__R2   R"   RG   R>   R0   R<   RL   RQ   RR   RS   R#   (    (    (    s:   /afs/athena.mit.edu/contrib/scripts/wizard/wizard/shell.pyR   3   s   	h							t   ParallelShellc           B   sY   e  Z d  Z e d d „ Z e d „  ƒ Z d „  Z d „  Z d „  Z	 d „  Z
 d „  Z RS(	   sc  
    Modifies the semantics of :class:`Shell` so that
    commands are queued here, and executed in parallel using waitpid
    with ``max`` subprocesses, and result in callback execution
    when they finish.

    .. method:: call(*args, **kwargs)

        Enqueues a system call for parallel processing.  If there are
        no openings in the queue, this will block.  Keyword arguments
        are the same as :meth:`Shell.call` with the following additions:

        :param on_success: Callback function for success (zero exit status).
            The callback function should accept two arguments,
            ``stdout`` and ``stderr``.
        :param on_error: Callback function for failure (nonzero exit status).
            The callback function should accept one argument, the
            exception that would have been thrown by the synchronous
            version.
        :return: The :class:`subprocess.Proc` object that was opened.

    .. method:: callAsUser(*args, **kwargs)

        Enqueues a system call under a different user for parallel
        processing.  Keyword arguments are the same as
        :meth:`Shell.callAsUser` with the additions of keyword
        arguments from :meth:`call`.

    .. method:: safeCall(*args, **kwargs)

        Enqueues a "safe" call for parallel processing.  Keyword
        arguments are the same as :meth:`Shell.safeCall` with the
        additions of keyword arguments from :meth:`call`.

    .. method:: eval(*args, **kwargs)

        No difference from :meth:`call`.  Consider having a
        non-parallel shell if the program you are shelling out
        to is fast.

    i
   c         C   s/   t  t |  ƒ j d | ƒ i  |  _ | |  _ d  S(   NR   (   t   superR]   R"   t   runningt   max(   R!   R   R`   (    (    s:   /afs/athena.mit.edu/contrib/scripts/wizard/wizard/shell.pyR"   #  s    	c         C   s   |  r t  ƒ  St d | ƒ Sd S(   s4   Convenience method oriented towards command modules.R`   N(   t   DummyParallelShellR]   (   t   no_parallelizeR`   (    (    s:   /afs/athena.mit.edu/contrib/scripts/wizard/wizard/shell.pyt   make'  s    c         K   s#   | | | | | f |  j  | j <t S(   s•   
        Gets handed a :class:`subprocess.Proc` object from our deferred
        execution.  See :meth:`Shell.call` source code for details.
        (   R_   t   pidR5   (   R!   RE   R   R   t
   on_successt   on_errorRC   (    (    s:   /afs/athena.mit.edu/contrib/scripts/wizard/wizard/shell.pyR<   .  s    c         C   sl   t  |  j ƒ |  j k  r d Sy |  j t j d d ƒ Œ  Wn, t k
 rg } | j t j k ra d S‚  n Xd S(   s„   
        Blocking call that waits for an open subprocess slot.  This is
        automatically called by :meth:`Shell.call`.
        Niÿÿÿÿi    (	   t   lenR_   R`   t   reapR	   t   waitpidt   OSErrort   errnot   ECHILD(   R!   RY   (    (    s:   /afs/athena.mit.edu/contrib/scripts/wizard/wizard/shell.pyR0   5  s      c         C   s]   y* x# t  r( |  j t j d d ƒ Œ  q WWn, t k
 rX } | j t j k rR d S‚  n Xd S(   s/   Waits for all of our subprocesses to terminate.iÿÿÿÿi    N(   R5   Rh   R	   Ri   Rj   Rk   Rl   (   R!   RY   (    (    s:   /afs/athena.mit.edu/contrib/scripts/wizard/wizard/shell.pyR6   D  s    	! c         C   s›   |  j  j | ƒ \ } } } } } | j j ƒ  } | j j ƒ  }	 |  j | |	 ƒ | rŠ | rd t }
 n t }
 | |
 | j | | |	 ƒ ƒ d S| | |	 ƒ d S(   s   Reaps a process.N(	   R_   RI   R&   t   readR(   R>   R@   RA   R?   (   R!   Rd   t   statusRE   R   R   Re   Rf   R&   R(   RF   (    (    s:   /afs/athena.mit.edu/contrib/scripts/wizard/wizard/shell.pyRh   L  s    ! 	c           C   s   t  d ƒ ‚ d  S(   Ns*   Cannot use interactive() on parallel shell(   t   Error(    (    (    s:   /afs/athena.mit.edu/contrib/scripts/wizard/wizard/shell.pyR#   [  s    (   RZ   R[   R\   R2   R"   t   staticmethodRc   R<   R0   R6   Rh   R#   (    (    (    s:   /afs/athena.mit.edu/contrib/scripts/wizard/wizard/shell.pyR]   ù   s   )				Ra   c           B   s   e  Z d  Z e d „ Z RS(   sp   Same API as :class:`ParallelShell`, but doesn't actually
    parallelize (i.e. all calls to :meth:`wait` block.)c         C   s#   t  t |  ƒ j d | d d ƒ d  S(   NR   R`   i   (   R^   Ra   R"   (   R!   R   (    (    s:   /afs/athena.mit.edu/contrib/scripts/wizard/wizard/shell.pyR"   i  s    (   RZ   R[   R\   R2   R"   (    (    (    s:   /afs/athena.mit.edu/contrib/scripts/wizard/wizard/shell.pyRa   f  s   Ro   c           B   s   e  Z d  Z RS(   s   Base exception for this module(   RZ   R[   R\   (    (    (    s:   /afs/athena.mit.edu/contrib/scripts/wizard/wizard/shell.pyRo   l  s   RA   c           B   s8   e  Z d  Z d Z d Z d Z d Z d „  Z d „  Z	 RS(   s@   Indicates that a subprocess call returned a nonzero exit status.c         C   s(   | |  _  | |  _ | |  _ | |  _ d  S(   N(   RW   R   R&   R(   (   R!   RW   R   R&   R(   (    (    s:   /afs/athena.mit.edu/contrib/scripts/wizard/wizard/shell.pyR"   z  s    			c         C   s3   |  j  j ƒ  j d ƒ d } d | |  j |  j  f S(   Ns   
iÿÿÿÿs   %s (exited with %d)
%s(   R(   RB   t   splitRW   (   R!   t   compact(    (    s:   /afs/athena.mit.edu/contrib/scripts/wizard/wizard/shell.pyt   __str__  s    N(
   RZ   R[   R\   R   RW   R   R&   R(   R"   Rs   (    (    (    s:   /afs/athena.mit.edu/contrib/scripts/wizard/wizard/shell.pyRA   p  s   	R@   c           B   s&   e  Z d  Z d Z d „  Z d „  Z RS(   s“   
    Indicates that a Python subprocess call had an uncaught exception.
    This exception also contains the attributes of :class:`CallError`.
    c         C   s8   | r t  j | ƒ |  _ n  t j |  | | | | ƒ d  S(   N(   R    t   get_exception_namet   nameRA   R"   (   R!   RW   R   R&   R(   (    (    s:   /afs/athena.mit.edu/contrib/scripts/wizard/wizard/shell.pyR"   Š  s     c         C   s,   |  j  r d |  j  |  j f Sd |  j Sd  S(   Ns   PythonCallError [%s]
%ss   PythonCallError
%s(   Ru   R(   (   R!   (    (    s:   /afs/athena.mit.edu/contrib/scripts/wizard/wizard/shell.pyRs     s    	N(   RZ   R[   R\   R   Ru   R"   Rs   (    (    (    s:   /afs/athena.mit.edu/contrib/scripts/wizard/wizard/shell.pyR@   ƒ  s   	(   R\   R3   R   R   R	   Rk   R   R    R   R9   R   R   t   objectR   R]   RU   RG   RL   RQ   RR   R#   Ra   Ro   RA   R@   (    (    (    s:   /afs/athena.mit.edu/contrib/scripts/wizard/wizard/shell.pyt   <module>   s,   		Æf						