Why does CreateEvent fail with ERROR_PATH_NOT_FOUND if I give it a name with a backslash?


A customer reported that the Create­Event function was failing with the unusual error code ERROR_PATH_NOT_FOUND:

HANDLE h = CreateEvent(0, FALSE, TRUE, "willy\\wonka");
if (h == NULL) {
 DWORD dwError = GetLastError(); // returns ERROR_PATH_NOT_FOUND
 ...
}

The customer continued, "The documentation for Create­Event says that the lpName parameter must not contain the backslash character. Clearly we are in error for having passed an illegal character, but why are we getting the strange error code? There is no file path involved. Right now, we've added ERROR_PATH_NOT_FOUND to our list of possible error codes, but we'd like an explanation of what the error means."

Okay, first of all, building a table of all known error codes is another compatibility problem waiting to happen. Suppose in the next version of Windows, a new error code is added, say, ERROR_REJECTED_BY_SLASHDOT. What will your program do when it gets this new error code?

Now back to the error code. There is no file path involved here, so why is there a path-not-found error?

Because it's not a file system path that failed. It's an object namespace path.

If a backslash appears in the name of a named object, it is treated as a namespace separator. (If there is no backslash, the name is interpreted as part of the Local namespace.) And the call fails with a path-not-found error since there is no namespace called willy, so the path traversal inside the object namespace fails.

The treatment of the backslash as a namespace separator is sort of alluded to in the very next sentence of the documentation: "For more information, see Kernel Object Namespaces." The following paragraph also expands upon this idea: "The object can be created in a private namespace. For more information, see Object Namespaces." The documentation sort of assumes you'll follow the links and learn more about those namespacey things, at which point you'll learn what that backslash in the object name really means (and why there is the rule about not allowing backslashes).

But here it is if you don't want to try to figure it out:

"If you put a backslash in the name, it is treated as a namespace separator, and if you don't know what a namespace is, then that's probably not what you want. So don't use backslashes unless you know what you're doing."

Comments (19)
  1. Adam Rosenfield says:

    You could also just look at it as undefined behavior: you violated the functions preconditions, so anything could happen.  It could fail silently and appear to succeed, it could crash, or it could start sending snarky emails to your coworkers.  You just got lucky in that it failed with an error code, at least in this version of Windows.

  2. MWF says:

    "Okay, first of all, building a table of all known error codes is another compatibility problem waiting to happen. … What will your program do when it gets this new error code?"

    It's not necessarily a compatibility problem (at least not one of any note). It depends on what your table of errors is used for… if you just want to translate/transform an error message to a preferred form, then it's not really a problem. If you don't get one of your expected values, then you just issue "Unknown error #123" or fall back to FormatMessage(). I do this often, in fact. There are lots of times when you – the programmer – can interpret a standard error code to a more context-correct error message than FormatMessage() would.

    Of course, it would be a huge folly to use a list of known error codes to "limit" the return codes of a function, resulting in an exception, crash, or otherwise bad behaviour if an unexpected value is encountered. That's just ridiculous; I don't know why any sane person would consider such an approach.

  3. Adam Rosenfield says:

    @MWF: Yes, it's good like this that makes me facepalm:

    int result = SomeAPIFunction();

    if(result == API_SUCCESS) {

     …

    } else if(result == API_FAILURE) {

     …

    }

    Where the code assumes that only API_SUCCESS or API_FAILURE will be returned.  When the next version of the API comes out and it now returns API_WARNING, the code will break because it failed to account for the third case.  Obviously the correct thing to do is just a bare else, not an else-if, for the final case.

  4. Maybe the person that asked the question in the first place didn't know about the Kernel namespace. But then, how did they know about CreateEvent? Where did they learn about it? And even without knowing about namespaces, Windows uses the backslash as path separator, so seeing that backslashes were banned, his/her psychic powers should have pointed him/her in the right direction…

    Where I work, we keep a list of common error codes not in the code, but in paper, in order to help technical support people diagnose them. It's like a knowledge base (sort of). I agree that hard-coding the list in the code would be insane, but keeping record of error codes and possible causes is really useful and saves a lot of time!

  5. Csaboka says:

    Adam Rosenfield: while I get what you are trying to say, I think the example isn't very good to demonstrate your point. To me, it isn't "obvious" that a new return value will represent a new kind of error and not a new kind of success. (Even your example can be interpreted that way: API_WARNING sounds like a success with minor issues, not like a failure.) If an API wants to extend the range of its return values during its evolution like your example shows, the least the designers need to do up front is separating the success codes from the error codes. If you have a rule like "error codes are negative, success codes are positive", callers can deal with new codes in a more roboust way.

    Of course, the simplest way to partition the return values between successes and errors is to limit one of the categories to one specific value (i.e. you have only one error code or only one success code), so that's what most APIs do.

  6. John says:

    The other problem with FormatMessage() is that a small number of the messages have insertion strings.

  7. Nick Lowe says:

    Hopefully you pointed them in the direction of the excellent Windows Internals book and WinObj ;)

    Seriously though, what were they using -named- events for? Was it a valid use case?

  8. Nick Lowe says:

    Also, on the topic of object names – why do all the other object types ignore obcaseinsensitive? Only NtCreateFile respects it… You can happily create events, for example, with NtCreateEvent that differ in case alone if OBJ_CASE_INSENSITIVE is not passed and the object manager is running in case insensitive mode…

  9. cheong00 says:

    Adam Rosenfield: Maybe the code should have else-if in the second part afterall, but not the API_FAILURE, just for those kinds like API_NOERROR.

  10. waleri says:

    >> what were they using -named- events for?

    Umm, sending events from one app to another?

  11. Nick Lowe says:

    Yes, I know what they're used for.

    I was shocked that they would have a use case where they needed named events, yet got away with being blissfully unaware of the object namespace and didn't research it further before contacting Microsoft when getting that error.

    I just find that worrying and unlikely, hence my curiosity if it was the valid usecase of needing an event to be cross-process or just naming it because the parameter is there, and, heck, why not?

  12. Crescens2k says:

    @Nick Lowe

    The way these functions work means that they can be used without knowing about the NT object namespace. For all intents and purposes, it is just a name for the event that Windows stores somewhere that you don't really need to care about. So someone asking why the is bad is understandable. Also remember that unless you have a bit of an idea of the workings under the bonnet, it is hard to know where to start in researching.

    So really it being a valid use case is a moot point here. Not knowing about the NT object namespace is not uncommon because the Win32 API abstracts it away. I think it would actually be more unreasonable to think that programmers new in the Win32 API field have read anything to do with the internals of Windows. Yet these programmers should be able to use CreateEvent or even CreateFile without having to worry about any of that. Just imagine what it would be like if people who used CreateFile had to know that C:thisfile.txt was in fact a symlink to something like DeviceHardDiskVolume1thisfile.txt in the object namespace. So is it any less reasonable for new Win32 API programmers to not know that the event MyEvent created with CreateEvent is actually something like Sessions1BaseNamedObjectsMyEvent in the object namespace? I don't.

  13. Crescens2k says:

    Oh, and just to make sure that people don't pick out the obvious faults.

    Yes, C: may not be on HardDiskVolume1, so if it isn't then use the actual number in place of 1.

    Also, yes, the event may be created in a different session, so substitute the correct session number in instead of 1. Finally, it may have been created in the global base named objects part of the namespace, in that case it would be BaseNamedObjectsMyEvent instead.

  14. Simon Farnsworth says:

    @Csaboka:

    It doesn't matter whether the new API return code is a failure or a success code – you don't know how to handle it, so treating it as a failure is the only safe option. Indeed, this is a restriction on existing APIs – you cannot add new success codes unless the cleanup required after success is the same as the cleanup required after failure.

  15. Neil says:

    @John Insertion strings also confuse NET HELPMSG which spits out its generic "error 3871" (a special message internal to NET HELPMSG) instead of (say) 317.

  16. Nick Lowe says:

    @Crescens2k:

    Having thought about what you've said, I agree with you entirely. I'm guilty of wearing the goggles and falling victim to the "It seems obvious to me, so it surely should be to others?" fallacy…

  17. Clovis says:

    Nice error code, but in reality, nothing is ever rejected by slashdot, they just moan on and on about what ever offends them until they all get into a hysterical ego feedback loop and need calming down by their mothers. So perhaps ERROR_POSSIBLE_RECURSIVE_SLASHDOT_EGO_STORM.

  18. Sean McGeough says:

    NTSTATUS values are a far better return than flat Win32 Error Codes as you have classes of severity – Success, Informational, Warning or Error – and, sadly, things often get lost in translation in RtlNtStatusToDosError.

    It's tougher to do the right thing in the Win32 world, but I suppose it avoids the need to understand bitops.

    (You get people often checking for a literal NTSTATUS value of STATUS_SUCCESS rather than looking at the severity class.)

  19. Crescens2k says:

    @Nick Lowe

    It is easy to do that, I've done it too many times myself.

    @Sean McGeough

    Of course, NTSTATUS is newer than the Win32 error codes. I'm sure that to get the Win32 error codes fixed, we'd need that time machine working so someone can go back and fix it.

Comments are closed.

Skip to main content