Chris
Chris

Reputation: 75

using ioctl to fill a winsize struct when piping to stdin

I'm trying to retrieve the width of the terminal using ioctl(), but it doesn't work when piping or redirecting to stdin.

I've managed to circumvent the issue by parsing the result of tput cols, but it feels dirty to use an external command. Also, I assume this makes it less portable as Windows doesn't use a bourne-compatible shell?

main.c

// tput method
char res[10];

FILE cmd = popen("tput cols", "r");
fgets(res, 10 - 1, cmd);
pclose(cmd);

unsigned short term_cols = atoi(res);
printf("Term width (tput): %d\n", term_cols);

// ioctl method
struct winsize ws;
if (ioctl(STDIN_FILENO, TIOCGWINSZ, &ws) == 0)
{
  printf("Term width (ioctl): %d\n", ws.ws_col);
}
else
{
  printf("Failed to retrieve term width from ioctl()");
}

output

$ bin/main  
Term width (tput): 84  
Term width (ioctl): 84
$ echo "test" | bin/main  
Term width (tput): 84  
Failed to retrieve term width from ioctl()

I've tried to fflush(stdin); at the start of my code but it doesn't make any difference. Is this just a limitation of ioctl() or is there a way around it?

Upvotes: 2

Views: 897

Answers (1)

melpomene
melpomene

Reputation: 85837

You're probably printing the value of an uninitialized variable. Your code doesn't check whether ioctl succeeds and if it fails, it leaves ws untouched.

Fix:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

...
if (ioctl(STDIN_FILENO, TIOCGWINSZ, &ws) == -1) {
    fprintf(stderr, "can't get the window size of stdin: %s\n", strerror(errno));
    exit(EXIT_FAILURE);
}

When you pipe something into your program, stdin doesn't refer to a terminal but a pipe. Pipes don't have a window size. That's why TIOCGWINSZ fails here.

The portable solution seems to be:

const char *term = ctermid(NULL);
if (!term[0]) {
    fprintf(stderr, "can't get the name of my controlling terminal\n");
    exit(EXIT_FAILURE);
}
int fd = open(term, O_RDONLY);
if (fd == -1) {
    fprintf(stderr, "can't open my terminal at %s: %s\n", term, strerror(errno));
    exit(EXIT_FAILURE);
}
if (ioctl(fd, TIOCGWINSZ, &ws) == -1) {
    fprintf(stderr, "can't get the window size of %s: %s\n", term, strerror(errno));
    exit(EXIT_FAILURE);
}
close(fd);

Upvotes: 1

Related Questions