Build tool
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

sys.cc 6.2KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280
  1. #include "sys.h"
  2. #include <libgen.h>
  3. #include <sys/types.h>
  4. #include <sys/stat.h>
  5. #include <unistd.h>
  6. #include <dirent.h>
  7. #include <string.h>
  8. #include <errno.h>
  9. #include <sys/types.h>
  10. #include <sys/wait.h>
  11. #include <stdexcept>
  12. #include <system_error>
  13. #include <unordered_set>
  14. #include <atomic>
  15. #include "logger.h"
  16. #include "globals.h"
  17. namespace sys {
  18. static thread_local std::unordered_set<std::string> mkdirp_set;
  19. static std::atomic<int> signalCounter(0);
  20. struct SigCtx {
  21. SigCtx(bool enabled): enabled(enabled) {
  22. if (enabled && signalCounter++ == 0) {
  23. signal(SIGINT, SIG_IGN);
  24. }
  25. }
  26. ~SigCtx() {
  27. if (enabled && --signalCounter == 0) {
  28. signal(SIGINT, SIG_DFL);
  29. }
  30. }
  31. bool enabled;
  32. };
  33. std::string sanitizePath(const std::string &path) {
  34. std::string npath;
  35. size_t idx = 0;
  36. while (idx < path.size()) {
  37. if (idx >= path.size() - 2) {
  38. npath += path[idx++];
  39. } else if (path[idx] == '.' && path[idx + 1] == '.' && path[idx + 2] == '/') {
  40. npath += "__parent__/";
  41. idx += 3;
  42. } else {
  43. npath += path[idx++];
  44. }
  45. }
  46. if (npath[npath.size() - 1] == '.' && npath[npath.size() - 2] == '.') {
  47. npath.replace(npath.size() - 2, 2, "__parent__");
  48. }
  49. return npath;
  50. }
  51. FileInfo fileInfo(const std::string &path) {
  52. FileInfo finfo;
  53. struct stat st;
  54. if (stat(path.c_str(), &st) < 0) {
  55. throw std::runtime_error("stat '" + path + "': " + strerror(errno));
  56. }
  57. finfo.mTimeSec = st.st_mtim.tv_sec;
  58. finfo.mTimeNsec = st.st_mtim.tv_nsec;
  59. finfo.isDir = S_ISDIR(st.st_mode);
  60. return finfo;
  61. }
  62. bool fileExists(const std::string &path) {
  63. struct stat st;
  64. if (stat(path.c_str(), &st) < 0) {
  65. if (errno == ENOENT) {
  66. return false;
  67. } else {
  68. throw std::runtime_error("stat '" + path + "': " + strerror(errno));
  69. }
  70. }
  71. return true;
  72. }
  73. void mkdirp(const std::string &path) {
  74. auto res = mkdirp_set.emplace(path);
  75. if (!res.second)
  76. return;
  77. // TODO: Implement this in C++ instead
  78. std::vector<std::string> argv;
  79. argv.push_back("mkdir");
  80. argv.push_back("-p");
  81. argv.push_back("--");
  82. argv.push_back(path);
  83. ProcConf conf;
  84. conf.print = global::verbose >= 1;
  85. conf.prefix = "(MKDIRP)";
  86. execute(argv, conf);
  87. }
  88. void rmrf(const std::string &path) {
  89. mkdirp_set.erase(path);
  90. // TODO: Implement this in C++ instead
  91. std::vector<std::string> argv;
  92. argv.push_back("rm");
  93. argv.push_back("-rf");
  94. argv.push_back("--");
  95. argv.push_back(path);
  96. ProcConf conf;
  97. conf.print = global::verbose >= 1;
  98. conf.prefix = "(RMRF)";
  99. execute(argv, conf);
  100. }
  101. void execute(const std::vector<std::string> &args, const ProcConf &conf) {
  102. if (conf.print || global::verbose >= 2) {
  103. std::string str;
  104. for (size_t i = 0; i < args.size(); ++i) {
  105. if (i != 0) {
  106. str += " ";
  107. }
  108. str += args[i];
  109. }
  110. if (conf.prefix.size() > 0) {
  111. str = conf.prefix + " " + str;
  112. }
  113. logger::log(str);
  114. }
  115. // argv[0] should be interpreted as a shell command, because being able to run
  116. // CC='gcc --sysroot=/blah' is used by some systems.
  117. std::string command = std::string(args[0]) + " \"$@\"";
  118. static thread_local std::vector<const char *> argv;
  119. argv.clear();
  120. argv.push_back("/bin/sh"); // TODO: Use $SHELL?
  121. argv.push_back("-c");
  122. argv.push_back(command.c_str());
  123. argv.push_back("--");
  124. for (size_t i = 1; i < args.size(); ++i) {
  125. argv.push_back(args[i].c_str());
  126. }
  127. argv.push_back(nullptr);
  128. int stdoutPipe[2];
  129. if (conf.output == nullptr) {
  130. stdoutPipe[1] = STDOUT_FILENO;
  131. } else {
  132. if (pipe(stdoutPipe) < 0) {
  133. throw std::runtime_error(std::string("pipe: ") + strerror(errno));
  134. }
  135. }
  136. SigCtx sigCtx(conf.forwardSignals);
  137. pid_t child = fork();
  138. if (child == 0) {
  139. signal(SIGINT, SIG_DFL);
  140. if (conf.output != nullptr) {
  141. close(stdoutPipe[0]);
  142. dup2(stdoutPipe[1], 1);
  143. }
  144. // So, from what I've read, execvp should never modify its argv; so this should be fine?
  145. if (execvp(argv[0], (char *const *)argv.data()) < 0) {
  146. perror(argv[0]);
  147. abort();
  148. }
  149. // This shouldn't happen
  150. abort();
  151. } else if (child < 0) {
  152. throw std::runtime_error(std::string("fork: ") + strerror(errno));
  153. }
  154. if (conf.output != nullptr) {
  155. close(stdoutPipe[1]);
  156. char buf[1025];
  157. while (true) {
  158. ssize_t num = read(stdoutPipe[0], buf, sizeof(buf) - 1);
  159. if (num < 0 && errno != EAGAIN) {
  160. close(stdoutPipe[0]);
  161. throw std::runtime_error(
  162. std::string("read: ") + strerror(errno) +
  163. " (fd: " + std::to_string(stdoutPipe[0]) + ")");
  164. } else if (num < 0) {
  165. continue;
  166. }
  167. if (num == 0) {
  168. close(stdoutPipe[0]);
  169. break;
  170. }
  171. buf[num] = '\0';
  172. *conf.output += buf;
  173. }
  174. }
  175. int wstatus;
  176. waitpid(child, &wstatus, 0);
  177. if (WIFEXITED(wstatus) && WEXITSTATUS(wstatus) != 0) {
  178. throw std::runtime_error(std::string(args[0]) +
  179. " exited with code " + std::to_string(WEXITSTATUS(wstatus)));
  180. }
  181. if (WTERMSIG(wstatus)) {
  182. throw std::runtime_error(std::string(args[0]) +
  183. " terminated due to " + strsignal(WTERMSIG(wstatus)));
  184. }
  185. if (conf.output != nullptr && conf.output->back() == '\n') {
  186. conf.output->pop_back();
  187. }
  188. }
  189. void readDir(const std::string &path, std::vector<std::string> &files) {
  190. DIR *d = opendir(path.c_str());
  191. if (d == NULL) {
  192. throw std::runtime_error("opendir '" + path + "': " + strerror(errno));
  193. }
  194. struct dirent *ent;
  195. while ((ent = readdir(d)) != NULL) {
  196. if (ent->d_name[0] == '.')
  197. continue;
  198. files.emplace_back(ent->d_name);
  199. }
  200. closedir(d);
  201. }
  202. void chdir(const std::string &path) {
  203. if (::chdir(path.c_str()) < 0) {
  204. throw std::runtime_error("chdir '" + path + "': " + strerror(errno));
  205. }
  206. }
  207. std::string cwd() {
  208. std::vector<char> buf;
  209. buf.reserve(128);
  210. while (1) {
  211. char *res = getcwd(buf.data(), buf.capacity());
  212. if (res == NULL) {
  213. if (errno == ERANGE) {
  214. buf.reserve(buf.capacity() * 2);
  215. } else {
  216. throw std::system_error(EFAULT, std::generic_category());
  217. }
  218. } else {
  219. std::string str(buf.data());
  220. return str;
  221. }
  222. }
  223. }
  224. void symlink(const std::string &from, const std::string &to) {
  225. // TODO: Implement this in C++ instead
  226. std::vector<std::string> argv;
  227. argv.push_back("ln");
  228. argv.push_back("-s");
  229. argv.push_back("-f");
  230. argv.push_back("--");
  231. argv.push_back(from);
  232. argv.push_back(to);
  233. sys::execute(argv, ProcConf{});
  234. }
  235. std::string dirname(const std::string &path) {
  236. std::vector<char> buf(path.begin(), path.end());
  237. buf.push_back('\0');
  238. char *dir = ::dirname(buf.data());
  239. return std::string(dir);
  240. }
  241. }