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>
216 lines
10 KiB
C
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
|