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.typing import StrPath
19 from nominatim.version import version_str
20 from nominatim.db.connection import get_pg_env
22 LOG = logging.getLogger()
24 def run_legacy_script(script: StrPath, *args: Union[int, str],
26 throw_on_fail: bool = False) -> int:
27 """ Run a Nominatim PHP script with the given arguments.
29 Returns the exit code of the script. If `throw_on_fail` is True
30 then throw a `CalledProcessError` on a non-zero exit.
32 cmd = ['/usr/bin/env', 'php', '-Cq',
33 str(nominatim_env.phplib_dir / 'admin' / script)]
34 cmd.extend([str(a) for a in args])
36 env = nominatim_env.config.get_os_env()
37 env['NOMINATIM_DATADIR'] = str(nominatim_env.data_dir)
38 env['NOMINATIM_SQLDIR'] = str(nominatim_env.sqllib_dir)
39 env['NOMINATIM_CONFIGDIR'] = str(nominatim_env.config_dir)
40 env['NOMINATIM_DATABASE_MODULE_SRC_PATH'] = str(nominatim_env.module_dir)
41 if not env['NOMINATIM_OSM2PGSQL_BINARY']:
42 env['NOMINATIM_OSM2PGSQL_BINARY'] = str(nominatim_env.osm2pgsql_path)
44 proc = subprocess.run(cmd, cwd=str(nominatim_env.project_dir), env=env,
47 return proc.returncode
49 def run_api_script(endpoint: str, project_dir: Path,
50 extra_env: Optional[Mapping[str, str]] = None,
51 phpcgi_bin: Optional[Path] = None,
52 params: Optional[Mapping[str, Any]] = None) -> int:
53 """ Execute a Nominatim API function.
55 The function needs a project directory that contains the website
56 directory with the scripts to be executed. The scripts will be run
57 using php_cgi. Query parameters can be added as named arguments.
59 Returns the exit code of the script.
61 log = logging.getLogger()
62 webdir = str(project_dir / 'website')
63 query_string = urlencode(params or {})
65 env = dict(QUERY_STRING=query_string,
66 SCRIPT_NAME=f'/{endpoint}.php',
67 REQUEST_URI=f'/{endpoint}.php?{query_string}',
68 CONTEXT_DOCUMENT_ROOT=webdir,
69 SCRIPT_FILENAME=f'{webdir}/{endpoint}.php',
70 HTTP_HOST='localhost',
71 HTTP_USER_AGENT='nominatim-tool',
72 REMOTE_ADDR='0.0.0.0',
75 SERVER_PROTOCOL='HTTP/1.1',
76 GATEWAY_INTERFACE='CGI/1.1',
77 REDIRECT_STATUS='CGI')
82 if phpcgi_bin is None:
83 cmd = ['/usr/bin/env', 'php-cgi']
85 cmd = [str(phpcgi_bin)]
87 proc = subprocess.run(cmd, cwd=str(project_dir), env=env,
88 stdout=subprocess.PIPE,
89 stderr=subprocess.PIPE,
92 if proc.returncode != 0 or proc.stderr:
94 log.error(proc.stderr.decode('utf-8').replace('\\n', '\n'))
96 log.error(proc.stdout.decode('utf-8').replace('\\n', '\n'))
97 return proc.returncode or 1
99 result = proc.stdout.decode('utf-8')
100 content_start = result.find('\r\n\r\n')
102 print(result[content_start + 4:].replace('\\n', '\n'))
107 def run_php_server(server_address: str, base_dir: StrPath) -> None:
108 """ Run the built-in server from the given directory.
110 subprocess.run(['/usr/bin/env', 'php', '-S', server_address],
111 cwd=str(base_dir), check=True)
114 def run_osm2pgsql(options: Mapping[str, Any]) -> None:
115 """ Run osm2pgsql with the given options.
117 env = get_pg_env(options['dsn'])
118 cmd = [str(options['osm2pgsql']),
119 '--hstore', '--latlon', '--slim',
120 '--log-progress', 'true',
121 '--number-processes', str(options['threads']),
122 '--cache', str(options['osm2pgsql_cache']),
123 '--style', str(options['osm2pgsql_style'])
126 if str(options['osm2pgsql_style']).endswith('.lua'):
127 env['LUA_PATH'] = ';'.join((str(options['osm2pgsql_style_path'] / 'flex-base.lua'),
128 os.environ.get('LUAPATH', ';')))
129 cmd.extend(('--output', 'flex'))
131 cmd.extend(('--output', 'gazetteer'))
133 cmd.append('--append' if options['append'] else '--create')
135 if options['flatnode_file']:
136 cmd.extend(('--flat-nodes', options['flatnode_file']))
138 if not options.get('forward_dependencies', False):
139 cmd.extend(('--with-forward-dependencies', 'false'))
141 for key, param in (('slim_data', '--tablespace-slim-data'),
142 ('slim_index', '--tablespace-slim-index'),
143 ('main_data', '--tablespace-main-data'),
144 ('main_index', '--tablespace-main-index')):
145 if options['tablespaces'][key]:
146 cmd.extend((param, options['tablespaces'][key]))
148 if options.get('disable_jit', False):
149 env['PGOPTIONS'] = '-c jit=off -c max_parallel_workers_per_gather=0'
151 if 'import_data' in options:
152 cmd.extend(('-r', 'xml', '-'))
153 elif isinstance(options['import_file'], list):
154 for fname in options['import_file']:
155 cmd.append(str(fname))
157 cmd.append(str(options['import_file']))
159 subprocess.run(cmd, cwd=options.get('cwd', '.'),
160 input=options.get('import_data'),
164 def get_url(url: str) -> str:
165 """ Get the contents from the given URL and return it as a UTF-8 string.
167 headers = {"User-Agent": f"Nominatim/{version_str()}"}
170 request = urlrequest.Request(url, headers=headers)
171 with urlrequest.urlopen(request) as response: # type: IO[bytes]
172 return response.read().decode('utf-8')
174 LOG.fatal('Failed to load URL: %s', url)