Wednesday, January 23, 2008

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 -- %s\n", 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 -- %s\n", 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 -- %s\n", 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 file\n" );
 }
 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.

No comments:

Post a Comment