#include "sys.h" #include #include #include #include #include #include #include #include #include #include #include "logger.h" #include "globals.h" namespace sys { FileInfo fileInfo(const std::string &path) { FileInfo finfo; struct stat st; if (stat(path.c_str(), &st) < 0) { throw std::runtime_error("stat '" + path + "': " + strerror(errno)); } finfo.mTimeSec = st.st_mtim.tv_sec; finfo.mTimeNsec = st.st_mtim.tv_nsec; finfo.isDir = S_ISDIR(st.st_mode); return finfo; } bool fileExists(const std::string &path) { struct stat st; if (stat(path.c_str(), &st) < 0) { if (errno == ENOENT) { return false; } else { throw std::runtime_error("stat '" + path + "': " + strerror(errno)); } } return true; } void mkdirp(const std::string &path) { // TODO: Implement this in C++ instead std::vector argv; argv.push_back("mkdir"); argv.push_back("-p"); argv.push_back("--"); argv.push_back(path); execute(argv, nullptr, global::verbose >= 2); } void execute(const std::vector &args, std::string *output, bool print) { if (print) { std::string str; for (size_t i = 0; i < args.size(); ++i) { if (i != 0) { str += " "; } str += args[i]; } logger::log(str); } // argv[0] should be interpreted as a shell command, because being able to run // CC='gcc --sysroot=/blah' is used by some systems. std::string command = std::string(args[0]) + " \"$@\""; std::vector argv; argv.push_back("/bin/sh"); // TODO: Use $SHELL? argv.push_back("-c"); argv.push_back(command.c_str()); argv.push_back("--"); for (size_t i = 1; i < args.size(); ++i) { argv.push_back(args[i].c_str()); } argv.push_back(nullptr); int fds[2]; if (pipe(fds) < 0) { throw std::runtime_error(std::string("fork: ") + strerror(errno)); } pid_t child = fork(); if (child == 0) { close(fds[0]); dup2(fds[1], 1); // So, from what I've read, execvp should never modify its argv; so this should be fine? if (execvp(argv[0], (char *const *)argv.data()) < 0) { perror(argv[0]); abort(); } // This shouldn't happen exit(0); } else if (child < 0) { throw std::runtime_error(std::string("fork: ") + strerror(errno)); } close(fds[1]); if (output == nullptr) { close(fds[0]); } else { char buf[1025]; while (true) { ssize_t num = read(fds[0], buf, sizeof(buf) - 1); if (num < 0 && errno != EAGAIN) { close(fds[0]); throw std::runtime_error( std::string("read: ") + strerror(errno) + " (fd: " + std::to_string(fds[0]) + ")"); } else if (num < 0) { continue; } if (num == 0) { close(fds[0]); break; } buf[num] = '\0'; *output += buf; } } int wstatus; waitpid(child, &wstatus, 0); if (WIFEXITED(wstatus) && WEXITSTATUS(wstatus) != 0) { throw std::runtime_error(std::string(args[0]) + " exited with code " + std::to_string(WEXITSTATUS(wstatus))); } if (WTERMSIG(wstatus)) { throw std::runtime_error(std::string(args[0]) + " terminated due to " + strsignal(WTERMSIG(wstatus))); } if (output != nullptr && output->back() == '\n') { output->pop_back(); } } void readDir(const std::string &path, std::vector &files) { DIR *d = opendir(path.c_str()); if (d == NULL) { throw std::runtime_error("opendir '" + path + "': " + strerror(errno)); } struct dirent *ent; while ((ent = readdir(d)) != NULL) { if (ent->d_name[0] == '.') continue; files.emplace_back(ent->d_name); } closedir(d); } void chdir(const std::string &path) { if (::chdir(path.c_str()) < 0) { throw std::runtime_error("chdir '" + path + "': " + strerror(errno)); } } std::string cwd() { std::vector buf; buf.reserve(128); while (1) { char *res = getcwd(buf.data(), buf.capacity()); if (res == NULL) { if (errno == ERANGE) { buf.reserve(buf.capacity() * 2); } else { throw std::system_error(EFAULT, std::generic_category()); } } else { std::string str(buf.data()); return str; } } } void symlink(const std::string &from, const std::string &to) { // TODO: Implement this in C++ instead std::vector argv; argv.push_back("ln"); argv.push_back("-s"); argv.push_back("-f"); argv.push_back("--"); argv.push_back(from); argv.push_back(to); sys::execute(argv, nullptr, global::verbose > 2); } }