logo

utils-std

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

chmod.c (9457B)


  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. #define _POSIX_C_SOURCE 200809L
  5. // NetBSD <10 hides fdopendir behind _NETBSD_SOURCE
  6. #if __NetBSD_Version__ < 1000000000
  7. #define _NETBSD_SOURCE
  8. #endif
  9. #include "../config.h" // HAS_*
  10. #include "../lib/mode.h"
  11. #include <dirent.h> // fdopendir, readdir, closedir
  12. #include <errno.h>
  13. #include <fcntl.h> // AT_FDCWD
  14. #include <limits.h> // PATH_MAX
  15. #include <stdbool.h>
  16. #include <stdio.h> // fprintf
  17. #include <string.h> // strerror
  18. #include <sys/stat.h> // chmod, fstatat, S_ISDIR
  19. #include <unistd.h> // getopt
  20. #ifdef HAS_GETOPT_LONG
  21. #include <getopt.h>
  22. #endif
  23. const char *argv0 = "chmod";
  24. bool opt_c = false, opt_v = false;
  25. static int
  26. do_fchmodat(int fd, char *mode_arg, char *name, char *acc_path, bool recursive)
  27. {
  28. struct stat stats;
  29. int err = 0;
  30. if(fstatat(fd, name, &stats, AT_SYMLINK_NOFOLLOW) != 0)
  31. {
  32. fprintf(stderr,
  33. "%s: error: Failed getting status for '%s': %s\n",
  34. argv0,
  35. acc_path,
  36. strerror(errno));
  37. errno = 0;
  38. return 1;
  39. }
  40. const char *errstr = NULL;
  41. mode_t mode = new_mode(mode_arg, stats.st_mode, &errstr);
  42. if(errstr != NULL)
  43. {
  44. fprintf(stderr, "%s: error: Failed parsing mode '%s': %s\n", argv0, mode_arg, errstr);
  45. return 1;
  46. }
  47. char mode_from[11] = "";
  48. symbolize_mode(stats.st_mode, mode_from);
  49. if(mode != stats.st_mode)
  50. {
  51. if(fchmodat(fd, name, mode, 0) != 0)
  52. {
  53. fprintf(stderr,
  54. "%s: error: Failed setting permissions to 0%04o for '%s': %s\n",
  55. argv0,
  56. mode,
  57. acc_path,
  58. strerror(errno));
  59. errno = 0;
  60. return 1;
  61. }
  62. if(opt_c || opt_v)
  63. {
  64. char mode_to[11] = "";
  65. symbolize_mode(mode, mode_to);
  66. printf("%s: Permissions changed from 0%04o/%s to 0%04o/%s for '%s'\n",
  67. argv0,
  68. stats.st_mode & 07777,
  69. mode_from,
  70. mode & 07777,
  71. mode_to,
  72. acc_path);
  73. }
  74. }
  75. else if(opt_v)
  76. printf("%s: Permissions already set to 0%04o/%s for '%s'\n",
  77. argv0,
  78. stats.st_mode & 07777,
  79. mode_from,
  80. acc_path);
  81. if(recursive && S_ISDIR(stats.st_mode))
  82. {
  83. int dir = openat(fd, name, O_RDONLY | O_DIRECTORY | O_CLOEXEC);
  84. if(dir == -1)
  85. {
  86. fprintf(stderr,
  87. "%s: error: Couldn't open '%s' as directory: %s\n",
  88. argv0,
  89. acc_path,
  90. strerror(errno));
  91. errno = 0;
  92. return 1;
  93. }
  94. DIR *dirp = fdopendir(dir);
  95. if(dirp == NULL)
  96. {
  97. fprintf(stderr,
  98. "%s: error: Couldn't get DIR entry for opened '%s': %s\n",
  99. argv0,
  100. acc_path,
  101. strerror(errno));
  102. errno = 0;
  103. return 1;
  104. }
  105. while(true)
  106. {
  107. struct dirent *dp = readdir(dirp);
  108. if(dp == NULL)
  109. {
  110. if(errno == 0) break;
  111. fprintf(stderr,
  112. "%s: error: Failed reading directory '%s': %s\n",
  113. argv0,
  114. acc_path,
  115. strerror(errno));
  116. closedir(dirp); // FIXME: unhandled error
  117. errno = 0;
  118. return 1;
  119. }
  120. if(strcmp(dp->d_name, ".") == 0) continue;
  121. if(strcmp(dp->d_name, "..") == 0) continue;
  122. char new_path[PATH_MAX] = "";
  123. if(snprintf(new_path, PATH_MAX, "%s/%s", acc_path, dp->d_name) < 0)
  124. {
  125. fprintf(
  126. stderr,
  127. "%s: error: Couldn't concatenate '%s' into parent '%s', skipping to next entry: %s\n",
  128. argv0,
  129. name,
  130. acc_path,
  131. strerror(errno));
  132. err++;
  133. errno = 0;
  134. continue;
  135. }
  136. // No depth counter for now, unlikely to be a problem
  137. int ret = do_fchmodat(dir, mode_arg, dp->d_name, new_path, true);
  138. if(ret != 0) return ret;
  139. }
  140. // fdopendir allocates memory for DIR, needs closedir
  141. if(closedir(dirp) != 0)
  142. {
  143. fprintf(stderr,
  144. "%s: error: Deallocating directory entry for '%s' failed: %s\n",
  145. argv0,
  146. acc_path,
  147. strerror(errno));
  148. errno = 0;
  149. return 1;
  150. }
  151. }
  152. return err;
  153. }
  154. static int
  155. copy_mode(int fd, mode_t mode, char *name, char *acc_path, bool recursive)
  156. {
  157. struct stat stats;
  158. int err = 0;
  159. if(fstatat(fd, name, &stats, AT_SYMLINK_NOFOLLOW) != 0)
  160. {
  161. fprintf(stderr,
  162. "%s: error: Failed getting status for '%s': %s\n",
  163. argv0,
  164. acc_path,
  165. strerror(errno));
  166. errno = 0;
  167. return 1;
  168. }
  169. char mode_from[11] = "";
  170. symbolize_mode(stats.st_mode, mode_from);
  171. if(mode != stats.st_mode)
  172. {
  173. if(fchmodat(fd, name, mode, 0) != 0)
  174. {
  175. fprintf(stderr,
  176. "%s: error: Failed setting permissions to 0%04o for '%s': %s\n",
  177. argv0,
  178. mode,
  179. acc_path,
  180. strerror(errno));
  181. errno = 0;
  182. return 1;
  183. }
  184. if(opt_c || opt_v)
  185. {
  186. char mode_to[11] = "";
  187. symbolize_mode(mode, mode_to);
  188. printf("%s: Permissions changed from 0%04o/%s to 0%04o/%s for '%s'\n",
  189. argv0,
  190. stats.st_mode & 07777,
  191. mode_from,
  192. mode & 07777,
  193. mode_to,
  194. acc_path);
  195. }
  196. }
  197. else if(opt_v)
  198. printf("%s: Permissions already set to 0%04o/%s for '%s'\n",
  199. argv0,
  200. stats.st_mode & 07777,
  201. mode_from,
  202. acc_path);
  203. if(recursive && S_ISDIR(stats.st_mode))
  204. {
  205. int dir = openat(fd, name, O_RDONLY | O_DIRECTORY | O_CLOEXEC);
  206. if(dir == -1)
  207. {
  208. fprintf(stderr,
  209. "%s: error: Couldn't open '%s' as directory: %s\n",
  210. argv0,
  211. acc_path,
  212. strerror(errno));
  213. errno = 0;
  214. return 1;
  215. }
  216. DIR *dirp = fdopendir(dir);
  217. if(dirp == NULL)
  218. {
  219. fprintf(stderr,
  220. "%s: error: Couldn't get DIR entry for opened '%s': %s\n",
  221. argv0,
  222. acc_path,
  223. strerror(errno));
  224. errno = 0;
  225. return 1;
  226. }
  227. while(true)
  228. {
  229. struct dirent *dp = readdir(dirp);
  230. if(dp == NULL)
  231. {
  232. if(errno == 0) break;
  233. fprintf(stderr,
  234. "%s: error: Failed reading directory '%s': %s\n",
  235. argv0,
  236. acc_path,
  237. strerror(errno));
  238. closedir(dirp); // FIXME: unhandled error
  239. errno = 0;
  240. return 1;
  241. }
  242. if(strcmp(dp->d_name, ".") == 0) continue;
  243. if(strcmp(dp->d_name, "..") == 0) continue;
  244. char new_path[PATH_MAX] = "";
  245. if(snprintf(new_path, PATH_MAX, "%s/%s", acc_path, dp->d_name) < 0)
  246. {
  247. fprintf(
  248. stderr,
  249. "%s: error: Couldn't concatenate '%s' into parent '%s', skipping to next entry: %s\n",
  250. argv0,
  251. name,
  252. acc_path,
  253. strerror(errno));
  254. err++;
  255. errno = 0;
  256. continue;
  257. }
  258. // No depth counter for now, unlikely to be a problem
  259. int ret = copy_mode(dir, mode, dp->d_name, new_path, true);
  260. if(ret != 0) return ret;
  261. }
  262. // fdopendir allocates memory for DIR, needs closedir
  263. if(closedir(dirp) != 0)
  264. {
  265. fprintf(stderr,
  266. "%s: error: Deallocating directory entry for '%s' failed: %s\n",
  267. argv0,
  268. acc_path,
  269. strerror(errno));
  270. errno = 0;
  271. return 1;
  272. }
  273. }
  274. return err;
  275. }
  276. static void
  277. usage(void)
  278. {
  279. fprintf(stderr, "\
  280. Usage: chmod [-cRv] <mode> <file...>\n\
  281. chmod [-cRv] -F <ref_file> <file...>\n");
  282. }
  283. int
  284. main(int argc, char *argv[])
  285. {
  286. bool opt_R = false;
  287. char *ref_file = NULL;
  288. #ifdef HAS_GETOPT_LONG
  289. // Strictly for GNUisms compatibility so no long-only options
  290. // clang-format off
  291. static struct option opts[] = {
  292. {"changes", no_argument, 0, 'c'},
  293. {"recursive", no_argument, 0, 'R'},
  294. {"reference", required_argument, 0, 'F'},
  295. {"verbose", no_argument, 0, 'v'},
  296. {0, 0, 0, 0},
  297. };
  298. // clang-format on
  299. #endif
  300. for(int c = -1;;)
  301. {
  302. if(optind >= argc || !argv[optind]) break;
  303. if(argv[optind][0] == '-' && strchr("rwxugoa", argv[optind][1]))
  304. {
  305. fprintf(stderr,
  306. "%s: warning: (portability) Pass -- before a mode with a leading dash(-) to "
  307. "separate it from options\n",
  308. argv0);
  309. break;
  310. }
  311. #ifdef HAS_GETOPT_LONG
  312. // Need + as first character to get POSIX-style option parsing
  313. c = getopt_long(argc, argv, "+:cF:Rv", opts, NULL);
  314. #else
  315. c = getopt(argc, argv, ":cF:Rv");
  316. #endif
  317. if(c == -1) break;
  318. switch(c)
  319. {
  320. case 'c': // GNU
  321. opt_c = true;
  322. break;
  323. case 'F': // GNU & NetBSD for --reference, utils-std for -F
  324. ref_file = optarg;
  325. break;
  326. case 'R': // POSIX
  327. opt_R = true;
  328. break;
  329. case 'v': // GNU
  330. opt_v = true;
  331. break;
  332. case ':':
  333. fprintf(stderr, "%s: error: Missing operand for option: '-%c'\n", argv0, optopt);
  334. usage();
  335. return 1;
  336. case '?': // GNU
  337. fprintf(stderr, "%s: error: Unrecognised option: '-%c'\n", argv0, optopt);
  338. usage();
  339. return 1;
  340. }
  341. }
  342. argc -= optind;
  343. argv += optind;
  344. if(ref_file != NULL)
  345. {
  346. if(argc < 1)
  347. {
  348. fprintf(stderr, "%s: error: Expected >=1 arguments, %d given\n", argv0, argc);
  349. usage();
  350. return 1;
  351. }
  352. struct stat ref_stat;
  353. if(stat(ref_file, &ref_stat) != 0)
  354. {
  355. fprintf(stderr,
  356. "%s: error: Failed to get status from reference file '%s': %s\n",
  357. argv0,
  358. ref_file,
  359. strerror(errno));
  360. return 1;
  361. }
  362. for(int i = 0; i < argc; i++)
  363. {
  364. int ret = copy_mode(AT_FDCWD, ref_stat.st_mode, argv[i], argv[i], opt_R);
  365. if(ret != 0) return ret;
  366. }
  367. return 0;
  368. }
  369. if(argc < 2)
  370. {
  371. fprintf(stderr, "%s: error: Expected >=2 arguments, %d given\n", argv0, argc);
  372. usage();
  373. return 1;
  374. }
  375. for(int i = 1; i < argc; i++)
  376. {
  377. int ret = do_fchmodat(AT_FDCWD, argv[0], argv[i], argv[i], opt_R);
  378. if(ret != 0) return ret;
  379. }
  380. return 0;
  381. }