logo

utils-std

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

chown.c (7051B)


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