logo

utils-std

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

chown.c (8067B)


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