Why does this PostgreSQL \copy fail when launched by the daemon?

I have a perl script. One portion of it loads data into a PostgreSQL table. It runs as expected from the command line. It fails when launched by another script. I don’t know why.

I feel the key fact is the lack of output from the psql command. Nothing. No errors. No success.

To be clear, the script always runs. What fails is the “echo ‘\copy… ‘ | psql” which does not produce any output.

Some of the code looks like this:

my $PSQL = "/usr/local/bin/psql";

...

    # echo "\\\copy vuxml_import from '/tmp/vnhvGB2yN0' WITH (FORMAT TEXT, HEADER false);" | $PSQL "sslmode=require host=pg01.int.unixathome.org user=commits_dvl dbname=freshports.dvl"

    # sslcertmode=disable avoids could not open certificate file “/root/.postgresql/postgresql.crt”: Permission denied
    my $psql_command = "$PSQL \"sslmode=$FreshPorts::Config::ssl_mode host=$FreshPorts::Config::host user=$FreshPorts::Config::user dbname=$FreshPorts::Config::dbname sslcertmode=disable\"";
    my $copy_command = "\\copy vuxml_import from '$file_path' WITH (FORMAT TEXT, HEADER false);";

    print "\$copy_command='$copy_command\n";
    print "\$psql_command='$psql_command\n";

    # save it to the database
    eval {
        $dbh->do($truncate_sql);
        print "committing truncate\n";
        # we commit this here because the psql import which follows will deadlock.
        $dbh->commit();


        print "echo \"$copy_command\" | $psql_command";
        print "\n";

        # need to check how many rows. Should be at least 5900

        # use qx, similar to `backticks` - use chomp to remove the trailing whitespace
        chomp(my $output = qx/echo "$copy_command;" | $psql_command/);
        print "copy command has finished\n";
        print "\$output is '$output'\n";

        # grab the number of rows imported: https://stackoverflow.com/questions/3574906/how-to-extract-a-number-from-a-string-in-perl#3574959
        my ($count) = $output =~ /(\d+)/;

        print "Imported $count items\n";

        if ($count < ExpectedCopy) {
            $dbh->rollback(); # Rollback on error
            # we do not return from this call
            FreshPorts::Utilities::ReportError('err', "FATAL: we imported $count items, which is less than our expected minimum value of " . ExpectedCopy . "\n", 1);
            # but just in case we do
            die("bad number of vuxml_import");
        }
    };
    if ($@) {
        warn "Error during COPY: $@";
        $dbh->rollback(); # Rollback on error
    }

When run by the system, as opposed to by myself, this output appears:

$copy_command='\copy vuxml_import from '/tmp/freshports_vuxml_processing_hgb4z' WITH (FORMAT TEXT, HEADER false);
$psql_command='/usr/local/bin/psql "sslmode=require host=pg01.int.unixathome.org user=commits_dvl dbname=freshports.dvl sslcertmode=disable"
committing truncate
echo "\copy vuxml_import from '/tmp/freshports_vuxml_processing_hgb4z' WITH (FORMAT TEXT, HEADER false);" | /usr/local/bin/psql "sslmode=require host=pg01.int.unixathome.org user=commits_dvl dbname=freshports.dvl sslcertmode=disable"
copy command has finished
$output is ''
Imported  items
FATAL: we imported  items, which is less than our expected minimum value of 5900

If I take line 4 above, and paste it in on the command line as the freshports user, we get:

$ echo "\copy vuxml_import from '/tmp/freshports_vuxml_processing_hgb4z' WITH (FORMAT TEXT, HEADER false);" | /usr/local/bin/psql "sslmode=require host=pg01.int.unixathome.org user=commits_dvl dbname=freshports.dvl sslcertmode=disable"
COPY 6010
$ 

This is the expected result: it imported many rows.

Any ideas?

Questions raised

Does it work without specifying perl?

$ touch ~/signals/vuxml
$ id
uid=10001(freshports) gid=10001(freshports) groups=10001(freshports)
$ ls -l /usr/local/libexec/freshports/process_vuxml.
/usr/local/libexec/freshports/process_vuxml.pl  /usr/local/libexec/freshports/process_vuxml.sh 
$ ls -l /usr/local/libexec/freshports/process_vuxml.pl
-rwxr-xr-x  1 dvl dvl 14037 Aug 26 20:07 /usr/local/libexec/freshports/process_vuxml.pl
$ /usr/local/libexec/freshports/process_vuxml.pl --filename=/jails/freshports/usr/ports/security/vuxml/vuln.xml
process_vuxml.pl starts
(there is usually a delay before further output)
There, the parsefile has completed
calculating the sha256 for each vuln
doing the findnodes() thing
findnodes() has finished, iteration has started
...........................................................................................................................................................................................................................................
...........................
44101b31-8ffd-11e7-b5af-a4badb2f4699 - cancelled: skipping that one for import
....
c7e8e955-6c61-11e7-9b01-2047478f2f70 - cancelled: skipping that one for import
........
04bc4e23-9a70-42cb-9fec-3613632d34bc - cancelled: skipping that one for import
.........
2aedd15f-ca8b-11e6-a9a5-b499baebfeaf - cancelled: skipping that one for import
.....................
cf0b5668-4d1b-11e6-b2ec-b499baebfeaf - cancelled: skipping that one for import

61b8c359-4aab-11e6-a7bd-14dae9d210b8 - cancelled: skipping that one for import
.
c17fe91d-4aa6-11e6-a7bd-14dae9d210b8 - cancelled: skipping that one for import

42ecf370-4aa4-11e6-a7bd-14dae9d210b8 - cancelled: skipping that one for import
.......
95564990-1138-11e6-b55e-b499baebfeaf - cancelled: skipping that one for import
.........................................................................
f99a4686-e694-11e3-9032-000c2980a9f3 - cancelled: skipping that one for import
.....
080c5370-886a-11e3-9533-60a44c524f57 - cancelled: skipping that one for import
....
cd082cc6-1548-4b8d-a3aa-a007be611a29 - cancelled: skipping that one for import
.........................................................
6887828f-0229-11e0-b84d-00262d5ed8ee - cancelled: skipping that one for import

b2a6fc0e-070f-11e0-a6e9-00215c6a37bb - cancelled: skipping that one for import
..........
f6b6beaa-4e0e-11df-83fb-0015587e2cc1 - cancelled: skipping that one for import
................................................
ff284bf0-3f32-11dc-a79a-0016179b2dd5 - cancelled: skipping that one for import
................
28ce7102-4039-11db-a838-00148584c7dd - cancelled: skipping that one for import
...
0a4cd819-0291-11db-bbf7-000c6ec775d9 - cancelled: skipping that one for import
...
af2a60ed-da3e-11da-93e0-00123ffe8333 - cancelled: skipping that one for import
.....................................
e8c6ade2-6bcc-11d9-8e6f-000a95bc6fae - cancelled: skipping that one for import
....
14e8f315-600e-11d9-a9e7-0001020eed82 - cancelled: skipping that one for import
......
1f8dea68-3436-11d9-952f-000c6e8f12ef - cancelled: skipping that one for import
...........
bef4515b-eaa9-11d8-9440-000347a4fa7d - cancelled: skipping that one for import
..
4764cfd6-d630-11d8-b479-02e0185c0b53 - cancelled: skipping that one for import
......
c7705712-92e6-11d8-8b29-0020ed76ef5a - cancelled: skipping that one for import
......
6fd9a1e9-efd3-11d8-9837-000c41e2cdad - cancelled: skipping that one for import

3362f2c1-8344-11d8-a41f-0020ed76ef5a - cancelled: skipping that one for import
all sha256 saved for each vuln
finished with checksum eval()
$copy_command='\copy vuxml_import from '/tmp/freshports_vuxml_processing_L1Uxw' WITH (FORMAT TEXT, HEADER false);'
$psql_command='/usr/local/bin/psql "sslmode=require host=pg01.int.unixathome.org user=commits_dvl dbname=freshports.dvl sslcertmode=disable"'
committing truncate
echo "\copy vuxml_import from '/tmp/freshports_vuxml_processing_L1Uxw' WITH (FORMAT TEXT, HEADER false);" | /usr/local/bin/psql "sslmode=require host=pg01.int.unixathome.org user=commits_dvl dbname=freshports.dvl sslcertmode=disable"
copy command has finished
$output is 'COPY 6010'
Imported 6010 items
deleting missing vuxml
vuln not present in xml files have been deleted from the database.
getting list of vulns to update
list of vulns to update has been obtained
We have 0 vuxml entries to add/update
invoking update_modified_vids()
Nothing to update
Total time: 21 seconds
Number of updates: 0
committing
process_vuxml.pl finishes

2025-08-27 – Let’s try Python

It seems that having a fever is good for ideas. Last night I concluded I should abandon trying to run this \copy from a shell command use python instead. I have a perfectly working example used for package imports. I have modified it to replace line 28 in the code above.

The new script is:

[13:15 dvl-ingress01 dvl ~/scripts] % cat process_vuxml_import_temp_file.py        
#!/usr/local/bin/python

# take a file and load it into a temp table.

import psycopg2
import psycopg2.extras
import configparser # for config.ini parsing
import re           # for escaping the database passwords
import syslog       # for logging
import sys
import getopt

import os

def main(argv):
   inputfile = ''
   try:
      opts, args = getopt.getopt(argv,"hi:o:",["ifile="])
   except getopt.GetoptError:
      sys.exit(2)
   for opt, arg in opts:
      if opt == '-h':
         print (__file__  + ' -i <inputfile>')
         sys.exit()
      elif opt in ("-i", "--ifile"):
         inputfile = arg


   syslog.syslog(syslog.LOG_NOTICE, 'copying in from ' + inputfile)

   config = configparser.ConfigParser()
   config.read('/usr/local/etc/freshports/config.ini')


   DSN = 'host=' + config['database']['HOST'] + ' dbname=' + config['database']['DBNAME'] + ' user=' + config['database']['COMMITS_DBUSER'] + ' password=' + re.escape(config['database']['COMMITS_PASSWORD']) + ' sslcertmode=disable sslmode=require'


   dbh = psycopg2.connect(DSN)
   curs = dbh.cursor(cursor_factory=psycopg2.extras.DictCursor)

#
# someone else does the truncate
#
   curs.execute("select freshports_branch_set('head')")
   with open(inputfile, 'r') as f:
      curs.copy_from(f, 'vuxml_import', sep = '\t', columns = ['vid', 'checksum'] )

   row_count = curs.rowcount
   dbh.commit()
   dbh.close();
   syslog.syslog(syslog.LOG_NOTICE, 'copying completed. %d rows copied.' % row_count)
   
   return row_count

syslog.openlog(ident=os.path.basename(__file__), facility=syslog.LOG_LOCAL3)
syslog.syslog(syslog.LOG_NOTICE, 'Starting up')

if __name__ == "__main__":
   row_count = main(sys.argv[1:])

syslog.syslog(syslog.LOG_NOTICE, 'Finishing')

print ('COPY %d' % row_count)

Line 28 now becomes:

chomp(my $output = qx|/usr/local/libexec/freshports/process_vuxml_import_temp_file.py --ifile=$file_path|);

As before, this works from the command line. Now I just have to wait for the next vuxml import event.

Website Pin Facebook Twitter Myspace Friendfeed Technorati del.icio.us Digg Google StumbleUpon Premium Responsive

Leave a Comment

Scroll to Top