rf.c revisited

Got bored, so I revisited an old program I wrote like last year. One of the very first ones I set out to write in C in fact. I admit there is no practical need for this program haha. The only reason I wrote it is that I found it some one annoying that the cat program was made to concatenate files and print them to stdout. But is all so often just used to print out a lone file when less -F does the same thing. For the fun of it I basically added the ability to mimic the head and tail commands with an implicit number of lines to print (e.g. like head -n / tail -n ) while still keeping to the basic function of outputing a lone file, later I made it handle multiple files just for fun.

I was remembering today the sfalloc() macro I had defined early on. Some thing like

#define sfalloc( _type, _ptr ) 
(_type *)malloc( sizeof(_ptr) )

The main point of it was to ensure I wrote sizeof(foo) instead of sizeof(int) or some thing. And to get it to compile with a C++ compiler. I was thinking about making a new version of it for one of my header files that would use the cast only if __cplusplus was defined or better yet the new operator instead for use in code I would want to be compatible with both C and C++ but why bother for such a small program? Althouhg it would be a good spot to test it out in if I did hehe. Besides MSVC++ is a pain in the ass any way for compiling C99 code (imho).

My favorite non standard extensions to the C standard library on BSD and GNU systems is deffo err(), errx(), warn(), warnx(), and related routines in err.h namely because they are major time savers imho. Since a unix like system is assumed unistd is included and getopt(3) used for parsing argv rather then doing it manually.

I didn’t use the various linked list macros provided by BSD boxes because I wanted to avoid them, in case I ever decided to build the app on Windows or DOS. It also was the first time I ever used a linked list in a C program so I wanted to give it a go hehe. I’m really glad I did to because it was a really good excuse to learn how to use the GNU Debugger at the time 😉

One of the changes I made today was ensuring it would compile fine without assuming a C99 environment, I also ran it by gcc42 with -std=c89 -ansi and -pedantic-errors in the hopes of catching any thing I might’ve visually missed.

the ‘final’ version of the old program

/*-
* Copyright (C) 2007
* [my name ]. All rights reserved.
*
* permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/


#if __STDC_VERSION__ >= 199901L
/* inline functions were added in C99 */
#else
#define inline /* protect me */
#endif

#define MALLOC_FAILED "Unable to initialize memory at __LINE__ n"

/* magic number for debugging read_bottom() and clean_up() */
#define NOMEM ((struct lnpos *)0xDEADBEEF)

#include <err.h>
#include <errno.h>
#include <limits.h>
#include <locale.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

/* For storing end of line characters */
typedef struct lnpos {
long eol;
struct lnpos *next;
struct lnpos *prev;
} LNPOS;


static inline FILE *open_file( char * );
static inline void read_all( FILE *, int );
static void read_top( FILE *, int );
static void read_bottom( FILE *, int );
static inline void clean_up( LNPOS * );
static inline void be_verbose( char * );
static inline void usage( void );


static const char *this_progname;


/*
* rf - read file to standard out v2.0 -- see changes.log for details
*/
int
main( int argc, char *argv[] ) {

char *errp;
int b_flag = 0, s_flag = 0, t_flag = 0, v_flag = 0;
int ch, f, lncnt = 0;
FILE *fp;


setlocale( LC_ALL, "" );
this_progname = argv[0];

while ( (ch = getopt( argc, argv, "b:st:v" )) != -1 ) {
switch ( ch ) {
case 'b':
/* Mimic tail(1) -n */
b_flag++;
lncnt = strtol( optarg, &errp, 10 );
if ( *errp || lncnt <= 0 ) {
errx( EXIT_FAILURE,
"Improper line count -- %sn", optarg );
}
break;
case 's':
/* Suppress -v when given multiple files. */
s_flag++;
v_flag = 0;
break;
case 't':
/* Mimic head(1) -n */
t_flag++;
lncnt = strtol( optarg, &errp, 10 );
if ( *errp || lncnt <= 0 ) {
errx( EXIT_FAILURE,
"Improper line count -- %sn", optarg );
}
break;
case 'v':
/* Append file names -- be_verbose() */
v_flag++;
break;
case '?':
default:
usage();
/* NOTREACHED */
}
}
argc -= optind;
argv += optind;

if ( argc < 1 ) {
usage();
}

/* Handle multiple files */
for ( f = 0; f < argc; f++ ) {
fp = open_file( argv[f] );

if ( (!t_flag) && (!b_flag) ) {
read_all( fp, lncnt );
} else if ( t_flag ) {
read_top( fp, lncnt );
} else if ( b_flag ) {
read_bottom( fp, lncnt );
} else {
usage();
/* NOTREACHED */
}

if ( (v_flag != 0) || (argc > 1) ) {
/* Don't suppress if not set */
if ( s_flag == 0 ) {
be_verbose( argv[f] );
v_flag++;
} else {
v_flag = 0;
}
}

fclose( fp );
}

return EXIT_SUCCESS;
}


/* Simple fopen wrapper to keep the if...else if...else blockage from getting
* ugly. Since doing other wise would defeat the purpose of it in this program.
* open_file() halts the program if fopen failed.
*/
static inline FILE *
open_file( char *arg ) {

FILE *fto;

fto = fopen( arg, "r" );
if ( fto == NULL ) {
errx( EXIT_FAILURE, "File can not be opened or"
" does not exist -- %sn", arg );
}

return fto;
}


/*
* print out an open file to standard output
*/
static inline void
read_all( FILE *fp, int lncnt ) {

while ( (lncnt = fgetc( fp )) != EOF ) {
printf( "%c", lncnt );
}
}

/*
* Read n lines from the top of the file.
*/
static void
read_top( FILE *fp, int lncnt ) {

int c = 0;
int f = 0;

while ( (c < lncnt) && (f != EOF ) ) {
f = fgetc( fp );
printf( "%c", f );
if ( f == 'n') {
c++;
}
}
}


/*
* Read n lines from the bottom of the file
*/
static void
read_bottom( FILE *fp, int lncnt ) {

int fin = 0, i;
long int eolnum = 0;
long int where;
struct lnpos *cur = NULL;
struct lnpos *root = NULL;
struct lnpos *last = NULL;

/* initiate the linked list */

root = malloc( sizeof(root) );
root->eol=0;
if ( root == NULL ) {
err( EXIT_FAILURE, MALLOC_FAILED );
}
root->next = NOMEM;
root->prev = NOMEM;
cur = root;

cur->next = malloc( sizeof(cur->next) );
cur->next->eol = 0;
if ( cur->next == NULL ) {
err( EXIT_FAILURE, MALLOC_FAILED );
}
cur->next->prev = cur;
cur->next->next = NOMEM;
cur = cur->next;


/*
* read the file, count every end of line and store them in a new
* member of our linked list.
*/
while ( (fin = fgetc( fp )) != EOF ) {
if ( fin == 'n' ) {
eolnum++;
cur->eol = ftell( fp );
cur->next = malloc( sizeof(cur->next) );
cur->next->eol = 0;
if ( cur->next == NULL ) {
err( EXIT_FAILURE, MALLOC_FAILED );
}
cur->next->prev = cur;
cur->next->next = NOMEM;
cur = cur->next;
}
}

/* double check last nodes prev is up to date before marking the end */

cur->next = malloc( sizeof(cur->next) );
cur->next->eol = 0;
if ( cur->next == NULL ) {
err( EXIT_FAILURE, MALLOC_FAILED );
}
cur->next->prev = cur;
cur->next->next = NOMEM;
cur = cur->next;
last = cur;

/* print out the rest of file from the given offset. */
for ( i = 0; i < lncnt; ) {
if ( cur->prev != NOMEM ) {
if ( cur->eol ) {
i++;
}
cur = cur->prev;
}
}


where = fseek( fp, cur->eol, SEEK_SET );
if ( where != 0 ) {
err( EXIT_FAILURE, "Could not seek through the filen" );
}
read_all( fp, lncnt );
clean_up( root );

}

static inline void
clean_up( LNPOS *root ) {

/* Free linked lists memory */
struct lnpos *cur = root;
while ( cur->next != NOMEM ) {
cur = cur->next;
if ( cur->prev != NOMEM ) {
free( cur->prev );
cur->prev = NOMEM;
}
}
free( cur ); /* free the end node */
}

static inline void
be_verbose( char *fname ) {

printf( "n==> %s <==n", fname );
}

static inline void
usage( void ) {

fprintf( stderr, "usage: %s [-t count | -b count] "
"[-v] [file ...]n", this_progname );
exit( EXIT_FAILURE );
}

The simple makefile compiles it:

gcc -Wall -Wpointer-arith -Wcast-qual -Wcast-align -Wconversion
-Waggregate-return -Wstrict-prototypes -Wmissing-prototypes
-Wmissing-declarations -Wredundant-decls -Winline -Wnested-externs
-std=c99 -march=i686

and Makefile.optimize adds the -fforce-mem -fforce-addr -finline-functions -fstrength-reduce -floop-optimize -O3 options for when testing is done, program works fine. FreeBSD’s lint implementation also doesn’t spew any serious messages.