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