Hacker News new | past | comments | ask | show | jobs | submit login

If you don't understand why errno is bad then it'll be hard to explain it.

Errno is fine if you're accessing it right after a stdlib function that sets it, because it's the equivalent of a function's error code. Once you get out of that context errno becomes less and less useful and probably shouldn't be used, because without that context you won't know who/what actually set it.




Assuming there is no thread context switch or OS signals between those calls.


errno is thread-local, so you don’t have to worry about context switches.


Historically, it wasn’t (of course; Unix didn’t have multiple threads in a program). https://pubs.opengroup.org/onlinepubs/7908799/xsh/errno.html:

“Previously both POSIX and X/Open documents were more restrictive than the ISO C standard in that they required errno to be defined as an external variable, whereas the ISO C standard required only that errno be defined as a modifiable lvalue with type int.”

Even now that it is guaranteed to be thread-local, it still is a bad API because of (same page)

“A program that uses errno for error checking should set it to 0 before a function call”

And yes, that still seems true today. https://en.cppreference.com/w/cpp/error/errno:

“library functions never store 0 in errno.”


That library functions don’t reset errno is considered a feature / convenience, it allows calling a bunch of them then checking if the entire thing succeeded or failed.

Obviously this assumes the failure of one function does not trigger a UB down the line, and that you care about general but not specific failure. And also obviously this is easy to replicate with an API which returns error objects / codes.

IIRC this is most(ly) convenient in graphics APIs, I don’t know how common leveraging this in the libc actually is.


It also requires code to explicitly set errno many, many times. I think there are many errors lurking in C code there, with code calling foo, not checking errno or checking it but not resetting it, later calling bar, and assuming errno was set by bar.


> It also requires code to explicitly set errno many, many times.

Yes, errno should be cleared every time you want to check it. Although you can check it multiple times with a single clear, as long as a set errno leads to an exit.

A more likely cause if the issue you outline is that errno is not context-local tho, so if you call something which causes errno to be set you might get an errno from an unexpected error. This is similar to overly broad exception contexts, but much harder to diagnose.


You won't find that on either ISO C nor POSIX specifications.

It only happens to be implemented that way in some environments, as a macro to either thread local or some function call that retrieves the right errno.

Still, even that doesn't take care about signal handling.


That's a total strawman. Errno has nothing to do with signal handling at all. While errno is archaic, it is a completely sound design.

That you need to be very careful in signal handlers is common knowledge, and obvious from what they do; signals handlers as a first approximation behave like executed in a separate thread, but even worse since they hijack a running thread and thus block the hijacked thread from executing the previously running code. (So you could say they're like fibers / cooperative multitasking?)

The bottom line is, you can't touch errno in your signal handler, obviously -- just like you can't printf() or any other thing that has "critical sections". And since you don't do that, you're safe.

By the way, signal handler safety is specified in POSIX and/or various other documents to a degree that neither I nor you seem to have cared to research properly. https://www.gnu.org/software/libc/manual/html_node/POSIX-Saf...

BTW2: I noticed on that page that errno is explicitly mentioned, but obviously only as an example of modifying thread-local data in a signal handler. As the docs point out, any such modified data must be restored before leaving the signal handler as to not pending code running on that thread. So in that sense, thread-local variable like errno are "safer" for than functions like printf() that take locks -- you _can_ touch them in a signal handler but you need to restore them.


You call it a strawman, I call it knowing what matters when writing portable code across multiple platforms and compilers.

You will notice that I have linked Open Group errno documentation in another post. That is what matters, not what GNU thinks.


So, what does matter? What is your actual statement? That you can't use errno in a multi-threaded environment? That you can't use errno in the presence of signal handlers?

What exactly is your complaint about the GNU link? As far as I can see it more or less regurgitates what POSIX has to say about async-signal-safety.

You made some vague statements that a mechanism that is used on billions of devices is fundamentally unsound, while providing no evidence other than maintaining that it would break in conjunction with signal handlers -- when signal handlers are expressly to be used with caution, no matter what you intend to touch.

If you worry that you can't safely touch errno in a signal handler (provided that you reset it) on some broken platform from decades ago, because a spec like this [0] is too much swallow (understandably so, since this spec is a futile attempt to reconcile all the relevant history into a useful document), then I have a solution... Don't touch errno in a signal handler, which is probably solid practical advice anyway.

Oh, and how do you reconcile the fact that many syscalls are declared async-safe by POSIX that may set errno themselves?

"Operations which obtain the value of errno and operations which assign a value to errno shall be async-signal-safe, provided that the signal-catching function saves the value of errno upon entry and restores it before it returns."

Tada!

[0] https://pubs.opengroup.org/onlinepubs/9699919799/functions/V...


One thing one learns about writing specifications in English is the meaning of must, should and shall.

As for soundness, it is C we are talking about here.


Where "shall" is usually understood as equivalent to "must". Are you going to cite me one reliable source for the adventurous claims you're making all the time, or are you just going to continue putting out blatant falsehoods and strawmans?

> As for soundness, it is C we are talking about here.

Well the only thing we're doing here is trolling, nothing of substance has been put on the plate so far.


Excerpt from C11:

"[...] errno which expands to a modifiable lvalue that has type int and thread local storage duration"


Still doesn't cover signals, only works if C11 threads are being used (it is all open if OS threads follow the same TLS mechanism), and those C89 and C99 code bases get nothing from it anyway.


In practice it works with major thread implementations even before C11, even though it doesn't say so in the standard.

You can't call OS functions from a signal handler so I can't see why signals would matter. Have I missed something?


> You can't call OS functions from a signal handler

You can, as long as these functions are "async signal safe", see https://man7.org/linux/man-pages/man7/signal-safety.7.html for details.


Oh wow. I mean, fair enough for simple utilities like strlen(). But open(), write(), close()? I had no idea.


You can, it is UB in what might happen, you either get lucky, or not.

Don't expect a compiler error, this is C, where the programmer knows best.


You can call any system function that is marked as async-signal-safe. Whether that's a great idea is a different question.

> Don't expect a compiler error, this is C, where the programmer knows best.

As for this snark, these considerations are pretty much OS and concurrency stuff that transcend any implementation language [0]. No one prevents you to switch back to single-threaded cushy OOP-y Javascript, however note that someone has to implement and run that for you.

[0] yes, signals aren't beautiful, and in 2023 a beautifully designed OS might better do delivery of asynchronous messages using event handling and/or using dedicated threads. However, that's computing history for you, and it can be worked with. If you don't like it find a different platform. C doesn't really require signals, and if you use Unix with a different language, signals won't just go away.


Indeed,

"Transcending POSIX: The End of an Era?"

https://www.usenix.org/publications/loginonline/transcending...


So you're going back to URL firing mode, instead of making clear statements that are reasonably backed up?


This [1] seems to contradict, that at least for more "recent" versions of POSIX.

[1]: https://unix.org/whitepapers/reentrant.html


That isn't the most recent version, this is,

https://pubs.opengroup.org/onlinepubs/9699919799/functions/e...


That’s all documented in the standard though. Why would you access errno any other way?


The main issues with errno are you need to remember to reset it before you enter a context whose failability you care about, so any code which relies on errno must be:

    errno = 0
    // code which may error here
    if(errno) { … }
And that errno is notably not scoped, so the code which may error should only be composed of calls to libc, or code whose interaction with libc you understand perfectly, otherwise you need to save and restore errno around uncontrolled calls.

This is quite verbose, annoying, and error prone.


> The main issues with errno are you need to remember to reset it before you enter a context whose failability you care about, so any code which relies on errno must be:

> errno = 0 > // code which may error here > if(errno) { … }

Which are the functions that set errno, and only errno, on failure?

In practice, you will clear errno once, and then repeatedly check for failure after every call to a libc function.

> And that errno is notably not scoped, so the code which may error should only be composed of calls to libc, or code whose interaction with libc you understand perfectly, otherwise you need to save and restore errno around uncontrolled calls.

I don't understand what this means.


Consider you are trying to do a foreign function call from an interpreted language. You make the call and then want to check errno to see what the error was, but you don't know what precise C calls the runtime may have made in the meantime, or whether it might have reset errno. The only reliable thing to do is to mark library functions explicitly as modifying errno, and storing that somewhere else so that you can reliably retrieve that value.

It's a pain, and if it's not done correctly by everyone then it leaves you with subtle intermittent bugs.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: