-/*
- * wildcard.c
- *
- * Wildcard matching functions.
- */
-
-/*
- * Copyright (C) 2013 Eric Biggers
- *
- * This file is free software; you can redistribute it and/or modify it under
- * the terms of the GNU Lesser General Public License as published by the Free
- * Software Foundation; either version 3 of the License, or (at your option) any
- * later version.
- *
- * This file is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
- * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
- * details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this file; if not, see http://www.gnu.org/licenses/.
- */
-
-#ifdef HAVE_CONFIG_H
-# include "config.h"
-#endif
-
-#include <ctype.h>
-
-#include "wimlib/dentry.h"
-#include "wimlib/encoding.h"
-#include "wimlib/error.h"
-#include "wimlib/metadata.h"
-#include "wimlib/paths.h"
-#include "wimlib/wildcard.h"
-
-struct match_dentry_ctx {
- int (*consume_dentry)(struct wim_dentry *, void *);
- void *consume_dentry_ctx;
- size_t consume_dentry_count;
- tchar *wildcard_path;
- size_t cur_component_offset;
- size_t cur_component_len;
- bool case_insensitive;
-};
-
-static bool
-do_match_wildcard(const tchar *string, size_t string_len,
- const tchar *wildcard, size_t wildcard_len,
- bool ignore_case)
-{
- for (;;) {
- if (string_len == 0) {
- while (wildcard_len != 0 && *wildcard == T('*')) {
- wildcard++;
- wildcard_len--;
- }
- return (wildcard_len == 0);
- } else if (wildcard_len == 0) {
- return false;
- } else if (*string == *wildcard || *wildcard == T('?') ||
- (ignore_case && totlower(*string) == totlower(*wildcard)))
- {
- string++;
- string_len--;
- wildcard_len--;
- wildcard++;
- continue;
- } else if (*wildcard == T('*')) {
- return do_match_wildcard(string, string_len,
- wildcard + 1, wildcard_len - 1,
- ignore_case) ||
- do_match_wildcard(string + 1, string_len - 1,
- wildcard, wildcard_len,
- ignore_case);
- } else {
- return false;
- }
- }
-}
-
-static bool
-match_wildcard(const tchar *string, const tchar *wildcard,
- size_t wildcard_len, bool ignore_case)
-{
- return do_match_wildcard(string, tstrlen(string),
- wildcard, wildcard_len, ignore_case);
-}
-
-/*
- * Determines whether a path matches a wildcard pattern.
- *
- * @path
- * The path to match. Assumptions: All path separators must be @path_sep,
- * there cannot be consecutive path separators, there cannot be a trailing
- * path separator, and there must be exactly one leading path separator.
- *
- * @path_nchars
- * Number of characters in @path.
- *
- * @wildcard
- * The wildcard pattern to match. It can contain the wildcard characters
- * '*' and '?'. The former matches zero or more characters except
- * @path_sep, and the latter matches any character except @path_sep. All
- * path separators in the pattern must be @path_sep, and there cannot be
- * consecutive path separators, and there cannot be a trailing path
- * separator. If there is a leading path separator, the match is attempted
- * with the filename only; otherwise, the match is attempted with the whole
- * path.
- *
- * @path_sep
- * Path separator character used in @path and @wildcard.
- *
- * @prefix_ok
- * If %true, allow a prefix of @path, terminated by a path separator, to
- * match the pattern, in addition to @path itself. In other words, return
- * %true if the pattern actually matches one of the ancestor directories of
- * @path.
- *
- * Returns %true if there was a match; %false if there was not.
- */
-bool
-match_path(const tchar *path, size_t path_nchars,
- const tchar *wildcard, tchar path_sep, bool prefix_ok)
-{
- if (*wildcard != path_sep) {
- /* Pattern doesn't begin with path separator. Try to match the
- * file name only. */
- return match_wildcard(path_basename_with_len(path, path_nchars),
- wildcard, tstrlen(wildcard),
- default_ignore_case);
- } else {
- /* Pattern begins with path separator. Try to match the whole
- * path. */
- do {
- if (!*wildcard) {
- /* Path has more components than pattern */
- return prefix_ok;
- }
-
- size_t path_component_len = 0;
- size_t wildcard_component_len = 0;
-
- do {
- path_component_len++;
- } while (path[path_component_len] != path_sep &&
- path[path_component_len] != T('\0'));
- do {
- wildcard_component_len++;
- } while (wildcard[wildcard_component_len] != path_sep &&
- wildcard[wildcard_component_len] != T('\0'));
- if (!do_match_wildcard(path, path_component_len,
- wildcard, wildcard_component_len,
- default_ignore_case))
- return false;
- path += path_component_len;
- wildcard += wildcard_component_len;
- } while (*path);
-
- return (*wildcard == '\0');
- }
-}
-
-static int
-expand_wildcard_recursive(struct wim_dentry *cur_dentry,
- struct match_dentry_ctx *ctx);
-
-enum {
- WILDCARD_STATUS_DONE_FULLY,
- WILDCARD_STATUS_DONE_TRAILING_SLASHES,
- WILDCARD_STATUS_NOT_DONE,
-};
-
-static int
-wildcard_status(const tchar *wildcard)
-{
- if (*wildcard == T('\0'))
- return WILDCARD_STATUS_DONE_FULLY;
- while (*wildcard == WIM_PATH_SEPARATOR)
- wildcard++;
- if (*wildcard == T('\0'))
- return WILDCARD_STATUS_DONE_TRAILING_SLASHES;
-
- return WILDCARD_STATUS_NOT_DONE;
-}
-
-static int
-match_dentry(struct wim_dentry *cur_dentry, struct match_dentry_ctx *ctx)
-{
- const tchar *name;
- size_t name_nchars;
- int ret;
-
- if (cur_dentry->d_name_nbytes == 0)
- return 0;
-
- ret = utf16le_get_tstr(cur_dentry->d_name,
- cur_dentry->d_name_nbytes,
- &name, &name_nchars);
- if (ret)
- return ret;
- name_nchars /= sizeof(tchar);
-
- if (do_match_wildcard(name,
- name_nchars,
- &ctx->wildcard_path[ctx->cur_component_offset],
- ctx->cur_component_len,
- ctx->case_insensitive))
- {
- switch (wildcard_status(&ctx->wildcard_path[
- ctx->cur_component_offset +
- ctx->cur_component_len]))
- {
- case WILDCARD_STATUS_DONE_TRAILING_SLASHES:
- if (!dentry_is_directory(cur_dentry)) {
- ret = 0;
- break;
- }
- /* Fall through */
- case WILDCARD_STATUS_DONE_FULLY:
- ret = (*ctx->consume_dentry)(cur_dentry,
- ctx->consume_dentry_ctx);
- ctx->consume_dentry_count++;
- break;
- case WILDCARD_STATUS_NOT_DONE:
- ret = expand_wildcard_recursive(cur_dentry, ctx);
- break;
- }
- } else {
- ret = 0;
- }
-
- utf16le_put_tstr(name);
-
- return ret;
-}
-
-static int
-expand_wildcard_recursive(struct wim_dentry *cur_dentry,
- struct match_dentry_ctx *ctx)
-{
- tchar *w;
- size_t begin;
- size_t end;
- size_t len;
- size_t offset_save;
- size_t len_save;
- int ret;
- struct wim_dentry *child;
-
- w = ctx->wildcard_path;
-
- begin = ctx->cur_component_offset + ctx->cur_component_len;
- while (w[begin] == WIM_PATH_SEPARATOR)
- begin++;
-
- end = begin;
-
- while (w[end] != T('\0') && w[end] != WIM_PATH_SEPARATOR)
- end++;
-
- len = end - begin;
-
- if (len == 0)
- return 0;
-
- offset_save = ctx->cur_component_offset;
- len_save = ctx->cur_component_len;
-
- ctx->cur_component_offset = begin;
- ctx->cur_component_len = len;
-
- ret = 0;
- for_dentry_child(child, cur_dentry) {
- ret = match_dentry(child, ctx);
- if (ret)
- break;
- }
-
- ctx->cur_component_len = len_save;
- ctx->cur_component_offset = offset_save;
-
- return ret;
-}
-
-/* Expand a wildcard relative to the current WIM image.
- *
- * @wim
- * WIMStruct whose currently selected image is searched to expand the
- * wildcard.
- * @wildcard_path
- * Wildcard path to expand, which may contain the '?' and '*' characters.
- * Path separators must be WIM_PATH_SEPARATOR. Leading path separators are
- * ignored, whereas one or more trailing path separators indicate that the
- * wildcard path can only match directories (and not reparse points).
- * @consume_dentry
- * Callback function which will receive each directory entry matched by the
- * wildcard.
- * @consume_dentry_ctx
- * Argument to pass to @consume_dentry.
- * @flags
- * Zero or more of the following flags:
- *
- * WILDCARD_FLAG_WARN_IF_NO_MATCH:
- * Issue a warning if the wildcard does not match any dentries.
- *
- * WILDCARD_FLAG_ERROR_IF_NO_MATCH:
- * Issue an error and return WIMLIB_ERR_PATH_DOES_NOT_EXIST if the
- * wildcard does not match any dentries.
- *
- * WILDCARD_FLAG_CASE_INSENSITIVE:
- * Perform the matching case insensitively. Note that this may
- * cause @wildcard to match multiple dentries, even if it does not
- * contain wildcard characters.
- *
- * @return 0 on success; a positive error code on error; or the first nonzero
- * value returned by @consume_dentry.
- */
-int
-expand_wildcard(WIMStruct *wim,
- const tchar *wildcard_path,
- int (*consume_dentry)(struct wim_dentry *, void *),
- void *consume_dentry_ctx,
- u32 flags)
-{
- struct wim_dentry *root;
- int ret;
-
- root = wim_get_current_root_dentry(wim);
- if (root == NULL)
- goto no_match;
-
- struct match_dentry_ctx ctx = {
- .consume_dentry = consume_dentry,
- .consume_dentry_ctx = consume_dentry_ctx,
- .consume_dentry_count = 0,
- .wildcard_path = TSTRDUP(wildcard_path),
- .cur_component_offset = 0,
- .cur_component_len = 0,
- .case_insensitive = ((flags & WILDCARD_FLAG_CASE_INSENSITIVE) != 0),
- };
-
- if (ctx.wildcard_path == NULL)
- return WIMLIB_ERR_NOMEM;
-
- ret = expand_wildcard_recursive(root, &ctx);
- FREE(ctx.wildcard_path);
- if (ret == 0 && ctx.consume_dentry_count == 0)
- goto no_match;
- return ret;
-
-no_match:
- ret = 0;
- if (flags & WILDCARD_FLAG_WARN_IF_NO_MATCH)
- WARNING("No matches for wildcard path \"%"TS"\"", wildcard_path);
-
- if (flags & WILDCARD_FLAG_ERROR_IF_NO_MATCH) {
- ERROR("No matches for wildcard path \"%"TS"\"", wildcard_path);
- ret = WIMLIB_ERR_PATH_DOES_NOT_EXIST;
- }
- return ret;
-}