/*-------------------------------------------------------------------------
 *
 * directory.c
 *
 * Utility functions for dealing with directories.
 *
 * Copyright (c) Citus Data, Inc.
 *
 *-------------------------------------------------------------------------
 */
#include <sys/stat.h>
#include <unistd.h>

#include "postgres.h"

#include "funcapi.h"
#include "miscadmin.h"

#include "distributed/utils/directory.h"


/* Local functions forward declarations */
static bool FileIsLink(const char *filename, struct stat filestat);


/*
 * CitusCreateDirectory creates a new directory with the given directory name.
 */
void
CitusCreateDirectory(StringInfo directoryName)
{
	int makeOK = MakePGDirectory(directoryName->data);
	if (makeOK != 0)
	{
		ereport(ERROR, (errcode_for_file_access(),
						errmsg("could not create directory \"%s\": %m",
							   directoryName->data)));
	}
}


/*
 * FileIsLink checks whether a file is a symbolic link.
 */
static bool
FileIsLink(const char *filename, struct stat filestat)
{
	return S_ISLNK(filestat.st_mode);
}


/*
 * CitusRemoveDirectory first checks if the given directory exists. If it does, the
 * function recursively deletes the contents of the given directory, and then
 * deletes the directory itself. This function is modeled on the Boost file
 * system library's remove_all() method.
 */
void
CitusRemoveDirectory(const char *filename)
{
	/* files may be added during execution, loop when that occurs */
	while (true)
	{
		struct stat fileStat;
		int removed = 0;

		int statOK = stat(filename, &fileStat);
		if (statOK < 0)
		{
			if (errno == ENOENT)
			{
				return;  /* if file does not exist, return */
			}
			else
			{
				ereport(ERROR, (errcode_for_file_access(),
								errmsg("could not stat file \"%s\": %m", filename)));
			}
		}

		/*
		 * If this is a directory, iterate over all its contents and for each
		 * content, recurse into this function. Also, make sure that we do not
		 * recurse into symbolic links.
		 */
		if (S_ISDIR(fileStat.st_mode) && !FileIsLink(filename, fileStat))
		{
			const char *directoryName = filename;

			DIR *directory = AllocateDir(directoryName);
			if (directory == NULL)
			{
				ereport(ERROR, (errcode_for_file_access(),
								errmsg("could not open directory \"%s\": %m",
									   directoryName)));
			}

			StringInfo fullFilename = makeStringInfo();
			struct dirent *directoryEntry = ReadDir(directory, directoryName);
			for (; directoryEntry != NULL; directoryEntry = ReadDir(directory,
																	directoryName))
			{
				const char *baseFilename = directoryEntry->d_name;

				/* if system file, skip it */
				if (strncmp(baseFilename, ".", MAXPGPATH) == 0 ||
					strncmp(baseFilename, "..", MAXPGPATH) == 0)
				{
					continue;
				}

				resetStringInfo(fullFilename);
				appendStringInfo(fullFilename, "%s/%s", directoryName, baseFilename);

				CitusRemoveDirectory(fullFilename->data);
			}

			pfree(fullFilename->data);
			pfree(fullFilename);
			FreeDir(directory);
		}

		/* we now have an empty directory or a regular file, remove it */
		if (S_ISDIR(fileStat.st_mode))
		{
			/*
			 * We ignore the TOCTUO race condition static analysis warning
			 * here, since we don't actually read the files or directories. We
			 * simply want to remove them.
			 */
			removed = rmdir(filename); /* lgtm[cpp/toctou-race-condition] */

			if (errno == ENOTEMPTY || errno == EEXIST)
			{
				continue;
			}
		}
		else
		{
			/*
			 * We ignore the TOCTUO race condition static analysis warning
			 * here, since we don't actually read the files or directories. We
			 * simply want to remove them.
			 */
			removed = unlink(filename); /* lgtm[cpp/toctou-race-condition] */
		}

		if (removed != 0 && errno != ENOENT)
		{
			ereport(ERROR, (errcode_for_file_access(),
							errmsg("could not remove file \"%s\": %m", filename)));
		}

		return;
	}
}


/*
 * CleanupJobCacheDirectory cleans up all files in the job cache directory
 * as part of this process's start-up logic.
 */
void
CleanupJobCacheDirectory(void)
{
	/* use the default tablespace in {datadir}/base */
	StringInfo jobCacheDirectory = makeStringInfo();
	appendStringInfo(jobCacheDirectory, "base/%s", PG_JOB_CACHE_DIR);

	CitusRemoveDirectory(jobCacheDirectory->data);
	CitusCreateDirectory(jobCacheDirectory);

	pfree(jobCacheDirectory->data);
	pfree(jobCacheDirectory);
}
