logo

utils-std

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

ln.c (6220B)


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