logo

utils-std

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

install.c (7833B)


  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. assert(errno == 0);
  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. assert(errno == 0);
  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. assert(errno == 0);
  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. assert(errno == 0);
  113. int dest_fd = open(dest_path, O_CREAT | O_WRONLY | O_TRUNC, mode);
  114. if(dest_fd < 0)
  115. {
  116. fprintf(stderr,
  117. "%s: error: Failed create-opening file '%s' for writing: %s\n",
  118. argv0,
  119. dest_path,
  120. strerror(errno));
  121. return -1;
  122. }
  123. if(opt_v) fprintf(stderr, "%s: Made file: %s\n", argv0, dest);
  124. if(auto_file_copy(src_fd, dest_fd, src_stat.st_size, 0) < 0)
  125. {
  126. fprintf(stderr,
  127. "%s: error: Failed copying data from '%s' to '%s': %s\n",
  128. argv0,
  129. src,
  130. dest_path,
  131. strerror(errno));
  132. return -1;
  133. }
  134. assert(errno == 0);
  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. assert(errno == 0);
  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. assert(errno == 0);
  170. return 0;
  171. }
  172. static void
  173. usage(void)
  174. {
  175. fprintf(stderr, "\
  176. Usage: install [-CcDpTv] [-g group] [-m mode] [-o owner] source... destination\n\
  177. install [-CcDpTv] [-g group] [-m mode] [-o owner] -t destination source...\n\
  178. install -d [-cv] [-g group] [-m mode] [-o owner] directory...\n\
  179. ");
  180. }
  181. int
  182. main(int argc, char *argv[])
  183. {
  184. const char *errstr = NULL;
  185. bool create_parents = false;
  186. bool opt_T = false;
  187. char *dest = NULL;
  188. mkdir_parents_filemask = umask(0);
  189. umask(mkdir_parents_filemask);
  190. int c = -1;
  191. #ifdef HAS_GETOPT_LONG
  192. // Strictly for GNUisms compatibility so no long-only options
  193. // clang-format off
  194. static struct option opts[] = {
  195. {"compare", no_argument, 0, 'c'},
  196. {"directory", no_argument, 0, 'd'},
  197. {"group", required_argument, 0, 'g'},
  198. {"mode", required_argument, 0, 'm'},
  199. {"owner", required_argument, 0, 'o'},
  200. {"preserve-timestamps", no_argument, 0, 'p'},
  201. {"target-directory", required_argument, 0, 't'},
  202. {"no-target-directory", no_argument, 0, 'T'},
  203. {"verbose", no_argument, 0, 'v'},
  204. {0, 0, 0, 0},
  205. };
  206. // clang-format on
  207. // Need + as first character to get POSIX-style option parsing
  208. while((c = getopt_long(argc, argv, "+:CcDdpTt:g:m:o:v", opts, NULL)) != -1)
  209. #else
  210. while((c = getopt(argc, argv, ":CcDdpTt:g:m:o:v")) != -1)
  211. #endif
  212. {
  213. switch(c)
  214. {
  215. case 'C':
  216. // ignore, for compatibility
  217. break;
  218. case 'c':
  219. // ignore, modern default behavior
  220. break;
  221. case 'D':
  222. create_parents = true;
  223. break;
  224. case 'd':
  225. create_directories = true;
  226. break;
  227. case 'p':
  228. preserve_times = true;
  229. break;
  230. case 'g':
  231. if(parse_group(optarg, &group) != 0) return 1;
  232. break;
  233. case 'o':
  234. if(parse_group(optarg, &user) != 0) return 1;
  235. break;
  236. case 'T':
  237. opt_T = true;
  238. break;
  239. case 't':
  240. dest = optarg;
  241. break;
  242. case 'm':
  243. mode = new_mode(optarg, 0755, &errstr);
  244. if(errstr != NULL)
  245. {
  246. fprintf(stderr, "install: error: Failed parsing mode '%s': %s\n", optarg, errstr);
  247. return 1;
  248. }
  249. break;
  250. case 'v':
  251. opt_v = true;
  252. mkdir_parents_verbose = true;
  253. break;
  254. case ':':
  255. fprintf(stderr, "install: error: Missing operand for option: '-%c'\n", optopt);
  256. usage();
  257. return 1;
  258. case '?':
  259. fprintf(stderr, "install: error: Unknown option '-%c'\n", optopt);
  260. usage();
  261. return 1;
  262. }
  263. }
  264. assert(errno == 0);
  265. argc -= optind;
  266. argv += optind;
  267. if(create_directories)
  268. {
  269. for(int i = 0; i < argc; i++)
  270. {
  271. char *dest = argv[i];
  272. if(mkdir_parents(dest, mode) != 0) return -1;
  273. if(opt_v) fprintf(stderr, "%s: Made directory: %s\n", argv0, dest);
  274. if(user != (uid_t)-1 || group != (gid_t)-1)
  275. {
  276. if(chown(dest, user, group) < 0)
  277. {
  278. fprintf(stderr,
  279. "%s: error: Failed changing ownership of '%s': %s\n",
  280. argv0,
  281. dest,
  282. strerror(errno));
  283. return -1;
  284. }
  285. }
  286. }
  287. return 0;
  288. }
  289. if(dest == NULL) dest = argv[--argc];
  290. bool multi_src = argc > 1;
  291. if(opt_T)
  292. {
  293. if(argc != 1)
  294. {
  295. fprintf(stderr,
  296. "%s: error: Option -T passed, expected exactly 1 source operand, got %d\n",
  297. argv0,
  298. argc);
  299. return 1;
  300. }
  301. assert(!multi_src);
  302. }
  303. else if(argc < 1)
  304. {
  305. fprintf(stderr, "%s: error: Expected at least 1 source operand\n", argv0);
  306. return 1;
  307. }
  308. if(create_parents)
  309. {
  310. char *destdir = dest;
  311. // Same as in mkdir_parents
  312. mode_t parent_mode = (S_IWUSR | S_IXUSR | ~mkdir_parents_filemask) & 0777;
  313. if(!multi_src)
  314. {
  315. char path_dup[PATH_MAX] = "";
  316. strncpy(path_dup, dest, PATH_MAX);
  317. destdir = dirname(path_dup);
  318. }
  319. if(mkdir_parents(destdir, parent_mode) != 0) return 1;
  320. }
  321. for(int i = 0; i < argc; i++)
  322. {
  323. char *src = argv[i];
  324. if(do_install(src, dest, multi_src) < 0) return 1;
  325. }
  326. return 0;
  327. }