0

Given that a simple program:

/* ttyname.c */
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

int main(int argc, char argv) { char tty = NULL;

tty = ttyname(fileno(stderr)); if (tty == NULL) { fprintf(stderr, "%s\n", strerror(errno)); exit(EXIT_FAILURE); }

printf("%s\n", tty);

exit(EXIT_SUCCESS); }

compile it as ttyname and invoke it as init , the result as following:

Inappropriate ioctl for device

which means that the error code is ENOTTY.
Why can fprintf(stderr, ....) output to screen when stderr doesn't refer to a terminal device ?

Li-Guangda
  • 257
  • 2
  • 3
  • 11

2 Answers2

3

If you're invoking it as init then you're not getting output to the screen; the output is being sent to the kernel and the kernel is printing it to the screen. init is a special process

You can think of this as similar to the following shell script:

$ x=$(ttyname 2>&1)
$ echo $x
Inappropriate ioctl for device

This is done via the /dev/console device; stdin/stdout/stderr for the init process are attached to this by the kernel. Writes to that device are handled by the kernel and sent to the current console device(s), which may be the current vty or a serial port or elsewhere.

  • How do you know the output is being sent to the kernel ? – Li-Guangda Mar 11 '22 at 04:03
  • @LGD - see update on /dev/console – Stephen Harris Mar 11 '22 at 12:22
  • Ok, but I try to readlink of /proc/self/fd/2 at init and the result is /dev/console (deleted), what does it mean ? – Li-Guangda Mar 11 '22 at 13:39
  • 1
    The "(deleted)" part just means that file node is no longer visible (maybe switched root, or overlayed with udev devfs or something else); the original device was opened on rootfs (maybe initramfs or similar); see console_on_rootfs at https://github.com/torvalds/linux/blob/master/init/main.c . But because the process had the file open ('cos it was given it by the kernel) writes will still occur to the device (major 5, minor 1) even though it says "(deleted)". – Stephen Harris Mar 11 '22 at 14:34
  • A good reference, thanks. :) – Li-Guangda Mar 11 '22 at 15:07
1

The problem is that ttyname is always going to fail when stderr is not a terminal. So in the case of stderr being a socket like potentially during boot, ttyname fails but you can write to stderr without a problem which is why the fprintf works.

You can get the socket name by doing what ttyname does and that is readlink on /proc/self/fd/FD with "FD" being 2 for stderr usually.

  char tty[1024];

ssize_t size = readlink("/proc/self/fd/2", tty, sizeof(tty)-1); if (size < 0) { fprintf(stderr, "%s\n", strerror(errno)); exit(EXIT_FAILURE); }

tty[size] = 0;

printf("%s\n", tty);

CR.
  • 1,199