Saturday, April 14, 2007

rf.c semi-final

Well to make a long story short. I found out that calling setlocale() made my program (rf) crash when ever I used to -b switch. But strangely on OpenBSD it ran perfectly fine.

Sure enough after 2 days of tracking I found a few bugs and the little blighter I've just squashed was just the one I expected to find. It was just a matter of how. Just a bad pointer, my guess is after the setlocale() call. It was pointing some where FreeBSD didn't like and without the call it wasn't enough to get ballocked at. Who cares how thats possible... as long as its fixed hehe. Well as best as _I_ can tell any way.

The projects grown and I've learned quite since the start. Both about working in C, using Makefiles, dealing with GCC, GDB in more depth, and groff e.t.c. For sear size we are at:

#wc [ lines | words | bytes | filename ] 
  16     121     778 COPYING
      25      68     735 Makefile.optimize
     255    1023    6517 rf.c
      11     114    3636 rf.o
      87     333    1819 rf.1
       9      68     404 tags
     403    1727   13889 total

I have 3 Makefiles, a generic makefile for building the application on i686. Makefile.debug that does likewise but includes debugging symbols. And Makefile.optimize that has a few extra flags with mostly optimization options in it (hence the name). Each also can do a 'make install' for me. All in all gcc gets called from Makefile.optimize with:

-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 -fforce-mem -fforce-addr -finline-functions -fstrength-reduce -floop-optimize

I made some other changes too. I rewrote parts of main, its still not pretty but a wrapper on fopen() I think makes it easier to dick with. I finally added a -v option to see the file name. I was trying to figure out how to make a variadic macro and stumbled onto the __FILE__ and __LINE__ additions to C hehehe. I also decided to have it clean up after its malloc'ing. AFAIK a programs memory is free()'d after it exits so I didn't bother. Now I did for completeness sake. clean_up() is called after handling the output done in read_bottom (which actually uses read_all() to printout the file). So that if for some odd reason theres really low memory or some thing and it takes *awhile* to handle malloc'ing and seeking semi-end-to-end through a linked-list. I figured it'd be a good 'just in case' even if its not likely.

I really would like to rewrite read_top() b/c I don't know of any comparable function to fgetln() on windows and a Macro that inlines a rough equivalent to my beloved err() function too. Which likewise is available from the BSD and GNU C Libraries and both are marked as 'first appeared in 4.4BSD' in the man pages. And AFAICT not available from Microsofts implementation.

One thing I need to do is learn more about groff and the man page macros and stuff. So far its mostly been a matter of reading my systems man pages and trial/error to get a fairly decent manpage.


I try to keep things small, my displays usually 80x25 or 80x35 if I'm working on Windows (GVIM) and 1 thing I really hate. Is a huge function thats so freaking big, you don't even remember the NAME of it by the time you hit the closing bra}e. So lol, I do my best not to write functions that long. At 60 some lines with comments I think read_bottom should be broken up into 2 smaller functions, the extra function call is negiable overhead I'm sure. I just think it would take longer to read. Really if you snip off the variable declarations and list startup (which could be function'ized) it almost fits on 1 screen. be_verbose man as well be a macro or inline function too. Although a compiler could take care of that choice it self I'm sure.

To be honest, I just like functions that are small, do whats asked, and avoid being swiss-army knifes.

/*-
 * Copyright (C) 2007
 *      Terry M. P*****. 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.
 */


// vim: set noexpandtab ts=8 sw=4 ai :
// vi: set ai nu ts=8 sw=4 :

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

struct lnpos {
        long nl;
        struct lnpos *next;
} lnpos = { .next = 0, .nl = 0 };

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


static const char *this_progname;

/*
 * rf - read file to standard out v1.27
 */

int
main( int argc, char *argv[] ) {

        char *erptr;
        int t_flag = 0, b_flag = 0, v_flag = 0;
        int ch, lncnt;
        FILE *fp;

 (void)setlocale( LC_ALL, "" );

        this_progname = argv[0];

        while ( (ch = getopt(argc, argv, "b:t:v")) != -1 ) {
                switch (ch) {
                case 'b':
                        b_flag++;
                        lncnt = strtol(optarg, &erptr, 10);
                        if ( *erptr || lncnt <= 0 )
                                errx( 1, "Improper line count -- %s", optarg );
                        break;
                case 't':
                        t_flag++;
                        lncnt = strtol(optarg, &erptr, 10);
                        if ( *erptr || lncnt <= 0 )
                                errx( 1, "Improper line count -- %s", optarg );
                        break;
                case 'v':
                        v_flag++;
                        break;
                case '?':
                default:
                        usage();
                        /* NOTREACHED */
                }
        }

 /* This is ugly but cleaner then w/o open_file(). */
 if ( argv[1] == NULL ) {
  usage();
 } else if ( (t_flag < 1) && (b_flag < 1) && (v_flag < 1) )  {
  fp = open_file( argv[1] );
  read_all( fp, lncnt );
 } else if ( (v_flag != 0) && (t_flag < 1) && (b_flag < 1) ) {
  fp = open_file( argv[2] );
  read_all( fp, lncnt );
 }  else if ( t_flag > 0 ) {
  fp = open_file( argv[3] );
  read_top( fp, lncnt );
 } else if ( b_flag > 0 ) {
  fp = open_file( argv[3] );
      read_bottom( fp, lncnt );
 } else {
  usage();
  /* NOTREACHED */
 }

 if ( v_flag != 0 )
     be_verbose();

 fclose( fp );
 return 0;

}


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

 FILE *fto;

 fto = fopen( arg, "r" );
 if ( fto == NULL ) 
  errx( 1, "File can not be opened or"
        " does not exist -- %s\n", arg );
 return fto;
}


/*
 * print out an open file to standard output
 */

static void
read_all( FILE *fp, int lncnt ) {

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

/* Read n lines from the top of the file.
 * note that it was very inspired by the head(1) implementation of BSD
 */
static void
read_top( FILE *fp, int lncnt ) {

        char *cp;
        size_t error, rlen;
        while ( lncnt && (cp = fgetln( fp, &rlen )) != NULL ) {
                error = fwrite( cp, sizeof(char), rlen, stdout );
                if ( error != rlen )
                        err( 1, "stdout" );
                lncnt--;
        }
}


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

        int hmany = lncnt;
        long nlnum = 0;
        long where;

        struct lnpos *root = 0;
        struct lnpos *cur = 0;

        root = malloc( sizeof(struct lnpos) );
        if ( root == NULL )
                err( 1, "can't init the list" );
        root->next = 0;
        cur = root;

        cur->next = malloc( sizeof(struct lnpos) );
        if ( cur->next == NULL )
                err( 1, "can't add first node" );
        cur = cur->next;

        /* read the file, count every '\n' and store them in a new member of
         * our linked list.
         */
        while ( (lncnt = fgetc( fp )) != EOF ) {
                if ( lncnt == '\n' ) {
                        nlnum++;
                        cur->nl = ftell( fp );
   cur->next = malloc( sizeof(struct lnpos) );
   if ( cur->next == NULL )
    err( 1, "can't store line feeds" );
   cur = cur->next;

                }
        }

 /* Let's mark the end of the list and move to it */
 cur->next = malloc( sizeof(struct lnpos) );
 if ( cur->next == NULL )
  err( 1, "can't terminate the list" );
 cur = cur->next;
 cur->next = NULL;

        /* rewind our linked-list and seek to b_flag segments. So readall() starts from
         * the correct fseek offset to print till EOF. - This keeps down on
         * unnecessary code here !
         */
        cur = root;
        while ( hmany < nlnum ) {
                cur = cur->next;
                nlnum--;

        }

        where = fseek( fp, cur->nl, SEEK_SET );
        if ( where != 0 )
                err( 1, "could not seek through the file\n" );

        read_all( fp, lncnt );
 clean_up( root );
}

/* Simple destructor - walk the list and free() the memory before the
 * program exits.
 */
static void
clean_up( struct lnpos *rp ) {

 struct lnpos *t = rp;
 struct lnpos *atpos = rp;

 while ( atpos != NULL ) {
  free( t );
  atpos = atpos->next;
  t = atpos;
 }
}

static void
be_verbose( void ) {

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

static void
usage( void ) {

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

        exit( EXIT_FAILURE );
}
    

I've learned so much in the past 2 months, its awesome. Really this is the largest project I've ever worked on in my ~/Programming directory. 255 Lines isn't much but when your a bumbling baboon trying to learn in the wilderness. It kinda helps to learn by reading and writing code I guess.

I also want to start assembling a little personal library of functions and macros that I can draw on to do odds and ends without rewriting them. After all with my little 'toys' I might have use of other stuff from time to time hehe.

Time for BEEEEEEEEEEEEEEEEEEEEEEEEEEDDDDDDDDDDDDDDD!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

No comments:

Post a Comment