logo

utils-std

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

install.c (7699B)


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