Skip to content
Snippets Groups Projects
util.cpp 17.43 KiB
// CS 225 util.h
// Created Spring 2011 by Jack Toole

#include <ctype.h>
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <iostream>
#include "util.h"

extern char ** environ; // man 7 environ

namespace util
{

namespace internal
{
const char * error_prefix = "";

template<typename StrType>
void exit_if_error_output(const char * file, int32_t line, StrType message)
{
	if (util::internal::error_prefix != NULL)
		cerr << util::internal::error_prefix;
	cerr << file << ":" << line << ": " << message;
	if (errno != 0)
		cerr << ": " << strerror(errno);
	cerr << endl;
	exit(-1);
}

}

void SET_ERROR_MESSAGE(const char * message)
{
	internal::error_prefix = message;
}

// originally from stackoverflow.com user plinth
// http://stackoverflow.com/questions/2180079/how-can-i-copy-a-file-on-unix-using-c
// Modified by Jack Toole
int8_t exec(int redirect_fd, const char * command,
            const char * arg1,
            const char * arg2,
            const char * arg3,
            const char * arg4,
            const char * arg5,
            const char * arg6)
{
	int childExitStatus;
	pid_t pid;

	// For debugging:
#if 0
	cerr << "exec(" << command << ' '
	     << ((arg1!=NULL)?arg1:"-") << ' '
	     << ((arg2!=NULL)?arg2:"-") << ' '
	     << ((arg3!=NULL)?arg3:"-") << ' '
	     << ((arg4!=NULL)?arg4:"-") << ' '
	     << ((arg5!=NULL)?arg5:"-") << ' '
	     << ((arg6!=NULL)?arg6:"-") << ' '
	     << ')' << endl;
#endif

	// avoid self destruction errors from closing then trying to duplicate output
	// you can't redirect to what's already there
	if (redirect_fd == STDOUT_FILENO || redirect_fd == STDERR_FILENO)
		redirect_fd = STDOUT_FILENO;

	// Save timer values :)
	// These are preserved across the parent, but not inherited by the child
	// let's change that
	struct itimerval remaining_real;
	struct itimerval remaining_virtual;
	struct itimerval remaining_prof;
	bool supports_virtual = true;
	bool supports_prof    = true;
	EXIT_IF_ERROR(getitimer(ITIMER_REAL,    &remaining_real));
	if (getitimer(ITIMER_VIRTUAL, &remaining_virtual) != 0)
	{
		if (errno == EINVAL)
		{
			supports_virtual = false;
			errno = 0;
		}
		else
			internal::exit_if_error_output(__FILE__, __LINE__, "getitimer(ITIMER_VIRTUAL) failed");
	}
	if (getitimer(ITIMER_PROF, &remaining_prof) != 0)
	{
		if (errno == EINVAL)
		{
			supports_prof = false;
			errno = 0;
		}
		else
			internal::exit_if_error_output(__FILE__, __LINE__, "getitimer(ITIMER_PROF) failed");
	}

	pid = fork();

	if (pid == 0) /* child */
	{

		// Restore timers
		EXIT_IF_ERROR(setitimer(ITIMER_REAL, &remaining_real, NULL));
		if (supports_virtual) EXIT_IF_ERROR(setitimer(ITIMER_VIRTUAL, &remaining_virtual, NULL));
		if (supports_prof)    EXIT_IF_ERROR(setitimer(ITIMER_PROF,    &remaining_prof, NULL));

		if (redirect_fd == -1)
		{
			int devnull_fd = open("/dev/null", O_WRONLY | O_NONBLOCK);
			close(STDOUT_FILENO);
			close(STDERR_FILENO);
			dup2(devnull_fd, STDOUT_FILENO);
			dup2(devnull_fd, STDERR_FILENO);
			close(devnull_fd);
		}
		else if (redirect_fd != STDOUT_FILENO)
		{
			close(STDOUT_FILENO);
			close(STDERR_FILENO);
			dup2(redirect_fd, STDOUT_FILENO);
			dup2(redirect_fd, STDERR_FILENO);
		}

		// Sanitize the environment
#if 1 //!! hack!
		char path[] = "PATH=/bin/:/usr/bin:/usr/local/bin";
		char redirect_glibc[] = "LIBC_FATAL_STDERR_=1";
		char * newenv[] = { path, redirect_glibc, NULL };
		//char * newenv[] = { path, NULL };
		environ = newenv;
#endif

		// Swap out child process image with the command, searching
		// in the specified path
		execlp(command, command, arg1, arg2, arg3, arg4, arg5, arg6, NULL);
		
        // An error occured
		cerr << "exec(" << '\"' << command << '\"';
		if (arg1 != NULL) cerr << ", \"" << arg1 << "\"";
		if (arg2 != NULL) cerr << ", \"" << arg2 << "\"";
		if (arg3 != NULL) cerr << ", \"" << arg3 << "\"";
		if (arg4 != NULL) cerr << ", \"" << arg4 << "\"";
		if (arg5 != NULL) cerr << ", \"" << arg5 << "\"";
		if (arg6 != NULL) cerr << ", \"" << arg6 << "\"";
		cerr << ") failed: " << strerror(errno) << endl;
		exit(-1);
	}
	else if (pid < 0)
	{
		/* error - couldn't start process - you decide how to handle */
		return -1;
	}
	else
	{
		/* parent - wait for child - this has all error handling, you
		 * could just call wait() as long as you are only expecting to
		 * have one child process at a time.
		 */
		pid_t ws = waitpid( pid, &childExitStatus, 0);
		if (ws == -1)
		{ /* error - handle as you wish */
			//cout << "exec error: " << __LINE__ << endl;
			return -1;
		}

		if (WIFEXITED(childExitStatus)) /* exit code in childExitStatus */
		{
			int8_t status = WEXITSTATUS(childExitStatus); /* zero is normal exit */
			/* handle non-zero as you wish */
			return status;
		}
		else if (WIFSIGNALED(childExitStatus)) /* killed */
		{
			kill(getpid(), WTERMSIG(childExitStatus));
			return -1;
		}
		else if (WIFSTOPPED(childExitStatus)) /* stopped */
		{
			//cout << "exec error: " << __LINE__ << endl;
			return -1;
		}
		else
			return -1;
	}
}


int chdir(const string & dir)
{
	return ::chdir(dir.c_str());
}

void assertExists(const string & path, int exit_code /* = -1 */)
{
	if (!exists(path))
	{
		cerr << "Error: " << path << " does not exist." << endl;
		exit(exit_code);
	}
}

bool exists(const string & path)
{
	// Try stat-ing it
	struct stat st;
	if (stat(path.c_str(), &st) != 0) return false;
	// Check for read permission
	if ((st.st_mode & S_IRUSR) == 0) return false;

	// Check for correct file/directory nature
	if (path[path.length()-1] != '/') return S_ISREG(st.st_mode);

	// Otherwise we want a directory
	if ((st.st_mode & S_IXUSR) == 0) return false;
	return S_ISDIR(st.st_mode);
}


mode_t permissions(const string & path)
{
	// Try stat-ing it
	struct stat st;
	if (stat(path.c_str(), &st) != 0) return -1;
	// Check for read permission
	if ((st.st_mode & S_IRUSR) == 0) return -1;

	return (st.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO));
}


void forceRemoveDir(string dir)
{
	size_t len = dir.length();
	if (dir[len-1] == '/') dir[len-1] = '\0';
	EXIT_IF_ERROR(exec("rm","-rf",dir.c_str()) != 0);
	if (dir[len-1] == '\0') dir[len-1] = '/';
}


string getcwdstr()
{
	int len = 256;
	char * buffer = new char[len];

	char * ret = getcwd(&buffer[0], len - 1);
	while (ret == NULL && errno == ERANGE)
	{
		len *= 2;

		delete buffer;
		buffer = new char[len];

		ret = getcwd(&buffer[0], len - 1);
	}

	EXIT_IF_ERROR(ret == NULL);

	string cwdstr(buffer);
	delete buffer;

	return cwdstr;
}


void copyFile(const string & source, const string & dest)
{
	assertExists(source);
	vector<string> folders = tokenize(dest, '/');
	string currdir = "";
	for (size_t i = 0; i < folders.size() - 1; i++)
	{
		currdir += folders[i] + '/';
		if (!exists(currdir))
			exec("mkdir", currdir.c_str());
	}
	exec("cp", source.c_str(), dest.c_str());
}

void copyFiles(const string & sourceFolder, const string & destFolder, const vector<string> & files)
{
	assertExists(destFolder);
	for (size_t i = 0; i < files.size(); i++)
	{
		string sourceFile = sourceFolder + files[i];
		assertExists(sourceFile);
		copyFile(sourceFile, destFolder);
	}
}


void protectFiles(const string & folder, const vector<string> & files)
{
#if 0 // (debug)
	for (size_t i = 0; i < files.size(); i++)
	{
		string file = folder + files[i];
		assertExists(file);

		if (chmod(file.c_str(), S_IRUSR) != 0)
		{
			perror("chmod failed");
			exit(-1);
		}
	}
#endif
}

void protectDir(const string & dir)
{
	// (debug) EXIT_IF_ERROR(exec("/bin/chmod", "-R", "ugoa-w", dir.c_str()) != 0);
}

// Originally from Simon Biber
// http://bytes.com/topic/c/answers/545614-list-files-current-directory
vector<string> get_files_in_dir(const string & dir)
{
	EXIT_IF_ERROR(dir == "" || dir[dir.length()-1] != '/', "Directory name must end in a '/'");
	
	vector<string> files;
	DIR * dirp = opendir(dir.c_str());
	if (dirp == NULL) return files;
	
	struct dirent * ent = readdir(dirp);
	while (ent != NULL)
	{
		string file = ent->d_name;
		if (file != "." && file != "..")
			files.push_back(dir + file);
		ent = readdir(dirp);
	}

	closedir(dirp);
	return files;
}

bool is_symlink(const string & file)
{
	// Try lstat-ing it
	struct stat st;
	if (lstat(file.c_str(), &st) != 0) return -1;
	// Check for read permission
	if ((st.st_mode & S_IRUSR) == 0) return false;

	// & with symlink bit
	return (S_ISLNK(st.st_mode)) != 0;
}

string get_symlink_target(const string & symlink)
{
	const size_t buf_size = 4096;
	char buf[buf_size+1]; // TODO (toole1): hack-y value
	ssize_t len = readlink(symlink.c_str(), buf, buf_size);
	EXIT_IF_ERROR(len < 0 || static_cast<size_t>(len) == buf_size, "Error getting target of symlink " + symlink);
	buf[len] = '\0';
	return string(buf);
}

void linkDirs(const string & sourceFolder, const string & destFolder, const vector<string> & dirs)
{
	assertExists(destFolder);
	for (size_t i = 0; i < dirs.size(); i++)
	{
		string source = sourceFolder + dirs[i];
		string target = destFolder   + dirs[i];

		// Check for redundant monad/ directory
		// This allows the monad/ dir to be safely renamed
		if (replaceFirst(source, "/../monad/","/"))
			replaceFirst(target, "/monad/","/");

		assertExists(destFolder + source + '/');

		if (symlink(source.c_str(), target.c_str()) != 0)
		{
			cerr << "symlink failed: " << target << ": ";
			perror(NULL);
			exit(-1);
		}
	}
}


bool replaceFirst(string & str, const string & toreplace, const string & with)
{
	size_t i = str.find(toreplace);
	if (i != string::npos)
	{
		str.replace(i,toreplace.length(),with);
		return true;
	}
	return false;
}

size_t replaceAll(string & str, const string & toreplace, const string & with)
{
	size_t i = str.find(toreplace);
	size_t count = 0;

	while (i != string::npos)
	{
		str.replace(i,toreplace.length(),with);
		i = str.find(toreplace, i + with.length());
		count++;
	}

	return count;
}

size_t replaceAllInternal(string & str, const string & toreplace, const string & with)
{
	size_t i = str.find(toreplace);
	size_t count = 0;

	while ((i != string::npos) && (i != str.length() - toreplace.length()))
	{
		str.replace(i,toreplace.length(),with);
		i = str.find(toreplace, i + with.length());
		count++;
	}

	return count;
}


size_t findNthLast(const string & str, char c, size_t n)
{
	if (str.length() == 0) return string::npos;
	size_t i = str.length() - 1;

	do
	{
		if (str[i] == c) n--;
		if (n == 0) return i;
	} while (i-- != 0);

	return string::npos;
}


string read_string_from_FILE(FILE * file, size_t max_length /* = -1 */)
{
	vector<char> v;
	v.reserve(256);

	while (true) 
	{
		int nextchar = fgetc(file);
		if (nextchar == '\0' || nextchar == EOF)
			break;
		if (v.size() < max_length)
			v.push_back(nextchar);
	}

	if (v.size() == max_length)
	{
		v.push_back('.');
		v.push_back('.');
		v.push_back('.');
	}

	v.push_back('\0');

	return string(&v[0]);
}

void write_string_to_FILE(FILE * file, const char * str)
{
	fflush(file);
	size_t i = 0;
	do
	{
//		cout << (int)str[i] << ' ';
		fputc(str[i], file);

		// We use a do-while because we want the \0 to be written to the stream
		// for sending multiple strings
	} while (str[i++] != 0);

//	cout << endl;
	fflush(file);
}



/**
*
*
**/

string readFile(const string & filename)
{
	ifstream file;
	file.open(filename.c_str());
	if (!file.good())
		return "";
	
	stringbuf linebuf;
	file.get(linebuf, '\0');
	linebuf.pubsync();
	return linebuf.str();
}


void readConfig(const string & testsFolder, FileMap & map, const string & discriminator /* = "" */)
{
	string file;
	if (testsFolder == "" || testsFolder[testsFolder.length()-1] == '/')
		file = testsFolder + "config.ini";
	else
		file = testsFolder;
	readFileGeneric(file, &map, NULL, discriminator);
}

void readFile(const string & file, vector<string> & lines)
{
	readFileGeneric(file, NULL, &lines);
}

void readFileGeneric(const string & filename, FileMap * map, vector<string> * lines, const string & discriminator /* = "" */)
{
	ifstream infile;
	istream * fileptr;
	if (filename == "/dev/stdin")
		fileptr = &cin;
	else
	{
		fileptr = &infile;
		infile.open(filename.c_str(), fstream::in);
	}
	istream & file = *fileptr;

	vector<string> * section = NULL;
	if (map != NULL) section = &(*map)[""];
	else section = lines;

	while ((file.good() && file.peek() == '\n') || file.peek() == '\r')
		file.get(); // get '\n'

	while (file.good())
	{
		// Read a line - A lot of code, I know, right?
		stringbuf linebuf;
		file.get(linebuf);
		while ((file.good() && file.peek() == '\n') || file.peek() == '\r')
			file.get(); // get '\n'
		if (linebuf.in_avail() == 0) continue;
		linebuf.pubsync();
		string line = linebuf.str();
		int len = line.length();
                if (line[len-1] == '\r')
                    line.replace(--len,1,"");

		if (len == 0 || line[0] == ';') continue; // skip comments
		
		if (map != NULL)
		{
			// Update the section
			if (line[0] == '[' && line[len-1] == ']')
			{
				section = &(*map)[line.substr(1, len - 2)];
				continue;
			}
			else if (line[0] == '[' || line[len-1] == ']')
			{
				cout << "config.ini: Format error: " << line << endl;
				exit(-1);
			}
		}

		// Or add the line/file to the section
		size_t delim_pos = line.find_first_of("?:");
		if (delim_pos == string::npos || map == NULL)
			section->push_back(line);
		else if ((line[delim_pos] == ':' && (delim_pos == 0 || discriminator == "")) ||
		         line.compare(0, delim_pos, discriminator) == 0)
			section->push_back(line.substr(delim_pos+1, line.size()-delim_pos-1));
	}

	if (filename != "/dev/stdin")
		infile.close();
}


char * processOptions(int argc, char ** argv, OptionsMap & opts, vector<string> & args)
{
	for (int arg_i = 1; arg_i < argc; arg_i++)
	{
		char * currarg = argv[arg_i];

		if (strncmp(currarg, "--", 2) == 0) //long option
		{
			bool value, invert;
			size_t string_i;

			if (strncasecmp(currarg+2, "no", 2) == 0)
			{
				invert = true;
				string_i = 4;
			}
			else
			{
				invert = false;
				string_i = 2;
			}
			
			size_t equals_i = string_i;
			while (currarg[equals_i] != '\0' && currarg[equals_i] != '=')
				equals_i++;
			if (currarg[equals_i] == '=')
				currarg[equals_i++] = '\0';

			OptionsMap::iterator option = opts.find(currarg+string_i);

			if (option == opts.end())
			{
				cerr << "Unknown option: " << currarg << endl;
				return currarg;
			}
			char valuechar = tolower(currarg[equals_i]);
			if (valuechar == 'o') valuechar = tolower(currarg[equals_i+1]) + 1;
			switch (valuechar)
			{
				case '\0'  : value = true;  break;
				case 'f'+1 : value = false; break; //off: see 'o' above
				case 'n'   : value = false; break;
				case 'n'+1 : value = true;  break; //on: contrast 'n': see 'o' above
				case 't'   : value = true;  break;
				case 'y'   : value = true;  break;
				default:
					cerr << "Unknown option value: " << currarg << endl;
					return currarg;
			}

			(*option).second = value ^ invert;
		} // "--"

		else if (currarg[0] == '-') //string of single char options
		{
			for (size_t c = 1; currarg[c] != '\0'; c++)
			{
				OptionsMap::iterator option = opts.find(string(1,currarg[c]));
				if (option == opts.end())
				{
					cerr << "Unknown option: -" << currarg[c] << endl;
					currarg[1] = currarg[c];
					currarg[2] = '\0';
					return currarg;
				}
				(*option).second = true;
			}
		}

		else //positional argument
			args.push_back(currarg);
	}
	
	return NULL;
}



void makeLower(string & str)
{
	for (size_t i = 0; i < str.length(); i++)
	{
		str[i] = tolower(str[i]);
	}
}

string toLower(const string & str)
{
	string s(str);
	makeLower(s);
	return s;
}



/**
*  A wrapper function which writes a buffer to a file.
**/
ssize_t writeBytesToFile(signed int fileDescriptor, const char * buffer, unsigned int bufferLength) {
	return writen(fileDescriptor, buffer, bufferLength);
}


// From Steven's Unix Net Programming
// http://www.kohala.com/start/
/* Write "n" bytes to a descriptor. */
ssize_t writen(int fd, const void *vptr, size_t n)
{
	size_t         nleft;
	ssize_t        nwritten;
	const int8_t * ptr;

	ptr = static_cast<const int8_t*>(vptr);
	nleft = n;
	while (nleft > 0) {
		if ( (nwritten = ::write(fd, ptr, nleft)) <= 0) {
			if (errno == EINTR)
				nwritten = 0; /* and call write() again */
			else
				return -1; /* error */
		}

		nleft -= nwritten;
		ptr   += nwritten;
	}
	return n;
}




// From Steven's Unix Net Programming
// http://www.kohala.com/start/
/* Read "n" bytes from a descriptor. */
ssize_t readn(int fd, void *vptr, size_t n)
{
	size_t   nleft;
	ssize_t  nread;
	int8_t * ptr;

	ptr = static_cast<int8_t*>(vptr);
	nleft = n;
	while (nleft > 0) {
		if ( (nread = ::read(fd, ptr, nleft)) < 0) {
			if (errno == EINTR)
				nread = 0; /* and call read() again */
			else
				return -1;
		}
		else if (nread == 0)
			break; /* EOF */

		nleft -= nread;
		ptr   += nread;
	}
	return n - nleft; /* return >= 0 */
}


void rename_main(const string & file, const string & newname)
{
	if (newname[0] != '_')
	{
		cerr << "INTERNAL ERROR: " __FILE__ << ":" << __LINE__
		     << "newname argument to rename_main must begin with an underscore to ensure replacement safety" << endl;
		exit(-2);
	}
	assertExists(file);
	exec( "sed", "-i",
	      ( "s/int[\\r\\n \\t][\\r\\n \\t]*main(.*)/int " + newname +
		    "(int argc, char ** argv)/" ).c_str(),
	      file.c_str() );
}

vector<string> tokenize(const string & str, char delim)
{
	vector<string> args;
	
	size_t start = 0;
	size_t end;
	for (end = str.find(delim); end != string::npos; end = str.find(delim, start))
	{
		args.push_back(str.substr(start, end - start));
		start = end+1;
	}
	args.push_back(str.substr(start, str.size() - start));
	
	return args;
}

} // namespace util