logo

utils-std

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

ln.c (4333B)


  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 <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. assert(errno == 0);
  26. if(opt_s)
  27. {
  28. if(symlinkat(src, AT_FDCWD, dest) == 0) return 0;
  29. if(errno != EEXIST)
  30. {
  31. fprintf(stderr, "ln: Failed creating symlink '%s': %s\n", dest, strerror(errno));
  32. return -1;
  33. }
  34. }
  35. else
  36. {
  37. if(linkat(AT_FDCWD, src, AT_FDCWD, dest, link_flags) == 0) return 0;
  38. if(errno != EEXIST)
  39. {
  40. fprintf(stderr,
  41. "ln: Failed creating hard link from '%s' to '%s': %s\n",
  42. src,
  43. dest,
  44. strerror(errno));
  45. return -1;
  46. }
  47. }
  48. // Fallback
  49. assert(errno == EEXIST);
  50. errno = 0;
  51. int dirfd = open(dest, open_dir_flags);
  52. if(dirfd < 0)
  53. {
  54. assert(errno != 0);
  55. // ENOTDIR: Found but not a directory
  56. // ELOOP: POSIX return code when O_NOFOLLOW encounters a symbolic link
  57. // EMLINK: Same as ELOOP but FreeBSD *sigh*
  58. if(errno == ENOTDIR || errno == ELOOP || errno == EMLINK)
  59. {
  60. if(!force)
  61. {
  62. fprintf(stderr, "ln: Error: Destination '%s' already exists\n", dest);
  63. return -1;
  64. }
  65. errno = 0;
  66. dirfd = AT_FDCWD;
  67. if(unlink(dest) < 0)
  68. {
  69. fprintf(stderr,
  70. "ln: Failed removing already existing destination '%s': %s\n",
  71. dest,
  72. strerror(errno));
  73. return -1;
  74. }
  75. }
  76. }
  77. if(errno != 0)
  78. {
  79. fprintf(
  80. stderr, "ln: Failed opening destination as directory '%s': %s\n", dest, strerror(errno));
  81. return -1;
  82. }
  83. if(opt_s)
  84. {
  85. if(symlinkat(src, dirfd, dest) == 0) goto cleanup;
  86. fprintf(stderr, "ln: 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: 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: 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 [-f] [-L|-P] source... target\n\
  113. ln -s [-f] reference... target\n\
  114. ");
  115. }
  116. int
  117. main(int argc, char *argv[])
  118. {
  119. int c = -1;
  120. while((c = getopt(argc, argv, ":fnsLP")) != -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 '?':
  140. fprintf(stderr, "ln: Unknown option '-%c'\n", optopt);
  141. usage();
  142. break;
  143. }
  144. }
  145. assert(errno == 0);
  146. argc -= optind;
  147. argv += optind;
  148. if(argc <= 1)
  149. {
  150. fprintf(stderr, "ln: Not enough operands, %d given, expect >= 2\n", argc);
  151. return 1;
  152. }
  153. else if(argc == 2)
  154. {
  155. struct stat dest_status;
  156. int ret_stat = fstatat(AT_FDCWD, argv[1], &dest_status, AT_SYMLINK_NOFOLLOW);
  157. if(argc == 2 && (errno == ENOENT || (ret_stat == 0 && !S_ISDIR(dest_status.st_mode))))
  158. {
  159. errno = 0;
  160. int ret = do_link(argv[0], argv[1]);
  161. return ret < 0 ? 1 : 0;
  162. }
  163. errno = 0;
  164. }
  165. char *dest = argv[argc - 1];
  166. char target[PATH_MAX] = "";
  167. for(int i = 0; i < argc - 1; i++)
  168. {
  169. char *src = argv[i];
  170. char *src_sep = strrchr(src, '/');
  171. char *src_basename = src_sep != NULL ? src_sep + 1 : src;
  172. if(snprintf(target, PATH_MAX, "%s/%s", dest, src_basename) < 0)
  173. {
  174. fprintf(stderr, "ln: Failed joining destination '%s' and target '%s'\n", dest, src);
  175. return 1;
  176. }
  177. if(do_link(src, target) < 0) return 1;
  178. }
  179. return 0;
  180. }