The rules below are conventions used in the src/ directory. When in doubt, match the surrounding file.
- Indentation: hard tabs. One tab per level. Don't mix tabs and spaces. Continuation lines align with tabs followed by spaces if needed.
- Line length: aim for ~120 characters. Tables, initialiser arrays, and long function declarations may run longer when it improves readability (see
src/lib/util/value.cfor examples that exceed 150 cols). - Braces: K&R. Opening brace on the same line as the control statement; function bodies open on the line after the signature.
void fr_pair_list_init(fr_pair_list_t *list) { if (cond) { ... } else { ... } }
- Single-statement bodies: braces are optional, but pick one and stay consistent within a function. Multi-line bodies always brace.
- Switch:
casealigns withswitch; statements indent one level undercase. - Spaces:
- Around all binary operators:
a + b,x == y. - After commas, never before.
- After keywords:
if (x),while (x),for (;;). - Not inside call parens:
foo(x), neverfoo( x ).
- Around all binary operators:
- Pointer asterisk binds to the variable:
char *p, notchar* porchar * p. constafter the type:char const *pis preferred.const char *pexists in older code but new code useschar const *.- Trailing commas in enums and array initialisers are allowed and common.
- Comments:
/* ... */are most commonly used. Rarely//. Doxygen blocks use/** ... */. #defineindentation: nested preprocessor directives indent the#with a leading space:# define.
- Functions:
snake_case. Public/exported functions use thefr_prefix and a subsystem prefix:fr_pair_list_init,fr_sbuff_out,fr_dict_attr_by_name. - Static functions:
snake_casewith nofr_prefix; an internal helper that backs a public function is often_fr_<name>(leading underscore). - Types and typedefs:
snake_casewith_tsuffix; library types take anfr_prefix:fr_pair_t,fr_dict_attr_t,fr_sbuff_t. Module-local types may omit thefr_prefix. - Macros / compile-time constants:
SCREAMING_SNAKE_CASE:FR_TYPE_STRING,SBUFF_CHAR_CLASS. Function-like macros that act like ordinary functions may be lowercase (e.g.fr_assert(...)). - Enum members:
ALL_CAPS_NAMESwith a shared prefix that identifies the enum:FR_TYPE_NULL,FR_TYPE_STRING, etc. - Struct fields:
snake_case. - Files:
snake_case.c/snake_case.h. No hyphens. - Conventional variable names - use these by reflex:
ctx-TALLOC_CTX *da-fr_dict_attr_t const *len- length (size_tor unsigned)request-request_t *mctx-module_ctx_t const *inst- module instance datat- module thread-instance datap,end- byte pointers walking a buffersbuff/dbuff- sbuff/dbuff parsersout,in- output / input sbuff or sbuff-pair function argumentsslen-fr_slen_torssize_tparse resultvborbox-fr_value_box_t *vp-fr_pair_t *vpt-tmpl_t *
Every .c file begins with:
- License boilerplate as a
/* ... */block. LGPL forsrc/lib/, GPL forsrc/modules/andsrc/bin/. - Doxygen file header:
/** One-line description * * @file path/relative/to/repo/foo.c * * @copyright YYYY Author Name (email) */
RCSID("$Id$")macro - required.- Optional private-section defines (e.g.
#define _PAIR_PRIVATE 1) before includes. - Includes: system headers first, then
<freeradius-devel/...>headers grouped by subsystem, roughly alphabetical within a group.
Every .h file begins with #pragma once, then license, then includes. Never use #ifndef FOO_H guards - #pragma once is the project convention.
Forward typedefs go near the top of headers; full struct definitions live in .c files or private _priv.h headers when the type should be opaque.
- Talloc everywhere:
talloc,talloc_zero,talloc_array,talloc_steal. Always parent allocations under a meaningful context - usually the longest-lived object that should own the memory. MEM(x)wraps an allocation that must not fail:MEM(vp = talloc(ctx, fr_pair_t));. On failure it logs OUT OF MEMORY and aborts. Use it for unrecoverable allocations; don't use it when the caller can recover gracefully.- Talloc thread safety: a hierarchy is owned by exactly one thread. Don't share talloc parents across threads without external locks.
- Integer-returning functions:
0on success,-1on error. A few cases use-2/ negative offsets to encode position; document those. - Pointer-returning functions:
NULLon error. bool-returning functions:trueon success.- Error messages: call
fr_strerror_const("...")for literal strings,fr_strerror_printf("fmt", ...)for formatted ones. Push extra context withfr_strerror_printf_push(...). Do not log directly from a library function - let the caller decide. - NULL checks: prefer
if (!ptr)overif (ptr == NULL). Both are accepted; the negation form is more common.
The standard pattern for multi-step allocation that needs to unwind on failure:
foo_t *f;
f = talloc(ctx, foo_t);
if (!f) return NULL;
if (step_one(f) < 0) goto error;
if (step_two(f) < 0) goto error;
return f;
error:
talloc_free(f);
return NULL;Common label names: error, fail, done, oom. Pick names that describe the exit condition, not the line number. Labels go at column 0 (or one tab indent) - typically just before the cleanup code at the end of the function.
CC_HINT(nonnull)/CC_HINT(nonnull(N))- argument N (1-indexed) is non-NULL. Use on public APIs to catch caller mistakes.CC_HINT(warn_unused_result)- caller must consume the return value.CC_HINT(always_inline)- for tiny helpers in hot paths.CC_HINT(flag_enum)- enum is a bitfield.UNUSED- parameter is intentionally unused.NDEBUG_UNUSED- parameter is intentionally unused in normal builds, used only forfr_assert(...)inNDEBUGbuilds.
DIAG_OFF(name)/DIAG_ON(name)for localised warning suppression. Pair them tightly around the offending construct; never disable a diagnostic file-wide.
Required on every non-static function. Minimum:
/** One-line summary
*
* Optional longer description.
*
* @param[in] arg what it is.
* @param[out] out what gets written.
* @return
* - 0 on success.
* - -1 on error.
*/Useful extras: @note, @hidecallergraph, @copybrief, @see.
- Signed
charas array index: never.arr[c]wherecischarand the byte is ≥ 0x80 reads OOB on signed-char platforms. Cast to(uint8_t)first, or use a helper likefr_sbuff_uint8()instead offr_sbuff_char(). ctype.hfunctions (tolower,isspace,isalpha, …): pass(uint8_t)c, notc. Negativeintarguments are undefined behaviour.- Dictionary attributes - never look up by number with a literal. Use the
fr_dict_attr_autoload_tmechanism so the dictionary can change without breaking code. - Module instance data is read-only after
instantiate(mprotected). Mutable runtime state belongs in thread-instance data (mctx->thread), notmi->data. - No bare
malloc/freein new code - talloc only. The only exception is a handful of system-call wrappers. - No magic numbers in code. Name the constant, or use a dictionary lookup, or both.
- Tabs, K&R,
*next to the variable,char const *. fr_prefix on exported APIs,snake_case_ttypes.#pragma oncein headers,RCSIDin.cfiles.- Doxygen block on every public function.
- Cast to
(uint8_t)before using acharas an array index orctype.hargument. - Talloc all the things;
MEM()for must-succeed allocations. fr_strerror_*for diagnostics; neverfprintf(stderr, ...)from library code.- Errors return
-1/NULL; cleanup viagoto error;.