#!/usr/bin/env python

'''
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements.  See the NOTICE file
distributed with this work for additional information
regarding copyright ownership.  The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License.  You may obtain a copy of the License at

  http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
'''

try: import readline  # For readline input support
except: pass

import sys, os, traceback, codeop, cStringIO, cPickle, tempfile

def pipename(pid):
  """Return name of pipe to use"""
  return os.path.join(tempfile.gettempdir(), 'debug-%d' % pid)

class NamedPipe(object):
  def __init__(self, name, end=0, mode=0666):
    """Open a pair of pipes, name.in and name.out for communication
    with another process.  One process should pass 1 for end, and the
    other 0.  Data is marshalled with pickle."""
    self.in_name, self.out_name = name +'.in',  name +'.out',
    try: os.mkfifo(self.in_name,mode)
    except OSError: pass
    try: os.mkfifo(self.out_name,mode)
    except OSError: pass
    
    # NOTE: The order the ends are opened in is important - both ends
    # of pipe 1 must be opened before the second pipe can be opened.
    if end:
      self.inp = open(self.out_name,'r')
      self.out = open(self.in_name,'w')
    else:
      self.out = open(self.out_name,'w')
      self.inp = open(self.in_name,'r')
    self._open = True

  def is_open(self):
    return not (self.inp.closed or self.out.closed)
    
  def put(self,msg):
    if self.is_open():
      data = cPickle.dumps(msg,1)
      self.out.write("%d\n" % len(data))
      self.out.write(data)
      self.out.flush()
    else:
      raise Exception("Pipe closed")
    
  def get(self):
    txt=self.inp.readline()
    if not txt: 
      self.inp.close()
    else:
      l = int(txt)
      data=self.inp.read(l)
      if len(data) < l: self.inp.close()
      return cPickle.loads(data)  # Convert back to python object.
      
  def close(self):
    self.inp.close()
    self.out.close()
    try: os.remove(self.in_name)
    except OSError: pass
    try: os.remove(self.out_name)
    except OSError: pass

  def __del__(self):
    self.close()
    
def remote_debug(sig,frame):
  """Handler to allow process to be remotely debugged."""
  def _raiseEx(ex):
    """Raise specified exception in the remote process"""
    _raiseEx.ex = ex
  _raiseEx.ex = None
  
  try:
    # Provide some useful functions.
    locs = {'_raiseEx' : _raiseEx}
    locs.update(frame.f_locals)  # Unless shadowed.
    globs = frame.f_globals
    
    pid = os.getpid()  # Use pipe name based on pid
    pipe = NamedPipe(pipename(pid))
  
    old_stdout, old_stderr = sys.stdout, sys.stderr
    txt = ''
    pipe.put("Interrupting process at following point:\n" + 
         ''.join(traceback.format_stack(frame)) + ">>> ")
    
    try:
      while pipe.is_open() and _raiseEx.ex is None:
        line = pipe.get()
        if line is None: continue # EOF
        txt += line
        try:
          code = codeop.compile_command(txt)
          if code:
            sys.stdout = cStringIO.StringIO()
            sys.stderr = sys.stdout
            exec code in globs,locs
            txt = ''
            pipe.put(sys.stdout.getvalue() + '>>> ')
          else:
            pipe.put('... ')
        except:
          txt='' # May be syntax err.
          sys.stdout = cStringIO.StringIO()
          sys.stderr = sys.stdout
          traceback.print_exc()
          pipe.put(sys.stdout.getvalue() + '>>> ')
    finally:
      sys.stdout = old_stdout # Restore redirected output.
      sys.stderr = old_stderr
      pipe.close()

  except Exception:  # Don't allow debug exceptions to propogate to real program.
    traceback.print_exc()
    
  if _raiseEx.ex is not None: raise _raiseEx.ex