logo

utils-std

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

ln.c (5727B)


  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 "../lib/bitmasks.h"
  8. #include "../libutils/getopt_nolong.h"
  9. #include <errno.h>
  10. #include <fcntl.h> // linkat, AT_SYMLINK_FOLLOW
  11. #include <libgen.h> // basename
  12. #include <limits.h> // PATH_MAX
  13. #include <stdbool.h>
  14. #include <stdio.h> // fprintf
  15. #include <string.h> // strerror
  16. #include <sys/stat.h> // fstat
  17. #include <unistd.h> // getopt, symlink, link
  18. const char *argv0 = "ln";
  19. static bool opt_s = false, force = false;
  20. static int link_flags = 0;
  21. static int open_target_flags = O_RDONLY | O_PATH;
  22. static int open_dest_flags = O_RDONLY | O_PATH | O_NOFOLLOW;
  23. static struct stat dest_stat;
  24. static int
  25. do_link(char *src, char *dest, int destfd)
  26. {
  27. if(opt_s)
  28. {
  29. if(symlinkat(src, AT_FDCWD, dest) == 0) return 0;
  30. if(errno != EEXIST)
  31. {
  32. fprintf(stderr, "ln: error: Failed creating symlink '%s': %s\n", dest, strerror(errno));
  33. return -1;
  34. }
  35. }
  36. else
  37. {
  38. if(linkat(AT_FDCWD, src, AT_FDCWD, dest, link_flags) == 0) return 0;
  39. if(errno != EEXIST)
  40. {
  41. fprintf(stderr,
  42. "ln: error: Failed creating hard link from '%s' to '%s': %s\n",
  43. src,
  44. dest,
  45. strerror(errno));
  46. return -1;
  47. }
  48. }
  49. if(strcmp(src, dest) == 0)
  50. {
  51. fprintf(stderr, "ln: error: Path '%s' passed to both source and destination\n", src);
  52. return 1;
  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(stderr,
  104. "ln: error: Source '%s' and destination '%s' refer to the same file\n",
  105. src,
  106. dest);
  107. return -1;
  108. }
  109. if(unlink(dest) < 0)
  110. {
  111. fprintf(stderr, "ln: error: Failed unlinking destination '%s': %s\n", dest, strerror(errno));
  112. return -1;
  113. }
  114. }
  115. if(opt_s)
  116. {
  117. if(symlinkat(src, dirfd, dest) == 0) goto cleanup;
  118. fprintf(stderr, "ln: error: Failed creating symlink '%s': %s\n", dest, strerror(errno));
  119. return -1;
  120. }
  121. else
  122. {
  123. if(linkat(AT_FDCWD, src, dirfd, dest, link_flags) == 0) goto cleanup;
  124. fprintf(stderr,
  125. "ln: error: Failed creating hard link from '%s' to '%s': %s\n",
  126. src,
  127. dest,
  128. strerror(errno));
  129. return -1;
  130. }
  131. cleanup:
  132. if(destfd < 0) return 0;
  133. if(close(destfd) != 0)
  134. {
  135. fprintf(stderr,
  136. "ln: error: Failed closing destination directory '%s': %s\n",
  137. dest,
  138. strerror(errno));
  139. return -1;
  140. }
  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. bool verbose = false;
  155. for(int c = -1; (c = getopt_nolong(argc, argv, ":fnsLPv")) != -1;)
  156. {
  157. switch(c)
  158. {
  159. case 'f':
  160. force = true;
  161. break;
  162. case 'n':
  163. FIELD_SET(open_target_flags, O_NOFOLLOW);
  164. break;
  165. case 's':
  166. opt_s = true;
  167. break;
  168. case 'L':
  169. FIELD_SET(link_flags, AT_SYMLINK_FOLLOW);
  170. break;
  171. case 'P':
  172. FIELD_CLR(link_flags, AT_SYMLINK_FOLLOW);
  173. break;
  174. case 'v':
  175. verbose = true;
  176. break;
  177. case '?':
  178. GETOPT_UNKNOWN_OPT
  179. usage();
  180. return 1;
  181. }
  182. }
  183. argc -= optind;
  184. argv += optind;
  185. char *target = argv[argc - 1];
  186. char dest[PATH_MAX] = "";
  187. if(argc <= 0)
  188. {
  189. fprintf(stderr, "ln: error: Not enough operands, %d given, expect >= 1\n", argc);
  190. return 1;
  191. }
  192. else if(argc == 1)
  193. {
  194. target = (char *)".";
  195. argc++;
  196. }
  197. else if(argc == 2)
  198. {
  199. int targetfd = open(target, open_target_flags);
  200. if(targetfd >= 0)
  201. {
  202. if(fstat(targetfd, &dest_stat) < 0)
  203. {
  204. fprintf(stderr,
  205. "ln: error: Failed getting status for target '%s': %s\n",
  206. dest,
  207. strerror(errno));
  208. return -1;
  209. }
  210. if(!S_ISDIR(dest_stat.st_mode))
  211. {
  212. int ret = do_link(argv[0], argv[1], targetfd);
  213. return ret < 0 ? 1 : 0;
  214. }
  215. if(close(targetfd) < 0)
  216. {
  217. fprintf(stderr,
  218. "ln: error: Failed closing target directory '%s': %s\n",
  219. target,
  220. strerror(errno));
  221. return -1;
  222. }
  223. }
  224. else
  225. {
  226. int ret = do_link(argv[0], argv[1], -1);
  227. return ret < 0 ? 1 : 0;
  228. }
  229. }
  230. for(int i = 0; i < argc - 1; i++)
  231. {
  232. char *src = argv[i];
  233. char *src_basename = basename(src);
  234. if(snprintf(dest, PATH_MAX, "%s/%s", target, src_basename) < 0)
  235. {
  236. fprintf(stderr,
  237. "ln: error: Failed joining target '%s' and source basename '%s'\n",
  238. target,
  239. src_basename);
  240. return 1;
  241. }
  242. if(do_link(src, dest, -1) < 0) return 1;
  243. if(verbose) printf("'%s' -> '%s'\n", src, dest);
  244. }
  245. return 0;
  246. }