logo

utils-std

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

install.c (7837B)


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