June 1998

Detecting disk errors

by Kent Reisdorph

If your application uses floppy disks, then you should be prepared to handle any potential disk errors. A disk error occurs when there's no disk in a floppy drive, when a disk in the floppy drive isn't formatted, when the disk is damaged, or for many other reasons. Handling disk errors gracefully makes your application more robust and saves your users from endless frustration. In this article, we'll explain how to handle disk errors in your application. We'll also look at the SetErrorMode and GetLastError functions of the Windows API.

Why handle disk errors?

Windows handles critical errors at the operating-system level by default. If, for example, you try to write to a floppy disk and the diskette in the drive isn't formatted, Windows displays a message box stating that a disk error has occurred. An example of this type of message box is shown in Figure A.

Figure A: Figure A shows an error message box for an unformatted disk (Windows NT 4).

While handling critical errors at the operating-system level is fine for many applications, some applications need better control of such errors. For example, if your application writes to diskette, you may want to suppress the normal Windows error message box in order to do some processing behind the scenes. Or maybe you just don't like the message boxes that Windows provides and you want to replace them with your own message boxes. Whatever the reason, you occasionally need to take control of critical errors from Windows.

Two steps to success

Handling disk errors is a two-step process. First, we must inform Windows that we don't want it to display critical error messages. We can accomplish this by calling the SetErrorMode function. Second, we must determine whether an error has occurred for any operations that could produce a diskette critical error. The GetLastError function provides an error code for the last system function called. We can use that error code to determine what went wrong. Let's take a look at each of these steps in more detail.

Setting the error mode

As we indicated, Windows usually takes the responsibility for handling system errors. You can override that behavior by calling SetErrorMode with the appropriate flags for the types of errors you want to handle. In this case, the flag in which we're interested is the SEM_FAILCRITICALERRORS flag. To tell Windows that you want to handle this type of error yourself, call SetErrorMode with the line:
SetErrorMode(SEM_FAILCRITICALERRORS);

After you've called this function, Windows will no longer handle critical errors. That's not the whole story, though. Make sure you save the old error mode and reset it when you're finished with a particular operation. We've done so in this example:

int OldMode;
OldMode = SetErrorMode(SEM_FAILCRITICALERRORS);
// Do some things that might cause an error and then reset the error mode.
SetErrorMode(OldMode);
As you might have surmised, SetErrorMode returns the previous error mode setting, thus allowing you to save the current error mode so you can restore it after you've completed a particular operation. Reset the error mode as soon as you've finished your processing. If you fail to do so, then the user may not receive notification of certain errors that are out of your control as a programmer. In short, do the following:
bullet Set the error mode as desired.
bullet Do your thing.
bullet Set the error mode back to what it was before.

By following this guideline, your applications can have custom error handling--yet not interfere with normal Windows operations. By the way, you can catch other errors besides critical errors. See the SetErrorMode topic in the Win32 API help file for complete information.

Detecting errors

Once you've told Windows that you'll be detecting critical errors, you must check with Windows to know when an error occurs. You use the GetLastError function to do this. Let's say, for example, that you wanted to copy a file to a diskette in drive A. You might go about it with the following code:
OldMode = SetErrorMode(SEM_FAILCRITICALERRORS);
CopyFile("test.dat", "A:\\test.dat", false);
int error = GetLastError();
// code to handle error here
SetErrorMode(OldMode);

Since you could get any number of errors from this operation, you must be prepared to handle them all. For example, the CopyFile function in the previous example could result in any of the following errors:

ERROR_FILE_NOT_FOUND
ERROR_PATH_NOT_FOUND
ERROR_TOO_MANY_OPEN_FILES
ERROR_ACCESS_DENIED
ERROR_NOT_ENOUGH_MEMORY
ERROR_OUTOFMEMORY
ERROR_INVALID_DRIVE
ERROR_NOT_SAME_DEVICE
ERROR_NO_MORE_FILES
ERROR_WRITE_PROTECT
ERROR_BAD_UNIT
ERROR_NOT_READY
ERROR_CRC
ERROR_NOT_DOS_DISK
ERROR_WRITE_FAULT
ERROR_GEN_FAILURE
ERROR_SHARING_VIOLATION
ERROR_LOCK_VIOLATION
ERROR_HANDLE_DISK_FULL
ERROR_FILE_EXISTS
ERROR_CANNOT_MAKE
ERROR_DISK_FULL
ERROR_UNRECOGNIZED_MEDIA

And this is just a sampling. Other errors could also apply. (Note: For a complete list of error codes, check out WINERROR.H. This header file contains a complete list of Windows error codes and a brief description of each.) You must take great care to handle any possible errors that might occur as the result of a file operation (or any other operation for that matter). You don't need to handle each and every error specifically, but you must at least let the user know about errors that you aren't handling. (See the "Getting Error Message Text" sidebar for a description of how to obtain the Windows error message text for errors that you don't handle directly.) You should also be aware of one other thing concerning GetLastError. Windows NT and Windows 95 don't necessarily return the same error code for identical disk error conditions. For example, if you attempt to set the current directory to a floppy drive that contains an unformatted disk under Windows NT, GetLastError will report an error code of ERROR_UNRECOGNIZED_MEDIA. On the other hand, under Windows 95 GetLastError might report ERROR_GEN_FAILURE or ERROR_INVALID_DRIVE for the same operation. Be sure to test your applications thoroughly under both operating systems so that you know your code will work on either platform.

Conclusion

Listings A and B contain a program that illustrates using SetErrorMode and GetLastError to detect disk errors and acting accordingly. As an added bonus, the program uses the FormatMessage function to display any errors not handled by the program. The program checks whether the diskette in drive A is good. If not, it reports an error. You can also get the code for this example from our Web site at www.cobb.com/cpb; click on the Source Code hyperlink. Custom error handling is vital in certain types of applications and a nice feature in others. Although you might not need SetErrorMode often, when you do, you should at least know how it works.

Listing A: ERRMODEU.H

#ifndef ErrModeUH
#define ErrModeUH
//--------------------------------------------
#include <Classes.hpp>
#include <Controls.hpp>
#include <StdCtrls.hpp>
#include <Forms.hpp>
//--------------------------------------------
class TForm1 : public TForm
{
  __published:  // IDE-managed Components
    TButton *Button2;
    void __fastcall Button1Click(TObject *Sender);
    void __fastcall Button2Click(TObject *Sender);
  private:  // User declarations
    void ShowErrorMessage(int code);
  public:  // User declarations
    __fastcall TForm1(TComponent* Owner);
};
//--------------------------------------------
extern TForm1 *Form1;
//--------------------------------------------
#endif

Listing B: ERRMODEU.CPP

#include <vcl.h>
#pragma hdrstop

#include "ErrModeU.h"
//--------------------------------------------
#pragma resource "*.dfm"
TForm1 *Form1;
//--------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
  : TForm(Owner)
{
}
//--------------------------------------------
void __fastcall TForm1::Button2Click(TObject *Sender)
{
  // Display the hourglass cursor.
  Screen->Cursor = crHourGlass;

  // Set the error mode to not show critical
  // errors. Save the current error mode.
  int OldMode = SetErrorMode(SEM_FAILCRITICALERRORS);

  // Set the last error code to -1 so we can
  // tell if an error occurred.
  SetLastError(-1);

  // Attempt to find a file on Drive A. This
  // will generate an error if no diskette is
  // in the drive or if the diskette is not
  // formatted.
  WIN32_FIND_DATA fd;
  FindFirstFile("A:\\*.*", &fd);

  // Display the default cursor.
  Screen->Cursor = crDefault;

  // Check the last error code.
  int error = GetLastError();

  switch (error) {
    // No error, diskette is OK. Also,
    // ignore ERROR_FILE_NOT_FOUND error since
    // the disk might be formatted but blank.
    case ERROR_FILE_NOT_FOUND :
    case -1 : {
      MessageBox(Handle, "The diskette is OK.",
        "Disk OK", MB_OK | MB_ICONASTERISK);
      break;
    }
    // Win95 might report a general device
    // failure or an invalid drive rather
    // than unrecognized media as NT does.
    case ERROR_GEN_FAILURE :
    case ERROR_INVALID_DRIVE : {
      int res = MessageBox(Handle, "Error"
        " reading drive A. Either there is no"
        " diskette in the drive or the diskette"
        " is not formatted. Do you want to"
        " format a diskette?", "Disk Error",
        MB_YESNO | MB_ICONQUESTION);
      if (res == IDYES)
        // code to format disk here
      break;
    }
    // If the error is ERROR_UNRECOGNIZED_MEDIA
    // then assume that the diskette is not
    // formatted.
    case ERROR_UNRECOGNIZED_MEDIA : {
      int res = MessageBox(Handle, "The disk in"
        " drive A is not formatted. Do you wish"
        " to format it?", "Disk Error",
        MB_YESNO | MB_ICONQUESTION);
      if (res == IDYES)
        // code to format disk here
      break;
    }
    // If the error is ERROR_NOT_READY then most
    // likely there is no diskette in the drive.
    case ERROR_NOT_READY : {
      MessageBox(Handle, "Drive A is not ready."
        " Be sure a diskette is in Drive A and"
        " try again.", "Disk Error",
        MB_OK | MB_ICONWARNING);
      break;
    }
    // Some other error occurred, so let
    // the user know.
    default :
      ShowErrorMessage(error);
  }

  // Reset the error mode.
  SetErrorMode(OldMode);
}

void TForm1::ShowErrorMessage(int code)
{
  char buff[1024];
  FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, 0, code, 0, buff, sizeof(buff), 0);
  MessageBox(Handle, buff, "System Error", MB_OK | MB_ICONWARNING);
}

Kent Reisdorph is a editor of the C++Builder Developer's Journal as well as director of systems and services at TurboPower Software Company, and a member of TeamB, Borland's volunteer online support group. He's the author of Teach Yourself C++Builder in 21 Days and Teach Yourself C++Builder in 14 Days. You can contact Kent at editor@bridgespublishing.com.