]> git.ipfire.org Git - thirdparty/libarchive.git/commitdiff
Introduce archive_matching APIs. This is a set of utility functions to
authorMichihiro NAKAJIMA <ggcueroad@gmail.com>
Tue, 17 Jan 2012 15:18:15 +0000 (10:18 -0500)
committerMichihiro NAKAJIMA <ggcueroad@gmail.com>
Tue, 17 Jan 2012 15:18:15 +0000 (10:18 -0500)
find matched archive_entry objects. This is based on libarchive_fe/matching.[ch]
I added serveral interfaces for wchar_t, and matching file stamps and owners.

I also plan to use that at archive_read_disk instead of
archive_read_disk_set_name_filter_callback function, which I recently added.

SVN-Revision: 4162

12 files changed:
Makefile.am
libarchive/CMakeLists.txt
libarchive/archive.h
libarchive/archive_check_magic.c
libarchive/archive_matching.c [new file with mode: 0644]
libarchive/archive_pathmatch.c [new file with mode: 0644]
libarchive/archive_pathmatch.h [new file with mode: 0644]
libarchive/archive_private.h
libarchive/test/CMakeLists.txt
libarchive/test/test_archive_matching_owner.c [new file with mode: 0644]
libarchive/test/test_archive_matching_path.c [new file with mode: 0644]
libarchive/test/test_archive_matching_time.c [new file with mode: 0644]

index 98884861a79f25dffc29701ecdbfd030f14ea657..e281901f5f033cbfbee5df4b8343d77ce469548a 100644 (file)
@@ -102,8 +102,11 @@ libarchive_la_SOURCES=                                             \
        libarchive/archive_entry_stat.c                         \
        libarchive/archive_entry_strmode.c                      \
        libarchive/archive_entry_xattr.c                        \
+       libarchive/archive_matching.c                           \
        libarchive/archive_options.c                            \
        libarchive/archive_options_private.h                    \
+       libarchive/archive_pathmatch.c                          \
+       libarchive/archive_pathmatch.h                          \
        libarchive/archive_platform.h                           \
        libarchive/archive_ppmd_private.h                       \
        libarchive/archive_ppmd7.c                              \
@@ -255,6 +258,9 @@ libarchive_test_SOURCES=                                    \
        libarchive/test/test_archive_api_feature.c              \
        libarchive/test/test_archive_clear_error.c              \
        libarchive/test/test_archive_crypto.c                   \
+       libarchive/test/test_archive_matching_owner.c           \
+       libarchive/test/test_archive_matching_path.c            \
+       libarchive/test/test_archive_matching_time.c            \
        libarchive/test/test_archive_read_close_twice.c         \
        libarchive/test/test_archive_read_close_twice_open_fd.c \
        libarchive/test/test_archive_read_close_twice_open_filename.c   \
index a801fb2888f5ca80666b2691efe4e7d864842237..2ffd55e68f65444fc23886037ff3b83cb75f6063 100644 (file)
@@ -28,8 +28,11 @@ SET(libarchive_SOURCES
   archive_entry_stat.c
   archive_entry_strmode.c
   archive_entry_xattr.c
+  archive_matching.c
   archive_options.c
   archive_options_private.h
+  archive_pathmatch.c
+  archive_pathmatch.h
   archive_platform.h
   archive_ppmd_private.h
   archive_ppmd7.c
index 4b899fe39baab1ce8b2b2590e28be539e9fa100b..7d7c493ba1f0377caa8cacd3152a9b9ffec1c2b3 100644 (file)
@@ -803,6 +803,114 @@ __LA_DECL void             archive_copy_error(struct archive *dest,
                            struct archive *src);
 __LA_DECL int           archive_file_count(struct archive *);
 
+/*
+ * ARCHIVE_MATCHING API
+ */
+__LA_DECL struct archive *archive_matching_new(void);
+__LA_DECL int  archive_matching_free(struct archive *);
+
+/*
+ * Test if archive_entry is excluded.
+ * This is a convenience function. This is the same as calling all
+ * archive_matching_path_excluded_ae, archive_matching_time_excluded_ae
+ * and archive_matching_owner_excluded_ae.
+ */
+__LA_DECL int  archive_matching_excluded_ae(struct archive *,
+                   struct archive_entry *);
+
+/*
+ * Test if filename is excluded. The conditions are set by following functions.
+ */
+__LA_DECL int  archive_matching_path_excluded(struct archive *,
+                   const char *_filename);
+__LA_DECL int  archive_matching_path_excluded_w(struct archive *,
+                   const wchar_t *_filename);
+__LA_DECL int  archive_matching_path_excluded_ae(struct archive *,
+                   struct archive_entry *);
+/* Add exclusion fielname pattern. */
+__LA_DECL int  archive_matching_exclude_pattern(struct archive *,
+                   const char *);
+__LA_DECL int  archive_matching_exclude_pattern_w(struct archive *,
+                   const wchar_t *);
+/* Add inclusion fielname pattern. */
+__LA_DECL int  archive_matching_include_pattern(struct archive *,
+                   const char *);
+__LA_DECL int  archive_matching_include_pattern_w(struct archive *,
+                   const wchar_t *);
+/*
+ * How to get statistic information for inclusion patterns.
+ */
+/* Return the amount number of unmatched inclusion patterns. */
+__LA_DECL int  archive_matching_path_unmatched_inclusions(
+                   struct archive *);
+/* Return the pattern of unmatched inclusion with ARCHIVE_OK.
+ * Return ARCHIVE_EOF if there is no inclusion pattern. */
+__LA_DECL int  archive_matching_path_unmatched_inclusions_next(
+                   struct archive *, const char **);
+__LA_DECL int  archive_matching_path_unmatched_inclusions_next_w(
+                   struct archive *, const wchar_t **);
+
+/*
+ * Test if a file is excluded by its time stamp.
+ * The conditions are set by following functions.
+ */
+__LA_DECL int  archive_matching_time_excluded_ae(struct archive *,
+                   struct archive_entry *);
+/* Set inclusion file times. */
+__LA_DECL int  archive_matching_newer_mtime(struct archive *,
+                   time_t _sec, long _nsec);
+__LA_DECL int  archive_matching_newer_mtime_than(struct archive *,
+                   const char *_pathname);
+__LA_DECL int  archive_matching_newer_mtime_than_w(struct archive *,
+                   const wchar_t *_pathname);
+__LA_DECL int  archive_matching_newer_ctime(struct archive *,
+                   time_t _sec, long _nsec);
+__LA_DECL int  archive_matching_newer_ctime_than(struct archive *,
+                   const char *_pathname);
+__LA_DECL int  archive_matching_newer_ctime_than_w(struct archive *,
+                   const wchar_t *_pathname);
+__LA_DECL int  archive_matching_older_mtime(struct archive *,
+                   time_t _sec, long _nsec);
+__LA_DECL int  archive_matching_older_mtime_than(struct archive *,
+                   const char *_pathname);
+__LA_DECL int  archive_matching_older_mtime_than_w(struct archive *,
+                   const wchar_t *_pathname);
+__LA_DECL int  archive_matching_older_ctime(struct archive *,
+                   time_t _sec, long _nsec);
+__LA_DECL int  archive_matching_older_ctime_than(struct archive *,
+                   const char *_pathname);
+__LA_DECL int  archive_matching_older_ctime_than_w(struct archive *,
+                   const wchar_t *_pathname);
+/* Add inclusion file times with its filename. */
+__LA_DECL int  archive_matching_pathname_newer_mtime(
+                   struct archive *, const char *_pathname,
+                   time_t _sec, long _nsec);
+__LA_DECL int  archive_matching_pathname_newer_mtime_w(
+                   struct archive *, const wchar_t *_pathname,
+                   time_t _sec, long _nsec);
+__LA_DECL int  archive_matching_pathname_newer_mtime_ae(
+                   struct archive *, struct archive_entry *);
+
+/*
+ * Test if a file is excluded by its uid ,gid, uname or gname.
+ * The conditions are set by following functions.
+ */
+__LA_DECL int  archive_matching_owner_excluded_ae(struct archive *,
+                   struct archive_entry *);
+/* Add inclusion uid, gid, uname and gname. */
+__LA_DECL int  archive_matching_include_uid(struct archive *,
+                   int64_t);
+__LA_DECL int  archive_matching_include_gid(struct archive *,
+                   int64_t);
+__LA_DECL int  archive_matching_include_uname(struct archive *,
+                   const char *);
+__LA_DECL int  archive_matching_include_uname_w(struct archive *,
+                   const wchar_t *);
+__LA_DECL int  archive_matching_include_gname(struct archive *,
+                   const char *);
+__LA_DECL int  archive_matching_include_gname_w(struct archive *,
+                   const wchar_t *);
+
 #ifdef __cplusplus
 }
 #endif
index 91229557a35dcf12abe654dd6c50e232e3a80853..f6f1a185641e5aa344dc40590559ff3af5ab8e71 100644 (file)
@@ -94,6 +94,7 @@ archive_handle_type_name(unsigned m)
        case ARCHIVE_READ_MAGIC:        return ("archive_read");
        case ARCHIVE_WRITE_DISK_MAGIC:  return ("archive_write_disk");
        case ARCHIVE_READ_DISK_MAGIC:   return ("archive_read_disk");
+       case ARCHIVE_MATCHING_MAGIC:    return ("archive_matching");
        default:                        return NULL;
        }
 }
diff --git a/libarchive/archive_matching.c b/libarchive/archive_matching.c
new file mode 100644 (file)
index 0000000..581aab6
--- /dev/null
@@ -0,0 +1,1642 @@
+/*-
+ * Copyright (c) 2003-2007 Tim Kientzle
+ * Copyright (c) 2012 Michihiro NAKAJIMA
+ * All rights reserved.
+ *
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``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 AUTHOR(S) 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 "archive_platform.h"
+__FBSDID("$FreeBSD$");
+
+#ifdef HAVE_ERRNO_H
+#include <errno.h>
+#endif
+#ifdef HAVE_STDLIB_H
+#include <stdlib.h>
+#endif
+#ifdef HAVE_STRING_H
+#include <string.h>
+#endif
+
+#include "archive.h"
+#include "archive_private.h"
+#include "archive_entry.h"
+#include "archive_pathmatch.h"
+#include "archive_rb.h"
+#include "archive_string.h"
+
+struct match {
+       struct match            *next;
+       int                      matches;
+       struct archive_mstring   pattern;
+};
+
+struct match_list {
+       struct match            *first;
+       struct match            **last;
+       int                      count;
+       int                      unmatched_count;
+       struct match            *unmatched_next;
+       int                      unmatched_eof;
+};
+
+struct newer_file {
+       struct archive_rb_node   node;
+       struct newer_file       *next;
+       struct archive_mstring   pathname;
+       time_t                   mtime_sec;
+       long                     mtime_nsec;
+};
+
+struct newer_file_list {
+       struct newer_file       *first;
+       struct newer_file       **last;
+       int                      count;
+};
+
+struct id_array {
+       size_t                   size;/* Allocated size */
+       size_t                   count;
+       int64_t                 *ids;
+};
+
+#define PATTERN_IS_SET         1
+#define TIME_IS_SET            2
+#define ID_IS_SET              4
+
+struct archive_matching {
+       struct archive           archive;
+
+       /* exclusion/inclusion set flag. */
+       int                      setflag;
+
+       /*
+        * Matching filename patterns.
+        */
+       struct match_list        exclusions;
+       struct match_list        inclusions;
+
+       /*
+        * Matching time stamps.
+        */
+       int                      newer_mtime_filter;
+       time_t                   newer_mtime_sec;
+       long                     newer_mtime_nsec;
+       int                      newer_ctime_filter;
+       time_t                   newer_ctime_sec;
+       long                     newer_ctime_nsec;
+       int                      older_mtime_filter;
+       time_t                   older_mtime_sec;
+       long                     older_mtime_nsec;
+       int                      older_ctime_filter;
+       time_t                   older_ctime_sec;
+       long                     older_ctime_nsec;
+       /*
+        * Matching time stamps with its filename.
+        */
+       struct archive_rb_tree   newer_tree;
+       struct newer_file_list   newer_list;
+
+       /*
+        * Matching file owners.
+        */
+       struct id_array          inclusion_uids;
+       struct id_array          inclusion_gids;
+       struct match_list        inclusion_unames;
+       struct match_list        inclusion_gnames;
+};
+
+static int     add_newer_mtime_pathname(struct archive_matching *, int,
+                   const void *, time_t sec, long nsec);
+static int     add_owner_id(struct archive_matching *, struct id_array *,
+                   int64_t);
+static int     add_owner_name(struct archive_matching *, struct match_list *,
+                   int, const void *);
+static int     add_pattern_mbs(struct archive_matching *, struct match_list *,
+                   const char *);
+static int     add_pattern_wcs(struct archive_matching *, struct match_list *,
+                   const wchar_t *);
+static int     cmp_key_mbs(const struct archive_rb_node *, const void *);
+static int     cmp_key_wcs(const struct archive_rb_node *, const void *);
+static int     cmp_node_mbs(const struct archive_rb_node *,
+                   const struct archive_rb_node *);
+static int     cmp_node_wcs(const struct archive_rb_node *,
+                   const struct archive_rb_node *);
+static int     error_nomem(struct archive_matching *);
+static int     get_filetime_mbs(struct archive_matching *, const char *,
+                   int, time_t *, long *);
+static int     get_filetime_wcs(struct archive_matching *, const wchar_t *,
+                   int, time_t *, long *);
+static void    match_list_add(struct match_list *, struct match *);
+static void    match_list_free(struct match_list *);
+static void    match_list_init(struct match_list *);
+static int     match_list_unmatched_inclusions_next(struct archive_matching *,
+                   struct match_list *, int, const void **);
+static int     match_owner_id(struct id_array *, int64_t);
+#if !defined(_WIN32) || defined(__CYGWIN__)
+static int     match_owner_name_mbs(struct archive_matching *,
+                   struct match_list *, const char *);
+#else
+static int     match_owner_name_wcs(struct archive_matching *,
+                   struct match_list *, const wchar_t *);
+#endif
+static int     match_path_exclusion(struct archive_matching *,
+                   struct match *, int, const void *);
+static int     match_path_inclusion(struct archive_matching *,
+                   struct match *, int, const void *);
+static void    newer_file_list_add(struct newer_file_list *,
+                   struct newer_file *);
+static void    newer_file_list_free(struct newer_file_list *);
+static void    newer_file_list_init(struct newer_file_list *);
+static int     owner_excluded(struct archive_matching *,
+                   struct archive_entry *);
+static int     path_excluded(struct archive_matching *, int, const void *);
+static int     time_excluded(struct archive_matching *,
+                   struct archive_entry *);
+
+static const struct archive_rb_tree_ops rb_ops_mbs = {
+       cmp_node_mbs, cmp_key_mbs
+};
+
+static const struct archive_rb_tree_ops rb_ops_wcs = {
+       cmp_node_wcs, cmp_key_wcs
+};
+
+/*
+ * The matching logic here needs to be re-thought.  I started out to
+ * try to mimic gtar's matching logic, but it's not entirely
+ * consistent.  In particular 'tar -t' and 'tar -x' interpret patterns
+ * on the command line as anchored, but --exclude doesn't.
+ */
+
+static int
+error_nomem(struct archive_matching *a)
+{
+       archive_set_error(&(a->archive), ENOMEM, "No memory");
+       a->archive.state = ARCHIVE_STATE_FATAL;
+       return (ARCHIVE_FATAL);
+}
+
+struct archive *
+archive_matching_new(void)
+{
+       struct archive_matching *a;
+
+       a = (struct archive_matching *)calloc(1, sizeof(*a));
+       if (a == NULL)
+               return (NULL);
+       a->archive.magic = ARCHIVE_MATCHING_MAGIC;
+       a->archive.state = ARCHIVE_STATE_NEW;
+       match_list_init(&(a->inclusions));
+       match_list_init(&(a->exclusions));
+       __archive_rb_tree_init(&(a->newer_tree), &rb_ops_mbs);
+       newer_file_list_init(&(a->newer_list));
+       match_list_init(&(a->inclusion_unames));
+       match_list_init(&(a->inclusion_gnames));
+       return (&(a->archive));
+}
+
+int
+archive_matching_free(struct archive *_a)
+{
+       struct archive_matching *a;
+
+       if (_a == NULL)
+               return (ARCHIVE_OK);
+       archive_check_magic(_a, ARCHIVE_MATCHING_MAGIC,
+           ARCHIVE_STATE_ANY | ARCHIVE_STATE_FATAL, "archive_matching_free");
+       a = (struct archive_matching *)_a;
+       match_list_free(&(a->inclusions));
+       match_list_free(&(a->exclusions));
+       newer_file_list_free(&(a->newer_list));
+       free(a->inclusion_uids.ids);
+       free(a->inclusion_gids.ids);
+       match_list_free(&(a->inclusion_unames));
+       match_list_free(&(a->inclusion_gnames));
+       free(a);
+       return (ARCHIVE_OK);
+}
+
+/*
+ * Convenience function to perform all exclusion tests.
+ *
+ * Returns 1 if archive entry is excluded.
+ * Returns 0 if archive entry is not excluded.
+ * Returns <0 if something error happened.
+ */
+int
+archive_matching_excluded_ae(struct archive *_a, struct archive_entry *entry)
+{
+       struct archive_matching *a;
+       int r;
+
+       archive_check_magic(_a, ARCHIVE_MATCHING_MAGIC,
+           ARCHIVE_STATE_NEW, "archive_matching_excluded_ae");
+
+       a = (struct archive_matching *)_a;
+       if (entry == NULL) {
+               archive_set_error(&(a->archive), EINVAL, "entry is NULL");
+               return (ARCHIVE_FAILED);
+       }
+
+       r = 0;
+       if (a->setflag & PATTERN_IS_SET) {
+#if defined(_WIN32) && !defined(__CYGWIN__)
+               r = path_excluded(a, 0, archive_entry_pathname_w(entry));
+#else
+               r = path_excluded(a, 1, archive_entry_pathname(entry));
+#endif
+               if (r != 0)
+                       return (r);
+       }
+
+       if (a->setflag & TIME_IS_SET) {
+               r = time_excluded(a, entry);
+               if (r != 0)
+                       return (r);
+       }
+
+       if (a->setflag & ID_IS_SET)
+               r = owner_excluded(a, entry);
+       return (r);
+}
+
+/*
+ * Utility functions to manage exclusion/inclusion patterns
+ */
+
+int
+archive_matching_exclude_pattern(struct archive *_a, const char *pattern)
+{
+       struct archive_matching *a;
+       int r;
+
+       archive_check_magic(_a, ARCHIVE_MATCHING_MAGIC,
+           ARCHIVE_STATE_NEW, "archive_matching_exclude_pattern");
+       a = (struct archive_matching *)_a;
+
+       if (pattern == NULL || *pattern == '\0') {
+               archive_set_error(&(a->archive), EINVAL, "pattern is empty");
+               return (ARCHIVE_FAILED);
+       }
+       if ((r = add_pattern_mbs(a, &(a->exclusions), pattern)) != ARCHIVE_OK)
+               return (r);
+       return (ARCHIVE_OK);
+}
+
+int
+archive_matching_exclude_pattern_w(struct archive *_a, const wchar_t *pattern)
+{
+       struct archive_matching *a;
+       int r;
+
+       archive_check_magic(_a, ARCHIVE_MATCHING_MAGIC,
+           ARCHIVE_STATE_NEW, "archive_matching_exclude_pattern_w");
+       a = (struct archive_matching *)_a;
+
+       if (pattern == NULL || *pattern == L'\0') {
+               archive_set_error(&(a->archive), EINVAL, "pattern is empty");
+               return (ARCHIVE_FAILED);
+       }
+       if ((r = add_pattern_wcs(a, &(a->exclusions), pattern)) != ARCHIVE_OK)
+               return (r);
+       return (ARCHIVE_OK);
+}
+
+int
+archive_matching_include_pattern(struct archive *_a, const char *pattern)
+{
+       struct archive_matching *a;
+       int r;
+
+       archive_check_magic(_a, ARCHIVE_MATCHING_MAGIC,
+           ARCHIVE_STATE_NEW, "archive_matching_include_pattern");
+       a = (struct archive_matching *)_a;
+
+       if (pattern == NULL || *pattern == '\0') {
+               archive_set_error(&(a->archive), EINVAL, "pattern is empty");
+               return (ARCHIVE_FAILED);
+       }
+       if ((r = add_pattern_mbs(a, &(a->inclusions), pattern)) != ARCHIVE_OK)
+               return (r);
+       return (ARCHIVE_OK);
+}
+
+int
+archive_matching_include_pattern_w(struct archive *_a, const wchar_t *pattern)
+{
+       struct archive_matching *a;
+       int r;
+
+       archive_check_magic(_a, ARCHIVE_MATCHING_MAGIC,
+           ARCHIVE_STATE_NEW, "archive_matching_include_pattern_w");
+       a = (struct archive_matching *)_a;
+
+       if (pattern == NULL || *pattern == L'\0') {
+               archive_set_error(&(a->archive), EINVAL, "pattern is empty");
+               return (ARCHIVE_FAILED);
+       }
+       if ((r = add_pattern_wcs(a, &(a->inclusions), pattern)) != ARCHIVE_OK)
+               return (r);
+       return (ARCHIVE_OK);
+}
+
+/*
+ * Test functions for pathname patterns.
+ *
+ * Returns 1 if archive entry is excluded.
+ * Returns 0 if archive entry is not excluded.
+ * Returns <0 if something error happened.
+ */
+int
+archive_matching_path_excluded(struct archive *_a, const char *pathname)
+{
+       struct archive_matching *a;
+
+       archive_check_magic(_a, ARCHIVE_MATCHING_MAGIC,
+           ARCHIVE_STATE_NEW, "archive_matching_path_excluded");
+
+       if (pathname == NULL || *pathname == L'\0')
+               return (0);
+       a = (struct archive_matching *)_a;
+
+       /* If we don't have exclusion/inclusion pattern set at all,
+        * the pathname is always not excluded. */
+       if ((a->setflag & PATTERN_IS_SET) == 0)
+               return (0);
+       return (path_excluded(a, 1, pathname));
+}
+
+int
+archive_matching_path_excluded_w(struct archive *_a, const wchar_t *pathname)
+{
+       struct archive_matching *a;
+
+       archive_check_magic(_a, ARCHIVE_MATCHING_MAGIC,
+           ARCHIVE_STATE_NEW, "archive_matching_path_excluded_w");
+
+       if (pathname == NULL || *pathname == L'\0')
+               return (0);
+       a = (struct archive_matching *)_a;
+
+       /* If we don't have exclusion/inclusion pattern set at all,
+        * the pathname is always not excluded. */
+       if ((a->setflag & PATTERN_IS_SET) == 0)
+               return (0);
+       return (path_excluded(a, 0, pathname));
+}
+
+int
+archive_matching_path_excluded_ae(struct archive *_a,
+    struct archive_entry *entry)
+{
+       struct archive_matching *a;
+
+       archive_check_magic(_a, ARCHIVE_MATCHING_MAGIC,
+           ARCHIVE_STATE_NEW, "archive_matching_path_excluded_ae");
+
+       a = (struct archive_matching *)_a;
+       if (entry == NULL) {
+               archive_set_error(&(a->archive), EINVAL, "entry is NULL");
+               return (ARCHIVE_FAILED);
+       }
+
+       /* If we don't have exclusion/inclusion pattern set at all,
+        * the entry is always not excluded. */
+       if ((a->setflag & PATTERN_IS_SET) == 0)
+               return (0);
+#if defined(_WIN32) && !defined(__CYGWIN__)
+       return (path_excluded(a, 0, archive_entry_pathname_w(entry)));
+#else
+       return (path_excluded(a, 1, archive_entry_pathname(entry)));
+#endif
+}
+
+/*
+ * Utilty functions to get statistic information for inclusion patterns.
+ */
+int
+archive_matching_path_unmatched_inclusions(struct archive *_a)
+{
+       struct archive_matching *a;
+
+       archive_check_magic(_a, ARCHIVE_MATCHING_MAGIC,
+           ARCHIVE_STATE_NEW, "archive_matching_unmatched_inclusions");
+       a = (struct archive_matching *)_a;
+
+       return (a->inclusions.unmatched_count);
+}
+
+int
+archive_matching_path_unmatched_inclusions_next(struct archive *_a,
+    const char **_p)
+{
+       struct archive_matching *a;
+       const void *v;
+       int r;
+
+       archive_check_magic(_a, ARCHIVE_MATCHING_MAGIC,
+           ARCHIVE_STATE_NEW, "archive_matching_unmatched_inclusions_next");
+       a = (struct archive_matching *)_a;
+
+       r = match_list_unmatched_inclusions_next(a, &(a->inclusions), 1, &v);
+       *_p = (const char *)v;
+       return (r);
+}
+
+int
+archive_matching_path_unmatched_inclusions_next_w(struct archive *_a,
+    const wchar_t **_p)
+{
+       struct archive_matching *a;
+       const void *v;
+       int r;
+
+       archive_check_magic(_a, ARCHIVE_MATCHING_MAGIC,
+           ARCHIVE_STATE_NEW, "archive_matching_unmatched_inclusions_next_w");
+       a = (struct archive_matching *)_a;
+
+       r = match_list_unmatched_inclusions_next(a, &(a->inclusions), 0, &v);
+       *_p = (const wchar_t *)v;
+       return (r);
+}
+
+static int
+add_pattern_mbs(struct archive_matching *a, struct match_list *list,
+    const char *pattern)
+{
+       struct match *match;
+       size_t len;
+
+       match = calloc(1, sizeof(*match));
+       if (match == NULL)
+               return (error_nomem(a));
+       /* Both "foo/" and "foo" should match "foo/bar". */
+       len = strlen(pattern);
+       if (len && pattern[len - 1] == '/')
+               --len;
+       archive_mstring_copy_mbs_len(&(match->pattern), pattern, len);
+       match_list_add(list, match);
+       a->setflag |= PATTERN_IS_SET;
+       return (ARCHIVE_OK);
+}
+
+static int
+add_pattern_wcs(struct archive_matching *a, struct match_list *list,
+    const wchar_t *pattern)
+{
+       struct match *match;
+       size_t len;
+
+       match = calloc(1, sizeof(*match));
+       if (match == NULL)
+               return (error_nomem(a));
+       /* Both "foo/" and "foo" should match "foo/bar". */
+       len = wcslen(pattern);
+       if (len && pattern[len - 1] == L'/')
+               --len;
+       archive_mstring_copy_wcs_len(&(match->pattern), pattern, len);
+       match_list_add(list, match);
+       a->setflag |= PATTERN_IS_SET;
+       return (ARCHIVE_OK);
+}
+
+static int
+path_excluded(struct archive_matching *a, int mbs, const void *pathname)
+{
+       struct match *match;
+       struct match *matched;
+       int r;
+
+       if (a == NULL)
+               return (0);
+
+       /* Mark off any unmatched inclusions. */
+       /* In particular, if a filename does appear in the archive and
+        * is explicitly included and excluded, then we don't report
+        * it as missing even though we don't extract it.
+        */
+       matched = NULL;
+       for (match = a->inclusions.first; match != NULL;
+           match = match->next){
+               if (match->matches == 0
+                   && (r = match_path_inclusion(a, match, mbs, pathname))) {
+                       if (r < 0)
+                               return (r);
+                       a->inclusions.unmatched_count--;
+                       match->matches++;
+                       matched = match;
+               }
+       }
+
+       /* Exclusions take priority */
+       for (match = a->exclusions.first; match != NULL;
+           match = match->next){
+               r = match_path_exclusion(a, match, mbs, pathname);
+               if (r)
+                       return (r);
+       }
+
+       /* It's not excluded and we found an inclusion above, so it's
+        * included. */
+       if (matched != NULL)
+               return (0);
+
+
+       /* We didn't find an unmatched inclusion, check the remaining ones. */
+       for (match = a->inclusions.first; match != NULL;
+           match = match->next){
+               /* We looked at previously-unmatched inclusions already. */
+               if (match->matches > 0
+                   && (r = match_path_inclusion(a, match, mbs, pathname))) {
+                       if (r < 0)
+                               return (r);
+                       match->matches++;
+                       return (0);
+               }
+       }
+
+       /* If there were inclusions, default is to exclude. */
+       if (a->inclusions.first != NULL)
+           return (1);
+
+       /* No explicit inclusions, default is to match. */
+       return (0);
+}
+
+/*
+ * This is a little odd, but it matches the default behavior of
+ * gtar.  In particular, 'a*b' will match 'foo/a1111/222b/bar'
+ *
+ */
+static int
+match_path_exclusion(struct archive_matching *a, struct match *m,
+    int mbs, const void *pn)
+{
+       int flag = PATHMATCH_NO_ANCHOR_START | PATHMATCH_NO_ANCHOR_END;
+       int r;
+
+       if (mbs) {
+               const char *p;
+               r = archive_mstring_get_mbs(&(a->archive), &(m->pattern), &p);
+               if (r == 0)
+                       return (archive_pathmatch(p, (const char *)pn, flag));
+       } else {
+               const wchar_t *p;
+               r = archive_mstring_get_wcs(&(a->archive), &(m->pattern), &p);
+               if (r == 0)
+                       return (archive_pathmatch_w(p, (const wchar_t *)pn,
+                               flag));
+       }
+       if (errno == ENOMEM)
+               return (error_nomem(a));
+       return (0);
+}
+
+/*
+ * Again, mimic gtar:  inclusions are always anchored (have to match
+ * the beginning of the path) even though exclusions are not anchored.
+ */
+static int
+match_path_inclusion(struct archive_matching *a, struct match *m,
+    int mbs, const void *pn)
+{
+       int flag = PATHMATCH_NO_ANCHOR_END;
+       int r;
+
+       if (mbs) {
+               const char *p;
+               r = archive_mstring_get_mbs(&(a->archive), &(m->pattern), &p);
+               if (r == 0)
+                       return (archive_pathmatch(p, (const char *)pn, flag));
+       } else {
+               const wchar_t *p;
+               r = archive_mstring_get_wcs(&(a->archive), &(m->pattern), &p);
+               if (r == 0)
+                       return (archive_pathmatch_w(p, (const wchar_t *)pn,
+                               flag));
+       }
+       if (errno == ENOMEM)
+               return (error_nomem(a));
+       return (0);
+}
+
+static void
+match_list_init(struct match_list *list)
+{
+       list->first = NULL;
+       list->last = &(list->first);
+       list->count = 0;
+}
+
+static void
+match_list_free(struct match_list *list)
+{
+       struct match *p, *q;
+
+       for (p = list->first; p != NULL; ) {
+               q = p;
+               p = p->next;
+               archive_mstring_clean(&(q->pattern));
+               free(q);
+       }
+}
+
+static void
+match_list_add(struct match_list *list, struct match *m)
+{
+       *list->last = m;
+       list->last = &(m->next);
+       list->count++;
+       list->unmatched_count++;
+}
+
+static int
+match_list_unmatched_inclusions_next(struct archive_matching *a,
+    struct match_list *list, int mbs, const void **vp)
+{
+       struct match *m;
+
+       *vp = NULL;
+       if (list->unmatched_eof) {
+               list->unmatched_eof = 0;
+               return (ARCHIVE_EOF);
+       }
+       if (list->unmatched_next == NULL) {
+               if (list->unmatched_count == 0)
+                       return (ARCHIVE_EOF);
+               list->unmatched_next = list->first;
+       }
+
+       for (m = list->unmatched_next; m != NULL; m = m->next) {
+               int r;
+
+               if (m->matches)
+                       continue;
+               if (mbs) {
+                       const char *p;
+                       r = archive_mstring_get_mbs(&(a->archive),
+                               &(m->pattern), &p);
+                       if (r < 0 && errno == ENOMEM)
+                               return (error_nomem(a));
+                       if (p == NULL)
+                               p = "";
+                       *vp = p;
+               } else {
+                       const wchar_t *p;
+                       r = archive_mstring_get_wcs(&(a->archive),
+                               &(m->pattern), &p);
+                       if (r < 0 && errno == ENOMEM)
+                               return (error_nomem(a));
+                       if (p == NULL)
+                               p = L"";
+                       *vp = p;
+               }
+               list->unmatched_next = m->next;
+               if (list->unmatched_next == NULL)
+                       /* To return EOF next time. */
+                       list->unmatched_eof = 1;
+               return (ARCHIVE_OK);
+       }
+       list->unmatched_next = NULL;
+       return (ARCHIVE_EOF);
+}
+
+/*
+ * Utility functions to manage inclusion timestamps.
+ */
+
+int
+archive_matching_newer_mtime(struct archive *_a, time_t sec, long nsec)
+{
+       struct archive_matching *a;
+
+       archive_check_magic(_a, ARCHIVE_MATCHING_MAGIC,
+           ARCHIVE_STATE_NEW, "archive_matching_newer_mtime");
+       a = (struct archive_matching *)_a;
+
+       a->newer_mtime_filter = 1;
+       a->newer_mtime_sec = sec;
+       a->newer_mtime_nsec = nsec;
+       a->setflag |= TIME_IS_SET;
+       return (ARCHIVE_OK);
+}
+
+int
+archive_matching_newer_mtime_than(struct archive *_a, const char *pathname)
+{
+       struct archive_matching *a;
+       int r;
+
+       archive_check_magic(_a, ARCHIVE_MATCHING_MAGIC,
+           ARCHIVE_STATE_NEW, "archive_matching_newer_mtime_than");
+       a = (struct archive_matching *)_a;
+
+       if (pathname == NULL || *pathname == '\0') {
+               archive_set_error(&(a->archive), EINVAL, "pathname is empty");
+               return (ARCHIVE_FAILED);
+       }
+       r = get_filetime_mbs(a, pathname, 0, &(a->newer_mtime_sec),
+               &(a->newer_mtime_nsec));
+       if (r != ARCHIVE_OK)
+               return (r);
+       a->newer_mtime_filter = 1;
+       a->setflag |= TIME_IS_SET;
+       return (ARCHIVE_OK);
+}
+
+int
+archive_matching_newer_mtime_than_w(struct archive *_a, const wchar_t *pathname)
+{
+       struct archive_matching *a;
+       int r;
+
+       archive_check_magic(_a, ARCHIVE_MATCHING_MAGIC,
+           ARCHIVE_STATE_NEW, "archive_matching_newer_mtime_than_w");
+       a = (struct archive_matching *)_a;
+
+       if (pathname == NULL || *pathname == L'\0') {
+               archive_set_error(&(a->archive), EINVAL, "pathname is empty");
+               return (ARCHIVE_FAILED);
+       }
+       r = get_filetime_wcs(a, pathname, 0, &(a->newer_mtime_sec),
+               &(a->newer_mtime_nsec));
+       if (r != ARCHIVE_OK)
+               return (r);
+       a->newer_mtime_filter = 1;
+       a->setflag |= TIME_IS_SET;
+       return (ARCHIVE_OK);
+}
+
+int
+archive_matching_newer_ctime(struct archive *_a, time_t sec, long nsec)
+{
+       struct archive_matching *a;
+
+       archive_check_magic(_a, ARCHIVE_MATCHING_MAGIC,
+           ARCHIVE_STATE_NEW, "archive_matching_newer_ctime");
+       a = (struct archive_matching *)_a;
+
+       a->newer_ctime_filter = 1;
+       a->newer_ctime_sec = sec;
+       a->newer_ctime_nsec = nsec;
+       a->setflag |= TIME_IS_SET;
+       return (ARCHIVE_OK);
+}
+
+int
+archive_matching_newer_ctime_than(struct archive *_a,
+    const char *pathname)
+{
+       struct archive_matching *a;
+       int r;
+
+       archive_check_magic(_a, ARCHIVE_MATCHING_MAGIC,
+           ARCHIVE_STATE_NEW, "archive_matching_newer_ctime_than");
+       a = (struct archive_matching *)_a;
+
+       if (pathname == NULL || *pathname == '\0') {
+               archive_set_error(&(a->archive), EINVAL, "pathname is empty");
+               return (ARCHIVE_FAILED);
+       }
+       r = get_filetime_mbs(a, pathname, 0, &(a->newer_ctime_sec),
+           &(a->newer_ctime_nsec));
+       if (r != ARCHIVE_OK)
+               return (r);
+       a->newer_ctime_filter = 1;
+       a->setflag |= TIME_IS_SET;
+       return (ARCHIVE_OK);
+}
+
+int
+archive_matching_newer_ctime_than_w(struct archive *_a, const wchar_t *pathname)
+{
+       struct archive_matching *a;
+       int r;
+
+       archive_check_magic(_a, ARCHIVE_MATCHING_MAGIC,
+           ARCHIVE_STATE_NEW, "archive_matching_newer_ctime_than_w");
+       a = (struct archive_matching *)_a;
+
+       if (pathname == NULL || *pathname == L'\0') {
+               archive_set_error(&(a->archive), EINVAL, "pathname is empty");
+               return (ARCHIVE_FAILED);
+       }
+       r = get_filetime_wcs(a, pathname, 0, &(a->newer_ctime_sec),
+               &(a->newer_ctime_nsec));
+       if (r != ARCHIVE_OK)
+               return (r);
+       a->newer_ctime_filter = 1;
+       a->setflag |= TIME_IS_SET;
+       return (ARCHIVE_OK);
+}
+
+int
+archive_matching_older_mtime(struct archive *_a, time_t sec, long nsec)
+{
+       struct archive_matching *a;
+
+       archive_check_magic(_a, ARCHIVE_MATCHING_MAGIC,
+           ARCHIVE_STATE_NEW, "archive_matching_older_mtime");
+       a = (struct archive_matching *)_a;
+
+       a->older_mtime_filter = 1;
+       a->older_mtime_sec = sec;
+       a->older_mtime_nsec = nsec;
+       a->setflag |= TIME_IS_SET;
+       return (ARCHIVE_OK);
+}
+
+int
+archive_matching_older_mtime_than(struct archive *_a, const char *pathname)
+{
+       struct archive_matching *a;
+       int r;
+
+       archive_check_magic(_a, ARCHIVE_MATCHING_MAGIC,
+           ARCHIVE_STATE_NEW, "archive_matching_older_mtime_than");
+       a = (struct archive_matching *)_a;
+
+       if (pathname == NULL || *pathname == '\0') {
+               archive_set_error(&(a->archive), EINVAL, "pathname is empty");
+               return (ARCHIVE_FAILED);
+       }
+       r = get_filetime_mbs(a, pathname, 0, &(a->older_mtime_sec),
+               &(a->older_mtime_nsec));
+       if (r != ARCHIVE_OK)
+               return (r);
+       a->older_mtime_filter = 1;
+       a->setflag |= TIME_IS_SET;
+       return (ARCHIVE_OK);
+}
+
+int
+archive_matching_older_mtime_than_w(struct archive *_a, const wchar_t *pathname)
+{
+       struct archive_matching *a;
+       int r;
+
+       archive_check_magic(_a, ARCHIVE_MATCHING_MAGIC,
+           ARCHIVE_STATE_NEW, "archive_matching_older_mtime_than_w");
+       a = (struct archive_matching *)_a;
+
+       if (pathname == NULL || *pathname == L'\0') {
+               archive_set_error(&(a->archive), EINVAL, "pathname is empty");
+               return (ARCHIVE_FAILED);
+       }
+       r = get_filetime_wcs(a, pathname, 0, &(a->older_mtime_sec),
+           &(a->older_mtime_nsec));
+       if (r != ARCHIVE_OK)
+               return (r);
+       a->older_mtime_filter = 1;
+       a->setflag |= TIME_IS_SET;
+       return (ARCHIVE_OK);
+}
+
+int
+archive_matching_older_ctime(struct archive *_a, time_t sec, long nsec)
+{
+       struct archive_matching *a;
+
+       archive_check_magic(_a, ARCHIVE_MATCHING_MAGIC,
+           ARCHIVE_STATE_NEW, "archive_matching_older_ctime");
+       a = (struct archive_matching *)_a;
+
+       a->older_ctime_filter = 1;
+       a->older_ctime_sec = sec;
+       a->older_ctime_nsec = nsec;
+       a->setflag |= TIME_IS_SET;
+       return (ARCHIVE_OK);
+}
+
+int
+archive_matching_older_ctime_than(struct archive *_a, const char *pathname)
+{
+       struct archive_matching *a;
+       int r;
+
+       archive_check_magic(_a, ARCHIVE_MATCHING_MAGIC,
+           ARCHIVE_STATE_NEW, "archive_matching_older_ctime_than");
+       a = (struct archive_matching *)_a;
+
+       if (pathname == NULL || *pathname == '\0') {
+               archive_set_error(&(a->archive), EINVAL, "pathname is empty");
+               return (ARCHIVE_FAILED);
+       }
+       r = get_filetime_mbs(a, pathname, 0, &(a->older_ctime_sec),
+               &(a->older_ctime_nsec));
+       if (r != ARCHIVE_OK)
+               return (r);
+       a->older_ctime_filter = 1;
+       a->setflag |= TIME_IS_SET;
+       return (ARCHIVE_OK);
+}
+
+int
+archive_matching_older_ctime_than_w(struct archive *_a, const wchar_t *pathname)
+{
+       struct archive_matching *a;
+       int r;
+
+       archive_check_magic(_a, ARCHIVE_MATCHING_MAGIC,
+           ARCHIVE_STATE_NEW, "archive_matching_older_ctime_than_w");
+       a = (struct archive_matching *)_a;
+
+       if (pathname == NULL || *pathname == '\0') {
+               archive_set_error(&(a->archive), EINVAL, "pathname is empty");
+               return (ARCHIVE_FAILED);
+       }
+       r = get_filetime_wcs(a, pathname, 0, &(a->older_ctime_sec),
+               &(a->older_ctime_nsec));
+       if (r != ARCHIVE_OK)
+               return (r);
+       a->older_ctime_filter = 1;
+       a->setflag |= TIME_IS_SET;
+       return (ARCHIVE_OK);
+}
+
+int
+archive_matching_pathname_newer_mtime(struct archive *_a,
+    const char *pathname, time_t sec, long nsec)
+{
+       struct archive_matching *a;
+
+       archive_check_magic(_a, ARCHIVE_MATCHING_MAGIC,
+           ARCHIVE_STATE_NEW, "archive_matching_add_newer_mtime_pathname");
+       a = (struct archive_matching *)_a;
+
+       if (pathname == NULL || *pathname == '\0') {
+               archive_set_error(&(a->archive), EINVAL, "pathname is empty");
+               return (ARCHIVE_FAILED);
+       }
+       a->newer_tree.rbt_ops = &rb_ops_mbs;
+       return (add_newer_mtime_pathname(a, 1, pathname, sec, nsec));
+}
+
+int
+archive_matching_pathname_newer_mtime_w(struct archive *_a,
+    const wchar_t *pathname, time_t sec, long nsec)
+{
+       struct archive_matching *a;
+
+       archive_check_magic(_a, ARCHIVE_MATCHING_MAGIC,
+           ARCHIVE_STATE_NEW, "archive_matching_add_newer_mtime_pathname_w");
+       a = (struct archive_matching *)_a;
+
+       if (pathname == NULL || *pathname == '\0') {
+               archive_set_error(&(a->archive), EINVAL, "pathname is empty");
+               return (ARCHIVE_FAILED);
+       }
+       a->newer_tree.rbt_ops = &rb_ops_wcs;
+       return (add_newer_mtime_pathname(a, 0, pathname, sec, nsec));
+}
+
+int
+archive_matching_pathname_newer_mtime_ae(struct archive *_a,
+    struct archive_entry *entry)
+{
+       struct archive_matching *a;
+       const void *pathname;
+       int mbs;
+
+       archive_check_magic(_a, ARCHIVE_MATCHING_MAGIC,
+           ARCHIVE_STATE_NEW, "archive_matching_add_newer_mtime_ae");
+       a = (struct archive_matching *)_a;
+
+       if (entry == NULL) {
+               archive_set_error(&(a->archive), EINVAL, "entry is NULL");
+               return (ARCHIVE_FAILED);
+       }
+#if defined(_WIN32) && !defined(__CYGWIN__)
+       a->newer_tree.rbt_ops = &rb_ops_wcs;
+       pathname = archive_entry_pathname_w(entry);
+       mbs = 0;
+#else
+       a->newer_tree.rbt_ops = &rb_ops_mbs;
+       pathname = archive_entry_pathname(entry);
+       mbs = 1;
+#endif
+       if (pathname == NULL) {
+               archive_set_error(&(a->archive), EINVAL, "pathname is NULL");
+               return (ARCHIVE_FAILED);
+       }
+       return (add_newer_mtime_pathname(a, mbs, pathname,
+               archive_entry_mtime(entry), archive_entry_mtime_nsec(entry)));
+}
+
+/*
+ * Test function for time stamps.
+ *
+ * Returns 1 if archive entry is excluded.
+ * Returns 0 if archive entry is not excluded.
+ * Returns <0 if something error happened.
+ */
+int
+archive_matching_time_excluded_ae(struct archive *_a,
+    struct archive_entry *entry)
+{
+       struct archive_matching *a;
+
+       archive_check_magic(_a, ARCHIVE_MATCHING_MAGIC,
+           ARCHIVE_STATE_NEW, "archive_matching_time_excluded_ae");
+
+       a = (struct archive_matching *)_a;
+       if (entry == NULL) {
+               archive_set_error(&(a->archive), EINVAL, "entry is NULL");
+               return (ARCHIVE_FAILED);
+       }
+
+       /* If we don't have inclusion time set at all, the entry is always
+        * not excluded. */
+       if ((a->setflag & TIME_IS_SET) == 0)
+               return (0);
+       return (time_excluded(a, entry));
+}
+
+#if defined(_WIN32) && !defined(__CYGWIN__)
+#define EPOC_TIME (116444736000000000ui64)
+#else
+static int
+get_time(struct archive_matching *a, struct stat *st, int is_ctime,
+    time_t *time, long *ns)
+{
+       struct archive_entry *ae;
+
+       ae = archive_entry_new();
+       if (ae == NULL)
+               return (error_nomem(a));
+       archive_entry_copy_stat(ae, st);
+       if (is_ctime) {
+               *time = archive_entry_ctime(ae);
+               *ns = archive_entry_ctime_nsec(ae);
+       } else {
+               *time = archive_entry_mtime(ae);
+               *ns = archive_entry_mtime_nsec(ae);
+       }
+       archive_entry_free(ae);
+       return (ARCHIVE_OK);
+}
+#endif
+
+static int
+get_filetime_mbs(struct archive_matching *a, const char *path,
+    int is_ctime, time_t *time, long *ns)
+{
+#if defined(_WIN32) && !defined(__CYGWIN__)
+       /* NOTE: stat() on Windows cannot handle nano seconds. */
+       HANDLE h;
+       WIN32_FIND_DATA d;
+       ULARGE_INTEGER utc;
+
+       h = FindFirstFileA(path, &d);
+       if (h == INVALID_HANDLE_VALUE) {
+               la_dosmaperr(GetLastError());
+               archive_set_error(&(a->archive), errno,
+                   "Failed to FindFirstFileA");
+               return (ARCHIVE_FAILED);
+       }
+       FindClose(h);
+       if (is_ctime) {
+               utc.HighPart = d.ftCreationTime.dwHighDateTime;
+               utc.LowPart = d.ftCreationTime.dwLowDateTime;
+       } else {
+               utc.HighPart = d.ftLastWriteTime.dwHighDateTime;
+               utc.LowPart = d.ftLastWriteTime.dwLowDateTime;
+       }
+       if (utc.QuadPart >= EPOC_TIME) {
+               utc.QuadPart -= EPOC_TIME;
+               *time = (time_t)(utc.QuadPart / 10000000);
+               *ns = (long)(utc.QuadPart % 10000000) * 100;
+       } else {
+               *time = 0;
+               *ns = 0;
+       }
+       return (ARCHIVE_OK);
+#else
+       struct stat st;
+
+       if (stat(path, &st) != 0) {
+               archive_set_error(&(a->archive), errno, "Failed to stat()");
+               return (ARCHIVE_FAILED);
+       }
+       return (get_time(a, &st, is_ctime, time, ns));
+#endif
+}
+
+static int
+get_filetime_wcs(struct archive_matching *a, const wchar_t *path,
+    int is_ctime, time_t *time, long *ns)
+{
+#if defined(_WIN32) && !defined(__CYGWIN__)
+       HANDLE h;
+       WIN32_FIND_DATAW d;
+       ULARGE_INTEGER utc;
+
+       h = FindFirstFileW(path, &d);
+       if (h == INVALID_HANDLE_VALUE) {
+               la_dosmaperr(GetLastError());
+               archive_set_error(&(a->archive), errno,
+                   "Failed to FindFirstFile");
+               return (ARCHIVE_FAILED);
+       }
+       FindClose(h);
+       if (is_ctime) {
+               utc.HighPart = d.ftCreationTime.dwHighDateTime;
+               utc.LowPart = d.ftCreationTime.dwLowDateTime;
+       } else {
+               utc.HighPart = d.ftLastWriteTime.dwHighDateTime;
+               utc.LowPart = d.ftLastWriteTime.dwLowDateTime;
+       }
+       if (utc.QuadPart >= EPOC_TIME) {
+               utc.QuadPart -= EPOC_TIME;
+               *time = (time_t)(utc.QuadPart / 10000000);
+               *ns = (long)(utc.QuadPart % 10000000) * 100;
+       } else {
+               *time = 0;
+               *ns = 0;
+       }
+       return (ARCHIVE_OK);
+#else
+       struct stat st;
+       struct archive_string as;
+
+       archive_string_init(&as);
+       if (archive_string_append_from_wcs(&as, path, wcslen(path)) < 0) {
+               if (errno == ENOMEM)
+                       return (error_nomem(a));
+               archive_set_error(&(a->archive), -1,
+                   "Failed to convert WCS to MBS");
+               return (ARCHIVE_FAILED);
+       }
+       if (stat(as.s, &st) != 0) {
+               archive_set_error(&(a->archive), errno, "Failed to stat()");
+               archive_string_free(&as);
+               return (ARCHIVE_FAILED);
+       }
+       archive_string_free(&as);
+       return (get_time(a, &st, is_ctime, time, ns));
+#endif
+}
+
+static int
+cmp_node_mbs(const struct archive_rb_node *n1,
+    const struct archive_rb_node *n2)
+{
+       struct newer_file *f1 = (struct newer_file *)n1;
+       struct newer_file *f2 = (struct newer_file *)n2;
+       const char *p1, *p2;
+
+       archive_mstring_get_mbs(NULL, &(f1->pathname), &p1);
+       archive_mstring_get_mbs(NULL, &(f2->pathname), &p2);
+       if (p1 == NULL)
+               return (1);
+       if (p2 == NULL)
+               return (-1);
+       return (strcmp(p1, p2));
+}
+        
+static int
+cmp_key_mbs(const struct archive_rb_node *n, const void *key)
+{
+       struct newer_file *f = (struct newer_file *)n;
+       const char *p;
+
+       archive_mstring_get_mbs(NULL, &(f->pathname), &p);
+       if (p == NULL)
+               return (-1);
+       return (strcmp(p, (const char *)key));
+}
+
+static int
+cmp_node_wcs(const struct archive_rb_node *n1,
+    const struct archive_rb_node *n2)
+{
+       struct newer_file *f1 = (struct newer_file *)n1;
+       struct newer_file *f2 = (struct newer_file *)n2;
+       const wchar_t *p1, *p2;
+
+       archive_mstring_get_wcs(NULL, &(f1->pathname), &p1);
+       archive_mstring_get_wcs(NULL, &(f2->pathname), &p2);
+       if (p1 == NULL)
+               return (1);
+       if (p2 == NULL)
+               return (-1);
+       return (wcscmp(p1, p2));
+}
+        
+static int
+cmp_key_wcs(const struct archive_rb_node *n, const void *key)
+{
+       struct newer_file *f = (struct newer_file *)n;
+       const wchar_t *p;
+
+       archive_mstring_get_wcs(NULL, &(f->pathname), &p);
+       if (p == NULL)
+               return (-1);
+       return (wcscmp(p, (const wchar_t *)key));
+}
+
+static void
+newer_file_list_init(struct newer_file_list *list)
+{
+       list->first = NULL;
+       list->last = &(list->first);
+       list->count = 0;
+}
+
+static void
+newer_file_list_free(struct newer_file_list *list)
+{
+       struct newer_file *p, *q;
+
+       for (p = list->first; p != NULL; ) {
+               q = p;
+               p = p->next;
+               archive_mstring_clean(&(q->pathname));
+               free(q);
+       }
+}
+
+static void
+newer_file_list_add(struct newer_file_list *list, struct newer_file *file)
+{
+       *list->last = file;
+       list->last = &(file->next);
+       list->count++;
+}
+
+static int
+add_newer_mtime_pathname(struct archive_matching *a, int mbs,
+    const void *pathname, time_t sec, long nsec)
+{
+       struct newer_file *f;
+       int r;
+
+       f = calloc(1, sizeof(*f));
+       if (f == NULL)
+               return (error_nomem(a));
+       if (mbs)
+               archive_mstring_copy_mbs(&(f->pathname), pathname);
+       else
+               archive_mstring_copy_wcs(&(f->pathname), pathname);
+       f->mtime_sec = sec;
+       f->mtime_nsec = nsec;
+       r = __archive_rb_tree_insert_node(&(a->newer_tree), &(f->node));
+       if (!r) {
+               struct newer_file *f2;
+
+               /* Get the duplicated file. */
+               f2 = (struct newer_file *)__archive_rb_tree_find_node(
+                       &(a->newer_tree), pathname);
+
+               /* Overwrite mtime condision if it is newer than. */
+               if (f2 != NULL && ((f2->mtime_sec < f->mtime_sec) ||
+                   (f2->mtime_sec == f->mtime_sec &&
+                    f2->mtime_nsec < f->mtime_nsec))) {
+                       f2->mtime_sec = f->mtime_sec;
+                       f2->mtime_nsec = f->mtime_nsec;
+                       /* Release the duplicated file. */ 
+                       archive_mstring_clean(&(f->pathname));
+                       free(f);
+                       return (ARCHIVE_OK);
+               }
+       }
+       newer_file_list_add(&(a->newer_list), f);
+       a->setflag |= TIME_IS_SET;
+       return (ARCHIVE_OK);
+}
+
+static int
+time_excluded(struct archive_matching *a, struct archive_entry *entry)
+{
+       struct newer_file *f;
+       const void *pathname;
+       time_t sec;
+       long nsec;
+
+       /*
+        * If this file/dir is excluded by a time comparison, skip it.
+        */
+       if (a->newer_ctime_filter) {
+               sec = archive_entry_ctime(entry);
+               if (sec < a->newer_ctime_sec)
+                       return (1); /* Too old, skip it. */
+               nsec = archive_entry_ctime_nsec(entry);
+               if (sec == a->newer_ctime_sec
+                   && nsec <= a->newer_ctime_nsec)
+                       return (1); /* Too old, skip it. */
+       }
+       if (a->older_ctime_filter) {
+               sec = archive_entry_ctime(entry);
+               if (sec > a->older_ctime_sec)
+                       return (1); /* Too new, skip it. */
+               nsec = archive_entry_ctime_nsec(entry);
+               if (sec == a->older_ctime_sec
+                   && nsec >= a->older_ctime_nsec)
+                       return (1); /* Too new, skip it. */
+       }
+       if (a->newer_mtime_filter) {
+               sec = archive_entry_mtime(entry);
+               if (sec < a->newer_mtime_sec)
+                       return (1); /* Too old, skip it. */
+               nsec = archive_entry_mtime_nsec(entry);
+               if (sec == a->newer_mtime_sec
+                   && nsec <= a->newer_mtime_nsec)
+                       return (1); /* Too old, skip it. */
+       }
+       if (a->older_mtime_filter) {
+               sec = archive_entry_mtime(entry);
+               if (sec > a->older_mtime_sec)
+                       return (1); /* Too new, skip it. */
+               nsec = archive_entry_mtime_nsec(entry);
+               if (sec == a->older_mtime_sec
+                   && nsec >= a->older_mtime_nsec)
+                       return (1); /* Too new, skip it. */
+       }
+
+       /* If there is no incluson list, include the file. */
+       if (a->newer_list.count == 0)
+               return (0);
+
+#if defined(_WIN32) && !defined(__CYGWIN__)
+       pathname = archive_entry_pathname_w(entry);
+       a->newer_tree.rbt_ops = &rb_ops_wcs;
+#else
+       pathname = archive_entry_pathname(entry);
+       a->newer_tree.rbt_ops = &rb_ops_mbs;
+#endif
+       if (pathname == NULL)
+               return (0);
+
+       f = (struct newer_file *)__archive_rb_tree_find_node(
+               &(a->newer_tree), pathname);
+       /* If the file wasn't rejected, include it. */
+       if (f == NULL)
+               return (0);
+
+       sec = archive_entry_mtime(entry);
+       if (f->mtime_sec < sec)
+               return (0);
+       nsec = archive_entry_mtime_nsec(entry);
+       return (f->mtime_sec > sec || f->mtime_nsec >= nsec);
+}
+
+/*
+ * Utility functions to manage inclusion owners
+ */
+
+int
+archive_matching_include_uid(struct archive *_a, int64_t uid)
+{
+       struct archive_matching *a;
+
+       archive_check_magic(_a, ARCHIVE_MATCHING_MAGIC,
+           ARCHIVE_STATE_NEW, "archive_matching_include_uid");
+       a = (struct archive_matching *)_a;
+       return (add_owner_id(a, &(a->inclusion_uids), uid));
+}
+
+int
+archive_matching_include_gid(struct archive *_a, int64_t gid)
+{
+       struct archive_matching *a;
+
+       archive_check_magic(_a, ARCHIVE_MATCHING_MAGIC,
+           ARCHIVE_STATE_NEW, "archive_matching_include_gid");
+       a = (struct archive_matching *)_a;
+       return (add_owner_id(a, &(a->inclusion_gids), gid));
+}
+
+int
+archive_matching_include_uname(struct archive *_a, const char *uname)
+{
+       struct archive_matching *a;
+
+       archive_check_magic(_a, ARCHIVE_MATCHING_MAGIC,
+           ARCHIVE_STATE_NEW, "archive_matching_include_uname");
+       a = (struct archive_matching *)_a;
+       return (add_owner_name(a, &(a->inclusion_unames), 1, uname));
+}
+
+int
+archive_matching_include_uname_w(struct archive *_a, const wchar_t *uname)
+{
+       struct archive_matching *a;
+
+       archive_check_magic(_a, ARCHIVE_MATCHING_MAGIC,
+           ARCHIVE_STATE_NEW, "archive_matching_include_uname_w");
+       a = (struct archive_matching *)_a;
+       return (add_owner_name(a, &(a->inclusion_unames), 0, uname));
+}
+
+int
+archive_matching_include_gname(struct archive *_a, const char *gname)
+{
+       struct archive_matching *a;
+
+       archive_check_magic(_a, ARCHIVE_MATCHING_MAGIC,
+           ARCHIVE_STATE_NEW, "archive_matching_include_gname");
+       a = (struct archive_matching *)_a;
+       return (add_owner_name(a, &(a->inclusion_gnames), 1, gname));
+}
+
+int
+archive_matching_include_gname_w(struct archive *_a, const wchar_t *gname)
+{
+       struct archive_matching *a;
+
+       archive_check_magic(_a, ARCHIVE_MATCHING_MAGIC,
+           ARCHIVE_STATE_NEW, "archive_matching_include_gname_w");
+       a = (struct archive_matching *)_a;
+       return (add_owner_name(a, &(a->inclusion_gnames), 0, gname));
+}
+
+/*
+ * Test function for owner(uid, gid, uname, gname).
+ *
+ * Returns 1 if archive entry is excluded.
+ * Returns 0 if archive entry is not excluded.
+ * Returns <0 if something error happened.
+ */
+int
+archive_matching_owner_excluded_ae(struct archive *_a,
+    struct archive_entry *entry)
+{
+       struct archive_matching *a;
+
+       archive_check_magic(_a, ARCHIVE_MATCHING_MAGIC,
+           ARCHIVE_STATE_NEW, "archive_matching_id_excluded_ae");
+
+       a = (struct archive_matching *)_a;
+       if (entry == NULL) {
+               archive_set_error(&(a->archive), EINVAL, "entry is NULL");
+               return (ARCHIVE_FAILED);
+       }
+
+       /* If we don't have inclusion id set at all, the entry is always
+        * not excluded. */
+       if ((a->setflag & ID_IS_SET) == 0)
+               return (0);
+       return (owner_excluded(a, entry));
+}
+
+static int
+add_owner_id(struct archive_matching *a, struct id_array *ids, int64_t id)
+{
+       if (ids->count + 1 >= ids->size) {
+               if (ids->size == 0)
+                       ids->size = 8;
+               else
+                       ids->size *= 2;
+               ids->ids = realloc(ids->ids, sizeof(*ids->ids) * ids->size);
+               if (ids->ids == NULL)
+                       return (error_nomem(a));
+       }
+       /*
+        * TODO: sort id list.
+        */
+       ids->ids[ids->count++] = id;
+       a->setflag |= ID_IS_SET;
+       return (ARCHIVE_OK);
+}
+
+static int
+match_owner_id(struct id_array *ids, int64_t id)
+{
+       int i;
+
+       for (i = 0; i < (int)ids->count; i++) {
+               if (ids->ids[i] == id)
+                       return (1);
+       }
+       return (0);
+}
+
+static int
+add_owner_name(struct archive_matching *a, struct match_list *list,
+    int mbs, const void *name)
+{
+       struct match *match;
+
+       match = calloc(1, sizeof(*match));
+       if (match == NULL)
+               return (error_nomem(a));
+       if (mbs)
+               archive_mstring_copy_mbs(&(match->pattern), name);
+       else
+               archive_mstring_copy_wcs(&(match->pattern), name);
+       match_list_add(list, match);
+       a->setflag |= ID_IS_SET;
+       return (ARCHIVE_OK);
+}
+
+#if !defined(_WIN32) || defined(__CYGWIN__)
+static int
+match_owner_name_mbs(struct archive_matching *a, struct match_list *list,
+    const char *name)
+{
+       struct match *m;
+       const char *p;
+
+       if (name == NULL || *name == '\0')
+               return (0);
+       for (m = list->first; m; m = m->next) {
+               if (archive_mstring_get_mbs(&(a->archive), &(m->pattern), &p)
+                   < 0 && errno == ENOMEM)
+                       return (error_nomem(a));
+               if (p != NULL && strcmp(p, name) == 0) {
+                       m->matches++;
+                       return (1);
+               }
+       }
+       return (0);
+}
+#else
+static int
+match_owner_name_wcs(struct archive_matching *a, struct match_list *list,
+    const wchar_t *name)
+{
+       struct match *m;
+       const wchar_t *p;
+
+       if (name == NULL || *name == L'\0')
+               return (0);
+       for (m = list->first; m; m = m->next) {
+               if (archive_mstring_get_wcs(&(a->archive), &(m->pattern), &p)
+                   < 0 && errno == ENOMEM)
+                       return (error_nomem(a));
+               if (p != NULL && wcscmp(p, name) == 0) {
+                       m->matches++;
+                       return (1);
+               }
+       }
+       return (0);
+}
+#endif
+
+static int
+owner_excluded(struct archive_matching *a, struct archive_entry *entry)
+{
+       int r;
+
+       if (a->inclusion_uids.count) {
+               if (!match_owner_id(&(a->inclusion_uids),
+                   archive_entry_uid(entry)))
+                       return (1);
+       }
+
+       if (a->inclusion_gids.count) {
+               if (!match_owner_id(&(a->inclusion_gids),
+                   archive_entry_gid(entry)))
+                       return (1);
+       }
+
+       if (a->inclusion_unames.count) {
+#if defined(_WIN32) && !defined(__CYGWIN__)
+               r = match_owner_name_wcs(a, &(a->inclusion_unames),
+                       archive_entry_uname_w(entry));
+#else
+               r = match_owner_name_mbs(a, &(a->inclusion_unames),
+                       archive_entry_uname(entry));
+#endif
+               if (!r)
+                       return (1);
+               else if (r < 0)
+                       return (r);
+       }
+
+       if (a->inclusion_gnames.count) {
+#if defined(_WIN32) && !defined(__CYGWIN__)
+               r = match_owner_name_wcs(a, &(a->inclusion_gnames),
+                       archive_entry_gname_w(entry));
+#else
+               r = match_owner_name_mbs(a, &(a->inclusion_gnames),
+                       archive_entry_gname(entry));
+#endif
+               if (!r)
+                       return (1);
+               else if (r < 0)
+                       return (r);
+       }
+       return (0);
+}
+
diff --git a/libarchive/archive_pathmatch.c b/libarchive/archive_pathmatch.c
new file mode 100644 (file)
index 0000000..0e2b72e
--- /dev/null
@@ -0,0 +1,459 @@
+/*-
+ * Copyright (c) 2003-2007 Tim Kientzle
+ * All rights reserved.
+ *
+ * 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
+ *    in this position and unchanged.
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``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 AUTHOR(S) 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 "archive_platform.h"
+__FBSDID("$FreeBSD$");
+
+#ifdef HAVE_STRING_H
+#include <string.h>
+#endif
+#ifdef HAVE_WCHAR_H
+#include <wchar.h>
+#endif
+
+#include "archive_pathmatch.h"
+
+/*
+ * Check whether a character 'c' is matched by a list specification [...]:
+ *    * Leading '!' or '^' negates the class.
+ *    * <char>-<char> is a range of characters
+ *    * \<char> removes any special meaning for <char>
+ *
+ * Some interesting boundary cases:
+ *   a-d-e is one range (a-d) followed by two single characters - and e.
+ *   \a-\d is same as a-d
+ *   a\-d is three single characters: a, d, -
+ *   Trailing - is not special (so [a-] is two characters a and -).
+ *   Initial - is not special ([a-] is same as [-a] is same as [\\-a])
+ *   This function never sees a trailing \.
+ *   [] always fails
+ *   [!] always succeeds
+ */
+static int
+pm_list(const char *start, const char *end, const char c, int flags)
+{
+       const char *p = start;
+       char rangeStart = '\0', nextRangeStart;
+       int match = 1, nomatch = 0;
+
+       /* This will be used soon... */
+       (void)flags; /* UNUSED */
+
+       /* If this is a negated class, return success for nomatch. */
+       if ((*p == '!' || *p == '^') && p < end) {
+               match = 0;
+               nomatch = 1;
+               ++p;
+       }
+
+       while (p < end) {
+               nextRangeStart = '\0';
+               switch (*p) {
+               case '-':
+                       /* Trailing or initial '-' is not special. */
+                       if ((rangeStart == '\0') || (p == end - 1)) {
+                               if (*p == c)
+                                       return (match);
+                       } else {
+                               char rangeEnd = *++p;
+                               if (rangeEnd == '\\')
+                                       rangeEnd = *++p;
+                               if ((rangeStart <= c) && (c <= rangeEnd))
+                                       return (match);
+                       }
+                       break;
+               case '\\':
+                       ++p;
+                       /* Fall through */
+               default:
+                       if (*p == c)
+                               return (match);
+                       nextRangeStart = *p; /* Possible start of range. */
+               }
+               rangeStart = nextRangeStart;
+               ++p;
+       }
+       return (nomatch);
+}
+
+static int
+pm_list_w(const wchar_t *start, const wchar_t *end, const wchar_t c, int flags)
+{
+       const wchar_t *p = start;
+       wchar_t rangeStart = L'\0', nextRangeStart;
+       int match = 1, nomatch = 0;
+
+       /* This will be used soon... */
+       (void)flags; /* UNUSED */
+
+       /* If this is a negated class, return success for nomatch. */
+       if ((*p == L'!' || *p == L'^') && p < end) {
+               match = 0;
+               nomatch = 1;
+               ++p;
+       }
+
+       while (p < end) {
+               nextRangeStart = L'\0';
+               switch (*p) {
+               case L'-':
+                       /* Trailing or initial '-' is not special. */
+                       if ((rangeStart == L'\0') || (p == end - 1)) {
+                               if (*p == c)
+                                       return (match);
+                       } else {
+                               char rangeEnd = *++p;
+                               if (rangeEnd == L'\\')
+                                       rangeEnd = *++p;
+                               if ((rangeStart <= c) && (c <= rangeEnd))
+                                       return (match);
+                       }
+                       break;
+               case L'\\':
+                       ++p;
+                       /* Fall through */
+               default:
+                       if (*p == c)
+                               return (match);
+                       nextRangeStart = *p; /* Possible start of range. */
+               }
+               rangeStart = nextRangeStart;
+               ++p;
+       }
+       return (nomatch);
+}
+
+/*
+ * If s is pointing to "./", ".//", "./././" or the like, skip it.
+ */
+static const char *
+pm_slashskip(const char *s) {
+       while ((*s == '/')
+           || (s[0] == '.' && s[1] == '/')
+           || (s[0] == '.' && s[1] == '\0'))
+               ++s;
+       return (s);
+}
+
+static const wchar_t *
+pm_slashskip_w(const wchar_t *s) {
+       while ((*s == L'/')
+           || (s[0] == L'.' && s[1] == L'/')
+           || (s[0] == L'.' && s[1] == L'\0'))
+               ++s;
+       return (s);
+}
+
+static int
+pm(const char *p, const char *s, int flags)
+{
+       const char *end;
+
+       /*
+        * Ignore leading './', './/', '././', etc.
+        */
+       if (s[0] == '.' && s[1] == '/')
+               s = pm_slashskip(s + 1);
+       if (p[0] == '.' && p[1] == '/')
+               p = pm_slashskip(p + 1);
+
+       for (;;) {
+               switch (*p) {
+               case '\0':
+                       if (s[0] == '/') {
+                               if (flags & PATHMATCH_NO_ANCHOR_END)
+                                       return (1);
+                               /* "dir" == "dir/" == "dir/." */
+                               s = pm_slashskip(s);
+                       }
+                       return (*s == '\0');
+               case '?':
+                       /* ? always succeeds, unless we hit end of 's' */
+                       if (*s == '\0')
+                               return (0);
+                       break;
+               case '*':
+                       /* "*" == "**" == "***" ... */
+                       while (*p == '*')
+                               ++p;
+                       /* Trailing '*' always succeeds. */
+                       if (*p == '\0')
+                               return (1);
+                       while (*s) {
+                               if (archive_pathmatch(p, s, flags))
+                                       return (1);
+                               ++s;
+                       }
+                       return (0);
+               case '[':
+                       /*
+                        * Find the end of the [...] character class,
+                        * ignoring \] that might occur within the class.
+                        */
+                       end = p + 1;
+                       while (*end != '\0' && *end != ']') {
+                               if (*end == '\\' && end[1] != '\0')
+                                       ++end;
+                               ++end;
+                       }
+                       if (*end == ']') {
+                               /* We found [...], try to match it. */
+                               if (!pm_list(p + 1, end, *s, flags))
+                                       return (0);
+                               p = end; /* Jump to trailing ']' char. */
+                               break;
+                       } else
+                               /* No final ']', so just match '['. */
+                               if (*p != *s)
+                                       return (0);
+                       break;
+               case '\\':
+                       /* Trailing '\\' matches itself. */
+                       if (p[1] == '\0') {
+                               if (*s != '\\')
+                                       return (0);
+                       } else {
+                               ++p;
+                               if (*p != *s)
+                                       return (0);
+                       }
+                       break;
+               case '/':
+                       if (*s != '/' && *s != '\0')
+                               return (0);
+                       /* Note: pattern "/\./" won't match "/";
+                        * pm_slashskip() correctly stops at backslash. */
+                       p = pm_slashskip(p);
+                       s = pm_slashskip(s);
+                       if (*p == '\0' && (flags & PATHMATCH_NO_ANCHOR_END))
+                               return (1);
+                       --p; /* Counteract the increment below. */
+                       --s;
+                       break;
+               case '$':
+                       /* '$' is special only at end of pattern and only
+                        * if PATHMATCH_NO_ANCHOR_END is specified. */
+                       if (p[1] == '\0' && (flags & PATHMATCH_NO_ANCHOR_END)){
+                               /* "dir" == "dir/" == "dir/." */
+                               return (*pm_slashskip(s) == '\0');
+                       }
+                       /* Otherwise, '$' is not special. */
+                       /* FALL THROUGH */
+               default:
+                       if (*p != *s)
+                               return (0);
+                       break;
+               }
+               ++p;
+               ++s;
+       }
+}
+
+static int
+pm_w(const wchar_t *p, const wchar_t *s, int flags)
+{
+       const wchar_t *end;
+
+       /*
+        * Ignore leading './', './/', '././', etc.
+        */
+       if (s[0] == L'.' && s[1] == L'/')
+               s = pm_slashskip_w(s + 1);
+       if (p[0] == L'.' && p[1] == L'/')
+               p = pm_slashskip_w(p + 1);
+
+       for (;;) {
+               switch (*p) {
+               case L'\0':
+                       if (s[0] == L'/') {
+                               if (flags & PATHMATCH_NO_ANCHOR_END)
+                                       return (1);
+                               /* "dir" == "dir/" == "dir/." */
+                               s = pm_slashskip_w(s);
+                       }
+                       return (*s == L'\0');
+               case L'?':
+                       /* ? always succeeds, unless we hit end of 's' */
+                       if (*s == L'\0')
+                               return (0);
+                       break;
+               case L'*':
+                       /* "*" == "**" == "***" ... */
+                       while (*p == L'*')
+                               ++p;
+                       /* Trailing '*' always succeeds. */
+                       if (*p == L'\0')
+                               return (1);
+                       while (*s) {
+                               if (archive_pathmatch_w(p, s, flags))
+                                       return (1);
+                               ++s;
+                       }
+                       return (0);
+               case L'[':
+                       /*
+                        * Find the end of the [...] character class,
+                        * ignoring \] that might occur within the class.
+                        */
+                       end = p + 1;
+                       while (*end != L'\0' && *end != L']') {
+                               if (*end == L'\\' && end[1] != L'\0')
+                                       ++end;
+                               ++end;
+                       }
+                       if (*end == L']') {
+                               /* We found [...], try to match it. */
+                               if (!pm_list_w(p + 1, end, *s, flags))
+                                       return (0);
+                               p = end; /* Jump to trailing ']' char. */
+                               break;
+                       } else
+                               /* No final ']', so just match '['. */
+                               if (*p != *s)
+                                       return (0);
+                       break;
+               case L'\\':
+                       /* Trailing '\\' matches itself. */
+                       if (p[1] == L'\0') {
+                               if (*s != L'\\')
+                                       return (0);
+                       } else {
+                               ++p;
+                               if (*p != *s)
+                                       return (0);
+                       }
+                       break;
+               case L'/':
+                       if (*s != L'/' && *s != L'\0')
+                               return (0);
+                       /* Note: pattern "/\./" won't match "/";
+                        * pm_slashskip() correctly stops at backslash. */
+                       p = pm_slashskip_w(p);
+                       s = pm_slashskip_w(s);
+                       if (*p == L'\0' && (flags & PATHMATCH_NO_ANCHOR_END))
+                               return (1);
+                       --p; /* Counteract the increment below. */
+                       --s;
+                       break;
+               case L'$':
+                       /* '$' is special only at end of pattern and only
+                        * if PATHMATCH_NO_ANCHOR_END is specified. */
+                       if (p[1] == L'\0' && (flags & PATHMATCH_NO_ANCHOR_END)){
+                               /* "dir" == "dir/" == "dir/." */
+                               return (*pm_slashskip_w(s) == L'\0');
+                       }
+                       /* Otherwise, '$' is not special. */
+                       /* FALL THROUGH */
+               default:
+                       if (*p != *s)
+                               return (0);
+                       break;
+               }
+               ++p;
+               ++s;
+       }
+}
+
+/* Main entry point. */
+int
+__archive_pathmatch(const char *p, const char *s, int flags)
+{
+       /* Empty pattern only matches the empty string. */
+       if (p == NULL || *p == '\0')
+               return (s == NULL || *s == '\0');
+
+       /* Leading '^' anchors the start of the pattern. */
+       if (*p == '^') {
+               ++p;
+               flags &= ~PATHMATCH_NO_ANCHOR_START;
+       }
+
+       if (*p == '/' && *s != '/')
+               return (0);
+
+       /* Certain patterns and file names anchor implicitly. */
+       if (*p == '*' || *p == '/' || *p == '/') {
+               while (*p == '/')
+                       ++p;
+               while (*s == '/')
+                       ++s;
+               return (pm(p, s, flags));
+       }
+
+       /* If start is unanchored, try to match start of each path element. */
+       if (flags & PATHMATCH_NO_ANCHOR_START) {
+               for ( ; s != NULL; s = strchr(s, '/')) {
+                       if (*s == '/')
+                               s++;
+                       if (pm(p, s, flags))
+                               return (1);
+               }
+               return (0);
+       }
+
+       /* Default: Match from beginning. */
+       return (pm(p, s, flags));
+}
+
+int
+__archive_pathmatch_w(const wchar_t *p, const wchar_t *s, int flags)
+{
+       /* Empty pattern only matches the empty string. */
+       if (p == NULL || *p == L'\0')
+               return (s == NULL || *s == L'\0');
+
+       /* Leading '^' anchors the start of the pattern. */
+       if (*p == L'^') {
+               ++p;
+               flags &= ~PATHMATCH_NO_ANCHOR_START;
+       }
+
+       if (*p == L'/' && *s != L'/')
+               return (0);
+
+       /* Certain patterns and file names anchor implicitly. */
+       if (*p == L'*' || *p == L'/' || *p == L'/') {
+               while (*p == L'/')
+                       ++p;
+               while (*s == L'/')
+                       ++s;
+               return (pm_w(p, s, flags));
+       }
+
+       /* If start is unanchored, try to match start of each path element. */
+       if (flags & PATHMATCH_NO_ANCHOR_START) {
+               for ( ; s != NULL; s = wcschr(s, L'/')) {
+                       if (*s == L'/')
+                               s++;
+                       if (pm_w(p, s, flags))
+                               return (1);
+               }
+               return (0);
+       }
+
+       /* Default: Match from beginning. */
+       return (pm_w(p, s, flags));
+}
diff --git a/libarchive/archive_pathmatch.h b/libarchive/archive_pathmatch.h
new file mode 100644 (file)
index 0000000..0aa608a
--- /dev/null
@@ -0,0 +1,50 @@
+/*-
+ * Copyright (c) 2003-2007 Tim Kientzle
+ * All rights reserved.
+ *
+ * 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
+ *    in this position and unchanged.
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``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 AUTHOR(S) 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.
+ *
+ * $FreeBSD$
+ */
+
+#ifndef __LIBARCHIVE_BUILD
+#error This header is only to be used internally to libarchive.
+#endi
+
+#ifndef ARCHIVE_PATHMATCH_H
+#define ARCHIVE_PATHMATCH_H
+
+/* Don't anchor at beginning unless the pattern starts with "^" */
+#define PATHMATCH_NO_ANCHOR_START      1
+/* Don't anchor at end unless the pattern ends with "$" */
+#define PATHMATCH_NO_ANCHOR_END        2
+
+/* Note that "^" and "$" are not special unless you set the corresponding
+ * flag above. */
+
+int __archive_pathmatch(const char *p, const char *s, int flags);
+int __archive_pathmatch_w(const wchar_t *p, const wchar_t *s, int flags);
+
+#define archive_pathmatch(p, s, f)     __archive_pathmatch(p, s, f)
+#define archive_pathmatch_w(p, s, f)   __archive_pathmatch_w(p, s, f)
+
+#endif
index 9941e96612ad508af70e82c4b5ffd03de4c25f27..937bba6eb7c29eef348f2b8eeca898ec1e95174c 100644 (file)
@@ -50,6 +50,7 @@
 #define        ARCHIVE_READ_MAGIC      (0xdeb0c5U)
 #define        ARCHIVE_WRITE_DISK_MAGIC (0xc001b0c5U)
 #define        ARCHIVE_READ_DISK_MAGIC (0xbadb0c5U)
+#define        ARCHIVE_MATCHING_MAGIC (0xcad11c9U)
 
 #define        ARCHIVE_STATE_NEW       1U
 #define        ARCHIVE_STATE_HEADER    2U
index de98e6bdb85a936a6ef638ca3f249ca9592ae0fc..4c97055347b58e8d47ee913cb654bc546f355ac3 100644 (file)
@@ -15,6 +15,9 @@ IF(ENABLE_TEST)
     test_archive_api_feature.c
     test_archive_clear_error.c
     test_archive_crypto.c
+    test_archive_matching_owner.c
+    test_archive_matching_path.c
+    test_archive_matching_time.c
     test_archive_read_close_twice.c
     test_archive_read_close_twice_open_fd.c
     test_archive_read_close_twice_open_filename.c
diff --git a/libarchive/test/test_archive_matching_owner.c b/libarchive/test/test_archive_matching_owner.c
new file mode 100644 (file)
index 0000000..50c63ec
--- /dev/null
@@ -0,0 +1,289 @@
+/*-
+ * Copyright (c) 2012 Michihiro NAKAJIMA
+ * All rights reserved.
+ *
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``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 AUTHOR(S) 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 "test.h"
+__FBSDID("$FreeBSD$");
+
+static void
+test_uid(void)
+{
+       struct archive_entry *ae;
+       struct archive *m;
+
+       if (!assert((m = archive_matching_new()) != NULL))
+               return;
+       if (!assert((ae = archive_entry_new()) != NULL)) {
+               archive_matching_free(m);
+               return;
+       }
+
+       assertEqualIntA(m, 0, archive_matching_include_uid(m, 1000));
+       assertEqualIntA(m, 0, archive_matching_include_uid(m, 1002));
+
+       archive_entry_set_uid(ae, 0);
+       failure("uid 0 should be excluded");
+       assertEqualInt(1, archive_matching_owner_excluded_ae(m, ae));
+       assertEqualInt(1, archive_matching_excluded_ae(m, ae));
+       archive_entry_set_uid(ae, 1000);
+       failure("uid 1000 should not be excluded");
+       assertEqualInt(0, archive_matching_owner_excluded_ae(m, ae));
+       assertEqualInt(0, archive_matching_excluded_ae(m, ae));
+       archive_entry_set_uid(ae, 1001);
+       failure("uid 1001 should be excluded");
+       assertEqualInt(1, archive_matching_owner_excluded_ae(m, ae));
+       assertEqualInt(1, archive_matching_excluded_ae(m, ae));
+       archive_entry_set_uid(ae, 1002);
+       failure("uid 1002 should not be excluded");
+       assertEqualInt(0, archive_matching_owner_excluded_ae(m, ae));
+       assertEqualInt(0, archive_matching_excluded_ae(m, ae));
+       archive_entry_set_uid(ae, 1003);
+       failure("uid 1003 should be excluded");
+       assertEqualInt(1, archive_matching_owner_excluded_ae(m, ae));
+       assertEqualInt(1, archive_matching_excluded_ae(m, ae));
+
+       /* Clean up. */
+       archive_entry_free(ae);
+       archive_matching_free(m);
+}
+
+static void
+test_gid(void)
+{
+       struct archive_entry *ae;
+       struct archive *m;
+
+       if (!assert((m = archive_matching_new()) != NULL))
+               return;
+       if (!assert((ae = archive_entry_new()) != NULL)) {
+               archive_matching_free(m);
+               return;
+       }
+
+       assertEqualIntA(m, 0, archive_matching_include_gid(m, 1000));
+       assertEqualIntA(m, 0, archive_matching_include_gid(m, 1002));
+
+       archive_entry_set_gid(ae, 0);
+       failure("uid 0 should be excluded");
+       assertEqualInt(1, archive_matching_owner_excluded_ae(m, ae));
+       assertEqualInt(1, archive_matching_excluded_ae(m, ae));
+       archive_entry_set_gid(ae, 1000);
+       failure("uid 1000 should not be excluded");
+       assertEqualInt(0, archive_matching_owner_excluded_ae(m, ae));
+       assertEqualInt(0, archive_matching_excluded_ae(m, ae));
+       archive_entry_set_gid(ae, 1001);
+       failure("uid 1001 should be excluded");
+       assertEqualInt(1, archive_matching_owner_excluded_ae(m, ae));
+       assertEqualInt(1, archive_matching_excluded_ae(m, ae));
+       archive_entry_set_gid(ae, 1002);
+       failure("uid 1002 should not be excluded");
+       assertEqualInt(0, archive_matching_owner_excluded_ae(m, ae));
+       assertEqualInt(0, archive_matching_excluded_ae(m, ae));
+       archive_entry_set_gid(ae, 1003);
+       failure("uid 1003 should be excluded");
+       assertEqualInt(1, archive_matching_owner_excluded_ae(m, ae));
+       assertEqualInt(1, archive_matching_excluded_ae(m, ae));
+
+       /* Clean up. */
+       archive_entry_free(ae);
+       archive_matching_free(m);
+}
+
+static void
+test_uname_mbs(void)
+{
+       struct archive_entry *ae;
+       struct archive *m;
+
+       if (!assert((m = archive_matching_new()) != NULL))
+               return;
+       if (!assert((ae = archive_entry_new()) != NULL)) {
+               archive_matching_free(m);
+               return;
+       }
+
+       assertEqualIntA(m, 0, archive_matching_include_uname(m, "foo"));
+       assertEqualIntA(m, 0, archive_matching_include_uname(m, "bar"));
+
+       archive_entry_copy_uname(ae, "unknown");
+       failure("User 'unknown' should be excluded");
+       assertEqualInt(1, archive_matching_owner_excluded_ae(m, ae));
+       assertEqualInt(1, archive_matching_excluded_ae(m, ae));
+       archive_entry_copy_uname(ae, "foo");
+       failure("User 'foo' should not be excluded");
+       assertEqualInt(0, archive_matching_owner_excluded_ae(m, ae));
+       assertEqualInt(0, archive_matching_excluded_ae(m, ae));
+       archive_entry_copy_uname(ae, "foo1");
+       failure("User 'foo1' should be excluded");
+       assertEqualInt(1, archive_matching_owner_excluded_ae(m, ae));
+       assertEqualInt(1, archive_matching_excluded_ae(m, ae));
+       archive_entry_copy_uname(ae, "bar");
+       failure("User 'bar' should not be excluded");
+       assertEqualInt(0, archive_matching_owner_excluded_ae(m, ae));
+       assertEqualInt(0, archive_matching_excluded_ae(m, ae));
+       archive_entry_copy_uname(ae, "bar1");
+       failure("User 'bar1' should be excluded");
+       assertEqualInt(1, archive_matching_owner_excluded_ae(m, ae));
+       assertEqualInt(1, archive_matching_excluded_ae(m, ae));
+
+       /* Clean up. */
+       archive_entry_free(ae);
+       archive_matching_free(m);
+}
+
+static void
+test_uname_wcs(void)
+{
+       struct archive_entry *ae;
+       struct archive *m;
+
+       if (!assert((m = archive_matching_new()) != NULL))
+               return;
+       if (!assert((ae = archive_entry_new()) != NULL)) {
+               archive_matching_free(m);
+               return;
+       }
+
+       assertEqualIntA(m, 0, archive_matching_include_uname_w(m, L"foo"));
+       assertEqualIntA(m, 0, archive_matching_include_uname_w(m, L"bar"));
+
+       archive_entry_copy_uname_w(ae, L"unknown");
+       failure("User 'unknown' should be excluded");
+       assertEqualInt(1, archive_matching_owner_excluded_ae(m, ae));
+       assertEqualInt(1, archive_matching_excluded_ae(m, ae));
+       archive_entry_copy_uname_w(ae, L"foo");
+       failure("User 'foo' should not be excluded");
+       assertEqualInt(0, archive_matching_owner_excluded_ae(m, ae));
+       assertEqualInt(0, archive_matching_excluded_ae(m, ae));
+       archive_entry_copy_uname_w(ae, L"foo1");
+       failure("User 'foo1' should be excluded");
+       assertEqualInt(1, archive_matching_owner_excluded_ae(m, ae));
+       assertEqualInt(1, archive_matching_excluded_ae(m, ae));
+       archive_entry_copy_uname_w(ae, L"bar");
+       failure("User 'bar' should not be excluded");
+       assertEqualInt(0, archive_matching_owner_excluded_ae(m, ae));
+       assertEqualInt(0, archive_matching_excluded_ae(m, ae));
+       archive_entry_copy_uname_w(ae, L"bar1");
+       failure("User 'bar1' should be excluded");
+       assertEqualInt(1, archive_matching_owner_excluded_ae(m, ae));
+       assertEqualInt(1, archive_matching_excluded_ae(m, ae));
+
+       /* Clean up. */
+       archive_entry_free(ae);
+       archive_matching_free(m);
+}
+
+static void
+test_gname_mbs(void)
+{
+       struct archive_entry *ae;
+       struct archive *m;
+
+       if (!assert((m = archive_matching_new()) != NULL))
+               return;
+       if (!assert((ae = archive_entry_new()) != NULL)) {
+               archive_matching_free(m);
+               return;
+       }
+
+       assertEqualIntA(m, 0, archive_matching_include_gname(m, "foo"));
+       assertEqualIntA(m, 0, archive_matching_include_gname(m, "bar"));
+
+       archive_entry_copy_gname(ae, "unknown");
+       failure("Group 'unknown' should be excluded");
+       assertEqualInt(1, archive_matching_owner_excluded_ae(m, ae));
+       assertEqualInt(1, archive_matching_excluded_ae(m, ae));
+       archive_entry_copy_gname(ae, "foo");
+       failure("Group 'foo' should not be excluded");
+       assertEqualInt(0, archive_matching_owner_excluded_ae(m, ae));
+       assertEqualInt(0, archive_matching_excluded_ae(m, ae));
+       archive_entry_copy_gname(ae, "foo1");
+       failure("Group 'foo1' should be excluded");
+       assertEqualInt(1, archive_matching_owner_excluded_ae(m, ae));
+       assertEqualInt(1, archive_matching_excluded_ae(m, ae));
+       archive_entry_copy_gname(ae, "bar");
+       failure("Group 'bar' should not be excluded");
+       assertEqualInt(0, archive_matching_owner_excluded_ae(m, ae));
+       assertEqualInt(0, archive_matching_excluded_ae(m, ae));
+       archive_entry_copy_gname(ae, "bar1");
+       failure("Group 'bar1' should be excluded");
+       assertEqualInt(1, archive_matching_owner_excluded_ae(m, ae));
+       assertEqualInt(1, archive_matching_excluded_ae(m, ae));
+
+       /* Clean up. */
+       archive_entry_free(ae);
+       archive_matching_free(m);
+}
+
+static void
+test_gname_wcs(void)
+{
+       struct archive_entry *ae;
+       struct archive *m;
+
+       if (!assert((m = archive_matching_new()) != NULL))
+               return;
+       if (!assert((ae = archive_entry_new()) != NULL)) {
+               archive_matching_free(m);
+               return;
+       }
+
+       assertEqualIntA(m, 0, archive_matching_include_gname_w(m, L"foo"));
+       assertEqualIntA(m, 0, archive_matching_include_gname_w(m, L"bar"));
+
+       archive_entry_copy_gname_w(ae, L"unknown");
+       failure("Group 'unknown' should be excluded");
+       assertEqualInt(1, archive_matching_owner_excluded_ae(m, ae));
+       assertEqualInt(1, archive_matching_excluded_ae(m, ae));
+       archive_entry_copy_gname_w(ae, L"foo");
+       failure("Group 'foo' should not be excluded");
+       assertEqualInt(0, archive_matching_owner_excluded_ae(m, ae));
+       assertEqualInt(0, archive_matching_excluded_ae(m, ae));
+       archive_entry_copy_gname_w(ae, L"foo1");
+       failure("Group 'foo1' should be excluded");
+       assertEqualInt(1, archive_matching_owner_excluded_ae(m, ae));
+       assertEqualInt(1, archive_matching_excluded_ae(m, ae));
+       archive_entry_copy_gname_w(ae, L"bar");
+       failure("Group 'bar' should not be excluded");
+       assertEqualInt(0, archive_matching_owner_excluded_ae(m, ae));
+       assertEqualInt(0, archive_matching_excluded_ae(m, ae));
+       archive_entry_copy_gname_w(ae, L"bar1");
+       failure("Group 'bar1' should be excluded");
+       assertEqualInt(1, archive_matching_owner_excluded_ae(m, ae));
+       assertEqualInt(1, archive_matching_excluded_ae(m, ae));
+
+       /* Clean up. */
+       archive_entry_free(ae);
+       archive_matching_free(m);
+}
+
+DEFINE_TEST(test_archive_matching_owner)
+{
+       test_uid();
+       test_gid();
+       test_uname_mbs();
+       test_uname_wcs();
+       test_gname_mbs();
+       test_gname_wcs();
+}
diff --git a/libarchive/test/test_archive_matching_path.c b/libarchive/test/test_archive_matching_path.c
new file mode 100644 (file)
index 0000000..2ac3e1a
--- /dev/null
@@ -0,0 +1,271 @@
+/*-
+ * Copyright (c) 2012 Michihiro NAKAJIMA
+ * All rights reserved.
+ *
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``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 AUTHOR(S) 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 "test.h"
+__FBSDID("$FreeBSD$");
+
+static void
+test_exclusion_mbs(void)
+{
+       struct archive_entry *ae;
+       struct archive *m;
+
+       if (!assert((m = archive_matching_new()) != NULL))
+               return;
+       if (!assert((ae = archive_entry_new()) != NULL)) {
+               archive_matching_free(m);
+               return;
+       }
+
+       /* Test for pattern "^aa*" */
+       assertEqualIntA(m, 0, archive_matching_exclude_pattern(m, "^aa*"));
+
+       /* Test with 'aa1234', which should be excluded. */
+       failure("'aa1234' should be excluded");
+       assertEqualInt(1, archive_matching_path_excluded(m, "aa1234"));
+       failure("'aa1234' should be excluded");
+       assertEqualInt(1, archive_matching_path_excluded_w(m, L"aa1234"));
+       archive_entry_copy_pathname(ae, "aa1234");
+       failure("'aa1234' should be excluded");
+       assertEqualInt(1, archive_matching_path_excluded_ae(m, ae));
+       assertEqualInt(1, archive_matching_excluded_ae(m, ae));
+
+       /* Test with 'a1234', which should not be excluded. */
+       failure("'a1234' should not be excluded");
+       assertEqualInt(0, archive_matching_path_excluded(m, "a1234"));
+       failure("'a1234' should not be excluded");
+       assertEqualInt(0, archive_matching_path_excluded_w(m, L"a1234"));
+       archive_entry_copy_pathname(ae, "a1234");
+       failure("'a1234' should not be excluded");
+       assertEqualInt(0, archive_matching_path_excluded_ae(m, ae));
+       assertEqualInt(0, archive_matching_excluded_ae(m, ae));
+
+       /* Clean up. */
+       archive_entry_free(ae);
+       archive_matching_free(m);
+}
+
+static void
+test_exclusion_wcs(void)
+{
+       struct archive_entry *ae;
+       struct archive *m;
+
+       if (!assert((m = archive_matching_new()) != NULL))
+               return;
+       if (!assert((ae = archive_entry_new()) != NULL)) {
+               archive_matching_free(m);
+               return;
+       }
+
+       /* Test for pattern "^aa*" */
+       assertEqualIntA(m, 0, archive_matching_exclude_pattern_w(m, L"^aa*"));
+
+       /* Test with 'aa1234', which should be excluded. */
+       failure("'aa1234' should be excluded");
+       assertEqualInt(1, archive_matching_path_excluded(m, "aa1234"));
+       failure("'aa1234' should be excluded");
+       assertEqualInt(1, archive_matching_path_excluded_w(m, L"aa1234"));
+       archive_entry_copy_pathname(ae, "aa1234");
+       failure("'aa1234' should be excluded");
+       assertEqualInt(1, archive_matching_path_excluded_ae(m, ae));
+       assertEqualInt(1, archive_matching_excluded_ae(m, ae));
+
+       /* Test with 'a1234', which should not be excluded. */
+       failure("'a1234' should not be excluded");
+       assertEqualInt(0, archive_matching_path_excluded(m, "a1234"));
+       failure("'a1234' should not be excluded");
+       assertEqualInt(0, archive_matching_path_excluded_w(m, L"a1234"));
+       archive_entry_copy_pathname(ae, "a1234");
+       failure("'a1234' should not be excluded");
+       assertEqualInt(0, archive_matching_path_excluded_ae(m, ae));
+       assertEqualInt(0, archive_matching_excluded_ae(m, ae));
+
+       /* Clean up. */
+       archive_entry_free(ae);
+       archive_matching_free(m);
+}
+
+static void
+test_inclusion_mbs(void)
+{
+       struct archive_entry *ae;
+       struct archive *m;
+       const char *mp;
+
+       if (!assert((m = archive_matching_new()) != NULL))
+               return;
+       if (!assert((ae = archive_entry_new()) != NULL)) {
+               archive_matching_free(m);
+               return;
+       }
+
+       /* Test for pattern "^aa*" */
+       assertEqualIntA(m, 0, archive_matching_include_pattern(m, "^aa*"));
+
+       /* Test with 'aa1234', which should not be excluded. */
+       failure("'aa1234' should not be excluded");
+       assertEqualInt(0, archive_matching_path_excluded(m, "aa1234"));
+       failure("'aa1234' should not be excluded");
+       assertEqualInt(0, archive_matching_path_excluded_w(m, L"aa1234"));
+       archive_entry_copy_pathname(ae, "aa1234");
+       failure("'aa1234' should not be excluded");
+       assertEqualInt(0, archive_matching_path_excluded_ae(m, ae));
+       assertEqualInt(0, archive_matching_excluded_ae(m, ae));
+
+       /* Test with 'a1234', which should be excluded. */
+       failure("'a1234' should be excluded");
+       assertEqualInt(1, archive_matching_path_excluded(m, "a1234"));
+       failure("'a1234' should be excluded");
+       assertEqualInt(1, archive_matching_path_excluded_w(m, L"a1234"));
+       archive_entry_copy_pathname(ae, "a1234");
+       failure("'a1234' should be excluded");
+       assertEqualInt(1, archive_matching_path_excluded_ae(m, ae));
+       assertEqualInt(1, archive_matching_excluded_ae(m, ae));
+
+       /* Verify unmatched_inclusions. */
+       assertEqualInt(0, archive_matching_path_unmatched_inclusions(m));
+       assertEqualIntA(m, ARCHIVE_EOF,
+           archive_matching_path_unmatched_inclusions_next(m, &mp));
+
+       /* Clean up. */
+       archive_entry_free(ae);
+       archive_matching_free(m);
+}
+
+static void
+test_inclusion_wcs(void)
+{
+       struct archive_entry *ae;
+       struct archive *m;
+       const char *mp;
+
+       if (!assert((m = archive_matching_new()) != NULL))
+               return;
+       if (!assert((ae = archive_entry_new()) != NULL)) {
+               archive_matching_free(m);
+               return;
+       }
+
+       /* Test for pattern "^aa*" */
+       assertEqualIntA(m, 0, archive_matching_include_pattern_w(m, L"^aa*"));
+
+       /* Test with 'aa1234', which should not be excluded. */
+       failure("'aa1234' should not be excluded");
+       assertEqualInt(0, archive_matching_path_excluded(m, "aa1234"));
+       failure("'aa1234' should not be excluded");
+       assertEqualInt(0, archive_matching_path_excluded_w(m, L"aa1234"));
+       archive_entry_copy_pathname(ae, "aa1234");
+       failure("'aa1234' should not be excluded");
+       assertEqualInt(0, archive_matching_path_excluded_ae(m, ae));
+       assertEqualInt(0, archive_matching_excluded_ae(m, ae));
+
+       /* Test with 'a1234', which should be excluded. */
+       failure("'a1234' should be excluded");
+       assertEqualInt(1, archive_matching_path_excluded(m, "a1234"));
+       failure("'a1234' should be excluded");
+       assertEqualInt(1, archive_matching_path_excluded_w(m, L"a1234"));
+       archive_entry_copy_pathname(ae, "a1234");
+       failure("'a1234' should be excluded");
+       assertEqualInt(1, archive_matching_path_excluded_ae(m, ae));
+       assertEqualInt(1, archive_matching_excluded_ae(m, ae));
+
+       /* Verify unmatched_inclusions. */
+       assertEqualInt(0, archive_matching_path_unmatched_inclusions(m));
+       assertEqualIntA(m, ARCHIVE_EOF,
+           archive_matching_path_unmatched_inclusions_next(m, &mp));
+
+       /* Clean up. */
+       archive_entry_free(ae);
+       archive_matching_free(m);
+}
+
+static void
+test_exclusion_and_inclusion(void)
+{
+       struct archive_entry *ae;
+       struct archive *m;
+       const char *mp;
+       const wchar_t *wp;
+
+       if (!assert((m = archive_matching_new()) != NULL))
+               return;
+       if (!assert((ae = archive_entry_new()) != NULL)) {
+               archive_matching_free(m);
+               return;
+       }
+
+       assertEqualIntA(m, 0, archive_matching_exclude_pattern(m, "^aaa*"));
+       assertEqualIntA(m, 0, archive_matching_include_pattern_w(m, L"^aa*"));
+       assertEqualIntA(m, 0, archive_matching_include_pattern(m, "^a1*"));
+
+       /* Test with 'aa1234', which should not be excluded. */
+       failure("'aa1234' should not be excluded");
+       assertEqualInt(0, archive_matching_path_excluded(m, "aa1234"));
+       failure("'aa1234' should not be excluded");
+       assertEqualInt(0, archive_matching_path_excluded_w(m, L"aa1234"));
+       archive_entry_copy_pathname(ae, "aa1234");
+       failure("'aa1234' should not be excluded");
+       assertEqualInt(0, archive_matching_path_excluded_ae(m, ae));
+       assertEqualInt(0, archive_matching_excluded_ae(m, ae));
+
+       /* Test with 'aaa1234', which should be excluded. */
+       failure("'aaa1234' should be excluded");
+       assertEqualInt(1, archive_matching_path_excluded(m, "aaa1234"));
+       failure("'aaa1234' should be excluded");
+       assertEqualInt(1, archive_matching_path_excluded_w(m, L"aaa1234"));
+       archive_entry_copy_pathname(ae, "aaa1234");
+       failure("'aaa1234' should be excluded");
+       assertEqualInt(1, archive_matching_path_excluded_ae(m, ae));
+       assertEqualInt(1, archive_matching_excluded_ae(m, ae));
+
+       /* Verify unmatched_inclusions. */
+       assertEqualInt(1, archive_matching_path_unmatched_inclusions(m));
+       /* Verify unmatched inclusion patterns. */
+       assertEqualIntA(m, ARCHIVE_OK,
+           archive_matching_path_unmatched_inclusions_next(m, &mp));
+       assertEqualString("^a1*", mp);
+       assertEqualIntA(m, ARCHIVE_EOF,
+           archive_matching_path_unmatched_inclusions_next(m, &mp));
+       /* Verify unmatched inclusion patterns again in Wide-Char. */
+       assertEqualIntA(m, ARCHIVE_OK,
+           archive_matching_path_unmatched_inclusions_next_w(m, &wp));
+       assertEqualWString(L"^a1*", wp);
+       assertEqualIntA(m, ARCHIVE_EOF,
+           archive_matching_path_unmatched_inclusions_next_w(m, &wp));
+
+       /* Clean up. */
+       archive_entry_free(ae);
+       archive_matching_free(m);
+}
+
+DEFINE_TEST(test_archive_matching_path)
+{
+       test_exclusion_mbs();
+       test_exclusion_wcs();
+       test_inclusion_mbs();
+       test_inclusion_wcs();
+       test_exclusion_and_inclusion();
+}
diff --git a/libarchive/test/test_archive_matching_time.c b/libarchive/test/test_archive_matching_time.c
new file mode 100644 (file)
index 0000000..289cb15
--- /dev/null
@@ -0,0 +1,703 @@
+/*-
+ * Copyright (c) 2012 Michihiro NAKAJIMA
+ * All rights reserved.
+ *
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``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 AUTHOR(S) 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 "test.h"
+__FBSDID("$FreeBSD$");
+
+static void
+test_newer_time(void)
+{
+       struct archive_entry *ae;
+       struct archive *m;
+
+       if (!assert((m = archive_matching_new()) != NULL))
+               return;
+       if (!assert((ae = archive_entry_new()) != NULL)) {
+               archive_matching_free(m);
+               return;
+       }
+
+       assertEqualIntA(m, 0, archive_matching_newer_mtime(m, 7880, 0));
+       assertEqualIntA(m, 0, archive_matching_newer_ctime(m, 7880, 0));
+
+       archive_entry_copy_pathname(ae, "file1");
+       archive_entry_set_mtime(ae, 7880, 0);
+       archive_entry_set_ctime(ae, 7880, 0);
+       failure("Both Its mtime and ctime should be excluded");
+       assertEqualInt(1, archive_matching_time_excluded_ae(m, ae));
+       assertEqualInt(1, archive_matching_excluded_ae(m, ae));
+       archive_entry_set_mtime(ae, 7879, 999);
+       archive_entry_set_ctime(ae, 7879, 999);
+       failure("Both Its mtime and ctime should be excluded");
+       assertEqualInt(1, archive_matching_time_excluded_ae(m, ae));
+       assertEqualInt(1, archive_matching_excluded_ae(m, ae));
+
+       archive_entry_set_mtime(ae, 7881, 0);
+       archive_entry_set_ctime(ae, 7881, 0);
+       failure("Both Its mtime and ctime should not be excluded");
+       assertEqualInt(0, archive_matching_time_excluded_ae(m, ae));
+       assertEqualInt(0, archive_matching_excluded_ae(m, ae));
+
+       archive_entry_set_mtime(ae, 7880, 1);
+       archive_entry_set_ctime(ae, 7880, 0);
+       failure("Its mtime should be excluded");
+       assertEqualInt(1, archive_matching_time_excluded_ae(m, ae));
+       assertEqualInt(1, archive_matching_excluded_ae(m, ae));
+
+       archive_entry_set_mtime(ae, 7880, 0);
+       archive_entry_set_ctime(ae, 7880, 1);
+       failure("Its ctime should be excluded");
+       assertEqualInt(1, archive_matching_time_excluded_ae(m, ae));
+       assertEqualInt(1, archive_matching_excluded_ae(m, ae));
+
+       /* Clean up. */
+       archive_entry_free(ae);
+       archive_matching_free(m);
+}
+
+static void
+test_newer_than_file_mbs(void)
+{
+       struct archive *a;
+       struct archive_entry *ae;
+       struct archive *m;
+
+       if (!assert((m = archive_matching_new()) != NULL))
+               return;
+       if (!assert((ae = archive_entry_new()) != NULL)) {
+               archive_matching_free(m);
+               return;
+       }
+       if (!assert((a = archive_read_disk_new()) != NULL)) {
+               archive_matching_free(m);
+               archive_entry_free(ae);
+               return;
+       }
+
+       assertMakeDir("test_newer_than_file_mbs", 0777);
+       assertChdir("test_newer_than_file_mbs");
+
+       assertMakeFile("old", 0666, "old");
+       sleepUntilAfter(time(NULL));
+       assertMakeFile("mid", 0666, "mid");
+       sleepUntilAfter(time(NULL));
+       assertMakeFile("new", 0666, "new");
+
+       /*
+        * Test 'newer mtime than'.
+        */
+       assertEqualIntA(m, 0, archive_matching_newer_mtime_than(m, "mid"));
+
+       /* Verify 'old' file. */
+       archive_entry_copy_pathname(ae, "old");
+       assertEqualIntA(a, ARCHIVE_OK,
+           archive_read_disk_entry_from_file(a, ae, -1, NULL));
+       failure("It should be excluded");
+       assertEqualInt(1, archive_matching_time_excluded_ae(m, ae));
+       assertEqualInt(1, archive_matching_excluded_ae(m, ae));
+
+       /* Verify 'mid' file. */
+       archive_entry_clear(ae);
+       archive_entry_copy_pathname(ae, "mid");
+       assertEqualIntA(a, ARCHIVE_OK,
+           archive_read_disk_entry_from_file(a, ae, -1, NULL));
+       failure("It should be excluded");
+       assertEqualInt(1, archive_matching_time_excluded_ae(m, ae));
+       assertEqualInt(1, archive_matching_excluded_ae(m, ae));
+
+       /* Verify 'new' file. */
+       archive_entry_clear(ae);
+       archive_entry_copy_pathname(ae, "new");
+       assertEqualIntA(a, ARCHIVE_OK,
+           archive_read_disk_entry_from_file(a, ae, -1, NULL));
+       failure("It should not be excluded");
+       assertEqualInt(0, archive_matching_time_excluded_ae(m, ae));
+       assertEqualInt(0, archive_matching_excluded_ae(m, ae));
+
+       /*
+        * Test 'newer ctime than'.
+        */
+       archive_matching_free(m);
+       if (!assert((m = archive_matching_new()) != NULL)) {
+               assertChdir("..");
+               archive_entry_free(ae);
+               archive_read_free(a);
+               return;
+       }
+       assertEqualIntA(m, 0, archive_matching_newer_ctime_than(m, "mid"));
+
+       /* Verify 'old' file. */
+       archive_entry_clear(ae);
+       archive_entry_copy_pathname(ae, "old");
+       assertEqualIntA(a, ARCHIVE_OK,
+           archive_read_disk_entry_from_file(a, ae, -1, NULL));
+       failure("It should be excluded");
+       assertEqualInt(1, archive_matching_time_excluded_ae(m, ae));
+       assertEqualInt(1, archive_matching_excluded_ae(m, ae));
+
+       /* Verify 'mid' file. */
+       archive_entry_clear(ae);
+       archive_entry_copy_pathname(ae, "mid");
+       assertEqualIntA(a, ARCHIVE_OK,
+           archive_read_disk_entry_from_file(a, ae, -1, NULL));
+       failure("It should be excluded");
+       assertEqualInt(1, archive_matching_time_excluded_ae(m, ae));
+       assertEqualInt(1, archive_matching_excluded_ae(m, ae));
+
+       /* Verify 'new' file. */
+       archive_entry_clear(ae);
+       archive_entry_copy_pathname(ae, "new");
+       assertEqualIntA(a, ARCHIVE_OK,
+           archive_read_disk_entry_from_file(a, ae, -1, NULL));
+       failure("It should not be excluded");
+       assertEqualInt(0, archive_matching_time_excluded_ae(m, ae));
+       assertEqualInt(0, archive_matching_excluded_ae(m, ae));
+
+       assertChdir("..");
+       /* Clean up. */
+       archive_read_free(a);
+       archive_entry_free(ae);
+       archive_matching_free(m);
+}
+
+static void
+test_newer_than_file_wcs(void)
+{
+       struct archive *a;
+       struct archive_entry *ae;
+       struct archive *m;
+
+       if (!assert((m = archive_matching_new()) != NULL))
+               return;
+       if (!assert((ae = archive_entry_new()) != NULL)) {
+               archive_matching_free(m);
+               return;
+       }
+       if (!assert((a = archive_read_disk_new()) != NULL)) {
+               archive_matching_free(m);
+               archive_entry_free(ae);
+               return;
+       }
+
+       assertMakeDir("test_newer_than_file_wcs", 0777);
+       assertChdir("test_newer_than_file_wcs");
+
+       assertMakeFile("old", 0666, "old");
+       sleepUntilAfter(time(NULL));
+       assertMakeFile("mid", 0666, "mid");
+       sleepUntilAfter(time(NULL));
+       assertMakeFile("new", 0666, "new");
+
+       /*
+        * Test 'newer mtime than'.
+        */
+       assertEqualIntA(m, 0, archive_matching_newer_mtime_than_w(m, L"mid"));
+
+       /* Verify 'old' file. */
+       archive_entry_copy_pathname(ae, "old");
+       assertEqualIntA(a, ARCHIVE_OK,
+           archive_read_disk_entry_from_file(a, ae, -1, NULL));
+       failure("It should be excluded");
+       assertEqualInt(1, archive_matching_time_excluded_ae(m, ae));
+       assertEqualInt(1, archive_matching_excluded_ae(m, ae));
+
+       /* Verify 'mid' file. */
+       archive_entry_clear(ae);
+       archive_entry_copy_pathname(ae, "mid");
+       assertEqualIntA(a, ARCHIVE_OK,
+           archive_read_disk_entry_from_file(a, ae, -1, NULL));
+       failure("It should be excluded");
+       assertEqualInt(1, archive_matching_time_excluded_ae(m, ae));
+       assertEqualInt(1, archive_matching_excluded_ae(m, ae));
+
+       /* Verify 'new' file. */
+       archive_entry_clear(ae);
+       archive_entry_copy_pathname(ae, "new");
+       assertEqualIntA(a, ARCHIVE_OK,
+           archive_read_disk_entry_from_file(a, ae, -1, NULL));
+       failure("It should not be excluded");
+       assertEqualInt(0, archive_matching_time_excluded_ae(m, ae));
+       assertEqualInt(0, archive_matching_excluded_ae(m, ae));
+
+       /*
+        * Test 'newer ctime than'.
+        */
+       archive_matching_free(m);
+       if (!assert((m = archive_matching_new()) != NULL)) {
+               assertChdir("..");
+               archive_entry_free(ae);
+               archive_read_free(a);
+               return;
+       }
+       assertEqualIntA(m, 0, archive_matching_newer_ctime_than_w(m, L"mid"));
+
+       /* Verify 'old' file. */
+       archive_entry_clear(ae);
+       archive_entry_copy_pathname(ae, "old");
+       assertEqualIntA(a, ARCHIVE_OK,
+           archive_read_disk_entry_from_file(a, ae, -1, NULL));
+       failure("It should be excluded");
+       assertEqualInt(1, archive_matching_time_excluded_ae(m, ae));
+       assertEqualInt(1, archive_matching_excluded_ae(m, ae));
+
+       /* Verify 'mid' file. */
+       archive_entry_clear(ae);
+       archive_entry_copy_pathname(ae, "mid");
+       assertEqualIntA(a, ARCHIVE_OK,
+           archive_read_disk_entry_from_file(a, ae, -1, NULL));
+       failure("It should be excluded");
+       assertEqualInt(1, archive_matching_time_excluded_ae(m, ae));
+       assertEqualInt(1, archive_matching_excluded_ae(m, ae));
+
+       /* Verify 'new' file. */
+       archive_entry_clear(ae);
+       archive_entry_copy_pathname(ae, "new");
+       assertEqualIntA(a, ARCHIVE_OK,
+           archive_read_disk_entry_from_file(a, ae, -1, NULL));
+       failure("It should not be excluded");
+       assertEqualInt(0, archive_matching_time_excluded_ae(m, ae));
+       assertEqualInt(0, archive_matching_excluded_ae(m, ae));
+
+       assertChdir("..");
+       /* Clean up. */
+       archive_read_free(a);
+       archive_entry_free(ae);
+       archive_matching_free(m);
+}
+
+static void
+test_older_time(void)
+{
+       struct archive_entry *ae;
+       struct archive *m;
+
+       if (!assert((m = archive_matching_new()) != NULL))
+               return;
+       if (!assert((ae = archive_entry_new()) != NULL)) {
+               archive_matching_free(m);
+               return;
+       }
+
+       assertEqualIntA(m, 0, archive_matching_older_mtime(m, 7880, 0));
+       assertEqualIntA(m, 0, archive_matching_older_ctime(m, 7880, 0));
+
+       archive_entry_copy_pathname(ae, "file1");
+       archive_entry_set_mtime(ae, 7880, 0);
+       archive_entry_set_ctime(ae, 7880, 0);
+       failure("Both Its mtime and ctime should be excluded");
+       assertEqualInt(1, archive_matching_time_excluded_ae(m, ae));
+       assertEqualInt(1, archive_matching_excluded_ae(m, ae));
+       archive_entry_set_mtime(ae, 7879, 999);
+       archive_entry_set_ctime(ae, 7879, 999);
+       failure("Both Its mtime and ctime should not be excluded");
+       assertEqualInt(0, archive_matching_time_excluded_ae(m, ae));
+       assertEqualInt(0, archive_matching_excluded_ae(m, ae));
+
+       archive_entry_set_mtime(ae, 7881, 0);
+       archive_entry_set_ctime(ae, 7881, 0);
+       failure("Both Its mtime and ctime should be excluded");
+       assertEqualInt(1, archive_matching_time_excluded_ae(m, ae));
+       assertEqualInt(1, archive_matching_excluded_ae(m, ae));
+
+       archive_entry_set_mtime(ae, 7880, 1);
+       archive_entry_set_ctime(ae, 7879, 0);
+       failure("Its mtime should be excluded");
+       assertEqualInt(1, archive_matching_time_excluded_ae(m, ae));
+       assertEqualInt(1, archive_matching_excluded_ae(m, ae));
+
+       archive_entry_set_mtime(ae, 7879, 0);
+       archive_entry_set_ctime(ae, 7880, 1);
+       failure("Its ctime should be excluded");
+       assertEqualInt(1, archive_matching_time_excluded_ae(m, ae));
+       assertEqualInt(1, archive_matching_excluded_ae(m, ae));
+
+       /* Clean up. */
+       archive_entry_free(ae);
+       archive_matching_free(m);
+}
+
+static void
+test_older_than_file_mbs(void)
+{
+       struct archive *a;
+       struct archive_entry *ae;
+       struct archive *m;
+
+       if (!assert((m = archive_matching_new()) != NULL))
+               return;
+       if (!assert((ae = archive_entry_new()) != NULL)) {
+               archive_matching_free(m);
+               return;
+       }
+       if (!assert((a = archive_read_disk_new()) != NULL)) {
+               archive_matching_free(m);
+               archive_entry_free(ae);
+               return;
+       }
+
+       assertMakeDir("test_older_than_file_mbs", 0777);
+       assertChdir("test_older_than_file_mbs");
+
+       assertMakeFile("old", 0666, "old");
+       sleepUntilAfter(time(NULL));
+       assertMakeFile("mid", 0666, "mid");
+       sleepUntilAfter(time(NULL));
+       assertMakeFile("new", 0666, "new");
+
+       /*
+        * Test 'older mtime than'.
+        */
+       assertEqualIntA(m, 0, archive_matching_older_mtime_than(m, "mid"));
+
+       /* Verify 'old' file. */
+       archive_entry_copy_pathname(ae, "old");
+       assertEqualIntA(a, ARCHIVE_OK,
+           archive_read_disk_entry_from_file(a, ae, -1, NULL));
+       failure("It should not be excluded");
+       assertEqualInt(0, archive_matching_time_excluded_ae(m, ae));
+       assertEqualInt(0, archive_matching_excluded_ae(m, ae));
+
+       /* Verify 'mid' file. */
+       archive_entry_clear(ae);
+       archive_entry_copy_pathname(ae, "mid");
+       assertEqualIntA(a, ARCHIVE_OK,
+           archive_read_disk_entry_from_file(a, ae, -1, NULL));
+       failure("It should be excluded");
+       assertEqualInt(1, archive_matching_time_excluded_ae(m, ae));
+       assertEqualInt(1, archive_matching_excluded_ae(m, ae));
+
+       /* Verify 'new' file. */
+       archive_entry_clear(ae);
+       archive_entry_copy_pathname(ae, "new");
+       assertEqualIntA(a, ARCHIVE_OK,
+           archive_read_disk_entry_from_file(a, ae, -1, NULL));
+       failure("It should be excluded");
+       assertEqualInt(1, archive_matching_time_excluded_ae(m, ae));
+       assertEqualInt(1, archive_matching_excluded_ae(m, ae));
+
+       /*
+        * Test 'older ctime than'.
+        */
+       archive_matching_free(m);
+       if (!assert((m = archive_matching_new()) != NULL)) {
+               assertChdir("..");
+               archive_entry_free(ae);
+               archive_read_free(a);
+               return;
+       }
+       assertEqualIntA(m, 0, archive_matching_older_ctime_than(m, "mid"));
+
+       /* Verify 'old' file. */
+       archive_entry_clear(ae);
+       archive_entry_copy_pathname(ae, "old");
+       assertEqualIntA(a, ARCHIVE_OK,
+           archive_read_disk_entry_from_file(a, ae, -1, NULL));
+       failure("It should not be excluded");
+       assertEqualInt(0, archive_matching_time_excluded_ae(m, ae));
+       assertEqualInt(0, archive_matching_excluded_ae(m, ae));
+
+       /* Verify 'mid' file. */
+       archive_entry_clear(ae);
+       archive_entry_copy_pathname(ae, "mid");
+       assertEqualIntA(a, ARCHIVE_OK,
+           archive_read_disk_entry_from_file(a, ae, -1, NULL));
+       failure("It should be excluded");
+       assertEqualInt(1, archive_matching_time_excluded_ae(m, ae));
+       assertEqualInt(1, archive_matching_excluded_ae(m, ae));
+
+       /* Verify 'new' file. */
+       archive_entry_clear(ae);
+       archive_entry_copy_pathname(ae, "new");
+       assertEqualIntA(a, ARCHIVE_OK,
+           archive_read_disk_entry_from_file(a, ae, -1, NULL));
+       failure("It should be excluded");
+       assertEqualInt(1, archive_matching_time_excluded_ae(m, ae));
+       assertEqualInt(1, archive_matching_excluded_ae(m, ae));
+
+       assertChdir("..");
+       /* Clean up. */
+       archive_read_free(a);
+       archive_entry_free(ae);
+       archive_matching_free(m);
+}
+
+static void
+test_older_than_file_wcs(void)
+{
+       struct archive *a;
+       struct archive_entry *ae;
+       struct archive *m;
+
+       if (!assert((m = archive_matching_new()) != NULL))
+               return;
+       if (!assert((ae = archive_entry_new()) != NULL)) {
+               archive_matching_free(m);
+               return;
+       }
+       if (!assert((a = archive_read_disk_new()) != NULL)) {
+               archive_matching_free(m);
+               archive_entry_free(ae);
+               return;
+       }
+
+       assertMakeDir("test_older_than_file_wcs", 0777);
+       assertChdir("test_older_than_file_wcs");
+
+       assertMakeFile("old", 0666, "old");
+       sleepUntilAfter(time(NULL));
+       assertMakeFile("mid", 0666, "mid");
+       sleepUntilAfter(time(NULL));
+       assertMakeFile("new", 0666, "new");
+
+       /*
+        * Test 'older mtime than'.
+        */
+       assertEqualIntA(m, 0, archive_matching_older_mtime_than_w(m, L"mid"));
+
+       /* Verify 'old' file. */
+       archive_entry_copy_pathname(ae, "old");
+       assertEqualIntA(a, ARCHIVE_OK,
+           archive_read_disk_entry_from_file(a, ae, -1, NULL));
+       failure("It should not be excluded");
+       assertEqualInt(0, archive_matching_time_excluded_ae(m, ae));
+       assertEqualInt(0, archive_matching_excluded_ae(m, ae));
+
+       /* Verify 'mid' file. */
+       archive_entry_clear(ae);
+       archive_entry_copy_pathname(ae, "mid");
+       assertEqualIntA(a, ARCHIVE_OK,
+           archive_read_disk_entry_from_file(a, ae, -1, NULL));
+       failure("It should be excluded");
+       assertEqualInt(1, archive_matching_time_excluded_ae(m, ae));
+       assertEqualInt(1, archive_matching_excluded_ae(m, ae));
+
+       /* Verify 'new' file. */
+       archive_entry_clear(ae);
+       archive_entry_copy_pathname(ae, "new");
+       assertEqualIntA(a, ARCHIVE_OK,
+           archive_read_disk_entry_from_file(a, ae, -1, NULL));
+       failure("It should be excluded");
+       assertEqualInt(1, archive_matching_time_excluded_ae(m, ae));
+       assertEqualInt(1, archive_matching_excluded_ae(m, ae));
+
+       /*
+        * Test 'older ctime than'.
+        */
+       archive_matching_free(m);
+       if (!assert((m = archive_matching_new()) != NULL)) {
+               assertChdir("..");
+               archive_entry_free(ae);
+               archive_read_free(a);
+               return;
+       }
+       assertEqualIntA(m, 0, archive_matching_older_ctime_than_w(m, L"mid"));
+
+       /* Verify 'old' file. */
+       archive_entry_clear(ae);
+       archive_entry_copy_pathname(ae, "old");
+       assertEqualIntA(a, ARCHIVE_OK,
+           archive_read_disk_entry_from_file(a, ae, -1, NULL));
+       failure("It should not be excluded");
+       assertEqualInt(0, archive_matching_time_excluded_ae(m, ae));
+       assertEqualInt(0, archive_matching_excluded_ae(m, ae));
+
+       /* Verify 'mid' file. */
+       archive_entry_clear(ae);
+       archive_entry_copy_pathname(ae, "mid");
+       assertEqualIntA(a, ARCHIVE_OK,
+           archive_read_disk_entry_from_file(a, ae, -1, NULL));
+       failure("It should be excluded");
+       assertEqualInt(1, archive_matching_time_excluded_ae(m, ae));
+       assertEqualInt(1, archive_matching_excluded_ae(m, ae));
+
+       /* Verify 'new' file. */
+       archive_entry_clear(ae);
+       archive_entry_copy_pathname(ae, "new");
+       assertEqualIntA(a, ARCHIVE_OK,
+           archive_read_disk_entry_from_file(a, ae, -1, NULL));
+       failure("It should be excluded");
+       assertEqualInt(1, archive_matching_time_excluded_ae(m, ae));
+       assertEqualInt(1, archive_matching_excluded_ae(m, ae));
+
+       assertChdir("..");
+       /* Clean up. */
+       archive_read_free(a);
+       archive_entry_free(ae);
+       archive_matching_free(m);
+}
+
+static void
+excluded(struct archive *m)
+{
+       struct archive_entry *ae;
+
+       if (!assert((ae = archive_entry_new()) != NULL))
+               return;
+
+       archive_entry_copy_pathname(ae, "file1");
+       archive_entry_set_mtime(ae, 7879, 999);
+       failure("It should be excluded");
+       assertEqualInt(1, archive_matching_time_excluded_ae(m, ae));
+       assertEqualInt(1, archive_matching_excluded_ae(m, ae));
+       archive_entry_set_mtime(ae, 7880, 0);
+       failure("It should be excluded");
+       assertEqualInt(1, archive_matching_time_excluded_ae(m, ae));
+       assertEqualInt(1, archive_matching_excluded_ae(m, ae));
+       archive_entry_set_mtime(ae, 7880, 1);
+       failure("It should not be excluded");
+       assertEqualInt(0, archive_matching_time_excluded_ae(m, ae));
+       assertEqualInt(0, archive_matching_excluded_ae(m, ae));
+
+       archive_entry_copy_pathname(ae, "file2");
+       archive_entry_set_mtime(ae, 7879, 999);
+       failure("It should not be excluded");
+       assertEqualInt(0, archive_matching_time_excluded_ae(m, ae));
+       assertEqualInt(0, archive_matching_excluded_ae(m, ae));
+       archive_entry_set_mtime(ae, 7880, 0);
+       failure("It should not be excluded");
+       assertEqualInt(0, archive_matching_time_excluded_ae(m, ae));
+       assertEqualInt(0, archive_matching_excluded_ae(m, ae));
+       archive_entry_set_mtime(ae, 7880, 1);
+       failure("It should not be excluded");
+       assertEqualInt(0, archive_matching_time_excluded_ae(m, ae));
+       assertEqualInt(0, archive_matching_excluded_ae(m, ae));
+
+       archive_entry_copy_pathname(ae, "file3");
+       archive_entry_set_mtime(ae, 7879, 999);
+       failure("It should be excluded");
+       assertEqualInt(1, archive_matching_time_excluded_ae(m, ae));
+       assertEqualInt(1, archive_matching_excluded_ae(m, ae));
+       archive_entry_set_mtime(ae, 7880, 0);
+       failure("It should be excluded");
+       assertEqualInt(1, archive_matching_time_excluded_ae(m, ae));
+       assertEqualInt(1, archive_matching_excluded_ae(m, ae));
+       archive_entry_set_mtime(ae, 7880, 1);
+       failure("It should be excluded");
+       assertEqualInt(1, archive_matching_time_excluded_ae(m, ae));
+       assertEqualInt(1, archive_matching_excluded_ae(m, ae));
+
+       archive_entry_copy_pathname(ae, "file4");
+       archive_entry_set_mtime(ae, 7879, 999);
+       failure("It should not be excluded");
+       assertEqualInt(0, archive_matching_time_excluded_ae(m, ae));
+       assertEqualInt(0, archive_matching_excluded_ae(m, ae));
+       archive_entry_set_mtime(ae, 7880, 0);
+       failure("It should not be excluded");
+       assertEqualInt(0, archive_matching_time_excluded_ae(m, ae));
+       assertEqualInt(0, archive_matching_excluded_ae(m, ae));
+       archive_entry_set_mtime(ae, 7880, 1);
+       failure("It should not be excluded");
+       assertEqualInt(0, archive_matching_time_excluded_ae(m, ae));
+       assertEqualInt(0, archive_matching_excluded_ae(m, ae));
+
+
+       /* Clean up. */
+       archive_entry_free(ae);
+}
+
+static void
+test_newer_pathname_mbs(void)
+{
+       struct archive *m;
+
+       if (!assert((m = archive_matching_new()) != NULL))
+               return;
+
+       assertEqualIntA(m, 0,
+           archive_matching_pathname_newer_mtime(m, "file1", 7880, 0));
+       assertEqualIntA(m, 0,
+           archive_matching_pathname_newer_mtime(m, "file2", 1, 0));
+       assertEqualIntA(m, 0,
+           archive_matching_pathname_newer_mtime(m, "file3", 99999, 0));
+
+       excluded(m);
+
+       /* Clean up. */
+       archive_matching_free(m);
+}
+
+static void
+test_newer_pathname_wcs(void)
+{
+       struct archive *m;
+
+       if (!assert((m = archive_matching_new()) != NULL))
+               return;
+
+       assertEqualIntA(m, 0,
+           archive_matching_pathname_newer_mtime_w(m, L"file1", 7880, 0));
+       assertEqualIntA(m, 0,
+           archive_matching_pathname_newer_mtime_w(m, L"file2", 1, 0));
+       assertEqualIntA(m, 0,
+           archive_matching_pathname_newer_mtime_w(m, L"file3", 99999, 0));
+
+       excluded(m);
+
+       /* Clean up. */
+       archive_matching_free(m);
+}
+
+static void
+test_newer_archive_entry(void)
+{
+       struct archive_entry *ae;
+       struct archive *m;
+
+       if (!assert((m = archive_matching_new()) != NULL))
+               return;
+       if (!assert((ae = archive_entry_new()) != NULL)) {
+               archive_matching_free(m);
+               return;
+       }
+
+       archive_entry_copy_pathname(ae, "file1");
+       archive_entry_set_mtime(ae, 7880, 0);
+       assertEqualIntA(m, 0, archive_matching_pathname_newer_mtime_ae(m, ae));
+       archive_entry_copy_pathname(ae, "file2");
+       archive_entry_set_mtime(ae, 1, 0);
+       assertEqualIntA(m, 0, archive_matching_pathname_newer_mtime_ae(m, ae));
+       archive_entry_copy_pathname(ae, "file3");
+       archive_entry_set_mtime(ae, 99999, 0);
+       assertEqualIntA(m, 0, archive_matching_pathname_newer_mtime_ae(m, ae));
+
+       excluded(m);
+
+       /* Clean up. */
+       archive_entry_free(ae);
+       archive_matching_free(m);
+}
+
+DEFINE_TEST(test_archive_matching_time)
+{
+       test_newer_time();
+       test_newer_than_file_mbs();
+       test_newer_than_file_wcs();
+       test_older_time();
+       test_older_than_file_mbs();
+       test_older_than_file_wcs();
+       test_newer_pathname_mbs();
+       test_newer_pathname_wcs();
+       test_newer_archive_entry();
+}