logo

utils-std

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

which.c (2218B)


  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 "../lib/getopt_nolong.h"
  6. #include "../lib/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. const char *prev = start;
  53. while(true)
  54. {
  55. static char buf[PATH_MAX] = "";
  56. const char *stop = utils_strchrnul(prev, ':');
  57. size_t buflen = stop - prev;
  58. memcpy(buf, prev, buflen);
  59. if((PATH_MAX - buflen - 1) < cmdlen)
  60. {
  61. buf[buflen] = '\0';
  62. fprintf(stderr,
  63. "which: warning: Concatenation of PATH element '%s' and command '%s' would be "
  64. "greater than PATH_MAX\n",
  65. buf,
  66. cmd);
  67. goto which_cont;
  68. }
  69. buf[buflen++] = '/';
  70. memcpy(buf + buflen, cmd, cmdlen);
  71. buflen += cmdlen;
  72. buf[buflen] = '\0';
  73. errno = 0;
  74. if(access(buf, X_OK) == 0)
  75. {
  76. if(!opt_s) puts(buf);
  77. found = true;
  78. if(!opt_a) break;
  79. }
  80. switch(errno)
  81. {
  82. case ENOENT:
  83. case 0:
  84. break;
  85. default:
  86. fprintf(stderr,
  87. "which: warning: Failed checking access on file '%s': %s\n",
  88. buf,
  89. strerror(errno));
  90. break;
  91. }
  92. which_cont:
  93. if(*stop == '\0') break;
  94. prev = stop + 1;
  95. }
  96. if(!found) missing++;
  97. }
  98. return missing;
  99. }