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, 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 NOMINATIM_VERSION
20 from nominatim.db.connection import get_pg_env
22 LOG = logging.getLogger()
24 def run_api_script(endpoint: str, project_dir: Path,
25 extra_env: Optional[Mapping[str, str]] = None,
26 phpcgi_bin: Optional[Path] = None,
27 params: Optional[Mapping[str, Any]] = None) -> int:
28 """ Execute a Nominatim API function.
30 The function needs a project directory that contains the website
31 directory with the scripts to be executed. The scripts will be run
32 using php_cgi. Query parameters can be added as named arguments.
34 Returns the exit code of the script.
36 log = logging.getLogger()
37 webdir = str(project_dir / 'website')
38 query_string = urlencode(params or {})
40 env = dict(QUERY_STRING=query_string,
41 SCRIPT_NAME=f'/{endpoint}.php',
42 REQUEST_URI=f'/{endpoint}.php?{query_string}',
43 CONTEXT_DOCUMENT_ROOT=webdir,
44 SCRIPT_FILENAME=f'{webdir}/{endpoint}.php',
45 HTTP_HOST='localhost',
46 HTTP_USER_AGENT='nominatim-tool',
47 REMOTE_ADDR='0.0.0.0',
50 SERVER_PROTOCOL='HTTP/1.1',
51 GATEWAY_INTERFACE='CGI/1.1',
52 REDIRECT_STATUS='CGI')
57 if phpcgi_bin is None:
58 cmd = ['/usr/bin/env', 'php-cgi']
60 cmd = [str(phpcgi_bin)]
62 proc = subprocess.run(cmd, cwd=str(project_dir), env=env,
63 stdout=subprocess.PIPE,
64 stderr=subprocess.PIPE,
67 if proc.returncode != 0 or proc.stderr:
69 log.error(proc.stderr.decode('utf-8').replace('\\n', '\n'))
71 log.error(proc.stdout.decode('utf-8').replace('\\n', '\n'))
72 return proc.returncode or 1
74 result = proc.stdout.decode('utf-8')
75 content_start = result.find('\r\n\r\n')
77 print(result[content_start + 4:].replace('\\n', '\n'))
82 def run_php_server(server_address: str, base_dir: StrPath) -> None:
83 """ Run the built-in server from the given directory.
85 subprocess.run(['/usr/bin/env', 'php', '-S', server_address],
86 cwd=str(base_dir), check=True)
89 def run_osm2pgsql(options: Mapping[str, Any]) -> None:
90 """ Run osm2pgsql with the given options.
92 env = get_pg_env(options['dsn'])
93 cmd = [str(options['osm2pgsql']),
94 '--hstore', '--latlon', '--slim',
95 '--log-progress', 'true',
96 '--number-processes', '1' if options['append'] else str(options['threads']),
97 '--cache', str(options['osm2pgsql_cache']),
98 '--style', str(options['osm2pgsql_style'])
101 if str(options['osm2pgsql_style']).endswith('.lua'):
102 env['LUA_PATH'] = ';'.join((str(options['osm2pgsql_style_path'] / '?.lua'),
103 os.environ.get('LUAPATH', ';')))
104 cmd.extend(('--output', 'flex'))
106 cmd.extend(('--output', 'gazetteer'))
108 cmd.append('--append' if options['append'] else '--create')
110 if options['flatnode_file']:
111 cmd.extend(('--flat-nodes', options['flatnode_file']))
113 for key, param in (('slim_data', '--tablespace-slim-data'),
114 ('slim_index', '--tablespace-slim-index'),
115 ('main_data', '--tablespace-main-data'),
116 ('main_index', '--tablespace-main-index')):
117 if options['tablespaces'][key]:
118 cmd.extend((param, options['tablespaces'][key]))
120 if options.get('disable_jit', False):
121 env['PGOPTIONS'] = '-c jit=off -c max_parallel_workers_per_gather=0'
123 if 'import_data' in options:
124 cmd.extend(('-r', 'xml', '-'))
125 elif isinstance(options['import_file'], list):
126 for fname in options['import_file']:
127 cmd.append(str(fname))
129 cmd.append(str(options['import_file']))
131 subprocess.run(cmd, cwd=options.get('cwd', '.'),
132 input=options.get('import_data'),
136 def get_url(url: str) -> str:
137 """ Get the contents from the given URL and return it as a UTF-8 string.
139 headers = {"User-Agent": f"Nominatim/{NOMINATIM_VERSION!s}"}
142 request = urlrequest.Request(url, headers=headers)
143 with urlrequest.urlopen(request) as response: # type: IO[bytes]
144 return response.read().decode('utf-8')
146 LOG.fatal('Failed to load URL: %s', url)