/*
 * Copyright (C) 2025 Jakub Kruszona-Zawadzki, Saglabs SA
 * 
 * This file is part of MooseFS.
 * 
 * MooseFS is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, version 2 (only).
 * 
 * MooseFS is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with MooseFS; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02111-1301, USA
 * or visit http://www.gnu.org/licenses/gpl-2.0.html
 */

#if defined(_THREAD_SAFE) || defined(_REENTRANT) || defined(_USE_PTHREADS)
#  define USE_PTHREADS 1
#endif

#include <stdlib.h>
#include <string.h>
#include <inttypes.h>
#include <errno.h>
#ifdef USE_PTHREADS
#include <pthread.h>
#endif

#include "massert.h"

typedef struct errent {
	int num;
	const char* str;
} errent;

static errent errtab[] = {
#ifdef E2BIG
	{E2BIG,"E2BIG (Argument list too long)"},
#endif
#ifdef EACCES
	{EACCES,"EACCES (Permission denied)"},
#endif
#ifdef EADDRINUSE
	{EADDRINUSE,"EADDRINUSE (Address already in use)"},
#endif
#ifdef EADDRNOTAVAIL
	{EADDRNOTAVAIL,"EADDRNOTAVAIL (Cannot assign requested address)"},
#endif
#ifdef EADV
	{EADV,"EADV (Advertise error)"},
#endif
#ifdef EAFNOSUPPORT
	{EAFNOSUPPORT,"EAFNOSUPPORT (Address family not supported by protocol family)"},
#endif
#ifdef EAGAIN
	{EAGAIN,"EAGAIN (Resource temporarily unavailable)"},
#endif
#ifdef EALREADY
	{EALREADY,"EALREADY (Operation already in progress)"},
#endif
#ifdef EAUTH
	{EAUTH,"EAUTH (Authentication error)"},
#endif
#ifdef EBADARCH
	{EBADARCH,"EBADARCH (Bad CPU type in executable)"},
#endif
#ifdef EBADEXEC
	{EBADEXEC,"EBADEXEC (Bad executable)"},
#endif
#ifdef EBADE
	{EBADE,"EBADE (Invalid exchange)"},
#endif
#ifdef EBADFD
	{EBADFD,"EBADFD (File descriptor invalid for this operation)"},
#endif
#ifdef EBADF
	{EBADF,"EBADF (Bad file descriptor)"},
#endif
#ifdef EBADMACHO
	{EBADMACHO,"EBADMACHO (Malformed Macho file)"},
#endif
#ifdef EBADMSG
	{EBADMSG,"EBADMSG (Bad message)"},
#endif
#ifdef EBADRPC
	{EBADRPC,"EBADRPC (RPC struct is bad)"},
#endif
#ifdef EBADRQC
	{EBADRQC,"EBADRQC (Invalid request code)"},
#endif
#ifdef EBADR
	{EBADR,"EBADR (Invalid request descriptor)"},
#endif
#ifdef EBADSLT
	{EBADSLT,"EBADSLT (Invalid slot)"},
#endif
#ifdef EBFONT
	{EBFONT,"EBFONT (Bad font file format)"},
#endif
#ifdef EBUSY
	{EBUSY,"EBUSY (Device or resource busy)"},
#endif
#ifdef ECANCELED
	{ECANCELED,"ECANCELED (Operation canceled)"},
#endif
#ifdef ECHILD
	{ECHILD,"ECHILD (No child processes)"},
#endif
#ifdef ECHRNG
	{ECHRNG,"ECHRNG (Channel number out of range)"},
#endif
#ifdef ECOMM
	{ECOMM,"ECOMM (Communication error on send)"},
#endif
#ifdef ECONNABORTED
	{ECONNABORTED,"ECONNABORTED (Software caused connection abort)"},
#endif
#ifdef ECONNREFUSED
	{ECONNREFUSED,"ECONNREFUSED (Connection refused)"},
#endif
#ifdef ECONNRESET
	{ECONNRESET,"ECONNRESET (Connection reset by peer)"},
#endif
#ifdef EDEADLK
	{EDEADLK,"EDEADLK (Resource deadlock would occur)"},
#endif
#ifdef EDEADLOCK
	{EDEADLOCK,"EDEADLOCK (File locking deadlock error)"},
#endif
#ifdef EDESTADDRREQ
	{EDESTADDRREQ,"EDESTADDRREQ (Destination address required)"},
#endif
#ifdef EDEVERR
	{EDEVERR,"EDEVERR (Device error, e.g. paper out)"},
#endif
#ifdef EDOM
	{EDOM,"EDOM (Numerical argument out of domain)"},
#endif
#ifdef EDOOFUS
	{EDOOFUS,"EDOOFUS (Programming error)"},
#endif
#ifdef EDOTDOT
	{EDOTDOT,"EDOTDOT (RFS specific error)"},
#endif
#ifdef EDQUOT
	{EDQUOT,"EDQUOT (Quota exceeded)"},
#endif
#ifdef EEXIST
	{EEXIST,"EEXIST (File exists)"},
#endif
#ifdef EFAULT
	{EFAULT,"EFAULT (Bad address)"},
#endif
#ifdef EFBIG
	{EFBIG,"EFBIG (File too large)"},
#endif
#ifdef EFTYPE
	{EFTYPE,"EFTYPE (Inappropriate file type or format)"},
#endif
#ifdef EHOSTDOWN
	{EHOSTDOWN,"EHOSTDOWN (Host is down)"},
#endif
#ifdef EHOSTUNREACH
	{EHOSTUNREACH,"EHOSTUNREACH (No route to host)"},
#endif
#ifdef EIDRM
	{EIDRM,"EIDRM (Identifier removed)"},
#endif
#ifdef EILSEQ
	{EILSEQ,"EILSEQ (Illegal byte sequence)"},
#endif
#ifdef EINPROGRESS
	{EINPROGRESS,"EINPROGRESS (Operation now in progress)"},
#endif
#ifdef EINTR
	{EINTR,"EINTR (Interrupted system call)"},
#endif
#ifdef EINVAL
	{EINVAL,"EINVAL (Invalid argument)"},
#endif
#ifdef EIO
	{EIO,"EIO (Input/output error)"},
#endif
#ifdef EISCONN
	{EISCONN,"EISCONN (Transport endpoint is already connected)"},
#endif
#ifdef EISDIR
	{EISDIR,"EISDIR (Is a directory)"},
#endif
#ifdef EISNAM
	{EISNAM,"EISNAM (Is a named type file)"},
#endif
#ifdef EKEYEXPIRED
	{EKEYEXPIRED,"EKEYEXPIRED (Key has expired)"},
#endif
#ifdef EKEYREJECTED
	{EKEYREJECTED,"EKEYREJECTED (Key was rejected by service)"},
#endif
#ifdef EKEYREVOKED
	{EKEYREVOKED,"EKEYREVOKED (Key has been revoked)"},
#endif
#ifdef EL2HLT
	{EL2HLT,"EL2HLT (Level 2 halted)"},
#endif
#ifdef EL2NSYNC
	{EL2NSYNC,"EL2NSYNC (Level 2 not synchronized)"},
#endif
#ifdef EL3HLT
	{EL3HLT,"EL3HLT (Level 3 halted)"},
#endif
#ifdef EL3RST
	{EL3RST,"EL3RST (Level 3 reset)"},
#endif
#ifdef ELIBACC
	{ELIBACC,"ELIBACC (Can not access a needed shared library)"},
#endif
#ifdef ELIBBAD
	{ELIBBAD,"ELIBBAD (Accessing a corrupted shared library)"},
#endif
#ifdef ELIBEXEC
	{ELIBEXEC,"ELIBEXEC (Cannot exec a shared library directly)"},
#endif
#ifdef ELIBMAX
	{ELIBMAX,"ELIBMAX (Attempting to link in too many shared libraries)"},
#endif
#ifdef ELIBSCN
	{ELIBSCN,"ELIBSCN (.lib section in a.out corrupted)"},
#endif
#ifdef ELNRNG
	{ELNRNG,"ELNRNG (Link number out of range)"},
#endif
#ifdef ELOCKUNMAPPED
	{ELOCKUNMAPPED,"ELOCKUNMAPPED (locked lock was unmapped)"},
#endif
#ifdef ELOOP
	{ELOOP,"ELOOP (Too many levels of symbolic links)"},
#endif
#ifdef EMEDIUMTYPE
	{EMEDIUMTYPE,"EMEDIUMTYPE (Wrong medium type)"},
#endif
#ifdef EMFILE
	{EMFILE,"EMFILE (Too many open files)"},
#endif
#ifdef EMLINK
	{EMLINK,"EMLINK (Too many links)"},
#endif
#ifdef EMSGSIZE
	{EMSGSIZE,"EMSGSIZE (Message too long)"},
#endif
#ifdef EMULTIHOP
	{EMULTIHOP,"EMULTIHOP (Multihop attempted)"},
#endif
#ifdef ENAMETOOLONG
	{ENAMETOOLONG,"ENAMETOOLONG (File name too long)"},
#endif
#ifdef ENAVAIL
	{ENAVAIL,"ENAVAIL (No XENIX semaphores available)"},
#endif
#ifdef ENEEDAUTH
	{ENEEDAUTH,"ENEEDAUTH (Need authenticator)"},
#endif
#ifdef ENETDOWN
	{ENETDOWN,"ENETDOWN (Network is down)"},
#endif
#ifdef ENETRESET
	{ENETRESET,"ENETRESET (Network dropped connection because of reset)"},
#endif
#ifdef ENETUNREACH
	{ENETUNREACH,"ENETUNREACH (Network is unreachable)"},
#endif
#ifdef ENFILE
	{ENFILE,"ENFILE (Too many open files in system)"},
#endif
#ifdef ENOANO
	{ENOANO,"ENOANO (No anode)"},
#endif
#ifdef ENOATTR
	{ENOATTR,"ENOATTR (Attribute not found)"},
#endif
#ifdef ENOBUFS
	{ENOBUFS,"ENOBUFS (No buffer space available)"},
#endif
#ifdef ENOCSI
	{ENOCSI,"ENOCSI (No CSI structure available)"},
#endif
#ifdef ENODATA
	{ENODATA,"ENODATA (No data available)"},
#endif
#ifdef ENODEV
	{ENODEV,"ENODEV (Operation not supported by device or no such device)"},
#endif
#ifdef ENOENT
	{ENOENT,"ENOENT (No such file or directory)"},
#endif
#ifdef ENOEXEC
	{ENOEXEC,"ENOEXEC (Exec format error)"},
#endif
#ifdef ENOKEY
	{ENOKEY,"ENOKEY (Required key not available)"},
#endif
#ifdef ENOLCK
	{ENOLCK,"ENOLCK (No locks available)"},
#endif
#ifdef ENOLINK
	{ENOLINK,"ENOLINK (Link has been severed)"},
#endif
#ifdef ENOMEDIUM
	{ENOMEDIUM,"ENOMEDIUM (No medium found)"},
#endif
#ifdef ENOMEM
	{ENOMEM,"ENOMEM (Cannot allocate memory)"},
#endif
#ifdef ENOMSG
	{ENOMSG,"ENOMSG (No message of desired type)"},
#endif
#ifdef ENONET
	{ENONET,"ENONET (Machine is not on the network)"},
#endif
#ifdef ENOPKG
	{ENOPKG,"ENOPKG (Package not installed)"},
#endif
#ifdef ENOPOLICY
	{ENOPOLICY,"ENOPOLICY (No such policy registered)"},
#endif
#ifdef ENOPROTOOPT
	{ENOPROTOOPT,"ENOPROTOOPT (Protocol not available)"},
#endif
#ifdef ENOSPC
	{ENOSPC,"ENOSPC (No space left on device)"},
#endif
#ifdef ENOSR
	{ENOSR,"ENOSR (Out of streams resources)"},
#endif
#ifdef ENOSTR
	{ENOSTR,"ENOSTR (Device not a stream)"},
#endif
#ifdef ENOSYS
	{ENOSYS,"ENOSYS (Unsupported file system operation)"},
#endif
#ifdef ENOTACTIVE
	{ENOTACTIVE,"ENOTACTIVE (Facility is not active)"},
#endif
#ifdef ENOTBLK
	{ENOTBLK,"ENOTBLK (Block device required)"},
#endif
#ifdef ENOTCONN
	{ENOTCONN,"ENOTCONN (Transport endpoint is not connected)"},
#endif
#ifdef ENOTDIR
	{ENOTDIR,"ENOTDIR (Not a directory)"},
#endif
#ifdef ENOTEMPTY
	{ENOTEMPTY,"ENOTEMPTY (Directory not empty)"},
#endif
#ifdef ENOTNAM
	{ENOTNAM,"ENOTNAM (Not a XENIX named type file)"},
#endif
#ifdef ENOTRECOVERABLE
	{ENOTRECOVERABLE,"ENOTRECOVERABLE (State not recoverable)"},
#endif
#ifdef ENOTSOCK
	{ENOTSOCK,"ENOTSOCK (Socket operation on non-socket)"},
#endif
#ifdef ENOTSUP
	{ENOTSUP,"ENOTSUP (Operation not supported)"},
#endif
#ifdef ENOTTY
	{ENOTTY,"ENOTTY (Inappropriate ioctl for device)"},
#endif
#ifdef ENOTUNIQ
	{ENOTUNIQ,"ENOTUNIQ (Name not unique on network)"},
#endif
#ifdef ENXIO
	{ENXIO,"ENXIO (No such device or address)"},
#endif
#ifdef EOPNOTSUPP
	{EOPNOTSUPP,"EOPNOTSUPP (Operation not supported on transport endpoint)"},
#endif
#ifdef EOVERFLOW
	{EOVERFLOW,"EOVERFLOW (Value too large to be stored in data type)"},
#endif
#ifdef EOWNERDEAD
	{EOWNERDEAD,"EOWNERDEAD (Process died with the lock)"},
#endif
#ifdef EPERM
	{EPERM,"EPERM (Operation not permitted)"},
#endif
#ifdef EPFNOSUPPORT
	{EPFNOSUPPORT,"EPFNOSUPPORT (Protocol family not supported)"},
#endif
#ifdef EPIPE
	{EPIPE,"EPIPE (Broken pipe)"},
#endif
#ifdef EPROCLIM
	{EPROCLIM,"EPROCLIM (Too many processes)"},
#endif
#ifdef EPROCUNAVAIL
	{EPROCUNAVAIL,"EPROCUNAVAIL (Bad procedure for program)"},
#endif
#ifdef EPROGMISMATCH
	{EPROGMISMATCH,"EPROGMISMATCH (Program version wrong)"},
#endif
#ifdef EPROGUNAVAIL
	{EPROGUNAVAIL,"EPROGUNAVAIL (RPC prog. not avail)"},
#endif
#ifdef EPROTONOSUPPORT
	{EPROTONOSUPPORT,"EPROTONOSUPPORT (Protocol not supported)"},
#endif
#ifdef EPROTOTYPE
	{EPROTOTYPE,"EPROTOTYPE (Protocol wrong type for socket)"},
#endif
#ifdef EPROTO
	{EPROTO,"EPROTO (Protocol error)"},
#endif
#ifdef EPWROFF
	{EPWROFF,"EPWROFF (Device power is off)"},
#endif
#ifdef ERANGE
	{ERANGE,"ERANGE (Result too large)"},
#endif
#ifdef EREMCHG
	{EREMCHG,"EREMCHG (Remote address changed)"},
#endif
#ifdef EREMOTEIO
	{EREMOTEIO,"EREMOTEIO (Remote I/O error)"},
#endif
#ifdef EREMOTE
	{EREMOTE,"EREMOTE (Object is remote)"},
#endif
#ifdef ERESTART
	{ERESTART,"ERESTART (Interrupted system call should be restarted)"},
#endif
#ifdef EROFS
	{EROFS,"EROFS (Read-only file system)"},
#endif
#ifdef ERPCMISMATCH
	{ERPCMISMATCH,"ERPCMISMATCH (RPC version wrong)"},
#endif
#ifdef ESHLIBVERS
	{ESHLIBVERS,"ESHLIBVERS (Shared library version mismatch)"},
#endif
#ifdef ESHUTDOWN
	{ESHUTDOWN,"ESHUTDOWN (Cannot send after transport endpoint shutdown)"},
#endif
#ifdef ESOCKTNOSUPPORT
	{ESOCKTNOSUPPORT,"ESOCKTNOSUPPORT (Socket type not supported)"},
#endif
#ifdef ESPIPE
	{ESPIPE,"ESPIPE (Illegal seek)"},
#endif
#ifdef ESRCH
	{ESRCH,"ESRCH (No such process)"},
#endif
#ifdef ESRMNT
	{ESRMNT,"ESRMNT (Srmount error)"},
#endif
#ifdef ESTALE
	{ESTALE,"ESTALE (Stale NFS file handle)"},
#endif
#ifdef ESTRPIPE
	{ESTRPIPE,"ESTRPIPE (Streams pipe error)"},
#endif
#ifdef ETIMEDOUT
	{ETIMEDOUT,"ETIMEDOUT (Operation timed out)"},
#endif
#ifdef ETIME
	{ETIME,"ETIME (Timer expired)"},
#endif
#ifdef ETOOMANYREFS
	{ETOOMANYREFS,"ETOOMANYREFS (Too many references: cannot splice)"},
#endif
#ifdef ETXTBSY
	{ETXTBSY,"ETXTBSY (Text file busy)"},
#endif
#ifdef EUCLEAN
	{EUCLEAN,"EUCLEAN (Structure needs cleaning)"},
#endif
#ifdef EUNATCH
	{EUNATCH,"EUNATCH (Protocol driver not attached)"},
#endif
#ifdef EUSERS
	{EUSERS,"EUSERS (Too many users)"},
#endif
#ifdef EXDEV
	{EXDEV,"EXDEV (Cross-device link)"},
#endif
#ifdef EXFULL
	{EXFULL,"EXFULL (Exchange full)"},
#endif
	{0,NULL}
};

static errent *errhash = NULL;
static uint32_t errhsize = 0;

#define STRERR_BUFF_SIZE 100

#ifdef USE_PTHREADS
static pthread_key_t strerrstorage;

static void strerr_storage_free(void *ptr) {
	if (ptr!=NULL) {
		free(ptr);
	}
}

static void strerr_storage_init(void) {
	zassert(pthread_key_create(&strerrstorage,strerr_storage_free));
	zassert(pthread_setspecific(strerrstorage,NULL));
}

static void* strerr_storage_get(void) {
	uint8_t *buff;
	buff = pthread_getspecific(strerrstorage);
	if (buff==NULL) {
		buff = malloc(STRERR_BUFF_SIZE);
		passert(buff);
		zassert(pthread_setspecific(strerrstorage,buff));
	}
	return buff;
}
#else
static void* strerrstorage = NULL;

static void strerr_storage_free(void) {
	if (strerrstorage!=NULL) {
		free(strerrstorage);
	}
}

static void* strerr_storage_get(void) {
	if (strerrstorage==NULL) {
		strerrstorage = malloc(STRERR_BUFF_SIZE);
		passert(strerrstorage);
	}
	return strerrstorage;
}
#endif

void strerr_init(void) {
	uint32_t n;
	uint32_t hash,disp;

	for (n=0 ; errtab[n].str ; n++) {}

	n = (n*3)/2;

	errhsize = 1;
	while (n>0) {
		errhsize<<=1;
		n>>=1;
	}

	errhash = malloc(sizeof(errent)*errhsize);
	memset(errhash,0,sizeof(errent)*errhsize);

	for (n=0 ; errtab[n].str ; n++) {
		hash = errtab[n].num;
		disp = ((hash * 760092119) & (errhsize-1)) | 1;
		hash = ((hash * 1905886897) & (errhsize-1));
		while (errhash[hash].str!=NULL && errhash[hash].num != errtab[n].num) {
			hash+=disp;
			hash&=(errhsize-1);
		}
		if (errhash[hash].str==NULL) {
			errhash[hash] = errtab[n];
		}
	}

#ifdef USE_PTHREADS
	strerr_storage_init();
#endif
}

const char* strerr(int error) {
	uint32_t hash,disp;
	char* strbuff;

	if (error==0) {
		return "Success (errno=0)";
	}
	hash = error;
	disp = ((hash * 760092119) & (errhsize-1)) | 1;
	hash = ((hash * 1905886897) & (errhsize-1));
	while (errhash[hash].str!=NULL) {
		if (errhash[hash].num == error) {
			return errhash[hash].str;
		}
		hash+=disp;
		hash&=(errhsize-1);
	}
	strbuff = strerr_storage_get();
	snprintf(strbuff,STRERR_BUFF_SIZE,"Unknown error: %d",error);
	strbuff[STRERR_BUFF_SIZE-1] = 0;
	return strbuff;
}

void strerr_term(void) {
	free(errhash);
#ifndef USE_PTHREADS
	strerr_storage_free();
#endif
}
