// 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, bool concatdir /* = true */) { 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(concatdir ? dir + file : 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(); } OptionsParser::OptionsParser() { valueMap[""] = true; valueMap["yes"] = true; valueMap["no"] = false; valueMap["on"] = true; valueMap["off"] = false; valueMap["true"] = true; valueMap["false"] = false; valueMap["1"] = true; valueMap["0"] = false; } vector<string> OptionsParser::parse(int argc, const char ** argv) { vector<string> unprocessedArgs; size_t out_arg_i = 0; for (int arg_i = 1; arg_i < argc; arg_i++) { string currarg = toLower(argv[arg_i]); if (currarg.compare(0, 2, "--") == 0) //long option { bool invert = (currarg.compare(2, 2, "no") == 0); size_t name_i = (invert ? 4 : 2); size_t equalspos = currarg.find_first_of("=-", name_i); string name = currarg.substr(name_i, equalspos - name_i); string value = (equalspos >= currarg.length()) ? "" : currarg.substr(equalspos); optsMap_t::iterator option = optsMap.find(name); if (option == optsMap.end()) { cerr << "Unknown option: " << currarg << endl; exit(-1); } valueMap_t::iterator valueIt = valueMap.find(value); if (valueIt == valueMap.end()) { cerr << "Unknown value: " << currarg << endl; exit(-1); } *option->second = valueIt->second ^ invert; } // "--" else if (currarg[0] == '-') //string of single char options { for (size_t c = 1; currarg[c] != '\0'; c++) { optsMap_t::iterator option = optsMap.find(string(1,currarg[c])); if (option == optsMap.end()) { cerr << "Unknown option: -" << currarg[c] << endl; exit(-1); } *option->second = true; } } else //positional argument { if (out_arg_i < args.size()) *args[out_arg_i] = currarg; else unprocessedArgs.push_back(currarg); out_arg_i++; } } return unprocessedArgs; } 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 colorize { const char * BLACK = "\033[39m"; const char * GREEN = "\033[32m"; const char * RED = "\033[31m"; const bool is_color_enabled = isatty(STDOUT_FILENO); } } // namespace util