diff options
author | John Bowler <jbowler@nslu2-linux.org> | 2005-06-07 22:32:06 +0000 |
---|---|---|
committer | John Bowler <jbowler@nslu2-linux.org> | 2005-06-07 22:32:06 +0000 |
commit | 689086642964939e81cabbfd3aa18e1777f6cf19 (patch) | |
tree | d1b9f7cee8f00c7cb382039221176b50899ce4d1 /packages/openslug-init/openslug-init-0.10 | |
parent | 6df171ed659aa21947821f2a5b8c5af96b69e8d6 (diff) |
Move devio to a separate package
BKrev: 42a62066p_4MA9b5mKFPNSB_JVH1YA
Diffstat (limited to 'packages/openslug-init/openslug-init-0.10')
-rw-r--r-- | packages/openslug-init/openslug-init-0.10/devio.c | 1767 |
1 files changed, 0 insertions, 1767 deletions
diff --git a/packages/openslug-init/openslug-init-0.10/devio.c b/packages/openslug-init/openslug-init-0.10/devio.c deleted file mode 100644 index bfaace2da1..0000000000 --- a/packages/openslug-init/openslug-init-0.10/devio.c +++ /dev/null @@ -1,1767 +0,0 @@ -/* vi: set sw=4 ts=4: */ -/* - * devio: correctly read a region of a device - * - * A dd like program designed to read correctly from mtd character - * (and maybe block) devices. Allows access to specific regions - * of the device and allows output of numbers from specific locations. - * - * Copyright (C) 2005 John Bowler <jbowler@acm.org> - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation files - * (the "Software"), to deal in the Software without restriction, - * including without limitation the rights to use, copy, modify, - * merge, publish, distribute, sublicense, and/or sell copies of the - * Software, and to permit persons to whom the Software is furnished - * to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - */ -#include <sys/types.h> -#include <sys/stat.h> -#include <fcntl.h> -#include <unistd.h> -#include <limits.h> -#include <errno.h> - -#include <stdlib.h> -#include <stdio.h> -#include <string.h> -#include <ctype.h> - -#ifndef S_ISSOCK -#define S_ISSOCK(fd) 0 -#endif - -/* Define to 0 to remove the detailed help. */ -#ifndef HELP -#define HELP 1 -#endif - -#ifndef STR_MAX -#define STR_MAX 4096 -#endif - -/* common error-and-die functions - reduces code size slightly. */ -static int error_level = 1; /* Increased by write operations. */ - -/* NDEBUG will only save about 600 bytes! */ -/* The noreturn attribute helps reduce size by 300 bytes, and removes - * warning messages - */ -#if NDEBUG -static void do_die(const unsigned char *why) __attribute__((noreturn)); -static void do_die(const unsigned char *why) { - fprintf(stderr, "devio: %s\n", why); - exit(error_level); -} -#define die(a,b) do_die(a) -#else -static void do_die(const unsigned char *why, const unsigned char *infile) - __attribute__((noreturn)); -static void do_die(const unsigned char *why, const unsigned char *infile) { - fprintf(stderr, "devio: %s: %s\n", infile, why); - exit(error_level); -} -#define die(a,b) do_die((a),(b)) -#endif - -#if NDEBUG -static void do_pdie(const unsigned char *why) __attribute__((noreturn)); -static void do_pdie(const unsigned char *why) { - fprintf(stderr, "devio: %s: %s\n", why, strerror(errno)); - exit(error_level); -} -#define pdie(a,b) do_pdie(a) -#else -static void do_pdie(const unsigned char *why, const unsigned char *infile) - __attribute__((noreturn)); -static void do_pdie(const unsigned char *why, const unsigned char *infile) { - fprintf(stderr, "devio: %s: %s: %s\n", infile, why, strerror(errno)); - exit(error_level); -} -#define pdie(a,b) do_pdie((a),(b)) -#endif - -/* This is a non-standard assert but it saves quite a lot of - * space (1kbyte) over the OS version. - */ -#if NDEBUG -#define assert(condition) do;while(0) -#elif 0 /* Expensive string asserts (lots of space in strings). */ -static void do_assert(const unsigned char *why) - __attribute__((noreturn)); -static void do_assert(const unsigned char *why) { - fprintf(stderr, "devio: internal error: %s\n", why); - exit(error_level); -} -#define assert(condition) do if (!(condition)) do_assert(#condition); while (0) -#else -static void do_assert(int line) __attribute__((noreturn)); -static void do_assert(int line) { - fprintf(stderr, "devio: internal error: %d\n", line); - exit(error_level); -} -#define assert(condition) do if (!(condition)) do_assert(__LINE__); while (0) -#endif - -/* This is a non-ANSI extension. */ -unsigned char *my_strdup(const unsigned char *from) { - size_t cb = strlen(from)+1; - unsigned char *to = malloc(cb); - if (to == 0) - die("out of memory", from); - memcpy(to, from, cb); - return to; -} - - -/*********************************************************************** - * mtd_file - * - * Basic device safe IO. - * Set mtd_seek to set the desired read or write point. - * Use mtd_getb and mtd_putb to read/write a single byte. - * Use mtd_readbytes and mtd_writebytes to move multiple bytes. - *********************************************************************/ -/* File structure, used for read and write operations. stdio() should do - * everything this does pretty much except that this allows for no-write - * buffering and it is 'weird' in that it won't overwrite beyond the end - * of the data. */ -typedef struct mtd_file { - unsigned char* pname; - int fwrite; - int fverify; /* do not write, just verify */ - int fwritten; /* something to do! */ - int fchanged; /* something was done! */ - int fd; - struct stat stat; - size_t cbbuf; - /* The user pointer is at 'useroffset', the buffer contains data from - * 'bufferoffset' to 'deviceoffset' (exclusive - the buffer may be empty), so - * the file descriptor is pointing to 'deviceoffset', which may be just beyond - * the end of the file. - */ - off_t useroffset; /* Current user position */ - off_t bufferoffset; /* Base of current buffer */ - off_t deviceoffset; /* End of current buffer */ - off_t endoffset; /* Length of char or block device. */ - unsigned char* pbuf; - unsigned char* pwritebuf; -} mtd_file; - - -/* Initialise an mtd structure. */ -static void init_mtd(mtd_file *pfile) { - pfile->pname = 0; - pfile->fwrite = 0; - pfile->fverify = 0; - pfile->fwritten = 0; - pfile->fchanged = 0; - pfile->fd = (-1); - memset(&pfile->stat, 0, sizeof pfile->stat); - pfile->cbbuf = 0; - pfile->useroffset = 0; - pfile->bufferoffset = 0; - pfile->deviceoffset = 0; - pfile->endoffset = (off_t)-1; - pfile->pbuf = 0; - pfile->pwritebuf = 0; -} - - -/* Return the size, in bytes. */ -static size_t size_mtd(mtd_file *pfile) { - if (S_ISCHR(pfile->stat.st_mode) || S_ISBLK(pfile->stat.st_mode)) { - if (pfile->endoffset == (off_t)-1) { - off_t len; - assert(pfile->stat.st_size == 0); - assert(pfile->stat.st_blocks == 0); - assert(pfile->stat.st_blksize > 0); - /* So seek to the end then come back here. */ - len = lseek(pfile->fd, -pfile->stat.st_blksize, SEEK_END); - if (len == (off_t)-1) - pdie("lseek(length)", pfile->pname); - if (lseek(pfile->fd, pfile->deviceoffset, SEEK_SET) != pfile->deviceoffset) - pdie("lseek(length reset)", pfile->pname); - len += pfile->stat.st_blksize; - pfile->endoffset = len; - } - return pfile->endoffset; - } else if (S_ISDIR(pfile->stat.st_mode) || S_ISFIFO(pfile->stat.st_mode) || - S_ISSOCK(pfile->stat.st_mode)) - die("cannot find size of this device", pfile->pname); - else - return pfile->stat.st_size; -} - - -/* Open the named file for read or write, the structure is initialised - * appropriately. The name is copied. */ -static void new_mtd(mtd_file *pfile, const char *pname, int fwrite, int fverify, int fd) { - pfile->pname = my_strdup(pname); - pfile->fwrite = fwrite; - pfile->fverify = fverify; - pfile->fwritten = 0; - pfile->fchanged = 0; - pfile->fd = fd; - - if (fstat(fd, &pfile->stat) != 0) - pdie("fstat", pname); - /* This can be made to work with fifos on read because it is possible - * to seek by reading so long as we only seek forward, but it really - * isn't worth spending time on this. - */ - if (S_ISDIR(pfile->stat.st_mode) || S_ISFIFO(pfile->stat.st_mode) || - S_ISSOCK(pfile->stat.st_mode)) - die("invalid device", pname); - /* Allow writing to a file for testing - i.e. S_ISREG is fine above. */ - pfile->cbbuf = pfile->stat.st_blksize; - if (pfile->cbbuf == 0) - pfile->cbbuf = 4096; - pfile->useroffset = 0; - pfile->bufferoffset = 0; - pfile->deviceoffset = 0; - pfile->pbuf = 0; - pfile->pwritebuf = 0; -} - -static void open_mtd(mtd_file *pfile, const char *pname, int fwrite, int fverify) { - int fd = open(pname, (fwrite && !fverify) ? O_RDWR : O_RDONLY); - if (fd < 0) - pdie("open", pname); - else if (fd < 3) - die("no standard streams", "-"); - new_mtd(pfile, pname, fwrite, fverify, fd); -} - - -/* Do the actual write. Any pending write buffers are checked and output - * to the device. Happens on close and can be called before. Does not - * do an fsync. The fwritten flag indicates that write_mtd needs to be - * called, the fchanegd flag indicates that something has been written and - * an fdatasync needs to happen before the close. - */ -static void write_mtd(mtd_file *pfile) { - if (pfile->fwritten) { - size_t count = pfile->deviceoffset - pfile->bufferoffset; - unsigned char *pbuf = pfile->pwritebuf; - - assert(pfile->pbuf != 0); - assert(pbuf != 0); - assert(pfile->deviceoffset > pfile->bufferoffset); - assert(pfile->deviceoffset <= pfile->bufferoffset + pfile->cbbuf); - /* If it changed write it. */ - if (memcmp(pfile->pbuf, pbuf, count) != 0) { - /* If verifying the verify just failed... */ - if (pfile->fverify) - die("verification failed", pfile->pname); - - /* So write the whole of this buffer back. Do not do a sync here - * because that would force a complete write of the flash erase - * block - not good. - */ - if (lseek(pfile->fd, pfile->bufferoffset, SEEK_SET) != pfile->bufferoffset) - pdie("lseek(write)", pfile->pname); - /* write, well, you have to keep doing it until it works, you - * also have to RTFM several times to get this write, so if - * this looks wrong please fix it. No, not that, that was - * deliberate. - */ - do { - ssize_t cb = write(pfile->fd, pbuf, count); - assert(cb != 0); - assert(cb <= count); - if (cb < 0) switch (errno) { - case EINTR: /* shall we try that again then? */ - /* This is the common case - this does happen, it is - * necessary to deal with it and it is sufficient to - * try again. - */ - break; - case EAGAIN: /* what, oh well, if at once you don't succeed. */ - /* We didn't say O_NONBLOCK above so this should never - * happen, however it has. The code will therefore go into - * a tight loop in the manner of a certain Scottish nobleman. - */ - break; - case EPIPE: /* you don't love me any more. */ - /* This is a little difficult, it means we were squirting - * data down a pipe, so somehow someone has managed to work - * out both how to create a named pipe and how much fun to - * have by passing it to this program on the command line, - * then they have worked out how to make the shell ignore - * SIGPIPE in a spawned program (possible with some shells) - * then they want to see the really dumb message that comes - * out as a result. We just say no. - */ - exit(1); - default: - pdie("write", pfile->pname); - } else { - count -= cb; - /* It is now necessary to fdatasync this file descriptor - * to ensure that this data really does get to its final - * destination. (Note that even this is probably not certain - * if the destination is a disk with a RAM buffer - which - * means *any* disk these days.) - */ - pfile->fchanged = 1; - /* Something has been written to flash, but not everything - * has (necessarily) been written yet, so if something goes - * wrong after this point we are in deep, deep, trouble. - */ - error_level = 3; - } - } while (count > 0); - - /* So now the device matches the write buffer and the device - * pointer is back where it was before. - */ - memcpy(pfile->pbuf, pfile->pwritebuf, pfile->deviceoffset-pfile->bufferoffset); - } - /* Nothing remains to write from this buffer (hence nothing at all - * for this device.) - */ - pfile->fwritten = 0; - } -} - - -/* Close the file, if anything was written out does an fsync. - */ -static void close_mtd(mtd_file *pfile) { - write_mtd(pfile); - assert(!pfile->fwritten); - if (pfile->pbuf != 0) { - free(pfile->pbuf); - pfile->pbuf = 0; - } - if (pfile->pwritebuf != 0) { - free(pfile->pwritebuf); - pfile->pwritebuf = 0; - } - if (pfile->fd >= 0) { - /* For a write file be very very careful. For read ignore errors: - * it is more important to successfully write than to whine about - * strange close errors from a file we don't care about. For a - * write file with nothing written we don't care either. - */ - if (pfile->fchanged) { - /* This is the all important bit. Doing the fdatasync is what - * flushes the data to the flash. If this isn't done there is - * no guarantee that close will detect a write error, 'cause the - * flash may not have completed the write before the close - * returns. - */ - if (fdatasync(pfile->fd) != 0) { - /* Trying an fdatasync on a pipe, etc, is silly, but we do - * it anyway. EROFS means we just tried to write to a - * read only file system, safe but still an error. - */ - if (errno != EINVAL) - pdie("sync", pfile->pname); - } - if (close(pfile->fd) != 0) - pdie("close", pfile->pname); - } else - (void)close(pfile->fd); - pfile->fd = (-1); - } - if (pfile->pname != 0) { - free(pfile->pname); - pfile->pname = 0; - } - init_mtd(pfile); -} - - -/* Obtain an input and, if necessary, an output buffer. */ -static void buffer_mtd(mtd_file *pfile) { - if (pfile->pbuf == 0) { - size_t blksize = pfile->cbbuf; - assert(blksize > 0); - assert(pfile->pwritebuf == 0); - - /* Get blksize bytes (note: things could be speeded up by aligning - * the buffer but this really doesn't matter, all the time goes in - * read/write of the flash!) - */ - pfile->pbuf = malloc(blksize); - if (pfile->fwrite) - pfile->pwritebuf = malloc(blksize); - if (pfile->pbuf == 0 || (pfile->fwrite && pfile->pwritebuf == 0)) - die("out of memory", pfile->pname); - } -} - - -/* Read some data including the current user position. This will also *write* data - * if something is waiting to be written. - * - * NOTE: in the original design I conceived of some scheme whereby all the writes - * would be buffered up for the end, but I can't see how this would actually help - * anything because even if data has to be read from the device to determine read - * locations it tends to happen before the relevant writes. In the access patterns - * I know (they are very simple - and that is important in itself) there is never - * a need to read from a write device. - */ -static void read_mtd(mtd_file *pfile) { - size_t cbread; - int ioffset; - - /* 'useroffset' is where we need to read from, 'deviceoffset' is where we are - * at (sic) and 'bufferoffset'..'deviceoffset' is what we have already. - */ - if (pfile->useroffset >= pfile->bufferoffset && - pfile->useroffset < pfile->deviceoffset) - return; - - if (pfile->useroffset < 0 || pfile->useroffset >= size_mtd(pfile)) - die("read outside file", pfile->pname); - - /* Make sure there is a buffer. */ - buffer_mtd(pfile); - - /* This is the maximum amount which can be read. */ - cbread = pfile->cbbuf; - if (pfile->useroffset >= pfile->bufferoffset && - pfile->useroffset < pfile->bufferoffset + cbread) { - /* Just fill the rest of the buffer. */ - ioffset = pfile->deviceoffset - pfile->bufferoffset; - - assert(pfile->deviceoffset < pfile->bufferoffset + cbread); - cbread -= ioffset; - } else { - off_t base; - - /* We to move the buffer therefore any pending write needs to be flushed. */ - write_mtd(pfile); - assert(!pfile->fwritten); - - /* Seek to the aligned buffer boundary if necessary. */ - base = (pfile->useroffset / cbread) * cbread; - if (base != pfile->deviceoffset) { - if (lseek(pfile->fd, base, SEEK_SET) != base) - pdie("lseek(read)", pfile->pname); - pfile->deviceoffset = base; - } - pfile->bufferoffset = base; - ioffset = 0; - } - - /* Reading is like writing, EINTR can stop it succeeding but is a - * continuable error. - */ - assert(pfile->bufferoffset <= pfile->useroffset); - assert(pfile->useroffset < pfile->bufferoffset + cbread); - assert(pfile->deviceoffset <= pfile->useroffset); - do { - ssize_t cb = read(pfile->fd, pfile->pbuf+ioffset, cbread); - if (cb < 0) switch (errno) { - case EINTR: /* simple restart */ - /* POSIX allows this to happen when something has been - * read. Reset the file pointer just in case. - */ - if (lseek(pfile->fd, pfile->deviceoffset, SEEK_SET) != pfile->deviceoffset) - pdie("lseek(read reset)", pfile->pname); - break; - case EAGAIN: /* O_NONBLOCK on the input? */ - break; - default: - pdie("read", pfile->pname); - } else if (cb == 0) { - die("unexpected end of file", pfile->pname); - } else { - /* Save a copy of the data so that it can be written out again - * by a write file. - */ - if (pfile->pwritebuf != 0) - memcpy(pfile->pwritebuf+ioffset, pfile->pbuf+ioffset, cb); - cbread -= cb; - ioffset += cb; - pfile->deviceoffset += cb; - } - } while (cbread > 0 && pfile->useroffset >= pfile->deviceoffset); - - assert(pfile->useroffset < pfile->deviceoffset); -} - - -/* Basic IO - these are the functions to use, not the internal read/write - * functions above. - */ -/* Set the current read/write pointer on this file. */ -#if 0 /*UNUSED*/ -static void mtd_seek(mtd_file *pfile, off_t offset) { - pfile->useroffset = offset; -} -#endif - - -/* Get a single byte (returned) and advance the read pointer by one. */ -static unsigned char mtd_getb(mtd_file *pfile) { - read_mtd(pfile); - return (pfile->fwrite ? pfile->pwritebuf : pfile->pbuf)[ - pfile->useroffset++ - pfile->bufferoffset]; -} - - -/* Store a single byte in a write file and advance the pointer by one. */ -static void mtd_putb(mtd_file *pfile, unsigned long b) { - if (!pfile->fwrite) - die("file is not writeable", pfile->pname); - read_mtd(pfile); - if (b != pfile->pwritebuf[pfile->useroffset-pfile->bufferoffset]) { - pfile->pwritebuf[pfile->useroffset-pfile->bufferoffset] = b; - pfile->fwritten = 1; - } - ++(pfile->useroffset); -} - - -/* Read a given number of bytes, which must exist in the file, and - * advance the pointer by that amount. - */ -static void mtd_readbytes(mtd_file *pfile, unsigned char *pbuf, size_t cb) { - if (pfile->useroffset+cb > size_mtd(pfile)) - die("read beyond end of file", pfile->pname); - - while (cb > 0) { - int cbavail; - - read_mtd(pfile); - cbavail = pfile->deviceoffset - pfile->useroffset; - assert(cbavail > 0 && cbavail <= pfile->cbbuf); - if (cbavail > cb) - cbavail = cb; - - assert(pfile->useroffset >= pfile->bufferoffset); - assert(pfile->useroffset < pfile->deviceoffset); - assert(pfile->deviceoffset <= pfile->bufferoffset + pfile->cbbuf); - - memcpy(pbuf, (pfile->fwrite ? pfile->pwritebuf : pfile->pbuf) + - (pfile->useroffset-pfile->bufferoffset), cbavail); - pfile->useroffset += cbavail; - pbuf += cbavail; - cb -= cbavail; - } -} - - -/* Write a given number of bytes and advance the pointer. As with readbytes - * the bytes must already exist in the file - mtd_file will never extend the - * file only change existing bytes. - */ -static void mtd_writebytes(mtd_file *pfile, const unsigned char *pbuf, size_t cb) { - if (!pfile->fwrite) - die("file is not writeable", pfile->pname); - if (pfile->useroffset+cb > size_mtd(pfile)) - die("write beyond end of file", pfile->pname); - while (cb > 0) { - int cbavail; - - /* This may look strange but it is correct - this code always reads - * before it writes to avoid unnecessary writes. - */ - read_mtd(pfile); - cbavail = pfile->deviceoffset - pfile->useroffset; - if (cbavail > cb) - cbavail = cb; - memcpy(pfile->pwritebuf + (pfile->useroffset-pfile->bufferoffset), pbuf, cbavail); - pfile->fwritten = 1; - pfile->useroffset += cbavail; - pbuf += cbavail; - cb -= cbavail; - } -} - - -#if 0 /* Commented out because I don't think this is worth while. */ -/* Copy bytes from the pointer in one file to the pointer in another - * file (avoids an intermediate buffer compared to readbytes/writebytes.) - */ -static void mtd_copy(mtd_file *pto, mtd_file *pfrom, size_t cb) { - int cbfrom, cbto; - - if (!pto->fwrite) - die("file is not writeable", pto->pname); - if (pto->useroffset+cb > size_mtd(pto)) - die("write beyond end of file", pto->pname); - if (pfrom->useroffset+cb > size_mtd(pfrom)) - die("read beyond end of file", pfrom->pname); - /* Copying from and to the same place has no effect. */ - if (pfrom == pto) - return; - - cbfrom = cbto = 0; - while (cb > 0) { - int cbavail; - - if (cbfrom <= 0) { - read_mtd(pfrom); - cbfrom = pfrom->deviceoffset - pfrom->useroffset; - compared to readbytes/writebytes} - if (cbto <= 0) { - read_mtd(pto); - cbto = pto->deviceoffset - pto->useroffset; - } - - /* Take the smallest byte count and copy it. */ - cbavail = cbfrom; - if (cbavail > cbto) - cbavail = cbto; - if (cbavail > cb) - cbavail = cb; - - memcpy(pto->pwritebuf + (pto->useroffset-pto->bufferoffset), - pfrom->pbuf + (pfrom->useroffset-pfrom->bufferoffset), - cbavail); - pto->fwritten = 1; - - pto->useroffset += cbavail; - cbto -= cbavail; - pfrom->useroffset += cbavail; - cbfrom -= cbavail; - - cb -= cbavail; - } -} -#endif - - -/*********************************************************************** - * parse - * - * Parse a command line option or a single line. See the help below - * for details... - ***********************************************************************/ -#define STACK_BASE 8 -#define STACK_SIZE 256 -#define NUM_FILES 16 -typedef struct parse_buf { - int fverify; /* Just verifying, do no write. */ - int cstack; - int fbreak; /* Break in an expression. */ - mtd_file* pfrom; - mtd_file* pto; - - /* The buffers. */ - unsigned long variables[256]; - unsigned long stack[STACK_SIZE]; - mtd_file files[NUM_FILES]; -} parse_buf; - - -/* Initialiser. */ -static void init_parse(parse_buf *pp, int fverify) { - int i; - memset(pp, 0, sizeof *pp); - pp->fverify = fverify; - pp->cstack = STACK_BASE; - pp->fbreak = 0; - pp->pfrom = 0; - pp->pto = 0; - for (i=0; i<NUM_FILES; ++i) - init_mtd(pp->files+i); -} - - -/* Terminator. */ -static void quit(parse_buf *pp, int exit_code) __attribute__((noreturn)); -static void quit(parse_buf *pp, int exit_code) { - int i; - /* Close all the files. */ - for (i=0; i<NUM_FILES; ++i) - if (pp->files[i].pname != 0) - close_mtd(pp->files+i); - - /* And make sure the output worked too. */ - if (fflush(stdout) == EOF || ferror(stdout) || fclose(stdout) == EOF) - pdie("output failed", "stdout"); - - exit(exit_code); -} - - -/* Input a single byte. */ -static unsigned char inb(parse_buf *pp) { - int b; - if (pp->pfrom == 0) { - b = getchar(); - if (b == EOF) - pdie("read error", "stdin"); - } else { - b = mtd_getb(pp->pfrom); - } - return b; -} - - -/* Output a single byte. */ -static void outb(parse_buf *pp, unsigned long b) { - if (pp->pto == 0) { - if (putchar(b) == EOF) - pdie("write error", "stdout"); - } else { - mtd_putb(pp->pto, b); - } -} - - -/* Output these bytes. */ -static void outputbytes(parse_buf *pp, const char *pbuf, size_t cb) { - if (pp->pto == 0) { - if (fwrite(pbuf, cb, 1, stdout) != 1) - pdie("write error", "stdout"); - } else - mtd_writebytes(pp->pto, pbuf, cb); -} - -/* Copy a stream of bytes. */ -static void copybytes(parse_buf *pp, size_t cb) { - while (cb > 0) { - size_t cbavail = cb; - unsigned char buf[1024]; - if (cbavail > sizeof buf) - cbavail = sizeof buf; - if (pp->pfrom == 0) { - if (fread(buf, cbavail, 1, stdin) != 1) - pdie("read error", "stdin"); - } else - mtd_readbytes(pp->pfrom, buf, cbavail); - - outputbytes(pp, buf, cbavail); - - cb -= cbavail; - } -} - - -/* Fill the output with a count of bytes of a given value. */ -static void fillbytes(parse_buf *pp, unsigned long val, size_t cb) { - unsigned char buf[1024]; - memset(buf, val, sizeof buf); - - while (cb > 0) { - size_t cbavail = cb; - if (cbavail > sizeof buf) - cbavail = sizeof buf; - - outputbytes(pp, buf, cbavail); - - cb -= cbavail; - } -} - - -/* Push a single numeric value onto the stack. */ -static void push(parse_buf *pp, unsigned long num, const unsigned char *str) { - if (pp->cstack >= STACK_SIZE) - die("stack overflow", str); - pp->stack[pp->cstack++] = num; -} - - -/* Pop one or move variables. */ -static void pop(parse_buf *pp, int num, const unsigned char *str) { - if (pp->cstack < STACK_BASE+num) - die("stack underflow", str); - pp->cstack -= num; -} - -/* Return (and pop) the top of stack. */ -static unsigned long top(parse_buf *pp, const unsigned char *str) { - if (pp->cstack <= STACK_BASE) - die("stack underflow", str); - return pp->stack[--(pp->cstack)]; -} - - -/* Store the result of an operator. */ -static void op(parse_buf *pp, int numpop, unsigned long num, - const unsigned char *str) { - pop(pp, numpop, str); - push(pp, num, str); -} - - -/* Parse a single expression, which may be empty. The conditional execution - * stuff is identical to that for a command except that (:?) are used instead - * of $($:$?$) - */ -static int parse_expression(parse_buf *pp, const unsigned char *line, int Ac, int AcEnd) { - int SP = 0, fnoexec = 0, test = 0; - int stack[16]; - - for (;Ac<AcEnd;++Ac) { - const unsigned char *lp = line+Ac; - unsigned char ch = *lp; - switch (ch) { - /* Control flow. These operators have to explicitly check - * the fnoexec state because they manipulate it. - */ - case '(': /* if */ - if (SP >= 16) - die("() stack overflow", lp); - stack[SP++] = Ac; - if (fnoexec) { - fnoexec += 3; - } else { - fnoexec = top(pp, lp) == 0; - } - break; - - case '[': /* test start */ - /* If fnoexec >= 3 the whole block is disabled. */ - if (fnoexec <= 2) { - /* Valid only inside an () block and there should only - * be one active at once. - */ - if (test != 0 || SP <= 0) - goto badnest; - /* Record the start of the test. */ - test = Ac+1; - /* If the previous block executed record this. */ - if (fnoexec == 0) - fnoexec = 2; - } - break; - - case ':': /* elif */ - /* fnoexec is 1 if nothing has executed in this block yet, - * and if the block itself is executing, it is 2 if something - * did execute, it is >2 for a non-executed block, including - * one with a break. - */ - if (fnoexec <= 2) { - if (test == 0 || SP <= 0) - goto badnest; - - assert(fnoexec > 0); - assert(!pp->fbreak); - /* 1: nothing has executed yet. - * 2: an if or elif has executed. - */ - if (fnoexec == 1) { - /* Parse the test. If this results in a break no - * condition is popped from the stack, otherwise - * the condition which the expression should push - * is popped. - */ - (void)parse_expression(pp, line, test, Ac); - if (pp->fbreak) { - fnoexec = 3; - pp->fbreak = 0; - } else - fnoexec = top(pp, lp) == 0; - } - - /* And the test has been consumed. */ - test = 0; - } - break; - - case ')': /* end+loop if */ - /* The stack must always be popped. */ - if (SP <= 0) - goto badnest; - --SP; - /* If fnoexec>2 then this is a nested disabled block or, in - * the case of 3, a break. In neither case is the expression - * evaluated and the test setting is for an enclosing block. - */ - if (fnoexec > 2) { - fnoexec -= 3; - } else { - /* In this case there must be a test. */ - if (test == 0) - goto badnest; - - /* Execution resumes regardless. */ - fnoexec = 0; - assert(!pp->fbreak); - - /* So make the loop test now - this may cause a branch back - * to the ( and that will push the stack again. Evaluate - * the test. - */ - (void)parse_expression(pp, line, test, Ac); - if (pp->fbreak) - pp->fbreak = 0; - else if (top(pp, lp) != 0) { - Ac = stack[SP]-1; /* Ac is incremented below */ - } - - /* And the test has been consumed. */ - test = 0; - } - break; - - badnest: - die("bad [: or [) nesting", lp); - break; - - case ';': - case '\n': - /* end of line terminates the loop, but Ac is stepped beyond the - * terminator. - */ - ++Ac; - goto end; - - case ' ': - case '\f': - case '\r': - case '\t': - case '\v': - case ',': /* Treat as a space */ - /* Skip other white space. */ - break; - - /* Everything else is glommed together because the fnoexec case - * can be simply handled by skipping character-by-character (because - * (:?); do not occur inside numbers!) - */ - default: - if (fnoexec) - break; - - if (isupper(ch)) - push(pp, pp->variables[ch], lp); - else if (isdigit(ch)) { - char *end = (char*)lp; - unsigned long num; - errno = 0; - num = strtoul(lp, &end, 0); - if (num == ULONG_MAX && (errno == EINVAL || errno == ERANGE)) - pdie("invalid number", lp); - push(pp, num, lp); - /* strotul returns a pointer to the first invalid character in - * end, so Ac becomes end-line-1, because it is incremented below. - */ - Ac = (const unsigned char*)end-line-1; - } else { - /* The operators are handled here. An unrecognised character is an - * error at this point. The left, right are always valid because - * the stack has 8 unused slots at the top... - */ - unsigned long left = pp->stack[pp->cstack - 2]; - unsigned long right = pp->stack[pp->cstack - 1]; - - switch (ch) { - case '?': /* break */ - /* break inside a condition is actually allowed, so this may - * happen with SP==0 while evaluating a condition. For the - * moment ? is also allowed outside a loop, it terminates the - * processing of the whole expression. - */ - if (top(pp, lp) != 0) { - /* break: skip to the ) and do not do the test on - * that either. - */ - fnoexec = 3; - } - break; - - #define DIOP(operator) op(pp, 2, left operator right, lp); break - #define MONOP(operator) op(pp, 1, operator right, lp); break - /* The C operators */ - case '*': DIOP(*); - case '+': DIOP(+); - case '-': DIOP(-); - case '/': DIOP(/); - case '%': DIOP(%); - case '<': DIOP(<); - case '>': DIOP(>); - case '|': DIOP(|); - case '&': DIOP(&); - case '^': DIOP(^); - case '~': MONOP(~); - case '!': MONOP(!); - case '=': /* equality */ - op(pp, 2, left == right, lp); - break; - case '{': /* shift left */ - op(pp, 2, left << right, lp); - break; - case '}': /* shift right */ - op(pp, 2, left >> right, lp); - break; - case 'r': /* rotate right */ - op(pp, 2, (left >> right) + (left << (32-right)), lp); - break; - case 'e': /* sign extend (right is number of valid bits). */ - op(pp, 2, (long)(left << (32-right)) >> (32-right), lp); - break; - case 'm': /* mask (right is number of valid bits). */ - op(pp, 2, (left << (32-right)) >> (32-right), lp); - break; - case '$': /* Size of input. */ - if (pp->pfrom == 0) - die("size of input unknown", lp); - push(pp, size_mtd(pp->pfrom), lp); - break; - case 'f': /* position of input ('from' pointer). */ - if (pp->pfrom == 0) - die("position of input unknown", lp); - push(pp, pp->pfrom->useroffset, lp); - break; - case '#': /* Size of output. */ - if (pp->pto == 0) - die("size of output unknown", lp); - push(pp, size_mtd(pp->pto), lp); - break; - case 't': /* position of output ('to' pointer). */ - if (pp->pto == 0) - die("position of output unknown", lp); - push(pp, pp->pto->useroffset, lp); - break; - case 'd': /* device number of the input device */ - if (pp->pfrom == 0) - die("input device number unknown", lp); - push(pp, pp->pfrom->stat.st_rdev == 0 ? pp->pfrom->stat.st_dev : - pp->pfrom->stat.st_rdev, lp); - break; - case '@': /* one byte read. */ - push(pp, inb(pp), lp); - break; - case 'b': /* big endian 4 byte read. */ - #define P(st) (void)parse_expression(pp, st, 0, (sizeof st)-1) - P("@8{@+8{@+8{@+;# 4 byte big-endian read"); - break; - case 'l': /* little endian 4 byte read. */ - P("@@@@8{+8{+8{+;# 4 byte little-endian read"); - break; - case '.': /* copy (dup) */ - push(pp, right, lp); - break; - case 'p': /* pop */ - pop(pp, 1, lp); - break; - case 's': /* swap (top two elements of the stack) */ - pop(pp, 2, lp); - push(pp, right, lp); - push(pp, left, lp); - break; - default: - die("invalid operator", lp); - } - } - } - } - - /* Here on terminator or Ac==AcEnd. */ -end: - /* If SP>0 then there was some bad nesting going on - i.e. the brackets have - * not been closed. fnoexec must be zero if SP is 0. - */ - if (SP > 0) - die("unclosed ( )", line); - assert(fnoexec == 0 || fnoexec == 3); - if (fnoexec == 3) - pp->fbreak = 1; - return Ac; -} - - -/* Parse an expression and return numbers off the top of stack. */ -static int need(parse_buf *pp, unsigned long arg[2], - const unsigned char *line, int Ac, int AcEnd, int num) { - int Acnew = parse_expression(pp, line, Ac, AcEnd); - - assert(num <= 2); - if (pp->cstack < STACK_BASE+num) - die("too few arguments", line+Ac); - - while (num > 0) - arg[--num] = pp->stack[--(pp->cstack)]; - - /* cancel a break at the top level. */ - pp->fbreak = 0; - return Acnew; -} - - -/* Parse a string. The string ends up in the buffer and is limited in - * size to STR_MAX. The API returns a null string for empty quoted - * strings and for the unquoted case where there is only whitespace - * in the rest of the command. The result is the index of the first - * character after the end of the command (*not* the end of the string.) - */ -static int string(unsigned char *buffer, const unsigned char *line, int Ac) { - int start, end; - - while (isspace(line[Ac]) && line[Ac] != '\n') ++Ac; - - if (line[Ac] == '"' || line[Ac] == '\'') { - const unsigned char quote = line[Ac]; - start = Ac; - end = Ac+1; - while (line[end] != 0 && line[end] != '\n' && line[end] != quote) ++end; - if (line[end] != quote) - die("unterminated quoted string", line+start); - Ac = end+1; - while (isspace(line[Ac]) && line[Ac] != '\n') ++Ac; - if (line[Ac] != 0 && line[Ac] != ';' && line[Ac] != '\n') - die("stuff on line after quoted string", line+start); - ++start; - } else { - start = Ac; - while (line[Ac] != 0 && line[Ac] != ';' && line[Ac] != '\n') ++Ac; - end = Ac; - } - - end -= start; - if (end >= STR_MAX) - die("string too long", line+start); - - /* Copy out the string into the buffer and null terminate it. */ - memcpy(buffer, line+start, end); - buffer[end] = 0; - - /* Skip over EOL, if present, and store the string pointer back. */ - if (line[Ac] != 0) ++Ac; - return Ac; -} - - -/* Find the end of line, Ac always points to the start. */ -static int eol(const unsigned char *line, int Ac) { - unsigned char quote = 0; - while (line[Ac] != '\n' && (line[Ac] != ';' || quote)) { - if (line[Ac] == 0) - return Ac; - if (line[Ac] == '"' || line[Ac] == '\"') { - if (quote == 0) - quote = line[Ac]; - else if (quote == line[Ac]) - quote = 0; - } - ++Ac; - } - return Ac+1; -} - - -/* Find a file given its name. */ -static mtd_file *find_file(parse_buf *pp, const unsigned char *name) { - int i; - for (i=0; i<NUM_FILES; ++i) { - if (pp->files[i].pname != 0 && - strcmp(name, pp->files[i].pname) == 0) - return pp->files+i; - } - return 0; -} - - -/* Open a file for read or write. */ -static mtd_file *open_file(parse_buf *pp, const unsigned char *name, int fwrite) { - int i; - for (i=0; i<NUM_FILES; ++i) - if (pp->files[i].pname == 0) - break; - if (i >= NUM_FILES) - die("no more files", name); - open_mtd(pp->files+i, name, fwrite, pp->fverify); - return pp->files+i; -} - - -/* Basic parse function. Does all the work. Simple line-by-line command - * parser. The input is a vector of strings (an argv), 'input' says where - * it came from. - */ -static void parse(parse_buf *pp, int lines, const char **prog) { - /* The program pointer is a line index Al and a character within the - * line Ac. The exec stack just has those references (i.e. (Al,Ac) - * is a PC). - */ - int Al = 0, SP = 0, fnoexec = 0; - struct stack { - int Al, Ac; - } stack[16]; - - /* fnoexec has these values: - * 0: normal execution - * 1: within an if $($:$) and none of the tests have passed (so - * nothing has been executed in this block.) - * 2: within an if and a test has passed (a previous block has - * been executed.) - * 3: within an if and a break ($?) has succeeded, execute nothing - * until *and including* the $) (i.e. do not evaluate the value - * on that either.) - * This applies to nested if blocks too when the block is executable. - * If a block is nested within a non-executing environment the $( - * adds 3 to fnoexec. The $) always subtracts 3 unless fnoexec is - * 0, 1, 2 or 3 thus the prior state is reliably restored. - */ - - while (Al < lines) { - int Ac = 0; - const unsigned char *line = prog[Al]; - const int AcEnd = strlen(line); - - while (line[Ac]) { - const unsigned char *lp = line+Ac; - unsigned char ch = *lp; - - /* A command is two characters, except that ';' is end-of-line, - * skip these things until we see a command, then use the next - * two characters. line[Ac] != 0 so the read of Ac+1 is safe. - */ - if (isspace(ch) || ch == ';') { - ++Ac; - } else if ((ch != '$' && fnoexec) || ch == '#') { - /* disabled execution and not a control flow command, or - * a comment. - */ - Ac = eol(line, Ac); - } else switch ((ch << 8) + line[Ac+1]) { - unsigned long arg[2]; - unsigned char buffer[STR_MAX]; - - /* Control flow. These commands have to explicitly check - * the fnoexec state because they manipulate it. - */ - #define CASE(l,r) case (((l)<<8)+(r)) - CASE('$','('): /* if */ - if (SP >= 16) - die("exec stack overflow", lp); - stack[SP].Al = Al; - stack[SP++].Ac = Ac; - if (fnoexec) { - fnoexec += 3; - goto noexec; - } - Ac = need(pp, arg, line, Ac+2, AcEnd, 1); - fnoexec = arg[0] == 0; - break; - - CASE('$',':'): /* elif */ - /* fnoexec is 1 if nothing has executed in this block yet, - * and if the block itself is executing. - */ - if (SP <= 0) - goto underflow; - if (fnoexec != 1) { - /* If the previous block executed record this. */ - if (fnoexec == 0) - fnoexec = 2; - goto noexec; - } - Ac = need(pp, arg, line, Ac+2, AcEnd, 1); - fnoexec = arg[0] == 0; - break; - - CASE('$',')'): /* end+loop if */ - /* The stack must always be popped. */ - if (SP <= 0) - goto underflow; - --SP; - /* If fnoexec>2 then this is a nested disabled block or, in - * the case of 3, a break. In neither case is the expression - * evaluated. - */ - if (fnoexec > 2) { - fnoexec -= 3; - goto noexec; - } - fnoexec = 0; - /* So make the loop test now - this may cause a branch back - * to the $( and that will push the stack again. - */ - Ac = need(pp, arg, line, Ac+2, AcEnd, 1); - if (arg[0] != 0) { - Al = stack[SP].Al; - Ac = stack[SP].Ac; - /* Do not ever forget this, because we may not exit the - * immediately enclosing loop and line is set up outside - * it! - */ - line = prog[Al]; - } - break; - - CASE('$','?'): /* break */ - if (SP <= 0) - goto underflow; - if (fnoexec) - goto noexec; - Ac = need(pp, arg, line, Ac+2, AcEnd, 1); - if (arg[0] != 0) { - /* break: skip to the $) and do not do the test on - * that either. - */ - fnoexec = 3; - } - break; - - underflow: - die("exec stack underflow", lp); - noexec: - /* Control flow no-exec case. */ - Ac = eol(line, Ac); - break; - - /* Program termination */ - CASE('!','?'): /* exit if non-zero */ - Ac = need(pp, arg, line, Ac+2, AcEnd, 1); - if (arg[0] != 0) - quit(pp, arg[0]); - break; - - CASE('!','!'): /* exit unconditionally */ - Ac = need(pp, arg, line, Ac+2, AcEnd, 1); - quit(pp, arg[0]); - break; - - /* Input/output */ - CASE('>','>'): /* write to (no argument reverts to stdout) */ - Ac = string(buffer, line, Ac+2); - if (buffer[0] != 0) { - mtd_file *pfile = find_file(pp, buffer); - if (pfile == 0) - pfile = open_file(pp, buffer, 1); - else if (!pfile->fwrite) - die("attempt to open a read file for write", buffer); - pp->pto = pfile; - } else - pp->pto = 0; - break; - - CASE('<','<'): /* read from */ - Ac = string(buffer, line, Ac+2); - if (buffer[0] != 0) { - mtd_file *pfile = find_file(pp, buffer); - if (pfile == 0) - pfile = open_file(pp, buffer, 0); - pp->pfrom = pfile; - } else - pp->pfrom = 0; - break; - - CASE('<','>'): /* close */ - CASE('>','<'): /* synonym of close */ - Ac = string(buffer, line, Ac+2); - if (buffer[0] != 0) { - mtd_file *pfile = find_file(pp, buffer); - if (pfile == 0) - die("no such file to close", buffer); - - if (pfile == pp->pfrom) - pp->pfrom = 0; - if (pfile == pp->pto) - pp->pto = 0; - close_mtd(pfile); - } else - die("<> (close) requires an argument", lp); - break; - - CASE('>','='): /* set to pointer */ - Ac = need(pp, arg, line, Ac+2, AcEnd, 1); - if (pp->pto == 0) - die("no output file", lp); - pp->pto->useroffset = arg[0]; - break; - - CASE('<','='): /* set from pointer */ - Ac = need(pp, arg, line, Ac+2, AcEnd, 1); - if (pp->pfrom == 0) - die("no input file", lp); - pp->pfrom->useroffset = arg[0]; - break; - - /* writing to output */ - CASE('w','b'): /* bigendian number bytes */ - Ac = need(pp, arg, line, Ac+2, AcEnd, 2); - switch (arg[1]) { - case 4: outb(pp, arg[0] >> 24); - case 3: outb(pp, arg[0] >> 16); - case 2: outb(pp, arg[0] >> 8); - case 1: outb(pp, arg[0]); - break; - default:die("byte count out of range", lp); - } - break; - - CASE('w','l'): /* little endian number bytes */ - Ac = need(pp, arg, line, Ac+2, AcEnd, 2); - switch (arg[1]) { - case 4: outb(pp, arg[0]); arg[0] >>= 8; - case 3: outb(pp, arg[0]); arg[0] >>= 8; - case 2: outb(pp, arg[0]); arg[0] >>= 8; - case 1: outb(pp, arg[0]); - break; - default:die("byte count out of range", lp); - } - break; - - CASE('w','s'): /* write string */ - Ac = string(buffer, line, Ac+2); - outputbytes(pp, buffer, strlen(buffer)); - break; - - CASE('f','b'): /* fill bytes */ - Ac = need(pp, arg, line, Ac+2, AcEnd, 2); - - /* The likely error is forgetting an argument, that may - * put the byte count into the fill value. - */ - if (arg[1] > 255) - die("fill value out of range", lp); - - /* Now call fillbytes, arguments same order as memset, but - * the reverse from the command (fb number value). - */ - fillbytes(pp, arg[1], arg[0]); - break; - - CASE('c','p'): /* copy bytes */ - Ac = need(pp, arg, line, Ac+2, AcEnd, 1); - copybytes(pp, arg[0]); - break; - - /* writing to stdout */ - CASE('p','r'): /* printf("%lu\n") */ - Ac = need(pp, arg, line, Ac+2, AcEnd, 1); - printf("%lu\n", arg[0]); - stdout_check: - if (ferror(stdout)) - pdie("write error", "stdout"); - break; - - CASE('p','f'): /* printf(string) */ - Ac = string(buffer, line, Ac+2); - /* This flushes the whole stack, so "printf;" can be useful - * under some circumstances! - */ - { - unsigned long *stack = pp->stack + pp->cstack - 1; - pp->cstack = STACK_BASE; - printf(buffer, stack[0], stack[-1], stack[-2], stack[-3], - stack[-4], stack[-5], stack[-6], stack[-7]); - } - goto stdout_check; - - CASE('p','n'): /* printf("\n") */ - Ac = need(pp, arg, line, Ac+2, AcEnd, 0); - printf("\n"); - goto stdout_check; - - - CASE('.','='): /* eval */ - Ac = need(pp, arg, line, Ac+2, AcEnd, 0); - break; - - default: - /* Assignment is handled here. */ - if (isupper(ch) && line[Ac+1] == '=') { - /* Assignment. */ - Ac = need(pp, arg, line, Ac+2, AcEnd, 1); - pp->variables[ch] = arg[0]; - } else - die("unknown command", lp); - break; - } - } - - /* end of line (line[Ac] is '\0'), move to the next line. */ - ++Al; - } - - /* If SP>0 then there was some bad nesting going on - i.e. the brackets have - * not been closed. fnoexec must be zero if SP is 0. - */ - if (SP > 0) - die("unclosed $( $)", "eof"); - assert(fnoexec == 0); -} - -/* The command only understands two options, -v for verify and -h for - * help. - */ -int main(int argc, const char **argv) { - int i = 0; - parse_buf p; - - /* the option must be the first argument. */ - if (argc < 2 || strcmp(argv[1], "-h") == 0) { - fprintf(stderr, "%s: usage: %s ([-v] {comands} |-h [command])\n", argv[0], argv[0]); -#if HELP - if (argc < 3) { - fprintf(stderr, " options:\n" - " -v: do not write, just verify a previous write\n" - " -h: output this help\n" - " -h commands: output command help\n" - " -h <command>: output help for command <command>\n"); - } else { - for (i=2; i<argc; ++i) { - const unsigned char *p = argv[i]; - switch ((argv[i][0] << 8) + argv[i][1]) { - CASE('a','d'): - p = "Enter the keyword in [] contained after the command list\n" - "for more information about those commands"; - break; - CASE('c','o'): - if (argv[i][2] != 'n') - p = " commands are terminated by ';', newline or the end of input:\n" - " <command> {arguments} ;\n\n" - " command: a two character command: [additional info]\n" - " conditional execution: $( $: $? $) [conditional]\n" - " program termination: !? !! [exit]\n" - " input/output control: >> << <> >= <= [io]\n" - " writing to the output: wb wl ws fb cp [write]\n" - " writing to stdout: pr pf pn [messages]\n" - " assignment: <C>= .= [assign]\n" - " comment: #\n\n" - " -h <command>: output the syntax of command <command>\n\n" - " arguments: a string or a postfix numeric expression\n" - " -h string: output the syntax of a string argument\n" - " -h number: output the syntax of a numeric argument\n" - " -h operator: output a summary of the operators\n\n" - " A command which starts with '#' is ignored (a comment)."; - else - p = "conditional evalation\n" - " Conditional evalation has the general form:\n\n" - " $( cond; $? cond; $: cond; $) cond; (command form)\n" - " cond(, cond?, [cond:, [cond), (operator from)\n\n" - " Conditional commands bracket other commands, conditional\n" - " operators bracket other numbers and operators in the expression\n" - " The conditions (cond) are intrepreted as boolean values, with 0\n" - " false and any other value true. Notice that the numbers come off\n" - " the number stack so it is not necessary for them to be given with\n" - " the specific command or before the specifc operator but it can\n" - " be very confusing if this is not done.\n\n" - " The commands/operators behave exactly as the following ANSI C\n" - " syntax:\n\n" - " $( n do if (n) {\n" - " $? n if (n) break;\n" - " $: n } else if (n) {\n" - " $) n } while (n);\n\n" - " Thus a simple if/then/else sequence might be:\n\n" - " $( condition; $: 1; $) 0\n" - " condition( [1: [0)\n\n" - " And a simple do while loop:\n\n" - " $( 1; $) condition\n" - " 1( [condition)\n\n" - " Notice that the operator syntax requires [cond: and [cond)"; - break; - CASE('$','('): /* if */ - p = "conditional: $( condition; do if (condition) {\n"; - break; - CASE('$',':'): /* elif */ - p = "conditional: $( condition; } else if (condition) {\n"; - break; - CASE('$',')'): /* end+loop if */ - p = "conditional: $: condition; } while (condition);\n"; - break; - CASE('$','?'): /* break */ - p = "conditional: $: condition; if (condition) break;\n"; - break; - CASE('e','x'): - p = "program termination commands:\n" - " !? and !! cause immediate termination of the program.\n" - " The currently opened files are closed (and flushed) on\n" - " termination and the program exits with the given code.\n" - " !? is condition - it only causes an exit if the code is\n" - " non zero."; - break; - CASE('!','?'): /* exit if non-zero */ - p = "exit: !? code; if (code != 0) exit(code);\n"; - break; - CASE('!','!'): /* exit unconditionally */ - p = "exit: !! code; exit(code);\n"; - break; - CASE('i','o'): - p = "input/output control:\n" - " >> << allow files to be opened for write or read (respectively)\n" - " A file may be opened for both write and read, but in that case\n" - " the first open must be the write one. Such a file has only one\n" - " position pointer. Supplying an empty file name to the commands\n" - " causes the standard IO stream (stdout or stdin) to be used, but\n" - " the file remains open. A file may be reselected by repeating\n" - " the open command. The position pointer will not have changed.\n\n" - " <> closes a file. If it is the current input or output then the\n" - " input or output, as appropriate, is redirected to the standard\n" - " streams.\n\n" - " <= >= set the input (from) or output (to) pointer. The pointer is\n" - " changed on the current input or output device and is a property of\n" - " that device, not a global setting."; - break; - CASE('>','>'): /* write to (no argument reverts to stdout) */ - p = "io: >> file; select file as the current output device\n" - " >> ; select stdout as the current output device\n"; - break; - CASE('<','<'): /* read from */ - p = "io: << file; select file as the current input device\n" - " << ; select stdin as the current input device\n"; - break; - CASE('<','>'): /* close */ - p = "io: <> file; close file\n"; - break; - CASE('>','<'): /* synonym of close */ - p = "io: <> file; close file\n"; - break; - CASE('>','='): /* set to pointer */ - p = "io: >= offset; set the 'to' pointer: output device file pointer\n"; - break; - CASE('<','='): /* set from pointer */ - p = "io: <= offset; set the 'from' pointer: input device file pointer\n"; - break; - CASE('w','r'): - p = "writing to the output:\n" - " wb, wl, ws, fb and cp write bytes to the current output device at\n" - " the current output position (which may be set using >=.)\n\n" - " See the individual command descriptions for more information."; - break; - CASE('w','b'): /* bigendian number bytes */ - p = "write: wb number bytes; write number in bytes big endian bytes\n"; - break; - CASE('w','l'): /* little endian number bytes */ - p = "write: wl number bytes; write number in bytes little endian bytes\n"; - break; - CASE('w','s'): /* write string */ - p = "write: wl string; write string\n"; - break; - CASE('f','b'): /* fill bytes */ - p = "write: fb number byte; write number bytes of value 'byte'\n"; - break; - CASE('c','p'): /* copy bytes */ - p = "write: cp number; copy number bytes from input to output\n"; - break; - CASE('m','e'): - p = "writing to stdout:\n" - " pr pf and pn allow output to stdout ignoring the current output\n" - " device. pf uses a C printf(3) format string and no checking is\n" - " made on the validity or number of parameters. The program will\n" - " crash if a pointer format (like %s) is used. This is by design.\n" - " Use of \\ escapes within the string will probably not produce the\n" - " expected result. The only way to output a newline is via the\n" - " separate pn command."; - break; - CASE('p','r'): /* printf("%lu\n") */ - p = "message: pr number; printf(\"%lu\\n\", number);\n"; - break; - CASE('p','f'): /* printf(string) */ - p = "message: pf string; printf(string, 1, 2, 3, 4, 5, 6, 7, 8)\n" - " uses string as a format string for the\n" - " top (up to) 8 arguments on the stack\n" - " Empties the stack!\n"; - break; - CASE('p','n'): /* printf("\n") */ - p = "message: pn; prints a newline (printf(\"\\n\");)\n"; - break; - CASE('.','='): - p = ".= [number]\n" - " The number is evaluated and pushed to the stack"; - CASE('a','s'): - p = "<C>= [number] (<C> is an upper case character in the range A-Z)\n" - " The number is assigned to the variable <C>."; - break; - CASE('s','t'): - p = "string format\n" - " A string is either quoted or unquoted. An unquoted string is\n" - " the rest of the line, terminated by ';' or newline (or the end\n" - " of the command). A quoted string is enclosed in either single\n" - " or double quotes (which are removed) and must be the only thing\n" - " on the line apart from white space."; - break; - CASE('n','u'): - p = "number format\n" - " A number is a post-fix expression consisting of numeric values,\n" - " variable values and operators.\n" - " A numeric value is anything starting with a digit. 0x prefixes a\n" - " hexadecimal value, 0 prefixes an octal value otherwise the value\n" - " is interpreted as decimal.\n" - " Variables are single upper case characters in the range A-Z,\n" - " thus 26 variables are available.\n" - " Numeric values and variables are pushed onto a stack which can\n" - " store up to 248 numbers.\n" - " Operators are single characters not identified as numeric values\n" - " or variables. They operate on the stack combining elements.\n" - " Use -h operators for more information."; - break; - CASE('o','p'): - p = "operators\n" - " The following operators are defined. Operators take one or\n" - " two elements from the stack and push back up to two elements\n" - " The conditional operators (:?) are special, see the description\n" - " under -h conditionals for more information. (Conditional\n" - " operators and the conditional commands behave identically.)\n\n" - " In the following descriptions 'left' is the last-but-one\n" - " number on the stack and 'right' is the last number pushed.\n" - " 'new' is a new number pushed on to the top of the stack.\n\n" - " arithmetic operators:\n" - " *: new := left * right (multiplication)\n" - " +: new := left + right\n" - " -: new := left - right\n" - " /: new := left / right (division)\n" - " %: new := left % right (i.e. C style modulus)\n" - " <: new := left < right\n" - " >: new := left > right\n" - " =: new := left == right (i.e. equality)\n" - " |: new := left | right (bitwise or)\n" - " &: new := left | right (bitwise and)\n" - " ^: new := left | right (bitwise xor)\n" - " ~: new := ~right (ones complement)\n" - " !: new := !right (logical complement - right == 0)\n" - " {: new := left << right (bitwise shift left by right bits)\n" - " }: new := left << right (bitwise shift right by right bits)\n" - " r: new := left ROR right (bitwise rotate right by right bits)\n" - " e: new := left SXT right (right least significant bits of\n" - " left are sign extended to 32 bits)\n" - " m: new := left MASK right (right least significant bits of left\n" - " are masked, upper bits become 0)\n" - " operators to read bytes:\n" - " @: new := input-byte (one byte is read from current input)\n" - " b: new := input-big-endian (4 big endian bytes are read)\n" - " l: new := input-little-endian (4 little endian bytes)\n" - " enquiry operators:\n" - " $: the size of the current input device\n" - " f: the current 'from' offset\n" - " #: the size of the current output device\n" - " t: the current 'to' offset\n" - " d: the full device number of the current input device\n" - " this returns the number of the underlying device for a file\n" - " operators to manipulate the stack:\n" - " .: new := right,right (i.e. right is duplicated)\n" - " p: pop - right is removed from the stack\n" - " s: swap - right and left are swapped on the stack\n" - " conditional operators:\n" - " ( exec := right (execution is stopped if right is 0)\n" - " : exec := else if right (execution is resumed or stopped)\n" - " ? if right break (break out of if/loop if right)\n" - " ) loop if right (end of current if/loop)"; - break; - default: - fprintf(stderr, "command '%s' not recognised\n", p); - p = 0; - } - - if (p != 0) - fprintf(stderr, "%s\n", p); - } - } -#endif - exit(1); - } else if (strcmp(argv[1], "-v") == 0) - i = 1; - - init_parse(&p, i); - ++i; - parse(&p, argc-i, argv+i); - quit(&p, 0); -} |