]> gitweb.michael.orlitzky.com - apply-default-acl.git/blob - src/apply-default-acl.c
Eliminate the apply_default_acl_ex() function.
[apply-default-acl.git] / src / apply-default-acl.c
1 /**
2 * @file apply-default-acl.c
3 *
4 * @brief The command-line interface.
5 *
6 */
7
8 /* On Linux, ftw.h needs this special voodoo to work. */
9 #define _XOPEN_SOURCE 500
10 #define _GNU_SOURCE
11
12 #include <errno.h> /* EINVAL */
13 #include <fcntl.h> /* AT_FOO constants */
14 #include <ftw.h> /* nftw() et al. */
15 #include <getopt.h> /* getopt_long() */
16 #include <stdbool.h> /* the "bool" type */
17 #include <stdio.h> /* perror() */
18 #include <stdlib.h> /* EXIT_FAILURE, EXIT_SUCCESS */
19 #include <unistd.h> /* faccessat() */
20
21 #include "libadacl.h"
22
23 /* We exit with EXIT_FAILURE for small errors, but we need something
24 * else for big ones. */
25 #define EXIT_ERROR 2
26
27 #define NFTW_ERROR -1
28
29
30 /**
31 * @brief Determine whether or not the given path is accessible.
32 *
33 * @param path
34 * The path to test.
35 *
36 * @return true if @c path is accessible to the current effective
37 * user/group, false otherwise.
38 */
39 bool path_accessible(const char* path) {
40 if (path == NULL) {
41 return false;
42 }
43
44 /* Test for access using the effective user and group rather than
45 the real one. */
46 int flags = AT_EACCESS;
47
48 /* Don't follow symlinks when checking for a path's existence,
49 since we won't follow them to set its ACLs either. */
50 flags |= AT_SYMLINK_NOFOLLOW;
51
52 /* If the path is relative, interpret it relative to the current
53 working directory (just like the access() system call). */
54 if (faccessat(AT_FDCWD, path, F_OK, flags) == 0) {
55 return true;
56 }
57 else {
58 return false;
59 }
60 }
61
62
63 /**
64 * @brief Display program usage information.
65 *
66 * @param program_name
67 * The program name to use in the output.
68 *
69 */
70 void usage(const char* program_name) {
71 if (program_name == NULL) {
72 /* ??? */
73 return;
74 }
75
76 printf("Apply any applicable default ACLs to the given files or "
77 "directories.\n\n");
78 printf("Usage: %s [flags] <target1> [<target2> [ <target3>...]]\n\n",
79 program_name);
80 printf("Flags:\n");
81 printf(" -h, --help Print this help message\n");
82 printf(" -r, --recursive Act on any given directories recursively\n");
83 printf(" -x, --no-exec-mask Apply execute permissions unconditionally\n");
84
85 return;
86 }
87
88
89 /**
90 * @brief Wrapper around @c apply_default_acl() for use with @c nftw().
91 *
92 * For parameter information, see the @c nftw man page.
93 *
94 * @return If the ACL was applied to @c target successfully, we return
95 * @c FTW_CONTINUE to signal to @ nftw() that we should proceed onto
96 * the next file or directory. Otherwise, we return @c FTW_STOP to
97 * signal failure.
98 *
99 */
100 int apply_default_acl_nftw(const char *target,
101 const struct stat *sp,
102 int info,
103 struct FTW *ftw) {
104
105 if (target == NULL) {
106 errno = EINVAL;
107 perror("apply_default_acl_nftw (args)");
108 return ACL_ERROR;
109 }
110
111
112 /* The apply_default_acl() function could make use of the stat
113 struct pointer sp, but for safety we choose to stat the result of
114 safe_open() ourselves. */
115 if (apply_default_acl(target, false) == ACL_ERROR) {
116 /* I guess we do want to bail out for serious/unexpected errors? */
117 return ACL_ERROR;
118 }
119
120 /* We don't want to kill the tree walk because we it a symlink. */
121 return 0;
122 }
123
124
125
126 /**
127 * @brief Wrapper around @c apply_default_acl() for use with @c nftw().
128 *
129 * This is identical to @c apply_default_acl_nftw(), except it passes
130 * @c true to @c apply_default_acl() as its no_exec_mask argument.
131 *
132 */
133 int apply_default_acl_nftw_x(const char *target,
134 const struct stat *sp,
135 int info,
136 struct FTW *ftw) {
137
138 if (target == NULL) {
139 errno = EINVAL;
140 perror("apply_default_acl_nftw_x (args)");
141 return ACL_ERROR;
142 }
143
144 /* The apply_default_acl() function could make use of the stat
145 struct pointer sp, but for safety we choose to stat the result of
146 safe_open() ourselves. */
147 if (apply_default_acl(target, true) == ACL_ERROR) {
148 /* I guess we do want to bail out for serious/unexpected errors? */
149 return ACL_ERROR;
150 }
151
152 /* We don't want to kill the tree walk because we it a symlink. */
153 return 0;
154 }
155
156
157
158 /**
159 * @brief Recursive version of @c apply_default_acl().
160 *
161 * If @c target is a directory, we use @c nftw() to call @c
162 * apply_default_acl() recursively on all of its children. Otherwise,
163 * we just delegate to @c apply_default_acl().
164 *
165 * @param target
166 * The root (path) of the recursive application.
167 *
168 * @param no_exec_mask
169 * The value (either true or false) of the --no-exec-mask flag.
170 *
171 * @return
172 * If @c nftw() fails with a serious error (returns NFTW_ERROR),
173 * then we return @c ACL_ERROR. Otherwise, we return @c ACL_SUCCESS.
174 */
175 int apply_default_acl_recursive(const char *target, bool no_exec_mask) {
176 if (target == NULL) {
177 errno = EINVAL;
178 perror("apply_default_acl_recursive (args)");
179 return ACL_ERROR;
180 }
181
182 int max_levels = 256;
183 int flags = FTW_MOUNT | FTW_PHYS;
184
185 /* There are two separate functions that could be passed to
186 nftw(). One passes no_exec_mask = true to apply_default_acl(),
187 and the other passes no_exec_mask = false. Since the function we
188 pass to nftw() cannot have parameters, we have to create separate
189 options and make the decision here. */
190 int (*fn)(const char *, const struct stat *, int, struct FTW *) = NULL;
191 fn = no_exec_mask ? apply_default_acl_nftw_x : apply_default_acl_nftw;
192
193 int nftw_result = nftw(target, fn, max_levels, flags);
194
195 /* nftw will itself return NFTW_ERROR on errors like malloc failure,
196 and since the only non-success value that "fn" can return us
197 ACL_ERROR == NFTW_ERROR, this covers all error cases. */
198 if (nftw_result == NFTW_ERROR) {
199 perror("apply_default_acl_recursive (nftw)");
200 return ACL_ERROR;
201 }
202
203 /* Beware: nftw indicates success with 0, but ACL_SUCCESS != 0. */
204 return ACL_SUCCESS;
205 }
206
207
208
209 /**
210 * @brief Call apply_default_acl (possibly recursively) on each
211 * command-line argument.
212 *
213 * @return Either @c EXIT_FAILURE or @c EXIT_SUCCESS. If everything
214 * goes as expected, we return @c EXIT_SUCCESS. Otherwise, we return
215 * @c EXIT_FAILURE.
216 */
217 int main(int argc, char* argv[]) {
218
219 if (argc < 2) {
220 usage(argv[0]);
221 return EXIT_FAILURE;
222 }
223
224 bool recursive = false;
225 bool no_exec_mask = false;
226
227 struct option long_options[] = {
228 /* These options set a flag. */
229 {"help", no_argument, NULL, 'h'},
230 {"recursive", no_argument, NULL, 'r'},
231 {"no-exec-mask", no_argument, NULL, 'x'},
232 {NULL, 0, NULL, 0}
233 };
234
235 int opt = 0;
236
237 while ((opt = getopt_long(argc, argv, "hrx", long_options, NULL)) != -1) {
238 switch (opt) {
239 case 'h':
240 usage(argv[0]);
241 return EXIT_SUCCESS;
242 case 'r':
243 recursive = true;
244 break;
245 case 'x':
246 no_exec_mask = true;
247 break;
248 default:
249 usage(argv[0]);
250 return EXIT_FAILURE;
251 }
252 }
253
254 int result = EXIT_SUCCESS;
255
256 int arg_index = 1;
257 for (arg_index = optind; arg_index < argc; arg_index++) {
258 const char* target = argv[arg_index];
259
260 /* Make sure we can access the given path before we go out of our
261 * way to please it. Doing this check outside of
262 * apply_default_acl() lets us spit out a better error message for
263 * typos, too.
264 */
265 if (!path_accessible(target)) {
266 perror(target);
267 result = EXIT_FAILURE;
268 continue;
269 }
270
271 int (*f)(const char *, bool) = recursive ? apply_default_acl_recursive
272 : apply_default_acl;
273 int reapp_result = f(target, no_exec_mask);
274
275 if (result == EXIT_SUCCESS && reapp_result == ACL_FAILURE) {
276 /* We don't want to turn an error into a (less-severe) failure. */
277 result = EXIT_FAILURE;
278 }
279 if (reapp_result == ACL_ERROR) {
280 /* Turn both success and failure into an error, if we encounter one. */
281 result = EXIT_ERROR;
282 }
283 }
284
285 return result;
286 }