logo

utils-std

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

ln.c (6757B)


  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 _GNU_SOURCE // O_PATH with glibc
  5. #define _DEFAULT_SOURCE // due to O_PATH
  6. // Don't define _POSIX_C_SOURCE otherwise FreeBSD hides O_PATH
  7. #include "../config.h"
  8. #include "../lib/bitmasks.h"
  9. #include "../libutils/getopt_nolong.h"
  10. #include <errno.h>
  11. #include <fcntl.h> // linkat, AT_SYMLINK_FOLLOW
  12. #include <libgen.h> // basename
  13. #include <limits.h> // PATH_MAX
  14. #include <stdbool.h>
  15. #include <stdio.h> // fprintf
  16. #include <string.h> // strerror
  17. #include <sys/stat.h> // fstat
  18. #include <unistd.h> // getopt, symlink, link
  19. #ifdef HAS_GETOPT_LONG
  20. #include <getopt.h>
  21. #endif
  22. const char *argv0 = "ln";
  23. static bool opt_s = false, force = false, opt_T = false;
  24. static int link_flags = 0;
  25. static int open_target_flags = O_RDONLY | O_PATH;
  26. static int open_dest_flags = O_RDONLY | O_PATH | O_NOFOLLOW;
  27. static struct stat dest_stat;
  28. static bool verbose = false;
  29. static int
  30. do_link(char *src, char *dest, int destfd)
  31. {
  32. if(opt_s)
  33. {
  34. if(symlinkat(src, AT_FDCWD, dest) == 0) return 0;
  35. if(errno != EEXIST)
  36. {
  37. fprintf(stderr, "ln: error: Failed creating symlink '%s': %s\n", dest, strerror(errno));
  38. return -1;
  39. }
  40. }
  41. else
  42. {
  43. if(linkat(AT_FDCWD, src, AT_FDCWD, dest, link_flags) == 0) return 0;
  44. if(errno != EEXIST)
  45. {
  46. fprintf(stderr,
  47. "ln: error: Failed creating hard link from '%s' to '%s': %s\n",
  48. src,
  49. dest,
  50. strerror(errno));
  51. return -1;
  52. }
  53. }
  54. errno = 0;
  55. if(!force)
  56. {
  57. fprintf(stderr, "ln: error: Destination '%s' already exists\n", dest);
  58. return -1;
  59. }
  60. if(destfd < 0)
  61. {
  62. destfd = open(dest, open_dest_flags);
  63. if(destfd < 0)
  64. {
  65. if(errno != ENOENT)
  66. {
  67. fprintf(stderr,
  68. "ln: error: Failed opening destination as path '%s': %s\n",
  69. dest,
  70. strerror(errno));
  71. return -1;
  72. }
  73. destfd = AT_FDCWD;
  74. }
  75. else if(fstat(destfd, &dest_stat) < 0)
  76. {
  77. fprintf(stderr,
  78. "ln: error: Failed getting status for destination '%s': %s\n",
  79. dest,
  80. strerror(errno));
  81. return -1;
  82. }
  83. }
  84. int dirfd = AT_FDCWD;
  85. if(S_ISDIR(dest_stat.st_mode))
  86. {
  87. if(opt_T)
  88. {
  89. fprintf(stderr, "ln: error: Option -T passed but target '%s' is a directory\n", dest);
  90. return -1;
  91. }
  92. dirfd = destfd;
  93. }
  94. else
  95. {
  96. /* check if symbolic/hard link refers to same file */
  97. struct stat src_stat;
  98. if(stat(src, &src_stat) < 0)
  99. {
  100. if(errno != ENOENT)
  101. {
  102. fprintf(
  103. stderr, "ln: error: Failed getting status for source '%s': %s\n", src, strerror(errno));
  104. return -1;
  105. }
  106. }
  107. else if(src_stat.st_dev == dest_stat.st_dev && src_stat.st_ino == dest_stat.st_ino)
  108. {
  109. fprintf(
  110. stderr, "ln: info: Source '%s' and destination '%s' refer to the same file\n", src, dest);
  111. return 0;
  112. }
  113. if(unlink(dest) < 0)
  114. {
  115. fprintf(stderr, "ln: error: Failed unlinking destination '%s': %s\n", dest, strerror(errno));
  116. return -1;
  117. }
  118. }
  119. if(opt_s)
  120. {
  121. if(symlinkat(src, dirfd, dest) == 0) goto cleanup;
  122. fprintf(stderr, "ln: error: Failed creating symlink '%s': %s\n", dest, strerror(errno));
  123. return -1;
  124. }
  125. else
  126. {
  127. if(linkat(AT_FDCWD, src, dirfd, dest, link_flags) == 0) goto cleanup;
  128. fprintf(stderr,
  129. "ln: error: Failed creating hard link from '%s' to '%s': %s\n",
  130. src,
  131. dest,
  132. strerror(errno));
  133. return -1;
  134. }
  135. cleanup:
  136. if(destfd < 0) return 0;
  137. if(close(destfd) != 0)
  138. {
  139. fprintf(stderr,
  140. "ln: error: Failed closing destination directory '%s': %s\n",
  141. dest,
  142. strerror(errno));
  143. return -1;
  144. }
  145. if(verbose) printf("'%s' -> '%s'\n", src, dest);
  146. return 0;
  147. }
  148. static void
  149. usage(void)
  150. {
  151. fprintf(stderr, "\
  152. Usage: ln [-fnv] [-L|-P] -T source target\n\
  153. ln [-fnv] [-L|-P] source... [target]\n\
  154. ln -s [-fnv] reference... [target]\n\
  155. ");
  156. }
  157. int
  158. main(int argc, char *argv[])
  159. {
  160. #ifdef HAS_GETOPT_LONG
  161. // Strictly for GNUisms compatibility so no long-only options
  162. // clang-format off
  163. static struct option opts[] = {
  164. {"force", no_argument, NULL, 'f'},
  165. {"logical", no_argument, NULL, 'L'},
  166. {"no-dereference", no_argument, NULL, 'n'},
  167. {"no-target-directory", no_argument, NULL, 'T'},
  168. {"physical", no_argument, NULL, 'P'},
  169. {"symbolic", no_argument, NULL, 's'},
  170. {"verbose", no_argument, NULL, 'v'},
  171. {0, 0, 0, 0},
  172. };
  173. // clang-format on
  174. // Need + as first character to get POSIX-style option parsing
  175. for(int c = -1; (c = getopt_long(argc, argv, "+:fnsLPTv", opts, NULL)) != -1;)
  176. #else
  177. for(int c = -1; (c = getopt_nolong(argc, argv, ":fnsLPTv")) != -1;)
  178. #endif
  179. {
  180. switch(c)
  181. {
  182. case 'f':
  183. force = true;
  184. break;
  185. case 'n':
  186. FIELD_SET(open_target_flags, O_NOFOLLOW);
  187. break;
  188. case 's':
  189. opt_s = true;
  190. break;
  191. case 'L':
  192. FIELD_SET(link_flags, AT_SYMLINK_FOLLOW);
  193. break;
  194. case 'P':
  195. FIELD_CLR(link_flags, AT_SYMLINK_FOLLOW);
  196. break;
  197. case 'v':
  198. verbose = true;
  199. break;
  200. case 'T':
  201. opt_T = true;
  202. break;
  203. case '?':
  204. GETOPT_UNKNOWN_OPT
  205. usage();
  206. return 1;
  207. }
  208. }
  209. argc -= optind;
  210. argv += optind;
  211. char *target = argv[argc - 1];
  212. char dest[PATH_MAX] = "";
  213. if(argc <= 0)
  214. {
  215. fprintf(stderr, "ln: error: Not enough operands, %d given, expect >= 1\n", argc);
  216. return 1;
  217. }
  218. else if(opt_T && argc != 2)
  219. {
  220. fprintf(stderr, "ln: error: Option -T passed, but got %d arguments instead of 2\n", argc);
  221. return 1;
  222. }
  223. else if(argc == 1)
  224. {
  225. target = (char *)".";
  226. argc++;
  227. }
  228. else if(argc == 2)
  229. {
  230. if(opt_T)
  231. {
  232. int ret = do_link(argv[0], argv[1], -1);
  233. return ret < 0 ? 1 : 0;
  234. }
  235. int targetfd = open(target, open_target_flags);
  236. if(targetfd >= 0)
  237. {
  238. if(fstat(targetfd, &dest_stat) < 0)
  239. {
  240. fprintf(stderr,
  241. "ln: error: Failed getting status for target '%s': %s\n",
  242. dest,
  243. strerror(errno));
  244. return -1;
  245. }
  246. if(!S_ISDIR(dest_stat.st_mode))
  247. {
  248. int ret = do_link(argv[0], argv[1], targetfd);
  249. return ret < 0 ? 1 : 0;
  250. }
  251. if(close(targetfd) < 0)
  252. {
  253. fprintf(stderr,
  254. "ln: error: Failed closing target directory '%s': %s\n",
  255. target,
  256. strerror(errno));
  257. return -1;
  258. }
  259. }
  260. else
  261. {
  262. int ret = do_link(argv[0], argv[1], -1);
  263. return ret < 0 ? 1 : 0;
  264. }
  265. }
  266. for(int i = 0; i < argc - 1; i++)
  267. {
  268. char *src = argv[i];
  269. char *src_basename = basename(src);
  270. if(snprintf(dest, PATH_MAX, "%s/%s", target, src_basename) < 0)
  271. {
  272. fprintf(stderr,
  273. "ln: error: Failed joining target '%s' and source basename '%s'\n",
  274. target,
  275. src_basename);
  276. return 1;
  277. }
  278. if(do_link(src, dest, -1) < 0) return 1;
  279. }
  280. return 0;
  281. }