comparison mercurial/commandserver.py @ 23324:69f86b937035

cmdserver: protect pipe server streams against corruption caused by direct io Because pipe-mode server uses stdio as IPC channel, other modules should not touch stdio directly and use ui instead. However, this strategy is brittle because several Python functions read and write stdio implicitly. print 'hello' # should use ui.write() # => ch = 'h', size = 1701604463 'ello', data = '\n' This patch adds protection for such mistakes. Both stdio files and low-level file descriptors are redirected to /dev/null while command server uses them.
author Yuya Nishihara <yuya@tcha.org>
date Sat, 15 Nov 2014 13:50:43 +0900
parents bc374458688b
children 328739ea70c3
comparison
equal deleted inserted replaced
23323:bc374458688b 23324:69f86b937035
5 # This software may be used and distributed according to the terms of the 5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version. 6 # GNU General Public License version 2 or any later version.
7 7
8 from i18n import _ 8 from i18n import _
9 import struct 9 import struct
10 import os, errno, traceback, SocketServer 10 import sys, os, errno, traceback, SocketServer
11 import dispatch, encoding, util 11 import dispatch, encoding, util
12 12
13 logfile = None 13 logfile = None
14 14
15 def log(*args): 15 def log(*args):
246 # its request 246 # its request
247 return 1 247 return 1
248 248
249 return 0 249 return 0
250 250
251 def _protectio(ui):
252 """ duplicates streams and redirect original to null if ui uses stdio """
253 ui.flush()
254 newfiles = []
255 nullfd = os.open(os.devnull, os.O_RDWR)
256 for f, sysf, mode in [(ui.fin, sys.stdin, 'rb'),
257 (ui.fout, sys.stdout, 'wb')]:
258 if f is sysf:
259 newfd = os.dup(f.fileno())
260 os.dup2(nullfd, f.fileno())
261 f = os.fdopen(newfd, mode)
262 newfiles.append(f)
263 os.close(nullfd)
264 return tuple(newfiles)
265
266 def _restoreio(ui, fin, fout):
267 """ restores streams from duplicated ones """
268 ui.flush()
269 for f, uif in [(fin, ui.fin), (fout, ui.fout)]:
270 if f is not uif:
271 os.dup2(f.fileno(), uif.fileno())
272 f.close()
273
251 class pipeservice(object): 274 class pipeservice(object):
252 def __init__(self, ui, repo, opts): 275 def __init__(self, ui, repo, opts):
253 self.ui = ui 276 self.ui = ui
254 self.repo = repo 277 self.repo = repo
255 278
256 def init(self): 279 def init(self):
257 pass 280 pass
258 281
259 def run(self): 282 def run(self):
260 ui = self.ui 283 ui = self.ui
261 sv = server(ui, self.repo, ui.fin, ui.fout) 284 # redirect stdio to null device so that broken extensions or in-process
262 return sv.serve() 285 # hooks will never cause corruption of channel protocol.
286 fin, fout = _protectio(ui)
287 try:
288 sv = server(ui, self.repo, fin, fout)
289 return sv.serve()
290 finally:
291 _restoreio(ui, fin, fout)
263 292
264 class _requesthandler(SocketServer.StreamRequestHandler): 293 class _requesthandler(SocketServer.StreamRequestHandler):
265 def handle(self): 294 def handle(self):
266 ui = self.server.ui 295 ui = self.server.ui
267 repo = self.server.repo 296 repo = self.server.repo