/*
 * include/haproxy/initcall.h
 *
 * Initcall management.
 *
 * Copyright (C) 2018-2020 Willy Tarreau - w@1wt.eu
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 */

#ifndef _HAPROXY_INITCALL_H
#define _HAPROXY_INITCALL_H

#include <haproxy/compiler.h>

/* List of known init stages. If others are added, please declare their
 * section at the end of the file below.
 */

/* The principle of the initcalls is to create optional sections in the target
 * program which are made of arrays of structures containing a function pointer
 * and 3 argument pointers. Then at boot time, these sections are scanned in a
 * well defined order to call in turn each of these functions with their
 * arguments. This allows to declare register callbacks in C files without
 * having to export lots of things nor to cross-reference functions. There are
 * several initialization stages defined so that certain guarantees are offered
 * (for example list heads might or might not be initialized, pools might or
 * might not have been created yet).
 *
 * On some very old platforms there is no convenient way to retrieve the start
 * or stop pointer for these sections so there is no reliable way to enumerate
 * the callbacks. When this is the case, as detected when USE_OBSOLETE_LINKER
 * is set, instead of using sections we exclusively use constructors whose name
 * is based on the current line number in the file to guarantee uniqueness.
 * When called, these constructors then add their callback to their respective
 * list. It works as well but slightly inflates the executable's size since
 * code has to be emitted just to register each of these callbacks.
 */

/*
 * Please keep those names short enough, they are used to generate section
 * names, Mac OS X accepts section names up to 16 characters, and we prefix
 * them with i_, so stage name can't be more than 14 characters.
 */
enum init_stage {
	STG_PREPARE = 0,      // preset variables, tables, list heads
	STG_LOCK,             // pre-initialize locks
	STG_REGISTER,         // register static lists (keywords etc)
	STG_ALLOC,            // allocate required structures
	STG_POOL,             // create pools
	STG_INIT,             // subsystems normal initialization
	STG_INIT_2,           // runs after step_init_2, to have global.nbthread
	STG_SIZE              // size of the stages array, must be last
};

/* This is the descriptor for an initcall */
struct initcall {
	void (*const fct)(void *arg1, void *arg2, void *arg3);
	void *arg1;
	void *arg2;
	void *arg3;
	const char *loc_file; /* file where the call is declared, or NULL */
	int loc_line;         /* line where the call is declared, or NULL */
#if defined(USE_OBSOLETE_LINKER)
	void *next;
#endif
};


#if !defined(USE_OBSOLETE_LINKER)

#define HA_INIT_SECTION(s)  HA_SECTION("i_" # s)

/* Declare a static variable in the init section dedicated to stage <stg>,
 * with an element referencing function <function> and arguments <a1..a3>.
 * <linenum> is needed to deduplicate entries created from a same file. The
 * trick with (stg<STG_SIZE) consists in verifying that stg if a valid enum
 * value from the initcall set, and to emit a warning or error if it is not.
 * The function's type is cast so that it is technically possible to call a
 * function taking other argument types, provided they are all the same size
 * as a pointer (args are cast to (void*)). Do not use this macro directly,
 * use INITCALL{0..3}() instead.
 */
#define __DECLARE_INITCALL(stg, linenum, function, a1, a2, a3)     \
        HA_GLOBL(__start_i_##stg );                                \
        HA_GLOBL(__stop_i_##stg );                                 \
	static const struct initcall *__initcb_##linenum           \
	    __attribute__((__used__)) HA_INIT_SECTION(stg) =	   \
	        (stg < STG_SIZE) ? &(const struct initcall) {      \
		.fct = (void (*)(void *,void *,void *))function,   \
		.arg1 = (void *)(a1),                              \
		.arg2 = (void *)(a2),                              \
		.arg3 = (void *)(a3),                              \
		.loc_file = __FILE__,                              \
		.loc_line = linenum,                               \
	} : NULL


#else // USE_OBSOLETE_LINKER

/* Declare a static constructor function to register a static descriptor for
 * stage <stg>, with an element referencing function <function> and arguments
 * <a1..a3>. <linenum> is needed to deduplicate entries created from a same
 * file. The trick with (stg<STG_SIZE) consists in verifying that stg if a
 * valid enum value from the initcall set, and to emit a warning or error if
 * it is not.
 * The function's type is cast so that it is technically possible to call a
 * function taking other argument types, provided they are all the same size
 * as a pointer (args are cast to (void*)). Do not use this macro directly,
 * use INITCALL{0..3}() instead.
 */
#define __DECLARE_INITCALL(stg, linenum, function, a1, a2, a3)     \
__attribute__((constructor)) static void __initcb_##linenum()      \
{                                                                  \
	static struct initcall entry = {                           \
		.fct  = (void (*)(void *,void *,void *))function,  \
		.arg1 = (void *)(a1),                              \
		.arg2 = (void *)(a2),                              \
		.arg3 = (void *)(a3),                              \
		.loc_file = __FILE__,                              \
		.loc_line = linenum,                               \
	};                                                         \
	if (stg < STG_SIZE) {                                      \
		entry.next = __initstg[stg];                       \
		__initstg[stg] = &entry;                           \
	};                                                         \
}

#endif // USE_OBSOLETE_LINKER

/* This is used to resolve <linenum> to an integer before calling
 * __DECLARE_INITCALL(). Do not use this macro directly, use INITCALL{0..3}()
 * instead.
 */
#define _DECLARE_INITCALL(...) \
	__DECLARE_INITCALL(__VA_ARGS__)

/* This requires that function <function> is called without arguments
 * during init stage <stage> which must be one of init_stage.
 */
#define INITCALL0(stage, function)                                     \
	_DECLARE_INITCALL(stage, __LINE__, function, 0, 0, 0)

/* This requires that function <function> is called with pointer argument
 * <arg1> during init stage <stage> which must be one of init_stage.
 */
#define INITCALL1(stage, function, arg1)                               \
	_DECLARE_INITCALL(stage, __LINE__, function, arg1, 0, 0)

/* This requires that function <function> is called with pointer arguments
 * <arg1..2> during init stage <stage> which must be one of init_stage.
 */
#define INITCALL2(stage, function, arg1, arg2)                         \
	_DECLARE_INITCALL(stage, __LINE__, function, arg1, arg2, 0)

/* This requires that function <function> is called with pointer arguments
 * <arg1..3> during init stage <stage> which must be one of init_stage.
 */
#define INITCALL3(stage, function, arg1, arg2, arg3)                   \
	_DECLARE_INITCALL(stage, __LINE__, function, arg1, arg2, arg3)

#if !defined(USE_OBSOLETE_LINKER)
/* Iterate pointer p (of type initcall**) over all registered calls at
 * stage <stg>.
 */
#define FOREACH_INITCALL(p,stg)                                               \
	for ((p) = &(__start_i_##stg); (p) < &(__stop_i_##stg); (p)++)

#else // USE_OBSOLETE_LINKER

#define FOREACH_INITCALL(p,stg)                                               \
	for ((p) = __initstg[stg]; (p); (p) = (p)->next)
#endif // USE_OBSOLETE_LINKER


#if !defined(USE_OBSOLETE_LINKER)
/* Declare a section for stage <stg>. The start and stop pointers are set by
 * the linker itself, which is why they're declared extern here. The weak
 * attribute is used so that we declare them ourselves if the section is
 * empty. The corresponding sections must contain exclusively pointers to
 * make sure each location may safely be visited by incrementing a pointer.
 */
#define DECLARE_INIT_SECTION(stg)                                                   \
	extern __attribute__((__weak__)) const struct initcall *__start_i_##stg HA_SECTION_START("i_" # stg); \
	extern __attribute__((__weak__)) const struct initcall *__stop_i_##stg  HA_SECTION_STOP("i_" # stg)

/* Declare all initcall sections here */
DECLARE_INIT_SECTION(STG_PREPARE);
DECLARE_INIT_SECTION(STG_LOCK);
DECLARE_INIT_SECTION(STG_REGISTER);
DECLARE_INIT_SECTION(STG_ALLOC);
DECLARE_INIT_SECTION(STG_POOL);
DECLARE_INIT_SECTION(STG_INIT);
DECLARE_INIT_SECTION(STG_INIT_2);

// for use in the main haproxy.c file
#define DECLARE_INIT_STAGES asm("")

/* not needed anymore */
#undef DECLARE_INIT_SECTION

#else // USE_OBSOLETE_LINKER

extern struct initcall *__initstg[STG_SIZE];

// for use in the main haproxy.c file
#define DECLARE_INIT_STAGES struct initcall *__initstg[STG_SIZE]

#endif // USE_OBSOLETE_LINKER

#if !defined(USE_OBSOLETE_LINKER)
/* Run the initcalls for stage <stg>. The test on <stg> is only there to
 * ensure it is a valid initcall stage.
 */
#define RUN_INITCALLS(stg)                                                     \
	do {                                                                   \
		const struct initcall **ptr;                                   \
		if (stg >= STG_SIZE)                                           \
			break;                                                 \
		FOREACH_INITCALL(ptr, stg) {                                   \
			caller_initcall = *ptr;                                \
			caller_file = (*ptr)->loc_file;                        \
			caller_line = (*ptr)->loc_line;                        \
			(*ptr)->fct((*ptr)->arg1, (*ptr)->arg2, (*ptr)->arg3); \
			caller_initcall = NULL;                                \
			caller_file = NULL;                                    \
			caller_line = 0;                                       \
		}                                                              \
	} while (0)

#else // USE_OBSOLETE_LINKER

/* Run the initcalls for stage <stg>. The test on <stg> is only there to
 * ensure it is a valid initcall stage.
 */
#define RUN_INITCALLS(stg)                                                     \
	do {                                                                   \
		const struct initcall *ptr;                                    \
		if (stg >= STG_SIZE)                                           \
			break;                                                 \
		FOREACH_INITCALL(ptr, stg) {                                   \
			caller_initcall = ptr;                                 \
			caller_file = (ptr)->loc_file;                         \
			caller_line = (ptr)->loc_line;                         \
			(ptr)->fct((ptr)->arg1, (ptr)->arg2, (ptr)->arg3);     \
			caller_initcall = NULL;                                \
			caller_file = NULL;                                    \
			caller_line = 0;                                       \
		}                                                              \
	} while (0)

#endif // USE_OBSOLETE_LINKER

#endif /* _HAPROXY_INITCALL_H */

/*
 * Local variables:
 *  c-indent-level: 8
 *  c-basic-offset: 8
 * End:
 */
