Stack overflows on user input:
- bug-1702 jobrun -m: host segment strncpy'd into a 256-byte buffer
unbounded (ASAN stack-buffer-overflow); now cmdutil_split_host_tasks()
refuses an over-long host.
- bug-1703 jobsub -n: min strncpy'd into a 64-byte buffer; now
cmdutil_parse_ntasks() parses in place, 1 <= min <= max.
- resupdate's 16-entry vals[] had no bound either; a 17th -n overflowed.
Wrong or silent results:
- bug-1574 resupdate type/model: set_resval() had no string case, left the
entry's name uninitialized, and crashed the encoder. Moved to
resval_typeresolve.c as resval_fill(), which fills every type.
- bug-1705 jobsub -x was overwritten (and leaked) by the default exdata;
cmdutil_default_exdata() only fills an unset one. jobsub's
execuser/exechome/execcwd copies leaked on every call.
- bug-1707 a trailing -n without -v was dropped silently; resupdate also
returned 0 after its RPC failed and "succeeded" with nothing to send.
- bug-1708 jobkill -s SIGKILL became signal 0 and was sent.
- bug-1709 jobinfo exited 0 with no -j, a malformed -j, or no hits.
- bug-1710 hello -c 0 sent nothing and reported success.
- bug-1712 a duplicate -v was reported as "no res name found".
- bug-1777 workerlist showed join-time maxmem/maxswap/maxtmp, not the
effective values (hosts.yaml overrides) the reply already carries in
resvals_to_sched and the scheduler uses.
- bug-1793 join's maxswap/maxtmp defaults were 1 KB / 2 KB in bytes; now
1 GiB / 2 GiB, and -m/-s/-t no longer overflow atoi() above 2 GB.
Error reporting and leaks:
- bug-1796 RPC failures printed perror() of a stale errno ("Connection
refused" / "Invalid argument" / "Inappropriate ioctl for device" for
the same failure). cmdutil_rpc_error() prints the RPC's own code
(E_XCE_* included) at all 19 sites.
- bug-1797 qctrl leaked the queue name on the RPC-error path; repeated
-q/-h leaked the earlier copy.
New src/harness/cmdutil.c (+ cmdutil_unit_test, 14 cases); 9 new
test_test_cli.c cases, 8 RED on the old xce_test; new resval_fill,
worker_effective_attr and join-default tests (join ones RED on the old
join_worker_args.c). Harness suite 6/6 (asan), harness_unit_test 141/0.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
131 lines
4.6 KiB
C
131 lines
4.6 KiB
C
/* joblist_display.c -- xce-test-cli-ux task 2.5 remediation (bug-1711 fixes)
|
|
*
|
|
* See joblist_display.h for the full rationale. Verbatim extraction of
|
|
* test.c's worker_status_str()/job_state_str()/format_job_time() -- no
|
|
* behavior change from the pre-extraction versions.
|
|
*/
|
|
#include "joblist_display.h"
|
|
|
|
#include <stdio.h> /* snprintf */
|
|
#include <string.h> /* strcmp */
|
|
|
|
#include "xce/ctrlstatus_fmt.h" /* ctrlstatus_format_time_ms */
|
|
#include "xce/types.h" /* XCE_WORKER_*, JOB_STAT_* bit definitions */
|
|
|
|
char* worker_status_str(uint32_t status)
|
|
{
|
|
/* Admin-set CLOSED takes priority over the dynamic UNAVAIL bit: the
|
|
* config-upgrade-snapshot soak (task 8.6) needs to assert a host's
|
|
* administratively-set status specifically, and a host closed via
|
|
* hostctrl that later disconnects (e.g. its xce_test `worker`/`join`
|
|
* connection exits after the script ends) would otherwise print only
|
|
* "UNAVAIL", masking the admin bit this check exists to verify. */
|
|
if ((status & XCE_WORKER_CLOSED) != 0) {
|
|
return "CLOSED";
|
|
} else if ((status & XCE_WORKER_UNAVAIL) != 0) {
|
|
return "UNAVAIL";
|
|
} else if ((status & XCE_WORKER_LOCKEDU) != 0) {
|
|
return "LOCKU";
|
|
} else if ((status & XCE_WORKER_LOCKEDW) != 0) {
|
|
return "LOCKW";
|
|
} else if ((status & XCE_WORKER_BUSY) != 0) {
|
|
return "BUSY";
|
|
} else if (status == 0) {
|
|
return "OK";
|
|
}
|
|
|
|
return "UNKNOWN";
|
|
}
|
|
|
|
char* job_state_str(int state)
|
|
{
|
|
/* bug-1711 (Requirement 11.7): raw-state fallback mirroring
|
|
* print_jobinfo's existing state_raw guard against the same ambiguity
|
|
* (see print_jobinfo's comment above its "state_raw: 0x%x" print) -- a
|
|
* job that is simultaneously RUN and EXIT (e.g. a worker-reported FAIL
|
|
* received without a prior FINISH; apply_job_fail_transition ORs in
|
|
* EXIT|PERR without clearing RUN) would otherwise silently print as
|
|
* plain "RUN" via the if/else-if priority ordering below, hiding the
|
|
* EXIT bit. Unlike print_jobinfo (which always has room for a separate
|
|
* state_raw line), job_state_str's result is a single cmd_joblist table
|
|
* cell, so the raw state is folded into the same string only for this
|
|
* specific ambiguous case. */
|
|
static char ambiguous_buf[32];
|
|
|
|
if ((state & JOB_STAT_PEND) != 0) {
|
|
if ((state & JOB_STAT_PSUSP) != 0) {
|
|
return "PSUSP";
|
|
}
|
|
|
|
return "PEND";
|
|
} else if ((state & JOB_STAT_RUN) != 0) {
|
|
if ((state & JOB_STAT_USUSP) != 0) {
|
|
return "USUSP";
|
|
} else if ((state & JOB_STAT_SSUSP) != 0) {
|
|
return "SSUSP";
|
|
} else if ((state & JOB_STAT_EXIT) != 0) {
|
|
snprintf(ambiguous_buf, sizeof(ambiguous_buf), "RUN(0x%x)", state);
|
|
return ambiguous_buf;
|
|
}
|
|
return "RUN";
|
|
} else if ((state & JOB_STAT_EXIT) != 0) {
|
|
return "EXIT";
|
|
} else if ((state & JOB_STAT_DONE) != 0) {
|
|
return "DONE";
|
|
} else if ((state & JOB_STAT_ERR) != 0) {
|
|
return "ERROR";
|
|
}
|
|
|
|
return "UNKNOWN";
|
|
}
|
|
|
|
/* bug-1711 (Requirement 11.5): format a job time_t field (epoch seconds) using
|
|
* ctrlstatus_fmt.h's ctrlstatus_format_time_ms human-readable formatter
|
|
* (which takes epoch milliseconds), instead of printing the raw epoch-seconds
|
|
* value directly as cmd_joblist did before. A job time of 0 means the
|
|
* corresponding event has not happened yet (e.g. an unstarted job's
|
|
* start_time/finish_time) -- print "-" rather than a misleading 1970-01-01
|
|
* epoch date. */
|
|
const char* format_job_time(time_t t, char* buf, size_t bufsize)
|
|
{
|
|
if (t == 0) {
|
|
return "-";
|
|
}
|
|
|
|
if (ctrlstatus_format_time_ms((uint64_t)t * 1000u, buf, bufsize) == NULL) {
|
|
return "-";
|
|
}
|
|
|
|
return buf;
|
|
}
|
|
|
|
double worker_effective_attr(const xce_worker_t* w, const char* resname,
|
|
double attr_value)
|
|
{
|
|
if (w == NULL) {
|
|
return attr_value;
|
|
}
|
|
for (int i = 0; i < w->nresvals_to_sched; i++) {
|
|
const xce_name_val_t* nv = w->resvals_to_sched[i];
|
|
|
|
if ((nv == NULL) || (nv->name == NULL) || (nv->val == NULL)
|
|
|| (strcmp(nv->name, resname) != 0)) {
|
|
continue;
|
|
}
|
|
switch (nv->val->which) {
|
|
case Value_u_fvalue:
|
|
return nv->val->u.fvalue;
|
|
case Value_u_ivalue32:
|
|
return (double)nv->val->u.ivalue32;
|
|
case Value_u_uvalue32:
|
|
return (double)nv->val->u.uvalue32;
|
|
case Value_u_ivalue64:
|
|
return (double)nv->val->u.ivalue64;
|
|
case Value_u_uvalue64:
|
|
return (double)nv->val->u.uvalue64;
|
|
default:
|
|
return attr_value; /* not numeric */
|
|
}
|
|
}
|
|
return attr_value;
|
|
}
|