changeset 14:4ba34e1cf310 default tip

untrusted hg commands now only have 60 seconds to execute.
author Atul Varma <avarma@mozilla.com>
date Thu, 03 Jun 2010 11:05:33 -0700
parents 88da4618d578
children
files bzezpatch/hg.py bzezpatch/timelimiter.py
diffstat 2 files changed, 66 insertions(+), 16 deletions(-) [+]
line wrap: on
line diff
--- a/bzezpatch/hg.py	Thu Jun 03 09:41:11 2010 -0700
+++ b/bzezpatch/hg.py	Thu Jun 03 11:05:33 2010 -0700
@@ -4,9 +4,16 @@
 import tempfile
 import shutil
 
+# Only give untrusted hg commands this long to execute.
+DEFAULT_TIMEOUT = 60
+
 class Hg(object):
-    def __init__(self, **kwargs):
-        self.__dict__.update(kwargs)
+    def __init__(self, canonical_repo, hg='hg', timeout=DEFAULT_TIMEOUT):
+        self.canonical_repo = canonical_repo
+        self.hg = hg
+        self.timeout = timeout
+        self.timelimiter = os.path.join(os.path.dirname(__file__),
+                                        'timelimiter.py')
 
     def trypatch(self, pull_url, out=sys.stdout, privout=sys.stdout):
         temp_dir = tempfile.mkdtemp(prefix='hg-trypatch-')
@@ -18,15 +25,19 @@
             privout.flush()
 
         def log(msg):
+            privlog(msg)
             out.flush()
             out.write('%s\n' % msg)
             out.flush()
 
+        hg = [sys.executable, self.timelimiter, str(self.timeout), self.hg]
+        privhg = [self.hg]
+
         try:
             # Step 1: Pull and update local canonical repo.
             privlog('pulling and updating local canonical repo')
-            rv = subprocess.call([self.hg, '-R', self.canonical_repo,
-                                  'pull', '-u'],
+            rv = subprocess.call(privhg + ['-R', self.canonical_repo,
+                                           'pull', '-u'],
                                  stdout=privout, stderr=privout)
             if rv:
                 log('Warning: server was unable to pull the latest version '
@@ -37,7 +48,8 @@
 
             # Step 2: Figure out tip changeset so we can diff against
             # it later.
-            popen = subprocess.Popen([self.hg, '-R', self.canonical_repo,
+            popen = subprocess.Popen(privhg + 
+                                     ['-R', self.canonical_repo,
                                       'tip', '--template',
                                       '{node}'], stdout=subprocess.PIPE)
             tip_revision, _ = popen.communicate()
@@ -47,25 +59,25 @@
 
             # Step 3: Create a temporary clone to apply the patch to.
             privlog('creating temporary clone at %s' % temp_repo)
-            subprocess.check_call([self.hg, 'clone', self.canonical_repo,
-                                   temp_repo],
+            subprocess.check_call(privhg + ['clone', self.canonical_repo,
+                                            temp_repo],
                                   stdout=privout, stderr=privout)
 
             # Step 4: Apply the patch by pulling from the foreign repo.
             log('pulling from %s' % pull_url)
-            subprocess.check_call([self.hg, '-R', temp_repo, 'pull', 
-                                   '--rebase', pull_url],
+            subprocess.check_call(hg + ['-R', temp_repo, 'pull', 
+                                        '--rebase', pull_url],
                                   stdout=out, stderr=out)
-            subprocess.check_call([self.hg, '-R', temp_repo, 'pull', 
-                                   '--update', pull_url],
+            subprocess.check_call(hg + ['-R', temp_repo, 'pull', 
+                                        '--update', pull_url],
                                   stdout=out, stderr=out)
 
             # Step 5: Generate a patch by diffing the tip of the
-            # foreign repo against 
-            popen = subprocess.Popen([self.hg, '-R', temp_repo,
-                                      'diff', '-r', tip_revision,
-                                      '-r', 'tip',
-                                      '--git', '--unified', '8'],
+            # foreign repo against the tip of the canonical one.
+            popen = subprocess.Popen(hg + ['-R', temp_repo,
+                                           'diff', '-r', tip_revision,
+                                           '-r', 'tip',
+                                           '--git', '--unified', '8'],
                                      stdout=subprocess.PIPE)
             patch, _ = popen.communicate()
             if popen.returncode:
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bzezpatch/timelimiter.py	Thu Jun 03 11:05:33 2010 -0700
@@ -0,0 +1,38 @@
+import os
+import sys
+import subprocess
+import threading
+import time
+import signal
+
+def killer(pid, deadline, now=time.time, sleep=time.sleep,
+           sig=signal.SIGINT, kill=os.kill, stderr=sys.stderr):
+    while now() < deadline:
+        sleep(0.10)
+    stderr.flush()
+    stderr.write('process is taking too long to execute, killing it.\n')
+    stderr.flush()
+    kill(pid, sig)
+
+if __name__ == '__main__':
+    if len(sys.argv) < 3:
+        print "usage: %s <timeout> <cmdline>" % sys.argv[0]
+        sys.exit(1)
+
+    try:
+        timeout = int(sys.argv[1])
+    except ValueError:
+        print "error: '%s' is not a valid timeout in seconds" % sys.argv[1]
+        sys.exit(1)
+
+    cmdline = sys.argv[2:]
+    popen = subprocess.Popen(cmdline)
+    watchdog = threading.Thread(
+        target=killer,
+        kwargs=dict(pid=popen.pid,
+                    deadline=time.time() + timeout)
+        )
+    watchdog.setDaemon(True)
+    watchdog.start()
+
+    sys.exit(popen.wait())