Paul Eggert wrote:
> @@ -48,7 +48,8 @@ get_locale_dependent_values (struct locale_dependent_values
> *result)
> snprintf (result->numeric, sizeof (result->numeric),
> "%g", 3.5);
> /* result->numeric is usually "3,5" */
> - strcpy (result->time, nl_langinfo (MON_1));
> + strncpy (result->time, nl_langinfo (MON_1), sizeof result->time - 1);
> + result->time[sizeof result->time - 1] = '\0';
> /* result->time is usually "janvier" */
> }
>
This change has replaced code with 1 drawback
- The string copy may overrun the buffer.
by code with 3 drawbacks
- The string copy may be silently truncated.
- The code needs 2 lines, instead of 1 line.
- In the common cases, the large result buffer gets needlessly filled
with NULs.
I think the best way to deal with this situation is the function 'strlcpy':
ASSERT (strlcpy (result->time, nl_langinfo (MON_1), sizeof result->time) <
sizeof result->time);
This way,
- The string copy will not overrun the buffer.
- The string copy will always be NUL-terminated.
- Silent truncation does not occur.
- The code fits in one line.
- The code is not needlessly inefficient.
Here's a proposal to add 'strlcpy' to gnulib.
Yes, I have read the relevant documentation:
https://www.freebsd.org/cgi/man.cgi?query=strlcpy&sektion=3
and the discussions:
https://www.sourceware.org/ml/libc-alpha/2000-08/msg00052.html
https://lwn.net/Articles/612244/
https://sourceware.org/ml/libc-alpha/2014-09/msg00350.html
https://sourceware.org/glibc/wiki/strlcpy
The major argument against strlcpy is that it is not fool-proof:
If the caller ignores the return value, silent truncation can occur.
To prevent this, the proposed patch declares strlcpy with
__attribute__((__warn_unused_result__)) on all platforms.
Bruno
>From dc4a8d880cb3be138b96b682ae57a10259a67ba4 Mon Sep 17 00:00:00 2001
From: Bruno Haible <[email protected]>
Date: Thu, 28 Sep 2017 00:30:05 +0200
Subject: [PATCH 1/2] New module 'strlcpy'.
* lib/string.in.h (_GL_ATTRIBUTE_RETURN_CHECK): New macro.
(strlcpy): New declaration.
* lib/strlcpy.c: New file.
* m4/strlcpy.m4: New file.
* modules/strlcpy: New file.
* m4/string_h.m4 (gl_HEADER_STRING_H_BODY): Test whether strlcpy is
declared.
(gl_HEADER_STRING_H_DEFAULTS): Initialize GNULIB_STRLCPY, HAVE_STRLCPY,
REPLACE_STRLCPY.
* modules/string (Makefile.am): Substitite GNULIB_STRLCPY, HAVE_STRLCPY,
REPLACE_STRLCPY.
* doc/gnulib.texi (BSD Function Substitutes): New chapter.
* doc/bsd-functions/strlcpy.texi: New file.
---
ChangeLog | 17 +++++++++++++
doc/bsd-functions/strlcpy.texi | 18 +++++++++++++
doc/gnulib.texi | 27 ++++++++++++++++++++
lib/string.in.h | 46 ++++++++++++++++++++++++++++++++++
lib/strlcpy.c | 57 ++++++++++++++++++++++++++++++++++++++++++
m4/string_h.m4 | 17 +++++++------
m4/strlcpy.m4 | 22 ++++++++++++++++
modules/string | 15 ++++++-----
modules/strlcpy | 26 +++++++++++++++++++
9 files changed, 232 insertions(+), 13 deletions(-)
create mode 100644 doc/bsd-functions/strlcpy.texi
create mode 100644 lib/strlcpy.c
create mode 100644 m4/strlcpy.m4
create mode 100644 modules/strlcpy
diff --git a/ChangeLog b/ChangeLog
index 8a9bbe6..c005f1c 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,20 @@
+2017-09-27 Bruno Haible <[email protected]>
+
+ New module 'strlcpy'.
+ * lib/string.in.h (_GL_ATTRIBUTE_RETURN_CHECK): New macro.
+ (strlcpy): New declaration.
+ * lib/strlcpy.c: New file.
+ * m4/strlcpy.m4: New file.
+ * modules/strlcpy: New file.
+ * m4/string_h.m4 (gl_HEADER_STRING_H_BODY): Test whether strlcpy is
+ declared.
+ (gl_HEADER_STRING_H_DEFAULTS): Initialize GNULIB_STRLCPY, HAVE_STRLCPY,
+ REPLACE_STRLCPY.
+ * modules/string (Makefile.am): Substitite GNULIB_STRLCPY, HAVE_STRLCPY,
+ REPLACE_STRLCPY.
+ * doc/gnulib.texi (BSD Function Substitutes): New chapter.
+ * doc/bsd-functions/strlcpy.texi: New file.
+
2017-09-26 Bruno Haible <[email protected]>
uniname/uniname-tests: Tighten code.
diff --git a/doc/bsd-functions/strlcpy.texi b/doc/bsd-functions/strlcpy.texi
new file mode 100644
index 0000000..5ce05dc
--- /dev/null
+++ b/doc/bsd-functions/strlcpy.texi
@@ -0,0 +1,18 @@
+@node strlcpy
+@subsection @code{strlcpy}
+@findex strlcpy
+
+Gnulib module: strlcpy
+
+Portability problems fixed by Gnulib:
+@itemize
+@item
+This function is missing on some platforms:
+glibc 2.24, AIX 5.1, HP-UX 11, OSF/1 5.1, mingw, MSVC 14, BeOS.
+@item
+Ignoring the return value of this function does not produce a warning.
+@end itemize
+
+Portability problems not fixed by Gnulib:
+@itemize
+@end itemize
diff --git a/doc/gnulib.texi b/doc/gnulib.texi
index 1468c14..6a52787 100644
--- a/doc/gnulib.texi
+++ b/doc/gnulib.texi
@@ -69,6 +69,7 @@ Documentation License''.
* Legacy Function Substitutes:: Replacing system functions.
* Glibc Header File Substitutes:: Overriding system headers.
* Glibc Function Substitutes:: Replacing system functions.
+* BSD Function Substitutes:: Replacing system functions.
* Native Windows Support:: Support for the native Windows platforms.
* Particular Modules:: Documentation of individual modules.
* Regular expressions:: The regex module.
@@ -6306,6 +6307,32 @@ This list of functions is sorted according to the header that declares them.
@c @section Glibc Extensions to @code{<wordexp.h>}
+@node BSD Function Substitutes
+@chapter BSD Function Substitutes
+
+This chapter describes which functions and function-like macros provided
+as extensions by the common BSD systems (FreeBSD, NetBSD, OpenBSD) are
+also supported by Gnulib, which portability pitfalls are fixed by Gnulib,
+and which (known) portability problems are not worked around by Gnulib.
+
+@nosuchmodulenote function
+
+This list of functions is sorted according to the header that declares them.
+
+@menu
+* BSD string.h::
+@end menu
+
+@node BSD string.h
+@section BSD Extensions to @code{<string.h>}
+
+@menu
+* strlcpy::
+@end menu
+
+@include bsd-functions/strlcpy.texi
+
+
@node Native Windows Support
@chapter Native Windows Support
diff --git a/lib/string.in.h b/lib/string.in.h
index eb5be69..e7d6f4b 100644
--- a/lib/string.in.h
+++ b/lib/string.in.h
@@ -60,6 +60,16 @@
# define _GL_ATTRIBUTE_PURE /* empty */
#endif
+/* Declares that the return value of a function should not be ignored by the
+ caller. The warn_unused_result attribute appeared first in gcc-3.4.0. */
+#ifndef _GL_ATTRIBUTE_RETURN_CHECK
+# if __GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 4)
+# define _GL_ATTRIBUTE_RETURN_CHECK __attribute__((__warn_unused_result__))
+# else
+# define _GL_ATTRIBUTE_RETURN_CHECK
+# endif
+#endif
+
/* NetBSD 5.0 declares strsignal in <unistd.h>, not in <string.h>. */
/* But in any case avoid namespace pollution on glibc systems. */
#if (@GNULIB_STRSIGNAL@ || defined GNULIB_POSIXCHECK) && defined __NetBSD__ \
@@ -1041,6 +1051,42 @@ _GL_WARN_ON_USE (strsignal, "strsignal is unportable - "
# endif
#endif
+/* Copies the string SRC, possibly truncated, into the bounded memory area
+ [DST,...,DST+DSTSIZE-1]. Returns strlen (SRC).
+ The result is truncated if and only if the return value is >= DSTSIZE.
+ The result is truncated without NUL terminator if and only if DSTSIZE == 0.
+ */
+#if @GNULIB_STRLCPY@
+# if @REPLACE_STRLCPY@
+# if !(defined __cplusplus && defined GNULIB_NAMESPACE)
+# undef strlcpy
+# define strlcpy rpl_strlcpy
+# endif
+_GL_FUNCDECL_RPL (strlcpy, size_t,
+ (char *__dst, const char *__src, size_t __dstsize)
+ _GL_ARG_NONNULL ((1, 2))
+ _GL_ATTRIBUTE_RETURN_CHECK);
+_GL_CXXALIAS_RPL (strlcpy, size_t,
+ (char *__dst, const char *__src, size_t __dstsize));
+# else
+# if !@HAVE_STRLCPY@
+_GL_FUNCDECL_SYS (strlcpy, size_t,
+ (char *__dst, const char *__src, size_t __dstsize)
+ _GL_ARG_NONNULL ((1, 2))
+ _GL_ATTRIBUTE_RETURN_CHECK);
+# endif
+_GL_CXXALIAS_SYS (strlcpy, size_t,
+ (char *__dst, const char *__src, size_t __dstsize));
+# endif
+_GL_CXXALIASWARN (strlcpy);
+#elif defined GNULIB_POSIXCHECK
+# undef strndup
+# if HAVE_RAW_DECL_STRLCPY
+_GL_WARN_ON_USE (strlcpy, "strlcpy is unportable - "
+ "use gnulib module strlcpy for portability");
+# endif
+#endif
+
#if @GNULIB_STRVERSCMP@
# if !@HAVE_STRVERSCMP@
_GL_FUNCDECL_SYS (strverscmp, int, (const char *, const char *)
diff --git a/lib/strlcpy.c b/lib/strlcpy.c
new file mode 100644
index 0000000..e6cb133
--- /dev/null
+++ b/lib/strlcpy.c
@@ -0,0 +1,57 @@
+/* Copy a string into a bounded memory area.
+ Copyright (C) 2017 Free Software Foundation, Inc.
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program 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 General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>. */
+
+/* Written by Bruno Haible <[email protected]>, 2017. */
+
+#include <config.h>
+
+/* Specification. */
+#include <string.h>
+
+/* Copies the string SRC, possibly truncated, into the bounded memory area
+ [DST,...,DST+DSTSIZE-1]. Returns strlen (SRC).
+ The result is truncated if and only if the return value is >= DSTSIZE.
+ The result is truncated without NUL terminator if and only if DSTSIZE == 0.
+ */
+size_t
+strlcpy (char *dst, const char *src, size_t dstsize)
+#undef strlcpy
+{
+ /* The implementation in dietlibc is broken, says
+ <https://sourceware.org/glibc/wiki/strlcpy>. */
+#if HAVE_STRLCPY && !defined __dietlibc__
+ return strlcpy (dst, src, dstsize);
+#else
+ if (dstsize > 0)
+ {
+ /* This implementation makes only one pass through SRC, for cache
+ efficiency. */
+ char *p = dst;
+ while (--dstsize > 0)
+ {
+ char c = *src++;
+ *p = c;
+ if (c == '\0')
+ return p - dst;
+ p++;
+ }
+ *p = '\0';
+ return (p - dst) + strlen (src);
+ }
+ else
+ return strlen (src);
+#endif
+}
diff --git a/m4/string_h.m4 b/m4/string_h.m4
index ac6311f..b8f68ce 100644
--- a/m4/string_h.m4
+++ b/m4/string_h.m4
@@ -5,7 +5,7 @@
# gives unlimited permission to copy and/or distribute it,
# with or without modifications, as long as this notice is preserved.
-# serial 21
+# serial 22
# Written by Paul Eggert.
@@ -29,7 +29,7 @@ AC_DEFUN([gl_HEADER_STRING_H_BODY],
]],
[ffsl ffsll memmem mempcpy memrchr rawmemchr stpcpy stpncpy strchrnul
strdup strncat strndup strnlen strpbrk strsep strcasestr strtok_r
- strerror_r strsignal strverscmp])
+ strerror_r strsignal strlcpy strverscmp])
])
AC_DEFUN([gl_STRING_MODULE_INDICATOR],
@@ -80,6 +80,7 @@ AC_DEFUN([gl_HEADER_STRING_H_DEFAULTS],
GNULIB_STRERROR=0; AC_SUBST([GNULIB_STRERROR])
GNULIB_STRERROR_R=0; AC_SUBST([GNULIB_STRERROR_R])
GNULIB_STRSIGNAL=0; AC_SUBST([GNULIB_STRSIGNAL])
+ GNULIB_STRLCPY=0; AC_SUBST([GNULIB_STRLCPY])
GNULIB_STRVERSCMP=0; AC_SUBST([GNULIB_STRVERSCMP])
HAVE_MBSLEN=0; AC_SUBST([HAVE_MBSLEN])
dnl Assume proper GNU behavior unless another module says otherwise.
@@ -103,20 +104,22 @@ AC_DEFUN([gl_HEADER_STRING_H_DEFAULTS],
HAVE_DECL_STRTOK_R=1; AC_SUBST([HAVE_DECL_STRTOK_R])
HAVE_DECL_STRERROR_R=1; AC_SUBST([HAVE_DECL_STRERROR_R])
HAVE_DECL_STRSIGNAL=1; AC_SUBST([HAVE_DECL_STRSIGNAL])
+ HAVE_STRLCPY=1; AC_SUBST([HAVE_STRLCPY])
HAVE_STRVERSCMP=1; AC_SUBST([HAVE_STRVERSCMP])
REPLACE_MEMCHR=0; AC_SUBST([REPLACE_MEMCHR])
REPLACE_MEMMEM=0; AC_SUBST([REPLACE_MEMMEM])
REPLACE_STPNCPY=0; AC_SUBST([REPLACE_STPNCPY])
+ REPLACE_STRCHRNUL=0; AC_SUBST([REPLACE_STRCHRNUL])
REPLACE_STRDUP=0; AC_SUBST([REPLACE_STRDUP])
+ REPLACE_STRNCAT=0; AC_SUBST([REPLACE_STRNCAT])
+ REPLACE_STRNDUP=0; AC_SUBST([REPLACE_STRNDUP])
+ REPLACE_STRNLEN=0; AC_SUBST([REPLACE_STRNLEN])
REPLACE_STRSTR=0; AC_SUBST([REPLACE_STRSTR])
REPLACE_STRCASESTR=0; AC_SUBST([REPLACE_STRCASESTR])
- REPLACE_STRCHRNUL=0; AC_SUBST([REPLACE_STRCHRNUL])
+ REPLACE_STRTOK_R=0; AC_SUBST([REPLACE_STRTOK_R])
REPLACE_STRERROR=0; AC_SUBST([REPLACE_STRERROR])
REPLACE_STRERROR_R=0; AC_SUBST([REPLACE_STRERROR_R])
- REPLACE_STRNCAT=0; AC_SUBST([REPLACE_STRNCAT])
- REPLACE_STRNDUP=0; AC_SUBST([REPLACE_STRNDUP])
- REPLACE_STRNLEN=0; AC_SUBST([REPLACE_STRNLEN])
REPLACE_STRSIGNAL=0; AC_SUBST([REPLACE_STRSIGNAL])
- REPLACE_STRTOK_R=0; AC_SUBST([REPLACE_STRTOK_R])
+ REPLACE_STRLCPY=0; AC_SUBST([REPLACE_STRLCPY])
UNDEFINE_STRTOK_R=0; AC_SUBST([UNDEFINE_STRTOK_R])
])
diff --git a/m4/strlcpy.m4 b/m4/strlcpy.m4
new file mode 100644
index 0000000..68eaf48
--- /dev/null
+++ b/m4/strlcpy.m4
@@ -0,0 +1,22 @@
+# strlcpy.m4 serial 1
+dnl Copyright (C) 2017 Free Software Foundation, Inc.
+dnl This file is free software; the Free Software Foundation
+dnl gives unlimited permission to copy and/or distribute it,
+dnl with or without modifications, as long as this notice is preserved.
+
+AC_DEFUN([gl_FUNC_STRLCPY],
+[
+ AC_REQUIRE([gl_HEADER_STRING_H_DEFAULTS])
+
+ AC_CHECK_FUNCS_ONCE([strlcpy])
+ if test $ac_cv_func_strlcpy = yes; then
+ dnl The system's <string.h> declares the function without
+ dnl __attribute__((__warn_unused_result__)). Therefore override it.
+ REPLACE_STRLCPY=1
+ else
+ HAVE_STRLCPY=0
+ fi
+])
+
+# Prerequisites of lib/strlcpy.c.
+AC_DEFUN([gl_PREREQ_STRLCPY], [:])
diff --git a/modules/string b/modules/string
index 8a07da5..daa079b 100644
--- a/modules/string
+++ b/modules/string
@@ -67,6 +67,7 @@ string.h: string.in.h $(top_builddir)/config.status $(CXXDEFS_H) $(ARG_NONNULL_H
-e 's/@''GNULIB_STRERROR''@/$(GNULIB_STRERROR)/g' \
-e 's/@''GNULIB_STRERROR_R''@/$(GNULIB_STRERROR_R)/g' \
-e 's/@''GNULIB_STRSIGNAL''@/$(GNULIB_STRSIGNAL)/g' \
+ -e 's/@''GNULIB_STRLCPY''@/$(GNULIB_STRLCPY)/g' \
-e 's/@''GNULIB_STRVERSCMP''@/$(GNULIB_STRVERSCMP)/g' \
< $(srcdir)/string.in.h | \
sed -e 's|@''HAVE_EXPLICIT_BZERO''@|$(HAVE_EXPLICIT_BZERO)|g' \
@@ -90,21 +91,23 @@ string.h: string.in.h $(top_builddir)/config.status $(CXXDEFS_H) $(ARG_NONNULL_H
-e 's|@''HAVE_DECL_STRTOK_R''@|$(HAVE_DECL_STRTOK_R)|g' \
-e 's|@''HAVE_DECL_STRERROR_R''@|$(HAVE_DECL_STRERROR_R)|g' \
-e 's|@''HAVE_DECL_STRSIGNAL''@|$(HAVE_DECL_STRSIGNAL)|g' \
+ -e 's|@''HAVE_STRLCPY''@|$(HAVE_STRLCPY)|g' \
-e 's|@''HAVE_STRVERSCMP''@|$(HAVE_STRVERSCMP)|g' \
- -e 's|@''REPLACE_STPNCPY''@|$(REPLACE_STPNCPY)|g' \
-e 's|@''REPLACE_MEMCHR''@|$(REPLACE_MEMCHR)|g' \
-e 's|@''REPLACE_MEMMEM''@|$(REPLACE_MEMMEM)|g' \
- -e 's|@''REPLACE_STRCASESTR''@|$(REPLACE_STRCASESTR)|g' \
+ -e 's|@''REPLACE_STPNCPY''@|$(REPLACE_STPNCPY)|g' \
-e 's|@''REPLACE_STRCHRNUL''@|$(REPLACE_STRCHRNUL)|g' \
-e 's|@''REPLACE_STRDUP''@|$(REPLACE_STRDUP)|g' \
- -e 's|@''REPLACE_STRSTR''@|$(REPLACE_STRSTR)|g' \
- -e 's|@''REPLACE_STRERROR''@|$(REPLACE_STRERROR)|g' \
- -e 's|@''REPLACE_STRERROR_R''@|$(REPLACE_STRERROR_R)|g' \
-e 's|@''REPLACE_STRNCAT''@|$(REPLACE_STRNCAT)|g' \
-e 's|@''REPLACE_STRNDUP''@|$(REPLACE_STRNDUP)|g' \
-e 's|@''REPLACE_STRNLEN''@|$(REPLACE_STRNLEN)|g' \
- -e 's|@''REPLACE_STRSIGNAL''@|$(REPLACE_STRSIGNAL)|g' \
+ -e 's|@''REPLACE_STRSTR''@|$(REPLACE_STRSTR)|g' \
+ -e 's|@''REPLACE_STRCASESTR''@|$(REPLACE_STRCASESTR)|g' \
-e 's|@''REPLACE_STRTOK_R''@|$(REPLACE_STRTOK_R)|g' \
+ -e 's|@''REPLACE_STRERROR''@|$(REPLACE_STRERROR)|g' \
+ -e 's|@''REPLACE_STRERROR_R''@|$(REPLACE_STRERROR_R)|g' \
+ -e 's|@''REPLACE_STRSIGNAL''@|$(REPLACE_STRSIGNAL)|g' \
+ -e 's|@''REPLACE_STRLCPY''@|$(REPLACE_STRLCPY)|g' \
-e 's|@''UNDEFINE_STRTOK_R''@|$(UNDEFINE_STRTOK_R)|g' \
-e '/definitions of _GL_FUNCDECL_RPL/r $(CXXDEFS_H)' \
-e '/definition of _GL_ARG_NONNULL/r $(ARG_NONNULL_H)' \
diff --git a/modules/strlcpy b/modules/strlcpy
new file mode 100644
index 0000000..f12d8d6
--- /dev/null
+++ b/modules/strlcpy
@@ -0,0 +1,26 @@
+Description:
+strlcpy() function: copy a string into a bounded memory area.
+
+Files:
+lib/strlcpy.c
+m4/strlcpy.m4
+
+Depends-on:
+string
+
+configure.ac:
+gl_FUNC_STRLCPY
+gl_PREREQ_STRLCPY
+gl_STRING_MODULE_INDICATOR([strlcpy])
+
+Makefile.am:
+lib_SOURCES += strlcpy.c
+
+Include:
+<string.h>
+
+License:
+LGPLv2+
+
+Maintainer:
+all
--
2.7.4
From 8e0c10ee607cfea17167fbcd540b18d7f23d9fec Mon Sep 17 00:00:00 2001
From: Bruno Haible <[email protected]>
Date: Thu, 28 Sep 2017 00:31:04 +0200
Subject: [PATCH 2/2] Tests for module 'strlcpy'.
* tests/test-strlcpy.c: New file, based on tests/unistr/test-strncpy.h.
* modules/strlcpy-tests: New file.
* tests/test-string-c++.cc: Check the signature of strlcpy.
---
ChangeLog | 5 ++
modules/strlcpy-tests | 19 ++++++++
tests/test-string-c++.cc | 5 ++
tests/test-strlcpy.c | 117 +++++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 146 insertions(+)
create mode 100644 modules/strlcpy-tests
create mode 100644 tests/test-strlcpy.c
diff --git a/ChangeLog b/ChangeLog
index c005f1c..8fa21e8 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,10 @@
2017-09-27 Bruno Haible <[email protected]>
+ Tests for module 'strlcpy'.
+ * tests/test-strlcpy.c: New file, based on tests/unistr/test-strncpy.h.
+ * modules/strlcpy-tests: New file.
+ * tests/test-string-c++.cc: Check the signature of strlcpy.
+
New module 'strlcpy'.
* lib/string.in.h (_GL_ATTRIBUTE_RETURN_CHECK): New macro.
(strlcpy): New declaration.
diff --git a/modules/strlcpy-tests b/modules/strlcpy-tests
new file mode 100644
index 0000000..fab82e2
--- /dev/null
+++ b/modules/strlcpy-tests
@@ -0,0 +1,19 @@
+Files:
+tests/test-strlcpy.c
+tests/zerosize-ptr.h
+tests/macros.h
+m4/mmap-anon.m4
+
+Depends-on:
+extensions
+getpagesize
+
+configure.ac:
+gl_FUNC_MMAP_ANON
+AC_CHECK_HEADERS_ONCE([sys/mman.h])
+AC_CHECK_FUNCS_ONCE([mprotect])
+
+Makefile.am:
+TESTS += test-strlcpy
+check_PROGRAMS += test-strlcpy
+test_strlcpy_SOURCES = test-strlcpy.c
diff --git a/tests/test-string-c++.cc b/tests/test-string-c++.cc
index 877cf68..c3df903 100644
--- a/tests/test-string-c++.cc
+++ b/tests/test-string-c++.cc
@@ -142,6 +142,11 @@ SIGNATURE_CHECK (GNULIB_NAMESPACE::strerror_r, int, (int, char *, size_t));
SIGNATURE_CHECK (GNULIB_NAMESPACE::strsignal, char *, (int));
#endif
+#if GNULIB_TEST_STRLCPY
+SIGNATURE_CHECK (GNULIB_NAMESPACE::strlcpy, size_t,
+ (char *, const char *, size_t));
+#endif
+
#if GNULIB_TEST_STRVERSCMP
SIGNATURE_CHECK (GNULIB_NAMESPACE::strverscmp, int,
(const char *, const char *));
diff --git a/tests/test-strlcpy.c b/tests/test-strlcpy.c
new file mode 100644
index 0000000..9d5eee9
--- /dev/null
+++ b/tests/test-strlcpy.c
@@ -0,0 +1,117 @@
+/* Tests of strlcpy().
+ Copyright (C) 2010-2017 Free Software Foundation, Inc.
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program 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 General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>. */
+
+/* Written by Bruno Haible <[email protected]>. */
+
+#include <config.h>
+
+#include <string.h>
+
+#include "signature.h"
+SIGNATURE_CHECK (strlcpy, size_t, (char *, const char *, size_t));
+
+#include <stdlib.h>
+
+#include "zerosize-ptr.h"
+#include "macros.h"
+
+#define MAGIC (char)0xBA
+
+static void
+check_single (const char *input, size_t length, size_t n)
+{
+ char *dest;
+ size_t result;
+ size_t i;
+
+ dest = (char *) malloc (1 + n + 1);
+ ASSERT (dest != NULL);
+
+ for (i = 0; i < 1 + n + 1; i++)
+ dest[i] = MAGIC;
+
+ result = strlcpy (dest + 1, input, n);
+ ASSERT (result == length);
+
+ ASSERT (dest[0] == MAGIC);
+ i = 0;
+ if (n > 0)
+ {
+ for (; i < (n <= length ? n - 1 : length); i++)
+ ASSERT (dest[1 + i] == input[i]);
+ ASSERT (dest[1 + i] == '\0');
+ i++;
+ }
+ for (; i < n + 1; i++)
+ ASSERT (dest[1 + i] == MAGIC);
+
+ free (dest);
+}
+
+static void
+check (const char *input, size_t input_length)
+{
+ size_t length;
+ size_t n;
+
+ ASSERT (input_length > 0);
+ ASSERT (input[input_length - 1] == 0);
+ length = input_length - 1; /* = strlen (input) */
+
+ for (n = 0; n <= 2 * length + 2; n++)
+ check_single (input, length, n);
+
+ /* Check that strlcpy (D, S, N) does not look at more than
+ strlen (S) + 1 bytes. */
+ {
+ char *page_boundary = (char *) zerosize_ptr ();
+
+ if (page_boundary != NULL)
+ {
+ size_t bytes_to_copy = length + 1;
+ char *copy;
+ size_t i;
+
+ copy = (char *) page_boundary - bytes_to_copy;
+ for (i = 0; i < bytes_to_copy; i++)
+ copy[i] = input[i];
+
+ for (n = 0; n <= 2 * length + 2; n++)
+ check_single (copy, length, n);
+ }
+ }
+}
+
+int
+main ()
+{
+ /* Simple string. */
+ { /* "Grüß Gott. Здравствуйте! x=(-b±sqrt(b²-4ac))/(2a) 日本語,中文,한글" */
+ static const char input[] =
+ { 'G', 'r', 0xC3, 0xBC, 0xC3, 0x9F, ' ', 'G', 'o', 't', 't', '.', ' ',
+ 0xD0, 0x97, 0xD0, 0xB4, 0xD1, 0x80, 0xD0, 0xB0, 0xD0, 0xB2, 0xD1, 0x81,
+ 0xD1, 0x82, 0xD0, 0xB2, 0xD1, 0x83, 0xD0, 0xB9, 0xD1, 0x82, 0xD0, 0xB5,
+ '!', ' ', 'x', '=', '(', '-', 'b', 0xC2, 0xB1, 's', 'q', 'r', 't', '(',
+ 'b', 0xC2, 0xB2, '-', '4', 'a', 'c', ')', ')', '/', '(', '2', 'a', ')',
+ ' ', ' ', 0xE6, 0x97, 0xA5, 0xE6, 0x9C, 0xAC, 0xE8, 0xAA, 0x9E, ',',
+ 0xE4, 0xB8, 0xAD, 0xE6, 0x96, 0x87, ',',
+ 0xED, 0x95, 0x9C, 0xEA, 0xB8, 0x80, '\0'
+ };
+ check (input, SIZEOF (input));
+ }
+
+ return 0;
+}
--
2.7.4