2

Simply, how can I make GPG not print that message? Here are the commands I'm using:

echo "test input" > test.in
echo "test" | gpg -q -c --passphrase-fd 0 --output test.enc --yes --force-mdc test.in
echo "test" | gpg -q -d --passphrase-fd 0 test.enc > test.out

And running it:

$ echo "test input" > test.in 
$ echo "test" | gpg -q -c --passphrase-fd 0 --output test.enc --yes --force-mdc test.in
Reading passphrase from file descriptor 0    
$ echo "test" | gpg -q -d --passphrase-fd 0 test.enc > test.out
Reading passphrase from file descriptor 0

EDIT: Redirecting stderr doesn't seem to work

$ echo "test" | gpg -q -c --passphrase-fd 0 --output test.enc --yes --force-mdc test.in 2> /dev/null
Reading passphrase from file descriptor 0    
Paul Tarjan
  • 559
  • 3
  • 8
  • 17

3 Answers3

8

One way to see what's going on is to trace the system calls involved. Utilities to do this vary according to platform. On Solaris, you would use truss. On Linux (as in my example) you would use strace.


To trace, we change the command used from:

echo "test" | gpg -q -c --passphrase-fd 0 --output test.enc --yes --force-mdc test.in 2> /dev/null

to:

echo "test" | strace gpg -q -c --passphrase-fd 0 --output test.enc --yes --force-mdc test.in 2>trace_output.txt .


The first thing that stands out as interesting (if a little unrelated) is that gpg is doing repeated single byte reads when taking the input passphrase from stdin. This is sometimes a tell-tale sign of inefficient code - but in this case, it's probably not that big a deal:

read(0, "t", 1)                         = 1
read(0, "e", 1)                         = 1
read(0, "s", 1)                         = 1
read(0, "t", 1)                         = 1
read(0, "\n", 1)                        = 1

The more relevant stuff regarding the output of the log message is all here:

open("/dev/tty", O_RDWR)                = 3
fstat(3, {st_mode=S_IFCHR|0666, st_rdev=makedev(5, 0), ...}) = 0
ioctl(3, SNDCTL_TMR_TIMEBASE or TCGETS, {B9600 opost isig icanon echo ...}) = 0
write(3, "Reading passphrase from file des"..., 45) = 45
write(3, "\10\10\10   \n", 7)           = 7

That's all we hear about file descriptor 3 until exit (it's not explicitly closed).

Looking at each of these in turn:

  • open("/dev/tty", O_RDWR) = 3

    That's opening the file /dev/tty, for both reading and writing. The return value (a new file descriptor for later use) is 3.

    /dev/tty is a synonym for the current controlling terminal. You can see the device which is effectively being referenced by this special file, by running $ tty

  • fstat(3, {st_mode=S_IFCHR|0666, st_rdev=makedev(5, 0), ...}) = 0

    This is used by gpg to find out about the file just opened with file descriptor 3. The stuff in curly brackets is what's returned (a populated struct stat, with the 5, 0 indicating that this is a particularly special file ).

  • ioctl(3, SNDCTL_TMR_TIMEBASE or TCGETS, {B9600 opost isig icanon echo ...}) = 0

    This is manipulating attributes of the controlling terminal, prior to output.

  • write(3, "Reading passphrase from file des"..., 45) = 45

    write(3, "\10\10\10 \n", 7) = 7

    These are more straightforward. gpg successfully writes that text (some of which was abbreviated in the strace output) to the terminal.


So - this is your answer.. gpg is writing this log message directly to /dev/tty (synonym for the controlling terminal), so you won't be able to redirect it in the same way you can for stdout or stderr.

There is a way around this. You can disconnect the controlling terminal prior to executing gpg.

Here's a short program which does just that:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>

int main(int argc, char* argv[])
{
  int rc, fd;
  if (argc != 2)
  {
    fprintf(stderr, 
     "Provide command line arg to execute after TIOCNOTTY\n");
    return EXIT_FAILURE;
  }
  fd = open("/dev/tty", O_RDWR);
  if (fd < 0)
  {
    fprintf(stderr, 
     "Failed to open controlling terminal: %s\n",
     strerror(errno));
    return EXIT_FAILURE;
  }
  rc = ioctl(fd, TIOCNOTTY);
  if (rc == -1)
  {
    fprintf(stderr,
     "Failed TIOCNOTTY ioctrl: %s\b",
     strerror(errno));
    return EXIT_FAILURE;
  }
  return system(argv[1]);
}

There should to be an existing utility to do the above, but I couldn't find one.

If you were to compile that code, call the resulting executable notty, then you could do this:

echo "test" | notty "gpg -q -c --passphrase-fd 0 --output test.enc --yes --force-mdc test.in"

This should suppress the message, but keep your stdout and stderr intact. It's unclear what else would be suppressed though (you'd need to look at the gpg source to see what else is output in this way).

user30579
  • 298
  • 1
  • 7
  • 2
    wow, very indepth and informative. Thank you. Accepting, even though `--batch` works as well, yours taught me a lot. And why would gpg do /dev/tty instead of stdout? – Paul Tarjan Jan 04 '10 at 07:09
  • 1
    Perhaps for this property.. (so the output isn't mixed and redirected with stdout,stderr). I notice also that there's a --no-tty option. Here's the relevant part of the gpg source: http://cvs.gnupg.org/cgi-bin/viewcvs.cgi/branches/STABLE-BRANCH-1-4/util/ttyio.c?rev=5146&root=GnuPG&view=markup (function tty_printf() in that module is used from other .c files) – user30579 Jan 05 '10 at 08:24
7

--batch is the answer, but I have no idea how it is outputting even when stderr is redirected...

Paul Tarjan
  • 559
  • 3
  • 8
  • 17
4

While user30579's answer is useful in understanding the issue, let's trim that down to the minimum required to actually fix the problem:

Just use the --no-tty switch built in to GPG. e.g.,:

echo "test" | gpg --no-tty -q -c --passphrase-fd 0 --output test.enc --yes --force-mdc test.in
Evan
  • 41
  • 1