summaryrefslogtreecommitdiff
path: root/packages
diff options
context:
space:
mode:
authorJohn Bowler <jbowler@nslu2-linux.org>2005-05-25 06:59:10 +0000
committerJohn Bowler <jbowler@nslu2-linux.org>2005-05-25 06:59:10 +0000
commit7e21c43a4a6405132c81a81d9f6af07b24cd9f27 (patch)
tree009bd788aa959ba76ee0fb90b3ba1b29c5e78972 /packages
parentba992a6143d7031b51a6ec76b91b46a6af88e0e8 (diff)
Minor fixes to sysconfsetup (which failed on systems using
udev for /dev) and devio (which failed on write if it had to write more than one buffer.) BKrev: 4294223e3XWI1bkqAgS1GviX6o-TAw
Diffstat (limited to 'packages')
-rw-r--r--packages/openslug-init/openslug-init-0.10/devio.c1767
-rw-r--r--packages/openslug-init/openslug-init-0.10/sysconfsetup7
-rw-r--r--packages/openslug-init/openslug-init_0.10.bb9
3 files changed, 1776 insertions, 7 deletions
diff --git a/packages/openslug-init/openslug-init-0.10/devio.c b/packages/openslug-init/openslug-init-0.10/devio.c
index e69de29bb2..bfaace2da1 100644
--- a/packages/openslug-init/openslug-init-0.10/devio.c
+++ b/packages/openslug-init/openslug-init-0.10/devio.c
@@ -0,0 +1,1767 @@
+/* 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);