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
Related
I have read that I can use fopen to read a file line by line, but I want to access the file from the terminal as such.
This is what I have tried:
$ ./myprogram < input.txt > output.txt
I'm not sure if there's a way to do it with scanf or another way?
Here, if you think about what you are doing, you are simply reading continually from stdin and writing the same bytes to stdout until you receive an EOF. While you can use a character oriented approach (e.g. getchar), a read with a fixed length buffer will dramatically cut down the number of reads and writes you have.
Simply declare a buffer of comfortable size, 1024 (or use the default BUFSIZ provided, generally 8192 on Linux and 512 on windoze). Then repeatedly call fgets reading a buffers worth of characters at a time and writing them back to stdout with fputs. That's about as simple as it gets.
#include <stdio.h>
#define BUFSZ 1024
int main (void) {
char buf[BUFSZ] = "";
while (fgets (buf, BUFSZ, stdin))
fputs (buf, stdout);
return 0;
}
Ideally, you would want a buffer size just longer than the longest line, although it really doesn't matter what size it is. You can read each line all at once, or in multiple calls to fgets. The only difference is the number of function calls made.
#include <stdio.h>
#define BUFSIZE 1024
int main(int argc, char *argv[])
{
char *line = (char *)malloc(BUFSIZE);
if (!line)
{
printf("malloc buffer failed...\n");
return 1;
}
memset(line, 0, sizeof(line));
FILE *fp;
FILE *writefp;
int c;
int count = 0;
int count_buf = BUFSIZE;
char scanf_answer;
if (argc != 3)
{
printf("./myprogram <input.txt> output.txt\n");
return 1;
}
fp = fopen(argv[1], "r");
for (;;)
{
c = getc(fp);
if (c == '\n')
{
printf("%s\n", line);
printf("<Did you want to write this line to [%s]?>", argv[2]);
scanf("%c", &scanf_answer);
if (scanf_answer == 'Y' || scanf_answer == 'y')
{
writefp = fopen(argv[2], "a+");
fprintf(writefp, "%s\n", line);
fclose(writefp);
}
memset(line, 0, sizeof(line));
}
else if (c == EOF)
{
printf("%s\n", line);
printf("<Did you want to write this line to [%s]?>", argv[2]);
scanf("%c", &scanf_answer);
if (scanf_answer == 'Y' || scanf_answer == 'y')
{
writefp = fopen(argv[2], "a+");
fprintf(writefp, "%s\n", line);
fclose(writefp);
}
printf("End of file\n");
break;
}
if (count >= count_buf)
{
line = realloc(line, BUFSIZE);
count_buf += BUFSIZE;
if (!line)
{
printf("realloc buffer failed...\s");
return 1;
}
count = 0;
}
sprintf(line,"%c%c", line, c);
++count;
}
fclose(fp);
return 0;
}
This code will print each line, you decide each line to write to the output.txt, and in the file end, it will print End of file
$ ./myprogram < input.txt > output.txt
The command you posted uses a shell feature called IO redirection to produce input on stdin from one file and redirect output to stdout to the other file.
To take lines as input to your program is super easy even for lines of arbitrary length if you can use POSIX getline(). Please consult the manpage (linked below) for details.
Here's an example:
#include <stdio.h>
#include <stdlib.h>
int main() {
// this is the buffer data is read to (including trailing newline)
char *buffer = 0;
// this will be set to the size of the buffer
size_t buffer_size = 0;
// this will be set to the number of bytes read
ssize_t bytes_read;
while ((bytes_read = getline(&buffer, &buffer_size, stdin)) != -1) {
// do something with line
printf("%s", buffer);
// the same buffer will be reused in the next loop iteration
}
// free buffer eventually
free(buffer);
return 0;
}
Possible output:
$ gcc test.c && ./a.out < test.c
#include <stdio.h>
#include <stdlib.h>
int main() {
[...]
Note that scanf() is for taking formatted input, which reading lines is not. I suggest you get to learn more about the different approaches to IO (on streams) here:
https://www.gnu.org/software/libc/manual/html_node/I_002fO-on-Streams.html#I_002fO-on-Streams
For reference:
http://man7.org/linux/man-pages/man3/getline.3.html
Full Edit:
I am getting frustrated, I don't know what am I doing wrong in here
I still have so many stuff to do in the code but I can't even open a file to continue my work.
#include <stdio.h>
#include <conio.h>
#include <stdlib.h>
void main()
{
char letter;
FILE *fp;
fp=fopen("C:\\Users\\LENOVO\\Desktop\\data.txt","r");
if(fp==NULL)
{
printf("error");
getch();
exit(1);
}
while(fscanf(fp,"%d",&letter)!=EOF)
putchar(letter);
getch();
fclose(fp);
}
Picture of the path: http://imgur.com/a/YwFYy
Still prints error
Ok, firstly let's take a look at your file path. There are two ways to acces a file from your local storage:
relative addresses if the file has the same root folder as your application
absolute addresses if the file is in a determined place on your machine's storage
I see that you are trying to use an absolute address to read from your file. Your path is correct but you have to take care about string formatting in C because the \ character could be interpreted as something else.
I would suggest to use this instead ( double back-slash )
input=fopen("C:\\Users\\LENOVO\\Desktop\\data.txt","r");
This will prevent string formatting interpretations.
Secondly, EOF is just a predefined macro constant and i think it is equal to -1 so your while(! (-1) ) code is not a good ideea for reading until the end of the file.
In order to read from a file until you reach the its end i would consider this property of fscanf() :
fscanf() returns EOF when it reaches the end of the file.
while(fscanf(input,"%ch",&letter) != EOF) {
putchar(letter);
}
This way of reading from a file should do the job.
To read everything from a text file and store its contents into a buffer:
First, you should count how many characters there are in the text file:
size_t get_file_len(FILE *fp)
{
size_t num = 0;
while (fgetc(fp) != EOF)
num++;
return (fseek(fp, 0, SEEK_SET) == 0 ? num : 0);
}
Then allocate memory for a buffer large enough and read all the characters:
char *load_text(const char *path)
{
char *buf = NULL;
FILE *fp = NULL;
size_t num = 0;
size_t i = 0;
int c = 0;
/* open the file in text mode */
fp = fopen(path, "r");
if (!fp)
return NULL;
/* if the file was empty or if an error occurred, return error */
if ((num = get_file_len(fp)) == 0) {
fclose(fp);
return NULL;
}
buf = malloc(num + 1);
if (!buf) {
fclose(fp);
return NULL;
}
while ((c = fgetc(fp)) != EOF)
buf[i++] = (char)c;
/* ensure that the string is null-terminated */
buf[i] = '\0';
fclose(fp);
return buf;
}
Also, in C, all escape sequences begin with a '\' (backslash), so if you wanted to write a backslash in a string or a char you should write it as a '\\' (double backslash):
input=fopen("C:\\Users\\LENOVO\\Desktop\\data.txt","r");
pretty simple here :
while(!feof(input)){
fscanf(input,"%c",&letter);
putchar(letter);
}
and remember to close file using fclose(input);
I am new to C and I am encountering a problem with stdin I cannot find a solution to. My program either reads from file(s) or reads from stdin if no arguments (files) are provided by the user.
If the user doesn't supply any arguments then it will automatically read from stdin. My program is supposed to take in input (from file or stdin) and then remove the blank lines from it.
My problem arises when the program reads from stdin. Every time the user types something then presses enter the program automatically out puts the results. When I'd prefer enter to just be a newline.
How can I make it so the program waits for the user to hit EOF instead of each enter?
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#define NUMCHARS 1024
int main(int argc, char** argv){
int good_file = 0;
if (argc <= 1) {
good_file++;
test(stdin);
}
FILE* files[argc - 1];
int i;
for (i = 0; i < argc - 1; i++) {
if ((files[i] = fopen(argv[i + 1], "r")) == NULL) {
continue;
} else {
good_file++;
test(files[i]);
}
}
if (!good_file) {
fprintf(stderr, "ERROR!\n");
}
}
int test(FILE *file) {
char buffer[NUMCHARS];
while (fgets(buffer, NUMCHARS, file) != NULL)
part2(buffer);
fclose(file);
}
int part2(char *buffer) {
if (!is_all_white(buffer)) {
fputs(buffer, stdout);
}
}
int is_all_white(char *s) {
while (*s) {
if (!('\n' == *s || '\t' == *s || ' ' == *s))
return 0;
s += 1;
}
return 1;
}
I appreciate any feedback!
It isn't an issue with stdin per se - if you want to wait to output your data, you will have to store it. You could write it to a file and read it back afterward. You could use malloc and realloc to store the data in memory. Mainly, if you don't want the data to output on every line, you need not to output it on every line.
Pre process stdin into a temp work file, this will then give you the control you require.
Use function mkstemp.
Be warned stdin is a pipe, where as the fopen files are probably disk files
I am a biology student and I am trying to learn perl, python and C and also use the scripts in my work. So, I have a file as follows:
>sequence1
ATCGATCGATCG
>sequence2
AAAATTTT
>sequence3
CCCCGGGG
The output should look like this, that is the name of each sequence and the count of characters in each line and printing the total number of sequences in the end of the file.
sequence1 12
sequence2 8
sequence3 8
Total number of sequences = 3
I could make the perl and python scripts work, this is the python script as an example:
#!/usr/bin/python
import sys
my_file = open(sys.argv[1]) #open the file
my_output = open(sys.argv[2], "w") #open output file
total_sequence_counts = 0
for line in my_file:
if line.startswith(">"):
sequence_name = line.rstrip('\n').replace(">","")
total_sequence_counts += 1
continue
dna_length = len(line.rstrip('\n'))
my_output.write(sequence_name + " " + str(dna_length) + '\n')
my_output.write("Total number of sequences = " + str(total_sequence_counts) + '\n')
Now, I want to write the same script in C, this is what I have achieved so far:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char *argv[])
{
input = FILE *fopen(const char *filename, "r");
output = FILE *fopen(const char *filename, "w");
double total_sequence_counts = 0;
char sequence_name[];
char line [4095]; // set a temporary line length
char buffer = (char *) malloc (sizeof(line) +1); // allocate some memory
while (fgets(line, sizeof(line), filename) != NULL) { // read until new line character is not found in line
buffer = realloc(*buffer, strlen(line) + strlen(buffer) + 1); // realloc buffer to adjust buffer size
if (buffer == NULL) { // print error message if memory allocation fails
printf("\n Memory error");
return 0;
}
if (line[0] == ">") {
sequence_name = strcpy(sequence_name, &line[1]);
total_sequence_counts += 1
}
else {
double length = strlen(line);
fprintf(output, "%s \t %ld", sequence_name, length);
}
fprintf(output, "%s \t %ld", "Total number of sequences = ", total_sequence_counts);
}
int fclose(FILE *input); // when you are done working with a file, you should close it using this function.
return 0;
int fclose(FILE *output);
return 0;
}
But this code, of course is full of mistakes, my problem is that despite studying a lot, I still can't properly understand and use the memory allocation and pointers so I know I especially have mistakes in that part. It would be great if you could comment on my code and see how it can turn into a script that actually work. By the way, in my actual data, the length of each line is not defined so I need to use malloc and realloc for that purpose.
For a simple program like this, where you look at short lines one at a time, you shouldn't worry about dynamic memory allocation. It is probably good enough to use local buffers of a reasonable size.
Another thing is that C isn't particularly suited for quick-and-dirty string processing. For example, there isn't a strstrip function in the standard library. You usually end up implementing such behaviour yourself.
An example implementation looks like this:
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#define MAXLEN 80 /* Maximum line length, including null terminator */
int main(int argc, char *argv[])
{
FILE *in;
FILE *out;
char line[MAXLEN]; /* Current line buffer */
char ref[MAXLEN] = ""; /* Sequence reference buffer */
int nseq = 0; /* Sequence counter */
if (argc != 3) {
fprintf(stderr, "Usage: %s infile outfile\n", argv[0]);
exit(1);
}
in = fopen(argv[1], "r");
if (in == NULL) {
fprintf(stderr, "Couldn't open %s.\n", argv[1]);
exit(1);
}
out = fopen(argv[2], "w");
if (in == NULL) {
fprintf(stderr, "Couldn't open %s for writing.\n", argv[2]);
exit(1);
}
while (fgets(line, sizeof(line), in)) {
int len = strlen(line);
/* Strip whitespace from end */
while (len > 0 && isspace(line[len - 1])) len--;
line[len] = '\0';
if (line[0] == '>') {
/* First char is '>': copy from second char in line */
strcpy(ref, line + 1);
} else {
/* Other lines are sequences */
fprintf(out, "%s: %d\n", ref, len);
nseq++;
}
}
fprintf(out, "Total number of sequences. %d\n", nseq);
fclose(in);
fclose(out);
return 0;
}
A lot of code is about enforcing arguments and opening and closing files. (You could cut out a lot of code if you used stdin and stdout with file redirections.)
The core is the big while loop. Things to note:
fgets returns NULL on error or when the end of file is reached.
The first lines determine the length of the line and then remove white-space from the end.
It is not enough to decrement length, at the end the stripped string must be terminated with the null character '\0'
When you check the first character in the line, you should check against a char, not a string. In C, single and double quotes are not interchangeable. ">" is a string literal of two characters, '>' and the terminating '\0'.
When dealing with countable entities like chars in a string, use integer types, not floating-point numbers. (I've used (signed) int here, but because there can't be a negative number of chars in a line, it might have been better to have used an unsigned type.)
The notation line + 1 is equivalent to &line[1].
The code I've shown doesn't check that there is always one reference per sequence. I'll leave this as exercide to the reader.
For a beginner, this can be quite a lot to keep track of. For small text-processing tasks like yours, Python and Perl are definitely better suited.
Edit: The solution above won't work for long sequences; it is restricted to MAXLEN characters. But you don't need dynamic allocation if you only need the length, not the contents of the sequences.
Here's an updated version that doesn't read lines, but read characters instead. In '>' context, it stored the reference. Otherwise it just keeps a count:
#include <stdlib.h>
#include <stdio.h>
#include <ctype.h> /* for isspace() */
#define MAXLEN 80 /* Maximum line length, including null terminator */
int main(int argc, char *argv[])
{
FILE *in;
FILE *out;
int nseq = 0; /* Sequence counter */
char ref[MAXLEN]; /* Reference name */
in = fopen(argv[1], "r");
out = fopen(argv[2], "w");
/* Snip: Argument and file checking as above */
while (1) {
int c = getc(in);
if (c == EOF) break;
if (c == '>') {
int n = 0;
c = fgetc(in);
while (c != EOF && c != '\n') {
if (n < sizeof(ref) - 1) ref[n++] = c;
c = fgetc(in);
}
ref[n] = '\0';
} else {
int len = 0;
int n = 0;
while (c != EOF && c != '\n') {
n++;
if (!isspace(c)) len = n;
c = fgetc(in);
}
fprintf(out, "%s: %d\n", ref, len);
nseq++;
}
}
fprintf(out, "Total number of sequences. %d\n", nseq);
fclose(in);
fclose(out);
return 0;
}
Notes:
fgetc reads a single byte from a file and returns this byte or EOF when the file has ended. In this implementation, that's the only reading function used.
Storing a reference string is implemented via fgetc here too. You could probably use fgets after skipping the initial angle bracket, too.
The counting just reads bytes without storing them. n is the total count, len is the count up to the last non-space. (Your lines probably consist only of ACGT without any trailing space, so you could skip the test for space and use n instead of len.)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char *argv[]){
FILE *my_file = fopen(argv[1], "r");
FILE *my_output = fopen(argv[2], "w");
int total_sequence_coutns = 0;
char *sequence_name;
int dna_length;
char *line = NULL;
size_t size = 0;
while(-1 != getline(&line, &size, my_file)){
if(line[0] == '>'){
sequence_name = strdup(strtok(line, ">\n"));
total_sequence_coutns +=1;
continue;
}
dna_length = strlen(strtok(line, "\n"));
fprintf(my_output, "%s %d\n", sequence_name, dna_length);
free(sequence_name);
}
fprintf(my_output, "Total number of sequences = %d\n", total_sequence_coutns);
fclose(my_file);
fclose(my_output);
free(line);
return (0);
}
What's the best way to read a file backwards in C? I know at first you may be thinking that this is no use whatsoever, but most logs etc. append the most recent data at the end of the file. I want to read in text from the file backwards, buffering it into lines - that is
abc
def
ghi
should read ghi, def, abc in lines.
So far I have tried:
#include <stdio.h>
#include <stdlib.h>
void read_file(FILE *fileptr)
{
char currentchar = '\0';
int size = 0;
while( currentchar != '\n' )
{
currentchar = fgetc(fileptr); printf("%c\n", currentchar);
fseek(fileptr, -2, SEEK_CUR);
if( currentchar == '\n') { fseek(fileptr, -2, SEEK_CUR); break; }
else size++;
}
char buffer[size]; fread(buffer, 1, size, fileptr);
printf("Length: %d chars\n", size);
printf("Buffer: %s\n", buffer);
}
int main(int argc, char *argv[])
{
if( argc < 2) { printf("Usage: backwards [filename]\n"); return 1; }
FILE *fileptr = fopen(argv[1], "rb");
if( fileptr == NULL ) { perror("Error:"); return 1; }
fseek(fileptr, -1, SEEK_END); /* Seek to END of the file just before EOF */
read_file(fileptr);
return 0;
}
In an attempt to simply read one line and buffer it. Sorry that my code is terrible, I am getting so very confused. I know that you would normally allocate memory for the whole file and then read in the data, but for large files that constantly change I thought it would be better to read directly (especially if I want to search for text in a file).
Thanks in advance
* Sorry forgot to mention this will be used on Linux, so newlines are just NL without CR. *
You could just pipe the input through the program tac, which is like cat but backwards!
http://linux.die.net/man/1/tac
I recommend a more portable (hopefully) way of file size determination since fseek(binaryStream, offset, SEEK_END) is not guaranteed to work. See the code below.
I believe that files should be at least minimally buffered at the kernel level (e.g. buffering at least one block per file by default), so seeks should not incur significant amount of extra I/O and should only advance the file position internally. If the default buffering is not satisfactory, you may try to use setvbuf() to speed up the I/O.
#include <limits.h>
#include <string.h>
#include <stdio.h>
/* File must be open with 'b' in the mode parameter to fopen() */
long fsize(FILE* binaryStream)
{
long ofs, ofs2;
int result;
if (fseek(binaryStream, 0, SEEK_SET) != 0 ||
fgetc(binaryStream) == EOF)
return 0;
ofs = 1;
while ((result = fseek(binaryStream, ofs, SEEK_SET)) == 0 &&
(result = (fgetc(binaryStream) == EOF)) == 0 &&
ofs <= LONG_MAX / 4 + 1)
ofs *= 2;
/* If the last seek failed, back up to the last successfully seekable offset */
if (result != 0)
ofs /= 2;
for (ofs2 = ofs / 2; ofs2 != 0; ofs2 /= 2)
if (fseek(binaryStream, ofs + ofs2, SEEK_SET) == 0 &&
fgetc(binaryStream) != EOF)
ofs += ofs2;
/* Return -1 for files longer than LONG_MAX */
if (ofs == LONG_MAX)
return -1;
return ofs + 1;
}
/* File must be open with 'b' in the mode parameter to fopen() */
/* Set file position to size of file before reading last line of file */
char* fgetsr(char* buf, int n, FILE* binaryStream)
{
long fpos;
int cpos;
int first = 1;
if (n <= 1 || (fpos = ftell(binaryStream)) == -1 || fpos == 0)
return NULL;
cpos = n - 1;
buf[cpos] = '\0';
for (;;)
{
int c;
if (fseek(binaryStream, --fpos, SEEK_SET) != 0 ||
(c = fgetc(binaryStream)) == EOF)
return NULL;
if (c == '\n' && first == 0) /* accept at most one '\n' */
break;
first = 0;
if (c != '\r') /* ignore DOS/Windows '\r' */
{
unsigned char ch = c;
if (cpos == 0)
{
memmove(buf + 1, buf, n - 2);
++cpos;
}
memcpy(buf + --cpos, &ch, 1);
}
if (fpos == 0)
{
fseek(binaryStream, 0, SEEK_SET);
break;
}
}
memmove(buf, buf + cpos, n - cpos);
return buf;
}
int main(int argc, char* argv[])
{
FILE* f;
long sz;
if (argc < 2)
{
printf("filename parameter required\n");
return -1;
}
if ((f = fopen(argv[1], "rb")) == NULL)
{
printf("failed to open file \'%s\'\n", argv[1]);
return -1;
}
sz = fsize(f);
// printf("file size: %ld\n", sz);
if (sz > 0)
{
char buf[256];
fseek(f, sz, SEEK_SET);
while (fgetsr(buf, sizeof(buf), f) != NULL)
printf("%s", buf);
}
fclose(f);
return 0;
}
I've only tested this on windows with 2 different compilers.
There are quite a few ways you could do this, but reading a byte at a time is definitely one of poorer choices.
Reading the last, say, 4KB and then walking back up from the last character to the previous newline would be my choice.
Another option is to mmap the file, and just pretend that the file is a lump of memory, and scan backwards in that. [You can tell mmap you are reading backwards too, to make it prefetch data for you].
If the file is VERY large (several gigabytes), you may want to only use a small portion of the file in mmap.
If you want to learn how to do it, here's a Debian/Ubuntu example (for other like RPM based distros, adapt as needed):
~$ which tac
/usr/bin/tac
~$ dpkg -S /usr/bin/tac
coreutils: /usr/bin/tac
~$ mkdir srcs
~$ cd srcs
~/srcs$ apt-get source coreutils
(clip apt-get output)
~/srcs$ ls
coreutils-8.13 coreutils_8.13-3.2ubuntu2.1.diff.gz coreutils_8.13-3.2ubuntu2.1.dsc coreutils_8.13.orig.tar.gz
~/srcs$ cd coreutils-8.13/
~/srcs/coreutils-8.13$ find . -name tac.c
./src/tac.c
~/srcs/coreutils-8.13$ less src/tac.c
That's not too long, a bit over 600 lines, and while it packs some advanced features, and uses functions from other sources, the reverse line buffering implementation seems to be in that tac.c source file.
FSEEKing for every byte sounds PAINFULLY slow.
If you've got the memory, just read the entire file into memory and either reverse it or scan it backwards.
Another option would be Windows memory mapped files.