Previous patch still missed to correct some bugs (those we're still bugs in original FNMatch code, not new bugs introduced by my fixes, in case you might ask...).
'*o' should not match 'blah.ow' and '*o' should not match 'fox'
Anyway, I'm attaching new version of the patch (unixutil.patch) and new version of the test program (fnmatch_test.pas). Just forget about previous ones.
BTW, I also corrected indentation around that couple lines of code that I am fixing. I hope you don't mind. Original indentation was totally messed up and inconsistent; I just couldn't resist, so I corrected it.
I hope this will make reading this code easier.
-- Michalis
{ Some tests of UnixUtil.FNMatch function (or Linux.FNMatch function under FPC 1.0.10). } uses SysUtils, {$ifdef VER1_0} Linux {$else} UnixUtil {$endif}, Libc; { Tests results of UnixUtil.FNMatch versus given "GoodResult" and result of Libc.FNMatch. They all should be equal. (I'm using Libc.FNMatch to 1) make sure that GoodResult is really correct 2) show that UnixUtil.FNMatch behaves really the same way as FNMatch from Libc (actually, Libc.FNMatch should get FNM_NOESCAPE as the 3rd arg to be fully identical) (i.e., it behaves the same after applying my fixes to UnixUtil.FNMatch...) 3) test Libc, while we're at it :-) ) } procedure Check(const Pattern, Name:string; GoodResult:boolean); var UnixUtilResult, LibcResult:boolean; begin UnixUtilResult:= {$ifdef VER1_0} Linux {$else} UnixUtil {$endif} .FNMatch(Pattern, Name); LibcResult:=Libc.FNMatch(PChar(Pattern), PChar(Name), 0) = 0;
{ We have 3 results. All should be equal. } if (UnixUtilResult<>LibcResult) or (LibcResult<>GoodResult) then Writeln(Format('"%s" with "%s" incorrect: %6s %6s %6s', [ Pattern, Name, BoolToStr(UnixUtilResult), BoolToStr(LibcResult), BoolToStr(GoodResult) ])); end; begin { Those tests fail with original FNMatch code, because FNMatch allowed '*x' (for any 'x') to match anything (ending with 'x' or not, zero length or not). } Check('*~', 'foo', false); Check('*b', 'foo', false); Check('*?', '', false); Check('???*o', 'foo', false); Check('*???*o', 'foo', false); (*This test fails with original FNMatch code, because after line 'inc(j);{We didn't find one, need to look further}' found is still assumed to be true (while it should be false) *) Check('*o', 'blah.ow', false); { This fails with original FNMatch code because subsequent tries to match char right after '*' (i.e., 'o' in this case) actually can miss that 'x' <> 'o' when 'x' is the last char of Name. } Check('*o', 'fox', false); { When first error is solved, we can see other problem (that was hidden by previous bug): When the '?' in Pattern matches last char of Name, some problems arise. That's because of original FNMatch code '?' : begin inc(j); Found:=(j<=LenName); end; Nonsense ? This should check FIRST if (j<=LenName). If not, if should terminate whole DoFNMatch with false, not only the loop labeled 'find the next character in pattern, different of ? and *'. And in that loop, variable i should get a chance to be > LenPat. Tests below ensure that these additional fixes are also applied. I.e. these tests worked before my fixes were applied AND they work after my fixes are applied. But they we're causing trouble when I was working on this and my fixes we're applied only partially. } Check('*?', '?', true); Check('*?', 'a', true); { Some additional tests, they worked before my fix and they work after my fix. Just to be sure that everything is OK now. } Check('*o', 'foo', true); Check('*.~', 'foo', false); Check('*.b', 'foo', false); Check('*.o', 'foo', false); Check('*??*o', 'foo', true); Check('?o', 'foo', false); Check('??o', 'foo', true); Check('?o?', 'foo', true); Check('o??', 'foo', false); Check('*', 'foo', true); end.
Index: unixutil.pp =================================================================== RCS file: /FPC/CVS/fpc/rtl/unix/unixutil.pp,v retrieving revision 1.5 diff -u -u -r1.5 unixutil.pp --- unixutil.pp 15 Mar 2004 20:43:07 -0000 1.5 +++ unixutil.pp 6 May 2004 05:39:55 -0000 @@ -227,44 +227,56 @@ '?' : Found:=(j<=LenName); '*' : Begin {find the next character in pattern, different of ? and *} - while Found and (i<LenPat) do + while Found do begin inc(i); + if i>LenPat then Break; case Pattern[i] of '*' : ; '?' : begin + if j>LenName then begin DoFNMatch:=false; Exit; end; inc(j); - Found:=(j<=LenName); end; else Found:=false; end; end; + Assert((i>LenPat) or ( (Pattern[i]<>'*') and (Pattern[i]<>'?') )); {Now, find in name the character which i points to, if the * or ? wasn't the last character in the pattern, else, use up all the chars in name} - Found:=true; + Found:=false; if (i<=LenPat) then - begin + begin repeat - {find a letter (not only first !) which maches pattern[i]} - while (j<=LenName) and (name[j]<>pattern[i]) do - inc (j); - if (j<LenName) then + {find a letter (not only first !) which maches pattern[i]} + while (j<=LenName) and (name[j]<>pattern[i]) do + inc (j); + if (j<LenName) then begin if DoFnMatch(i+1,j+1) then - begin - i:=LenPat; - j:=LenName;{we can stop} - Found:=true; - end - else - inc(j);{We didn't find one, need to look further} + begin + i:=LenPat; + j:=LenName;{we can stop} + Found:=true; + Break; + end else + inc(j);{We didn't find one, need to look further} + end else + if j=LenName then + begin + Found:=true; + Break; end; - until (j>=LenName); - end - else + { This 'until' condition must be j>LenName, not j>=LenName. + That's because when we 'need to look further' and + j = LenName then loop must not terminate. } + until (j>LenName); + end else + begin j:=LenName;{we can stop} + Found:=true; + end; end; else {not a wildcard character in pattern} Found:=(j<=LenName) and (pattern[i]=name[j]);