-1

I am currently working on a cat program and have to distinguish the following cases:

./cat 

and

  ./cat  < file1 > file2.txt

This is the code

  while((fileFD=read(STDIN_FILENO,str,1000))!=0)// If we just execute the command cat, it will receive input from the stdin and put it in the stdout.
   {
     write(STDOUT_FILENO,str,fileFD);// Write it to the standard output
     if(fileFD==-1)
     {
     perror("error");
     exit(1);
     }
   }
     break;    

I use the parameters argv and argc to keep track of the arguments. In both cases argc =1 because anything on the right of < is not taken into account. How would you do this?

daniel
  • 557
  • 1
  • 4
  • 15
  • 7
    `while(true==0)` is all kinds of weird – StoryTeller - Unslander Monica Apr 09 '17 at 06:25
  • IS there while(true) in C?. I need to clean up my code. I will do it later. Assume it's just a while true and somehow I need to break the loopp in the second case. – daniel Apr 09 '17 at 06:26
  • And you don't need to "handle" case three. The shell sets up the stream for you correctly. Reading simply from `stdin` will pull the contents of file1. – StoryTeller - Unslander Monica Apr 09 '17 at 06:27
  • 2
    What are you actually trying to distinguish? That `stdin` and/or `stdout` are redirected? Or that one or the other is coming from a disk file instead of a terminal? – wallyk Apr 09 '17 at 06:28
  • 1
    There's `stdbool.h`. But if you want an infinite loop, my personal choice is `for(;;)` – StoryTeller - Unslander Monica Apr 09 '17 at 06:28
  • 3
    Your program cannot directly tell which of the two invocations was used. In both command lines, the program is invoked with a single argument, the command name — the redirection operators and file names are not passed to the program by the shell. If you're sure that otherwise the input and output are the terminal, then you can use `isatty()` to check whether either standard input or standard output or both is a terminal. However, if the command is invoked in a shell script, those may have been redirected, even without the `<` or `>` notation at the point where the command is invoked. – Jonathan Leffler Apr 09 '17 at 06:31
  • 2
    Note that `./cat < /dev/tty >/dev/tty` and variations on that theme will confuse things further. – Jonathan Leffler Apr 09 '17 at 06:32
  • `fileFD=read(STDIN_FILENO,str,1000); str[fileFD]='\n';` is UB unless `str` have room for 1001 characters. – chux - Reinstate Monica Apr 09 '17 at 06:47
  • 2
    @daniel: You incorrectly think you must distinguish between these cases, but you don't. If you actually needed to distinguish between the cases it would not be a standard `cat` utility, should be named differently, and could use (require?) command line arguments to distinguish the cases. – Brendan Apr 09 '17 at 06:56
  • The problem is that when we execute cat, the program executes an infinite loop with an IO interface and the only way to skip that loop is by using CTRL C. If I don't implement the second case, it correctly outputs the content of the file, but it doesn't exit the IO interface in the terminal. The original cat command does it. – daniel Apr 09 '17 at 07:20
  • 1
    "only way to skip that loop is by using CTRL+C" no, it is not the only or the preferred way. You can signal end-of-file by typing CTRL+D on a separate line. You should check for it after `read` (the returned value should be 0 if end-of-file is reached). – n. m. could be an AI Apr 09 '17 at 07:31
  • `filieFD` is a confusing name, what's your rationale for choosing it? What is `isCaseThree`? Also `true=-1` gets you fired on the spot with extreme prejudice. – n. m. could be an AI Apr 09 '17 at 07:33
  • I think that the problem can be solved that way @n.m. I need to detect the end of the file, but I don't know how to do it. Check my approach. It doesn't work though. – daniel Apr 09 '17 at 07:38
  • 1
    Which word of **the returned value should be 0 if end-of-file is reached** are you stuck at? If in doubt, type `man 2 read`at your favourite text interface (Unix terminal and Google search both work well). – n. m. could be an AI Apr 09 '17 at 07:40
  • 1
    You get 0 bytes read on EOF (or negative, -1, on error). If you get number of bytes written != number of bytes attempted to be written, you can consider a retry to write what wasn't written, unless the amount written was 0 or negative, -1, in which case you've got problems and should probably abandon ship. There could be reasons to get 0 bytes written if you've played games (O_NONBLOCK, etc) with the output file descriptor, but you're not trying that sort of stuff yet (or you'd better not be). Similar comments apply to read descriptors and O_NONBLOCK, etc. – Jonathan Leffler Apr 09 '17 at 07:41
  • @n.m hahahaha. This is getting kind of funny. I will use file pointer. – daniel Apr 09 '17 at 07:44
  • You've got a dubious `exit(0);` and an equally dubious `break;` in the code shown. The `break;` doesn't break the `while (1)` loop — it breaks some surrounding loop (or switch). The `exit(0);` probably should simply be deleted. – Jonathan Leffler Apr 09 '17 at 07:46
  • Ok, that solved one problem. ./a.out < file.txt > file.txt successfully exits the IO interface, but the problem is that when I execute cat and press CTRL F, it doesn't detect the end of the file. – daniel Apr 09 '17 at 07:46
  • 1
    *"The problem is that when we execute cat, the program executes an infinite loop with an IO interface"* -- the original `cat` doesn't execute an infinite loop. It always reads until it reaches the end of the input file(s). On Unix, the EOF of the console input is marked by the `CTRL-D` keys combination. – axiac Apr 09 '17 at 07:46
  • What @axiac said, with the minor but mostly irrelevant caveat that you can use `stty` to change the key that indicates `EOF` to something other than Control-D, but to do so would probably be regarded as somewhat perverse. It is unlikely you've done it, so Control-D is the correct EOF indicator on Unix; the equivalent on Windows would be Control-Z (which does something completely different on Unix). – Jonathan Leffler Apr 09 '17 at 07:49
  • I hope now everyone likes the new code. CTRL F still doesn't exit the IO interface though. – daniel Apr 09 '17 at 07:53
  • O wait sorry it's CTRL D . – daniel Apr 09 '17 at 08:00
  • Note in a Windows console you should type enter, Ctrl+Z and then enter again. – n. m. could be an AI Apr 09 '17 at 08:05
  • The new loop looks almost OK. You should check for -1 first, then write. – n. m. could be an AI Apr 09 '17 at 08:07
  • 1
    The loop `while((fileFD=read(STDIN_FILENO,str,1000))!=0)` is almost there; you need `> 0` instead of `!= 0`, though. Negative numbers (actually, always `-1`) indicate errors, and `0` indicates EOF. You still need to capture the number of bytes written and do a separate (different) check on it. – Jonathan Leffler Apr 09 '17 at 08:45
  • What is Ctrl-F supposed to do/trigger in this context? – alk Apr 09 '17 at 09:43

3 Answers3

2

Given two file descriptors, one for input, one for output, the following function copies the data from the input to the output reasonably reliably.

#include <unistd.h>

/* Copy file from input file descriptor ifd to output file descriptor ofd */
extern int copy_file(int ifd, int ofd);

int copy_file(int ifd, int ofd)
{
    char buffer[65536];   // Resize to suit your requirements
    ssize_t i_bytes;
    while ((i_bytes = read(ifd, buffer, sizeof(buffer))) > 0)
    {
        ssize_t bytes_left = i_bytes;
        char *posn = buffer;
        ssize_t o_bytes;
        while ((o_bytes = write(ofd, posn, bytes_left)) > 0)
        {
            bytes_left -= o_bytes;
            posn += o_bytes;
        }
        if (o_bytes < 0)
            return -1;
    }
    if (i_bytes < 0)
        return -1;
    return 0;
}

A bare minimal test program might be:

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

…declaration or definition of file_copy()…

int main(void)
{
    int rc = EXIT_SUCCESS;
    if (copy_file(STDIN_FILENO, STDOUT_FILENO) != 0)
    {
        int errnum = errno;
        rc = EXIT_FAILURE;
        fprintf(stderr, "copy_file failed: (%d) %s\n", errnum, strerror(errnum));
    }
    return rc;
}

This correctly copies files from standard input to standard output. I've not definitively tested a 'short write'. It's a pretty rare scenario, and the most likely context is when the output file descriptor is a socket. Testing the error paths is harder, too. I called the program cf89. One test was:

$ (trap '' 13; ./cf89 < /dev/zero) | (sleep 1)
copy_file failed: (32) Broken pipe
$

The trap ignores SIGPIPE (13), so the writing blocks because nothing is reading the pipe. When the sleep exits, the writing process gets an error from the write() — because the signal is ignored — and it generates the error message.

As an aside, there are those who argue against reporting both the error number and the error message on security grounds — it tells the attacker too much about the system if they manage to trigger an error. I'm not in that camp; the attacker already knows the system type by the time they get to attacking a program such as this. It might be relevant in other contexts, such as perhaps a web server.

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
0

this command line:

cat < file1 > file2.txt

is simply redirecting stdin and stdout and the program needs to do nothing special.

However, if the command line were:

cat file1

Then the code needs to reopen() stdin to the file

and there may be a message to stderr if the reopen() fails

in either set of command line parameters,, all I/O would be from stdin and to stdout

user3629249
  • 16,402
  • 1
  • 16
  • 17
0

You forgot other cases. What about pipelines, like

date | ./cat | wc

perhaps you want to test if stdin is a pseudo tty (or not). Read the tty demystified first (with termios(3)...). Then consider using isatty(3) as isatty(STDIN_FILENO) to test that. But you should provide some option to override the test. See L. Serrni's deleted answer.

BTW, you might need to use poll(2). Read Advanced Linux Programming.

The question looks like some XY problem.

Community
  • 1
  • 1
Basile Starynkevitch
  • 223,805
  • 18
  • 296
  • 547