server.py
237 lines
| 7.0 KiB
| text/x-python
|
PythonLexer
/ mercurial / server.py
Yuya Nishihara
|
r30506 | # server.py - utility and factory of server | ||
# | ||||
Raphaël Gomès
|
r47575 | # Copyright 2005-2007 Olivia Mackall <olivia@selenic.com> | ||
Yuya Nishihara
|
r30506 | # | ||
# This software may be used and distributed according to the terms of the | ||||
# GNU General Public License version 2 or any later version. | ||||
Matt Harbison
|
r52756 | from __future__ import annotations | ||
Yuya Nishihara
|
r30506 | |||
import os | ||||
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, | ||
) | ||||
r47669 | from .utils import ( | |||
procutil, | ||||
urlutil, | ||||
) | ||||
Augie Fackler
|
r43346 | |||
Yuya Nishihara
|
r37137 | |||
Augie Fackler
|
r43346 | def runservice( | ||
opts, | ||||
parentfn=None, | ||||
initfn=None, | ||||
runfn=None, | ||||
logfile=None, | ||||
runargs=None, | ||||
appendpid=False, | ||||
): | ||||
Yuya Nishihara
|
r30506 | '''Run a command as a service.''' | ||
Matt Harbison
|
r37232 | postexecargs = {} | ||
Augie Fackler
|
r43347 | if opts[b'daemon_postexec']: | ||
for inst in opts[b'daemon_postexec']: | ||||
if inst.startswith(b'unlink:'): | ||||
postexecargs[b'unlink'] = inst[7:] | ||||
elif inst.startswith(b'chdir:'): | ||||
postexecargs[b'chdir'] = inst[6:] | ||||
elif inst != b'none': | ||||
Augie Fackler
|
r43346 | raise error.Abort( | ||
Augie Fackler
|
r43347 | _(b'invalid value for --daemon-postexec: %s') % inst | ||
Augie Fackler
|
r43346 | ) | ||
Matt Harbison
|
r37232 | |||
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(). | ||||
Augie Fackler
|
r43347 | if pycompat.iswindows and opts[b'daemon_postexec']: | ||
if b'unlink' in postexecargs and os.path.exists( | ||||
postexecargs[b'unlink'] | ||||
): | ||||
Matt Harbison
|
r37232 | procutil.stdout.flush() | ||
procutil.stderr.flush() | ||||
Matt Harbison
|
r37229 | |||
Augie Fackler
|
r43346 | fd = os.open( | ||
Augie Fackler
|
r43347 | postexecargs[b'unlink'], os.O_WRONLY | os.O_APPEND | os.O_BINARY | ||
Augie Fackler
|
r43346 | ) | ||
Matt Harbison
|
r37232 | try: | ||
Matt Harbison
|
r37233 | os.dup2(fd, procutil.stdout.fileno()) | ||
os.dup2(fd, procutil.stderr.fileno()) | ||||
Matt Harbison
|
r37232 | finally: | ||
os.close(fd) | ||||
Matt Harbison
|
r37229 | |||
Yuya Nishihara
|
r30506 | def writepid(pid): | ||
Augie Fackler
|
r43347 | if opts[b'pid_file']: | ||
Yuya Nishihara
|
r30506 | if appendpid: | ||
Matt Harbison
|
r53275 | mode = 'ab' | ||
Yuya Nishihara
|
r30506 | else: | ||
Matt Harbison
|
r53275 | mode = 'wb' | ||
Augie Fackler
|
r43347 | fp = open(opts[b'pid_file'], mode) | ||
fp.write(b'%d\n' % pid) | ||||
Yuya Nishihara
|
r30506 | fp.close() | ||
Augie Fackler
|
r43347 | if opts[b'daemon'] and not opts[b'daemon_postexec']: | ||
Yuya Nishihara
|
r30506 | # Signal child process startup with file removal | ||
Augie Fackler
|
r43347 | lockfd, lockpath = pycompat.mkstemp(prefix=b'hg-service-') | ||
Yuya Nishihara
|
r30506 | os.close(lockfd) | ||
try: | ||||
if not runargs: | ||||
Yuya Nishihara
|
r37138 | runargs = procutil.hgcmd() + pycompat.sysargv[1:] | ||
Augie Fackler
|
r43347 | runargs.append(b'--daemon-postexec=unlink:%s' % lockpath) | ||
Yuya Nishihara
|
r30506 | # Don't pass --cwd to the child process, because we've already | ||
# changed directory. | ||||
Manuel Jacob
|
r50179 | for i in range(1, len(runargs)): | ||
Augie Fackler
|
r43347 | if runargs[i].startswith(b'--cwd='): | ||
Yuya Nishihara
|
r30506 | del runargs[i] | ||
break | ||||
Augie Fackler
|
r43347 | elif runargs[i].startswith(b'--cwd'): | ||
Augie Fackler
|
r43346 | del runargs[i : i + 2] | ||
Yuya Nishihara
|
r30506 | break | ||
Augie Fackler
|
r43346 | |||
Yuya Nishihara
|
r30506 | def condfn(): | ||
return not os.path.exists(lockpath) | ||||
Augie Fackler
|
r43346 | |||
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): | ||||
Matt Harbison
|
r53275 | with open(lockpath, 'rb') as log: | ||
Matt Harbison
|
r37229 | for line in log: | ||
procutil.stderr.write(line) | ||||
Augie Fackler
|
r43347 | raise error.Abort(_(b'child process failed to start')) | ||
Yuya Nishihara
|
r30506 | writepid(pid) | ||
finally: | ||||
Ryan McElroy
|
r31548 | util.tryunlink(lockpath) | ||
Yuya Nishihara
|
r30506 | if parentfn: | ||
return parentfn(pid) | ||||
else: | ||||
return | ||||
if initfn: | ||||
initfn() | ||||
Augie Fackler
|
r43347 | if not opts[b'daemon']: | ||
Yuya Nishihara
|
r37138 | writepid(procutil.getpid()) | ||
Yuya Nishihara
|
r30506 | |||
Augie Fackler
|
r43347 | if opts[b'daemon_postexec']: | ||
Yuya Nishihara
|
r30506 | try: | ||
os.setsid() | ||||
except AttributeError: | ||||
pass | ||||
Matt Harbison
|
r37229 | |||
Augie Fackler
|
r43347 | if b'chdir' in postexecargs: | ||
os.chdir(postexecargs[b'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: | ||||
Augie Fackler
|
r43346 | logfilefd = os.open( | ||
logfile, os.O_RDWR | os.O_CREAT | os.O_APPEND, 0o666 | ||||
) | ||||
Matt Harbison
|
r37233 | os.dup2(nullfd, procutil.stdin.fileno()) | ||
os.dup2(logfilefd, procutil.stdout.fileno()) | ||||
os.dup2(logfilefd, procutil.stderr.fileno()) | ||||
Augie Fackler
|
r43346 | stdio = ( | ||
procutil.stdin.fileno(), | ||||
procutil.stdout.fileno(), | ||||
procutil.stderr.fileno(), | ||||
) | ||||
Matt Harbison
|
r37233 | if nullfd not in stdio: | ||
Yuya Nishihara
|
r30506 | os.close(nullfd) | ||
Matt Harbison
|
r37233 | if logfile and logfilefd not in stdio: | ||
Yuya Nishihara
|
r30506 | os.close(logfilefd) | ||
Matt Harbison
|
r37229 | # Only unlink after redirecting stdout/stderr, so Windows doesn't | ||
# complain about a sharing violation. | ||||
Augie Fackler
|
r43347 | if b'unlink' in postexecargs: | ||
os.unlink(postexecargs[b'unlink']) | ||||
Matt Harbison
|
r37229 | |||
Yuya Nishihara
|
r30506 | if runfn: | ||
return runfn() | ||||
Yuya Nishihara
|
r30507 | |||
Augie Fackler
|
r43346 | |||
Yuya Nishihara
|
r30507 | _cmdservicemap = { | ||
Augie Fackler
|
r43347 | b'chgunix': chgserver.chgunixservice, | ||
b'pipe': commandserver.pipeservice, | ||||
b'unix': commandserver.unixforkingservice, | ||||
Yuya Nishihara
|
r30507 | } | ||
Augie Fackler
|
r43346 | |||
Yuya Nishihara
|
r30510 | def _createcmdservice(ui, repo, opts): | ||
Augie Fackler
|
r43347 | mode = opts[b'cmdserver'] | ||
Yuya Nishihara
|
r30507 | try: | ||
Yuya Nishihara
|
r40858 | servicefn = _cmdservicemap[mode] | ||
Yuya Nishihara
|
r30507 | except KeyError: | ||
Augie Fackler
|
r43347 | raise error.Abort(_(b'unknown mode %s') % mode) | ||
Yuya Nishihara
|
r40859 | commandserver.setuplogging(ui, repo) | ||
Yuya Nishihara
|
r40858 | return servicefn(ui, repo, opts) | ||
Yuya Nishihara
|
r30509 | |||
Augie Fackler
|
r43346 | |||
Yuya Nishihara
|
r30510 | def _createhgwebservice(ui, repo, opts): | ||
Yuya Nishihara
|
r30509 | # this way we can check if something was given in the command-line | ||
Augie Fackler
|
r43347 | if opts.get(b'port'): | ||
r47669 | opts[b'port'] = urlutil.getport(opts.get(b'port')) | |||
Yuya Nishihara
|
r30509 | |||
Martin von Zweigbergk
|
r32291 | alluis = {ui} | ||
Yuya Nishihara
|
r30509 | if repo: | ||
baseui = repo.baseui | ||||
alluis.update([repo.baseui, repo.ui]) | ||||
else: | ||||
baseui = ui | ||||
Augie Fackler
|
r43347 | webconf = opts.get(b'web_conf') or opts.get(b'webdir_conf') | ||
Yuya Nishihara
|
r30509 | if webconf: | ||
Augie Fackler
|
r43347 | if opts.get(b'subrepos'): | ||
raise error.Abort(_(b'--web-conf cannot be used with --subrepos')) | ||||
Matt Harbison
|
r32005 | |||
Yuya Nishihara
|
r30509 | # load server settings (e.g. web.port) to "copied" ui, which allows | ||
# hgwebdir to reload webconf cleanly | ||||
servui = ui.copy() | ||||
Augie Fackler
|
r43347 | servui.readconfig(webconf, sections=[b'web']) | ||
Yuya Nishihara
|
r30509 | alluis.add(servui) | ||
Augie Fackler
|
r43347 | elif opts.get(b'subrepos'): | ||
Matt Harbison
|
r32005 | servui = ui | ||
# If repo is None, hgweb.createapp() already raises a proper abort | ||||
# message as long as webconf is None. | ||||
if repo: | ||||
webconf = dict() | ||||
Augie Fackler
|
r43347 | cmdutil.addwebdirpath(repo, b"", webconf) | ||
Yuya Nishihara
|
r30509 | else: | ||
servui = ui | ||||
Augie Fackler
|
r43346 | optlist = ( | ||
Augie Fackler
|
r43347 | b"name templates style address port prefix ipv6" | ||
b" accesslog errorlog certificate encoding" | ||||
Augie Fackler
|
r43346 | ) | ||
Yuya Nishihara
|
r30509 | for o in optlist.split(): | ||
Augie Fackler
|
r43347 | val = opts.get(o, b'') | ||
if val in (None, b''): # should check against default options instead | ||||
Yuya Nishihara
|
r30509 | continue | ||
for u in alluis: | ||||
Augie Fackler
|
r43347 | u.setconfig(b"web", o, val, b'serve') | ||
Yuya Nishihara
|
r30509 | |||
app = hgweb.createapp(baseui, repo, webconf) | ||||
return hgweb.httpservice(servui, app, opts) | ||||
Yuya Nishihara
|
r30510 | |||
Augie Fackler
|
r43346 | |||
Yuya Nishihara
|
r30510 | def createservice(ui, repo, opts): | ||
Augie Fackler
|
r43347 | if opts[b"cmdserver"]: | ||
Yuya Nishihara
|
r30510 | return _createcmdservice(ui, repo, opts) | ||
else: | ||||
return _createhgwebservice(ui, repo, opts) | ||||