logo

utils-std

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

chown.c (8351B)


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