logo

utils-std

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

which.c (2287B)


  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 "../libutils/getopt_nolong.h"
  6. #include "../libutils/strchrnul.h"
  7. #include <errno.h>
  8. #include <limits.h> // PATH_MAX
  9. #include <stdbool.h>
  10. #include <stdio.h> // fprintf
  11. #include <stdlib.h> // getenv
  12. #include <string.h> // strcpy, memcpy
  13. #include <unistd.h> // access, getopt
  14. const char *argv0 = "which";
  15. int
  16. main(int argc, char *argv[])
  17. {
  18. bool opt_a = false, opt_s = false;
  19. int missing = 0;
  20. char *path = getenv("PATH");
  21. if(path == NULL)
  22. {
  23. fputs("which: error: $PATH environment unset", stderr);
  24. return 1;
  25. }
  26. for(int c = -1; (c = getopt_nolong(argc, argv, "as")) != -1;)
  27. {
  28. switch(c)
  29. {
  30. case 'a':
  31. opt_a = true;
  32. break;
  33. case 's':
  34. opt_s = true;
  35. break;
  36. case '?':
  37. GETOPT_UNKNOWN_OPT
  38. return 1;
  39. default:
  40. abort();
  41. }
  42. }
  43. argc -= optind;
  44. argv += optind;
  45. if(argc <= 0) return 1;
  46. for(int i = 0; i < argc; i++)
  47. {
  48. char *cmd = argv[i];
  49. size_t cmdlen = strlen(cmd);
  50. bool found = false;
  51. char *start = path;
  52. if(strchr(cmd, '/'))
  53. {
  54. puts(cmd);
  55. continue;
  56. }
  57. const char *prev = start;
  58. while(true)
  59. {
  60. static char buf[PATH_MAX] = "";
  61. const char *stop = utils_strchrnul(prev, ':');
  62. size_t buflen = stop - prev;
  63. memcpy(buf, prev, buflen);
  64. if((PATH_MAX - buflen - 1) < cmdlen)
  65. {
  66. buf[buflen] = '\0';
  67. fprintf(stderr,
  68. "which: warning: Concatenation of PATH element '%s' and command '%s' would be "
  69. "greater than PATH_MAX\n",
  70. buf,
  71. cmd);
  72. goto which_cont;
  73. }
  74. buf[buflen++] = '/';
  75. memcpy(buf + buflen, cmd, cmdlen);
  76. buflen += cmdlen;
  77. buf[buflen] = '\0';
  78. errno = 0;
  79. if(access(buf, X_OK) == 0)
  80. {
  81. if(!opt_s) puts(buf);
  82. found = true;
  83. if(!opt_a) break;
  84. }
  85. switch(errno)
  86. {
  87. case ENOENT:
  88. case 0:
  89. break;
  90. default:
  91. fprintf(stderr,
  92. "which: warning: Failed checking access on file '%s': %s\n",
  93. buf,
  94. strerror(errno));
  95. break;
  96. }
  97. which_cont:
  98. if(*stop == '\0') break;
  99. prev = stop + 1;
  100. }
  101. if(!found) missing++;
  102. }
  103. return missing;
  104. }