get_line(): An alternative implementation of standard C library's getline() function. Syntax: `char *get_line(int fd, int *error_num);` I use `K & R` style of programming so I declare/define all my variables at the top of the function. So, please don't give review comments on this. The code has been compiled using the following gcc flags: -Wall -Werror -Wextra -Wundef -Wunreachable-code -Winit-self -Wparentheses -Wconversion -Wsign-conversion -Wsign-compare -Werror-implicit-function-declaration -Wmissing-prototypes -Wmissing-declarations -Wformat-security The code is below: --------------------- get_lines_from_file.c --------------------- ``` #include "get_lines_from_file.h" #include <stdio.h> #include <stdlib.h> #include <unistd.h> /* * const char *get_error_string(int error): * * Function get_error_string() returns the error string corresponding to the * value in 'error' argument. * */ const char *get_error_string(int error) { switch (error) { case NO_ERROR: return "No error happened."; case INVALID_FD: return "Invalid fd. fd value is less than 0."; case NO_MEMORY: return "No memory available."; case NO_MORE_DATA: return "The file doesn't have any more data."; default: return "Invalid error number given."; } } // end of print_error_string /* * char *get_line(int fd, int *error_num): * * Function get_line() returns a line from the file represented by fd. * * If fd is less than 0 then NULL is returned and if error_num is not NULL, then * *error_num is assigned the appropriate error value (INVALID_FD in this case). * * In case of any error, if error_num is not NULL then *error_num is assigned * the appropriate error value. * * The high level algorithm of this function is: * * The function get_line() reads some bytes in a buffer from the file and * tries to find newline in the buffer. If a newline is not found then it * reads more bytes from the file in the buffer. When a newline is found in * the buffer, then the newline is replaced with null byte and the buffer * is reallocated to correct size. Then the file offset for reading is set * to the start of the next line. And then the buffer is returned to the user. * * Please note that the returned line/buffer doesn't contain any newlines. * * The line/buffer returned by this function is allocated using realloc(), so it is * user's responsibility to free the line (free memory). * */ char *get_line(int fd, int *error_num) { #define NEW_LINE '\n' #define BUF_SIZE_INCREMENT 256 // normally lines are not greater than 256 bytes. char *buf = NULL; char *buf_addr_holder = NULL; long index_to_load_data = 0; long curr_buf_size = 0; long num_chars_read = 0; ssize_t ret_val = -1; int end_of_file_reached_or_error = 0; int new_line_found = 0; long i = 0; if (error_num) *error_num = NO_ERROR; if (fd < 0) { if (error_num) *error_num = INVALID_FD; return NULL; } while (1) { #ifdef DEBUG_ON printf("%s: curr_buf_size before realloc = %ld.\n", __FILE__, curr_buf_size); #endif buf_addr_holder = realloc(buf, (size_t)(curr_buf_size + BUF_SIZE_INCREMENT)); if(!buf_addr_holder) { free(buf); #ifdef DEBUG_ON printf("%s:%d: buffer has been freed.\n", __FILE__, __LINE__); #endif if (error_num) *error_num = NO_MEMORY; return NULL; } curr_buf_size = curr_buf_size + BUF_SIZE_INCREMENT; buf = buf_addr_holder; index_to_load_data = num_chars_read; #ifdef DEBUG_ON printf("%s: curr_buf_size after realloc = %ld.\n", __FILE__, curr_buf_size); #endif // read data from file while (1) { ret_val = read(fd, buf + index_to_load_data, (size_t)(curr_buf_size - num_chars_read)); if (ret_val <= 0) { if (num_chars_read == 0) { if (error_num) *error_num = NO_MORE_DATA; if (buf) { free(buf); #ifdef DEBUG_ON printf("%s:%d: buffer has been freed.\n", __FILE__, __LINE__); #endif } return NULL; } else { end_of_file_reached_or_error = 1; break; } } num_chars_read = num_chars_read + ret_val; if (num_chars_read == curr_buf_size) break; } // end of inner while (1) if (num_chars_read > curr_buf_size) { printf("%s: Some bug in program. num_chars_read (%ld) is greater than" " curr_buf_size (%ld). Exiting..\n", __FILE__, num_chars_read, curr_buf_size); exit(1); } #ifdef DEBUG_ON if (num_chars_read < curr_buf_size) { printf("%s: num_chars_read (%ld) is less than curr_buf_size (%ld).\n", __FILE__, num_chars_read, curr_buf_size); } else if (num_chars_read == curr_buf_size) { printf("%s: num_chars_read (%ld) is equal to curr_buf_size (%ld).\n", __FILE__, num_chars_read, curr_buf_size); } #endif new_line_found = 0; for (i = index_to_load_data; i < num_chars_read; i++) { if (buf[i] == NEW_LINE) { buf[i] = 0; new_line_found = 1; break; } } // end of for loop if (new_line_found) { // realloc and seek buf_addr_holder = realloc(buf, (size_t)(i + 1)); #ifdef DEBUG_ON printf("%s: buffer reallocated from %ld bytes to %ld bytes.\n", __FILE__, curr_buf_size, i + 1); #endif if(!buf_addr_holder) { // strange, this should not happen free(buf); #ifdef DEBUG_ON printf("%s:%d: buffer has been freed.\n", __FILE__, __LINE__); #endif if (error_num) *error_num = NO_MEMORY; return NULL; } lseek(fd, (i + 1) - num_chars_read, SEEK_CUR); #ifdef DEBUG_ON printf("%s: Seeking %ld bytes from current read offset.\n", __FILE__, (i + 1) - num_chars_read); #endif return buf_addr_holder; } else { // new line not found // new line not found and end of file has been reached. // so, allocate one extra byte for terminating null byte and return. if (end_of_file_reached_or_error) { #ifdef DEBUG_ON printf("%s: Newline not found in buffer but end of file reached. curr_buf_size" " = %ld, num_chars_read = %ld.\n", __FILE__, curr_buf_size, num_chars_read); if (num_chars_read == curr_buf_size) { printf("%s: -----THIS SHOULD NEVER HAPPEN-----. NEWLINE NOT FOUND IN BUFFER BUT" " END OF FILE REACHED AND CURR_BUF_SIZE AND NUM_CHARS_READ ARE EQUAL" " (%ld).\n", __FILE__, curr_buf_size); } #endif buf_addr_holder = realloc(buf, (size_t)(num_chars_read + 1)); if(!buf_addr_holder) { free(buf); #ifdef DEBUG_ON printf("%s:%d: buffer has been freed.\n", __FILE__, __LINE__); #endif if (error_num) *error_num = NO_MEMORY; return NULL; } buf_addr_holder[num_chars_read] = 0; return buf_addr_holder; } } // end of if - else (new_line_found) #ifdef DEBUG_ON printf("%s: Newline not found in buffer, reading more data. curr_buf_size" " = %ld, num_chars_read = %ld.\n", __FILE__, curr_buf_size, num_chars_read); #endif } // end of outer while (1) } // end of get_line ``` --------------------- get_lines_from_file.h --------------------- ``` #ifndef GET_LINES_FROM_FILE_H #define GET_LINES_FROM_FILE_H enum { NO_ERROR = 0, // no error happened. INVALID_FD = -1, // fd is less than 0. NO_MEMORY = -2, // memory not available. NO_MORE_DATA = -3, // file has no bytes/characters. }; /* * char *get_line(int fd, int *error_num): * * Function get_line() returns a line from the file represented by fd. * * If fd is less than 0 then NULL is returned and if error_num is not NULL, then * *error_num is assigned the appropriate error value (INVALID_FD in this case). * * In case of any error, if error_num is not NULL then *error_num is assigned * the appropriate error value. * * The high level algorithm of this function is: * * The function get_line() reads some bytes in a buffer from the file and * tries to find newline in the buffer. If a newline is not found then it * reads more bytes from the file in the buffer. When a newline is found in * the buffer, then the newline is replaced with null byte and the buffer * is reallocated to correct size. Then the file offset for reading is set * to the start of the next line. And then the buffer is returned to the user. * * Please note that the returned line/buffer doesn't contain any newlines. * * The line/buffer returned by this function is allocated using realloc(), so it is * user's responsibility to free the line (free memory). * */ char *get_line(int fd, int *error_num); /* * const char *get_error_string(int error): * * Function get_error_string() returns the error string corresponding to the * value in 'error' argument. * */ const char *get_error_string(int error); #endif ``` -------------------------- test_get_lines_from_file.c -------------------------- ``` #include "get_lines_from_file.h" #include <stdio.h> #include <stdlib.h> #include <sys/stat.h> #include <fcntl.h> static void print_usage_and_exit(void) { printf("\n"); printf("Usage: test_get_lines_from_file file_name\n"); printf("\n"); exit(1); } // print_usage_and_exit int main(int argc, char * argv[]) { int fd = -1; char *line = NULL; int error_num = 0; if (argc != 2) { printf("\nOnly two arguments are required. Program name and file name.\n"); print_usage_and_exit(); } fd = open(argv[1], O_RDONLY); if (fd < 0) { printf("\n%s: File \"%s\" could not be opened. Exiting..\n", __FILE__, argv[1]); exit(1); } while ((line = get_line(fd, &error_num)) != NULL) { printf("%s\n", line); free(line); } if ((error_num != NO_ERROR) && (error_num != NO_MORE_DATA)) { printf("%s\n", get_error_string(error_num)); } } // end of main ```