From: Michihiro NAKAJIMA Date: Tue, 17 Jan 2012 15:18:15 +0000 (-0500) Subject: Introduce archive_matching APIs. This is a set of utility functions to X-Git-Tag: v3.0.4~2^2~183 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=3b14daeb93a81cb840947e85a2e91a629e325cd6;p=thirdparty%2Flibarchive.git Introduce archive_matching APIs. This is a set of utility functions to 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 --- diff --git a/Makefile.am b/Makefile.am index 98884861a..e281901f5 100644 --- a/Makefile.am +++ b/Makefile.am @@ -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 \ diff --git a/libarchive/CMakeLists.txt b/libarchive/CMakeLists.txt index a801fb288..2ffd55e68 100644 --- a/libarchive/CMakeLists.txt +++ b/libarchive/CMakeLists.txt @@ -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 diff --git a/libarchive/archive.h b/libarchive/archive.h index 4b899fe39..7d7c493ba 100644 --- a/libarchive/archive.h +++ b/libarchive/archive.h @@ -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 diff --git a/libarchive/archive_check_magic.c b/libarchive/archive_check_magic.c index 91229557a..f6f1a1856 100644 --- a/libarchive/archive_check_magic.c +++ b/libarchive/archive_check_magic.c @@ -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 index 000000000..581aab695 --- /dev/null +++ b/libarchive/archive_matching.c @@ -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 +#endif +#ifdef HAVE_STDLIB_H +#include +#endif +#ifdef HAVE_STRING_H +#include +#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 index 000000000..0e2b72ef6 --- /dev/null +++ b/libarchive/archive_pathmatch.c @@ -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 +#endif +#ifdef HAVE_WCHAR_H +#include +#endif + +#include "archive_pathmatch.h" + +/* + * Check whether a character 'c' is matched by a list specification [...]: + * * Leading '!' or '^' negates the class. + * * - is a range of characters + * * \ removes any special meaning for + * + * 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 index 000000000..0aa608a72 --- /dev/null +++ b/libarchive/archive_pathmatch.h @@ -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 diff --git a/libarchive/archive_private.h b/libarchive/archive_private.h index 9941e9661..937bba6eb 100644 --- a/libarchive/archive_private.h +++ b/libarchive/archive_private.h @@ -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 diff --git a/libarchive/test/CMakeLists.txt b/libarchive/test/CMakeLists.txt index de98e6bdb..4c9705534 100644 --- a/libarchive/test/CMakeLists.txt +++ b/libarchive/test/CMakeLists.txt @@ -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 index 000000000..50c63ec4f --- /dev/null +++ b/libarchive/test/test_archive_matching_owner.c @@ -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 index 000000000..2ac3e1ace --- /dev/null +++ b/libarchive/test/test_archive_matching_path.c @@ -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 index 000000000..289cb15d9 --- /dev/null +++ b/libarchive/test/test_archive_matching_time.c @@ -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(); +}