logo

utils-std

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

install.c (7354B)


  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. // For copy_file_range
  5. #define _GNU_SOURCE // musl, glibc
  6. #define _DEFAULT_SOURCE // FreeBSD
  7. #include "../lib/fs.h"
  8. #include "../lib/mkdir.h"
  9. #include "../lib/mode.h"
  10. #include "../lib/user_group_parse.h"
  11. #include <assert.h>
  12. #include <errno.h>
  13. #include <fcntl.h> // linkat, AT_SYMLINK_FOLLOW
  14. #include <libgen.h> // dirname
  15. #include <limits.h> // PATH_MAX
  16. #include <stdbool.h>
  17. #include <stdio.h> // fprintf
  18. #include <string.h> // strerror
  19. #include <sys/stat.h>
  20. #include <unistd.h> // getopt
  21. #ifdef HAS_GETOPT_LONG
  22. #include <getopt.h>
  23. #endif
  24. bool preserve_times = false, create_directories = false;
  25. // See lib/mkdir.c
  26. bool mkdir_parents_verbose = false;
  27. mode_t mkdir_parents_filemask;
  28. mode_t mode = 00755;
  29. uid_t user = (uid_t)-1;
  30. gid_t group = (gid_t)-1;
  31. const char *argv0 = "install";
  32. static int
  33. do_install(char *src, char *dest, bool is_dir)
  34. {
  35. assert(errno == 0);
  36. int src_fd = open(src, O_RDONLY);
  37. if(src_fd < 0)
  38. {
  39. fprintf(stderr, "%s: Failed opening file '%s' for reading: %s\n", argv0, src, strerror(errno));
  40. return -1;
  41. }
  42. struct stat src_stat;
  43. if(fstat(src_fd, &src_stat) < 0)
  44. {
  45. fprintf(
  46. stderr, "%s: Failed getting status for source '%s': %s\n", argv0, dest, strerror(errno));
  47. return -1;
  48. }
  49. if(!is_dir)
  50. {
  51. assert(errno == 0);
  52. struct stat dest_stat;
  53. if(stat(dest, &dest_stat) < 0)
  54. {
  55. if(errno != ENOENT)
  56. {
  57. fprintf(stderr,
  58. "%s: Failed getting status for destination '%s': %s\n",
  59. argv0,
  60. dest,
  61. strerror(errno));
  62. return -1;
  63. }
  64. else
  65. errno = 0;
  66. }
  67. else
  68. {
  69. if(S_ISDIR(dest_stat.st_mode))
  70. {
  71. is_dir = true;
  72. }
  73. else
  74. {
  75. if(unlink(dest) < 0)
  76. {
  77. fprintf(stderr,
  78. "%s: Failed removing existing file at destination '%s': %s\n",
  79. argv0,
  80. dest,
  81. strerror(errno));
  82. return -1;
  83. }
  84. }
  85. }
  86. }
  87. assert(errno == 0);
  88. char *dest_path = dest;
  89. if(is_dir)
  90. {
  91. char target[PATH_MAX] = "";
  92. char *src_basename = static_basename(src);
  93. if(snprintf(target, PATH_MAX, "%s/%s", dest, src_basename) < 0)
  94. {
  95. fprintf(stderr,
  96. "%s: Failed joining destination '%s' and source '%s'\n",
  97. argv0,
  98. dest,
  99. src_basename);
  100. return -1;
  101. }
  102. dest_path = target;
  103. }
  104. assert(errno == 0);
  105. int dest_fd = open(dest_path, O_CREAT | O_WRONLY | O_TRUNC, mode);
  106. if(dest_fd < 0)
  107. {
  108. fprintf(stderr,
  109. "%s: Failed create-opening file '%s' for writing: %s\n",
  110. argv0,
  111. dest_path,
  112. strerror(errno));
  113. return -1;
  114. }
  115. if(auto_file_copy(src_fd, dest_fd, src_stat.st_size, 0) < 0)
  116. {
  117. fprintf(stderr,
  118. "%s: Error: Failed copying data from '%s' to '%s': %s\n",
  119. argv0,
  120. src,
  121. dest_path,
  122. strerror(errno));
  123. return -1;
  124. }
  125. assert(errno == 0);
  126. if(user != (uid_t)-1 || group != (gid_t)-1)
  127. if(fchown(dest_fd, user, group) < 0)
  128. {
  129. fprintf(stderr,
  130. "%s: Error: Failed changing ownership of '%s': %s\n",
  131. argv0,
  132. dest_path,
  133. strerror(errno));
  134. return -1;
  135. }
  136. assert(errno == 0);
  137. if(preserve_times)
  138. {
  139. struct timespec src_times[2] = {src_stat.st_atim, src_stat.st_mtim};
  140. if(futimens(dest_fd, src_times) != 0)
  141. {
  142. fprintf(stderr,
  143. "%s: Error while setting access/modification times on '%s': %s\n",
  144. argv0,
  145. dest_path,
  146. strerror(errno));
  147. return -1;
  148. }
  149. }
  150. if(close(src_fd) < 0)
  151. {
  152. fprintf(stderr, "%s: Error closing '%s'\n", argv0, src);
  153. return -1;
  154. }
  155. if(close(dest_fd) < 0)
  156. {
  157. fprintf(stderr, "%s: Error closing '%s'\n", argv0, dest_path);
  158. return -1;
  159. }
  160. assert(errno == 0);
  161. return 0;
  162. }
  163. static void
  164. usage(void)
  165. {
  166. fprintf(stderr, "\
  167. Usage: install [-CcDpT] [-g group] [-m mode] [-o owner] source... destination\n\
  168. install [-CcDpT] [-g group] [-m mode] [-o owner] -t destination source...\n\
  169. install -d [-c] [-g group] [-m mode] [-o owner] directory...\n\
  170. ");
  171. }
  172. int
  173. main(int argc, char *argv[])
  174. {
  175. const char *errstr = NULL;
  176. bool create_parents = false;
  177. bool opt_T = false;
  178. char *dest = NULL;
  179. mkdir_parents_filemask = umask(0);
  180. umask(mkdir_parents_filemask);
  181. int c = -1;
  182. #ifdef HAS_GETOPT_LONG
  183. // Strictly for GNUisms compatibility so no long-only options
  184. // clang-format off
  185. static struct option opts[] = {
  186. {"compare", no_argument, 0, 'c'},
  187. {"directory", no_argument, 0, 'd'},
  188. {"group", required_argument, 0, 'g'},
  189. {"mode", required_argument, 0, 'm'},
  190. {"owner", required_argument, 0, 'o'},
  191. {"preserve-timestamps", no_argument, 0, 'p'},
  192. {"target-directory", required_argument, 0, 't'},
  193. {"no-target-directory", no_argument, 0, 'T'},
  194. {0, 0, 0, 0},
  195. };
  196. // clang-format on
  197. // Need + as first character to get POSIX-style option parsing
  198. while((c = getopt_long(argc, argv, "+:CcDdpTt:g:m:o:", opts, NULL)) != -1)
  199. #else
  200. while((c = getopt(argc, argv, ":CcDdpTt:g:m:o:")) != -1)
  201. #endif
  202. {
  203. switch(c)
  204. {
  205. case 'C':
  206. // ignore, for compatibility
  207. break;
  208. case 'c':
  209. // ignore, modern default behavior
  210. break;
  211. case 'D':
  212. create_parents = true;
  213. break;
  214. case 'd':
  215. create_directories = true;
  216. break;
  217. case 'p':
  218. preserve_times = true;
  219. break;
  220. case 'g':
  221. if(parse_group(optarg, &group) != 0) return 1;
  222. break;
  223. case 'o':
  224. if(parse_group(optarg, &user) != 0) return 1;
  225. break;
  226. case 'T':
  227. opt_T = true;
  228. break;
  229. case 't':
  230. dest = optarg;
  231. break;
  232. case 'm':
  233. mode = new_mode(optarg, 0755, &errstr);
  234. if(errstr != NULL)
  235. {
  236. fprintf(stderr, "install: Failed parsing mode '%s': %s\n", optarg, errstr);
  237. return 1;
  238. }
  239. break;
  240. case ':':
  241. fprintf(stderr, "install: Missing operand for option: '-%c'\n", optopt);
  242. usage();
  243. return 1;
  244. case '?':
  245. fprintf(stderr, "install: Unknown option '-%c'\n", optopt);
  246. usage();
  247. return 1;
  248. }
  249. }
  250. assert(errno == 0);
  251. argc -= optind;
  252. argv += optind;
  253. if(create_directories)
  254. {
  255. for(int i = 0; i < argc; i++)
  256. {
  257. char *dest = argv[i];
  258. if(mkdir_parents(dest, mode) != 0) return -1;
  259. if(user != (uid_t)-1 || group != (gid_t)-1)
  260. {
  261. if(chown(dest, user, group) < 0)
  262. {
  263. fprintf(stderr,
  264. "%s: Error: Failed changing ownership of '%s': %s\n",
  265. argv0,
  266. dest,
  267. strerror(errno));
  268. return -1;
  269. }
  270. }
  271. }
  272. return 0;
  273. }
  274. if(dest == NULL) dest = argv[--argc];
  275. bool multi_src = argc > 1;
  276. if(opt_T)
  277. {
  278. if(argc != 1)
  279. {
  280. fprintf(
  281. stderr, "%s: Option -T passed, expected exactly 1 source operand, got %d\n", argv0, argc);
  282. return 1;
  283. }
  284. assert(!multi_src);
  285. }
  286. else if(argc < 1)
  287. {
  288. fprintf(stderr, "%s: Expected at least 1 source operand\n", argv0);
  289. return 1;
  290. }
  291. if(create_parents)
  292. {
  293. char *destdir = dest;
  294. // Same as in mkdir_parents
  295. mode_t parent_mode = (S_IWUSR | S_IXUSR | ~mkdir_parents_filemask) & 0777;
  296. if(!multi_src)
  297. {
  298. char path_dup[PATH_MAX] = "";
  299. strncpy(path_dup, dest, PATH_MAX);
  300. destdir = dirname(path_dup);
  301. }
  302. if(mkdir_parents(destdir, parent_mode) != 0) return 1;
  303. }
  304. for(int i = 0; i < argc; i++)
  305. {
  306. char *src = argv[i];
  307. if(do_install(src, dest, multi_src) < 0) return 1;
  308. }
  309. return 0;
  310. }