April 27, 2005

Пля... Не пишите на Delphi...

Задачка: Поиск текста в файлах по сети (логи в основном).
Проблемма: При вызове

FS := TFileStream.Create('//EUGENE/C$/WINDOWS/WindowsUptate.log', 
  fmOpenRead, fmShareCompat);

вылетало по "файл занят другим процессом". В NotePad.exe открывается без вопросов.

Дактилоскопический анализ хелпа и сорцов VCL показал, что share-ный параметр в Win32 игнорится:

{$IFDEF MSWINDOWS}
  Create(Filename, Mode, 0);
{$ELSE}
  Create(Filename, Mode, FileAccessRights);
{$ENDIF}

Такой-же анализ MSDN показал, что нихрена он игнориться не должен:

HANDLE CreateFile(
  LPCTSTR lpFileName, 
  DWORD dwDesiredAccess, 
  DWORD dwShareMode,
  LPSECURITY_ATTRIBUTES lpSecurityAttributes, 
  DWORD dwCreationDispostion, 
  DWORD dwFlagsAndAttributes, 
  HANDLE hTemplateFile
); 

Пля, началось. Ладно, смотрим TFileStream.Create(..., ..., ...):

...
inherited Create(FileOpen(FileName, Mode));
...

Идем к FileOpen:

...
  if ((Mode and 3) <= fmOpenReadWrite) and
    ((Mode and $F0) <= fmShareDenyNone) then
    Result := Integer(CreateFile(PChar(FileName), AccessMode[Mode and 3],
      ShareMode[(Mode and $F0) shr 4], nil, OPEN_EXISTING,
      FILE_ATTRIBUTE_NORMAL, 0));
...

Я рыдалъ полчаса... Мало того, что эти черти привязали фиксировано все к значениям констант,
дык они еще и в MSDN полянились глянуть, где вроде четко написано:

FILE_SHARE_READ Enables subsequent open operations on the object to request read access. 
Otherwise, other processes cannot open the object if they request read access.  

Вобщем, пришлось переписывать, ибо заказчик уж ооочень хотел по логам искать. Мало-ли кому пригодицца:

...
function TSystemFileStream.OpenHandle(const FileName: string; Mode: Word;
  Rights: Cardinal): integer;
var
  dwDesiredAccess: LongWord;
  dwShareMode: LongWord;
begin
  { File open mode }
  case Mode of
    fmOpenRead:
      { Open the file for reading only. }
      dwDesiredAccess := GENERIC_READ;
    fmOpenWrite:
      { Open the file for writing only. Writing to the file completely }
      { replaces the current contents. }
      dwDesiredAccess := GENERIC_WRITE;
    fmOpenReadWrite:
      { Open the file to modify the current contents rather than replace them. }
      dwDesiredAccess := GENERIC_READ or GENERIC_WRITE;
  else
    raise EFOpenError.CreateFmt(SOpenModeError, []);
  end;

{$WARNINGS OFF}
  { File shared access rights }
  case Rights of
    fmShareDenyNone:
      { No attempt is made to prevent other applications from reading from or }
      { writing to the file. }
      dwShareMode := FILE_SHARE_DELETE or FILE_SHARE_READ or FILE_SHARE_WRITE;
    fmShareCompat:
      { Sharing is compatible with the way FCBs are opened. }
      dwShareMode := FILE_SHARE_READ or FILE_SHARE_WRITE;
    fmShareExclusive:
      { Other applications can not open the file for any reason. }
      dwShareMode := 0;
    fmShareDenyWrite:
      { Other applications can open the file for reading but not for writing. }
      dwShareMode := FILE_SHARE_READ;
    fmShareDenyRead:
      { Other applications can open the file for writing but not for reading. }
      dwShareMode := FILE_SHARE_DELETE or FILE_SHARE_WRITE;
  else
    raise EFOpenError.CreateFmt(SOpenRightsError, []);
  end;
{$WARNINGS ON}

  Result := Integer(CreateFile(PChar(FileName), dwDesiredAccess,
    dwShareMode, nil, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0));
end;
...

Итого: Еще один повод послать Delphi в пешее эротическое... %/

UPD:
остался вопрос насчет fmShareCompat: Какие именно права в досе должны быть по дефолту?