which.c 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451
  1. /* SPDX-FileCopyrightText: 2021 John Scott <jscott@posteo.net>
  2. * SPDX-License-Identifier: GPL-3.0-or-later */
  3. /* We do not support the obsolete extension where an
  4. * omitted directory name is interpreted as the current
  5. * working directory. In $PATH = "/usr::/bin:", the lack
  6. * of a path in the middle or one at the end is simply ignored. */
  7. #define _XOPEN_SOURCE 700
  8. #include <assert.h>
  9. #include <errno.h>
  10. #include <limits.h>
  11. #include <locale.h>
  12. #include <pthread.h>
  13. #include <search.h>
  14. #include <semaphore.h>
  15. #include <stdbool.h>
  16. #include <stdint.h>
  17. #include <stdio.h>
  18. #include <stdlib.h>
  19. #include <string.h>
  20. #include <sys/stat.h>
  21. #include <unistd.h>
  22. #if _POSIX_ADVISORY_INFO > -1
  23. #include <sys/mman.h>
  24. #endif
  25. /* A NULL-terminated list of directories in PATH. */
  26. static char **list;
  27. /* We need a way to tell which threads are running at any given time
  28. * so we know which ones we can send cancellation requests to.
  29. * This is a boolean list indicating whether a given thread is
  30. * running or not. thread_is_running[0] will correspond to the
  31. * first child we create, thread_is_running[1] to the second,
  32. * and so on. */
  33. static bool *thread_is_running;
  34. /* This mutex not only protects thread_is_running from data races,
  35. * but also has the effect that, if we lock it in the main thread,
  36. * child threads will be waiting for this to become unlocked just
  37. * before they set thread_is_running[j] to false and bail out.
  38. * This means that locking this mutex in the main thread will
  39. * freeze which threads are running or not, so that we may safely
  40. * send cancellation requests to those that are. */
  41. static pthread_mutex_t thread_is_running_guard = PTHREAD_MUTEX_INITIALIZER;
  42. /* We need indices into thread_is_running. This integer is
  43. * an identifier for the thread which may be used as an index.
  44. * We increment it, starting at 0, for every thread we create. */
  45. static int *seq_thread_id;
  46. /* We need to give every child thread a chance to read seq_thread_id
  47. * before clobbering it. This semaphore ensures that we do that; the
  48. * main thread will wait for it to be unlocked by the child, and the
  49. * child will unlock it after it has copied the value. */
  50. static sem_t seq_thread_id_guard;
  51. /* This is a list of groups we are in. Its lifetime is managed by main(). */
  52. static gid_t *groups;
  53. static int groupcount;
  54. static void *reallocarray(void *p, size_t m, size_t n) {
  55. if(n && m > SIZE_MAX / n) {
  56. errno = ENOMEM;
  57. return NULL;
  58. }
  59. return realloc(p, m * n);
  60. }
  61. static int giddiff(const void *a, const void *b) {
  62. gid_t gid_a = *(const gid_t*)a;
  63. gid_t gid_b = *(const gid_t*)b;
  64. /* We do not simply return gid_a - gid_b, because that
  65. * bears the risk of overflow if gid_t is a signed type. */
  66. if(gid_a > gid_b) {
  67. return 1;
  68. } else if(gid_a < gid_b) {
  69. return -1;
  70. } else {
  71. return 0;
  72. }
  73. }
  74. static void stop_running(void *i) {
  75. int k = pthread_mutex_lock(&thread_is_running_guard);
  76. if(k) {
  77. char errstr[NL_TEXTMAX];
  78. if(strerror_r(k, errstr, sizeof(errstr))) {
  79. abort();
  80. }
  81. fprintf(stderr, "Failed to lock mutex: %s\n", errstr);
  82. abort();
  83. }
  84. assert(thread_is_running[*(int*)i]);
  85. thread_is_running[*(int*)i] = false;
  86. k = pthread_mutex_unlock(&thread_is_running_guard);
  87. if(k) {
  88. abort();
  89. }
  90. }
  91. static bool file_is_executable(const char filename[restrict static 1]) {
  92. struct stat st;
  93. if(stat(filename, &st) == -1 || !S_ISREG(st.st_mode)) {
  94. return false;
  95. }
  96. if(st.st_uid == geteuid()) {
  97. return (st.st_mode & S_IXUSR) ? true : false;
  98. }
  99. if(bsearch(&st.st_gid, groups, groupcount, sizeof(*groups), giddiff)) {
  100. return (st.st_mode & S_IXGRP) ? true : false;
  101. }
  102. return (st.st_mode & S_IXOTH) ? true : false;
  103. }
  104. /* This is the main function that corresponds to our child thread.
  105. * It tries to find the program called filename in our list of
  106. * directories in PATH and to return a dynamically-allocated
  107. * string to the full pathname, or NULL if it can't do that. */
  108. static void *find_program(void *filename) {
  109. int my_seq_thread_id = *seq_thread_id;
  110. if(sem_post(&seq_thread_id_guard) == -1) {
  111. abort();
  112. }
  113. int k = pthread_mutex_lock(&thread_is_running_guard);
  114. if(k) {
  115. char errstr[NL_TEXTMAX];
  116. if(strerror_r(k, errstr, sizeof(errstr))) {
  117. abort();
  118. }
  119. fprintf(stderr, "Failed to lock mutex: %s\n", errstr);
  120. abort();
  121. }
  122. assert(!thread_is_running[my_seq_thread_id]);
  123. thread_is_running[my_seq_thread_id] = true;
  124. k = pthread_mutex_unlock(&thread_is_running_guard);
  125. if(k) {
  126. abort();
  127. }
  128. /* These have to be declared outside of our cleanup handler calls. */
  129. char *pathname;
  130. bool pathname_found = false;
  131. /* We hit no cancellation points between setting thread_is_running[j]
  132. * to true and pushing this handler, since pthread_mutex_unlock is not one. */
  133. pthread_cleanup_push(stop_running, &my_seq_thread_id);
  134. if(strchr(filename, '/')) {
  135. /* We were either given an absolute pathname, or a relative
  136. * path that must be evaluated with respect to the current
  137. * working directory, which must not use prefixes from PATH. */
  138. if(file_is_executable(filename)) {
  139. /* The caller expects that the string we return is dynamically allocated. */
  140. pathname = strdup(filename);
  141. if(!pathname) {
  142. perror("Failed to duplicate string");
  143. }
  144. pthread_exit(pathname);
  145. } else {
  146. pthread_exit(NULL);
  147. }
  148. }
  149. /* It's not an absolute pathname; try prefixing the string
  150. * with strings from PATH and see what sticks. */
  151. const size_t filenamelen = strlen(filename);
  152. for(char **directory = list; *directory; directory++) {
  153. const size_t directorylen = strlen(*directory);
  154. if(filenamelen > SIZE_MAX - directorylen || filenamelen + directorylen > SIZE_MAX - 2) {
  155. continue;
  156. }
  157. pathname = malloc(directorylen + filenamelen + 2); /* one extra byte for a /, one for the NUL */
  158. if(!pathname) {
  159. perror("Failed to allocate memory for pathname");
  160. pthread_exit(NULL);
  161. }
  162. pthread_cleanup_push(free, pathname);
  163. char *end = (char*)memcpy(pathname, *directory, directorylen) + directorylen;
  164. if(end[-1] != '/') {
  165. *end++ = '/';
  166. }
  167. memcpy(end, filename, filenamelen + 1);
  168. pathname_found = file_is_executable(pathname);
  169. pthread_cleanup_pop(!pathname_found); /* free(pathname)? */
  170. if(pathname_found) {
  171. break;
  172. }
  173. }
  174. pthread_cleanup_pop(true); /* stop_running(&my_seq_thread_id) */
  175. pthread_exit(pathname_found ? pathname : NULL);
  176. }
  177. int main(int argc, char *argv[]) {
  178. if(!setlocale(LC_ALL, "")) {
  179. fputs("Failed to enable default locale\n", stderr);
  180. exit(EXIT_FAILURE);
  181. }
  182. int opt;
  183. while((opt = getopt(argc, argv, "")) != -1) {
  184. if(opt == '?') {
  185. exit(EXIT_FAILURE);
  186. }
  187. }
  188. argc -= optind;
  189. argv += optind;
  190. if(!argc) {
  191. exit(EXIT_SUCCESS);
  192. }
  193. const char *const envpath = getenv("PATH");
  194. char *path;
  195. size_t l;
  196. if(!envpath || !envpath[0]) {
  197. l = confstr(_CS_PATH, NULL, 0);
  198. if(!l) {
  199. fputs("Failed to obtain value of PATH\n", stderr);
  200. exit(EXIT_FAILURE);
  201. }
  202. path = aligned_alloc(sysconf(_SC_PAGESIZE), l);
  203. if(!path) {
  204. perror("Failed to allocate memory for PATH");
  205. exit(EXIT_FAILURE);
  206. }
  207. confstr(_CS_PATH, path, l);
  208. } else {
  209. l = strlen(envpath) + 1;
  210. path = aligned_alloc(sysconf(_SC_PAGESIZE), l);
  211. if(!path) {
  212. perror("Failed to duplicate string");
  213. exit(EXIT_FAILURE);
  214. }
  215. memcpy(path, envpath, l);
  216. }
  217. #if _POSIX_ADVISORY_INFO > -1
  218. int p = posix_madvise(path, l, POSIX_MADV_SEQUENTIAL);
  219. if(p && sysconf(_SC_ADVISORY_INFO) != -1) {
  220. fprintf(stderr, "Failed to advise the system on memory usage: %s\n", strerror(p));
  221. }
  222. #endif
  223. /* The maximum number of directories in PATH is one plus
  224. * the number of colons, where multiple consecutive colons
  225. * can be treated as a single one. */
  226. size_t numdirs = 1;
  227. assert(path[0]);
  228. for(size_t i = 1; path[i]; i++) {
  229. /* In case of a set of multiple consecutive colons,
  230. * only count the last one. */
  231. if(path[i] == ':' && path[i+1] && path[i+1] != ':') {
  232. numdirs++;
  233. }
  234. }
  235. assert(numdirs < SIZE_MAX);
  236. list = reallocarray(NULL, numdirs + 1, sizeof(*list));
  237. if(!list) {
  238. perror("Failed to allocate memory for directory list");
  239. goto endpath;
  240. }
  241. char *tok = NULL;
  242. size_t n = 0;
  243. do {
  244. tok = strtok(tok ? NULL : path, ":");
  245. assert(n <= numdirs);
  246. list[n++] = tok;
  247. } while(tok);
  248. pthread_t *ids = reallocarray(NULL, argc, sizeof(*ids));
  249. if(!ids) {
  250. perror("Failed to allocate memory for thread list");
  251. goto endlist;
  252. }
  253. if((groupcount = getgroups(0, groups)) == -1) {
  254. perror("Failed to get number of groups");
  255. goto endids;
  256. }
  257. #pragma GCC diagnostic ignored "-Wsign-compare"
  258. if(groupcount == INT_MAX || groupcount == SIZE_MAX) {
  259. #pragma GCC diagnostic pop
  260. fprintf(stderr, "Failed to create group list: %s\n", strerror(EOVERFLOW));
  261. goto endids;
  262. }
  263. /* We might need an extra member for the effective group ID. */
  264. groups = reallocarray(NULL, groupcount + 1, sizeof(*groups));
  265. if(!groups) {
  266. perror("Failed to allocate memory for group list");
  267. goto endids;
  268. }
  269. /* It's possible that in a TOCTTOU sort of way, the number of
  270. * groups we're in now is fewer than the number we were in before,
  271. * hence the reassignment to groupcount. */
  272. if((groupcount = getgroups(groupcount, groups)) == -1) {
  273. perror("Failed to populate group list");
  274. goto endgroups;
  275. }
  276. /* The group list may not include the effective group ID. */
  277. if(!lfind(&(gid_t){getegid()}, groups, &(size_t){groupcount}, sizeof(*groups), giddiff)) {
  278. groups[groupcount++] = getegid();
  279. }
  280. qsort(groups, groupcount, sizeof(*groups), giddiff);
  281. if(sem_init(&seq_thread_id_guard, false, 0U) == -1) {
  282. perror("Failed to initialize semaphore");
  283. goto endgroups;
  284. }
  285. thread_is_running = calloc(argc, sizeof(*thread_is_running));
  286. if(!thread_is_running) {
  287. perror("Failed to allocate memory for running thread list");
  288. goto endseq_thread_id_guard;
  289. }
  290. void **retval = reallocarray(NULL, argc, sizeof(*retval));
  291. if(!retval) {
  292. perror("Failed to allocate memory for thread return values");
  293. goto endthread_is_running;
  294. }
  295. int i;
  296. seq_thread_id = &i;
  297. for(i = 0; i < argc; i++) {
  298. int k;
  299. tryagain:
  300. k = pthread_create(ids + i, NULL, find_program, argv[i]);
  301. switch(k) {
  302. case 0:
  303. break;
  304. case EAGAIN:
  305. if(sched_yield() == -1) {
  306. perror("Failed to yield");
  307. }
  308. goto tryagain;
  309. default:
  310. fprintf(stderr, "Failed to create thread: %s\n", strerror(k));
  311. k = pthread_mutex_lock(&thread_is_running_guard);
  312. if(k) {
  313. fprintf(stderr, "Failed to lock mutex: %s\n", strerror(k));
  314. abort();
  315. }
  316. for(int j = 0; j < i; j++) {
  317. if(thread_is_running[j]) {
  318. k = pthread_cancel(ids[j]);
  319. if(k) {
  320. fprintf(stderr, "Failed to cancel thread: %s\n", strerror(k));
  321. abort();
  322. }
  323. }
  324. }
  325. k = pthread_mutex_unlock(&thread_is_running_guard);
  326. if(k) {
  327. fprintf(stderr, "Failed to unlock mutex: %s\n", strerror(k));
  328. abort();
  329. }
  330. void *threadreturn;
  331. for(int j = 0; j < i; j++) {
  332. k = pthread_join(ids[j], &threadreturn);
  333. if(k) {
  334. fprintf(stderr, "Failed to join with thread: %s\n", strerror(k));
  335. abort();
  336. }
  337. if(threadreturn != PTHREAD_CANCELED) {
  338. free(threadreturn);
  339. }
  340. }
  341. goto endthread_is_running_guard;
  342. }
  343. if(sem_wait(&seq_thread_id_guard) == -1) {
  344. perror("Failed to lock semaphore");
  345. abort();
  346. }
  347. }
  348. for(int j = 0; j < argc; j++) {
  349. int k = pthread_join(ids[j], retval + j);
  350. if(k) {
  351. fprintf(stderr, "Failed to join with thread: %s\n", strerror(k));
  352. abort();
  353. }
  354. }
  355. bool all_found = true;
  356. for(int j = 0; j < argc; j++) {
  357. if(retval[j]) {
  358. if(puts(retval[j]) == EOF) {
  359. perror("Failed to print filename");
  360. all_found = false;
  361. }
  362. free(retval[j]);
  363. } else {
  364. all_found = false;
  365. }
  366. }
  367. int k = pthread_mutex_destroy(&thread_is_running_guard);
  368. if(k) {
  369. fprintf(stderr, "Failed to destroy mutex: %s\n", strerror(k));
  370. abort();
  371. }
  372. free(retval);
  373. if(sem_destroy(&seq_thread_id_guard) == -1) {
  374. perror("Failed to destroy semaphore");
  375. abort();
  376. }
  377. free(groups);
  378. free(ids);
  379. free(list);
  380. free(path);
  381. exit(all_found ? EXIT_SUCCESS : EXIT_FAILURE);
  382. endthread_is_running_guard:
  383. k = pthread_mutex_destroy(&thread_is_running_guard);
  384. if(k) {
  385. fprintf(stderr, "Failed to destroy mutex: %s\n", strerror(k));
  386. abort();
  387. }
  388. free(retval);
  389. endthread_is_running:
  390. free(thread_is_running);
  391. endseq_thread_id_guard:
  392. if(sem_destroy(&seq_thread_id_guard) == -1) {
  393. perror("Failed to destroy semaphore");
  394. abort();
  395. }
  396. endgroups:
  397. free(groups);
  398. endids:
  399. free(ids);
  400. endlist:
  401. free(list);
  402. endpath:
  403. free(path);
  404. exit(EXIT_FAILURE);
  405. }