1{
2
3    This file is part of the Free Pascal run time library.
4    Copyright (c) 2010 by Sven Barth
5    member of the Free Pascal development team
6
7    Sysutils unit for NativeNT
8
9    See the file COPYING.FPC, included in this distribution,
10    for details about the copyright.
11
12    This program is distributed in the hope that it will be useful,
13    but WITHOUT ANY WARRANTY; without even the implied warranty of
14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
15
16 **********************************************************************}
17unit sysutils;
18interface
19
20{$MODE objfpc}
21{$MODESWITCH OUT}
22{ force ansistrings }
23{$H+}
24{$modeswitch typehelpers}
25{$modeswitch advancedrecords}
26
27uses
28  ndk;
29
30{$DEFINE HAS_SLEEP}
31{$DEFINE HAS_CREATEGUID}
32
33type
34  TNativeNTFindData = record
35    SearchSpec: UnicodeString;
36    NamePos: LongInt;
37    Handle: THandle;
38    IsDirObj: Boolean;
39    SearchAttr: LongInt;
40    Context: ULONG;
41    LastRes: NTSTATUS;
42  end;
43
44{ used OS file system APIs use ansistring }
45{$define SYSUTILS_HAS_UNICODESTR_FILEUTIL_IMPL}
46{ OS has an ansistring/single byte environment variable API (actually it's
47  unicodestring, but that's not yet implemented) }
48{$define SYSUTILS_HAS_ANSISTR_ENVVAR_IMPL}
49
50{ Include platform independent interface part }
51{$i sysutilh.inc}
52
53implementation
54
55  uses
56    sysconst, ndkutils;
57
58{$DEFINE FPC_NOGENERICANSIROUTINES}
59
60{ Include platform independent implementation part }
61{$i sysutils.inc}
62
63{****************************************************************************
64                              File Functions
65****************************************************************************}
66
67function FileOpen(const FileName : UnicodeString; Mode : Integer) : THandle;
68const
69  AccessMode: array[0..2] of ACCESS_MASK  = (
70    GENERIC_READ,
71    GENERIC_WRITE,
72    GENERIC_READ or GENERIC_WRITE);
73  ShareMode: array[0..4] of ULONG = (
74               0,
75               0,
76               FILE_SHARE_READ,
77               FILE_SHARE_WRITE,
78               FILE_SHARE_READ or FILE_SHARE_WRITE or FILE_SHARE_DELETE);
79var
80  ntstr: UNICODE_STRING;
81  objattr: OBJECT_ATTRIBUTES;
82  iostatus: IO_STATUS_BLOCK;
83begin
84  UnicodeStrToNtStr(FileName, ntstr);
85  InitializeObjectAttributes(objattr, @ntstr, 0, 0, Nil);
86  NtCreateFile(@Result, AccessMode[Mode and 3] or NT_SYNCHRONIZE, @objattr,
87    @iostatus, Nil, FILE_ATTRIBUTE_NORMAL, ShareMode[(Mode and $F0) shr 4],
88    FILE_OPEN, FILE_NON_DIRECTORY_FILE or FILE_SYNCHRONOUS_IO_NONALERT, Nil, 0);
89  FreeNtStr(ntstr);
90end;
91
92
93function FileCreate(const FileName : UnicodeString) : THandle;
94begin
95  FileCreate := FileCreate(FileName, fmShareDenyNone, 0);
96end;
97
98
99function FileCreate(const FileName : UnicodeString; Rights: longint) : THandle;
100begin
101  FileCreate := FileCreate(FileName, fmShareDenyNone, Rights);
102end;
103
104
105function FileCreate(const FileName : UnicodeString; ShareMode : longint; Rights: longint) : THandle;
106const
107  ShareModeFlags: array[0..4] of ULONG = (
108               0,
109               0,
110               FILE_SHARE_READ,
111               FILE_SHARE_WRITE,
112               FILE_SHARE_READ or FILE_SHARE_WRITE or FILE_SHARE_DELETE);
113var
114  ntstr: UNICODE_STRING;
115  objattr: OBJECT_ATTRIBUTES;
116  iostatus: IO_STATUS_BLOCK;
117  res: NTSTATUS;
118begin
119  UnicodeStrToNtStr(FileName, ntstr);
120  InitializeObjectAttributes(objattr, @ntstr, 0, 0, Nil);
121  NtCreateFile(@Result, GENERIC_READ or GENERIC_WRITE or NT_SYNCHRONIZE,
122    @objattr, @iostatus, Nil, FILE_ATTRIBUTE_NORMAL,
123    ShareModeFlags[(ShareMode and $F0) shr 4], FILE_OVERWRITE_IF,
124    FILE_NON_DIRECTORY_FILE or FILE_SYNCHRONOUS_IO_NONALERT, Nil, 0);
125  FreeNtStr(ntstr);
126end;
127
128
129function FileRead(Handle : THandle; out Buffer; Count : longint) : Longint;
130var
131  iostatus: IO_STATUS_BLOCK;
132  res: NTSTATUS;
133begin
134  res := NtReadFile(Handle, 0, Nil, Nil, @iostatus, @Buffer, Count, Nil, Nil);
135
136  if res = STATUS_PENDING then begin
137    res := NtWaitForSingleObject(Handle, False, Nil);
138    if NT_SUCCESS(res) then
139      res := iostatus.union1.Status;
140  end;
141
142  if NT_SUCCESS(res) then
143    Result := LongInt(iostatus.Information)
144  else
145    Result := -1;
146end;
147
148
149function FileWrite(Handle : THandle; const Buffer; Count : Longint) : Longint;
150var
151  iostatus: IO_STATUS_BLOCK;
152  res: NTSTATUS;
153begin
154  res := NtWriteFile(Handle, 0, Nil, Nil, @iostatus, @Buffer, Count, Nil,
155           Nil);
156
157  if res = STATUS_PENDING then begin
158    res := NtWaitForSingleObject(Handle, False, Nil);
159    if NT_SUCCESS(res) then
160      res := iostatus.union1.Status;
161  end;
162
163  if NT_SUCCESS(res) then
164    Result := LongInt(iostatus.Information)
165  else
166    Result := -1;
167end;
168
169
170function FileSeek(Handle : THandle;FOffset,Origin : Longint) : Longint;
171begin
172  Result := longint(FileSeek(Handle, Int64(FOffset), Origin));
173end;
174
175
176function FileSeek(Handle : THandle; FOffset: Int64; Origin: Longint) : Int64;
177const
178  ErrorCode = $FFFFFFFFFFFFFFFF;
179var
180  position: FILE_POSITION_INFORMATION;
181  standard: FILE_STANDARD_INFORMATION;
182  iostatus: IO_STATUS_BLOCK;
183  res: NTSTATUS;
184begin
185  { determine the new position }
186  case Origin of
187    fsFromBeginning:
188      position.CurrentByteOffset.QuadPart := FOffset;
189    fsFromCurrent: begin
190      res := NtQueryInformationFile(Handle, @iostatus, @position,
191               SizeOf(FILE_POSITION_INFORMATION), FilePositionInformation);
192      if res < 0 then begin
193        Result := ErrorCode;
194        Exit;
195      end;
196      position.CurrentByteOffset.QuadPart :=
197        position.CurrentByteOffset.QuadPart + FOffset;
198    end;
199    fsFromEnd: begin
200      res := NtQueryInformationFile(Handle, @iostatus, @standard,
201               SizeOf(FILE_STANDARD_INFORMATION), FileStandardInformation);
202      if res < 0 then begin
203        Result := ErrorCode;
204        Exit;
205      end;
206      position.CurrentByteOffset.QuadPart := standard.EndOfFile.QuadPart +
207                                               FOffset;
208    end;
209    else begin
210      Result := ErrorCode;
211      Exit;
212    end;
213  end;
214
215  { set the new position }
216  res := NtSetInformationFile(Handle, @iostatus, @position,
217           SizeOf(FILE_POSITION_INFORMATION), FilePositionInformation);
218  if res < 0 then
219    Result := ErrorCode
220  else
221    Result := position.CurrentByteOffset.QuadPart;
222end;
223
224
225procedure FileClose(Handle : THandle);
226begin
227  NtClose(Handle);
228end;
229
230
231function FileTruncate(Handle : THandle;Size: Int64) : boolean;
232var
233  endoffileinfo: FILE_END_OF_FILE_INFORMATION;
234  allocinfo: FILE_ALLOCATION_INFORMATION;
235  iostatus: IO_STATUS_BLOCK;
236  res: NTSTATUS;
237begin
238  // based on ReactOS' SetEndOfFile
239  endoffileinfo.EndOfFile.QuadPart := Size;
240  res := NtSetInformationFile(Handle, @iostatus, @endoffileinfo,
241           SizeOf(FILE_END_OF_FILE_INFORMATION), FileEndOfFileInformation);
242  if NT_SUCCESS(res) then begin
243    allocinfo.AllocationSize.QuadPart := Size;
244    res := NtSetInformationFile(handle, @iostatus, @allocinfo,
245             SizeOf(FILE_ALLOCATION_INFORMATION), FileAllocationInformation);
246    Result := NT_SUCCESS(res);
247  end else
248    Result := False;
249end;
250
251function NTToDosTime(const NtTime: LARGE_INTEGER): LongInt;
252var
253  userdata: PKUSER_SHARED_DATA;
254  local, bias: LARGE_INTEGER;
255  fields: TIME_FIELDS;
256  zs: LongInt;
257begin
258  userdata := SharedUserData;
259  repeat
260    bias.u.HighPart := userdata^.TimeZoneBias.High1Time;
261    bias.u.LowPart := userdata^.TimeZoneBias.LowPart;
262  until bias.u.HighPart = userdata^.TimeZoneBias.High2Time;
263
264  local.QuadPart := NtTime.QuadPart - bias.QuadPart;
265
266  RtlTimeToTimeFields(@local, @fields);
267
268  { from objpas\datutil.inc\DateTimeToDosDateTime }
269  Result := - 1980;
270  Result := Result + fields.Year and 127;
271  Result := Result shl 4;
272  Result := Result + fields.Month;
273  Result := Result shl 5;
274  Result := Result + fields.Day;
275  Result := Result shl 16;
276  zs := fields.Hour;
277  zs := zs shl 6;
278  zs := zs + fields.Minute;
279  zs := zs shl 5;
280  zs := zs + fields.Second div 2;
281  Result := Result + (zs and $ffff);
282end;
283
284function DosToNtTime(aDTime: LongInt; var aNtTime: LARGE_INTEGER): Boolean;
285var
286  fields: TIME_FIELDS;
287  local, bias: LARGE_INTEGER;
288  userdata: PKUSER_SHARED_DATA;
289begin
290  { from objpas\datutil.inc\DosDateTimeToDateTime }
291  fields.Second := (aDTime and 31) * 2;
292  aDTime := aDTime shr 5;
293  fields.Minute := aDTime and 63;
294  aDTime := aDTime shr 6;
295  fields.Hour := aDTime and 31;
296  aDTime := aDTime shr 5;
297  fields.Day := aDTime and 31;
298  aDTime := aDTime shr 5;
299  fields.Month := aDTime and 15;
300  aDTime := aDTime shr 4;
301  fields.Year := aDTime + 1980;
302
303  Result := RtlTimeFieldsToTime(@fields, @local);
304  if not Result then
305    Exit;
306
307  userdata := SharedUserData;
308  repeat
309    bias.u.HighPart := userdata^.TimeZoneBias.High1Time;
310    bias.u.LowPart := userdata^.TimeZoneBias.LowPart;
311  until bias.u.HighPart = userdata^.TimeZoneBias.High2Time;
312
313  aNtTime.QuadPart := local.QuadPart + bias.QuadPart;
314end;
315
316function FileAge(const FileName: UnicodeString): Longint;
317begin
318  { TODO }
319  Result := -1;
320end;
321
322
323function FileGetSymLinkTarget(const FileName: UnicodeString; out SymLinkRec: TUnicodeSymLinkRec): Boolean;
324begin
325  Result := False;
326end;
327
328
329function FileExists(const FileName: UnicodeString; FollowLink : Boolean): Boolean;
330var
331  ntstr: UNICODE_STRING;
332  objattr: OBJECT_ATTRIBUTES;
333  res: NTSTATUS;
334  iostatus: IO_STATUS_BLOCK;
335  h: THandle;
336begin
337  UnicodeStrToNtStr(FileName, ntstr);
338  InitializeObjectAttributes(objattr, @ntstr, 0, 0, Nil);
339  res := NtOpenFile(@h, FILE_READ_ATTRIBUTES or NT_SYNCHRONIZE, @objattr,
340           @iostatus, FILE_SHARE_READ or FILE_SHARE_WRITE,
341           FILE_NON_DIRECTORY_FILE or FILE_SYNCHRONOUS_IO_NONALERT);
342  Result := NT_SUCCESS(res);
343
344  if Result then
345    NtClose(h);
346  FreeNtStr(ntstr);
347end;
348
349
350function DirectoryExists(const Directory : UnicodeString; FollowLink : Boolean) : Boolean;
351var
352  ntstr: UNICODE_STRING;
353  objattr: OBJECT_ATTRIBUTES;
354  res: NTSTATUS;
355  iostatus: IO_STATUS_BLOCK;
356  h: THandle;
357begin
358  UnicodeStrToNtStr(Directory, ntstr);
359  InitializeObjectAttributes(objattr, @ntstr, 0, 0, Nil);
360
361  { first test wether this is a object directory }
362  res := NtOpenDirectoryObject(@h, DIRECTORY_QUERY, @objattr);
363  if NT_SUCCESS(res) then
364    Result := True
365  else begin
366    if res = STATUS_OBJECT_TYPE_MISMATCH then begin
367      { this is a file object! }
368      res := NtOpenFile(@h, FILE_READ_ATTRIBUTES or NT_SYNCHRONIZE, @objattr,
369               @iostatus, FILE_SHARE_READ or FILE_SHARE_WRITE,
370               FILE_DIRECTORY_FILE or FILE_SYNCHRONOUS_IO_NONALERT);
371      Result := NT_SUCCESS(res);
372    end else
373      Result := False;
374  end;
375
376  if Result then
377    NtClose(h);
378  FreeNtStr(ntstr);
379end;
380
381{ copied from rtl/unix/sysutils.pp and adapted to UTF-16 }
382Function FNMatch(const Pattern,Name:UnicodeString):Boolean;
383Var
384  LenPat,LenName : longint;
385
386  function NameUtf16CodePointLen(index: longint): longint;
387    begin
388      { see https://en.wikipedia.org/wiki/UTF-16#Description for details }
389      Result:=1;
390      { valid surrogate pair? }
391      if (Name[index]>=#$D800) and
392         (Name[index]<=#$DBFF) then
393        begin
394          if (index+1<=LenName) and
395             (Name[index+1]>=#$DC00) and
396             (Name[index+1]<=#$DFFF) then
397            inc(Result)
398          else
399            exit;
400        end;
401      { combining diacritics?
402          1) U+0300 - U+036F
403          2) U+1DC0 - U+1DFF
404          3) U+20D0 - U+20FF
405          4) U+FE20 - U+FE2F
406      }
407      while (index+Result+1<=LenName) and
408            ((word(ord(Name[index+Result+1])-$0300) <= word($036F-$0300)) or
409             (word(ord(Name[index+Result+1])-$1DC0) <= word($1DFF-$1DC0)) or
410             (word(ord(Name[index+Result+1])-$20D0) <= word($20FF-$20D0)) or
411             (word(ord(Name[index+Result+1])-$FE20) <= word($FE2F-$FE20))) do
412        begin
413          inc(Result)
414        end;
415    end;
416
417    procedure GoToLastByteOfUtf16CodePoint(var j: longint);
418    begin
419      { Take one less, because we have to stop at the last word of the sequence.
420      }
421      inc(j,NameUtf16CodePointLen(j)-1);
422    end;
423
424  { input:
425      i: current position in pattern (start of utf-16 code point)
426      j: current position in name (start of utf-16 code point)
427      update_i_j: should i and j be changed by the routine or not
428
429    output:
430      i: if update_i_j, then position of last matching part of code point in
431         pattern, or first non-matching code point in pattern. Otherwise the
432         same value as on input.
433      j: if update_i_j, then position of last matching part of code point in
434         name, or first non-matching code point in name. Otherwise the
435         same value as on input.
436      result: true if match, false if no match
437  }
438  function CompareUtf16CodePoint(var i,j: longint; update_i_j: boolean): Boolean;
439    var
440      words,
441      new_i,
442      new_j: longint;
443    begin
444      words:=NameUtf16CodePointLen(j);
445      new_i:=i;
446      new_j:=j;
447      { ensure that a part of an UTF-8 codepoint isn't interpreted
448        as '*' or '?' }
449      repeat
450        dec(words);
451        Result:=
452          (new_j<=LenName) and
453          (new_i<=LenPat) and
454          (Pattern[new_i]=Name[new_j]);
455        inc(new_i);
456        inc(new_j);
457      until not(Result) or
458            (words=0);
459      if update_i_j then
460        begin
461          i:=new_i;
462          j:=new_j;
463        end;
464    end;
465
466
467  Function DoFNMatch(i,j:longint):Boolean;
468  Var
469    Found : boolean;
470  Begin
471    Found:=true;
472    While Found and (i<=LenPat) Do
473     Begin
474       Case Pattern[i] of
475        '?' :
476          begin
477            Found:=(j<=LenName);
478            GoToLastByteOfUtf16CodePoint(j);
479          end;
480        '*' : Begin
481              {find the next character in pattern, different of ? and *}
482                while Found do
483                  begin
484                    inc(i);
485                    if i>LenPat then
486                      Break;
487                    case Pattern[i] of
488                      '*' : ;
489                      '?' : begin
490                              if j>LenName then
491                                begin
492                                  DoFNMatch:=false;
493                                  Exit;
494                                end;
495                              GoToLastByteOfUtf16CodePoint(j);
496                              inc(j);
497                            end;
498                      else
499                        Found:=false;
500                      end;
501                 end;
502                Assert((i>LenPat) or ( (Pattern[i]<>'*') and (Pattern[i]<>'?') ));
503                { Now, find in name the character which i points to, if the * or
504                  ? wasn't the last character in the pattern, else, use up all
505                  the chars in name }
506                Found:=false;
507                if (i<=LenPat) then
508                  begin
509                    repeat
510                      {find a letter (not only first !) which maches pattern[i]}
511                      while (j<=LenName) and
512                            ((name[j]<>pattern[i]) or
513                             not CompareUtf16CodePoint(i,j,false)) do
514                        begin
515                          GoToLastByteOfUtf16CodePoint(j);
516                          inc(j);
517                        end;
518                      if (j<LenName) then
519                        begin
520                          { while positions i/j have already been checked, we have to
521                            ensure that we don't split a code point }
522                          if DoFnMatch(i,j) then
523                            begin
524                              i:=LenPat;
525                              j:=LenName;{we can stop}
526                              Found:=true;
527                              Break;
528                            end
529                          { We didn't find one, need to look further }
530                          else
531                            begin
532                              GoToLastByteOfUtf16CodePoint(j);
533                              inc(j);
534                            end;
535                        end
536                      else if j=LenName then
537                        begin
538                          Found:=true;
539                          Break;
540                        end;
541                      { This 'until' condition must be j>LenName, not j>=LenName.
542                        That's because when we 'need to look further' and
543                        j = LenName then loop must not terminate. }
544                    until (j>LenName);
545                  end
546                else
547                  begin
548                    j:=LenName;{we can stop}
549                    Found:=true;
550                  end;
551              end;
552        #$D800..#$DBFF:
553          begin
554            { ensure that a part of an UTF-16 codepoint isn't matched with
555              '*' or '?' }
556            Found:=CompareUtf16CodePoint(i,j,true);
557            { at this point, either Found is false (and we'll stop), or
558              both pattern[i] and name[j] are the end of the current code
559              point and equal }
560          end
561       else {not a wildcard character in pattern}
562         Found:=(j<=LenName) and (pattern[i]=name[j]);
563       end;
564       inc(i);
565       inc(j);
566     end;
567    DoFnMatch:=Found and (j>LenName);
568  end;
569
570Begin {start FNMatch}
571  LenPat:=Length(Pattern);
572  LenName:=Length(Name);
573  FNMatch:=DoFNMatch(1,1);
574End;
575
576
577function FindGetFileInfo(const s: UnicodeString; var f: TAbstractSearchRec; var Name: UnicodeString): Boolean;
578var
579  ntstr: UNICODE_STRING;
580  objattr: OBJECT_ATTRIBUTES;
581  res: NTSTATUS;
582  h: THandle;
583  iostatus: IO_STATUS_BLOCK;
584  attr: LongInt;
585  filename: UnicodeString;
586  isfileobj: Boolean;
587  objinfo: OBJECT_BASIC_INFORMATION;
588  fileinfo: FILE_BASIC_INFORMATION;
589  time: LongInt;
590begin
591  UnicodeStrToNtStr(s, ntstr);
592  InitializeObjectAttributes(objattr, @ntstr, 0, 0, Nil);
593
594  filename := ExtractFileName(s);
595
596  { TODO : handle symlinks }
597{  If Assigned(F.FindHandle) and ((((PUnixFindData(f.FindHandle)^.searchattr)) and faSymlink) > 0) then
598    FindGetFileInfo:=(fplstat(pointer(s),st)=0)
599  else
600    FindGetFileInfo:=(fpstat(pointer(s),st)=0);}
601
602  attr := 0;
603  Result := False;
604
605  if (faDirectory and f.FindData.SearchAttr <> 0) and
606      ((filename = '.') or (filename = '..')) then begin
607    attr := faDirectory;
608    res := STATUS_SUCCESS;
609  end else
610    res := STATUS_INVALID_PARAMETER;
611
612  isfileobj := False;
613
614  if not NT_SUCCESS(res) then begin
615    { first check whether it's a directory }
616    res := NtOpenDirectoryObject(@h, DIRECTORY_QUERY, @objattr);
617    if not NT_SUCCESS(res) then
618      if res = STATUS_OBJECT_TYPE_MISMATCH then begin
619        res := NtOpenFile(@h, FILE_READ_ATTRIBUTES or NT_SYNCHRONIZE, @objattr,
620                 @iostatus, FILE_SHARE_READ or FILE_SHARE_WRITE,
621                 FILE_DIRECTORY_FILE or FILE_SYNCHRONOUS_IO_NONALERT);
622        isfileobj := NT_SUCCESS(res);
623      end;
624
625    if NT_SUCCESS(res) then
626      attr := faDirectory;
627  end;
628
629  if not NT_SUCCESS(res) then begin
630    { first try whether we have a file object }
631    res := NtOpenFile(@h, FILE_READ_ATTRIBUTES or NT_SYNCHRONIZE, @objattr,
632             @iostatus, FILE_SHARE_READ or FILE_SHARE_WRITE,
633             FILE_NON_DIRECTORY_FILE or FILE_SYNCHRONOUS_IO_NONALERT);
634    isfileobj := NT_SUCCESS(res);
635    if res = STATUS_OBJECT_TYPE_MISMATCH then begin
636      { is this an object? }
637      res := NtOpenFile(@h, FILE_READ_ATTRIBUTES or NT_SYNCHRONIZE, @objattr,
638               @iostatus, FILE_SHARE_READ or FILE_SHARE_WRITE,
639               FILE_SYNCHRONOUS_IO_NONALERT);
640      if (res = STATUS_OBJECT_TYPE_MISMATCH)
641          and (f.FindData.SearchAttr and faSysFile <> 0) then begin
642        { this is some other system file like an event or port, so we can only
643          provide it's name }
644        res := STATUS_SUCCESS;
645        attr := faSysFile;
646      end;
647    end;
648  end;
649
650  FreeNtStr(ntstr);
651
652  if not NT_SUCCESS(res) then
653    Exit;
654
655  time := 0;
656
657  if isfileobj then begin
658    res := NtQueryInformationFile(h, @iostatus, @fileinfo, SizeOf(fileinfo),
659             FileBasicInformation);
660    if NT_SUCCESS(res) then begin
661      time := NtToDosTime(fileinfo.LastWriteTime);
662      { copy file attributes? }
663    end;
664  end else begin
665    res := NtQueryObject(h, ObjectBasicInformation, @objinfo, SizeOf(objinfo),
666             Nil);
667    if NT_SUCCESS(res) then begin
668      time := NtToDosTime(objinfo.CreateTime);
669      { what about attributes? }
670    end;
671  end;
672
673  if (attr and not f.FindData.SearchAttr) = 0 then begin
674    Name := filename;
675    f.Attr := attr;
676    f.Size := 0;
677{$ifndef FPUNONE}
678    if time = 0 then
679      { for now we use "Now" as a fall back; ideally this should be the system
680        start time }
681      f.Time := DateTimeToFileDate(Now)
682    else
683      f.Time := time;
684{$endif}
685    Result := True;
686  end else
687    Result := False;
688
689  NtClose(h);
690end;
691
692
693Procedure InternalFindClose (var Handle: THandle; var FindData: TFindData);
694begin
695  if FindData.Handle <> 0 then
696    begin
697      NtClose(FindData.Handle);
698      FindData.Handle:=0;
699    end;
700end;
701
702
703Function InternalFindNext (Var Rslt : TAbstractSearchRec; var Name: UnicodeString) : Longint;
704{
705  re-opens dir if not already in array and calls FindGetFileInfo
706}
707Var
708  DirName  : UnicodeString;
709  FName,
710  SName    : UnicodeString;
711  Found,
712  Finished : boolean;
713  ntstr: UNICODE_STRING;
714  objattr: OBJECT_ATTRIBUTES;
715  buf: array of WideChar;
716  len: LongWord;
717  res: NTSTATUS;
718  i: LongInt;
719  dirinfo: POBJECT_DIRECTORY_INFORMATION;
720  filedirinfo: PFILE_DIRECTORY_INFORMATION;
721  pc: PChar;
722  filename: UnicodeString;
723  iostatus: IO_STATUS_BLOCK;
724begin
725  { TODO : relative directories }
726  Result := -1;
727  { SearchSpec='' means that there were no wild cards, so only one file to
728    find.
729  }
730  if Rslt.FindData.SearchSpec = '' then
731    Exit;
732  { relative directories not supported for now }
733  if Rslt.FindData.NamePos = 0 then
734    Exit;
735
736  if Rslt.FindData.Handle = 0 then begin
737    if Rslt.FindData.NamePos > 1 then
738      filename := Copy(Rslt.FindData.SearchSpec, 1, Rslt.FindData.NamePos - 1)
739    else
740    if Rslt.FindData.NamePos = 1 then
741      filename := Copy(Rslt.FindData.SearchSpec, 1, 1)
742    else
743      filename := Rslt.FindData.SearchSpec;
744    UnicodeStrToNtStr(filename, ntstr);
745    InitializeObjectAttributes(objattr, @ntstr, 0, 0, Nil);
746
747    res := NtOpenDirectoryObject(@Rslt.FindData.Handle,
748             DIRECTORY_QUERY or DIRECTORY_TRAVERSE, @objattr);
749    if not NT_SUCCESS(res) then begin
750      if res = STATUS_OBJECT_TYPE_MISMATCH then
751        res := NtOpenFile(@Rslt.FindData.Handle,
752                 FILE_LIST_DIRECTORY or NT_SYNCHRONIZE, @objattr,
753                 @iostatus, FILE_SHARE_READ or FILE_SHARE_WRITE,
754                 FILE_DIRECTORY_FILE or FILE_SYNCHRONOUS_IO_NONALERT);
755    end else
756      Rslt.FindData.IsDirObj := True;
757
758    FreeNTStr(ntstr);
759
760    if not NT_SUCCESS(res) then
761      Exit;
762  end;
763{  if (NTFindData^.SearchType = 0) and
764     (NTFindData^.Dirptr = Nil) then
765    begin
766      If NTFindData^.NamePos = 0 Then
767        DirName:='./'
768      Else
769        DirName:=Copy(NTFindData^.SearchSpec,1,NTFindData^.NamePos);
770      NTFindData^.DirPtr := fpopendir(Pchar(pointer(DirName)));
771    end;}
772  SName := Copy(Rslt.FindData.SearchSpec, Rslt.FindData.NamePos + 1,
773             Length(Rslt.FindData.SearchSpec));
774  Found := False;
775  Finished := not NT_SUCCESS(Rslt.FindData.LastRes)
776              or (Rslt.FindData.LastRes = STATUS_NO_MORE_ENTRIES);
777  SetLength(buf, 200);
778  dirinfo := @buf[0];
779  filedirinfo := @buf[0];
780  while not Finished do begin
781    if Rslt.FindData.IsDirObj then
782      res := NtQueryDirectoryObject(Rslt.FindData.Handle, @buf[0],
783               Length(buf) * SizeOf(buf[0]), True, False,
784               @Rslt.FindData.Context, @len)
785    else
786      res := NtQueryDirectoryFile(Rslt.FindData.Handle, 0, Nil, Nil, @iostatus,
787               @buf[0], Length(buf) * SizeOf(buf[0]), FileDirectoryInformation,
788               True, Nil, False);
789    if Rslt.FindData.IsDirObj then begin
790      Finished := (res = STATUS_NO_MORE_ENTRIES)
791                    or (res = STATUS_NO_MORE_FILES)
792                    or not NT_SUCCESS(res);
793      Rslt.FindData.LastRes := res;
794      if dirinfo^.Name.Length > 0 then begin
795        SetLength(FName, dirinfo^.Name.Length div 2);
796        move(dirinfo^.Name.Buffer[0],FName[1],dirinfo^.Name.Length);
797{$ifdef debug_findnext}
798        Write(FName, ' (');
799        for i := 0 to dirinfo^.TypeName.Length div 2 - 1 do
800          if dirinfo^.TypeName.Buffer[i] < #256 then
801            Write(AnsiChar(Byte(dirinfo^.TypeName.Buffer[i])))
802          else
803            Write('?');
804        Writeln(')');
805{$endif debug_findnext}
806      end else
807        FName := '';
808    end else begin
809      SetLength(FName, filedirinfo^.FileNameLength div 2);
810      move(filedirinfo^.FileName[0],FName[1],filedirinfo^.FileNameLength);
811    end;
812    if FName = '' then
813      Finished := True
814    else begin
815      if FNMatch(SName, FName) then begin
816        Found := FindGetFileInfo(Copy(Rslt.FindData.SearchSpec, 1,
817                   Rslt.FindData.NamePos) + FName, Rslt, Name);
818        if Found then begin
819          Result := 0;
820          Exit;
821        end;
822      end;
823    end;
824  end;
825end;
826
827
828Function InternalFindFirst (Const Path : UnicodeString; Attr : Longint; out Rslt : TAbstractSearchRec; var Name : UnicodeString) : Longint;
829{
830  opens dir and calls FindNext if needed.
831}
832Begin
833  Result := -1;
834  if Path = '' then
835    Exit;
836  Rslt.FindData.SearchAttr := Attr;
837  {Wildcards?}
838  if (Pos('?', Path) = 0) and (Pos('*', Path) = 0) then begin
839    if FindGetFileInfo(Path, Rslt, Name) then
840      Result := 0;
841  end else begin
842    {Create Info}
843    Rslt.FindData.SearchSpec := Path;
844    Rslt.FindData.NamePos := Length(Rslt.FindData.SearchSpec);
845    while (Rslt.FindData.NamePos > 0)
846        and (Rslt.FindData.SearchSpec[Rslt.FindData.NamePos] <> DirectorySeparator)
847        do
848      Dec(Rslt.FindData.NamePos);
849    Result := InternalFindNext(Rslt,Name);
850  end;
851  if Result <> 0 then
852    InternalFindClose(Rslt.FindHandle,Rslt.FindData);
853end;
854
855
856function FileGetDate(Handle: THandle): Longint;
857var
858  res: NTSTATUS;
859  basic: FILE_BASIC_INFORMATION;
860  iostatus: IO_STATUS_BLOCK;
861begin
862  res := NtQueryInformationFile(Handle, @iostatus, @basic,
863           SizeOf(FILE_BASIC_INFORMATION), FileBasicInformation);
864  if NT_SUCCESS(res) then
865    Result := NtToDosTime(basic.LastWriteTime)
866  else
867    Result := -1;
868end;
869
870
871function FileSetDate(Handle: THandle;Age: Longint): Longint;
872var
873  res: NTSTATUS;
874  basic: FILE_BASIC_INFORMATION;
875  iostatus: IO_STATUS_BLOCK;
876begin
877  res := NtQueryInformationFile(Handle, @iostatus, @basic,
878           SizeOf(FILE_BASIC_INFORMATION), FileBasicInformation);
879  if NT_SUCCESS(res) then begin
880    if not DosToNtTime(Age, basic.LastWriteTime) then begin
881      Result := -1;
882      Exit;
883    end;
884
885    res := NtSetInformationFile(Handle, @iostatus, @basic,
886             SizeOf(FILE_BASIC_INFORMATION), FileBasicInformation);
887    if NT_SUCCESS(res) then
888      Result := 0
889    else
890      Result := res;
891  end else
892    Result := res;
893end;
894
895
896function FileGetAttr(const FileName: UnicodeString): Longint;
897var
898  objattr: OBJECT_ATTRIBUTES;
899  info: FILE_NETWORK_OPEN_INFORMATION;
900  res: NTSTATUS;
901  ntstr: UNICODE_STRING;
902begin
903  UnicodeStrToNtStr(FileName, ntstr);
904  InitializeObjectAttributes(objattr, @ntstr, 0, 0, Nil);
905
906  res := NtQueryFullAttributesFile(@objattr, @info);
907  if NT_SUCCESS(res) then
908    Result := info.FileAttributes
909  else
910    Result := 0;
911
912  FreeNtStr(ntstr);
913end;
914
915
916function FileSetAttr(const Filename: UnicodeString; Attr: LongInt): Longint;
917var
918  h: THandle;
919  objattr: OBJECT_ATTRIBUTES;
920  ntstr: UNICODE_STRING;
921  basic: FILE_BASIC_INFORMATION;
922  res: NTSTATUS;
923  iostatus: IO_STATUS_BLOCK;
924begin
925  UnicodeStrToNtStr(Filename, ntstr);
926  InitializeObjectAttributes(objattr, @ntstr, 0, 0, Nil);
927  res := NtOpenFile(@h,
928           NT_SYNCHRONIZE or FILE_READ_ATTRIBUTES or FILE_WRITE_ATTRIBUTES,
929           @objattr, @iostatus,
930           FILE_SHARE_READ or FILE_SHARE_WRITE or FILE_SHARE_DELETE,
931           FILE_SYNCHRONOUS_IO_NONALERT);
932
933  FreeNtStr(ntstr);
934
935  if NT_SUCCESS(res) then begin
936    res := NtQueryInformationFile(h, @iostatus, @basic,
937             SizeOf(FILE_BASIC_INFORMATION), FileBasicInformation);
938
939    if NT_SUCCESS(res) then begin
940      basic.FileAttributes := Attr;
941      Result := NtSetInformationFile(h, @iostatus, @basic,
942                  SizeOf(FILE_BASIC_INFORMATION), FileBasicInformation);
943    end;
944
945    NtClose(h);
946  end else
947    Result := res;
948end;
949
950
951function DeleteFile(const FileName: UnicodeString): Boolean;
952var
953  h: THandle;
954  objattr: OBJECT_ATTRIBUTES;
955  ntstr: UNICODE_STRING;
956  dispinfo: FILE_DISPOSITION_INFORMATION;
957  res: NTSTATUS;
958  iostatus: IO_STATUS_BLOCK;
959begin
960  UnicodeStrToNtStr(Filename, ntstr);
961  InitializeObjectAttributes(objattr, @ntstr, 0, 0, Nil);
962  res := NtOpenFile(@h, NT_DELETE, @objattr, @iostatus,
963           FILE_SHARE_READ or FILE_SHARE_WRITE or FILE_SHARE_DELETE,
964           FILE_NON_DIRECTORY_FILE);
965
966  FreeNtStr(ntstr);
967
968  if NT_SUCCESS(res) then begin
969    dispinfo.DeleteFile := True;
970
971    res := NtSetInformationFile(h, @iostatus, @dispinfo,
972             SizeOf(FILE_DISPOSITION_INFORMATION), FileDispositionInformation);
973
974    Result := NT_SUCCESS(res);
975
976    NtClose(h);
977  end else
978    Result := False;
979end;
980
981
982function RenameFile(const OldName, NewName: UnicodeString): Boolean;
983var
984  h: THandle;
985  objattr: OBJECT_ATTRIBUTES;
986  iostatus: IO_STATUS_BLOCK;
987  dest, src: UNICODE_STRING;
988  renameinfo: PFILE_RENAME_INFORMATION;
989  res: LongInt;
990begin
991  { check whether the destination exists first }
992  UnicodeStrToNtStr(NewName, dest);
993  InitializeObjectAttributes(objattr, @dest, 0, 0, Nil);
994
995  res := NtCreateFile(@h, 0, @objattr, @iostatus, Nil, 0,
996           FILE_SHARE_READ or FILE_SHARE_WRITE, FILE_OPEN,
997           FILE_NON_DIRECTORY_FILE, Nil, 0);
998  if NT_SUCCESS(res) then begin
999    { destination already exists => error }
1000    NtClose(h);
1001    Result := False;
1002  end else begin
1003    UnicodeStrToNtStr(OldName, src);
1004    InitializeObjectAttributes(objattr, @src, 0, 0, Nil);
1005
1006    res := NtCreateFile(@h,
1007             GENERIC_ALL or NT_SYNCHRONIZE or FILE_READ_ATTRIBUTES,
1008             @objattr, @iostatus, Nil, 0, FILE_SHARE_READ or FILE_SHARE_WRITE,
1009             FILE_OPEN, FILE_OPEN_FOR_BACKUP_INTENT or FILE_OPEN_REMOTE_INSTANCE
1010             or FILE_NON_DIRECTORY_FILE or FILE_SYNCHRONOUS_IO_NONALERT, Nil,
1011             0);
1012
1013    if NT_SUCCESS(res) then begin
1014      renameinfo := GetMem(SizeOf(FILE_RENAME_INFORMATION) + dest.Length);
1015      with renameinfo^ do begin
1016        ReplaceIfExists := False;
1017        RootDirectory := 0;
1018        FileNameLength := dest.Length;
1019        Move(dest.Buffer^, renameinfo^.FileName, dest.Length);
1020      end;
1021
1022      res := NtSetInformationFile(h, @iostatus, renameinfo,
1023               SizeOf(FILE_RENAME_INFORMATION) + dest.Length,
1024               FileRenameInformation);
1025      if not NT_SUCCESS(res) then begin
1026        { this could happen if src and destination reside on different drives,
1027          so we need to copy the file manually }
1028        {$message warning 'RenameFile: Implement file copy!'}
1029        Result := False;
1030      end else
1031        Result := True;
1032
1033      NtClose(h);
1034    end else
1035      Result := False;
1036
1037    FreeNtStr(src);
1038  end;
1039
1040  FreeNtStr(dest);
1041end;
1042
1043
1044{****************************************************************************
1045                              Disk Functions
1046****************************************************************************}
1047
1048function diskfree(drive: byte): int64;
1049begin
1050  { here the mount manager needs to be queried }
1051  Result := -1;
1052end;
1053
1054
1055function disksize(drive: byte): int64;
1056begin
1057  { here the mount manager needs to be queried }
1058  Result := -1;
1059end;
1060
1061
1062{****************************************************************************
1063                              Time Functions
1064****************************************************************************}
1065
1066
1067procedure GetLocalTime(var SystemTime: TSystemTime);
1068var
1069  bias, syst: LARGE_INTEGER;
1070  fields: TIME_FIELDS;
1071  userdata: PKUSER_SHARED_DATA;
1072begin
1073  // get UTC time
1074  userdata := SharedUserData;
1075  repeat
1076    syst.u.HighPart := userdata^.SystemTime.High1Time;
1077    syst.u.LowPart := userdata^.SystemTime.LowPart;
1078  until syst.u.HighPart = userdata^.SystemTime.High2Time;
1079
1080  // adjust to local time
1081  repeat
1082    bias.u.HighPart := userdata^.TimeZoneBias.High1Time;
1083    bias.u.LowPart := userdata^.TimeZoneBias.LowPart;
1084  until bias.u.HighPart = userdata^.TimeZoneBias.High2Time;
1085  syst.QuadPart := syst.QuadPart - bias.QuadPart;
1086
1087  RtlTimeToTimeFields(@syst, @fields);
1088
1089  SystemTime.Year := fields.Year;
1090  SystemTime.Month := fields.Month;
1091  SystemTime.Day := fields.Day;
1092  SystemTime.Hour := fields.Hour;
1093  SystemTime.Minute := fields.Minute;
1094  SystemTime.Second := fields.Second;
1095  SystemTime.Millisecond := fields.MilliSeconds;
1096end;
1097
1098
1099{****************************************************************************
1100                              Misc Functions
1101****************************************************************************}
1102
1103procedure sysbeep;
1104begin
1105  { empty }
1106end;
1107
1108procedure InitInternational;
1109begin
1110  InitInternationalGeneric;
1111end;
1112
1113
1114{****************************************************************************
1115                           Target Dependent
1116****************************************************************************}
1117
1118function SysErrorMessage(ErrorCode: Integer): String;
1119begin
1120  Result := 'NT error code: 0x' + IntToHex(ErrorCode, 8);
1121end;
1122
1123{****************************************************************************
1124                              Initialization code
1125****************************************************************************}
1126
1127function wstrlen(p: PWideChar): SizeInt; external name 'FPC_PWIDECHAR_LENGTH';
1128
1129function GetEnvironmentVariable(const EnvVar: String): String;
1130var
1131   s, upperenvvar : UTF8String;
1132   i : longint;
1133   hp: pwidechar;
1134   len: sizeint;
1135begin
1136   { TODO : test once I know how to execute processes }
1137   Result:='';
1138   hp:=PPEB(CurrentPEB)^.ProcessParameters^.Environment;
1139   { first convert to UTF-8, then uppercase in order to avoid potential data
1140     loss }
1141   upperenvvar:=EnvVar;
1142   upperenvvar:=UpperCase(upperenvvar);
1143   while hp^<>#0 do
1144     begin
1145        len:=UnicodeToUTF8(Nil, hp, 0);
1146        SetLength(s,len);
1147        UnicodeToUTF8(PChar(s), hp, len);
1148        i:=pos('=',s);
1149        if uppercase(copy(s,1,i-1))=upperenvvar then
1150          begin
1151             { copy() returns a rawbytestring -> will keep UTF-8 encoding }
1152             Result:=copy(s,i+1,length(s)-i);
1153             break;
1154          end;
1155        { next string entry}
1156        hp:=hp+wstrlen(hp)+1;
1157     end;
1158end;
1159
1160function GetEnvironmentVariableCount: Integer;
1161var
1162  hp : pwidechar;
1163begin
1164  Result:=0;
1165  hp:=PPEB(CurrentPEB)^.ProcessParameters^.Environment;
1166  If (Hp<>Nil) then
1167    while hp^<>#0 do
1168      begin
1169      Inc(Result);
1170      hp:=hp+wstrlen(hp)+1;
1171      end;
1172end;
1173
1174function GetEnvironmentString(Index: Integer): {$ifdef FPC_RTL_UNICODE}UnicodeString{$else}AnsiString{$endif};
1175var
1176  hp : pwidechar;
1177  len: sizeint;
1178begin
1179  Result:='';
1180  hp:=PPEB(CurrentPEB)^.ProcessParameters^.Environment;
1181  If (Hp<>Nil) then
1182    begin
1183    while (hp^<>#0) and (Index>1) do
1184      begin
1185        Dec(Index);
1186        hp:=hp+wstrlen(hp)+1;
1187      end;
1188    If (hp^<>#0) then
1189      begin
1190{$ifdef FPC_RTL_UNICODE}
1191        Result:=hp;
1192{$else}
1193        len:=UnicodeToUTF8(Nil, hp, 0);
1194        SetLength(Result, len);
1195        UnicodeToUTF8(PChar(Result), hp, len);
1196        SetCodePage(RawByteString(Result),CP_UTF8,false);
1197{$endif}
1198      end;
1199    end;
1200end;
1201
1202
1203function ExecuteProcess(const Path: RawByteString; const ComLine: RawByteString;
1204  Flags: TExecuteFlags = []): Integer;
1205begin
1206  { TODO : implement }
1207  Result := 0;
1208end;
1209
1210function ExecuteProcess(const Path: RawByteString;
1211  const ComLine: Array of RawByteString; Flags:TExecuteFlags = []): Integer;
1212var
1213  CommandLine: RawByteString;
1214  I: integer;
1215begin
1216  Commandline := '';
1217  for I := 0 to High (ComLine) do
1218   if Pos (' ', ComLine [I]) <> 0 then
1219    CommandLine := CommandLine + ' ' + '"' + ComLine [I] + '"'
1220   else
1221    CommandLine := CommandLine + ' ' + Comline [I];
1222  ExecuteProcess := ExecuteProcess (Path, CommandLine,Flags);
1223end;
1224
1225function ExecuteProcess(const Path: UnicodeString; const ComLine: UnicodeString;
1226  Flags: TExecuteFlags = []): Integer;
1227begin
1228  { TODO : implement }
1229  Result := 0;
1230end;
1231
1232function ExecuteProcess(const Path: UnicodeString;
1233  const ComLine: Array of UnicodeString; Flags:TExecuteFlags = []): Integer;
1234var
1235  CommandLine: UnicodeString;
1236  I: integer;
1237begin
1238  Commandline := '';
1239  for I := 0 to High (ComLine) do
1240   if Pos (' ', ComLine [I]) <> 0 then
1241    CommandLine := CommandLine + ' ' + '"' + ComLine [I] + '"'
1242   else
1243    CommandLine := CommandLine + ' ' + Comline [I];
1244  ExecuteProcess := ExecuteProcess (Path, CommandLine,Flags);
1245end;
1246
1247procedure Sleep(Milliseconds: Cardinal);
1248const
1249  DelayFactor = 10000;
1250var
1251  interval: LARGE_INTEGER;
1252begin
1253  interval.QuadPart := - Milliseconds * DelayFactor;
1254  NtDelayExecution(False, @interval);
1255end;
1256
1257{****************************************************************************
1258                              Initialization code
1259****************************************************************************}
1260
1261initialization
1262  InitExceptions;       { Initialize exceptions. OS independent }
1263  InitInternational;    { Initialize internationalization settings }
1264  OnBeep := @SysBeep;
1265finalization
1266  FreeTerminateProcs;
1267  DoneExceptions;
1268end.
1269