Subversion Repositories planix.SVN

Rev

Blame | Last modification | View Log | RSS feed

#include        <u.h>
#include        <libc.h>
#include        <ctype.h>
#include        <bio.h>

/*
 * tail command, posix plus v10 option -r.
 * the simple command tail -c, legal in v10, is illegal
 */

long    count;
int     anycount;
int     follow;
int     file    = 0;
char*   umsg    = "usage: tail [-n N] [-c N] [-f] [-r] [+-N[bc][fr]] [file]";

Biobuf  bout;
enum
{
        BEG,
        END
} origin = END;
enum
{
        CHARS,
        LINES
} units = LINES;
enum
{
        FWD,
        REV
} dir = FWD;

extern  void    copy(void);
extern  void    fatal(char*);
extern  int     getnumber(char*);
extern  void    keep(void);
extern  void    reverse(void);
extern  void    skip(void);
extern  void    suffix(char*);
extern  long    tread(char*, long);
extern  void    trunc(Dir*, Dir**);
extern  vlong   tseek(vlong, int);
extern  void    twrite(char*, long);
extern  void    usage(void);
static  int     isseekable(int fd);

#define JUMP(o,p) tseek(o,p), copy()

void
main(int argc, char **argv)
{
        int seekable, c;

        Binit(&bout, 1, OWRITE);
        for(; argc > 1 && ((c=*argv[1])=='-'||c=='+'); argc--,argv++ ) {
                if(getnumber(argv[1])) {
                        suffix(argv[1]);
                        continue;
                } else
                if(c == '-')
                        switch(argv[1][1]) {
                        case 'c':
                                units = CHARS;
                        case 'n':
                                if(getnumber(argv[1]+2))
                                        continue;
                                else
                                if(argc > 2 && getnumber(argv[2])) {
                                        argc--, argv++;
                                        continue;
                                } else
                                        usage();
                        case 'r':
                                dir = REV;
                                continue;
                        case 'f':
                                follow++;
                                continue;
                        case '-':
                                argc--, argv++;
                        }
                break;
        }
        if(dir==REV && (units==CHARS || follow || origin==BEG))
                fatal("incompatible options");
        if(!anycount)
                count = dir==REV? ~0UL>>1: 10;
        if(origin==BEG && units==LINES && count>0)
                count--;
        if(argc > 2)
                usage();
        if(argc > 1 && (file=open(argv[1],0)) < 0)
                fatal(argv[1]);
        seekable = isseekable(file);

        if(!seekable && origin==END)
                keep();
        else
        if(!seekable && origin==BEG)
                skip();
        else
        if(units==CHARS && origin==END)
                JUMP(-count, 2);
        else
        if(units==CHARS && origin==BEG)
                JUMP(count, 0);
        else
        if(units==LINES && origin==END)
                reverse();
        else
        if(units==LINES && origin==BEG)
                skip();
        if(follow && seekable)
                for(;;) {
                        static Dir *sb0, *sb1;
                        trunc(sb1, &sb0);
                        copy();
                        trunc(sb0, &sb1);
                        sleep(5000);
                }
        exits(0);
}

void
trunc(Dir *old, Dir **new)
{
        Dir *d;
        vlong olength;

        d = dirfstat(file);
        if(d == nil)
                return;
        olength = 0;
        if(old)
                olength = old->length;
        if(d->length < olength)
                d->length = tseek(0LL, 0);
        free(*new);
        *new = d;
}

void
suffix(char *s)
{
        while(*s && strchr("0123456789+-", *s))
                s++;
        switch(*s) {
        case 'b':
                if((count *= 1024) < 0)
                        fatal("too big");
        case 'c':
                units = CHARS;
        case 'l':
                s++;
        }
        switch(*s) {
        case 'r':
                dir = REV;
                return;
        case 'f':
                follow++;
                return;
        case 0:
                return;
        }
        usage();
}

/*
 * read past head of the file to find tail
 */
void
skip(void)
{
        int i;
        long n;
        char buf[Bsize];
        if(units == CHARS) {
                for( ; count>0; count -=n) {
                        n = count<Bsize? count: Bsize;
                        if(!(n = tread(buf, n)))
                                return;
                }
        } else /*units == LINES*/ {
                n = i = 0;
                while(count > 0) {
                        if(!(n = tread(buf, Bsize)))
                                return;
                        for(i=0; i<n && count>0; i++)
                                if(buf[i]=='\n')
                                        count--;
                }
                twrite(buf+i, n-i);
        }
        copy();
}

void
copy(void)
{
        long n;
        char buf[Bsize];
        while((n=tread(buf, Bsize)) > 0) {
                twrite(buf, n);
                Bflush(&bout);  /* for FWD on pipe; else harmless */
        }
}

/*
 * read whole file, keeping the tail
 *      complexity is length(file)*length(tail).
 *      could be linear.
 */
void
keep(void)
{
        int len = 0;
        long bufsiz = 0;
        char *buf = 0;
        int j, k, n;

        for(n=1; n;) {
                if(len+Bsize > bufsiz) {
                        bufsiz += 2*Bsize;
                        if(!(buf = realloc(buf, bufsiz+1)))
                                fatal("out of space");
                }
                for(; n && len<bufsiz; len+=n)
                        n = tread(buf+len, bufsiz-len);
                if(count >= len)
                        continue;
                if(units == CHARS)
                        j = len - count;
                else {
                        /* units == LINES */
                        j = buf[len-1]=='\n'? len-1: len;
                        for(k=0; j>0; j--)
                                if(buf[j-1] == '\n')
                                        if(++k >= count)
                                                break;
                }
                memmove(buf, buf+j, len-=j);
        }
        if(dir == REV) {
                if(len>0 && buf[len-1]!='\n')
                        buf[len++] = '\n';
                for(j=len-1 ; j>0; j--)
                        if(buf[j-1] == '\n') {
                                twrite(buf+j, len-j);
                                if(--count <= 0)
                                        return;
                                len = j;
                        }
        }
        if(count > 0)
                twrite(buf, len);
}

/*
 * count backward and print tail of file
 */
void
reverse(void)
{
        int first;
        long len = 0;
        long n = 0;
        long bufsiz = 0;
        char *buf = 0;
        vlong pos = tseek(0LL, 2);

        for(first=1; pos>0 && count>0; first=0) {
                n = pos>Bsize? Bsize: (long)pos;
                pos -= n;
                if(len+n > bufsiz) {
                        bufsiz += 2*Bsize;
                        if(!(buf = realloc(buf, bufsiz+1)))
                                fatal("out of space");
                }
                memmove(buf+n, buf, len);
                len += n;
                tseek(pos, 0);
                if(tread(buf, n) != n)
                        fatal("length error");
                if(first && buf[len-1]!='\n')
                        buf[len++] = '\n';
                for(n=len-1 ; n>0 && count>0; n--)
                        if(buf[n-1] == '\n') {
                                count--;
                                if(dir == REV)
                                        twrite(buf+n, len-n);
                                len = n;
                        }
        }
        if(dir == FWD) {
                if(n)
                        tseek(pos+n+1, 0);
                else
                        tseek(0, 0);
                copy();
        } else
        if(count > 0)
                twrite(buf, len);
}

vlong
tseek(vlong o, int p)
{
        o = seek(file, o, p);
        if(o == -1)
                fatal("");
        return o;
}

long
tread(char *buf, long n)
{
        int r = read(file, buf, n);
        if(r == -1)
                fatal("");
        return r;
}

void
twrite(char *s, long n)
{
        if(Bwrite(&bout, s, n) != n)
                fatal("");
}

int
getnumber(char *s)
{
        if(*s=='-' || *s=='+')
                s++;
        if(!isdigit(*s))
                return 0;
        if(s[-1] == '+')
                origin = BEG;
        if(anycount++)
                fatal("excess option");
        count = atol(s);

        /* check range of count */
        if(count < 0 || (int)count != count)
                fatal("too big");
        return 1;
}       

void            
fatal(char *s)
{
        char buf[ERRMAX];

        errstr(buf, sizeof buf);
        fprint(2, "tail: %s: %s\n", s, buf);
        exits(s);
}

void
usage(void)
{
        fprint(2, "%s\n", umsg);
        exits("usage");
}

/* return true if seeks work and if the file is > 0 length.
 * this will eventually bite me in the ass if seeking a file
 * is not conservative. - presotto
 */
static int
isseekable(int fd)
{       
        vlong m;

        m = seek(fd, 0, 1);
        if(m < 0)
                return 0;
        return 1;
}