I found some bugs in UnixUtil.FNMatch function. In short, patterns like '*~' matched anything (instead of matching only filenames ending with '~'). I'm attaching patch "unixutil.patch" that fixes these issues. I'm also attaching a test program, "fnmatch_test.pas", so you can see that these bugs really occur and this patch really fixes them. You may want to include this test program somewhere in FPC tests/ dir (tests/test/ ?). This test program is heavily commented, hopefully it explains exactly what bugs were present in FNMatch implementation and why it's OK now.
Note to FPC 1.0.10 users: you can use this patch to fix these issues for FPC 1.0.10 too. You have to apply this to rtl/unix/linux.pp file. Oh, and the test program is usable under FPC 1.0.10 too.
Regards, 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);
{ 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 -r1.5 unixutil.pp
--- unixutil.pp 15 Mar 2004 20:43:07 -0000 1.5
+++ unixutil.pp 6 May 2004 01:54:31 -0000
@@ -227,19 +227,25 @@
'?' : 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}
@@ -250,6 +256,8 @@
{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
+ Found:=false else
if (j<LenName) then
begin
if DoFnMatch(i+1,j+1) then
