/** * @file apply-default-acl.c * * @brief The command-line interface. * */ /* On Linux, ftw.h needs this special voodoo to work. */ #define _XOPEN_SOURCE 500 #define _GNU_SOURCE #include /* AT_FOO constants */ #include /* nftw() et al. */ #include /* getopt_long() */ #include /* the "bool" type */ #include /* perror() */ #include /* EXIT_FAILURE, EXIT_SUCCESS */ #include /* faccessat() */ #include "libadacl.h" /* We exit with EXIT_FAILURE for small errors, but we need something * else for big ones. */ #define EXIT_ERROR 2 #define NFTW_ERROR -1 /** * @brief Determine whether or not the given path is accessible. * * @param path * The path to test. * * @return true if @c path is accessible to the current effective * user/group, false otherwise. */ bool path_accessible(const char* path) { if (path == NULL) { return false; } /* Test for access using the effective user and group rather than the real one. */ int flags = AT_EACCESS; /* Don't follow symlinks when checking for a path's existence, since we won't follow them to set its ACLs either. */ flags |= AT_SYMLINK_NOFOLLOW; /* If the path is relative, interpret it relative to the current working directory (just like the access() system call). */ if (faccessat(AT_FDCWD, path, F_OK, flags) == 0) { return true; } else { return false; } } /** * @brief Display program usage information. * * @param program_name * The program name to use in the output. * */ void usage(const char* program_name) { printf("Apply any applicable default ACLs to the given files or " "directories.\n\n"); printf("Usage: %s [flags] [ [ ...]]\n\n", program_name); printf("Flags:\n"); printf(" -h, --help Print this help message\n"); printf(" -r, --recursive Act on any given directories recursively\n"); printf(" -x, --no-exec-mask Apply execute permissions unconditionally\n"); return; } /** * @brief Wrapper around @c apply_default_acl() for use with @c nftw(). * * For parameter information, see the @c nftw man page. * * @return If the ACL was applied to @c target successfully, we return * @c FTW_CONTINUE to signal to @ nftw() that we should proceed onto * the next file or directory. Otherwise, we return @c FTW_STOP to * signal failure. * */ int apply_default_acl_nftw(const char *target, const struct stat *sp, int info, struct FTW *ftw) { if (apply_default_acl_ex(target, sp, false) == ACL_ERROR) { /* I guess we do want to bail out for serious/unexpected errors? */ return ACL_ERROR; } /* We don't want to kill the tree walk because we it a symlink. */ return 0; } /** * @brief Wrapper around @c apply_default_acl() for use with @c nftw(). * * This is identical to @c apply_default_acl_nftw(), except it passes * @c true to @c apply_default_acl() as its no_exec_mask argument. * */ int apply_default_acl_nftw_x(const char *target, const struct stat *sp, int info, struct FTW *ftw) { if (apply_default_acl_ex(target, sp, true) == ACL_ERROR) { /* I guess we do want to bail out for serious/unexpected errors? */ return ACL_ERROR; } /* We don't want to kill the tree walk because we it a symlink. */ return 0; } /** * @brief Recursive version of @c apply_default_acl(). * * If @c target is a directory, we use @c nftw() to call @c * apply_default_acl() recursively on all of its children. Otherwise, * we just delegate to @c apply_default_acl(). * * @param target * The root (path) of the recursive application. * * @param no_exec_mask * The value (either true or false) of the --no-exec-mask flag. * * @return * If @c nftw() fails with a serious error (returns NFTW_ERROR), * then we return @c ACL_ERROR. Otherwise, we return @c ACL_SUCCESS. */ int apply_default_acl_recursive(const char *target, bool no_exec_mask) { int max_levels = 256; int flags = FTW_MOUNT | FTW_PHYS; /* There are two separate functions that could be passed to nftw(). One passes no_exec_mask = true to apply_default_acl(), and the other passes no_exec_mask = false. Since the function we pass to nftw() cannot have parameters, we have to create separate options and make the decision here. */ int (*fn)(const char *, const struct stat *, int, struct FTW *) = NULL; fn = no_exec_mask ? apply_default_acl_nftw_x : apply_default_acl_nftw; int nftw_result = nftw(target, fn, max_levels, flags); /* nftw will itself return NFTW_ERROR on errors like malloc failure, and since the only non-success value that "fn" can return us ACL_ERROR == NFTW_ERROR, this covers all error cases. */ if (nftw_result == NFTW_ERROR) { perror("apply_default_acl_recursive (nftw)"); return ACL_ERROR; } /* Beware: nftw indicates success with 0, but ACL_SUCCESS != 0. */ return ACL_SUCCESS; } /** * @brief Call apply_default_acl (possibly recursively) on each * command-line argument. * * @return Either @c EXIT_FAILURE or @c EXIT_SUCCESS. If everything * goes as expected, we return @c EXIT_SUCCESS. Otherwise, we return * @c EXIT_FAILURE. */ int main(int argc, char* argv[]) { if (argc < 2) { usage(argv[0]); return EXIT_FAILURE; } bool recursive = false; bool no_exec_mask = false; struct option long_options[] = { /* These options set a flag. */ {"help", no_argument, NULL, 'h'}, {"recursive", no_argument, NULL, 'r'}, {"no-exec-mask", no_argument, NULL, 'x'}, {NULL, 0, NULL, 0} }; int opt = 0; while ((opt = getopt_long(argc, argv, "hrx", long_options, NULL)) != -1) { switch (opt) { case 'h': usage(argv[0]); return EXIT_SUCCESS; case 'r': recursive = true; break; case 'x': no_exec_mask = true; break; default: usage(argv[0]); return EXIT_FAILURE; } } int result = EXIT_SUCCESS; int arg_index = 1; for (arg_index = optind; arg_index < argc; arg_index++) { const char* target = argv[arg_index]; /* Make sure we can access the given path before we go out of our * way to please it. Doing this check outside of * apply_default_acl() lets us spit out a better error message for * typos, too. */ if (!path_accessible(target)) { fprintf(stderr, "%s: %s: No such file or directory\n", argv[0], target); result = EXIT_FAILURE; continue; } int (*f)(const char *, bool) = recursive ? apply_default_acl_recursive : apply_default_acl; int reapp_result = f(target, no_exec_mask); if (result == EXIT_SUCCESS && reapp_result == ACL_FAILURE) { /* We don't want to turn an error into a (less-severe) failure. */ result = EXIT_FAILURE; } if (reapp_result == ACL_ERROR) { /* Turn both success and failure into an error, if we encounter one. */ result = EXIT_ERROR; } } return result; }