<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>