Reputation: 828
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
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
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