In the C programming language, "goto fail" is a perfectly acceptable means of (function local) error handling because of the absence of any kind of exceptions (setjmp doesn't count). Just look at any significant C program, and you'll see that this is used a lot. The alternative for "goto fail" is duplicating the clean up code for each and every error condition that happens in the function.
Blindly following advice like "goto is bad" and applying it everywhere is a bad idea. You should try to understand the reasoning behind it.
You should not use goto for control flow if possible, but it's not bad practice to use "goto fail" for cleaning up on error conditions.