Can we continue to call FindNextFile() with the same handle that returned an error last time?


A customer wanted to know whether it was okay to call Find­Next­File with the same handle that returned an error last time. In other words, consider the following sequence of events:

  1. h = Find­First­File(...); succeeds
  2. Find­Next­File(h, ...); fails
  3. Find­Next­File(h, ...); ??? profit ???

The customer elaborated:

Suppose that the directory contains four files, A, B, C, and D. We expect the following:

  • Find­First­File returns A
  • Find­Next­File returns B
  • Find­Next­File fails (C is selected but an error occurred)
  • Find­Next­File returns D ← is this expected?

After Find­Next­File returns an error, can we continue to search with the same handle? Or should we close the handle and get a new one from Find­First­File? If it depends on the type of error that occurred, the customer would like to know more details about when the search can be continued and when it cannot.

We asked the customer what problem they're encountering that is causing them to ask this strange question. The customer replied, "Sometimes we get the error ERROR_FILE_CORRUPT or ERROR_INVALID_FUNCTION, but we don't know what end-user configurations are causing those error codes. We would like to know whether we can continue to use Find­Next­File in these two cases."

The assumption "C is selected by an error occurred" is already wrong. The error might not have selected C. The error may have failed before C was selected. (For example, maybe the network cable was unplugged, so the server doesn't even know that we tried to select C.) Or the error may result in C and D both being skipped. Since an error occurred, any of these things may have happened.

There is no value in trying to continue to use a find handle that is in an error state because you cannot reason about it. Maybe it got stuck in a permanent error state (the user removed the USB drive). Maybe it is a transient error state (somebody finds the network cable and plugs it back in). It's like asking, "If something is broken, can I expect it to continue working?"

Even closing the handle and restarting the enumeration may not succeed. For example, as long as the drive or network cable remains unplugged, your enumeration will fail. And it might be a repeatable error state due to drive corruption which causes enumerations always to fail at the same place.

(My guess is that ERROR_FILE_CORRUPT is the case of drive corruption, and ERROR_INVALID_FUNCTION is some sort of device driver error state, perhaps because the device was unplugged.)

You should just accept that you cannot enumerate the contents and proceed accordingly.

Comments (25)
  1. Joshua says:

    Punch the driver writer who decided ERROR_INVALID_FUNCTION for anything other than API not implemented for this device.

  2. Daniel says:

    @Joshua: Maybe he uses a function from another driver that is not implemented? (Well you still can punch him for using a function without checking if it is implemented. Or you can punch the other driver writer who returned ERROR_INVALID_FUNCTION without a possibility to check which methods are implemented)

  3. Rob says:

    I find it hard to imagine the confusion of mind that would cause someone to expect that an error'ed object can continue to be used.

    (I may be misquoting Babbage).

  4. Chris Long says:

    Unfortunately we don't know whether the customer tried the experiment, or what their results were.  If the customer really saw errors of this type and yet file D was still returned, I think it would be a valid question to ask 'it looks like we can continue to use the handle, but should we?'.  If they saw errors of this type and subsequent calls to FindNextFile always returned errors, then they are silly customers and they were wasting the customer liaison's time.

    Disclaimer: I don't know how FindNextFile behaves after an error and I haven't done the experiment either.

  5. Joshua says:

    @Daniel: You still shouldn't only get here in error condition.

  6. Karellen says:

    I guess it's an issue of which file the ERROR_FILE_CORRUPT error is referring to.

    If file "C" is corrupt, well, I suppose that that's useful information and I might use it to not bother trying to open C, but that shouldn't prevent me from continuing to enumerate the other non-corrupt files in the directory, should it?

    However, if the directory file referenced by handle "h" is corrupt, then obviously we can't continue enumerating through h.

    So, does ERROR_FILE_CORRUPT refer to "C" or "h"? Clearing that possible source of misunderstanding up might help reduce the customer's confusion.

  7. Antonio 'Grijan' says:

    Here you have to apply the "fail fast" principle: if you don't know what caused the call to fail, you have no option but abort (and perhaps tell the user so s/he can correct it and retry).

    @Karellen: FindFirstFile/FindNextFile do directory enumeration. They don't open the files or otherwise look at their contents, so the call should success even if the file enumerated is physically corrupt. So I guess the ERROR_FILE_CORRUPT should refer to the directory itself (technically, a directory is just a special file that contains a list of other files).

  8. Marvin says:

    Maybe sometimes this century Microsoft API designers will discover exception safety guarantees and start documenting them for their product. What you describe above is the weak guarantee. The docs should just say so.

    The fact that you use error codes to report exceptions rather than true language exceptions doesn't change the semantics or applicability of the general theory.

  9. Tautvydas says:

    Does this only apply for obscure errors like ERROR_FILE_CORRUPT? I mean, FindNextFile consistently fails with ERROR_ACCESS_DENIED sometimes, and subsequent queries work just fine. Here's a demo program that demonstrates it:

    http://pastebin.com/gK3QQRdV

    On my machine, it hits the breakpoint 6 times. On my phone, however, it hits the breakpoint so many times I can't even finish the search (probably has to do with Windows Phone app permissions). Isn't the right way to do it is to just ignore such errors and keep going at it until it returns ERROR_NO_MORE_FILES?

  10. mikeb says:

    The Babbage quote:

    On two occasions I have been asked [by members of Parliament], 'Pray, Mr. Babbage, if you put into the machine wrong figures, will the right answers come out?' I am not able rightly to apprehend the kind of confusion of ideas that could provoke such a question.

  11. Hans says:

    @Tautvydas

    Is this ok: You call FindNextFile, then GetLastError without checking return of FindNextFile?

    I always have the pattern in mind, GetLastError returns correct error only if previous API function signaled failure (by returning FALSE, or INVALID_HANDLE_VALUE, or NULL …)

    On topic: Strange question indeed, I would also abandon if FindNextFile returns FALSE.

  12. Joshua says:

    @Hans: No. There are API calls that don't reset GetLastError so you could potentially see an old error.

  13. Tautvydas says:

    @Hans: I believe you're right! Apparently, sometimes FindNextFile returns success and sets an error code in the same call. I should really get a habit of checking return values more thoroughly :)

  14. Tautvydas says:

    @Myria: That was my mistake. I wasn't checking for the return value of FindNextFile. FindNextFile isn't failing to return a file, but it sometimes sets the error code anyway – my guess is that it is failing to get some specific attributes of the file in question.

  15. Crescens2k says:

    @Gabe:

    Afair, GetLastError is undefined when the function returns success. These functions may call some internal functions that set the error code, or there are some functions which don't call SetLastError and set the system error code to 0 before it runs.

    One other function which is perfect for seeing this in action is CreateProcess and trying to run a program with a space in the path, but have it unquoted.

  16. Evan says:

    @Antonio: "FindFirstFile/FindNextFile do directory enumeration. They don't open the files or otherwise look at their contents…"

    It's still not so unreasonable. You could have directory entry where portions of the entry are corrupt, but it looks like the rest is fine. Find{First/Next}File return information that, if it were on Unix, would require looking into the inode of the file; in Unix terms, the inode could be corrupt while the directory entry is fine. In that case, the desired behavior would be totally reasonable. Does NTFS store file information in something like inodes? I don't know, and I don't really think so — except that I know it supports hard links, and I'm not sure how they would work otherwise.

    In any case, I agree with Karellen: I think it's a pretty reasonable question, depending on what they looked at, what they were able to look at, and what the results of their looking were.

  17. Myria says:

    @Tautvydas: If this is correct, then yes, Raymond's article here is slightly incorrect.  It seems like it should have a special case for ERROR_ACCESS_DENIED.  FindFirstFileExW + FindNextFileW would be too limiting if ERROR_ACCESS_DENIED were considered a point at which undefined behavior started to occur and thus became unreliable afterward.

    I am curious, though: Are you getting ERROR_ACCESS_DENIED for *some* files in a directory and not others, or is it all-or-nothing for a given directory?

  18. Gabe says:

    Tautvydas: So it actually *succeeds* with ERROR_ACCESS_DENIED? How interesting.

  19. Kirby FC says:

    As in all things involving computers, the correct answer is:  It depends.

  20. alegr1 says:

    Does FindNextFile go through the language-dependent filename substitution?

  21. Goran says:

    @Tautvydas: your program does NOT demonstrate what you claim. You never look for error_access_denoed and you wrongly check for FindNextFile failure (can't just use GetLastError). Your code is seriously wrong, really.

  22. Klimax says:

    @Evan:

    NTFS stores metadata for files in directory entry, that's why getting them is cheap unlike on UNIX. Even small files can be stored directly there. IIRC all those links are implemented as special file entries. At least some are of type reparse points.

    msdn.microsoft.com/…/aa365006(v=vs.85).aspx

    msdn.microsoft.com/…/aa365460(v=vs.85).aspx

  23. @Marvin says:

    Exception safety guarantees for a C interface? You are one confused kid.

  24. GregM says:

    "Exception safety guarantees for a C interface? You are one confused kid."

    Did you miss this part?

    The fact that you use error codes to report exceptions rather than true language exceptions doesn't change the semantics or applicability of the general theory.

  25. immibis says:

    @Rob: perfectly reasonable for several types of errors. The problem is you don't know whether the error is one that it's reasonable to continue from.

Comments are closed.