Hi

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

Reply via email to