Inspiration

The most common first question about a new platform is "can I bring my existing code?" For QNX, the answer is usually yes, but you only find out how one compile error at a time. Every Linux-ism turns into a build-fail, search, patch, rebuild loop: epoll, a glibc-only header, a missing -lsocket. Some breakages are subtle and specific to QNX 8. SA_RESTART doesn't exist. <limits.h> deliberately leaves IOV_MAX undefined. An empty #define _XOPEN_SOURCE hits an #error. Even code that already supports QNX 7 breaks, because the syspage layout changed.

We wanted the tool we would have liked on day one of a port: one command that lists every blocker up front, explains it with the QNX equivalent, fixes the mechanical ones, then builds and runs the result on QNX without hardware.

What it does

qnx-port-doctor is a command-line tool with five subcommands:

  • scan walks a C/C++ tree and matches 45 rules in five families: Linux-only headers and APIs (epoll, inotify, prctl, eventfd, signalfd, timerfd, linux/*), glibc-isms, Linux /proc and sysfs paths, link-flag differences (-lrt/-ldl; QNX's libsocket/libregex), and QNX 8 quirks plus QNX 7 → 8 migration issues. Each finding comes with a concrete QNX fix.
  • Context-aware: a naive grep drowns you in false positives, because portable projects already wrap their Linux code. The scanner understands #ifdef __linux__ / HAVE_* guards, files only compiled under Automake if or CMake platform branches, and per-platform sources (osdep-linux.c, os390-syscalls.h). Those findings become info with an explanation. QNX quirks are still reported in code that will compile on QNX. On tmux this turns 30+ raw hits into exactly the 8 lines where the QNX build actually fails.
  • fix writes a reviewable unified diff (patch -p1-ready, idempotent) for six auto-fixable rules. Every edit is valid on all platforms: guarded fallbacks (#ifndef SA_RESTART), version-qualified feature macros, spelled-out strptime formats, and the QNX 8 syspage rename.
  • build cross-compiles a single file, a plain Makefile, a CMake project (it generates the QNX toolchain file) or an Autotools project (--autotools sets the x86_64-pc-nto-qnx8.0.0 / aarch64-unknown-nto-qnx8.0.0 host, QNX libraries, pkg-config sysroot and an optional compat header).
  • run boots the QNX 8 QEMU quickstart image headless, copies the binary (plus extra files or stdin) in over ssh, runs it and returns its output and exit code. That makes "does it work on QNX?" one command, scriptable in CI.

Reports come as text, Markdown, JSON or SARIF 2.1.0, so findings show up as GitHub code-scanning alerts. Rules live in a YAML file, and teams can add their own.

How we built it

  • Pure Python 3 (one dependency, PyYAML), MIT-licensed, 20 unit tests.
  • The rule engine is regex rules over source and build files. A small preprocessor-aware pass tracks #if/#elif/#else/#endif and positive and negated conditions, and a build-system pass parses Automake conditionals, PLATFORM_DEPENDENT source lists and CMake if(CMAKE_SYSTEM_NAME …) blocks. Rules carry a guardable flag: Linux-only APIs are harmless inside a Linux guard, but QNX quirks like SA_RESTART matter wherever the code is compiled for QNX.
  • Fixers are small, conservative, line-level transformations. Their output is checked with patch -p1 and re-scanned to prove idempotence.
  • build/run wrap the QNX SDP 8.0 toolchain (qcc) and the official QEMU quickstart image. run uses QEMU user networking with an ssh port forward and -snapshot, so every run starts from a clean disk.
  • Every rule that came out of a real port was validated on QNX: we built the code with qcc, ran it in QEMU, and confirmed the behaviour. For example, a tiny C probe showed that QNX strptime("%FT%T") returns NULL.

Real-world results

All built with QNX SDP 8.0 for x86_64 and run on QNX 8 in QEMU:

Project Build system Blockers found → fixed Result on QNX 8
jq 1.8.1 Autotools empty _XOPEN_SOURCE (auto-fixed) jq's own suite: 509/522 (remaining: module fixtures not copied, one real strptime difference)
libevent 2.1.12 Autotools SA_RESTART (auto-fixed) static library used by tmux
tmux 3.5a Autotools SA_RESTART ×4, IOV_MAX ×4 (auto-fixed), -lsocket -lregex sessions, split panes and capture all work
cJSON 1.7.18 CMake none 18/18 test suites pass
libuv 1.49.2 CMake QNX 7-era syspage API (new_cpuinfo auto-fixed, qtime->nsec manual) 24/26 selected tests pass

jq, cJSON and libuv also cross-build for aarch64le. jq (509/522) and cJSON (18/18) give the same test results on QNX 8 for Arm in QEMU, and run picks the target from the binary's ELF header.

Challenges we ran into

  • False positives were the real enemy. The first version flagged dozens of lines in tmux and libevent that never compile on QNX. Making the scanner understand preprocessor guards and build-system conditionals (Automake, CMake, per-platform file naming) took most of the iteration. So did deciding which rules a guard should not silence. Our libevent regression test catches exactly that mistake.
  • QNX 8 is not QNX 7. libuv ships a QNX backend, yet it doesn't compile on QNX 8: new_cpuinfo and qtime->nsec are gone. We added a QNX 7 → 8 migration rule family.
  • Running QNX headless in a container. The quickstart image crashes very early under nested KVM ("Too early for per-cpu data"), so run falls back to software emulation (about 30 s boot). A truncated image unpack showed up as a "corrupted" /data filesystem, which took some digging to track down.
  • Toolchain drift. CMake 4 rejects many older CMakeLists.txt files, so build sets CMAKE_POLICY_VERSION_MINIMUM for you.

Accomplishments that we're proud of

  • Five well-known open-source projects ported and actually running on QNX 8, each verified with its own tests where available.
  • On tmux, libevent and jq the scanner's warnings and errors match exactly the lines a QNX build fails on, with zero false alarms at warning level.
  • fix patches small enough to send upstream.
  • A codelab-style tutorial ("Port your first open-source project to QNX 8") that a new QNX developer can follow end to end.

What we learned

  • The QNX differences that break most ports are few and learnable. The table at the end of our tutorial covers the majority of what we hit.
  • POSIX leaves room for interpretation (IOV_MAX, SA_RESTART, strptime), and QNX makes different, valid choices than glibc.
  • Context matters more than rule count: one well-placed guard check removes more noise than ten new rules add.

What's next for qnx-port-doctor

  • Real Arm hardware (Raspberry Pi 4) as a run target, plus KVM-accelerated runs.
  • Meson support, plus compile-database (compile_commands.json) mode for exact include paths.
  • More QNX 7 → 8 migration rules, and fixers for socket and link flags in CMake.
  • A GitHub Action wrapping scan -f sarif, and an upstream patch submission for libuv's QNX 8 support.
  • A rule-contribution guide so the QNX community can grow the database.

Credits

Demo video narration: Kokoro-82M by hexgrad (Apache-2.0), voice af_heart, synthesized locally. Music: Spiral Maps of Light, original track by Aleksej Sg.

Built With

  • autotools
  • cmake
  • python
  • qemu
  • qnx
  • sarif
  • yaml
Share this project:

Updates

Submission history