Skip to content

Fix off64_t portability on musl-based systems - #6779

Draft
blowekamp wants to merge 1 commit into
InsightSoftwareConsortium:mainfrom
blowekamp:fix/gdcm-musl-off64t-portability
Draft

Fix off64_t portability on musl-based systems#6779
blowekamp wants to merge 1 commit into
InsightSoftwareConsortium:mainfrom
blowekamp:fix/gdcm-musl-off64t-portability

Conversation

@blowekamp

Copy link
Copy Markdown
Member

Problem

Builds on musl-based Linux distributions (like Alpine Linux) were failing with:

error: 'off64_t' has not been declared

The issue occurs in gdcmFileStreamer.cxx where the code defines off64_t for Windows and BSD systems, but doesn't handle musl-based systems which don't define off64_t by default.

Solution

Modified the platform detection condition to check for non-glibc systems. This covers musl and other libc implementations by defining off64_t as off_t, which is the standard portability approach.

The specific change checks for !defined(__GLIBC__) to detect non-glibc systems, complementing existing checks for specific BSD and Apple platforms.

Testing

This fix should allow musl-based builds to compile without the off64_t error while maintaining compatibility with existing platforms (Windows, glibc-based Linux, BSD variants, etc.).

Fixes SimpleITK build failures on Alpine Linux and other musl-based distributions.

@github-actions github-actions Bot added the area:ThirdParty Issues affecting the ThirdParty module label Aug 21, 2026
@hjmjohnson

Copy link
Copy Markdown
Member

FYI: I'm looking in to a more robust fix in GDCM as an upstream fix.

The complex conditionals tend to be fragile, and I think a better solution could be achieved.

I am not opposed to this workaround, but I don't think it is the correct permanent fix.

@blowekamp

Copy link
Copy Markdown
Member Author

FYI: I'm looking in to a more robust fix in GDCM as an upstream fix.

The complex conditionals tend to be fragile, and I think a better solution could be achieved.

I am not opposed to this workaround, but I don't think it is the correct permanent fix.

This was a quick fix to get a build working. GDCM as several places where system depended defines are checked, that could be problematic or incorrectly defined with muslinux. It appears this defined approach was preferred over try compiles in these places, and appears to be a source of trouble and changes needed.

hjmjohnson added a commit to BRAINSia/GDCM that referenced this pull request Aug 21, 2026
gdcmFileStreamer.cxx used off64_t and hand-maintained a list of platforms
on which it must be #defined to off_t. musl-based distributions (Alpine)
were not on that list and fail with "off64_t has not been declared": musl
gates its LFS64 names on _LARGEFILE64_SOURCE and, unlike glibc, does not
imply that from _GNU_SOURCE. musl is also removing those aliases, so
enabling _LARGEFILE64_SOURCE is not a durable answer.

Extending the list is not a fix either. off64_t is a typedef, so the
preprocessor cannot test for it; every guard is a proxy for "which libc is
this", which is why the list grew one platform at a time. The glibc build
worked only because the C++ driver implicitly defines _GNU_SOURCE, which
turns on __USE_LARGEFILE64; the same block does not compile as C.

Separately, the _FILE_OFFSET_BITS 64 definition in this file was a no-op.
It sat after the gdcm*.h include block, which transitively pulls
<features.h>, so it was ignored. On 32-bit glibc off_t therefore stayed
32-bit and FSeeko/FTello/FTruncate silently narrowed, breaking writes past
2 GB.

Move the definition ahead of all includes and replace off64_t with a
GDCM-owned gdcm::offset_t: off_t on POSIX, int64_t on the Windows CRT.
This deletes the __APPLE__ || __FreeBSD__ || __OpenBSD__ || __NetBSD__ ||
__EMSCRIPTEN__ list without adding anything. A static_assert makes a
32-bit off_t a build error rather than silent truncation.

offset_t does not appear in gdcmFileStreamer.h, so the change is contained
to one translation unit and alters no public API.

Verified: Alpine/musl reproduces the reported error before the change and
compiles clean after; on 32-bit glibc the static_assert fires with the old
define placement and passes with the new one; macOS arm64 full build at
-Wall -Wextra with all six TestFileStreamer tests passing and no change to
the pre-existing failure set.

Reported downstream as InsightSoftwareConsortium/ITK#6779.

Assisted-by: Claude Code -- cross-platform instrumentation and patch drafting
hjmjohnson added a commit to BRAINSia/GDCM that referenced this pull request Aug 21, 2026
gdcmFileStreamer.cxx used off64_t and hand-maintained a list of platforms
on which it must be #defined to off_t. musl-based distributions (Alpine)
were not on that list and fail with "off64_t has not been declared": musl
gates its LFS64 names on _LARGEFILE64_SOURCE and, unlike glibc, does not
imply that from _GNU_SOURCE. musl is also removing those aliases, so
enabling _LARGEFILE64_SOURCE is not a durable answer.

Extending the list is not a fix either. off64_t is a typedef, so the
preprocessor cannot test for it; every guard is a proxy for "which libc is
this", which is why the list grew one platform at a time. The glibc build
worked only because the C++ driver implicitly defines _GNU_SOURCE, which
turns on __USE_LARGEFILE64; the same block does not compile as C.

Separately, the _FILE_OFFSET_BITS 64 definition in this file was a no-op.
It sat after the gdcm*.h include block, which transitively pulls
<features.h>, so it was ignored. On 32-bit glibc off_t therefore stayed
32-bit and FSeeko/FTello/FTruncate silently narrowed, breaking writes past
2 GB.

Move the definition ahead of all includes and replace off64_t with a
GDCM-owned gdcm::offset_t: off_t on POSIX, int64_t on the Windows CRT.
This deletes the __APPLE__ || __FreeBSD__ || __OpenBSD__ || __NetBSD__ ||
__EMSCRIPTEN__ list without adding anything. A static_assert makes a
32-bit off_t a build error rather than silent truncation.

offset_t does not appear in gdcmFileStreamer.h, so the change is contained
to one translation unit and alters no public API.

Verified: Alpine/musl reproduces the reported error before the change and
compiles clean after; on 32-bit glibc the static_assert fires with the old
define placement and passes with the new one; macOS arm64 full build at
-Wall -Wextra with all six TestFileStreamer tests passing and no change to
the pre-existing failure set.

Reported downstream as InsightSoftwareConsortium/ITK#6779.

Assisted-by: Claude Code -- cross-platform instrumentation and patch drafting
@hjmjohnson

Copy link
Copy Markdown
Member

I've opened an upstream fix for this at malaterre/GDCM#228, so ITK can eventually drop the local patch instead of carrying a platform list that grows one distro at a time.

To be clear up front: this PR works. I built it on Alpine and it resolves the error. Nothing below is a reason to hold it if you want Alpine unblocked now — that's your call, and option (b) at the bottom is entirely reasonable.

Why upstream took a different shape

off64_t is a typedef, so the preprocessor can never test for it — every guard is necessarily a proxy for "which libc is this", which is why the list keeps growing. The upstream patch stops spelling the type off64_t altogether: a GDCM-owned gdcm::offset_t that is off_t on POSIX and int64_t on the Microsoft CRT. That deletes the __APPLE__ || __FreeBSD__ || __OpenBSD__ || __NetBSD__ || __EMSCRIPTEN__ list without adding anything, and the one remaining #if tests "is this the Windows CRT", which is a real API difference rather than a libc proxy.

Worth noting the glibc path currently works somewhat by accident: off64_t resolves only because the C++ driver implicitly defines _GNU_SOURCE, which turns on __USE_LARGEFILE64. The same block does not compile as C.

I checked whether !defined(__GLIBC__) had an include-ordering hazard, since __GLIBC__ is only defined once a libc header has been read. It does not — the guard sits after <cstdio>, so __GLIBC__ is already visible there. I verified the exact condition from this PR on musl, glibc x86_64 and glibc i386; all three behave as intended.

A separate latent bug in the same file, which ITK's vendored copy has verbatim

While investigating I found that #define _FILE_OFFSET_BITS 64 in this file is a no-op. It sits after the nine gdcm*.h includes, which transitively pull <features.h>; by then the definition is ignored. I confirmed the vendored copy at Modules/ThirdParty/GDCM/src/gdcm/.../gdcmFileStreamer.cxx is byte-identical in this region.

Consequence on 32-bit glibc (armhf, i386, 32-bit MIPS): off_t stays 32-bit, off64_t is 64-bit, and FSeeko/FTello/FTruncate silently narrow — writes past 2 GB are broken and fail quietly. Measured on i386 Debian against the real GDCM source:

sizeof(off_t)
current define placement 4
define hoisted above all includes 8

The same probe with this PR applied gives sizeof(off64_t)=8, sizeof(off_t)=4 on i386 — so #6779 fixes the Alpine build but leaves the 32-bit truncation in place. Not a criticism of the PR, which doesn't claim to address it; just flagging that the two issues live in the same five lines.

Upstream #228 hoists the define and adds static_assert(sizeof(offset_t) >= 8, ...) so a 32-bit off_t becomes a build error rather than silent truncation. It only matters to ITK if ITK ships 32-bit Linux builds.

Two ways forward, and I don't have a stake in which:

  • (a) hold this for the upstream fix — clean, but blocks Alpine users on Malaterre's review latency;
  • (b) merge this now to unblock Alpine, and replace it with the upstream patch at the next GDCM vendor bump. ITK vendors 3.2.8 while upstream master is 3.3.0, so a bump isn't imminent — this seems like the pragmatic choice to me.

Either way it's worth recording the local change per ITK's ThirdParty patching convention so the next UpdateFromUpstream.sh doesn't silently drop it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:ThirdParty Issues affecting the ThirdParty module

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants