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/else uses 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:

  1. System headers (<linux/...>, <errno.h>, <stdlib.h>)

  2. External library headers (<bpfilter/...>)

  3. 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 0 on success, negative errno on failure (-ENOMEM, -EEXIST)

  • Cleanup functions return void and 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 information

  • bf_info(): informational messages

  • bf_warn(): warnings

  • bf_err(): errors

  • bf_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.

Comments

Single-line vs multi-line

Use // for comments that fit on one line, /* */ for longer comments:

// Single line comment

/* Multi-line comment with aligned asterisks
 * and no empty lines. Close on the last text line. */

Avoid:

/*
    Empty lines and misaligned asterisks.
*/

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 @brief tag

  • Use @param for parameters, @return for return values

  • Use backticks to reference function, variable, and parameter names

  • Use @code{.c} for examples

/**
 * @brief Create a new chain from the given parameters.
 *
 * The caller takes ownership of the returned chain and must free it
 * with `bf_chain_free()`.
 *
 * @param chain On success, points to the new chain. Unchanged on
 *        failure.
 * @param type Type of chain to create.
 * @return 0 on success, negative errno on failure.
 */
int bf_chain_new(struct bf_chain **chain, enum bf_chain_type type);

For Doxygen comments specifically, the first and last lines of multi-line documentation should be empty (unlike regular multi-line comments).

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.