* MODULES.html.sh (func_all_modules): * lib/unistd.in.h (getentropy, getrandom): * m4/unistd_h.m4 (gl_UNISTD_H, gl_UNISTD_H_DEFAULTS): * modules/unistd (unistd.h): Add support for getentropy, getrandom. * doc/glibc-functions/getentropy.texi (getentropy): * doc/glibc-functions/getrandom.texi (getrandom): These are now fixed on some platforms. * lib/getentropy.c, lib/getrandom.c, lib/sys_random.in.h: * m4/getentropy.m4, m4/getrandom.m4: * modules/getentropy, modules/getentropy-tests: * modules/getrandom, modules/getrandom-tests: * tests/test-getentropy.c, tests/test-getrandom.c: New files. --- ChangeLog | 18 ++++++++++ MODULES.html.sh | 2 ++ doc/glibc-functions/getentropy.texi | 10 ++++-- doc/glibc-functions/getrandom.texi | 8 +++-- lib/getentropy.c | 51 ++++++++++++++++++++++++++++ lib/getrandom.c | 52 +++++++++++++++++++++++++++++ lib/sys_random.in.h | 9 +++++ lib/unistd.in.h | 34 +++++++++++++++++++ m4/getentropy.m4 | 16 +++++++++ m4/getrandom.m4 | 15 +++++++++ m4/unistd_h.m4 | 8 +++-- modules/getentropy | 29 ++++++++++++++++ modules/getentropy-tests | 12 +++++++ modules/getrandom | 44 ++++++++++++++++++++++++ modules/unistd | 4 +++ tests/test-getentropy.c | 43 ++++++++++++++++++++++++ 16 files changed, 348 insertions(+), 7 deletions(-) create mode 100644 lib/getentropy.c create mode 100644 lib/getrandom.c create mode 100644 lib/sys_random.in.h create mode 100644 m4/getentropy.m4 create mode 100644 m4/getrandom.m4 create mode 100644 modules/getentropy create mode 100644 modules/getentropy-tests create mode 100644 modules/getrandom create mode 100644 tests/test-getentropy.c
diff --git a/ChangeLog b/ChangeLog index 6b2faf17e..b1acb99ca 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,21 @@ +2020-05-25 Paul Eggert <egg...@cs.ucla.edu> + + getentropy, getrandom: new modules + * MODULES.html.sh (func_all_modules): + * lib/unistd.in.h (getentropy, getrandom): + * m4/unistd_h.m4 (gl_UNISTD_H, gl_UNISTD_H_DEFAULTS): + * modules/unistd (unistd.h): + Add support for getentropy, getrandom. + * doc/glibc-functions/getentropy.texi (getentropy): + * doc/glibc-functions/getrandom.texi (getrandom): + These are now fixed on some platforms. + * lib/getentropy.c, lib/getrandom.c, lib/sys_random.in.h: + * m4/getentropy.m4, m4/getrandom.m4: + * modules/getentropy, modules/getentropy-tests: + * modules/getrandom, modules/getrandom-tests: + * tests/test-getentropy.c, tests/test-getrandom.c: + New files. + 2020-05-25 Bruno Haible <br...@clisp.org> Add missing C99 dependencies. diff --git a/MODULES.html.sh b/MODULES.html.sh index 318a15a1d..4db2a77ec 100755 --- a/MODULES.html.sh +++ b/MODULES.html.sh @@ -3484,9 +3484,11 @@ func_all_modules () func_module forkpty func_module getdomainname func_module xgetdomainname + func_module getentropy func_module getloadavg func_module getpagesize func_module getprogname + func_module getrandom func_module getusershell func_module lib-symbol-visibility func_module login_tty diff --git a/doc/glibc-functions/getentropy.texi b/doc/glibc-functions/getentropy.texi index 5ad873a71..5281dcd4e 100644 --- a/doc/glibc-functions/getentropy.texi +++ b/doc/glibc-functions/getentropy.texi @@ -15,15 +15,19 @@ Documentation: @uref{https://www.kernel.org/doc/man-pages/online/pages/man3/getentropy.3.html,,man getentropy}. @end itemize -Gnulib module: --- +Gnulib module: getentropy Portability problems fixed by Gnulib: @itemize +@item +This function is missing on some platforms: +glibc 2.24, Mac OS X 10.5, FreeBSD 11.0, NetBSD 5.0, OpenBSD 3.8, +Solaris 11.0, Android 9.0. @end itemize Portability problems not fixed by Gnulib: @itemize @item -This function is missing on many non-glibc platforms: -glibc 2.24, Mac OS X 10.5, FreeBSD 11.0, NetBSD 5.0, OpenBSD 3.8, Minix 3.1.8, AIX 7.1, HP-UX 11.31, IRIX 6.5, Solaris 11.0, Cygwin, mingw, MSVC 14, Android 9.0. +This function is missing on some platforms: +Minix 3.1.8, IRIX 6.5, mingw, MSVC 14. @end itemize diff --git a/doc/glibc-functions/getrandom.texi b/doc/glibc-functions/getrandom.texi index 25e899d63..2f4debcfe 100644 --- a/doc/glibc-functions/getrandom.texi +++ b/doc/glibc-functions/getrandom.texi @@ -19,11 +19,15 @@ Gnulib module: --- Portability problems fixed by Gnulib: @itemize +@item +This function is missing on some platforms: +glibc 2.24, Mac OS X 10.5, FreeBSD 11.0, NetBSD 5.0, OpenBSD 3.8, +Solaris 11.0, Android 9.0. @end itemize Portability problems not fixed by Gnulib: @itemize @item -This function is missing on many non-glibc platforms: -glibc 2.24, Mac OS X 10.5, FreeBSD 11.0, NetBSD 5.0, OpenBSD 3.8, Minix 3.1.8, AIX 7.1, HP-UX 11.31, IRIX 6.5, Solaris 11.0, Cygwin, mingw, MSVC 14, Android 9.0. +This function is missing on some platforms: +Minix 3.1.8, IRIX 6.5, mingw, MSVC 14. @end itemize diff --git a/lib/getentropy.c b/lib/getentropy.c new file mode 100644 index 000000000..2b6c3f55f --- /dev/null +++ b/lib/getentropy.c @@ -0,0 +1,51 @@ +/* Fill a buffer with random bytes. + Copyright 2020 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 Paul Eggert. */ + +#include <config.h> + +#include <unistd.h> + +#include <errno.h> +#include <sys/random.h> + +/* Overwrite BUFFER with random data. BUFFER contains LENGTH bytes. + LENGTH must be at most 256. Return 0 on success, -1 (setting + errno) on failure. */ +int +getentropy (void *buffer, size_t length) +{ + char *buf = buffer; + + if (length <= 256) + for (;;) + { + ssize_t bytes; + if (length == 0) + return 0; + while ((bytes = getrandom (buf, length, 0)) < 0) + if (errno != EINTR) + return -1; + if (bytes == 0) + break; + buf += bytes; + length -= bytes; + } + + errno = EIO; + return -1; +} diff --git a/lib/getrandom.c b/lib/getrandom.c new file mode 100644 index 000000000..9b6ecb43b --- /dev/null +++ b/lib/getrandom.c @@ -0,0 +1,52 @@ +/* Obtain a series of random bytes. + + Copyright 2020 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 Paul Eggert. */ + +#include <config.h> + +#include <sys/random.h> + +#include "minmax.h" +#include <fcntl.h> +#include <stdbool.h> +#include <unistd.h> + +/* Set BUFFER (of size LENGTH) to random bytes under the control of FLAGS. + Return the number of bytes written, or -1 on error. */ +ssize_t +getrandom (void *buffer, size_t length, unsigned int flags) +{ + static int randfd[2] = { -1, -1 }; + bool devrandom = (flags & GRND_RANDOM) != 0; + int fd = randfd[devrandom]; + + if (fd < 0) + { + static char const randdevice[][MAX (sizeof NAME_OF_NONCE_DEVICE, + sizeof NAME_OF_RANDOM_DEVICE)] + = { NAME_OF_NONCE_DEVICE, NAME_OF_RANDOM_DEVICE }; + int oflags = (O_RDONLY + O_CLOEXEC + + (flags & GRND_NONBLOCK ? O_NONBLOCK : 0)); + fd = open (randdevice[devrandom], oflags); + if (fd < 0) + return fd; + randfd[devrandom] = fd; + } + + return read (fd, buffer, length); +} diff --git a/lib/sys_random.in.h b/lib/sys_random.in.h new file mode 100644 index 000000000..e3f308ce4 --- /dev/null +++ b/lib/sys_random.in.h @@ -0,0 +1,9 @@ +#ifndef _SYS_RANDOM_H +#define _SYS_RANDOM_H 1 + +#include <sys/types.h> +#define GRND_NONBLOCK 1 +#define GRND_RANDOM 2 +ssize_t getrandom (void *, size_t, unsigned int); + +#endif diff --git a/lib/unistd.in.h b/lib/unistd.in.h index 1454d7e85..4a0d6655a 100644 --- a/lib/unistd.in.h +++ b/lib/unistd.in.h @@ -763,6 +763,22 @@ _GL_WARN_ON_USE (getdtablesize, "getdtablesize is unportable - " #endif +#if @GNULIB_GETENTROPY@ +/* Fill a buffer with random bytes. */ +# if !@HAVE_GETENTROPY@ +_GL_FUNCDECL_SYS (getentropy, int, (void *buffer, size_t length)); +# endif +_GL_CXXALIAS_SYS (getentropy, int, (void *buffer, size_t length)); +_GL_CXXALIASWARN (getentropy); +#elif defined GNULIB_POSIXCHECK +# undef getentropy +# if HAVE_RAW_DECL_GETENTROPY +_GL_WARN_ON_USE (getentropy, "getentropy is unportable - " + "use gnulib module getentropy for portability"); +# endif +#endif + + #if @GNULIB_GETGROUPS@ /* Return the supplemental groups that the current process belongs to. It is unspecified whether the effective group id is in the list. @@ -1014,6 +1030,24 @@ _GL_WARN_ON_USE (getpass, "getpass is unportable - " #endif +#if @GNULIB_GETRANDOM@ +/* Fill a buffer with random bytes. */ +# if !@HAVE_GETRANDOM@ +_GL_FUNCDECL_SYS (getrandom, int, (void *buffer, size_t length, + unsigned int flags)); +# endif +_GL_CXXALIAS_SYS (getrandom, int, (void *buffer, size_t length, + unsigned int flags)); +_GL_CXXALIASWARN (getrandom); +#elif defined GNULIB_POSIXCHECK +# undef getrandom +# if HAVE_RAW_DECL_GETRANDOM +_GL_WARN_ON_USE (getrandom, "getrandom is unportable - " + "use gnulib module getrandom for portability"); +# endif +#endif + + #if @GNULIB_GETUSERSHELL@ /* Return the next valid login shell on the system, or NULL when the end of the list has been reached. */ diff --git a/m4/getentropy.m4 b/m4/getentropy.m4 new file mode 100644 index 000000000..596544b2e --- /dev/null +++ b/m4/getentropy.m4 @@ -0,0 +1,16 @@ +# getentropy.m4 +dnl Copyright 2020 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. + +dnl Written by Paul Eggert. + +AC_DEFUN([gl_FUNC_GETENTROPY], +[ + AC_REQUIRE([gl_UNISTD_H_DEFAULTS]) + AC_CHECK_FUNCS_ONCE([getentropy]) + if test $ac_cv_func_getentropy = no; then + HAVE_GETENTROPY=0 + fi +]) diff --git a/m4/getrandom.m4 b/m4/getrandom.m4 new file mode 100644 index 000000000..ecb44aebf --- /dev/null +++ b/m4/getrandom.m4 @@ -0,0 +1,15 @@ +# getrandom.m4 +dnl Copyright 2020 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. + +dnl Written by Paul Eggert. + +AC_DEFUN([gl_FUNC_GETRANDOM], +[ + AC_CHECK_FUNCS_ONCE([getrandom]) + if test "$ac_cv_func_getrandom" != yes; then + HAVE_GETRANDOM=0 + fi +]) diff --git a/m4/unistd_h.m4 b/m4/unistd_h.m4 index e776f3bd4..1f39d53e1 100644 --- a/m4/unistd_h.m4 +++ b/m4/unistd_h.m4 @@ -43,8 +43,8 @@ AC_DEFUN([gl_UNISTD_H], #endif ]], [access chdir chown dup dup2 dup3 environ euidaccess faccessat fchdir fchownat fdatasync fsync ftruncate getcwd getdomainname getdtablesize - getgroups gethostname getlogin getlogin_r getpagesize getpass - getusershell setusershell endusershell + getentropy getgroups gethostname getlogin getlogin_r getpagesize getpass + getrandom getusershell setusershell endusershell group_member isatty lchown link linkat lseek pipe pipe2 pread pwrite readlink readlinkat rmdir sethostname sleep symlink symlinkat truncate ttyname_r unlink unlinkat usleep]) @@ -82,6 +82,7 @@ AC_DEFUN([gl_UNISTD_H_DEFAULTS], GNULIB_GETCWD=0; AC_SUBST([GNULIB_GETCWD]) GNULIB_GETDOMAINNAME=0; AC_SUBST([GNULIB_GETDOMAINNAME]) GNULIB_GETDTABLESIZE=0; AC_SUBST([GNULIB_GETDTABLESIZE]) + GNULIB_GETENTROPY=0; AC_SUBST([GNULIB_GETENTROPY]) GNULIB_GETGROUPS=0; AC_SUBST([GNULIB_GETGROUPS]) GNULIB_GETHOSTNAME=0; AC_SUBST([GNULIB_GETHOSTNAME]) GNULIB_GETLOGIN=0; AC_SUBST([GNULIB_GETLOGIN]) @@ -89,6 +90,7 @@ AC_DEFUN([gl_UNISTD_H_DEFAULTS], GNULIB_GETOPT_POSIX=0; AC_SUBST([GNULIB_GETOPT_POSIX]) GNULIB_GETPAGESIZE=0; AC_SUBST([GNULIB_GETPAGESIZE]) GNULIB_GETPASS=0; AC_SUBST([GNULIB_GETPASS]) + GNULIB_GETRANDOM=0; AC_SUBST([GNULIB_GETRANDOM]) GNULIB_GETUSERSHELL=0; AC_SUBST([GNULIB_GETUSERSHELL]) GNULIB_GROUP_MEMBER=0; AC_SUBST([GNULIB_GROUP_MEMBER]) GNULIB_ISATTY=0; AC_SUBST([GNULIB_ISATTY]) @@ -129,11 +131,13 @@ AC_DEFUN([gl_UNISTD_H_DEFAULTS], HAVE_FSYNC=1; AC_SUBST([HAVE_FSYNC]) HAVE_FTRUNCATE=1; AC_SUBST([HAVE_FTRUNCATE]) HAVE_GETDTABLESIZE=1; AC_SUBST([HAVE_GETDTABLESIZE]) + HAVE_GETENTROPY=1; AC_SUBST([HAVE_GETENTROPY]) HAVE_GETGROUPS=1; AC_SUBST([HAVE_GETGROUPS]) HAVE_GETHOSTNAME=1; AC_SUBST([HAVE_GETHOSTNAME]) HAVE_GETLOGIN=1; AC_SUBST([HAVE_GETLOGIN]) HAVE_GETPAGESIZE=1; AC_SUBST([HAVE_GETPAGESIZE]) HAVE_GETPASS=1; AC_SUBST([HAVE_GETPASS]) + HAVE_GETRANDOM=1; AC_SUBST([HAVE_GETRANDOM]) HAVE_GROUP_MEMBER=1; AC_SUBST([HAVE_GROUP_MEMBER]) HAVE_LCHOWN=1; AC_SUBST([HAVE_LCHOWN]) HAVE_LINK=1; AC_SUBST([HAVE_LINK]) diff --git a/modules/getentropy b/modules/getentropy new file mode 100644 index 000000000..9696c1e1d --- /dev/null +++ b/modules/getentropy @@ -0,0 +1,29 @@ +Description: +Fill buffer with random bytes. + +Files: +lib/getentropy.c +m4/getentropy.m4 + +Depends-on: +getrandom [test $HAVE_GETENTROPY = 0] +extensions +unistd + +configure.ac: +gl_FUNC_GETENTROPY +if test $HAVE_GETENTROPY = 0; then + AC_LIBOBJ([getentropy]) +fi +gl_UNISTD_MODULE_INDICATOR([getentropy]) + +Makefile.am: + +Include: +<unistd.h> + +License: +LGPL + +Maintainer: +all diff --git a/modules/getentropy-tests b/modules/getentropy-tests new file mode 100644 index 000000000..b5c4b1144 --- /dev/null +++ b/modules/getentropy-tests @@ -0,0 +1,12 @@ +Files: +tests/test-getentropy.c +tests/signature.h +tests/macros.h + +Depends-on: + +configure.ac: + +Makefile.am: +TESTS += test-getentropy +check_PROGRAMS += test-getentropy diff --git a/modules/getrandom b/modules/getrandom new file mode 100644 index 000000000..2ae374f61 --- /dev/null +++ b/modules/getrandom @@ -0,0 +1,44 @@ +Description: +Fill buffer with random bytes. + +Files: +lib/getrandom.c +lib/sys_random.in.h +m4/getrandom.m4 + +Depends-on: +crypto/gc-random [test $HAVE_GETRANDOM = 0] +fcntl-h [test $HAVE_GETRANDOM = 0] +minmax [test $HAVE_GETRANDOM = 0] +open [test $HAVE_GETRANDOM = 0] + +configure.ac: +gl_FUNC_GETRANDOM +if test $HAVE_GETRANDOM = 0; then + AC_LIBOBJ([getrandom]) +fi +gl_UNISTD_MODULE_INDICATOR([getrandom]) + +Makefile.am: +BUILT_SOURCES += sys/random.h + +# We need the following in order to create <sys/random.h> when the system +# doesn't have one that works with the given compiler. +sys/random.h: sys_random.in.h $(top_builddir)/config.status $(CXXDEFS_H) $(WARN_ON_USE_H) $(ARG_NONNULL_H) + $(AM_V_at)$(MKDIR_P) sys + $(AM_V_GEN)rm -f $@-t $@ && \ + { echo '/* DO NOT EDIT! GENERATED AUTOMATICALLY! */'; \ + cat $(srcdir)/sys_random.in.h; \ + } > $@-t && \ + mv -f $@-t $@ +MOSTLYCLEANFILES += sys/random.h sys/random.h-t +MOSTLYCLEANDIRS += sys + +Include: +<sys/random.h> + +License: +LGPL + +Maintainer: +all diff --git a/modules/unistd b/modules/unistd index 3c28f36ca..dc32ce056 100644 --- a/modules/unistd +++ b/modules/unistd @@ -55,6 +55,7 @@ unistd.h: unistd.in.h $(top_builddir)/config.status $(CXXDEFS_H) $(ARG_NONNULL_H -e 's/@''GNULIB_GETCWD''@/$(GNULIB_GETCWD)/g' \ -e 's/@''GNULIB_GETDOMAINNAME''@/$(GNULIB_GETDOMAINNAME)/g' \ -e 's/@''GNULIB_GETDTABLESIZE''@/$(GNULIB_GETDTABLESIZE)/g' \ + -e 's/@''GNULIB_GETENTROPY''@/$(GNULIB_GETENTROPY)/g' \ -e 's/@''GNULIB_GETGROUPS''@/$(GNULIB_GETGROUPS)/g' \ -e 's/@''GNULIB_GETHOSTNAME''@/$(GNULIB_GETHOSTNAME)/g' \ -e 's/@''GNULIB_GETLOGIN''@/$(GNULIB_GETLOGIN)/g' \ @@ -62,6 +63,7 @@ unistd.h: unistd.in.h $(top_builddir)/config.status $(CXXDEFS_H) $(ARG_NONNULL_H -e 's/@''GNULIB_GETOPT_POSIX''@/$(GNULIB_GETOPT_POSIX)/g' \ -e 's/@''GNULIB_GETPAGESIZE''@/$(GNULIB_GETPAGESIZE)/g' \ -e 's/@''GNULIB_GETPASS''@/$(GNULIB_GETPASS)/g' \ + -e 's/@''GNULIB_GETRANDOM''@/$(GNULIB_GETRANDOM)/g' \ -e 's/@''GNULIB_GETUSERSHELL''@/$(GNULIB_GETUSERSHELL)/g' \ -e 's/@''GNULIB_GROUP_MEMBER''@/$(GNULIB_GROUP_MEMBER)/g' \ -e 's/@''GNULIB_ISATTY''@/$(GNULIB_ISATTY)/g' \ @@ -103,10 +105,12 @@ unistd.h: unistd.in.h $(top_builddir)/config.status $(CXXDEFS_H) $(ARG_NONNULL_H -e 's|@''HAVE_FSYNC''@|$(HAVE_FSYNC)|g' \ -e 's|@''HAVE_FTRUNCATE''@|$(HAVE_FTRUNCATE)|g' \ -e 's|@''HAVE_GETDTABLESIZE''@|$(HAVE_GETDTABLESIZE)|g' \ + -e 's|@''HAVE_GETENTROPY''@|$(HAVE_GETENTROPY)|g' \ -e 's|@''HAVE_GETGROUPS''@|$(HAVE_GETGROUPS)|g' \ -e 's|@''HAVE_GETHOSTNAME''@|$(HAVE_GETHOSTNAME)|g' \ -e 's|@''HAVE_GETPAGESIZE''@|$(HAVE_GETPAGESIZE)|g' \ -e 's|@''HAVE_GETPASS''@|$(HAVE_GETPASS)|g' \ + -e 's|@''HAVE_GETRANDOM''@|$(HAVE_GETRANDOM)|g' \ -e 's|@''HAVE_GROUP_MEMBER''@|$(HAVE_GROUP_MEMBER)|g' \ -e 's|@''HAVE_LCHOWN''@|$(HAVE_LCHOWN)|g' \ -e 's|@''HAVE_LINK''@|$(HAVE_LINK)|g' \ diff --git a/tests/test-getentropy.c b/tests/test-getentropy.c new file mode 100644 index 000000000..845c5dcaf --- /dev/null +++ b/tests/test-getentropy.c @@ -0,0 +1,43 @@ +/* Test the getentropy function. + Copyright 2020 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 Paul Eggert. */ + +#include <config.h> + +#include <unistd.h> + +#include <string.h> + +#include "signature.h" +SIGNATURE_CHECK (getentropy, int, (void *, size_t)); + +#include "macros.h" + +char empty_buf[256]; +char buf[256]; + +int +main (int argc, char *argv[]) +{ + /* If this test fails, there's something wrong with the setup anyway. */ + ASSERT (getentropy (buf, sizeof buf) == 0); + + /* This test fails with probability 2**-2048. (Run it again if so. :-) */ + ASSERT (memcmp (buf, empty_buf, sizeof buf) != 0); + + return 0; +} -- 2.17.1