/* ========================================================================== */
/*! \file
 * \brief Implementation of XDG Base Directory Specification
 *
 * Copyright (c) 2020-2024 by the developers. See the LICENSE file for details.
 *
 * This is used by different modules that should not depend on CORE module.
 */


/* ========================================================================== */
/* Include headers */

#include "posix.h"  /* Include this first because of feature test macros */

#include <string.h>

#include "fileutils.h"
#include "main.h"
#include "xdg.h"


/* ========================================================================== */
/*! \defgroup XDG XDG: Freedesktop.org Base Directory Specification
 *
 * Implementation of XDG Base Directory Specification (version 0.7).
 */
/*! @{ */


/* ========================================================================== */
/* Constants */

/*! \brief Message prefix for XDG module */
#define MAIN_ERR_PREFIX  "XDG: "


/* ========================================================================== */
/*! \brief Append path component to buffer
 *
 * \param[in] buf      Pointer to buffer pointer
 * \param[in] newcomp  New path component to append
 *
 * First a slash "/" is appended to the content of buf.
 * Then, if \e newcomp is not \c NULL , it is appended after the slash.
 * Additional memory is automatically allocated.
 *
 * \attention Dereferenced \e buf must be usable for \c realloc() .
 *
 * \return
 * - Zero on success
 * - Negative value on error
 */

int  xdg_append_to_path(const char**  buf, const char*  newcomp)
{
   int  res = 0;
   char*  tmp;
   size_t  len_buf = 0;
   size_t  len_new = 0;

   if(NULL != *buf)
   {
      len_buf = strlen(*buf);
   }
   if(NULL != newcomp)
   {
      len_new = strlen(newcomp);
   }

   /* Allocate 2 additional bytes for "/" separator and string termination */
   tmp = (char*) api_posix_realloc((void*) *buf,
                                   len_buf + len_new + (size_t) 2);
   if(NULL == tmp)
   {
      PRINT_ERROR("Cannot allocate memory for path or pathname");
      res = -1;
   }
   else
   {
      tmp[len_buf++] = '/';  /* Increment length for slash */
      if(NULL != newcomp)
      {
         memcpy((void*) &tmp[len_buf], (void*) newcomp, len_new);
      }
      /* Terminate new string */
      tmp[len_buf + len_new] = 0;
      *buf = tmp;
   }

   return(res);
}


/* ========================================================================== */
/*! \brief Get configuration directory
 *
 * \param[in] progname  Program name
 *
 * If \e progname is \c NULL then \c $XDG_CONFIG_HOME is returned. Otherwise
 * \e progname is appended as subdirectory and $XDG_CONFIG_HOME/progname is
 * returned.
 *
 * If \c XDG_CONFIG_HOME is not defined, the default \c $HOME/.config is used
 * (it is treated as error if \c HOME is not defined in this case).
 *
 * \note
 * On success, the caller is responsible to free the memory allocated for the
 * result.
 *
 * \return
 * - Pointer to result on success
 * - \c NULL on error
 */

const char*  xdg_get_confdir(const char*  progname)
{
   static int  confpath_msg_printed = 0;
   size_t  len;
   char*  tmp;
   const char*  buf = NULL;

   /* User specified path has higher precedence than XDG_CONFIG_HOME */
   if(NULL != main_confprefix)
   {
      if('/' != main_confprefix[0])
      {
         PRINT_ERROR("No absolute path specified for configuration directory");
         goto error;
      }

      /* Use path specified by user */
      len = strlen(main_confprefix);
      tmp = (char*) api_posix_malloc(++len);
      if(NULL == tmp)
      {
         goto error;
      }
      memcpy(tmp, main_confprefix, len);
      buf = tmp;
   }
   else
   {
      /* Use path specified by XDG */
      if(0 == ts_getenv("XDG_CONFIG_HOME", &buf))
      {
         /* XDG_CONFIG_HOME must contain an absolute path */
         if('/' != buf[0])
         {
            PRINT_ERROR("XDG_CONFIG_HOME present, but not an absolute path");
            goto error;
         }
      }
      else
      {
         /* "XDG_CONFIG_HOME" not defined => Use default "$HOME/.config" */
         if(0 != ts_getenv("HOME", &buf))
         {
            /* Treat missing "HOME" as error */
            PRINT_ERROR("Environment variable HOME is not defined");
            goto error;
         }
         else
         {
            if(0 != xdg_append_to_path(&buf, ".config"))
            {
               goto error;
            }
         }
      }

      /* Append program name, if specified */
      if(NULL != progname)
      {
         if(0 != xdg_append_to_path(&buf, progname))
         {
            goto error;
         }
      }
   }

   /* Check path for unsupported characters */
   if(0 != fu_check_path(buf))
   {
      goto error;
   }

   /* Create configuration path, if it doesn't exist */
   if(0 != fu_create_path(buf, (api_posix_mode_t) API_POSIX_S_IRWXU))
   {
      goto error;
   }

   if(main_debug)
   {
      /* Print debug message only once */
      if(!confpath_msg_printed)
      {
         printf("%s: %sConfiguration path: %s\n",
                 CFG_NAME, MAIN_ERR_PREFIX, buf);
         confpath_msg_printed = 1;
      }
   }

exit:
   return(buf);

error:
   api_posix_free((void*) buf);
   buf = NULL;
   goto exit;
}


/*! @} */

/* EOF */
