OverviewFilesCommitsIssuesDocsReleasesPeers

opencog-link-grammar / link-parser / lg_xdg.c

245 lines · c ref:
/***************************************************************************/
/* Amir Plivatsky                                                          */
/*                                                                         */
/* Use of the link grammar parsing system is subject to the terms of the   */
/* license set forth in the LICENSE file included with this software.      */
/* This license allows free redistribution and use in source and binary    */
/* forms, with or without modification, subject to certain conditions.     */
/*                                                                         */
/***************************************************************************/

/**
 * This file includes minimal implementation of the XDG
 * specification version 0.8. See:
 * https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html
 *
 * It is supports just what needed to support the history file location.
 */

#if HAVE_WIDECHAR_EDITLINE
#include <ctype.h>
#include <stdarg.h>

#include "link-grammar/link-includes.h"
#include <sys/stat.h>
#include <stdbool.h>
#include <stdlib.h>                     // malloc etc.
#include <string.h>
#include <errno.h>

#include "lg_xdg.h"
#include "parser-utilities.h"

typedef struct
{
	const char *rel_path;
	const char *env_var;
} xdg_definition;;

xdg_definition xdg_def[] =
{
	{ "/.local/state", "XDG_STATE_HOME" },
	// Add more definitions if needed.
};

static bool is_empty(const char *path)
{
	return path == NULL || path[0] == '\0';
}

static bool is_absolute_path(const char *path)
{
	if (is_empty(path)) return false; // Empty path is not absolute

#if defined(_WIN32) || defined(__CYGWIN__)
	// Check for Windows absolute paths.
	if ((isalpha((unsigned char)path[0]) && path[1] == ':' &&
	     (path[2] == '\\' || path[2] == '/')) ||
	    (path[0] == '\\' && path[1] == '\\'))
	{
		return true;
	}
#endif

	// Check for POSIX absolute path.
	if (path[0] == '/') return true;

	return false; // Path is not absolute
}

static bool is_sep(int c)
{
#if defined(_WIN32) || defined(__CYGWIN__)
	if (c == '\\') return true;
#endif
	if (c == '/') return true;

	return false;
}

/**
 * Make directories for each directory component of \p path.
 * If the last component is not ending with "/", it is considered
 * a file name.
 */
static bool make_dirpath(const char *path)
{
	struct stat sb;

#if defined(_WIN32) || defined(__CYGWIN__)
	// Skip Windows UNC path \\X
	if (is_sep(path[0]) && is_sep(path[1]))
	{
		const char *p;

		// Start from the root or network share
		for (p = path + 2; *p != '\0'; p++)
			if (is_sep(*p)) break;
		if (*p == '\0') return true;  // No further subdirectories
	}
#endif

	char *dir = strdup(path);

	for (char *p = dir+1; '\0' != *p; p++)
	{
		char sep = *p;
		if (is_sep(*p))
		{
			if (is_sep(p[-1])) continue; // Ignore directory separator sequences
			*p = '\0'; // Now dir is the path up to this point
			//prt_error("Debug: mkdir: '%s'\n", dir);
			if (mkdir(dir, S_IRWXU) == -1)
			{
				int save_errno = errno;
				if (errno == EEXIST)
				{
					if (stat(dir, &sb) != 0 || !S_ISDIR(sb.st_mode))
						goto mkdir_error;
					errno = 0;
				}
mkdir_error:
				if (errno != 0)
				{
					prt_error("Error: Cannot create directory '%s': %s.\n",
					          dir, strerror(save_errno));
					free(dir);
					return false;
				}
			}
			*p = sep;
		}
	}

	free(dir);
	return true;
}

/**
 * @brief Get the home directory for the given XDG base directory type.
 *
 * @param bd_type The XDG base directory type.
 * @return const char* The home directory path (freed by caller).
 */
const char *xdg_get_home(xdg_basedir_type bd_type)
{
	const char *def_suffix = xdg_def[bd_type].rel_path;
	const char *evars[] = { xdg_def[bd_type].env_var, "HOME",
#if defined(_WIN32) || defined(__CYGWIN__)
		"USERPROFILE"
#endif
	};
	char *dir;

	size_t num_evars = sizeof(evars)/sizeof(evars[0]);
	size_t i;
	for (i = 0; i < num_evars; i++)
	{
		dir = getenv(evars[i]);
		if (is_empty(dir)) continue;

		if (is_absolute_path(dir)) break;
		if (num_evars - 1 != i) // Avoid a double notification
		{
			prt_error("Warning: %s is not an absolute path (ignored).\n",
			          evars[i]);
		}
	}

	if (!is_absolute_path(dir))
	{
		prt_error("Error: %s is not set or is not an absolute path.\n",
		          evars[num_evars - 1]);
		return NULL;
	}

	char *def_dir;
	// def_suffix is a directory - append '/' so make_dirpath() will create it.
	int n = asprintf(&def_dir, "%s%s/", dir, (i > 0) ? def_suffix : "");
	if (!make_dirpath(def_dir))
	{
		free(def_dir);
		return NULL;
	}
	def_dir[n-1] = '\0'; // Remove the last '/'

	return def_dir;
}

/**
 * @brief Get the program name from the provided path.
 *
 * @param argv0 The path to the executable.
 * @return const char* The program name.
 */
const char *xdg_get_program_name(const char *argv0)
{
	if ((NULL == argv0) || ('\0' == argv0[0])) return NULL;

	const char *basename = argv0;
	for (const char *p = argv0; *p != '\0'; p++)
		if (is_sep(*p)) basename = p + 1;

	if (0 == strcmp(basename, "..")) return NULL;

	return basename;
}


/**
 * @brief Construct a full path within the specified XDG base directory.
 *
 * @param bd_type The XDG base directory type.
 * @param fmt The format string for the path.
 * @param ... Additional arguments for the format string.
 * @return const char* The constructed full path (freed by caller).
 */

const char *xdg_make_path(xdg_basedir_type bd_typec, const char *fmt, ...)
{
	const char *xdg_home = xdg_get_home(bd_typec);

	if (NULL == xdg_home) return NULL;

	char *xdg_fmt;
	int rc = asprintf(&xdg_fmt, "%s/%s", xdg_home, fmt);
	if (rc < 0) return NULL;

	va_list filename_components;
	va_start(filename_components, fmt);
	char *xdg_filepath;
	rc = vasprintf(&xdg_filepath, xdg_fmt, filename_components);
	va_end(filename_components);
	if (rc < 0) return NULL;

	free((void *)xdg_home);
	free(xdg_fmt);
	if (!make_dirpath(xdg_filepath))
	{
		free(xdg_filepath);
		return NULL;
	}
	return xdg_filepath;
}
#endif /* HAVE_WIDECHAR_EDITLINE */