JSON Brody
JSON Brody

Reputation: 828

fprintf not writing on ^C but is writing on exit

I am writing a terminal shell language in C called tsh (tech shell). The shell has to be able to handle output redirection (">"). I am using fprintf with fopen to accomplish this. The issue looks similar to the following:

If I use the redirection as it in my shell with the exit command:

tsh$ pwd > out.txt
tsh$ exit

bash$ cat out.txt
/tmp
bash$

But if I do the same thing with ctrl+c instead of exit:

tsh$ pwd > out.txt
tsh$ ^C

bash$ cat out.txt
bash$

Which means that the file gets opened but when I stop the process the output of tsh's pwd doesn't get written for some reason.

tsh$ pwd does, however, print to stdout just fine if there is no redirection.

The reason I cat the file in bash is because tsh doesn't support non-built-in commands (yet), but considering that the content doesn't get written until I exit my shell, I don't think it would work even if I could cat from my shell.

Also, if the out.txt does not exist yet, it does get created in both cases, but still only gets written to in the first. In short, I have no issues obtaining a reference to the file, just writing to it.

This is the current source for tsh.c, compiled with gcc:

#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>

const char EXIT[4] = "exit";
const char PWD[3] = "pwd";
const char * DELIM = " \n\r\t";

FILE * outfile, * infile;

char cwd[1024]; //directory to print at beginning of each line

char in[1024]; //user input
char * token;

int main()
{

    while(1)
    {

        outfile = stdout;   
        infile = stdin;

        getcwd(cwd, sizeof(cwd));
        printf("%s$ ",cwd); //prompt with cwd       

        fgets(in, sizeof(in), stdin);

        char * fileref;
        if((fileref = strstr(in,">")))
        {

            fileref = strtok(++fileref, DELIM);
            outfile = fopen(fileref, "w");

        }

        token = strtok(in, DELIM);
        if (token != NULL)
        {
            if(!strncmp(token, EXIT, sizeof(EXIT)))
            {

                token = strtok(NULL, DELIM);
                if (token == NULL)
                {
                    printf("Exiting with status code 0.\n");
                    exit(0);
                }
                else if (isdigit(*token))
                {
                    int code = *token - '0';
                    printf("Exiting with status code %d\n",code);
                    exit(code);
                }
            }
            else if(!strncmp(token, PWD, sizeof(PWD)))
            {
                fprintf(outfile, "%s\n",cwd); //This should get written to the file if I provide a redirection
                continue;
            }

            fputs("\n", outfile);

        }

        fclose(outfile);

    }

}

Is there some kind of built-in rollback mechanism that is preventing file writing if the process doesn't exit cleanly? Do I have to intercept and override the ctrl+c signal?

I am aware that there are many problems and bad practices here, as this is my first experience with C, but I would appreciate it if suggestions could be limited to the specific problem I am experiencing.

Thanks in advance.

Upvotes: 1

Views: 626

Answers (2)

Francesco Lavra
Francesco Lavra

Reputation: 889

When you call exit(), you are causing your process to terminate in a clean way, which among other things makes sure any open files are correctly closed (thus any buffered output is flushed).

On the other hand, when you press CTRL+C you are sending a SIGINT to your process, and the default behavior of a process when receiving such signal is to terminate; but this is not a clean process termination (it's similar to what happens when you get a segmentation fault), so any open files are not correctly closed, and if there is any buffered output it will be lost.

In your application, you will be able to resolve the issue you described by fixing a logical error in the code: inside the main while() loop, you are calling fopen() when you encounter the '>' character in the user input, but when you execute the 'pwd' command you don't close the file stream returned by fopen(), and at the next iteration of the while() loop you overwrite the file stream pointer with stdout, effectively leaking an open file. You should instead make sure to close the file before beginning the next iteration of the loop. Like below:

while(1)
{

    outfile = stdout;
    infile = stdin;

    getcwd(cwd, sizeof(cwd));
    printf("%s$ ",cwd); //prompt with cwd

    fgets(in, sizeof(in), stdin);

    char * fileref;
    if((fileref = strstr(in,">")))
    {

        fileref = strtok(++fileref, DELIM);
        outfile = fopen(fileref, "w");

    }

    token = strtok(in, DELIM);
    if (token != NULL)
    {
        if(!strncmp(token, EXIT, sizeof(EXIT)))
        {

            token = strtok(NULL, DELIM);
            if (token == NULL)
            {
                printf("Exiting with status code 0.\n");
                exit(0);
            }
            else if (isdigit(*token))
            {
                int code = *token - '0';
                printf("Exiting with status code %d\n",code);
                exit(code);
            }
        }
        else if(!strncmp(token, PWD, sizeof(PWD)))
        {
            fprintf(outfile, "%s\n",cwd); //This should get written to the file if I provide a redirection
        }
        else
        {
            fputs("\n", outfile);
        }
    }
    if(fileref)
    {
        fclose(outfile);
    }
}

Upvotes: 1

aeliton
aeliton

Reputation: 361

^C is sending the KILL signal to your program (KILL). You should use ^D to end the input instead (EOF).

   fgets() returns s on success, and NULL on error or when end of file occurs while no characters have been read.

So you have to use: while (fgets(in, sizeof(in), stdin)) {}

Upvotes: 0

Related Questions