logo

utils-std

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

chmod.c (5678B)


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