diff mbox series

[v2,2/3] Add the wcslcpy, wcslcat functions

Message ID b1f657f157716de519b2b927d9ba214e04cbdca6.1681993374.git.fweimer@redhat.com
State New
Headers show
Series strlcpy and related functions | expand

Commit Message

Florian Weimer April 20, 2023, 12:28 p.m. UTC
These functions are about to be added to POSIX, under Austin Group
issue 986.

The fortified versions use byte counters instead of character counts
for the compiler-generated size argument.  This is expected to result
in less generated code for dynamic object sizes because most allocation
functions use byte counts, not wide character counts.
---
 debug/Makefile                                |  2 +
 debug/Versions                                |  2 +
 debug/tst-fortify.c                           | 17 ++++
 debug/wcslcat_chk.c                           | 31 +++++++
 debug/wcslcpy_chk.c                           | 31 +++++++
 include/wchar.h                               |  5 +
 sysdeps/mach/hurd/i386/libc.abilist           |  4 +
 sysdeps/unix/sysv/linux/aarch64/libc.abilist  |  4 +
 sysdeps/unix/sysv/linux/alpha/libc.abilist    |  4 +
 sysdeps/unix/sysv/linux/arc/libc.abilist      |  4 +
 sysdeps/unix/sysv/linux/arm/be/libc.abilist   |  4 +
 sysdeps/unix/sysv/linux/arm/le/libc.abilist   |  4 +
 sysdeps/unix/sysv/linux/csky/libc.abilist     |  4 +
 sysdeps/unix/sysv/linux/hppa/libc.abilist     |  4 +
 sysdeps/unix/sysv/linux/i386/libc.abilist     |  4 +
 sysdeps/unix/sysv/linux/ia64/libc.abilist     |  4 +
 .../sysv/linux/loongarch/lp64/libc.abilist    |  4 +
 .../sysv/linux/m68k/coldfire/libc.abilist     |  4 +
 .../unix/sysv/linux/m68k/m680x0/libc.abilist  |  4 +
 .../sysv/linux/microblaze/be/libc.abilist     |  4 +
 .../sysv/linux/microblaze/le/libc.abilist     |  4 +
 .../sysv/linux/mips/mips32/fpu/libc.abilist   |  4 +
 .../sysv/linux/mips/mips32/nofpu/libc.abilist |  4 +
 .../sysv/linux/mips/mips64/n32/libc.abilist   |  4 +
 .../sysv/linux/mips/mips64/n64/libc.abilist   |  4 +
 sysdeps/unix/sysv/linux/nios2/libc.abilist    |  4 +
 sysdeps/unix/sysv/linux/or1k/libc.abilist     |  4 +
 .../linux/powerpc/powerpc32/fpu/libc.abilist  |  4 +
 .../powerpc/powerpc32/nofpu/libc.abilist      |  4 +
 .../linux/powerpc/powerpc64/be/libc.abilist   |  4 +
 .../linux/powerpc/powerpc64/le/libc.abilist   |  4 +
 .../unix/sysv/linux/riscv/rv32/libc.abilist   |  4 +
 .../unix/sysv/linux/riscv/rv64/libc.abilist   |  4 +
 .../unix/sysv/linux/s390/s390-32/libc.abilist |  4 +
 .../unix/sysv/linux/s390/s390-64/libc.abilist |  4 +
 sysdeps/unix/sysv/linux/sh/be/libc.abilist    |  4 +
 sysdeps/unix/sysv/linux/sh/le/libc.abilist    |  4 +
 .../sysv/linux/sparc/sparc32/libc.abilist     |  4 +
 .../sysv/linux/sparc/sparc64/libc.abilist     |  4 +
 .../unix/sysv/linux/x86_64/64/libc.abilist    |  4 +
 .../unix/sysv/linux/x86_64/x32/libc.abilist   |  4 +
 wcsmbs/Makefile                               |  4 +
 wcsmbs/Versions                               |  2 +
 wcsmbs/bits/wchar2.h                          | 37 ++++++++
 wcsmbs/tst-wcslcat.c                          | 93 +++++++++++++++++++
 wcsmbs/tst-wcslcpy.c                          | 78 ++++++++++++++++
 wcsmbs/wchar.h                                | 13 +++
 wcsmbs/wcslcat.c                              | 60 ++++++++++++
 wcsmbs/wcslcpy.c                              | 46 +++++++++
 49 files changed, 561 insertions(+)
 create mode 100644 debug/wcslcat_chk.c
 create mode 100644 debug/wcslcpy_chk.c
 create mode 100644 wcsmbs/tst-wcslcat.c
 create mode 100644 wcsmbs/tst-wcslcpy.c
 create mode 100644 wcsmbs/wcslcat.c
 create mode 100644 wcsmbs/wcslcpy.c

Comments

Siddhesh Poyarekar June 6, 2023, 5:56 a.m. UTC | #1
On 2023-04-20 08:28, Florian Weimer via Libc-alpha wrote:
> These functions are about to be added to POSIX, under Austin Group
> issue 986.
> 
> The fortified versions use byte counters instead of character counts
> for the compiler-generated size argument.  This is expected to result
> in less generated code for dynamic object sizes because most allocation
> functions use byte counts, not wide character counts.

Does it really matter?  You'd be scaling in the _chk function instead of 
outside, so there doesn't really seem anything worthwhile to gain.  Not 
to mention that it's a constant, power of 2 scaling, which any CPU worth 
its salt should just breeze through.

I feel like it'll become a wee bit harder to talk about these functions 
years later where we'd be like "the fortified variants of the wide char 
functions take in the size of the destination in number of wchars, 
except wcslcat and wcslcpy, which take in number of bytes", not to 
mention situations where we forget that wcslcat and wcslcpy are different.

The rest looks OK.

> ---
>   debug/Makefile                                |  2 +
>   debug/Versions                                |  2 +
>   debug/tst-fortify.c                           | 17 ++++
>   debug/wcslcat_chk.c                           | 31 +++++++
>   debug/wcslcpy_chk.c                           | 31 +++++++
>   include/wchar.h                               |  5 +
>   sysdeps/mach/hurd/i386/libc.abilist           |  4 +
>   sysdeps/unix/sysv/linux/aarch64/libc.abilist  |  4 +
>   sysdeps/unix/sysv/linux/alpha/libc.abilist    |  4 +
>   sysdeps/unix/sysv/linux/arc/libc.abilist      |  4 +
>   sysdeps/unix/sysv/linux/arm/be/libc.abilist   |  4 +
>   sysdeps/unix/sysv/linux/arm/le/libc.abilist   |  4 +
>   sysdeps/unix/sysv/linux/csky/libc.abilist     |  4 +
>   sysdeps/unix/sysv/linux/hppa/libc.abilist     |  4 +
>   sysdeps/unix/sysv/linux/i386/libc.abilist     |  4 +
>   sysdeps/unix/sysv/linux/ia64/libc.abilist     |  4 +
>   .../sysv/linux/loongarch/lp64/libc.abilist    |  4 +
>   .../sysv/linux/m68k/coldfire/libc.abilist     |  4 +
>   .../unix/sysv/linux/m68k/m680x0/libc.abilist  |  4 +
>   .../sysv/linux/microblaze/be/libc.abilist     |  4 +
>   .../sysv/linux/microblaze/le/libc.abilist     |  4 +
>   .../sysv/linux/mips/mips32/fpu/libc.abilist   |  4 +
>   .../sysv/linux/mips/mips32/nofpu/libc.abilist |  4 +
>   .../sysv/linux/mips/mips64/n32/libc.abilist   |  4 +
>   .../sysv/linux/mips/mips64/n64/libc.abilist   |  4 +
>   sysdeps/unix/sysv/linux/nios2/libc.abilist    |  4 +
>   sysdeps/unix/sysv/linux/or1k/libc.abilist     |  4 +
>   .../linux/powerpc/powerpc32/fpu/libc.abilist  |  4 +
>   .../powerpc/powerpc32/nofpu/libc.abilist      |  4 +
>   .../linux/powerpc/powerpc64/be/libc.abilist   |  4 +
>   .../linux/powerpc/powerpc64/le/libc.abilist   |  4 +
>   .../unix/sysv/linux/riscv/rv32/libc.abilist   |  4 +
>   .../unix/sysv/linux/riscv/rv64/libc.abilist   |  4 +
>   .../unix/sysv/linux/s390/s390-32/libc.abilist |  4 +
>   .../unix/sysv/linux/s390/s390-64/libc.abilist |  4 +
>   sysdeps/unix/sysv/linux/sh/be/libc.abilist    |  4 +
>   sysdeps/unix/sysv/linux/sh/le/libc.abilist    |  4 +
>   .../sysv/linux/sparc/sparc32/libc.abilist     |  4 +
>   .../sysv/linux/sparc/sparc64/libc.abilist     |  4 +
>   .../unix/sysv/linux/x86_64/64/libc.abilist    |  4 +
>   .../unix/sysv/linux/x86_64/x32/libc.abilist   |  4 +
>   wcsmbs/Makefile                               |  4 +
>   wcsmbs/Versions                               |  2 +
>   wcsmbs/bits/wchar2.h                          | 37 ++++++++
>   wcsmbs/tst-wcslcat.c                          | 93 +++++++++++++++++++
>   wcsmbs/tst-wcslcpy.c                          | 78 ++++++++++++++++
>   wcsmbs/wchar.h                                | 13 +++
>   wcsmbs/wcslcat.c                              | 60 ++++++++++++
>   wcsmbs/wcslcpy.c                              | 46 +++++++++
>   49 files changed, 561 insertions(+)
>   create mode 100644 debug/wcslcat_chk.c
>   create mode 100644 debug/wcslcpy_chk.c
>   create mode 100644 wcsmbs/tst-wcslcat.c
>   create mode 100644 wcsmbs/tst-wcslcpy.c
>   create mode 100644 wcsmbs/wcslcat.c
>   create mode 100644 wcsmbs/wcslcpy.c
> 
> diff --git a/debug/Makefile b/debug/Makefile
> index f5f27f793c..4f018abecf 100644
> --- a/debug/Makefile
> +++ b/debug/Makefile
> @@ -104,6 +104,8 @@ routines = \
>     wcrtomb_chk \
>     wcscat_chk \
>     wcscpy_chk \
> +  wcslcat_chk \
> +  wcslcpy_chk \
>     wcsncat_chk \
>     wcsncpy_chk \
>     wcsnrtombs_chk \
> diff --git a/debug/Versions b/debug/Versions
> index 94dfa5f428..9cf2725992 100644
> --- a/debug/Versions
> +++ b/debug/Versions
> @@ -61,6 +61,8 @@ libc {
>     GLIBC_2.38 {
>       __strlcat_chk;
>       __strlcpy_chk;
> +    __wcslcat_chk;
> +    __wcslcpy_chk;
>     }
>     GLIBC_PRIVATE {
>       __fortify_fail;

OK.

> diff --git a/debug/tst-fortify.c b/debug/tst-fortify.c
> index f74a5e04dc..9fe20627ee 100644
> --- a/debug/tst-fortify.c
> +++ b/debug/tst-fortify.c
> @@ -782,6 +782,18 @@ do_test (void)
>     wcsncpy (wbuf + 9, L"XABCDEFGH", 8);
>     CHK_FAIL_END
>   
> +  CHK_FAIL_START
> +  wcslcpy (wbuf + 7, L"X", 4);
> +  CHK_FAIL_END
> +
> +  CHK_FAIL_START
> +  wcslcpy (wbuf + 7, L"X", l0 + 4);
> +  CHK_FAIL_END
> +
> +  CHK_FAIL_START
> +  wcslcpy (wbuf + 9, L"XABCDEFGH", 8);
> +  CHK_FAIL_END
> +
>     CHK_FAIL_START
>     wcpncpy (wbuf + 9, L"XABCDEFGH", 8);
>     CHK_FAIL_END
> @@ -804,6 +816,11 @@ do_test (void)
>     wcsncat (wbuf, L"ZYXWV", l0 + 3);
>     CHK_FAIL_END
>   
> +  wmemcpy (wbuf, wstr1 + 4, 7);
> +  CHK_FAIL_START
> +  wcslcat (wbuf, L"ZYXWV", l0 + 11);
> +  CHK_FAIL_END
> +
>     CHK_FAIL_START
>     wmemcpy (wa.buf1 + 1, L"abcdefghij", 10);
>     CHK_FAIL_END

OK.

> diff --git a/debug/wcslcat_chk.c b/debug/wcslcat_chk.c
> new file mode 100644
> index 0000000000..5d63fba076
> --- /dev/null
> +++ b/debug/wcslcat_chk.c
> @@ -0,0 +1,31 @@
> +/* Fortified version of wcslcat.
> +   Copyright (C) 2023 Free Software Foundation, Inc.
> +   This file is part of the GNU C Library.
> +
> +   The GNU C Library is free software; you can redistribute it and/or
> +   modify it under the terms of the GNU Lesser General Public
> +   License as published by the Free Software Foundation; either
> +   version 2.1 of the License, or (at your option) any later version.
> +
> +   The GNU C Library is distributed in the hope that it will be useful,
> +   but WITHOUT ANY WARRANTY; without even the implied warranty of
> +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> +   Lesser General Public License for more details.
> +
> +   You should have received a copy of the GNU Lesser General Public
> +   License along with the GNU C Library; if not, see
> +   <https://www.gnu.org/licenses/>.  */
> +
> +#include <wchar.h>
> +
> +/* Check that the user-supplied size does not exceed the
> +   compiler-determined size, and then forward to wcslcat.  */
> +size_t
> +__wcslcat_chk (wchar_t *__restrict s1, const wchar_t *__restrict s2,
> +               size_t n, size_t s1len)
> +{
> +  if (__glibc_unlikely (s1len / sizeof (wchar_t) < n))
> +    __chk_fail ();
> +
> +  return __wcslcat (s1, s2, n);
> +}
> diff --git a/debug/wcslcpy_chk.c b/debug/wcslcpy_chk.c
> new file mode 100644
> index 0000000000..ff7434b59a
> --- /dev/null
> +++ b/debug/wcslcpy_chk.c
> @@ -0,0 +1,31 @@
> +/* Fortified version of wcslcpy.
> +   Copyright (C) 2023 Free Software Foundation, Inc.
> +   This file is part of the GNU C Library.
> +
> +   The GNU C Library is free software; you can redistribute it and/or
> +   modify it under the terms of the GNU Lesser General Public
> +   License as published by the Free Software Foundation; either
> +   version 2.1 of the License, or (at your option) any later version.
> +
> +   The GNU C Library is distributed in the hope that it will be useful,
> +   but WITHOUT ANY WARRANTY; without even the implied warranty of
> +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> +   Lesser General Public License for more details.
> +
> +   You should have received a copy of the GNU Lesser General Public
> +   License along with the GNU C Library; if not, see
> +   <https://www.gnu.org/licenses/>.  */
> +
> +#include <wchar.h>
> +
> +/* Check that the user-supplied size does not exceed the
> +   compiler-determined size, and then forward to wcslcpy.  */
> +size_t
> +__wcslcpy_chk (wchar_t *__restrict s1, const wchar_t *__restrict s2,
> +	       size_t n, size_t s1len)
> +{
> +  if (__glibc_unlikely (s1len / sizeof (wchar_t) < n))
> +    __chk_fail ();
> +
> +  return __wcslcpy (s1, s2, n);
> +}
> diff --git a/include/wchar.h b/include/wchar.h
> index fafe7c8e9b..ff4ae52045 100644
> --- a/include/wchar.h
> +++ b/include/wchar.h
> @@ -203,6 +203,8 @@ extern size_t __wcslen (const wchar_t *__s) __attribute_pure__;
>   extern size_t __wcsnlen (const wchar_t *__s, size_t __maxlen)
>        __attribute_pure__;
>   extern wchar_t *__wcscat (wchar_t *dest, const wchar_t *src);
> +extern __typeof (wcslcat) __wcslcat;
> +libc_hidden_proto (__wcslcat)
>   extern wint_t __btowc (int __c) attribute_hidden;
>   extern int __mbsinit (const __mbstate_t *__ps);
>   extern size_t __mbrtowc (wchar_t *__restrict __pwc,
> @@ -237,8 +239,11 @@ extern wchar_t *__wcscpy (wchar_t *__restrict __dest,
>   			  const wchar_t *__restrict __src)
>   			  attribute_hidden __nonnull ((1, 2));
>   libc_hidden_proto (__wcscpy)
> +extern __typeof (wcslcpy) __wcslcpy;
> +libc_hidden_proto (__wcslcpy)
>   extern wchar_t *__wcsncpy (wchar_t *__restrict __dest,
>   			   const wchar_t *__restrict __src, size_t __n);
> +
>   extern wchar_t *__wcpcpy (wchar_t *__dest, const wchar_t *__src);
>   extern wchar_t *__wcpncpy (wchar_t *__dest, const wchar_t *__src,
>   			   size_t __n);
> diff --git a/sysdeps/mach/hurd/i386/libc.abilist b/sysdeps/mach/hurd/i386/libc.abilist
> index 9b4c5f4719..74a9f427b2 100644
> --- a/sysdeps/mach/hurd/i386/libc.abilist
> +++ b/sysdeps/mach/hurd/i386/libc.abilist
> @@ -2328,8 +2328,12 @@ GLIBC_2.38 __isoc23_wcstoumax F
>   GLIBC_2.38 __isoc23_wscanf F
>   GLIBC_2.38 __strlcat_chk F
>   GLIBC_2.38 __strlcpy_chk F
> +GLIBC_2.38 __wcslcat_chk F
> +GLIBC_2.38 __wcslcpy_chk F
>   GLIBC_2.38 strlcat F
>   GLIBC_2.38 strlcpy F
> +GLIBC_2.38 wcslcat F
> +GLIBC_2.38 wcslcpy F
>   GLIBC_2.4 __confstr_chk F
>   GLIBC_2.4 __fgets_chk F
>   GLIBC_2.4 __fgets_unlocked_chk F
> diff --git a/sysdeps/unix/sysv/linux/aarch64/libc.abilist b/sysdeps/unix/sysv/linux/aarch64/libc.abilist
> index cf51b88932..c49363e70e 100644
> --- a/sysdeps/unix/sysv/linux/aarch64/libc.abilist
> +++ b/sysdeps/unix/sysv/linux/aarch64/libc.abilist
> @@ -2667,5 +2667,9 @@ GLIBC_2.38 __isoc23_wcstoumax F
>   GLIBC_2.38 __isoc23_wscanf F
>   GLIBC_2.38 __strlcat_chk F
>   GLIBC_2.38 __strlcpy_chk F
> +GLIBC_2.38 __wcslcat_chk F
> +GLIBC_2.38 __wcslcpy_chk F
>   GLIBC_2.38 strlcat F
>   GLIBC_2.38 strlcpy F
> +GLIBC_2.38 wcslcat F
> +GLIBC_2.38 wcslcpy F
> diff --git a/sysdeps/unix/sysv/linux/alpha/libc.abilist b/sysdeps/unix/sysv/linux/alpha/libc.abilist
> index 4b25f343b8..d6b1dcaae6 100644
> --- a/sysdeps/unix/sysv/linux/alpha/libc.abilist
> +++ b/sysdeps/unix/sysv/linux/alpha/libc.abilist
> @@ -2776,8 +2776,12 @@ GLIBC_2.38 __nldbl___isoc23_vwscanf F
>   GLIBC_2.38 __nldbl___isoc23_wscanf F
>   GLIBC_2.38 __strlcat_chk F
>   GLIBC_2.38 __strlcpy_chk F
> +GLIBC_2.38 __wcslcat_chk F
> +GLIBC_2.38 __wcslcpy_chk F
>   GLIBC_2.38 strlcat F
>   GLIBC_2.38 strlcpy F
> +GLIBC_2.38 wcslcat F
> +GLIBC_2.38 wcslcpy F
>   GLIBC_2.4 _IO_fprintf F
>   GLIBC_2.4 _IO_printf F
>   GLIBC_2.4 _IO_sprintf F
> diff --git a/sysdeps/unix/sysv/linux/arc/libc.abilist b/sysdeps/unix/sysv/linux/arc/libc.abilist
> index 5a58cc0477..dfe0c3f7b6 100644
> --- a/sysdeps/unix/sysv/linux/arc/libc.abilist
> +++ b/sysdeps/unix/sysv/linux/arc/libc.abilist
> @@ -2428,5 +2428,9 @@ GLIBC_2.38 __isoc23_wcstoumax F
>   GLIBC_2.38 __isoc23_wscanf F
>   GLIBC_2.38 __strlcat_chk F
>   GLIBC_2.38 __strlcpy_chk F
> +GLIBC_2.38 __wcslcat_chk F
> +GLIBC_2.38 __wcslcpy_chk F
>   GLIBC_2.38 strlcat F
>   GLIBC_2.38 strlcpy F
> +GLIBC_2.38 wcslcat F
> +GLIBC_2.38 wcslcpy F
> diff --git a/sysdeps/unix/sysv/linux/arm/be/libc.abilist b/sysdeps/unix/sysv/linux/arm/be/libc.abilist
> index 99ce948c5c..6c75e5aa76 100644
> --- a/sysdeps/unix/sysv/linux/arm/be/libc.abilist
> +++ b/sysdeps/unix/sysv/linux/arm/be/libc.abilist
> @@ -548,8 +548,12 @@ GLIBC_2.38 __isoc23_wcstoumax F
>   GLIBC_2.38 __isoc23_wscanf F
>   GLIBC_2.38 __strlcat_chk F
>   GLIBC_2.38 __strlcpy_chk F
> +GLIBC_2.38 __wcslcat_chk F
> +GLIBC_2.38 __wcslcpy_chk F
>   GLIBC_2.38 strlcat F
>   GLIBC_2.38 strlcpy F
> +GLIBC_2.38 wcslcat F
> +GLIBC_2.38 wcslcpy F
>   GLIBC_2.4 _Exit F
>   GLIBC_2.4 _IO_2_1_stderr_ D 0xa0
>   GLIBC_2.4 _IO_2_1_stdin_ D 0xa0
> diff --git a/sysdeps/unix/sysv/linux/arm/le/libc.abilist b/sysdeps/unix/sysv/linux/arm/le/libc.abilist
> index c00bf72ebc..03d6f7ae2d 100644
> --- a/sysdeps/unix/sysv/linux/arm/le/libc.abilist
> +++ b/sysdeps/unix/sysv/linux/arm/le/libc.abilist
> @@ -545,8 +545,12 @@ GLIBC_2.38 __isoc23_wcstoumax F
>   GLIBC_2.38 __isoc23_wscanf F
>   GLIBC_2.38 __strlcat_chk F
>   GLIBC_2.38 __strlcpy_chk F
> +GLIBC_2.38 __wcslcat_chk F
> +GLIBC_2.38 __wcslcpy_chk F
>   GLIBC_2.38 strlcat F
>   GLIBC_2.38 strlcpy F
> +GLIBC_2.38 wcslcat F
> +GLIBC_2.38 wcslcpy F
>   GLIBC_2.4 _Exit F
>   GLIBC_2.4 _IO_2_1_stderr_ D 0xa0
>   GLIBC_2.4 _IO_2_1_stdin_ D 0xa0
> diff --git a/sysdeps/unix/sysv/linux/csky/libc.abilist b/sysdeps/unix/sysv/linux/csky/libc.abilist
> index 71130f2c6b..d858c108c6 100644
> --- a/sysdeps/unix/sysv/linux/csky/libc.abilist
> +++ b/sysdeps/unix/sysv/linux/csky/libc.abilist
> @@ -2704,5 +2704,9 @@ GLIBC_2.38 __isoc23_wcstoumax F
>   GLIBC_2.38 __isoc23_wscanf F
>   GLIBC_2.38 __strlcat_chk F
>   GLIBC_2.38 __strlcpy_chk F
> +GLIBC_2.38 __wcslcat_chk F
> +GLIBC_2.38 __wcslcpy_chk F
>   GLIBC_2.38 strlcat F
>   GLIBC_2.38 strlcpy F
> +GLIBC_2.38 wcslcat F
> +GLIBC_2.38 wcslcpy F
> diff --git a/sysdeps/unix/sysv/linux/hppa/libc.abilist b/sysdeps/unix/sysv/linux/hppa/libc.abilist
> index 5a651c03df..82a14f8ace 100644
> --- a/sysdeps/unix/sysv/linux/hppa/libc.abilist
> +++ b/sysdeps/unix/sysv/linux/hppa/libc.abilist
> @@ -2653,8 +2653,12 @@ GLIBC_2.38 __isoc23_wcstoumax F
>   GLIBC_2.38 __isoc23_wscanf F
>   GLIBC_2.38 __strlcat_chk F
>   GLIBC_2.38 __strlcpy_chk F
> +GLIBC_2.38 __wcslcat_chk F
> +GLIBC_2.38 __wcslcpy_chk F
>   GLIBC_2.38 strlcat F
>   GLIBC_2.38 strlcpy F
> +GLIBC_2.38 wcslcat F
> +GLIBC_2.38 wcslcpy F
>   GLIBC_2.4 __confstr_chk F
>   GLIBC_2.4 __fgets_chk F
>   GLIBC_2.4 __fgets_unlocked_chk F
> diff --git a/sysdeps/unix/sysv/linux/i386/libc.abilist b/sysdeps/unix/sysv/linux/i386/libc.abilist
> index 12b91ef632..1950b15d5d 100644
> --- a/sysdeps/unix/sysv/linux/i386/libc.abilist
> +++ b/sysdeps/unix/sysv/linux/i386/libc.abilist
> @@ -2837,8 +2837,12 @@ GLIBC_2.38 __isoc23_wcstoumax F
>   GLIBC_2.38 __isoc23_wscanf F
>   GLIBC_2.38 __strlcat_chk F
>   GLIBC_2.38 __strlcpy_chk F
> +GLIBC_2.38 __wcslcat_chk F
> +GLIBC_2.38 __wcslcpy_chk F
>   GLIBC_2.38 strlcat F
>   GLIBC_2.38 strlcpy F
> +GLIBC_2.38 wcslcat F
> +GLIBC_2.38 wcslcpy F
>   GLIBC_2.4 __confstr_chk F
>   GLIBC_2.4 __fgets_chk F
>   GLIBC_2.4 __fgets_unlocked_chk F
> diff --git a/sysdeps/unix/sysv/linux/ia64/libc.abilist b/sysdeps/unix/sysv/linux/ia64/libc.abilist
> index f223c5e08d..d0b9cb279b 100644
> --- a/sysdeps/unix/sysv/linux/ia64/libc.abilist
> +++ b/sysdeps/unix/sysv/linux/ia64/libc.abilist
> @@ -2602,8 +2602,12 @@ GLIBC_2.38 __isoc23_wcstoumax F
>   GLIBC_2.38 __isoc23_wscanf F
>   GLIBC_2.38 __strlcat_chk F
>   GLIBC_2.38 __strlcpy_chk F
> +GLIBC_2.38 __wcslcat_chk F
> +GLIBC_2.38 __wcslcpy_chk F
>   GLIBC_2.38 strlcat F
>   GLIBC_2.38 strlcpy F
> +GLIBC_2.38 wcslcat F
> +GLIBC_2.38 wcslcpy F
>   GLIBC_2.4 __confstr_chk F
>   GLIBC_2.4 __fgets_chk F
>   GLIBC_2.4 __fgets_unlocked_chk F
> diff --git a/sysdeps/unix/sysv/linux/loongarch/lp64/libc.abilist b/sysdeps/unix/sysv/linux/loongarch/lp64/libc.abilist
> index b91ed6e704..e760a631dd 100644
> --- a/sysdeps/unix/sysv/linux/loongarch/lp64/libc.abilist
> +++ b/sysdeps/unix/sysv/linux/loongarch/lp64/libc.abilist
> @@ -2188,5 +2188,9 @@ GLIBC_2.38 __isoc23_wcstoumax F
>   GLIBC_2.38 __isoc23_wscanf F
>   GLIBC_2.38 __strlcat_chk F
>   GLIBC_2.38 __strlcpy_chk F
> +GLIBC_2.38 __wcslcat_chk F
> +GLIBC_2.38 __wcslcpy_chk F
>   GLIBC_2.38 strlcat F
>   GLIBC_2.38 strlcpy F
> +GLIBC_2.38 wcslcat F
> +GLIBC_2.38 wcslcpy F
> diff --git a/sysdeps/unix/sysv/linux/m68k/coldfire/libc.abilist b/sysdeps/unix/sysv/linux/m68k/coldfire/libc.abilist
> index 0d91d7f1ae..35785a3d5f 100644
> --- a/sysdeps/unix/sysv/linux/m68k/coldfire/libc.abilist
> +++ b/sysdeps/unix/sysv/linux/m68k/coldfire/libc.abilist
> @@ -549,8 +549,12 @@ GLIBC_2.38 __isoc23_wcstoumax F
>   GLIBC_2.38 __isoc23_wscanf F
>   GLIBC_2.38 __strlcat_chk F
>   GLIBC_2.38 __strlcpy_chk F
> +GLIBC_2.38 __wcslcat_chk F
> +GLIBC_2.38 __wcslcpy_chk F
>   GLIBC_2.38 strlcat F
>   GLIBC_2.38 strlcpy F
> +GLIBC_2.38 wcslcat F
> +GLIBC_2.38 wcslcpy F
>   GLIBC_2.4 _Exit F
>   GLIBC_2.4 _IO_2_1_stderr_ D 0x98
>   GLIBC_2.4 _IO_2_1_stdin_ D 0x98
> diff --git a/sysdeps/unix/sysv/linux/m68k/m680x0/libc.abilist b/sysdeps/unix/sysv/linux/m68k/m680x0/libc.abilist
> index e87b22747a..4ab2426e0a 100644
> --- a/sysdeps/unix/sysv/linux/m68k/m680x0/libc.abilist
> +++ b/sysdeps/unix/sysv/linux/m68k/m680x0/libc.abilist
> @@ -2780,8 +2780,12 @@ GLIBC_2.38 __isoc23_wcstoumax F
>   GLIBC_2.38 __isoc23_wscanf F
>   GLIBC_2.38 __strlcat_chk F
>   GLIBC_2.38 __strlcpy_chk F
> +GLIBC_2.38 __wcslcat_chk F
> +GLIBC_2.38 __wcslcpy_chk F
>   GLIBC_2.38 strlcat F
>   GLIBC_2.38 strlcpy F
> +GLIBC_2.38 wcslcat F
> +GLIBC_2.38 wcslcpy F
>   GLIBC_2.4 __confstr_chk F
>   GLIBC_2.4 __fgets_chk F
>   GLIBC_2.4 __fgets_unlocked_chk F
> diff --git a/sysdeps/unix/sysv/linux/microblaze/be/libc.abilist b/sysdeps/unix/sysv/linux/microblaze/be/libc.abilist
> index f7623d6d72..38faa16232 100644
> --- a/sysdeps/unix/sysv/linux/microblaze/be/libc.abilist
> +++ b/sysdeps/unix/sysv/linux/microblaze/be/libc.abilist
> @@ -2753,5 +2753,9 @@ GLIBC_2.38 __isoc23_wcstoumax F
>   GLIBC_2.38 __isoc23_wscanf F
>   GLIBC_2.38 __strlcat_chk F
>   GLIBC_2.38 __strlcpy_chk F
> +GLIBC_2.38 __wcslcat_chk F
> +GLIBC_2.38 __wcslcpy_chk F
>   GLIBC_2.38 strlcat F
>   GLIBC_2.38 strlcpy F
> +GLIBC_2.38 wcslcat F
> +GLIBC_2.38 wcslcpy F
> diff --git a/sysdeps/unix/sysv/linux/microblaze/le/libc.abilist b/sysdeps/unix/sysv/linux/microblaze/le/libc.abilist
> index 298aa99b42..374d658988 100644
> --- a/sysdeps/unix/sysv/linux/microblaze/le/libc.abilist
> +++ b/sysdeps/unix/sysv/linux/microblaze/le/libc.abilist
> @@ -2750,5 +2750,9 @@ GLIBC_2.38 __isoc23_wcstoumax F
>   GLIBC_2.38 __isoc23_wscanf F
>   GLIBC_2.38 __strlcat_chk F
>   GLIBC_2.38 __strlcpy_chk F
> +GLIBC_2.38 __wcslcat_chk F
> +GLIBC_2.38 __wcslcpy_chk F
>   GLIBC_2.38 strlcat F
>   GLIBC_2.38 strlcpy F
> +GLIBC_2.38 wcslcat F
> +GLIBC_2.38 wcslcpy F
> diff --git a/sysdeps/unix/sysv/linux/mips/mips32/fpu/libc.abilist b/sysdeps/unix/sysv/linux/mips/mips32/fpu/libc.abilist
> index f83bdc50cd..fcc5e88e91 100644
> --- a/sysdeps/unix/sysv/linux/mips/mips32/fpu/libc.abilist
> +++ b/sysdeps/unix/sysv/linux/mips/mips32/fpu/libc.abilist
> @@ -2745,8 +2745,12 @@ GLIBC_2.38 __isoc23_wcstoumax F
>   GLIBC_2.38 __isoc23_wscanf F
>   GLIBC_2.38 __strlcat_chk F
>   GLIBC_2.38 __strlcpy_chk F
> +GLIBC_2.38 __wcslcat_chk F
> +GLIBC_2.38 __wcslcpy_chk F
>   GLIBC_2.38 strlcat F
>   GLIBC_2.38 strlcpy F
> +GLIBC_2.38 wcslcat F
> +GLIBC_2.38 wcslcpy F
>   GLIBC_2.4 __confstr_chk F
>   GLIBC_2.4 __fgets_chk F
>   GLIBC_2.4 __fgets_unlocked_chk F
> diff --git a/sysdeps/unix/sysv/linux/mips/mips32/nofpu/libc.abilist b/sysdeps/unix/sysv/linux/mips/mips32/nofpu/libc.abilist
> index 611ece2ac4..01eb96cd93 100644
> --- a/sysdeps/unix/sysv/linux/mips/mips32/nofpu/libc.abilist
> +++ b/sysdeps/unix/sysv/linux/mips/mips32/nofpu/libc.abilist
> @@ -2743,8 +2743,12 @@ GLIBC_2.38 __isoc23_wcstoumax F
>   GLIBC_2.38 __isoc23_wscanf F
>   GLIBC_2.38 __strlcat_chk F
>   GLIBC_2.38 __strlcpy_chk F
> +GLIBC_2.38 __wcslcat_chk F
> +GLIBC_2.38 __wcslcpy_chk F
>   GLIBC_2.38 strlcat F
>   GLIBC_2.38 strlcpy F
> +GLIBC_2.38 wcslcat F
> +GLIBC_2.38 wcslcpy F
>   GLIBC_2.4 __confstr_chk F
>   GLIBC_2.4 __fgets_chk F
>   GLIBC_2.4 __fgets_unlocked_chk F
> diff --git a/sysdeps/unix/sysv/linux/mips/mips64/n32/libc.abilist b/sysdeps/unix/sysv/linux/mips/mips64/n32/libc.abilist
> index 0af286fda1..a2748b7b74 100644
> --- a/sysdeps/unix/sysv/linux/mips/mips64/n32/libc.abilist
> +++ b/sysdeps/unix/sysv/linux/mips/mips64/n32/libc.abilist
> @@ -2751,8 +2751,12 @@ GLIBC_2.38 __isoc23_wcstoumax F
>   GLIBC_2.38 __isoc23_wscanf F
>   GLIBC_2.38 __strlcat_chk F
>   GLIBC_2.38 __strlcpy_chk F
> +GLIBC_2.38 __wcslcat_chk F
> +GLIBC_2.38 __wcslcpy_chk F
>   GLIBC_2.38 strlcat F
>   GLIBC_2.38 strlcpy F
> +GLIBC_2.38 wcslcat F
> +GLIBC_2.38 wcslcpy F
>   GLIBC_2.4 __confstr_chk F
>   GLIBC_2.4 __fgets_chk F
>   GLIBC_2.4 __fgets_unlocked_chk F
> diff --git a/sysdeps/unix/sysv/linux/mips/mips64/n64/libc.abilist b/sysdeps/unix/sysv/linux/mips/mips64/n64/libc.abilist
> index 8285f2196e..0ae7ba499d 100644
> --- a/sysdeps/unix/sysv/linux/mips/mips64/n64/libc.abilist
> +++ b/sysdeps/unix/sysv/linux/mips/mips64/n64/libc.abilist
> @@ -2653,8 +2653,12 @@ GLIBC_2.38 __isoc23_wcstoumax F
>   GLIBC_2.38 __isoc23_wscanf F
>   GLIBC_2.38 __strlcat_chk F
>   GLIBC_2.38 __strlcpy_chk F
> +GLIBC_2.38 __wcslcat_chk F
> +GLIBC_2.38 __wcslcpy_chk F
>   GLIBC_2.38 strlcat F
>   GLIBC_2.38 strlcpy F
> +GLIBC_2.38 wcslcat F
> +GLIBC_2.38 wcslcpy F
>   GLIBC_2.4 __confstr_chk F
>   GLIBC_2.4 __fgets_chk F
>   GLIBC_2.4 __fgets_unlocked_chk F
> diff --git a/sysdeps/unix/sysv/linux/nios2/libc.abilist b/sysdeps/unix/sysv/linux/nios2/libc.abilist
> index c7144d7cd8..947495a0e2 100644
> --- a/sysdeps/unix/sysv/linux/nios2/libc.abilist
> +++ b/sysdeps/unix/sysv/linux/nios2/libc.abilist
> @@ -2792,5 +2792,9 @@ GLIBC_2.38 __isoc23_wcstoumax F
>   GLIBC_2.38 __isoc23_wscanf F
>   GLIBC_2.38 __strlcat_chk F
>   GLIBC_2.38 __strlcpy_chk F
> +GLIBC_2.38 __wcslcat_chk F
> +GLIBC_2.38 __wcslcpy_chk F
>   GLIBC_2.38 strlcat F
>   GLIBC_2.38 strlcpy F
> +GLIBC_2.38 wcslcat F
> +GLIBC_2.38 wcslcpy F
> diff --git a/sysdeps/unix/sysv/linux/or1k/libc.abilist b/sysdeps/unix/sysv/linux/or1k/libc.abilist
> index bb43247795..115f1039e7 100644
> --- a/sysdeps/unix/sysv/linux/or1k/libc.abilist
> +++ b/sysdeps/unix/sysv/linux/or1k/libc.abilist
> @@ -2174,5 +2174,9 @@ GLIBC_2.38 __isoc23_wcstoumax F
>   GLIBC_2.38 __isoc23_wscanf F
>   GLIBC_2.38 __strlcat_chk F
>   GLIBC_2.38 __strlcpy_chk F
> +GLIBC_2.38 __wcslcat_chk F
> +GLIBC_2.38 __wcslcpy_chk F
>   GLIBC_2.38 strlcat F
>   GLIBC_2.38 strlcpy F
> +GLIBC_2.38 wcslcat F
> +GLIBC_2.38 wcslcpy F
> diff --git a/sysdeps/unix/sysv/linux/powerpc/powerpc32/fpu/libc.abilist b/sysdeps/unix/sysv/linux/powerpc/powerpc32/fpu/libc.abilist
> index 7cc5660830..19c4c325b0 100644
> --- a/sysdeps/unix/sysv/linux/powerpc/powerpc32/fpu/libc.abilist
> +++ b/sysdeps/unix/sysv/linux/powerpc/powerpc32/fpu/libc.abilist
> @@ -2819,8 +2819,12 @@ GLIBC_2.38 __nldbl___isoc23_vwscanf F
>   GLIBC_2.38 __nldbl___isoc23_wscanf F
>   GLIBC_2.38 __strlcat_chk F
>   GLIBC_2.38 __strlcpy_chk F
> +GLIBC_2.38 __wcslcat_chk F
> +GLIBC_2.38 __wcslcpy_chk F
>   GLIBC_2.38 strlcat F
>   GLIBC_2.38 strlcpy F
> +GLIBC_2.38 wcslcat F
> +GLIBC_2.38 wcslcpy F
>   GLIBC_2.4 _IO_fprintf F
>   GLIBC_2.4 _IO_printf F
>   GLIBC_2.4 _IO_sprintf F
> diff --git a/sysdeps/unix/sysv/linux/powerpc/powerpc32/nofpu/libc.abilist b/sysdeps/unix/sysv/linux/powerpc/powerpc32/nofpu/libc.abilist
> index dd290af782..3e043c4044 100644
> --- a/sysdeps/unix/sysv/linux/powerpc/powerpc32/nofpu/libc.abilist
> +++ b/sysdeps/unix/sysv/linux/powerpc/powerpc32/nofpu/libc.abilist
> @@ -2852,8 +2852,12 @@ GLIBC_2.38 __nldbl___isoc23_vwscanf F
>   GLIBC_2.38 __nldbl___isoc23_wscanf F
>   GLIBC_2.38 __strlcat_chk F
>   GLIBC_2.38 __strlcpy_chk F
> +GLIBC_2.38 __wcslcat_chk F
> +GLIBC_2.38 __wcslcpy_chk F
>   GLIBC_2.38 strlcat F
>   GLIBC_2.38 strlcpy F
> +GLIBC_2.38 wcslcat F
> +GLIBC_2.38 wcslcpy F
>   GLIBC_2.4 _IO_fprintf F
>   GLIBC_2.4 _IO_printf F
>   GLIBC_2.4 _IO_sprintf F
> diff --git a/sysdeps/unix/sysv/linux/powerpc/powerpc64/be/libc.abilist b/sysdeps/unix/sysv/linux/powerpc/powerpc64/be/libc.abilist
> index f2b001402c..e4f3a766bb 100644
> --- a/sysdeps/unix/sysv/linux/powerpc/powerpc64/be/libc.abilist
> +++ b/sysdeps/unix/sysv/linux/powerpc/powerpc64/be/libc.abilist
> @@ -2573,8 +2573,12 @@ GLIBC_2.38 __nldbl___isoc23_vwscanf F
>   GLIBC_2.38 __nldbl___isoc23_wscanf F
>   GLIBC_2.38 __strlcat_chk F
>   GLIBC_2.38 __strlcpy_chk F
> +GLIBC_2.38 __wcslcat_chk F
> +GLIBC_2.38 __wcslcpy_chk F
>   GLIBC_2.38 strlcat F
>   GLIBC_2.38 strlcpy F
> +GLIBC_2.38 wcslcat F
> +GLIBC_2.38 wcslcpy F
>   GLIBC_2.4 _IO_fprintf F
>   GLIBC_2.4 _IO_printf F
>   GLIBC_2.4 _IO_sprintf F
> diff --git a/sysdeps/unix/sysv/linux/powerpc/powerpc64/le/libc.abilist b/sysdeps/unix/sysv/linux/powerpc/powerpc64/le/libc.abilist
> index 9cc431666e..dafe1c4a59 100644
> --- a/sysdeps/unix/sysv/linux/powerpc/powerpc64/le/libc.abilist
> +++ b/sysdeps/unix/sysv/linux/powerpc/powerpc64/le/libc.abilist
> @@ -2887,5 +2887,9 @@ GLIBC_2.38 __nldbl___isoc23_vwscanf F
>   GLIBC_2.38 __nldbl___isoc23_wscanf F
>   GLIBC_2.38 __strlcat_chk F
>   GLIBC_2.38 __strlcpy_chk F
> +GLIBC_2.38 __wcslcat_chk F
> +GLIBC_2.38 __wcslcpy_chk F
>   GLIBC_2.38 strlcat F
>   GLIBC_2.38 strlcpy F
> +GLIBC_2.38 wcslcat F
> +GLIBC_2.38 wcslcpy F
> diff --git a/sysdeps/unix/sysv/linux/riscv/rv32/libc.abilist b/sysdeps/unix/sysv/linux/riscv/rv32/libc.abilist
> index b9b725f913..b9740a1afc 100644
> --- a/sysdeps/unix/sysv/linux/riscv/rv32/libc.abilist
> +++ b/sysdeps/unix/sysv/linux/riscv/rv32/libc.abilist
> @@ -2430,5 +2430,9 @@ GLIBC_2.38 __isoc23_wcstoumax F
>   GLIBC_2.38 __isoc23_wscanf F
>   GLIBC_2.38 __strlcat_chk F
>   GLIBC_2.38 __strlcpy_chk F
> +GLIBC_2.38 __wcslcat_chk F
> +GLIBC_2.38 __wcslcpy_chk F
>   GLIBC_2.38 strlcat F
>   GLIBC_2.38 strlcpy F
> +GLIBC_2.38 wcslcat F
> +GLIBC_2.38 wcslcpy F
> diff --git a/sysdeps/unix/sysv/linux/riscv/rv64/libc.abilist b/sysdeps/unix/sysv/linux/riscv/rv64/libc.abilist
> index e0f4863856..e3b4656aa2 100644
> --- a/sysdeps/unix/sysv/linux/riscv/rv64/libc.abilist
> +++ b/sysdeps/unix/sysv/linux/riscv/rv64/libc.abilist
> @@ -2630,5 +2630,9 @@ GLIBC_2.38 __isoc23_wcstoumax F
>   GLIBC_2.38 __isoc23_wscanf F
>   GLIBC_2.38 __strlcat_chk F
>   GLIBC_2.38 __strlcpy_chk F
> +GLIBC_2.38 __wcslcat_chk F
> +GLIBC_2.38 __wcslcpy_chk F
>   GLIBC_2.38 strlcat F
>   GLIBC_2.38 strlcpy F
> +GLIBC_2.38 wcslcat F
> +GLIBC_2.38 wcslcpy F
> diff --git a/sysdeps/unix/sysv/linux/s390/s390-32/libc.abilist b/sysdeps/unix/sysv/linux/s390/s390-32/libc.abilist
> index 8db68fcea7..84cb7a50ed 100644
> --- a/sysdeps/unix/sysv/linux/s390/s390-32/libc.abilist
> +++ b/sysdeps/unix/sysv/linux/s390/s390-32/libc.abilist
> @@ -2817,8 +2817,12 @@ GLIBC_2.38 __nldbl___isoc23_vwscanf F
>   GLIBC_2.38 __nldbl___isoc23_wscanf F
>   GLIBC_2.38 __strlcat_chk F
>   GLIBC_2.38 __strlcpy_chk F
> +GLIBC_2.38 __wcslcat_chk F
> +GLIBC_2.38 __wcslcpy_chk F
>   GLIBC_2.38 strlcat F
>   GLIBC_2.38 strlcpy F
> +GLIBC_2.38 wcslcat F
> +GLIBC_2.38 wcslcpy F
>   GLIBC_2.4 _IO_fprintf F
>   GLIBC_2.4 _IO_printf F
>   GLIBC_2.4 _IO_sprintf F
> diff --git a/sysdeps/unix/sysv/linux/s390/s390-64/libc.abilist b/sysdeps/unix/sysv/linux/s390/s390-64/libc.abilist
> index ec9747b7ea..33df3b1646 100644
> --- a/sysdeps/unix/sysv/linux/s390/s390-64/libc.abilist
> +++ b/sysdeps/unix/sysv/linux/s390/s390-64/libc.abilist
> @@ -2610,8 +2610,12 @@ GLIBC_2.38 __nldbl___isoc23_vwscanf F
>   GLIBC_2.38 __nldbl___isoc23_wscanf F
>   GLIBC_2.38 __strlcat_chk F
>   GLIBC_2.38 __strlcpy_chk F
> +GLIBC_2.38 __wcslcat_chk F
> +GLIBC_2.38 __wcslcpy_chk F
>   GLIBC_2.38 strlcat F
>   GLIBC_2.38 strlcpy F
> +GLIBC_2.38 wcslcat F
> +GLIBC_2.38 wcslcpy F
>   GLIBC_2.4 _IO_fprintf F
>   GLIBC_2.4 _IO_printf F
>   GLIBC_2.4 _IO_sprintf F
> diff --git a/sysdeps/unix/sysv/linux/sh/be/libc.abilist b/sysdeps/unix/sysv/linux/sh/be/libc.abilist
> index 9576b818d8..94cbccd715 100644
> --- a/sysdeps/unix/sysv/linux/sh/be/libc.abilist
> +++ b/sysdeps/unix/sysv/linux/sh/be/libc.abilist
> @@ -2660,8 +2660,12 @@ GLIBC_2.38 __isoc23_wcstoumax F
>   GLIBC_2.38 __isoc23_wscanf F
>   GLIBC_2.38 __strlcat_chk F
>   GLIBC_2.38 __strlcpy_chk F
> +GLIBC_2.38 __wcslcat_chk F
> +GLIBC_2.38 __wcslcpy_chk F
>   GLIBC_2.38 strlcat F
>   GLIBC_2.38 strlcpy F
> +GLIBC_2.38 wcslcat F
> +GLIBC_2.38 wcslcpy F
>   GLIBC_2.4 __confstr_chk F
>   GLIBC_2.4 __fgets_chk F
>   GLIBC_2.4 __fgets_unlocked_chk F
> diff --git a/sysdeps/unix/sysv/linux/sh/le/libc.abilist b/sysdeps/unix/sysv/linux/sh/le/libc.abilist
> index b67b1b2bb5..3bb316a787 100644
> --- a/sysdeps/unix/sysv/linux/sh/le/libc.abilist
> +++ b/sysdeps/unix/sysv/linux/sh/le/libc.abilist
> @@ -2657,8 +2657,12 @@ GLIBC_2.38 __isoc23_wcstoumax F
>   GLIBC_2.38 __isoc23_wscanf F
>   GLIBC_2.38 __strlcat_chk F
>   GLIBC_2.38 __strlcpy_chk F
> +GLIBC_2.38 __wcslcat_chk F
> +GLIBC_2.38 __wcslcpy_chk F
>   GLIBC_2.38 strlcat F
>   GLIBC_2.38 strlcpy F
> +GLIBC_2.38 wcslcat F
> +GLIBC_2.38 wcslcpy F
>   GLIBC_2.4 __confstr_chk F
>   GLIBC_2.4 __fgets_chk F
>   GLIBC_2.4 __fgets_unlocked_chk F
> diff --git a/sysdeps/unix/sysv/linux/sparc/sparc32/libc.abilist b/sysdeps/unix/sysv/linux/sparc/sparc32/libc.abilist
> index b251fc9c69..6341b491b4 100644
> --- a/sysdeps/unix/sysv/linux/sparc/sparc32/libc.abilist
> +++ b/sysdeps/unix/sysv/linux/sparc/sparc32/libc.abilist
> @@ -2812,8 +2812,12 @@ GLIBC_2.38 __nldbl___isoc23_vwscanf F
>   GLIBC_2.38 __nldbl___isoc23_wscanf F
>   GLIBC_2.38 __strlcat_chk F
>   GLIBC_2.38 __strlcpy_chk F
> +GLIBC_2.38 __wcslcat_chk F
> +GLIBC_2.38 __wcslcpy_chk F
>   GLIBC_2.38 strlcat F
>   GLIBC_2.38 strlcpy F
> +GLIBC_2.38 wcslcat F
> +GLIBC_2.38 wcslcpy F
>   GLIBC_2.4 _IO_fprintf F
>   GLIBC_2.4 _IO_printf F
>   GLIBC_2.4 _IO_sprintf F
> diff --git a/sysdeps/unix/sysv/linux/sparc/sparc64/libc.abilist b/sysdeps/unix/sysv/linux/sparc/sparc64/libc.abilist
> index 5ef9bbec34..8ed1ea2926 100644
> --- a/sysdeps/unix/sysv/linux/sparc/sparc64/libc.abilist
> +++ b/sysdeps/unix/sysv/linux/sparc/sparc64/libc.abilist
> @@ -2625,8 +2625,12 @@ GLIBC_2.38 __isoc23_wcstoumax F
>   GLIBC_2.38 __isoc23_wscanf F
>   GLIBC_2.38 __strlcat_chk F
>   GLIBC_2.38 __strlcpy_chk F
> +GLIBC_2.38 __wcslcat_chk F
> +GLIBC_2.38 __wcslcpy_chk F
>   GLIBC_2.38 strlcat F
>   GLIBC_2.38 strlcpy F
> +GLIBC_2.38 wcslcat F
> +GLIBC_2.38 wcslcpy F
>   GLIBC_2.4 __confstr_chk F
>   GLIBC_2.4 __fgets_chk F
>   GLIBC_2.4 __fgets_unlocked_chk F
> diff --git a/sysdeps/unix/sysv/linux/x86_64/64/libc.abilist b/sysdeps/unix/sysv/linux/x86_64/64/libc.abilist
> index 9ad800b62e..57cfcc2086 100644
> --- a/sysdeps/unix/sysv/linux/x86_64/64/libc.abilist
> +++ b/sysdeps/unix/sysv/linux/x86_64/64/libc.abilist
> @@ -2576,8 +2576,12 @@ GLIBC_2.38 __isoc23_wcstoumax F
>   GLIBC_2.38 __isoc23_wscanf F
>   GLIBC_2.38 __strlcat_chk F
>   GLIBC_2.38 __strlcpy_chk F
> +GLIBC_2.38 __wcslcat_chk F
> +GLIBC_2.38 __wcslcpy_chk F
>   GLIBC_2.38 strlcat F
>   GLIBC_2.38 strlcpy F
> +GLIBC_2.38 wcslcat F
> +GLIBC_2.38 wcslcpy F
>   GLIBC_2.4 __confstr_chk F
>   GLIBC_2.4 __fgets_chk F
>   GLIBC_2.4 __fgets_unlocked_chk F
> diff --git a/sysdeps/unix/sysv/linux/x86_64/x32/libc.abilist b/sysdeps/unix/sysv/linux/x86_64/x32/libc.abilist
> index 6a3a66c5d4..3f0a9f6d82 100644
> --- a/sysdeps/unix/sysv/linux/x86_64/x32/libc.abilist
> +++ b/sysdeps/unix/sysv/linux/x86_64/x32/libc.abilist
> @@ -2682,5 +2682,9 @@ GLIBC_2.38 __isoc23_wcstoumax F
>   GLIBC_2.38 __isoc23_wscanf F
>   GLIBC_2.38 __strlcat_chk F
>   GLIBC_2.38 __strlcpy_chk F
> +GLIBC_2.38 __wcslcat_chk F
> +GLIBC_2.38 __wcslcpy_chk F
>   GLIBC_2.38 strlcat F
>   GLIBC_2.38 strlcpy F
> +GLIBC_2.38 wcslcat F
> +GLIBC_2.38 wcslcpy F
> diff --git a/wcsmbs/Makefile b/wcsmbs/Makefile
> index 4aa43252d7..ac03437661 100644
> --- a/wcsmbs/Makefile
> +++ b/wcsmbs/Makefile
> @@ -66,6 +66,8 @@ routines := \
>     wcscpy \
>     wcscspn \
>     wcsdup \
> +  wcslcat \
> +  wcslcpy \
>     wcslen \
>     wcsmbsload \
>     wcsncase \
> @@ -146,6 +148,8 @@ tests := \
>     tst-wchar-h \
>     tst-wcpncpy \
>     tst-wcrtomb \
> +  tst-wcslcat \
> +  tst-wcslcpy \
>     tst-wcsnlen \
>     tst-wcstod-nan-locale \
>     tst-wcstod-nan-sign \
> diff --git a/wcsmbs/Versions b/wcsmbs/Versions
> index 2d9391348a..7bdfe43b4a 100644
> --- a/wcsmbs/Versions
> +++ b/wcsmbs/Versions
> @@ -65,5 +65,7 @@ libc {
>       __isoc23_vswscanf;
>       __isoc23_vwscanf;
>       __isoc23_wscanf;
> +    wcslcat;
> +    wcslcpy;
>     }
>   }
> diff --git a/wcsmbs/bits/wchar2.h b/wcsmbs/bits/wchar2.h
> index 8b41e6fbd6..02f44ab373 100644
> --- a/wcsmbs/bits/wchar2.h
> +++ b/wcsmbs/bits/wchar2.h
> @@ -199,6 +199,43 @@ __NTH (wcsncat (wchar_t *__restrict __dest, const wchar_t *__restrict __src,
>     return __wcsncat_alias (__dest, __src, __n);
>   }
>   
> +#ifdef __USE_MISC
> +extern size_t __wcslcpy_chk (wchar_t *__dest, const wchar_t *__src, size_t __n,
> +			     size_t __destlen) __THROW;
> +extern size_t __REDIRECT_NTH (__wcslcpy_alias,
> +			      (wchar_t *__dest, const wchar_t *__src,
> +			       size_t __n), wcslcpy);
> +
> +__fortify_function size_t
> +__NTH (wcslcpy (wchar_t *__restrict __dest, const wchar_t *__restrict __src,
> +		size_t __n))
> +{
> +  if (__glibc_objsize (__dest) != (size_t) -1
> +      && (!__builtin_constant_p (__n
> +				 > __glibc_objsize (__dest) / sizeof (wchar_t))
> +	  || __n > __glibc_objsize (__dest) / sizeof (wchar_t)))
> +    return __wcslcpy_chk (__dest, __src, __n, __glibc_objsize (__dest));
> +  return __wcslcpy_alias (__dest, __src, __n);
> +}
> +
> +extern size_t __wcslcat_chk (wchar_t *__dest, const wchar_t *__src, size_t __n,
> +			     size_t __destlen) __THROW;
> +extern size_t __REDIRECT_NTH (__wcslcat_alias,
> +			      (wchar_t *__dest, const wchar_t *__src,
> +			       size_t __n), wcslcat);
> +
> +__fortify_function size_t
> +__NTH (wcslcat (wchar_t *__restrict __dest, const wchar_t *__restrict __src,
> +		size_t __n))
> +{
> +  if (__glibc_objsize (__dest) != (size_t) -1
> +      && (!__builtin_constant_p (__n > __glibc_objsize (__dest)
> +				 / sizeof (wchar_t))
> +	  || __n > __glibc_objsize (__dest) / sizeof (wchar_t)))
> +    return __wcslcat_chk (__dest, __src, __n, __glibc_objsize (__dest));
> +  return __wcslcat_alias (__dest, __src, __n);
> +}
> +#endif /* __USE_MISC */
>   
>   
>   extern int __REDIRECT_NTH_LDBL (__swprintf_alias,
> diff --git a/wcsmbs/tst-wcslcat.c b/wcsmbs/tst-wcslcat.c
> new file mode 100644
> index 0000000000..63c3a164b5
> --- /dev/null
> +++ b/wcsmbs/tst-wcslcat.c
> @@ -0,0 +1,93 @@
> +/* Test the wcslcat function.
> +   Copyright (C) 2023 Free Software Foundation, Inc.
> +   This file is part of the GNU C Library.
> +
> +   The GNU C Library is free software; you can redistribute it and/or
> +   modify it under the terms of the GNU Lesser General Public
> +   License as published by the Free Software Foundation; either
> +   version 2.1 of the License, or (at your option) any later version.
> +
> +   The GNU C Library is distributed in the hope that it will be useful,
> +   but WITHOUT ANY WARRANTY; without even the implied warranty of
> +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> +   Lesser General Public License for more details.
> +
> +   You should have received a copy of the GNU Lesser General Public
> +   License along with the GNU C Library; if not, see
> +   <https://www.gnu.org/licenses/>.  */
> +
> +#include <array_length.h>
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <support/check.h>
> +#include <wchar.h>
> +
> +static int
> +do_test (void)
> +{
> +  struct {
> +    wchar_t buf1[16];
> +    wchar_t buf2[16];
> +  } s;
> +
> +  /* Nothing is written to the destination if its size is 0.  */
> +  wmemset (s.buf1, '@', array_length (s.buf1));
> +  wmemset (s.buf2, '@', array_length (s.buf2));
> +  TEST_COMPARE (wcslcat (s.buf1, L"", 0), 0);
> +  TEST_COMPARE_BLOB (&s, sizeof (s), L"@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@", 128);
> +  TEST_COMPARE (wcslcat (s.buf1, L"Hello!", 0), 6);
> +  TEST_COMPARE_BLOB (&s, sizeof (s), L"@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@", 128);
> +
> +  /* No bytes are are modified in the target buffer if the source
> +     string is short enough.  */
> +  wmemset (s.buf1, '@', array_length (s.buf1));
> +  wmemset (s.buf2, '@', array_length (s.buf2));
> +  wcscpy (s.buf1, L"He");
> +  TEST_COMPARE (wcslcat (s.buf1, L"llo!", array_length (s.buf1)), 6);
> +  TEST_COMPARE_BLOB (&s, sizeof (s), L"Hello!\0@@@@@@@@@@@@@@@@@@@@@@@@@", 128);
> +
> +  /* A source string which fits exactly into the destination buffer is
> +     not truncated.  */
> +  wmemset (s.buf1, '@', array_length (s.buf1));
> +  wmemset (s.buf2, '@', array_length (s.buf2));
> +  wcscpy (s.buf1, L"H");
> +  TEST_COMPARE (wcslcat (s.buf1, L"ello, world!!!", array_length (s.buf1)),
> +                15);
> +  TEST_COMPARE_BLOB (&s, sizeof (s),
> +		     L"Hello, world!!!\0@@@@@@@@@@@@@@@@@@@@@@@@@", 128);
> +
> +  /* A source string one character longer than the destination buffer
> +     is truncated by one character.  The total length is returned.  */
> +  wmemset (s.buf1, '@', array_length (s.buf1));
> +  wmemset (s.buf2, '@', array_length (s.buf2));
> +  wcscpy (s.buf1, L"Hello");
> +  TEST_COMPARE (wcslcat (s.buf1, L", world!!!!", array_length (s.buf1)), 16);
> +  TEST_COMPARE_BLOB (&s, sizeof (s),
> +		     L"Hello, world!!!\0@@@@@@@@@@@@@@@@@@@@@@@@@", 128);
> +
> +  /* An even longer source string is truncated as well, and the total
> +     length is returned.  */
> +  wmemset (s.buf1, '@', array_length (s.buf1));
> +  wmemset (s.buf2, '@', array_length (s.buf2));
> +  wcscpy (s.buf1, L"Hello,");
> +  TEST_COMPARE (wcslcat (s.buf1, L" world!!!!!!!!", array_length (s.buf1)),
> +                20);
> +  TEST_COMPARE_BLOB (&s, sizeof (s),
> +		     L"Hello, world!!!\0@@@@@@@@@@@@@@@@@@@@@@@@@", 128);
> +
> +  /* A destination string which is not NUL-terminated does not result
> +     in any changes to the buffer.  */
> +  wmemset (s.buf1, '$', array_length (s.buf1));
> +  wmemset (s.buf2, '@', array_length (s.buf2));
> +  TEST_COMPARE (wcslcat (s.buf1, L"", array_length (s.buf1)), 16);
> +  TEST_COMPARE_BLOB (&s, sizeof (s), L"$$$$$$$$$$$$$$$$@@@@@@@@@@@@@@@@", 128);
> +  TEST_COMPARE (wcslcat (s.buf1, L"Hello!", array_length (s.buf1)), 22);
> +  TEST_COMPARE_BLOB (&s, sizeof (s), L"$$$$$$$$$$$$$$$$@@@@@@@@@@@@@@@@", 128);
> +  TEST_COMPARE (wcslcat (s.buf1, L"Hello, world!!!!!!!!",
> +                         array_length (s.buf1)), 36);
> +  TEST_COMPARE_BLOB (&s, sizeof (s), L"$$$$$$$$$$$$$$$$@@@@@@@@@@@@@@@@", 128);
> +
> +  return 0;
> +}
> +
> +#include <support/test-driver.c>
> diff --git a/wcsmbs/tst-wcslcpy.c b/wcsmbs/tst-wcslcpy.c
> new file mode 100644
> index 0000000000..8eaffbf0c4
> --- /dev/null
> +++ b/wcsmbs/tst-wcslcpy.c
> @@ -0,0 +1,78 @@
> +/* Test the wcslcpy function.
> +   Copyright (C) 2023 Free Software Foundation, Inc.
> +   This file is part of the GNU C Library.
> +
> +   The GNU C Library is free software; you can redistribute it and/or
> +   modify it under the terms of the GNU Lesser General Public
> +   License as published by the Free Software Foundation; either
> +   version 2.1 of the License, or (at your option) any later version.
> +
> +   The GNU C Library is distributed in the hope that it will be useful,
> +   but WITHOUT ANY WARRANTY; without even the implied warranty of
> +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> +   Lesser General Public License for more details.
> +
> +   You should have received a copy of the GNU Lesser General Public
> +   License along with the GNU C Library; if not, see
> +   <https://www.gnu.org/licenses/>.  */
> +
> +#include <array_length.h>
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <support/check.h>
> +#include <wchar.h>
> +
> +static int
> +do_test (void)
> +{
> +  struct {
> +    wchar_t buf1[16];
> +    wchar_t buf2[16];
> +  } s;
> +
> +  /* Nothing is written to the destination if its size is 0.  */
> +  wmemset (s.buf1, '@', array_length (s.buf1));
> +  wmemset (s.buf2, '@', array_length (s.buf2));
> +  TEST_COMPARE (wcslcpy (s.buf1, L"Hello!", 0), 6);
> +  TEST_COMPARE_BLOB (&s, sizeof (s), L"@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@", 128);
> +
> +  /* No bytes are are modified in the target buffer if the source
> +     string is short enough.  */
> +  wmemset (s.buf1, '@', array_length (s.buf1));
> +  wmemset (s.buf2, '@', array_length (s.buf2));
> +  TEST_COMPARE (wcslcpy (s.buf1, L"Hello!", array_length (s.buf1)), 6);
> +  TEST_COMPARE_BLOB (&s, sizeof (s),
> +                     L"Hello!\0@@@@@@@@@@@@@@@@@@@@@@@@@", 128);
> +
> +  /* A source string which fits exactly into the destination buffer is
> +     not truncated.  */
> +  wmemset (s.buf1, '@', array_length (s.buf1));
> +  wmemset (s.buf2, '@', array_length (s.buf2));
> +  TEST_COMPARE (wcslcpy (s.buf1, L"Hello, world!!!", array_length (s.buf1)),
> +                15);
> +  TEST_COMPARE_BLOB (&s, sizeof (s),
> +                     L"Hello, world!!!\0@@@@@@@@@@@@@@@@@@@@@@@@@", 128);
> +
> +  /* A source string one character longer than the destination buffer
> +     is truncated by one character.  The untruncated source length is
> +     returned.  */
> +  wmemset (s.buf1, '@', array_length (s.buf1));
> +  wmemset (s.buf2, '@', array_length (s.buf2));
> +  TEST_COMPARE (wcslcpy (s.buf1, L"Hello, world!!!!", array_length (s.buf1)),
> +                16);
> +  TEST_COMPARE_BLOB (&s, sizeof (s),
> +                     L"Hello, world!!!\0@@@@@@@@@@@@@@@@@@@@@@@@@", 128);
> +
> +  /* An even longer source string is truncated as well, and the
> +     original length is returned.  */
> +  wmemset (s.buf1, '@', array_length (s.buf1));
> +  wmemset (s.buf2, '@', array_length (s.buf2));
> +  TEST_COMPARE (wcslcpy (s.buf1, L"Hello, world!!!!!!!!",
> +                         array_length (s.buf1)), 20);
> +  TEST_COMPARE_BLOB (&s, sizeof (s),
> +                     L"Hello, world!!!\0@@@@@@@@@@@@@@@@@@@@@@@@@", 128);
> +
> +  return 0;
> +}
> +
> +#include <support/test-driver.c>
> diff --git a/wcsmbs/wchar.h b/wcsmbs/wchar.h
> index acc2eb9ddf..6d15503830 100644
> --- a/wcsmbs/wchar.h
> +++ b/wcsmbs/wchar.h
> @@ -93,6 +93,19 @@ extern wchar_t *wcsncpy (wchar_t *__restrict __dest,
>   			 const wchar_t *__restrict __src, size_t __n)
>        __THROW __nonnull ((1, 2));
>   
> +#ifdef __USE_MISC
> +/* Copy at most N - 1 characters from SRC to DEST.  */
> +extern size_t wcslcpy (wchar_t *__restrict __dest,
> +		       const wchar_t *__restrict __src, size_t __n)
> +  __THROW __nonnull ((1, 2)) __attr_access ((__write_only__, 1, 3));
> +
> +/* Append SRC to DEST, possibly with truncation to keep the total size
> +   below N.  */
> +extern size_t wcslcat (wchar_t *__restrict __dest,
> +		       const wchar_t *__restrict __src, size_t __n)
> +  __THROW __nonnull ((1, 2))  __attr_access ((__read_write__, 1, 3));
> +#endif
> +
>   /* Append SRC onto DEST.  */
>   extern wchar_t *wcscat (wchar_t *__restrict __dest,
>   			const wchar_t *__restrict __src)
> diff --git a/wcsmbs/wcslcat.c b/wcsmbs/wcslcat.c
> new file mode 100644
> index 0000000000..3bac6a2aa0
> --- /dev/null
> +++ b/wcsmbs/wcslcat.c
> @@ -0,0 +1,60 @@
> +/* Append a null-terminated wide string to another, with length checking.
> +   Copyright (C) 2023 Free Software Foundation, Inc.
> +   This file is part of the GNU C Library.
> +
> +   The GNU C Library is free software; you can redistribute it and/or
> +   modify it under the terms of the GNU Lesser General Public
> +   License as published by the Free Software Foundation; either
> +   version 2.1 of the License, or (at your option) any later version.
> +
> +   The GNU C Library is distributed in the hope that it will be useful,
> +   but WITHOUT ANY WARRANTY; without even the implied warranty of
> +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> +   Lesser General Public License for more details.
> +
> +   You should have received a copy of the GNU Lesser General Public
> +   License along with the GNU C Library; if not, see
> +   <https://www.gnu.org/licenses/>.  */
> +
> +#include <stdint.h>
> +#include <wchar.h>
> +
> +size_t
> +__wcslcat (wchar_t *__restrict dest, const wchar_t *__restrict src,
> +           size_t size)
> +{
> +  size_t src_length = __wcslen (src);
> +
> +  /* Our implementation strlcat supports dest == NULL if size == 0
> +     (for consistency with snprintf and strlcpy), but wcsnlen does
> +     not, so we have to cover this case explicitly.  */
> +  if (size == 0)
> +    return src_length;
> +
> +  size_t dest_length = __wcsnlen (dest, size);
> +  if (dest_length != size)
> +    {
> +      /* Copy at most the remaining number of characters in the
> +	 destination buffer.  Leave for the null terminator.  */
> +      size_t to_copy = size - dest_length - 1;
> +      /* But not more than what is available in the source string.  */
> +      if (to_copy > src_length)
> +	to_copy = src_length;
> +
> +      wchar_t *target = dest + dest_length;
> +      __wmemcpy (target, src, to_copy);
> +      target[to_copy] = '\0';
> +    }
> +
> +  /* If the sum wraps around, we have more than SIZE_MAX + 2 bytes in
> +     the two input strings (including both null terminators).  If each
> +     byte in the address space can be assigned a unique size_t value
> +     (which the static_assert checks), then by the pigeonhole
> +     principle, the two input strings must overlap, which is
> +     undefined.  */
> +  _Static_assert (sizeof (uintptr_t) == sizeof (size_t),
> +		  "theoretical maximum object size covers address space");
> +  return dest_length + src_length;
> +}
> +libc_hidden_def (__wcslcat)
> +weak_alias (__wcslcat, wcslcat)
> diff --git a/wcsmbs/wcslcpy.c b/wcsmbs/wcslcpy.c
> new file mode 100644
> index 0000000000..a1b1f1b43f
> --- /dev/null
> +++ b/wcsmbs/wcslcpy.c
> @@ -0,0 +1,46 @@
> +/* Copy a null-terminated wide string to a fixed-size buffer.
> +   Copyright (C) 2023 Free Software Foundation, Inc.
> +   This file is part of the GNU C Library.
> +
> +   The GNU C Library is free software; you can redistribute it and/or
> +   modify it under the terms of the GNU Lesser General Public
> +   License as published by the Free Software Foundation; either
> +   version 2.1 of the License, or (at your option) any later version.
> +
> +   The GNU C Library is distributed in the hope that it will be useful,
> +   but WITHOUT ANY WARRANTY; without even the implied warranty of
> +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> +   Lesser General Public License for more details.
> +
> +   You should have received a copy of the GNU Lesser General Public
> +   License along with the GNU C Library; if not, see
> +   <http://www.gnu.org/licenses/>.  */
> +
> +#include <wchar.h>
> +
> +size_t
> +__wcslcpy (wchar_t *__restrict dest, const wchar_t *__restrict src, size_t size)
> +{
> +  size_t src_length = __wcslen (src);
> +
> +  if (__glibc_unlikely (src_length >= size))
> +    {
> +      if (size > 0)
> +	{
> +	  /* Copy the leading portion of the string.  The last
> +	     character is subsequently overwritten with the null
> +	     terminator, but the destination size is usually a
> +	     multiple of a small power of two, so writing it twice
> +	     should be more efficient than copying an odd number of
> +	     character.  */
> +	  __wmemcpy (dest, src, size);
> +	  dest[size - 1] = '\0';
> +	}
> +    }
> +  else
> +    /* Copy the string and its terminating null character.  */
> +    __wmemcpy (dest, src, src_length + 1);
> +  return src_length;
> +}
> +libc_hidden_def (__wcslcpy)
> +weak_alias (__wcslcpy, wcslcpy)
diff mbox series

Patch

diff --git a/debug/Makefile b/debug/Makefile
index f5f27f793c..4f018abecf 100644
--- a/debug/Makefile
+++ b/debug/Makefile
@@ -104,6 +104,8 @@  routines = \
   wcrtomb_chk \
   wcscat_chk \
   wcscpy_chk \
+  wcslcat_chk \
+  wcslcpy_chk \
   wcsncat_chk \
   wcsncpy_chk \
   wcsnrtombs_chk \
diff --git a/debug/Versions b/debug/Versions
index 94dfa5f428..9cf2725992 100644
--- a/debug/Versions
+++ b/debug/Versions
@@ -61,6 +61,8 @@  libc {
   GLIBC_2.38 {
     __strlcat_chk;
     __strlcpy_chk;
+    __wcslcat_chk;
+    __wcslcpy_chk;
   }
   GLIBC_PRIVATE {
     __fortify_fail;
diff --git a/debug/tst-fortify.c b/debug/tst-fortify.c
index f74a5e04dc..9fe20627ee 100644
--- a/debug/tst-fortify.c
+++ b/debug/tst-fortify.c
@@ -782,6 +782,18 @@  do_test (void)
   wcsncpy (wbuf + 9, L"XABCDEFGH", 8);
   CHK_FAIL_END
 
+  CHK_FAIL_START
+  wcslcpy (wbuf + 7, L"X", 4);
+  CHK_FAIL_END
+
+  CHK_FAIL_START
+  wcslcpy (wbuf + 7, L"X", l0 + 4);
+  CHK_FAIL_END
+
+  CHK_FAIL_START
+  wcslcpy (wbuf + 9, L"XABCDEFGH", 8);
+  CHK_FAIL_END
+
   CHK_FAIL_START
   wcpncpy (wbuf + 9, L"XABCDEFGH", 8);
   CHK_FAIL_END
@@ -804,6 +816,11 @@  do_test (void)
   wcsncat (wbuf, L"ZYXWV", l0 + 3);
   CHK_FAIL_END
 
+  wmemcpy (wbuf, wstr1 + 4, 7);
+  CHK_FAIL_START
+  wcslcat (wbuf, L"ZYXWV", l0 + 11);
+  CHK_FAIL_END
+
   CHK_FAIL_START
   wmemcpy (wa.buf1 + 1, L"abcdefghij", 10);
   CHK_FAIL_END
diff --git a/debug/wcslcat_chk.c b/debug/wcslcat_chk.c
new file mode 100644
index 0000000000..5d63fba076
--- /dev/null
+++ b/debug/wcslcat_chk.c
@@ -0,0 +1,31 @@ 
+/* Fortified version of wcslcat.
+   Copyright (C) 2023 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <https://www.gnu.org/licenses/>.  */
+
+#include <wchar.h>
+
+/* Check that the user-supplied size does not exceed the
+   compiler-determined size, and then forward to wcslcat.  */
+size_t
+__wcslcat_chk (wchar_t *__restrict s1, const wchar_t *__restrict s2,
+               size_t n, size_t s1len)
+{
+  if (__glibc_unlikely (s1len / sizeof (wchar_t) < n))
+    __chk_fail ();
+
+  return __wcslcat (s1, s2, n);
+}
diff --git a/debug/wcslcpy_chk.c b/debug/wcslcpy_chk.c
new file mode 100644
index 0000000000..ff7434b59a
--- /dev/null
+++ b/debug/wcslcpy_chk.c
@@ -0,0 +1,31 @@ 
+/* Fortified version of wcslcpy.
+   Copyright (C) 2023 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <https://www.gnu.org/licenses/>.  */
+
+#include <wchar.h>
+
+/* Check that the user-supplied size does not exceed the
+   compiler-determined size, and then forward to wcslcpy.  */
+size_t
+__wcslcpy_chk (wchar_t *__restrict s1, const wchar_t *__restrict s2,
+	       size_t n, size_t s1len)
+{
+  if (__glibc_unlikely (s1len / sizeof (wchar_t) < n))
+    __chk_fail ();
+
+  return __wcslcpy (s1, s2, n);
+}
diff --git a/include/wchar.h b/include/wchar.h
index fafe7c8e9b..ff4ae52045 100644
--- a/include/wchar.h
+++ b/include/wchar.h
@@ -203,6 +203,8 @@  extern size_t __wcslen (const wchar_t *__s) __attribute_pure__;
 extern size_t __wcsnlen (const wchar_t *__s, size_t __maxlen)
      __attribute_pure__;
 extern wchar_t *__wcscat (wchar_t *dest, const wchar_t *src);
+extern __typeof (wcslcat) __wcslcat;
+libc_hidden_proto (__wcslcat)
 extern wint_t __btowc (int __c) attribute_hidden;
 extern int __mbsinit (const __mbstate_t *__ps);
 extern size_t __mbrtowc (wchar_t *__restrict __pwc,
@@ -237,8 +239,11 @@  extern wchar_t *__wcscpy (wchar_t *__restrict __dest,
 			  const wchar_t *__restrict __src)
 			  attribute_hidden __nonnull ((1, 2));
 libc_hidden_proto (__wcscpy)
+extern __typeof (wcslcpy) __wcslcpy;
+libc_hidden_proto (__wcslcpy)
 extern wchar_t *__wcsncpy (wchar_t *__restrict __dest,
 			   const wchar_t *__restrict __src, size_t __n);
+
 extern wchar_t *__wcpcpy (wchar_t *__dest, const wchar_t *__src);
 extern wchar_t *__wcpncpy (wchar_t *__dest, const wchar_t *__src,
 			   size_t __n);
diff --git a/sysdeps/mach/hurd/i386/libc.abilist b/sysdeps/mach/hurd/i386/libc.abilist
index 9b4c5f4719..74a9f427b2 100644
--- a/sysdeps/mach/hurd/i386/libc.abilist
+++ b/sysdeps/mach/hurd/i386/libc.abilist
@@ -2328,8 +2328,12 @@  GLIBC_2.38 __isoc23_wcstoumax F
 GLIBC_2.38 __isoc23_wscanf F
 GLIBC_2.38 __strlcat_chk F
 GLIBC_2.38 __strlcpy_chk F
+GLIBC_2.38 __wcslcat_chk F
+GLIBC_2.38 __wcslcpy_chk F
 GLIBC_2.38 strlcat F
 GLIBC_2.38 strlcpy F
+GLIBC_2.38 wcslcat F
+GLIBC_2.38 wcslcpy F
 GLIBC_2.4 __confstr_chk F
 GLIBC_2.4 __fgets_chk F
 GLIBC_2.4 __fgets_unlocked_chk F
diff --git a/sysdeps/unix/sysv/linux/aarch64/libc.abilist b/sysdeps/unix/sysv/linux/aarch64/libc.abilist
index cf51b88932..c49363e70e 100644
--- a/sysdeps/unix/sysv/linux/aarch64/libc.abilist
+++ b/sysdeps/unix/sysv/linux/aarch64/libc.abilist
@@ -2667,5 +2667,9 @@  GLIBC_2.38 __isoc23_wcstoumax F
 GLIBC_2.38 __isoc23_wscanf F
 GLIBC_2.38 __strlcat_chk F
 GLIBC_2.38 __strlcpy_chk F
+GLIBC_2.38 __wcslcat_chk F
+GLIBC_2.38 __wcslcpy_chk F
 GLIBC_2.38 strlcat F
 GLIBC_2.38 strlcpy F
+GLIBC_2.38 wcslcat F
+GLIBC_2.38 wcslcpy F
diff --git a/sysdeps/unix/sysv/linux/alpha/libc.abilist b/sysdeps/unix/sysv/linux/alpha/libc.abilist
index 4b25f343b8..d6b1dcaae6 100644
--- a/sysdeps/unix/sysv/linux/alpha/libc.abilist
+++ b/sysdeps/unix/sysv/linux/alpha/libc.abilist
@@ -2776,8 +2776,12 @@  GLIBC_2.38 __nldbl___isoc23_vwscanf F
 GLIBC_2.38 __nldbl___isoc23_wscanf F
 GLIBC_2.38 __strlcat_chk F
 GLIBC_2.38 __strlcpy_chk F
+GLIBC_2.38 __wcslcat_chk F
+GLIBC_2.38 __wcslcpy_chk F
 GLIBC_2.38 strlcat F
 GLIBC_2.38 strlcpy F
+GLIBC_2.38 wcslcat F
+GLIBC_2.38 wcslcpy F
 GLIBC_2.4 _IO_fprintf F
 GLIBC_2.4 _IO_printf F
 GLIBC_2.4 _IO_sprintf F
diff --git a/sysdeps/unix/sysv/linux/arc/libc.abilist b/sysdeps/unix/sysv/linux/arc/libc.abilist
index 5a58cc0477..dfe0c3f7b6 100644
--- a/sysdeps/unix/sysv/linux/arc/libc.abilist
+++ b/sysdeps/unix/sysv/linux/arc/libc.abilist
@@ -2428,5 +2428,9 @@  GLIBC_2.38 __isoc23_wcstoumax F
 GLIBC_2.38 __isoc23_wscanf F
 GLIBC_2.38 __strlcat_chk F
 GLIBC_2.38 __strlcpy_chk F
+GLIBC_2.38 __wcslcat_chk F
+GLIBC_2.38 __wcslcpy_chk F
 GLIBC_2.38 strlcat F
 GLIBC_2.38 strlcpy F
+GLIBC_2.38 wcslcat F
+GLIBC_2.38 wcslcpy F
diff --git a/sysdeps/unix/sysv/linux/arm/be/libc.abilist b/sysdeps/unix/sysv/linux/arm/be/libc.abilist
index 99ce948c5c..6c75e5aa76 100644
--- a/sysdeps/unix/sysv/linux/arm/be/libc.abilist
+++ b/sysdeps/unix/sysv/linux/arm/be/libc.abilist
@@ -548,8 +548,12 @@  GLIBC_2.38 __isoc23_wcstoumax F
 GLIBC_2.38 __isoc23_wscanf F
 GLIBC_2.38 __strlcat_chk F
 GLIBC_2.38 __strlcpy_chk F
+GLIBC_2.38 __wcslcat_chk F
+GLIBC_2.38 __wcslcpy_chk F
 GLIBC_2.38 strlcat F
 GLIBC_2.38 strlcpy F
+GLIBC_2.38 wcslcat F
+GLIBC_2.38 wcslcpy F
 GLIBC_2.4 _Exit F
 GLIBC_2.4 _IO_2_1_stderr_ D 0xa0
 GLIBC_2.4 _IO_2_1_stdin_ D 0xa0
diff --git a/sysdeps/unix/sysv/linux/arm/le/libc.abilist b/sysdeps/unix/sysv/linux/arm/le/libc.abilist
index c00bf72ebc..03d6f7ae2d 100644
--- a/sysdeps/unix/sysv/linux/arm/le/libc.abilist
+++ b/sysdeps/unix/sysv/linux/arm/le/libc.abilist
@@ -545,8 +545,12 @@  GLIBC_2.38 __isoc23_wcstoumax F
 GLIBC_2.38 __isoc23_wscanf F
 GLIBC_2.38 __strlcat_chk F
 GLIBC_2.38 __strlcpy_chk F
+GLIBC_2.38 __wcslcat_chk F
+GLIBC_2.38 __wcslcpy_chk F
 GLIBC_2.38 strlcat F
 GLIBC_2.38 strlcpy F
+GLIBC_2.38 wcslcat F
+GLIBC_2.38 wcslcpy F
 GLIBC_2.4 _Exit F
 GLIBC_2.4 _IO_2_1_stderr_ D 0xa0
 GLIBC_2.4 _IO_2_1_stdin_ D 0xa0
diff --git a/sysdeps/unix/sysv/linux/csky/libc.abilist b/sysdeps/unix/sysv/linux/csky/libc.abilist
index 71130f2c6b..d858c108c6 100644
--- a/sysdeps/unix/sysv/linux/csky/libc.abilist
+++ b/sysdeps/unix/sysv/linux/csky/libc.abilist
@@ -2704,5 +2704,9 @@  GLIBC_2.38 __isoc23_wcstoumax F
 GLIBC_2.38 __isoc23_wscanf F
 GLIBC_2.38 __strlcat_chk F
 GLIBC_2.38 __strlcpy_chk F
+GLIBC_2.38 __wcslcat_chk F
+GLIBC_2.38 __wcslcpy_chk F
 GLIBC_2.38 strlcat F
 GLIBC_2.38 strlcpy F
+GLIBC_2.38 wcslcat F
+GLIBC_2.38 wcslcpy F
diff --git a/sysdeps/unix/sysv/linux/hppa/libc.abilist b/sysdeps/unix/sysv/linux/hppa/libc.abilist
index 5a651c03df..82a14f8ace 100644
--- a/sysdeps/unix/sysv/linux/hppa/libc.abilist
+++ b/sysdeps/unix/sysv/linux/hppa/libc.abilist
@@ -2653,8 +2653,12 @@  GLIBC_2.38 __isoc23_wcstoumax F
 GLIBC_2.38 __isoc23_wscanf F
 GLIBC_2.38 __strlcat_chk F
 GLIBC_2.38 __strlcpy_chk F
+GLIBC_2.38 __wcslcat_chk F
+GLIBC_2.38 __wcslcpy_chk F
 GLIBC_2.38 strlcat F
 GLIBC_2.38 strlcpy F
+GLIBC_2.38 wcslcat F
+GLIBC_2.38 wcslcpy F
 GLIBC_2.4 __confstr_chk F
 GLIBC_2.4 __fgets_chk F
 GLIBC_2.4 __fgets_unlocked_chk F
diff --git a/sysdeps/unix/sysv/linux/i386/libc.abilist b/sysdeps/unix/sysv/linux/i386/libc.abilist
index 12b91ef632..1950b15d5d 100644
--- a/sysdeps/unix/sysv/linux/i386/libc.abilist
+++ b/sysdeps/unix/sysv/linux/i386/libc.abilist
@@ -2837,8 +2837,12 @@  GLIBC_2.38 __isoc23_wcstoumax F
 GLIBC_2.38 __isoc23_wscanf F
 GLIBC_2.38 __strlcat_chk F
 GLIBC_2.38 __strlcpy_chk F
+GLIBC_2.38 __wcslcat_chk F
+GLIBC_2.38 __wcslcpy_chk F
 GLIBC_2.38 strlcat F
 GLIBC_2.38 strlcpy F
+GLIBC_2.38 wcslcat F
+GLIBC_2.38 wcslcpy F
 GLIBC_2.4 __confstr_chk F
 GLIBC_2.4 __fgets_chk F
 GLIBC_2.4 __fgets_unlocked_chk F
diff --git a/sysdeps/unix/sysv/linux/ia64/libc.abilist b/sysdeps/unix/sysv/linux/ia64/libc.abilist
index f223c5e08d..d0b9cb279b 100644
--- a/sysdeps/unix/sysv/linux/ia64/libc.abilist
+++ b/sysdeps/unix/sysv/linux/ia64/libc.abilist
@@ -2602,8 +2602,12 @@  GLIBC_2.38 __isoc23_wcstoumax F
 GLIBC_2.38 __isoc23_wscanf F
 GLIBC_2.38 __strlcat_chk F
 GLIBC_2.38 __strlcpy_chk F
+GLIBC_2.38 __wcslcat_chk F
+GLIBC_2.38 __wcslcpy_chk F
 GLIBC_2.38 strlcat F
 GLIBC_2.38 strlcpy F
+GLIBC_2.38 wcslcat F
+GLIBC_2.38 wcslcpy F
 GLIBC_2.4 __confstr_chk F
 GLIBC_2.4 __fgets_chk F
 GLIBC_2.4 __fgets_unlocked_chk F
diff --git a/sysdeps/unix/sysv/linux/loongarch/lp64/libc.abilist b/sysdeps/unix/sysv/linux/loongarch/lp64/libc.abilist
index b91ed6e704..e760a631dd 100644
--- a/sysdeps/unix/sysv/linux/loongarch/lp64/libc.abilist
+++ b/sysdeps/unix/sysv/linux/loongarch/lp64/libc.abilist
@@ -2188,5 +2188,9 @@  GLIBC_2.38 __isoc23_wcstoumax F
 GLIBC_2.38 __isoc23_wscanf F
 GLIBC_2.38 __strlcat_chk F
 GLIBC_2.38 __strlcpy_chk F
+GLIBC_2.38 __wcslcat_chk F
+GLIBC_2.38 __wcslcpy_chk F
 GLIBC_2.38 strlcat F
 GLIBC_2.38 strlcpy F
+GLIBC_2.38 wcslcat F
+GLIBC_2.38 wcslcpy F
diff --git a/sysdeps/unix/sysv/linux/m68k/coldfire/libc.abilist b/sysdeps/unix/sysv/linux/m68k/coldfire/libc.abilist
index 0d91d7f1ae..35785a3d5f 100644
--- a/sysdeps/unix/sysv/linux/m68k/coldfire/libc.abilist
+++ b/sysdeps/unix/sysv/linux/m68k/coldfire/libc.abilist
@@ -549,8 +549,12 @@  GLIBC_2.38 __isoc23_wcstoumax F
 GLIBC_2.38 __isoc23_wscanf F
 GLIBC_2.38 __strlcat_chk F
 GLIBC_2.38 __strlcpy_chk F
+GLIBC_2.38 __wcslcat_chk F
+GLIBC_2.38 __wcslcpy_chk F
 GLIBC_2.38 strlcat F
 GLIBC_2.38 strlcpy F
+GLIBC_2.38 wcslcat F
+GLIBC_2.38 wcslcpy F
 GLIBC_2.4 _Exit F
 GLIBC_2.4 _IO_2_1_stderr_ D 0x98
 GLIBC_2.4 _IO_2_1_stdin_ D 0x98
diff --git a/sysdeps/unix/sysv/linux/m68k/m680x0/libc.abilist b/sysdeps/unix/sysv/linux/m68k/m680x0/libc.abilist
index e87b22747a..4ab2426e0a 100644
--- a/sysdeps/unix/sysv/linux/m68k/m680x0/libc.abilist
+++ b/sysdeps/unix/sysv/linux/m68k/m680x0/libc.abilist
@@ -2780,8 +2780,12 @@  GLIBC_2.38 __isoc23_wcstoumax F
 GLIBC_2.38 __isoc23_wscanf F
 GLIBC_2.38 __strlcat_chk F
 GLIBC_2.38 __strlcpy_chk F
+GLIBC_2.38 __wcslcat_chk F
+GLIBC_2.38 __wcslcpy_chk F
 GLIBC_2.38 strlcat F
 GLIBC_2.38 strlcpy F
+GLIBC_2.38 wcslcat F
+GLIBC_2.38 wcslcpy F
 GLIBC_2.4 __confstr_chk F
 GLIBC_2.4 __fgets_chk F
 GLIBC_2.4 __fgets_unlocked_chk F
diff --git a/sysdeps/unix/sysv/linux/microblaze/be/libc.abilist b/sysdeps/unix/sysv/linux/microblaze/be/libc.abilist
index f7623d6d72..38faa16232 100644
--- a/sysdeps/unix/sysv/linux/microblaze/be/libc.abilist
+++ b/sysdeps/unix/sysv/linux/microblaze/be/libc.abilist
@@ -2753,5 +2753,9 @@  GLIBC_2.38 __isoc23_wcstoumax F
 GLIBC_2.38 __isoc23_wscanf F
 GLIBC_2.38 __strlcat_chk F
 GLIBC_2.38 __strlcpy_chk F
+GLIBC_2.38 __wcslcat_chk F
+GLIBC_2.38 __wcslcpy_chk F
 GLIBC_2.38 strlcat F
 GLIBC_2.38 strlcpy F
+GLIBC_2.38 wcslcat F
+GLIBC_2.38 wcslcpy F
diff --git a/sysdeps/unix/sysv/linux/microblaze/le/libc.abilist b/sysdeps/unix/sysv/linux/microblaze/le/libc.abilist
index 298aa99b42..374d658988 100644
--- a/sysdeps/unix/sysv/linux/microblaze/le/libc.abilist
+++ b/sysdeps/unix/sysv/linux/microblaze/le/libc.abilist
@@ -2750,5 +2750,9 @@  GLIBC_2.38 __isoc23_wcstoumax F
 GLIBC_2.38 __isoc23_wscanf F
 GLIBC_2.38 __strlcat_chk F
 GLIBC_2.38 __strlcpy_chk F
+GLIBC_2.38 __wcslcat_chk F
+GLIBC_2.38 __wcslcpy_chk F
 GLIBC_2.38 strlcat F
 GLIBC_2.38 strlcpy F
+GLIBC_2.38 wcslcat F
+GLIBC_2.38 wcslcpy F
diff --git a/sysdeps/unix/sysv/linux/mips/mips32/fpu/libc.abilist b/sysdeps/unix/sysv/linux/mips/mips32/fpu/libc.abilist
index f83bdc50cd..fcc5e88e91 100644
--- a/sysdeps/unix/sysv/linux/mips/mips32/fpu/libc.abilist
+++ b/sysdeps/unix/sysv/linux/mips/mips32/fpu/libc.abilist
@@ -2745,8 +2745,12 @@  GLIBC_2.38 __isoc23_wcstoumax F
 GLIBC_2.38 __isoc23_wscanf F
 GLIBC_2.38 __strlcat_chk F
 GLIBC_2.38 __strlcpy_chk F
+GLIBC_2.38 __wcslcat_chk F
+GLIBC_2.38 __wcslcpy_chk F
 GLIBC_2.38 strlcat F
 GLIBC_2.38 strlcpy F
+GLIBC_2.38 wcslcat F
+GLIBC_2.38 wcslcpy F
 GLIBC_2.4 __confstr_chk F
 GLIBC_2.4 __fgets_chk F
 GLIBC_2.4 __fgets_unlocked_chk F
diff --git a/sysdeps/unix/sysv/linux/mips/mips32/nofpu/libc.abilist b/sysdeps/unix/sysv/linux/mips/mips32/nofpu/libc.abilist
index 611ece2ac4..01eb96cd93 100644
--- a/sysdeps/unix/sysv/linux/mips/mips32/nofpu/libc.abilist
+++ b/sysdeps/unix/sysv/linux/mips/mips32/nofpu/libc.abilist
@@ -2743,8 +2743,12 @@  GLIBC_2.38 __isoc23_wcstoumax F
 GLIBC_2.38 __isoc23_wscanf F
 GLIBC_2.38 __strlcat_chk F
 GLIBC_2.38 __strlcpy_chk F
+GLIBC_2.38 __wcslcat_chk F
+GLIBC_2.38 __wcslcpy_chk F
 GLIBC_2.38 strlcat F
 GLIBC_2.38 strlcpy F
+GLIBC_2.38 wcslcat F
+GLIBC_2.38 wcslcpy F
 GLIBC_2.4 __confstr_chk F
 GLIBC_2.4 __fgets_chk F
 GLIBC_2.4 __fgets_unlocked_chk F
diff --git a/sysdeps/unix/sysv/linux/mips/mips64/n32/libc.abilist b/sysdeps/unix/sysv/linux/mips/mips64/n32/libc.abilist
index 0af286fda1..a2748b7b74 100644
--- a/sysdeps/unix/sysv/linux/mips/mips64/n32/libc.abilist
+++ b/sysdeps/unix/sysv/linux/mips/mips64/n32/libc.abilist
@@ -2751,8 +2751,12 @@  GLIBC_2.38 __isoc23_wcstoumax F
 GLIBC_2.38 __isoc23_wscanf F
 GLIBC_2.38 __strlcat_chk F
 GLIBC_2.38 __strlcpy_chk F
+GLIBC_2.38 __wcslcat_chk F
+GLIBC_2.38 __wcslcpy_chk F
 GLIBC_2.38 strlcat F
 GLIBC_2.38 strlcpy F
+GLIBC_2.38 wcslcat F
+GLIBC_2.38 wcslcpy F
 GLIBC_2.4 __confstr_chk F
 GLIBC_2.4 __fgets_chk F
 GLIBC_2.4 __fgets_unlocked_chk F
diff --git a/sysdeps/unix/sysv/linux/mips/mips64/n64/libc.abilist b/sysdeps/unix/sysv/linux/mips/mips64/n64/libc.abilist
index 8285f2196e..0ae7ba499d 100644
--- a/sysdeps/unix/sysv/linux/mips/mips64/n64/libc.abilist
+++ b/sysdeps/unix/sysv/linux/mips/mips64/n64/libc.abilist
@@ -2653,8 +2653,12 @@  GLIBC_2.38 __isoc23_wcstoumax F
 GLIBC_2.38 __isoc23_wscanf F
 GLIBC_2.38 __strlcat_chk F
 GLIBC_2.38 __strlcpy_chk F
+GLIBC_2.38 __wcslcat_chk F
+GLIBC_2.38 __wcslcpy_chk F
 GLIBC_2.38 strlcat F
 GLIBC_2.38 strlcpy F
+GLIBC_2.38 wcslcat F
+GLIBC_2.38 wcslcpy F
 GLIBC_2.4 __confstr_chk F
 GLIBC_2.4 __fgets_chk F
 GLIBC_2.4 __fgets_unlocked_chk F
diff --git a/sysdeps/unix/sysv/linux/nios2/libc.abilist b/sysdeps/unix/sysv/linux/nios2/libc.abilist
index c7144d7cd8..947495a0e2 100644
--- a/sysdeps/unix/sysv/linux/nios2/libc.abilist
+++ b/sysdeps/unix/sysv/linux/nios2/libc.abilist
@@ -2792,5 +2792,9 @@  GLIBC_2.38 __isoc23_wcstoumax F
 GLIBC_2.38 __isoc23_wscanf F
 GLIBC_2.38 __strlcat_chk F
 GLIBC_2.38 __strlcpy_chk F
+GLIBC_2.38 __wcslcat_chk F
+GLIBC_2.38 __wcslcpy_chk F
 GLIBC_2.38 strlcat F
 GLIBC_2.38 strlcpy F
+GLIBC_2.38 wcslcat F
+GLIBC_2.38 wcslcpy F
diff --git a/sysdeps/unix/sysv/linux/or1k/libc.abilist b/sysdeps/unix/sysv/linux/or1k/libc.abilist
index bb43247795..115f1039e7 100644
--- a/sysdeps/unix/sysv/linux/or1k/libc.abilist
+++ b/sysdeps/unix/sysv/linux/or1k/libc.abilist
@@ -2174,5 +2174,9 @@  GLIBC_2.38 __isoc23_wcstoumax F
 GLIBC_2.38 __isoc23_wscanf F
 GLIBC_2.38 __strlcat_chk F
 GLIBC_2.38 __strlcpy_chk F
+GLIBC_2.38 __wcslcat_chk F
+GLIBC_2.38 __wcslcpy_chk F
 GLIBC_2.38 strlcat F
 GLIBC_2.38 strlcpy F
+GLIBC_2.38 wcslcat F
+GLIBC_2.38 wcslcpy F
diff --git a/sysdeps/unix/sysv/linux/powerpc/powerpc32/fpu/libc.abilist b/sysdeps/unix/sysv/linux/powerpc/powerpc32/fpu/libc.abilist
index 7cc5660830..19c4c325b0 100644
--- a/sysdeps/unix/sysv/linux/powerpc/powerpc32/fpu/libc.abilist
+++ b/sysdeps/unix/sysv/linux/powerpc/powerpc32/fpu/libc.abilist
@@ -2819,8 +2819,12 @@  GLIBC_2.38 __nldbl___isoc23_vwscanf F
 GLIBC_2.38 __nldbl___isoc23_wscanf F
 GLIBC_2.38 __strlcat_chk F
 GLIBC_2.38 __strlcpy_chk F
+GLIBC_2.38 __wcslcat_chk F
+GLIBC_2.38 __wcslcpy_chk F
 GLIBC_2.38 strlcat F
 GLIBC_2.38 strlcpy F
+GLIBC_2.38 wcslcat F
+GLIBC_2.38 wcslcpy F
 GLIBC_2.4 _IO_fprintf F
 GLIBC_2.4 _IO_printf F
 GLIBC_2.4 _IO_sprintf F
diff --git a/sysdeps/unix/sysv/linux/powerpc/powerpc32/nofpu/libc.abilist b/sysdeps/unix/sysv/linux/powerpc/powerpc32/nofpu/libc.abilist
index dd290af782..3e043c4044 100644
--- a/sysdeps/unix/sysv/linux/powerpc/powerpc32/nofpu/libc.abilist
+++ b/sysdeps/unix/sysv/linux/powerpc/powerpc32/nofpu/libc.abilist
@@ -2852,8 +2852,12 @@  GLIBC_2.38 __nldbl___isoc23_vwscanf F
 GLIBC_2.38 __nldbl___isoc23_wscanf F
 GLIBC_2.38 __strlcat_chk F
 GLIBC_2.38 __strlcpy_chk F
+GLIBC_2.38 __wcslcat_chk F
+GLIBC_2.38 __wcslcpy_chk F
 GLIBC_2.38 strlcat F
 GLIBC_2.38 strlcpy F
+GLIBC_2.38 wcslcat F
+GLIBC_2.38 wcslcpy F
 GLIBC_2.4 _IO_fprintf F
 GLIBC_2.4 _IO_printf F
 GLIBC_2.4 _IO_sprintf F
diff --git a/sysdeps/unix/sysv/linux/powerpc/powerpc64/be/libc.abilist b/sysdeps/unix/sysv/linux/powerpc/powerpc64/be/libc.abilist
index f2b001402c..e4f3a766bb 100644
--- a/sysdeps/unix/sysv/linux/powerpc/powerpc64/be/libc.abilist
+++ b/sysdeps/unix/sysv/linux/powerpc/powerpc64/be/libc.abilist
@@ -2573,8 +2573,12 @@  GLIBC_2.38 __nldbl___isoc23_vwscanf F
 GLIBC_2.38 __nldbl___isoc23_wscanf F
 GLIBC_2.38 __strlcat_chk F
 GLIBC_2.38 __strlcpy_chk F
+GLIBC_2.38 __wcslcat_chk F
+GLIBC_2.38 __wcslcpy_chk F
 GLIBC_2.38 strlcat F
 GLIBC_2.38 strlcpy F
+GLIBC_2.38 wcslcat F
+GLIBC_2.38 wcslcpy F
 GLIBC_2.4 _IO_fprintf F
 GLIBC_2.4 _IO_printf F
 GLIBC_2.4 _IO_sprintf F
diff --git a/sysdeps/unix/sysv/linux/powerpc/powerpc64/le/libc.abilist b/sysdeps/unix/sysv/linux/powerpc/powerpc64/le/libc.abilist
index 9cc431666e..dafe1c4a59 100644
--- a/sysdeps/unix/sysv/linux/powerpc/powerpc64/le/libc.abilist
+++ b/sysdeps/unix/sysv/linux/powerpc/powerpc64/le/libc.abilist
@@ -2887,5 +2887,9 @@  GLIBC_2.38 __nldbl___isoc23_vwscanf F
 GLIBC_2.38 __nldbl___isoc23_wscanf F
 GLIBC_2.38 __strlcat_chk F
 GLIBC_2.38 __strlcpy_chk F
+GLIBC_2.38 __wcslcat_chk F
+GLIBC_2.38 __wcslcpy_chk F
 GLIBC_2.38 strlcat F
 GLIBC_2.38 strlcpy F
+GLIBC_2.38 wcslcat F
+GLIBC_2.38 wcslcpy F
diff --git a/sysdeps/unix/sysv/linux/riscv/rv32/libc.abilist b/sysdeps/unix/sysv/linux/riscv/rv32/libc.abilist
index b9b725f913..b9740a1afc 100644
--- a/sysdeps/unix/sysv/linux/riscv/rv32/libc.abilist
+++ b/sysdeps/unix/sysv/linux/riscv/rv32/libc.abilist
@@ -2430,5 +2430,9 @@  GLIBC_2.38 __isoc23_wcstoumax F
 GLIBC_2.38 __isoc23_wscanf F
 GLIBC_2.38 __strlcat_chk F
 GLIBC_2.38 __strlcpy_chk F
+GLIBC_2.38 __wcslcat_chk F
+GLIBC_2.38 __wcslcpy_chk F
 GLIBC_2.38 strlcat F
 GLIBC_2.38 strlcpy F
+GLIBC_2.38 wcslcat F
+GLIBC_2.38 wcslcpy F
diff --git a/sysdeps/unix/sysv/linux/riscv/rv64/libc.abilist b/sysdeps/unix/sysv/linux/riscv/rv64/libc.abilist
index e0f4863856..e3b4656aa2 100644
--- a/sysdeps/unix/sysv/linux/riscv/rv64/libc.abilist
+++ b/sysdeps/unix/sysv/linux/riscv/rv64/libc.abilist
@@ -2630,5 +2630,9 @@  GLIBC_2.38 __isoc23_wcstoumax F
 GLIBC_2.38 __isoc23_wscanf F
 GLIBC_2.38 __strlcat_chk F
 GLIBC_2.38 __strlcpy_chk F
+GLIBC_2.38 __wcslcat_chk F
+GLIBC_2.38 __wcslcpy_chk F
 GLIBC_2.38 strlcat F
 GLIBC_2.38 strlcpy F
+GLIBC_2.38 wcslcat F
+GLIBC_2.38 wcslcpy F
diff --git a/sysdeps/unix/sysv/linux/s390/s390-32/libc.abilist b/sysdeps/unix/sysv/linux/s390/s390-32/libc.abilist
index 8db68fcea7..84cb7a50ed 100644
--- a/sysdeps/unix/sysv/linux/s390/s390-32/libc.abilist
+++ b/sysdeps/unix/sysv/linux/s390/s390-32/libc.abilist
@@ -2817,8 +2817,12 @@  GLIBC_2.38 __nldbl___isoc23_vwscanf F
 GLIBC_2.38 __nldbl___isoc23_wscanf F
 GLIBC_2.38 __strlcat_chk F
 GLIBC_2.38 __strlcpy_chk F
+GLIBC_2.38 __wcslcat_chk F
+GLIBC_2.38 __wcslcpy_chk F
 GLIBC_2.38 strlcat F
 GLIBC_2.38 strlcpy F
+GLIBC_2.38 wcslcat F
+GLIBC_2.38 wcslcpy F
 GLIBC_2.4 _IO_fprintf F
 GLIBC_2.4 _IO_printf F
 GLIBC_2.4 _IO_sprintf F
diff --git a/sysdeps/unix/sysv/linux/s390/s390-64/libc.abilist b/sysdeps/unix/sysv/linux/s390/s390-64/libc.abilist
index ec9747b7ea..33df3b1646 100644
--- a/sysdeps/unix/sysv/linux/s390/s390-64/libc.abilist
+++ b/sysdeps/unix/sysv/linux/s390/s390-64/libc.abilist
@@ -2610,8 +2610,12 @@  GLIBC_2.38 __nldbl___isoc23_vwscanf F
 GLIBC_2.38 __nldbl___isoc23_wscanf F
 GLIBC_2.38 __strlcat_chk F
 GLIBC_2.38 __strlcpy_chk F
+GLIBC_2.38 __wcslcat_chk F
+GLIBC_2.38 __wcslcpy_chk F
 GLIBC_2.38 strlcat F
 GLIBC_2.38 strlcpy F
+GLIBC_2.38 wcslcat F
+GLIBC_2.38 wcslcpy F
 GLIBC_2.4 _IO_fprintf F
 GLIBC_2.4 _IO_printf F
 GLIBC_2.4 _IO_sprintf F
diff --git a/sysdeps/unix/sysv/linux/sh/be/libc.abilist b/sysdeps/unix/sysv/linux/sh/be/libc.abilist
index 9576b818d8..94cbccd715 100644
--- a/sysdeps/unix/sysv/linux/sh/be/libc.abilist
+++ b/sysdeps/unix/sysv/linux/sh/be/libc.abilist
@@ -2660,8 +2660,12 @@  GLIBC_2.38 __isoc23_wcstoumax F
 GLIBC_2.38 __isoc23_wscanf F
 GLIBC_2.38 __strlcat_chk F
 GLIBC_2.38 __strlcpy_chk F
+GLIBC_2.38 __wcslcat_chk F
+GLIBC_2.38 __wcslcpy_chk F
 GLIBC_2.38 strlcat F
 GLIBC_2.38 strlcpy F
+GLIBC_2.38 wcslcat F
+GLIBC_2.38 wcslcpy F
 GLIBC_2.4 __confstr_chk F
 GLIBC_2.4 __fgets_chk F
 GLIBC_2.4 __fgets_unlocked_chk F
diff --git a/sysdeps/unix/sysv/linux/sh/le/libc.abilist b/sysdeps/unix/sysv/linux/sh/le/libc.abilist
index b67b1b2bb5..3bb316a787 100644
--- a/sysdeps/unix/sysv/linux/sh/le/libc.abilist
+++ b/sysdeps/unix/sysv/linux/sh/le/libc.abilist
@@ -2657,8 +2657,12 @@  GLIBC_2.38 __isoc23_wcstoumax F
 GLIBC_2.38 __isoc23_wscanf F
 GLIBC_2.38 __strlcat_chk F
 GLIBC_2.38 __strlcpy_chk F
+GLIBC_2.38 __wcslcat_chk F
+GLIBC_2.38 __wcslcpy_chk F
 GLIBC_2.38 strlcat F
 GLIBC_2.38 strlcpy F
+GLIBC_2.38 wcslcat F
+GLIBC_2.38 wcslcpy F
 GLIBC_2.4 __confstr_chk F
 GLIBC_2.4 __fgets_chk F
 GLIBC_2.4 __fgets_unlocked_chk F
diff --git a/sysdeps/unix/sysv/linux/sparc/sparc32/libc.abilist b/sysdeps/unix/sysv/linux/sparc/sparc32/libc.abilist
index b251fc9c69..6341b491b4 100644
--- a/sysdeps/unix/sysv/linux/sparc/sparc32/libc.abilist
+++ b/sysdeps/unix/sysv/linux/sparc/sparc32/libc.abilist
@@ -2812,8 +2812,12 @@  GLIBC_2.38 __nldbl___isoc23_vwscanf F
 GLIBC_2.38 __nldbl___isoc23_wscanf F
 GLIBC_2.38 __strlcat_chk F
 GLIBC_2.38 __strlcpy_chk F
+GLIBC_2.38 __wcslcat_chk F
+GLIBC_2.38 __wcslcpy_chk F
 GLIBC_2.38 strlcat F
 GLIBC_2.38 strlcpy F
+GLIBC_2.38 wcslcat F
+GLIBC_2.38 wcslcpy F
 GLIBC_2.4 _IO_fprintf F
 GLIBC_2.4 _IO_printf F
 GLIBC_2.4 _IO_sprintf F
diff --git a/sysdeps/unix/sysv/linux/sparc/sparc64/libc.abilist b/sysdeps/unix/sysv/linux/sparc/sparc64/libc.abilist
index 5ef9bbec34..8ed1ea2926 100644
--- a/sysdeps/unix/sysv/linux/sparc/sparc64/libc.abilist
+++ b/sysdeps/unix/sysv/linux/sparc/sparc64/libc.abilist
@@ -2625,8 +2625,12 @@  GLIBC_2.38 __isoc23_wcstoumax F
 GLIBC_2.38 __isoc23_wscanf F
 GLIBC_2.38 __strlcat_chk F
 GLIBC_2.38 __strlcpy_chk F
+GLIBC_2.38 __wcslcat_chk F
+GLIBC_2.38 __wcslcpy_chk F
 GLIBC_2.38 strlcat F
 GLIBC_2.38 strlcpy F
+GLIBC_2.38 wcslcat F
+GLIBC_2.38 wcslcpy F
 GLIBC_2.4 __confstr_chk F
 GLIBC_2.4 __fgets_chk F
 GLIBC_2.4 __fgets_unlocked_chk F
diff --git a/sysdeps/unix/sysv/linux/x86_64/64/libc.abilist b/sysdeps/unix/sysv/linux/x86_64/64/libc.abilist
index 9ad800b62e..57cfcc2086 100644
--- a/sysdeps/unix/sysv/linux/x86_64/64/libc.abilist
+++ b/sysdeps/unix/sysv/linux/x86_64/64/libc.abilist
@@ -2576,8 +2576,12 @@  GLIBC_2.38 __isoc23_wcstoumax F
 GLIBC_2.38 __isoc23_wscanf F
 GLIBC_2.38 __strlcat_chk F
 GLIBC_2.38 __strlcpy_chk F
+GLIBC_2.38 __wcslcat_chk F
+GLIBC_2.38 __wcslcpy_chk F
 GLIBC_2.38 strlcat F
 GLIBC_2.38 strlcpy F
+GLIBC_2.38 wcslcat F
+GLIBC_2.38 wcslcpy F
 GLIBC_2.4 __confstr_chk F
 GLIBC_2.4 __fgets_chk F
 GLIBC_2.4 __fgets_unlocked_chk F
diff --git a/sysdeps/unix/sysv/linux/x86_64/x32/libc.abilist b/sysdeps/unix/sysv/linux/x86_64/x32/libc.abilist
index 6a3a66c5d4..3f0a9f6d82 100644
--- a/sysdeps/unix/sysv/linux/x86_64/x32/libc.abilist
+++ b/sysdeps/unix/sysv/linux/x86_64/x32/libc.abilist
@@ -2682,5 +2682,9 @@  GLIBC_2.38 __isoc23_wcstoumax F
 GLIBC_2.38 __isoc23_wscanf F
 GLIBC_2.38 __strlcat_chk F
 GLIBC_2.38 __strlcpy_chk F
+GLIBC_2.38 __wcslcat_chk F
+GLIBC_2.38 __wcslcpy_chk F
 GLIBC_2.38 strlcat F
 GLIBC_2.38 strlcpy F
+GLIBC_2.38 wcslcat F
+GLIBC_2.38 wcslcpy F
diff --git a/wcsmbs/Makefile b/wcsmbs/Makefile
index 4aa43252d7..ac03437661 100644
--- a/wcsmbs/Makefile
+++ b/wcsmbs/Makefile
@@ -66,6 +66,8 @@  routines := \
   wcscpy \
   wcscspn \
   wcsdup \
+  wcslcat \
+  wcslcpy \
   wcslen \
   wcsmbsload \
   wcsncase \
@@ -146,6 +148,8 @@  tests := \
   tst-wchar-h \
   tst-wcpncpy \
   tst-wcrtomb \
+  tst-wcslcat \
+  tst-wcslcpy \
   tst-wcsnlen \
   tst-wcstod-nan-locale \
   tst-wcstod-nan-sign \
diff --git a/wcsmbs/Versions b/wcsmbs/Versions
index 2d9391348a..7bdfe43b4a 100644
--- a/wcsmbs/Versions
+++ b/wcsmbs/Versions
@@ -65,5 +65,7 @@  libc {
     __isoc23_vswscanf;
     __isoc23_vwscanf;
     __isoc23_wscanf;
+    wcslcat;
+    wcslcpy;
   }
 }
diff --git a/wcsmbs/bits/wchar2.h b/wcsmbs/bits/wchar2.h
index 8b41e6fbd6..02f44ab373 100644
--- a/wcsmbs/bits/wchar2.h
+++ b/wcsmbs/bits/wchar2.h
@@ -199,6 +199,43 @@  __NTH (wcsncat (wchar_t *__restrict __dest, const wchar_t *__restrict __src,
   return __wcsncat_alias (__dest, __src, __n);
 }
 
+#ifdef __USE_MISC
+extern size_t __wcslcpy_chk (wchar_t *__dest, const wchar_t *__src, size_t __n,
+			     size_t __destlen) __THROW;
+extern size_t __REDIRECT_NTH (__wcslcpy_alias,
+			      (wchar_t *__dest, const wchar_t *__src,
+			       size_t __n), wcslcpy);
+
+__fortify_function size_t
+__NTH (wcslcpy (wchar_t *__restrict __dest, const wchar_t *__restrict __src,
+		size_t __n))
+{
+  if (__glibc_objsize (__dest) != (size_t) -1
+      && (!__builtin_constant_p (__n
+				 > __glibc_objsize (__dest) / sizeof (wchar_t))
+	  || __n > __glibc_objsize (__dest) / sizeof (wchar_t)))
+    return __wcslcpy_chk (__dest, __src, __n, __glibc_objsize (__dest));
+  return __wcslcpy_alias (__dest, __src, __n);
+}
+
+extern size_t __wcslcat_chk (wchar_t *__dest, const wchar_t *__src, size_t __n,
+			     size_t __destlen) __THROW;
+extern size_t __REDIRECT_NTH (__wcslcat_alias,
+			      (wchar_t *__dest, const wchar_t *__src,
+			       size_t __n), wcslcat);
+
+__fortify_function size_t
+__NTH (wcslcat (wchar_t *__restrict __dest, const wchar_t *__restrict __src,
+		size_t __n))
+{
+  if (__glibc_objsize (__dest) != (size_t) -1
+      && (!__builtin_constant_p (__n > __glibc_objsize (__dest)
+				 / sizeof (wchar_t))
+	  || __n > __glibc_objsize (__dest) / sizeof (wchar_t)))
+    return __wcslcat_chk (__dest, __src, __n, __glibc_objsize (__dest));
+  return __wcslcat_alias (__dest, __src, __n);
+}
+#endif /* __USE_MISC */
 
 
 extern int __REDIRECT_NTH_LDBL (__swprintf_alias,
diff --git a/wcsmbs/tst-wcslcat.c b/wcsmbs/tst-wcslcat.c
new file mode 100644
index 0000000000..63c3a164b5
--- /dev/null
+++ b/wcsmbs/tst-wcslcat.c
@@ -0,0 +1,93 @@ 
+/* Test the wcslcat function.
+   Copyright (C) 2023 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <https://www.gnu.org/licenses/>.  */
+
+#include <array_length.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <support/check.h>
+#include <wchar.h>
+
+static int
+do_test (void)
+{
+  struct {
+    wchar_t buf1[16];
+    wchar_t buf2[16];
+  } s;
+
+  /* Nothing is written to the destination if its size is 0.  */
+  wmemset (s.buf1, '@', array_length (s.buf1));
+  wmemset (s.buf2, '@', array_length (s.buf2));
+  TEST_COMPARE (wcslcat (s.buf1, L"", 0), 0);
+  TEST_COMPARE_BLOB (&s, sizeof (s), L"@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@", 128);
+  TEST_COMPARE (wcslcat (s.buf1, L"Hello!", 0), 6);
+  TEST_COMPARE_BLOB (&s, sizeof (s), L"@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@", 128);
+
+  /* No bytes are are modified in the target buffer if the source
+     string is short enough.  */
+  wmemset (s.buf1, '@', array_length (s.buf1));
+  wmemset (s.buf2, '@', array_length (s.buf2));
+  wcscpy (s.buf1, L"He");
+  TEST_COMPARE (wcslcat (s.buf1, L"llo!", array_length (s.buf1)), 6);
+  TEST_COMPARE_BLOB (&s, sizeof (s), L"Hello!\0@@@@@@@@@@@@@@@@@@@@@@@@@", 128);
+
+  /* A source string which fits exactly into the destination buffer is
+     not truncated.  */
+  wmemset (s.buf1, '@', array_length (s.buf1));
+  wmemset (s.buf2, '@', array_length (s.buf2));
+  wcscpy (s.buf1, L"H");
+  TEST_COMPARE (wcslcat (s.buf1, L"ello, world!!!", array_length (s.buf1)),
+                15);
+  TEST_COMPARE_BLOB (&s, sizeof (s),
+		     L"Hello, world!!!\0@@@@@@@@@@@@@@@@@@@@@@@@@", 128);
+
+  /* A source string one character longer than the destination buffer
+     is truncated by one character.  The total length is returned.  */
+  wmemset (s.buf1, '@', array_length (s.buf1));
+  wmemset (s.buf2, '@', array_length (s.buf2));
+  wcscpy (s.buf1, L"Hello");
+  TEST_COMPARE (wcslcat (s.buf1, L", world!!!!", array_length (s.buf1)), 16);
+  TEST_COMPARE_BLOB (&s, sizeof (s),
+		     L"Hello, world!!!\0@@@@@@@@@@@@@@@@@@@@@@@@@", 128);
+
+  /* An even longer source string is truncated as well, and the total
+     length is returned.  */
+  wmemset (s.buf1, '@', array_length (s.buf1));
+  wmemset (s.buf2, '@', array_length (s.buf2));
+  wcscpy (s.buf1, L"Hello,");
+  TEST_COMPARE (wcslcat (s.buf1, L" world!!!!!!!!", array_length (s.buf1)),
+                20);
+  TEST_COMPARE_BLOB (&s, sizeof (s),
+		     L"Hello, world!!!\0@@@@@@@@@@@@@@@@@@@@@@@@@", 128);
+
+  /* A destination string which is not NUL-terminated does not result
+     in any changes to the buffer.  */
+  wmemset (s.buf1, '$', array_length (s.buf1));
+  wmemset (s.buf2, '@', array_length (s.buf2));
+  TEST_COMPARE (wcslcat (s.buf1, L"", array_length (s.buf1)), 16);
+  TEST_COMPARE_BLOB (&s, sizeof (s), L"$$$$$$$$$$$$$$$$@@@@@@@@@@@@@@@@", 128);
+  TEST_COMPARE (wcslcat (s.buf1, L"Hello!", array_length (s.buf1)), 22);
+  TEST_COMPARE_BLOB (&s, sizeof (s), L"$$$$$$$$$$$$$$$$@@@@@@@@@@@@@@@@", 128);
+  TEST_COMPARE (wcslcat (s.buf1, L"Hello, world!!!!!!!!",
+                         array_length (s.buf1)), 36);
+  TEST_COMPARE_BLOB (&s, sizeof (s), L"$$$$$$$$$$$$$$$$@@@@@@@@@@@@@@@@", 128);
+
+  return 0;
+}
+
+#include <support/test-driver.c>
diff --git a/wcsmbs/tst-wcslcpy.c b/wcsmbs/tst-wcslcpy.c
new file mode 100644
index 0000000000..8eaffbf0c4
--- /dev/null
+++ b/wcsmbs/tst-wcslcpy.c
@@ -0,0 +1,78 @@ 
+/* Test the wcslcpy function.
+   Copyright (C) 2023 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <https://www.gnu.org/licenses/>.  */
+
+#include <array_length.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <support/check.h>
+#include <wchar.h>
+
+static int
+do_test (void)
+{
+  struct {
+    wchar_t buf1[16];
+    wchar_t buf2[16];
+  } s;
+
+  /* Nothing is written to the destination if its size is 0.  */
+  wmemset (s.buf1, '@', array_length (s.buf1));
+  wmemset (s.buf2, '@', array_length (s.buf2));
+  TEST_COMPARE (wcslcpy (s.buf1, L"Hello!", 0), 6);
+  TEST_COMPARE_BLOB (&s, sizeof (s), L"@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@", 128);
+
+  /* No bytes are are modified in the target buffer if the source
+     string is short enough.  */
+  wmemset (s.buf1, '@', array_length (s.buf1));
+  wmemset (s.buf2, '@', array_length (s.buf2));
+  TEST_COMPARE (wcslcpy (s.buf1, L"Hello!", array_length (s.buf1)), 6);
+  TEST_COMPARE_BLOB (&s, sizeof (s),
+                     L"Hello!\0@@@@@@@@@@@@@@@@@@@@@@@@@", 128);
+
+  /* A source string which fits exactly into the destination buffer is
+     not truncated.  */
+  wmemset (s.buf1, '@', array_length (s.buf1));
+  wmemset (s.buf2, '@', array_length (s.buf2));
+  TEST_COMPARE (wcslcpy (s.buf1, L"Hello, world!!!", array_length (s.buf1)),
+                15);
+  TEST_COMPARE_BLOB (&s, sizeof (s),
+                     L"Hello, world!!!\0@@@@@@@@@@@@@@@@@@@@@@@@@", 128);
+
+  /* A source string one character longer than the destination buffer
+     is truncated by one character.  The untruncated source length is
+     returned.  */
+  wmemset (s.buf1, '@', array_length (s.buf1));
+  wmemset (s.buf2, '@', array_length (s.buf2));
+  TEST_COMPARE (wcslcpy (s.buf1, L"Hello, world!!!!", array_length (s.buf1)),
+                16);
+  TEST_COMPARE_BLOB (&s, sizeof (s),
+                     L"Hello, world!!!\0@@@@@@@@@@@@@@@@@@@@@@@@@", 128);
+
+  /* An even longer source string is truncated as well, and the
+     original length is returned.  */
+  wmemset (s.buf1, '@', array_length (s.buf1));
+  wmemset (s.buf2, '@', array_length (s.buf2));
+  TEST_COMPARE (wcslcpy (s.buf1, L"Hello, world!!!!!!!!",
+                         array_length (s.buf1)), 20);
+  TEST_COMPARE_BLOB (&s, sizeof (s),
+                     L"Hello, world!!!\0@@@@@@@@@@@@@@@@@@@@@@@@@", 128);
+
+  return 0;
+}
+
+#include <support/test-driver.c>
diff --git a/wcsmbs/wchar.h b/wcsmbs/wchar.h
index acc2eb9ddf..6d15503830 100644
--- a/wcsmbs/wchar.h
+++ b/wcsmbs/wchar.h
@@ -93,6 +93,19 @@  extern wchar_t *wcsncpy (wchar_t *__restrict __dest,
 			 const wchar_t *__restrict __src, size_t __n)
      __THROW __nonnull ((1, 2));
 
+#ifdef __USE_MISC
+/* Copy at most N - 1 characters from SRC to DEST.  */
+extern size_t wcslcpy (wchar_t *__restrict __dest,
+		       const wchar_t *__restrict __src, size_t __n)
+  __THROW __nonnull ((1, 2)) __attr_access ((__write_only__, 1, 3));
+
+/* Append SRC to DEST, possibly with truncation to keep the total size
+   below N.  */
+extern size_t wcslcat (wchar_t *__restrict __dest,
+		       const wchar_t *__restrict __src, size_t __n)
+  __THROW __nonnull ((1, 2))  __attr_access ((__read_write__, 1, 3));
+#endif
+
 /* Append SRC onto DEST.  */
 extern wchar_t *wcscat (wchar_t *__restrict __dest,
 			const wchar_t *__restrict __src)
diff --git a/wcsmbs/wcslcat.c b/wcsmbs/wcslcat.c
new file mode 100644
index 0000000000..3bac6a2aa0
--- /dev/null
+++ b/wcsmbs/wcslcat.c
@@ -0,0 +1,60 @@ 
+/* Append a null-terminated wide string to another, with length checking.
+   Copyright (C) 2023 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <https://www.gnu.org/licenses/>.  */
+
+#include <stdint.h>
+#include <wchar.h>
+
+size_t
+__wcslcat (wchar_t *__restrict dest, const wchar_t *__restrict src,
+           size_t size)
+{
+  size_t src_length = __wcslen (src);
+
+  /* Our implementation strlcat supports dest == NULL if size == 0
+     (for consistency with snprintf and strlcpy), but wcsnlen does
+     not, so we have to cover this case explicitly.  */
+  if (size == 0)
+    return src_length;
+
+  size_t dest_length = __wcsnlen (dest, size);
+  if (dest_length != size)
+    {
+      /* Copy at most the remaining number of characters in the
+	 destination buffer.  Leave for the null terminator.  */
+      size_t to_copy = size - dest_length - 1;
+      /* But not more than what is available in the source string.  */
+      if (to_copy > src_length)
+	to_copy = src_length;
+
+      wchar_t *target = dest + dest_length;
+      __wmemcpy (target, src, to_copy);
+      target[to_copy] = '\0';
+    }
+
+  /* If the sum wraps around, we have more than SIZE_MAX + 2 bytes in
+     the two input strings (including both null terminators).  If each
+     byte in the address space can be assigned a unique size_t value
+     (which the static_assert checks), then by the pigeonhole
+     principle, the two input strings must overlap, which is
+     undefined.  */
+  _Static_assert (sizeof (uintptr_t) == sizeof (size_t),
+		  "theoretical maximum object size covers address space");
+  return dest_length + src_length;
+}
+libc_hidden_def (__wcslcat)
+weak_alias (__wcslcat, wcslcat)
diff --git a/wcsmbs/wcslcpy.c b/wcsmbs/wcslcpy.c
new file mode 100644
index 0000000000..a1b1f1b43f
--- /dev/null
+++ b/wcsmbs/wcslcpy.c
@@ -0,0 +1,46 @@ 
+/* Copy a null-terminated wide string to a fixed-size buffer.
+   Copyright (C) 2023 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <http://www.gnu.org/licenses/>.  */
+
+#include <wchar.h>
+
+size_t
+__wcslcpy (wchar_t *__restrict dest, const wchar_t *__restrict src, size_t size)
+{
+  size_t src_length = __wcslen (src);
+
+  if (__glibc_unlikely (src_length >= size))
+    {
+      if (size > 0)
+	{
+	  /* Copy the leading portion of the string.  The last
+	     character is subsequently overwritten with the null
+	     terminator, but the destination size is usually a
+	     multiple of a small power of two, so writing it twice
+	     should be more efficient than copying an odd number of
+	     character.  */
+	  __wmemcpy (dest, src, size);
+	  dest[size - 1] = '\0';
+	}
+    }
+  else
+    /* Copy the string and its terminating null character.  */
+    __wmemcpy (dest, src, src_length + 1);
+  return src_length;
+}
+libc_hidden_def (__wcslcpy)
+weak_alias (__wcslcpy, wcslcpy)