logo

utils-std

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

chmod.c (5853B)


  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 "../lib/mode.h"
  10. #include <assert.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. assert(errno == 0);
  31. if(fstatat(fd, name, &stats, AT_SYMLINK_NOFOLLOW) != 0)
  32. {
  33. fprintf(stderr,
  34. "%s: error: Failed getting status for '%s': %s\n",
  35. argv0,
  36. acc_path,
  37. strerror(errno));
  38. errno = 0;
  39. return 1;
  40. }
  41. const char *errstr = NULL;
  42. mode_t mode = new_mode(mode_arg, stats.st_mode, &errstr);
  43. if(errstr != NULL)
  44. {
  45. fprintf(stderr, "%s: error: Failed parsing mode '%s': %s\n", argv0, mode_arg, errstr);
  46. return 1;
  47. }
  48. char mode_from[11] = "";
  49. symbolize_mode(stats.st_mode, mode_from);
  50. if(mode != stats.st_mode)
  51. {
  52. assert(errno == 0);
  53. if(fchmodat(fd, name, mode, 0) != 0)
  54. {
  55. fprintf(stderr,
  56. "%s: error: Failed setting permissions to 0%04o for '%s': %s\n",
  57. argv0,
  58. mode,
  59. acc_path,
  60. strerror(errno));
  61. errno = 0;
  62. return 1;
  63. }
  64. if(opt_c || opt_v)
  65. {
  66. char mode_to[11] = "";
  67. symbolize_mode(mode, mode_to);
  68. printf("%s: Permissions changed from 0%04o/%s to 0%04o/%s for '%s'\n",
  69. argv0,
  70. stats.st_mode & 07777,
  71. mode_from,
  72. mode & 07777,
  73. mode_to,
  74. acc_path);
  75. }
  76. }
  77. else if(opt_v)
  78. printf("%s: Permissions already set to 0%04o/%s for '%s'\n",
  79. argv0,
  80. stats.st_mode & 07777,
  81. mode_from,
  82. acc_path);
  83. if(recursive && S_ISDIR(stats.st_mode))
  84. {
  85. assert(errno == 0);
  86. int dir = openat(fd, name, O_RDONLY | O_DIRECTORY | O_CLOEXEC);
  87. if(dir == -1)
  88. {
  89. fprintf(stderr,
  90. "%s: error: Couldn't open '%s' as directory: %s\n",
  91. argv0,
  92. acc_path,
  93. strerror(errno));
  94. errno = 0;
  95. return 1;
  96. }
  97. assert(errno == 0);
  98. DIR *dirp = fdopendir(dir);
  99. if(dirp == NULL)
  100. {
  101. fprintf(stderr,
  102. "%s: error: Couldn't get DIR entry for opened '%s': %s\n",
  103. argv0,
  104. acc_path,
  105. strerror(errno));
  106. errno = 0;
  107. return 1;
  108. }
  109. while(true)
  110. {
  111. assert(errno == 0);
  112. struct dirent *dp = readdir(dirp);
  113. if(dp == NULL)
  114. {
  115. if(errno == 0) break;
  116. fprintf(stderr,
  117. "%s: error: Failed reading directory '%s': %s\n",
  118. argv0,
  119. acc_path,
  120. strerror(errno));
  121. closedir(dirp); // FIXME: unhandled error
  122. errno = 0;
  123. return 1;
  124. }
  125. if(strcmp(dp->d_name, ".") == 0) continue;
  126. if(strcmp(dp->d_name, "..") == 0) continue;
  127. assert(errno == 0);
  128. char new_path[PATH_MAX] = "";
  129. if(snprintf(new_path, PATH_MAX, "%s/%s", acc_path, dp->d_name) < 0)
  130. {
  131. fprintf(
  132. stderr,
  133. "%s: error: Couldn't concatenate '%s' into parent '%s', skipping to next entry: %s\n",
  134. argv0,
  135. name,
  136. acc_path,
  137. strerror(errno));
  138. err++;
  139. errno = 0;
  140. continue;
  141. }
  142. // No depth counter for now, unlikely to be a problem
  143. int ret = do_fchmodat(dir, mode_arg, dp->d_name, new_path, true);
  144. if(ret != 0) return ret;
  145. }
  146. // fdopendir allocates memory for DIR, needs closedir
  147. assert(errno == 0);
  148. if(closedir(dirp) != 0)
  149. {
  150. fprintf(stderr,
  151. "%s: error: Deallocating directory entry for '%s' failed: %s\n",
  152. argv0,
  153. acc_path,
  154. strerror(errno));
  155. errno = 0;
  156. return 1;
  157. }
  158. }
  159. return err;
  160. }
  161. static void
  162. usage(void)
  163. {
  164. fprintf(stderr, "Usage: chmod [-cRv] <mode> <file ...>\n");
  165. }
  166. int
  167. main(int argc, char *argv[])
  168. {
  169. bool opt_R = false;
  170. int c = -1;
  171. #ifdef HAS_GETOPT_LONG
  172. // Strictly for GNUisms compatibility so no long-only options
  173. // clang-format off
  174. static struct option opts[] = {
  175. {"changes", no_argument, 0, 'c'},
  176. {"recursive", no_argument, 0, 'R'},
  177. {"verbose", no_argument, 0, 'v'},
  178. {0, 0, 0, 0},
  179. };
  180. // clang-format on
  181. #endif
  182. while(true)
  183. {
  184. if(optind >= argc || !argv[optind]) break;
  185. if(argv[optind][0] == '-' && strchr("rwxugoa", argv[optind][1]))
  186. {
  187. fprintf(stderr,
  188. "%s: warning: (portability) Pass -- before a mode with a leading dash(-) to "
  189. "separate it from options\n",
  190. argv0);
  191. break;
  192. }
  193. #ifdef HAS_GETOPT_LONG
  194. // Need + as first character to get POSIX-style option parsing
  195. c = getopt_long(argc, argv, "+:cRv", opts, NULL);
  196. #else
  197. c = getopt(argc, argv, ":cRv");
  198. #endif
  199. if(c == -1) break;
  200. switch(c)
  201. {
  202. case 'c': // GNU
  203. opt_c = true;
  204. break;
  205. case 'R': // POSIX
  206. opt_R = true;
  207. break;
  208. case 'v': // GNU
  209. opt_v = true;
  210. break;
  211. case ':':
  212. fprintf(stderr, "%s: error: Missing operand for option: '-%c'\n", argv0, optopt);
  213. usage();
  214. return 1;
  215. case '?': // GNU
  216. fprintf(stderr, "%s: error: Unrecognised option: '-%c'\n", argv0, optopt);
  217. usage();
  218. return 1;
  219. }
  220. }
  221. argc -= optind;
  222. argv += optind;
  223. if(argc < 2)
  224. {
  225. fprintf(stderr, "%s: error: Expects >=2 arguments, %d given\n", argv0, argc);
  226. usage();
  227. return 1;
  228. }
  229. for(int i = 1; i < argc; i++)
  230. {
  231. int ret = do_fchmodat(AT_FDCWD, argv[0], argv[i], argv[i], opt_R);
  232. if(ret != 0) return ret;
  233. }
  234. return 0;
  235. }