| Message ID | 20260109173604.2600880-1-jwakely@redhat.com |
|---|---|
| State | New |
| Headers |
Return-Path: <gcc-patches-bounces~patchwork=sourceware.org@gcc.gnu.org> X-Original-To: patchwork@sourceware.org Delivered-To: patchwork@sourceware.org Received: from vm01.sourceware.org (localhost [127.0.0.1]) by sourceware.org (Postfix) with ESMTP id 7E5BE4BA2E2C for <patchwork@sourceware.org>; Fri, 9 Jan 2026 17:37:17 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 7E5BE4BA2E2C Authentication-Results: sourceware.org; dkim=pass (1024-bit key, unprotected) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256 header.s=mimecast20190719 header.b=DxnQT4B8 X-Original-To: gcc-patches@gcc.gnu.org Delivered-To: gcc-patches@gcc.gnu.org Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.129.124]) by sourceware.org (Postfix) with ESMTP id 1E1D04BA2E06 for <gcc-patches@gcc.gnu.org>; Fri, 9 Jan 2026 17:36:20 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org 1E1D04BA2E06 Authentication-Results: sourceware.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com Authentication-Results: sourceware.org; spf=pass smtp.mailfrom=redhat.com ARC-Filter: OpenARC Filter v1.0.0 sourceware.org 1E1D04BA2E06 Authentication-Results: server2.sourceware.org; arc=none smtp.remote-ip=170.10.129.124 ARC-Seal: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1767980180; cv=none; b=czCMY6t7PwR3Cl/I8a6XeB56GveS190khRxRiD8/3h/lggUqbMi6wV17qwa8IZ9+nbpbdAmansBZfs5cfr/mhVNXFv3ZlNjFdAXfPBEkgMgoVt7+DVFmP8R+HrRADScFXqcYHmz9JMQx2ipNEZ6IbDHDxdkoozVRQk99iHZDoq0= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1767980180; c=relaxed/simple; bh=HoQp7zb0wVd5UwPoZnjuea5nG86izE/eLchbjSHXmZk=; h=DKIM-Signature:From:To:Subject:Date:Message-ID:MIME-Version; b=WOqPkTT5yzoqDuHBq+xq0S1SFE9QbciVq6n+wj40IN3OLVdnriJcth0YNS2ybKStmMpb4NyYOaYl0EZKDLs8sUESQVBPctLqhv/Eo/+ABvO6k5U03qRu9J6ZKfpHwXhFOcKguUBfdSBbqFQ7ncgXvTRd0UjB+6EReoAkuxaySMw= ARC-Authentication-Results: i=1; server2.sourceware.org DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 1E1D04BA2E06 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1767980179; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding; bh=FpnjJgPtkGOEZHVtG+gieWsUm7ejlNi39/yj8X7xLh8=; b=DxnQT4B8wX9jQvXskEFrFjqNj4aE+tvBEUnqJ220NqpoQ+fh12Fh7leYzEqM0AD2fKjW88 xCAPdsgG+oRaQJp9zOyWGCJeKCzXInZkVAN3lggXiURG0MD46fmg5arNf6c63UGKBaK/KR XevHAc4ygPWMg4m6Kp/7tJVR59SxAXI= Received: from mx-prod-mc-08.mail-002.prod.us-west-2.aws.redhat.com (ec2-35-165-154-97.us-west-2.compute.amazonaws.com [35.165.154.97]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-637-i-IvrbfDMxSq4HmjTSMNdA-1; Fri, 09 Jan 2026 12:36:15 -0500 X-MC-Unique: i-IvrbfDMxSq4HmjTSMNdA-1 X-Mimecast-MFC-AGG-ID: i-IvrbfDMxSq4HmjTSMNdA_1767980174 Received: from mx-prod-int-08.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-08.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.111]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-08.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 02C55180AE8B; Fri, 9 Jan 2026 17:36:07 +0000 (UTC) Received: from zen.kayari.org (unknown [10.42.28.2]) by mx-prod-int-08.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP id 23CF4180066A; Fri, 9 Jan 2026 17:36:05 +0000 (UTC) From: Jonathan Wakely <jwakely@redhat.com> To: gcc-patches@gcc.gnu.org, libstdc++@gcc.gnu.org Subject: [PATCH] libstdc++: Fix chrono::current_zone() for three component names [PR122567] Date: Fri, 9 Jan 2026 17:35:17 +0000 Message-ID: <20260109173604.2600880-1-jwakely@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 3.4.1 on 10.30.177.111 X-Mimecast-Spam-Score: 0 X-Mimecast-MFC-PROC-ID: vEkTVW38LaRWepR204wDUlZSA_SKacgsfCpLK-F33pE_1767980174 X-Mimecast-Originator: redhat.com Content-Type: text/plain Content-Transfer-Encoding: 8bit X-Spam-Status: No, score=-12.3 required=5.0 tests=BAYES_00, DKIMWL_WL_HIGH, DKIM_SIGNED, DKIM_VALID, DKIM_VALID_AU, DKIM_VALID_EF, GIT_PATCH_0, RCVD_IN_DNSWL_BLOCKED, RCVD_IN_MSPIKE_H2, RCVD_IN_VALIDITY_RPBL_BLOCKED, RCVD_IN_VALIDITY_SAFE_BLOCKED, SPF_HELO_PASS, SPF_NONE, TXREP, URIBL_BLOCKED autolearn=unavailable autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on sourceware.org X-BeenThere: gcc-patches@gcc.gnu.org X-Mailman-Version: 2.1.30 Precedence: list List-Id: Gcc-patches mailing list <gcc-patches.gcc.gnu.org> List-Unsubscribe: <https://gcc.gnu.org/mailman/options/gcc-patches>, <mailto:gcc-patches-request@gcc.gnu.org?subject=unsubscribe> List-Archive: <https://gcc.gnu.org/pipermail/gcc-patches/> List-Post: <mailto:gcc-patches@gcc.gnu.org> List-Help: <mailto:gcc-patches-request@gcc.gnu.org?subject=help> List-Subscribe: <https://gcc.gnu.org/mailman/listinfo/gcc-patches>, <mailto:gcc-patches-request@gcc.gnu.org?subject=subscribe> Errors-To: gcc-patches-bounces~patchwork=sourceware.org@gcc.gnu.org |
| Series |
libstdc++: Fix chrono::current_zone() for three component names [PR122567]
|
|
Commit Message
Jonathan Wakely
Jan. 9, 2026, 5:35 p.m. UTC
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(-)
Comments
On Fri, Jan 9, 2026 at 6:37 PM Jonathan Wakely <jwakely@redhat.com> wrote: > 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) > Is there any real benefit of hardcoding 3 components here, instead of using while (last != first), so we would handle any longer names? > + { > + --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 > >
On Fri, Jan 9, 2026 at 6:58 PM Tomasz Kaminski <tkaminsk@redhat.com> wrote: > > > On Fri, Jan 9, 2026 at 6:37 PM Jonathan Wakely <jwakely@redhat.com> wrote: > >> 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) >> > Is there any real benefit of hardcoding 3 components here, instead of using > while (last != first), so we would handle any longer names? > If we have more than 4 components in future, then such a new timezone database can be downloaded by the older version of the library. And we will checking only more than tree components, only if the current zone is not something that we could match. > > + { >> + --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 >> >>
On Fri, 9 Jan 2026 at 18:13, Tomasz Kaminski <tkaminsk@redhat.com> wrote: > > > > On Fri, Jan 9, 2026 at 6:58 PM Tomasz Kaminski <tkaminsk@redhat.com> wrote: >> >> >> >> On Fri, Jan 9, 2026 at 6:37 PM Jonathan Wakely <jwakely@redhat.com> wrote: >>> >>> 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) >> >> Is there any real benefit of hardcoding 3 components here, instead of using >> while (last != first), so we would handle any longer names? It will slow down the unlikely case, where the symlink refers to a zone that we don't recognize as a valid name, but that's probably not a major concern. The docs say that most zones should have a name of the form AREA/LOCATION so more than two components is already rare: https://data.iana.org/time-zones/theory.html#naming Four or more components isn't actually forbidden, but it seems very unlikely. > > If we have more than 4 components in future, then such a new timezone database > can be downloaded by the older version of the library. And we will checking only more > than tree components, only if the current zone is not something that we could match. >> >> >>> >>> + { >>> + --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 >>>
On Fri, 9 Jan 2026 at 18:32, Jonathan Wakely <jwakely@redhat.com> wrote: > > On Fri, 9 Jan 2026 at 18:13, Tomasz Kaminski <tkaminsk@redhat.com> wrote: > > > > > > > > On Fri, Jan 9, 2026 at 6:58 PM Tomasz Kaminski <tkaminsk@redhat.com> wrote: > >> > >> > >> > >> On Fri, Jan 9, 2026 at 6:37 PM Jonathan Wakely <jwakely@redhat.com> wrote: > >>> > >>> 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) > >> > >> Is there any real benefit of hardcoding 3 components here, instead of using > >> while (last != first), so we would handle any longer names? > > It will slow down the unlikely case, where the symlink refers to a > zone that we don't recognize as a valid name, but that's probably not > a major concern. > > The docs say that most zones should have a name of the form > AREA/LOCATION so more than two components is already rare: > https://data.iana.org/time-zones/theory.html#naming > Four or more components isn't actually forbidden, but it seems very unlikely. I suppose it's possible that somebody could use an arbitrary number of filename components in a custom version of the database, replacing or overriding tzdata.zi with their own file and then setting /etc/localtime to refer to one of their own zones. > > > > > If we have more than 4 components in future, then such a new timezone database > > can be downloaded by the older version of the library. And we will checking only more > > than tree components, only if the current zone is not something that we could match. > >> > >> > >>> > >>> + { > >>> + --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 > >>>
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