logo

utils-std

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

chown.c (7185B)


  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/fs.h"
  10. #include "../lib/user_group_parse.h"
  11. #include <assert.h>
  12. #include <dirent.h> // fdopendir, readdir, closedir
  13. #include <errno.h>
  14. #include <fcntl.h> // AT_FDCWD, fchownat
  15. #include <limits.h> // PATH_MAX
  16. #include <stdbool.h>
  17. #include <stdio.h> // fprintf
  18. #include <stdlib.h> // abort, exit
  19. #include <string.h> // strerror, strcmp
  20. #include <sys/stat.h> // fstatat, S_ISDIR
  21. #include <unistd.h> // getopt
  22. #ifdef HAS_GETOPT_LONG
  23. #include <getopt.h>
  24. #endif
  25. const char *argv0 = NULL;
  26. static uid_t user = (uid_t)-1;
  27. static uid_t group = (uid_t)-1;
  28. static bool opt_v = false, opt_R = false;
  29. enum chown_follow_symlinks
  30. {
  31. CHOWN_FOLLOW_UNK_SYMLINKS,
  32. CHOWN_FOLLOW_ALL_SYMLINKS,
  33. CHOWN_FOLLOW_NO_SYMLINK,
  34. CHOWN_FOLLOW_ONE_SYMLINK,
  35. };
  36. static int
  37. do_fchownat(int fd, char *name, char *acc_path, enum chown_follow_symlinks follow_symlinks)
  38. {
  39. struct stat stats;
  40. int err = 0;
  41. int stat_opts = (follow_symlinks == CHOWN_FOLLOW_NO_SYMLINK) ? AT_SYMLINK_NOFOLLOW : 0;
  42. int chown_opts = (follow_symlinks == CHOWN_FOLLOW_NO_SYMLINK) ? AT_SYMLINK_NOFOLLOW : 0;
  43. assert(errno == 0);
  44. if(fstatat(fd, name, &stats, stat_opts) != 0)
  45. {
  46. fprintf(stderr,
  47. "%s: error: Failed getting status for '%s': %s\n",
  48. argv0,
  49. acc_path,
  50. strerror(errno));
  51. errno = 0;
  52. return 1;
  53. }
  54. bool change = false;
  55. if(user != (uid_t)-1 && stats.st_uid != user) change = true;
  56. if(group != (uid_t)-1 && stats.st_gid != group) change = true;
  57. if(change)
  58. {
  59. assert(errno == 0);
  60. if(fchownat(fd, name, user, group, chown_opts) != 0)
  61. {
  62. fprintf(stderr,
  63. "%s: error: Failed setting ownership to '%d:%d' for '%s': %s\n",
  64. argv0,
  65. user,
  66. group,
  67. acc_path,
  68. strerror(errno));
  69. errno = 0;
  70. return 1;
  71. }
  72. if(opt_v)
  73. printf("%s: Ownership changed from %d:%d to %d:%d for '%s'\n",
  74. argv0,
  75. stats.st_uid,
  76. stats.st_gid,
  77. user,
  78. group,
  79. acc_path);
  80. }
  81. else
  82. {
  83. if(opt_v)
  84. printf("%s: Ownership already set to %d:%d for '%s'\n",
  85. argv0,
  86. stats.st_uid,
  87. stats.st_gid,
  88. acc_path);
  89. }
  90. if(opt_R && S_ISDIR(stats.st_mode))
  91. {
  92. assert(errno == 0);
  93. int dir = openat(fd, name, O_RDONLY | O_DIRECTORY | O_CLOEXEC);
  94. if(dir == -1)
  95. {
  96. fprintf(stderr,
  97. "%s: error: Couldn't open '%s' as directory: %s\n",
  98. argv0,
  99. acc_path,
  100. strerror(errno));
  101. errno = 0;
  102. return 1;
  103. }
  104. assert(errno == 0);
  105. DIR *dirp = fdopendir(dir);
  106. if(dirp == NULL)
  107. {
  108. fprintf(stderr,
  109. "%s: error: Couldn't get DIR entry for opened '%s': %s\n",
  110. argv0,
  111. acc_path,
  112. strerror(errno));
  113. errno = 0;
  114. return 1;
  115. }
  116. while(true)
  117. {
  118. assert(errno == 0);
  119. struct dirent *dp = readdir(dirp);
  120. if(dp == NULL)
  121. {
  122. if(errno == 0) break;
  123. fprintf(stderr,
  124. "%s: error: Failed reading directory '%s': %s\n",
  125. argv0,
  126. acc_path,
  127. strerror(errno));
  128. closedir(dirp); // FIXME: unhandled error
  129. errno = 0;
  130. return 1;
  131. }
  132. if(strcmp(dp->d_name, ".") == 0) continue;
  133. if(strcmp(dp->d_name, "..") == 0) continue;
  134. assert(errno == 0);
  135. char new_path[PATH_MAX] = "";
  136. if(snprintf(new_path, PATH_MAX, "%s/%s", acc_path, dp->d_name) < 0)
  137. {
  138. fprintf(stderr,
  139. "%s: error: Couldn't concatenate '%s' into parent '%s', skipping to next entry: %s",
  140. argv0,
  141. name,
  142. acc_path,
  143. strerror(errno));
  144. err++;
  145. errno = 0;
  146. continue;
  147. }
  148. enum chown_follow_symlinks child_follow_symlinks =
  149. (follow_symlinks == CHOWN_FOLLOW_ONE_SYMLINK) ? CHOWN_FOLLOW_NO_SYMLINK : follow_symlinks;
  150. // No depth counter for now, unlikely to be a problem as symlinks aren't followed
  151. int ret = do_fchownat(dir, dp->d_name, new_path, child_follow_symlinks);
  152. if(ret != 0) return ret;
  153. }
  154. // fdopendir allocates memory for DIR, needs closedir
  155. assert(errno == 0);
  156. if(closedir(dirp) != 0)
  157. {
  158. fprintf(stderr,
  159. "%s: error: Deallocating directory entry for '%s' failed: %s\n",
  160. argv0,
  161. acc_path,
  162. strerror(errno));
  163. errno = 0;
  164. return 1;
  165. }
  166. }
  167. return err;
  168. }
  169. static void
  170. usage(void)
  171. {
  172. if(strcmp(argv0, "chown") == 0)
  173. fprintf(stderr, "Usage: %s [-h | -R [-H|-L|-P]] owner[:group] file...\n", argv0);
  174. else if(strcmp(argv0, "chgrp") == 0)
  175. fprintf(stderr, "Usage: %s [-h | -R [-H|-L|-P]] group file...\n", argv0);
  176. else
  177. {
  178. fprintf(
  179. stderr, "%s: error: Unknown utility '%s' expected either chown or chgrp\n", argv0, argv0);
  180. exit(1);
  181. }
  182. }
  183. int
  184. main(int argc, char *argv[])
  185. {
  186. argv0 = static_basename(argv[0]);
  187. enum chown_follow_symlinks follow_symlinks = CHOWN_FOLLOW_UNK_SYMLINKS;
  188. int c = -1;
  189. #ifdef HAS_GETOPT_LONG
  190. // Strictly for GNUisms compatibility so no long-only options
  191. // clang-format off
  192. static struct option opts[] = {
  193. {"no-dereference", no_argument, 0, 'h'},
  194. {"recursive", no_argument, 0, 'R'},
  195. {"verbose", no_argument, 0, 'v'},
  196. {0, 0, 0, 0},
  197. };
  198. // clang-format on
  199. // Need + as first character to get POSIX-style option parsing
  200. while((c = getopt_long(argc, argv, "+:hRHLPv", opts, NULL)) != -1)
  201. #else
  202. while((c = getopt(argc, argv, ":hRHLPv")) != -1)
  203. #endif
  204. {
  205. switch(c)
  206. {
  207. case 'h':
  208. follow_symlinks = CHOWN_FOLLOW_NO_SYMLINK;
  209. break;
  210. case 'R':
  211. opt_R = true;
  212. break;
  213. case 'H':
  214. follow_symlinks = CHOWN_FOLLOW_ONE_SYMLINK;
  215. break;
  216. case 'L':
  217. follow_symlinks = CHOWN_FOLLOW_ALL_SYMLINKS;
  218. break;
  219. case 'P':
  220. follow_symlinks = CHOWN_FOLLOW_NO_SYMLINK;
  221. break;
  222. case 'v':
  223. opt_v = true;
  224. break;
  225. case ':':
  226. fprintf(stderr, "%s: error: Missing operand for option: '-%c'\n", argv0, optopt);
  227. usage();
  228. return 1;
  229. case '?':
  230. fprintf(stderr, "%s: error: Unrecognised option: '-%c'\n", argv0, optopt);
  231. usage();
  232. return 1;
  233. default:
  234. abort();
  235. }
  236. }
  237. argc -= optind;
  238. argv += optind;
  239. if(follow_symlinks == CHOWN_FOLLOW_UNK_SYMLINKS)
  240. follow_symlinks = opt_R ? CHOWN_FOLLOW_NO_SYMLINK : CHOWN_FOLLOW_ALL_SYMLINKS;
  241. if(argc < 2)
  242. {
  243. fprintf(stderr, "%s: error: Expects >=2 arguments, %d given\n", argv0, argc);
  244. usage();
  245. return 1;
  246. }
  247. if(strcmp(argv0, "chown") == 0)
  248. {
  249. char *gname = strchr(argv[0], ':');
  250. if(gname != NULL)
  251. {
  252. gname[0] = 0;
  253. gname++;
  254. if(parse_group(gname, &group) < 0) return 1;
  255. }
  256. if(parse_user(argv[0], &user) < 0) return 1;
  257. }
  258. else if(strcmp(argv0, "chgrp") == 0)
  259. {
  260. if(parse_group(argv[0], &group) < 0) return 1;
  261. }
  262. else
  263. {
  264. fprintf(
  265. stderr, "%s: error: Unknown utility '%s' expected either chown or chgrp\n", argv0, argv0);
  266. return 1;
  267. }
  268. for(int i = 1; i < argc; i++)
  269. {
  270. int ret = do_fchownat(AT_FDCWD, argv[i], argv[i], follow_symlinks);
  271. if(ret != 0) return ret;
  272. }
  273. return 0;
  274. }