{***************************************************************************} { } { LFN - Free unit for long filename support. 100% asm code. } { All functions return an error code } { and also store it in DosError in the Dos unit. } { A demo program is at the end of this unit. } { } { Author: Pino Navato } { E-Mail: pnavato@poboxes.com } { pnavato@geocities.com } { Pino Navato, 2:335/225.18 (The Bits BBS, Fidonet) } { WWW: www.poboxes.com/pnavato } { (currently forwards to www.geocities.com/SiliconValley/4421) } { } { Advertisement: } { Do you need new CHR fonts for the BGI? Visit my home page! } { } { Acknowledgments: } { - This unit is partially based on the LDOS unit by Arne de Bruijn. } { - Technical info obtained from the Ralf Brown's Interrupt List. } { } {***************************************************************************} Unit LFN; interface uses DOS; type QuadWord = array[0..3] of word; { For W95 file date/time } LSearchRec = record Attr : LongInt; CreationTime, LastAccessTime, LastModTime : QuadWord; { See below for conversion } HiSize, LoSize : LongInt; reserved : array[0..7] of byte; name : array[0..259] of char; ShortName : array[0..13] of char; { Only if longname exists } Handle : word; end; function LFileSystemInfo(RootName: PChar; FSName: PChar; FSNameBufSize: word; var Flags, MaxFileNameLen, MaxPathLen: word): word; { Return File System Information, for FSName 32 bytes should be sufficient } { Rootname is, for example, 'C:\' } { WARNING: due to a bug in Windows95, this function returns MaxPathLen = 0 } { for CD-ROMs! } { Bitfields for long filename volume information flags: } { Bit(s) Description } { 0 searches are case sensitive } { 1 preserves case in directory entries } { 2 uses Unicode characters in file and directory names } { 3-13 reserved (0) } { 14 supports DOS long filename functions } { 15 volume is compressed } function LFindFirst(FileSpec: PChar; Attr: word; var SRec: LSearchRec): word; { Search for files } function LFindNext(var SRec: LSearchRec): word; { Find next file } function LFindClose(SRec: LSearchRec): word; { Free search handle } function LGetTrueName(FileName, TrueName: PChar): word; { Return complete path, in buffer TrueName (261 bytes) } function LGetShortName(FileName, ShortName: PChar): word; { Return complete short name/path for input file/path in buffer ShortName (128 bytes) } function LGetLongName(FileName, LongName: PChar): word; { Return complete long name/path for input file/path in buffer LongName (261 bytes) } function LRename(OldName, NewName: PChar): word; { Rename file } function LErase(Filename: PChar): word; { Erase file } function LMultiErase(FileMask: PChar; SearchAttr, MustMatchAttr: byte): word; { Erase files (wildcards allowed) } function LMkDir(Dir: PChar): word; { Make directory } function LRmDir(Dir: PChar): word; { Remove directory } function LChDir(Dir: PChar): word; { Change current directory } function LGetDir(Drive: byte; Dir: PChar): word; { Get current directory (no drive letter nor leading backslash). Drive: 0=current, 1=A: etc. } function LGetFAttr(Filename: PChar; var Attr: word): word; { Get file attributes} function LSetFAttr(Filename: PChar; Attr: word): word; { Set file attributes } function LGetFTime(FileName: PChar; var FTime: LongInt): word; { Get last-write date/time } function LSetFTime(FileName: PChar; FTime: LongInt): word; { Set last-write date/time } function LGetCreationFTime(FileName: PChar; var CFTime: LongInt): word; { Get creation file date/time } function LSetCreationFTime(FileName: PChar; CFTime: LongInt): word; { Set creation file date/time } function LGetLastAccessFDate(FileName: PChar; var LAFDate: LongInt): word; { Get last-access file date } function LSetLastAccessFDate(FileName: PChar; LAFDate: LongInt): word; { Set last-access file date } function LTimeToDos(LTime: QuadWord; var DosTime: LongInt): word; { Convert 64-bit W95 file date/time to local DOS date/time (packed format) } function LUnpackTime(LTime: QuadWord; var DT: DateTime): word; { Convert 64-bit time to date/time record } function LGetPhysicalFSize(FileName: PChar; var Size: LongInt): word; { Get physical size of compressed file } implementation function LFileSystemInfo(RootName: PChar; FSName: PChar; FSNameBufSize: word; var Flags, MaxFileNameLen, MaxPathLen: word): word; assembler; { Return File System Information } { WARNING: due to a bug in Windows95, this function returns MaxPathLen = 0 } { for CD-ROMs! } asm push ds lds dx,RootName les di,FSName mov cx,FSNameBufSize mov ax,71A0h stc int 21h lds di,Flags mov ds:[di],bx lds di,MaxFileNameLen mov ds:[di],cx lds di,MaxPathLen mov ds:[di],dx pop ds sbb bx,bx { if CF=1 then BX:=$FFFF else BX:=0 } and ax,bx mov [DosError],ax end; function LFindFirst(FileSpec: PChar; Attr: word; var SRec: LSearchRec): word; assembler; { Search for files } asm push ds lds dx,FileSpec mov cx,Attr les di,SRec xor si,si mov ax,714Eh stc int 21h pop ds mov es:[di].LSearchRec.Handle,ax sbb bx,bx and ax,bx mov [DosError],ax end; function LFindNext(var SRec: LSearchRec): word; assembler; { Find next file } asm les di,SRec mov bx,es:[di].LSearchRec.Handle xor si,si mov ax,714Fh stc int 21h sbb bx,bx and ax,bx mov [DosError],ax end; function LFindClose(SRec: LSearchRec): word; assembler; { Free search handle } asm les di,SRec mov bx,es:[di].LSearchRec.Handle mov ax,71A1h stc int 21h sbb bx,bx and ax,bx mov [DosError],ax end; function LGetTrueName(FileName, TrueName: PChar): word; assembler; { Return complete path, in buffer TrueName (261 bytes) } asm push ds lds si,FileName les di,TrueName mov ax,7160h xor cx,cx stc int 21h pop ds sbb bx,bx and ax,bx mov [DosError],ax end; function LGetShortName(FileName, ShortName: PChar): word; assembler; { Return complete short name/path for input file/path in buffer ShortName (128 bytes) } asm push ds lds si,FileName les di,ShortName mov ax,7160h mov cx,1 { Return a path containing true path for a SUBSTed drive letter } stc int 21h pop ds sbb bx,bx and ax,bx mov [DosError],ax end; function LGetLongName(FileName, LongName: PChar): word; assembler; { Return complete long name/path for input file/path in buffer LongName (261 bytes) } asm push ds lds si,FileName les di,LongName mov ax,7160h mov cx,2 { Return a path containing true path for a SUBSTed drive letter } stc int 21h pop ds sbb bx,bx and ax,bx mov [DosError],ax end; function LRename(OldName, NewName: PChar): word; assembler; asm push ds lds dx,OldName les di,NewName mov ax,7156h stc int 21h pop ds sbb bx,bx and ax,bx mov [DosError],ax end; function LErase(Filename: PChar): word; assembler; asm push ds lds dx,Filename xor si,si { Wildcards not allowed } mov ax,7141h stc int 21h pop ds sbb bx,bx and ax,bx mov [DosError],ax end; function LMultiErase(FileMask: PChar; SearchAttr, MustMatchAttr: byte): word; assembler; { Erase files (wildcards allowed) } asm push ds lds dx,FileMask mov si,1 { Wildcards allowed } mov cl,[SearchAttr] mov ch,[MustMatchAttr] mov ax,7141h stc int 21h pop ds sbb bx,bx and ax,bx mov [DosError],ax end; function LMkDir(Dir: PChar): word; assembler; asm push ds lds dx,Dir mov ax,7139h stc int 21h pop ds sbb bx,bx and ax,bx mov [DosError],ax end; function LRmDir(Dir: PChar): word; assembler; asm push ds lds dx,Dir mov ax,713Ah stc int 21h pop ds sbb bx,bx and ax,bx mov [DosError],ax end; function LChDir(Dir: PChar): word; assembler; asm push ds lds dx,Dir mov ax,713Bh int 21h pop ds sbb bx,bx and ax,bx mov [DosError],ax end; function LGetDir(Drive:byte; Dir: PChar): word; assembler; asm push ds mov dl,[Drive] lds si,Dir mov ax,7147h stc int 21h pop ds sbb bx,bx and ax,bx mov [DosError],ax end; function LGetFAttr(Filename: PChar; var Attr: word): word; assembler; asm push ds lds dx,Filename mov ax,7143h xor bl,bl stc int 21h lds di,Attr mov ds:[di],cx pop ds sbb bx,bx and ax,bx mov [DosError],ax end; function LSetFAttr(Filename: PChar; Attr: word): word; assembler; asm push ds lds dx,Filename mov cx,[Attr] mov ax,7143h mov bl,1 stc int 21h pop ds sbb bx,bx and ax,bx mov [DosError],ax end; function LGetFTime(FileName: PChar; var FTime: LongInt): word; assembler; { Get last-write date/time } asm push ds lds dx,Filename mov ax,7143h mov bl,4 stc int 21h lds bx,FTime mov ds:[bx],cx mov ds:[bx+2],di pop ds sbb bx,bx and ax,bx mov [DosError],ax end; function LSetFTime(FileName: PChar; FTime: LongInt): word; assembler; { Set last-write date/time } asm push ds lds dx,Filename mov cx,word ptr [FTime] mov di,word ptr [FTime+2] mov ax,7143h mov bl,3 stc int 21h pop ds sbb bx,bx and ax,bx mov [DosError],ax end; function LGetCreationFTime(FileName: PChar; var CFTime: LongInt): word; assembler; { Get creation file date/time } asm push ds lds dx,Filename mov ax,7143h mov bl,8 stc int 21h lds bx,CFTime mov ds:[bx],cx mov ds:[bx+2],di pop ds sbb bx,bx and ax,bx mov [DosError],ax end; function LSetCreationFTime(FileName: PChar; CFTime: LongInt): word; assembler; { Set creation file date/time } asm push ds lds dx,Filename mov cx,word ptr [CFTime] mov di,word ptr [CFTime+2] xor si,si mov ax,7143h mov bl,7 stc int 21h pop ds sbb bx,bx and ax,bx mov [DosError],ax end; function LGetLastAccessFDate(FileName: PChar; var LAFDate: LongInt): word; assembler; { Get last-access file date } asm push ds lds dx,Filename mov ax,7143h mov bl,6 stc int 21h lds bx,LAFDate mov ds:[bx],cx mov ds:[bx+2],di pop ds sbb bx,bx and ax,bx mov [DosError],ax end; function LSetLastAccessFDate(FileName: PChar; LAFDate: LongInt): word; assembler; { Set last-access file date } asm push ds lds dx,Filename mov di,word ptr [LAFDate+2] mov ax,7143h mov bl,5 stc int 21h pop ds sbb bx,bx and ax,bx mov [DosError],ax end; function LTimeToDos(LTime: QuadWord; var DosTime: LongInt): word; assembler; { Convert 64-bit W95 file date/time to local DOS date/time (packed format) } asm push ds lds si,LTime mov ax,71A7h xor bl,bl stc int 21h lds di,DosTime mov ds:[di],cx mov ds:[di+2],dx pop ds sbb bx,bx and ax,bx mov [DosError],ax end; function LUnpackTime(LTime: QuadWord; var DT: DateTime): word; assembler; { Convert 64-bit time to date/time record } var DosTime : LongInt; asm les di,Ltime push es push di lea di,DosTime push ss push di push cs { PUSH CS + CALL NEAR is faster than CALL FAR } call near ptr LTimeToDos { LTimeToDos(Ltime, DosTime) } jc @end push word ptr [DosTime+2] push word ptr [DosTime] les di,DT push es push di call UnpackTime { UnpackTime(DosTime, DT) } xor ax,ax @end: end; function LGetPhysicalFSize(FileName: PChar; var Size: LongInt): word; assembler; { Get physical size of compressed file } asm push ds lds dx,Filename mov ax,7143h mov bl,2 stc int 21h lds bx,Size mov ds:[bx],ax mov ds:[bx+2],dx pop ds sbb bx,bx and ax,bx mov [DosError],ax end; end. {***************************************************************************} {***************************************************************************} Program LFN_demo; {$M 4096,0,0} {$X+} uses LFN, strings, DOS; type string2 = string[2]; const RootName = 'C:\'; TempDirName = 'Temporary Directory'; TempFile0 = 'temp$$$$.tmp'; TempFile1 = 'Temporary File.tmp'; TempFile2 = 'Another Temporary File.tmp'; TempFile3 = 'Yet another temporary file.tmp'; var Buf : array[0..1023] of char; W1, W2, W3 : word; f : text; SRec : LSearchRec; DT : DateTime; LN, SN : Pchar; size : LongInt; PDT : LongInt; { Packed-format file date/time } function Str0(B: byte): string2; { Put a 0 in front of numbers <10 } begin Str0[0] := #2; Str0[1] := char(B div 10 + 48); Str0[2] := char(B mod 10 + 48); end; begin { Main } writeln; writeln; if LFileSystemInfo(RootName, Buf, 32, W1, W2, W3) <> 0 then begin writeln('Long names not supported!'); halt end; if Buf[0] = #0 then { This extra check is necessary } begin { if you run the demo from the IDE } writeln('Long names not supported!'); { under MS-DOS v6.22 } halt { I don't know why. } end; writeln('File System name: ', Buf, ' Max Filename Len: ', W2, ' Max Path Len: ', W3); writeln('Flags:'); writeln(' Searches are case sensitive = ', W1 and 1 = 1); writeln(' Preserves case in directory entries = ', W1 and 2 = 2); writeln(' Uses Unicode chars for names = ', W1 and 4 = 4); writeln(' Support LFN functions = ', W1 and $4000 = $4000); writeln(' Volume is compressed = ', W1 and $8000 = $8000); writeln(' Reserved fields = ', W1 and $3FF8); writeln; writeln('Press ENTER to continue'); readln; writeln('Creating temporary directory.'); LMkDir(TempDirName); writeln('Changing default directory.'); LChDir(TempDirName); write('Default directory is now '); LGetDir(0, Buf); writeln(Buf); writeln; writeln('Creating temporary file #1.'); assign(f, TempFile0); rewrite(f); writeln(f, TempFile1); close(f); writeln('Renaming file #1 to long name.'); LRename(TempFile0, TempFile1); writeln('Creating temporary file #2.'); rewrite(f); writeln(f, TempFile2); close(f); writeln('Renaming file #2 to long name.'); LRename(TempFile0, TempFile2); writeln('Creating temporary file #3.'); rewrite(f); writeln(f, TempFile3); close(f); writeln('Renaming file #3 to long name.'); LRename(TempFile0, TempFile3); writeln; writeln; writeln('Directory of ', Buf); writeln; LFindFirst('*', AnyFile, SRec); while DosError = 0 do begin LUnpackTime(SRec.LastModTime, DT); if SRec.ShortName[0] = #0 then begin SN := @SRec.name; LN := nil end else begin SN := @SRec.shortname; LN := @SRec.name end; with DT do { Italian-style output } WriteLn(SN, '':13-StrLen(SN), SRec.LoSize:9, ' ', Day:3, '/', Str0(Month), '/', Year, ' ', Hour:2, '.', Str0(Min), ' ', LN); LFindNext(SRec) end; LFindClose(SRec); writeln; writeln('Press ENTER to continue'); readln; writeln('True name of ', SN, ' ='); LGetTrueName(SN, Buf); writeln(' ', Buf); writeln('Short name of ', Tempfile3, ' ='); LGetShortName(TempFile3, Buf); writeln(' ', Buf); writeln('Long name of ', Buf, ' ='); LGetLongName(Buf, Buf); writeln(' ', Buf); if LGetPhysicalFSize(SN, size) <> 0 then writeln('Physical size of ', SN, ' = ', size, ' bytes.'); writeln; with DT do begin Day:= 1; Month := 2; Year := 1997; Hour := 0; Min := 1; Sec := 2 end; PackTime(DT, PDT); LSetCreationFTime(TempFile1, PDT); write('Creation date/time of ', TempFile1, ' is now '); LGetCreationFTime(TempFile1, PDT); Unpacktime(PDT, DT); with DT do { Italian-style output } WriteLn(Day:3, '/', Str0(Month), '/', Year, ' ', Hour:2, '.', Str0(Min), '.', Str0(sec)); with DT do begin Day:= 3; Month := 4; Year := 1997; Hour := 4; Min := 5; Sec := 6 end; PackTime(DT, PDT); LSetFTime(TempFile1, PDT); write('Last-write date/time of ', TempFile1, ' is now'); LGetFTime(TempFile1, PDT); Unpacktime(PDT, DT); with DT do { Italian-style output } WriteLn(Day:3, '/', Str0(Month), '/', Year, ' ', Hour:2, '.', Str0(Min), '.', Str0(sec)); with DT do begin Day:= 5; Month := 6; Year := 1997; end; PackTime(DT, PDT); LSetLastAccessFDate(TempFile1, PDT); write('Last-access date of ', TempFile1, ' is now '); LGetLastAccessFDate(TempFile1, PDT); Unpacktime(PDT, DT); with DT do { Italian-style output } WriteLn(Day:3, '/', Str0(Month), '/', Year); writeln; writeln('Setting the hidden file-attribute of ', TempFile1); LSetFAttr(TempFile1, archive + hidden); write('Checking... '); LGetFAttr(TempFile1, W1); if W1 = archive + hidden then writeln('OK') else begin writeln('Error!'); halt end; writeln; writeln('Deleting ', TempFile1); LErase(TempFile1); writeln('Deleting *.tmp'); LMultiErase('*.tmp', Archive, Archive); LChDir('..'); writeln('Deleting temporary directory.'); LRmDir(TempDirName); writeln; writeln('Done.') end.