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