text: /*************************************************************************\
  *                  Copyright (C) Michael Kerrisk, 2020.                   *
  *                                                                         *
  * This program is free software. You may use, modify, and redistribute 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 without any warranty.  See   *
  * the file COPYING.gpl-v3 for details.                                    *
  \*************************************************************************/
  
  /* Listing 31-3 */
  
  /* strerror_tsd.c
  
     An implementation of strerror() that is made thread-safe through
     the use of thread-specific data.
  
     See also strerror_tls.c.
  */
  #define _GNU_SOURCE                 /* Get '_sys_nerr' and '_sys_errlist'
                                         declarations from <stdio.h> */
  #include <stdio.h>
  #include <string.h>                 /* Get declaration of strerror() */
  #include <pthread.h>
  #include "tlpi_hdr.h"
  
  static pthread_once_t once = PTHREAD_ONCE_INIT;
  static pthread_key_t strerrorKey;
  
  #define MAX_ERROR_LEN 256           /* Maximum length of string in per-thread
                                         buffer returned by strerror() */
  
  static void                         /* Free thread-specific data buffer */
  destructor(void *buf)
  {
      free(buf);
  }
  
  static void                         /* One-time key creation function */
  createKey(void)
  {
      int s;
  
      /* Allocate a unique thread-specific data key and save the address
         of the destructor for thread-specific data buffers */
  
      s = pthread_key_create(&strerrorKey, destructor);
      if (s != 0)
          errExitEN(s, "pthread_key_create");
  }
  
  char *
  strerror(int err)
  {
      int s;
      char *buf;
  
      /* Make first caller allocate key for thread-specific data */
  
      s = pthread_once(&once, createKey);
      if (s != 0)
          errExitEN(s, "pthread_once");
  
      buf = pthread_getspecific(strerrorKey);
      if (buf == NULL) {          /* If first call from this thread, allocate
                                     buffer for thread, and save its location */
          buf = malloc(MAX_ERROR_LEN);
          if (buf == NULL)
              errExit("malloc");
  
          s = pthread_setspecific(strerrorKey, buf);
          if (s != 0)
              errExitEN(s, "pthread_setspecific");
      }
  
      if (err < 0 || err >= _sys_nerr || _sys_errlist[err] == NULL) {
          snprintf(buf, MAX_ERROR_LEN, "Unknown error %d", err);
      } else {
          strncpy(buf, _sys_errlist[err], MAX_ERROR_LEN - 1);
          buf[MAX_ERROR_LEN - 1] = '\0';          /* Ensure null termination */
      }
  
      return buf;
  }
  