Solved

'rename' function in Win3.1, WinNT vs. Win95 OS

Posted on 1997-11-20
7
942 Views
Last Modified: 2006-11-17
I am somewhat at an end with the rename(), _chdir() and _chdrive() run-time functions. (I am developing a 16-bit program, MSVC 1.5) Function calls within NT 4.0 and Windows 3.1 works great. My return value is zero. When I call the very same functions in Windows 95, all three fail

Does anyone have a suggestion or comment on this??

Please respond to struax@compuserve.com
0
Comment
Question by:stevetr
7 Comments
 
LVL 1

Expert Comment

by:cph
ID: 1256378
INT 21 7156 - Windows95 - LONG FILENAME - RENAME FILE
Inp.:
      AX = 7156h
      DS:DX -> ASCIZ old file or directory name (long names allowed)
      ES:DI -> ASCIZ new name (long names allowed)
Return: CF clear if successful
      CF set on error
          AX = error code
            7100h if function not supported
Note:      the file may be renamed into a different directory, but not across disks

INT 21 56-- - DOS 2+ - "RENAME" - RENAME FILE
Inp.:
      AH = 56h
      DS:DX -> ASCIZ filename of existing file (no wildcards, but see below)
      ES:DI -> ASCIZ new filename (no wildcards)
      CL = attribute mask (server call only, see below)
Return: CF clear if successful
      CF set on error
          AX = error code (02h,03h,05h,11h) (see #1332)
Notes:      allows move between directories on same logical volume
      this function does not set the archive attribute
        (see #1073 at AX=4301h), which results in incremental backups not backing up the file under its new name open files should not be renamed (DOS 2.x only) this function renames file by creating a new directory entry with the new name,then marking the old entry deleted (DOS 3.0+) allows renaming of directories
(DOS 3.1+) wildcards are allowed if invoked via AX=5D00h, in which case  error 12h (no more files) is returned on success, and both source and destination specs must be canonical (as returned by AH=60h).

That's a kernel point of view.
My opinion is that under windows 95 before you want to do anything you got to lock the file....

Hope this help,

CpH
0
 

Author Comment

by:stevetr
ID: 1256379
Edited text of question
0
 
LVL 3

Expert Comment

by:LucHoltkamp
ID: 1256380
If have no problems what so ever with rename() etc. under Borland 5.0 and Win95.
.luc.

0
How your wiki can always stay up-to-date

Quip doubles as a “living” wiki and a project management tool that evolves with your organization. As you finish projects in Quip, the work remains, easily accessible to all team members, new and old.
- Increase transparency
- Onboard new hires faster
- Access from mobile/offline

 
LVL 2

Expert Comment

by:AlFa
ID: 1256381
Long Filenames Functions
The long filename functions match the following Win32 file management functions.

Long filename function Win32 function
Interrupt 21h Function 5704h
Get Last Access Date and Time GetFileTime
Interrupt 21h Function 5705h
Set Last Access Date and Time SetFileTime
Interrupt 21h Function 5706h
Get Creation Date and Time GetFileTime
Interrupt 21h Function 5707h
Set Creation Date and Time SetFileTime
Interrupt 21h Function 7139h
Make Directory CreateDirectory
Interrupt 21h Function 713Ah
Remove Directory RemoveDirectory
Interrupt 21h Function 713Bh
Change Directory SetCurrentDirectory
Interrupt 21h Function 7141h
Delete File DeleteFile
Interrupt 21h Function 7143h
Get or Set File Attributes GetFileAttributes, SetFileAttributes
Interrupt 21h Function 7147h
Get Current Directory GetCurrentDirectory
Interrupt 21h Function 714Eh
Find First File FindFirstFile
Interrupt 21h Function 714Fh
Find Next File FindNextFile
Interrupt 21h Function 7156h
Rename File MoveFile
Interrupt 21h Function 7160h Minor Code 0h
Get Full Path Name GetFullPathName
Interrupt 21h Function 7160h Minor Code 1h
Get Short Path Name GetShortPathName
Interrupt 21h Function 7160h Minor Code 2h
Get Long Path Name No Win32 function equivalent
Interrupt 21h Function 716Ch
Create or Open File CreateFile, OpenFile
Interrupt 21h Function 71A0h
Get Volume Information GetVolumeInformation
Interrupt 21h Function 71A1h
Find Close FindClose
Interrupt 21h Function 71A6h
Get File Info By Handle GetFileInformationByHandle
Interrupt 21h Function 71A7h Minor Code 0h
File Time To DOS Time FileTimeToDOSDateTime
Interrupt 21h Function 71A7h Minor Code 1h
DOS Time To File Time DOSDateTimeToFileTime
Interrupt 21h Function 71A8h
Generate Short Name No Win32 function equivalent
Interrupt 21h Function 71A9h
Server Create or Open File No Win32 function equivalent
Interrupt 21h Function 71AAh Minor Code 0h
Create Subst No Win32 function equivalent
Interrupt 21h Function 71AAh Minor Code 1h
Terminate Subst No Win32 function equivalent
Interrupt 21h Function 71AAh Minor Code 2h
Query Subst No Win32 function equivalent


Note that Interrupt 21h Functions 71A2h through 71A5h exist, but they are for internal use by Windows 95 only.

0
 
LVL 2

Expert Comment

by:AlFa
ID: 1256382
Use network driver to deal with long file name :

The network driver can provide a variety of long-filename functions. The long-filename functions give access to files with names that do not fit the MS-DOS standard filename convention on network drives. These are the following long filename functions.
 
Function      Description

LFNCopy      Copies a file.
LFNDelete      Deletes a file.
LFNFindClose      Ends the search for matching files.
LFNFindFirst      Searches for the first file with a matching name.
LFNFindNext      Searches for the next file with a matching name.
LFNGetAttributes      Retrieves file attributes.
LFNGetVolumeLabel      Retrieves the volume label.
LFNMKDir      Creates a directory.
LFNMove      Moves a file.
LFNParse      Parses paths.
LFNRMDir      Removes a directory.
LFNSetAttributes      Sets file attributes.
LFNSetVolumeLabel      Sets the volume label.
LFNVolumeType      Specifies volume type.
Typically, these functions are called only when a network drive with long filenames is detected. In other words, they do not need to support local drives. The exception is the LFNCopy function, which must be able to copy to or from any type of volume.
The return value is typically an MS-DOS error function (as returned from MS-DOS functions when the carry flag is set, or from Interrupt 21h Function 59h, Get Extended Error Information). If a function returns the special error value 0xFFFF, then the network error message functions will be called to retrieve error message text.
The maximum length of any long filename will be assumed to be 260 characters, including the terminator.
File-attribute values are identical to MS-DOS file-attribute fields.


0
 

Author Comment

by:stevetr
ID: 1256383
Disregard this question. I had to create a new file, B, use
_lread() to read file A and _lwrite() to file B. Then I deleted file A.

Thanks for your input,

Steve
0
 
LVL 2

Accepted Solution

by:
AlFa earned 100 total points
ID: 1256384
An other try

I get in ms jornal :
The Long Name API
The new set of INT 21H APIs gives access to the long-name functionality for 16-bit applications (see Figure 1). Applications built for earlier versions of MS-DOS continue to work unchanged because all the earlier INT 21H calls work as before. In general, each 8.3 function that uses a filename now has a counterpart in which AH is equal to 71H (a previously unsupported function) and AL is equal to the old function number. For example, Function 6CH is the extended file open/create API introduced in MS-DOS version 4.0. Function 716CH is the long filename equivalent. Notice that there are no long filename equivalents for Functions 3CH (Create File) and 3DH (Open Existing File) because Function 6CH encompasses the functionality of both.
Figure 1 Long Filename INT 21H Functions

7139H      Create Directory
713AH      Remove Directory
713BH      Set Current Directory
7141H      Delete File
7143H      Get/Set File Attributes
7147H      Get Current Directory
714EH      Find First File
714FH      Find Next File
7156H      Move File (aka Rename File)
716CH      Extended Open/Create File
71A0H      Get Volume Information
71A1H      Find Close
Most of these functions expect the same register contents and deliver back the same outputs as the corresponding 8.3 functions. I’ve built a C-callable interface to these routines that I’ll describe later on. The listings of those interface routines also document the register contents for the new functions, so I won’t repeat that information.
While most of the new functions are familiar from older versions of MS-DOS, Find First/Next is used a little differently in Chicago. Function 714EH (Find First) requires the address of a results buffer, identical in format to the WIN32_FIND_DATA structure, in ES:DI and returns a search handle in AX if it succeeds. Hooray! It’s no longer necessary to muck with the Disk Transfer Area address to use the function. Function 714FH (Find Next) requires the search handle in BX and the results buffer address in ES:DI. Also, in order to release resources at the end of a series of Find Next calls, you must issue Function 71A1H, Find Close, with BX equal to the search handle.
Function 71A0H, Get Volume Information, is also new with Chicago. To call this function, set ES:DI to point to a buffer that will receive the name of the current file system (for example “FAT”) and CX equal to the length of that buffer. Set DS:DX to point to a NULL-terminated string naming the root directory of the designated drive (for example “C:\”). Finally, set AX equal to 71A0H and issue INT 21H. On return, BX will hold flag bits (see Figure 2). CX holds the maximum length of a filename for the volume (including the NULL terminator), and DX holds the maximum length of a pathname for the volume (also including the NULL terminator).
Figure 2 Flags Returned in BX by INT 21H Function 71A0H

Flag Bit      Meaning
8000      Volume is compressed
4000      File system supports long filename functions
0004      Uses Unicode in file and directory names
0002      Preserves case in directory entries
0001      Searches are case sensitive
When I used Function 71A0H to inquire about the hard disk on my M5-level Chicago system, I found that I was running a FAT file system that supports long filename functions, uses Unicode, and preserves case. The maximum filename length was 255 and the maximum pathname length was 260. That response reveals an inconsistency between the online help file that documents Function 71A0H and the actual implementation. The online help says that the function returns the maximum pathname length, excluding the drive letter and leading backslash. In that case, the function would have returned 257 instead of 260. The actual behavior of Function 71A0H is preferable because it tells you how much memory to allocate for a buffer.
If your application uses these new functions, you should also provide for running on systems that don’t support these functions. Microsoft recommends that you use Function 71A0H to see if the system supports long filenames. If not, you must use 8.3 APIs exclusively. If it does support long filenames, you would use only long name APIs. You can also use detection code like the following fragment:
 
    mov    ax, 71xxh
    stc
    int    21h
    jnc    success
    cmp    ax, 7100h
    jne    failure
    mov    ah, xx
    int    21h
    jc     failure
success:
 
Under an earlier version of MS-DOS, Function 71XXH clears the AL register and leaves the carry flag unchanged. You would end up reissuing INT 21H with the original function code in AH instead of AL. Under Chicago, Function 71XXH sets or clears the carry flag depending on whether it succeeds or not, but an error will leave AX holding an error code instead of 7100.
In a performance-critical situation, you might not want to use the alternate technique shown in the fragment because you can end up executing two INT 21H instructions instead of one. This is expensive in real-mode MS-DOS and worse if your program is running in protected mode under Windows_ or an MS-DOS extender where it causes an additional switch to real mode and back. But since the availability of long filename support depends on the version of the operating system and the disk drive you’re addressing, you may need to use this code despite its potential performance problems. I tested this code with the MS-DOS extender that’s built into Windows (both Chicago and pre-Chicago) and it worked. If you’re using a commercial MS-DOS extender that handles INT 21H calls itself, test the code before relying on it.
Do not, by the way, rely simply on the MS-DOS version number to decide whether to use long filename functions. Chicago’s MS-DOS, for example, identifies itself as 7.0 in response to INT 21H, Function 30H. But VFAT is implemented as part of the Windows component of Chicago, not as part of MS-DOS. In Single MS-DOS Application Mode, you’re running MS-DOS raw. You can also run under raw MS-DOS by preventing Windows from starting up at boot time (by making sure, for example, that the path contains an empty WIN.BAT before the AUTOEXEC finishes). In either case, an MS-DOS version query will yield 7.0, but you can’t access VFAT services like long filenames.
Alias Names
Compatibility between VFAT and FAT ensures that current applications that understand only 8.3 filenames can operate on files having long names. When an application creates a file, the API it uses determines whether the file has a long name. Whereas using the old API creates a file having just an 8.3 name, using the new API creates a file having both a long name and an 8.3 alias. The basic algorithm for constructing the alias is to select the first eight characters of the long name and convert them to uppercase. If the resulting name already exists in the same directory, the last two characters are replaced by a tilde and a unique integer. There are many other complications related to the algorithm, depending on the presence of dots or spaces in the long name, the existence of more than 10 duplicate names, and so on. But the basic description gives you the idea and helps you understand what is on your disk.
The alias depends both on the long name and on the directory in which the file lives. If you change a file’s long name or copy the file to a different directory, the alias may change. For example, suppose I create a subdirectory named Microsoft® C Compiler in a directory that already contains a subdirectory named Microsoft Office Applications. VFAT would like to use the alias MICROSOF for the new subdirectory, but because that’s already in use it assigns the alias MICROS~1 instead. If I move this new subdirectory to another parent, the alias might stay the same or change to MICROSOF, to MICROS~2, or to something else. If I rename this new subdirectory to C Compiler, the alias will become CCOMPILE, CCOMPI~1, or the like.
If you change a file’s alias, VFAT has no choice but to discard the long name because it can no longer correlate the two names. Beware of this when you use a pre-Chicago tool like the popular MOVE or RENDIR utilities or if you maintain the directory under an earlier version of MS-DOS. Similarly, if you delete a file under either name, VFAT removes both names from the directory. You might hope there’d be no problem renaming a long-named file under an operating system that doesn’t understand long filenames in the first place, but that’s not the case. As an experiment, I renamed a file under MS-DOS 6. Chicago failed to detect the long name from then on, perhaps because it and the 8.3 name no longer matched according to the alias-generation rules. Worse yet, when I deleted the file, the directory entry for the long name remained allocated and therefore unavailable for reuse until recovered by a disk utility.
While aliases will at least allow older applications to keep running in Chicago, they aren’t a panacea. Consider one common programming practice that can trip on the alias maintenance rules. To avoid losing data, I often build an updated or edited disk file under a temporary name first. Then I delete the target file and rename the temporary one. The delete-and-rename sequence works fine under Chicago if your application uses only long filename API calls. If you use 8.3 API calls, however, you will lose any long filename associated with the file. I’m told future version of Chicago will permit the long name to “tunnel” from a deleted directory entry to a renamed directory entry to avoid this problem.
I also encountered trouble undeleting a file that had a long name prior to deletion. Evidently the command encountered an access-denied error internally, for it reported that I was attempting to write to a read-only disk. This problem apparently occurs because the volume needs to be exclusively locked; it has reportedly been corrected by adding volume-locking code to the M6 version of UNDELETE.
Another side effect of aliases occurs when you use the Find First and Find Next functions. For compatibility, the Chicago implementation of these APIs checks both the long and alias names. Consequently, a search for the pattern “*1” might find a file named LongFileName just because VFAT created an alias of “LONGFI~1” for this file. In that case, you would find the long name in the cFileName member of the returned WIN32_FIND_DATA structure and the alias in the cAlternateFileName member. Microsoft recommends that you check both members to see which one caused a match. It helps to know that the alternate filename won’t be stored if the main filename already holds an 8.3 name.
Limits to Name Lengths
As mentioned, VFAT supports pathnames up to 260 characters long, including the NULL terminator. Individual names within a pathname can be up to 256 characters, again including the NULL terminator. If you’re in the habit of reserving fixed-size buffers to hold filenames, you may run into trouble almost immediately. FAT provides for a maximum pathname length of 81 bytes, including the NULL terminator. This consists of three bytes for the drive letter and initial backslash (for example, “C:\”), 64 bytes for the current directory name, and 13 bytes for the final backslash and 8.3 filename. If you’ve got 81-byte buffers lying around, therefore, you’ll need to resize them.
Here’s a seemingly benign piece of code that can cause trouble:
 
#include <stdlib.h>
char drive[_MAX_DRIVE], dir[_MAX_DIR], fname[_MAX_FNAME],
   ext[_MAX_EXT], fullpath[_MAX_PATH];
_splitpath(filename, drive, dir, fname, ext);
_makepath(fullpath,drive,dir,fname,ext[0]?ext: ".C");
 
I’ve used code like this countless times to parse and recompose pathnames in various ways. Microsoft Visual C++_ 1.5 defines the manifest constants used to declare drive and so on, as 3, 256, 256, 256, and 260, respectively, and these values are adequate for VFAT names. Earlier versions of Microsoft compilers, and compilers from other vendors, may use smaller values. For example, I found that version 9.5 of the Watcom compiler gives you these numbers if you define one of the preprocessor macro names __OS2__ or __NT__. Otherwise, it gives you the much smaller values 3, 130, 9, 5, and 144, respectively.
The minimum you have to do to accommodate VFAT names, therefore, is modify any buffer dimensions you may be using and recompile your application. Microsoft suggests, however, that you dynamically allocate the buffers you’ll use for filenames and their components.
Implementing Long Filenames
Although details about how long filenames are implemented on top of FAT are not yet available (and are also subject to change), let’s take a brief look at the internal operation of this feature. I created a file named Test File using the command
 
echo Hello, World! >"Test File"
 
Predictably, the file had an alias of TESTFILE. The “main” directory entry for the file contains the data shown in Figure 3.
Figure 3 Main Directory Entry for TESTFILE

Offset      Contents (Hex)      Interpretation
00      54455354 46494C45      Filename (TESTFILE)
08      202020      Extension (blank)
0B      20      Attribute (Archive)
0C      00000000 00000000 0000      Reserved
16      FA6D A51C      Date/time of last update
1A      FB4E      Starting cluster
1C      0D000000      File size
The long filename appears in a separate directory entry (see Figure 4). In effect, Microsoft increased the size of a directory entry for VFAT so that FAT code sees only the part it understands. The directory entries that contain the long filename simply precede the main entry on disk. There are as many entries as needed to record the long name, with up to 13 characters per extra directory entry. The bytes at offsets 00H and 0BH and the words at offsets 0CH and 1AH all contain control information. The remainder of the entry contains Unicode characters that form part of the long name. Byte 00 itself is a flag.
Figure 4 Long Filename Entry for TESTFILE

Offset      Contents (Hex)      Interpretation
00      41      1st and last (40) entry
01      5400 6500 7300 7400 2000      Unicode string "Test "
0B      0F      Impossible attribute byte
0C      009B      ?
12      4600 6900 6C00 6500 0000      Unicode string "File"
18      FFFF      Filler
1A      0000      Impossible sector number
1C      FFFFFFFF      Filler
The 40H bit is set for the entry containing the last few characters of the name; this entry appears physically first in the directory. The low-order 5 bits of the flag contain an integer indicating where this entry fits in the sequence of long name entries, with 1 being the first. In all, 32 entries of 13 characters each hold 403 characters including a NULL terminator, much more than the maximum length of 256.
Byte 0BH of the directory entry is interpreted by many programs as an attribute byte. In a long filename entry, this byte is always 0FH. This value indicates the otherwise impossible combination of read only, hidden, system, and volume label. There’s no documented way to access one of these directory entries apart from absolute sector reads. In particular, a Find First call with an attribute mask of 0FH won’t find one of these entries. Word 1AH will be interpreted as the starting cluster number for the file. This word is always 0, which is the same value that an empty file would contain. No one at Microsoft could tell me what the two bytes at offset 0CH are. In practice, byte 0CH is always 0, while byte 0DH varies with the alias for the file. My guess is that this byte is computed by hashing the alias in some way and serves to verify that the long name and the alias belong together.
Microsoft recognizes that developers of disk utilities need to know what the long name directory entries contain and is planning to publish documentation.
This implementation of long filenames seems at first blush to elegantly balance the need for additional functionality while preserving compatibility. Of course, you can find pathological situations where sufficient adjacent empty directory entries won’t exist to store the long filename. This could be a frequent problem in a disk’s root directory, whose fixed size is determined during formatting time. Note also that a long filename entry isn’t flagged using a reserved attribute bit. Instead, a previously impossible combination of existing bits flags these entries. Some disk utilities may fail as a result (for example, the Windows NT version of CHKDSK needs to be updated from the Chicago disk). In any case, disk utilities that rearrange directory entries must be modified to preserve the sequential clustering of long-filename and regular entries. On the plus side, storing the additional information in the directory helps protect it from corruption.
Character Sets
By default, the characters in a long filename belong to the ANSI character set. There are plans to provide a SetFileAPIsToOEM function to change to the OEM character set. At the time of the M5 release last December, the long names were still stored on disk as Unicode strings. Even so, M5 limits you to the lower 127 characters of the ANSI set. For example, I got a file creation error from the following command:
 
C:\TEST>echo Hello from Bavaria! >"Grüß Gott"
 
The error presumably arose from using the special German language characters in the name.
For the future, it seems that Microsoft will stick with storing long filenames in Unicode, which is the right choice. Even so, Chicago will only support the ANSI versions of the Win32® API. The 8.3 aliases will be stored in the OEM character set and will presumably display according to the current code page. If either a 16-bit or a 32-bit application uses the long filename API to retrieve a name that doesn’t map into the ANSI character set, the current plan is to substitute an underscore for the unmappable bytes. Beware that renaming a file containing these substituted underscores will result in information being lost.
Coding Portable 32-bit Applications
When building 32-bit applications, it’s easy to take advantage of VFAT’s new features. Running in Chicago, the Win32 API is ready for long filenames. Presently, however, some of the details of the user interface remain preliminary. For example, the GetOpenFileName API in the M5 version will list long filenames (in lowercase) but won’t allow you to select one. You can freely navigate through long-named directories, though. I fully expect this API to be corrected as the Chicago beta program progresses.
The run-time library that accompanies the 32-bit edition of Microsoft Visual C++ 1.0 uses the Win32 API internally and therefore can use long filenames. Any application that simply passes filename strings through to a Win32 API will automatically take advantage of this fact. Consider the following simple program:
 
#include <stdio.h>
void main(int argc, char *argv[]) {
   FILE *fp = fopen(argv[1], "r");
   char buffer[256];
   while (fgets(buffer, sizeof(buffer), fp))
      fputs(buffer, stdout);
   fclose(fp);
   }
 
I compiled this program using the 32-bit Chicago SDK and used double quotes to be sure that the long filename would be parsed into the single value argv[1]:
 
C:\TEST>test "Test File"
Hello, world!
 
For much the same reason, the following command successfully invoked the 32-bit version of the venerable Notepad application (replaced by WordPad in M6) against a long-named file, but I had to rename the file to match the application’s assumptions about file extensions:
 
C:\TEST>note32 "Test File.txt"
 
What about Win32-based applications running on Windows NT or under Win32s? If you run Windows NT version 3.1 on a FAT partition, it won’t support long filenames. And Win32s running under Windows 3.1 relies on the underlying FAT file system and can also only handle 8.3 names. You shouldn’t have any trouble so long as you don’t hard-code pathnames into the application or any data files, or leave it to the operating system to validate the format of any user filenames.
Coding Portable 16-bit Applications
If you’re updating a 16-bit application, you have more work to do. The 8.3 aliasing scheme described earlier lets you write an old-style MS-DOS-based or Windows-based application that can at least access all the files in a Chicago system. As mentioned, you will inadvertently destroy the long name of a file if you rename it using the 8.3 API. And, unless you modify your application, you won’t be able to do anything with long names from users. I’ve written some APIs to ease the pain of making long filenames accessible to 16-bit programs. Before discussing the code in detail, though, I’ll discuss the design considerations for these APIs.
I wondered why Microsoft decided to invent new functions for all the MS-DOS APIs that manipulate filenames. If I’m trying to open a file, for example, I’m likely to derive the string from user input. If the user specifies a well-formed long filename and I simply pass it through to the operating system, I don’t care that it’s a long name, do I? It seemed to me that VFAT could match a string sent from the application to the long name for a file. If the string wasn’t found in the directory as a long name, it could look at the alias. This kind of search would be slightly more expensive than a search for only one kind of name at a time (which is what I infer happens now with the long and 8.3 versions of each function). But it seems that the extra cost would be absorbed by the disk or cache access to the directory. In any case, if you adopt the approach of first trying the long name and then the 8.3 version of each API, the cost will be more than a low-level double search.
Several MS-DOS functions send name strings back to the application. VFAT couldn’t simply assume that your buffer is long enough to hold the new longer strings, so having new long filename versions of these functions is essential. Further on, I’ll cover the implications of this. It also turns out that some applications depend on the fact that MS-DOS truncates names longer than the current limits. To avoid breaking these applications, Microsoft had to invent a new API.
Thinking that application logic might easily accept long filename input from users, I first wanted to craft an API that would automatically give 16-bit applications the benefits of long filenames. I thought I might be able to build a TSR that would intercept selected file system requests and turn them into their long-name counterparts. For example, if an MS-DOS or Windows-based application tried to open a file using INT 21H, Function 3DH, I could substitute an INT 21H Function 716CH with appropriate parameters. If the application would tolerate embedded blanks and other special characters in filenames, you wouldn’t have to modify the application to gain long-name compatibility.
As it turns out, a file system interceptor TSR is fine for previous versions of Windows, but is useless for Chicago because VFAT isn’t part of MS-DOS. Rather, it’s part of the web of virtual device drivers that make up the VMM32 (formerly WIN386) operating system underneath Windows. When an MS-DOS-based or Windows-based application issues a file-system INT 21H under Chicago, VxD code handles the request from start to finish. A real-mode TSR never even sees the interrupt. Chicago will feature a 3.1 compatibility mode that lets a TSR see file-system INT 21H functions. No long-name functions will be supported in this mode, however. As best as I can tell, you would need to write a complete file system provider VxD to perform the kind of automatic redirection I envisioned.
I also discovered that I had been much too optimistic about how many applications might “just run” with long names. Word for Windows 2.0, for example, won’t tolerate a quoted string as a filename. The character-mode MS-DOS edit command apparently parses its command line in a nonstandard way too, because a command like
 
C:\TEST>edit "Test File"
 
elicits the help text showing how to use the edit command. In other words, even if I knew enough to write a file system provider VxD, it wouldn’t actually improve very many applications.
Owners of 16-bit applications will therefore have to rework them to support long filenames. A few of the long-name functions—namely, Find First/Next, Find Close, and Get Current Directory—return information in a different format from their 8.3 predecessors. Get Volume Information is new.
Using the new functions evidently requires source code changes. This being so, I decided that the most natural way to define the API was to mimic the names and arguments of corresponding Win32 functions. Unfortunately the Win32 GetVolumeInformation API doesn’t exactly fit the requirements. It returns the maximum length of a filename component but not the maximum length of a complete pathname. It also bears excess baggage in the form of volume label and serial information. I decided to extend this API and rename it.
The other long-name functions transmit strings from the application to the operating system. After installing a more tolerant name parser, you might be able to substitute a different INT 21H call at the lowest level of your subroutine library. Further on I’ll show how I did this for the functions in the Microsoft Visual C++ 1.5 run-time library, and you can adapt it to fit your own case.
I wrote a small test program to demonstrate the new functions and the run-time library replacements I’ll describe further on. You can retrieve the test program source from any MSJ bulletin board. To run the test program under Chicago, type commands like this:
 
C:\TEST>echo Hello, world!> "Test File"
C:\TEST>test *.c "Test File" "Test Directory"
 
The test program uses GetVolumeInformationEx to retrieve information about the file system and the new Find First/Find Next API to locate all files matching the pattern given as the first argument. It types out the contents of the file named in the second argument. Finally, it creates and later deletes a directory with the name given by the third argument. You can also run the test on a non-Chicago system by using legal 8.3 names for all three arguments.
Imitating WIN32
I wrote a program named LONGNAME.ASM (excerpted in Figure 5) to implement the Win32 lookalike functions needed to retrieve long filenames from the operating system. To obtain the entire source (including a header file named LONGNAME.H that will let you call these functions from C), access any MSJ bulletin board. Figure 5 shows the GetCurrentDirectory and GetVolumeInformationEx functions.
Figure 5 Excerpts from LONGNAME.ASM
 
;==============================================================================
;
;    LONGNAME.ASM -- 16-bit lookalikes for selected WIN32 APIs
;
;    Written by Walter Oney
;
;==============================================================================

     name  longname
     .model small, c
     .386
byp  equ   <byte ptr>
wp   equ   <word ptr>
dwp  equ   <dword ptr>

     .code

;    [Note: Code and data omitted for FindFirstFile, FindNextFile, FindClose,
;    and date conversion routines used by those functions. Please see any
;    MSJ bulletin board for full details.]

GetCurrentDirectory proc far pascal uses si di, cbPath:dword, lpszPath:dword
     local dirname[260]:byte
     mov   lasterr, 0          ; assume call will succeed
     mov   ah, 19h             ; fcn 19: get default disk drive
     int   21h                 ; issue DOS request
     add   al, 'A'             ; convert 0 to A, etc.
     mov   dirname, al         ; build initial part of name
     mov   dirname+1, ':'      ;   ..
     mov   dirname+2, '\'      ;   ..
     push  ds                  ; save DS across call
     mov   ax, 7147h           ; fcn 7147: get current directory (long name version)
 

 
     xor   dl, dl              ; DL = drive (0 means default drive)
     mov   si, ss              ; DS:SI -> buffer to receive name
     mov   ds, si              ;   ..
     lea   si, dirname+3       ;   ..
     stc                       ; set carry in case not supported
     int   21h                 ; issue DOS request
     jnc   success             ; skip if call succeed in long-name form
     cmp   ax, 7100h           ; check for bona fide error
     jne   failure             ; skip if so
     mov   ah, 47h             ; reissue request in 8.3 form
     int   21h                 ;   ..
     jc    failure             ; skip if error
success:les  di, lpszPath      ; ES:DI -> destination buffer
     mov   cx, wp cbPath       ; CX = length of destination incl null
     cld                       ; should already be clear, but make sure
     lea   si, dirname         ; DS:SI -> start of current directory
@@:  test  cx, cx              ; exhausted buffer count yet?
     jz    toosmall            ; if yes, leave loop
     lodsb                     ; copy a byte from our buffer to destination
     stosb                     ;   ..
     dec   cx                  ; reduce residual buffer count
     test  al, al              ; reached null terminator yet?
     jnz   @B                  ; if not, continue
;    Indicate success by returning the number of bytes copied to the caller's
;    buffer. If we're returning the root directory, the length is zero. The
;    caller can tell this isn't an error by calling GetLastError. Otherwise,
;    the return value will be nonzero but less than the cbPath argument
;    because we don't include the trailing null terminator.
     mov   ax, wp cbPath       ; get buffer length again
     sub   ax, cx              ; # bytes we copied
     dec   ax                  ; (don't include null terminator)
     pop   ds                  ; restore DS
goback:  ret                   ; return to caller
;    Indicate failure by returning zero. Caller can get the error code by
;    calling GetLastError.
failure: pop   ds              ; restore DS
     mov   lasterr, ax         ; save error code
     xor   ax, ax              ; set return code zero to indicate failure
     jmp   goback              ; done
;    Here when the caller's buffer is too small to hold the string. We've
;    already copied cbPath bytes. DS:SI points to the first byte we weren't
;    able to copy.
toosmall:pop   ds              ; restore DS
     mov   di, ss              ; set ES:DI -> uncopied string
     mov   es, di              ;   ..
     mov   di, si              ;   ..
     xor   al, al              ; get zero to compare against
     dec   cx                  ; get infinite count
     repne scasb               ; scan for null terminator
     neg   cx                  ; get number of characters left in string
     dec   cx                  ;   ..
     dec   cx                  ;   ..
     add   cx, wp cbPath       ; include the stuff we were able to copy
     mov   ax, cx              ; set AX = return value
     jmp   goback              ; return to caller
GetCurrentDirectory endp
 

 
;    [Code for GetLastError omitted for brevity. Please see any MSJ bulletin
;    board for full details.]

GetVolumeInformationEx proc far pascal uses di si, lpRoot:dword, \
                           lpVolName:dword, cbVolName:dword, \
                           lpVolSer:dword, lpMaxName:dword, \
                           lpMaxPath:dword, lpFlag:dword, \
                           lpFSName:dword, cbFSName:dword
     local diskinfo:dinfo      ; to fill in via 21/69
     mov   lasterr, 0          ; assume function will succeed
     push  ds                  ; save DS across DOS calls (1)
;    Set DS:DX to the name of the root directory for the volume we're
;    interested in.
     lds   dx, lpRoot          ; DS:DX -> name of root directory
     mov   ax, ds              ; is pointer null?
     or    ax, dx              ;   ..
     jnz   @F                  ; if not, good
     mov   dx, cs              ; yes. use "\" instead
     mov   ds, dx              ;   ..
     mov   dx, offset root     ;   ..
;    Use undocumented function 21/69 to get volume info and the file
;    system name. This function isn't translated by any of the DOS
;    extenders (including Windows) I tested in "Testing the Windows DOS
;    Extender", Windows/DOS Developer's Journal, Vol. 5, No. 2 (Feb. 1994),
;    pp. 47-60, but nothing catastrophic happened either
@@:  push  ds                 ; save root name across call (2)
     push  dx                 ;   ..
     mov   bx, dx             ; BL = drive number
     cmp   byp [bx+1], ':'    ; colon in string?
     mov   bl, byp [bx]       ; get 1st character in any case
     je    @F                 ; skip if there's a colon
     mov   bl, 'a' - 1        ; no colon, so ask for default drive
@@:  or    bl, ' '            ; convert drive letter to lower case
     sub   bl, 'a' - 1        ; BL = drive number (0 = default, 1 = A)
     mov   dx, ss             ; DS:DX -> results buffer
     mov   ds, dx             ;   ..
     lea   dx, diskinfo       ;   ..
     mov   ax, 6900h          ; fcn 6900: get disk serial number
     int   21h                ; issue DOS request
     pop   dx                 ; restore root name in any case (2)
     pop   ds                 ;   ..
     jc    failure            ; die if error in call
;    First try the Chicago GetVolumeInformation call. Note that the NT API
;    allows a NULL lpRoot parameter, whereas the INT 21 function does not.
     les   di, lpFSName       ; ES:DI -> buffer to receive file system name
     mov   cx, wp cbFSName    ; CX = length of that buffer
     mov   ax, 71A0h          ; fcn 71A0: get volume information
     stc                      ; set carry in case fcn not supported
     int   21h                ; issue DOS request
     jnc   setflags           ; skip ahead if no error
     cmp   ax, 7100h          ; is function supported?
     jne   failure            ; if not, error of some kind
;    21/71A0 isn't supported by the operating system. Store values that
;    indicate we're dealing with a vanilla FAT file system, but get the
;    file system name from 21/69
     mov   si, ss             ; set DS:SI -> file system name from 21/69
     mov   ds, si             ;   ..
     lea   si, diskinfo.fsname;   ..
 

 
     mov   diskinfo.nullchar, 0 ; add a null terminator
     call  strncpy            ; copy file system name to caller's buffer
     xor   bx, bx             ; flags = 0
     mov   cx, 13             ; length of xxxxxxxx.xxx0
     mov   dx, 81             ; maximum length of a pathname
;    Store the flags and capacities
setflags:les   di, lpMaxName      ; save maximum name component length
     movzx ecx, cx            ;   ..
     mov   dwp es:[di], ecx   ;   ..
     les   di, lpMaxPath      ; save maximum path length
     movzx edx, dx            ;   ..
     mov   dwp es:[di], edx   ;   ..
     les   di, lpFlag         ; save file system capability flags
     movzx ebx, bx            ;   ..
     mov   dwp es:[di], ebx   ;   ..
;    If requested, fill in volume name and volume serial number from the
;    previous results of 21/69
     les   di, lpVolName      ; ES:DI -> volume name buffer
     mov   ax, es             ; is pointer null?
     or    ax, di             ;   ..
     jz    @F                 ; if yes, don't copy anything
     mov   cx, wp cbVolName   ; CX = length of buffer
     mov   si, ss             ; DS:SI -> volume name from 21/69
     mov   ds, si             ;   ..
     lea   si, diskinfo.volname ; ..
     mov   diskinfo.fsname, 0 ; force a null terminator
     call  strncpy            ; copy volume name to caller's buffer
@@:  les   di, lpVolSer       ; ES:DI -> volume serial number
     mov   ax, es             ; is pointer NULL?
     or    ax, di             ;   ..
     jz    @F                 ; if yes, don't copy anything
     mov   eax, diskinfo.volser ; copy volume serial number
     mov   dwp es:[di], eax   ;   ..
@@:  jmp   success            ; done -- everything worked
failure: pop   ds             ; restore DS (1)
     mov   lasterr, ax        ; save error code
     xor   ax, ax             ; return FALSE to indicate failure
     jmp   goback             ;   ..
success: pop   ds             ; restore DS (1)
     mov   ax, 1              ; return TRUE to indicate success
goback:  ret
root db    '\', 0             ; default root directory name
GetVolumeInformationEx endp

strncpy  proc  private
@@:  test  cx, cx             ; is buffer count down to zero?
     jz    @F                 ; if yes, leave loop
     lodsb                    ; no. copy one character
     stosb                    ;   ..
     dec   cx                 ; reduce buffer count
     test  al, al             ; copied null terminator yet?
     jnz   @B                 ; if not, continue
@@:  ret
strncpy  endp
     end
 
To implement GetCurrentDirectory, I first use INT 21H, Function 19H, to determine the default drive. This is because the Win32 API of this name returns the drive letter as well as the name of the current directory on that drive. Unfortunately, the Win32 API only works for the default drive. I preserved this behavior in my implementation even though it makes this API less useful than one that would allow you to specify which drive you’re interested in. I then use INT 21H, Function 7147H, to obtain the long form of the directory name. This function will fail in a non-VFAT environment, whereupon simply use Function 47H to get the 8.3 form of the directory name. In either case, I pass the address of a 260-byte buffer to MS-DOS. Then I copy as much of the name as will fit into the caller’s buffer.
I noticed one difference between my 16-bit version of GetCurrentDirectory and the Win32 API by the same name. If the current directory is the root directory, my version returns a string like “C:\”. In the M5 release of Chicago, however, the Win32 API returns an empty string instead; this seems like a bug to me.
GetVolumeInformationEx is my name for an API that retrieves information about the long filename support of the file system. I added an argument to the Win32 API to allow the caller to determine the maximum length of a pathname as well as the maximum length of a filename. Since this Win32 API also returns the label and volume serial number from the specified disk drive, I first use INT 21H Function 69H to determine these values. This undocumented function returns incorrect information under every MS-DOS extender I know of, including those extenders built into Windows (both Chicago and 3.1) and Windows NT. The only place you will get reliable results from this function is in a real-mode MS-DOS-based application.
Luckily, there aren’t any impediments to determining the various name capacities for GetVolumeInformationEx to return. Under Chicago, INT 21H, Function 71A0H, will return this information. If that function fails, my API simply returns data appropriate to a vanilla FAT file system.
LONGNAME.ASM also contains 16-bit implementations of FindFirstFile, FindNextFile, and FindClose. Under Chicago, these are almost trivial because the underlying INT 21H Functions 714EH, 714FH, and 71A1H map to the same data structures and semantics. I had to do some programming to implement these functions outside the VFAT environment. Since the new APIs don’t use the Disk Transfer Area, I allocate a small block of memory using INT 21H, Function 48H, to hold the state information used by the 8.3 versions of the MS-DOS APIs. The segment of this memory block becomes the handle used by FindNextFile and FindClose. Filling in the WIN32_FIND_DATA structure would be tolerably simple except for the difference in time stamps between MS-DOS and Windows NT. Whereas MS-DOS uses a packed date and time, Windows NT uses Coordinated Universal Time, which is based on the interval in 100-nanosecond units since January 1, 1601. To provide the appropriate time stamp in the WIN32_FIND_DATA structure, I needed to provide a DosDateTimeToFileTime function. I had an implementation of this function already lying around, but it uses 32-bit registers. As a result, LONGNAME.ASM requires a 386 or later processor.
Patching the Run-time Library
REPLACE.ASM (excerpted in Figure 6 and also available on any MSJ bulletin board) contains replacement functions for the members of the Microsoft Visual C++ 1.5 run-time library that pass filenames to MS-DOS. These include _chdir, _mkdir, _open, remove (_unlink), rename, _rmdir, and _sopen, as well as several of the _dos_xxx interface routines. I wrote these functions by reviewing the run-time library documentation and assembly-language listings of the library members. Among the things I learned was the way Microsoft’s library uses several common back-end routines such as dosreturn, dosret0, and dosretax (which has nothing to do with taxation, as I once thought) to interpret error returns from MS-DOS functions. Using these specific routines instead of trying to duplicate them is important for consistent error handling.
Figure 6 Excerpts for REPLACE.ASM
 
;==============================================================================
;
;    REPLACE.ASM -- Visual C++ long filename replacement functions
;
;    Written by Walter Oney
;
;==============================================================================

         name  replace
         .model MODEL, c
         .386

byp      equ   <byte ptr>
wp       equ   <word ptr>
dwp      equ   <dword ptr>

         .data
         extrn _osmajor:byte
         extrn _fmode:word
         extrn _umaskval:word
         extrn _nfile:word
         extrn _osfile:byte

         .code

         if @CodeSize
           extrn _dosreturn:far
           extrn _dosret0:far
           extrn _dosretax:far
         else
           extrn _dosreturn:far
           extrn _dosret0:near
           extrn _dosretax:near
          endif

_mkdir    proc  dirname:ptr byte
          mov   ax, 7139h        ; fcn 7139: make directory (long name)
          jmp   dirop            ; join common code with _chdir
_mkdir    endp

_chdir    proc  dirname:ptr byte
          mov   ax, 713Bh        ; fcn 713B: change directory (long name)
dirop::
         if @DataSize
           push ds               ; save DS across call
           lds dx, dirname       ; DS:DX -> directory name
         else
           mov dx, dirname       ;   ..
         endif
         mov   bl, al            ; save function code
         stc                     ; set carry in case fcn not supported
         int   21h               ; issue long filename form of request
         jnc   goback            ; skip ahead if success
         cmp   ax, 7100h         ; did it fail because fcn not supported?
         jne   failure           ; if not, error
         mov   ah, bl            ; reissue 8.3 form of request
         int   21h               ;   ..
goback:  jmp   _dosret0          ; return 0 or error code
failure: stc                     ; force carry on
         jmp   goback            ; return with error code in AX
_chdir   endp

;    [The remainder of this file is omitted for brevity. Please see any
;    MSJ bulletin board for details.]

     end
 
Figure 6 only shows the _chdir and _mkdir functions, which are illustrative of the coding techniques used in the other REPLACE.ASM functions, not shown here. To use these functions, you assemble REPLACE.ASM for whatever memory model you’re working with and link it with your application. My intent was for you to leave most of your application alone and rely on these low-level routines to give you access to long filename functions if they’re available. I gave each function the “straight face” test; that is, I was able to keep a straight face while asserting that the functions worked correctly when used as intended at least once. You should apply more substantial QA if you choose to use them.
The implementation of these replacement functions is straightforward except for _open and _sopen. The Visual C++ 1.5 run-time library preserves compatibility with MS-DOS 3.x and 8088 processors. Consequently, it uses very complex logic to open and create files, given all the possible creation, truncation, and permission options. Mostly, the extended file-open function (INT 21H, Function 6CH) in DOS version 4 subsumed this logic. Because I needed to set up registers for INT 21H, Function 716CH to handle long filenames, I elected to just reissue INT 21H, Function 6CH with the same registers. If you need to run under MS-DOS 3.x, or if you’re using an MS-DOS extender that handles this function incorrectly, you may need to find more primitive functions.
To prove the concept behind this replacement module, I modified a small Windows-based editor to use long names. Boiled down to its essentials, the file-reading logic of the program used to be this:
 
int hfile = _lopen(filename, READ);
long cbfile = _llseek(hfile, 0, SEEK_END);
_llseek(hfile, 0, SEEK_SET);
_lread(hfile, ...);
_lclose(hfile);
 
I simply replaced these calls with equivalent run-time library functions:
 
AnsiToOem(filename, filename);
int hfile = _open(filename, _O_RDONLY | _O_BINARY);
long cbfile = _lseek(hfile, 0, SEEK_END);
_lseek(hfile, 0, SEEK_SET);
_read(hfile, ...);
_close(hfile);
 
By deriving the filename from the command-line arguments to the application, I was able to get long filename functionality under Chicago with little trouble.
While some of the 16-bit APIs (such as GetOpenFileName) will surely be modified to handle long filenames, others will not. OpenFile is an example of an API that will only support 8.3 names. This subject will be discussed in a white paper (LFNPAPER.DOC) on the M6 disk, along with other file system features, tips, and issues.
This article is reproduced from Microsoft Systems Journal. Copyright © 1994 by Miller Freeman, Inc. All rights are reserved. No part of this article may be reproduced in any fashion (except in brief quotations used in critical articles and reviews) without the prior consent of Miller Freeman.
To contact Miller Freeman regarding subscription information, call (800) 666-1084 in the U.S., or (303) 447-9330 in all other countries. For other inquiries, call (415) 358-9500.

 

0

Featured Post

How your wiki can always stay up-to-date

Quip doubles as a “living” wiki and a project management tool that evolves with your organization. As you finish projects in Quip, the work remains, easily accessible to all team members, new and old.
- Increase transparency
- Onboard new hires faster
- Access from mobile/offline

Join & Write a Comment

Have you thought about creating an iPhone application (app), but didn't even know where to get started? Here's how: ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ Important pre-programming comments: I’ve never tri…
This is a short and sweet, but (hopefully) to the point article. There seems to be some fundamental misunderstanding about the function prototype for the "main" function in C and C++, more specifically what type this function should return. I see so…
The goal of this video is to provide viewers with basic examples to understand opening and writing to files in the C programming language.
The goal of this video is to provide viewers with basic examples to understand how to create, access, and change arrays in the C programming language.

747 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

9 Experts available now in Live!

Get 1:1 Help Now