// SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause)
// Copyright (c) 2020 Anton Protopopov
#include <stdlib.h>
#include <limits.h>
#include <string.h>
#include <errno.h>
#include <stdio.h>

#define warn(...) fprintf(stderr, __VA_ARGS__)

#ifdef __x86_64__
static int errno_by_name_x86_64(const char *errno_name)
{

#define strcase(X, N) if (!strcmp(errno_name, (X))) return N

	strcase("EPERM", 1);
	strcase("ENOENT", 2);
	strcase("ESRCH", 3);
	strcase("EINTR", 4);
	strcase("EIO", 5);
	strcase("ENXIO", 6);
	strcase("E2BIG", 7);
	strcase("ENOEXEC", 8);
	strcase("EBADF", 9);
	strcase("ECHILD", 10);
	strcase("EAGAIN", 11);
	strcase("EWOULDBLOCK", 11);
	strcase("ENOMEM", 12);
	strcase("EACCES", 13);
	strcase("EFAULT", 14);
	strcase("ENOTBLK", 15);
	strcase("EBUSY", 16);
	strcase("EEXIST", 17);
	strcase("EXDEV", 18);
	strcase("ENODEV", 19);
	strcase("ENOTDIR", 20);
	strcase("EISDIR", 21);
	strcase("EINVAL", 22);
	strcase("ENFILE", 23);
	strcase("EMFILE", 24);
	strcase("ENOTTY", 25);
	strcase("ETXTBSY", 26);
	strcase("EFBIG", 27);
	strcase("ENOSPC", 28);
	strcase("ESPIPE", 29);
	strcase("EROFS", 30);
	strcase("EMLINK", 31);
	strcase("EPIPE", 32);
	strcase("EDOM", 33);
	strcase("ERANGE", 34);
	strcase("EDEADLK", 35);
	strcase("EDEADLOCK", 35);
	strcase("ENAMETOOLONG", 36);
	strcase("ENOLCK", 37);
	strcase("ENOSYS", 38);
	strcase("ENOTEMPTY", 39);
	strcase("ELOOP", 40);
	strcase("ENOMSG", 42);
	strcase("EIDRM", 43);
	strcase("ECHRNG", 44);
	strcase("EL2NSYNC", 45);
	strcase("EL3HLT", 46);
	strcase("EL3RST", 47);
	strcase("ELNRNG", 48);
	strcase("EUNATCH", 49);
	strcase("ENOCSI", 50);
	strcase("EL2HLT", 51);
	strcase("EBADE", 52);
	strcase("EBADR", 53);
	strcase("EXFULL", 54);
	strcase("ENOANO", 55);
	strcase("EBADRQC", 56);
	strcase("EBADSLT", 57);
	strcase("EBFONT", 59);
	strcase("ENOSTR", 60);
	strcase("ENODATA", 61);
	strcase("ETIME", 62);
	strcase("ENOSR", 63);
	strcase("ENONET", 64);
	strcase("ENOPKG", 65);
	strcase("EREMOTE", 66);
	strcase("ENOLINK", 67);
	strcase("EADV", 68);
	strcase("ESRMNT", 69);
	strcase("ECOMM", 70);
	strcase("EPROTO", 71);
	strcase("EMULTIHOP", 72);
	strcase("EDOTDOT", 73);
	strcase("EBADMSG", 74);
	strcase("EOVERFLOW", 75);
	strcase("ENOTUNIQ", 76);
	strcase("EBADFD", 77);
	strcase("EREMCHG", 78);
	strcase("ELIBACC", 79);
	strcase("ELIBBAD", 80);
	strcase("ELIBSCN", 81);
	strcase("ELIBMAX", 82);
	strcase("ELIBEXEC", 83);
	strcase("EILSEQ", 84);
	strcase("ERESTART", 85);
	strcase("ESTRPIPE", 86);
	strcase("EUSERS", 87);
	strcase("ENOTSOCK", 88);
	strcase("EDESTADDRREQ", 89);
	strcase("EMSGSIZE", 90);
	strcase("EPROTOTYPE", 91);
	strcase("ENOPROTOOPT", 92);
	strcase("EPROTONOSUPPORT", 93);
	strcase("ESOCKTNOSUPPORT", 94);
	strcase("ENOTSUP", 95);
	strcase("EOPNOTSUPP", 95);
	strcase("EPFNOSUPPORT", 96);
	strcase("EAFNOSUPPORT", 97);
	strcase("EADDRINUSE", 98);
	strcase("EADDRNOTAVAIL", 99);
	strcase("ENETDOWN", 100);
	strcase("ENETUNREACH", 101);
	strcase("ENETRESET", 102);
	strcase("ECONNABORTED", 103);
	strcase("ECONNRESET", 104);
	strcase("ENOBUFS", 105);
	strcase("EISCONN", 106);
	strcase("ENOTCONN", 107);
	strcase("ESHUTDOWN", 108);
	strcase("ETOOMANYREFS", 109);
	strcase("ETIMEDOUT", 110);
	strcase("ECONNREFUSED", 111);
	strcase("EHOSTDOWN", 112);
	strcase("EHOSTUNREACH", 113);
	strcase("EALREADY", 114);
	strcase("EINPROGRESS", 115);
	strcase("ESTALE", 116);
	strcase("EUCLEAN", 117);
	strcase("ENOTNAM", 118);
	strcase("ENAVAIL", 119);
	strcase("EISNAM", 120);
	strcase("EREMOTEIO", 121);
	strcase("EDQUOT", 122);
	strcase("ENOMEDIUM", 123);
	strcase("EMEDIUMTYPE", 124);
	strcase("ECANCELED", 125);
	strcase("ENOKEY", 126);
	strcase("EKEYEXPIRED", 127);
	strcase("EKEYREVOKED", 128);
	strcase("EKEYREJECTED", 129);
	strcase("EOWNERDEAD", 130);
	strcase("ENOTRECOVERABLE", 131);
	strcase("ERFKILL", 132);
	strcase("EHWPOISON", 133);

#undef strcase

	return -1;

}
#endif

/* Try to find the errno number using the errno(1) program */
static int errno_by_name_dynamic(const char *errno_name)
{
	int len = strlen(errno_name);
	int err, number = -1;
	char buf[128];
	char cmd[64];
	char *end;
	long val;
	FILE *f;

	/* sanity check to not call popen with random input */
	for (int i = 0; i < len; i++) {
		if (errno_name[i] < 'A' || errno_name[i] > 'Z') {
			warn("errno_name contains invalid char 0x%02x: %s\n",
					errno_name[i], errno_name);
			return -1;
		}
	}

	snprintf(cmd, sizeof(cmd), "errno %s", errno_name);
	f = popen(cmd, "r");
	if (!f) {
		warn("popen: %s: %s\n", cmd, strerror(errno));
		return -1;
	}

	if (!fgets(buf, sizeof(buf), f)) {
		goto close;
	} else if (ferror(f)) {
		warn("fgets: %s\n", strerror(errno));
		goto close;
	}

	// expecting "<name> <number> <description>"
	if (strncmp(errno_name, buf, len) || strlen(buf) < len+2) {
		warn("expected '%s': %s\n", errno_name, buf);
		goto close;
	}
	errno = 0;
	val = strtol(buf+len+2, &end, 10);
	if (errno || end == (buf+len+2) || number < 0 || number > INT_MAX) {
		warn("can't parse the second column, expected int: %s\n", buf);
		goto close;
	}
	number = val;

close:
	err = pclose(f);
	if (err < 0)
		warn("pclose: %s\n", strerror(errno));
#ifndef __x86_64__
	/* Ignore the error for x86_64 where we have a table compiled in */
	else if (err && WEXITSTATUS(err) == 127) {
		warn("errno(1) required for errno name/number mapping\n");
	} else if (err) {
		warn("errno(1) exit status (see wait(2)): 0x%x\n", err);
	}
#endif
	return number;
}

int errno_by_name(const char *errno_name)
{
#ifdef __x86_64__
	int err;

	err = errno_by_name_x86_64(errno_name);
	if (err >= 0)
		return err;
#endif

	return errno_by_name_dynamic(errno_name);
}
