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.
12 import urllib.request as urlrequest
13 from urllib.parse import urlencode
15 from nominatim.version import version_str
16 from nominatim.db.connection import get_pg_env
18 LOG = logging.getLogger()
20 def run_legacy_script(script, *args, nominatim_env=None, throw_on_fail=False):
21 """ Run a Nominatim PHP script with the given arguments.
23 Returns the exit code of the script. If `throw_on_fail` is True
24 then throw a `CalledProcessError` on a non-zero exit.
26 cmd = ['/usr/bin/env', 'php', '-Cq',
27 str(nominatim_env.phplib_dir / 'admin' / script)]
28 cmd.extend([str(a) for a in args])
30 env = nominatim_env.config.get_os_env()
31 env['NOMINATIM_DATADIR'] = str(nominatim_env.data_dir)
32 env['NOMINATIM_SQLDIR'] = str(nominatim_env.sqllib_dir)
33 env['NOMINATIM_CONFIGDIR'] = str(nominatim_env.config_dir)
34 env['NOMINATIM_DATABASE_MODULE_SRC_PATH'] = str(nominatim_env.module_dir)
35 if not env['NOMINATIM_OSM2PGSQL_BINARY']:
36 env['NOMINATIM_OSM2PGSQL_BINARY'] = str(nominatim_env.osm2pgsql_path)
38 proc = subprocess.run(cmd, cwd=str(nominatim_env.project_dir), env=env,
41 return proc.returncode
43 def run_api_script(endpoint, project_dir, extra_env=None, phpcgi_bin=None,
45 """ Execute a Nominatim API function.
47 The function needs a project directory that contains the website
48 directory with the scripts to be executed. The scripts will be run
49 using php_cgi. Query parameters can be added as named arguments.
51 Returns the exit code of the script.
53 log = logging.getLogger()
54 webdir = str(project_dir / 'website')
55 query_string = urlencode(params or {})
57 env = dict(QUERY_STRING=query_string,
58 SCRIPT_NAME=f'/{endpoint}.php',
59 REQUEST_URI=f'/{endpoint}.php?{query_string}',
60 CONTEXT_DOCUMENT_ROOT=webdir,
61 SCRIPT_FILENAME=f'{webdir}/{endpoint}.php',
62 HTTP_HOST='localhost',
63 HTTP_USER_AGENT='nominatim-tool',
64 REMOTE_ADDR='0.0.0.0',
67 SERVER_PROTOCOL='HTTP/1.1',
68 GATEWAY_INTERFACE='CGI/1.1',
69 REDIRECT_STATUS='CGI')
74 if phpcgi_bin is None:
75 cmd = ['/usr/bin/env', 'php-cgi']
77 cmd = [str(phpcgi_bin)]
79 proc = subprocess.run(cmd, cwd=str(project_dir), env=env,
80 stdout=subprocess.PIPE,
81 stderr=subprocess.PIPE,
84 if proc.returncode != 0 or proc.stderr:
86 log.error(proc.stderr.decode('utf-8').replace('\\n', '\n'))
88 log.error(proc.stdout.decode('utf-8').replace('\\n', '\n'))
89 return proc.returncode or 1
91 result = proc.stdout.decode('utf-8')
92 content_start = result.find('\r\n\r\n')
94 print(result[content_start + 4:].replace('\\n', '\n'))
99 def run_php_server(server_address, base_dir):
100 """ Run the built-in server from the given directory.
102 subprocess.run(['/usr/bin/env', 'php', '-S', server_address],
103 cwd=str(base_dir), check=True)
106 def run_osm2pgsql(options):
107 """ Run osm2pgsql with the given options.
109 env = get_pg_env(options['dsn'])
110 cmd = [str(options['osm2pgsql']),
111 '--hstore', '--latlon', '--slim',
112 '--with-forward-dependencies', 'false',
113 '--log-progress', 'true',
114 '--number-processes', str(options['threads']),
115 '--cache', str(options['osm2pgsql_cache']),
116 '--output', 'gazetteer',
117 '--style', str(options['osm2pgsql_style'])
119 if options['append']:
120 cmd.append('--append')
122 cmd.append('--create')
124 if options['flatnode_file']:
125 cmd.extend(('--flat-nodes', options['flatnode_file']))
127 for key, param in (('slim_data', '--tablespace-slim-data'),
128 ('slim_index', '--tablespace-slim-index'),
129 ('main_data', '--tablespace-main-data'),
130 ('main_index', '--tablespace-main-index')):
131 if options['tablespaces'][key]:
132 cmd.extend((param, options['tablespaces'][key]))
134 if options.get('disable_jit', False):
135 env['PGOPTIONS'] = '-c jit=off -c max_parallel_workers_per_gather=0'
137 if 'import_data' in options:
138 cmd.extend(('-r', 'xml', '-'))
139 elif isinstance(options['import_file'], list):
140 for fname in options['import_file']:
141 cmd.append(str(fname))
143 cmd.append(str(options['import_file']))
145 subprocess.run(cmd, cwd=options.get('cwd', '.'),
146 input=options.get('import_data'),
151 """ Get the contents from the given URL and return it as a UTF-8 string.
153 headers = {"User-Agent": f"Nominatim/{version_str()}"}
156 with urlrequest.urlopen(urlrequest.Request(url, headers=headers)) as response:
157 return response.read().decode('utf-8')
159 LOG.fatal('Failed to load URL: %s', url)