]> git.openstreetmap.org Git - nominatim.git/blobdiff - utils/setup.php
PR review changes
[nominatim.git] / utils / setup.php
index 624c49a381c145f867c62582c01071a866b45d54..b71e07e0b8c49628603f3234053d3b8953d12778 100755 (executable)
@@ -5,15 +5,18 @@ require_once(dirname(dirname(__FILE__)).'/settings/settings.php');
 require_once(CONST_BasePath.'/lib/init-cmd.php');
 ini_set('memory_limit', '800M');
 
+# (long-opt, short-opt, min-occurs, max-occurs, num-arguments, num-arguments, type, help)
+
 $aCMDOptions
 = array(
-   "Create and setup nominatim search system",
+   'Create and setup nominatim search system',
    array('help', 'h', 0, 1, 0, 0, false, 'Show Help'),
    array('quiet', 'q', 0, 1, 0, 0, 'bool', 'Quiet output'),
    array('verbose', 'v', 0, 1, 0, 0, 'bool', 'Verbose output'),
 
    array('osm-file', '', 0, 1, 1, 1, 'realpath', 'File to import'),
    array('threads', '', 0, 1, 1, 1, 'int', 'Number of threads (where possible)'),
+   array('module-path', '', 0, 1, 1, 1, 'string', 'Directory on Postgres server containing Nominatim module'),
 
    array('all', '', 0, 1, 0, 0, 'bool', 'Do the complete process'),
 
@@ -28,7 +31,7 @@ $aCMDOptions
    array('create-tables', '', 0, 1, 0, 0, 'bool', 'Create main tables'),
    array('create-partition-tables', '', 0, 1, 0, 0, 'bool', 'Create required partition tables'),
    array('create-partition-functions', '', 0, 1, 0, 0, 'bool', 'Create required partition triggers'),
-   array('no-partitions', '', 0, 1, 0, 0, 'bool', "Do not partition search indices (speeds up import of single country extracts)"),
+   array('no-partitions', '', 0, 1, 0, 0, 'bool', 'Do not partition search indices (speeds up import of single country extracts)'),
    array('import-wikipedia-articles', '', 0, 1, 0, 0, 'bool', 'Import wikipedia article dump'),
    array('load-data', '', 0, 1, 0, 0, 'bool', 'Copy data to live tables from import table'),
    array('disable-token-precalc', '', 0, 1, 0, 0, 'bool', 'Disable name precalculation (EXPERT)'),
@@ -60,17 +63,15 @@ if ($aCMDResult['import-data'] || $aCMDResult['all']) {
     }
 }
 
+// by default, use all but one processor, but never more than 15.
+$iInstances = isset($aCMDResult['threads'])
+              ? $aCMDResult['threads']
+              : (min(16, getProcessorCount()) - 1);
 
-// This is a pretty hard core default - the number of processors in the box - 1
-$iInstances = isset($aCMDResult['threads'])?$aCMDResult['threads']:(getProcessorCount()-1);
 if ($iInstances < 1) {
     $iInstances = 1;
     warn("resetting threads to $iInstances");
 }
-if ($iInstances > getProcessorCount()) {
-    $iInstances = getProcessorCount();
-    warn("resetting threads to $iInstances");
-}
 
 // Assume we can steal all the cache memory in the box (unless told otherwise)
 if (isset($aCMDResult['osm2pgsql-cache'])) {
@@ -79,21 +80,42 @@ if (isset($aCMDResult['osm2pgsql-cache'])) {
     $iCacheMemory = getCacheMemoryMB();
 }
 
+$sModulePath = CONST_InstallPath . '/module';
+if (isset($aCMDResult['module-path'])) {
+    $sModulePath = $aCMDResult['module-path'];
+    echo 'module path: ' . $sModulePath . '\n';
+}
+
 $aDSNInfo = DB::parseDSN(CONST_Database_DSN);
 if (!isset($aDSNInfo['port']) || !$aDSNInfo['port']) $aDSNInfo['port'] = 5432;
 
 if ($aCMDResult['create-db'] || $aCMDResult['all']) {
-    info("Create DB");
+    info('Create DB');
     $bDidSomething = true;
     $oDB = DB::connect(CONST_Database_DSN, false);
     if (!PEAR::isError($oDB)) {
         fail('database already exists ('.CONST_Database_DSN.')');
     }
-    passthruCheckReturn('createdb -E UTF-8 -p '.$aDSNInfo['port'].' '.$aDSNInfo['database']);
+
+    $sCreateDBCmd = 'createdb -E UTF-8 -p '.$aDSNInfo['port'].' '.$aDSNInfo['database'];
+    if (isset($aDSNInfo['username']) && $aDSNInfo['username']) {
+        $sCreateDBCmd .= ' -U ' . $aDSNInfo['username'];
+    }
+    if (isset($aDSNInfo['hostspec']) && $aDSNInfo['hostspec']) {
+        $sCreateDBCmd .= ' -h ' . $aDSNInfo['hostspec'];
+    }
+
+    $aProcEnv = null;
+    if (isset($aDSNInfo['password']) && $aDSNInfo['password']) {
+        $aProcEnv = array_merge(array('PGPASSWORD' => $aDSNInfo['password']), $_ENV);
+    }
+
+    $result = runWithEnv($sCreateDBCmd, $aProcEnv);
+    if ($result != 0) fail('Error executing external command: '.$sCreateDBCmd);
 }
 
 if ($aCMDResult['setup-db'] || $aCMDResult['all']) {
-    info("Setup DB");
+    info('Setup DB');
     $bDidSomething = true;
 
     $oDB =& getDB();
@@ -102,7 +124,7 @@ if ($aCMDResult['setup-db'] || $aCMDResult['all']) {
     echo 'Postgres version found: '.$fPostgresVersion."\n";
 
     if ($fPostgresVersion < 9.1) {
-        fail("Minimum supported version of Postgresql is 9.1.");
+        fail('Minimum supported version of Postgresql is 9.1.');
     }
 
     pgsqlRunScript('CREATE EXTENSION IF NOT EXISTS hstore');
@@ -137,22 +159,13 @@ if ($aCMDResult['setup-db'] || $aCMDResult['all']) {
         exit(1);
     }
 
-    // Try accessing the C module, so we know early if something is wrong
-    // and can simply error out.
-    $sSQL = "CREATE FUNCTION nominatim_test_import_func(text) RETURNS text AS '";
-    $sSQL .= CONST_InstallPath."/module/nominatim.so', 'transliteration' LANGUAGE c IMMUTABLE STRICT";
-    $sSQL .= ';DROP FUNCTION nominatim_test_import_func(text);';
-    $oResult = $oDB->query($sSQL);
-
-    if (PEAR::isError($oResult)) {
-        echo "\nERROR: Failed to load nominatim module. Reason:\n";
-        echo $oResult->userinfo."\n\n";
-        exit(1);
+    if (!checkModulePresence()) {
+        fail('error loading nominatim.so module');
     }
 
     if (!file_exists(CONST_ExtraDataPath.'/country_osm_grid.sql.gz')) {
-        echo "Error: you need to download the country_osm_grid first:";
-        echo "\n    wget -O ".CONST_ExtraDataPath."/country_osm_grid.sql.gz http://www.nominatim.org/data/country_grid.sql.gz\n";
+        echo 'Error: you need to download the country_osm_grid first:';
+        echo "\n    wget -O ".CONST_ExtraDataPath."/country_osm_grid.sql.gz https://www.nominatim.org/data/country_grid.sql.gz\n";
         exit(1);
     }
 
@@ -177,8 +190,8 @@ if ($aCMDResult['setup-db'] || $aCMDResult['all']) {
     // is only defined in the subsequently called create_tables.
     // Create dummies here that will be overwritten by the proper
     // versions in create-tables.
-    pgsqlRunScript('CREATE TABLE place_boundingbox ()');
-    pgsqlRunScript('create type wikipedia_article_match as ()');
+    pgsqlRunScript('CREATE TABLE IF NOT EXISTS place_boundingbox ()');
+    pgsqlRunScript('CREATE TYPE wikipedia_article_match AS ()', false);
 }
 
 if ($aCMDResult['import-data'] || $aCMDResult['all']) {
@@ -192,7 +205,7 @@ if ($aCMDResult['import-data'] || $aCMDResult['all']) {
         fail("osm2pgsql not found in '$osm2pgsql'");
     }
 
-    if (!is_null(CONST_Osm2pgsql_Flatnode_File)) {
+    if (!is_null(CONST_Osm2pgsql_Flatnode_File) && CONST_Osm2pgsql_Flatnode_File) {
         $osm2pgsql .= ' --flat-nodes '.CONST_Osm2pgsql_Flatnode_File;
     }
     if (CONST_Tablespace_Osm2pgsql_Data)
@@ -206,8 +219,20 @@ if ($aCMDResult['import-data'] || $aCMDResult['all']) {
     $osm2pgsql .= ' -lsc -O gazetteer --hstore --number-processes 1';
     $osm2pgsql .= ' -C '.$iCacheMemory;
     $osm2pgsql .= ' -P '.$aDSNInfo['port'];
+    if (isset($aDSNInfo['username']) && $aDSNInfo['username']) {
+        $osm2pgsql .= ' -U ' . $aDSNInfo['username'];
+    }
+    if (isset($aDSNInfo['hostspec']) && $aDSNInfo['hostspec']) {
+        $osm2pgsql .= ' -H ' . $aDSNInfo['hostspec'];
+    }
+
+    $aProcEnv = null;
+    if (isset($aDSNInfo['password']) && $aDSNInfo['password']) {
+        $aProcEnv = array_merge(array('PGPASSWORD' => $aDSNInfo['password']), $_ENV);
+    }
+
     $osm2pgsql .= ' -d '.$aDSNInfo['database'].' '.$aCMDResult['osm-file'];
-    passthruCheckReturn($osm2pgsql);
+    runWithEnv($osm2pgsql, $aProcEnv);
 
     $oDB =& getDB();
     if (!$aCMDResult['ignore-errors'] && !chksql($oDB->getRow('select * from place limit 1'))) {
@@ -218,9 +243,11 @@ if ($aCMDResult['import-data'] || $aCMDResult['all']) {
 if ($aCMDResult['create-functions'] || $aCMDResult['all']) {
     info('Create Functions');
     $bDidSomething = true;
-    if (!file_exists(CONST_InstallPath.'/module/nominatim.so')) {
-        fail("nominatim module not built");
+
+    if (!checkModulePresence()) {
+        fail('error loading nominatim.so module');
     }
+
     create_sql_functions($aCMDResult);
 }
 
@@ -393,7 +420,7 @@ if ($aCMDResult['load-data'] || $aCMDResult['all']) {
         $sSQL = "INSERT INTO placex ($sColumns) SELECT $sColumns FROM place WHERE osm_id % $iLoadThreads = $i";
         $sSQL .= " and not (class='place' and type='houses' and osm_type='W'";
         $sSQL .= "          and ST_GeometryType(geometry) = 'ST_LineString')";
-        $sSQL .= " and ST_IsValid(geometry)";
+        $sSQL .= ' and ST_IsValid(geometry)';
         if ($aCMDResult['verbose']) echo "$sSQL\n";
         if (!pg_send_query($aDBInstances[$i]->connection, $sSQL)) {
             fail(pg_last_error($aDBInstances[$i]->connection));
@@ -410,14 +437,23 @@ if ($aCMDResult['load-data'] || $aCMDResult['all']) {
         fail(pg_last_error($aDBInstances[$iLoadThreads]->connection));
     }
 
-    $bAnyBusy = true;
-    while ($bAnyBusy) {
-        $bAnyBusy = false;
-        for ($i = 0; $i <= $iLoadThreads; $i++) {
-            if (pg_connection_busy($aDBInstances[$i]->connection)) $bAnyBusy = true;
+    $bFailed = false;
+    for ($i = 0; $i <= $iLoadThreads; $i++) {
+        while (($hPGresult = pg_get_result($aDBInstances[$i]->connection)) !== false) {
+            $resultStatus = pg_result_status($hPGresult);
+            // PGSQL_EMPTY_QUERY, PGSQL_COMMAND_OK, PGSQL_TUPLES_OK,
+            // PGSQL_COPY_OUT, PGSQL_COPY_IN, PGSQL_BAD_RESPONSE,
+            // PGSQL_NONFATAL_ERROR and PGSQL_FATAL_ERROR
+            echo 'Query result ' . $i . ' is: ' . $resultStatus . "\n";
+            if ($resultStatus != PGSQL_COMMAND_OK && $resultStatus != PGSQL_TUPLES_OK) {
+                $resultError = pg_result_error($hPGresult);
+                echo '-- error text ' . $i . ': ' . $resultError . "\n";
+                $bFailed = true;
+            }
         }
-        sleep(1);
-        echo '.';
+    }
+    if ($bFailed) {
+        fail('SQL errors loading placex and/or location_property_osmline tables');
     }
     echo "\n";
     info('Reanalysing database');
@@ -459,7 +495,7 @@ if ($aCMDResult['import-tiger-data']) {
 
     foreach (glob(CONST_Tiger_Data_Path.'/*.sql') as $sFile) {
         echo $sFile.': ';
-        $hFile = fopen($sFile, "r");
+        $hFile = fopen($sFile, 'r');
         $sSQL = fgets($hFile, 100000);
         $iLines = 0;
 
@@ -472,7 +508,7 @@ if ($aCMDResult['import-tiger-data']) {
                     if (!pg_send_query($aDBInstances[$i]->connection, $sSQL)) fail(pg_last_error($oDB->connection));
                     $iLines++;
                     if ($iLines == 1000) {
-                        echo ".";
+                        echo '.';
                         $iLines = 0;
                     }
                 }
@@ -517,15 +553,15 @@ if ($aCMDResult['calculate-postcodes'] || $aCMDResult['all']) {
         fail(pg_last_error($oDB->connection));
     }
 
-    $sSQL  = "INSERT INTO location_postcode";
-    $sSQL .= " (place_id, indexed_status, country_code, postcode, geometry) ";
+    $sSQL  = 'INSERT INTO location_postcode';
+    $sSQL .= ' (place_id, indexed_status, country_code, postcode, geometry) ';
     $sSQL .= "SELECT nextval('seq_place'), 1, country_code,";
     $sSQL .= "       upper(trim (both ' ' from address->'postcode')) as pc,";
-    $sSQL .= "       ST_Centroid(ST_Collect(ST_Centroid(geometry)))";
-    $sSQL .= "  FROM placex";
+    $sSQL .= '       ST_Centroid(ST_Collect(ST_Centroid(geometry)))';
+    $sSQL .= '  FROM placex';
     $sSQL .= " WHERE address ? 'postcode' AND address->'postcode' NOT SIMILAR TO '%(,|;)%'";
-    $sSQL .= "       AND geometry IS NOT null";
-    $sSQL .= " GROUP BY country_code, pc";
+    $sSQL .= '       AND geometry IS NOT null';
+    $sSQL .= ' GROUP BY country_code, pc';
 
     if (!pg_query($oDB->connection, $sSQL)) {
         fail(pg_last_error($oDB->connection));
@@ -533,35 +569,34 @@ if ($aCMDResult['calculate-postcodes'] || $aCMDResult['all']) {
 
     if (CONST_Use_Extra_US_Postcodes) {
         // only add postcodes that are not yet available in OSM
-        $sSQL  = "INSERT INTO location_postcode";
-        $sSQL .= " (place_id, indexed_status, country_code, postcode, geometry) ";
+        $sSQL  = 'INSERT INTO location_postcode';
+        $sSQL .= ' (place_id, indexed_status, country_code, postcode, geometry) ';
         $sSQL .= "SELECT nextval('seq_place'), 1, 'us', postcode,";
-        $sSQL .= "       ST_SetSRID(ST_Point(x,y),4326)";
-        $sSQL .= "  FROM us_postcode WHERE postcode NOT IN";
-        $sSQL .= "        (SELECT postcode FROM location_postcode";
+        $sSQL .= '       ST_SetSRID(ST_Point(x,y),4326)';
+        $sSQL .= '  FROM us_postcode WHERE postcode NOT IN';
+        $sSQL .= '        (SELECT postcode FROM location_postcode';
         $sSQL .= "          WHERE country_code = 'us')";
-
         if (!pg_query($oDB->connection, $sSQL)) fail(pg_last_error($oDB->connection));
     }
 
     // add missing postcodes for GB (if available)
-    $sSQL  = "INSERT INTO location_postcode";
-    $sSQL .= " (place_id, indexed_status, country_code, postcode, geometry) ";
+    $sSQL  = 'INSERT INTO location_postcode';
+    $sSQL .= ' (place_id, indexed_status, country_code, postcode, geometry) ';
     $sSQL .= "SELECT nextval('seq_place'), 1, 'gb', postcode, geometry";
-    $sSQL .= "  FROM gb_postcode WHERE postcode NOT IN";
-    $sSQL .= "           (SELECT postcode FROM location_postcode";
+    $sSQL .= '  FROM gb_postcode WHERE postcode NOT IN';
+    $sSQL .= '           (SELECT postcode FROM location_postcode';
     $sSQL .= "             WHERE country_code = 'gb')";
     if (!pg_query($oDB->connection, $sSQL)) fail(pg_last_error($oDB->connection));
 
     if (!$aCMDResult['all']) {
         $sSQL = "DELETE FROM word WHERE class='place' and type='postcode'";
-        $sSQL .= "and word NOT IN (SELECT postcode FROM location_postcode)";
+        $sSQL .= 'and word NOT IN (SELECT postcode FROM location_postcode)';
         if (!pg_query($oDB->connection, $sSQL)) {
             fail(pg_last_error($oDB->connection));
         }
     }
-    $sSQL = "SELECT count(getorcreate_postcode_id(v)) FROM ";
-    $sSQL .= "(SELECT distinct(postcode) as v FROM location_postcode) p";
+    $sSQL = 'SELECT count(getorcreate_postcode_id(v)) FROM ';
+    $sSQL .= '(SELECT distinct(postcode) as v FROM location_postcode) p';
 
     if (!pg_query($oDB->connection, $sSQL)) {
         fail(pg_last_error($oDB->connection));
@@ -577,14 +612,34 @@ if ($aCMDResult['index'] || $aCMDResult['all']) {
     $bDidSomething = true;
     $sOutputFile = '';
     $sBaseCmd = CONST_InstallPath.'/nominatim/nominatim -i -d '.$aDSNInfo['database'].' -P '.$aDSNInfo['port'].' -t '.$iInstances.$sOutputFile;
+    if (isset($aDSNInfo['hostspec']) && $aDSNInfo['hostspec']) {
+        $sBaseCmd .= ' -H ' . $aDSNInfo['hostspec'];
+    }
+    if (isset($aDSNInfo['username']) && $aDSNInfo['username']) {
+        $sBaseCmd .= ' -U ' . $aDSNInfo['username'];
+    }
+    $aProcEnv = null;
+    if (isset($aDSNInfo['password']) && $aDSNInfo['password']) {
+        $aProcEnv = array_merge(array('PGPASSWORD' => $aDSNInfo['password']), $_ENV);
+    }
+
     info('Index ranks 0 - 4');
-    passthruCheckReturn($sBaseCmd.' -R 4');
+    $iStatus = runWithEnv($sBaseCmd.' -R 4', $aProcEnv);
+    if ($iStatus != 0) {
+        fail('error status ' . $iStatus . ' running nominatim!');
+    }
     if (!$aCMDResult['index-noanalyse']) pgsqlRunScript('ANALYSE');
     info('Index ranks 5 - 25');
-    passthruCheckReturn($sBaseCmd.' -r 5 -R 25');
+    $iStatus = runWithEnv($sBaseCmd.' -r 5 -R 25', $aProcEnv);
+    if ($iStatus != 0) {
+        fail('error status ' . $iStatus . ' running nominatim!');
+    }
     if (!$aCMDResult['index-noanalyse']) pgsqlRunScript('ANALYSE');
     info('Index ranks 26 - 30');
-    passthruCheckReturn($sBaseCmd.' -r 26');
+    $iStatus = runWithEnv($sBaseCmd.' -r 26', $aProcEnv);
+    if ($iStatus != 0) {
+        fail('error status ' . $iStatus . ' running nominatim!');
+    }
 
     info('Index postcodes');
     $oDB =& getDB();
@@ -597,6 +652,7 @@ if ($aCMDResult['create-search-indices'] || $aCMDResult['all']) {
     $bDidSomething = true;
 
     $sTemplate = file_get_contents(CONST_BasePath.'/sql/indices.src.sql');
+    $sTemplate = str_replace('{www-user}', CONST_Database_Web_User, $sTemplate);
     $sTemplate = replace_tablespace(
         '{ts:address-index}',
         CONST_Tablespace_Address_Index,
@@ -622,7 +678,7 @@ if ($aCMDResult['create-country-names'] || $aCMDResult['all']) {
 
     pgsqlRunScript("select getorcreate_country(make_standard_name('uk'), 'gb')");
     pgsqlRunScript("select getorcreate_country(make_standard_name('united states'), 'us')");
-    pgsqlRunScript("select count(*) from (select getorcreate_country(make_standard_name(country_code), country_code) from country_name where country_code is not null) as x");
+    pgsqlRunScript('select count(*) from (select getorcreate_country(make_standard_name(country_code), country_code) from country_name where country_code is not null) as x');
     pgsqlRunScript("select count(*) from (select getorcreate_country(make_standard_name(name->'name'), country_code) from country_name where name ? 'name') as x");
 
     $sSQL = 'select count(*) from (select getorcreate_country(make_standard_name(v), country_code) from (select country_code, skeys(name) as k, svals(name) as v from country_name) x where k ';
@@ -652,21 +708,21 @@ if ($aCMDResult['drop']) {
 
     // tables we want to keep. everything else goes.
     $aKeepTables = array(
-                    "*columns",
-                    "import_polygon_*",
-                    "import_status",
-                    "place_addressline",
-                    "location_property*",
-                    "placex",
-                    "search_name",
-                    "seq_*",
-                    "word",
-                    "query_log",
-                    "new_query_log",
-                    "gb_postcode",
-                    "spatial_ref_sys",
-                    "country_name",
-                    "place_classtype_*"
+                    '*columns',
+                    'import_polygon_*',
+                    'import_status',
+                    'place_addressline',
+                    'location_postcode',
+                    'location_property*',
+                    'placex',
+                    'search_name',
+                    'seq_*',
+                    'word',
+                    'query_log',
+                    'new_query_log',
+                    'spatial_ref_sys',
+                    'country_name',
+                    'place_classtype_*'
                    );
 
     $oDB =& getDB();
@@ -691,8 +747,8 @@ if ($aCMDResult['drop']) {
         // been deleted already by CASCADE
     }
 
-    if (!is_null(CONST_Osm2pgsql_Flatnode_File)) {
-        if ($aCMDResult['verbose']) echo "deleting ".CONST_Osm2pgsql_Flatnode_File."\n";
+    if (!is_null(CONST_Osm2pgsql_Flatnode_File) && CONST_Osm2pgsql_Flatnode_File) {
+        if ($aCMDResult['verbose']) echo 'deleting '.CONST_Osm2pgsql_Flatnode_File."\n";
         unlink(CONST_Osm2pgsql_Flatnode_File);
     }
 }
@@ -719,6 +775,16 @@ function pgsqlRunScriptFile($sFilename)
     if (!$aCMDResult['verbose']) {
         $sCMD .= ' -q';
     }
+    if (isset($aDSNInfo['hostspec']) && $aDSNInfo['hostspec']) {
+        $sCMD .= ' -h ' . $aDSNInfo['hostspec'];
+    }
+    if (isset($aDSNInfo['username']) && $aDSNInfo['username']) {
+        $sCMD .= ' -U ' . $aDSNInfo['username'];
+    }
+    $aProcEnv = null;
+    if (isset($aDSNInfo['password']) && $aDSNInfo['password']) {
+        $aProcEnv = array_merge(array('PGPASSWORD' => $aDSNInfo['password']), $_ENV);
+    }
 
     $ahGzipPipes = null;
     if (preg_match('/\\.gz$/', $sFilename)) {
@@ -742,10 +808,9 @@ function pgsqlRunScriptFile($sFilename)
                      2 => array('file', '/dev/null', 'a')
                     );
     $ahPipes = null;
-    $hProcess = proc_open($sCMD, $aDescriptors, $ahPipes);
+    $hProcess = proc_open($sCMD, $aDescriptors, $ahPipes, null, $aProcEnv);
     if (!is_resource($hProcess)) fail('unable to start pgsql');
 
-
     // TODO: error checking
     while (!feof($ahPipes[1])) {
         echo fread($ahPipes[1], 4096);
@@ -827,32 +892,24 @@ function pgsqlRunDropAndRestore($sDumpFile)
     $aDSNInfo = DB::parseDSN(CONST_Database_DSN);
     if (!isset($aDSNInfo['port']) || !$aDSNInfo['port']) $aDSNInfo['port'] = 5432;
     $sCMD = 'pg_restore -p '.$aDSNInfo['port'].' -d '.$aDSNInfo['database'].' -Fc --clean '.$sDumpFile;
-
-    $aDescriptors = array(
-                     0 => array('pipe', 'r'),
-                     1 => array('pipe', 'w'),
-                     2 => array('file', '/dev/null', 'a')
-                    );
-    $ahPipes = null;
-    $hProcess = proc_open($sCMD, $aDescriptors, $ahPipes);
-    if (!is_resource($hProcess)) fail('unable to start pg_restore');
-
-    fclose($ahPipes[0]);
-
-    // TODO: error checking
-    while (!feof($ahPipes[1])) {
-        echo fread($ahPipes[1], 4096);
+    if (isset($aDSNInfo['hostspec']) && $aDSNInfo['hostspec']) {
+        $sCMD .= ' -h ' . $aDSNInfo['hostspec'];
+    }
+    if (isset($aDSNInfo['username']) && $aDSNInfo['username']) {
+        $sCMD .= ' -U ' . $aDSNInfo['username'];
+    }
+    $aProcEnv = null;
+    if (isset($aDSNInfo['password']) && $aDSNInfo['password']) {
+        $aProcEnv = array_merge(array('PGPASSWORD' => $aDSNInfo['password']), $_ENV);
     }
-    fclose($ahPipes[1]);
 
-    $iReturn = proc_close($hProcess);
+    $iReturn = runWithEnv($sCMD, $aProcEnv);
 }
 
-function passthruCheckReturn($cmd)
+function passthruCheckReturn($sCmd)
 {
-    $result = -1;
-    passthru($cmd, $result);
-    if ($result != 0) fail('Error executing external command: '.$cmd);
+    $iResult = -1;
+    passthru($sCmd, $iResult);
 }
 
 function replace_tablespace($sTemplate, $sTablespace, $sSql)
@@ -868,8 +925,9 @@ function replace_tablespace($sTemplate, $sTablespace, $sSql)
 
 function create_sql_functions($aCMDResult)
 {
+    global $sModulePath;
     $sTemplate = file_get_contents(CONST_BasePath.'/sql/functions.sql');
-    $sTemplate = str_replace('{modulepath}', CONST_InstallPath.'/module', $sTemplate);
+    $sTemplate = str_replace('{modulepath}', $sModulePath, $sTemplate);
     if ($aCMDResult['enable-diff-updates']) {
         $sTemplate = str_replace('RETURN NEW; -- %DIFFUPDATES%', '--', $sTemplate);
     }
@@ -887,3 +945,23 @@ function create_sql_functions($aCMDResult)
     }
     pgsqlRunScript($sTemplate);
 }
+
+function checkModulePresence()
+{
+    // Try accessing the C module, so we know early if something is wrong
+    // and can simply error out.
+    $sSQL = "CREATE FUNCTION nominatim_test_import_func(text) RETURNS text AS '";
+    $sSQL .= $sModulePath."/nominatim.so', 'transliteration' LANGUAGE c IMMUTABLE STRICT";
+    $sSQL .= ';DROP FUNCTION nominatim_test_import_func(text);';
+    $oResult = $oDB->query($sSQL);
+
+    $bResult = true;
+
+    if (PEAR::isError($oResult)) {
+        echo "\nERROR: Failed to load nominatim module. Reason:\n";
+        echo $oResult->userinfo."\n\n";
+        $bResult = false;
+    }
+
+    return $bResult;
+}