/* Test of strerror_r() function.
   Copyright (C) 2007-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, 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/>.  */

#include <config.h>

#include <string.h>

#include "signature.h"
SIGNATURE_CHECK (strerror_r, int, (int, char *, size_t));

#include <errno.h>

#include "macros.h"

int
main (void)
{
  char buf[100];
  int ret;

  /* Test results with valid errnum and enough room.  */

  errno = 0;
  buf[0] = '\0';
  ASSERT (strerror_r (EACCES, buf, sizeof buf) == 0);
  ASSERT (buf[0] != '\0');
  ASSERT (errno == 0);
  ASSERT (strlen (buf) < sizeof buf);

  errno = 0;
  buf[0] = '\0';
  ASSERT (strerror_r (ETIMEDOUT, buf, sizeof buf) == 0);
  ASSERT (buf[0] != '\0');
  ASSERT (errno == 0);
  ASSERT (strlen (buf) < sizeof buf);

  errno = 0;
  buf[0] = '\0';
  ASSERT (strerror_r (EOVERFLOW, buf, sizeof buf) == 0);
  ASSERT (buf[0] != '\0');
  ASSERT (errno == 0);
  ASSERT (strlen (buf) < sizeof buf);

  /* POSIX requires strerror (0) to succeed.  Reject use of "Unknown
     error", but allow "Success", "No error", or even Solaris' "Error
     0" which are distinct patterns from true out-of-range strings.
     http://austingroupbugs.net/view.php?id=382  */
  errno = 0;
  buf[0] = '\0';
  ret = strerror_r (0, buf, sizeof buf);
  ASSERT (ret == 0);
  ASSERT (buf[0]);
  ASSERT (errno == 0);
  ASSERT (strstr (buf, "nknown") == NULL);
  ASSERT (strstr (buf, "ndefined") == NULL);

  /* Test results with out-of-range errnum and enough room.  POSIX
     allows an empty string on success, and allows an unchanged buf on
     error, but these are not useful, so we guarantee contents.  */
  errno = 0;
  buf[0] = '^';
  ret = strerror_r (-3, buf, sizeof buf);
  ASSERT (ret == 0 || ret == EINVAL);
  ASSERT (buf[0] != '^');
  ASSERT (*buf);
  ASSERT (errno == 0);
  ASSERT (strlen (buf) < sizeof buf);

  /* Test results with a too small buffer.  POSIX requires an error;
     only ERANGE for 0 and valid errors, and a choice of ERANGE or
     EINVAL for out-of-range values.  On error, POSIX permits buf to
     be empty, unchanged, or unterminated, but these are not useful,
     so we guarantee NUL-terminated truncated contents for all but
     size 0.  http://austingroupbugs.net/view.php?id=398.  Also ensure
     that no out-of-bounds writes occur.  */
  {
    int errs[] = { EACCES, 0, -3, };
    int j;

    buf[sizeof buf - 1] = '\0';
    for (j = 0; j < SIZEOF (errs); j++)
      {
        int err = errs[j];
        char buf2[sizeof buf] = "";
        size_t len;
        size_t i;

        strerror_r (err, buf2, sizeof buf2);
        len = strlen (buf2);
        ASSERT (len < sizeof buf);

        for (i = 0; i <= len; i++)
          {
            memset (buf, '^', sizeof buf - 1);
            errno = 0;
            ret = strerror_r (err, buf, i);
            ASSERT (errno == 0);
            if (j == 2)
              ASSERT (ret == ERANGE || ret == EINVAL);
            else
              ASSERT (ret == ERANGE);
            if (i)
              {
                ASSERT (strncmp (buf, buf2, i - 1) == 0);
                ASSERT (buf[i - 1] == '\0');
              }
            ASSERT (strspn (buf + i, "^") == sizeof buf - 1 - i);
          }

        strcpy (buf, "BADFACE");
        errno = 0;
        ret = strerror_r (err, buf, len + 1);
        ASSERT (ret != ERANGE);
        ASSERT (errno == 0);
        ASSERT (strcmp (buf, buf2) == 0);
      }
  }

#if GNULIB_STRERROR
  /* Test that strerror_r does not clobber strerror buffer.  On some
     platforms, this test can only succeed if gnulib also replaces
     strerror.  */
  {
    const char *msg1;
    const char *msg2;
    const char *msg3;
    const char *msg4;
    char *str1;
    char *str2;
    char *str3;
    char *str4;

    msg1 = strerror (ENOENT);
    ASSERT (msg1);
    str1 = strdup (msg1);
    ASSERT (str1);

    msg2 = strerror (ERANGE);
    ASSERT (msg2);
    str2 = strdup (msg2);
    ASSERT (str2);

    msg3 = strerror (-4);
    ASSERT (msg3);
    str3 = strdup (msg3);
    ASSERT (str3);

    msg4 = strerror (1729576);
    ASSERT (msg4);
    str4 = strdup (msg4);
    ASSERT (str4);

    strerror_r (EACCES, buf, sizeof buf);
    strerror_r (-5, buf, sizeof buf);
    ASSERT (msg1 == msg2 || msg1 == msg4 || STREQ (msg1, str1));
    ASSERT (msg2 == msg4 || STREQ (msg2, str2));
    ASSERT (msg3 == msg4 || STREQ (msg3, str3));
    ASSERT (STREQ (msg4, str4));

    free (str1);
    free (str2);
    free (str3);
    free (str4);
  }
#endif

  return 0;
}
