myxce/src/include/utils.h
Rongsong Shen 7ebcba6d49
feat(dirty-content-fingerprint): add --version-only composition with dirty_fingerprint
Adds xce_compose_version_ex() as a sibling of the existing, completely
untouched xce_compose_version(): when dirty_fingerprint is non-empty, it
strips a trailing -dirty from the commit field (anchored tail-slice
strcmp, not a substring search) and appends -<dirty_fingerprint> instead;
when empty/NULL, it delegates directly to the original function, so
output is structurally guaranteed byte-identical -- including the
Requirement 5.2 fallback case (dirty tree, fingerprint computation
failed) where commit_id's own pre-existing -dirty suffix already signals
"modified" with no new sentinel needed.

No main.c call sites touched -- consuming daemons (tasks 3.2/3.3) still
call the original xce_compose_version() and will switch to the new
function themselves.

Verified independently by review, full lib suite on both debug and ASAN
profiles: 301 tests, 0 failures, 0 ignored; suffix-anchoring, buffer
safety, and the empty-fingerprint delegation path all confirmed correct
by hand-tracing the implementation, not just trusting the test results.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-21 05:19:12 +00:00

216 lines
10 KiB
C

#if !defined(_UTILS_H_)
#define _UTILS_H_
#include "config.h"
#include <capnp_c.h>
#include <math.h>
#include <stdlib.h>
#include <string.h>
#include <tlog.h>
#include "controller.capnp.h"
#include "xce/errnox.h"
#include "xce/types.h"
#define CONTAINER_OF(P, T, F) ((T*)(((char*)(P)) - offsetof(T, F)))
#define MAKE_JOBID(cluster, apiserver, timestamp, tag, sequence) \
((((uint64_t)cluster) << CLUSTER_ID_SHIFT) \
| (((uint64_t)apiserver) << APISERVER_ID_SHIFT) \
| (((uint64_t)timestamp) << TIMESTAMP_SHIFT) \
| (((uint64_t)tag) << TAG_SHIFT) | (sequence))
#define EXTRACT_BITS(id, shift, bits) (((id) >> (shift)) & (UMAX_BITS(bits)))
#define EXTRACT_ID_TAG(id) EXTRACT_BITS(id, TAG_SHIFT, N_TAG_BITS)
#define EXTRACT_ID_CLUSTER(id) \
EXTRACT_BITS(id, CLUSTER_ID_SHIFT, N_CLUSTER_ID_BITS)
#define EXTRACT_ID_APISERVER(id) \
EXTRACT_BITS(id, APISERVER_ID_SHIFT, N_APISERVER_ID_BITS)
#define EXTRACT_ID_TIMESTAMP(id) \
EXTRACT_BITS(id, TIMESTAMP_SHIFT, N_TIMESTAMP_BITS)
#define EXTRACT_ID_SEQUENCE(id) EXTRACT_BITS(id, 0, N_SEQUENCE_BITS)
#define EXTRACT_ARRAY_JOB(id) \
((((id) >> SEQUENCE_HIGH_SHIFT) \
& UMAX_BITS(N_SEQUENCE_BITS - SEQUENCE_HIGH_SHIFT)) \
<< SEQUENCE_HIGH_SHIFT)
#define EXTRACT_ARRAY_BASEID(id) \
MAKE_JOBID(EXTRACT_ID_CLUSTER(id), EXTRACT_ID_APISERVER(id), \
EXTRACT_ID_TIMESTAMP(id), 0x0, EXTRACT_ARRAY_JOB(id))
#define EXTRACT_ARRAY_IDX(id) EXTRACT_BITS(id, 0, N_ARRAY_IDX_BITS)
#define ROUNDUP(v, bits) (((v) + UMAX_BITS(bits)) & ~UMAX_BITS(bits))
#define xce_use(x) (void)(x)
#define float_near_zero (0.000001)
#define float_equal(a, b) (fabs((a) - (b)) < float_near_zero)
#define STR(x) _STR(x)
#define _STR(x) #x
#define MK_VERSION(ver, commit) ver commit
/* SUPERSEDED for version-string composition (build-time-versioning task
* 3.1). MK_VERSION/STR (immediately above) cannot express an
* empty-value -> "unknown" fallback: an unset commit_id/build_time meson
* option makes STR(x) expand to an empty string literal ("", not a missing
* token), and a preprocessor macro has no way to branch on "is this string
* empty" at expansion time. xce_version_field()/xce_compose_version()
* (extern decls below, implementation + doc comments in utils.c) are the
* runtime replacement — they DO perform that substitution, and are what
* compose the version string everywhere it now matters. Use those, not
* MK_VERSION, for any new version-string composition.
*
* MK_VERSION/STR are kept in place here (not deleted) only because task 3.2
* — which will migrate controller/main.c's and scheduler/src/main.c's
* argp_program_version construction to xce_compose_version() — has not
* landed yet. Grep finding (2026-08-20, `grep -rn 'STR(\|MK_VERSION(' src/`,
* excluding NULLSTR/comment/definition matches): STR() has ZERO call sites
* anywhere in this codebase outside this exact MK_VERSION-based
* argp_program_version pattern — controller/main.c:23,
* scheduler/src/main.c:26, AND controller/testsuite/test.c:662 (the
* xce_test REPL tool). That third caller is NOT listed in task 3.2's
* boundary (`controller/main.c, scheduler/main.c` only, per tasks.md) —
* worth confirming whether it's in scope when 3.2 lands, or it will keep
* printing a commit-id-only, build-time-less version string after the
* other two are migrated. STR() itself is a generic single-argument
* stringification macro (not inherently version-specific), but per this
* grep it currently has no unrelated use, so it is not being kept around
* for any other live caller — only in service of the not-yet-migrated
* MK_VERSION call sites above. Once task 3.2 (and any update to
* controller/testsuite/test.c) lands and no caller of MK_VERSION remains,
* both MK_VERSION and this STR usage can be deleted outright — not done
* here, out of this task's boundary.
*
* NOTE FOR TASK 3.2 (or whoever migrates argp_program_version):
* xce_compose_version() is a REGULAR RUNTIME FUNCTION, not a constant
* expression — unlike MK_VERSION's pure string-literal concatenation, it
* CANNOT initialize a file-scope variable. `const char* argp_program_version
* = xce_compose_version(...)` at file scope will not compile (C file-scope
* initializers must be constant expressions). controller/main.c:22-23 and
* scheduler/src/main.c:25-26 currently declare argp_program_version exactly
* that way — a file-scope const initialized at compile time — so this is
* not a one-line macro-call swap. Instead: compute the version string at
* RUNTIME into a `static char buf[...]` (module scope or local to main()),
* via xce_compose_version(), early in main() and BEFORE argp_parse() runs
* (argp reads argp_program_version by pointer when --version/-V is
* processed) — then assign argp_program_version (no longer const-qualified
* at its declaration; assigned at runtime instead of initialized) to point
* at that buffer. See xce_compose_version()'s doc comment in utils.c for
* the buffer-sizing contract. */
#ifndef FREEUP
#define FREEUP(p) \
if ((p) != NULL) { \
free(p); \
(p) = NULL; \
}
#endif
#define EQUAL_STRING(s1, s2) \
(((s1) == (s2)) \
|| (((s1) != NULL) && ((s2) != NULL) && (strcmp((s1), (s2)) == 0)))
#define N_EL_ARRAY(arr) (sizeof(arr) / sizeof(arr[0]))
#define PTR_STEP(p, delta) ((void*)(((char*)p) + (delta)))
/* move the owner ship of pointer from src to dst */
#define MOVE_PTR(dst, src) \
do { \
(dst) = (src); \
(src) = NULL; \
} while (0)
#define NULLSTR(p) (((p) == NULL) || (strlen(p) == 0))
#define SET_ERROR(code) \
do { \
set_error((char*)__FILE__, (char*)__func__, __LINE__, (code)); \
} while (0)
#define TRACE(...) tlog_debug3(__VA_ARGS__)
#define INFO(...) tlog_info(__VA_ARGS__)
#define ERROR(...) tlog_error(__VA_ARGS__)
#define WARN(...) tlog_warn(__VA_ARGS__)
#define PERROR(s) xce_perror(s)
typedef struct {
char* file;
char* func;
int line;
int code;
} ectx_t;
extern int parse_argv(char*, char***);
extern void free_argv(char**, int);
extern int get_myname(char*, size_t);
extern int mylog_init(char*, char*, int, int);
extern void mylog_fini(void);
extern int mylog_setlevel(char*);
extern char* verbose_to_loglevel(int);
/* See the SUPERSEDED/NOTE comment above MK_VERSION for what these replace
* and why, and the constraint they impose on any file-scope
* argp_program_version-style global (xce_compose_version() is not a
* constant expression). */
extern const char* xce_version_field(const char*);
extern int xce_compose_version(char*, size_t, const char*, const char*,
const char*);
/* dirty-content-fingerprint task 3.1: --version-only sibling of
* xce_compose_version() above. Same composition (prefix + commit +
* " built " + build_time), plus one more optional field: when
* dirty_fingerprint is non-empty, a trailing "-dirty" on commit is
* stripped and "-<dirty_fingerprint>" is appended in its place, producing
* "<commit>-<dirty_fingerprint>". When dirty_fingerprint is NULL or empty,
* this function delegates straight to xce_compose_version() and produces
* byte-identical output -- commit_id's own computation and its existing
* -dirty suffix are never touched by this function. The startup-log
* composition call sites (controller/scheduler/worker main.c) must keep
* calling xce_compose_version() directly, never this function -- this is
* what keeps the startup log's format unchanged per Requirement 3.2 of the
* dirty-content-fingerprint spec. */
extern int xce_compose_version_ex(char*, size_t, const char*, const char*,
const char*, const char*);
extern void set_error(char*, char*, int, int);
extern ectx_t* get_ectx(void);
extern int xce_vprintf(const char*, va_list);
extern int xce_strerror_r(int, char*, size_t);
extern void xce_perror(const char*);
extern char* frame_symbol(int);
extern int copy_int_list(int**, int*, int);
extern int copy_strlist(xce_strlist_t*, xce_strlist_t);
extern void free_timewindow_config(xce_timewindow_config_t*);
extern xce_timewindow_config_t* dup_timewindow_config(xce_timewindow_config_t*);
extern void free_resource_thresholds(xce_res_threshold_t**, int);
extern xce_res_threshold_t** dup_resource_thresholds(xce_res_threshold_t**,
int);
extern void free_name_val_list(xce_name_val_t***, int*);
extern void free_strlist(xce_strlist_t*);
extern int fmt_jobid_ex(char*, size_t, uint64_t, char*, char*);
extern int fmt_jobid(char*, size_t, uint64_t);
extern int parse_jobid(uint64_t*, char*);
extern uint64_t array_jobid(uint64_t);
extern uint64_t array_idx(uint64_t);
extern void unblock_sig(int);
extern void block_sig(int);
extern uint32_t hash4(uint32_t, uint32_t, uint32_t, uint32_t);
extern uint32_t hash64(uint64_t);
extern uint32_t hash_string(const char*);
extern uint32_t hash_block(char*, size_t);
#endif