Skip to content

fix: measure sizeof(struct timespec) instead of reading __TIMESIZE - #20

Merged
diwic merged 2 commits into
diwic:masterfrom
b0bbywan:fix/time64-probe-measures-timespec
Jul 29, 2026
Merged

fix: measure sizeof(struct timespec) instead of reading __TIMESIZE#20
diwic merged 2 commits into
diwic:masterfrom
b0bbywan:fix/time64-probe-measures-timespec

Conversation

@b0bbywan

Copy link
Copy Markdown
Contributor

Context

While cross-compiling an audio application for 32-bit Raspberry Pi OS (armhf), I hit the segfault described in diwic/alsa-rs#158 and RustAudio/cpal#1285: libasound writes a 16-byte struct timespec while alsa-sys re-exports the 8-byte libc::timespec.

To be upfront: I'm a developer, but not a Rust developer, and I used Claude to track this down and to write the patch. The measurements and verification below were run for real in the two Raspberry Pi OS images, and I'm happy to rework anything.

Problem

The probe added in #19 reads __TIMESIZE, but that macro reports the port's default time_t width. Debian's time64 transition works differently: it switches existing 32-bit targets to a 64-bit time_t by passing _TIME_BITS=64 in the toolchain, which leaves __TIMESIZE at 32. So on transitioned targets such as armhf and i386 the cfg is not emitted, which is exactly where it is needed — forcing it by hand is what made @d3d9's test pass in #158.

Measured in the two Raspberry Pi OS armhf images, by poisoning a 16-byte buffer and passing it to snd_pcm_status_get_htstamp on the null PCM:

bookworm trixie
libasound package libasound2 1.2.8-1+rpt1 libasound2t64 1.2.14-1+rpt1
bytes written 8 16
sizeof(struct timespec) 8 16
__TIMESIZE 32 32
__USE_TIME_BITS64 undefined defined

Change

Measure sizeof(struct timespec) directly by compiling a _Static_assert, instead of inferring the width from a macro. The snippet is compiled but never run, so cross-compiling against the target's sysroot keeps working. A control compile first checks that <time.h> yields a usable timespec, so a broken toolchain produces a warning and the safe 32-bit fallback instead of being misread as a pre-transition target (the direction that corrupts memory).

Checking __USE_TIME_BITS64 would also work on both images and is a smaller change if you prefer it; I went with the size because it holds regardless of which mechanism set the width.

Verification

Same container, same libasound, trixie armhf:

with this change       cargo:rustc-cfg=alsa_sys_time64
build.rs from master   no rustc-cfg emitted

On bookworm armhf the cfg is still (correctly) not emitted, since both libc::timespec and the library's timespec are 8 bytes there. 64-bit glibc and musl targets return early at the existing guard, as before.

One gap: I could not test riscv32, a port born with a 64-bit time_t where __TIMESIZE is already 64. The assertion should compile there and keep the cfg on, but that path is reasoned, not measured.

__TIMESIZE reports the port's default time_t width, so it still reads
32 on targets that gained a 64-bit time_t through Debian's time64
transition (_TIME_BITS=64 in the toolchain, e.g. armhf on trixie).
On those targets the probe from diwic#19 does not emit alsa_sys_time64,
libc::timespec stays 8 bytes, and libasound writes 16-byte timespecs
past it (diwic/alsa-rs#158, RustAudio/cpal#1285).

Measure the size directly instead: try to compile a _Static_assert
that sizeof(struct timespec) == 16. The snippet is compiled but never
run, so cross compiling against the target's sysroot keeps working.
A control compile first checks that <time.h> yields a usable timespec,
so a broken toolchain produces a warning and the safe 32-bit fallback
instead of being misread as a pre-transition target.

Verified on Raspberry Pi OS armhf: with this change the cfg is emitted
on trixie (16-byte timespec) and still not on bookworm (8-byte
timespec). 64-bit glibc and musl targets return early as before.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@diwic

diwic commented Jul 26, 2026

Copy link
Copy Markdown
Owner

@roderickvd Can you review this patch?

@roderickvd

Copy link
Copy Markdown
Contributor

Nice! This should definitely be more robust than __TIMESIZE/__USE_TIME_BITS64 probing.

Reverting on what I had introduced myself, I think we should fall back to 16-byte alsa_sys_time64 if any part of the probe fails. Best case, it's the right choice. Worst case, timespec values won't make sense but prevent a segfault. Unless we want to segfault to bail out early. I'm not sure.

@diwic

diwic commented Jul 28, 2026

Copy link
Copy Markdown
Owner

@b0bbywan Thanks for the patch! How about this for even more robustness:

  • Actually test against snd_htimestamp_t (and then include asoundlib.h) instead of time.h.
  • For now; if the "either 8 or 16 bytes" test fails, we bail out completely and say we can't compile it at all. And if we get any fallout, go from there and see what we can do (instead of guessing).

…rors

Review feedback from diwic#20:

- Measure sizeof(snd_htimestamp_t) from <alsa/asoundlib.h> instead of
  struct timespec from <time.h>: it is the exact type libasound writes
  through. The probe now runs after pkg-config so it can reuse the
  reported include paths. The alsa/ prefix is required since alsa-lib
  1.2.5, when alsa.pc stopped adding -I${includedir}/alsa.

- If the control snippet does not compile, fail the build with an
  explicit error instead of assuming a 32-bit time_t: guessing 8 can
  segfault and guessing 16 yields garbage timestamps, so there is no
  safe fallback to pick silently.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@b0bbywan

Copy link
Copy Markdown
Contributor Author

Thanks both! I pushed a commit implementing @diwic's two suggestions.

Probe snd_htimestamp_t via the ALSA headers. The snippets now measure sizeof(snd_htimestamp_t) — the exact type libasound writes through, and the probe runs after the pkg-config call so it compiles against the include paths pkg-config reports.

One detail: I had to use #include <alsa/asoundlib.h> rather than <asoundlib.h>, because alsa.pc stopped adding -I${includedir}/alsa in alsa-lib 1.2.5, so the bare form no longer resolves on current distros (verified: pkg-config --cflags alsa is empty on both Raspberry Pi OS images and on Fedora).

Fail the build if the control snippet doesn't compile. No more warning + fallback: the build now stops with an error that explains the stakes and includes the failing compiler invocation. I think this also answers @roderickvd's question, instead of choosing between a possible segfault (assume 8 bytes) and silently nonsensical timestamps (assume 16), the failure is loud and diagnosable, and we can revisit if real-world fallout shows up. Since the snippet now needs the ALSA headers, a failing control compile means the crate build was doomed anyway (pkg-config or bindgen would trip over the same problem), so the hard failure should be near-zero risk for configurations that worked before.

Re-verified in the same Raspberry Pi OS containers as the original patch:

bookworm armhf trixie armhf
control snippet (8 or 16) compiles compiles
sizeof(snd_htimestamp_t) 8 16
result no cfg cargo:rustc-cfg=alsa_sys_time64

A full cargo build inside the trixie armhf image emits the cfg, and forcing a broken toolchain (CC=/bin/false) produces the new build error with the message and the failing command line.

@diwic
diwic merged commit 6f04ac1 into diwic:master Jul 29, 2026
@diwic

diwic commented Jul 29, 2026

Copy link
Copy Markdown
Owner

@b0bbywan @roderickvd @d3d9 So I merged it (thanks!) and reworded the panic message slightly.
Next thing would be to release 0.6.1 and yank 0.6.0, I presume?

@roderickvd

Copy link
Copy Markdown
Contributor

A new release would be great. Yanking the previous one is up to you, not entirely necessary for me (nor was it entirely broken).

Thanks all!

@b0bbywan

b0bbywan commented Jul 30, 2026

Copy link
Copy Markdown
Contributor Author

Thx guys! I think 0.6.1 release is enough, no need to yank 6.0.0 in my opinion, since it's an edge case and previous version also have the same bug.

@b0bbywan
b0bbywan deleted the fix/time64-probe-measures-timespec branch July 30, 2026 09:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants