Blame view

lib/glob.c 3.55 KB
b01250856   George Spelvin   lib: add lib/glob.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
  #include <linux/module.h>
  #include <linux/glob.h>
  
  /*
   * The only reason this code can be compiled as a module is because the
   * ATA code that depends on it can be as well.  In practice, they're
   * both usually compiled in and the module overhead goes away.
   */
  MODULE_DESCRIPTION("glob(7) matching");
  MODULE_LICENSE("Dual MIT/GPL");
  
  /**
   * glob_match - Shell-style pattern matching, like !fnmatch(pat, str, 0)
   * @pat: Shell-style pattern to match, e.g. "*.[ch]".
   * @str: String to match.  The pattern must match the entire string.
   *
   * Perform shell-style glob matching, returning true (1) if the match
   * succeeds, or false (0) if it fails.  Equivalent to !fnmatch(@pat, @str, 0).
   *
   * Pattern metacharacters are ?, *, [ and \.
   * (And, inside character classes, !, - and ].)
   *
   * This is small and simple implementation intended for device blacklists
   * where a string is matched against a number of patterns.  Thus, it
   * does not preprocess the patterns.  It is non-recursive, and run-time
   * is at most quadratic: strlen(@str)*strlen(@pat).
   *
   * An example of the worst case is glob_match("*aaaaa", "aaaaaaaaaa");
   * it takes 6 passes over the pattern before matching the string.
   *
   * Like !fnmatch(@pat, @str, 0) and unlike the shell, this does NOT
   * treat / or leading . specially; it isn't actually used for pathnames.
   *
   * Note that according to glob(7) (and unlike bash), character classes
   * are complemented by a leading !; this does not support the regex-style
   * [^a-z] syntax.
   *
   * An opening bracket without a matching close is matched literally.
   */
  bool __pure glob_match(char const *pat, char const *str)
  {
  	/*
  	 * Backtrack to previous * on mismatch and retry starting one
  	 * character later in the string.  Because * matches all characters
  	 * (no exception for /), it can be easily proved that there's
  	 * never a need to backtrack multiple levels.
  	 */
  	char const *back_pat = NULL, *back_str = back_str;
  
  	/*
  	 * Loop over each token (character or class) in pat, matching
  	 * it against the remaining unmatched tail of str.  Return false
  	 * on mismatch, or true after matching the trailing nul bytes.
  	 */
  	for (;;) {
  		unsigned char c = *str++;
  		unsigned char d = *pat++;
  
  		switch (d) {
  		case '?':	/* Wildcard: anything but nul */
  			if (c == '\0')
  				return false;
  			break;
  		case '*':	/* Any-length wildcard */
  			if (*pat == '\0')	/* Optimize trailing * case */
  				return true;
  			back_pat = pat;
  			back_str = --str;	/* Allow zero-length match */
  			break;
  		case '[': {	/* Character class */
  			bool match = false, inverted = (*pat == '!');
  			char const *class = pat + inverted;
  			unsigned char a = *class++;
  
  			/*
  			 * Iterate over each span in the character class.
  			 * A span is either a single character a, or a
  			 * range a-b.  The first span may begin with ']'.
  			 */
  			do {
  				unsigned char b = a;
  
  				if (a == '\0')	/* Malformed */
  					goto literal;
  
  				if (class[0] == '-' && class[1] != ']') {
  					b = class[1];
  
  					if (b == '\0')
  						goto literal;
  
  					class += 2;
  					/* Any special action if a > b? */
  				}
  				match |= (a <= c && c <= b);
  			} while ((a = *class++) != ']');
  
  			if (match == inverted)
  				goto backtrack;
  			pat = class;
  			}
  			break;
  		case '\\':
  			d = *pat++;
6a9dc5fd6   Gustavo A. R. Silva   lib: Revert use o...
105
  			/* fall through */
b01250856   George Spelvin   lib: add lib/glob.c
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
  		default:	/* Literal character */
  literal:
  			if (c == d) {
  				if (d == '\0')
  					return true;
  				break;
  			}
  backtrack:
  			if (c == '\0' || !back_pat)
  				return false;	/* No point continuing */
  			/* Try again from last *, one character later in str. */
  			pat = back_pat;
  			str = ++back_str;
  			break;
  		}
  	}
  }
  EXPORT_SYMBOL(glob_match);