text: /* vi: set sw=4 ts=4: */
  /*
   * jffs2reader v0.0.18 A jffs2 image reader
   *
   * Copyright (c) 2001 Jari Kirma <Jari.Kirma@hut.fi>
   *
   * This software is provided 'as-is', without any express or implied
   * warranty. In no event will the author be held liable for any damages
   * arising from the use of this software.
   *
   * Permission is granted to anyone to use this software for any
   * purpose, including commercial applications, and to alter it and
   * redistribute it freely, subject to the following restrictions:
   *
   * 1. The origin of this software must not be misrepresented; you must
   * not claim that you wrote the original software. If you use this
   * software in a product, an acknowledgment in the product
   * documentation would be appreciated but is not required.
   *
   * 2. Altered source versions must be plainly marked as such, and must
   * not be misrepresented as being the original software.
   *
   * 3. This notice may not be removed or altered from any source
   * distribution.
   *
   *
   *********
   *  This code was altered September 2001
   *  Changes are Copyright (c) Erik Andersen <andersen@codepoet.org>
   *
   * In compliance with (2) above, this is hereby marked as an altered
   * version of this software.  It has been altered as follows:
   *      *) Listing a directory now mimics the behavior of 'ls -l'
   *      *) Support for recursive listing has been added
   *      *) Without options, does a recursive 'ls' on the whole filesystem
   *      *) option parsing now uses getopt()
   *      *) Now uses printf, and error messages go to stderr.
   *      *) The copyright notice has been cleaned up and reformatted
   *      *) The code has been reformatted
   *      *) Several twisty code paths have been fixed so I can understand them.
   *  -Erik, 1 September 2001
   *
   *      *) Made it show major/minor numbers for device nodes
   *      *) Made it show symlink targets
   *  -Erik, 13 September 2001
   */
  
  
  /*
  TODO:
  
  - Add CRC checking code to places marked with XXX.
  - Add support for other node compression types.
  
  - Test with real life images.
  - Maybe port into bootloader.
   */
  
  /*
  BUGS:
  
  - Doesn't check CRC checksums.
   */
  
  
  #include <errno.h>
  #include <stdint.h>
  #include <stdio.h>
  #include <stdlib.h>
  #include <string.h>
  #include <unistd.h>
  #include <fcntl.h>
  #include <time.h>
  #include <sys/types.h>
  #include <sys/stat.h>
  #include <sys/param.h>
  #include <dirent.h>
  #include <zlib.h>
  #include <linux/jffs2.h>
  
  #define SCRATCH_SIZE (5*1024*1024)
  
  #ifndef MAJOR
  /* FIXME:  I am using illicit insider knowledge of
   * kernel major/minor representation...  */
  #define MAJOR(dev) (((dev)>>8)&0xff)
  #define MINOR(dev) ((dev)&0xff)
  #endif
  
  
  #define DIRENT_INO(dirent) ((dirent)!=NULL?(dirent)->ino:0)
  #define DIRENT_PINO(dirent) ((dirent)!=NULL?(dirent)->pino:0)
  
  struct dir {
  	struct dir *next;
  	uint8_t type;
  	uint8_t nsize;
  	uint32_t ino;
  	char name[256];
  };
  
  void putblock(char *, size_t, size_t *, struct jffs2_raw_inode *);
  struct dir *putdir(struct dir *, struct jffs2_raw_dirent *);
  void printdir(char *o, size_t size, struct dir *d, char *path,
  		int recurse);
  void freedir(struct dir *);
  
  struct jffs2_raw_inode *find_raw_inode(char *o, size_t size, uint32_t ino);
  struct jffs2_raw_dirent *resolvedirent(char *, size_t, uint32_t, uint32_t,
  		char *, uint8_t);
  struct jffs2_raw_dirent *resolvename(char *, size_t, uint32_t, char *, uint8_t);
  struct jffs2_raw_dirent *resolveinode(char *, size_t, uint32_t);
  
  struct jffs2_raw_dirent *resolvepath0(char *, size_t, uint32_t, char *,
  		uint32_t *, int);
  struct jffs2_raw_dirent *resolvepath(char *, size_t, uint32_t, char *,
  		uint32_t *);
  
  void lsdir(char *, size_t, char *, int);
  void catfile(char *, size_t, char *, char *, size_t, size_t *);
  
  int main(int, char **);
  
  /* writes file node into buffer, to the proper position. */
  /* reading all valid nodes in version order reconstructs the file. */
  
  /*
     b       - buffer
     bsize   - buffer size
     rsize   - result size
     n       - node
   */
  
  void putblock(char *b, size_t bsize, size_t * rsize,
  		struct jffs2_raw_inode *n)
  {
  	uLongf dlen = n->dsize;
  
  	if (n->isize > bsize || (n->offset + dlen) > bsize) {
  		fprintf(stderr, "File does not fit into buffer!\n");
  		exit(EXIT_FAILURE);
  	}
  
  	if (*rsize < n->isize)
  		bzero(b + *rsize, n->isize - *rsize);
  
  	switch (n->compr) {
  		case JFFS2_COMPR_ZLIB:
  			uncompress((Bytef *) b + n->offset, &dlen,
  					(Bytef *) ((char *) n) + sizeof(struct jffs2_raw_inode),
  					(uLongf) n->csize);
  			break;
  
  		case JFFS2_COMPR_NONE:
  			memcpy(b + n->offset,
  					((char *) n) + sizeof(struct jffs2_raw_inode), dlen);
  			break;
  
  		case JFFS2_COMPR_ZERO:
  			bzero(b + n->offset, dlen);
  			break;
  
  			/* [DYN]RUBIN support required! */
  
  		default:
  			fprintf(stderr, "Unsupported compression method!\n");
  			exit(EXIT_FAILURE);
  	}
  
  	*rsize = n->isize;
  }
  
  /* adds/removes directory node into dir struct. */
  /* reading all valid nodes in version order reconstructs the directory. */
  
  /*
     dd      - directory struct being processed
     n       - node
  
     return value: directory struct value replacing dd
   */
  
  struct dir *putdir(struct dir *dd, struct jffs2_raw_dirent *n)
  {
  	struct dir *o, *d, *p;
  
  	o = dd;
  
  	if (n->ino) {
  		if (dd == NULL) {
  			d = malloc(sizeof(struct dir));
  			d->type = n->type;
  			memcpy(d->name, n->name, n->nsize);
  			d->nsize = n->nsize;
  			d->ino = n->ino;
  			d->next = NULL;
  
  			return d;
  		}
  
  		while (1) {
  			if (n->nsize == dd->nsize &&
  					!memcmp(n->name, dd->name, n->nsize)) {
  				dd->type = n->type;
  				dd->ino = n->ino;
  
  				return o;
  			}
  
  			if (dd->next == NULL) {
  				dd->next = malloc(sizeof(struct dir));
  				dd->next->type = n->type;
  				memcpy(dd->next->name, n->name, n->nsize);
  				dd->next->nsize = n->nsize;
  				dd->next->ino = n->ino;
  				dd->next->next = NULL;
  
  				return o;
  			}
  
  			dd = dd->next;
  		}
  	} else {
  		if (dd == NULL)
  			return NULL;
  
  		if (n->nsize == dd->nsize && !memcmp(n->name, dd->name, n->nsize)) {
  			d = dd->next;
  			free(dd);
  			return d;
  		}
  
  		while (1) {
  			p = dd;
  			dd = dd->next;
  
  			if (dd == NULL)
  				return o;
  
  			if (n->nsize == dd->nsize &&
  					!memcmp(n->name, dd->name, n->nsize)) {
  				p->next = dd->next;
  				free(dd);
  
  				return o;
  			}
  		}
  	}
  }
  
  
  #define TYPEINDEX(mode) (((mode) >> 12) & 0x0f)
  #define TYPECHAR(mode)  ("0pcCd?bB-?l?s???" [TYPEINDEX(mode)])
  
  /* The special bits. If set, display SMODE0/1 instead of MODE0/1 */
  static const mode_t SBIT[] = {
  	0, 0, S_ISUID,
  	0, 0, S_ISGID,
  	0, 0, S_ISVTX
  };
  
  /* The 9 mode bits to test */
  static const mode_t MBIT[] = {
  	S_IRUSR, S_IWUSR, S_IXUSR,
  	S_IRGRP, S_IWGRP, S_IXGRP,
  	S_IROTH, S_IWOTH, S_IXOTH
  };
  
  static const char MODE1[] = "rwxrwxrwx";
  static const char MODE0[] = "---------";
  static const char SMODE1[] = "..s..s..t";
  static const char SMODE0[] = "..S..S..T";
  
  /*
   * Return the standard ls-like mode string from a file mode.
   * This is static and so is overwritten on each call.
   */
  const char *mode_string(int mode)
  {
  	static char buf[12];
  
  	int i;
  
  	buf[0] = TYPECHAR(mode);
  	for (i = 0; i < 9; i++) {
  		if (mode & SBIT[i])
  			buf[i + 1] = (mode & MBIT[i]) ? SMODE1[i] : SMODE0[i];
  		else
  			buf[i + 1] = (mode & MBIT[i]) ? MODE1[i] : MODE0[i];
  	}
  	return buf;
  }
  
  /* prints contents of directory structure */
  
  /*
     d       - dir struct
   */
  
  void printdir(char *o, size_t size, struct dir *d, char *path, int recurse)
  {
  	char m;
  	char *filetime;
  	time_t age;
  	struct jffs2_raw_inode *ri;
  
  	if (!path)
  		return;
  	if (strlen(path) == 1 && *path == '/')
  		path++;
  
  	while (d != NULL) {
  		switch (d->type) {
  			case DT_REG:
  				m = ' ';
  				break;
  
  			case DT_FIFO:
  				m = '|';
  				break;
  
  			case DT_CHR:
  				m = ' ';
  				break;
  
  			case DT_BLK:
  				m = ' ';
  				break;
  
  			case DT_DIR:
  				m = '/';
  				break;
  
  			case DT_LNK:
  				m = ' ';
  				break;
  
  			case DT_SOCK:
  				m = '=';
  				break;
  
  			default:
  				m = '?';
  		}
  		ri = find_raw_inode(o, size, d->ino);
  		if (!ri) {
  			fprintf(stderr, "bug: raw_inode missing!\n");
  			d = d->next;
  			continue;
  		}
  
  		filetime = ctime((const time_t *) &(ri->ctime));
  		age = time(NULL) - ri->ctime;
  		printf("%s %-4d %-8d %-8d ", mode_string(ri->mode),
  				1, ri->uid, ri->gid);
  		if ( d->type==DT_BLK || d->type==DT_CHR ) {
  			dev_t rdev;
  			size_t devsize;
  			putblock((char*)&rdev, sizeof(rdev), &devsize, ri);
  			printf("%4d, %3d ", (int)MAJOR(rdev), (int)MINOR(rdev));
  		} else {
  			printf("%9ld ", (long)ri->dsize);
  		}
  		d->name[d->nsize]='\0';
  		if (age < 3600L * 24 * 365 / 2 && age > -15 * 60) {
  			/* hh:mm if less than 6 months old */
  			printf("%6.6s %5.5s %s/%s%c", filetime + 4, filetime + 11, path, d->name, m);
  		} else {
  			printf("%6.6s %4.4s %s/%s%c", filetime + 4, filetime + 20, path, d->name, m);
  		}
  		if (d->type == DT_LNK) {
  			char symbuf[1024];
  			size_t symsize;
  			putblock(symbuf, sizeof(symbuf), &symsize, ri);
  			symbuf[symsize] = 0;
  			printf(" -> %s", symbuf);
  		}
  		printf("\n");
  
  		if (d->type == DT_DIR && recurse) {
  			char *tmp;
  			tmp = malloc(BUFSIZ);
  			if (!tmp) {
  				fprintf(stderr, "memory exhausted\n");
  				exit(EXIT_FAILURE);
  			}
  			sprintf(tmp, "%s/%s", path, d->name);
  			lsdir(o, size, tmp, recurse);		/* Go recursive */
  			free(tmp);
  		}
  
  		d = d->next;
  	}
  }
  
  /* frees memory used by directory structure */
  
  /*
     d       - dir struct
   */
  
  void freedir(struct dir *d)
  {
  	struct dir *t;
  
  	while (d != NULL) {
  		t = d->next;
  		free(d);
  		d = t;
  	}
  }
  
  /* collects directory/file nodes in version order. */
  
  /*
     f       - file flag.
     if zero, collect file, compare ino to inode
     otherwise, collect directory, compare ino to parent inode
     o       - filesystem image pointer
     size    - size of filesystem image
     ino     - inode to compare against. see f.
  
     return value: a jffs2_raw_inode that corresponds the the specified
     inode, or NULL
   */
  
  struct jffs2_raw_inode *find_raw_inode(char *o, size_t size, uint32_t ino)
  {
  	/* aligned! */
  	union jffs2_node_union *n;
  	union jffs2_node_union *e = (union jffs2_node_union *) (o + size);
  	union jffs2_node_union *lr;	/* last block position */
  	union jffs2_node_union *mp = NULL;	/* minimum position */
  
  	uint32_t vmin, vmint, vmaxt, vmax, vcur, v;
  
  	vmin = 0;					/* next to read */
  	vmax = ~((uint32_t) 0);		/* last to read */
  	vmint = ~((uint32_t) 0);
  	vmaxt = 0;					/* found maximum */
  	vcur = 0;					/* XXX what is smallest version number used? */
  	/* too low version number can easily result excess log rereading */
  
  	n = (union jffs2_node_union *) o;
  	lr = n;
  
  	do {
  		while (n < e && n->u.magic != JFFS2_MAGIC_BITMASK)
  			((char *) n) += 4;
  
  		if (n < e && n->u.magic == JFFS2_MAGIC_BITMASK) {
  			if (n->u.nodetype == JFFS2_NODETYPE_INODE &&
  					n->i.ino == ino && (v = n->i.version) > vcur) {
  				/* XXX crc check */
  
  				if (vmaxt < v)
  					vmaxt = v;
  				if (vmint > v) {
  					vmint = v;
  					mp = n;
  				}
  
  				if (v == (vcur + 1))
  					return (&(n->i));
  			}
  
  			((char *) n) += ((n->u.totlen + 3) & ~3);
  		} else
  			n = (union jffs2_node_union *) o;	/* we're at the end, rewind to the beginning */
  
  		if (lr == n) {			/* whole loop since last read */
  			vmax = vmaxt;
  			vmin = vmint;
  			vmint = ~((uint32_t) 0);
  
  			if (vcur < vmax && vcur < vmin)
  				return (&(mp->i));
  		}
  	} while (vcur < vmax);
  
  	return NULL;
  }
  
  /* collects dir struct for selected inode */
  
  /*
     o       - filesystem image pointer
     size    - size of filesystem image
     pino    - inode of the specified directory
     d       - input directory structure
  
     return value: result directory structure, replaces d.
   */
  
  struct dir *collectdir(char *o, size_t size, uint32_t ino, struct dir *d)
  {
  	/* aligned! */
  	union jffs2_node_union *n;
  	union jffs2_node_union *e = (union jffs2_node_union *) (o + size);
  	union jffs2_node_union *lr;	/* last block position */
  	union jffs2_node_union *mp = NULL;	/* minimum position */
  
  	uint32_t vmin, vmint, vmaxt, vmax, vcur, v;
  
  	vmin = 0;					/* next to read */
  	vmax = ~((uint32_t) 0);		/* last to read */
  	vmint = ~((uint32_t) 0);
  	vmaxt = 0;					/* found maximum */
  	vcur = 0;					/* XXX what is smallest version number used? */
  	/* too low version number can easily result excess log rereading */
  
  	n = (union jffs2_node_union *) o;
  	lr = n;
  
  	do {
  		while (n < e && n->u.magic != JFFS2_MAGIC_BITMASK)
  			((char *) n) += 4;
  
  		if (n < e && n->u.magic == JFFS2_MAGIC_BITMASK) {
  			if (n->u.nodetype == JFFS2_NODETYPE_DIRENT &&
  					n->d.pino == ino && (v = n->d.version) > vcur) {
  				/* XXX crc check */
  
  				if (vmaxt < v)
  					vmaxt = v;
  				if (vmint > v) {
  					vmint = v;
  					mp = n;
  				}
  
  				if (v == (vcur + 1)) {
  					d = putdir(d, &(n->d));
  
  					lr = n;
  					vcur++;
  					vmint = ~((uint32_t) 0);
  				}
  			}
  
  			((char *) n) += ((n->u.totlen + 3) & ~3);
  		} else
  			n = (union jffs2_node_union *) o;	/* we're at the end, rewind to the beginning */
  
  		if (lr == n) {			/* whole loop since last read */
  			vmax = vmaxt;
  			vmin = vmint;
  			vmint = ~((uint32_t) 0);
  
  			if (vcur < vmax && vcur < vmin) {
  				d = putdir(d, &(mp->d));
  
  				lr = n =
  					(union jffs2_node_union *) (((char *) mp) +
  							((mp->u.totlen + 3) & ~3));
  
  				vcur = vmin;
  			}
  		}
  	} while (vcur < vmax);
  
  	return d;
  }
  
  
  
  /* resolve dirent based on criteria */
  
  /*
     o       - filesystem image pointer
     size    - size of filesystem image
     ino     - if zero, ignore,
     otherwise compare against dirent inode
     pino    - if zero, ingore,
     otherwise compare against parent inode
     and use name and nsize as extra criteria
     name    - name of wanted dirent, used if pino!=0
     nsize   - length of name of wanted dirent, used if pino!=0
  
     return value: pointer to relevant dirent structure in
     filesystem image or NULL
   */
  
  struct jffs2_raw_dirent *resolvedirent(char *o, size_t size,
  		uint32_t ino, uint32_t pino,
  		char *name, uint8_t nsize)
  {
  	/* aligned! */
  	union jffs2_node_union *n;
  	union jffs2_node_union *e = (union jffs2_node_union *) (o + size);
  
  	struct jffs2_raw_dirent *dd = NULL;
  
  	uint32_t vmax, v;
  
  	if (!pino && ino <= 1)
  		return dd;
  
  	vmax = 0;
  
  	n = (union jffs2_node_union *) o;
  
  	do {
  		while (n < e && n->u.magic != JFFS2_MAGIC_BITMASK)
  			((char *) n) += 4;
  
  		if (n < e && n->u.magic == JFFS2_MAGIC_BITMASK) {
  			if (n->u.nodetype == JFFS2_NODETYPE_DIRENT &&
  					(!ino || n->d.ino == ino) &&
  					(v = n->d.version) > vmax &&
  					(!pino || (n->d.pino == pino &&
  							   nsize == n->d.nsize &&
  							   !memcmp(name, n->d.name, nsize)))) {
  				/* XXX crc check */
  
  				if (vmax < v) {
  					vmax = v;
  					dd = &(n->d);
  				}
  			}
  
  			((char *) n) += ((n->u.totlen + 3) & ~3);
  		} else
  			return dd;
  	} while (1);
  }
  
  /* resolve name under certain parent inode to dirent */
  
  /*
     o       - filesystem image pointer
     size    - size of filesystem image
     pino    - requested parent inode
     name    - name of wanted dirent
     nsize   - length of name of wanted dirent
  
     return value: pointer to relevant dirent structure in
     filesystem image or NULL
   */
  
  struct jffs2_raw_dirent *resolvename(char *o, size_t size, uint32_t pino,
  		char *name, uint8_t nsize)
  {
  	return resolvedirent(o, size, 0, pino, name, nsize);
  }
  
  /* resolve inode to dirent */
  
  /*
     o       - filesystem image pointer
     size    - size of filesystem image
     ino     - compare against dirent inode
  
     return value: pointer to relevant dirent structure in
     filesystem image or NULL
   */
  
  struct jffs2_raw_dirent *resolveinode(char *o, size_t size, uint32_t ino)
  {
  	return resolvedirent(o, size, ino, 0, NULL, 0);
  }
  
  /* resolve slash-style path into dirent and inode.
     slash as first byte marks absolute path (root=inode 1).
     . and .. are resolved properly, and symlinks are followed.
   */
  
  /*
     o       - filesystem image pointer
     size    - size of filesystem image
     ino     - root inode, used if path is relative
     p       - path to be resolved
     inos    - result inode, zero if failure
     recc    - recursion count, to detect symlink loops
  
     return value: pointer to dirent struct in file system image.
     note that root directory doesn't have dirent struct
     (return value is NULL), but it has inode (*inos=1)
   */
  
  struct jffs2_raw_dirent *resolvepath0(char *o, size_t size, uint32_t ino,
  		char *p, uint32_t * inos, int recc)
  {
  	struct jffs2_raw_dirent *dir = NULL;
  
  	int d = 1;
  	uint32_t tino;
  
  	char *next;
  
  	char *path, *pp;
  
  	char symbuf[1024];
  	size_t symsize;
  
  	if (recc > 16) {
  		/* probably symlink loop */
  		*inos = 0;
  		return NULL;
  	}
  
  	pp = path = strdup(p);
  
  	if (*path == '/') {
  		path++;
  		ino = 1;
  	}
  
  	if (ino > 1) {
  		dir = resolveinode(o, size, ino);
  
  		ino = DIRENT_INO(dir);
  	}
  
  	next = path - 1;
  
  	while (ino && next != NULL && next[1] != 0 && d) {
  		path = next + 1;
  		next = strchr(path, '/');
  
  		if (next != NULL)
  			*next = 0;
  
  		if (*path == '.' && path[1] == 0)
  			continue;
  		if (*path == '.' && path[1] == '.' && path[2] == 0) {
  			if (DIRENT_PINO(dir) == 1) {
  				ino = 1;
  				dir = NULL;
  			} else {
  				dir = resolveinode(o, size, DIRENT_PINO(dir));
  				ino = DIRENT_INO(dir);
  			}
  
  			continue;
  		}
  
  		dir = resolvename(o, size, ino, path, (uint8_t) strlen(path));
  
  		if (DIRENT_INO(dir) == 0 ||
  				(next != NULL &&
  				 !(dir->type == DT_DIR || dir->type == DT_LNK))) {
  			free(pp);
  
  			*inos = 0;
  
  			return NULL;
  		}
  
  		if (dir->type == DT_LNK) {
  			struct jffs2_raw_inode *ri;
  			ri = find_raw_inode(o, size, DIRENT_INO(dir));
  			putblock(symbuf, sizeof(symbuf), &symsize, ri);
  			symbuf[symsize] = 0;
  
  			tino = ino;
  			ino = 0;
  
  			dir = resolvepath0(o, size, tino, symbuf, &ino, ++recc);
  
  			if (dir != NULL && next != NULL &&
  					!(dir->type == DT_DIR || dir->type == DT_LNK)) {
  				free(pp);
  
  				*inos = 0;
  				return NULL;
  			}
  		}
  		if (dir != NULL)
  			ino = DIRENT_INO(dir);
  	}
  
  	free(pp);
  
  	*inos = ino;
  
  	return dir;
  }
  
  /* resolve slash-style path into dirent and inode.
     slash as first byte marks absolute path (root=inode 1).
     . and .. are resolved properly, and symlinks are followed.
   */
  
  /*
     o       - filesystem image pointer
     size    - size of filesystem image
     ino     - root inode, used if path is relative
     p       - path to be resolved
     inos    - result inode, zero if failure
  
     return value: pointer to dirent struct in file system image.
     note that root directory doesn't have dirent struct
     (return value is NULL), but it has inode (*inos=1)
   */
  
  struct jffs2_raw_dirent *resolvepath(char *o, size_t size, uint32_t ino,
  		char *p, uint32_t * inos)
  {
  	return resolvepath0(o, size, ino, p, inos, 0);
  }
  
  /* lists files on directory specified by path */
  
  /*
     o       - filesystem image pointer
     size    - size of filesystem image
     p       - path to be resolved
   */
  
  void lsdir(char *o, size_t size, char *path, int recurse)
  {
  	struct jffs2_raw_dirent *dd;
  	struct dir *d = NULL;
  
  	uint32_t ino;
  
  	dd = resolvepath(o, size, 1, path, &ino);
  
  	if (ino == 0 ||
  			(dd == NULL && ino == 0) || (dd != NULL && dd->type != DT_DIR)) {
  		fprintf(stderr, "jffs2reader: %s: No such file or directory\n",
  				path);
  		exit(EXIT_FAILURE);
  	}
  
  	d = collectdir(o, size, ino, d);
  	printdir(o, size, d, path, recurse);
  	freedir(d);
  }
  
  /* writes file specified by path to the buffer */
  
  /*
     o       - filesystem image pointer
     size    - size of filesystem image
     p       - path to be resolved
     b       - file buffer
     bsize   - file buffer size
     rsize   - file result size
   */
  
  void catfile(char *o, size_t size, char *path, char *b, size_t bsize,
  		size_t * rsize)
  {
  	struct jffs2_raw_dirent *dd;
  	struct jffs2_raw_inode *ri;
  	uint32_t ino;
  
  	dd = resolvepath(o, size, 1, path, &ino);
  
  	if (ino == 0) {
  		fprintf(stderr, "%s: No such file or directory\n", path);
  		exit(EXIT_FAILURE);
  	}
  
  	if (dd == NULL || dd->type != DT_REG) {
  		fprintf(stderr, "%s: Not a regular file\n", path);
  		exit(EXIT_FAILURE);
  	}
  
  	ri = find_raw_inode(o, size, ino);
  	putblock(b, bsize, rsize, ri);
  
  	write(1, b, *rsize);
  }
  
  /* usage example */
  
  int main(int argc, char **argv)
  {
  	int fd, opt, recurse = 0;
  	struct stat st;
  
  	char *scratch, *dir = NULL, *file = NULL;
  	size_t ssize = 0;
  
  	char *buf;
  
  	while ((opt = getopt(argc, argv, "rd:f:")) > 0) {
  		switch (opt) {
  			case 'd':
  				dir = optarg;
  				break;
  			case 'f':
  				file = optarg;
  				break;
  			case 'r':
  				recurse++;
  				break;
  			default:
  				fprintf(stderr,
  						"Usage: jffs2reader <image> [-d|-f] < path > \n");
  				exit(EXIT_FAILURE);
  		}
  	}
  
  	fd = open(argv[optind], O_RDONLY);
  	if (fd == -1) {
  		fprintf(stderr, "%s: %s\n", argv[optind], strerror(errno));
  		exit(2);
  	}
  
  	if (fstat(fd, &st)) {
  		fprintf(stderr, "%s: %s\n", argv[optind], strerror(errno));
  		exit(3);
  	}
  
  	buf = malloc((size_t) st.st_size);
  	if (buf == NULL) {
  		fprintf(stderr, "%s: memory exhausted\n", argv[optind]);
  		exit(4);
  	}
  
  	if (read(fd, buf, st.st_size) != (ssize_t) st.st_size) {
  		fprintf(stderr, "%s: %s\n", argv[optind], strerror(errno));
  		exit(5);
  	}
  
  	if (dir)
  		lsdir(buf, st.st_size, dir, recurse);
  
  	if (file) {
  		scratch = malloc(SCRATCH_SIZE);
  		if (scratch == NULL) {
  			fprintf(stderr, "%s: memory exhausted\n", argv[optind]);
  			exit(6);
  		}
  
  		catfile(buf, st.st_size, file, scratch, SCRATCH_SIZE, &ssize);
  		free(scratch);
  	}
  
  	if (!dir && !file)
  		lsdir(buf, st.st_size, "/", 1);
  
  
  	free(buf);
  	exit(EXIT_SUCCESS);
  }
  