Coding style¶
This document describes the code style and guidelines to use for bpfilter contributors. ClangFormat is used to validate and enforce as many of those rules as possible based on .clang-format at the root of the repository. The check build target validates all source files, and the fixstyle build target can automatically fix all the violations.
Note
The CI will use the ClangFormat version from the latest Fedora release. If you use a different version, you might trigger violation(s) during the CI run, which we expect you to fix.
Formatting¶
Use 4 spaces for indentation (no tabs)
Line length is limited to 80 characters, but string literals should not be split (makes grepping for error messages easier)
Opening braces should be on the next line for function, structure, and enumeration definition. For control statements, opening braces should be on the same line
Single-statement bodies don’t require braces. However, if either branch of an
if/elseuses braces, both must use braces
File structure¶
Header guards¶
Use #pragma once, not #ifndef guards:
#pragma once
Include ordering¶
Group and order includes as follows, with a blank line between groups:
System headers (
<linux/...>,<errno.h>,<stdlib.h>)External library headers (
<bpfilter/...>)Local project headers (
"module/file.h")
#include <linux/bpf.h>
#include <errno.h>
#include <stdlib.h>
#include <bpfilter/chain.h>
#include "cgen/program.h"
#include "ctx.h"
Forward declarations¶
Prefer forward declarations over includes when only a pointer to a type is needed:
struct bf_chain;
struct bf_program;
Naming conventions¶
Functions and variables¶
Lowercase with underscores:
bf_chain_new(),bf_ctx_setup()Prefix with module name:
bf_chain_*,bf_program_*,bf_ctx_*Static functions and variables use leading underscore:
_bf_ctx_free()CLI utilities use
bfc_prefix:bfc_parse_file()Macros use uppercase with underscores:
EMIT(),TAKE_PTR(),ARRAY_SIZE()
Types¶
Structs use bf_ prefix:
struct bf_chain
{
// Structure definition
};
Enums use bf_ prefix, values are uppercase with enum prefix:
enum bf_log_level
{
BF_LOG_DBG,
BF_LOG_INFO,
BF_LOG_WARN,
BF_LOG_ERR,
_BF_LOG_MAX, // Sentinel value
};
Sentinel values use leading underscore and _MAX suffix.
Functions¶
Return values¶
Return
0on success, negative errno on failure (-ENOMEM,-EEXIST)Cleanup functions return
voidand take double pointers
int bf_chain_new(struct bf_chain **chain); // 0 or -errno
void bf_chain_free(struct bf_chain **chain); // Sets *chain to NULL
bool bf_chain_is_empty(const struct bf_chain *chain);
Error checking¶
Check errors with if (r) or if (r < 0):
r = bf_chain_new(&chain);
if (r)
return r;
Parameter validation¶
Use assert() to validate preconditions:
int bf_chain_add_rule(struct bf_chain *chain, struct bf_rule *rule)
{
assert(chain);
assert(rule);
...
}
Only use assert() for pointer values. For other validation, use if () with appropriate error logging and return codes.
Memory management¶
Cleanup attributes¶
Use __attribute__((cleanup)) extensively. Two naming conventions:
_free_*: cleanup dynamically allocated objects_clean_*: cleanup objects with automatic storage duration
#define _free_bf_chain_ __attribute__((cleanup(_bf_chain_free)))
void example(void)
{
_free_bf_chain_ struct bf_chain *chain = NULL;
r = bf_chain_new(&chain);
if (r)
return r;
// chain is automatically freed when function returns
}
Cleanup functions¶
Cleanup functions take double pointers, are no-op if *ptr is NULL (already freed), and set *ptr to NULL after freeing:
static void _bf_chain_free(struct bf_chain **chain)
{
if (!*chain)
return;
free(*chain);
*chain = NULL;
}
Ownership transfer¶
Use TAKE_PTR() to transfer ownership:
*out = TAKE_PTR(local); // local becomes NULL, *out takes ownership
Use TAKE_FD() for file descriptors, TAKE_STRUCT() for struct values.
Error handling and logging¶
Logging macros¶
Use the appropriate log level:
bf_dbg(): debug informationbf_info(): informational messagesbf_warn(): warningsbf_err(): errorsbf_abort(): critical errors (terminates)
Log and return errors with bf_err_r():
if (!ptr)
return bf_err_r(-ENOMEM, "failed to allocate chain");
This logs the error and returns the error code in one statement.
Error codes¶
Always use negative errno values: -ENOMEM, -EINVAL, -EEXIST, -ENOENT.
Structs¶
Document struct purpose in the header. Use inline Doxygen comments for non-obvious fields:
/**
* @brief Represents a filtering chain.
*/
struct bf_chain
{
enum bf_chain_type type;
struct bf_list rules; /// List of `struct bf_rule`
size_t rule_count;
int flags; /// Bitmask of enum bf_chain_flags
};
Macros¶
Helper macros¶
Use uppercase for utility macros:
ARRAY_SIZE(arr) // Number of elements
UNUSED(x) // Suppress unused warnings
DUMP(prefix, fmt, ...) // Debug output
Static assertions¶
Use static_assert() to validate assumptions at compile time. This catches errors early and documents invariants:
// Validate enum-to-string array size
static_assert_enum_mapping(log_level_strs, _BF_LOG_MAX);
// Validate struct size assumptions
static_assert(sizeof(struct bf_header) == 16, "unexpected header size");
Testing¶
See Tests for testing conventions and guidelines.
Commit messages¶
Commit messages should be formatted as component: subcomponent: short description:
- Lowercase, imperative mood (“add”, “fix”, “remove”, not “added”, “fixes”)
- No period at the end
- Keep under 72 characters
Components are lib, daemon, cli, tests, build, tools, doc. Subcomponents reflect the directory structure (e.g., tests: e2e:, daemon: cgen: link:). If you’re unsure, check the commit history for a hint.
Examples:
lib: matcher: add meta.flow_hash matcher
tests: e2e: fix end-to-end tests leaving files behind
build: create targets to run tests suites individually
Use the commit description to explain why the change is necessary. The “what” is taken care of by the code changes. Details about a specific algorithm or complex changes should be documented in the code.
Comments¶
Single-line vs multi-line¶
Use
//for comments that fit on one line,/* */for longer comments:Avoid:
Doxygen documentation¶
Not every function needs documentation. Skip documentation for trivial getters/setters. Document functions when:
Behavior needs explanation
Arguments have specific requirements
Return values need clarification
For documented functions:
First line is a brief description with
@brieftagUse
@paramfor parameters,@returnfor return valuesUse backticks to reference function, variable, and parameter names
Use
@code{.c}for examplesFor Doxygen comments specifically, the first and last lines of multi-line documentation should be empty (unlike regular multi-line comments).