1 # SPDX-License-Identifier: GPL-2.0-only
3 # This file is part of Nominatim. (https://nominatim.org)
5 # Copyright (C) 2022 by the Nominatim developer community.
6 # For a full list of authors see the git log.
8 Helper functions for executing external programs.
10 from typing import Any, Union, Optional, Mapping, IO
11 from pathlib import Path
15 import urllib.request as urlrequest
16 from urllib.parse import urlencode
18 from nominatim.config import Configuration
19 from nominatim.typing import StrPath
20 from nominatim.version import NOMINATIM_VERSION
21 from nominatim.db.connection import get_pg_env
23 LOG = logging.getLogger()
25 def run_legacy_script(script: StrPath, *args: Union[int, str],
26 config: Configuration,
27 throw_on_fail: bool = False) -> int:
28 """ Run a Nominatim PHP script with the given arguments.
30 Returns the exit code of the script. If `throw_on_fail` is True
31 then throw a `CalledProcessError` on a non-zero exit.
33 cmd = ['/usr/bin/env', 'php', '-Cq',
34 str(config.lib_dir.php / 'admin' / script)]
35 cmd.extend([str(a) for a in args])
37 env = config.get_os_env()
38 env['NOMINATIM_DATADIR'] = str(config.lib_dir.data)
39 env['NOMINATIM_SQLDIR'] = str(config.lib_dir.sql)
40 env['NOMINATIM_CONFIGDIR'] = str(config.config_dir)
41 env['NOMINATIM_DATABASE_MODULE_SRC_PATH'] = str(config.lib_dir.module)
42 if not env['NOMINATIM_OSM2PGSQL_BINARY']:
43 env['NOMINATIM_OSM2PGSQL_BINARY'] = str(config.lib_dir.osm2pgsql)
45 proc = subprocess.run(cmd, cwd=str(config.project_dir), env=env,
48 return proc.returncode
50 def run_api_script(endpoint: str, project_dir: Path,
51 extra_env: Optional[Mapping[str, str]] = None,
52 phpcgi_bin: Optional[Path] = None,
53 params: Optional[Mapping[str, Any]] = None) -> int:
54 """ Execute a Nominatim API function.
56 The function needs a project directory that contains the website
57 directory with the scripts to be executed. The scripts will be run
58 using php_cgi. Query parameters can be added as named arguments.
60 Returns the exit code of the script.
62 log = logging.getLogger()
63 webdir = str(project_dir / 'website')
64 query_string = urlencode(params or {})
66 env = dict(QUERY_STRING=query_string,
67 SCRIPT_NAME=f'/{endpoint}.php',
68 REQUEST_URI=f'/{endpoint}.php?{query_string}',
69 CONTEXT_DOCUMENT_ROOT=webdir,
70 SCRIPT_FILENAME=f'{webdir}/{endpoint}.php',
71 HTTP_HOST='localhost',
72 HTTP_USER_AGENT='nominatim-tool',
73 REMOTE_ADDR='0.0.0.0',
76 SERVER_PROTOCOL='HTTP/1.1',
77 GATEWAY_INTERFACE='CGI/1.1',
78 REDIRECT_STATUS='CGI')
83 if phpcgi_bin is None:
84 cmd = ['/usr/bin/env', 'php-cgi']
86 cmd = [str(phpcgi_bin)]
88 proc = subprocess.run(cmd, cwd=str(project_dir), env=env,
89 stdout=subprocess.PIPE,
90 stderr=subprocess.PIPE,
93 if proc.returncode != 0 or proc.stderr:
95 log.error(proc.stderr.decode('utf-8').replace('\\n', '\n'))
97 log.error(proc.stdout.decode('utf-8').replace('\\n', '\n'))
98 return proc.returncode or 1
100 result = proc.stdout.decode('utf-8')
101 content_start = result.find('\r\n\r\n')
103 print(result[content_start + 4:].replace('\\n', '\n'))
108 def run_php_server(server_address: str, base_dir: StrPath) -> None:
109 """ Run the built-in server from the given directory.
111 subprocess.run(['/usr/bin/env', 'php', '-S', server_address],
112 cwd=str(base_dir), check=True)
115 def run_osm2pgsql(options: Mapping[str, Any]) -> None:
116 """ Run osm2pgsql with the given options.
118 env = get_pg_env(options['dsn'])
119 cmd = [str(options['osm2pgsql']),
120 '--hstore', '--latlon', '--slim',
121 '--log-progress', 'true',
122 '--number-processes', '1' if options['append'] else str(options['threads']),
123 '--cache', str(options['osm2pgsql_cache']),
124 '--style', str(options['osm2pgsql_style'])
127 if str(options['osm2pgsql_style']).endswith('.lua'):
128 env['LUA_PATH'] = ';'.join((str(options['osm2pgsql_style_path'] / '?.lua'),
129 os.environ.get('LUAPATH', ';')))
130 cmd.extend(('--output', 'flex'))
132 cmd.extend(('--output', 'gazetteer'))
134 cmd.append('--append' if options['append'] else '--create')
136 if options['flatnode_file']:
137 cmd.extend(('--flat-nodes', options['flatnode_file']))
139 for key, param in (('slim_data', '--tablespace-slim-data'),
140 ('slim_index', '--tablespace-slim-index'),
141 ('main_data', '--tablespace-main-data'),
142 ('main_index', '--tablespace-main-index')):
143 if options['tablespaces'][key]:
144 cmd.extend((param, options['tablespaces'][key]))
146 if options.get('disable_jit', False):
147 env['PGOPTIONS'] = '-c jit=off -c max_parallel_workers_per_gather=0'
149 if 'import_data' in options:
150 cmd.extend(('-r', 'xml', '-'))
151 elif isinstance(options['import_file'], list):
152 for fname in options['import_file']:
153 cmd.append(str(fname))
155 cmd.append(str(options['import_file']))
157 subprocess.run(cmd, cwd=options.get('cwd', '.'),
158 input=options.get('import_data'),
162 def get_url(url: str) -> str:
163 """ Get the contents from the given URL and return it as a UTF-8 string.
165 headers = {"User-Agent": f"Nominatim/{NOMINATIM_VERSION!s}"}
168 request = urlrequest.Request(url, headers=headers)
169 with urlrequest.urlopen(request) as response: # type: IO[bytes]
170 return response.read().decode('utf-8')
172 LOG.fatal('Failed to load URL: %s', url)