{ Hi, here's another contribution - an LFN unit which is actually useful. It allows working with near-normal TP/TPW commands, transparently on LFN and non-LFN disks. Enjoy. Eyal Doron } {$IFDEF WINDOWS} {$N-,V-,W-,G+} {$ELSE} {N-,V-,G+} {$ENDIF} Unit lfnunit; {========================================================================} { LFNUnit - A long filename support unit for TP6 and TPW1.5. } { Written by Eyal Doron, doron@physics,technion.ac.il, June 1997. } { Released into the public domain. } { } { This is a unit to support long filenames in Win95 and WinNT, for use } { in ordinary 16-bit programs in Turbo Pascal 6.0 and Turbo Pascal for } { Windows 1.5. It should be a simple matter to adapt to TP/BP 7 as well. } { The unit is built to support LFN if available, and the usual FAT16 } { format if not, in a transparent manner, i.e. the programmer should not } { worry whether LFN is supported or not, the routines work the same in } { both cases. The unit is not complete, in the sense that not all of the } { interrupts are supported, but the main thrust is the enhancement of } { the Turbo Pascal I/O scheme to support LFN in as natural a way as } { possible. } { } { The unit contains three families of procedures and functions: } { 1) Basic LFN API: This is a set of procedures and functions that give } { access to the LFN interrupts: FindFirst/Next/Close, short and long } { names, time, attributes and creation. } { 2) Service routines. These routines make use of the LFN API to mimic } { the operation of the DOS or WinDos supplied routines, only with LFN } { support. I chose to mimic the TP6.0 routines, rather than the TPW } { ones, because I prefer Pascal-type strings to C-type strings, but } { its a simple matter to add these as well. All the routines return } { their error codes inside the DOS/WinDos global "DosError". } { 3) Input-output support. This set of procedures and functions defines } { the paradigm for LFN support in Turbo Pascal. It "rides" on top of } { the usual file variables ("file", "file of" and "text"), and stores } { the additional LFN info in the UserData field of the file records. } { The routines have an interface which is similar to the TP ones, } { namely "LFNAssign" is equivalent to "Assign", "LFNRewrite" to } { "Rewrite", and so on. This paradigm enables use of the usual Pascal } { I/O scheme and routines with long file names, in an almost } { transparent manner. The differences are: } { a) Before using a file variable it has to be initialized by calling } { LFNNew. After you are done with it, you should call LFNDispose } { to free the allocated memory. } { b) You MUST consistently use LFNNew, LFNAssign, LFNRewrite, } { LFNRename and LFNDispose in order to support LFN. The other } { routines are optional, providing error detection and consistent } { error trapping, but the TP equivalents should also work. } { c) LFNReset, LFNRewrite and LFNAppend always accept a RecLen } { parameter, which is optional in Reset and Rewrite and missing in } { Append. This is because TP does not support overloading. The } { parameter is ignored for text files and when it is zero. } { d) LFNAppend differs from Append also in that if the file does not } { exist, Append reports a DOS error, while LFNAppend creates it } { using LFNRewrite. } { e) LFNFindFirst/LFNFindNext return the name as an AsciiZ string, } { not a Pascal string, even in TP6, for the sake of consistency. } { f) All the routines return the error code in the DosError global } { Dos/WinDos variable, and most of them also return it as a } { functional result. Additionally, the "LFNRuntimeErrors" global } { boolean variable controls the generation of runtime errors. } { } { Comments, bug reports, etc. are welcome. } {========================================================================} Interface Uses {$IFDEF WINDOWS} WinDos,WObjects,WinTypes,WinProcs,strings; {$ELSE} Dos,Objects; {$ENDIF} const ShortPathName = 79; LFNRuntimeErrors: boolean = false; { Determines if runtime errors are generated } LFNErr_Uninitialized = 120; { LFN routines called before LFNAssign } LFNErr_NotAllocated = 121; { LFN routines called before LFNNew } LFNErr_NotATextFile = 122; { Appending to a non-text file } {$IFDEF WINDOWS} ofn_LongNames = $00200000; { Required to support LFN in the common dialogs. } { OR it into the Flags record of TOpenFilename. } {$ENDIF} type ShortPathStr = string[ShortPathName]; {$IFNDEF WINDOWS} TSearchRec = SearchRec; TDateTime = DateTime; PChar = ^Char; {$ENDIF} TLFNSearchRec = record Attr : longint; Creation : comp; LastAccess : comp; LastMod : comp; HighFileSize : longint; { high 32 bits } Size : longint; { low 32 bits } Reserved : comp; Name : array[0..259] of char; ShortName : array[0..13] of char; Handle : word; end; PLFNSearchRec = ^TLFNSearchRec; { Form used for old-style searches, with an embedded TSearchRec } TLFNShortSearchRec = record Attr : longint; Creation : comp; LastAccess : comp; LastMod : comp; HighFileSize : longint; Size : longint; Reserved : comp; Name : array[0..13] of char; SRec : TSearchRec; Filler : array[1..260-14-sizeof(TSearchRec)] of byte; ShortName : array[0..13] of char; Handle : word; end; PLFNShortSearchRec = ^TLFNShortSearchRec; { A record to isolate the UserData parameters } TLFNFileParam = record Handle : word; { The file handle } Mode : word; { The file mode } Res1 : array[1..28] of byte; { Everything else up to UserData } { Begin UserData } lfname : PString; { The long filename in String form } plfname : PChar; { The long filename in AsciiZ form } TextFile : boolean; { Is it a text or binary file } Initialized: boolean; { Has it been LFNAssigned } Magic : string[3]; { ID to check LFNNew } Res2 : array[0..1] of byte; { 2 bytes left in UserData } { End UserData } SName : array[0..79] of char; { The short filename } end; PLFNFileParam = ^TLFNFileParam; var LFNAble: boolean; { Is LFN supported or not. Upon startup it is determined } { by the OS, but can be switched off later if need be. } function LFNToggleSupport(on: boolean): boolean; {$IFNDEF WINDOWS} { I need these to access the Srec.Name field properly } function PCharOf(var F): Pchar; function StrPas(P: PChar): string; {$ENDIF} function PChar2Pstring(F: Pchar): PString; function PString2PChar(F: Pstring): PChar; { Basic API calls } function LFNTimeToDos(var LTime: comp): longint; function DosTimeToLFN(var Time: longint; var LTime: comp): word; function LGetAttr(Filename: PChar; var Attr: word): word; function LRenameFile(FromName,ToName: PChar): word; function LCreateEmpty(fname: PChar): word; function LFNFindFirst(filespec: string; attr: word; var S: TLFNSearchRec): word; function LFNFindNext(var S: TLFNSearchRec): word; function LFNFindClose(var S: TLFNSearchRec): word; function LFNShortName(LongName: string): ShortPathStr; function LFNLongName(ShortName: ShortPathStr): string; { Service routines } procedure LFNUnpackTime(var LTime: comp; var DT: TDateTime); function LFNGetFAttr(var F; var Attr: word): integer; function LFNFileExist(fname: string): boolean; function LFNFSearch(Path,DirList: string): string; procedure LFNFSplit(Path: string; Dir,Name,Ext: PString); function LFNFExpand(Path: string): string; procedure CanonicalFname(var S: string); function CanonicalFilename(Fname: PChar): Pchar; { Interface to the Pascal Input/Output routines } procedure LFNNew (var F; IsText: boolean); function LFNAssign (var F; name: string): integer; function LFNRewrite(var F; RecLen: word): integer; function LFNAppend (var F; RecLen: word): integer; function LFNReset (var F; RecLen: word): integer; function LFNErase (var F): integer; function LFNClose (var F): integer; procedure LFNDispose(var F); function LFNRename (var F; NewName: string): integer; implementation const {$IFNDEF WINDOWS} faReadOnly = ReadOnly; faHidden = Hidden; faSysFile = SysFile; faVolumeID = VolumeID; faDirectory = Directory; faArchive = Archive; faAnyFile = AnyFile; {$ENDIF} LFNMagic = 'LFN'; type PSearchRec = ^TSearchRec; TByteArray = array[0..$FFF0-1] of char; PByteArray = ^TByteArray; {$IFNDEF WINDOWS} function PCharOf(var F): Pchar; { A very simple function which returns a pointer to its argument. } { Its main use is in turning array[...] of char in to PChar, to } { simulate the TPW/TP7/BP7 extended syntax. } begin PCharOf:=@F; end; function StrPas(P: PChar): string; var i: integer; tmp: PString; begin New(tmp); tmp^:=''; if P=Nil then Exit; i:=0; while (length(tmp^)<256) and (PByteArray(P)^[i]<>#0) do begin tmp^:=tmp^+PByteArray(P)^[i]; inc(i); end; StrPas:=tmp^; Dispose(tmp); end; function StrLen(P: PChar): integer; var i: integer; begin i:=0; if P<>Nil then while (i<$7FFF) and (PByteArray(P)^[i]<>#0) do inc(i); StrLen:=i; end; {$ENDIF} function PChar2Pstring(F: Pchar): PString; { This routine changes a PChar (AsciiZ) string to a } { Pascal-type string, in the same memory location. } var i,len: integer; begin len:=StrLen(F); if len>255 then len:=255; for i:=len downto 1 do PByteArray(F)^[i]:=PByteArray(F)^[i-1]; F^:=Chr(len); PChar2PString:=PString(F); end; { PChar2Pstring } function PString2PChar(F: Pstring): PChar; { This routine changes a Pascal-type string to an } { AsciiZ string, in the same memory location. } var i,len: integer; begin len:=length(F^); for i:=1 to len do F^[i-1]:=F^[i]; F^[len]:=#0; PString2PChar:=PChar(F); end; { PString2PChar } {$IFDEF WINDOWS} function SupportsLFN: boolean; var WinVersion: word; begin { SupportsLFN:=false; Exit;} WinVersion := LoWord(GetVersion); SupportsLFN:=true; If ((Lo(WinVersion) = 3) and {windows 95 first} (Hi(WinVersion) < 95)) or {version is 3.95 } (Lo(WinVersion) < 3) then SupportsLFN := False; end; {$ELSE} function SupportsLFN: boolean; assembler; asm mov ax, $160a int $2f cmp ax, 0 jne @no { Not running under Windows } cmp bh, 2 jle @no { Major version <3 } cmp bh, 4 jge @yes { Major version >3 } cmp bl, 94 jle @no { Major version =3, minor <95 } @yes: mov al, true jmp @exit @no: mov al, false @exit: end; { SupportsLFN } {$ENDIF} function LFNToggleSupport(on: boolean): boolean; { This routine toggles LFN support on and off, provided } { the OS supports it. It returns the previous status. } begin LFNToggleSupport:=LFNAble; LFNAble:=on and SupportsLFN; end; {==============================================================} { BASIC LFN API CALLS. } { This is a set of routines which implement the WIn95 LFN API, } { in Turbo Pascal form. } {==============================================================} function LFNTimeToDos(var LTime: comp): longint; assembler; { Convert 64-bit number of 100ns since 01-01-1601 UTC to local DOS format time} { (LTime is var to avoid putting it on the stack) } asm push ds lds si,LTime xor bl,bl mov ax,71a7h int 21h pop ds mov ax,cx cmc sbb cx,cx and ax,cx and dx,cx end; { LFNTimeToDos } function DosTimeToLFN(var Time: longint; var LTime: comp): word; { Convert DOS time to the 64-bit Win95 format } var DosTime,DosDate: word; DT: TDateTime; begin UnpackTime(Time,DT); FillChar(LTime,sizeof(LTime),0); with DT do begin DosTime:=(sec div 2) or (min shl 5) or (hour shl 11); DosDate:=day or (Month shl 5) or ((Year-1980) shl 9); end; asm mov ax, $71A7 mov bl, 1 mov cx, DosTime mov dx, DosDate mov bh, 0 les di, LTime int $21 jnc @1 mov [DosError],ax @1: end; DosTimeToLFN:=DosError; end; { DosTimeToLFN } function LGetAttr(Filename: PChar; var Attr: word): word; assembler; { Get the attributes of a file, PChar syntax } asm push ds lds dx,Filename mov ax,7143h xor bl,bl int 21h pop ds les di,Attr mov es:[di],cx sbb bx,bx and ax,bx mov [DosError],ax end; { LGetAttr } function LFindFirst(FileSpec: pchar; Attr: word; var SRec: TLFNSearchRec): word; assembler; { Search for files } asm push ds lds dx,FileSpec les di,SRec mov cx,Attr xor si,si mov ax,714eh int 21h pop ds sbb bx,bx mov es:[di].TLFNSearchRec.Handle,ax and ax,bx mov [DosError],ax end; function LFindNext(var SRec: TLFNSearchRec): word; assembler; { Find next file } asm mov ax,714fh xor si,si les di,SRec mov bx,es:[di].TLFNSearchRec.Handle int 21h sbb bx,bx and ax,bx mov [DosError],ax end; function LFindClose(var SRec: TLFNSearchRec): word; assembler; { Free search handle } asm mov ax,714fh mov bx,es:[di].TLFNSearchRec.Handle int 21h sbb bx,bx and ax,bx mov [DosError],ax end; function LGetShortName(FileName: pchar; Result: pchar): word; assembler; { Return complete short name/path for input file/path in buffer } { Result (79 bytes) } asm push ds lds si,FileName les di,Result mov ax,7160h mov cx,1 int 21h pop ds sbb bx,bx and ax,bx mov [DosError],ax end; function LGetLongName(FileName: PChar; Result: PChar): word; assembler; { Return complete long name/path for input file/path in buffer } { Result (261 bytes) } asm push ds lds si,FileName les di,Result mov ax,7160h mov cx,2 int 21h pop ds sbb bx,bx and ax,bx mov [DosError],ax end; function LRenameFile(FromName,ToName: PChar): word; assembler; { Rename a file, supports long filenames. } asm push ds mov ax, $7156 lds dx, FromName les di, ToName int $21 jc @1 mov ax, 0 @1: pop ds mov [DosError],ax end; { LRenameFile } function LCreateEmpty(fname: PChar): word; assembler; { Create an empty file with the given (long) name. } asm push ds mov ax, $716C mov bx, 000010b { Open long file name for writing } mov cx, 0 mov dx, 10001b { Open if exists, create of not. } lds si, fname mov di, 0 int $21 jc @1 { error creating file } mov bx, ax { ok, close it again } mov ah, $3E int $21 jc @1 { error closing file } mov ax, 0 { ok, return zero } @1: pop ds mov [DosError],ax end; { LCreateEmpty } { Pascal-string based interface routines } function LFNFindFirst(filespec: string; attr: word; var S: TLFNSearchRec): word; { Implement the FindFirst procedure. This routine will call the TP } { FindFirst if LFN is not supported, and will translate the result } { into the TLFNSearchRec variable. } { NOTE: Under Win95, the filespec will be checked against both the } { long and the short filenames, so an additional check may be } { necessary. } begin If LFNAble then begin filespec := filespec + #0; LFindFirst(PChar(@Filespec[1]),Attr,S); if (DosError=0) and (S.shortname[0]=#0) then begin move(S.name,S.shortname,sizeof(S.shortname)-1); S.shortname[sizeof(S.shortname)-1]:=#0; end; end else begin FillChar(S,sizeof(S),0); {$IFDEF WINDOWS} FileSpec:=FileSpec+#0; FindFirst(PChar(@FileSpec[1]),Attr,PLFNShortSearchRec(@S)^.SRec); {$ELSE} FindFirst(FileSpec,Attr,PLFNShortSearchRec(@S)^.SRec); {$ENDIF} if DosError=0 then begin {$IFDEF WINDOWS} Move(PLFNShortSearchRec(@S)^.SRec.name,S.Name,13); S.name[13]:=#0; {$ELSE} FillChar(S.Name,14,0); Move(PLFNShortSearchRec(@S)^.SRec.name[1],S.Name, byte(PLFNShortSearchRec(@S)^.SRec.name[0])); {$ENDIF} DosTimeToLFN(PLFNShortSearchRec(@S)^.SRec.Time,S.LastMod); S.Attr:=PLFNShortSearchRec(@S)^.SRec.Attr; S.Size:=PLFNShortSearchRec(@S)^.SRec.Size; end; end; LFNFindFirst:=DosError; end; { LFNFindFirst } function LFNFindNext(var S: TLFNSearchRec): word; { Implement the FindNext procedure. This routine will call the TP } { FindNext if LFN is not supported, and will translate the result } { into the TLFNSearchRec variable. } { NOTE: Under Win95, the filespec will be checked against both the } { long and the short filenames, so an additional check may be } { necessary. } begin If LFNAble then begin LFindNext(S); if (DosError=0) and (S.shortname[0]=#0) then begin move(S.name,S.shortname,sizeof(S.shortname)-1); S.shortname[sizeof(S.shortname)-1]:=#0; end; end else begin FindNext(PLFNShortSearchRec(@S)^.SRec); if DosError=0 then begin {$IFDEF WINDOWS} Move(PLFNShortSearchRec(@S)^.SRec.name,S.Name,13); S.name[13]:=#0; {$ELSE} FillChar(S.Name,14,0); Move(PLFNShortSearchRec(@S)^.SRec.name[1],S.Name, byte(PLFNShortSearchRec(@S)^.SRec.name[0])); {$ENDIF} DosTimeToLFN(PLFNShortSearchRec(@S)^.SRec.Time,S.LastMod); S.Attr:=PLFNShortSearchRec(@S)^.SRec.Attr; S.Size:=PLFNShortSearchRec(@S)^.SRec.Size; end; end; LFNFindNext:=DosError; end; { LFNFindNext } function LFNFindClose(var S: TLFNSearchRec): word; { Close the Win95 TLFNSearchRec structure. if LFN is not suppported, } { this routine does nothing. } begin If LFNAble then LFNFindClose:=LFindClose(S) else LFNFindClose:=0; end; {function} function LFNShortName(LongName: string): ShortPathStr; { Returns the short name of the specified file. If LFN is not } { supported, returns the input filename. } var P,Q: PChar; i,len: integer; begin if not LFNAble then begin LFNShortName:=LongName; Exit; end; len:=length(LongName); for i:=1 to len do LongName[i-1]:=LongName[i]; LongName[len]:=#0; P:=@Longname; GetMem(Q,270); Q^:=#0; if LGetShortName(P,Q)=0 then begin if Q^=#0 then LFNShortName:=LongName else LFNShortName:=StrPas(Q); end else LFNShortName:=''; FreeMem(Q,270); end; { ShortName } function LFNLongName(ShortName: ShortPathStr): string; { Returns the long name of the specified file. If LFN is not } { supported, returns the input filename. } var SRec: PLFNSearchRec; P: PChar; P0,D,N,E: PString; i,len: integer; begin LFNLongName:=ShortName; if not LFNAble then Exit; len:=length(ShortName); if len=0 then Exit; New(D); LFNFSplit(ShortName,D,Nil,Nil); for i:=1 to len do ShortName[i-1]:=ShortName[i]; ShortName[len]:=#0; GetMem(P0,270); P:=@PByteArray(P0)^[1]; P0^:=''; P^:=#0; LGetLongName(PChar(@ShortName),P); PByteArray(P)^[256]:=#0; P0^[0]:=Chr(StrLen(P)); Dispose(D); if P^=#0 then LFNLongName:=ShortName else LFNLongName:=StrPas(P); FreeMem(P0,270); end; { LFNLongName } {====================================================================} { DERIVATIVE SERVICE ROUTINES. } { This is a set of routines which mimic, as closely as possible, the } { equivalent routines in Turbo Pascal, except that they support } { long filenames. In many cases, they are drop-in replacements, but } { some are new. } {====================================================================} procedure LFNUnpackTime(var LTime: comp; var DT: TDateTime); { Convert 64-bit time to date/time record } begin UnpackTime(LFNTimeToDos(LTime),DT); end; function LFNGetFAttr(var F; var Attr: word): integer; { Get the attributes of a file, using its File variable. } { The file should have been LFNAssign'ed first. Its not } { strictly required, except for error checking. } { Returns the DOS error code. } begin LFNGetFAttr:=0; DosError:=0; with PLFNFileParam(@F)^ do if (Magic<>LFNMagic) or (not Initialized) then begin DosError:=2; LFNGetFAttr:=2; Exit; end; GetFAttr(F,Attr); LFNGetFAttr:=DosError; end; { LFNGetFAttr } function LFNFileExist(fname: string): boolean; { Returns TRUE if the file exists, and FALSE otherwise. } var fl: file; attr,i,len: word; P: PChar; begin if fName='' then begin LFNFileExist:=false; Exit; end; if LFNAble then begin len:=length(fname); for i:=1 to len do fname[i-1]:=fname[i]; fname[len]:=#0; LGetAttr(PChar(@fname),Attr) end else begin Assign(fl,fname); GetFAttr(fl,Attr); end; LFNFileExist:=(DosError=0); end; { LFNFileExist } function LFNFSearch(Path,DirList: string): string; { Search for a file in a semicolon-delimited list of directories. } { This is a drop-in replacement for FSearch (TP6), which I } { personally find more useful than the later FileSearch. } var i,len,Ind: integer; which: PChar; tmp: PString; found: boolean; begin LFNFSearch:=''; if Path='' then Exit; if LFNAble then begin if (DirList='') and not LFNFileExist(Path) then Exit; if DirList='' then begin LFNFSearch:=Path; Exit; end; Ind:=1; New(tmp); found:=false; while (DirList<>'') and (DirList[1]=';') do delete(DirList,1,1); repeat tmp^:=''; while (Ind<=length(DirList)) and (DirList[Ind]<>';') do begin tmp^:=tmp^+DirList[Ind]; inc(Ind); end; while (Ind<=length(DirList)) and (DirList[Ind]=';') do inc(Ind); if Ind>length(DirList) then Ind:=0 else inc(Ind); if tmp^<>'' then begin if tmp^[length(tmp^)]<>'\' then tmp^:=tmp^+'\'; if LFNFileExist(tmp^+Path) then begin LFNFSearch:=LFNFExpand(tmp^+Path); found:=true; end; end; until found or (Ind=0); Dispose(tmp); end else begin {$IFDEF WINDOWS} GetMem(Which,256); len:=length(Path); for i:=1 to len do Path[i-1]:=Path[i]; Path[len]:=#0; len:=length(DirList); for i:=1 to len do DirList[i-1]:=DirList[i]; DirList[len]:=#0; FileSearch(which,PChar(@Path),PChar(@DirList)); LFNFSearch:=StrPas(Which); FreeMem(Which,256); {$ELSE} LFNFSearch:=FSearch(Path,DirList); {$ENDIF} end; end; { LFNFSearch } procedure LFNFSplit(Path: string; Dir,Name,Ext: PString); { An almost drop-in replacement for the TP6 FSplit, which supports LFN. } { The additional difference is that the arguments are passed as pointers, } { rather than VAR variables. This is so that if a file segment is not } { needed, one can pass NIL in the respective variable, and it will not } { be returned. } var StrPt,StrSlash,StrEnd: integer; begin StrEnd:=length(Path); StrPt:=StrEnd; StrSlash:=0; while(StrPt>0) and (Path[StrPt]<>'.') and (Path[StrPt]<>'\') do dec(StrPt); if (StrPt>0) and (Path[StrPt]='.') then { found extension } begin StrSlash:=StrPt-1; while (StrSlash>0) and (Path[StrSlash]<>'\') do dec(StrSlash); end else if (StrPt>0) and (Path[StrPt]='\') then { No extension } begin StrSlash:=StrPt; StrPt:=StrEnd+1; end else if StrPt=0 then { All name } begin StrPt:=StrEnd+1; StrSlash:=0; end; if Dir<>Nil then begin Dir^:=''; if StrSlash>0 then Dir^:=Copy(Path,1,StrSlash); end; if Name<>Nil then begin Name^:=''; if StrPt>StrSlash+1 then Name^:=Copy(Path,StrSlash+1,StrPt-StrSlash-1); end; if Ext<>Nil then begin Ext^:=''; if StrPt<=StrEnd then Ext^:=Copy(Path,StrPt,255); end; end; { LFNFSplit } function LFNFExpand(Path: string): string; { Drop-in replacement for the TP6 FExpand, which supports LFN. } { Personally, I prefer it to the later FileExpand. } var D,N,E,P: PString; i,j,ndots: integer; begin for i:=1 to length(Path) do if Path[i]='/' then Path[i]:='\'; LFNFExpand:=''; GetMem(P,270); {$IFDEF WINDOWS} FileExpand(PChar(P)+1,'.'); P^[0]:=chr(StrLen(PChar(P)+1)); {$ELSE} P^:=FExpand('.'); {$ENDIF} if (P^<>'') and (P^[length(P^)]<>'\') then P^:=P^+'\'; P^:=LFNLongName(P^); ndots:=0; while (ndots1) and (UpCase(Path[1]) in ['A'..'Z']) and (Path[2]=':') then P^:=Path { Fully qualified } else if Path[1]='\' then { Only drive missing } P^:=Copy(P^,1,2)+Path else begin for i:=1 to ndots-1 do { relative filenames, multiple dots } begin if length(P^)>3 then begin j:=length(P^)-1; while (j>3) and (P^[j]<>'\') do dec(j); P^[0]:=Chr(j); end; delete(Path,1,1); end; if Pos('.\',Path)=1 then Delete(Path,1,2) else if Pos('.',Path)=1 then Delete(Path,1,1); P^:=P^+Path; end; LFNFExpand:=P^; FreeMem(P,270); end; { LFNFExpand } procedure CanonicalFname(var S: string); { This routine takes a filename and changes its case to a canonical form: } { 1. Without LFN support, lowercase. } { 1. For existing short filenames, or dir names, lowercase. } { 2. For existing long filenames, the system-supplied case. } { 3. For non-existing filenames, expand the existing part of the path, } { and leave the rest unchanged. } { In all cases '/' is changed to '\'. } type TBf = array[1..3] of string; var lname,sname,res: Pstring; Buf: ^TBf; i,j: integer; exists: boolean; procedure StrLwr(var L: string); var i: integer; begin for i:=1 to length(L) do if L[i] in ['A'..'Z'] then L[i]:=Chr(Ord(L[i])-Ord('A')+Ord('a')); end; begin for i:=1 to length(S) do if S[i]='/' then S[i]:='\'; if LFNAble then begin New(Buf); Buf^[1]:=''; repeat i:=Pos('\',S); if i=0 then i:=length(S); if S[i]='\' then exists:=LFNFileExist(Buf^[1]+Copy(S,1,i)+'.') else exists:=LFNFileExist(Buf^[1]+Copy(S,1,i)); if exists then begin Buf^[2]:=LFNShortName(Buf^[1]+Copy(S,1,i)); Buf^[3]:=LFNLongName(Buf^[2]); j:=length(Buf^[2])-1; while (j>0) and (Buf^[2][j]<>'\') do dec(j); Delete(Buf^[2],1,j); j:=length(Buf^[3])-1; while (j>0) and (Buf^[3][j]<>'\') do dec(j); Delete(Buf^[3],1,j); if Buf^[3]=Buf^[2] then StrLwr(Buf^[3]); Buf^[1]:=Buf^[1]+Buf^[3]; delete(S,1,i); end; until (not exists) or (S=''); S:=Buf^[1]+S; Dispose(Buf); end else StrLwr(S); end; { CanonicalFname } function CanonicalFilename(fname: PChar): PChar; begin CanonicalFName(PChar2PString(fname)^); fname:=PString2PChar(PString(fname)); CanonicalFilename:=fname; end; {=========================================================================} { BINARY AND TEXT FILE INPUT/OUTPUT ROUTINES. } { This set of routines is an interface between the LFN API and the Pascal } { style input/output routines. It uses ordinary text and file variables, } { storing special info in the UserData field. The variable is then fully } { compatible with the Pascal read(ln), write(ln), BlockRead, BlockWrite, } { etc input/output routines. } { All the functions return the DOS error code, and also put it into } { DOSERROR. The global "LFNRuntimeError" determines if runtime errors } { will be generated (by default, no.) } {=========================================================================} procedure LFNNew(var F; IsText: boolean); { This routine prepares a text or file variable for LFN use. It allocates } { memory for the long name, and initializes the entries in the UserData. } { It must be called before any other. } { The "IsText" flag tells if the variable is of type "file" or "text". } begin with PLFNFileParam(@F)^ do begin TextFile:=IsText; Initialized:=false; Magic:=LFNMagic; lfname:=Nil; plfname:=Nil; if LFNAble then begin GetMem(lfname,270); FillChar(lfname^,270,0); plfname:=PChar(@PByteArray(lfname)^[1]); end; end; end; { LFNNew } function LFNAssign(var F; name: string): integer; { This routine replaces the Pascal "Assign" routine. For existing files, } { it first determines the short name, and then invokes "Assign". If the } { file does not exist, it only stores the information in the UserData } { fields, since the equivalent short name is not known. The assign } { operation is then deferred to the first "LFNRewrite" call. } { LFNAssign may be called for the same variable for different filenames, } { so long as the type (file or text) is the same. } var tmp,fname: PString; IsText: boolean; P: PChar; begin if PLFNFileParam(@F)^.Magic<>LFNMagic then begin DosError:=LFNErr_NotAllocated; LFNAssign:=DosError; {$IFDEF WINDOWS} MessageBox(0,'Bug, LFNAssign',Nil,mb_ok); { for debugging } {$ENDIF} Exit; end; LFNAssign:=0; DosError:=0; if LFNAble then begin GetMem(fname,270); if LFNFileExist(name) then begin fname^:=LFNShortName(name); PByteArray(fname)^[length(fname^)+1]:=#0; end else fname^:=''; end else fname:=@name; with PLFNFileParam(@F)^ do begin if fname^='' then Initialized:=false else begin IsText:=TextFile; tmp:=lfname; P:=plfname; if IsText then Assign(text(F),fname^) else assign(file(F),fname^); Initialized:=true; TextFile:=IsText; lfname:=tmp; plfname:=P; Magic:=LFNMagic; end; if LFNAble then begin lfname^:=name; PByteArray(lfname)^[length(lfname^)+1]:=#0; end; end; if LFNAble then FreeMem(fname,270); end; { LFNAssign } function LFNRewrite(var F; RecLen: word): integer; { This routine readies a file for output. If the file does not yet exist, } { it creates an empty file to get the system-determined short name, and } { performs a deferred Assign, since at Assign time a short name was not } { yet available (see description of LFNAssign). } { The routine returns 0 if successful, and the DOS errorcode if not. } var tmp,fname: PString; IsText: boolean; P: PChar; function Err(e: byte): byte; begin LFNRewrite:=e; DosError:=e; Err:=e; if LFNRuntimeErrors and (e<>0) then RunError(e); end; begin Err(0); if PLFNFileParam(@F)^.Magic<>LFNMagic then begin {$IFDEF WINDOWS} MessageBox(0,'Bug, LFNRewrite',Nil,mb_ok); { for debugging } {$ENDIF} Err(LFNErr_NotAllocated); Exit; end; if LFNAble then with PLFNFileParam(@F)^ do begin if not Initialized then { create the file, so we can get a valid short name } begin if Err(LCreateEmpty(plfname))=0 then begin New(fname); fname^:=LFNShortName(lfname^); IsText:=TextFile; tmp:=lfname; P:=plfname; if IsText then Assign(text(F),fname^) else assign(file(F),fname^); Initialized:=true; TextFile:=IsText; lfname:=tmp; plfname:=P; Magic:=LFNMagic; end; end; if Initialized then begin {$I-} if TextFile then Rewrite(text(F)) else if RecLen=0 then Rewrite(file(F)) else Rewrite(file(F),RecLen); Err(IoResult); {$I+} end; end else with PLFNFileParam(@F)^ do if Initialized then begin {$I-} if TextFile then Rewrite(text(F)) else if RecLen=0 then rewrite(file(F)) else Rewrite(file(F),RecLen); Err(IoResult); {$I+} end; end; { LFNRewrite } function LFNAppend(var F; RecLen: word): integer; { This routines opens a previously LFNAssigned for output at the EOF. } { Its not really necessary, except that it performs additional error } { checking to make sure that the file was properly initialized. } { Also, in contrast to the TP Append, if the file does not exist the } { routine calls LFNRewrite to create and open it. } { The routine returns 0 if successful, and the DOS errorcode if not. } function Err(e: byte): byte; begin LFNAppend:=e; DosError:=e; Err:=e; if LFNRuntimeErrors and (e<>0) then RunError(e); end; begin Err(0); if PLFNFileParam(@F)^.Magic<>LFNMagic then begin Err(LFNErr_NotAllocated); Exit; end; with PLFNFileParam(@F)^ do begin if Magic<>LFNMagic then begin Err(LFNErr_NotAllocated); Exit; end else if not TextFile then begin Err(LFNErr_NotATextFile); Exit; end else if not Initialized then Err(LFNRewrite(F,RecLen)) else begin {$I-} Append(text(F)); Err(IoResult); {$I+} end; end; end; { LFNAppend } function LFNReset(var F; RecLen: word): integer; { This routines opens a file for input, instead of "reset". Its not really } { necessary, except that it performs additional error checking to make } { sure that the file was properly initialized. } { The routine returns 0 if successful, and the DOS errorcode if not. } procedure Err(e: byte); begin LFNReset:=e; DosError:=e; if LFNRuntimeErrors and (e<>0) then RunError(e); end; begin Err(0); if PLFNFileParam(@F)^.Magic<>LFNMagic then begin {$IFDEF WINDOWS} MessageBox(0,'Bug, LFNReset',Nil,mb_ok); { for debugging } {$ENDIF} Err(LFNErr_NotAllocated); Exit; end; with PLFNFileParam(@F)^ do begin if not Initialized then LFNReset:=LFNErr_UnInitialized else begin {$I-} if TextFile then Reset(text(F)) else if RecLen=0 then Reset(file(F)) else Reset(file(F),RecLen); Err(IoResult); {$I+} end; end; end; { LFNReset } function LFNErase(var F): integer; { This routines erases a previously LFNAssigned, but not opened, file. } { Its not really necessary, except that it performs additional error } { checking to make sure that the file was properly initialized. Also, } { it re-assignes the file so it will be properly ready for a rewrite. } { The routine returns 0 if successful, and the DOS errorcode if not. } var S: PString; S1: PChar; function Err(e: byte): byte; begin LFNErase:=e; DosError:=e; Err:=e; if LFNRuntimeErrors and (e<>0) then RunError(e); end; begin with PLFNFileParam(@F)^ do begin LFNErase:=0; if (Magic<>LFNMagic) then begin Err(LFNErr_NotAllocated); Exit; end else if (not Initialized) then begin Err(LFNErr_UnInitialized); Exit; end; LFNClose(F); if not LFNAble then begin GetMem(S,81); S1:=PChar(@PByteArray(S)^[1]); Move(SName,S1^,80); S^:=Chr(StrLen(S1)); end; {$I-} if TextFile then Erase(text(F)) else Erase(file(F)); if Err(IoResult)=0 then begin if LFNAble then LFNAssign(F,lfname^) else begin LFNAssign(F,S^); FreeMem(S,81); end; end; {$I+} end; end; { LFNErase } function LFNClose(var F): integer; { This routines closes a previously LFNAssigned and opened file. } { Its not really necessary, except that it performs additional error } { checking to make sure that the file was properly initialized. } { The routine returns 0 if successful, and the DOS errorcode if not. } function Err(e: byte): byte; begin LFNClose:=e; DosError:=e; Err:=e; if LFNRuntimeErrors and (e<>0) then RunError(e); end; begin Err(0); with PLFNFileParam(@F)^ do begin if Magic<>LFNMagic then begin Err(LFNErr_NotAllocated); Exit; end else if not Initialized then begin Err(LFNErr_UnInitialized); Exit; end; {$I-} if TextFile then close(text(F)) else close(file(F)); Err(IoResult); {$I+} end; end; { LFNClose } procedure LFNDispose(var F); { This routine disposes of the additional memory allocated by LFNNew, } { and cleans up the UserData fields. If the file is open, it also } { closes it, so that there is no need to call LFNClose previously. } begin with PLFNFileParam(@F)^ do begin if (Magic<>LFNMagic) or (not Initialized) then Exit; LFNClose(F); if lfname<>Nil then FreeMem(lfname,270); lfname:=Nil; plfname:=Nil; Initialized:=false; Magic:=''; end; end; { LFNDispose } function LFNRename(var F; NewName: string): integer; { This routines renames a previously LFNAssigned, but not opened, file. } { The file variable is then re-assigned to the new name. } { The routine returns 0 if successful, and the DOS errorcode if not. } var i,len: integer; function Err(e: byte): byte; begin LFNRename:=e; DosError:=e; Err:=e; if LFNRuntimeErrors and (e<>0) then RunError(e); end; begin Err(0); if NewName='' then Exit; with PLFNFileParam(@F)^ do begin if Magic<>LFNMagic then begin Err(LFNErr_NotAllocated); Exit; end else if not Initialized then begin Err(LFNErr_UnInitialized); Exit; end; if not LFNAble then { The usual TP stuff } begin {$I-} if TextFile then Rename(text(F),NewName) else Rename(file(F),NewName); Err(IoResult); {$I+} end else { LFN } begin len:=length(NewName); for i:=1 to len do NewName[i-1]:=NewName[i]; NewName[len]:=#0; if Err(LRenameFile(plfname,PChar(@NewName)))=0 then begin for i:=len downto 1 do NewName[i]:=NewName[i-1]; NewName[0]:=chr(len); LFNAssign(F,NewName); end; end; end; end; { LFNRename } begin LFNAble:=SupportsLFN; end.