Rick
Rick

Reputation: 7506

How do I get stdin length from a pipe? echo "hello" | ./get_stdin_size

I installed an application and its command line can do:

  1. command -input 1.txt

  2. command < 1.txt

  3. echo "hello" | command

and output something. I don't have the source code and want to implement that behaviour too.

What I've tried is:

#include <stdio.h>
#include <unistd.h>
int main(int argc, char* argv[]){
    if ((fseek(stdin, 0, SEEK_END), ftell(stdin)) > 0){
        rewind(stdin);
        printf("stdin has data\n");
        char buffer[100];
        fgets(buffer, sizeof buffer, stdin);
        printf("stdin data are: %s\n", buffer);
    }else{
        if (argc < 2){
            printf("no cmd arguments\n");
            return -1;
        }else{
            printf("command line argument: %s\n", argv[1]);
            FILE* fp = fopen(argv[1], "r");
            if (fp == NULL){
                printf("NULL fp pointer\n");
                return -1;
            }
            char a[100] = {0};
            fgets(a, sizeof a, fp);
            printf("first line of file: %s\n", a);
        }
    }
    return 0;
}

But the problem is that pipes are not seekable. So ((fseek(stdin, 0, SEEK_END), ftell(stdin)) > 0) doesn't fit all cases.

One solution that I think of is:

#include <stdio.h>
#include <unistd.h>
int main(int argc, char* argv[]){
    if (argc > 1){
        //open file with argv[1] as filename
        //read data from disk file
    }else{
        //read data from stdin
        if(stdin is file){
            //get file size
            //read data from stdin
        }else if(stdin is pipe){
            //get pipe size
            //read data from stdin
        }
    }
    return 0;
}

I have 2 problems with this code:

  1. Is there a ispipe() function which works like isatty(fileno(stdin))? I need to tell if stdin is a pipe.

  2. How do I get the stdin size/length from a pipe? Apparently I can't use:

    fseek(stdin, 0, SEEK_END);
    long size = ftell(stdin));

As @Peter pointed out in the comment, I should not try to get the stdin size from a pipe beforehand, then how do I know it reaches the end? Could anyone gives me an minimum example about this "stream-based processing"?

Upvotes: 0

Views: 457

Answers (2)

Shawn
Shawn

Reputation: 52354

You can use the fstat() syscall to tell if standard input is a pipe (Either anonymous or named), or a file (And if a file, find its size):

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

int main(void) {
  struct stat s;

  if (fstat(STDIN_FILENO, &s) < 0) {
    perror("fstat");
    return EXIT_FAILURE;
  }

  switch (s.st_mode & S_IFMT) {
  case S_IFIFO:
    puts("standard input is a pipe.");
    break;
  case S_IFREG:
    printf("standard input is a file that is %ld bytes in size.\n",
           (long)s.st_size);
    break;
  case S_IFCHR:
    if (isatty(STDIN_FILENO)) {
      puts("standard input is a terminal.");
    } else {
      puts("standard input is a character device.");
    }
    break;
  default:
    puts("standard input is something else.");
  }
  return 0;
}

Example:

$ gcc testpipe.c
$ cat testpipe.c | ./a.out
standard input is a pipe.
$ ./a.out < testpipe.c    
standard input is a file that is 525 bytes in size.
$ ./a.out
standard input is a terminal.

Upvotes: 3

Maxime B.
Maxime B.

Reputation: 1236

The only way to be sure that you won't recieve more data from a pipe is when it is closed (SIGPIPE signal).

Thus, as stated in comments, allocating/reading the right of memory is challenging with pipes, since they can be infinite (e.g. /dev/random). You have to make hypothesis or use extra data in order to handle the pipe.

Depending on your use case, these strategies can be one of:

  1. Sending the data length at the beginning of the message. This can be like: echo -e'\x05\x00\x00\x00Hello'|./myprog. With that strategy, it is trivial to read the pipe but it requieres that you know the total size of the input before you start sending it.
  2. Allocating and reading a limited amount of data/time. If you recieve than PIPE_MAX_SIZE bytes or you wait more than TIMEOUT_PIPE, close the pipe and handle the possibly incomplete message.
  3. Handle the message block by block. If your message follows a regular pattern, you can read it this way and handle blocks sequentially until you reach the end of the message. This also allows you to discard previous buffer to read unlimited amount of data that would not fit in memory.

Upvotes: 2

Related Questions