Files
seta75D d6fe8fe829 Init
2021-10-11 22:19:34 -03:00

990 lines
25 KiB
C

static char sccsid[] = "@(#)57 1.26 src/bos/usr/bin/tail/tail.c, cmdscan, bos412, 9446B 11/15/94 20:11:57";
/*
* COMPONENT_NAME: (CMDSCAN) commands that scan files
*
* FUNCTIONS:
*
* ORIGINS: 3, 26, 27, 18
*
* This module contains IBM CONFIDENTIAL code. -- (IBM
* Confidential Restricted when combined with the aggregated
* modules for this product)
* SOURCE MATERIALS
* (C) COPYRIGHT International Business Machines Corp. 1989, 1994
* All Rights Reserved
*
* US Government Users Restricted Rights - Use, duplication or
* disclosure restricted by GSA ADP Schedule Contract with IBM Corp.
*
* Copyright (c) 1980 Regents of the University of California.
* All rights reserved. The Berkeley software License Agreement
* specifies the terms and conditions for redistribution.
*
* Copyright 1976, Bell Telephone Laboratories, Inc.
*
* (c) Copyright 1990 OPEN SOFTWARE FOUNDATION, INC.
* ALL RIGHTS RESERVED
*
* OSF/1 1.0
*
* tail.c,v $ $Revision: 2.6.1.2 $ (OSF) $Date: 91/05/03 14:23:01 $
*/
/*
*
* Tail writes from a file beginning at a specified point to the
* end of file. Points can be specified by:
*
* +/- [n]l for n lines
* +/- [n]b for n blocks
* +/- [n]c for n bytes
* +/- [n]k for n k-block (1024)
* +/- [n]m for n multi-byte characters
*
* The -f option tell you to loop forever looking for updates. The
* assumption is the file is growing.
*
* The -r option will copy output from end of file to specified point
* in reverse order.
*
* -c number
* This option is in bytes. It is an integer
* number with +/- prepended to the number. + means
* relative to the beginning of the file, - means
* relative to the end of the file. No sign means
* same as -. This is same as +/-c obsolescent version.
*
* -n number
* number is measure in lines. This is same as +/-l
* obsolescent version.
*/
#define _ILS_MACROS
#include <stdio.h>
#include <unistd.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/errno.h>
#include <locale.h>
#include "tail_msg.h"
static nl_catd catd;
#define MSGSTR(Num, Str) catgets(catd,MS_TAIL,Num, Str)
#define BLOCK 512 /* multiplier for b suffix */
#define KBLOCK 1024 /* multiplier for k suffix */
#define CHUNK 4096 /* how much we are willing to read at once */
#define DEFAULT_NUMBER "10" /* default number used in case none specified */
#define BUFFSIZE (LINE_MAX*10) /* POSIX.2 says this is default */
#define NEWLINE '\n'
typedef int bool;
#define NO 0
#define YES 1
#include <stdlib.h>
static void dowidechars(); /* for tail -m */
static void dowidefromstart();
static void dowidefromend();
static void setcount(char *arg);
static void usage();
static void dofollow();
static void mb_dofollow();
static void dotail();
static void dorev();
static void dobytes();
static void dolines();
static int cflag = 0;
static int nflag = 0;
static int mflag = 0;
static int bflag = 0;
static int kflag = 0;
static int mexclusive = 0;
static int no_reverse = 0;
static int multibyte = 0;
static bool explicit = NO; /* kludge to allow Berkeley compatibility */
/* was n_tail set explicitly? */
static bool canseek;
static bool bylines = YES;
static bool follow = NO;
static bool fromtop = NO;
static bool reverse = NO;
static long n_tail = 10L;
static struct stat statb;
static char bin[CHUNK + (2*MB_LEN_MAX)];
/* add X bytes so that in the case of *
* the following unlikely(but possible) *
* scenario: *
* mblen(bin[CHUNK-1], MB_CUR_MAX) *
* mblen will not attempt to read beyond*
* array boundaries */
static char inputfile[PATH_MAX];
static FILE *fpinput;
static int mb_cur_max;
main(argc, argv)
int argc; char **argv;
{
register int err;
register int rc, file = 0;
(void) setlocale(LC_ALL,"");
catd = catopen(MF_TAIL,NL_CAT_LOCALE);
while (--argc) {
register char *arg = *++argv;
switch (*arg) {
case '+': /* always a number */
fromtop = YES;
setcount(arg);
break;
case '-': /* may be number or "-f" */
nextarg:
if (arg[1] == '?') {
usage();
} else if (arg[1] == 'f') {
follow = 1;
} else if (arg[1] == 'r') { /* ... or even -r or so on */
reverse = YES;
if (!explicit) n_tail = BUFFSIZE;
} else if (arg[1] == 'c') {
cflag++;
no_reverse = 1;
} else if (arg[1] == 'n') {
nflag++;
} else if (arg[1] == 'm') {
mflag++;
no_reverse = 1;
/* if not in a multibyte locale then don't set this flag
* and let the single byte code path be used since it
* should be somewhat faster.
*/
if (MB_CUR_MAX != 1)
multibyte++;
} else if (arg[1] == 'b') {
bflag++;
no_reverse = 1;
} else if (arg[1] == 'k') {
kflag++;
no_reverse = 1;
} else if ((arg[1] == '-') && (arg[2] == '\0')) { /* -- is found */
arg = *++argv;
argc--;
goto file;
} else {
/* no more flag or "l" flag */
fromtop = NO;
setcount(arg);
break;
}
if (arg[2] != '\0') {
arg[1] = '-';
arg++;
goto nextarg;
}
if (*argv[1] == '-' || *argv[1] == '+')
break;
if (!isdigit((int)*argv[1]) && (cflag || nflag || mflag ||
bflag || kflag)) /* no # argument specified by user */
{
/* POSIX & XOPEN requires a numeric argument with the -n flag */
if (nflag)
usage();
arg = DEFAULT_NUMBER;
fromtop = NO;
setcount(arg);
}
break;
file:
default: /* it's a filename */
if (cflag || nflag || mflag || bflag || kflag) {
fromtop = NO;
setcount(arg);
}
else {
if (argc > 1){
fprintf(stderr, MSGSTR(PRSFILE,
"tail: Can only process one file at a time.\n"));
usage();
}
strcpy(inputfile, arg);
(void)close(0);
if (open(inputfile, 0) != 0) {
perror(inputfile);
exit(1);
}
file++;
}
break;
} /* switch */
if (mexclusive > 1) {
fprintf(stderr, MSGSTR(EXCLOPT,
"tail: Options are mutually exclusive.\n"));
usage();
}
/* -r flag can be used with -n option only */
if (no_reverse || follow) {
if (reverse)
usage();
}
} /* while */
if (!explicit && fromtop)
n_tail--;
fstat(0, &statb);
errno = 0;
rc = lseek(0, 0L, SEEK_CUR);
err = errno;
canseek = (rc != -1 && (statb.st_mode & S_IFMT) != S_IFCHR);
if (reverse)
dorev();
else {
dotail();
/* -f option works with canseek file or FIFO only */
if (follow && (canseek || (err==ESPIPE && file))) {
if (multibyte)
mb_dofollow();
else
dofollow();
}
}
exit(0);
/* NOTREACHED */
}
/*
* NAME: setcount
*
* FUNCTION: Determine the count (+/-) and by what value to do the tail.
* n_tail is set to this value. by_lines for lines else
* it is by character.
*
* RETURN VALUE: void
*
*/
static void setcount(arg) char *arg; {
char *ptr;
int nopref = 0; /* if no + or - prefix passed */
if (cflag || mflag || bflag || kflag || nflag) {
ptr = arg;
if ( (*ptr == '+') || (*ptr == '-') )
ptr++;
else
nopref++;
while (*ptr != '\0') {
if (!isdigit((int)*ptr)) {
usage();
}
ptr++;
}
if (nflag)
bylines = 1;
else
bylines = 0;
}
if (!nopref)
arg++;
if (isdigit((int)*arg)) {
explicit = YES;
n_tail = 0;
do n_tail = n_tail * 10 + (*arg++ - '0'); while (isdigit((int)*arg));
}
if (!nflag && !cflag && !mflag && !bflag && !kflag) {
while (*arg) {
switch (*arg++) {
case 'l': /* lines */
bylines = 1;
break;
case 'b':
bylines = 0; /* blocks - convert to characters */
n_tail *= BLOCK;
mexclusive++;
no_reverse=1;
break;
case 'm': /* tail exact number of characters */
/* if not in a multibyte locale then don't set this flag
* and let the single byte code path be used since it's
* should be somewhat faster.
*/
if (MB_CUR_MAX != 1)
multibyte = 1; /* instead of bytes */
bylines = 0;
mexclusive++;
no_reverse=1;
break;
case 'k':
bylines = 0; /* blocks - convert to characters */
n_tail *= KBLOCK;
mexclusive++;
no_reverse=1;
break;
case 'c': /* characters */
bylines = 0;
mexclusive++;
no_reverse=1;
break;
case 'f':
follow = 1; /* "-10f" same as "-10 -f" */
break;
case 'r':
reverse = 1; /* "-10r" same as "-10 -r" */
if (!explicit) /* kludge to allow Berkeley compatibility */
n_tail = BUFFSIZE;
break;
default:
usage();
}
}
}
if (bflag) n_tail *= BLOCK;
if (kflag) n_tail *= KBLOCK;
mexclusive = mexclusive + cflag + bflag + mflag + kflag;
nflag = cflag = bflag = kflag = mflag = 0;
if (explicit && fromtop && n_tail > 0) --n_tail;
}
/*
* NAME: dofollow
*
* FUNCTION: every second write out any changes
*
* RETURN VALUE: never returns. Must be killed.
*/
static void dofollow() {
register int n;
while ((n = read(0, bin, (unsigned)CHUNK)) != -1) {
if (n == 0) {
sleep((unsigned)1);
} else {
write(1, bin, (unsigned)n);
}
}
}
/*
* NAME: mb_dofollow
*
* FUNCTION: every second write out any changes
*
* RETURN VALUE: never returns. Must be killed.
*/
static void mb_dofollow() {
wchar_t wc;
char *s, *tmpbin;
int i, n, wclen, leftovers = 0;
tmpbin = bin;
while ((n = read(0, tmpbin, (unsigned)CHUNK)) != -1) {
if (n == 0) {
sleep((unsigned)1);
} else {
s = bin;
i = 0;
while (s < (bin + n + leftovers)) {
if ((wclen = mbtowc(&wc, s, mb_cur_max)) == -1) {
if (s + mb_cur_max < bin + n + leftovers) {
/* skip invalid multibyte chars */
s++;
} else {
/* save leftovers and check again after next read */
while (s < (bin + n + leftovers)) {
*(bin+i) = *s;
s++;
i++;
}
}
} else {
putwchar(wc);
s += wclen;
}
}
leftovers = i;
tmpbin = bin + leftovers;
}
fflush(0);
}
}
/*
* NAME: dotail
*
* FUNCTION: Actually do the tail. lseek to the starting point.
* skip multibyte characters if necessary, and print out the
* requested lines.
*
* RETURN VALUE: none
*
*/
static void dotail() {
register int nc = 0;
char *p = bin;
if (fromtop) {
if (n_tail == 0) {
/* nothing */
} else if (multibyte) {
dowidechars();
} else if (bylines) {
do {
if (nc == 0) {
if ((nc = read(0, bin, (unsigned)CHUNK)) <= 0) return;
p = bin;
}
--nc;
} while (*p++ != '\n' || --n_tail > 0);
} else if (canseek) {
lseek(0,n_tail,SEEK_CUR);
} else {
do {
if ((nc = read(0, bin, (unsigned)CHUNK)) <= 0) return;
} while ((n_tail -= nc) > 0);
p = bin + nc + n_tail;
nc = -n_tail;
}
if (nc > 0) write(1, p, (unsigned)nc);
while ((nc = read(0, bin, (unsigned)CHUNK)) > 0)
write(1, bin, (unsigned)nc);
/* from here down are the -number cases */
} else if (bylines) {
dolines();
} else if (multibyte) {
dowidechars();
} else if (canseek) {
lseek(0, -n_tail, SEEK_END);
while ((nc = read(0, bin, (unsigned)CHUNK)) > 0)
write(1, bin, (unsigned)nc);
} else
dobytes();
}
/*
* NAME: dorev
*
* FUNCTION: - output last n_tail lines in reverse order (last first)
* This function faithfully emulates the Berzerkely -r operation of tail.
* Including limitations.
*
* RETURN VALUE: none
*/
static void dorev()
{
static char buf[BUFFSIZE]; /* circular buffer storage area */
register char *p; /* current location in buffer */
register char *bufend; /* circular buffer end */
char *last; /* location of last newline */
int partial; /* is the buffer full or partial*/
int lines; /* number of lines output */
int nc; /* number of characters read */
int offset = 0; /* offset into buffer */
long size = BUFFSIZE; /* size of file/size of buffer */
if (n_tail <= 0) return;
/*
* read the file, saving the last BUFFSIZE bytes; handle wrapping
*/
/* if input is seekable, win on large input files */
if (canseek) {
size = lseek(0, 0L, SEEK_END);
if (size > BUFFSIZE) size = BUFFSIZE;
lseek(0, -size, SEEK_CUR);
}
partial = YES;
while ((nc = read(0, buf + offset, (unsigned)(BUFFSIZE - offset))) > 0) {
if ((offset += nc) >= BUFFSIZE) {
partial = NO;
offset = 0;
}
}
/* force a trailing newline (also ensures buffer is never empty) */
if (buf[offset==0 ? BUFFSIZE-1 : offset-1] != NEWLINE) {
buf[offset] = NEWLINE;
if (++offset >= BUFFSIZE) {
offset = 0;
partial = NO;
}
}
/* point to last character in buffer */
if (offset > 0) bufend = buf + offset - 1;
else bufend = &buf[BUFFSIZE-1];
/*
* output lines in reverse order
*/
lines = 0;
p = bufend;
do {
last = p;
/* scan for a newline, handle wrap-around */
do {
if (--p < buf) {
if (partial) {
write(1, buf, (unsigned)(last-buf+1));
return;
}
p = buf + BUFFSIZE;
}
} while (*p != NEWLINE && p != bufend);
/* found a newline, output collected line */
if (p < last)
write(1, p+1, (unsigned)(last-p));
else {
write(1, p+1, (unsigned)(&buf[BUFFSIZE]-p));
write(1, buf, (unsigned)(last-buf+1));
}
} while (++lines < n_tail && p != bufend); /* up to n_tail lines */
return;
}
/*
* NAME: dobytes
*
* FUNCTION:
*
* Last n_tail chars, no seek. We use a circular buffer. If the static bin
* isn't big enough, we get the space from malloc(), rounding up to the next
* CHUNK boundary if possible. PORT WARNING: This routines assumes that long
* (n_tail) and unsigned int (argument expected by malloc) and the difference
* between two pointers (argument passed to to read/write) are all the same.
* Not making this assumption requires a bit of finesse.
*
* RETURN VALUE: none
*/
static void dobytes() {
register char *b;
register int nc;
register long m, size;
register bool isfull;
if (n_tail <= CHUNK) {
size = CHUNK;
b = bin;
} else {
size = (n_tail+CHUNK-1) / CHUNK * CHUNK;
if ((b = malloc((size_t)size)) == NULL) {
size = n_tail;
if ((b = malloc((size_t)size)) == NULL) {
fprintf(stderr, MSGSTR(TOOMUCH, "tail: Unable to malloc memory.\n"));
exit(1);
}
}
}
isfull = NO;
m = 0;
while ((nc = read(0, b+m, (unsigned)(size-m))) > 0) {
if ((m += nc) >= size) {
isfull = YES;
m = 0;
}
}
if (m >= n_tail) {
write(1, b+m-n_tail, (unsigned)(n_tail));
} else {
if (isfull) write(1, b+size+m-n_tail, (unsigned)(n_tail-m));
write(1, b, (unsigned)m);
}
}
/*
* Last n_tail lines: the most likely case, and also the hardest.
* The strategy is to maintain a linked list of buffers, of which all but
* the last are full. If the file is seekable, the "last" (partial) buffer
* is the first one read; its size is adjusted according to the size of the
* file.
*/
typedef struct Buffer Buffer;
struct Buffer {
Buffer *next;
long lines;
char buf[CHUNK];
};
static Buffer *qh = NULL; /* head of linked list of full buffers */
static Buffer *qp; /* partial buffer at end of file */
static int psize; /* size of partial buffer */
/*
* NAME: mkbuf
*
* FUNCTION: get a buffer.
*
* RETURN VALUE: The buffer just malloc'ed is returned.
*
*/
static Buffer
*mkbuf()
{
register Buffer *q;
if ((q = (Buffer *)malloc((size_t)sizeof(Buffer))) == NULL) {
fprintf(stderr, MSGSTR(TOOMUCH, "tail: Unable to malloc memory.\n"));
exit(1);
}
q->lines = 0;
return (q);
}
/*
* NAME: rmbuf
*
* FUNCTION: Release a buffer.
*
* RETURN VALUE: none
*/
static void rmbuf(q) Buffer *q; {
free((void *)q);
}
/*
* NAME: nlines
*
* FUNCTION: Count the number of lines in a buffer.
*
* RETURN VALUE: The number of lines.
*/
static int nlines(p, n) register char *p; register int n; {
register int r = 0;
while (--n >= 0) if (*p++ == '\n') ++r;
return (r);
}
/*
* NAME: dolines
*
* FUNCTION: If seek-able file. seek to the tailing point. Otherwise,
* read from the file, keeping a ring buffer of the amount
* you want to print at the end.
*
* RETURN VALUE: void
*/
static void dolines() {
register int nc, nl;
register long tlines = 0;
qp = mkbuf();
if (canseek) {
long e = lseek(0, 0L, SEEK_END);
long r = lseek(0, -(e % CHUNK), SEEK_CUR);
while (psize < e-r) {
if ((nc = read(0, &qp->buf[psize], (unsigned)(e-r-psize))) <= 0) {
fprintf(stderr, MSGSTR(FSHRUNK, "tail: file shrunk!\n"));
perror("");
exit(1);
}
nl = nlines(&qp->buf[psize], nc);
psize += nc;
qp->lines += nl;
tlines += nl;
}
while (r > 0 && tlines <= n_tail) {
register int k;
register Buffer *temp = mkbuf();
temp->next = qh;
qh = temp;
r = lseek(0, r-CHUNK, SEEK_SET);
for (k = 0; k < CHUNK; k += nc) {
if ((nc = read(0, &qh->buf[k], (unsigned)(CHUNK-k))) <= 0) {
fprintf(stderr, MSGSTR(FSHRUNK, "tail: file shrunk!\n"));
perror("");
exit(1);
}
}
tlines += qh->lines = nlines(&qh->buf[0], CHUNK);
}
lseek(0, e, SEEK_SET);
} else {
register Buffer **qt = &qh;
while ((nc = read(0, &qp->buf[psize], (unsigned)(CHUNK-psize))) > 0) {
nl = nlines(&qp->buf[psize], nc);
psize += nc;
qp->lines += nl;
tlines += nl;
while (qh != NULL && tlines > qh->lines + n_tail) {
register Buffer *temp = qh;
qh = qh->next;
if (qh == NULL) qt = &qh;
tlines -= temp->lines;
rmbuf(temp);
}
if (psize >= CHUNK) {
qp->next = NULL;
*qt = qp;
qt = &qp->next;
qp = mkbuf();
psize = 0;
}
}
}
if (qh != NULL) {
register char *p = &qh->buf[0];
while (tlines > n_tail) if (*p++ == '\n') --tlines;
if (p < &qh->buf[CHUNK]) write(1, p, (unsigned)(&qh->buf[CHUNK]-p));
while ((qh = qh->next) != NULL) write(1, &qh->buf[0], (unsigned)CHUNK);
if (psize > 0) write(1, &qp->buf[0], (unsigned)psize);
} else {
register char *p = &qp->buf[0];
while (tlines > n_tail && p - &qp->buf[0] < psize )
if (*p++ == '\n') --tlines;
psize -= p - &qp->buf[0];
if (psize > 0) write(1, p, (unsigned)psize);
}
}
static void
dowidechars()
{
(void)close(0);
if((fpinput = fopen(inputfile, "r")) == NULL) {
perror(inputfile);
exit(1);
}
mb_cur_max = MB_CUR_MAX;
if (fromtop == YES) {
dowidefromstart();
} else {
dowidefromend();
}
}
static void
dowidefromstart()
{
wint_t wc;
int i;
fseek(fpinput, 0L, SEEK_SET);
for(i = 0; i < n_tail; i++) {
if((wc = fgetwc(fpinput)) == WEOF) {
/* not that many mb chars in file */
exit(1);
}
}
while((wc = fgetwc(fpinput)) != WEOF) {
putwchar(wc);
}
}
static void
dowidefromend()
{
char *beginp, *endp;
int readseg(), excess_chs;
int bytesread = 0; /* total # of bytes from eof */
int sg_mb_cnt; /* cnt of mb chars in this segment of data */
int mb_cnt = 0; /* total count of mb chars */
int bytes_this_seg; /* total bytes in currect segment */
wint_t wc;
/* start at end of file and read data backwards *
* till we locate byte position corresponding to*
* the start of the last n_tail multibyte chars */
for(;;) {
if(bytesread >= statb.st_size)
break;
bytes_this_seg = readseg(&beginp, &endp);
/* count multibyte chars in current segment */
sg_mb_cnt = cnt_mbchars(beginp, endp);
if(mb_cnt + sg_mb_cnt < n_tail) {
/* still short of total number *
* of mb chars desired, adjust *
* counters and keep looping */
mb_cnt += sg_mb_cnt;
bytesread += bytes_this_seg;
} else {
/* this segment of data may have contained *
* more mb chars than needed; calculate *
* the excess */
excess_chs = sg_mb_cnt - (n_tail-mb_cnt);
/* if this segment has more mb chars than *
* are needed to fill quota of n_tail *
* chars, calculate the excess in bytes *
* and subtract it from the byte counter*/
bytes_this_seg -= cnt_bytes(beginp, endp, excess_chs);
bytesread += bytes_this_seg;
break;
}
}
fseek(fpinput, -bytesread, SEEK_END);
while((wc = fgetwc(fpinput)) != WEOF) {
putwchar(wc);
}
}
#define UNIQUE_CODE_PT 0x3f
/*
* readseg: read in a "segment" of data where
* "segment" is defined to be the data
* between successive occurrences of
* characters in the unique code point
* range
*/
static int
readseg(beginpp, endpp)
char **beginpp;
char **endpp;
{
static char *beginp = bin - 1;
static char *endp;
static long totalbytes = 0;
int inputblksz = 0;
long offset = 0;
int byteno;
for(;;) {
endp = beginp;
/* if offset from eof = size of file then we're *
* positioned at the beginning of the file and *
* there's no need to search for a char in the *
* unique code point range ... this is also ne- *
* cessary because if n_tail is > the file size *
* (EX. file sz=1000 mb chs & tail -1001m file) *
* and if there are no chars from the uniq c pt *
* range at the top of the file we'll loop for *
* ever in this routine (ie. there's no EOF at *
* the top of a file!) */
if(offset == statb.st_size) {
beginp = bin;
*beginpp = beginp;
*endpp = endp;
beginp--;
return(inputblksz);
}
for(byteno = 0; beginp >= bin; byteno++, beginp--) {
if(*beginp <= UNIQUE_CODE_PT) {
*beginpp = beginp;
*endpp = endp;
beginp--;
totalbytes += (byteno + 1);
return(byteno + 1);
}
}
/* If there are CHUNK unread bytes left then *
* read CHUNK more, otherwise set inputblksz *
* to number of remaining unread bytes. */
if(totalbytes + CHUNK > statb.st_size) {
inputblksz = statb.st_size - totalbytes;
} else {
inputblksz = CHUNK;
}
/* offset from eof of next block to read */
offset = totalbytes + inputblksz;
fseek(fpinput, -offset, SEEK_END);
fread(bin, 1, inputblksz, fpinput);
beginp = bin + inputblksz - 1;
}
}
/*
* cnt_mbchars: for a pair of char pointers delimiting an
* area of data within a buffer, count the
* number of legal multibyte characters
*/
static int
cnt_mbchars(beginp, endp)
char *beginp;
char *endp;
{
int mb_cnt = 0; /* count of multibyte chars */
int len = 0; /* length of multibyte char */
while(beginp <= endp) {
if((len = mblen(beginp, mb_cur_max)) <= 0) {
/* Either a NULL was returned *
* or valid mb char not found *
* skip a byte & keep trying */
len = 1;
} else {
/* found one, increment mb cnt *
* for this segment of data */
mb_cnt++;
}
beginp += len;
}
return(mb_cnt);
}
/*
* cnt_bytes: for a given number of multibyte characters
* count the number of bytes
*/
static int
cnt_bytes(beginp, endp, n_mbchars)
char *beginp;
char *endp;
int n_mbchars; /* number of mb chars for which we *
* need the corresponding # of bytes */
{
int mb_cnt = 0;
int byte_cnt = 0;
int len;
while( mb_cnt < n_mbchars && beginp <= endp ) {
if((len = mblen(beginp, mb_cur_max)) <= 0) {
len = 1;
} else {
mb_cnt++;
}
beginp += len;
byte_cnt += len;
}
return(byte_cnt);
}
static void usage()
{
fprintf(stderr, MSGSTR(USAGE2,"Usage: tail [-f] [-c number|-n number|-m number|-b number|-k number] [file]\n"));
fprintf(stderr, MSGSTR(USAGE1,"Usage: tail [-r] [-n number] [file]\n"));
fprintf(stderr, MSGSTR(USAGE,"Usage: tail [+|-[number]][l|b|c|k|m][f] [file]\n"));
exit(2);
}