aboutsummaryrefslogblamecommitdiffstats
path: root/src/libmdsserver/util.c
blob: 09d996a5b255ae1c257d5ae6c727b744408f9f34 (plain) (tree)
1
2
3

                                 
                                                                         















                                                                        
                   
 
                   



                   
                   

                       
                  

                     
                   
                     

 

   







                                        




                                                      

                                
 











                                                  


 





                                                                                 

                                
 

                                       



   












                                                    

                                        

                    
 











                                                                                  



   


                                         





                                                                      
   

                                                  
 




















                                                                 

 



                                                   

                                                    






                                                                   

                                                  
 








                                               

 








                                                                            

                                                            
 






















                                                                                                            

 









                                                                 

                                                          
 











                                                   

 

   







                                                                 

                                                                         
 












                                                                
         


























                                                               


 






                                                                       








                                                                  

                                                                             
 
























                                                               
 


                           

 








                                                                                                      

 

                                                      
















































































































































































































                                                                     
   






                                                     

                                                     
 










                                                  



   
                                                  
   


                                                                                       
   

                                 
 


































                                                                                      

 

   






                                                              

                                                         
 
                    
  








                                                             



   








                                                                          

                                                                                          
 








                                             

 




                                                                        



                                                                                    
   

                                                                     
 




















                                                                  

 







                                                                                                           

                                                       
 





























































                                                                                                   

 





                                                                                                          

                                                                                                 














                                                                                            
                                                            

                                                                      




                                                                                                  
 








































































                                                                                                             







                                                                                                          

                                                                                                 















                                                                                            
                                                            

                                                          




                                                                                                   
 






                                                                                                                  
 
/**
 * mds — A micro-display server
 * Copyright © 2014, 2015, 2016, 2017  Mattias Andrée (maandree@kth.se)
 * 
 * This program 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, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
 */
#include "util.h"
#include "config.h"
#include "macros.h"

#include <alloca.h>
#include <stdlib.h>
#include <unistd.h>
#include <limits.h>
#include <string.h>
#include <signal.h>
#include <sys/socket.h>
#include <errno.h>
#include <ctype.h>
#include <time.h>
#include <sys/wait.h>
#include <stdint.h>
#include <inttypes.h>



/**
 * The content of `/proc/self/exe`, when
 * `prepare_reexec` was invoked.
 */
static char self_exe[PATH_MAX] = {0};



/**
 * Convert a client ID string into a client ID integer
 * 
 * @param   str  The client ID string
 * @return       The client ID integer
 */
uint64_t
parse_client_id(const char *str)
{
	char client_words[22];
	char *client_high;
	char *client_low;
	uint64_t client;

	strcpy(client_high = client_words, str);
	client_low = rawmemchr(client_words, ':');
	*client_low++ = '\0';
	client = atou64(client_high) << 32;
	client |= atou64(client_low);

	return client;
}


/**
 * Read an environment variable, but handle it as undefined if empty
 * 
 * @param   var  The environment variable's name
 * @return       The environment variable's value, `NULL` if empty or not defined
 */
char *
getenv_nonempty(const char *var)
{
	char *rc = getenv(var);
	return (rc && *rc) ? rc : NULL;
}


/**
 * Prepare the server so that it can reexec into
 * a newer version of the executed file.
 * 
 * This is required for two reasons:
 * 1:  We cannot use argv[0] as PATH-resolution may
 *     cause it to reexec into another pathname, and
 *     maybe to wrong program. Additionally argv[0]
 *     may not even refer to the program, and chdir
 *     could also hinter its use.
 * 2:  The kernel appends ` (deleted)` to
 *     `/proc/self/exe` once it has been removed,
 *     so it cannot be replaced.
 * 
 * @return  Zero on success, -1 on error
 */
int
prepare_reexec(void)
{
	ssize_t len;
	len = readlink(SELF_EXE, self_exe, (sizeof(self_exe) / sizeof(char)) - 1);
	fail_if (len < 0);
	/* ‘readlink() does not append a null byte to buf.’ */
	self_exe[len] = '\0';
	/* Handle possible race condition: file was removed. */
	if (access(self_exe, F_OK) < 0)
		if (!strcmp(self_exe + (len - 10), " (deleted)"))
			self_exe[len - 10] = '\0';
	return 0;
fail:
	return -1;
}


/**
 * Re-exec the server.
 * This function only returns on failure.
 * 
 * If `prepare_reexec` failed or has not been called,
 * `argv[0]` will be used as a fallback.
 * 
 * @param  argc      The number of elements in `argv`
 * @param  argv      The command line arguments
 * @param  reexeced  Whether the server has previously been re-exec:ed
 */
void
reexec_server(int argc, char **argv, int reexeced)
{
	char **reexec_args;
	char **reexec_args_;
	int i;

	/* Re-exec the server. */
	reexec_args = alloca(((size_t)argc + 2) * sizeof(char*));
	reexec_args_ = reexec_args;
	if (!reexeced) {
		*reexec_args_++ = *argv;
		fail_if (xstrdup(*reexec_args_, "--re-exec"));
		for (i = 1; i < argc; i++)
			reexec_args_[i] = argv[i];
	} else { /* Don't let the --re-exec:s accumulate. */
		*reexec_args_ = *argv;
	}
	for (i = 1; i < argc; i++)
		reexec_args_[i] = argv[i];
	reexec_args_[argc] = NULL;
	execv(self_exe[0] ? self_exe : argv[0], reexec_args);
fail:
	return;
}


/**
 * Set up a signal trap.
 * This function should only be used for common mds
 * signals and signals that does not require and
 * special settings. This function may choose to add
 * additional behaviour depending on the signal, such
 * as blocking other signals.
 * 
 * @param   signo     The signal to trap
 * @param   function  The function to run when the signal is caught
 * @return            Zero on success, -1 on error
 */
int
xsigaction(int signo, void (*function)(int signo))
{
	struct sigaction action;
	sigset_t sigset;

	sigemptyset(&sigset);
	action.sa_handler = function;
	action.sa_mask = sigset;
	action.sa_flags = 0;

	return sigaction(signo, &action, NULL);
}


/**
 * Send a message over a socket
 * 
 * @param   socket   The file descriptor of the socket
 * @param   message  The message to send
 * @param   length   The length of the message
 * @return           The number of bytes that have been sent (even on error)
 */
size_t
send_message(int socket, const char *message, size_t length)
{
	size_t block_size = length;
	size_t sent = 0;
	ssize_t just_sent;

	errno = 0;
	while (length > 0) {
		if ((just_sent = send(socket, message + sent, min(block_size, length), MSG_NOSIGNAL)) < 0) {
			if (errno == EPIPE)
				errno = ECONNRESET;
			if (errno == EMSGSIZE) {
				block_size >>= 1;
				if (!block_size)
					return sent;
			} else {
				return sent;
			}
		} else {
			sent += (size_t)just_sent;
			length -= (size_t)just_sent;
		}
	}

	return sent;
}


/**
 * A version of `atoi` that is strict about the syntax and bounds
 * 
 * @param   str    The text to parse
 * @param   value  Slot in which to store the value
 * @param   min    The minimum accepted value
 * @param   max    The maximum accepted value
 * @return         Zero on success, -1 on syntax error
 */
int
strict_atoi(const char *str, int *value, int min, int max)
{
	long long int r;
	char *endptr;

	r = strtoll(str, &endptr, 10);
	if (!*str || isspace(*str) ||
	    endptr - str != (ssize_t)strlen(str) ||
	    r < (long long int)min ||
	    r > (long long int)max)
		return -1;

	*value = (int)r;
	return 0;
}


/**
 * A version of `atoj` that is strict about the syntax and bounds
 * 
 * @param   str    The text to parse
 * @param   value  Slot in which to store the value
 * @param   min    The minimum accepted value
 * @param   max    The maximum accepted value
 * @return         Zero on success, -1 on syntax error
 */
int
strict_atoj(const char *str, intmax_t *value, intmax_t min, intmax_t max)
{
	static char minstr[3 * sizeof(intmax_t) + 2] = { '\0' };
	intmax_t r = 0;
	char c;
	int neg = 0;

	if (*str == '-') {
		if (!*minstr)
			sprintf(minstr, "%j", INTMAX_MIN);
		if (!strcmp(str, minstr)) {
			r = INTMAX_MIN;
			goto done;
		}
		neg = 1, str++;
	}

	if (!*str)
		return -1;

	while ((c = *str)) {
		if ('0' <= c && c <= '9') {
			if (r > INTMAX_MAX / 10) {
				return -1;
			} else if (r == INTMAX_MAX / 10) {
				if ((c & 15) > INTMAX_MAX % 10)
					return -1;
			}
			r = r * 10 + (c & 15);
		} else {
			return -1;
		}
	}

	if (neg)
		r = -r;

done:
	if (r < min || r > max)
		return -1;

	*value = r;
	return 0;
}


#if defined(__GNUC__)
/* GCC says strict_atouj is a candidate for the attribute ‘pure’,
 * however the line `*value = r` means that it is not, at least
 * if you only consider what GCC's manuals says about the attribute. */
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wsuggest-attribute=pure"
#endif
/**
 * A version of `atouj` that is strict about the syntax and bounds
 * 
 * @param   str    The text to parse
 * @param   value  Slot in which to store the value
 * @param   min    The minimum accepted value
 * @param   max    The maximum accepted value
 * @return         Zero on success, -1 on syntax error
 */
int
strict_atouj(const char *str, uintmax_t *value, uintmax_t min, uintmax_t max)
{
	uintmax_t r = 0;
	char c;

	if (!*str)
		return -1;

	while ((c = *str)) {
		if ('0' <= c && c <= '9') {
			if (r > INTMAX_MAX / 10) {
				return -1;
			} else if (r == INTMAX_MAX / 10) {
				if ((c & 15) > INTMAX_MAX % 10)
					return -1;
			}
			r = r * 10 + (c & 15);
		} else {
			return -1;
		}
	}

	if (r < min || r > max)
		return -1;

	*value = r;
	return 0;
}
#if defined(__GNUC__)
# pragma GCC diagnostic pop
#endif


#define __strict_y(Y, TYPE, PARA_TYPE, HYPER_Y, HYPER_TYPE)\
	int\
	strict_##Y(const char *str, TYPE *value, PARA_TYPE min, PARA_TYPE max)\
	{\
		HYPER_TYPE intermediate_value;\
		if (strict_##HYPER_Y(str, &intermediate_value, (HYPER_TYPE)min, (HYPER_TYPE)max) < 0)\
			return -1;\
		return *value = (TYPE)intermediate_value, 0;\
	}


#define __strict_x(X, TYPE, HYPER_X, HYPER_TYPE)\
	__strict_y(X, TYPE, TYPE, HYPER_X, HYPER_TYPE)


/**
 * A version of `atoh` that is strict about the syntax and bounds
 * 
 * @param   str    The text to parse
 * @param   value  Slot in which to store the value
 * @param   min    The minimum accepted value
 * @param   max    The maximum accepted value
 * @return         Zero on success, -1 on syntax error
 */
__strict_y(atoh, short int, int, atoi, int)


/**
 * A version of `atouh` that is strict about the syntax and bounds
 * 
 * @param   str    The text to parse
 * @param   value  Slot in which to store the value
 * @param   min    The minimum accepted value
 * @param   max    The maximum accepted value
 * @return         Zero on success, -1 on syntax error
 */
__strict_y(atouh, unsigned short int, unsigned int, atouj, uintmax_t)


/**
 * A version of `atou` that is strict about the syntax and bounds
 * 
 * @param   str    The text to parse
 * @param   value  Slot in which to store the value
 * @param   min    The minimum accepted value
 * @param   max    The maximum accepted value
 * @return         Zero on success, -1 on syntax error
 */
__strict_x(atou, unsigned int, atouj, uintmax_t)


/**
 * A version of `atol` that is strict about the syntax and bounds
 * 
 * @param   str    The text to parse
 * @param   value  Slot in which to store the value
 * @param   min    The minimum accepted value
 * @param   max    The maximum accepted value
 * @return         Zero on success, -1 on syntax error
 */
__strict_x(atol, long int, atoj, intmax_t)


/**
 * A version of `atoul` that is strict about the syntax and bounds
 * 
 * @param   str    The text to parse
 * @param   value  Slot in which to store the value
 * @param   min    The minimum accepted value
 * @param   max    The maximum accepted value
 * @return         Zero on success, -1 on syntax error
 */
__strict_x(atoul, unsigned long int, atouj, uintmax_t)


/**
 * A version of `atoll` that is strict about the syntax and bounds
 * 
 * @param   str    The text to parse
 * @param   value  Slot in which to store the value
 * @param   min    The minimum accepted value
 * @param   max    The maximum accepted value
 * @return         Zero on success, -1 on syntax error
 */
__strict_x(atoll, long long int, atoj, intmax_t)


/**
 * A version of `atoull` that is strict about the syntax and bounds
 * 
 * @param   str    The text to parse
 * @param   value  Slot in which to store the value
 * @param   min    The minimum accepted value
 * @param   max    The maximum accepted value
 * @return         Zero on success, -1 on syntax error
 */
__strict_x(atoull, unsigned long long int, atouj, uintmax_t)


/**
 * A version of `atoz` that is strict about the syntax and bounds
 * 
 * @param   str    The text to parse
 * @param   value  Slot in which to store the value
 * @param   min    The minimum accepted value
 * @param   max    The maximum accepted value
 * @return         Zero on success, -1 on syntax error
 */
__strict_x(atoz, size_t, atouj, uintmax_t)


/**
 * A version of `atosz` that is strict about the syntax and bounds
 * 
 * @param   str    The text to parse
 * @param   value  Slot in which to store the value
 * @param   min    The minimum accepted value
 * @param   max    The maximum accepted value
 * @return         Zero on success, -1 on syntax error
 */
__strict_x(atosz, ssize_t, atoj, intmax_t)


/**
 * A version of `ato8` that is strict about the syntax and bounds
 * 
 * @param   str    The text to parse
 * @param   value  Slot in which to store the value
 * @param   min    The minimum accepted value
 * @param   max    The maximum accepted value
 * @return         Zero on success, -1 on syntax error
 */
__strict_y(ato8, int8_t, int, atoi, int)


/**
 * A version of `atou8` that is strict about the syntax and bounds
 * 
 * @param   str    The text to parse
 * @param   value  Slot in which to store the value
 * @param   min    The minimum accepted value
 * @param   max    The maximum accepted value
 * @return         Zero on success, -1 on syntax error
 */
__strict_y(atou8, uint8_t, int, atoi, int)


/**
 * A version of `ato16` that is strict about the syntax and bounds
 * 
 * @param   str    The text to parse
 * @param   value  Slot in which to store the value
 * @param   min    The minimum accepted value
 * @param   max    The maximum accepted value
 * @return         Zero on success, -1 on syntax error
 */
__strict_y(ato16, int16_t, int, atoi, int)


/**
 * A version of `atou16` that is strict about the syntax and bounds
 * 
 * @param   str    The text to parse
 * @param   value  Slot in which to store the value
 * @param   min    The minimum accepted value
 * @param   max    The maximum accepted value
 * @return         Zero on success, -1 on syntax error
 */
__strict_y(atou16, uint16_t, unsigned int, atouj, uintmax_t)


/**
 * A version of `ato32` that is strict about the syntax and bounds
 * 
 * @param   str    The text to parse
 * @param   value  Slot in which to store the value
 * @param   min    The minimum accepted value
 * @param   max    The maximum accepted value
 * @return         Zero on success, -1 on syntax error
 */
__strict_x(ato32, int32_t, atoj, intmax_t)


/**
 * A version of `atou32` that is strict about the syntax and bounds
 * 
 * @param   str    The text to parse
 * @param   value  Slot in which to store the value
 * @param   min    The minimum accepted value
 * @param   max    The maximum accepted value
 * @return         Zero on success, -1 on syntax error
 */
__strict_x(atou32, uint32_t, atouj, uintmax_t)


/**
 * A version of `ato64` that is strict about the syntax and bounds
 * 
 * @param   str    The text to parse
 * @param   value  Slot in which to store the value
 * @param   min    The minimum accepted value
 * @param   max    The maximum accepted value
 * @return         Zero on success, -1 on syntax error
 */
__strict_x(ato64, int64_t, atoj, intmax_t)


/**
 * A version of `atou64` that is strict about the syntax and bounds
 * 
 * @param   str    The text to parse
 * @param   value  Slot in which to store the value
 * @param   min    The minimum accepted value
 * @param   max    The maximum accepted value
 * @return         Zero on success, -1 on syntax error
 */
__strict_x(atou64, uint64_t, atouj, uintmax_t)


#undef __strict_x


/**
 * Send a buffer into a file and ignore interruptions
 * 
 * @param   fd      The file descriptor
 * @param   buffer  The buffer
 * @param   length  The length of the buffer
 * @return          Zero on success, -1 on error
 */
int
full_write(int fd, const char *buffer, size_t length)
{
	ssize_t wrote;
	while (length > 0) {
		errno = 0;
		wrote = write(fd, buffer, length);
		fail_if (errno && errno != EINTR);
		length -= (size_t)max(wrote, 0);
		buffer += (size_t)max(wrote, 0);
	}
	return 0;
fail:
	return -1;
}


/**
 * Read a file completely and ignore interruptions
 * 
 * @param   fd      The file descriptor
 * @param   length  Output parameter for the length of the file, may be `NULL`
 * @return          The content of the file, you will need to free it. `NULL` on error.
 */
char *
full_read(int fd, size_t *length)
{
	char *old_buf = NULL;
	size_t buffer_size = 8 << 10;
	size_t buffer_ptr = 0;
	char *buffer;
	ssize_t got;
	int saved_errno;

	if (length)
		*length = 0;

	/* Allocate buffer for data. */
	fail_if (xmalloc(buffer, buffer_size, char));

	/* Read the file. */
	for (;;) {
		/* Grow buffer if it is too small. */
		if (buffer_size == buffer_ptr)
			fail_if (xxrealloc(old_buf, buffer, buffer_size <<= 1, char));

		/* Read from the file into the buffer. */
		got = read(fd, buffer + buffer_ptr, buffer_size - buffer_ptr);
		fail_if (got < 0 && errno != EINTR);
		if (!got)
			break;
		buffer_ptr += (size_t)got;
	}

	if (length)
		*length = buffer_ptr;
	return buffer;
fail:
	saved_errno = errno;
	free(old_buf);
	free(buffer);
	return errno = saved_errno, NULL;
}


/**
 * Send a full message even if interrupted
 * 
 * @param   socket   The file descriptor for the socket to use
 * @param   message  The message to send
 * @param   length   The length of the message
 * @return           Zero on success, -1 on error
 */
int
full_send(int socket, const char *message, size_t length)
{
	size_t sent;
  
	while (length > 0) {
		sent = send_message(socket, message, length);
		fail_if (sent < length && errno != EINTR);
		message += sent;
		length -= sent;
	}
	return 0;
fail:
	return -1;
}


/**
 * Check whether a string begins with a specific string,
 * where neither of the strings are necessarily NUL-terminated
 * 
 * @param   haystack    The string that should start with the other string
 * @param   needle      The string the first string should start with
 * @param   haystack_n  The length of `haystack`
 * @param   needle_n    The length of `needle`
 * @return              Whether the `haystack` begins with `needle`
 */
int
startswith_n(const char *haystack, const char *needle, size_t haystack_n, size_t needle_n)
{
	size_t i;
	if (haystack_n < needle_n)
		return 0;

	for (i = 0; i < needle_n; i++)
		if (haystack[i] != needle[i])
			return 0;

	return 1;
}


/**
 * Wrapper around `waitpid` that never returns on an interruption unless
 * it is interrupted 100 times within the same second
 * 
 * @param   pid      See description of `pid` in the documentation for `waitpid`
 * @param   status   See description of `status` in the documentation for `waitpid`
 * @param   options  See description of `options` in the documentation for `waitpid`
 * @return           See the documentation for `waitpid`
 */
pid_t
uninterruptable_waitpid(pid_t pid, int *restrict status, int options)
{
	struct timespec time_start;
	struct timespec time_intr;
	int intr_count = 0, have_time;
	pid_t rc;

	have_time = (monotone(&time_start) >= 0);

rewait:
	rc = waitpid(pid, status, options);
	if (rc == (pid_t)-1) {
		fail_if (errno != EINTR);
		if (have_time && monotone(&time_intr) >= 0)
			if (time_start.tv_sec != time_intr.tv_sec)
				intr_count = 0;
		if (intr_count++ < 100)
			goto rewait;
		/* Don't let the CPU catch fire! */
		errno = EINTR;
	}
fail:
	return rc;
}


/**
 * Check whether a NUL-terminated string is encoded in UTF-8
 * 
 * @param   string              The string
 * @param   allow_modified_nul  Whether Modified UTF-8 is allowed, which allows a two-byte encoding for NUL
 * @return                      Zero if good, -1 on encoding error
 */
int
verify_utf8(const char *string, int allow_modified_nul)
{
	static long BYTES_TO_MIN_BITS[] = {0, 0,  8, 12, 17, 22, 37};
	static long BYTES_TO_MAX_BITS[] = {0, 7, 11, 16, 21, 26, 31};
	long bytes = 0, read_bytes = 0, bits = 0, c, character;

	/*                                                      min bits  max bits
	  0.......                                                 0         7
	  110..... 10......                                        8        11
	  1110.... 10...... 10......                              12        16
	  11110... 10...... 10...... 10......                     17        21
	  111110.. 10...... 10...... 10...... 10......            22        26
	  1111110. 10...... 10...... 10...... 10...... 10......   27        31
	*/

	while ((c = (long)(*string++))) {
		if (read_bytes == 0) {
			/* First byte of the character. */

			if (!(c & 0x80))
				/* Single-byte character. */
				continue;

			if ((c & 0xC0) == 0x80)
				/* Single-byte character marked as multibyte, or
				   a non-first byte in a multibyte character. */
				return -1;

			/* Multibyte character. */
			while ((c & 0x80))
				bytes++, c <<= 1;
			read_bytes = 1;
			character = c & 0x7F;
			if (bytes > 6)
				/* 31-bit characters can be encoded with 6-bytes,
				   and UTF-8 does not cover higher code points. */
				return -1;
		} else {
			/* Not first byte of the character. */

			if ((c & 0xC0) != 0x80)
				/* Beginning of new character before a
				   multibyte character has ended. */
				return -1;

			character = (character << 6) | (c & 0x7F);

			if (++read_bytes < bytes)
				/* Not at last byte yet. */
				continue;

			/* Check that the character is not unnecessarily long. */
			while (character)
				character >>= 1, bits++;
			bits = ((bits == 0) && (bytes == 2) && allow_modified_nul) ? 8 : bits;
			if ((bits < BYTES_TO_MIN_BITS[bytes]) || (BYTES_TO_MAX_BITS[bytes] < bits))
				return -1;

			read_bytes = bytes = bits = 0;
		}
	}

	/* Make sure we did not stop at the middle of a multibyte character. */
	return read_bytes == 0 ? 0 : -1;
}


/**
 * Construct an error message to be sent to a client
 * 
 * @param   recv_client_id    The client ID attached on the message that was received, must not be `NULL`
 * @param   recv_message_id   The message ID attached on the message that was received, must not be `NULL`
 * @param   recv_command      The value of the `Command`-header on the message that was received,
 *                            must not be `NULL`
 * @param   custom            Non-zero if the error is a custom error
 * @param   errnum            The error number, `errno` should be used if the error
 *                            is not a custom error, zero should be used on success,
 *                            a negative integer, such as -1, indicates that the error
 *                            message should not include an error number,
 *                            it is not allowed to have this value be negative and
 *                            `custom` be zero at the same time
 * @param   message           The description of the error, the line feed at the end
 *                            is added automatically, `NULL` if the description should
 *                            be omitted
 * @param   send_buffer       Pointer to the buffer where the message should be stored,
 *                            it should contain the current send buffer, must not be `NULL`
 * @param   send_buffer_size  Pointer to the allocation size of `*send_buffer`, it should
 *                            contain the current size of `*send_buffer` and will be updated
 *                            with the new size, must not be `NULL`
 * @param   message_id        The message ID of this message
 * @return                    The length of the message, zero on error
 */
size_t
construct_error_message(const char *restrict recv_client_id, const char *restrict recv_message_id,
                        const char *restrict recv_command, int custom, int errnum,
                        const char *restrict message, char **restrict send_buffer,
                        size_t *restrict send_buffer_size, uint32_t message_id)
{
	ssize_t part_length;
	size_t length;
	char *temp;

	/* Measure the maximum length of message, including NUL-termination.. */
	length = sizeof("Command: error\n"
	                "To: 4294967296:4294967296\n"
	                "In response to: 4294967296\n"
	                "Origin command: \n"
	                "Message ID: 4294967296\n"
	                "Error: custom \n"
	                "Length: \n"
	                "\n") / sizeof(char) + 3 * (sizeof(int)) + strlen(recv_command);
	if (message)
		length += (sizeof("Length: \n") / sizeof(char) - 1) + 3 * sizeof(char) + strlen(message) + 1;

	/* Ensure that the send buffer is large enough. */
	if (length > *send_buffer_size) {
		fail_if (yrealloc(temp, *send_buffer, length, char));
		*send_buffer_size = length;
	}

	/* Reset `length` to indicate that the currently written
	   message has zero in length. */
	length = 0;

	/* Start writting the error message, begin with required
	   headers, but exclude the error number. */
	sprintf((*send_buffer) + length,
	        "Command: error\n"
	        "To: %s\n"
	        "In response to: %s\n"
	        "Origin command: %s\n"
	        "Message ID: %"PRIu32"\n"
	        "Error: %s%zn",
	        recv_client_id, recv_message_id, recv_command,
	        message_id, custom ? "custom" : "",
	        &part_length);
	length += (size_t)part_length;

	/* If the error is custom and has a number we need
	   blank space before we write the error number. */
	if (custom && errnum >= 0)
		(*send_buffer)[length++] = ' ';

	/* Add the error number of there is one. */
	if (errnum >= 0)
		sprintf((*send_buffer) + length, "%i%zn", errnum, &part_length),
			length += (size_t)part_length;

	/* End the `Error`-header line. */
	(*send_buffer)[length++] = '\n';

	/* Add the `Length`-header if there is a description. */
	if (message) {
		sprintf((*send_buffer) + length, "Length: %zu\n%zn",
		        strlen(message) + 1, &part_length);
		length += (size_t)part_length + strlen(message) + 1;
	}

	/* Add an empty line to mark the end of headers. */
	(*send_buffer)[length++] = '\n';

	/* Write the description if there is one. */
	if (message) {
		memcpy((*send_buffer) + length, message, strlen(message) * sizeof(char));
		length += strlen(message);
		(*send_buffer)[length++] = '\n';
	}

	return length;
fail:
	return 0;
}


/**
 * Send an error message
 * 
 * @param   recv_client_id    The client ID attached on the message that was received, must not be `NULL`
 * @param   recv_message_id   The message ID attached on the message that was received, must not be `NULL`
 * @param   recv_command      The value of the `Command`-header on the message that was received,
 *                            must not be `NULL`
 * @param   custom            Non-zero if the error is a custom error
 * @param   errnum            The error number, `errno` should be used if the error
 *                            is not a custom error, zero should be used on success,
 *                            a negative integer, such as -1, indicates that the error
 *                            message should not include an error number,
 *                            it is not allowed to have this value be negative and
 *                            `custom` be zero at the same time
 * @param   message           The description of the error, the line feed at the end
 *                            is added automatically, `NULL` if the description should
 *                            be omitted
 * @param   send_buffer       Pointer to the buffer where the message should be stored,
 *                            it should contain the current send buffer, must not be `NULL`
 * @param   send_buffer_size  Pointer to the allocation size of `*send_buffer`, it should
 *                            contain the current size of `*send_buffer` and will be updated
 *                            with the new size, must not be `NULL`
 * @param   socket_fd         The file descriptor of the socket
 * @param   message_id        The message ID of this message
 * @return                    Zero on success, -1 on error
 */
int
send_error(const char *restrict recv_client_id, const char *restrict recv_message_id,
           const char *restrict recv_command, int custom, int errnum, const char *restrict message,
           char **restrict send_buffer, size_t *restrict send_buffer_size, uint32_t message_id,
           int socket_fd)
{
	size_t length;
	fail_if (!(length = construct_error_message(recv_client_id, recv_message_id, recv_command, custom, errnum,
	                                            message, send_buffer, send_buffer_size, message_id)));
	fail_if (full_send(socket_fd, *send_buffer, length));
	return 0;
fail:
	return -1;
}