logo

utils-std

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

realpath.c (5914B)


  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. #define _XOPEN_SOURCE 700 // realpath is in XSI
  6. #include "../config.h"
  7. #include "../libutils/fs.h"
  8. #include "../libutils/getopt_nolong.h"
  9. #include <errno.h>
  10. #include <limits.h> // PATH_MAX
  11. #include <stdbool.h>
  12. #include <stdio.h> // fprintf(), puts()
  13. #include <stdlib.h> // realpath()
  14. #include <string.h> // strncmp(), strnlen, strerror
  15. #include <unistd.h> // getopt
  16. #ifdef HAS_GETOPT_LONG
  17. #include <getopt.h>
  18. #endif
  19. static bool must_exists = false;
  20. static bool offline = false;
  21. static bool quiet = false;
  22. const char *argv0 = NULL;
  23. static char sep = '\n';
  24. static int
  25. print_realpath(char *path)
  26. {
  27. char *file = NULL;
  28. if(offline)
  29. file = offline_realpath(path, NULL);
  30. else
  31. file = realpath(path, NULL);
  32. if(file)
  33. {
  34. if(printf("%s", file) < 0)
  35. {
  36. fprintf(stderr, "%s: error: Failed writing to stdout: %s\n", argv0, strerror(errno));
  37. free(file);
  38. return 1;
  39. }
  40. free(file);
  41. return 0;
  42. }
  43. if(must_exists || errno != ENOENT)
  44. {
  45. if(!quiet)
  46. fprintf(stderr, "%s: error: Failed canonilizing \"%s\": %s\n", argv0, path, strerror(errno));
  47. return 1;
  48. }
  49. char *child = path_split_static(path, true);
  50. if(child == NULL)
  51. {
  52. // Return as if realpath just failed
  53. if(!quiet)
  54. fprintf(stderr, "%s: error: Failed canonilizing \"%s\": %s\n", argv0, path, strerror(errno));
  55. return 1;
  56. }
  57. errno = 0;
  58. char *parent = realpath(path, NULL);
  59. if(!parent)
  60. {
  61. if(!quiet)
  62. fprintf(stderr,
  63. "%s: error: Failed canonilizing parent of full path \"%s/%s\": %s\n",
  64. argv0,
  65. path,
  66. child,
  67. strerror(errno));
  68. return 1;
  69. }
  70. if(printf("%s/%s", parent, child) < 0)
  71. {
  72. fprintf(stderr, "%s: error: Failed writing to stdout: %s\n", argv0, strerror(errno));
  73. free(parent);
  74. return 1;
  75. }
  76. free(parent);
  77. return 0;
  78. }
  79. static void
  80. usage_realpath(void)
  81. {
  82. fprintf(stderr, "Usage: realpath [-E|-e] [-n|-z] path...\n");
  83. }
  84. static int
  85. main_realpath(int argc, char *argv[])
  86. {
  87. must_exists = false;
  88. quiet = false;
  89. int offset_sep = 0;
  90. #ifdef HAS_GETOPT_LONG
  91. // Strictly for GNUisms compatibility so no long-only options
  92. // clang-format off
  93. static struct option opts[] = {
  94. {"canonicalize-existing", no_argument, NULL, 'e'},
  95. {"canonicalize-missing", no_argument, NULL, 'm'},
  96. {"quiet", no_argument, NULL, 'q'},
  97. {"strip", no_argument, NULL, 's'},
  98. {"zero", no_argument, NULL, 'z'},
  99. {0, 0, 0, 0},
  100. };
  101. // clang-format on
  102. // Need + as first character to get POSIX-style option parsing
  103. for(int c = -1; (c = getopt_long(argc, argv, "+:Eemnszq", opts, NULL)) != -1;)
  104. #else
  105. for(int c = -1; (c = getopt_nolong(argc, argv, ":Eemnszq")) != -1;)
  106. #endif
  107. {
  108. switch(c)
  109. {
  110. case 'E':
  111. must_exists = false;
  112. break;
  113. case 'e':
  114. must_exists = true;
  115. break;
  116. case 'n':
  117. offset_sep = 1;
  118. break;
  119. case 's':
  120. offline = true;
  121. break;
  122. case 'z':
  123. sep = '\0';
  124. break;
  125. case 'q':
  126. quiet = true;
  127. break;
  128. case ':':
  129. fprintf(stderr, "%s: error: Missing operand for option: '-%c'\n", argv0, optopt);
  130. usage_realpath();
  131. return 1;
  132. case '?':
  133. GETOPT_UNKNOWN_OPT
  134. usage_realpath();
  135. return 1;
  136. }
  137. }
  138. argv += optind;
  139. argc -= optind;
  140. if(argc == 0)
  141. {
  142. fprintf(stderr, "%s: error: Expected one file as argument, got 0\n", argv0);
  143. usage_realpath();
  144. return 1;
  145. }
  146. for(int i = 0; i < argc; i++)
  147. {
  148. if(print_realpath(argv[i]) != 0) return 1;
  149. if(i != argc - offset_sep) printf("%c", sep);
  150. }
  151. return 0;
  152. }
  153. static void
  154. usage_readlink(void)
  155. {
  156. fprintf(stderr, "Usage: readlink [-f|-e] [-n|-z] file...\n");
  157. }
  158. static int
  159. main_readlink(int argc, char *argv[])
  160. {
  161. must_exists = true;
  162. bool canonicalize = false;
  163. int offset_sep = 0;
  164. #ifdef HAS_GETOPT_LONG
  165. // Strictly for GNUisms compatibility so no long-only options
  166. // clang-format off
  167. static struct option opts[] = {
  168. {"canonicalize", no_argument, NULL, 'f'},
  169. {"canonicalize-existing", no_argument, NULL, 'e'},
  170. {"canonicalize-missing", no_argument, NULL, 'm'},
  171. {"no-newline", no_argument, NULL, 'n'},
  172. {"zero", no_argument, NULL, 'z'},
  173. {0, 0, 0, 0},
  174. };
  175. // clang-format on
  176. // Need + as first character to get POSIX-style option parsing
  177. for(int c = -1; (c = getopt_long(argc, argv, "+:femnz", opts, NULL)) != -1;)
  178. #else
  179. for(int c = -1; (c = getopt_nolong(argc, argv, ":femnz")) != -1;)
  180. #endif
  181. {
  182. switch(c)
  183. {
  184. case 'f':
  185. canonicalize = true;
  186. must_exists = false;
  187. break;
  188. case 'e':
  189. must_exists = true;
  190. break;
  191. case 'n':
  192. offset_sep = 1;
  193. break;
  194. case 'z':
  195. sep = '\0';
  196. break;
  197. case ':':
  198. fprintf(stderr, "%s: error: Missing operand for option: '-%c'\n", argv0, optopt);
  199. usage_readlink();
  200. return 1;
  201. case '?':
  202. GETOPT_UNKNOWN_OPT
  203. usage_readlink();
  204. return 1;
  205. }
  206. }
  207. argv += optind;
  208. argc -= optind;
  209. if(argc == 0)
  210. {
  211. fprintf(stderr, "%s: error: Expected one file as argument, got 0\n", argv0);
  212. usage_readlink();
  213. return 1;
  214. }
  215. for(int i = 0; i < argc; i++)
  216. {
  217. char *path = argv[i];
  218. if(canonicalize)
  219. {
  220. if(print_realpath(path) != 0) return 1;
  221. if(i != argc - offset_sep) printf("%c", sep);
  222. continue;
  223. }
  224. char buf[PATH_MAX] = "";
  225. if(readlink(path, buf, sizeof(buf) - 1) < 0)
  226. {
  227. fprintf(stderr,
  228. "%s: error: Failed reading symbolic link of '%s': %s\n",
  229. argv0,
  230. path,
  231. strerror(errno));
  232. return 1;
  233. }
  234. printf("%s", buf);
  235. if(i != argc - offset_sep) printf("%c", sep);
  236. }
  237. return 0;
  238. }
  239. int
  240. main(int argc, char *argv[])
  241. {
  242. argv0 = static_basename(argv[0]);
  243. if(strcmp(argv0, "realpath") == 0) return main_realpath(argc, argv);
  244. if(strcmp(argv0, "readlink") == 0) return main_readlink(argc, argv);
  245. fprintf(stderr, "%s: error: Unknown utility '%s', expected realpath or readlink\n", argv0, argv0);
  246. return 1;
  247. }