Logical error with writing to a file in C - c

I have a project I am working on, basically it requires me to read switches from the command line and reads and writes some output to the same file overwriting its previous content. If the file is not specified it reads from stdin and writes to stdout, here is a function to number all output lines:
int main(int argc,char *argv[]) {
FILE *fp;
if((fp = fopen(argv[2]),"a+")) == 0) {
fp = stdin; /* if we cant read the file then it is read from stdin*/
}
if((strcmp("-e",argv[1])) == 0){
if(fp == stdin) {
display_dollar(fp);
}
}
fclose(fp);
}
and this is my display_dollar function (the one that actually prints to stdout or fp)
void display_dollar(FILE *fp) {
char line[LINESIZE];
char lines[LINESIZE];
while(fgets(line,LINESIZE,fp)) {
sscanf(line,"%s\n",lines);
if(fp == stdin) {
printf("%s%c\n",lines,'$');
lines[0] = '\0'; /* "clear" buffer */
} else {
fprintf(fp,"%s%c\n",lines,'$');
lines[0] = '\0'; /* "clear" buffer */
}
}
}
This works perfectly for stdout but I am having trouble printing anything to the file specified by fp, I dont understand what I am doing wrong. Thanks!
Some suggested i take out the parantheses when I compare fp but I get an compiler warning when I compile it with
gcc -ansi -W -Wall -pedantic
The warning as follows:
assign2.c: In function ‘main’:
assign2.c:20:9: warning: assignment makes pointer from integer without a cast [enabled by default]
if(fp = fopen(argv[argc - 1],"a+") == 0) {
^
assign2.c:20:3: warning: suggest parentheses around assignment used as truth value [-Wparentheses]
if(fp = fopen(argv[argc - 1],"a+") == 0) {
^

Original code
Look very carefully at this line — it doesn't do what you want:
if((fp = fopen(argv[2]),"a+") == 0) {
^ ^ ^ ^
1 2 2 1
The parentheses aren't paired up as you intend. You're not passing enough arguments to fopen(). And you're comparing "a+" with 0.
You really want:
if ((fp = fopen(argv[2], "a+")) == 0) {
Revised code
In the revised code, you seem to have:
if(fp = fopen(argv[argc - 1],"a+") == 0) {
This is parsed as:
if (fp = (fopen(argv[argc - 1], "a+") == 0)) {
It is assigning the 0 or 1 result of comparing the return value from fopen() with 0, and that isn't a pointer, hence the complaint about assignment makes pointer from integer without a cast.
The second warning about suggest parentheses around assignment used as truth value is indicative of the problem; the result of the assignment is used as the condition, which is only sometimes correct (and this is one of the times when it isn't). It more usually appears in a context such as:
if (i = j)
where you probably intended:
if (i == j)
but if you really wanted to test the assignment, then you should write:
if ((i = j) != 0)
or (perish the thought — I hate it when people do this!):
if ((i = j))
Still having problems…
but it still won't write to the file,
You have a single read/write position in a file. You are reading from and writing to the same file, fp. If that is stdin, you've go one set of problems; if it is a regular text file, you have another set.
First of all, between a read and a write (or a write and a read) on a file opened for update, you must do a positioning operation — fseek() or rewind(), mainly. Secondly, because you opened the file in append mode, all writes occur at the end of the file. That means that when you try to read after a write, the position is at the end of the file (aka EOF).
If the file is standard input, that is often not seekable, which has implications, too. Standard input (or, at least, the underlying file descriptor 0 on Unix) is very often writable, but that's not something you're supposed (ab)use like that.
You either need to have an input file and an output file, or you need to be seeking appropriately.
And maybe you want "r+" instead of "a+" mode.

this code:
void display_dollar(FILE *fp) {
char line[LINESIZE];
char lines[LINESIZE];
while(fgets(line,LINESIZE,fp)) {
sscanf(line,"%s\n",lines);
if(fp == stdin) {
printf("%s%c\n",lines,'$');
lines[0] = '\0'; /* "clear" buffer */
} else {
fprintf(fp,"%s%c\n",lines,'$');
lines[0] = '\0'; /* "clear" buffer */
}
}
}
1) changes the number of characters in the next line, so the output file will be corrupted.
2) each read/write to the file moves the file-position-pointer.
you could use ftell() to see this happening.
3) the code must not corrupt the file, so it will have to write a new file.
4) this has a logic error, the %s will stop when any white space is encountered.
This includes newlines (of which there will (almost) always be one
or any tab or any space.
then appending a '$' will overlay something in the line.
therefore, the line to output will be corrupted.
the corruption could be a space or a NUL or a tab or a newline being overlayed
Suggestions:
1) When outputting to a file, always create a new file
2) always find the newline with strstr() (or similar), overlay the newline with "$\0",
then strcat() "\n\0"
this will mean modifying the display_dollar() declaration to include both the input and output file pointers

Alright, long story short I rewrote the code to have an input and output file, because things would get more complicated overwriting the current file in place without using a buffer.
Please let me know if there are any issues, or if things need to be explained.
#include <stddef.h>
#include <stdio.h>
#include <string.h>
#define LINESIZE 1024
void display_dollar(FILE *, FILE *);
int main(int argc, char *argv[]) {
FILE *infp = stdin;
FILE *outfp = stdout;
for (int i = 4; i < argc; ++i)
fprintf(stderr, "Unknown argument: %s\n", argv[i]);
if (argc >= 4 && (outfp = fopen(argv[3], "w")) == NULL)
outfp = stdout;
if (argc >= 3 && (infp = fopen(argv[2], "r")) == NULL)
infp = stdin;
if (argc >= 2 && strcmp("-e", argv[1]) == 0)
display_dollar(infp, outfp);
else
fprintf(stderr, "Usage: %s -e [infile [outfile]]\n", argc ? argv[0] : "program");
fclose(infp);
fclose(outfp);
return 0;
}
void remove_newlines(char *str) {
for(long i = strlen(str)-1; i >= 0 && (str[i] == '\n' || str[i] == '\r'); --i)
str[i] = '\0';
}
void display_dollar(FILE *infp, FILE *outfp) {
char line[LINESIZE];
while (fgets(line, LINESIZE, infp)) {
remove_newlines(line);
fprintf(outfp, "%s%c\n", line, '$');
}
}

Related

C - Why does my code break when removing unused variable declaration

I am writing a program in C to recover images from a raw file for CS50 and I am having a strange problem. I have a variable int cnt that I was using for debug purposes and I got the program to work so I was removing leftover debug code. But when I remove the cnt declaration I start outputting corrupt files.
Before removing line 25 below I was outputing .jpg files that I could open and view, then I removed the line, recompiled, deleted the photos from the last run, and reran the program on the same .raw data and the new files I got were unrecognized. So I put the declaration back in, recompiled, deleted the old photos, and ran the program again and got good files. Does anyone know why removing an unused declaration is messing with my results? The offending declaration is on line 25.
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
int main(int argc, char *argv[])
{
if (argc != 2)
{
printf("Usage: ./recover image\n");
return 1;
}
int filesFound = 0;
FILE *inFile = fopen(argv[1], "r");
FILE *outFile = NULL;
if (inFile == NULL)
{
printf("Image file could not be opened\n");
return 1;
}
uint8_t buffer[512];
int cnt = 0;
while (!feof(inFile))
{
fread(buffer, 512, 1, inFile);
// check for start of jpg file
if (buffer[0] == 0xff && buffer[1] == 0xd8 && buffer[2] == 0xff && (buffer[3] & 0xf0) == 0xe0)
{
// start of jpg was found
if (outFile != NULL)
{
// close the current file and then open a new file to write to
fclose(outFile);
outFile = NULL;
}
// open a file to write to
char fName[4];
sprintf(fName, "%03i.jpg", filesFound);
outFile = fopen(fName, "w");
filesFound++;
}
if (outFile != NULL){
// we have found data to write and opened a file
fwrite(buffer, 512, 1, outFile);
}
}
//Be sure to close my files
fclose(inFile);
if (outFile != NULL)
{
fclose(outFile);
}
return 0;
}
char fName[4] does not have sufficient room for the name generated by "%03i.jpg", so you are overrunning the buffer. Make it larger and use snprintf, not sprintf, and test the return value to detect errors:
int result = snprintf(fName, sizeof fName, "%03i.jpg", filesFound);
if (sizeof fName <= result)
{
fprintf(stderr, "Internal error, buffer is too small for file name.\n");
exit(EXIT_FAILURE);
}
Instead of printing an error, you could instead use the return value of snprintf, which indicates the length needed, to allocate memory for a larger buffer and then redo the snprintf with that buffer.
(Note that snprintf may return a negative result if an error occurs. Normally, this will become a large number upon conversion to size_t for the comparison, so it will trigger this error message. However, in a robust program, you might want to insert a separate test for result < 0.)

Inserting text into a txt file

I'm trying to accomplish the following:
I have a text file with the last printable character "]" in a separate line.
This line is not necessary to be the last line of the file. Some blank lines (line returns) can be there.
The purpose of the project is to insert new text before the line with "]".
The way I try to implement this is to search the file from the end of the file to find the line number with the character "]" (char_line).
Copy line by line from the original file rules1.txt to rules2.txt up to the line char_line. Next step is to append the new text with "]" at the end.
After that, I can delete the original file and rename the new file from rules2.txt to rules1.txt.
The problem I have is that the program finds the line with the character "]" and I can do a printf and see the correct line number.
I am assigning char_line = "%d".
When I'm using if(i < char_line) the file is copied all the way to EOF.
If I assign a numerical value, char_line = 23, the file is copied up to line 22, which is what I want.
This is the part of the code which should find line number for "]", copy line by line rules1.txt to rules2.txt up the line with "]".
#include <stdio.h>
int main(void) {
int end, loop, line;
char str[64];
FILE *file;
FILE *write;
int char_line;
int ret;
file = fopen("rules1.txt", "r");
if (file == NULL) {
printf("Failed to open file\n");
return -1;
}
int ch, line_num = 0;
do {
ch = fgetc(file);
if(ch == '\n')
line_num++;
} while (ch != EOF);
// last line doesn't end with a new line!
// but there has to be a line at least before the last line
if(ch != '\n' && line_num != 0)
line_num++;
fclose(file);
line = line_num-1;
start:
file = fopen("rules1.txt", "r");
if (file == NULL) {
printf("Failed to open file\n");
return -1;
}
for(end = loop = 0;loop<line;++loop){
if(0==fgets(str, sizeof(str), file)){//include '\n'
end = 1;//can't input (EOF)
break;
}
}
if(!end)
if (strncmp ("]", str, 1) == 0){
char_line ="%d";
goto next;
} else if(line >1){
line == line--;//WTF?
fclose(file);
goto start;
} else //What to do here?
next:
file = fopen("rules1.txt", "r");
write = fopen("rules2.txt", "w");
char linec [64]; /* line size */
int i = 0;
while (fgets(linec, sizeof(linec), file)) { /* read line from file */
i++;
if(i < char_line) {
fprintf (write , linec); /* write line to file */
}
}
fclose(file);
fclose(write);
return(0);
}
You have declared int char_line; and later you code
char_line ="%d";
This is nonsense. Since a literal string is some char[] array (better think of it as some constant array), decayed to a pointer, and assigning a pointer to an int does not make sense at all. On many machines (x86-64 notably), a pointer has 64 bits but an int has only 32 bits.
Please, compile your code with all warnings and debug info, so gcc -Wall -Wextra -g with GCC, improve your code to get no warnings, then use the debugger gdb.
Take several days to read some good books on C programming. Be aware and work hard to avoid undefined behavior. Use the debugger to run your program step by step and query its state to understand what is happening.
Read the documentation of every standard function you are using such as fgets.
You probably want a loop that reads every line, and copies sometimes that line to another file.

How do I write a program to swap a character in the input file with a character specified on the command line?

I'm trying to write a program to swap a character that I would specify on the command line (a command line argument) with a character in the input text file. The first command line argument is the character I want to change, the second argument is character that I want to replace the old character with, and the third argument is the input file.
When I do this, my program should generate an output file named: "translation.txt". I know that the problem with my program is in the "if" statements/the fprintf statements, but I'm not sure how to fix this. I was thinking of reading each character in the input file separately, and from there, I wanted to use "if" statements to determine whether or not to replace the character.
void replace_character(int arg_list, char *arguments[])
{
FILE *input, *output;
input = fopen(arguments[3], "r");
output = fopen("translation.txt", "w");
if (input == NULL)
{
perror("Error: file cannot be opened\n");
}
for (int i = 0; i != EOF; i++)
{
if (input[i] == arguments[1])
{
fprintf(output, "%c\n", arguments[2]);
}
else
{
fprintf(output, "%c\n", arguments[1]);
}
}
}
int main(int argc, char *argv[])
{
if (argc < 5)
{
perror("Error!\n");
}
replace_character(argc, argv);
}
Okay I think this can help:
#include <stdio.h>
int main(int argc, char** argv)
{
if (argc < 4) return -1; /* quit if argument list not there */
FILE* handle = fopen(argv[3], "r+"); /* open the file for reading and updating */
if (handle == NULL) return -1; /* if file not found quit */
char current_char = 0;
char to_replace = argv[1][0]; /* get the character to be replaced */
char replacement = argv[2][0]; /* get the replacing character */
while ((current_char = fgetc(handle)) != EOF) /* while it's not the end-of-file */
{ /* read a character at a time */
if (current_char == to_replace) /* if we've found our character */
{
fseek(handle, ftell(handle) - 1, SEEK_SET); /* set the position of the stream
one character back, this is done by
getting the current position using
ftell, subtracting one from it and
using fseek to set a new position */
fprintf(handle, "%c", replacement); /* write the new character at the new position */
}
}
fclose(handle); /* it's important to close the file_handle
when you're done with it to avoid memory leaks */
return 0;
}
Given an input specified as the first argument, it will seek a character to replace and then replace it with what is stored in replacement. Give it a try and let me know if it doesn't work. I run it like this:
./a.out l a input_trans.txt
My file has just the string 'Hello, World!'. After running this it's changed to 'Heaao, Worad!'.
Read up on ftell and fseek, as they're key here for what you need to do.
EDIT: Forgot to add an fclose statement that closes the file handle at the end of the program. Fixed!

Piping log output through a C program for easy log rotation

I'm trying to make it really easy to logrotate some of my apps that log via bash redirection. Basically, I have a C program that reads STDIN into a buffer. It reads this buffer, and whenever it encounters a newline, it will write the output it has gathered to a file.
The difference in this program is that it does not leave the file open. It opens it for appending each time a new line is encountered. This works great with the logrotate utility, but I'm wondering if there's some sort of horrible unforseen issue I'm not accounting for that I'll run into later on.
Is it better just to implement signal handling in this utility and have logrotate send it a SIGHUP? Are there horrible performance penalties to what I'm doing?
So normally where you'd do:
./app >> output.log
With the logger util you do:
./app | ./mylogger output.log
Although I'm too bad in C, I'm not very well versed in its best practices. Any guidance would be greatly appreciated.
Here's the source:
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#define BUFSIZE 1024
#define MAX_WRITE_FAILS 3
/**
* outputs the given content to the specified file.
*/
int file_output(char *filename, char *content, size_t content_length)
{
FILE *fp;
fp = fopen(filename, "a");
content[content_length + 1] = '\0';
if(fp == NULL) return errno;
fwrite(content, sizeof(char), content_length, fp);
fclose(fp);
return 0;
}
/**
* Loops over STDIN and whenever it finds a newline, sends the current content
* buffer to the file specified on the command line.
*/
int main(int argc, char *argv[])
{
int i;
char buffer[BUFSIZE];
char *content = malloc(sizeof(char) * BUFSIZE);
size_t content_size = 0;
int content_buf_size = BUFSIZE;
int write_failures = 0;
char *file;
if(argc < 2)
{
fprintf(stderr, "Usage: logger <file>");
exit(1);
}
file = argv[1];
// loop over STDIN
while(fgets(buffer, BUFSIZE, stdin))
{
int output_err;
int buflength = strlen(buffer);
// loop over character for character, searching for newlines and
// appending our buffer to the output content as we go along
for(i = 0; i < buflength; i++)
{
char *old = content;
// check if we have a newline or end of string
if(buffer[i] == '\n' || buffer[i] == '\0' || (i != (buflength - 1) && buffer[i] == '\r' && buffer[i+1] == '\n'))
{
content[content_size] = '\n';
output_err = file_output(file, content, content_size + 1);
if(output_err == 0)
{
// success! reset the content size (ie more or less resets
// the output content string)
content_size = 0;
write_failures = 0;
}
else
{
// write failed, try to keep going. this will preserve our
// newline so that the next newline we encounter will write
// both lines (this AND and the next).
content_size++;
write_failures++;
}
}
if(write_failures >= MAX_WRITE_FAILS)
{
fprintf(stderr, "Failed to write output to file %d times (errno: %d). Quitting.\n", write_failures, output_err);
exit(3);
}
if(buffer[i] != '\n' && buffer[i] != '\r' && buffer[i] != '\0')
{
// copy buffer into content (if it's not a newline/null)
content[content_size] = buffer[i];
content_size++;
}
// check if we're pushing the limits of our content buffer
if(content_size >= content_buf_size - 1)
{
// we need to up the size of our output buffer
content_buf_size += BUFSIZE;
content = (char *)realloc(content, sizeof(char) * content_buf_size);
if(content == NULL)
{
fprintf(stderr, "Failed to reallocate buffer memory.\n");
free(old);
exit(2);
}
}
}
}
return 0;
}
Thanks!
Since my suggestion in the comments turned out to be what you needed, I am adding it as an answer, with more of an explanation.
When you have a logging application which can not be told to close its logfile (usually via SIGHUP), you can use the 'copytruncate' option in your logrotate.conf.
Here is the description from the man page:
Truncate the original log file in place after creating a copy,
instead of moving the old log file and optionally creating a new
one, It can be used when some program can not be told to close
its logfile and thus might continue writing (appending) to the
previous log file forever. Note that there is a very small time
slice between copying the file and truncating it, so some log-
ging data might be lost. When this option is used, the create
option will have no effect, as the old log file stays in place.
Source: http://linuxcommand.org/man_pages/logrotate8.html

Looking for a string of characters copied to a buffer

I have an assignment that has asked me to copy a file using buffered i/o. It has multiple requirements:
Take one parameter and an optional second
Open the first parameter for reading
Open the second for writing
If there is no second parameter make a new file called prog1.out
Use a buffer size of 20 bytes
When copying the file, print any buffer starting with the characters "rwxr"
close all opened files before exiting.
The problem I'm having is with number six, I've looked around and can't figure this out. I've tried memchr but I don't think I'm on the right track. If anyone can help push me in the right direction I'd be grateful.
This is my code:
# include <stdlib.h>
# include <stdio.h>
int main(int argc, char *argv[])
{
FILE *readfile, *writefile;
char buffer[1024];
int fileSize;
int readResult;
int writeResult;
// making sure arguments exist
if (argc < 2|| argc > 3){
printf("This program takes either 1 or 2 arguments.\n");
exit(1);
}
//Opening file for reading
readfile = fopen(argv[1], "r");
if (!readfile) {
printf("Unable to open file %s.\n", argv[1]);
exit(1);
}
//finding the file size
fseek (readfile, 0, SEEK_END);
fileSize = ftell (readfile);
fseek (readfile, 0, SEEK_SET);
// read the file
readResult = fread(buffer, 20, fileSize/20, readfile);
if (readResult == 0) {
printf("A read error occured.\n");
exit(1);
}
//check to see if there is a second parameter (argument)
if (argc == 3) {
writefile = fopen(argv[2], "w");
if (!writefile) {
printf("Unable to open file %s.\n", argv[2]);
exit(1);
}
writeResult = fwrite(buffer, 20, fileSize/20, writefile);
if (writeResult != readResult) {
printf("A write error occured.\n");
exit(1);
}
printf("File %s successfully copied to %s.\n", argv[1], argv[2]);
}
else {
writefile = fopen("program1.out", "w");
if (!writefile) {
printf("Unable to open file program1.out\n");
exit(1);
}
writeResult = fwrite(buffer, 20, fileSize/20, writefile);
if (writeResult != readResult) {
printf("A write error occured.\n");
exit(1);
}
printf("File %s successfully copied to %s.\n", argv[1], "program1.out
}
fclose(readfile);
fclose(writefile);
exit(0);
}
There's the naive way:
if(buffer[0] == 'r' && buffer[1] == 'w'
&& buffer[2] == 'x' && buffer[3] == 'r') {
//do something
}
But take a look at strncmp(), which you can use for comparing parts of a string.
remember to first check if you have read at least 4 chars into the buffer. e.g. if the file is 21 bytes long, your 2. fread might only read 1 character, and you shouldn't compare against the other 3 chars in the buffer.
If you print out the buffer with e.g. printf or puts or any other function that expects a string, the buffer needs to end with a '\0' byte, otherwise the string functions doesn't know when to stop.
I'll first answer the question you actually asked: memcmp is a good way to compare two buffers. Some caveats:
You also have to make sure the size of the buffer is at least as large as the size of the target string
memcmp returns 0 if the two buffers match, which can be counter-intuitive.
So for example, if you wanted to see if a buffer is equal to the string "rwxw", you could write
if (readresult >= strlen("rwxw") && !memcmp(buffer, "rwxw", strlen("rwxw"))) {
// buffer match occurred!
}
Personally I would use a "#define" or const char to ensure that the three places where that string appear are actually the same string. For example:
#define MATCH_STRING "rwxw"
if (readresult >= strlen(MATCH_STRING) && !memcmp(buffer, MATCH_STRING, strlen(MATCH_STRING))) {
// buffer match occurred!
}
However there are a couple of other problems with your code. One is that you need a loop that continually reads from the input file and write from the output file until the input is exhausted. For example, along the lines of
while (true) {
readResult = fread(buffer, 20, 1, readfile);
if (readResult == 0) {
// end of file
break;
}
// put your check for the "rwxr" string here!
writeResult = fwrite(buffer, readResult, 1, writefile);
if (writeResult != readREsult) {
printf("error\n");
}
}
Finally, you have what might be called a "stylistic" bug. There are two cases in your program: a specified filename and a default filename. Those two cases share a lot of code in common, but you did a cut and paste. This makes the code harder to understand, and more prone to bugs if it's changed in the future. If you are cutting-and-pasting code you're doing something wrong! Consider instead something like this, which maximizes shared code paths:
char *outFileName;
if (argc == 3) {
outFileName = argv[2];
} else {
outFileName = "prog1.out";
}
writefile = fopen(outFileName, "w");
if (!writefile) {
printf("Unable to open file %s.\n", writeFileName);
exit(1);
}

Resources