logo

utils-std

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

ln.c (6243B)


  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;
  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. dirfd = destfd;
  88. }
  89. else
  90. {
  91. /* check if symbolic/hard link refers to same file */
  92. struct stat src_stat;
  93. if(stat(src, &src_stat) < 0)
  94. {
  95. if(errno != ENOENT)
  96. {
  97. fprintf(
  98. stderr, "ln: error: Failed getting status for source '%s': %s\n", src, strerror(errno));
  99. return -1;
  100. }
  101. }
  102. else if(src_stat.st_dev == dest_stat.st_dev && src_stat.st_ino == dest_stat.st_ino)
  103. {
  104. fprintf(
  105. stderr, "ln: info: Source '%s' and destination '%s' refer to the same file\n", src, dest);
  106. return 0;
  107. }
  108. if(unlink(dest) < 0)
  109. {
  110. fprintf(stderr, "ln: error: Failed unlinking destination '%s': %s\n", dest, strerror(errno));
  111. return -1;
  112. }
  113. }
  114. if(opt_s)
  115. {
  116. if(symlinkat(src, dirfd, dest) == 0) goto cleanup;
  117. fprintf(stderr, "ln: error: Failed creating symlink '%s': %s\n", dest, strerror(errno));
  118. return -1;
  119. }
  120. else
  121. {
  122. if(linkat(AT_FDCWD, src, dirfd, dest, link_flags) == 0) goto cleanup;
  123. fprintf(stderr,
  124. "ln: error: Failed creating hard link from '%s' to '%s': %s\n",
  125. src,
  126. dest,
  127. strerror(errno));
  128. return -1;
  129. }
  130. cleanup:
  131. if(destfd < 0) return 0;
  132. if(close(destfd) != 0)
  133. {
  134. fprintf(stderr,
  135. "ln: error: Failed closing destination directory '%s': %s\n",
  136. dest,
  137. strerror(errno));
  138. return -1;
  139. }
  140. if(verbose) printf("'%s' -> '%s'\n", src, dest);
  141. return 0;
  142. }
  143. static void
  144. usage(void)
  145. {
  146. fprintf(stderr, "\
  147. Usage: ln [-fnv] [-L|-P] source... target\n\
  148. ln -s [-fnv] reference... target\n\
  149. ");
  150. }
  151. int
  152. main(int argc, char *argv[])
  153. {
  154. #ifdef HAS_GETOPT_LONG
  155. // Strictly for GNUisms compatibility so no long-only options
  156. // clang-format off
  157. static struct option opts[] = {
  158. {"force", no_argument, NULL, 'f'},
  159. {"logical", no_argument, NULL, 'L'},
  160. {"no-dereference", no_argument, NULL, 'n'},
  161. {"physical", no_argument, NULL, 'P'},
  162. {"symbolic", no_argument, NULL, 's'},
  163. {"verbose", no_argument, NULL, 'v'},
  164. {0, 0, 0, 0},
  165. };
  166. // clang-format on
  167. // Need + as first character to get POSIX-style option parsing
  168. for(int c = -1; (c = getopt_long(argc, argv, "+:fnsLPv", opts, NULL)) != -1;)
  169. #else
  170. for(int c = -1; (c = getopt_nolong(argc, argv, ":fnsLPv")) != -1;)
  171. #endif
  172. {
  173. switch(c)
  174. {
  175. case 'f':
  176. force = true;
  177. break;
  178. case 'n':
  179. FIELD_SET(open_target_flags, O_NOFOLLOW);
  180. break;
  181. case 's':
  182. opt_s = true;
  183. break;
  184. case 'L':
  185. FIELD_SET(link_flags, AT_SYMLINK_FOLLOW);
  186. break;
  187. case 'P':
  188. FIELD_CLR(link_flags, AT_SYMLINK_FOLLOW);
  189. break;
  190. case 'v':
  191. verbose = true;
  192. break;
  193. case '?':
  194. GETOPT_UNKNOWN_OPT
  195. usage();
  196. return 1;
  197. }
  198. }
  199. argc -= optind;
  200. argv += optind;
  201. char *target = argv[argc - 1];
  202. char dest[PATH_MAX] = "";
  203. if(argc <= 0)
  204. {
  205. fprintf(stderr, "ln: error: Not enough operands, %d given, expect >= 1\n", argc);
  206. return 1;
  207. }
  208. else if(argc == 1)
  209. {
  210. target = (char *)".";
  211. argc++;
  212. }
  213. else if(argc == 2)
  214. {
  215. int targetfd = open(target, open_target_flags);
  216. if(targetfd >= 0)
  217. {
  218. if(fstat(targetfd, &dest_stat) < 0)
  219. {
  220. fprintf(stderr,
  221. "ln: error: Failed getting status for target '%s': %s\n",
  222. dest,
  223. strerror(errno));
  224. return -1;
  225. }
  226. if(!S_ISDIR(dest_stat.st_mode))
  227. {
  228. int ret = do_link(argv[0], argv[1], targetfd);
  229. return ret < 0 ? 1 : 0;
  230. }
  231. if(close(targetfd) < 0)
  232. {
  233. fprintf(stderr,
  234. "ln: error: Failed closing target directory '%s': %s\n",
  235. target,
  236. strerror(errno));
  237. return -1;
  238. }
  239. }
  240. else
  241. {
  242. int ret = do_link(argv[0], argv[1], -1);
  243. return ret < 0 ? 1 : 0;
  244. }
  245. }
  246. for(int i = 0; i < argc - 1; i++)
  247. {
  248. char *src = argv[i];
  249. char *src_basename = basename(src);
  250. if(snprintf(dest, PATH_MAX, "%s/%s", target, src_basename) < 0)
  251. {
  252. fprintf(stderr,
  253. "ln: error: Failed joining target '%s' and source basename '%s'\n",
  254. target,
  255. src_basename);
  256. return 1;
  257. }
  258. if(do_link(src, dest, -1) < 0) return 1;
  259. }
  260. return 0;
  261. }