Is it possible to traverse a file from bottom up? [C] - c

How can I traverse a file line by line going from the bottom up? For example, here is my code for traversing it from top to bottom:
void *readFileTB(char *str1)
{
int size = 1024;
char *buffer = malloc(size);
FILE *fp;
fp = fopen("testfile.txt", "r");
while(fgets(buffer, 1024, fp)) //get a line from a file
{
printf(buffer);
}
return 0;
}
If a file contains:
line1onetest
line2twotest
line3threetest
This function will print the following if executed:
line1onetest
line2twotest
line3threetest
How can I write a function that does the above but in the opposite direction so it outputs the following?
line3threetest
line2twotest
line1onetest
Any thoughts?

Line by line it's a little difficult. If we start with bytes, it's pretty simply: we first fseek to a little before the bottom:
if(fseek(fp, 256, SEEK_END) == -1) { /* handle error */ }
Since we've seeked 256 bytes before the end, we can read 256 bytes. Then we can seek back 256 more bytes, etc., until we hit the top of the file.
Now if you're trying to read lines of text, this can be tricky: you need to read a number of bytes at the end of the file and find the last newline character. If there is none, you didn't read enough and you need to read more. Once you've found it, your line starts there. To read the next line, you need to seek backwards again and don't go past your previous line start.

Here, I got a bit into it and coded the entire thing. I have no clue if it's any good, but at least you can get an idea of how it works. (I have a feeling there are better ways to do this)
To compile and use program:
$ gcc -Wall -o reverser main.c
Usage:
$ ./reverser text.txt
text.txt contents:
int main(int argc, char *argv[]){
if(argc != 2)
return 1;
print_rev_file(argv[1], 64);
return 0;
}
Results:
}
return 0;
print_rev_file(argv[1]);
return 1;
if(argc != 2)
int main(int argc, char *argv[]){
How to use in code:
main.c
#include <header.h>
int main(int argc, char *argv[]){
if(argc != 2)
return 1;
print_rev_file(argv[1], 64);
return 0;
}
header.h:
Note: It's bad form to use double underscore because many compilers use them.
#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <string.h>
///SUPPORT for get_line
size_t __file_size(FILE ** file); //It'd make sense to homogenize the parameters...
char * __get_line_copy(FILE ** file, size_t chunk_size, size_t position);
char * __get_line(int line_number, FILE ** file, size_t chunk_size);
size_t __nth_line(FILE ** file, int line_number, size_t chunk_size);
unsigned int __line_count(FILE ** file, size_t chunk_size);
#define file_size(x) __file_size(&x)
size_t __file_size(FILE ** file){
size_t old_pos = ftell(*file);
fseek(*file, 0, SEEK_END);
int file_size = ftell(*file);
fseek(*file, 0, old_pos);
return file_size;
}
char * __get_line_copy(FILE ** file, size_t chunk_size, size_t position){
int i;
char c;
char * buffer = malloc(chunk_size);
memset(buffer, 0, chunk_size);
size_t old_pos = ftell(*file);
fseek(*file, position, SEEK_SET);
for(i = 0; (i < chunk_size && (c = fgetc(*file)) != '\n' && !feof(*file)); i++){
*(buffer+i) = c;
}
*(buffer+chunk_size) = '\0';
fseek(*file, 0, old_pos);
return buffer;
}
#define FIRST 0
#define LAST -1
#define get_line(x, y, z) __get_line(x, &y, z);
char * __get_line(int line_number, FILE ** file, size_t chunk_size){
char * line = __get_line_copy(file, chunk_size, __nth_line(file, line_number, chunk_size));
return line;
}
size_t __nth_line(FILE ** file, int line_number, size_t chunk_size){
int i = 0, index;
size_t old_pos = ftell(*file);
fseek(*file, 0, SEEK_SET);
if(line_number > 0){
while(i <= line_number && !feof(*file)){
if(fgetc(*file) == '\n')
i++;
}
} else {
while(!feof(*file)){
if(fgetc(*file) == '\n')
i++;
}
index = i + (line_number+1);
fseek(*file, 0, SEEK_SET);
int i = 0;
while(i < index){
if(fgetc(*file) == '\n')
i++;
}
}
size_t position = ftell(*file);
fseek(*file, 0, old_pos);
return position;
}
#define line_count(x, y) __line_count(&x, y)
unsigned int __line_count(FILE ** file, size_t chunk_size){
int i = 1;
while(!feof(*file))
if(fgetc(*file) == '\n')
i++;
return i;
}
int print_rev_file(char * filename, size_t buffer){
FILE * file = fopen(filename, "r");
if(file == NULL){
return -1;
}
int i, lines = line_count(file, buffer);
for(i = 0; i < lines; i++){
char * line = get_line(LAST-i, file, buffer);
puts(line);
free(line);
}
return 0;
}
int main(int argc, char *argv[]){
if(argc != 2)
return 1;
print_rev_file(argv[1], 64);
return 0;
}

There's a utility in the GNU coreutils called tac that does exactly that.
You can view the source for it below.
http://git.savannah.gnu.org/gitweb/?p=coreutils.git;a=blob_plain;f=src/tac.c;hb=HEAD

Related

How to extract literally just 2 lines from a file

I am writing a program in C. I use low level functions like open, read, close. I have a file descriptor, etc, but I don't know how to print only the first 2 lines from a file that has e.g. 30 lines of text. how to do it?
you need to read a file into a string, iterate through the string, concat any character into the string variable, define a int variable for lines count, when lines reaches 2, break the loop
here’s an example how you can do it
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char *get_first_two_lines(char *file_name) {
FILE *file = fopen(file_name, "r");
fseek(file, 0, SEEK_END);
long size = ftell(file);
fseek(file, 0, SEEK_SET);
char *buffer = malloc(size);
fread(buffer, 1, size, file);
char *two_lines = calloc(1, sizeof(char));
unsigned int lines = 0;
for (int i=0;i<strlen(buffer);i++) {
if (lines == 2) break;
if (buffer[i] == '\n') {
if (lines < 1) {
two_lines = realloc(two_lines, (strlen(two_lines) + 2) * sizeof(char));
strcat(two_lines, (char []) {'\n', 0});
}
lines++;
continue;
}
two_lines = realloc(two_lines, (strlen(two_lines) + 2) * sizeof(char));
strcat(two_lines, (char []) {buffer[i], 0});
}
return two_lines;
}
int main(int argc, char *argv[]) {
char *first_two_lines = get_first_two_lines("file_name");
printf("%s", first_two_lines);
return 0;
}

Save a text file as a String in C

I want to save a text file as a String and then print it. Here's my code:
int lastLines(const char nameFileIn[], int nLines) {
char *text = 0;
FILE *inF; long length;
inF = fopen(nameFileIn, "r");
if (inF == NULL)
return -1;
fseek(inF, 0, SEEK_END);
length = ftell(inF);
fseek(inF, 0, SEEK_SET);
text = malloc(length + 1);
fread(text, length + 1, 1, inF);
fclose(inF);
text[length] = 0;
printf("%s", text);
free(text);
return 0; } /* lastLines */
int main(int argc, char** argv) {
char* nameFileIn = (argv[1]);
int nLines = atoi((argv[2]));
int num = lastLines(nameFileIn, nLines);
printf("\n%d\n", num);
return 1; } /* main */
Everything is alright 'till I add the second parameter in the command line (I'll need it in a postprocessing phase). At that time in my String there are a number of characters (equals to the number of lines in my file) which are not in my text file. Why?

Function to insert text line by line from a file into string array passed by pointer

I'm trying to create a function read_lines that takes a file *fp, a pointer to char** lines, and pointer to int num_lines. The function should insert each line of text into lines, and increase num_lines to however many lines the file has.
Its probably really simple but I've been trying to insert the text for several hours now.
This is what main.c would look like. Everything but read_lines is already defined and working.
int main(int argc, char* argv[]){
char** lines = NULL;
int num_lines = 0;
FILE* fp = validate_input(argc, argv);
read_lines(fp, &lines, &num_lines);
print_lines(lines, num_lines);
free_lines(lines, num_lines);
fclose(fp);
return 0;
}
This is one of my attempts at trying to append lines, but I couldn't figure it out.
read_lines.c
void read_lines(FILE *fp, char ***lines, int *num_lines) {
int i;
int N = 0;
char s[200];
for (i=0; i<3; i++)
{
while(fgets(s, 200, fp)!=NULL){N++;}
char strings[50][200];
rewind(fp);
fgets(s, 200, fp);
strcpy(lines[i],s);
}
}
I'd appreciate any help at solving this, thanks.
A solution (without headers and error checking for readability):
void read_lines(FILE *stream, char ***lines_ptr, size_t *num_lines_ptr) {
char **lines = NULL;
size_t num_lines = 0;
char *line = NULL;
size_t len = 0;
ssize_t nread;
while ((nread = getline(&line, &len, stream)) != -1) {
lines = lines == NULL
? malloc(sizeof(char*))
: realloc(lines, (num_lines+1)*sizeof(char*));
lines[num_lines] = malloc(nread+1);
memcpy(lines[num_lines], line);
++num_lines;
}
free(line);
*lines_ptr = lines;
*num_lines_ptr = num_lines;
}
The full solution:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// lines_ptr: Output. Initial value ignored. To be freed by caller on success.
// num_lines_ptr: Output. Initial value ignored.
// Returns: 0 on error (errno set). 1 on success.
int read_lines(FILE *stream, char ***lines_ptr, size_t *num_lines_ptr) {
char ***lines = NULL;
size_t num_lines = 0;
char *line = NULL;
size_t len = 0;
ssize_t nread;
while ((nread = getline(&line, &len, stream)) != -1) {
char **new_lines = lines == NULL
? malloc(sizeof(char*))
: realloc(lines, (num_lines+1)*sizeof(char*));
if (new_lines == NULL)
goto error;
lines = new_lines;
lines[num_lines] = malloc(nread+1);
if (lines[num_lines] == NULL)
goto error;
memcpy(lines[num_lines], line);
++num_lines;
}
if (ferror(stream))
goto error;
free(line);
*lines_ptr = lines;
*num_lines_ptr = num_lines;
return 1;
error:
for (size_t i=num_lines; i--; )
free(lines[i]);
free(lines);
free(line);
*lines_ptr = NULL;
*num_lines_ptr = 0;
return 0;
}
(You could save three lines by using the ..._ptr vars instead of setting them at the end, but is that really worth the readability cost?)
I find fgets hard to use and more trouble than it's worth. Here is a fgetc and malloc-based approach:
void read_lines(FILE *fp, char ***lines, int *num_lines) {
int c;
size_t line = 0;
size_t pos = 0;
size_t len = 64;
*lines = malloc(1 * sizeof(char*));
(*lines)[0] = malloc(len);
while ((c = fgetc(fp)) != EOF) {
if (c == '\n') {
(*lines)[line][pos] = '\0';
line++;
pos = 0;
len = 64;
*lines = realloc(*lines, (line+1) * sizeof(char*));
} else {
(*lines)[line][pos] = c;
}
pos++;
if (pos >= len) {
len *= 2;
(*lines)[line] = realloc((*lines)[line], len);
}
}
*num_lines = line+1;
}
I haven't checked this, so correct me if I made any mistakes. Also, in real code you would do lots of error checking here that I have omitted.
assuming you have allocated enough memory to lines, following should work
if not you have to malloc/calloc() for lines[i] before doing strcpy() in every
iteration of the loop.
void read_lines(FILE *fp, char ***lines, int *num_lines) {
int N = 0;
char s[200];
while(fgets(s, 200, fp)!=NULL){
N++;
strcpy((*lines)[N],s);
}
*num_lines = N; // update pointer with value of N which is number of lines in file
}

Can't encrypt long string with XOR C crypter

I wrote a C program to perform an XOR encryption,
my problem is that the program is not able to encrypt files with more than 24 characters.
The code:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#define BUF_SIZE 2
char* xor(char*, char*);
char* gen_key(size_t);
int main(int argc, char **argv) {
char *buffer = NULL,* encrypted_buffer = NULL;
size_t file_size;
char *key = gen_key(6);
char tmp_buffer[BUF_SIZE];
FILE *finput = NULL, *foutput = NULL;
finput = fopen("file.txt", "rb");
fseek(finput, 0, SEEK_END);
file_size = ftell(finput);
rewind(finput);
printf("File size : %d\n", (int)file_size);
buffer = (char*)malloc((file_size + 1) * sizeof(char));
memset(buffer, 0, sizeof(buffer));
while (!feof(finput)) {
memset(tmp_buffer, 0, sizeof(tmp_buffer));
fgets(tmp_buffer, sizeof(tmp_buffer), finput);
strcat(buffer, tmp_buffer);
}
printf("%s", buffer);
encrypted_buffer = xor(buffer, key);
free(buffer);
buffer = xor(encrypted_buffer, key);
printf("Encrypted : %s\n", encrypted_buffer);
printf("Decrypted : %s\n", buffer);
printf("EOF\n");
free(encrypted_buffer);
fclose(finput);
return 0;
}
char *gen_key(size_t length) {
srand(time(NULL));
const char charset[] = "AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz012345679";
const size_t charset_size = (sizeof(charset) - 1);
unsigned int i;
char *key = NULL;
key = (char*)malloc((length + 1) * sizeof(char));
memset(key, 0, sizeof(key));
for (i = 0; i < length; i++)
key[i] = charset[rand() % charset_size];
return key;
}
char *xor(char *file, char *key) {
unsigned int i;
char *xor = NULL;
xor = (char*)malloc(sizeof(file));
memset(xor, 0, sizeof(xor));
for (i = 0; i < strlen(file); i++)
*(xor + i) = *(file + i) ^ *(key + (i % strlen(key) - 1));
return xor;
}
And the output is :
File size : 55
ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklimnopqrstuvwxyz
Encrypted : A2#G8- M >7S$1!
Decrypted : ABCDEFGHIJKLMNOPQRSTUVWX!:!e!
EOF
There are multiple problems in your code:
The buffer size is very small: #define BUF_SIZE 2. You should use a reasonable size for the line buffer, such as 80 or 100.
in memset(buffer, 0, sizeof(buffer));, buffer is a pointer, so sizeof(buffer) is not the size of the array, just the size of the pointer. Use file_size + 1 in this case.
You make the same mistake in other places in your code: pass the size of the buffers instead of relying on the sizeof() operator.
while (!feof(f)) is always wrong: you could jut use fread to read the file in one step or read line by line with:
while (fgets(tmp_buffer, sizeof(tmp_buffer), finput)) {
...
Note that there is a major problem with your approach: the key is composed of letters and digits and the file is assumed to contain text. If the file contains one of the characters in the key at the appropriate position, xoring this character with the key byte will produce a null byte that will stop the output in printf() and that would also stop the decryption if you were to store it in an output file. You rightfully use binary mode ("rb") for the file stream, but you should also make no assumptions on the file contents and handle null bytes transparently.
Here is a modified version of your program:
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
unsigned char *xor(unsigned char *file, size_t size, const char *key);
char *gen_key(size_t length);
void print_buffer(const char *msg, unsigned char *buf, size_t size) {
printf("%s: ", msg);
for (size_t i = 0; i < size; i++) {
switch (buf[i]) {
case '\n':
printf("\\n");
break;
case '\\':
printf("\\\\");
break;
default:
if (buf[i] >= ' ' && buf[i] < 127)
putchar(buf[i]);
else
printf("\\%02x", buf[i]);
break;
}
}
printf("\n");
}
int main(int argc, char **argv) {
long file_size, nread, nwritten;
unsigned char *buffer, *encrypted_buffer, *decrypted_buffer;
char *key = gen_key(6);
FILE *finput = fopen("file.txt", "rb");
if (finput == NULL) {
fprintf(stderr, "cannot open file.txt: %s\n", strerror(errno));
return 1;
}
fseek(finput, 0, SEEK_END);
file_size = ftell(finput);
rewind(finput);
printf("File size: %ld\n", file_size);
buffer = calloc(file_size, sizeof(char));
nread = fread(buffer, 1, file_size, finput);
if (nread != file_size) {
fprintf(stderr, "error reading file.txt: read %ld bytes, expected %ld\n",
nread, file_size);
}
fclose(finput);
FILE *foutput = fopen("output.bin", "wb");
if (foutput == NULL) {
fprintf(stderr, "cannot open output.bin: %s\n", strerror(errno));
return 1;
}
encrypted_buffer = xor(buffer, nread, key);
nwritten = fwrite(encrypted_buffer, 1, nread, foutput);
if (nwritten != nread) {
fprintf(stderr, "error writing output.bin: wrote %ld bytes, expected %ld\n",
nwritten, nread);
}
fclose(foutput);
decrypted_buffer = xor(encrypted_buffer, nread, key);
printf("Key : %s\n", key);
print_buffer("Original ", buffer, nread);
print_buffer("Encrypted", encrypted_buffer, nread);
print_buffer("Decrypted", decrypted_buffer, nread);
if (!memcmp(decrypted_buffer, buffer, nread))
printf("OK\n");
free(decrypted_buffer);
free(encrypted_buffer);
free(buffer);
return 0;
}
char *gen_key(size_t length) {
const char charset[] = "AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz012345679";
const size_t charset_size = sizeof(charset) - 1;
char *key = (char*)calloc(length + 1, sizeof(char));
size_t i;
srand(time(NULL));
for (i = 0; i < length; i++)
key[i] = charset[rand() % charset_size];
return key;
}
unsigned char *xor(unsigned char *file, size_t size, const char *key) {
size_t i, keylen = strlen(key);
unsigned char *xor = calloc(size, sizeof(char));
for (i = 0; i < size; i++)
xor[i] = file[i] ^ key[i % keylen];
return xor;
}

C memory allocation with File input

Hello i have a problem with memory allocation,
1. open file
2. take lenght of text inside
3. make buffer in size of lenght (array[] ? malloc ?)
4. make operations on text in buffer.
5. close
it terminates when text any longer than 1xx characters i have no idea whats going on.
ps.attention! im learning and quality of this code can be bad
#include <stdio.h>
#include <stdlib.h>
void copy_to_buffer(FILE *fp, int length, char *buffer){
for(int i = 0; i < length; i++){
char c = fgetc(fp);
buffer[i] = c;
}
}
int length_of_text(FILE *fp) {
fseek(fp, 0L, SEEK_END);
int size = ftell(fp);
rewind(fp);
return size;
}
void char_counter(int length, char *buffer, int *charBuffer) {
int counts[128] = { 0 };
for (int i = 0; i < length; i++) {
counts[(int)(buffer[i])]++;
charBuffer[i] = counts[i];
}
for (int i = 0; i < 128; i++) {
charBuffer[i] = counts[i];
if(counts[i] != 0)
printf("%d.(%c) counted: %d times.\n", i,i, counts[i]);
}
}
/***********************************MAIN***********************************/
int main(int argc, char** argv) {
FILE *fp = fopen("tekst.txt" , "r");
int length = length_of_text(fp); //lenght of text
char *buffer = malloc(sizeof(char)*length); //buffer for text from file
if(buffer == NULL)
printf("error");
else
printf("alocated at = %p\n", &buffer);
int charBuffer[128] = {0}; // charcount buffer
buffer[length] = '\0'; // '\0' after last sign
copy_to_buffer(fp, length, buffer);
char_counter(length, buffer, charBuffer);
free(buffer);
fclose(fp);
return 0;
}
In this line
charBuffer[i] = counts[i];
you will overflow charBuffer[128] when the file size is >= 128, since i is indexing by up to the length of the file.
In your char_counter function you do
charBuffer[i] = counts[i];
in the first for loop but buffer is only defined to be 128 ints. If the text is longer than 128 characters this will cause a buffer overflow and a segmentation fault.
Remove that line and let the 2nd for loop do it.

Resources