/*
 *	util.c
 *
 *	utility functions
 *
 *	Copyright (c) 2010-2025, PostgreSQL Global Development Group
 *	src/bin/pg_upgrade/util.c
 */

#include "postgres_fe.h"

#include <signal.h>

#include "common/username.h"
#include "pg_upgrade.h"

LogOpts		log_opts;

static void pg_log_v(eLogType type, const char *fmt, va_list ap) pg_attribute_printf(2, 0);


/*
 * report_status()
 *
 *	Displays the result of an operation (ok, failed, error message,...)
 *
 *	This is no longer functionally different from pg_log(), but we keep
 *	it around to maintain a notational distinction between operation
 *	results and other messages.
 */
void
report_status(eLogType type, const char *fmt,...)
{
	va_list		args;

	va_start(args, fmt);
	pg_log_v(type, fmt, args);
	va_end(args);
}


void
end_progress_output(void)
{
	/*
	 * For output to a tty, erase prior contents of progress line. When either
	 * tty or verbose, indent so that report_status() output will align
	 * nicely.
	 */
	if (log_opts.isatty)
	{
		printf("\r");
		pg_log(PG_REPORT_NONL, "%-*s", MESSAGE_WIDTH, "");
	}
	else if (log_opts.verbose)
		pg_log(PG_REPORT_NONL, "%-*s", MESSAGE_WIDTH, "");
}

/*
 * Remove any logs generated internally.  To be used once when exiting.
 */
void
cleanup_output_dirs(void)
{
	fclose(log_opts.internal);

	/* Remove dump and log files? */
	if (log_opts.retain)
		return;

	/*
	 * Try twice.  The second time might wait for files to finish being
	 * unlinked, on Windows.
	 */
	if (!rmtree(log_opts.basedir, true))
		rmtree(log_opts.basedir, true);

	/* Remove pg_upgrade_output.d only if empty */
	switch (pg_check_dir(log_opts.rootdir))
	{
		case 0:					/* non-existent */
		case 3:					/* exists and contains a mount point */
			Assert(false);
			break;

		case 1:					/* exists and empty */
		case 2:					/* exists and contains only dot files */

			/*
			 * Try twice.  The second time might wait for files to finish
			 * being unlinked, on Windows.
			 */
			if (!rmtree(log_opts.rootdir, true))
				rmtree(log_opts.rootdir, true);
			break;

		case 4:					/* exists */

			/*
			 * Keep the root directory as this includes some past log
			 * activity.
			 */
			break;

		default:
			/* different failure, just report it */
			pg_log(PG_WARNING, "could not access directory \"%s\": %m",
				   log_opts.rootdir);
			break;
	}
}

/*
 * prep_status
 *
 *	Displays a message that describes an operation we are about to begin.
 *	We pad the message out to MESSAGE_WIDTH characters so that all of the
 *	"ok" and "failed" indicators line up nicely.  (Overlength messages
 *	will be truncated, so don't get too verbose.)
 *
 *	A typical sequence would look like this:
 *		prep_status("about to flarb the next %d files", fileCount);
 *		if ((message = flarbFiles(fileCount)) == NULL)
 *		  report_status(PG_REPORT, "ok");
 *		else
 *		  pg_log(PG_FATAL, "failed: %s", message);
 */
void
prep_status(const char *fmt,...)
{
	va_list		args;
	char		message[MAX_STRING];

	va_start(args, fmt);
	vsnprintf(message, sizeof(message), fmt, args);
	va_end(args);

	/* trim strings */
	pg_log(PG_REPORT_NONL, "%-*s", MESSAGE_WIDTH, message);
}

/*
 * prep_status_progress
 *
 *   Like prep_status(), but for potentially longer running operations.
 *   Details about what item is currently being processed can be displayed
 *   with pg_log(PG_STATUS, ...). A typical sequence would look like this:
 *
 *   prep_status_progress("copying files");
 *   for (...)
 *     pg_log(PG_STATUS, "%s", filename);
 *   end_progress_output();
 *   report_status(PG_REPORT, "ok");
 */
void
prep_status_progress(const char *fmt,...)
{
	va_list		args;
	char		message[MAX_STRING];

	va_start(args, fmt);
	vsnprintf(message, sizeof(message), fmt, args);
	va_end(args);

	/*
	 * If outputting to a tty or in verbose, append newline. pg_log_v() will
	 * put the individual progress items onto the next line.
	 */
	if (log_opts.isatty || log_opts.verbose)
		pg_log(PG_REPORT, "%-*s", MESSAGE_WIDTH, message);
	else
		pg_log(PG_REPORT_NONL, "%-*s", MESSAGE_WIDTH, message);
}

static void
pg_log_v(eLogType type, const char *fmt, va_list ap)
{
	char		message[QUERY_ALLOC];

	/* No incoming message should end in newline; we add that here. */
	Assert(fmt);
	Assert(fmt[0] == '\0' || fmt[strlen(fmt) - 1] != '\n');

	vsnprintf(message, sizeof(message), _(fmt), ap);

	/* PG_VERBOSE and PG_STATUS are only output in verbose mode */
	/* fopen() on log_opts.internal might have failed, so check it */
	if (((type != PG_VERBOSE && type != PG_STATUS) || log_opts.verbose) &&
		log_opts.internal != NULL)
	{
		if (type == PG_STATUS)
			/* status messages get two leading spaces, see below */
			fprintf(log_opts.internal, "  %s\n", message);
		else if (type == PG_REPORT_NONL)
			fprintf(log_opts.internal, "%s", message);
		else
			fprintf(log_opts.internal, "%s\n", message);
		fflush(log_opts.internal);
	}

	switch (type)
	{
		case PG_VERBOSE:
			if (log_opts.verbose)
				printf("%s\n", message);
			break;

		case PG_STATUS:

			/*
			 * For output to a terminal, we add two leading spaces and no
			 * newline; instead append \r so that the next message is output
			 * on the same line.  Truncate on the left to fit into
			 * MESSAGE_WIDTH (counting the spaces as part of that).
			 *
			 * If going to non-interactive output, only display progress if
			 * verbose is enabled. Otherwise the output gets unreasonably
			 * large by default.
			 */
			if (log_opts.isatty)
			{
				bool		itfits = (strlen(message) <= MESSAGE_WIDTH - 2);

				/* prefix with "..." if we do leading truncation */
				printf("  %s%-*.*s\r",
					   itfits ? "" : "...",
					   MESSAGE_WIDTH - 2, MESSAGE_WIDTH - 2,
					   itfits ? message :
					   message + strlen(message) - MESSAGE_WIDTH + 3 + 2);
			}
			else if (log_opts.verbose)
				printf("  %s\n", message);
			break;

		case PG_REPORT_NONL:
			/* This option is for use by prep_status and friends */
			printf("%s", message);
			break;

		case PG_REPORT:
		case PG_WARNING:
			printf("%s\n", message);
			break;

		case PG_FATAL:
			/* Extra newline in case we're interrupting status output */
			printf("\n%s\n", message);
			printf(_("Failure, exiting\n"));
			exit(1);
			break;

			/* No default:, we want a warning for omitted cases */
	}
	fflush(stdout);
}


void
pg_log(eLogType type, const char *fmt,...)
{
	va_list		args;

	va_start(args, fmt);
	pg_log_v(type, fmt, args);
	va_end(args);
}


void
pg_fatal(const char *fmt,...)
{
	va_list		args;

	va_start(args, fmt);
	pg_log_v(PG_FATAL, fmt, args);
	va_end(args);
	/* NOTREACHED */
	printf(_("Failure, exiting\n"));
	exit(1);
}


void
check_ok(void)
{
	/* all seems well */
	report_status(PG_REPORT, "ok");
}


/*
 * quote_identifier()
 *		Properly double-quote a SQL identifier.
 *
 * The result should be pg_free'd, but most callers don't bother because
 * memory leakage is not a big deal in this program.
 */
char *
quote_identifier(const char *s)
{
	char	   *result = pg_malloc(strlen(s) * 2 + 3);
	char	   *r = result;

	*r++ = '"';
	while (*s)
	{
		if (*s == '"')
			*r++ = *s;
		*r++ = *s;
		s++;
	}
	*r++ = '"';
	*r++ = '\0';

	return result;
}


/*
 * get_user_info()
 */
int
get_user_info(char **user_name_p)
{
	int			user_id;
	const char *user_name;
	char	   *errstr;

#ifndef WIN32
	user_id = geteuid();
#else
	user_id = 1;
#endif

	user_name = get_user_name(&errstr);
	if (!user_name)
		pg_fatal("%s", errstr);

	/* make a copy */
	*user_name_p = pg_strdup(user_name);

	return user_id;
}


/*
 *	str2uint()
 *
 *	convert string to oid
 */
unsigned int
str2uint(const char *str)
{
	return strtoul(str, NULL, 10);
}
