merge
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 3869050..bf6e557 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -48,16 +48,13 @@
 
 CHECK_SYMBOL_EXISTS(MoveFileExA windows.h HAVE_MOVEFILEEXA)
 
+CHECK_INCLUDE_FILES(fts.h HAVE_FTS_H)
 CHECK_INCLUDE_FILES(strings.h HAVE_STRINGS_H)
 CHECK_INCLUDE_FILES(unistd.h HAVE_UNISTD_H)
 
 CHECK_INCLUDE_FILES(inttypes.h HAVE_INTTYPES_H_LIBZIP)
 CHECK_INCLUDE_FILES(stdint.h HAVE_STDINT_H_LIBZIP)
 CHECK_INCLUDE_FILES(sys/types.h HAVE_SYS_TYPES_H_LIBZIP)
-CHECK_INCLUDE_FILES(dirent.h HAVE_DIRENT_H)
-CHECK_INCLUDE_FILES(ndir.h HAVE_NDIR_H)
-CHECK_INCLUDE_FILES(sys/dir.h HAVE_SYS_DIR_H)
-CHECK_INCLUDE_FILES(sys/ndir.h HAVE_SYS_NDIR_H)
 
 CHECK_TYPE_SIZE(__int8 __INT8_LIBZIP)
 CHECK_TYPE_SIZE(int8_t INT8_T_LIBZIP)
diff --git a/TODO b/TODO
index b9a4c26..b49df63 100644
--- a/TODO
+++ b/TODO
@@ -25,11 +25,11 @@
 
 Features
 ========
-! test and if necessary fix zipcmp on Windows
 * progress callback for zip_close (called after each file written completely)
 * function to copy file from one archive to another
 * do not compress if storing is smaller
 * set O_CLOEXEC flag after fopen and mkstemp
+* switch to new perl test framework
 
 ! W test extra fields api
   . set
@@ -56,7 +56,6 @@
 
 Bugs
 ====
-! invalidate zip_source_zip when source archive is closed
 * embed stripped down old zlib for torrentzip
 ! ensure that nentries is small enough not to cause overflow (size_t for entry, uint64 for CD on disk)
 * Xcode 32bit build uses 64bit config.h
diff --git a/configure.ac b/configure.ac
index afc5f7f..4e63629 100644
--- a/configure.ac
+++ b/configure.ac
@@ -63,8 +63,7 @@
 			[Define to 1 if you have the `MoveFileExA' function.])],
 	       [AC_MSG_RESULT([no])])
 
-AC_CHECK_HEADERS([unistd.h strings.h])
-AC_HEADER_DIRENT
+AC_CHECK_HEADERS([fts.h strings.h unistd.h])
 
 AC_CHECK_TYPES([int8_t])
 AC_CHECK_TYPES([int16_t])
diff --git a/lib/zip_close.c b/lib/zip_close.c
index 1ad796d..577e921 100644
--- a/lib/zip_close.c
+++ b/lib/zip_close.c
@@ -88,8 +88,16 @@
     /* don't create zip files with no entries */
     if (survivors == 0) {
 	if (za->zn && ((za->open_flags & ZIP_TRUNCATE) || (changed && za->zp))) {
+	    int reopen = 0;
+	    if (za->zp) {
+		reopen = 1;
+		fclose(za->zp);
+		za->zp = NULL;
+	    }
 	    if (remove(za->zn) != 0) {
 		_zip_error_set(&za->error, ZIP_ER_REMOVE, errno);
+		if (reopen)
+		    za->zp = fopen(za->zn, "rb");
 		return -1;
 	    }
 	}
diff --git a/lib/zip_discard.c b/lib/zip_discard.c
index 051fd72..3fc6d8f 100644
--- a/lib/zip_discard.c
+++ b/lib/zip_discard.c
@@ -1,6 +1,6 @@
 /*
   zip_discard.c -- discard and free struct zip
-  Copyright (C) 1999-2012 Dieter Baron and Thomas Klausner
+  Copyright (C) 1999-2014 Dieter Baron and Thomas Klausner
 
   This file is part of libzip, a library to manipulate ZIP archives.
   The authors can be contacted at <libzip@nih.at>
@@ -65,15 +65,12 @@
 	free(za->entry);
     }
 
-    for (i=0; i<za->nfile; i++) {
-	if (za->file[i]->error.zip_err == ZIP_ER_OK) {
-	    _zip_error_set(&za->file[i]->error, ZIP_ER_ZIPCLOSED, 0);
-	    za->file[i]->za = NULL;
-	}
+    for (i=0; i<za->nsource; i++) {
+	_zip_source_filep_invalidate(za->source[i]);
     }
 
     _zip_error_fini(&za->error);
-    free(za->file);
+    free(za->source);
     
     free(za);
 
diff --git a/lib/zip_fclose.c b/lib/zip_fclose.c
index a7dfdf3..c336c31 100644
--- a/lib/zip_fclose.c
+++ b/lib/zip_fclose.c
@@ -1,6 +1,6 @@
 /*
   zip_fclose.c -- close file in zip archive
-  Copyright (C) 1999-2007 Dieter Baron and Thomas Klausner
+  Copyright (C) 1999-2014 Dieter Baron and Thomas Klausner
 
   This file is part of libzip, a library to manipulate ZIP archives.
   The authors can be contacted at <libzip@nih.at>
@@ -46,16 +46,6 @@
     if (zf->src)
 	zip_source_free(zf->src);
 
-    if (zf->za) {
-        for (i=0; i<zf->za->nfile; i++) {
-            if (zf->za->file[i] == zf) {
-                zf->za->file[i] = zf->za->file[zf->za->nfile-1];
-                zf->za->nfile--;
-                break;
-            }
-        }
-    }
-
     ret = 0;
     if (zf->error.zip_err)
 	ret = zf->error.zip_err;
diff --git a/lib/zip_fopen_index_encrypted.c b/lib/zip_fopen_index_encrypted.c
index e429f6c..7aaf894 100644
--- a/lib/zip_fopen_index_encrypted.c
+++ b/lib/zip_fopen_index_encrypted.c
@@ -1,6 +1,6 @@
 /*
   zip_fopen_index_encrypted.c -- open file for reading by index w/ password
-  Copyright (C) 1999-2013 Dieter Baron and Thomas Klausner
+  Copyright (C) 1999-2014 Dieter Baron and Thomas Klausner
 
   This file is part of libzip, a library to manipulate ZIP archives.
   The authors can be contacted at <libzip@nih.at>
@@ -77,22 +77,6 @@
 	_zip_error_set(&za->error, ZIP_ER_MEMORY, 0);
 	return NULL;
     }
-    
-    if (za->nfile+1 >= za->nfile_alloc) {
-	unsigned int n;
-	n = za->nfile_alloc + 10;
-	file = (struct zip_file **)realloc(za->file,
-					   n*sizeof(struct zip_file *));
-	if (file == NULL) {
-	    _zip_error_set(&za->error, ZIP_ER_MEMORY, 0);
-	    free(zf);
-	    return NULL;
-	}
-	za->nfile_alloc = n;
-	za->file = file;
-    }
-
-    za->file[za->nfile++] = zf;
 
     zf->za = za;
     _zip_error_init(&zf->error);
diff --git a/lib/zip_new.c b/lib/zip_new.c
index 7acc84e..db64be3 100644
--- a/lib/zip_new.c
+++ b/lib/zip_new.c
@@ -1,6 +1,6 @@
 /*
   zip_new.c -- create and init struct zip
-  Copyright (C) 1999-2012 Dieter Baron and Thomas Klausner
+  Copyright (C) 1999-2014 Dieter Baron and Thomas Klausner
 
   This file is part of libzip, a library to manipulate ZIP archives.
   The authors can be contacted at <libzip@nih.at>
@@ -62,8 +62,8 @@
     za->comment_changed = 0;
     za->nentry = za->nentry_alloc = 0;
     za->entry = NULL;
-    za->nfile = za->nfile_alloc = 0;
-    za->file = NULL;
+    za->nsource = za->nsource_alloc = 0;
+    za->source = NULL;
     za->tempdir = NULL;
     
     return za;
diff --git a/lib/zip_source_filep.c b/lib/zip_source_filep.c
index 48f1878..5c1a7ab 100644
--- a/lib/zip_source_filep.c
+++ b/lib/zip_source_filep.c
@@ -1,6 +1,6 @@
 /*
   zip_source_filep.c -- create data source from FILE *
-  Copyright (C) 1999-2009 Dieter Baron and Thomas Klausner
+  Copyright (C) 1999-2014 Dieter Baron and Thomas Klausner
 
   This file is part of libzip, a library to manipulate ZIP archives.
   The authors can be contacted at <libzip@nih.at>
@@ -50,10 +50,13 @@
     zip_int64_t len;	/* length of data to copy */
     zip_int64_t remain;	/* bytes remaining to be copied */
     int e[2];		/* error codes */
+    int source_closed;  /* set if source archive is closed */
+    struct zip *source_archive;  /* zip archive we're reading from, NULL if not from archive */
 };
 
-static zip_int64_t read_file(void *state, void *data, zip_uint64_t len,
-		     enum zip_source_cmd cmd);
+static zip_int64_t read_file(void *state, void *data, zip_uint64_t len, enum zip_source_cmd cmd);
+static void _zip_deregister_source(struct zip *za, void *ud);
+static int _zip_register_source(struct zip *za, struct zip_source *src);
 
 
 ZIP_EXTERN struct zip_source *
@@ -102,6 +105,8 @@
     f->off = start;
     f->len = (len ? len : -1);
     f->closep = f->fname ? 1 : closep;
+    f->source_closed = 0;
+    f->source_archive = NULL;
     if (st)
 	memcpy(&f->st, st, sizeof(f->st));
     else
@@ -116,6 +121,31 @@
 }
 
 
+int
+_zip_source_filep_set_source_archive(struct zip_source *src, struct zip *za)
+{
+    struct read_file *z = (struct read_file *)src->ud;
+
+    z->source_archive = za;
+    return _zip_register_source(za, src);
+}
+
+
+/* called by zip_discard to avoid operating on file from closed archive */
+void
+_zip_source_filep_invalidate(struct zip_source *src)
+{
+    struct read_file *z = (struct read_file *)src->ud;
+
+    z->source_closed = 1;
+    z->f = NULL;
+    if (z->e[0] == ZIP_ER_OK) {
+	z->e[0] = ZIP_ER_ZIPCLOSED;
+	z->e[1] = 0;
+    }
+}
+
+
 static zip_int64_t
 read_file(void *state, void *data, zip_uint64_t len, enum zip_source_cmd cmd)
 {
@@ -129,6 +159,9 @@
 
     switch (cmd) {
     case ZIP_SOURCE_OPEN:
+	if (z->source_closed)
+	    return -1;
+
 	if (z->fname) {
 	    if ((z->f=fopen(z->fname, "rb")) == NULL) {
 		z->e[0] = ZIP_ER_OPEN;
@@ -148,6 +181,9 @@
 	return 0;
 	
     case ZIP_SOURCE_READ:
+	if (z->source_closed)
+	    return -1;
+
 	if (z->remain != -1)
 	    n = len > (zip_uint64_t)z->remain ? (zip_uint64_t)z->remain : len;
 	else
@@ -179,6 +215,9 @@
 	return (zip_int64_t)i;
 	
     case ZIP_SOURCE_CLOSE:
+	if (z->source_closed)
+	    return -1;
+
 	if (z->fname) {
 	    fclose(z->f);
 	    z->f = NULL;
@@ -186,6 +225,9 @@
 	return 0;
 
     case ZIP_SOURCE_STAT:
+	if (z->source_closed)
+	    return -1;
+
         {
 	    if (len < sizeof(z->st))
 		return -1;
@@ -233,6 +275,9 @@
 	return sizeof(int)*2;
 
     case ZIP_SOURCE_FREE:
+	if (z->source_archive && !z->source_closed) {
+	    _zip_deregister_source(z->source_archive, state);
+	}
 	free(z->fname);
 	if (z->closep && z->f)
 	    fclose(z->f);
@@ -245,3 +290,41 @@
 
     return -1;
 }
+
+
+static void
+_zip_deregister_source(struct zip *za, void *ud)
+{
+    int i;
+
+    for (i=0; i<za->nsource; i++) {
+	if (za->source[i]->ud == ud) {
+	    za->source[i] = za->source[za->nsource-1];
+	    za->nsource--;
+	    break;
+	}
+    }
+}
+
+
+static int
+_zip_register_source(struct zip *za, struct zip_source *src)
+{
+    struct zip_source **source;
+
+    if (za->nsource+1 >= za->nsource_alloc) {
+	unsigned int n;
+	n = za->nsource_alloc + 10;
+	source = (struct zip_source **)realloc(za->source, n*sizeof(struct zip_source *));
+	if (source == NULL) {
+	    _zip_error_set(&za->error, ZIP_ER_MEMORY, 0);
+	    return -1;
+	}
+	za->nsource_alloc = n;
+	za->source = source;
+    }
+
+    za->source[za->nsource++] = src;
+
+    return 0;
+}
diff --git a/lib/zip_source_zip_new.c b/lib/zip_source_zip_new.c
index 70ac4e8..3f4fbd2 100644
--- a/lib/zip_source_zip_new.c
+++ b/lib/zip_source_zip_new.c
@@ -1,6 +1,6 @@
 /*
   zip_source_zip_new.c -- prepare data structures for zip_fopen/zip_source_zip
-  Copyright (C) 2012 Dieter Baron and Thomas Klausner
+  Copyright (C) 2012-2014 Dieter Baron and Thomas Klausner
 
   This file is part of libzip, a library to manipulate ZIP archives.
   The authors can be contacted at <libzip@nih.at>
@@ -129,6 +129,11 @@
 		return NULL;
 	}
 	
+	if (_zip_source_filep_set_source_archive(src, srcza) < 0) {
+	    zip_source_free(src);
+	    return NULL;
+	}
+	
 	if (enc_impl) {
 	    if ((s2=enc_impl(za, src, st.encryption_method, 0, password)) == NULL) {
 		zip_source_free(src);
diff --git a/lib/zipint.h b/lib/zipint.h
index c3852b5..6f13361 100644
--- a/lib/zipint.h
+++ b/lib/zipint.h
@@ -3,7 +3,7 @@
 
 /*
   zipint.h -- internal declarations.
-  Copyright (C) 1999-2013 Dieter Baron and Thomas Klausner
+  Copyright (C) 1999-2014 Dieter Baron and Thomas Klausner
 
   This file is part of libzip, a library to manipulate ZIP archives.
   The authors can be contacted at <libzip@nih.at>
@@ -284,9 +284,9 @@
     zip_uint64_t nentry_alloc;		/* number of entries allocated */
     struct zip_entry *entry;		/* entries */
 
-    unsigned int nfile;			/* number of opened files within archive */
-    unsigned int nfile_alloc;		/* number of files allocated */
-    struct zip_file **file;		/* opened files within archive */
+    unsigned int nsource;		/* number of open sources using archive */
+    unsigned int nsource_alloc;		/* number of sources allocated */
+    struct zip_source **source;		/* open sources using archive */
     
     char *tempdir;                      /* custom temp dir (needed e.g. for OS X sandboxing) */
 };
@@ -458,19 +458,17 @@
 struct zip_dirent *_zip_get_dirent(struct zip *, zip_uint64_t, zip_flags_t, struct zip_error *);
 
 enum zip_encoding_type _zip_guess_encoding(struct zip_string *, enum zip_encoding_type);
-zip_uint8_t *_zip_cp437_to_utf8(const zip_uint8_t * const, zip_uint32_t,
-				zip_uint32_t *, struct zip_error *error);
+zip_uint8_t *_zip_cp437_to_utf8(const zip_uint8_t * const, zip_uint32_t, zip_uint32_t *, struct zip_error *);
 
 struct zip *_zip_open(const char *, FILE *, unsigned int, int *);
 
 int _zip_read_local_ef(struct zip *, zip_uint64_t);
 
-struct zip_source *_zip_source_file_or_p(struct zip *, const char *, FILE *,
-					 zip_uint64_t, zip_int64_t, int,
-					 const struct zip_stat *);
+struct zip_source *_zip_source_file_or_p(struct zip *, const char *, FILE *, zip_uint64_t, zip_int64_t, int, const struct zip_stat *);
+void _zip_source_filep_invalidate(struct zip_source *);
+int _zip_source_filep_set_source_archive(struct zip_source *, struct zip *);
 struct zip_source *_zip_source_new(struct zip *);
-struct zip_source *_zip_source_zip_new(struct zip *, struct zip *, zip_uint64_t, zip_flags_t,
-				       zip_uint64_t, zip_uint64_t, const char *);
+struct zip_source *_zip_source_zip_new(struct zip *, struct zip *, zip_uint64_t, zip_flags_t, zip_uint64_t, zip_uint64_t, const char *);
 
 int _zip_string_equal(const struct zip_string *, const struct zip_string *);
 void _zip_string_free(struct zip_string *);
diff --git a/regress/Makefile.am b/regress/Makefile.am
index 29e2b5b..3630c53 100644
--- a/regress/Makefile.am
+++ b/regress/Makefile.am
@@ -20,6 +20,7 @@
 	runtest \
 	${TESTS} \
 	bigzero-zip.zip \
+	bogus.zip \
 	broken.zip \
 	encrypt.zip \
 	encrypt_plus_extra.zip \
@@ -113,6 +114,7 @@
 	add_from_file_twice_duplicate.test \
 	add_from_filep.test \
 	add_from_stdin.test \
+	add_from_zip_closed.test \
 	add_from_zip_deflated.test \
 	add_from_zip_partial_deflated.test \
 	add_from_zip_partial_stored.test \
diff --git a/regress/add_from_zip_closed.test b/regress/add_from_zip_closed.test
new file mode 100644
index 0000000..25507d9
--- /dev/null
+++ b/regress/add_from_zip_closed.test
@@ -0,0 +1,6 @@
+# add deflated file from zip to zip, but close the source before it can be read
+program modify
+return 1
+args -- testfile.zip   add_from_zip abac-repeat.txt testdeflated.zzip 0 0 -1 zin_close
+file testdeflated.zzip testdeflated.zip testdeflated.zip
+stderr can't close zip archive 'testfile.zip': Containing zip archive was closed
diff --git a/regress/modify.c b/regress/modify.c
index 9a70bca..5a0b0d2 100644
--- a/regress/modify.c
+++ b/regress/modify.c
@@ -72,6 +72,7 @@
     "\tset_extra index extra_id extra_index flags value\n"
     "\tset_file_comment index comment\n"
     "\tset_file_compression index method flags\n"
+    "\tzin_close\n"
     "\nThe index is zero-based.\n";
 
 static zip_flags_t
@@ -193,10 +194,18 @@
 	    arg += 2;
 	} else if (strcmp(argv[arg], "add_file") == 0 && arg+4 < argc) {
 	    /* add */
-    	    if ((zs=zip_source_file(za, argv[arg+2], atoi(argv[arg+3]), atoi(argv[arg+4]))) == NULL) {
-		fprintf(stderr, "can't create zip_source from file: %s\n", zip_strerror(za));
-		err = 1;
-		break;
+	    if (strcmp(argv[arg+2], "/dev/stdin") == 0) {
+		if ((zs=zip_source_filep(za, stdin, atoi(argv[arg+3]), atoi(argv[arg+4]))) == NULL) {
+		    fprintf(stderr, "can't create zip_source from stdin: %s\n", zip_strerror(za));
+		    err = 1;
+		    break;
+		}
+	    } else {
+		if ((zs=zip_source_file(za, argv[arg+2], atoi(argv[arg+3]), atoi(argv[arg+4]))) == NULL) {
+		    fprintf(stderr, "can't create zip_source from file: %s\n", zip_strerror(za));
+		    err = 1;
+		    break;
+		}
 	    }
 
 	    if (zip_add(za, argv[arg+1], zs) == -1) {
@@ -399,6 +408,13 @@
 		break;
             }
             arg += 4;
+        } else if (strcmp(argv[arg], "zin_close") == 0) {
+	    if (zip_close(z_in) < 0) {
+		fprintf(stderr, "can't close source archive: %s\n", zip_strerror(z_in));
+		err = 1;
+		break;
+	    }
+	    arg += 1;
 	} else {
 	    fprintf(stderr, "unrecognized command '%s', or not enough arguments\n", argv[arg]);
 	    err = 1;
diff --git a/src/Makefile.am b/src/Makefile.am
index 58d53ec..d1ca9b9 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -1,6 +1,6 @@
 bin_PROGRAMS=zipcmp zipmerge ziptorrent
 
-zipcmp_SOURCES=zipcmp.c dir.c parray.c parray_push.c parray_pop.c
+zipcmp_SOURCES=zipcmp.c
 
 zipcmp_CPPFLAGS=-I${top_srcdir}/lib -I../lib
 zipcmp_LDADD=${top_builddir}/lib/libzip.la
@@ -9,4 +9,4 @@
 ziptorrent_CPPFLAGS=-I${top_srcdir}/lib -I../lib
 ziptorrent_LDADD=${top_builddir}/lib/libzip.la
 
-EXTRA_DIST=	CMakeLists.txt getopt.c getopt.h compat.h dir.h parray.h
+EXTRA_DIST=	CMakeLists.txt getopt.c getopt.h compat.h
diff --git a/src/dir.c b/src/dir.c
deleted file mode 100644
index 0437de5..0000000
--- a/src/dir.c
+++ /dev/null
@@ -1,197 +0,0 @@
-/*
-  dir.h -- reading a directory
-  Copyright (C) 2005-2014 Dieter Baron and Thomas Klausner
-
-  This file is part of ckmame, a program to check rom sets for MAME.
-  The authors can be contacted at <ckmame@nih.at>
-
-  Redistribution and use in source and binary forms, with or without
-  modification, are permitted provided that the following conditions
-  are met:
-  1. Redistributions of source code must retain the above copyright
-     notice, this list of conditions and the following disclaimer.
-  2. Redistributions in binary form must reproduce the above copyright
-     notice, this list of conditions and the following disclaimer in
-     the documentation and/or other materials provided with the
-     distribution.
-  3. The name of the author may not be used to endorse or promote
-     products derived from this software without specific prior
-     written permission.
- 
-  THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS
-  OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
-  WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
-  ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY
-  DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
-  DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
-  GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
-  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
-  IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
-  OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
-  IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-*/
-
-#include <sys/stat.h>
-#include <errno.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-
-/* copied from autoconf manual (AC_HEADER_DIRENT) */
-
-#include "config.h"
-
-#ifdef HAVE_DIRENT_H
-# include <dirent.h>
-# define NAMLEN(dirent) strlen((dirent)->d_name)
-#else
-# define dirent direct
-# define NAMLEN(dirent) (dirent)->d_namlen
-# ifdef HAVE_SYS_NDIR_H
-#  include <sys/ndir.h>
-# endif
-# ifdef HAVE_SYS_DIR_H
-#  include <sys/dir.h>
-# endif
-# ifdef HAVE_NDIR_H
-#  include <ndir.h>
-# endif
-#endif
-
-#include "dir.h"
-
-struct dir_one {
-    DIR *dir;
-    char *name;
-    int len;
-};
-
-typedef struct dir_one dir_one_t;
-
-static int dir_one_free(dir_one_t *);
-static dir_one_t *dir_one_new(const char *);
-
-
-int
-dir_close(dir_t *dir)
-{
-    dir_one_t *d;
-    int ret;
-
-    ret = 0;
-    while ((d=parray_pop(dir->stack)) != NULL)
-	ret |= dir_one_free(d);
-    
-    parray_free(dir->stack, (void (*)())dir_one_free);
-    free(dir);
-
-    return ret;
-}
-
-
-dir_status_t
-dir_next(dir_t *dir, char *name, int len)
-{
-    struct dirent *de;
-    dir_one_t *d;
-    int l;
-    struct stat st;
-
-    d = parray_get_last(dir->stack);
-    for (;;) {
-	if ((de=readdir(d->dir)) == NULL) {
-	    dir_one_free(d);
-	    parray_pop(dir->stack);
-	    if ((d=parray_get_last(dir->stack)) == NULL)
-		return DIR_EOD;
-	    continue;
-	}
-	
-	l = NAMLEN(de);
-
-	if ((l == 1 && strncmp(de->d_name, ".", 1) == 0)
-	    || (l == 2 && strncmp(de->d_name, "..", 2) == 0))
-	    continue;
-	    
-	if (d->len + l + 2 > len) {
-	    errno = ENAMETOOLONG;
-	    return DIR_ERROR;
-	}
-	
-	sprintf(name, "%s/%*s", d->name, l, de->d_name);
-
-	if (dir->flags & DIR_RECURSE) {
-
-	    if (stat(name, &st) < 0)
-		return DIR_ERROR;
-
-	    if ((st.st_mode & S_IFMT) == S_IFDIR) {
-		if ((d=dir_one_new(name)) == NULL)
-		    return DIR_ERROR;
-
-		parray_push(dir->stack, d);
-		continue;
-	    }
-	}
-	return DIR_OK;
-    }
-}
-
-
-dir_t *
-dir_open(const char *name, int flags)
-{
-    dir_one_t *d;
-    dir_t *dir;
-
-    if ((d=dir_one_new(name)) == NULL)
-	return NULL;
-
-    if ((dir=(dir_t *)malloc(sizeof(*dir))) == NULL)
-	return NULL;
-
-    dir->stack = parray_new();
-    parray_push(dir->stack, d);
-    dir->flags = flags;
-
-    return dir;
-}
-
-
-static int
-dir_one_free(dir_one_t *d)
-{
-    int ret;
-
-    if (d == NULL)
-	return 0;
-    
-    ret = closedir(d->dir);
-    free(d->name);
-    free(d);
-
-    return ret;
-}
-
-
-static dir_one_t *
-dir_one_new(const char *name)
-{
-    DIR *dir;
-    dir_one_t *d;
-
-    if ((dir=opendir(name)) == NULL)
-	return NULL;
-
-    if ((d=(dir_one_t *)malloc(sizeof(*d))) == NULL)
-	return NULL;
-    d->dir = dir;
-    if ((d->name=strdup(name)) == NULL) {
-	free(d);
-	return NULL;
-    }
-    
-    d->len = strlen(name);
-
-    return d;
-}
diff --git a/src/dir.h b/src/dir.h
deleted file mode 100644
index ae2fbe2..0000000
--- a/src/dir.h
+++ /dev/null
@@ -1,61 +0,0 @@
-#ifndef HAD_DIR_H
-#define HAD_DIR_H
-
-/*
-  dir.h -- reading a directory
-  Copyright (C) 2005-2014 Dieter Baron and Thomas Klausner
-
-  This file is part of ckmame, a program to check rom sets for MAME.
-  The authors can be contacted at <ckmame@nih.at>
-
-  Redistribution and use in source and binary forms, with or without
-  modification, are permitted provided that the following conditions
-  are met:
-  1. Redistributions of source code must retain the above copyright
-     notice, this list of conditions and the following disclaimer.
-  2. Redistributions in binary form must reproduce the above copyright
-     notice, this list of conditions and the following disclaimer in
-     the documentation and/or other materials provided with the
-     distribution.
-  3. The name of the author may not be used to endorse or promote
-     products derived from this software without specific prior
-     written permission.
- 
-  THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS
-  OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
-  WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
-  ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY
-  DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
-  DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
-  GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
-  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
-  IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
-  OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
-  IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-*/
-
-
-#include "parray.h"
-
-#define DIR_RECURSE	1
-
-struct dir {
-    int flags;
-    parray_t *stack;
-};
-
-typedef struct dir dir_t;
-
-enum dir_status {
-    DIR_ERROR = -1,
-    DIR_OK,
-    DIR_EOD
-};
-
-typedef enum dir_status dir_status_t;
-
-int dir_close(dir_t *);
-dir_status_t dir_next(dir_t *, char *, int);
-dir_t *dir_open(const char *, int);
-
-#endif /* dir.h */
diff --git a/src/parray.c b/src/parray.c
deleted file mode 100644
index ac132eb..0000000
--- a/src/parray.c
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
-  parray.c -- create / free array of pointers
-  Copyright (C) 2005-2014 Dieter Baron and Thomas Klausner
-
-  This file is part of ckmame, a program to check rom sets for MAME.
-  The authors can be contacted at <ckmame@nih.at>
-
-  Redistribution and use in source and binary forms, with or without
-  modification, are permitted provided that the following conditions
-  are met:
-  1. Redistributions of source code must retain the above copyright
-     notice, this list of conditions and the following disclaimer.
-  2. Redistributions in binary form must reproduce the above copyright
-     notice, this list of conditions and the following disclaimer in
-     the documentation and/or other materials provided with the
-     distribution.
-  3. The name of the author may not be used to endorse or promote
-     products derived from this software without specific prior
-     written permission.
- 
-  THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS
-  OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
-  WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
-  ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY
-  DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
-  DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
-  GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
-  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
-  IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
-  OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
-  IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-*/
-
-#include <stdlib.h>
-
-#include "parray.h"
-
-void
-parray_free(parray_t *pa, void (*fn)(void *))
-{
-    int i;
-    
-    if (pa == NULL)
-	return;
-
-    if (fn) {
-	for (i=0; i<parray_length(pa); i++)
-	    fn(parray_get(pa, i));
-    }
-
-    free(pa->entry);
-    free(pa);
-}
-
-parray_t *
-parray_new_sized(int n)
-{
-    parray_t *pa;
-
-    if (n<0)
-	n = 0;
-
-    if ((pa=malloc(sizeof(*pa))) == NULL)
-	exit(1);
-
-    if (n == 0)
-	pa->entry = 0;
-    else {
-	if ((pa->entry=malloc(n*sizeof(pa->entry[0]))) == NULL)
-	    exit(1);
-    }
-    pa->nentry = 0;
-    pa->alloc_len = n;
-
-    return pa;
-}
diff --git a/src/parray.h b/src/parray.h
deleted file mode 100644
index d0e26f1..0000000
--- a/src/parray.h
+++ /dev/null
@@ -1,57 +0,0 @@
-#ifndef _HAD_PARRAY_H
-#define _HAD_PARRAY_H
-
-/*
-  parray.h -- array of pointers
-  Copyright (C) 2005-2014 Dieter Baron and Thomas Klausner
-
-  This file is part of ckmame, a program to check rom sets for MAME.
-  The authors can be contacted at <ckmame@nih.at>
-
-  Redistribution and use in source and binary forms, with or without
-  modification, are permitted provided that the following conditions
-  are met:
-  1. Redistributions of source code must retain the above copyright
-     notice, this list of conditions and the following disclaimer.
-  2. Redistributions in binary form must reproduce the above copyright
-     notice, this list of conditions and the following disclaimer in
-     the documentation and/or other materials provided with the
-     distribution.
-  3. The name of the author may not be used to endorse or promote
-     products derived from this software without specific prior
-     written permission.
- 
-  THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS
-  OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
-  WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
-  ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY
-  DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
-  DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
-  GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
-  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
-  IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
-  OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
-  IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-*/
-
-struct parray {
-    void **entry;
-    int nentry;
-    int alloc_len;
-};
-
-typedef struct parray parray_t;
-
-#define parray_get(a, i)	((a)->entry[i])
-#define parray_get_last(a)	\
-	(parray_length(a) ? parray_get((a), parray_length(a)-1) : NULL)
-#define parray_length(a)	((a)->nentry)
-#define parray_new()		(parray_new_sized(0))
-
-/* function arguments not specified to avoid lots of casts */
-void parray_free(parray_t *, void (*)(/* void * */));
-parray_t *parray_new_sized(int);
-void *parray_pop(parray_t *);
-void parray_push(parray_t *, void *);
-
-#endif /* parray.h */
diff --git a/src/parray_pop.c b/src/parray_pop.c
deleted file mode 100644
index 108b693..0000000
--- a/src/parray_pop.c
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
-  parray_pop.c -- delete element from array of pointers and return it
-  Copyright (C) 2005-2014 Dieter Baron and Thomas Klausner
-
-  This file is part of ckmame, a program to check rom sets for MAME.
-  The authors can be contacted at <ckmame@nih.at>
-
-  Redistribution and use in source and binary forms, with or without
-  modification, are permitted provided that the following conditions
-  are met:
-  1. Redistributions of source code must retain the above copyright
-     notice, this list of conditions and the following disclaimer.
-  2. Redistributions in binary form must reproduce the above copyright
-     notice, this list of conditions and the following disclaimer in
-     the documentation and/or other materials provided with the
-     distribution.
-  3. The name of the author may not be used to endorse or promote
-     products derived from this software without specific prior
-     written permission.
- 
-  THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS
-  OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
-  WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
-  ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY
-  DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
-  DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
-  GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
-  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
-  IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
-  OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
-  IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-*/
-
-#include <string.h>
-
-#include "parray.h"
-
-void *
-parray_pop(parray_t *pa)
-{
-    if (parray_length(pa) == 0)
-	return NULL;
-
-    return (parray_get(pa, --pa->nentry));
-}
diff --git a/src/parray_push.c b/src/parray_push.c
deleted file mode 100644
index fb47e28..0000000
--- a/src/parray_push.c
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
-  parray_push.c -- append element to end of array of pointers
-  Copyright (C) 2005-2014 Dieter Baron and Thomas Klausner
-
-  This file is part of ckmame, a program to check rom sets for MAME.
-  The authors can be contacted at <ckmame@nih.at>
-
-  Redistribution and use in source and binary forms, with or without
-  modification, are permitted provided that the following conditions
-  are met:
-  1. Redistributions of source code must retain the above copyright
-     notice, this list of conditions and the following disclaimer.
-  2. Redistributions in binary form must reproduce the above copyright
-     notice, this list of conditions and the following disclaimer in
-     the documentation and/or other materials provided with the
-     distribution.
-  3. The name of the author may not be used to endorse or promote
-     products derived from this software without specific prior
-     written permission.
- 
-  THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS
-  OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
-  WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
-  ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY
-  DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
-  DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
-  GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
-  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
-  IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
-  OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
-  IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-*/
-
-#include <stdlib.h>
-
-#include "parray.h"
-
-void
-parray_push(parray_t *pa, void *e)
-{
-    if (pa->nentry >= pa->alloc_len) {
-	void **tmp;
-	if (pa->alloc_len == 0)
-	    pa->alloc_len = 1;
-	else
-	    pa->alloc_len *= 2;
-	if ((tmp=realloc(pa->entry, sizeof(pa->entry[0])*pa->alloc_len)) == NULL)
-	    exit(1);
-	pa->entry = tmp;
-    }
-
-    pa->entry[pa->nentry++] = e;
-}
diff --git a/src/zipcmp.c b/src/zipcmp.c
index 2411e4d..ead0abd 100644
--- a/src/zipcmp.c
+++ b/src/zipcmp.c
@@ -45,6 +45,9 @@
 #ifdef HAVE_UNISTD_H
 #include <unistd.h>
 #endif
+#ifdef HAVE_FTS_H
+#include <fts.h>
+#endif
 #include <zlib.h>
 
 #ifndef HAVE_GETOPT
@@ -56,8 +59,6 @@
 #include "zip.h"
 #include "compat.h"
 
-#include "dir.h"
-
 struct archive {
     const char *name;
     struct zip *za;
@@ -131,7 +132,9 @@
 static int entry_paranoia_checks(char *const name[2], const void *p1, const void *p2);
 static void entry_print(const void *p);
 static int is_directory(const char *name);
+#ifdef HAVE_FTS_H
 static int list_directory(const char *name, struct archive *a);
+#endif
 static int list_zip(const char *name, struct archive *a);
 static int test_file(struct zip *za, int idx, zip_int64_t size, unsigned int crc);
 
@@ -211,9 +214,14 @@
 	a[i].comment_length =0;
 
 	if (is_directory(zn[i])) {
+#ifndef HAVE_FTS_H
+	    fprintf(stderr, "%s: reading directories not supported\n", prg);
+	    exit(2);
+#else
 	    if (list_directory(zn[i], a+i) < 0)
 		exit(2);
 	    paranoid = 0; /* paranoid checks make no sense for directories, since they compare zip metadata */
+#endif
 	}
 	else {
 	    if (list_zip(zn[i], a+i) < 0)
@@ -267,7 +275,7 @@
 
 
     if ((f=fopen(fname, "r")) == NULL) {
-	fprintf(stderr, "%s: can't open %s: %s", prg, fname, strerror(errno));
+	fprintf(stderr, "%s: can't open %s: %s\n", prg, fname, strerror(errno));
 	return -1;
     }
 
@@ -276,7 +284,7 @@
     }
 
     if (ferror(f)) {
-	fprintf(stderr, "%s: read error on %s: %s", prg, fname, strerror(errno));
+	fprintf(stderr, "%s: read error on %s: %s\n", prg, fname, strerror(errno));
 	fclose(f);
 	return -1;
     }
@@ -299,64 +307,77 @@
 }
 
 
+#ifdef HAVE_FTS_H
 static int
 list_directory(const char *name, struct archive *a)
 {
-    char namebuf[8192];
-    dir_t *dir;
-    dir_status_t status;
+    FTS *fts;
+    FTSENT *ent;
     zip_uint64_t nalloc;
 
-    if ((dir=dir_open(name, DIR_RECURSE)) == NULL) {
-	fprintf(stderr, "%s: can't open directory '%s': %s", prg, name, strerror(errno));
+    char * const names[2] = { (char *)name, NULL };
+
+
+    if ((fts = fts_open(names, FTS_NOCHDIR|FTS_LOGICAL, NULL)) == NULL) {
+	fprintf(stderr, "%s: can't open directory '%s': %s\n", prg, name, strerror(errno));
 	return -1;
     }
+    size_t prefix_length = strlen(name)+1;
 
     nalloc = 0;
-    while ((status=dir_next(dir, namebuf, sizeof(namebuf))) == DIR_OK) {
-	struct stat sb;
+
+    while ((ent = fts_read(fts))) {
 	zip_int64_t crc;
 
-	if (stat(namebuf, &sb) < 0) {
-	    fprintf(stderr, "%s: can't stat '%s': %s", prg, namebuf, strerror(errno));
-	    dir_close(dir);
-	    return -1;
-	}
+	switch (ent->fts_info) {
+	case FTS_D:
+	case FTS_DOT:
+	case FTS_DP:
+	case FTS_DEFAULT:
+	case FTS_SL:
+	case FTS_NSOK:
+	    break;
 
-	if (S_ISREG(sb.st_mode)) {
+	case FTS_DC:
+	case FTS_DNR:
+	case FTS_ERR:
+	case FTS_NS:
+	case FTS_SLNONE:
+	    /* TODO: error */
+	    fts_close(fts);
+	    return -1;
+
+	case FTS_F:
 	    if (a->nentry >= nalloc) {
 		nalloc += 16;
 		a->entry = realloc(a->entry, sizeof(a->entry[0])*nalloc);
 		if (a->entry == NULL) {
-		    fprintf(stderr, "%s: malloc failure", prg);
+		    fprintf(stderr, "%s: malloc failure\n", prg);
 		    exit(1);
 		}
 	    }
-
-	    a->entry[a->nentry].name = strdup(namebuf+strlen(a->name)+1);
-	    a->entry[a->nentry].size = sb.st_size;
-	    if ((crc = compute_crc(namebuf)) < 0) {
-		dir_close(dir);
+	    
+	    a->entry[a->nentry].name = strdup(ent->fts_path+prefix_length);
+	    a->entry[a->nentry].size = ent->fts_statp->st_size;
+	    if ((crc = compute_crc(ent->fts_accpath)) < 0) {
+		fts_close(fts);
 		return -1;
 	    }
-
+	    
 	    a->entry[a->nentry].crc = (zip_uint32_t)crc;
 	    a->nentry++;
+	    break;
 	}
     }
 
-    if (status != DIR_EOD) {
-	fprintf(stderr, "%s: error reading directory '%s': %s", prg, a->name, strerror(errno));
-	dir_close(dir);
-	return -1;
-    }
-    if (dir_close(dir) < 0) {
-	fprintf(stderr, "%s: error closing directory '%s': %s", prg, a->name, strerror(errno));
+    if (fts_close(fts)) {
+	fprintf(stderr, "%s: error closing directory '%s': %s\n", prg, a->name, strerror(errno));
 	return -1;
     }
 
     return 0;
 }
+#endif
 
 
 static int