postgresqlfileperlneomutt

I'm running neomutt in a loop with Perl. The config values come from PostgreSQL, but neomutt only reads from files. How to imitate a file from Perl?


I run a while(1) loop in perl to pull out email addresses and each one's configuration values from PostgreSQL tables. Right now, I write a temporary file and use neomutt -nF the_temp_file with system. Then I unlink the file. Neomutt quits. Then the loop gives me the list of email addresses to start neomutt again with any one of those addresses I select.

I haven't asked this question yet on the neomutt mailing list, but I will. I would like to know in general if there is a way to imitate the temporary file without writing one into the file system.

To be more clear:

Get the config values, like:

set beep = 0
set beep_new = 0
set bounce = ask-yes
set check_mbox_size = 1
set check_new = 1

and send that directly to the spot neomutt expects a file at neomutt -F config_file

Is this possible? Thanks


Solution

  • It still involves a temporary file, but if you're using an OS like Linux that has a /dev/fd filesystem, you can open a temporary file, immediately delete it to keep things tidy, and pass /dev/fd/N as the filename to neomutt, where N is the underlying file descriptor number of the perl file handle. If you use the core File::Temp module to create the temporary file, it can be done securely without potential race conditions or having to manually delete the file.

    There is a bit of drudgery in stopping the descriptor from being closed before system executes the child program, though.

    Example:

    #!/usr/bin/env perl
    use strict;
    use warnings;
    use File::Temp qw/tempfile/;
    use Fcntl qw/:DEFAULT/;
    
    # Get a handle to an anonymous temporary file
    my $fh = tempfile;
    
    print $fh <<EOF;
    set beep = 0
    set beep_new = 0
    set bounce = ask-yes
    set check_mbox_size = 1
    set check_new = 1
    EOF
    flush $fh;
    
    # Clear the CLOEXEC bit so the descriptor is available to the program run
    # by system
    my $flags = fcntl $fh, F_GETFD, 0
        or die "Unable to get descriptor flags: $!";
    fcntl $fh, F_SETFD, $flags & ~FD_CLOEXEC
        or die "Unable to set descriptor flags: $!";
    
    my $fd = fileno $fh;
    system("cat", "/dev/fd/$fd");
    

    An alternative that completely avoids temporary files (but is a bit more complicated) is to open up a pipe, and fork off a child that writes the data to it, and again using the /dev/fd/N interface with neomutt:

    #!/usr/bin/env perl
    use strict;
    use warnings;
    use Fcntl qw/:DEFAULT/;
    
    pipe my $reader, my $writer or die "Unable to pipe: $!\n";
    
    my $pid = fork;
    die "Unable to fork" unless defined $pid;
    if ($pid == 0) { # Child process
        close $reader;
        print $writer <<EOF;
    set beep = 0
    set beep_new = 0
    set bounce = ask-yes
    set check_mbox_size = 1
    set check_new = 1
    EOF
        close $writer;
        exit;
    } else { # Parent process
        close $writer;
        # Clear the CLOEXEC bit so the descriptor is available to the program run
        # by system
        my $flags = fcntl $reader, F_GETFD, 0;
        fcntl $reader, F_SETFD, $flags & ~FD_CLOEXEC;
        my $fd = fileno $reader;
        system("cat", "/dev/fd/$fd");
        close $reader;
        waitpid $pid, 0; # Reap the child process
    }