feat(errno): public errno.h with strerror/strerror_r
This commit is contained in:
@@ -0,0 +1,296 @@
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include <config.h>
|
||||
#endif
|
||||
|
||||
#include <errno.h>
|
||||
|
||||
/*
|
||||
* vlibc — strerror / strerror_r (todo 2).
|
||||
*
|
||||
* strerror maps an errno value to an informative, descriptive message. The
|
||||
* messages are this libc's own standard English texts (they need not match
|
||||
* glibc verbatim). Known values resolve to immutable static string constants;
|
||||
* unknown values resolve to "Unknown error <N>" with <N> formatted in place.
|
||||
*
|
||||
* Thread-safety of the unknown-value path: the formatted string lives in a
|
||||
* single static buffer, so two threads formatting *unknown* errno values
|
||||
* concurrently can observe each other's text. This tradeoff is deliberate at
|
||||
* this stage: unknown errno values are a rare diagnostic path, the library
|
||||
* has no per-thread storage beyond the errno TCB slot yet, and strerror must
|
||||
* not depend on malloc (or any other subsystem). The pointer returned for
|
||||
* known values is unaffected. Once the real TCB layout lands (startup todo),
|
||||
* this can migrate to a per-thread buffer without changing the interface.
|
||||
*
|
||||
* The unknown-value formatter is hand-rolled: strerror is libc base and must
|
||||
* not pull in stdio/string before those exist.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Message table: one entry per defined E* value, indexed directly by the
|
||||
* errno number. Designated initializers keep name/value pairs provably in
|
||||
* sync with <errno.h>; entries for the ABI gaps (41, 58 — values the kernel
|
||||
* never produces) stay NULL and are treated as unknown. EWOULDBLOCK and
|
||||
* EDEADLOCK are aliases of EAGAIN and EDEADLK, so they share those entries.
|
||||
* The array auto-sizes to EHWPOISON + 1 (134) entries.
|
||||
*/
|
||||
static const char *const errmsg[] = {
|
||||
[0] = "Success",
|
||||
[EPERM] = "Operation not permitted",
|
||||
[ENOENT] = "No such file or directory",
|
||||
[ESRCH] = "No such process",
|
||||
[EINTR] = "Interrupted system call",
|
||||
[EIO] = "Input/output error",
|
||||
[ENXIO] = "No such device or address",
|
||||
[E2BIG] = "Argument list too long",
|
||||
[ENOEXEC] = "Exec format error",
|
||||
[EBADF] = "Bad file descriptor",
|
||||
[ECHILD] = "No child processes",
|
||||
[EAGAIN] = "Resource temporarily unavailable",
|
||||
[ENOMEM] = "Cannot allocate memory",
|
||||
[EACCES] = "Permission denied",
|
||||
[EFAULT] = "Bad address",
|
||||
[ENOTBLK] = "Block device required",
|
||||
[EBUSY] = "Device or resource busy",
|
||||
[EEXIST] = "File exists",
|
||||
[EXDEV] = "Invalid cross-device link",
|
||||
[ENODEV] = "No such device",
|
||||
[ENOTDIR] = "Not a directory",
|
||||
[EISDIR] = "Is a directory",
|
||||
[EINVAL] = "Invalid argument",
|
||||
[ENFILE] = "Too many open files in system",
|
||||
[EMFILE] = "Too many open files",
|
||||
[ENOTTY] = "Inappropriate ioctl for device",
|
||||
[ETXTBSY] = "Text file busy",
|
||||
[EFBIG] = "File too large",
|
||||
[ENOSPC] = "No space left on device",
|
||||
[ESPIPE] = "Illegal seek",
|
||||
[EROFS] = "Read-only file system",
|
||||
[EMLINK] = "Too many links",
|
||||
[EPIPE] = "Broken pipe",
|
||||
[EDOM] = "Numerical argument out of domain",
|
||||
[ERANGE] = "Numerical result out of range",
|
||||
[EDEADLK] = "Resource deadlock would occur",
|
||||
[ENAMETOOLONG] = "File name too long",
|
||||
[ENOLCK] = "No locks available",
|
||||
[ENOSYS] = "Function not implemented",
|
||||
[ENOTEMPTY] = "Directory not empty",
|
||||
[ELOOP] = "Too many levels of symbolic links",
|
||||
[ENOMSG] = "No message of desired type",
|
||||
[EIDRM] = "Identifier removed",
|
||||
[ECHRNG] = "Channel number out of range",
|
||||
[EL2NSYNC] = "Level 2 not synchronized",
|
||||
[EL3HLT] = "Level 3 halted",
|
||||
[EL3RST] = "Level 3 reset",
|
||||
[ELNRNG] = "Link number out of range",
|
||||
[EUNATCH] = "Protocol driver not attached",
|
||||
[ENOCSI] = "No CSI structure available",
|
||||
[EL2HLT] = "Level 2 halted",
|
||||
[EBADE] = "Invalid exchange",
|
||||
[EBADR] = "Invalid request descriptor",
|
||||
[EXFULL] = "Exchange full",
|
||||
[ENOANO] = "No anode",
|
||||
[EBADRQC] = "Invalid request code",
|
||||
[EBADSLT] = "Invalid slot",
|
||||
[EBFONT] = "Bad font file format",
|
||||
[ENOSTR] = "Device not a stream",
|
||||
[ENODATA] = "No data available",
|
||||
[ETIME] = "Timer expired",
|
||||
[ENOSR] = "Out of streams resources",
|
||||
[ENONET] = "Machine is not on the network",
|
||||
[ENOPKG] = "Package not installed",
|
||||
[EREMOTE] = "Object is remote",
|
||||
[ENOLINK] = "Link has been severed",
|
||||
[EADV] = "Advertise error",
|
||||
[ESRMNT] = "Srmount error",
|
||||
[ECOMM] = "Communication error on send",
|
||||
[EPROTO] = "Protocol error",
|
||||
[EMULTIHOP] = "Multihop attempted",
|
||||
[EDOTDOT] = "RFS specific error",
|
||||
[EBADMSG] = "Bad message",
|
||||
[EOVERFLOW] = "Value too large for defined data type",
|
||||
[ENOTUNIQ] = "Name not unique on network",
|
||||
[EBADFD] = "File descriptor in bad state",
|
||||
[EREMCHG] = "Remote address changed",
|
||||
[ELIBACC] = "Can not access a needed shared library",
|
||||
[ELIBBAD] = "Accessing a corrupted shared library",
|
||||
[ELIBSCN] = ".lib section in a.out corrupted",
|
||||
[ELIBMAX] = "Attempting to link in too many shared libraries",
|
||||
[ELIBEXEC] = "Cannot exec a shared library directly",
|
||||
[EILSEQ] = "Invalid or incomplete multibyte or wide character",
|
||||
[ERESTART] = "Interrupted system call should be restarted",
|
||||
[ESTRPIPE] = "Streams pipe error",
|
||||
[EUSERS] = "Too many users",
|
||||
[ENOTSOCK] = "Socket operation on non-socket",
|
||||
[EDESTADDRREQ] = "Destination address required",
|
||||
[EMSGSIZE] = "Message too long",
|
||||
[EPROTOTYPE] = "Protocol wrong type for socket",
|
||||
[ENOPROTOOPT] = "Protocol not available",
|
||||
[EPROTONOSUPPORT] = "Protocol not supported",
|
||||
[ESOCKTNOSUPPORT] = "Socket type not supported",
|
||||
[EOPNOTSUPP] = "Operation not supported",
|
||||
[EPFNOSUPPORT] = "Protocol family not supported",
|
||||
[EAFNOSUPPORT] = "Address family not supported by protocol",
|
||||
[EADDRINUSE] = "Address already in use",
|
||||
[EADDRNOTAVAIL] = "Cannot assign requested address",
|
||||
[ENETDOWN] = "Network is down",
|
||||
[ENETUNREACH] = "Network is unreachable",
|
||||
[ENETRESET] = "Network dropped connection on reset",
|
||||
[ECONNABORTED] = "Software caused connection abort",
|
||||
[ECONNRESET] = "Connection reset by peer",
|
||||
[ENOBUFS] = "No buffer space available",
|
||||
[EISCONN] = "Transport endpoint is already connected",
|
||||
[ENOTCONN] = "Transport endpoint is not connected",
|
||||
[ESHUTDOWN] = "Cannot send after transport endpoint shutdown",
|
||||
[ETOOMANYREFS] = "Too many references: cannot splice",
|
||||
[ETIMEDOUT] = "Connection timed out",
|
||||
[ECONNREFUSED] = "Connection refused",
|
||||
[EHOSTDOWN] = "Host is down",
|
||||
[EHOSTUNREACH] = "No route to host",
|
||||
[EALREADY] = "Operation already in progress",
|
||||
[EINPROGRESS] = "Operation now in progress",
|
||||
[ESTALE] = "Stale file handle",
|
||||
[EUCLEAN] = "Structure needs cleaning",
|
||||
[ENOTNAM] = "Not a XENIX named type file",
|
||||
[ENAVAIL] = "No XENIX semaphores available",
|
||||
[EISNAM] = "Is a named type file",
|
||||
[EREMOTEIO] = "Remote I/O error",
|
||||
[EDQUOT] = "Disk quota exceeded",
|
||||
[ENOMEDIUM] = "No medium found",
|
||||
[EMEDIUMTYPE] = "Wrong medium type",
|
||||
[ECANCELED] = "Operation canceled",
|
||||
[ENOKEY] = "Required key not available",
|
||||
[EKEYEXPIRED] = "Key has expired",
|
||||
[EKEYREVOKED] = "Key has been revoked",
|
||||
[EKEYREJECTED] = "Key was rejected by service",
|
||||
[EOWNERDEAD] = "Owner died",
|
||||
[ENOTRECOVERABLE] = "State not recoverable",
|
||||
[ERFKILL] = "Operation not possible due to RF-kill",
|
||||
[EHWPOISON] = "Memory page has hardware error",
|
||||
};
|
||||
|
||||
/* The table spans 0..EHWPOISON exactly: the ABI gaps stay NULL entries. */
|
||||
_Static_assert(sizeof errmsg / sizeof errmsg[0] == EHWPOISON + 1,
|
||||
"strerror table must cover every E* value 0..EHWPOISON");
|
||||
|
||||
/* Shared storage for the unknown-value path (see the file-top comment). */
|
||||
static char errbuf[32];
|
||||
|
||||
/*
|
||||
* Write "Unknown error <errnum>" into dst, truncating to cap bytes and always
|
||||
* NUL-terminating when cap > 0. Hand-rolled so this file needs no stdio.
|
||||
*/
|
||||
static void
|
||||
format_unknown(int errnum, char *dst, size_t cap)
|
||||
{
|
||||
static const char prefix[] = "Unknown error ";
|
||||
char digits[12]; /* enough for "-2147483648" */
|
||||
size_t ndigits;
|
||||
size_t i;
|
||||
unsigned long mag;
|
||||
|
||||
if (cap == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
/* Absolute value as unsigned long; INT_MIN negates safely in long. */
|
||||
mag = (unsigned long)(errnum < 0 ? -(long)errnum : errnum);
|
||||
|
||||
/* Digits, least significant first. */
|
||||
ndigits = 0;
|
||||
do
|
||||
{
|
||||
digits[ndigits] = (char)('0' + (int)(mag % 10));
|
||||
ndigits++;
|
||||
mag /= 10;
|
||||
} while (mag != 0);
|
||||
|
||||
i = 0;
|
||||
while (i + 1 < cap && prefix[i] != '\0')
|
||||
{
|
||||
dst[i] = prefix[i];
|
||||
i++;
|
||||
}
|
||||
if (i + 1 < cap && errnum < 0)
|
||||
{
|
||||
dst[i] = '-';
|
||||
i++;
|
||||
}
|
||||
while (i + 1 < cap && ndigits > 0)
|
||||
{
|
||||
ndigits--;
|
||||
dst[i] = digits[ndigits];
|
||||
i++;
|
||||
}
|
||||
dst[i] = '\0';
|
||||
}
|
||||
|
||||
/*
|
||||
* Return the message for errnum, or NULL when it is unknown (out of table
|
||||
* range or an ABI gap).
|
||||
*/
|
||||
static const char *
|
||||
lookup(int errnum)
|
||||
{
|
||||
if (errnum >= 0 && errnum < (int)(sizeof errmsg / sizeof errmsg[0]))
|
||||
{
|
||||
return errmsg[errnum];
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
char *
|
||||
strerror(int errnum)
|
||||
{
|
||||
const char *msg;
|
||||
|
||||
msg = lookup(errnum);
|
||||
if (msg == NULL)
|
||||
{
|
||||
format_unknown(errnum, errbuf, sizeof errbuf);
|
||||
msg = errbuf;
|
||||
}
|
||||
return (char *)msg;
|
||||
}
|
||||
|
||||
#if VLIBC_LEVEL_GE(2)
|
||||
/*
|
||||
* XSI strerror_r: copy the message for errnum into buf (truncated to buflen,
|
||||
* always NUL-terminated when buflen > 0) and return 0. Unknown errnum values
|
||||
* produce "Unknown error <errnum>" with the same success return; buf == NULL
|
||||
* with buflen > 0 yields EINVAL; buflen == 0 writes nothing and returns 0.
|
||||
* The GNU char*-returning semantics are deliberately not implemented.
|
||||
*/
|
||||
int
|
||||
strerror_r(int errnum, char *buf, size_t buflen)
|
||||
{
|
||||
const char *msg;
|
||||
char unknown[32];
|
||||
size_t i;
|
||||
|
||||
if (buflen == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
if (buf == NULL)
|
||||
{
|
||||
return EINVAL;
|
||||
}
|
||||
|
||||
msg = lookup(errnum);
|
||||
if (msg == NULL)
|
||||
{
|
||||
format_unknown(errnum, unknown, sizeof unknown);
|
||||
msg = unknown;
|
||||
}
|
||||
|
||||
i = 0;
|
||||
while (i + 1 < buflen && msg[i] != '\0')
|
||||
{
|
||||
buf[i] = msg[i];
|
||||
i++;
|
||||
}
|
||||
buf[i] = '\0';
|
||||
return 0;
|
||||
}
|
||||
#endif /* VLIBC_LEVEL_GE(2) */
|
||||
Reference in New Issue
Block a user