logo

utils-std

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

ln.c (4497B)


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