Skip to content

Commit 5c9fc74

Browse files
committed
rtapi: explain why realtime scheduling is unavailable
The fallback-to-POSIX warning said only "SCHED_FIFO not permitted" which left users guessing among missing caps, stock kernel, or rlimits. Now print the sched_setscheduler errno, the effective cap_sys_nice and cap_ipc_lock state via libcap, and mention LINUXCNC_FORCE_REALTIME as a testing-only override. Addresses the diagnostic-output request in issue #3928. While here, switch detect_preempt_rt and has_setuid_root from __attribute__((unused)) to static inline so the unused-function suppression matches the wider codebase style.
1 parent f7dcb46 commit 5c9fc74

3 files changed

Lines changed: 60 additions & 19 deletions

File tree

src/rtapi/Submakefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ $(call TOOBJSDEPS, $(RTAPI_APP_SRCS)): EXTRAFLAGS += -DSIM \
4444
-UULAPI -DRTAPI -pthread
4545
../bin/rtapi_app: $(call TOOBJS, $(RTAPI_APP_SRCS))
4646
$(ECHO) Linking $(notdir $@)
47-
$(Q)$(CXX) -rdynamic -o $@ $^ $(LIBDL) -pthread -lrt -lfmt $(LIBUDEV_LIBS) -ldl $(LDFLAGS)
47+
$(Q)$(CXX) -rdynamic -o $@ $^ $(LIBDL) -pthread -lrt -lfmt $(LIBUDEV_LIBS) -ldl -lcap $(LDFLAGS)
4848
TARGETS += ../bin/rtapi_app
4949
endif
5050

src/rtapi/uspace_common.h

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -359,18 +359,14 @@ int rtapi_is_kernelspace() { return 0; }
359359
// for diagnostic warning at startup; callers must not gate behavior on
360360
// the kernel string, since SCHED_FIFO on a PREEMPT_DYNAMIC kernel is still
361361
// useful (better than SCHED_OTHER, worse than PREEMPT_RT).
362-
// Marked unused because uspace_common.h is also included from ULAPI TUs
363-
// that do not reference it.
364-
__attribute__((unused))
365-
static int detect_preempt_rt() {
362+
static inline int detect_preempt_rt() {
366363
struct utsname u;
367364
if(uname(&u) < 0) return 0;
368365
return strcasestr(u.version, "PREEMPT RT") != 0
369366
|| strcasestr(u.version, "PREEMPT_RT") != 0;
370367
}
371368
#else
372-
__attribute__((unused))
373-
static int detect_preempt_rt() {
369+
static inline int detect_preempt_rt() {
374370
return 0;
375371
}
376372
#endif
@@ -382,11 +378,7 @@ static int detect_preempt_rt() {
382378
// with udev rules + a 'xenomai'/'evl' group; @hdiethelm has a follow-up
383379
// planned. Until then, an unprivileged user on a Xenomai kernel cannot
384380
// claim the Xenomai backend, and falls back to the SCHED_FIFO probe.
385-
// Marked unused because the helper is called only when one of the
386-
// USPACE_RTAI / USPACE_XENOMAI / USPACE_XENOMAI_EVL macros is defined,
387-
// and the header is included from ULAPI TUs that define none of them.
388-
__attribute__((unused))
389-
static int has_setuid_root() {
381+
static inline int has_setuid_root() {
390382
return geteuid() == 0;
391383
}
392384

@@ -427,6 +419,11 @@ static int detect_xenomai_evl() {
427419
}
428420
#endif
429421

422+
// errno from the most recent sched_setscheduler(SCHED_FIFO) probe. Zero
423+
// when the probe succeeded or has not run yet. Read via
424+
// rtapi_sched_fifo_errno() from diagnostic code.
425+
static int rtapi_sched_fifo_last_errno = 0;
426+
430427
// Success-probe for realtime scheduling: briefly try to set SCHED_FIFO on
431428
// the calling thread and restore the previous policy. Succeeds when the
432429
// process holds CAP_SYS_NICE (file caps or setuid root) or has a matching
@@ -436,20 +433,31 @@ static int detect_xenomai_evl() {
436433
static int can_set_sched_fifo(void) {
437434
struct sched_param old_param, probe_param;
438435
int old_policy = sched_getscheduler(0);
439-
if(old_policy < 0) return 0;
440-
if(sched_getparam(0, &old_param) < 0) return 0;
436+
if(old_policy < 0) {
437+
rtapi_sched_fifo_last_errno = errno;
438+
return 0;
439+
}
440+
if(sched_getparam(0, &old_param) < 0) {
441+
rtapi_sched_fifo_last_errno = errno;
442+
return 0;
443+
}
441444

442445
memset(&probe_param, 0, sizeof(probe_param));
443446
probe_param.sched_priority = sched_get_priority_min(SCHED_FIFO);
444-
if(sched_setscheduler(0, SCHED_FIFO, &probe_param) < 0)
447+
if(sched_setscheduler(0, SCHED_FIFO, &probe_param) < 0) {
448+
rtapi_sched_fifo_last_errno = errno;
445449
return 0;
450+
}
446451

447452
// Best-effort restore; if this fails we are still on SCHED_FIFO at
448453
// minimum priority, which is no worse than where we started.
449454
sched_setscheduler(0, old_policy, &old_param);
455+
rtapi_sched_fifo_last_errno = 0;
450456
return 1;
451457
}
452458

459+
static inline int rtapi_sched_fifo_errno(void) { return rtapi_sched_fifo_last_errno; }
460+
453461
// rtapi_is_realtime() reports whether this process can actually run
454462
// realtime code. This matches the convention used by JACK, PipeWire,
455463
// rtkit, Xenomai, and Klipper: surface the observed capability, not

src/rtapi/uspace_rtapi_main.cc

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
#ifdef __linux__
4747
#include <malloc.h>
4848
#include <sys/prctl.h>
49+
#include <sys/capability.h>
4950
#endif
5051
#ifdef __FreeBSD__
5152
#include <pthread_np.h>
@@ -974,15 +975,47 @@ static RtapiApp *makeDllApp(const std::string &dllName, int policy) {
974975
return result;
975976
}
976977

978+
// Diagnostic helper: report cap_effective state for a single capability.
979+
// Returns "yes", "no", or "unknown" if libcap could not introspect.
980+
#ifdef __linux__
981+
static const char *cap_effective_str(cap_t caps, cap_value_t cap) {
982+
if (!caps) return "unknown";
983+
cap_flag_value_t v;
984+
if (cap_get_flag(caps, cap, CAP_EFFECTIVE, &v) != 0) return "unknown";
985+
return v == CAP_SET ? "yes" : "no";
986+
}
987+
#endif
988+
977989
static RtapiApp *makeApp() {
978990
RtapiApp *app;
979991
bool rt_ok = rtapi_is_realtime();
980992
if (!rt_ok) {
993+
// Surface the actual reason so the user does not have to guess
994+
// between "no caps", "stock kernel", or "wrong rlimits" (issue
995+
// #3928). errno comes from the SCHED_FIFO probe in
996+
// can_set_sched_fifo(); cap state comes from libcap.
997+
int sched_err = rtapi_sched_fifo_errno();
998+
#ifdef __linux__
999+
cap_t caps = cap_get_proc();
1000+
const char *nice_s = cap_effective_str(caps, CAP_SYS_NICE);
1001+
const char *lock_s = cap_effective_str(caps, CAP_IPC_LOCK);
1002+
#else
1003+
const char *nice_s = "unknown";
1004+
const char *lock_s = "unknown";
1005+
#endif
9811006
rtapi_print_msg(RTAPI_MSG_ERR,
982-
"Note: SCHED_FIFO not permitted for this process, "
983-
"falling back to POSIX non-realtime. "
984-
"Run 'sudo make setcap' (preferred) or 'sudo make setuid' "
985-
"on rtapi_app to enable realtime scheduling.\n");
1007+
"Note: realtime scheduling unavailable "
1008+
"(sched_setscheduler SCHED_FIFO: %s).\n"
1009+
" Process capabilities: cap_sys_nice=%s cap_ipc_lock=%s.\n"
1010+
" Falling back to POSIX non-realtime.\n"
1011+
" Fix: 'sudo make setcap' (preferred) or 'sudo make setuid' "
1012+
"on rtapi_app.\n"
1013+
" Override (testing only): set LINUXCNC_FORCE_REALTIME=1.\n",
1014+
sched_err ? strerror(sched_err) : "denied",
1015+
nice_s, lock_s);
1016+
#ifdef __linux__
1017+
if (caps) cap_free(caps);
1018+
#endif
9861019
}
9871020
if (!rt_ok || harden_rt() < 0) {
9881021
app = makeDllApp("liblinuxcnc-uspace-posix.so.0", SCHED_OTHER);

0 commit comments

Comments
 (0)