logo

utils-std

Collection of commonly available Unix tools git clone https://anongit.hacktivis.me/git/utils-std.git/

which.c (2727B)


  1. // utils-std: Collection of commonly available Unix tools
  2. // SPDX-FileCopyrightText: 2017 Haelwenn (lanodan) Monnier <contact+utils@hacktivis.me>
  3. // SPDX-License-Identifier: MPL-2.0
  4. #define _POSIX_C_SOURCE 200809L
  5. #include "../config.h"
  6. #include "../libutils/getopt_nolong.h"
  7. #include "../libutils/strchrnul.h"
  8. #include <errno.h>
  9. #include <limits.h> // PATH_MAX
  10. #include <stdbool.h>
  11. #include <stdio.h> // fprintf
  12. #include <stdlib.h> // getenv
  13. #include <string.h> // strcpy, memcpy
  14. #include <unistd.h> // access, getopt
  15. #ifdef HAS_GETOPT_LONG
  16. #include <getopt.h>
  17. #endif
  18. const char *argv0 = "which";
  19. int
  20. main(int argc, char *argv[])
  21. {
  22. bool opt_a = false, opt_s = false;
  23. int missing = 0;
  24. char *path = getenv("PATH");
  25. if(path == NULL)
  26. {
  27. fputs("which: error: $PATH environment unset", stderr);
  28. return 1;
  29. }
  30. #ifdef HAS_GETOPT_LONG
  31. // Strictly for GNUisms compatibility so no long-only options
  32. // clang-format off
  33. static struct option opts[] = {
  34. {"all", no_argument, NULL, 'a'},
  35. {0, 0, 0, 0},
  36. };
  37. // clang-format on
  38. // Need + as first character to get POSIX-style option parsing
  39. for(int c = -1; (c = getopt_long(argc, argv, "+as", opts, NULL)) != -1;)
  40. #else
  41. for(int c = -1; (c = getopt_nolong(argc, argv, "as")) != -1;)
  42. #endif
  43. {
  44. switch(c)
  45. {
  46. case 'a':
  47. opt_a = true;
  48. break;
  49. case 's':
  50. opt_s = true;
  51. break;
  52. case '?':
  53. GETOPT_UNKNOWN_OPT
  54. return 1;
  55. default:
  56. abort();
  57. }
  58. }
  59. argc -= optind;
  60. argv += optind;
  61. if(argc <= 0) return 1;
  62. for(int i = 0; i < argc; i++)
  63. {
  64. char *cmd = argv[i];
  65. size_t cmdlen = strlen(cmd);
  66. bool found = false;
  67. char *start = path;
  68. if(strchr(cmd, '/'))
  69. {
  70. puts(cmd);
  71. continue;
  72. }
  73. const char *prev = start;
  74. while(true)
  75. {
  76. static char buf[PATH_MAX] = "";
  77. const char *stop = utils_strchrnul(prev, ':');
  78. size_t buflen = stop - prev;
  79. memcpy(buf, prev, buflen);
  80. if((PATH_MAX - buflen - 1) < cmdlen)
  81. {
  82. buf[buflen] = '\0';
  83. fprintf(stderr,
  84. "which: warning: Concatenation of PATH element '%s' and command '%s' would be "
  85. "greater than PATH_MAX\n",
  86. buf,
  87. cmd);
  88. goto which_cont;
  89. }
  90. buf[buflen++] = '/';
  91. memcpy(buf + buflen, cmd, cmdlen);
  92. buflen += cmdlen;
  93. buf[buflen] = '\0';
  94. errno = 0;
  95. if(access(buf, X_OK) == 0)
  96. {
  97. if(!opt_s) puts(buf);
  98. found = true;
  99. if(!opt_a) break;
  100. }
  101. switch(errno)
  102. {
  103. case ENOENT:
  104. case 0:
  105. break;
  106. default:
  107. fprintf(stderr,
  108. "which: warning: Failed checking access on file '%s': %s\n",
  109. buf,
  110. strerror(errno));
  111. break;
  112. }
  113. which_cont:
  114. if(*stop == '\0') break;
  115. prev = stop + 1;
  116. }
  117. if(!found) missing++;
  118. }
  119. return missing;
  120. }