LFN-QBasic

This is the explainations to LFNBASIC.BAS, the subroutines are 7,212 bytes is size WITHOUT the DECLARE's or including any REM's in the file, so rather than make it bigger, I created this file for those who wish the read about how the nitty gritty details of the programming is done. To those who like to have spaces indenting subsections of code for clairity, even that is missing, them there spaces added several kilos to the result, and some of you may have tight packages already. You can reduce the package size further, matter of fact I did that, the result is much smaller, but harder to read. I do recommend that you leave the major function names alone unless you're really good at reading codified names.. You may want to refine my reduction further, I use LFNxx's as the replacements, and remove all subfunctions not used in the final product.

For examples:
No need of  RANDOM or APPEND? Edit LFNOPEN and remove the subroutines called by the function.
All work done in current directory? No need to have LFNDOSPATH and it related sub functions taking up space.
Never change directories? Remove LFNCHDIR
Never create new files? Remove LFNNAMENEW and edit all the opens which call upon it.
Never use long filenames? Why have this package?

Bear in mind that some routines are shared by other parts of the package, for example LFNEXIST is used frequently as a check to prevent error messages about files which don't exist. While you could rely on QBasic to do this job, I prefered to catch this possibility before it happened. You can also save some space by collapsing certain parts of the code because in several places I use a subroutine to contain code which may only be used by that routine for ease of reading. And in other places I use a subroutine for code which can be done 'inline', like QUOTE$.

The Code The Reason
TYPE dos
full AS STRING * 128
real AS STRING * 95
used AS STRING * 1
END TYPE
The subroutines rely on a structure to hold the filenames. I could've used separate arrays for the system, but it is actuallly easier this way, though it does tend to be a little memory intensive. DOS.FULL is for the Long FileName, DOS.REAL is for the DOS8.3 name, and DOS.USED is an indicator that the entry is in use. LFNOPEN set this flag, and LFNCLOSE clears it. Should the program be called upon to open a file already in use, LFNERROR(155) is called.
DIM SHARED lfn(1 TO 3) AS dos
COMMON SHARED errnum, lfnmax, true, false
ON ERROR GOTO errhand
true = -1: false = 0: lfnmax = 3
FOR i = 1 TO lfnmax: lfn(i).used = " ": NEXT i
The array is called LFN, for most applications three entries is enough, I have one program which does a recursive directory search which uses as many as eight. ERRNUM, TRUE, FALSE are carry-overs, you DON'T need to COMMON them for the subroutines to work, but ERRNUM is usefull because you can use it in the ERRHAND to be set to the ERR and find out what happened to cause the error. LFNMAX does need to be GLOBAL because otherwise the subrroutine will generate a SUBSCRIPT OUT OF RANGE error if you use LFNOPENS a fourth time. Also, the code about going to the ERROR handling routine needs to be here. The only other piece of initialization is that LFN(x).USED needs to be set to CHR$(32). This is primarily for checking the used status of the entry. This is detailed in LFNOPEN.
testlfn Main Entry Point for demo. (Or another way of putting this, YOUR CODE HERE.) The actuall code follows ERRHAND, and most of the code detailed in the file does NOT follow the manner in which QBasic stores code. Rather it's detailed in a logical progression.
errhand:
FOR i = 1 TO lfnmax: lfnclose (i): NEXT i
PRINT ERR: BEEP: STOP
RESUME NEXT
The error handling is needed because of one small minor detail which is explained further in LFNNAMENEW, all the LFNFILES should be closed if you don't need to do any other error processing functions. The RESUME is needed by QBasic, else you get an error for excluding it, and it's also handy to step back to the point where the error happened. That way you can find the offending statement.
SUB testlfn  
functions called LFNCHDIR
LFNOPEN
LFNOPENS
LFNCLOSE
LFNCLOSEALL
lfnchdir ("C:\Program Files\123 Free Solitaire")
file1 = lfnopen("another readme.txt", "OUTPUT", 1, 0)
PRINT #1, "Fresh Text " + TIME$ + " " + DATE$
lfnclose (1)
file2 = lfnopen("D:\Program Files\Buttonz & Tilez\readme.txt", "INPUT", 2, 0)
file1 = lfnopens("another readme.txt", "INPUT")
INPUT #file2, a$
PRINT a$
INPUT #file1, a$
PRINT a$
lfncloseall
SYSTEM

END SUB

These are some very simple examples because once you get through all the nitty-gritty details of the sub-routines, you don't have to really know more than this:

file=LFNOPEN("[path\]filename.ext","mode",which,size)
file=LFNOPENS("[path\]filename.ext","mode")
LFNCLOSE(file)

And

LFNCHDIR("path")

Beyond this, it gets complex as you wade through all the code which is used to make this seem like such a simple task to access Long File Names under QBasic.

The only other routine you may want to use is LFNEXIST("[path\]filename.ext"), this will return TRUE if it find the file, or FALSE if the file doesn't exist.

The only other requirement of the code is that [C:\TEMP] exists, if you want to know more about these subroutines, read on, and if you have any questions WRITE me.

BTW: I realize that someone having the exact same setup as me is astronomical, so if you want to use the examples, be sure to find some places it can work with.

SUB lfnchdir (where AS STRING)
functions called QUOTE$
STRIPR$

a$ = stripr$(where)
SHELL "dir " + quote$(stripr$((a$)))
+ " >c:\temp\readit.txt"
file = FREEFILE
OPEN "c:\temp\readit.txt" FOR INPUT AS file
FOR i = 1 TO 4: LINE INPUT #file, b$: NEXT i
CLOSE file
KILL "c:\temp\readit.txt"
IF INSTR(b$, a$) = 0 THEN lfnerror (176)
IF MID$(a$, 2, 1) = ":" THEN
SHELL LEFT$(a$, 2)
SHELL "chdir " + quote$(stripr$MID$(a$, 3)))
ELSE
SHELL "chdir " + quote$(stripr$(a$, 3))
END IF

END SUB

First off, all the subroutines use your [C:\TEMP] directory to create a file READIT.TXT for the purposes of verifing the presense of the intended function, if you don't have a [C:\TEMP] then be sure to change this to a valid directory. Not that it's serious, but I've found it handy to see what ended up in the file just to see what the heck happened at times. Elsewhere in the subroutines we use the SHELLing to find a file, here we take a different twist. We're looking for the fourth line of a STANDARD directory listing which contains the line: Directory of ...., and ensuring our a$ equal it. If not, LFNERROR(176), Path not found. If the line is there, then we do a check for a drive letter by seeing if a ':' is in the second position and doing a separate SHELL to swap drives before changing directories. You'll see the function's QUOTE$ (and STRIPR$) pop up fairly often within these routines, it's a darn sight easier on the eyes to read what you're doing rather than CHR$(34)+a$+CHR$(34).
FUNCTION lfnopen% (file AS STRING
, mode AS STRING
, which AS INTEGER
, size AS INTEGER)
functions called LFNERROR
LFNIOAPPEND
LFNIOINPUT
LFNIOOUTPUT
LFNIORANDOM
IF which = 0 THEN which = FREEFILE
IF lfn(which).used <> " " THEN lfnerror (155)
SELECT CASE (mode)
CASE ("INPUT")
a = lfnioinput(file, which)
CASE ("OUTPUT")
a = lfniooutput(file, which)
CASE ("APPEND")
a = lfnioappend(file, which)
CASE ("RANDOM")
a = lfniorandom(file, which, size)
CASE ELSE
lfnerror (154)
END SELECT
lfnopen = which

END FUNCTION

Here's the Beef! This is the core of the LFN functions, what you call on to access those LFN's without having to do any fancy yourself to get at the DOS8.3 name. All you need to do is replace all your normal:
OPEN "filename.ext" FOR mode AS which
with:
LFNOPEN("Long Filename.extlong","mode",which,size)
and not worry about a Bad Filename Crash. One of the handier sides of this function is that should you decide to use it, it provides a 'C' type of ability and if it's passed a 0 as the WHICH, if loads WHICH with FREEFILE, and because is passes WHICH back to the caller, you can ignore the file number and rely on the stacking nature to keep overlapping filenumbers from happening. If you do need to have a definate file open for some reason, open it first via the subroutine or using the OPEN yourself. My apologies, I didn't provide a BINARY mode, I've never used it myself, but it's easy to add once you see the subroutines. I also don't provide for the ACCESS or LOCK statements because:
A: I haven't done any programming in need of it, therefore I've never used it myself.
B: All my programs run under windows which implicately LOCKS any file opened by a DOS window. If you don't know about this one, open a file with QBasic, open the same file with NotePad, modify with notepad, and try to save. If filelocking is working correctly on your machine, you'll get an error.
In any case, that's easy to add should your programming need it, it'll make the subroutine call bigger.
FUNCTION lfnopens% (file AS STRING
, mode AS STRING)
functions called LFNOPEN
lfnopens = lfnopen(file, mode, 0, 0)

END FUNCTION

Which is why I have a shorter call LFNOPENS, all you need to provide it is a MODE and (of course) the filename. It calls LFNOPEN and provide Zero's for WHICH and SIZE, while SIZE is only needed for RANDOM, LFNOPEN was designed to accomadate it as well. LFNOPENS returns WHICH to the caller as well.
SUB lfnclose (which AS INTEGER)
functions called LFNRENAME
IF lfn(which).used = " " THEN EXIT SUB
CLOSE which
IF lfn(which).used = "*"
THEN a = lfnrename (lfn(which).real, lfn(which).full)
lfn(which).used = " "

END SUB

LFNCLOSE is needed for two reasons:
A: the LFN().USED flag needs to be cleared.
B: New filenames need to be converted to their LFN's
The New Filenames are explained further in the function LFNRENAME. While it's provided as separate function, is short so if you want to, the function call can be replaced with the lines from the subroutine. Unless you need to do any LFN renaming.
SUB lfncloseall  
functions called LFNCLOSE
FOR i = 1 TO lfnmax: lfnclose (i): NEXT i

END SUB

This is primarily for quickly closing all your LFN's, it doesn't even check if there's any open.
FUNCTION lfnrename (oldname AS STRING
, newname AS STRING)
functions called QUOTE$
STRIPR$
a$ = stripr$(newname)
IF INSTR(a$, "\") THEN
DO
ix = INSTR(a$, "\")
IF ix = 0 THEN EXIT DO
a$ = MID$(a$, ix + 1)
LOOP
END IF
SHELL "rename " + quote$(stripr$(oldname)) + " "
+ quote$(stripr$(a$))

END FUNCTION

This is faily straightforward, we're just calling upon DOS to do our renaming. The flaw is that QBasic pads it's structure().strings with righthand spaces, so the function STRIPR$ is needed to remove them before wrapping the quotes around the filename. And while I have not run into any errors wrapping the DOS8.3 names in quotes, I may not have used the function under all possible filenames. One thing that must be done is the stripping of any possible path from the NEWNAME otherwise rename thinks you're trying to rename across directories and/or drives.

What I call the FATAL FLAW needs to be explained: LFN-QBasic generates unique filenames for any NEW files you open. Should the program abort in any means which does not allow it to properly convert these names to their LFN equivalents, you'll end up with 'Mystery Files'.

FUNCTION lfnioappend% (file AS STRING
, which AS INTEGER)
functions called LFNEXIST
LFNNAMENEW
LFNNAMEOLD
STRIPR$
IF lfnexist(file) THEN
a = lfnnameold(file, which)
ELSE
a = lfnnamenew(file, which)
END IF
OPEN stripr$(lfn(which).real)
FOR APPEND AS which

END FUNCTION

Of these next four subroutines, three are almost exactly alike. Matter of fact, even the fourth looks like those three except it doesn't have the check on whether it should call upon LFNNAMENEW, that one is LFNIOINPUT because, of course, the file must exist for reading. APPEND and OUTPUT, as well as RANDOM, create the file if is doesn't exist, and only RANDOM nees the SIZE parameter. If you want, you can directly call these and bypass LFNOPEN routine, I built that primarily to have a central access to these without having to type in a different routine name for each open. The LFNNAME---'s are detailed following this section. This, (of course), is APPEND, opens a file for appending to. Otherwise it's like LFNIOOUTPUT.
FUNCTION lfnioinput% (file AS STRING
, which AS INTEGER)
functions called LFNEXIST
LFNNAMEOLD
STRIPR$
IF NOT lfnexist(file) THEN lfnerror (164)
a = lfnnameold(file, which)
OPEN stripr$(lfn(which).real)
FOR INPUT AS which

END FUNCTION

Only one which will generate an LFNERROR(164) should the file not exist, all the LFNERROR's are mostly the standard QBasic codes+100. So 164 means Bad File Name, Punish it! ;)
FUNCTION lfniooutput% (file AS STRING
, which AS INTEGER)
functions called LFNEXIST
LFNNAMENEW
LFNNAMEOLD
STRIPR$
IF lfnexist(file) THEN
a = lfnnameold(file, which)
ELSE
a = lfnnamenew(file, which)
END IF
OPEN stripr$(lfn(which).real)
FOR OUTPUT AS which

END FUNCTION

I suppose I could've written a more complex LFNOPEN setup and done away with these subs, but they actually expand the system. You can add you own unique LFNOPEN's by expanding the basic SELECT CASE.

All the file functions check to see if the files exists so the routine can determine whether to call upon the subroutine which retrieves the DOS8.3 name, or call upon the newname generator.

FUNCTION lfniorandom% (file AS STRING
, which AS INTEGER
, size AS INTEGER)
functions called LFNEXIST
LFNNAMENEW
LFNNAMEOLD
STRIPR$
IF lfnexist(file) THEN
a = lfnnameold(file, which)
ELSE
a = lfnnamenew(file, which)
END IF
OPEN stripr$(lfn(which).real)
FOR RANDOM AS which LEN = size

END FUNCTION

The RANDOM OPEN is the only one which uses the SIZE parameter of LFNOPEN, like I said, I've never used the BINARY mode because generally when I access a file for direct byte access, I use RANDOM and get at big chunks, check out my GIFSIZE program. This is the only function with which you can't call LFNOPENS because then you'll end up with a size of ZERO!
FUNCTION lfnnameold% (file AS STRING
, which AS INTEGER)
functions called LFNDOS$
lfn(which).full = file
lfn(which).real = lfndos$(file)
lfn(which).used = "!"

END FUNCTION

This is primarily an assigner routine, it calls upon LFNDOS to retrieve the DOS8.3 filename. I suppose LFNDOS could've been included here, but I had my reasons for making it a separate routine for debugging this system and haven't really determined if I should tie them together.
FUNCTION lfnnamenew% (file AS STRING
, which AS INTEGER)
functions called LFNDOSPATH$
STRIPR$
STATIC old$
IF INSTR(file, "\") THEN p$ = lfndospath$(file)
lfn(which).full = stripr$(file)
lfn(which).used = "*"
DO
a$ = "tt" + LEFT$(TIME$, 2) + MID$(TIME$, 4, 2)
+ RIGHT$(TIME$, 2) + "."
+ CHR$(which + 65) + "lf"
IF a$ <> old$ THEN EXIT DO
LOOP
old$ = a$
lfn(which).real = p$ + a$

END FUNCTION

I use a unique means for generating a filename for new files, I use the current TIME$ to create a filename which is different for each instance. All the filenames start with 'TT', use the hours, minutes, and seconds, and have the extension of the filenumber+65 to generate an letter. This does have a limit, you can only generate ONE filename for each filenumber every second, and without making the code more complex, it doesn't check different extensions, though that shouldn't happen because you MUST close the filenumber before using it again. The only other exception is if you include a PATH to the file in question, then the subroutine calls upon another subroutine to get the DOS8.3 pathname, that that get's a little crazy, you'll see why in a minute because while LFNNAMEOLD doesn't do that, it calls upon LFNDOS, which does.
FUNCTION lfndos$ (item AS STRING)
functions called LFNDOSPATH$
STRIPR$
a$ = item
IF INSTR(a$, "\") THEN p$ = lfndospath$(a$)
a$ = lfndosname$(a$)
lfndos$ = p$ + a$

END FUNCTION

While this is only called upon by LFNNAMEOLD, and like I said, could be included there, when I was debugging this system, I found it handier to make it separate. Like LFNNAMENEW, it checks for the presence of a path by the simple fact that paths have a '\' in them. I don't know if you can do an OPEN "d:filename.ext", I don't program like that, I always include the backslash. Otherwise the routine calls LFNDOSNAME to retrieve the DOS8.3 name to be returned to the caller, and returns the PATH$ (if any) plus the returned name.
FUNCTION lfndosname$ (item AS STRING)
functions called QUOTE$
STRIPR$
a$ = item
SHELL "dir " + quote$(stripr$(item))
+ " >c:\temp\readit.txt"
file = FREEFILE
OPEN "c:\temp\readit.txt" FOR INPUT AS file
FOR i = 1 TO 5: LINE INPUT #file, a$: NEXT i
LINE INPUT #file, a$
CLOSE file
KILL "c:\temp\readit.txt"
a$ = stripr$(LEFT$(a$, INSTR(a$, " ") - 1) + "."
+ MID$(a$, 10, 3))
lfndosname$ = a$

END FUNCTION

If you've ever taken time to examine the directory listings produced under Win95DOS, they all have something in common, (at least mine does, I'm not sure this holds true for EVERY Win95 installation), five lines of header information, followed by the listing itself. This does a simple SHELL directory call providing the stripped and quoted filename to the call, piping the result into READIT.TXT. This is opened using the FREEFILE function to avoid filenumber conflicts, the first five lines are bypassed, then the filename line is read. We then close the file, kill it, and decypher the line we just read. There is two possible errors which I haven't run across because I never call this directly.
A: File Not Found - It was passed a non-existant file.
B: <DIR> - it was passed a filename which happens to be a directory name.
In both cases it will return a string which does not reflect what you wanted, and cause a crash.
FUNCTION lfndospath$ (item AS STRING)
functions called LFNDOSHUNT$
LFNDOSTEST
LFNDOSSEARCH$
LFNERROR
undone$ = lfndoshunt$(item)
IF lfndostest(item) THEN
ix = INSTR(undone$, "\")
done$ = LEFT$(undone$, ix - 1)
hunt$ = done$
undone$ = MID$(undone$, ix)
path$ = lfndossearch$(hunt$, "")
ELSE
ix = INSTR(undone$, "\")
done$ = done$ + LEFT$(undone$, ix)
undone$ = MID$(undone$, ix + 1)
ix = INSTR(undone$, "\")
IF ix = 0 THEN lfnerror(187)
hunt$ = LEFT$(undone$, ix - 1)
path$ = done$ + path$
+ lfndossearch$(hunt$, done$) + "\"
END IF
DO
ix = INSTR(undone$, "\")
IF ix = LEN(undone$) THEN EXIT DO
done$ = done$ + LEFT$(undone$, ix)
undone$ = MID$(undone$, ix + 1)
ix = INSTR(undone$, "\")
IF ix = 0 THEN lfnerror(187)
hunt$ = LEFT$(undone$, ix - 1)
path$ = path$ + lfndossearch$(hunt$, path$) + "\"
LOOP
lfndospath$ = path$

END FUNCTION

Things get a little hectic when you pass a path\filename to these functions, because the path needs to be converted to its DOS8.3 name as well, and this can't be done directly. At least in every method I've tried. So the only method which I've come up with which works every time is to do a DIR of the preceeding directory, and scan for the LFN starting in column 45 to equal the subdirectory name I'm hunting for, and get the DOS8.3 name out of the left-hand columns.

First thing done is: Strip The FileName Off!. Because I pass the entire path\filename to the function.
UNDONE$ is the keeper of the path to be scanned.
DONE$ holds 'unconverted' path which has been done.
HUNT$ is the directory we're looking for.
PATH$ is the converted path.

One of the things which need to be checked is whether we're starting in the current directory, or from elsewhere. If you take time to think about it, you can't use the same starting sequence for both because of the way the path works. Starting from any other directory other than the current directory involved having a '\', 'D:\', or '..\' prefixing the path. Yes, I know, '..\' does start in the current directory, but not the way the path works. This is Legal:

'D:\directory\subdirectory\..\otherdirectory\filename.ext'

So can be started in the same manner as we use in starting from the root, or drive:root combination. But when we use a directory off the current, we need to scan the current first to GET our starting point and it has to be scanned by not passing LFNDOSSEARCH the 'start path' parameter. On the other hand, we do need to pass LFNDOSSEARCH a 'start path' to get the correct starting DOS8.3 filename in the other three cases, and the 'start path' needs to be part of the resulting translation.

After getting started, it's a simple matter to scan from left to right until we run out of UNDONE path, then passing the resulting translation back to the caller. The only possible problem would be a bad path somehow gets here, resulting in a parsing problem, LFNERROR(187) is one of two error especially for LFN's, called 'Path irregularity'.

FUNCTION lfndoshunt$ (item AS STRING)
functions called none
a$ = item
DO
ix = INSTR(a$, "\")
IF ix = 0 THEN EXIT DO
b$ = b$ + LEFT$(a$, ix)
a$ = MID$(a$, ix + 1)
LOOP
lfndoshunt$ = b$
END FUNCTION
Simple to explain, scan until you run out of '\'s, and pass everything from here left back.
FUNCTION lfndossearch$ (item AS STRING
, where AS STRING)
functions called LFNERROR
item = UCASE$(item)
SHELL "dir " + where + "*. >c:\temp\readit.txt"
file = FREEFILE
OPEN "c:\temp\readit.txt" FOR INPUT AS file
FOR i = 1 TO 5: LINE INPUT #file, a$: NEXT i
DO
LINE INPUT #file, a$
IF ASC(a$) = 32 THEN lfnerror (186)
IF UCASE$(MID$(a$, 45)) = item
THEN EXIT DO
LOOP
CLOSE file
KILL "c:\temp\readit.txt"
b$ = LEFT$(a$, INSTR(a$, " ") - 1)
IF ASC(MID$(a$, 10)) <> 32
THEN b$ = b$ + "." + stripr$(MID$(a$, 10, 3))
lfndossearch$ = b$

END FUNCTION

This is the scanning routine and we do it by simply blasting the contents of the previous directory into READIT.TXT and looking for our match. As far as I've been able to determine, you can't have the same name twice, regardless of capitalization, try it sometimes. So to avoid the possibliliy of passing up the name, all comparisons are done in UCASE. Like most of the other SHELL dir call's, we bypass the first five lines, and commence with looking for the LFN directory at line six. You will note that when we called this routine, we passed it the current translated path, if any, for it to work with. Namely because you put quotes around [*.], you get different results than without. Try that sometime too. The only possible error from here is that it's a bad directory name it's looking for, and rather than hitting a EOF error, we check for a space as being the first character in the directory line, also part of a standard listing. We find that, we produce LFNERROR(186), 'Couldn't resolve path'. Otherwise the found name is extracted from the left-hand side just like the filename resolver, except if column 10 contains anything BUT a space. Then it adds the '.3' extension to the directory.

BTW, in case you haven't figured it out from the previous use of the command, the dos directory listing always has a space at column 9, but we want everything before the FIRST space in the line.

FUNCTION lfndostest% (item AS STRING)
functions called none
IF LEFT$(item, 1) = "\" THEN EXIT FUNCTION
IF LEFT$(item, 3) = "..\" THEN EXIT FUNCTION
IF MID$(item, 2, 1) = ":" THEN EXIT FUNCTION
lfndostest = true

END FUNCTION

This is just a minor routine used by LFNDOSPATH to determine whether it's staring from the current directory or not. The easy way to describe this is:

IF NOT (one of these three checks) RETURN FALSE

FUNCTION lfnexist% (which AS STRING)
functions called QUOTE$
STRIPR$
a$ = which
errnum = false
SHELL "dir /b/v " + quote$(stripr$(a$))
+ " >c:\temp\readit.txt"
file = FREEFILE
OPEN "c:\temp\readit.txt" FOR INPUT AS file
IF EOF(file) THEN errnum = true
CLOSE file
KILL "c:\temp\readit.txt"
IF errnum THEN lfnexist% = false
ELSE lfnexist% = true

END FUNCTION

I'm sure some of you have made some form of a EXIST function before, but doing it under LFNQBasic has it's own problems. First off, you need the DOS8.3 name to execute a RENAME test, and SHELLing to DOS and asking for it to do a RENAME test leads to the problem, How Do You Get The Error Message?!? Well, this does the job with the SHELL dir action, using the '/B/V' switches for
'/B' Uses bare format (no heading information or summary).
'/V' Verbose mode.
So that all we will end up with is either one of two results:
A: file exists, there is a line in READIT.TXT
B: file doesn't exists, READIT.TXT is empty.
Rather that try to read the file and seeing if we generate an error, pop open the file and see if the first thing we get is EOF(file) = TRUE and follow through with setting the ERRNUM flag as needed. BTW, while this is the only place ERRNUM is used, I made it GLOBAL for debugging reasons.
SUB lfnerror (what AS INTEGER)
functions called LFNCLOSE
PRINT "LFN Error: ";
SELECT CASE (what)
CASE (154)
PRINT "Bad file mode"
CASE (155)
PRINT "File already open or name already used"
CASE (176)
PRINT "Path not found or path/file access error"
CASE (164)
PRINT "File not found"
CASE (178)
PRINT "General catchall error message"
CASE (186)
PRINT "Couldn't resolve path"
CASE (187)
PRINT "Path irregularity"
END SELECT
PRINT "Active files:"
FOR i = 1 TO lfnmax
IF ASC(lfn(i).used) <> 32
THEN PRINT i; lfn(i).full
lfnclose (i)
NEXT i
BEEP
STOP

END SUB

Here we do two things, print the LFN error message, and close all the open files. Like the ERRHAND up top, if you want to do any sort of 'live' work, you will need to adjust this so that it DOESN'T close the files, but do remember that if you create any NEW files, they ahve that unique filename, and you may find yourself with a directory full of TThhmmss.l's. I did when I first started working out the details of these routines, more that 300 of them!

BTW: I could've called LFNCLOSEALL, but for debugging reasons I wanted to include a system where it printed all open files at the same time.

FUNCTION quote$ (item AS STRING)
functions called none
IF ASC(item) <> 34
THEN item = CHR$(34) + item + CHR$(34)
quote$ = item

END FUNCTION

While this could've been elimated and code straight into the routines, it does keep the codelines shorter so that it's easier to read. Besides which, I use it in other programs.
FUNCTION stripl$ (item AS STRING)
functions called none
DO
IF item = " " THEN EXIT DO
IF ASC(LEFT$(item, 1)) <> 32 THEN EXIT DO
item = MID$(item, 2)
LOOP
stripl$ = item

END FUNCTION

This is extra, it's not needed.

It's included as a compliment to STRIPR.

FUNCTION stripr$ (item AS STRING)
functions called none
DO
IF item = " " THEN EXIT DO
IF ASC(RIGHT$(item, 1)) <> 32 THEN EXIT DO
item = LEFT$(item, LEN(item) - 1)
LOOP
stripr$ = item

END FUNCTION

This is needed because QBasic pads it's TYPE strings with spaces, and you can't just use the first space as a kill from here point because it may be part of the filename. It was just as easier to do it like this as in a FOR:NEXT loop, either one would work, and cause a crash when passed a ALL-SPACE string. Hence the check for a single space left in the string before the test. It could be extended to return a null string if that happens, 'snot hard to do.