|
|
Subscribe / Log in / New account

Glibc wrappers for (nearly all) Linux system calls

Glibc wrappers for (nearly all) Linux system calls

Posted Aug 21, 2015 8:36 UTC (Fri) by xnox (guest, #63320)
Parent article: Glibc wrappers for (nearly all) Linux system calls

I'm stopping to like errno. At first, golang style return error everywhere felt weird, but then it started to make a lot of sense. Diving into systemd & linux kernel code, everything returning -errno or 0 (success) makes so much more sense, with the rest of things passed by reference if additional outputs are required. glibc is fast, but is it pleasant to use?! i'm not so sure any more.


to post comments

Glibc wrappers for (nearly all) Linux system calls

Posted Aug 21, 2015 19:47 UTC (Fri) by wahern (subscriber, #37304) [Link]

If everything is else is returned via reference, why -errno? In my code I also usually return errno directly, but without negating it. I return negative values for application-defined errors. All C- and POSIX-defined errno values must be positive, and no unix-like system uses the negative range for implementation-specific errno values; nor does Windows AFAIK. That way my libraries can pass-through system errors, and I don't have to define ad hoc types for error reporting (which sounds like good idea in principle until you have to glue together a dozen different libraries; even using enums causes headaches because of GCC and clang warnings).

I partition the negative range using a simple prefix system, which makes it easy to mix-and-match components. For example, from my DNS library,

#define DNS_EBASE -(('d' << 24) | ('n' << 16) | ('s' << 8) | 64)
enum dns_errno {
        DNS_ENOBUFS = DNS_EBASE,
        DNS_EILLEGAL,
        DNS_EORDER,
        DNS_ESECTION,
        DNS_EUNKNOWN,
        DNS_EADDRESS,
        DNS_ENOQUERY,
        DNS_ENOANSWER,
        DNS_EFETCHED,
        DNS_ESERVICE, /* EAI_SERVICE */
        DNS_ENONAME,  /* EAI_NONAME */
        DNS_EFAIL,    /* EAI_FAIL */
        DNS_ELAST,
}; /* dns_errno */

/* for documentation only; will always be type int */
#define dns_error_t int

...

dns_error_t dns_res_submit(struct dns_resolver *, const char *, enum dns_type, enum dns_class);
struct dns_packet *dns_res_fetch(struct dns_resolver *, dns_error_t *);

Helpfully, strerror must always return a valid string for all integer values, even for unknown values.

The strerror function maps the number in errnum to a message string. Typically,
the values for errnum come from errno, but strerror shall map any value of type
int to a message.
C11 (N1570) 7.24.6.2p2

So if an application-specific value accidentally leaks to a component that doesn't understand the protocol it's relatively benign. In fact, most strerror implementations will include the value in the message, so you'll actually get useful output if it gets passed to strerror. But usually each component will define it's own strerror interface that forwards to strerror or a sub-component's strerror.

It's the most useful and practical error reporting method I've found, at least for C code, and especially for C libraries. Of course, sometimes a routine is better defined (easier to use, more intuitive) by returning the error value through a reference, rather than as the return value. But that's just a variation on the theme. Unless you know you'll always operate in a closed software ecosystem, every other scheme is just chasing a dragon like other classic rookie mistakes: constantly writing configuration parsers, relying too heavily on malloc/free replacements, and reinventing logging instead of using stderr or perhaps syslog. Billions of man hours have been wasted down those rabbit holes.


Copyright © 2025, Eklektix, Inc.
Comments and public postings are copyrighted by their creators.
Linux is a registered trademark of Linus Torvalds