logo

utils-std

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

ln.c (4518B)


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