]> gitweb.michael.orlitzky.com - apply-default-acl.git/commitdiff
Replace nftw() with manual recursion in apply_default_acl().
authorMichael Orlitzky <michael@orlitzky.com>
Fri, 2 Mar 2018 20:19:13 +0000 (15:19 -0500)
committerMichael Orlitzky <michael@orlitzky.com>
Fri, 2 Mar 2018 20:22:35 +0000 (15:22 -0500)
The nftw() tree walk worked well for a while; in particular, before we
handled symlinks safely, it was empirically faster than a hand-written
recursive descent. But recently, the very slow safe_open() function
was being called on the path that was passed to apply_default_acl(),
and nftw() fed that function a whole bunch of paths.

The apply_default_acl() function now takes a third "recusive"
parameter, and implements the recursion on its own. This lets us pass
down the old child file descriptor as the new parent file descriptor,
and avoid calling safe_open() more than once when we're operating
recursively. The result is a big speed improvement with --recursive,
tested for example on the Linux kernel source tree.

The hand-written recursion also allows us to fix a lingering exit code
bug. Now --recursive acts as if all of the targets were passed (in the
right order) on the command-line.

The new parameter affects the public API, so in the next release the
library will get a new version. The upside to this is that now it's
easy for other programs to operate recursively, simply by passing
"true" as the third parameter to apply_default_acl().

src/apply-default-acl.c
src/libadacl.c
src/libadacl.h

index d83e428756cadbe4d94d666bdee739e6a7c46a76..68de557b1511fbe90e117bab1175194d3b6069ea 100644 (file)
@@ -5,13 +5,8 @@
  *
  */
 
-/* On Linux, ftw.h needs this special voodoo to work. */
-#define _XOPEN_SOURCE 500
-#define _GNU_SOURCE
-
 #include <errno.h>   /* EINVAL */
 #include <fcntl.h>   /* AT_FOO constants */
-#include <ftw.h>     /* nftw() et al. */
 #include <getopt.h>  /* getopt_long() */
 #include <stdbool.h> /* the "bool" type */
 #include <stdio.h>   /* perror() */
@@ -24,8 +19,6 @@
  * else for big ones. */
 #define EXIT_ERROR 2
 
-#define NFTW_ERROR -1
-
 
 /**
  * @brief Determine whether or not the given path is accessible.
@@ -86,124 +79,6 @@ void usage(const char* program_name) {
 }
 
 
-/**
- * @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 (target == NULL) {
-    errno = EINVAL;
-    perror("apply_default_acl_nftw (args)");
-    return ACL_ERROR;
-  }
-
-
-  /* The apply_default_acl() function could make use of the stat
-     struct pointer sp, but for safety we choose to stat the result of
-     safe_open() ourselves. */
-  if (apply_default_acl(target, 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 (target == NULL) {
-    errno = EINVAL;
-    perror("apply_default_acl_nftw_x (args)");
-    return ACL_ERROR;
-  }
-
-  /* The apply_default_acl() function could make use of the stat
-     struct pointer sp, but for safety we choose to stat the result of
-     safe_open() ourselves. */
-  if (apply_default_acl(target, 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) {
-  if (target == NULL) {
-    errno = EINVAL;
-    perror("apply_default_acl_recursive (args)");
-    return ACL_ERROR;
-  }
-
-  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;
-}
-
 
 
 /**
@@ -254,6 +129,7 @@ int main(int argc, char* argv[]) {
   int result = EXIT_SUCCESS;
 
   int arg_index = 1;
+  int reapp_result = ACL_SUCCESS;
   for (arg_index = optind; arg_index < argc; arg_index++) {
     const char* target = argv[arg_index];
 
@@ -268,9 +144,7 @@ int main(int argc, char* argv[]) {
       continue;
     }
 
-    int (*f)(const char *, bool) = recursive ? apply_default_acl_recursive
-                                             : apply_default_acl;
-    int reapp_result = f(target, no_exec_mask);
+    reapp_result = apply_default_acl(target, no_exec_mask, recursive);
 
     if (result == EXIT_SUCCESS && reapp_result == ACL_FAILURE) {
       /* We don't want to turn an error into a (less-severe) failure. */
index e83f46be630bc40c779f9792786fa2c82e9ff944..38e362e5066fdeb4f086ba19774fd65faae78c99 100644 (file)
@@ -8,6 +8,7 @@
 /* Enables get_current_dir_name() in unistd.h and the O_PATH flag. */
 #define _GNU_SOURCE
 
+#include <dirent.h>     /* readdir(), etc. */
 #include <errno.h>      /* EINVAL, ELOOP, ENOTDIR, etc. */
 #include <fcntl.h>      /* openat() */
 #include <libgen.h>     /* basename(), dirname() */
@@ -622,35 +623,42 @@ int has_default_acl_fd(int fd) {
 }
 
 
+
 /**
- * @brief Apply parent default ACL to a path.
+ * @brief The recursive portion of @c apply_default_acl.
  *
- * This overwrites any existing ACLs on @c path.
+ * The @c apply_default_acl function takes a path, but then opens file
+ * descriptors for the path and its parent. Afterwards, everything is
+ * done using file descriptors, including the recursive application on
+ * the path's children. This function encapsulates the portion of @c
+ * apply_default_acl that uses only file descriptors; for the
+ * recursion, this function ultimately calls itself.
  *
- * @param path
- *   The path whose ACL we would like to reset to its default.
+ * This overwrites any existing ACLs on @c fd and, if @c recursive is
+ * @c true, its children. When @c recursive is @c true, the "worst"
+ * result encountered is returned as the overall result.
+ *
+ * @param parent_fd
+ *   A file descriptor for the parent directory of @c fd.
+ *
+ * @param fd
+ *   The file descriptor that should inherit its parent's default ACL.
  *
  * @param no_exec_mask
  *   The value (either true or false) of the --no-exec-mask flag.
  *
+ * @param recursive
+ *   Should we recurse into subdirectories?
+ *
  * @return
- *   - @c ACL_SUCCESS - The parent default ACL was inherited successfully.
+ *   - @c ACL_SUCCESS - The parent default ACLs were inherited successfully.
  *   - @c ACL_FAILURE - If symlinks or hard links are encountered.
  *   - @c ACL_ERROR - Unexpected library error.
  */
-int apply_default_acl(const char* path, bool no_exec_mask) {
-
-  if (path == NULL) {
-    errno = EINVAL;
-    perror("apply_default_acl (args)");
-    return ACL_ERROR;
-  }
-
-  /* Define these next three variables here because we may have to
-   * jump to the cleanup routine which expects them to exist.
-   */
-
-  /* Our return value. */
+int apply_default_acl_fds(int parent_fd,
+                          int fd,
+                          bool no_exec_mask,
+                          bool recursive) {
   int result = ACL_SUCCESS;
 
   /* The new ACL for this path */
@@ -660,71 +668,6 @@ int apply_default_acl(const char* path, bool no_exec_mask) {
      order to mask the execute bit. */
   acl_t new_acl_unmasked = (acl_t)NULL;
 
-  /* The file descriptor corresponding to "path" */
-  int fd = 0;
-
-  /* The file descriptor for the directory containing "path" */
-  int parent_fd = 0;
-
-  /* dirname() and basename() mangle their arguments, so we need
-     to make copies of "path" before using them. */
-  char* dirname_path_copy = NULL;
-  char* basename_path_copy = NULL;
-
-  /* Get the parent directory of "path" with dirname(), which happens
-   * to murder its argument and necessitates a path_copy. */
-  dirname_path_copy = strdup(path);
-  if (dirname_path_copy == NULL) {
-    perror("apply_default_acl (strdup)");
-    return ACL_ERROR;
-  }
-  char* parent = dirname(dirname_path_copy);
-  parent_fd = safe_open(parent, O_DIRECTORY | O_NOFOLLOW);
-  if (parent_fd == OPEN_ERROR) {
-    if (errno == ELOOP || errno == ENOTDIR) {
-      /* We hit a symlink, either in the last path component (ELOOP)
-         or higher up (ENOTDIR). */
-      result = ACL_FAILURE;
-      goto cleanup;
-    }
-    else {
-      perror("apply_default_acl (open parent fd)");
-      result = ACL_ERROR;
-      goto cleanup;
-    }
-  }
-
-  /* Check to make sure the parent descriptor actually has a default
-     ACL. If it doesn't, then we can "succeed" immediately. */
-  if (has_default_acl_fd(parent_fd) == ACL_FAILURE) {
-    result = ACL_SUCCESS;
-    goto cleanup;
-  }
-
-  /* We already obtained the parent fd safely, so if we use the
-     basename of path here instead of the full thing, then we can get
-     away with using openat() and spare ourselves the slowness of
-     another safe_open(). */
-  basename_path_copy = strdup(path);
-  if (basename_path_copy == NULL) {
-    perror("apply_default_acl (strdup)");
-    return ACL_ERROR;
-  }
-  fd = openat(parent_fd, basename(basename_path_copy), O_NOFOLLOW);
-  if (fd == OPEN_ERROR) {
-    if (errno == ELOOP || errno == ENOTDIR) {
-      /* We hit a symlink, either in the last path component (ELOOP)
-         or higher up (ENOTDIR). */
-      result = ACL_FAILURE;
-      goto cleanup;
-    }
-    else {
-      perror("apply_default_acl (open fd)");
-      result = ACL_ERROR;
-      goto cleanup;
-    }
-  }
-
   /* Refuse to operate on hard links, which can be abused by an
    * attacker to trick us into changing the ACL on a file we didn't
    * intend to; namely the "target" of the hard link. There is TOCTOU
@@ -734,16 +677,33 @@ int apply_default_acl(const char* path, bool no_exec_mask) {
   */
   struct stat s;
   if (fstat(fd, &s) == STAT_ERROR) {
-    perror("apply_default_acl (fstat)");
+    perror("apply_default_acl_fds (fstat)");
+    /* We can't recurse without the stat struct for fd */
     goto cleanup;
   }
 
+
+  /* Check to make sure the parent descriptor actually has a default
+     ACL. If it doesn't, then we can "succeed" immediately, saving a
+     little work, particularly in any_can_execute(). Note that we
+     can't skip the fstat() above, because we need it in case we
+     recurse. */
+  if (has_default_acl_fd(parent_fd) == ACL_FAILURE) {
+    result = ACL_SUCCESS;
+    /* Just because this target can't inherit anything doesn't mean
+       that one of it's children can't. For example, if there's a
+       default on "c" in "a/b/c/d", then we don't want to skip all
+       children of "a"! */
+    goto recurse;
+  }
+
+
   if (!S_ISDIR(s.st_mode)) {
     /* If it's not a directory, make sure it's a regular,
        non-hard-linked file. */
     if (!S_ISREG(s.st_mode) || s.st_nlink != 1) {
       result = ACL_FAILURE;
-      goto cleanup;
+      goto cleanup; /* It's not a directory, so we can skip the recursion. */
     }
   }
 
@@ -759,7 +719,7 @@ int apply_default_acl(const char* path, bool no_exec_mask) {
     int ace_result = any_can_execute(fd,&s) || S_ISDIR(s.st_mode);
 
     if (ace_result == ACL_ERROR) {
-      perror("apply_default_acl (any_can_execute)");
+      perror("apply_default_acl_fds (any_can_execute)");
       result = ACL_ERROR;
       goto cleanup;
     }
@@ -773,7 +733,7 @@ int apply_default_acl(const char* path, bool no_exec_mask) {
                        ACL_TYPE_DEFAULT,
                        fd,
                        ACL_TYPE_DEFAULT) == ACL_ERROR) {
-      perror("apply_default_acl (acl_copy_xattr default)");
+      perror("apply_default_acl_fds (acl_copy_xattr default)");
       result = ACL_ERROR;
       goto cleanup;
     }
@@ -784,7 +744,7 @@ int apply_default_acl(const char* path, bool no_exec_mask) {
                      ACL_TYPE_DEFAULT,
                      fd,
                      ACL_TYPE_ACCESS) == ACL_ERROR) {
-    perror("apply_default_acl (acl_copy_xattr access)");
+    perror("apply_default_acl_fds (acl_copy_xattr access)");
     result = ACL_ERROR;
     goto cleanup;
   }
@@ -803,14 +763,15 @@ int apply_default_acl(const char* path, bool no_exec_mask) {
   /* Now we potentially need to mask the execute permissions in the
      ACL on fd; or maybe not. */
   if (allow_exec) {
-    goto cleanup;
+    /* Skip the mask code for this target, but don't skip its children! */
+    goto recurse;
   }
 
   /* OK, we need to mask some execute permissions. First obtain the
      current ACL... */
   new_acl = acl_get_fd(fd);
   if (new_acl == (acl_t)NULL) {
-    perror("apply_default_acl (acl_get_fd)");
+    perror("apply_default_acl_fds (acl_get_fd)");
     result = ACL_ERROR;
     goto cleanup;
   }
@@ -820,7 +781,7 @@ int apply_default_acl(const char* path, bool no_exec_mask) {
      looping over it no worky). */
   new_acl_unmasked = acl_dup(new_acl);
   if (new_acl_unmasked == (acl_t)NULL) {
-    perror("apply_default_acl (acl_dup)");
+    perror("apply_default_acl_fds (acl_dup)");
     result = ACL_ERROR;
     goto cleanup;
   }
@@ -832,7 +793,7 @@ int apply_default_acl(const char* path, bool no_exec_mask) {
     acl_tag_t tag = ACL_UNDEFINED_TAG;
 
     if (acl_get_tag_type(entry, &tag) == ACL_ERROR) {
-       perror("apply_default_acl (acl_get_tag_type)");
+       perror("apply_default_acl_fds (acl_get_tag_type)");
        result = ACL_ERROR;
        goto cleanup;
     }
@@ -841,7 +802,7 @@ int apply_default_acl(const char* path, bool no_exec_mask) {
     /* We've got an entry/tag from the default ACL. Get its permset. */
     acl_permset_t permset;
     if (acl_get_permset(entry, &permset) == ACL_ERROR) {
-      perror("apply_default_acl (acl_get_permset)");
+      perror("apply_default_acl_fds (acl_get_permset)");
       result = ACL_ERROR;
       goto cleanup;
     }
@@ -855,20 +816,20 @@ int apply_default_acl(const char* path, bool no_exec_mask) {
          minimal ACLs) or acl_other entries, so if execute should be
          masked, we have to do it manually. */
       if (acl_delete_perm(permset, ACL_EXECUTE) == ACL_ERROR) {
-        perror("apply_default_acl (acl_delete_perm)");
+        perror("apply_default_acl_fds (acl_delete_perm)");
         result = ACL_ERROR;
         goto cleanup;
       }
 
       if (acl_set_permset(entry, permset) == ACL_ERROR) {
-        perror("apply_default_acl (acl_set_permset)");
+        perror("apply_default_acl_fds (acl_set_permset)");
         result = ACL_ERROR;
         goto cleanup;
       }
     }
 
     if (acl_update_entry(new_acl, entry) == ACL_ERROR) {
-      perror("apply_default_acl (acl_update_entry)");
+      perror("apply_default_acl_fds (acl_update_entry)");
       result = ACL_ERROR;
       goto cleanup;
     }
@@ -879,30 +840,190 @@ int apply_default_acl(const char* path, bool no_exec_mask) {
   /* Catches the first acl_get_entry as well as the ones at the end of
      the loop. */
   if (ge_result == ACL_ERROR) {
-    perror("apply_default_acl (acl_get_entry)");
+    perror("apply_default_acl_fds (acl_get_entry)");
     result = ACL_ERROR;
     goto cleanup;
   }
 
   if (acl_set_fd(fd, new_acl) == ACL_ERROR) {
-    perror("apply_default_acl (acl_set_fd)");
+    perror("apply_default_acl_fds (acl_set_fd)");
     result = ACL_ERROR;
     goto cleanup;
   }
 
+ recurse:
+  if (recursive && S_ISDIR(s.st_mode)) {
+    /* Recurse into subdirectories. Don't call closedir() on d! It
+       closes the open file descriptor as well, and subsequent calls
+       to close() then throw errors. */
+    DIR* d = fdopendir(fd);
+    if (d == NULL) {
+      perror("apply_default_acl_fds (fdopendir)");
+      result = ACL_ERROR;
+      goto cleanup;
+    }
+
+    struct dirent* de;
+    int new_fd = 0;
+    while ((de = readdir(d)) != NULL) {
+      if (de->d_type != DT_DIR && de->d_type != DT_REG) {
+        /* Hit a symlink or whatever. */
+        result = ACL_FAILURE;
+        continue;
+      }
+      if (strcmp(de->d_name, ".") == 0) { continue; }
+      if (strcmp(de->d_name, "..") == 0) { continue; }
+
+      /* Be careful not to "return" out of this loop and leave the
+         new_fd open! */
+      new_fd = openat(fd, de->d_name, O_NOFOLLOW);
+      if (new_fd == OPEN_ERROR) {
+        if (errno == ELOOP || errno == ENOTDIR) {
+          /* We hit a symlink, either in the last path component (ELOOP)
+             or higher up (ENOTDIR). */
+          if (result == ACL_SUCCESS) {
+            /* Don't overwrite an error result with success/failure. */
+            result = ACL_FAILURE;
+          }
+          continue;
+        }
+        else {
+          perror("apply_default_acl_fds (openat)");
+          result = ACL_ERROR;
+          continue;
+        }
+      }
+      switch (apply_default_acl_fds(fd, new_fd, no_exec_mask, recursive)) {
+        /* Don't overwrite an error result with success/failure. */
+        case ACL_FAILURE:
+          if (result == ACL_SUCCESS) {
+            result = ACL_FAILURE;
+          }
+        case ACL_ERROR:
+          result = ACL_ERROR;
+        default:
+          if (close(new_fd) == CLOSE_ERROR) {
+            perror("apply_default_acl_fds (close)");
+            result = ACL_ERROR;
+          }
+      }
+    }
+  }
+
  cleanup:
-  free(dirname_path_copy);
-  free(basename_path_copy);
   acl_free(new_acl);
   acl_free(new_acl_unmasked);
+  return result;
+}
 
-  if (fd > 0 && close(fd) == CLOSE_ERROR) {
-    perror("apply_default_acl (close fd)");
-    result = ACL_ERROR;
+
+/**
+ * @brief Apply parent default ACL to a path and optionally its children.
+ *
+ * This overwrites any existing ACLs on the target, and, if @c
+ * recursive is @c true, its children. When @c recursive is @c true,
+ * the "worst" result encountered is returned as the overall result.
+ *
+ * @param path
+ *   The path whose ACL we would like to reset to its default.
+ *
+ * @param no_exec_mask
+ *   The value (either true or false) of the --no-exec-mask flag.
+ *
+ * @param recursive
+ *   Should we recurse into subdirectories?
+ *
+ * @return
+ *   - @c ACL_SUCCESS - The parent default ACLs were inherited successfully.
+ *   - @c ACL_FAILURE - If symlinks or hard links are encountered.
+ *   - @c ACL_ERROR - Unexpected library error.
+ */
+int apply_default_acl(const char* path, bool no_exec_mask, bool recursive) {
+
+  if (path == NULL) {
+    errno = EINVAL;
+    perror("apply_default_acl (args)");
+    return ACL_ERROR;
+  }
+
+  /* Define these next three variables here because we may have to
+   * jump to the cleanup routine which expects them to exist.
+   */
+
+  /* Our return value. */
+  int result = ACL_SUCCESS;
+
+  /* The file descriptor corresponding to "path" */
+  int fd = 0;
+
+  /* The file descriptor for the directory containing "path" */
+  int parent_fd = 0;
+
+  /* dirname() and basename() mangle their arguments, so we need
+     to make copies of "path" before using them. */
+  char* dirname_path_copy = NULL;
+  char* basename_path_copy = NULL;
+
+  /* Get the parent directory of "path" with dirname(), which happens
+   * to murder its argument and necessitates a path_copy. */
+  dirname_path_copy = strdup(path);
+  if (dirname_path_copy == NULL) {
+    perror("apply_default_acl (strdup)");
+    return ACL_ERROR;
+  }
+  char* parent = dirname(dirname_path_copy);
+  parent_fd = safe_open(parent, O_DIRECTORY | O_NOFOLLOW);
+  if (parent_fd == OPEN_ERROR) {
+    if (errno == ELOOP || errno == ENOTDIR) {
+      /* We hit a symlink, either in the last path component (ELOOP)
+         or higher up (ENOTDIR). */
+      result = ACL_FAILURE;
+      goto cleanup;
+    }
+    else {
+      perror("apply_default_acl (open parent fd)");
+      result = ACL_ERROR;
+      goto cleanup;
+    }
   }
+
+  /* We already obtained the parent fd safely, so if we use the
+     basename of path here instead of the full thing, then we can get
+     away with using openat() and spare ourselves the slowness of
+     another safe_open(). */
+  basename_path_copy = strdup(path);
+  if (basename_path_copy == NULL) {
+    perror("apply_default_acl (strdup)");
+    return ACL_ERROR;
+  }
+  fd = openat(parent_fd, basename(basename_path_copy), O_NOFOLLOW);
+  if (fd == OPEN_ERROR) {
+    if (errno == ELOOP || errno == ENOTDIR) {
+      /* We hit a symlink, either in the last path component (ELOOP)
+         or higher up (ENOTDIR). */
+      result = ACL_FAILURE;
+      goto cleanup;
+    }
+    else {
+      perror("apply_default_acl (open fd)");
+      result = ACL_ERROR;
+      goto cleanup;
+    }
+  }
+
+  result = apply_default_acl_fds(parent_fd, fd, no_exec_mask, recursive);
+
+ cleanup:
+  free(dirname_path_copy);
+  free(basename_path_copy);
+
   if (parent_fd > 0 && close(parent_fd) == CLOSE_ERROR) {
     perror("apply_default_acl (close parent_fd)");
     result = ACL_ERROR;
   }
+  if (fd > 0 && close(fd) == CLOSE_ERROR) {
+    perror("apply_default_acl (close fd)");
+    result = ACL_ERROR;
+  }
   return result;
 }
index 1a7f802727f11e7aa7e25ede8768d761f9d5a74e..b9f0e21e864bc966c544ff3a0ccb69f66ded1384 100644 (file)
@@ -15,4 +15,4 @@
 #define ACL_FAILURE 0
 #define ACL_SUCCESS 1
 
-int apply_default_acl(const char* path, bool no_exec_mask);
+int apply_default_acl(const char* path, bool no_exec_mask, bool recursive);