/*------------------------------------------------------------------------- * * directory.c * * Utility functions for dealing with directories. * * Copyright (c) Citus Data, Inc. * *------------------------------------------------------------------------- */ #include #include #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); }