7
votes

I'm writing some C++ code that needs to call the CopyFileEx function. The documentation for CopyFileEx, like most other WIN32 functions, says:

If the function fails, the return value is zero. To get extended error information, call GetLastError.

Which is all well and good - however does anyone know where I can find a list of the error codes that a specific API function may return via GetLastError? In this case I want to handle different error conditions in different ways but without a list of the error codes for this function I'm going to be reduced to generating the error conditions I want to handle just to see what error code is prodcued or going through the system error codes from numbers 0 to 15999 trying to guess which ones might apply!

Edit: Here is a little more context to help explain the issue and why I want to know if there is a definitive list of error codes that can be returned by a function anywhere.

The code will be used as part of a Windows service so while there are users they won't always be there to respond to errors. I need to be able to distinguish between errors that don't need reporting every time, if a file is locked I'm just to re-try it again later. If I don't have permissions to read a particular file I can log the problem and carry on, if the destination directory is unreadable or is full then I want the service to stop and to trigger a reporting process that will atrract the attention of a user.

Without a comprehensive list of the ways that CopyFileEx can fail I'm finding it hard to do this.

3

3 Answers

6
votes

Microsoft doesn't give a list of all error codes an API might return for the simple reason that the list may change over time and various implementations of Windows, installed drivers or simple oversight (APIs often return errors caused by other APIs called within the one you called).

Sometimes the docs call out specific errors that are of particular interest for users of that API, but in general they will not has a definitive complete list of errors. Nor should they, which is unfortunate, but is a fact of life.

I sympathize with your plight - there are many times I would have liked this kind of information so I could have a better idea of how to handle problems that should be anticipated - particularly those that have a reasonable recovery path. Usually I try to deal with this by testing to find the failure behavior of the APIs, and I'd like to avoid that because it's a pain and it doesn't help much with ensuring that I've covered all the scenarios or against future differences.

However, covering all the scenarios (with a comprehensive list of error codes) or protecting against future changes is really an impossible goal. Consider how Microsoft might have to manage documenting all possble error codes in Win32:

Say the Win32 API has only 2 functions: foo() and bar(). foo() might generate its own error, ERROR_FOO and bar() might generate its own error, ERROR_BAR. However, foo() calls bar(), so foo() might also return ERROR_BAR if its call to bar() returns that error.

The docs reflect the following:

  • foo() may retun either ERROR_FOO or ERROR_BAR
  • bar() may return ERROR_BAR

Now, when API v2 is released, bar() has been extended to also return ERROR_BAZ. for something the size of this API it's simple to manage that the docs for bar() need to be updated to add the new error code (however, note that for an API as large as the real Win32 and an organization as large as MS, the same might not be true, but lets assume it is).

However, the guy adding the new error to bar() has no direct visibility to the fact the foo()'s behavior has also changed in terms of what errors it might return. In an API small as this, it's probably not a big deal - in something like Win32 it would be a mess. Now throw in the fact that Win32 can be dependant on 3rd party code (drivers, plug-ins, COM objects, etc) and the task is now pretty near impossible.

Actually that's not necessarily a great example, since if the error codes were part of the contract of an API ERROR_BAZ should have never come into the picture.

So here's another scenario: the API has an OpenObject() function that can return ERROR_NO_MEMORY or ERROR_NOT_FOUND. When this system was first developed, it had no concept of security (say like MS-DOS), but a new release adds access controls. Now we'd like OpenObject() to be able to return ERROR_ACCESS_DENIED, but it can't because that would change the contract, so a new API OpenObjectEx() is added to deal with that situation. There are at least 2 problems here:

  • You'll get an explosion of APIs over time that really add little or no value over the old APIs
  • what should happen to a legacy application that calls the old OpenObject() API and fails because of access restrictions? Neither of the contracted error returns would tell the truth about what the problem is.

This problem is one of the reasons that exception specifications (in C++ or Java) are considered by many to have been a bad idea.

3
votes

There is a list of Windows error codes here but it doesn't specify the errorcodes per API call. Still, the MSDN site does provide a lot of useful information about most API methods, including the possible error codes they could return.

The FormatMessage function can be used to translate an error to the proper error message, taking the current Windows language into account. Often, displaying such an error message should be enough. You'd only want to add logic for errors that you might expect and even then you'd just want to show an error message.

When reading about CopyFileEx, you will read the following beyond the part about getting the GetLastError() call:

Remarks

This function fails with ERROR_ACCESS_DENIED if the destination file already exists and has the FILE_ATTRIBUTE_HIDDEN or FILE_ATTRIBUTE_READONLY attribute set.

When encrypted files are copied using CopyFileEx, the function attempts to encrypt the destination file with the keys used in the encryption of the source file. If this cannot be done, this function attempts to encrypt the destination file with default keys. If both of these methods cannot be done, CopyFileEx fails with an ERROR_ENCRYPTION_FAILED error code. If you want CopyFileEx to complete the copy operation even if the destination file cannot be encrypted, include the COPY_FILE_ALLOW_DECRYPTED_DESTINATION as the value of the dwCopyFlags parameter in your call to CopyFileEx.

Basically, those are the errors you could normally expect. All others should just result in an error message to the user.

(And I hate it when this site translates the underscores to something else...)

1
votes

CopyFileEx is eventually handled by a chain of device drivers, some are written by Microsoft and some might be not, and the device driver returns an arbitrary NTSTATUS code which is translated into a Win32 error code. Since it can be a custom driver returning this NTSTATUS, Microsoft cannot guarantee you anything about it.

The only way CopyFileEx could give you a "short and sweet" list would be if CopyFileEx has changed the verbose error codes into some simple ones -- i.e. it would either return ERROR_ACCESS_DENIED, ERROR_SHARING_VIOLATION or ERROR_GEN_FAILURE for all other errors (i.e. it would hide all other errors behind a cryptic error code). We wouldn't want that, wouldn't we?

Update: To speak in terms of contract, the user code SHOULD handle CopyFileEx failure (i.e. when it returns FALSE). The user code MAY handle zero or more specific error codes by querying GetLastError upon failure.

It is rare for user code to actually handle error codes. Trying to handle all of them is certainly not the best practice. If a user code has something specific to do for a specific error code, it should do so. Otherwise, the only thing to do with an error code is to indicate it to the user or log it (see FormatMessage) while otherwise treating it as an opaque value.

As I said, the best practice says you SHOULD handle the failure itself -- thus, rollback changes you might've made, memory allocations etc. Things like 'finally' clauses and C++ RAII can help greatly here.