##// END OF EJS Templates
commandserver: enable logging when server process started...
Yuya Nishihara -
r40858:368ecbf7 default
parent child Browse files
Show More
@@ -1,600 +1,613 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 __future__ import absolute_import
9 9
10 10 import errno
11 11 import gc
12 12 import os
13 13 import random
14 14 import signal
15 15 import socket
16 16 import struct
17 17 import traceback
18 18
19 19 try:
20 20 import selectors
21 21 selectors.BaseSelector
22 22 except ImportError:
23 23 from .thirdparty import selectors2 as selectors
24 24
25 25 from .i18n import _
26 26 from . import (
27 27 encoding,
28 28 error,
29 29 pycompat,
30 30 util,
31 31 )
32 32 from .utils import (
33 33 cborutil,
34 34 procutil,
35 35 )
36 36
37 37 logfile = None
38 38
39 39 def log(*args):
40 40 if not logfile:
41 41 return
42 42
43 43 for a in args:
44 44 logfile.write(str(a))
45 45
46 46 logfile.flush()
47 47
48 48 class channeledoutput(object):
49 49 """
50 50 Write data to out in the following format:
51 51
52 52 data length (unsigned int),
53 53 data
54 54 """
55 55 def __init__(self, out, channel):
56 56 self.out = out
57 57 self.channel = channel
58 58
59 59 @property
60 60 def name(self):
61 61 return '<%c-channel>' % self.channel
62 62
63 63 def write(self, data):
64 64 if not data:
65 65 return
66 66 # single write() to guarantee the same atomicity as the underlying file
67 67 self.out.write(struct.pack('>cI', self.channel, len(data)) + data)
68 68 self.out.flush()
69 69
70 70 def __getattr__(self, attr):
71 71 if attr in (r'isatty', r'fileno', r'tell', r'seek'):
72 72 raise AttributeError(attr)
73 73 return getattr(self.out, attr)
74 74
75 75 class channeledmessage(object):
76 76 """
77 77 Write encoded message and metadata to out in the following format:
78 78
79 79 data length (unsigned int),
80 80 encoded message and metadata, as a flat key-value dict.
81 81
82 82 Each message should have 'type' attribute. Messages of unknown type
83 83 should be ignored.
84 84 """
85 85
86 86 # teach ui that write() can take **opts
87 87 structured = True
88 88
89 89 def __init__(self, out, channel, encodename, encodefn):
90 90 self._cout = channeledoutput(out, channel)
91 91 self.encoding = encodename
92 92 self._encodefn = encodefn
93 93
94 94 def write(self, data, **opts):
95 95 opts = pycompat.byteskwargs(opts)
96 96 if data is not None:
97 97 opts[b'data'] = data
98 98 self._cout.write(self._encodefn(opts))
99 99
100 100 def __getattr__(self, attr):
101 101 return getattr(self._cout, attr)
102 102
103 103 class channeledinput(object):
104 104 """
105 105 Read data from in_.
106 106
107 107 Requests for input are written to out in the following format:
108 108 channel identifier - 'I' for plain input, 'L' line based (1 byte)
109 109 how many bytes to send at most (unsigned int),
110 110
111 111 The client replies with:
112 112 data length (unsigned int), 0 meaning EOF
113 113 data
114 114 """
115 115
116 116 maxchunksize = 4 * 1024
117 117
118 118 def __init__(self, in_, out, channel):
119 119 self.in_ = in_
120 120 self.out = out
121 121 self.channel = channel
122 122
123 123 @property
124 124 def name(self):
125 125 return '<%c-channel>' % self.channel
126 126
127 127 def read(self, size=-1):
128 128 if size < 0:
129 129 # if we need to consume all the clients input, ask for 4k chunks
130 130 # so the pipe doesn't fill up risking a deadlock
131 131 size = self.maxchunksize
132 132 s = self._read(size, self.channel)
133 133 buf = s
134 134 while s:
135 135 s = self._read(size, self.channel)
136 136 buf += s
137 137
138 138 return buf
139 139 else:
140 140 return self._read(size, self.channel)
141 141
142 142 def _read(self, size, channel):
143 143 if not size:
144 144 return ''
145 145 assert size > 0
146 146
147 147 # tell the client we need at most size bytes
148 148 self.out.write(struct.pack('>cI', channel, size))
149 149 self.out.flush()
150 150
151 151 length = self.in_.read(4)
152 152 length = struct.unpack('>I', length)[0]
153 153 if not length:
154 154 return ''
155 155 else:
156 156 return self.in_.read(length)
157 157
158 158 def readline(self, size=-1):
159 159 if size < 0:
160 160 size = self.maxchunksize
161 161 s = self._read(size, 'L')
162 162 buf = s
163 163 # keep asking for more until there's either no more or
164 164 # we got a full line
165 165 while s and s[-1] != '\n':
166 166 s = self._read(size, 'L')
167 167 buf += s
168 168
169 169 return buf
170 170 else:
171 171 return self._read(size, 'L')
172 172
173 173 def __iter__(self):
174 174 return self
175 175
176 176 def next(self):
177 177 l = self.readline()
178 178 if not l:
179 179 raise StopIteration
180 180 return l
181 181
182 182 __next__ = next
183 183
184 184 def __getattr__(self, attr):
185 185 if attr in (r'isatty', r'fileno', r'tell', r'seek'):
186 186 raise AttributeError(attr)
187 187 return getattr(self.in_, attr)
188 188
189 189 _messageencoders = {
190 190 b'cbor': lambda v: b''.join(cborutil.streamencode(v)),
191 191 }
192 192
193 193 def _selectmessageencoder(ui):
194 194 # experimental config: cmdserver.message-encodings
195 195 encnames = ui.configlist(b'cmdserver', b'message-encodings')
196 196 for n in encnames:
197 197 f = _messageencoders.get(n)
198 198 if f:
199 199 return n, f
200 200 raise error.Abort(b'no supported message encodings: %s'
201 201 % b' '.join(encnames))
202 202
203 203 class server(object):
204 204 """
205 205 Listens for commands on fin, runs them and writes the output on a channel
206 206 based stream to fout.
207 207 """
208 208 def __init__(self, ui, repo, fin, fout):
209 209 self.cwd = encoding.getcwd()
210 210
211 # developer config: cmdserver.log
212 logpath = ui.config("cmdserver", "log")
213 if logpath:
211 if ui.config("cmdserver", "log") == '-':
214 212 global logfile
215 if logpath == '-':
216 # write log on a special 'd' (debug) channel
217 logfile = channeledoutput(fout, 'd')
218 else:
219 logfile = open(logpath, 'a')
213 # switch log stream to the 'd' (debug) channel
214 logfile = channeledoutput(fout, 'd')
220 215
221 216 if repo:
222 217 # the ui here is really the repo ui so take its baseui so we don't
223 218 # end up with its local configuration
224 219 self.ui = repo.baseui
225 220 self.repo = repo
226 221 self.repoui = repo.ui
227 222 else:
228 223 self.ui = ui
229 224 self.repo = self.repoui = None
230 225
231 226 self.cerr = channeledoutput(fout, 'e')
232 227 self.cout = channeledoutput(fout, 'o')
233 228 self.cin = channeledinput(fin, fout, 'I')
234 229 self.cresult = channeledoutput(fout, 'r')
235 230
236 231 # TODO: add this to help/config.txt when stabilized
237 232 # ``channel``
238 233 # Use separate channel for structured output. (Command-server only)
239 234 self.cmsg = None
240 235 if ui.config(b'ui', b'message-output') == b'channel':
241 236 encname, encfn = _selectmessageencoder(ui)
242 237 self.cmsg = channeledmessage(fout, b'm', encname, encfn)
243 238
244 239 self.client = fin
245 240
246 241 def cleanup(self):
247 242 """release and restore resources taken during server session"""
248 243
249 244 def _read(self, size):
250 245 if not size:
251 246 return ''
252 247
253 248 data = self.client.read(size)
254 249
255 250 # is the other end closed?
256 251 if not data:
257 252 raise EOFError
258 253
259 254 return data
260 255
261 256 def _readstr(self):
262 257 """read a string from the channel
263 258
264 259 format:
265 260 data length (uint32), data
266 261 """
267 262 length = struct.unpack('>I', self._read(4))[0]
268 263 if not length:
269 264 return ''
270 265 return self._read(length)
271 266
272 267 def _readlist(self):
273 268 """read a list of NULL separated strings from the channel"""
274 269 s = self._readstr()
275 270 if s:
276 271 return s.split('\0')
277 272 else:
278 273 return []
279 274
280 275 def runcommand(self):
281 276 """ reads a list of \0 terminated arguments, executes
282 277 and writes the return code to the result channel """
283 278 from . import dispatch # avoid cycle
284 279
285 280 args = self._readlist()
286 281
287 282 # copy the uis so changes (e.g. --config or --verbose) don't
288 283 # persist between requests
289 284 copiedui = self.ui.copy()
290 285 uis = [copiedui]
291 286 if self.repo:
292 287 self.repo.baseui = copiedui
293 288 # clone ui without using ui.copy because this is protected
294 289 repoui = self.repoui.__class__(self.repoui)
295 290 repoui.copy = copiedui.copy # redo copy protection
296 291 uis.append(repoui)
297 292 self.repo.ui = self.repo.dirstate._ui = repoui
298 293 self.repo.invalidateall()
299 294
300 295 for ui in uis:
301 296 ui.resetstate()
302 297 # any kind of interaction must use server channels, but chg may
303 298 # replace channels by fully functional tty files. so nontty is
304 299 # enforced only if cin is a channel.
305 300 if not util.safehasattr(self.cin, 'fileno'):
306 301 ui.setconfig('ui', 'nontty', 'true', 'commandserver')
307 302
308 303 req = dispatch.request(args[:], copiedui, self.repo, self.cin,
309 304 self.cout, self.cerr, self.cmsg)
310 305
311 306 try:
312 307 ret = dispatch.dispatch(req) & 255
313 308 self.cresult.write(struct.pack('>i', int(ret)))
314 309 finally:
315 310 # restore old cwd
316 311 if '--cwd' in args:
317 312 os.chdir(self.cwd)
318 313
319 314 def getencoding(self):
320 315 """ writes the current encoding to the result channel """
321 316 self.cresult.write(encoding.encoding)
322 317
323 318 def serveone(self):
324 319 cmd = self.client.readline()[:-1]
325 320 if cmd:
326 321 handler = self.capabilities.get(cmd)
327 322 if handler:
328 323 handler(self)
329 324 else:
330 325 # clients are expected to check what commands are supported by
331 326 # looking at the servers capabilities
332 327 raise error.Abort(_('unknown command %s') % cmd)
333 328
334 329 return cmd != ''
335 330
336 331 capabilities = {'runcommand': runcommand,
337 332 'getencoding': getencoding}
338 333
339 334 def serve(self):
340 335 hellomsg = 'capabilities: ' + ' '.join(sorted(self.capabilities))
341 336 hellomsg += '\n'
342 337 hellomsg += 'encoding: ' + encoding.encoding
343 338 hellomsg += '\n'
344 339 if self.cmsg:
345 340 hellomsg += 'message-encoding: %s\n' % self.cmsg.encoding
346 341 hellomsg += 'pid: %d' % procutil.getpid()
347 342 if util.safehasattr(os, 'getpgid'):
348 343 hellomsg += '\n'
349 344 hellomsg += 'pgid: %d' % os.getpgid(0)
350 345
351 346 # write the hello msg in -one- chunk
352 347 self.cout.write(hellomsg)
353 348
354 349 try:
355 350 while self.serveone():
356 351 pass
357 352 except EOFError:
358 353 # we'll get here if the client disconnected while we were reading
359 354 # its request
360 355 return 1
361 356
362 357 return 0
363 358
359 def setuplogging(ui):
360 """Set up server logging facility
361
362 If cmdserver.log is '-', log messages will be sent to the 'd' channel
363 while a client is connected. Otherwise, messages will be written to
364 the stderr of the server process.
365 """
366 # developer config: cmdserver.log
367 logpath = ui.config(b'cmdserver', b'log')
368 if not logpath:
369 return
370
371 global logfile
372 if logpath == b'-':
373 logfile = ui.ferr
374 else:
375 logfile = open(logpath, 'ab')
376
364 377 class pipeservice(object):
365 378 def __init__(self, ui, repo, opts):
366 379 self.ui = ui
367 380 self.repo = repo
368 381
369 382 def init(self):
370 383 pass
371 384
372 385 def run(self):
373 386 ui = self.ui
374 387 # redirect stdio to null device so that broken extensions or in-process
375 388 # hooks will never cause corruption of channel protocol.
376 389 with procutil.protectedstdio(ui.fin, ui.fout) as (fin, fout):
377 390 sv = server(ui, self.repo, fin, fout)
378 391 try:
379 392 return sv.serve()
380 393 finally:
381 394 sv.cleanup()
382 395
383 396 def _initworkerprocess():
384 397 # use a different process group from the master process, in order to:
385 398 # 1. make the current process group no longer "orphaned" (because the
386 399 # parent of this process is in a different process group while
387 400 # remains in a same session)
388 401 # according to POSIX 2.2.2.52, orphaned process group will ignore
389 402 # terminal-generated stop signals like SIGTSTP (Ctrl+Z), which will
390 403 # cause trouble for things like ncurses.
391 404 # 2. the client can use kill(-pgid, sig) to simulate terminal-generated
392 405 # SIGINT (Ctrl+C) and process-exit-generated SIGHUP. our child
393 406 # processes like ssh will be killed properly, without affecting
394 407 # unrelated processes.
395 408 os.setpgid(0, 0)
396 409 # change random state otherwise forked request handlers would have a
397 410 # same state inherited from parent.
398 411 random.seed()
399 412
400 413 def _serverequest(ui, repo, conn, createcmdserver):
401 414 fin = conn.makefile(r'rb')
402 415 fout = conn.makefile(r'wb')
403 416 sv = None
404 417 try:
405 418 sv = createcmdserver(repo, conn, fin, fout)
406 419 try:
407 420 sv.serve()
408 421 # handle exceptions that may be raised by command server. most of
409 422 # known exceptions are caught by dispatch.
410 423 except error.Abort as inst:
411 424 ui.error(_('abort: %s\n') % inst)
412 425 except IOError as inst:
413 426 if inst.errno != errno.EPIPE:
414 427 raise
415 428 except KeyboardInterrupt:
416 429 pass
417 430 finally:
418 431 sv.cleanup()
419 432 except: # re-raises
420 433 # also write traceback to error channel. otherwise client cannot
421 434 # see it because it is written to server's stderr by default.
422 435 if sv:
423 436 cerr = sv.cerr
424 437 else:
425 438 cerr = channeledoutput(fout, 'e')
426 439 cerr.write(encoding.strtolocal(traceback.format_exc()))
427 440 raise
428 441 finally:
429 442 fin.close()
430 443 try:
431 444 fout.close() # implicit flush() may cause another EPIPE
432 445 except IOError as inst:
433 446 if inst.errno != errno.EPIPE:
434 447 raise
435 448
436 449 class unixservicehandler(object):
437 450 """Set of pluggable operations for unix-mode services
438 451
439 452 Almost all methods except for createcmdserver() are called in the main
440 453 process. You can't pass mutable resource back from createcmdserver().
441 454 """
442 455
443 456 pollinterval = None
444 457
445 458 def __init__(self, ui):
446 459 self.ui = ui
447 460
448 461 def bindsocket(self, sock, address):
449 462 util.bindunixsocket(sock, address)
450 463 sock.listen(socket.SOMAXCONN)
451 464 self.ui.status(_('listening at %s\n') % address)
452 465 self.ui.flush() # avoid buffering of status message
453 466
454 467 def unlinksocket(self, address):
455 468 os.unlink(address)
456 469
457 470 def shouldexit(self):
458 471 """True if server should shut down; checked per pollinterval"""
459 472 return False
460 473
461 474 def newconnection(self):
462 475 """Called when main process notices new connection"""
463 476
464 477 def createcmdserver(self, repo, conn, fin, fout):
465 478 """Create new command server instance; called in the process that
466 479 serves for the current connection"""
467 480 return server(self.ui, repo, fin, fout)
468 481
469 482 class unixforkingservice(object):
470 483 """
471 484 Listens on unix domain socket and forks server per connection
472 485 """
473 486
474 487 def __init__(self, ui, repo, opts, handler=None):
475 488 self.ui = ui
476 489 self.repo = repo
477 490 self.address = opts['address']
478 491 if not util.safehasattr(socket, 'AF_UNIX'):
479 492 raise error.Abort(_('unsupported platform'))
480 493 if not self.address:
481 494 raise error.Abort(_('no socket path specified with --address'))
482 495 self._servicehandler = handler or unixservicehandler(ui)
483 496 self._sock = None
484 497 self._oldsigchldhandler = None
485 498 self._workerpids = set() # updated by signal handler; do not iterate
486 499 self._socketunlinked = None
487 500
488 501 def init(self):
489 502 self._sock = socket.socket(socket.AF_UNIX)
490 503 self._servicehandler.bindsocket(self._sock, self.address)
491 504 if util.safehasattr(procutil, 'unblocksignal'):
492 505 procutil.unblocksignal(signal.SIGCHLD)
493 506 o = signal.signal(signal.SIGCHLD, self._sigchldhandler)
494 507 self._oldsigchldhandler = o
495 508 self._socketunlinked = False
496 509
497 510 def _unlinksocket(self):
498 511 if not self._socketunlinked:
499 512 self._servicehandler.unlinksocket(self.address)
500 513 self._socketunlinked = True
501 514
502 515 def _cleanup(self):
503 516 signal.signal(signal.SIGCHLD, self._oldsigchldhandler)
504 517 self._sock.close()
505 518 self._unlinksocket()
506 519 # don't kill child processes as they have active clients, just wait
507 520 self._reapworkers(0)
508 521
509 522 def run(self):
510 523 try:
511 524 self._mainloop()
512 525 finally:
513 526 self._cleanup()
514 527
515 528 def _mainloop(self):
516 529 exiting = False
517 530 h = self._servicehandler
518 531 selector = selectors.DefaultSelector()
519 532 selector.register(self._sock, selectors.EVENT_READ)
520 533 while True:
521 534 if not exiting and h.shouldexit():
522 535 # clients can no longer connect() to the domain socket, so
523 536 # we stop queuing new requests.
524 537 # for requests that are queued (connect()-ed, but haven't been
525 538 # accept()-ed), handle them before exit. otherwise, clients
526 539 # waiting for recv() will receive ECONNRESET.
527 540 self._unlinksocket()
528 541 exiting = True
529 542 try:
530 543 ready = selector.select(timeout=h.pollinterval)
531 544 except OSError as inst:
532 545 # selectors2 raises ETIMEDOUT if timeout exceeded while
533 546 # handling signal interrupt. That's probably wrong, but
534 547 # we can easily get around it.
535 548 if inst.errno != errno.ETIMEDOUT:
536 549 raise
537 550 ready = []
538 551 if not ready:
539 552 # only exit if we completed all queued requests
540 553 if exiting:
541 554 break
542 555 continue
543 556 try:
544 557 conn, _addr = self._sock.accept()
545 558 except socket.error as inst:
546 559 if inst.args[0] == errno.EINTR:
547 560 continue
548 561 raise
549 562
550 563 pid = os.fork()
551 564 if pid:
552 565 try:
553 566 self.ui.debug('forked worker process (pid=%d)\n' % pid)
554 567 self._workerpids.add(pid)
555 568 h.newconnection()
556 569 finally:
557 570 conn.close() # release handle in parent process
558 571 else:
559 572 try:
560 573 selector.close()
561 574 self._sock.close()
562 575 self._runworker(conn)
563 576 conn.close()
564 577 os._exit(0)
565 578 except: # never return, hence no re-raises
566 579 try:
567 580 self.ui.traceback(force=True)
568 581 finally:
569 582 os._exit(255)
570 583 selector.close()
571 584
572 585 def _sigchldhandler(self, signal, frame):
573 586 self._reapworkers(os.WNOHANG)
574 587
575 588 def _reapworkers(self, options):
576 589 while self._workerpids:
577 590 try:
578 591 pid, _status = os.waitpid(-1, options)
579 592 except OSError as inst:
580 593 if inst.errno == errno.EINTR:
581 594 continue
582 595 if inst.errno != errno.ECHILD:
583 596 raise
584 597 # no child processes at all (reaped by other waitpid()?)
585 598 self._workerpids.clear()
586 599 return
587 600 if pid == 0:
588 601 # no waitable child processes
589 602 return
590 603 self.ui.debug('worker process exited (pid=%d)\n' % pid)
591 604 self._workerpids.discard(pid)
592 605
593 606 def _runworker(self, conn):
594 607 signal.signal(signal.SIGCHLD, self._oldsigchldhandler)
595 608 _initworkerprocess()
596 609 h = self._servicehandler
597 610 try:
598 611 _serverequest(self.ui, self.repo, conn, h.createcmdserver)
599 612 finally:
600 613 gc.collect() # trigger __del__ since worker process uses os._exit
@@ -1,210 +1,212 b''
1 1 # server.py - utility and factory of server
2 2 #
3 3 # Copyright 2005-2007 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 __future__ import absolute_import
9 9
10 10 import os
11 11
12 12 from .i18n import _
13 13
14 14 from . import (
15 15 chgserver,
16 16 cmdutil,
17 17 commandserver,
18 18 error,
19 19 hgweb,
20 20 pycompat,
21 21 util,
22 22 )
23 23
24 24 from .utils import (
25 25 procutil,
26 26 )
27 27
28 28 def runservice(opts, parentfn=None, initfn=None, runfn=None, logfile=None,
29 29 runargs=None, appendpid=False):
30 30 '''Run a command as a service.'''
31 31
32 32 postexecargs = {}
33 33
34 34 if opts['daemon_postexec']:
35 35 for inst in opts['daemon_postexec']:
36 36 if inst.startswith('unlink:'):
37 37 postexecargs['unlink'] = inst[7:]
38 38 elif inst.startswith('chdir:'):
39 39 postexecargs['chdir'] = inst[6:]
40 40 elif inst != 'none':
41 41 raise error.Abort(_('invalid value for --daemon-postexec: %s')
42 42 % inst)
43 43
44 44 # When daemonized on Windows, redirect stdout/stderr to the lockfile (which
45 45 # gets cleaned up after the child is up and running), so that the parent can
46 46 # read and print the error if this child dies early. See 594dd384803c. On
47 47 # other platforms, the child can write to the parent's stdio directly, until
48 48 # it is redirected prior to runfn().
49 49 if pycompat.iswindows and opts['daemon_postexec']:
50 50 if 'unlink' in postexecargs and os.path.exists(postexecargs['unlink']):
51 51 procutil.stdout.flush()
52 52 procutil.stderr.flush()
53 53
54 54 fd = os.open(postexecargs['unlink'],
55 55 os.O_WRONLY | os.O_APPEND | os.O_BINARY)
56 56 try:
57 57 os.dup2(fd, procutil.stdout.fileno())
58 58 os.dup2(fd, procutil.stderr.fileno())
59 59 finally:
60 60 os.close(fd)
61 61
62 62 def writepid(pid):
63 63 if opts['pid_file']:
64 64 if appendpid:
65 65 mode = 'ab'
66 66 else:
67 67 mode = 'wb'
68 68 fp = open(opts['pid_file'], mode)
69 69 fp.write('%d\n' % pid)
70 70 fp.close()
71 71
72 72 if opts['daemon'] and not opts['daemon_postexec']:
73 73 # Signal child process startup with file removal
74 74 lockfd, lockpath = pycompat.mkstemp(prefix='hg-service-')
75 75 os.close(lockfd)
76 76 try:
77 77 if not runargs:
78 78 runargs = procutil.hgcmd() + pycompat.sysargv[1:]
79 79 runargs.append('--daemon-postexec=unlink:%s' % lockpath)
80 80 # Don't pass --cwd to the child process, because we've already
81 81 # changed directory.
82 82 for i in pycompat.xrange(1, len(runargs)):
83 83 if runargs[i].startswith('--cwd='):
84 84 del runargs[i]
85 85 break
86 86 elif runargs[i].startswith('--cwd'):
87 87 del runargs[i:i + 2]
88 88 break
89 89 def condfn():
90 90 return not os.path.exists(lockpath)
91 91 pid = procutil.rundetached(runargs, condfn)
92 92 if pid < 0:
93 93 # If the daemonized process managed to write out an error msg,
94 94 # report it.
95 95 if pycompat.iswindows and os.path.exists(lockpath):
96 96 with open(lockpath, 'rb') as log:
97 97 for line in log:
98 98 procutil.stderr.write(line)
99 99 raise error.Abort(_('child process failed to start'))
100 100 writepid(pid)
101 101 finally:
102 102 util.tryunlink(lockpath)
103 103 if parentfn:
104 104 return parentfn(pid)
105 105 else:
106 106 return
107 107
108 108 if initfn:
109 109 initfn()
110 110
111 111 if not opts['daemon']:
112 112 writepid(procutil.getpid())
113 113
114 114 if opts['daemon_postexec']:
115 115 try:
116 116 os.setsid()
117 117 except AttributeError:
118 118 pass
119 119
120 120 if 'chdir' in postexecargs:
121 121 os.chdir(postexecargs['chdir'])
122 122 procutil.hidewindow()
123 123 procutil.stdout.flush()
124 124 procutil.stderr.flush()
125 125
126 126 nullfd = os.open(os.devnull, os.O_RDWR)
127 127 logfilefd = nullfd
128 128 if logfile:
129 129 logfilefd = os.open(logfile, os.O_RDWR | os.O_CREAT | os.O_APPEND,
130 130 0o666)
131 131 os.dup2(nullfd, procutil.stdin.fileno())
132 132 os.dup2(logfilefd, procutil.stdout.fileno())
133 133 os.dup2(logfilefd, procutil.stderr.fileno())
134 134 stdio = (procutil.stdin.fileno(), procutil.stdout.fileno(),
135 135 procutil.stderr.fileno())
136 136 if nullfd not in stdio:
137 137 os.close(nullfd)
138 138 if logfile and logfilefd not in stdio:
139 139 os.close(logfilefd)
140 140
141 141 # Only unlink after redirecting stdout/stderr, so Windows doesn't
142 142 # complain about a sharing violation.
143 143 if 'unlink' in postexecargs:
144 144 os.unlink(postexecargs['unlink'])
145 145
146 146 if runfn:
147 147 return runfn()
148 148
149 149 _cmdservicemap = {
150 150 'chgunix': chgserver.chgunixservice,
151 151 'pipe': commandserver.pipeservice,
152 152 'unix': commandserver.unixforkingservice,
153 153 }
154 154
155 155 def _createcmdservice(ui, repo, opts):
156 156 mode = opts['cmdserver']
157 157 try:
158 return _cmdservicemap[mode](ui, repo, opts)
158 servicefn = _cmdservicemap[mode]
159 159 except KeyError:
160 160 raise error.Abort(_('unknown mode %s') % mode)
161 commandserver.setuplogging(ui)
162 return servicefn(ui, repo, opts)
161 163
162 164 def _createhgwebservice(ui, repo, opts):
163 165 # this way we can check if something was given in the command-line
164 166 if opts.get('port'):
165 167 opts['port'] = util.getport(opts.get('port'))
166 168
167 169 alluis = {ui}
168 170 if repo:
169 171 baseui = repo.baseui
170 172 alluis.update([repo.baseui, repo.ui])
171 173 else:
172 174 baseui = ui
173 175 webconf = opts.get('web_conf') or opts.get('webdir_conf')
174 176 if webconf:
175 177 if opts.get('subrepos'):
176 178 raise error.Abort(_('--web-conf cannot be used with --subrepos'))
177 179
178 180 # load server settings (e.g. web.port) to "copied" ui, which allows
179 181 # hgwebdir to reload webconf cleanly
180 182 servui = ui.copy()
181 183 servui.readconfig(webconf, sections=['web'])
182 184 alluis.add(servui)
183 185 elif opts.get('subrepos'):
184 186 servui = ui
185 187
186 188 # If repo is None, hgweb.createapp() already raises a proper abort
187 189 # message as long as webconf is None.
188 190 if repo:
189 191 webconf = dict()
190 192 cmdutil.addwebdirpath(repo, "", webconf)
191 193 else:
192 194 servui = ui
193 195
194 196 optlist = ("name templates style address port prefix ipv6"
195 197 " accesslog errorlog certificate encoding")
196 198 for o in optlist.split():
197 199 val = opts.get(o, '')
198 200 if val in (None, ''): # should check against default options instead
199 201 continue
200 202 for u in alluis:
201 203 u.setconfig("web", o, val, 'serve')
202 204
203 205 app = hgweb.createapp(baseui, repo, webconf)
204 206 return hgweb.httpservice(servui, app, opts)
205 207
206 208 def createservice(ui, repo, opts):
207 209 if opts["cmdserver"]:
208 210 return _createcmdservice(ui, repo, opts)
209 211 else:
210 212 return _createhgwebservice(ui, repo, opts)
General Comments 0
You need to be logged in to leave comments. Login now