zic: fix PostgreSQL build failure on filesystems without hard link support

Started by Vladlen Popolitov24 days ago10 messageshackers
Beta feature

Hackorum builds and tests every patch posted to the lists, not only commitfest submissions. This is Hackorum's own CI rather than the PostgreSQL project's, and it is still under testing - please report anything that looks wrong.

won't retrysuccessCI history

This thread has been committed, so CI has stopped here. Anything below is the last result it produced.

You can run a PostgreSQL built from this patch straight from Docker, with no checkout and no build:

docker run --rm -p 5432:5432 ghcr.io/hackorum-dev/postgres-patch:t253253
psql -h localhost -U postgres

Built from patchset v8 (message #8), July 31, 2026 at 06:49 PM.

Every patchset is also pushed to a branch of our PostgreSQL fork, so you can check out the same tree CI built. Without a PostgreSQL checkout:

git clone --branch t253253_8 https://github.com/hackorum-dev/postgres.git

In a checkout you already have, add the fork once:

git remote add hackorum https://github.com/hackorum-dev/postgres.git

then, for this patchset and every later one:

git fetch hackorum t253253_8 && git checkout t253253_8

Patchset v8 (message #8) is on t253253_8

Jump to latest
#1Vladlen Popolitov
v.popolitov@postgrespro.ru

Hi!

Problem:

PostgreSQL fails to build on filesystems that do not support hard links
(e.g., exFAT, FAT32, some network filesystems). The build process stops
with the following error during timezone data generation:

Can't link <target> to <linkname>: Invalid argument

This regression was introduced by commit
aeb07c55fab5c17a600b77ffcdc3b71425d6a8e7
("Sync our copy of the timezone library with IANA release tzcode2026b"),
which updated zic to a new version from upstream tzcode. The previous
version of zic worked correctly on such filesystems by falling back to
copying files when hard links were not supported.

The issue affects users building PostgreSQL on Windows using exFAT-
formatted drives, as well as other platforms where the build directory
resides on filesystems lacking hard link support.

Root Cause Analysis:

In the new dolink() function introduced by the upstream update, the
error
handling logic for hard link creation only treats EXDEV and ENOTSUP as
recoverable errors:

if (link_errno == EXDEV || link_errno == ENOTSUP)
break; /* proceed to fallback */

All other errno values are considered fatal and cause zic to abort.

However, the link() system call returns EINVAL on filesystems that do
not
support hard links (including exFAT, FAT32, and some network
filesystems).
Since EINVAL is not handled as a recoverable error, the new zic fails
where the old one succeeded.

Solution:

Proposed patch adds EINVAL to the list of errno values that
trigger the fallback path (symbolic link or file copy),
restoring the pre-2026b behavior.

Testing:

- Verified on exFAT partition: zic successfully completes without
errors.
- Verified on NTFS and ext4: no regression, hard links work as expected.

--
Best regards,

Vladlen Popolitov.

Attachments:

t253253_1
0001-zic-fix-PostgreSQL-build-failure-on-filesystems-with.patchtext/x-diff; name=0001-zic-fix-PostgreSQL-build-failure-on-filesystems-with.patchDownload+2-3
#2Tom Lane
tgl@sss.pgh.pa.us
In reply to: Vladlen Popolitov (#1)
Re: zic: fix PostgreSQL build failure on filesystems without hard link support

Vladlen Popolitov <v.popolitov@postgrespro.ru> writes:

PostgreSQL fails to build on filesystems that do not support hard links
(e.g., exFAT, FAT32, some network filesystems). The build process stops
with the following error during timezone data generation:
Can't link <target> to <linkname>: Invalid argument

I've bounced this to tzdb upstream to see just how they want to fix it
[1]: https://lists.iana.org/hyperkitty/list/tz@iana.org/message/O5Q2I3GLZIF6GEFZ3MDWRZKG4ENPJVFQ/

regards, tom lane

[1]: https://lists.iana.org/hyperkitty/list/tz@iana.org/message/O5Q2I3GLZIF6GEFZ3MDWRZKG4ENPJVFQ/

#3Tom Lane
tgl@sss.pgh.pa.us
In reply to: Vladlen Popolitov (#1)
Re: zic: fix PostgreSQL build failure on filesystems without hard link support

Vladlen Popolitov <v.popolitov@postgrespro.ru> writes:

PostgreSQL fails to build on filesystems that do not support hard links
(e.g., exFAT, FAT32, some network filesystems). The build process stops
with the following error during timezone data generation:
Can't link <target> to <linkname>: Invalid argument

A question from the tzdb guys:

* Does the 'symlink' system call have the same problem on this MS-Windows platform? Should zic worry about them as well? (But if so, why didn't zic complain to your users about symlink?)

Also, it occurs to me to wonder if we're doing this to ourselves.
Specifically, it looks like src/port/win32error.c's _dosmaperr
will map ERROR_NOT_SUPPORTED to EINVAL, due to the lack of any
table entry for ERROR_NOT_SUPPORTED. Can you confirm which
underlying Windows error code is being returned?

regards, tom lane

#4Vladlen Popolitov
v.popolitov@postgrespro.ru
In reply to: Tom Lane (#3)
Re: zic: fix PostgreSQL build failure on filesystems without hard link support

Tom Lane писал(а) 2026-07-31 02:57:

Vladlen Popolitov <v.popolitov@postgrespro.ru> writes:

PostgreSQL fails to build on filesystems that do not support hard
links
(e.g., exFAT, FAT32, some network filesystems). The build process
stops
with the following error during timezone data generation:
Can't link <target> to <linkname>: Invalid argument

A question from the tzdb guys:

* Does the 'symlink' system call have the same problem on this
MS-Windows platform? Should zic worry about them as well? (But if so,
why didn't zic complain to your users about symlink?)

Also, it occurs to me to wonder if we're doing this to ourselves.
Specifically, it looks like src/port/win32error.c's _dosmaperr
will map ERROR_NOT_SUPPORTED to EINVAL, due to the lack of any
table entry for ERROR_NOT_SUPPORTED. Can you confirm which
underlying Windows error code is being returned?

regards, tom lane

I have confirmed that the Windows error code returned by
CreateHardLinkA() on exFAT is ERROR_INVALID_FUNCTION (numeric value 1).
This code has no mapping in PostgreSQL's _dosmaperr(), which
falls back to returning EINVAL (errno == 22). This is why zic
receives EINVAL instead of ENOTSUP.

One more observation: Microsoft's documentation for CreateHardLinkA()
does
not list possible error codes. For most Windows API functions, the
documentation explicitly lists all error codes that the function can
return.
But for CreateHardLinkA(), this information is missing. Therefore, it is
unclear whether different error codes are returned for different failure
scenarios on Windows. This is different from the POSIX link() function,
which has a clearly defined set of error codes for each specific failure
case.
Also I see in man: Linux returns EPERM for unsupported filesystems,
FreeBSD returns EOPNOTSUPP. Neither returns ENOTSUP.

Regarding symlink(): PostgreSQL maps symlink to pgsymlink(), which is
implemented using NTFS junction points on Windows. Junction points work
only with directories, not files. Since zic needs to create symbolic
references to timezone data files, this mechanism is unsuitable, and
zic correctly excludes symlink support using #ifdef HAVE_SYMLINK.

--
Best regards,

Vladlen Popolitov.

#5Tom Lane
tgl@sss.pgh.pa.us
In reply to: Vladlen Popolitov (#4)
Re: zic: fix PostgreSQL build failure on filesystems without hard link support

Vladlen Popolitov <v.popolitov@postgrespro.ru> writes:

Tom Lane писал(а) 2026-07-31 02:57:

Also, it occurs to me to wonder if we're doing this to ourselves.
Specifically, it looks like src/port/win32error.c's _dosmaperr
will map ERROR_NOT_SUPPORTED to EINVAL, due to the lack of any
table entry for ERROR_NOT_SUPPORTED. Can you confirm which
underlying Windows error code is being returned?

I have confirmed that the Windows error code returned by
CreateHardLinkA() on exFAT is ERROR_INVALID_FUNCTION (numeric value 1).
This code has no mapping in PostgreSQL's _dosmaperr(), which
falls back to returning EINVAL (errno == 22). This is why zic
receives EINVAL instead of ENOTSUP.

Interesting. I wonder if it'd be sane to put in an explicit mapping
for ERROR_INVALID_FUNCTION, although I'm not quite sure whether to
prefer ENOTSUP or EOPNOTSUPP. (If so, I'd be inclined to also add a
mapping for ERROR_NOT_SUPPORTED, but apparently that's not relevant to
the immediate problem.)

Also I see in man: Linux returns EPERM for unsupported filesystems,
FreeBSD returns EOPNOTSUPP. Neither returns ENOTSUP.

Oooh. I didn't experiment on either, but I concur with your reading
of their man pages. Also, NetBSD's man page says the same as FreeBSD,
and I quickly verified on NetBSD 10 that EOPNOTSUPP (45) is different
from ENOTSUP (86), unlike the situation on Linux. So tzcode's
expectation of ENOTSUP is pretty widely broken already.

Regarding symlink(): PostgreSQL maps symlink to pgsymlink(), which is
implemented using NTFS junction points on Windows. Junction points work
only with directories, not files. Since zic needs to create symbolic
references to timezone data files, this mechanism is unsuitable, and
zic correctly excludes symlink support using #ifdef HAVE_SYMLINK.

Right, so that part of it is not a problem.

But this is a mess :-(. We're not doing the best possible job in
_dosmaperr, but it seems tzcode's expectations need some adjustment
anyway, and that side should probably get sorted before we decide
what to do in _dosmaperr. I'll go consult further with them.

regards, tom lane

#6Vladlen Popolitov
v.popolitov@postgrespro.ru
In reply to: Tom Lane (#5)
Re: zic: fix PostgreSQL build failure on filesystems without hard link support

Tom Lane писал(а) 2026-07-31 17:08:

Vladlen Popolitov <v.popolitov@postgrespro.ru> writes:

Tom Lane писал(а) 2026-07-31 02:57:

Also, it occurs to me to wonder if we're doing this to ourselves.
Specifically, it looks like src/port/win32error.c's _dosmaperr
will map ERROR_NOT_SUPPORTED to EINVAL, due to the lack of any
table entry for ERROR_NOT_SUPPORTED. Can you confirm which
underlying Windows error code is being returned?

I have confirmed that the Windows error code returned by
CreateHardLinkA() on exFAT is ERROR_INVALID_FUNCTION (numeric value
1).
This code has no mapping in PostgreSQL's _dosmaperr(), which
falls back to returning EINVAL (errno == 22). This is why zic
receives EINVAL instead of ENOTSUP.

Interesting. I wonder if it'd be sane to put in an explicit mapping
for ERROR_INVALID_FUNCTION, although I'm not quite sure whether to
prefer ENOTSUP or EOPNOTSUPP. (If so, I'd be inclined to also add a
mapping for ERROR_NOT_SUPPORTED, but apparently that's not relevant to
the immediate problem.)

We could try other approach - avoid changing zic and _dosmaperr()
and fix link() like this in src/port/win32link.c:

if (CreateHardLinkA(dst, src, NULL) == 0)
{
int returncode;

returncode = GetLastError();

if (returncode == ERROR_INVALID_FUNCTION)
errno = ENOTSUP;
else
dosmaperr(returncode);
return -1;
}
It will not affect other places where link() is called, and resolve
zic issue.
--
Best regards,

Vladlen Popolitov.

#7Tom Lane
tgl@sss.pgh.pa.us
In reply to: Vladlen Popolitov (#6)
Re: zic: fix PostgreSQL build failure on filesystems without hard link support

Vladlen Popolitov <v.popolitov@postgrespro.ru> writes:

Tom Lane писал(а) 2026-07-31 17:08:

Interesting. I wonder if it'd be sane to put in an explicit mapping
for ERROR_INVALID_FUNCTION, although I'm not quite sure whether to
prefer ENOTSUP or EOPNOTSUPP.

We could try other approach - avoid changing zic and _dosmaperr()
and fix link() like this in src/port/win32link.c:

if (returncode == ERROR_INVALID_FUNCTION)
errno = ENOTSUP;
else
dosmaperr(returncode);

Yeah, that sounds like the solution with the smallest blast radius.
We know that ERROR_INVALID_FUNCTION means this for link(), but
it's hardly clear that that applies across-the-board; it seems
like EINVAL is probably the best translation in other cases.

Another advantage is that we don't have to sync it with changes in
zic.c: this will still do what we want after Eggert's changes of
today[1]https://lists.iana.org/hyperkitty/list/tz@iana.org/thread/O5Q2I3GLZIF6GEFZ3MDWRZKG4ENPJVFQ/ arrive, whereas other answers might not.

I'll go make it so in a bit. Thanks for the report!

regards, tom lane

[1]: https://lists.iana.org/hyperkitty/list/tz@iana.org/thread/O5Q2I3GLZIF6GEFZ3MDWRZKG4ENPJVFQ/

#8Vladlen Popolitov
v.popolitov@postgrespro.ru
In reply to: Tom Lane (#7)
Re: zic: fix PostgreSQL build failure on filesystems without hard link support

Tom Lane писал(а) 2026-07-31 20:07:

Vladlen Popolitov <v.popolitov@postgrespro.ru> writes:

Tom Lane писал(а) 2026-07-31 17:08:

Interesting. I wonder if it'd be sane to put in an explicit mapping
for ERROR_INVALID_FUNCTION, although I'm not quite sure whether to
prefer ENOTSUP or EOPNOTSUPP.

We could try other approach - avoid changing zic and _dosmaperr()
and fix link() like this in src/port/win32link.c:

if (returncode == ERROR_INVALID_FUNCTION)
errno = ENOTSUP;
else
dosmaperr(returncode);

Yeah, that sounds like the solution with the smallest blast radius.
We know that ERROR_INVALID_FUNCTION means this for link(), but
it's hardly clear that that applies across-the-board; it seems
like EINVAL is probably the best translation in other cases.

Another advantage is that we don't have to sync it with changes in
zic.c: this will still do what we want after Eggert's changes of
today[1] arrive, whereas other answers might not.

I'll go make it so in a bit. Thanks for the report!

I have already created and tested a new patch based on this idea.

It fixes the link() error handling. The patch also fixes dolink()
on Linux and FreeBSD, since these platforms are affected as well.
I hope this can be fixed in upstream tzcode. Otherwise, we can
apply the patch only to our PostgreSQL code.

ENOTSUP is the constant returned on macOS, where this code
works without problems.

I also verified that PostgreSQL build on Ubuntu with an exFAT
drive fails with EPERM (Operation not permitted).

This patch fixes the code for Linux and the *BSD systems.
I tested it on exFAT.

It is worth noting that exFAT is a modern filesystem supported
by all major operating systems. It is commonly used as a portable
filesystem for exchanging data between drives. Supporting it
would be beneficial.

--
Best regards,

Vladlen Popolitov.

Attachments:

t253253_8
v2-0001-zic-fix-PostgreSQL-build-failure-on-filesystems-w.patchtext/x-diff; name=v2-0001-zic-fix-PostgreSQL-build-failure-on-filesystems-w.patchDownload+23-4
#9Tom Lane
tgl@sss.pgh.pa.us
In reply to: Vladlen Popolitov (#8)
Re: zic: fix PostgreSQL build failure on filesystems without hard link support

Vladlen Popolitov <v.popolitov@postgrespro.ru> writes:

Tom Lane писал(а) 2026-07-31 20:07:

Yeah, that sounds like the solution with the smallest blast radius.
I'll go make it so in a bit. Thanks for the report!

I have already created and tested a new patch based on this idea.

Oh, I'd already pushed an equivalent fix in win32link.c before
seeing this message.

It fixes the link() error handling. The patch also fixes dolink()
on Linux and FreeBSD, since these platforms are affected as well.
I hope this can be fixed in upstream tzcode.

Eggert already committed something, as per the tz@iana.org thread.
I don't want to take a local patch that doesn't match his, and I'm
not really in a big hurry to worry about this on Linux/*BSD anyway.
I think there are few people installing zoneinfo trees on
non-hard-link filesystems on those platforms, else he'd have gotten
pushback sooner. (I didn't research their git history, but I'm
thinking this error behavior has probably been in tzcode for awhile.)
There are even fewer people doing so with Postgres' copy of zic,
because I imagine just about everyone on those platforms uses
--with-system-tzdata instead of a private, often out-of-date zoneinfo
tree. So I'm content to let this go for non-Windows until the next
time we sync our copy of tzcode.

regards, tom lane

#10Vladlen Popolitov
v.popolitov@postgrespro.ru
In reply to: Tom Lane (#9)
Re: zic: fix PostgreSQL build failure on filesystems without hard link support

Tom Lane писал(а) 2026-07-31 22:03:

Vladlen Popolitov <v.popolitov@postgrespro.ru> writes:

Tom Lane писал(а) 2026-07-31 20:07:

Yeah, that sounds like the solution with the smallest blast radius.
I'll go make it so in a bit. Thanks for the report!

I have already created and tested a new patch based on this idea.

Oh, I'd already pushed an equivalent fix in win32link.c before
seeing this message.

It fixes the link() error handling. The patch also fixes dolink()
on Linux and FreeBSD, since these platforms are affected as well.
I hope this can be fixed in upstream tzcode.

Eggert already committed something, as per the tz@iana.org thread.
I don't want to take a local patch that doesn't match his, and I'm
not really in a big hurry to worry about this on Linux/*BSD anyway.
I think there are few people installing zoneinfo trees on
non-hard-link filesystems on those platforms, else he'd have gotten
pushback sooner. (I didn't research their git history, but I'm
thinking this error behavior has probably been in tzcode for awhile.)
There are even fewer people doing so with Postgres' copy of zic,
because I imagine just about everyone on those platforms uses
--with-system-tzdata instead of a private, often out-of-date zoneinfo
tree. So I'm content to let this go for non-Windows until the next
time we sync our copy of tzcode.

Tom,

Thank you for the quick fix. I'm glad this is resolved.

This issue came up unexpectedly while I was working with other
patches on Windows. I was using an exFAT drive for the build,
and the build stopped with the link() error. The previous
version of zic handled this correctly because it had slightly
different logic for fallback.

By the way, I am also working on another patch related to link
on Windows (pgsymlink() which uses junction points). In
the PostgreSQL code, there is an incorrect constant used
for buffer length (to short). This can cause failures not
only on servers but also during builds and tests, because
Meson tends to create very long directory names. As a result,
one of the build tests starts failing.

There is a discussion about this issue in the hackers list,
and another participant has confirmed the failure:
/messages/by-id/ac3f262fdc3613acfb88176972938fd1@postgrespro.ru

Best regards,
Vladlen