server.py
209 lines
| 6.6 KiB
| text/x-python
|
PythonLexer
/ mercurial / server.py
Yuya Nishihara
|
r30506 | # server.py - utility and factory of server | ||
# | ||||
# Copyright 2005-2007 Matt Mackall <mpm@selenic.com> | ||||
# | ||||
# This software may be used and distributed according to the terms of the | ||||
# GNU General Public License version 2 or any later version. | ||||
from __future__ import absolute_import | ||||
import os | ||||
import tempfile | ||||
from .i18n import _ | ||||
from . import ( | ||||
Yuya Nishihara
|
r30513 | chgserver, | ||
Matt Harbison
|
r32005 | cmdutil, | ||
Yuya Nishihara
|
r30507 | commandserver, | ||
Yuya Nishihara
|
r30506 | error, | ||
Yuya Nishihara
|
r30509 | hgweb, | ||
Augie Fackler
|
r32530 | pycompat, | ||
Yuya Nishihara
|
r30506 | util, | ||
) | ||||
Yuya Nishihara
|
r37137 | from .utils import ( | ||
procutil, | ||||
) | ||||
Yuya Nishihara
|
r30506 | def runservice(opts, parentfn=None, initfn=None, runfn=None, logfile=None, | ||
runargs=None, appendpid=False): | ||||
'''Run a command as a service.''' | ||||
Matt Harbison
|
r37232 | postexecargs = {} | ||
if opts['daemon_postexec']: | ||||
for inst in opts['daemon_postexec']: | ||||
if inst.startswith('unlink:'): | ||||
postexecargs['unlink'] = inst[7:] | ||||
elif inst.startswith('chdir:'): | ||||
postexecargs['chdir'] = inst[6:] | ||||
elif inst != 'none': | ||||
raise error.Abort(_('invalid value for --daemon-postexec: %s') | ||||
% inst) | ||||
Matt Harbison
|
r37229 | # When daemonized on Windows, redirect stdout/stderr to the lockfile (which | ||
# gets cleaned up after the child is up and running), so that the parent can | ||||
# read and print the error if this child dies early. See 594dd384803c. On | ||||
# other platforms, the child can write to the parent's stdio directly, until | ||||
# it is redirected prior to runfn(). | ||||
if pycompat.iswindows and opts['daemon_postexec']: | ||||
Matt Harbison
|
r37232 | if 'unlink' in postexecargs and os.path.exists(postexecargs['unlink']): | ||
procutil.stdout.flush() | ||||
procutil.stderr.flush() | ||||
Matt Harbison
|
r37229 | |||
Matt Harbison
|
r37232 | fd = os.open(postexecargs['unlink'], | ||
os.O_WRONLY | os.O_APPEND | os.O_BINARY) | ||||
try: | ||||
os.dup2(fd, 1) | ||||
os.dup2(fd, 2) | ||||
finally: | ||||
os.close(fd) | ||||
Matt Harbison
|
r37229 | |||
Yuya Nishihara
|
r30506 | def writepid(pid): | ||
if opts['pid_file']: | ||||
if appendpid: | ||||
Augie Fackler
|
r32548 | mode = 'ab' | ||
Yuya Nishihara
|
r30506 | else: | ||
Augie Fackler
|
r32548 | mode = 'wb' | ||
Yuya Nishihara
|
r30506 | fp = open(opts['pid_file'], mode) | ||
Yuya Nishihara
|
r32617 | fp.write('%d\n' % pid) | ||
Yuya Nishihara
|
r30506 | fp.close() | ||
if opts['daemon'] and not opts['daemon_postexec']: | ||||
# Signal child process startup with file removal | ||||
lockfd, lockpath = tempfile.mkstemp(prefix='hg-service-') | ||||
os.close(lockfd) | ||||
try: | ||||
if not runargs: | ||||
Yuya Nishihara
|
r37138 | runargs = procutil.hgcmd() + pycompat.sysargv[1:] | ||
Yuya Nishihara
|
r30506 | runargs.append('--daemon-postexec=unlink:%s' % lockpath) | ||
# Don't pass --cwd to the child process, because we've already | ||||
# changed directory. | ||||
for i in xrange(1, len(runargs)): | ||||
if runargs[i].startswith('--cwd='): | ||||
del runargs[i] | ||||
break | ||||
elif runargs[i].startswith('--cwd'): | ||||
del runargs[i:i + 2] | ||||
break | ||||
def condfn(): | ||||
return not os.path.exists(lockpath) | ||||
Yuya Nishihara
|
r37138 | pid = procutil.rundetached(runargs, condfn) | ||
Yuya Nishihara
|
r30506 | if pid < 0: | ||
Matt Harbison
|
r37229 | # If the daemonized process managed to write out an error msg, | ||
# report it. | ||||
if pycompat.iswindows and os.path.exists(lockpath): | ||||
with open(lockpath) as log: | ||||
for line in log: | ||||
procutil.stderr.write(line) | ||||
Yuya Nishihara
|
r30506 | raise error.Abort(_('child process failed to start')) | ||
writepid(pid) | ||||
finally: | ||||
Ryan McElroy
|
r31548 | util.tryunlink(lockpath) | ||
Yuya Nishihara
|
r30506 | if parentfn: | ||
return parentfn(pid) | ||||
else: | ||||
return | ||||
if initfn: | ||||
initfn() | ||||
if not opts['daemon']: | ||||
Yuya Nishihara
|
r37138 | writepid(procutil.getpid()) | ||
Yuya Nishihara
|
r30506 | |||
if opts['daemon_postexec']: | ||||
try: | ||||
os.setsid() | ||||
except AttributeError: | ||||
pass | ||||
Matt Harbison
|
r37229 | |||
Matt Harbison
|
r37232 | if 'chdir' in postexecargs: | ||
os.chdir(postexecargs['chdir']) | ||||
Yuya Nishihara
|
r37138 | procutil.hidewindow() | ||
Yuya Nishihara
|
r37137 | procutil.stdout.flush() | ||
procutil.stderr.flush() | ||||
Yuya Nishihara
|
r30506 | |||
nullfd = os.open(os.devnull, os.O_RDWR) | ||||
logfilefd = nullfd | ||||
if logfile: | ||||
Yuya Nishihara
|
r34925 | logfilefd = os.open(logfile, os.O_RDWR | os.O_CREAT | os.O_APPEND, | ||
0o666) | ||||
Yuya Nishihara
|
r30506 | os.dup2(nullfd, 0) | ||
os.dup2(logfilefd, 1) | ||||
os.dup2(logfilefd, 2) | ||||
if nullfd not in (0, 1, 2): | ||||
os.close(nullfd) | ||||
if logfile and logfilefd not in (0, 1, 2): | ||||
os.close(logfilefd) | ||||
Matt Harbison
|
r37229 | # Only unlink after redirecting stdout/stderr, so Windows doesn't | ||
# complain about a sharing violation. | ||||
Matt Harbison
|
r37232 | if 'unlink' in postexecargs: | ||
os.unlink(postexecargs['unlink']) | ||||
Matt Harbison
|
r37229 | |||
Yuya Nishihara
|
r30506 | if runfn: | ||
return runfn() | ||||
Yuya Nishihara
|
r30507 | |||
_cmdservicemap = { | ||||
Yuya Nishihara
|
r30513 | 'chgunix': chgserver.chgunixservice, | ||
Yuya Nishihara
|
r30507 | 'pipe': commandserver.pipeservice, | ||
'unix': commandserver.unixforkingservice, | ||||
} | ||||
Yuya Nishihara
|
r30510 | def _createcmdservice(ui, repo, opts): | ||
Yuya Nishihara
|
r30507 | mode = opts['cmdserver'] | ||
try: | ||||
return _cmdservicemap[mode](ui, repo, opts) | ||||
except KeyError: | ||||
raise error.Abort(_('unknown mode %s') % mode) | ||||
Yuya Nishihara
|
r30509 | |||
Yuya Nishihara
|
r30510 | def _createhgwebservice(ui, repo, opts): | ||
Yuya Nishihara
|
r30509 | # this way we can check if something was given in the command-line | ||
if opts.get('port'): | ||||
opts['port'] = util.getport(opts.get('port')) | ||||
Martin von Zweigbergk
|
r32291 | alluis = {ui} | ||
Yuya Nishihara
|
r30509 | if repo: | ||
baseui = repo.baseui | ||||
alluis.update([repo.baseui, repo.ui]) | ||||
else: | ||||
baseui = ui | ||||
webconf = opts.get('web_conf') or opts.get('webdir_conf') | ||||
if webconf: | ||||
Matt Harbison
|
r32005 | if opts.get('subrepos'): | ||
raise error.Abort(_('--web-conf cannot be used with --subrepos')) | ||||
Yuya Nishihara
|
r30509 | # load server settings (e.g. web.port) to "copied" ui, which allows | ||
# hgwebdir to reload webconf cleanly | ||||
servui = ui.copy() | ||||
servui.readconfig(webconf, sections=['web']) | ||||
alluis.add(servui) | ||||
Matt Harbison
|
r32005 | elif opts.get('subrepos'): | ||
servui = ui | ||||
# If repo is None, hgweb.createapp() already raises a proper abort | ||||
# message as long as webconf is None. | ||||
if repo: | ||||
webconf = dict() | ||||
cmdutil.addwebdirpath(repo, "", webconf) | ||||
Yuya Nishihara
|
r30509 | else: | ||
servui = ui | ||||
optlist = ("name templates style address port prefix ipv6" | ||||
" accesslog errorlog certificate encoding") | ||||
for o in optlist.split(): | ||||
val = opts.get(o, '') | ||||
if val in (None, ''): # should check against default options instead | ||||
continue | ||||
for u in alluis: | ||||
u.setconfig("web", o, val, 'serve') | ||||
app = hgweb.createapp(baseui, repo, webconf) | ||||
return hgweb.httpservice(servui, app, opts) | ||||
Yuya Nishihara
|
r30510 | |||
def createservice(ui, repo, opts): | ||||
if opts["cmdserver"]: | ||||
return _createcmdservice(ui, repo, opts) | ||||
else: | ||||
return _createhgwebservice(ui, repo, opts) | ||||