/*-
 * redshift-ng - Automatically adjust display colour temperature according the Sun
 * 
 * Copyright (c) 2009-2018        Jon Lund Steffensen <jonlst@gmail.com>
 * Copyright (c) 2014-2016, 2025  Mattias Andrée <m@maandree.se>
 * 
 * redshift-ng 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.
 * 
 * redshift-ng 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 redshift-ng.  If not, see <http://www.gnu.org/licenses/>.
 */
#include "common.h"


char *
rtrim(char *s, char *end)
{
	end = end ? end : strchr(s, '\0');
	while (end != s && (end[-1] == ' ' || end[-1] == '\t'))
		end--;
	*end = '\0';
	return s;
}


char *
ltrim(char *s)
{
	while (*s == ' ' || *s == '\t')
		s++;
	return s;
}


const char *
get_home(void)
{
#ifdef WINDOWS
	return NULL;
#else
	static const char *home = NULL;
	struct passwd *pw;
	if (!home) {
		pw = getpwuid(getuid());
		if (pw) {
			home = pw->pw_dir;
			if (home && *home)
				return home;
			weprintf(_("Cannot determine your home directory, "
			           "it is from the system's user table."));
		} else if (errno) {
			weprintf("getpwuid:");
		} else {
			weprintf(_("Cannot determine your home directory, your"
			           " user ID is missing from the system's user table."));
			/* `errno` can either be set to any number of error codes,
			 * or be zero if the user does not have a passwd entry */
		}
		home = "";
	}
	return home;
#endif
}


/**
 * Search for a file and open it in some manner
 * 
 * @param   path_spec    Specification for the path to try
 * @param   path_out     Output parameter for the found file
 * @param   pathbuf_out  Output parameter for the memory allocation for `*path_out`;
 *                       shall be free(3)d by the caller
 * @param   open_cb      Pointer to function used to open the file, unless it
 *                       returns `NULL` it's return value is returned by the
 *                       function (`try_path`); `NULL` shall be returned if the
 *                       file `path` does not exist
 * @return               File access object to the found file; `NULL` if not found
 */
static void *
try_path(const struct env_path *path_spec, const char **path_out, char **pathbuf_out, void *(*open_cb)(const char *path))
{
	const char *prefix, *p, *q;
	char *path;
	size_t len;
	void *f = NULL;

	*path_out = NULL;
	*pathbuf_out = NULL;

	if (!path_spec->prefix_env) {
		prefix = get_home();
	} else if (*path_spec->prefix_env) {
		prefix = getenv(path_spec->prefix_env);
	} else {
		f = (*open_cb)(path_spec->suffix);
		if (f)
			*path_out = path_spec->suffix;
		return f;
	}
	if (!prefix || !*prefix)
		return NULL;

	path = emalloc(strlen(prefix) + strlen(path_spec->suffix) + 1U);

	if (path_spec->multidir_env) {
		for (p = prefix; !f && *p; p = &q[!!*q]) {
#ifdef strchrnul
			q = strchrnul(p, PATH_DELIMITER);
#else
			q = strchr(p, PATH_DELIMITER);
			q = q ? q : strchr(p, '\0');
#endif
			len = (size_t)(q - p);
			if (!len)
				continue;

			memcpy(path, p, len);
			stpcpy(&path[len], path_spec->suffix);
			f = (*open_cb)(path);
		}
	} else {
		stpcpy(stpcpy(path, prefix), path_spec->suffix);
		f = (*open_cb)(path);
	}

	if (f)
		*path_out = *pathbuf_out = path;
	else
		free(path);
	return f;	
}


/**
 * Open a file for reading, if it exists
 * 
 * @param   path  The path to the file
 * @return        `FILE` object for reading the file,
 *                `NULL` if it doesn't exist
 */
static void *
open_file(const char *path)
{
	FILE *f = fopen(path, "r");
	if (!f && errno != ENOENT)
		eprintf("fopen %s \"r\":", path);
	return f;
}


FILE *
try_path_fopen(const struct env_path *path_spec, const char **path_out, char **pathbuf_out)
{
	return try_path(path_spec, path_out, pathbuf_out, &open_file);
}


/**
 * Open a directory for reading, if it exists
 * 
 * @param   path  The path to the directory
 * @return        `DIR` object for reading the directory,
 *                `NULL` if it doesn't exist
 */
static void *
open_dir(const char *path)
{
	DIR *f = opendir(path);
	if (!f && errno != ENOENT)
		eprintf("opendir %s:", path);
	return f;
}


DIR *
try_path_opendir(const struct env_path *path_spec, const char **path_out, char **pathbuf_out)
{
	return try_path(path_spec, path_out, pathbuf_out, &open_dir);
}



#ifndef WINDOWS
void
pipe_rdnonblock(int pipefds[2])
{
	int i, flags;

	/* Try to use pipe2(2) create O_CLOEXEC pipe */
# if defined(__linux__) && !defined(MISSING_PIPE2)
	if (!pipe2(pipefds, O_CLOEXEC))
		goto apply_nonblock;
	else if (errno != ENOSYS)
		eprintf("pipe2 <buffer> O_CLOEXEC:");
# endif

	/* Fallback for when pipe2(2) is not available */
	if (pipe(pipefds))
		eprintf("pipe:");
	for (i = 0; i < 2; i++) {
		flags = fcntl(pipefds[i], F_GETFD);
		if (flags == -1)
			eprintf("fcntl <pipe> F_GETFD:");
		if (fcntl(pipefds[i], F_SETFD, flags | O_CLOEXEC))
			eprintf("fcntl <pipe> F_SETFD +O_CLOEXEC:");
	}

	/* Make the read-end non-blocking */
# if defined(__linux__) && !defined(MISSING_PIPE2)
apply_nonblock:
# endif
	flags = fcntl(pipefds[0], F_GETFL);
	if (flags == -1)
		eprintf("fcntl <pipe> F_GETFL:");
	if (fcntl(pipefds[0], F_SETFL, flags | O_NONBLOCK))
		eprintf("fcntl <pipe> F_SETFL +O_NONBLOCK:");
}
#endif


void *
ecalloc(size_t n, size_t m)
{
	char *ret = calloc(n, m);
	if (!ret)
		eprintf("calloc:");
	return ret;
}


void *
emalloc(size_t n)
{
	char *ret = malloc(n);
	if (!ret)
		eprintf("malloc:");
	return ret;
}


void *
erealloc(void *ptr, size_t n)
{
	char *ret = realloc(ptr, n);
	if (!ret)
		eprintf("realloc:");
	return ret;
}


char *
estrdup(const char *s)
{
	char *ret = strdup(s);
	if (!ret)
		eprintf("strdup:");
	return ret;
}


void
vweprintf(const char *fmt, va_list args)
{
	int saved_errno;
	const char *errstrprefix, *errstr;

	saved_errno = errno;
	if (!*fmt) {
		errstrprefix = "";
		errstr = strerror(saved_errno);
	} else if (strchr(fmt, '\0')[-1] == '\n') {
		errstrprefix = "";
		errstr = NULL;
	} else if (strchr(fmt, '\0')[-1] == ':') {
		errstrprefix = " ";
		errstr = strerror(saved_errno);
	} else {
		errstrprefix = "";
		errstr = "";
	}

	fprintf(stderr, "%s: ", argv0);
	vfprintf(stderr, fmt, args);
	if (errstr)
		fprintf(stderr, "%s%s\n", errstrprefix, errstr);

	errno = saved_errno;
}


void
weprintf(const char *fmt, ...)
{
	va_list args;
	va_start(args, fmt);
	vweprintf(fmt, args);
	va_end(args);
}


void
eprintf(const char *fmt, ...)
{
	va_list args;
	va_start(args, fmt);
	vweprintf(fmt, args);
	va_end(args);
	exit(1);
}