logo

utils-std

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

install.c (7658B)


  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/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/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. bool opt_v = false;
  33. static int
  34. do_install(char *src, char *dest, bool is_dir)
  35. {
  36. int src_fd = open(src, O_RDONLY);
  37. if(src_fd < 0)
  38. {
  39. fprintf(stderr,
  40. "%s: error: Failed opening file '%s' for reading: %s\n",
  41. argv0,
  42. src,
  43. strerror(errno));
  44. return -1;
  45. }
  46. struct stat src_stat;
  47. if(fstat(src_fd, &src_stat) < 0)
  48. {
  49. fprintf(stderr,
  50. "%s: error: Failed getting status for source '%s': %s\n",
  51. argv0,
  52. dest,
  53. strerror(errno));
  54. return -1;
  55. }
  56. if(!is_dir)
  57. {
  58. struct stat dest_stat;
  59. if(stat(dest, &dest_stat) < 0)
  60. {
  61. if(errno != ENOENT)
  62. {
  63. fprintf(stderr,
  64. "%s: error: Failed getting status for destination '%s': %s\n",
  65. argv0,
  66. dest,
  67. strerror(errno));
  68. return -1;
  69. }
  70. else
  71. errno = 0;
  72. }
  73. else
  74. {
  75. if(S_ISDIR(dest_stat.st_mode))
  76. {
  77. is_dir = true;
  78. }
  79. else
  80. {
  81. if(unlink(dest) < 0)
  82. {
  83. fprintf(stderr,
  84. "%s: error: Failed removing existing file at destination '%s': %s\n",
  85. argv0,
  86. dest,
  87. strerror(errno));
  88. return -1;
  89. }
  90. }
  91. }
  92. }
  93. char *dest_path = dest;
  94. if(is_dir)
  95. {
  96. static char target[PATH_MAX] = "";
  97. char *src_basename = static_basename(src);
  98. if(snprintf(target, PATH_MAX, "%s/%s", dest, src_basename) < 0)
  99. {
  100. fprintf(stderr,
  101. "%s: error: Failed joining destination '%s' and source '%s'\n",
  102. argv0,
  103. dest,
  104. src_basename);
  105. return -1;
  106. }
  107. dest_path = target;
  108. }
  109. int dest_fd = open(dest_path, O_CREAT | O_WRONLY | O_TRUNC, mode);
  110. if(dest_fd < 0)
  111. {
  112. fprintf(stderr,
  113. "%s: error: Failed create-opening file '%s' for writing: %s\n",
  114. argv0,
  115. dest_path,
  116. strerror(errno));
  117. return -1;
  118. }
  119. if(opt_v) fprintf(stderr, "%s: Made file: %s\n", argv0, dest);
  120. if(auto_file_copy(src_fd, dest_fd, src_stat.st_size, 0) < 0)
  121. {
  122. fprintf(stderr,
  123. "%s: error: Failed copying data from '%s' to '%s': %s\n",
  124. argv0,
  125. src,
  126. dest_path,
  127. strerror(errno));
  128. return -1;
  129. }
  130. if(user != (uid_t)-1 || group != (gid_t)-1)
  131. if(fchown(dest_fd, user, group) < 0)
  132. {
  133. fprintf(stderr,
  134. "%s: error: Failed changing ownership of '%s': %s\n",
  135. argv0,
  136. dest_path,
  137. strerror(errno));
  138. return -1;
  139. }
  140. if(preserve_times)
  141. {
  142. struct timespec src_times[2] = {src_stat.st_atim, src_stat.st_mtim};
  143. if(futimens(dest_fd, src_times) != 0)
  144. {
  145. fprintf(stderr,
  146. "%s: error: Failed setting access/modification times on '%s': %s\n",
  147. argv0,
  148. dest_path,
  149. strerror(errno));
  150. return -1;
  151. }
  152. }
  153. if(close(src_fd) < 0)
  154. {
  155. fprintf(stderr, "%s: error: Failed closing '%s'\n", argv0, src);
  156. return -1;
  157. }
  158. if(close(dest_fd) < 0)
  159. {
  160. fprintf(stderr, "%s: error: Failed closing '%s'\n", argv0, dest_path);
  161. return -1;
  162. }
  163. return 0;
  164. }
  165. static void
  166. usage(void)
  167. {
  168. fprintf(stderr, "\
  169. Usage: install [-CcDpTv] [-g group] [-m mode] [-o owner] source... destination\n\
  170. install [-CcDpTv] [-g group] [-m mode] [-o owner] -t destination source...\n\
  171. install -d [-cv] [-g group] [-m mode] [-o owner] directory...\n\
  172. ");
  173. }
  174. int
  175. main(int argc, char *argv[])
  176. {
  177. const char *errstr = NULL;
  178. bool create_parents = false;
  179. bool opt_T = false;
  180. char *dest = NULL;
  181. mkdir_parents_filemask = umask(0);
  182. umask(mkdir_parents_filemask);
  183. int c = -1;
  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. while((c = getopt_long(argc, argv, "+:CcDdpTt:g:m:o:v", opts, NULL)) != -1)
  202. #else
  203. while((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. }