logo

utils-std

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

install.c (7902B)


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