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