<div dir="ltr"><div dir="ltr"><br></div><br><div class="gmail_quote gmail_quote_container"><div dir="ltr" class="gmail_attr">On Wed, Jul 1, 2026 at 9:30 AM Jonathan Wakely <<a href="mailto:jwakely.gcc@gmail.com">jwakely.gcc@gmail.com</a>> wrote:<br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex"><div dir="auto"><div><br><br><div class="gmail_quote"><div dir="ltr" class="gmail_attr">On Wed, 1 Jul 2026, 07:45 Tomasz Kaminski, <<a href="mailto:tkaminsk@redhat.com" target="_blank">tkaminsk@redhat.com</a>> wrote:<br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex"><div dir="ltr"><div dir="ltr"><br></div><br><div class="gmail_quote"><div dir="ltr" class="gmail_attr">On Tue, Jun 30, 2026 at 7:48 PM Jonathan Wakely <<a href="mailto:jwakely@redhat.com" rel="noreferrer" target="_blank">jwakely@redhat.com</a>> wrote:<br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">Although the systemd docs say that /etc/localtime should be a symlink to<br>
one of the zoneinfo files, some systems make it a symlink to another<br>
path, where that second path is a symlink to a zoneinfo file (e.g. if<br>
/etc is mounted read-only then /etc/localtime can be a symlink to<br>
another symlink on a writable disk, so that the system timezone can be<br>
altered by re-pointing the symlink on the writable disk).<br>
<br>
In that case, using readlink would only tell us the location of the<br>
second symlink, not which zoneinfo file it points to. Therefore, we<br>
would not be able to extract a valid time zone name from the path, and<br>
chrono::current_zone() would fail.<br>
<br>
To support multiple symlinks we could recursively keep resolving<br>
symlinks with readlink until we reach a path from which we can extract a<br>
zone name. Alternatively, we can just use realpath to resolve all<br>
symlinks to a physical file (which is what HowardHinnant/date does).<br>
This means we only need one system call and don't need the extra<br>
complexity of calling readlink in a loop.<br>
<br>
The realpath system call also removes redunant slashes, so we can remove<br>
the code that did that manually.<br>
<br>
The possible downsides of this approach that I'm aware of are:<br>
<br>
- When /etc/localtime is a symlink to /invalid/Europe/London but that<br>
file doesn't exist. With the previous implementation we would have<br>
resolved that symlink to the zone "Europe/London" as long as that name<br>
is known to the current chrono::tzdb object. With this change, we<br>
won't get a valid zone name and current_zone() will fail. I'm not sure<br>
how realistic this case is. It might be plausible if libstdc++ is<br>
using the embedded static copy of tzdata.zi and there are no zoneinfo<br>
files on disk at all. In that case the system might still use<br>
/etc/localtime to name a zone, even though the symlink is dangling.<br>
We could fall back to filesystem::weakly_canonical for this case, but<br>
this patch leaves that for a future change, if it turns out to be<br>
needed by any users.<br>
<br>
- When /etc/localtime is a symlink to /usr/share/zoneinfo/Foo/Bar where<br>
"Foo/Bar" is a valid zone in the chrono::tzdb object, but the Bar file<br>
is another symlink to ./Baz where "Foo/Bar" is also a valid zone.<br>
With the previous implementation current_zone() would have returned<br>
the "Foo/Bar" zone. With this change it would return "Foo/Baz". I<br>
don't think it's realistic to have two zones which are distinct zones<br>
(not a Zone and a Link to it) but where one of them is defined on-disk<br>
using a symlink to the other.<br>
<br>
libstdc++-v3/ChangeLog:<br>
<br>
PR libstdc++/125467<br>
* src/c++20/tzdb.cc (tzdb::current_zone): Use realpath to<br>
resolve the /etc/localtime symlink instead of readlink.<br>
---<br>
<br>
v2: Make the type of 'str' always std::string_view. Check str !=<br>
"/etc/localtime" so that we don't bother trying to extract a zone name<br>
from the symlink target if it isn't even a symlink.<br>
<br>
Tested x86_64-linux.<br></blockquote><div>LGTM with very small suggestion. </div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">
<br>
libstdc++-v3/src/c++20/tzdb.cc | 76 +++++++++++++---------------------<br>
1 file changed, 28 insertions(+), 48 deletions(-)<br>
<br>
diff --git a/libstdc++-v3/src/c++20/tzdb.cc b/libstdc++-v3/src/c++20/tzdb.cc<br>
index 9e601fc176f3..c658e0c9cd37 100644<br>
--- a/libstdc++-v3/src/c++20/tzdb.cc<br>
+++ b/libstdc++-v3/src/c++20/tzdb.cc<br>
@@ -41,8 +41,13 @@<br>
# include <ext/concurrence.h> // __gnu_cxx::__mutex<br>
#endif<br>
<br>
-#if defined(_GLIBCXX_HAVE_READLINK) && defined(_GLIBCXX_HAVE_UNISTD_H)<br>
-# include <unistd.h> // readlink<br>
+#ifdef _GLIBCXX_HAVE_UNISTD_H<br>
+# include <unistd.h> // _XOPEN_VERSION<br>
+#endif<br>
+#if defined _GLIBCXX_USE_REALPATH && _XOPEN_VERSION >= 700<br>
+# include <stdlib.h> // malloc, free, realpath<br>
+#else<br>
+# include <filesystem> // filesystem::canonicalize<br>
#endif<br>
<br>
#ifdef _AIX<br>
@@ -2098,58 +2103,33 @@ constinit tzdb_list::_Node::NumLeapSeconds tzdb_list::_Node::num_leap_seconds;<br>
// to have a way to force a re-read.<br>
<br>
#if !defined(_AIX) && !defined(_GLIBCXX_HAVE_WINDOWS_H)<br>
-#if defined(_GLIBCXX_HAVE_READLINK) && defined(_GLIBCXX_HAVE_UNISTD_H)<br>
- string_view str;<br>
- char buf[128]; // strlen("../usr/share/zoneinfo/...") is usually < 55<br>
- string dynbuf;<br>
// /etc/localtime should be a symlink that ends with a zone name,<br>
// e.g. /etc/localtime -> /usr/share/zoneinfo/Europe/London<br>
// <a href="https://www.freedesktop.org/software/systemd/man/latest/localtime.html" rel="noreferrer noreferrer" target="_blank">https://www.freedesktop.org/software/systemd/man/latest/localtime.html</a><br>
// This should work on GNU/Linux, macOS, NetBSD, and OpenBSD.<br>
- // Some FreeBSD systems also use a symlink for /etc/localtime.<br>
- // Use readlink directly to avoid std::filesystem overhead.<br>
- if (auto n = ::readlink("/etc/localtime", buf, sizeof(buf)); n > 0)<br>
+ // Some FreeBSD systems also use a symlink for /etc/localtime (since 15.0).<br>
+<br>
+ // N.B. we do not support dangling symlinks here. If that becomes necessary<br>
+ // then after realpath fails we could fallback to using<br>
+ // filesystem::weakly_canonical(filesystem::read_symlink("etc/localtime")).<br>
+<br>
+#if defined _GLIBCXX_USE_REALPATH && _XOPEN_VERSION >= 700<br>
+ unique_ptr<char[], void(*)(void*)> cbuf{ nullptr, &::free };<br>
+ string_view str;<br></blockquote><div>This is very subjective and not necessary, but I would prefer this string_view to be</div><div>declared before #if here. I find it easier to follow that way.</div></div></div></blockquote></div></div><div dir="auto"><br></div><div dir="auto">The order I used means that the string view lifetime is shorter than the buffer lifetime so it can't dangle.</div></div></blockquote><div>OK.<br>You would need to observe that from the destructor of the object created before this #ifdef,</div><div>but it is some argument.</div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex"><div dir="auto"><div dir="auto"> </div></div></blockquote><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex"><div dir="auto"><div dir="auto"><br></div><div dir="auto"><div class="gmail_quote"><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex"><div dir="ltr"><div class="gmail_quote"><div> </div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">
+ // Use realpath directly to avoid std::filesystem overhead.<br>
+ // We use realpath not readlink to resolve multiple levels of symlinks.<br>
+ if (char* p = ::realpath("/etc/localtime", nullptr))<br>
{<br>
- if (static_cast<size_t>(n) < sizeof(buf))<br>
- str = string_view(buf, n);<br>
- else [[unlikely]]<br>
- {<br>
- // We read the symlink but it didn't fit in buf[], use dynbuf.<br>
- do<br>
- {<br>
- n *= 2;<br>
- dynbuf.__resize_and_overwrite(n, [](char* p, size_t len) {<br>
- auto n2 = ::readlink("/etc/localtime", p, len);<br>
- if (n2 == -1) // symlink removed or replaced by file?!<br>
- __throw_runtime_error("tzdb: error reading /etc/localtime");<br>
- const size_t r = n2;<br>
- return r < len ? r : 0;<br>
- });<br>
- }<br>
- while (dynbuf.empty());<br>
- str = dynbuf;<br>
- }<br>
+ cbuf.reset(p);<br>
+ str = p;<br>
}<br>
+#else<br>
+ string sbuf = std::filesystem::canonical("/etc/localtime").string();<br>
+ string_view str = sbuf;<br></blockquote><div>And this will become an assignment. </div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">
+#endif<br>
<br>
- if (!str.empty())<br>
+ if (!str.empty() && str != "/etc/localtime")<br>
{<br>
- // Remove any redundant slashes so we can match zone names.<br>
- // e.g. /usr/share/zoneinfo/Europe//London is a valid symlink,<br>
- // but won't match against "Europe/London".<br>
- if (auto pos = str.rfind("//"); pos != str.npos) [[unlikely]]<br>
- {<br>
- if (str.data() != dynbuf.data())<br>
- dynbuf = str;<br>
- string::size_type spos = pos;<br>
- do<br>
- {<br>
- dynbuf.erase(spos, 1);<br>
- spos = dynbuf.rfind("//", spos);<br>
- }<br>
- while (spos != dynbuf.npos);<br>
- str = dynbuf;<br>
- }<br>
-<br>
// Check the trailing components of the path against known zone names.<br>
// Valid IANA times zones can have one, two, or three parts, e.g.<br>
// "UTC", "Europe/London", and "America/Indiana/Indianapolis".<br>
@@ -2175,10 +2155,10 @@ constinit tzdb_list::_Node::NumLeapSeconds tzdb_list::_Node::num_leap_seconds;<br>
str.substr(pos + 1)))<br>
return tz;<br>
}<br>
-#endif<br>
+<br>
// Otherwise, look for a file naming the time zone.<br>
string_view files[] {<br>
- "/etc/timezone", // Debian derivates<br>
+ "/etc/timezone", // Debian derivates, non-systemd Gentoo<br>
"/var/db/zoneinfo", // FreeBSD<br>
};<br>
for (auto f : files)<br>
-- <br>
2.54.0<br>
<br>
</blockquote></div></div>
</blockquote></div></div></div>
</blockquote></div></div>