logo

utils-std

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

chown.c (7010B)


  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 <dirent.h> // fdopendir, readdir, closedir
  12. #include <errno.h>
  13. #include <fcntl.h> // AT_FDCWD, fchownat
  14. #include <limits.h> // PATH_MAX
  15. #include <stdbool.h>
  16. #include <stdio.h> // fprintf
  17. #include <stdlib.h> // abort, exit
  18. #include <string.h> // strerror, strcmp
  19. #include <sys/stat.h> // fstatat, S_ISDIR
  20. #include <unistd.h> // getopt
  21. #ifdef HAS_GETOPT_LONG
  22. #include <getopt.h>
  23. #endif
  24. const char *argv0 = NULL;
  25. static uid_t user = (uid_t)-1;
  26. static uid_t group = (uid_t)-1;
  27. static bool opt_v = false, opt_R = false;
  28. enum chown_follow_symlinks
  29. {
  30. CHOWN_FOLLOW_UNK_SYMLINKS,
  31. CHOWN_FOLLOW_ALL_SYMLINKS,
  32. CHOWN_FOLLOW_NO_SYMLINK,
  33. CHOWN_FOLLOW_ONE_SYMLINK,
  34. };
  35. static int
  36. do_fchownat(int fd, char *name, char *acc_path, enum chown_follow_symlinks follow_symlinks)
  37. {
  38. struct stat stats;
  39. int err = 0;
  40. int stat_opts = (follow_symlinks == CHOWN_FOLLOW_NO_SYMLINK) ? AT_SYMLINK_NOFOLLOW : 0;
  41. int chown_opts = (follow_symlinks == CHOWN_FOLLOW_NO_SYMLINK) ? AT_SYMLINK_NOFOLLOW : 0;
  42. if(fstatat(fd, name, &stats, stat_opts) != 0)
  43. {
  44. fprintf(stderr,
  45. "%s: error: Failed getting status for '%s': %s\n",
  46. argv0,
  47. acc_path,
  48. strerror(errno));
  49. errno = 0;
  50. return 1;
  51. }
  52. bool change = false;
  53. if(user != (uid_t)-1 && stats.st_uid != user) change = true;
  54. if(group != (uid_t)-1 && stats.st_gid != group) change = true;
  55. if(change)
  56. {
  57. if(fchownat(fd, name, user, group, chown_opts) != 0)
  58. {
  59. fprintf(stderr,
  60. "%s: error: Failed setting ownership to '%d:%d' for '%s': %s\n",
  61. argv0,
  62. user,
  63. group,
  64. acc_path,
  65. strerror(errno));
  66. errno = 0;
  67. return 1;
  68. }
  69. if(opt_v)
  70. printf("%s: Ownership changed from %d:%d to %d:%d for '%s'\n",
  71. argv0,
  72. stats.st_uid,
  73. stats.st_gid,
  74. user,
  75. group,
  76. acc_path);
  77. }
  78. else
  79. {
  80. if(opt_v)
  81. printf("%s: Ownership already set to %d:%d for '%s'\n",
  82. argv0,
  83. stats.st_uid,
  84. stats.st_gid,
  85. acc_path);
  86. }
  87. if(opt_R && S_ISDIR(stats.st_mode))
  88. {
  89. int dir = openat(fd, name, O_RDONLY | O_DIRECTORY | O_CLOEXEC);
  90. if(dir == -1)
  91. {
  92. fprintf(stderr,
  93. "%s: error: Couldn't open '%s' as directory: %s\n",
  94. argv0,
  95. acc_path,
  96. strerror(errno));
  97. errno = 0;
  98. return 1;
  99. }
  100. DIR *dirp = fdopendir(dir);
  101. if(dirp == NULL)
  102. {
  103. fprintf(stderr,
  104. "%s: error: Couldn't get DIR entry for opened '%s': %s\n",
  105. argv0,
  106. acc_path,
  107. strerror(errno));
  108. errno = 0;
  109. return 1;
  110. }
  111. while(true)
  112. {
  113. struct dirent *dp = readdir(dirp);
  114. if(dp == NULL)
  115. {
  116. if(errno == 0) break;
  117. fprintf(stderr,
  118. "%s: error: Failed reading directory '%s': %s\n",
  119. argv0,
  120. acc_path,
  121. strerror(errno));
  122. closedir(dirp); // FIXME: unhandled error
  123. errno = 0;
  124. return 1;
  125. }
  126. if(strcmp(dp->d_name, ".") == 0) continue;
  127. if(strcmp(dp->d_name, "..") == 0) continue;
  128. char new_path[PATH_MAX] = "";
  129. if(snprintf(new_path, PATH_MAX, "%s/%s", acc_path, dp->d_name) < 0)
  130. {
  131. fprintf(stderr,
  132. "%s: error: Couldn't concatenate '%s' into parent '%s', skipping to next entry: %s",
  133. argv0,
  134. name,
  135. acc_path,
  136. strerror(errno));
  137. err++;
  138. errno = 0;
  139. continue;
  140. }
  141. enum chown_follow_symlinks child_follow_symlinks =
  142. (follow_symlinks == CHOWN_FOLLOW_ONE_SYMLINK) ? CHOWN_FOLLOW_NO_SYMLINK : follow_symlinks;
  143. // No depth counter for now, unlikely to be a problem as symlinks aren't followed
  144. int ret = do_fchownat(dir, dp->d_name, new_path, child_follow_symlinks);
  145. if(ret != 0) return ret;
  146. }
  147. // fdopendir allocates memory for DIR, needs closedir
  148. if(closedir(dirp) != 0)
  149. {
  150. fprintf(stderr,
  151. "%s: error: Deallocating directory entry for '%s' failed: %s\n",
  152. argv0,
  153. acc_path,
  154. strerror(errno));
  155. errno = 0;
  156. return 1;
  157. }
  158. }
  159. return err;
  160. }
  161. static void
  162. usage(void)
  163. {
  164. if(strcmp(argv0, "chown") == 0)
  165. fprintf(stderr, "Usage: %s [-h | -R [-H|-L|-P]] owner[:group] file...\n", argv0);
  166. else if(strcmp(argv0, "chgrp") == 0)
  167. fprintf(stderr, "Usage: %s [-h | -R [-H|-L|-P]] group file...\n", argv0);
  168. else
  169. {
  170. fprintf(
  171. stderr, "%s: error: Unknown utility '%s' expected either chown or chgrp\n", argv0, argv0);
  172. exit(1);
  173. }
  174. }
  175. int
  176. main(int argc, char *argv[])
  177. {
  178. argv0 = static_basename(argv[0]);
  179. enum chown_follow_symlinks follow_symlinks = CHOWN_FOLLOW_UNK_SYMLINKS;
  180. int c = -1;
  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. while((c = getopt_long(argc, argv, "+:hRHLPv", opts, NULL)) != -1)
  193. #else
  194. while((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. }