From c87f0918b01f1c40c0da1fc82bebc0252b932b9d Mon Sep 17 00:00:00 2001
From: Paul Eggert <eggert@cs.ucla.edu>
Date: Wed, 24 Sep 2025 15:37:08 -0700
Subject: [PROPOSED 2/4] Use strnlen
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

* Makefile, NEWS: Mention this.
* localtime.c (scrub_abbrs, tzloadbody, tzset_unlocked):
* zic.c (newabbr): Prefer strnlen to strlen if we merely want to
check whether a string fits.
* private.h (strnlen): Define if platform doesn’t.
---
 Makefile    |  1 +
 NEWS        |  3 +++
 localtime.c |  9 +++++----
 private.h   | 12 ++++++++++++
 zic.c       |  2 +-
 5 files changed, 22 insertions(+), 5 deletions(-)

diff --git a/Makefile b/Makefile
index 6aac7b2d..321b447d 100644
--- a/Makefile
+++ b/Makefile
@@ -267,6 +267,7 @@ LDLIBS=
 #  -DHAVE_STDINT_H=0 if <stdint.h> does not work*+
 #  -DHAVE_STRFTIME_L if <time.h> declares locale_t and strftime_l
 #  -DHAVE_STRDUP=0 if your system lacks the strdup function
+#  -DHAVE_STRNLEN=0 if your system lacks the strnlen function+
 #  -DHAVE_STRTOLL=0 if your system lacks the strtoll function+
 #  -DHAVE_SYMLINK=0 if your system lacks the symlink function
 #  -DHAVE_SYS_STAT_H=0 if <sys/stat.h> does not work*
diff --git a/NEWS b/NEWS
index 2421950e..4f2d08e6 100644
--- a/NEWS
+++ b/NEWS
@@ -32,6 +32,9 @@ Unreleased, experimental changes
     It is defined if STD_INSPIRED is defined.
     (Patch from Dag-Erling Smørgrav.)
 
+    tzcode now uses strnlen to improve asymptotic performance a bit.
+    Compile with -DHAVE_STRNLEN=0 if your platform lacks it.
+
   Changes to commentary
 
     The leapseconds file contains commentary about the IERS and NIST
diff --git a/localtime.c b/localtime.c
index 378694fb..bae8217f 100644
--- a/localtime.c
+++ b/localtime.c
@@ -460,7 +460,7 @@ scrub_abbrs(struct state *sp)
 
 	/* Reject overlong abbreviations.  */
 	for (i = 0; i < sp->charcnt - (TZNAME_MAXIMUM + 1); ) {
-	  int len = strlen(&sp->chars[i]);
+	  int len = strnlen(&sp->chars[i], TZNAME_MAXIMUM + 1);
 	  if (TZNAME_MAXIMUM < len)
 	    return EOVERFLOW;
 	  i += len + 1;
@@ -549,7 +549,8 @@ tzloadbody(char const *name, struct state *sp, char tzloadflags,
 #endif
 	if (!doaccess) {
 		char const *dot;
-		if (sizeof lsp->fullname - sizeof tzdirslash <= strlen(name))
+		if (sizeof lsp->fullname - sizeof tzdirslash
+		    <= strnlen(name, sizeof lsp->fullname - sizeof tzdirslash))
 		  return ENAMETOOLONG;
 
 		/* Create a string "TZDIR/NAME".  Using sprintf here
@@ -793,7 +794,7 @@ tzloadbody(char const *name, struct state *sp, char tzloadflags,
 				break;
 			      }
 			    if (! (j < charcnt)) {
-			      int tsabbrlen = strlen(tsabbr);
+			      int tsabbrlen = strnlen(tsabbr, TZ_MAX_CHARS - j);
 			      if (j + tsabbrlen < TZ_MAX_CHARS) {
 				strcpy(sp->chars + j, tsabbr);
 				charcnt = j + tsabbrlen + 1;
@@ -1474,7 +1475,7 @@ tzset_unlocked(void)
 {
   char const *name = getenv("TZ");
   struct state *sp = lclptr;
-  int lcl = name ? strlen(name) < sizeof lcl_TZname : -1;
+  int lcl = name ? strnlen(name, sizeof lcl_TZname) < sizeof lcl_TZname : -1;
   if (lcl < 0
       ? lcl_is_set < 0
       : 0 < lcl_is_set && strcmp(lcl_TZname, name) == 0)
diff --git a/private.h b/private.h
index b2856e2e..223caf6b 100644
--- a/private.h
+++ b/private.h
@@ -212,7 +212,19 @@
 #undef tzfree
 
 #include <stddef.h>
+
 #include <string.h>
+#if defined HAVE_STRNLEN && !HAVE_STRNLEN
+static size_t
+strnlen (char const *s, size_t maxlen)
+{
+  size_t i;
+  for (i = 0; i < maxlen && s[i]; i++)
+    continue;
+  return i;
+}
+#endif
+
 #if !PORT_TO_C89
 # include <inttypes.h>
 #endif
diff --git a/zic.c b/zic.c
index 854e306b..6bdb039d 100644
--- a/zic.c
+++ b/zic.c
@@ -3932,7 +3932,7 @@ mp = _("time zone abbreviation differs from POSIX standard");
 		if (mp != NULL)
 			warning("%s (%s)", mp, string);
 	}
-	i = strlen(string) + 1;
+	i = strnlen(string, TZ_MAX_CHARS - charcnt) + 1;
 	if (charcnt + i > TZ_MAX_CHARS) {
 		error(_("too many, or too long, time zone abbreviations"));
 		exit(EXIT_FAILURE);
-- 
2.48.1

