hush/libbb/dump.c
Rob Landley d921b2ecc0 Remove bb_ prefixes from xfuncs.c (and a few other places), consolidate
things like xasprintf() into xfuncs.c, remove xprint_file_by_name() (it only
had one user), clean up lots of #includes...  General cleanup pass.  What I've
been doing for the last couple days.

And it conflicts!  I've removed httpd.c from this checkin due to somebody else
touching that file.  It builds for me.  I have to catch a bus.  (Now you know
why I'm looking forward to Mercurial.)
2006-08-03 15:41:12 +00:00

804 lines
19 KiB
C

/* vi: set sw=4 ts=4: */
/*
* Support code for the hexdump and od applets,
* based on code from util-linux v 2.11l
*
* Copyright (c) 1989
* The Regents of the University of California. All rights reserved.
*
* Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
*
* Original copyright notice is retained at the end of this file.
*/
#include "libbb.h"
#include "dump.h"
enum _vflag bb_dump_vflag = FIRST;
FS *bb_dump_fshead; /* head of format strings */
static FU *endfu;
static char **_argv;
static off_t savaddress; /* saved address/offset in stream */
static off_t eaddress; /* end address */
static off_t address; /* address/offset in stream */
off_t bb_dump_skip; /* bytes to skip */
static int exitval; /* final exit value */
int bb_dump_blocksize; /* data block size */
int bb_dump_length = -1; /* max bytes to read */
static const char index_str[] = ".#-+ 0123456789";
static const char size_conv_str[] =
"\x1\x4\x4\x4\x4\x4\x4\x8\x8\x8\x8\010cdiouxXeEfgG";
static const char lcc[] = "diouxX";
int bb_dump_size(FS * fs)
{
FU *fu;
int bcnt, cur_size;
char *fmt;
const char *p;
int prec;
/* figure out the data block bb_dump_size needed for each format unit */
for (cur_size = 0, fu = fs->nextfu; fu; fu = fu->nextfu) {
if (fu->bcnt) {
cur_size += fu->bcnt * fu->reps;
continue;
}
for (bcnt = prec = 0, fmt = fu->fmt; *fmt; ++fmt) {
if (*fmt != '%')
continue;
/*
* bb_dump_skip any special chars -- save precision in
* case it's a %s format.
*/
while (strchr(index_str + 1, *++fmt));
if (*fmt == '.' && isdigit(*++fmt)) {
prec = atoi(fmt);
while (isdigit(*++fmt));
}
if (!(p = strchr(size_conv_str + 12, *fmt))) {
if (*fmt == 's') {
bcnt += prec;
} else if (*fmt == '_') {
++fmt;
if ((*fmt == 'c') || (*fmt == 'p') || (*fmt == 'u')) {
bcnt += 1;
}
}
} else {
bcnt += size_conv_str[p - (size_conv_str + 12)];
}
}
cur_size += bcnt * fu->reps;
}
return (cur_size);
}
static void rewrite(FS * fs)
{
enum { NOTOKAY, USEBCNT, USEPREC } sokay;
PR *pr, **nextpr = NULL;
FU *fu;
char *p1, *p2, *p3;
char savech, *fmtp;
const char *byte_count_str;
int nconv, prec = 0;
for (fu = fs->nextfu; fu; fu = fu->nextfu) {
/*
* break each format unit into print units; each
* conversion character gets its own.
*/
for (nconv = 0, fmtp = fu->fmt; *fmtp; nextpr = &pr->nextpr) {
/* NOSTRICT */
/* DBU:[dvae@cray.com] calloc so that forward ptrs start out NULL*/
pr = xzalloc(sizeof(PR));
if (!fu->nextpr)
fu->nextpr = pr;
/* ignore nextpr -- its unused inside the loop and is
* uninitialized 1st time thru.
*/
/* bb_dump_skip preceding text and up to the next % sign */
for (p1 = fmtp; *p1 && *p1 != '%'; ++p1);
/* only text in the string */
if (!*p1) {
pr->fmt = fmtp;
pr->flags = F_TEXT;
break;
}
/*
* get precision for %s -- if have a byte count, don't
* need it.
*/
if (fu->bcnt) {
sokay = USEBCNT;
/* bb_dump_skip to conversion character */
for (++p1; strchr(index_str, *p1); ++p1);
} else {
/* bb_dump_skip any special chars, field width */
while (strchr(index_str + 1, *++p1));
if (*p1 == '.' && isdigit(*++p1)) {
sokay = USEPREC;
prec = atoi(p1);
while (isdigit(*++p1));
} else
sokay = NOTOKAY;
}
p2 = p1 + 1; /* set end pointer */
/*
* figure out the byte count for each conversion;
* rewrite the format as necessary, set up blank-
* pbb_dump_adding for end of data.
*/
if (*p1 == 'c') {
pr->flags = F_CHAR;
DO_BYTE_COUNT_1:
byte_count_str = "\001";
DO_BYTE_COUNT:
if (fu->bcnt) {
do {
if (fu->bcnt == *byte_count_str) {
break;
}
} while (*++byte_count_str);
}
/* Unlike the original, output the remainder of the format string. */
if (!*byte_count_str) {
bb_error_msg_and_die("bad byte count for conversion character %s.", p1);
}
pr->bcnt = *byte_count_str;
} else if (*p1 == 'l') {
++p2;
++p1;
DO_INT_CONV:
{
const char *e;
if (!(e = strchr(lcc, *p1))) {
goto DO_BAD_CONV_CHAR;
}
pr->flags = F_INT;
if (e > lcc + 1) {
pr->flags = F_UINT;
}
byte_count_str = "\004\002\001";
goto DO_BYTE_COUNT;
}
/* NOTREACHED */
} else if (strchr(lcc, *p1)) {
goto DO_INT_CONV;
} else if (strchr("eEfgG", *p1)) {
pr->flags = F_DBL;
byte_count_str = "\010\004";
goto DO_BYTE_COUNT;
} else if (*p1 == 's') {
pr->flags = F_STR;
if (sokay == USEBCNT) {
pr->bcnt = fu->bcnt;
} else if (sokay == USEPREC) {
pr->bcnt = prec;
} else { /* NOTOKAY */
bb_error_msg_and_die("%%s requires a precision or a byte count.");
}
} else if (*p1 == '_') {
++p2;
switch (p1[1]) {
case 'A':
endfu = fu;
fu->flags |= F_IGNORE;
/* FALLTHROUGH */
case 'a':
pr->flags = F_ADDRESS;
++p2;
if ((p1[2] != 'd') && (p1[2] != 'o') && (p1[2] != 'x')) {
goto DO_BAD_CONV_CHAR;
}
*p1 = p1[2];
break;
case 'c':
pr->flags = F_C;
/* *p1 = 'c'; set in conv_c */
goto DO_BYTE_COUNT_1;
case 'p':
pr->flags = F_P;
*p1 = 'c';
goto DO_BYTE_COUNT_1;
case 'u':
pr->flags = F_U;
/* *p1 = 'c'; set in conv_u */
goto DO_BYTE_COUNT_1;
default:
goto DO_BAD_CONV_CHAR;
}
} else {
DO_BAD_CONV_CHAR:
bb_error_msg_and_die("bad conversion character %%%s.\n", p1);
}
/*
* copy to PR format string, set conversion character
* pointer, update original.
*/
savech = *p2;
p1[1] = '\0';
pr->fmt = xstrdup(fmtp);
*p2 = savech;
pr->cchar = pr->fmt + (p1 - fmtp);
/* DBU:[dave@cray.com] w/o this, trailing fmt text, space is lost.
* Skip subsequent text and up to the next % sign and tack the
* additional text onto fmt: eg. if fmt is "%x is a HEX number",
* we lose the " is a HEX number" part of fmt.
*/
for (p3 = p2; *p3 && *p3 != '%'; p3++);
if (p3 > p2)
{
savech = *p3;
*p3 = '\0';
pr->fmt = xrealloc(pr->fmt, strlen(pr->fmt)+(p3-p2)+1);
strcat(pr->fmt, p2);
*p3 = savech;
p2 = p3;
}
fmtp = p2;
/* only one conversion character if byte count */
if (!(pr->flags & F_ADDRESS) && fu->bcnt && nconv++) {
bb_error_msg_and_die("byte count with multiple conversion characters.\n");
}
}
/*
* if format unit byte count not specified, figure it out
* so can adjust rep count later.
*/
if (!fu->bcnt)
for (pr = fu->nextpr; pr; pr = pr->nextpr)
fu->bcnt += pr->bcnt;
}
/*
* if the format string interprets any data at all, and it's
* not the same as the bb_dump_blocksize, and its last format unit
* interprets any data at all, and has no iteration count,
* repeat it as necessary.
*
* if, rep count is greater than 1, no trailing whitespace
* gets output from the last iteration of the format unit.
*/
for (fu = fs->nextfu;; fu = fu->nextfu) {
if (!fu->nextfu && fs->bcnt < bb_dump_blocksize &&
!(fu->flags & F_SETREP) && fu->bcnt)
fu->reps += (bb_dump_blocksize - fs->bcnt) / fu->bcnt;
if (fu->reps > 1) {
for (pr = fu->nextpr;; pr = pr->nextpr)
if (!pr->nextpr)
break;
for (p1 = pr->fmt, p2 = NULL; *p1; ++p1)
p2 = isspace(*p1) ? p1 : NULL;
if (p2)
pr->nospace = p2;
}
if (!fu->nextfu)
break;
}
}
static void do_skip(char *fname, int statok)
{
struct stat sbuf;
if (statok) {
if (fstat(STDIN_FILENO, &sbuf)) {
bb_perror_msg_and_die("%s", fname);
}
if ((!(S_ISCHR(sbuf.st_mode) ||
S_ISBLK(sbuf.st_mode) ||
S_ISFIFO(sbuf.st_mode))) && bb_dump_skip >= sbuf.st_size) {
/* If bb_dump_size valid and bb_dump_skip >= size */
bb_dump_skip -= sbuf.st_size;
address += sbuf.st_size;
return;
}
}
if (fseek(stdin, bb_dump_skip, SEEK_SET)) {
bb_perror_msg_and_die("%s", fname);
}
savaddress = address += bb_dump_skip;
bb_dump_skip = 0;
}
static int next(char **argv)
{
static int done;
int statok;
if (argv) {
_argv = argv;
return (1);
}
for (;;) {
if (*_argv) {
if (!(freopen(*_argv, "r", stdin))) {
bb_perror_msg("%s", *_argv);
exitval = 1;
++_argv;
continue;
}
statok = done = 1;
} else {
if (done++)
return (0);
statok = 0;
}
if (bb_dump_skip)
do_skip(statok ? *_argv : "stdin", statok);
if (*_argv)
++_argv;
if (!bb_dump_skip)
return (1);
}
/* NOTREACHED */
}
static unsigned char *get(void)
{
static int ateof = 1;
static unsigned char *curp=NULL, *savp; /*DBU:[dave@cray.com]initialize curp */
int n;
int need, nread;
unsigned char *tmpp;
if (!curp) {
address = (off_t)0; /*DBU:[dave@cray.com] initialize,initialize..*/
curp = (unsigned char *) xmalloc(bb_dump_blocksize);
savp = (unsigned char *) xmalloc(bb_dump_blocksize);
} else {
tmpp = curp;
curp = savp;
savp = tmpp;
address = savaddress += bb_dump_blocksize;
}
for (need = bb_dump_blocksize, nread = 0;;) {
/*
* if read the right number of bytes, or at EOF for one file,
* and no other files are available, zero-pad the rest of the
* block and set the end flag.
*/
if (!bb_dump_length || (ateof && !next((char **) NULL))) {
if (need == bb_dump_blocksize) {
return ((unsigned char *) NULL);
}
if (bb_dump_vflag != ALL && !memcmp(curp, savp, nread)) {
if (bb_dump_vflag != DUP) {
printf("*\n");
}
return ((unsigned char *) NULL);
}
memset((char *) curp + nread, 0, need);
eaddress = address + nread;
return (curp);
}
n = fread((char *) curp + nread, sizeof(unsigned char),
bb_dump_length == -1 ? need : MIN(bb_dump_length, need), stdin);
if (!n) {
if (ferror(stdin)) {
bb_perror_msg("%s", _argv[-1]);
}
ateof = 1;
continue;
}
ateof = 0;
if (bb_dump_length != -1) {
bb_dump_length -= n;
}
if (!(need -= n)) {
if (bb_dump_vflag == ALL || bb_dump_vflag == FIRST
|| memcmp(curp, savp, bb_dump_blocksize)) {
if (bb_dump_vflag == DUP || bb_dump_vflag == FIRST) {
bb_dump_vflag = WAIT;
}
return (curp);
}
if (bb_dump_vflag == WAIT) {
printf("*\n");
}
bb_dump_vflag = DUP;
address = savaddress += bb_dump_blocksize;
need = bb_dump_blocksize;
nread = 0;
} else {
nread += n;
}
}
}
static void bpad(PR * pr)
{
char *p1, *p2;
/*
* remove all conversion flags; '-' is the only one valid
* with %s, and it's not useful here.
*/
pr->flags = F_BPAD;
*pr->cchar = 's';
for (p1 = pr->fmt; *p1 != '%'; ++p1);
for (p2 = ++p1; *p1 && strchr(" -0+#", *p1); ++p1)
if (pr->nospace) pr->nospace--;
while ((*p2++ = *p1++) != 0);
}
static const char conv_str[] =
"\0\\0\0"
"\007\\a\0" /* \a */
"\b\\b\0"
"\f\\b\0"
"\n\\n\0"
"\r\\r\0"
"\t\\t\0"
"\v\\v\0"
"\0";
static void conv_c(PR * pr, unsigned char * p)
{
const char *str = conv_str;
char buf[10];
do {
if (*p == *str) {
++str;
goto strpr;
}
str += 4;
} while (*str);
if (isprint(*p)) {
*pr->cchar = 'c';
(void) printf(pr->fmt, *p);
} else {
sprintf(buf, "%03o", (int) *p);
str = buf;
strpr:
*pr->cchar = 's';
printf(pr->fmt, str);
}
}
static void conv_u(PR * pr, unsigned char * p)
{
static const char list[] =
"nul\0soh\0stx\0etx\0eot\0enq\0ack\0bel\0"
"bs\0_ht\0_lf\0_vt\0_ff\0_cr\0_so\0_si\0_"
"dle\0dcl\0dc2\0dc3\0dc4\0nak\0syn\0etb\0"
"can\0em\0_sub\0esc\0fs\0_gs\0_rs\0_us";
/* od used nl, not lf */
if (*p <= 0x1f) {
*pr->cchar = 's';
printf(pr->fmt, list + (4 * (int)*p));
} else if (*p == 0x7f) {
*pr->cchar = 's';
printf(pr->fmt, "del");
} else if (isprint(*p)) {
*pr->cchar = 'c';
printf(pr->fmt, *p);
} else {
*pr->cchar = 'x';
printf(pr->fmt, (int) *p);
}
}
static void display(void)
{
/* extern FU *endfu; */
FS *fs;
FU *fu;
PR *pr;
int cnt;
unsigned char *bp;
off_t saveaddress;
unsigned char savech = 0, *savebp;
while ((bp = get()) != NULL) {
for (fs = bb_dump_fshead, savebp = bp, saveaddress = address; fs;
fs = fs->nextfs, bp = savebp, address = saveaddress) {
for (fu = fs->nextfu; fu; fu = fu->nextfu) {
if (fu->flags & F_IGNORE) {
break;
}
for (cnt = fu->reps; cnt; --cnt) {
for (pr = fu->nextpr; pr; address += pr->bcnt,
bp += pr->bcnt, pr = pr->nextpr) {
if (eaddress && address >= eaddress &&
!(pr->flags & (F_TEXT | F_BPAD))) {
bpad(pr);
}
if (cnt == 1 && pr->nospace) {
savech = *pr->nospace;
*pr->nospace = '\0';
}
/* PRINT; */
switch (pr->flags) {
case F_ADDRESS:
printf(pr->fmt, (unsigned int) address);
break;
case F_BPAD:
printf(pr->fmt, "");
break;
case F_C:
conv_c(pr, bp);
break;
case F_CHAR:
printf(pr->fmt, *bp);
break;
case F_DBL:{
double dval;
float fval;
switch (pr->bcnt) {
case 4:
memmove((char *) &fval, (char *) bp,
sizeof(fval));
printf(pr->fmt, fval);
break;
case 8:
memmove((char *) &dval, (char *) bp,
sizeof(dval));
printf(pr->fmt, dval);
break;
}
break;
}
case F_INT:{
int ival;
short sval;
switch (pr->bcnt) {
case 1:
printf(pr->fmt, (int) *bp);
break;
case 2:
memmove((char *) &sval, (char *) bp,
sizeof(sval));
printf(pr->fmt, (int) sval);
break;
case 4:
memmove((char *) &ival, (char *) bp,
sizeof(ival));
printf(pr->fmt, ival);
break;
}
break;
}
case F_P:
printf(pr->fmt, isprint(*bp) ? *bp : '.');
break;
case F_STR:
printf(pr->fmt, (char *) bp);
break;
case F_TEXT:
printf(pr->fmt);
break;
case F_U:
conv_u(pr, bp);
break;
case F_UINT:{
unsigned int ival;
unsigned short sval;
switch (pr->bcnt) {
case 1:
printf(pr->fmt, (unsigned int) * bp);
break;
case 2:
memmove((char *) &sval, (char *) bp,
sizeof(sval));
printf(pr->fmt, (unsigned int) sval);
break;
case 4:
memmove((char *) &ival, (char *) bp,
sizeof(ival));
printf(pr->fmt, ival);
break;
}
break;
}
}
if (cnt == 1 && pr->nospace) {
*pr->nospace = savech;
}
}
}
}
}
}
if (endfu) {
/*
* if eaddress not set, error or file bb_dump_size was multiple of
* bb_dump_blocksize, and no partial block ever found.
*/
if (!eaddress) {
if (!address) {
return;
}
eaddress = address;
}
for (pr = endfu->nextpr; pr; pr = pr->nextpr) {
switch (pr->flags) {
case F_ADDRESS:
(void) printf(pr->fmt, (unsigned int) eaddress);
break;
case F_TEXT:
(void) printf(pr->fmt);
break;
}
}
}
}
int bb_dump_dump(char **argv)
{
FS *tfs;
/* figure out the data block bb_dump_size */
for (bb_dump_blocksize = 0, tfs = bb_dump_fshead; tfs; tfs = tfs->nextfs) {
tfs->bcnt = bb_dump_size(tfs);
if (bb_dump_blocksize < tfs->bcnt) {
bb_dump_blocksize = tfs->bcnt;
}
}
/* rewrite the rules, do syntax checking */
for (tfs = bb_dump_fshead; tfs; tfs = tfs->nextfs) {
rewrite(tfs);
}
next(argv);
display();
return (exitval);
}
void bb_dump_add(const char *fmt)
{
const char *p;
char *p1;
char *p2;
static FS **nextfs;
FS *tfs;
FU *tfu, **nextfu;
const char *savep;
/* start new linked list of format units */
tfs = xzalloc(sizeof(FS)); /*DBU:[dave@cray.com] start out NULL */
if (!bb_dump_fshead) {
bb_dump_fshead = tfs;
} else {
*nextfs = tfs;
}
nextfs = &tfs->nextfs;
nextfu = &tfs->nextfu;
/* take the format string and break it up into format units */
for (p = fmt;;) {
/* bb_dump_skip leading white space */
p = skip_whitespace(p);
if (!*p) {
break;
}
/* allocate a new format unit and link it in */
/* NOSTRICT */
/* DBU:[dave@cray.com] calloc so that forward pointers start out NULL */
tfu = xzalloc(sizeof(FU));
*nextfu = tfu;
nextfu = &tfu->nextfu;
tfu->reps = 1;
/* if leading digit, repetition count */
if (isdigit(*p)) {
for (savep = p; isdigit(*p); ++p);
if (!isspace(*p) && *p != '/') {
bb_error_msg_and_die("bad format {%s}", fmt);
}
/* may overwrite either white space or slash */
tfu->reps = atoi(savep);
tfu->flags = F_SETREP;
/* bb_dump_skip trailing white space */
p = skip_whitespace(++p);
}
/* bb_dump_skip slash and trailing white space */
if (*p == '/') {
p = skip_whitespace(++p);
}
/* byte count */
if (isdigit(*p)) {
for (savep = p; isdigit(*p); ++p);
if (!isspace(*p)) {
bb_error_msg_and_die("bad format {%s}", fmt);
}
tfu->bcnt = atoi(savep);
/* bb_dump_skip trailing white space */
p = skip_whitespace(++p);
}
/* format */
if (*p != '"') {
bb_error_msg_and_die("bad format {%s}", fmt);
}
for (savep = ++p; *p != '"';) {
if (*p++ == 0) {
bb_error_msg_and_die("bad format {%s}", fmt);
}
}
tfu->fmt = xmalloc(p - savep + 1);
strncpy(tfu->fmt, savep, p - savep);
tfu->fmt[p - savep] = '\0';
/* escape(tfu->fmt); */
p1 = tfu->fmt;
/* alphabetic escape sequences have to be done in place */
for (p2 = p1;; ++p1, ++p2) {
if (!*p1) {
*p2 = *p1;
break;
}
if (*p1 == '\\') {
const char *cs = conv_str + 4;
++p1;
*p2 = *p1;
do {
if (*p1 == cs[2]) {
*p2 = cs[0];
break;
}
cs += 4;
} while (*cs);
}
}
p++;
}
}
/*
* Copyright (c) 1989 The Regents of the University of California.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the University nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/