logo

utils-std

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

ln.c (4639B)


  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. #include "../lib/bitmasks.h"
  6. #include "../lib/getopt_nolong.h"
  7. // NetBSD (9.3 and 10) hides symlink behind _XOPEN_SOURCE / _NETBSD_SOURCE
  8. #ifdef __NetBSD__
  9. #define _XOPEN_SOURCE 700
  10. #endif
  11. #include <errno.h>
  12. #include <fcntl.h> // linkat, AT_SYMLINK_FOLLOW
  13. #include <libgen.h> // basename
  14. #include <limits.h> // PATH_MAX
  15. #include <stdbool.h>
  16. #include <stdio.h> // fprintf
  17. #include <string.h> // strerror
  18. #include <sys/stat.h>
  19. #include <unistd.h> // getopt, symlink, link
  20. const char *argv0 = "ln";
  21. static bool opt_s = false, force = false;
  22. static int link_flags = 0;
  23. static int open_dir_flags = O_RDONLY | O_DIRECTORY;
  24. static int
  25. do_link(char *src, char *dest)
  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. errno = 0;
  50. int dirfd = open(dest, open_dir_flags);
  51. if(dirfd < 0)
  52. {
  53. // ENOTDIR: Found but not a directory
  54. // ELOOP: POSIX return code when O_NOFOLLOW encounters a symbolic link
  55. // EMLINK: Same as ELOOP but FreeBSD *sigh*
  56. if(errno == ENOTDIR || errno == ELOOP || errno == EMLINK)
  57. {
  58. if(!force)
  59. {
  60. fprintf(stderr, "ln: error: Destination '%s' already exists\n", dest);
  61. return -1;
  62. }
  63. errno = 0;
  64. dirfd = AT_FDCWD;
  65. if(unlink(dest) < 0)
  66. {
  67. fprintf(stderr,
  68. "ln: error: Failed removing already existing destination '%s': %s\n",
  69. dest,
  70. strerror(errno));
  71. return -1;
  72. }
  73. }
  74. else
  75. {
  76. fprintf(stderr,
  77. "ln: error: Failed opening destination as directory '%s': %s\n",
  78. dest,
  79. strerror(errno));
  80. return -1;
  81. }
  82. }
  83. if(opt_s)
  84. {
  85. if(symlinkat(src, dirfd, dest) == 0) goto cleanup;
  86. fprintf(stderr, "ln: error: Failed creating symlink '%s': %s\n", dest, strerror(errno));
  87. return -1;
  88. }
  89. else
  90. {
  91. if(linkat(AT_FDCWD, src, dirfd, dest, link_flags) == 0) goto cleanup;
  92. fprintf(stderr,
  93. "ln: error: Failed creating hard link from '%s' to '%s': %s\n",
  94. src,
  95. dest,
  96. strerror(errno));
  97. return -1;
  98. }
  99. cleanup:
  100. if(dirfd == AT_FDCWD) return 0;
  101. if(close(dirfd) != 0)
  102. {
  103. fprintf(stderr, "ln: error: Failed closing directory '%s': %s\n", dest, strerror(errno));
  104. return -1;
  105. }
  106. return 0;
  107. }
  108. static void
  109. usage(void)
  110. {
  111. fprintf(stderr, "\
  112. Usage: ln [-fv] [-L|-P] source... target\n\
  113. ln -s [-fv] reference... target\n\
  114. ");
  115. }
  116. int
  117. main(int argc, char *argv[])
  118. {
  119. bool verbose = false;
  120. for(int c = -1; (c = getopt_nolong(argc, argv, ":fnsLPv")) != -1;)
  121. {
  122. switch(c)
  123. {
  124. case 'f':
  125. force = true;
  126. break;
  127. case 'n':
  128. FIELD_SET(open_dir_flags, O_NOFOLLOW);
  129. break;
  130. case 's':
  131. opt_s = true;
  132. break;
  133. case 'L':
  134. FIELD_SET(link_flags, AT_SYMLINK_FOLLOW);
  135. break;
  136. case 'P':
  137. FIELD_CLR(link_flags, AT_SYMLINK_FOLLOW);
  138. break;
  139. case 'v':
  140. verbose = true;
  141. break;
  142. case '?':
  143. if(!got_long_opt) fprintf(stderr, "ln: error: Unknown option '-%c'\n", optopt);
  144. usage();
  145. break;
  146. }
  147. }
  148. argc -= optind;
  149. argv += optind;
  150. char *dest = argv[argc - 1];
  151. char target[PATH_MAX] = "";
  152. if(argc <= 0)
  153. {
  154. fprintf(stderr, "ln: error: Not enough operands, %d given, expect >= 1\n", argc);
  155. return 1;
  156. }
  157. else if(argc == 1)
  158. {
  159. dest = (char *)".";
  160. argc++;
  161. }
  162. else if(argc == 2)
  163. {
  164. errno = 0;
  165. struct stat dest_status;
  166. int ret_stat = fstatat(AT_FDCWD, argv[1], &dest_status, AT_SYMLINK_NOFOLLOW);
  167. if(
  168. // clang-format off
  169. argc == 2 && (
  170. (ret_stat != 0 && errno == ENOENT) ||
  171. (ret_stat == 0 && !S_ISDIR(dest_status.st_mode))
  172. )
  173. // clang-format on
  174. )
  175. {
  176. errno = 0;
  177. int ret = do_link(argv[0], argv[1]);
  178. return ret < 0 ? 1 : 0;
  179. }
  180. }
  181. for(int i = 0; i < argc - 1; i++)
  182. {
  183. char *src = argv[i];
  184. char *src_basename = basename(src);
  185. if(snprintf(target, PATH_MAX, "%s/%s", dest, src_basename) < 0)
  186. {
  187. fprintf(stderr, "ln: error: Failed joining destination '%s' and target '%s'\n", dest, src);
  188. return 1;
  189. }
  190. if(do_link(src, target) < 0) return 1;
  191. if(verbose) printf("'%s' -> '%s'\n", src, dest);
  192. }
  193. return 0;
  194. }