mksh/shf.c
tg a34b05d2e6 Import OpenBSD 3.3 source repository from CTM 3132 the first time
This opens an OpenBSD-mirabile (aka MirBSD) repository.

### MirBSD is:
# Copyright (c) 1982-2003 by Thorsten "mirabile" Glaser <x86@ePost.de>
# Copyright © 1968-2003  The authors of And contributors to UNIX®, the
#       C Language, BSD/Berkeley Unix; 386BSD, NetBSD 1.1 and OpenBSD.
#
# Anyone who obtained a copy of this work is hereby permitted to freely use,
# distribute, modify, merge, sublicence, give away or sell it as long as the
# authors are given due credit and the following notice is retained:
#
# This work is provided "as is", with no explicit or implicit warranty what-
# soever. Use it only at your own risk. In no event may an author or contri-
# butor be held liable for any damage, directly or indirectly, that origina-
# ted through or is caused by creation or modification of this work.

MirBSD is my private tree. MirBSD does not differ very much from OpenBSD
and intentionally tracks OpenBSD. That's why it _is_ OpenBSD, just not the
official one. It's like with DarrenBSD.

At time of this writing, no advertising for MirBSD must be done,
because the advertising clause has not yet been sorted out.

http://templeofhate.com/tglaser/MirBSD/index.php
2003-03-22 17:35:03 +00:00

1296 lines
27 KiB
C

/* $OpenBSD: shf.c,v 1.8 2003/02/28 09:45:09 jmc Exp $ */
/*
* Shell file I/O routines
*/
#include "sh.h"
#include "ksh_stat.h"
#include "ksh_limval.h"
/* flags to shf_emptybuf() */
#define EB_READSW 0x01 /* about to switch to reading */
#define EB_GROW 0x02 /* grow buffer if necessary (STRING+DYNAMIC) */
/*
* Replacement stdio routines. Stdio is too flakey on too many machines
* to be useful when you have multiple processes using the same underlying
* file descriptors.
*/
static int shf_fillbuf ARGS((struct shf *shf));
static int shf_emptybuf ARGS((struct shf *shf, int flags));
/* Open a file. First three args are for open(), last arg is flags for
* this package. Returns NULL if file could not be opened, or if a dup
* fails.
*/
struct shf *
shf_open(name, oflags, mode, sflags)
const char *name;
int oflags;
int mode;
int sflags;
{
struct shf *shf;
int bsize = sflags & SHF_UNBUF ? (sflags & SHF_RD ? 1 : 0) : SHF_BSIZE;
int fd;
/* Done before open so if alloca fails, fd won't be lost. */
shf = (struct shf *) alloc(sizeof(struct shf) + bsize, ATEMP);
shf->areap = ATEMP;
shf->buf = (unsigned char *) &shf[1];
shf->bsize = bsize;
shf->flags = SHF_ALLOCS;
/* Rest filled in by reopen. */
fd = open(name, oflags, mode);
if (fd < 0) {
afree(shf, shf->areap);
return NULL;
}
if ((sflags & SHF_MAPHI) && fd < FDBASE) {
int nfd;
nfd = ksh_dupbase(fd, FDBASE);
close(fd);
if (nfd < 0) {
afree(shf, shf->areap);
return NULL;
}
fd = nfd;
}
sflags &= ~SHF_ACCMODE;
sflags |= (oflags & O_ACCMODE) == O_RDONLY ? SHF_RD
: ((oflags & O_ACCMODE) == O_WRONLY ? SHF_WR
: SHF_RDWR);
return shf_reopen(fd, sflags, shf);
}
/* Set up the shf structure for a file descriptor. Doesn't fail. */
struct shf *
shf_fdopen(fd, sflags, shf)
int fd;
int sflags;
struct shf *shf;
{
int bsize = sflags & SHF_UNBUF ? (sflags & SHF_RD ? 1 : 0) : SHF_BSIZE;
/* use fcntl() to figure out correct read/write flags */
if (sflags & SHF_GETFL) {
int flags = fcntl(fd, F_GETFL, 0);
if (flags < 0)
/* will get an error on first read/write */
sflags |= SHF_RDWR;
else
switch (flags & O_ACCMODE) {
case O_RDONLY: sflags |= SHF_RD; break;
case O_WRONLY: sflags |= SHF_WR; break;
case O_RDWR: sflags |= SHF_RDWR; break;
}
}
if (!(sflags & (SHF_RD | SHF_WR)))
internal_errorf(1, "shf_fdopen: missing read/write");
if (shf) {
if (bsize) {
shf->buf = (unsigned char *) alloc(bsize, ATEMP);
sflags |= SHF_ALLOCB;
} else
shf->buf = (unsigned char *) 0;
} else {
shf = (struct shf *) alloc(sizeof(struct shf) + bsize, ATEMP);
shf->buf = (unsigned char *) &shf[1];
sflags |= SHF_ALLOCS;
}
shf->areap = ATEMP;
shf->fd = fd;
shf->rp = shf->wp = shf->buf;
shf->rnleft = 0;
shf->rbsize = bsize;
shf->wnleft = 0; /* force call to shf_emptybuf() */
shf->wbsize = sflags & SHF_UNBUF ? 0 : bsize;
shf->flags = sflags;
shf->errno_ = 0;
shf->bsize = bsize;
if (sflags & SHF_CLEXEC)
fd_clexec(fd);
return shf;
}
/* Set up an existing shf (and buffer) to use the given fd */
struct shf *
shf_reopen(fd, sflags, shf)
int fd;
int sflags;
struct shf *shf;
{
int bsize = sflags & SHF_UNBUF ? (sflags & SHF_RD ? 1 : 0) : SHF_BSIZE;
/* use fcntl() to figure out correct read/write flags */
if (sflags & SHF_GETFL) {
int flags = fcntl(fd, F_GETFL, 0);
if (flags < 0)
/* will get an error on first read/write */
sflags |= SHF_RDWR;
else
switch (flags & O_ACCMODE) {
case O_RDONLY: sflags |= SHF_RD; break;
case O_WRONLY: sflags |= SHF_WR; break;
case O_RDWR: sflags |= SHF_RDWR; break;
}
}
if (!(sflags & (SHF_RD | SHF_WR)))
internal_errorf(1, "shf_reopen: missing read/write");
if (!shf || !shf->buf || shf->bsize < bsize)
internal_errorf(1, "shf_reopen: bad shf/buf/bsize");
/* assumes shf->buf and shf->bsize already set up */
shf->fd = fd;
shf->rp = shf->wp = shf->buf;
shf->rnleft = 0;
shf->rbsize = bsize;
shf->wnleft = 0; /* force call to shf_emptybuf() */
shf->wbsize = sflags & SHF_UNBUF ? 0 : bsize;
shf->flags = (shf->flags & (SHF_ALLOCS | SHF_ALLOCB)) | sflags;
shf->errno_ = 0;
if (sflags & SHF_CLEXEC)
fd_clexec(fd);
return shf;
}
/* Open a string for reading or writing. If reading, bsize is the number
* of bytes that can be read. If writing, bsize is the maximum number of
* bytes that can be written. If shf is not null, it is filled in and
* returned, if it is null, shf is allocated. If writing and buf is null
* and SHF_DYNAMIC is set, the buffer is allocated (if bsize > 0, it is
* used for the initial size). Doesn't fail.
* When writing, a byte is reserved for a trailing null - see shf_sclose().
*/
struct shf *
shf_sopen(buf, bsize, sflags, shf)
char *buf;
int bsize;
int sflags;
struct shf *shf;
{
/* can't have a read+write string */
if (!(sflags & (SHF_RD | SHF_WR))
|| (sflags & (SHF_RD | SHF_WR)) == (SHF_RD | SHF_WR))
internal_errorf(1, "shf_sopen: flags 0x%x", sflags);
if (!shf) {
shf = (struct shf *) alloc(sizeof(struct shf), ATEMP);
sflags |= SHF_ALLOCS;
}
shf->areap = ATEMP;
if (!buf && (sflags & SHF_WR) && (sflags & SHF_DYNAMIC)) {
if (bsize <= 0)
bsize = 64;
sflags |= SHF_ALLOCB;
buf = alloc(bsize, shf->areap);
}
shf->fd = -1;
shf->buf = shf->rp = shf->wp = (unsigned char *) buf;
shf->rnleft = bsize;
shf->rbsize = bsize;
shf->wnleft = bsize - 1; /* space for a '\0' */
shf->wbsize = bsize;
shf->flags = sflags | SHF_STRING;
shf->errno_ = 0;
shf->bsize = bsize;
return shf;
}
/* Flush and close file descriptor, free the shf structure */
int
shf_close(shf)
struct shf *shf;
{
int ret = 0;
if (shf->fd >= 0) {
ret = shf_flush(shf);
if (close(shf->fd) < 0)
ret = EOF;
}
if (shf->flags & SHF_ALLOCS)
afree(shf, shf->areap);
else if (shf->flags & SHF_ALLOCB)
afree(shf->buf, shf->areap);
return ret;
}
/* Flush and close file descriptor, don't free file structure */
int
shf_fdclose(shf)
struct shf *shf;
{
int ret = 0;
if (shf->fd >= 0) {
ret = shf_flush(shf);
if (close(shf->fd) < 0)
ret = EOF;
shf->rnleft = 0;
shf->rp = shf->buf;
shf->wnleft = 0;
shf->fd = -1;
}
return ret;
}
/* Close a string - if it was opened for writing, it is null terminated;
* returns a pointer to the string and frees shf if it was allocated
* (does not free string if it was allocated).
*/
char *
shf_sclose(shf)
struct shf *shf;
{
unsigned char *s = shf->buf;
/* null terminate */
if (shf->flags & SHF_WR) {
shf->wnleft++;
shf_putc('\0', shf);
}
if (shf->flags & SHF_ALLOCS)
afree(shf, shf->areap);
return (char *) s;
}
/* Flush and free file structure, don't close file descriptor */
int
shf_finish(shf)
struct shf *shf;
{
int ret = 0;
if (shf->fd >= 0)
ret = shf_flush(shf);
if (shf->flags & SHF_ALLOCS)
afree(shf, shf->areap);
else if (shf->flags & SHF_ALLOCB)
afree(shf->buf, shf->areap);
return ret;
}
/* Un-read what has been read but not examined, or write what has been
* buffered. Returns 0 for success, EOF for (write) error.
*/
int
shf_flush(shf)
struct shf *shf;
{
if (shf->flags & SHF_STRING)
return (shf->flags & SHF_WR) ? EOF : 0;
if (shf->fd < 0)
internal_errorf(1, "shf_flush: no fd");
if (shf->flags & SHF_ERROR) {
errno = shf->errno_;
return EOF;
}
if (shf->flags & SHF_READING) {
shf->flags &= ~(SHF_EOF | SHF_READING);
if (shf->rnleft > 0) {
lseek(shf->fd, (off_t) -shf->rnleft, 1);
shf->rnleft = 0;
shf->rp = shf->buf;
}
return 0;
} else if (shf->flags & SHF_WRITING)
return shf_emptybuf(shf, 0);
return 0;
}
/* Write out any buffered data. If currently reading, flushes the read
* buffer. Returns 0 for success, EOF for (write) error.
*/
static int
shf_emptybuf(shf, flags)
struct shf *shf;
int flags;
{
int ret = 0;
if (!(shf->flags & SHF_STRING) && shf->fd < 0)
internal_errorf(1, "shf_emptybuf: no fd");
if (shf->flags & SHF_ERROR) {
errno = shf->errno_;
return EOF;
}
if (shf->flags & SHF_READING) {
if (flags & EB_READSW) /* doesn't happen */
return 0;
ret = shf_flush(shf);
shf->flags &= ~SHF_READING;
}
if (shf->flags & SHF_STRING) {
unsigned char *nbuf;
/* Note that we assume SHF_ALLOCS is not set if SHF_ALLOCB
* is set... (changing the shf pointer could cause problems)
*/
if (!(flags & EB_GROW) || !(shf->flags & SHF_DYNAMIC)
|| !(shf->flags & SHF_ALLOCB))
return EOF;
/* allocate more space for buffer */
nbuf = (unsigned char *) aresize(shf->buf, shf->wbsize * 2,
shf->areap);
shf->rp = nbuf + (shf->rp - shf->buf);
shf->wp = nbuf + (shf->wp - shf->buf);
shf->rbsize += shf->wbsize;
shf->wnleft += shf->wbsize;
shf->wbsize *= 2;
shf->buf = nbuf;
} else {
if (shf->flags & SHF_WRITING) {
int ntowrite = shf->wp - shf->buf;
unsigned char *buf = shf->buf;
int n;
while (ntowrite > 0) {
n = write(shf->fd, buf, ntowrite);
if (n < 0) {
if (errno == EINTR
&& !(shf->flags & SHF_INTERRUPT))
continue;
shf->flags |= SHF_ERROR;
shf->errno_ = errno;
shf->wnleft = 0;
if (buf != shf->buf) {
/* allow a second flush
* to work */
memmove(shf->buf, buf,
ntowrite);
shf->wp = shf->buf + ntowrite;
}
return EOF;
}
buf += n;
ntowrite -= n;
}
if (flags & EB_READSW) {
shf->wp = shf->buf;
shf->wnleft = 0;
shf->flags &= ~SHF_WRITING;
return 0;
}
}
shf->wp = shf->buf;
shf->wnleft = shf->wbsize;
}
shf->flags |= SHF_WRITING;
return ret;
}
/* Fill up a read buffer. Returns EOF for a read error, 0 otherwise. */
static int
shf_fillbuf(shf)
struct shf *shf;
{
if (shf->flags & SHF_STRING)
return 0;
if (shf->fd < 0)
internal_errorf(1, "shf_fillbuf: no fd");
if (shf->flags & (SHF_EOF | SHF_ERROR)) {
if (shf->flags & SHF_ERROR)
errno = shf->errno_;
return EOF;
}
if ((shf->flags & SHF_WRITING) && shf_emptybuf(shf, EB_READSW) == EOF)
return EOF;
shf->flags |= SHF_READING;
shf->rp = shf->buf;
while (1) {
shf->rnleft = blocking_read(shf->fd, (char *) shf->buf,
shf->rbsize);
if (shf->rnleft < 0 && errno == EINTR
&& !(shf->flags & SHF_INTERRUPT))
continue;
break;
}
if (shf->rnleft <= 0) {
if (shf->rnleft < 0) {
shf->flags |= SHF_ERROR;
shf->errno_ = errno;
shf->rnleft = 0;
shf->rp = shf->buf;
return EOF;
}
shf->flags |= SHF_EOF;
}
return 0;
}
/* Seek to a new position in the file. If writing, flushes the buffer
* first. If reading, optimizes small relative seeks that stay inside the
* buffer. Returns 0 for success, EOF otherwise.
*/
int
shf_seek(shf, where, from)
struct shf *shf;
off_t where;
int from;
{
if (shf->fd < 0) {
errno = EINVAL;
return EOF;
}
if (shf->flags & SHF_ERROR) {
errno = shf->errno_;
return EOF;
}
if ((shf->flags & SHF_WRITING) && shf_emptybuf(shf, EB_READSW) == EOF)
return EOF;
if (shf->flags & SHF_READING) {
if (from == SEEK_CUR &&
(where < 0 ?
-where >= shf->rbsize - shf->rnleft :
where < shf->rnleft)) {
shf->rnleft -= where;
shf->rp += where;
return 0;
}
shf->rnleft = 0;
shf->rp = shf->buf;
}
shf->flags &= ~(SHF_EOF | SHF_READING | SHF_WRITING);
if (lseek(shf->fd, where, from) < 0) {
shf->errno_ = errno;
shf->flags |= SHF_ERROR;
return EOF;
}
return 0;
}
/* Read a buffer from shf. Returns the number of bytes read into buf,
* if no bytes were read, returns 0 if end of file was seen, EOF if
* a read error occurred.
*/
int
shf_read(buf, bsize, shf)
char *buf;
int bsize;
struct shf *shf;
{
int orig_bsize = bsize;
int ncopy;
if (!(shf->flags & SHF_RD))
internal_errorf(1, "shf_read: flags %x", shf->flags);
if (bsize <= 0)
internal_errorf(1, "shf_read: bsize %d", bsize);
while (bsize > 0) {
if (shf->rnleft == 0
&& (shf_fillbuf(shf) == EOF || shf->rnleft == 0))
break;
ncopy = shf->rnleft;
if (ncopy > bsize)
ncopy = bsize;
memcpy(buf, shf->rp, ncopy);
buf += ncopy;
bsize -= ncopy;
shf->rp += ncopy;
shf->rnleft -= ncopy;
}
/* Note: fread(3S) returns 0 for errors - this doesn't */
return orig_bsize == bsize ? (shf_error(shf) ? EOF : 0)
: orig_bsize - bsize;
}
/* Read up to a newline or EOF. The newline is put in buf; buf is always
* null terminated. Returns NULL on read error or if nothing was read before
* end of file, returns a pointer to the null byte in buf otherwise.
*/
char *
shf_getse(buf, bsize, shf)
char *buf;
int bsize;
struct shf *shf;
{
unsigned char *end;
int ncopy;
char *orig_buf = buf;
if (!(shf->flags & SHF_RD))
internal_errorf(1, "shf_getse: flags %x", shf->flags);
if (bsize <= 0)
return (char *) 0;
--bsize; /* save room for null */
do {
if (shf->rnleft == 0) {
if (shf_fillbuf(shf) == EOF)
return NULL;
if (shf->rnleft == 0) {
*buf = '\0';
return buf == orig_buf ? NULL : buf;
}
}
end = (unsigned char *) memchr((char *) shf->rp, '\n',
shf->rnleft);
ncopy = end ? end - shf->rp + 1 : shf->rnleft;
if (ncopy > bsize)
ncopy = bsize;
memcpy(buf, (char *) shf->rp, ncopy);
shf->rp += ncopy;
shf->rnleft -= ncopy;
buf += ncopy;
bsize -= ncopy;
#ifdef OS2
if (end && buf > orig_buf + 1 && buf[-2] == '\r') {
buf--;
bsize++;
buf[-1] = '\n';
}
#endif
} while (!end && bsize);
*buf = '\0';
return buf;
}
/* Returns the char read. Returns EOF for error and end of file. */
int
shf_getchar(shf)
struct shf *shf;
{
if (!(shf->flags & SHF_RD))
internal_errorf(1, "shf_getchar: flags %x", shf->flags);
if (shf->rnleft == 0 && (shf_fillbuf(shf) == EOF || shf->rnleft == 0))
return EOF;
--shf->rnleft;
return *shf->rp++;
}
/* Put a character back in the input stream. Returns the character if
* successful, EOF if there is no room.
*/
int
shf_ungetc(c, shf)
int c;
struct shf *shf;
{
if (!(shf->flags & SHF_RD))
internal_errorf(1, "shf_ungetc: flags %x", shf->flags);
if ((shf->flags & SHF_ERROR) || c == EOF
|| (shf->rp == shf->buf && shf->rnleft))
return EOF;
if ((shf->flags & SHF_WRITING) && shf_emptybuf(shf, EB_READSW) == EOF)
return EOF;
if (shf->rp == shf->buf)
shf->rp = shf->buf + shf->rbsize;
if (shf->flags & SHF_STRING) {
/* Can unget what was read, but not something different - we
* don't want to modify a string.
*/
if (shf->rp[-1] != c)
return EOF;
shf->flags &= ~SHF_EOF;
shf->rp--;
shf->rnleft++;
return c;
}
shf->flags &= ~SHF_EOF;
*--(shf->rp) = c;
shf->rnleft++;
return c;
}
/* Write a character. Returns the character if successful, EOF if
* the char could not be written.
*/
int
shf_putchar(c, shf)
int c;
struct shf *shf;
{
if (!(shf->flags & SHF_WR))
internal_errorf(1, "shf_putchar: flags %x", shf->flags);
if (c == EOF)
return EOF;
if (shf->flags & SHF_UNBUF) {
char cc = c;
int n;
if (shf->fd < 0)
internal_errorf(1, "shf_putchar: no fd");
if (shf->flags & SHF_ERROR) {
errno = shf->errno_;
return EOF;
}
while ((n = write(shf->fd, &cc, 1)) != 1)
if (n < 0) {
if (errno == EINTR
&& !(shf->flags & SHF_INTERRUPT))
continue;
shf->flags |= SHF_ERROR;
shf->errno_ = errno;
return EOF;
}
} else {
/* Flush deals with strings and sticky errors */
if (shf->wnleft == 0 && shf_emptybuf(shf, EB_GROW) == EOF)
return EOF;
shf->wnleft--;
*shf->wp++ = c;
}
return c;
}
/* Write a string. Returns the length of the string if successful, EOF if
* the string could not be written.
*/
int
shf_puts(s, shf)
const char *s;
struct shf *shf;
{
if (!s)
return EOF;
return shf_write(s, strlen(s), shf);
}
/* Write a buffer. Returns nbytes if successful, EOF if there is an error. */
int
shf_write(buf, nbytes, shf)
const char *buf;
int nbytes;
struct shf *shf;
{
int orig_nbytes = nbytes;
int n;
int ncopy;
if (!(shf->flags & SHF_WR))
internal_errorf(1, "shf_write: flags %x", shf->flags);
if (nbytes < 0)
internal_errorf(1, "shf_write: nbytes %d", nbytes);
/* Don't buffer if buffer is empty and we're writting a large amount. */
if ((ncopy = shf->wnleft)
&& (shf->wp != shf->buf || nbytes < shf->wnleft))
{
if (ncopy > nbytes)
ncopy = nbytes;
memcpy(shf->wp, buf, ncopy);
nbytes -= ncopy;
buf += ncopy;
shf->wp += ncopy;
shf->wnleft -= ncopy;
}
if (nbytes > 0) {
/* Flush deals with strings and sticky errors */
if (shf_emptybuf(shf, EB_GROW) == EOF)
return EOF;
if (nbytes > shf->wbsize) {
ncopy = nbytes;
if (shf->wbsize)
ncopy -= nbytes % shf->wbsize;
nbytes -= ncopy;
while (ncopy > 0) {
n = write(shf->fd, buf, ncopy);
if (n < 0) {
if (errno == EINTR
&& !(shf->flags & SHF_INTERRUPT))
continue;
shf->flags |= SHF_ERROR;
shf->errno_ = errno;
shf->wnleft = 0;
/* Note: fwrite(3S) returns 0 for
* errors - this doesn't */
return EOF;
}
buf += n;
ncopy -= n;
}
}
if (nbytes > 0) {
memcpy(shf->wp, buf, nbytes);
shf->wp += nbytes;
shf->wnleft -= nbytes;
}
}
return orig_nbytes;
}
int
#ifdef HAVE_PROTOTYPES
shf_fprintf(struct shf *shf, const char *fmt, ...)
#else
shf_fprintf(shf, fmt, va_alist)
struct shf *shf;
const char *fmt;
va_dcl
#endif
{
va_list args;
int n;
SH_VA_START(args, fmt);
n = shf_vfprintf(shf, fmt, args);
va_end(args);
return n;
}
int
#ifdef HAVE_PROTOTYPES
shf_snprintf(char *buf, int bsize, const char *fmt, ...)
#else
shf_snprintf(buf, bsize, fmt, va_alist)
char *buf;
int bsize;
const char *fmt;
va_dcl
#endif
{
struct shf shf;
va_list args;
int n;
if (!buf || bsize <= 0)
internal_errorf(1, "shf_snprintf: buf %lx, bsize %d",
(long) buf, bsize);
shf_sopen(buf, bsize, SHF_WR, &shf);
SH_VA_START(args, fmt);
n = shf_vfprintf(&shf, fmt, args);
va_end(args);
shf_sclose(&shf); /* null terminates */
return n;
}
char *
#ifdef HAVE_PROTOTYPES
shf_smprintf(const char *fmt, ...)
#else
shf_smprintf(fmt, va_alist)
char *fmt;
va_dcl
#endif
{
struct shf shf;
va_list args;
shf_sopen((char *) 0, 0, SHF_WR|SHF_DYNAMIC, &shf);
SH_VA_START(args, fmt);
shf_vfprintf(&shf, fmt, args);
va_end(args);
return shf_sclose(&shf); /* null terminates */
}
#undef FP /* if you want floating point stuff */
#define BUF_SIZE 128
#define FPBUF_SIZE (DMAXEXP+16)/* this must be >
* MAX(DMAXEXP, log10(pow(2, DSIGNIF)))
* + ceil(log10(DMAXEXP)) + 8 (I think).
* Since this is hard to express as a
* constant, just use a large buffer.
*/
/*
* What kinda of machine we on? Hopefully the C compiler will optimize
* this out...
*
* For shorts, we want sign extend for %d but not for %[oxu] - on 16 bit
* machines it don't matter. Assumes C compiler has converted shorts to
* ints before pushing them.
*/
#define POP_INT(f, s, a) (((f) & FL_LONG) ? \
va_arg((a), unsigned long) \
: \
(sizeof(int) < sizeof(long) ? \
((s) ? \
(long) va_arg((a), int) \
: \
va_arg((a), unsigned)) \
: \
va_arg((a), unsigned)))
#define ABIGNUM 32000 /* big numer that will fit in a short */
#define LOG2_10 3.321928094887362347870319429 /* log base 2 of 10 */
#define FL_HASH 0x001 /* `#' seen */
#define FL_PLUS 0x002 /* `+' seen */
#define FL_RIGHT 0x004 /* `-' seen */
#define FL_BLANK 0x008 /* ` ' seen */
#define FL_SHORT 0x010 /* `h' seen */
#define FL_LONG 0x020 /* `l' seen */
#define FL_ZERO 0x040 /* `0' seen */
#define FL_DOT 0x080 /* '.' seen */
#define FL_UPPER 0x100 /* format character was uppercase */
#define FL_NUMBER 0x200 /* a number was formated %[douxefg] */
#ifdef FP
#include <math.h>
static double
my_ceil(d)
double d;
{
double i;
return d - modf(d, &i) + (d < 0 ? -1 : 1);
}
#endif /* FP */
int
shf_vfprintf(shf, fmt, args)
struct shf *shf;
const char *fmt;
va_list args;
{
char c, *s;
int UNINITIALIZED(tmp);
int field, precision;
int len;
int flags;
unsigned long lnum;
/* %#o produces the longest output */
char numbuf[(BITS(long) + 2) / 3 + 1];
/* this stuff for dealing with the buffer */
int nwritten = 0;
#ifdef FP
/* should be in <math.h>
* extern double frexp();
*/
extern char *ecvt();
double fpnum;
int expo, decpt;
char style;
char fpbuf[FPBUF_SIZE];
#endif /* FP */
if (!fmt)
return 0;
while ((c = *fmt++)) {
if (c != '%') {
shf_putc(c, shf);
nwritten++;
continue;
}
/*
* This will accept flags/fields in any order - not
* just the order specified in printf(3), but this is
* the way _doprnt() seems to work (on bsd and sysV).
* The only restriction is that the format character must
* come last :-).
*/
flags = field = precision = 0;
for ( ; (c = *fmt++) ; ) {
switch (c) {
case '#':
flags |= FL_HASH;
continue;
case '+':
flags |= FL_PLUS;
continue;
case '-':
flags |= FL_RIGHT;
continue;
case ' ':
flags |= FL_BLANK;
continue;
case '0':
if (!(flags & FL_DOT))
flags |= FL_ZERO;
continue;
case '.':
flags |= FL_DOT;
precision = 0;
continue;
case '*':
tmp = va_arg(args, int);
if (flags & FL_DOT)
precision = tmp;
else if ((field = tmp) < 0) {
field = -field;
flags |= FL_RIGHT;
}
continue;
case 'l':
flags |= FL_LONG;
continue;
case 'h':
flags |= FL_SHORT;
continue;
}
if (digit(c)) {
tmp = c - '0';
while (c = *fmt++, digit(c))
tmp = tmp * 10 + c - '0';
--fmt;
if (tmp < 0) /* overflow? */
tmp = 0;
if (flags & FL_DOT)
precision = tmp;
else
field = tmp;
continue;
}
break;
}
if (precision < 0)
precision = 0;
if (!c) /* nasty format */
break;
if (c >= 'A' && c <= 'Z') {
flags |= FL_UPPER;
c = c - 'A' + 'a';
}
switch (c) {
case 'p': /* pointer */
flags &= ~(FL_LONG | FL_SHORT);
if (sizeof(char *) > sizeof(int))
flags |= FL_LONG; /* hope it fits.. */
/* aaahhh... */
case 'd':
case 'i':
case 'o':
case 'u':
case 'x':
flags |= FL_NUMBER;
s = &numbuf[sizeof(numbuf)];
lnum = POP_INT(flags, c == 'd', args);
switch (c) {
case 'd':
case 'i':
if (0 > (long) lnum)
lnum = - (long) lnum, tmp = 1;
else
tmp = 0;
/* aaahhhh..... */
case 'u':
do {
*--s = lnum % 10 + '0';
lnum /= 10;
} while (lnum);
if (c != 'u') {
if (tmp)
*--s = '-';
else if (flags & FL_PLUS)
*--s = '+';
else if (flags & FL_BLANK)
*--s = ' ';
}
break;
case 'o':
do {
*--s = (lnum & 0x7) + '0';
lnum >>= 3;
} while (lnum);
if ((flags & FL_HASH) && *s != '0')
*--s = '0';
break;
case 'p':
case 'x':
{
const char *digits = (flags & FL_UPPER) ?
"0123456789ABCDEF"
: "0123456789abcdef";
do {
*--s = digits[lnum & 0xf];
lnum >>= 4;
} while (lnum);
if (flags & FL_HASH) {
*--s = (flags & FL_UPPER) ? 'X' : 'x';
*--s = '0';
}
}
}
len = &numbuf[sizeof(numbuf)] - s;
if (flags & FL_DOT) {
if (precision > len) {
field = precision;
flags |= FL_ZERO;
} else
precision = len; /* no loss */
}
break;
#ifdef FP
case 'e':
case 'g':
case 'f':
{
char *p;
/*
* This could probably be done better,
* but it seems to work. Note that gcvt()
* is not used, as you cannot tell it to
* not strip the zeros.
*/
flags |= FL_NUMBER;
if (!(flags & FL_DOT))
precision = 6; /* default */
/*
* Assumes doubles are pushed on
* the stack. If this is not so, then
* FL_LONG/FL_SHORT should be checked.
*/
fpnum = va_arg(args, double);
s = fpbuf;
style = c;
/*
* This is the same as
* expo = ceil(log10(fpnum))
* but doesn't need -lm. This is an
* approximation as expo is rounded up.
*/
(void) frexp(fpnum, &expo);
expo = my_ceil(expo / LOG2_10);
if (expo < 0)
expo = 0;
p = ecvt(fpnum, precision + 1 + expo,
&decpt, &tmp);
if (c == 'g') {
if (decpt < -4 || decpt > precision)
style = 'e';
else
style = 'f';
if (decpt > 0 && (precision -= decpt) < 0)
precision = 0;
}
if (tmp)
*s++ = '-';
else if (flags & FL_PLUS)
*s++ = '+';
else if (flags & FL_BLANK)
*s++ = ' ';
if (style == 'e')
*s++ = *p++;
else {
if (decpt > 0) {
/* Overflow check - should
* never have this problem.
*/
if (decpt >
&fpbuf[sizeof(fpbuf)]
- s - 8)
decpt =
&fpbuf[sizeof(fpbuf)]
- s - 8;
(void) memcpy(s, p, decpt);
s += decpt;
p += decpt;
} else
*s++ = '0';
}
/* print the fraction? */
if (precision > 0) {
*s++ = '.';
/* Overflow check - should
* never have this problem.
*/
if (precision > &fpbuf[sizeof(fpbuf)]
- s - 7)
precision =
&fpbuf[sizeof(fpbuf)]
- s - 7;
for (tmp = decpt; tmp++ < 0 &&
precision > 0 ; precision--)
*s++ = '0';
tmp = strlen(p);
if (precision > tmp)
precision = tmp;
/* Overflow check - should
* never have this problem.
*/
if (precision > &fpbuf[sizeof(fpbuf)]
- s - 7)
precision =
&fpbuf[sizeof(fpbuf)]
- s - 7;
(void) memcpy(s, p, precision);
s += precision;
/*
* `g' format strips trailing
* zeros after the decimal.
*/
if (c == 'g' && !(flags & FL_HASH)) {
while (*--s == '0')
;
if (*s != '.')
s++;
}
} else if (flags & FL_HASH)
*s++ = '.';
if (style == 'e') {
*s++ = (flags & FL_UPPER) ? 'E' : 'e';
if (--decpt >= 0)
*s++ = '+';
else {
*s++ = '-';
decpt = -decpt;
}
p = &numbuf[sizeof(numbuf)];
for (tmp = 0; tmp < 2 || decpt ; tmp++) {
*--p = '0' + decpt % 10;
decpt /= 10;
}
tmp = &numbuf[sizeof(numbuf)] - p;
(void) memcpy(s, p, tmp);
s += tmp;
}
len = s - fpbuf;
s = fpbuf;
precision = len;
break;
}
#endif /* FP */
case 's':
if (!(s = va_arg(args, char *)))
s = "(null %s)";
len = strlen(s);
break;
case 'c':
flags &= ~FL_DOT;
numbuf[0] = va_arg(args, int);
s = numbuf;
len = 1;
break;
case '%':
default:
numbuf[0] = c;
s = numbuf;
len = 1;
break;
}
/*
* At this point s should point to a string that is
* to be formatted, and len should be the length of the
* string.
*/
if (!(flags & FL_DOT) || len < precision)
precision = len;
if (field > precision) {
field -= precision;
if (!(flags & FL_RIGHT)) {
field = -field;
/* skip past sign or 0x when padding with 0 */
if ((flags & FL_ZERO) && (flags & FL_NUMBER)) {
if (*s == '+' || *s == '-' || *s ==' ')
{
shf_putc(*s, shf);
s++;
precision--;
nwritten++;
} else if (*s == '0') {
shf_putc(*s, shf);
s++;
nwritten++;
if (--precision > 0 &&
(*s | 0x20) == 'x')
{
shf_putc(*s, shf);
s++;
precision--;
nwritten++;
}
}
c = '0';
} else
c = flags & FL_ZERO ? '0' : ' ';
if (field < 0) {
nwritten += -field;
for ( ; field < 0 ; field++)
shf_putc(c, shf);
}
} else
c = ' ';
} else
field = 0;
if (precision > 0) {
nwritten += precision;
for ( ; precision-- > 0 ; s++)
shf_putc(*s, shf);
}
if (field > 0) {
nwritten += field;
for ( ; field > 0 ; --field)
shf_putc(c, shf);
}
}
return shf_error(shf) ? EOF : nwritten;
}