chrono::current_zone() fails if /etc/localtime is a symlink to a zone
with three components, like "America/Indiana/Indianapolis", because we
only try to find "Indianapolis" and "Indiana/Indianapolis" but neither
of those exists.

We need to try up to three components to handle all valid cases, such as
"UTC", "America/Indianapolis", and "America/Indiana/Indianapolis".

Since two components is the most common case, we could consider trying
last[-2]/last[-1] first, then if that doesn't match trying only the last
component and the last three components, but this patch doesn't do that.

libstdc++-v3/ChangeLog:

        PR libstdc++/122567
        * src/c++20/tzdb.cc (tzdb::current_zone): Loop over trailing
        components of /etc/localtime path for up to three components.
---

Tested x86_64-linux.

There's no new testcase for this because it requires root access to
change /etc/localtime. I've verified it locally.

 libstdc++-v3/src/c++20/tzdb.cc | 26 ++++++++++++++++++--------
 1 file changed, 18 insertions(+), 8 deletions(-)

diff --git a/libstdc++-v3/src/c++20/tzdb.cc b/libstdc++-v3/src/c++20/tzdb.cc
index 53441880ae6e..44ba3ea890f2 100644
--- a/libstdc++-v3/src/c++20/tzdb.cc
+++ b/libstdc++-v3/src/c++20/tzdb.cc
@@ -1896,14 +1896,24 @@ namespace std::chrono
        auto first = path.begin(), last = path.end();
        if (std::distance(first, last) > 2)
          {
-           --last;
-           string name = last->string();
-           if (auto tz = do_locate_zone(this->zones, this->links, name))
-             return tz;
-           --last;
-           name = last->string() + '/' + name;
-           if (auto tz = do_locate_zone(this->zones, this->links, name))
-             return tz;
+           string name, part;
+           // Check trailing components of the path against known zone names.
+           // Valid zones can have one, two, or three components, e.g.
+           // "UTC", "Europe/London", "America/Indiana/Indianapolis"
+           for (int i = 0; i < 3 && last != first; ++i)
+             {
+               --last;
+               part = last->string();
+               if (name.empty())
+                 name = std::move(part);
+               else
+                 {
+                   part += '/';
+                   name = std::move(part) + std::move(name);
+                 }
+               if (auto tz = do_locate_zone(this->zones, this->links, name))
+                 return tz;
+             }
          }
       }
 #endif
-- 
2.52.0

Reply via email to