Long File Name (LFN) Entries in the FAT Root Directory of Floppy Disks

Introduction

In this article, I am trying to explore the Long file name for a FAT file system. In my previous article, “FAT Root Directory Structure,” you have seen the root directory structure and file information. The same root directory is equally responsible for holding “Long File Names (LFN)”. Before proceeding, I would like to recommend you to go through the Microsoft specification for LFN entries.

As of now, y ou know each entry size in the root directory is 32 bytes. But, the structure for LFN entries in the same root directory does not follow the rule for normal entry. It is different.

Structure for LFN Entries

Byte Range Description
0 Sequence Number and allocation Status
1 – 10 File Name Characters (Unicode)
11 File Attributes
12 Reserved
13 Checksum
14 – 25 File Name Characters (Unicode)
26 – 27 Reserved
28 – 31 File Name Characters (Unicode)

From the above structure, it is clear that File name characters are Unicode. Now, the next question is, how do we find LFN entries from the root directory? To work with LFN, there is a rule of thumb as per the FAT specification. These are:

  1. In root directory, LFN entries will precede normal entry. It means LFN entry will come first and then normal entry will follow.
  2. The sequence number field is a counter for each entry. For a file having a long name that cannot be fit into one 32-byte entry in the root directory, another entry will be there and the sequence number will increment. I have copied a long name file on a FAT-formatted floppy in a Windows environment and opened the same using the WinHex tool. Please follow the picture below:
  3. In the picture above, it is clear that offset 2660 is the mark for normal entry. Just before the red marked box represents LFN entry for the same file. If you take a look at the first byte at offset 2640, it is 0x01. This is the mark of sequence number and it is first entry.
    1. At offset 2620, it is 0x02 and at offset 2600 it is 0x43. It proves LFN entries precede the normal entry and it will be in reverse order.
    2. The sequence number got incremented and the last sequence number is 0x43. It is quiet amazing why the last sequence number is 0x43. The specification says that the last entry value will be ORed with 0x40 and it is the mark for last entry. If you subtract 0x43 with 0x40, you will get 0x03; it is the count for max LFN entry for the file in the root directory.
    3. If you look at each entry for LFN, you will see that the 11th byte is 0x0f, which is a file attribute for LFN. It helps to identify whether or not that is a LFN entry.
    4. In the first entry in the root directory, you can see the last four bytes are set to FF FF FF FF (0xffff). This marks the end of LFN entry for a file in the root directory.

Approach to Getting the LFN from the Root Directory

The following code snippet will show how to extract the LFN name from the FAT root directory of a floppy disk.

// LFN_Entry_Test.cpp : Defines the entry point for the console
// application.
//

#include "stdafx.h"
#include <iostream.h>
#include <windows.h>
#define INVALID_SET_FILE_POINTER ((DWORD)-1)


#pragma pack(1)
typedef struct _LongFileName
{
   BYTE sequenceNo;            // Sequence number, 0xe5 for
                               // deleted entry
   BYTE fileName_Part1[10];    // file name part
   BYTE fileattribute;         // File attibute
   BYTE reserved_1;
   BYTE checksum;              // Checksum
   BYTE fileName_Part2[12];    // WORD reserved_2;
   BYTE fileName_Part3[4];
}LFN;

typedef struct _FileMarker_Part2
{
   DWORD _Mark1;
   DWORD _Mark2;
   DWORD _Mark3;
}FMark;

// This structure is responsible for holding file information
// and the short file name.
typedef struct root_Entries
{
   BYTE short_FileName[11];
   BYTE fileAttributes;
   BYTE reserved;
   BYTE createTime_ms;
   WORD createTime;
   WORD createDate;
   WORD accessedDate;
   WORD clusterNumber_High;
   WORD modifiedTime;
   WORD modifiedDate;
   WORD firstClusterAddress_FAT12;
   DWORD sizeofFile;
} root;

#pragma pack()

void ShowFileInformationFromNormalEntry(BYTE *pByteRoot);

int main(int argc, char* argv[])
{
   int nRetCode = 0;

   DWORD dwFilePointer;
   DWORD dwBytesRead;
   int nMaxLFNEntryForFile(0);
   BYTE byteRoot[512];
   memset(&byteRoot, 0, 512);

   HANDLE hFloppy = NULL;
   hFloppy = CreateFile("\\\\.\\A:",    // Floppy drive to open
      GENERIC_READ,                     // Access mode
      FILE_SHARE_READ,                  // Share Mode
      NULL,                             // Security Descriptor
      OPEN_EXISTING,                    // How to create
      0,                                // File attributes
      NULL);                            // Handle to template

   if(hFloppy != NULL)
   {
      dwFilePointer = SetFilePointer(hFloppy,
         (512 * 19), NULL, FILE_BEGIN);

      // Test for failure
      if (dwFilePointer != INVALID_SET_FILE_POINTER)
      {
         int iSector = 19;
         BOOL bNoEntry = FALSE;
         do
         {
            if (!ReadFile(hFloppy, byteRoot, 512, &dwBytesRead, NULL))
            {
               printf("Error in Reading Root Entry.\n");
            }
            else
            {
               BYTE *pByteRoot = byteRoot;
               for(int i = 0; i < (512/32); i++)
               {
                  BYTE rootEntryBuffer[32];
                  memset(rootEntryBuffer, 0, 32);
                  memcpy(rootEntryBuffer, pByteRoot, 32);
                  if(rootEntryBuffer[0] == 0x00)
                  {
                     // Stop iteration
                     bNoEntry = TRUE;
                     break;
                  }
                  else
                  {
                     char szLongFileName[MAX_PATH];
                     memset(szLongFileName, 0, MAX_PATH);

                     // Long file name entry
                     if(rootEntryBuffer[11] == 0x0F)
                     {
                        if((rootEntryBuffer[0] | 0x40) ==
                           rootEntryBuffer[0])
                        {
                           // This is highest sequence number
                           // and this is also the last LFN entry
                           // for the file in root directory.

                           nMaxLFNEntryForFile =
                              (int)(rootEntryBuffer[0] - 0x40);

                           BYTE fileNameFilter[12];
                           memset(fileNameFilter, 0, 12);

                           char szLongFileNameTemp[MAX_PATH];
                           memset(szLongFileNameTemp, 0, MAX_PATH);

                           for(int i = nMaxLFNEntryForFile;
                               i > 0; i--)
                           {
                              // First copy file name to temp buffer
                              strcpy(szLongFileNameTemp,
                                     szLongFileName);

                              LFN stLFN = {0};
                              memcpy(&stLFN, pByteRoot, 32);
                              WCHAR szNamePart1[5];
                              wcscpy(szNamePart1,
                                 (LPCWSTR)stLFN.fileName_Part1);
                              szNamePart1[5] = L'\0';

                              char szNameTemporary[MAX_PATH];
                              memset(szNameTemporary, 0, MAX_PATH);
                              WideCharToMultiByte( CP_ACP, 0,
                                 szNamePart1, -1,
                                 szNameTemporary, MAX_PATH, NULL,
                                 NULL );

                              strcpy(szLongFileName, szNameTemporary);

                              FMark marker;
                              memcpy(&marker, stLFN.fileName_Part2, 12);
                              if((marker._Mark1 == 0xffffffff) &&
                                 (marker._Mark1 == 0xffffffff) &&
                                 (marker._Mark1 == 0xffffffff))
                              {
                                 // This is the marker for the end
                                 // of LFN the entry for the file
                                 // in the root directory.

                                 // Only for check. Don't do anything
                              }
                              else
                              {
                                 WCHAR szNamePart2[7];
                                 wcscpy(szNamePart2,
                                 (LPCWSTR)stLFN.fileName_Part2);
                                 szNamePart2[7] = L'\0';

                                 memset(szNameTemporary, 0, MAX_PATH);
                                 WideCharToMultiByte( CP_ACP, 0,
                                    szNamePart2, -1,
                                    szNameTemporary, MAX_PATH,
                                    NULL, NULL );

                                 strcat(szLongFileName,
                                        szNameTemporary);
                              }

                              if((stLFN.fileName_Part3[0] == 0xFF) &&
                                 (stLFN.fileName_Part3[1] == 0xFF) &&
                                 (stLFN.fileName_Part3[2] == 0xFF) &&
                                 (stLFN.fileName_Part3[3] == 0xFF))
                              {
                                 // This is the marker for the end
                                 // of the LFN entry for the file
                                 // in the root directory.

                                 // Only for check. Don't do anything
                              }
                              else
                              {
                                 WCHAR szNamePart3[2];
                                 wcscpy(szNamePart3,
                                 (LPCWSTR)stLFN.fileName_Part3);
                                 szNamePart3[2] = L'\0';

                                 memset(szNameTemporary, 0, MAX_PATH);
                                 WideCharToMultiByte( CP_ACP, 0,
                                    szNamePart3, -1,
                                    szNameTemporary, MAX_PATH, NULL,
                                    NULL );

                                 strcat(szLongFileName,
                                        szNameTemporary);
                              }
                              pByteRoot += 32;
                              strcat(szLongFileName,
                                     szLongFileNameTemp);
                           }

                           cout << "Long File Name:-"
                              << szLongFileName << endl;
                        }
                     }
                     else
                     {
                        // This is for short name entry and also
                        // to get file information
                        ShowFileInformationFromNormalEntry(pByteRoot);
                        pByteRoot += 32;
                     }
                  }
               }
               if(bNoEntry)
                  break;
               else
               {
                  iSector += 1;
               }
            }
         } while(iSector <= 33);
      }

      CloseHandle(hFloppy);
   }
   return 0;
}

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read