##// END OF EJS Templates
commandserver: mark developer-only logging option
Matt Mackall -
r25832:5857be01 default
parent child Browse files
Show More
@@ -1,353 +1,354 b''
1 # commandserver.py - communicate with Mercurial's API over a pipe
1 # commandserver.py - communicate with Mercurial's API over a pipe
2 #
2 #
3 # Copyright Matt Mackall <mpm@selenic.com>
3 # Copyright Matt Mackall <mpm@selenic.com>
4 #
4 #
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 sys, 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):
16 if not logfile:
16 if not logfile:
17 return
17 return
18
18
19 for a in args:
19 for a in args:
20 logfile.write(str(a))
20 logfile.write(str(a))
21
21
22 logfile.flush()
22 logfile.flush()
23
23
24 class channeledoutput(object):
24 class channeledoutput(object):
25 """
25 """
26 Write data to out in the following format:
26 Write data to out in the following format:
27
27
28 data length (unsigned int),
28 data length (unsigned int),
29 data
29 data
30 """
30 """
31 def __init__(self, out, channel):
31 def __init__(self, out, channel):
32 self.out = out
32 self.out = out
33 self.channel = channel
33 self.channel = channel
34
34
35 def write(self, data):
35 def write(self, data):
36 if not data:
36 if not data:
37 return
37 return
38 self.out.write(struct.pack('>cI', self.channel, len(data)))
38 self.out.write(struct.pack('>cI', self.channel, len(data)))
39 self.out.write(data)
39 self.out.write(data)
40 self.out.flush()
40 self.out.flush()
41
41
42 def __getattr__(self, attr):
42 def __getattr__(self, attr):
43 if attr in ('isatty', 'fileno'):
43 if attr in ('isatty', 'fileno'):
44 raise AttributeError(attr)
44 raise AttributeError(attr)
45 return getattr(self.out, attr)
45 return getattr(self.out, attr)
46
46
47 class channeledinput(object):
47 class channeledinput(object):
48 """
48 """
49 Read data from in_.
49 Read data from in_.
50
50
51 Requests for input are written to out in the following format:
51 Requests for input are written to out in the following format:
52 channel identifier - 'I' for plain input, 'L' line based (1 byte)
52 channel identifier - 'I' for plain input, 'L' line based (1 byte)
53 how many bytes to send at most (unsigned int),
53 how many bytes to send at most (unsigned int),
54
54
55 The client replies with:
55 The client replies with:
56 data length (unsigned int), 0 meaning EOF
56 data length (unsigned int), 0 meaning EOF
57 data
57 data
58 """
58 """
59
59
60 maxchunksize = 4 * 1024
60 maxchunksize = 4 * 1024
61
61
62 def __init__(self, in_, out, channel):
62 def __init__(self, in_, out, channel):
63 self.in_ = in_
63 self.in_ = in_
64 self.out = out
64 self.out = out
65 self.channel = channel
65 self.channel = channel
66
66
67 def read(self, size=-1):
67 def read(self, size=-1):
68 if size < 0:
68 if size < 0:
69 # if we need to consume all the clients input, ask for 4k chunks
69 # if we need to consume all the clients input, ask for 4k chunks
70 # so the pipe doesn't fill up risking a deadlock
70 # so the pipe doesn't fill up risking a deadlock
71 size = self.maxchunksize
71 size = self.maxchunksize
72 s = self._read(size, self.channel)
72 s = self._read(size, self.channel)
73 buf = s
73 buf = s
74 while s:
74 while s:
75 s = self._read(size, self.channel)
75 s = self._read(size, self.channel)
76 buf += s
76 buf += s
77
77
78 return buf
78 return buf
79 else:
79 else:
80 return self._read(size, self.channel)
80 return self._read(size, self.channel)
81
81
82 def _read(self, size, channel):
82 def _read(self, size, channel):
83 if not size:
83 if not size:
84 return ''
84 return ''
85 assert size > 0
85 assert size > 0
86
86
87 # tell the client we need at most size bytes
87 # tell the client we need at most size bytes
88 self.out.write(struct.pack('>cI', channel, size))
88 self.out.write(struct.pack('>cI', channel, size))
89 self.out.flush()
89 self.out.flush()
90
90
91 length = self.in_.read(4)
91 length = self.in_.read(4)
92 length = struct.unpack('>I', length)[0]
92 length = struct.unpack('>I', length)[0]
93 if not length:
93 if not length:
94 return ''
94 return ''
95 else:
95 else:
96 return self.in_.read(length)
96 return self.in_.read(length)
97
97
98 def readline(self, size=-1):
98 def readline(self, size=-1):
99 if size < 0:
99 if size < 0:
100 size = self.maxchunksize
100 size = self.maxchunksize
101 s = self._read(size, 'L')
101 s = self._read(size, 'L')
102 buf = s
102 buf = s
103 # keep asking for more until there's either no more or
103 # keep asking for more until there's either no more or
104 # we got a full line
104 # we got a full line
105 while s and s[-1] != '\n':
105 while s and s[-1] != '\n':
106 s = self._read(size, 'L')
106 s = self._read(size, 'L')
107 buf += s
107 buf += s
108
108
109 return buf
109 return buf
110 else:
110 else:
111 return self._read(size, 'L')
111 return self._read(size, 'L')
112
112
113 def __iter__(self):
113 def __iter__(self):
114 return self
114 return self
115
115
116 def next(self):
116 def next(self):
117 l = self.readline()
117 l = self.readline()
118 if not l:
118 if not l:
119 raise StopIteration
119 raise StopIteration
120 return l
120 return l
121
121
122 def __getattr__(self, attr):
122 def __getattr__(self, attr):
123 if attr in ('isatty', 'fileno'):
123 if attr in ('isatty', 'fileno'):
124 raise AttributeError(attr)
124 raise AttributeError(attr)
125 return getattr(self.in_, attr)
125 return getattr(self.in_, attr)
126
126
127 class server(object):
127 class server(object):
128 """
128 """
129 Listens for commands on fin, runs them and writes the output on a channel
129 Listens for commands on fin, runs them and writes the output on a channel
130 based stream to fout.
130 based stream to fout.
131 """
131 """
132 def __init__(self, ui, repo, fin, fout):
132 def __init__(self, ui, repo, fin, fout):
133 self.cwd = os.getcwd()
133 self.cwd = os.getcwd()
134
134
135 # developer config: cmdserver.log
135 logpath = ui.config("cmdserver", "log", None)
136 logpath = ui.config("cmdserver", "log", None)
136 if logpath:
137 if logpath:
137 global logfile
138 global logfile
138 if logpath == '-':
139 if logpath == '-':
139 # write log on a special 'd' (debug) channel
140 # write log on a special 'd' (debug) channel
140 logfile = channeledoutput(fout, 'd')
141 logfile = channeledoutput(fout, 'd')
141 else:
142 else:
142 logfile = open(logpath, 'a')
143 logfile = open(logpath, 'a')
143
144
144 if repo:
145 if repo:
145 # the ui here is really the repo ui so take its baseui so we don't
146 # the ui here is really the repo ui so take its baseui so we don't
146 # end up with its local configuration
147 # end up with its local configuration
147 self.ui = repo.baseui
148 self.ui = repo.baseui
148 self.repo = repo
149 self.repo = repo
149 self.repoui = repo.ui
150 self.repoui = repo.ui
150 else:
151 else:
151 self.ui = ui
152 self.ui = ui
152 self.repo = self.repoui = None
153 self.repo = self.repoui = None
153
154
154 self.cerr = channeledoutput(fout, 'e')
155 self.cerr = channeledoutput(fout, 'e')
155 self.cout = channeledoutput(fout, 'o')
156 self.cout = channeledoutput(fout, 'o')
156 self.cin = channeledinput(fin, fout, 'I')
157 self.cin = channeledinput(fin, fout, 'I')
157 self.cresult = channeledoutput(fout, 'r')
158 self.cresult = channeledoutput(fout, 'r')
158
159
159 self.client = fin
160 self.client = fin
160
161
161 def _read(self, size):
162 def _read(self, size):
162 if not size:
163 if not size:
163 return ''
164 return ''
164
165
165 data = self.client.read(size)
166 data = self.client.read(size)
166
167
167 # is the other end closed?
168 # is the other end closed?
168 if not data:
169 if not data:
169 raise EOFError
170 raise EOFError
170
171
171 return data
172 return data
172
173
173 def runcommand(self):
174 def runcommand(self):
174 """ reads a list of \0 terminated arguments, executes
175 """ reads a list of \0 terminated arguments, executes
175 and writes the return code to the result channel """
176 and writes the return code to the result channel """
176
177
177 length = struct.unpack('>I', self._read(4))[0]
178 length = struct.unpack('>I', self._read(4))[0]
178 if not length:
179 if not length:
179 args = []
180 args = []
180 else:
181 else:
181 args = self._read(length).split('\0')
182 args = self._read(length).split('\0')
182
183
183 # copy the uis so changes (e.g. --config or --verbose) don't
184 # copy the uis so changes (e.g. --config or --verbose) don't
184 # persist between requests
185 # persist between requests
185 copiedui = self.ui.copy()
186 copiedui = self.ui.copy()
186 uis = [copiedui]
187 uis = [copiedui]
187 if self.repo:
188 if self.repo:
188 self.repo.baseui = copiedui
189 self.repo.baseui = copiedui
189 # clone ui without using ui.copy because this is protected
190 # clone ui without using ui.copy because this is protected
190 repoui = self.repoui.__class__(self.repoui)
191 repoui = self.repoui.__class__(self.repoui)
191 repoui.copy = copiedui.copy # redo copy protection
192 repoui.copy = copiedui.copy # redo copy protection
192 uis.append(repoui)
193 uis.append(repoui)
193 self.repo.ui = self.repo.dirstate._ui = repoui
194 self.repo.ui = self.repo.dirstate._ui = repoui
194 self.repo.invalidateall()
195 self.repo.invalidateall()
195
196
196 for ui in uis:
197 for ui in uis:
197 # any kind of interaction must use server channels
198 # any kind of interaction must use server channels
198 ui.setconfig('ui', 'nontty', 'true', 'commandserver')
199 ui.setconfig('ui', 'nontty', 'true', 'commandserver')
199
200
200 req = dispatch.request(args[:], copiedui, self.repo, self.cin,
201 req = dispatch.request(args[:], copiedui, self.repo, self.cin,
201 self.cout, self.cerr)
202 self.cout, self.cerr)
202
203
203 ret = (dispatch.dispatch(req) or 0) & 255 # might return None
204 ret = (dispatch.dispatch(req) or 0) & 255 # might return None
204
205
205 # restore old cwd
206 # restore old cwd
206 if '--cwd' in args:
207 if '--cwd' in args:
207 os.chdir(self.cwd)
208 os.chdir(self.cwd)
208
209
209 self.cresult.write(struct.pack('>i', int(ret)))
210 self.cresult.write(struct.pack('>i', int(ret)))
210
211
211 def getencoding(self):
212 def getencoding(self):
212 """ writes the current encoding to the result channel """
213 """ writes the current encoding to the result channel """
213 self.cresult.write(encoding.encoding)
214 self.cresult.write(encoding.encoding)
214
215
215 def serveone(self):
216 def serveone(self):
216 cmd = self.client.readline()[:-1]
217 cmd = self.client.readline()[:-1]
217 if cmd:
218 if cmd:
218 handler = self.capabilities.get(cmd)
219 handler = self.capabilities.get(cmd)
219 if handler:
220 if handler:
220 handler(self)
221 handler(self)
221 else:
222 else:
222 # clients are expected to check what commands are supported by
223 # clients are expected to check what commands are supported by
223 # looking at the servers capabilities
224 # looking at the servers capabilities
224 raise util.Abort(_('unknown command %s') % cmd)
225 raise util.Abort(_('unknown command %s') % cmd)
225
226
226 return cmd != ''
227 return cmd != ''
227
228
228 capabilities = {'runcommand' : runcommand,
229 capabilities = {'runcommand' : runcommand,
229 'getencoding' : getencoding}
230 'getencoding' : getencoding}
230
231
231 def serve(self):
232 def serve(self):
232 hellomsg = 'capabilities: ' + ' '.join(sorted(self.capabilities))
233 hellomsg = 'capabilities: ' + ' '.join(sorted(self.capabilities))
233 hellomsg += '\n'
234 hellomsg += '\n'
234 hellomsg += 'encoding: ' + encoding.encoding
235 hellomsg += 'encoding: ' + encoding.encoding
235 hellomsg += '\n'
236 hellomsg += '\n'
236 hellomsg += 'pid: %d' % os.getpid()
237 hellomsg += 'pid: %d' % os.getpid()
237
238
238 # write the hello msg in -one- chunk
239 # write the hello msg in -one- chunk
239 self.cout.write(hellomsg)
240 self.cout.write(hellomsg)
240
241
241 try:
242 try:
242 while self.serveone():
243 while self.serveone():
243 pass
244 pass
244 except EOFError:
245 except EOFError:
245 # we'll get here if the client disconnected while we were reading
246 # we'll get here if the client disconnected while we were reading
246 # its request
247 # its request
247 return 1
248 return 1
248
249
249 return 0
250 return 0
250
251
251 def _protectio(ui):
252 def _protectio(ui):
252 """ duplicates streams and redirect original to null if ui uses stdio """
253 """ duplicates streams and redirect original to null if ui uses stdio """
253 ui.flush()
254 ui.flush()
254 newfiles = []
255 newfiles = []
255 nullfd = os.open(os.devnull, os.O_RDWR)
256 nullfd = os.open(os.devnull, os.O_RDWR)
256 for f, sysf, mode in [(ui.fin, sys.stdin, 'rb'),
257 for f, sysf, mode in [(ui.fin, sys.stdin, 'rb'),
257 (ui.fout, sys.stdout, 'wb')]:
258 (ui.fout, sys.stdout, 'wb')]:
258 if f is sysf:
259 if f is sysf:
259 newfd = os.dup(f.fileno())
260 newfd = os.dup(f.fileno())
260 os.dup2(nullfd, f.fileno())
261 os.dup2(nullfd, f.fileno())
261 f = os.fdopen(newfd, mode)
262 f = os.fdopen(newfd, mode)
262 newfiles.append(f)
263 newfiles.append(f)
263 os.close(nullfd)
264 os.close(nullfd)
264 return tuple(newfiles)
265 return tuple(newfiles)
265
266
266 def _restoreio(ui, fin, fout):
267 def _restoreio(ui, fin, fout):
267 """ restores streams from duplicated ones """
268 """ restores streams from duplicated ones """
268 ui.flush()
269 ui.flush()
269 for f, uif in [(fin, ui.fin), (fout, ui.fout)]:
270 for f, uif in [(fin, ui.fin), (fout, ui.fout)]:
270 if f is not uif:
271 if f is not uif:
271 os.dup2(f.fileno(), uif.fileno())
272 os.dup2(f.fileno(), uif.fileno())
272 f.close()
273 f.close()
273
274
274 class pipeservice(object):
275 class pipeservice(object):
275 def __init__(self, ui, repo, opts):
276 def __init__(self, ui, repo, opts):
276 self.ui = ui
277 self.ui = ui
277 self.repo = repo
278 self.repo = repo
278
279
279 def init(self):
280 def init(self):
280 pass
281 pass
281
282
282 def run(self):
283 def run(self):
283 ui = self.ui
284 ui = self.ui
284 # redirect stdio to null device so that broken extensions or in-process
285 # redirect stdio to null device so that broken extensions or in-process
285 # hooks will never cause corruption of channel protocol.
286 # hooks will never cause corruption of channel protocol.
286 fin, fout = _protectio(ui)
287 fin, fout = _protectio(ui)
287 try:
288 try:
288 sv = server(ui, self.repo, fin, fout)
289 sv = server(ui, self.repo, fin, fout)
289 return sv.serve()
290 return sv.serve()
290 finally:
291 finally:
291 _restoreio(ui, fin, fout)
292 _restoreio(ui, fin, fout)
292
293
293 class _requesthandler(SocketServer.StreamRequestHandler):
294 class _requesthandler(SocketServer.StreamRequestHandler):
294 def handle(self):
295 def handle(self):
295 ui = self.server.ui
296 ui = self.server.ui
296 repo = self.server.repo
297 repo = self.server.repo
297 sv = server(ui, repo, self.rfile, self.wfile)
298 sv = server(ui, repo, self.rfile, self.wfile)
298 try:
299 try:
299 try:
300 try:
300 sv.serve()
301 sv.serve()
301 # handle exceptions that may be raised by command server. most of
302 # handle exceptions that may be raised by command server. most of
302 # known exceptions are caught by dispatch.
303 # known exceptions are caught by dispatch.
303 except util.Abort as inst:
304 except util.Abort as inst:
304 ui.warn(_('abort: %s\n') % inst)
305 ui.warn(_('abort: %s\n') % inst)
305 except IOError as inst:
306 except IOError as inst:
306 if inst.errno != errno.EPIPE:
307 if inst.errno != errno.EPIPE:
307 raise
308 raise
308 except KeyboardInterrupt:
309 except KeyboardInterrupt:
309 pass
310 pass
310 except: # re-raises
311 except: # re-raises
311 # also write traceback to error channel. otherwise client cannot
312 # also write traceback to error channel. otherwise client cannot
312 # see it because it is written to server's stderr by default.
313 # see it because it is written to server's stderr by default.
313 traceback.print_exc(file=sv.cerr)
314 traceback.print_exc(file=sv.cerr)
314 raise
315 raise
315
316
316 class unixservice(object):
317 class unixservice(object):
317 """
318 """
318 Listens on unix domain socket and forks server per connection
319 Listens on unix domain socket and forks server per connection
319 """
320 """
320 def __init__(self, ui, repo, opts):
321 def __init__(self, ui, repo, opts):
321 self.ui = ui
322 self.ui = ui
322 self.repo = repo
323 self.repo = repo
323 self.address = opts['address']
324 self.address = opts['address']
324 if not util.safehasattr(SocketServer, 'UnixStreamServer'):
325 if not util.safehasattr(SocketServer, 'UnixStreamServer'):
325 raise util.Abort(_('unsupported platform'))
326 raise util.Abort(_('unsupported platform'))
326 if not self.address:
327 if not self.address:
327 raise util.Abort(_('no socket path specified with --address'))
328 raise util.Abort(_('no socket path specified with --address'))
328
329
329 def init(self):
330 def init(self):
330 class cls(SocketServer.ForkingMixIn, SocketServer.UnixStreamServer):
331 class cls(SocketServer.ForkingMixIn, SocketServer.UnixStreamServer):
331 ui = self.ui
332 ui = self.ui
332 repo = self.repo
333 repo = self.repo
333 self.server = cls(self.address, _requesthandler)
334 self.server = cls(self.address, _requesthandler)
334 self.ui.status(_('listening at %s\n') % self.address)
335 self.ui.status(_('listening at %s\n') % self.address)
335 self.ui.flush() # avoid buffering of status message
336 self.ui.flush() # avoid buffering of status message
336
337
337 def run(self):
338 def run(self):
338 try:
339 try:
339 self.server.serve_forever()
340 self.server.serve_forever()
340 finally:
341 finally:
341 os.unlink(self.address)
342 os.unlink(self.address)
342
343
343 _servicemap = {
344 _servicemap = {
344 'pipe': pipeservice,
345 'pipe': pipeservice,
345 'unix': unixservice,
346 'unix': unixservice,
346 }
347 }
347
348
348 def createservice(ui, repo, opts):
349 def createservice(ui, repo, opts):
349 mode = opts['cmdserver']
350 mode = opts['cmdserver']
350 try:
351 try:
351 return _servicemap[mode](ui, repo, opts)
352 return _servicemap[mode](ui, repo, opts)
352 except KeyError:
353 except KeyError:
353 raise util.Abort(_('unknown mode %s') % mode)
354 raise util.Abort(_('unknown mode %s') % mode)
General Comments 0
You need to be logged in to leave comments. Login now