]> git.ipfire.org Git - thirdparty/snapper.git/commitdiff
- handle ACL modifications outside XAttributes
authorOndrej Kozina <okozina@redhat.com>
Mon, 24 Feb 2014 12:48:46 +0000 (13:48 +0100)
committerOndrej Kozina <okozina@redhat.com>
Mon, 24 Feb 2014 13:17:53 +0000 (14:17 +0100)
14 files changed:
snapper/Acls.cc [new file with mode: 0644]
snapper/Acls.h [new file with mode: 0644]
snapper/Btrfs.cc
snapper/Compare.cc
snapper/Compare.h
snapper/File.cc
snapper/File.h
snapper/Makefile.am
snapper/XAttributes.cc
snapper/XAttributes.h
testsuite-real/Makefile.am
testsuite-real/run-all
testsuite-real/xattrs1.cc
testsuite-real/xattrs4.cc [new file with mode: 0644]

diff --git a/snapper/Acls.cc b/snapper/Acls.cc
new file mode 100644 (file)
index 0000000..83821cb
--- /dev/null
@@ -0,0 +1,147 @@
+/*
+ * Copyright (c) [2014] Red Hat, Inc.
+ *
+ * All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of version 2 of the GNU General Public License as published
+ * by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include "snapper/Acls.h"
+#include "snapper/AppUtil.h"
+#include "snapper/Exception.h"
+#include "snapper/Log.h"
+
+namespace snapper
+{
+    bool
+    is_acl_signature(const std::string& name)
+    {
+       for (std::vector<string>::const_iterator cit = _acl_signatures.begin(); cit != _acl_signatures.end(); cit++)
+       {
+           if (name == *cit)
+               return true;
+       }
+       return false;
+    }
+
+    Acls::Acls(const string& path)
+    : allowed_types(0x0), acl_access(NULL), acl_default(NULL)
+    {
+       struct stat buf;
+
+       int fd = ::open(path.c_str(), O_RDONLY | O_NOFOLLOW | O_NONBLOCK | O_NOATIME |
+                         O_CLOEXEC);
+       if (fd < 0)
+       {
+           if (errno == ELOOP)
+           {
+               y2deb("can't read ACLs from symlink '" << path << "' itself");
+               return;
+           }
+
+           if (stat(path.c_str(), &buf) < 0)
+           {
+               y2err("stat failed errno: " << errno << " (" << stringerror(errno) << ")");
+               throw AclException();
+           }
+       }
+       else
+       {
+           if (fstat(fd, &buf) < 0)
+           {
+               y2err("fstat failed errno: " << errno << " (" << stringerror(errno) << ")");
+               ::close(fd);
+               throw AclException();
+           }
+
+           acl_access = acl_get_fd(fd);
+           if (!acl_access)
+           {
+               y2err("acl_get_fd failed errno: " << errno << " (" << stringerror(errno) << ")");
+               ::close(fd);
+               throw AclException();
+           }
+
+           ::close(fd);
+           allowed_types = ACL_TYPE_ACCESS;
+       }
+
+       allowed_types |= (S_ISDIR(buf.st_mode)) ? ACL_TYPE_DEFAULT : 0x0;
+
+       // in case open failed for some reason
+       if (!(allowed_types & ACL_TYPE_ACCESS))
+       {
+           allowed_types |= ACL_TYPE_ACCESS;
+           acl_access = acl_get_file(path.c_str(), ACL_TYPE_ACCESS);
+           if (!acl_access)
+           {
+               y2err("acl_get_file failed errno: " << errno << " (" << stringerror(errno) << ")");
+               throw AclException();
+           }
+       }
+
+       // ACL_TYPE_DEFAULT can't be read from fd
+       if (allowed_types & ACL_TYPE_DEFAULT)
+       {
+           acl_default = acl_get_file(path.c_str(), ACL_TYPE_DEFAULT);
+           if (!acl_default)
+           {
+               y2err("acl_get_file failed errno: " << errno << " (" << stringerror(errno) << ")");
+               if (acl_free(acl_access))
+               {
+                   y2err("acl_free failed errno: " << errno << " (" << stringerror(errno) << ")");
+               }
+
+               throw AclException();
+           }
+       }
+    }
+
+
+    Acls::~Acls()
+    {
+       if (acl_access)
+           acl_free(acl_access);
+       if (acl_default)
+           acl_free(acl_default);
+    }
+
+
+    void
+    Acls::serializeTo(const string& path) const
+    {
+       if (empty())
+           return;
+
+       if (acl_set_file(path.c_str(), ACL_TYPE_ACCESS, acl_access))
+       {
+           y2err("acl_set_file failed errno: " << errno << " (" << stringerror(errno) << ")");
+           throw AclException();
+       }
+
+       if (get_acl_types() & ACL_TYPE_DEFAULT)
+       {
+           if (acl_set_file(path.c_str(), ACL_TYPE_DEFAULT, acl_default))
+           {
+               y2err("acl_set_file failed errno: " << errno << " (" << stringerror(errno) << ")");
+               throw AclException();
+           }
+       }
+    }
+}
diff --git a/snapper/Acls.h b/snapper/Acls.h
new file mode 100644 (file)
index 0000000..6aa5d7e
--- /dev/null
@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) [2014] Red Hat, Inc.
+ *
+ * All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of version 2 of the GNU General Public License as published
+ * by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#ifndef SNAPPER_ACLS_H
+#define SNAPPER_ACLS_H
+
+#include <string>
+#include <vector>
+#include <sys/acl.h>
+
+#include <boost/assign/list_of.hpp>
+
+#ifndef ENABLE_ACL_SIGNATURES
+#define ENABLE_ACL_SIGNATURES  ("system.posix_acl_access") \
+                               ("system.posix_acl_default") \
+                               ("trusted.SGI_ACL_FILE") \
+                               ("trusted.SGI_ACL_DEFAULT")
+#endif
+
+namespace snapper
+{
+    using std::string;
+
+    const std::vector<string> _acl_signatures = boost::assign::list_of ENABLE_ACL_SIGNATURES;
+
+    bool is_acl_signature(const string& name);
+
+    class Acls
+    {
+    public:
+
+       Acls(const string& path);
+       ~Acls();
+
+       acl_type_t get_acl_types() const { return allowed_types; }
+       bool empty() const { return allowed_types == 0x0; }
+       void serializeTo(const string& path) const;
+
+    private:
+
+       acl_type_t allowed_types;
+       acl_t acl_access;
+       acl_t acl_default;
+    };
+
+}
+#endif //SNAPPER_ACLS_H
index 39442ea573b50bc3420878cc9b23e3084cb097b8..7335615280d39bf3861b1864197a691872e33ce5 100644 (file)
@@ -47,6 +47,7 @@
 #include "snapper/Snapper.h"
 #include "snapper/SnapperTmpl.h"
 #include "snapper/SnapperDefines.h"
+#include "snapper/Acls.h"
 
 
 namespace snapper
@@ -450,10 +451,10 @@ namespace snapper
        if (status & CREATED) status = CREATED;
        if (status & DELETED) status = DELETED;
 
-       if (status & (CONTENT | PERMISSIONS | USER | GROUP | XATTRS))
+       if (status & (CONTENT | PERMISSIONS | USER | GROUP | XATTRS | ACL))
        {
            // TODO check for content sometimes not required
-           status &= ~(CONTENT | PERMISSIONS | USER | GROUP | XATTRS);
+           status &= ~(CONTENT | PERMISSIONS | USER | GROUP | XATTRS | ACL);
 
            string dirname = snapper::dirname(name);
            string basename = snapper::basename(name);
@@ -546,7 +547,7 @@ namespace snapper
        else
        {
            node->status &= ~(CREATED | DELETED);
-           node->status |= CONTENT | PERMISSIONS | USER | GROUP | XATTRS;
+           node->status |= CONTENT | PERMISSIONS | USER | GROUP | XATTRS | ACL;
        }
     }
 
@@ -679,7 +680,7 @@ namespace snapper
                else
                {
                    node->status &= ~(CREATED | DELETED);
-                   node->status |= CONTENT | PERMISSIONS | USER | GROUP | XATTRS;
+                   node->status |= CONTENT | PERMISSIONS | USER | GROUP | XATTRS | ACL;
                }
 
                merge(processor, &it->second, from, to, x);
@@ -697,7 +698,7 @@ namespace snapper
                else
                {
                    node->status &= ~(CREATED | DELETED);
-                   node->status |= CONTENT | PERMISSIONS | USER | GROUP | XATTRS;
+                   node->status |= CONTENT | PERMISSIONS | USER | GROUP | XATTRS | ACL;
                }
 
                merge(processor, &it->second, from, to, x);
@@ -854,6 +855,14 @@ namespace snapper
 
        tree_node* node = processor->files.insert(path);
        node->status |= XATTRS;
+
+       if (is_acl_signature(name))
+       {
+           #ifdef DEBUG_PROCESS
+               y2deb("adding acl flag, signature:'" << name << "'");
+           #endif
+           node->status |= ACL;
+       }
 #endif
 
        return 0;
@@ -872,6 +881,14 @@ namespace snapper
 
        tree_node* node = processor->files.insert(path);
        node->status |= XATTRS;
+
+       if (is_acl_signature(name))
+       {
+           #ifdef DEBUG_PROCESS
+               y2deb("adding acl flag, signature:'" << name << "'");
+           #endif
+           node->status |= ACL;
+       }
 #endif
 
        return 0;
index 9b62baabcac3214e2707c8d9a73ecdbf0da27ed3..fa1b771e30d3e03fd77dbcd966cabf07b484c909 100644 (file)
@@ -38,6 +38,7 @@
 #include "snapper/Compare.h"
 #include "snapper/Exception.h"
 #include "snapper/XAttributes.h"
+#include "snapper/Acls.h"
 
 
 namespace snapper
@@ -222,10 +223,7 @@ namespace snapper
 #ifdef ENABLE_XATTRS
        if (file1.xaSupported() && file2.xaSupported())
        {
-           if (!cmpFilesXattrs(file1, file2))
-           {
-               status |= XATTRS;
-           }
+           status |= cmpFilesXattrs(file1, file2);
        }
 #endif
 
@@ -467,19 +465,34 @@ namespace snapper
     }
 
 
-    bool
+    unsigned int
     cmpFilesXattrs(const SFile& file1, const SFile& file2)
     {
         try
         {
            XAttributes xa(file1);
            XAttributes xb(file2);
-           return xa == xb;
+
+           if (xa == xb)
+           {
+               return 0;
+           }
+           else
+           {
+               unsigned int status = XATTRS;
+
+               CompareAcls acl_a(xa);
+               CompareAcls acl_b(xb);
+
+               status |= (acl_a == acl_b) ? 0 : ACL;
+
+               return status;
+           }
         }
        catch (const XAttributesException& e)
         {
-            y2err("extended attributes compare failed");
-           return false;
+           y2err("extended attributes or ACL compare failed");
+           return (XATTRS | ACL);
        }
     }
 
index 2975280cf3700bc8b06b69219bdf7e29b1e985bd..0b56c85a1bb80bde504a7a60630e002201070e53 100644 (file)
@@ -48,7 +48,9 @@ namespace snapper
     void
     cmpDirs(const SDir& dir1, const SDir& dir2, cmpdirs_cb_t cb);
 
-    bool
+    /* Compares the two files extended attributes and ACLs.
+       Returns 0 or XATTRS or (XATTRS | ACL) */
+    unsigned int
     cmpFilesXattrs(const SFile&, const SFile&);
 
 }
index bc40ba01ff7b2975c102e0b3b4374a07db2e1a20..4e0fee5c2053da165965e4580bdf5ce76a3914f9 100644 (file)
@@ -40,6 +40,7 @@
 #include "snapper/Compare.h"
 #include "snapper/Exception.h"
 #include "snapper/XAttributes.h"
+#include "snapper/Acls.h"
 
 
 namespace snapper
@@ -540,6 +541,8 @@ namespace snapper
             XAModification xa_mod(xa_src, xa_dest);
             y2deb("xa_modmap(xa_dest) object: " << xa_mod);
 
+           xa_mod.filterOutAcls();
+
             xaCreated = xa_mod.getXaCreateNum();
             xaDeleted = xa_mod.getXaDeleteNum();
             xaReplaced = xa_mod.getXaReplaceNum();
@@ -556,6 +559,28 @@ namespace snapper
         return ret_val;
     }
 
+
+    bool
+    File::modifyAcls()
+    {
+       bool ret_val;
+
+       try
+       {
+           Acls acl(getAbsolutePath(LOC_PRE));
+           acl.serializeTo(getAbsolutePath(LOC_SYSTEM));
+
+           ret_val = true;
+       }
+       catch (const SnapperException& e)
+       {
+           ret_val = false;
+       }
+
+       return ret_val;
+    }
+
+
     XAUndoStatistic& operator+=(XAUndoStatistic &out, const XAUndoStatistic &src)
     {
         out.numCreate += src.numCreate;
@@ -628,6 +653,12 @@ namespace snapper
             if (!modifyXattributes())
                 error = true;
         }
+
+        if (getPreToPostStatus() & (ACL | TYPE | DELETED))
+       {
+           if (!modifyAcls())
+               error = true;
+       }
 #endif
 
        pre_to_system_status = (unsigned int) -1;
@@ -727,7 +758,8 @@ namespace snapper
        ret += status & PERMISSIONS ? "p" : ".";
        ret += status & USER ? "u" : ".";
        ret += status & GROUP ? "g" : ".";
-        ret += status & XATTRS ? "x" : ".";
+       ret += status & XATTRS ? "x" : ".";
+       ret += status & ACL ? "a" : ".";
 
        return ret;
     }
@@ -768,10 +800,16 @@ namespace snapper
        }
 
        if (str.length() >= 5)
-        {
-            if (str[4] == 'x')
-                ret |= XATTRS;
-        }
+       {
+           if (str[4] == 'x')
+               ret |= XATTRS;
+       }
+
+       if (str.length() >= 6)
+       {
+           if (str[5] == 'a')
+               ret |= ACL;
+       }
 
        return ret;
     }
index 19c3dc41f015d445803972e16187ac63022e33e6..96c1e39e8d743198b41e5c1beb9971e156cc7ba7 100644 (file)
@@ -39,7 +39,7 @@ namespace snapper
     enum StatusFlags
     {
        CREATED = 1, DELETED = 2, TYPE = 4, CONTENT = 8, PERMISSIONS = 16, USER = 32,
-       GROUP = 64, XATTRS = 128
+       GROUP = 64, XATTRS = 128, ACL = 256
     };
 
     enum Cmp
@@ -156,6 +156,7 @@ namespace snapper
        bool undo;
 
        bool modifyXattributes();
+       bool modifyAcls();
 
        unsigned int xaCreated;
        unsigned int xaDeleted;
index a6975afaae32f63f6193a69427787c737c2c3b59..8518f0f1c3463f5f4df395f040d6d3ef78eecc2a 100644 (file)
@@ -26,12 +26,12 @@ libsnapper_la_SOURCES =                                     \
        SystemCmd.cc            SystemCmd.h             \
        AsciiFile.cc            AsciiFile.h             \
        Regex.cc                Regex.h                 \
+       Acls.cc                 Acls.h                  \
        Exception.h                                     \
        SnapperTmpl.h                                   \
        SnapperTypes.h                                  \
        SnapperDefines.h                                \
-       Version.h                                       \
-       $(TMP_XA)
+       Version.h
 
 
 if ENABLE_BTRFS
index 756e0a0387e2d5b358a7a43bb5aec7ec55904d0f..bcf8c6f6c1ab97689bb7922de4ddaa7bc32e61c1 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) [2013] Red Hat, Inc.
+ * Copyright (c) [2013-2014] Red Hat, Inc.
  *
  * All Rights Reserved.
  *
 #include <errno.h>
 #include <iomanip>
 #include <boost/scoped_array.hpp>
+#include <algorithm>
 
 #include "snapper/AppUtil.h"
 #include "snapper/Exception.h"
 #include "snapper/Log.h"
 #include "snapper/XAttributes.h"
+#include "snapper/Acls.h"
 
 
 namespace snapper
 {
+    struct FilterAclsHelper
+    {
+       FilterAclsHelper(const vector<string>& acl_sigs)
+           : acl_sigs(acl_sigs) {}
+
+       bool operator()(const xa_pair_t& pair)
+       {
+           for (vector<string>::const_iterator cit = acl_sigs.begin(); cit != acl_sigs.end(); cit++)
+               if (pair.first == *cit)
+                   return true;
+           return false;
+       }
+
+       bool operator()(const string& name)
+       {
+           for (vector<string>::const_iterator cit = acl_sigs.begin(); cit != acl_sigs.end(); cit++)
+               if (name == *cit)
+                   return true;
+           return false;
+       }
+
+       const vector<string>& acl_sigs;
+    };
+
+
+    struct InsertAclsHelper
+    {
+       InsertAclsHelper(xa_map_t& xamap, const vector<string>& acl_sigs)
+       : map(xamap), acl_sigs(acl_sigs) {}
+       void operator()(const xa_pair_t& xapair)
+       {
+           for (vector<string>::const_iterator cit = acl_sigs.begin(); cit != acl_sigs.end(); cit++)
+           {
+               if (*cit == xapair.first)
+               {
+                   map.insert(xapair);
+                   break;
+               }
+           }
+       }
+
+       xa_map_t& map;
+       const vector<string>& acl_sigs;
+    };
+
 
     XAttributes::XAttributes(const string &path)
     {
@@ -73,6 +120,7 @@ namespace snapper
             // move beyond separating '\0' char
             pos += name.length() + 1;
 
+
             ssize_t v_size = lgetxattr(path.c_str(), name.c_str(), NULL, 0);
             if (v_size < 0)
             {
@@ -180,6 +228,7 @@ namespace snapper
         return (this == &xa) ? true : (this->xamap == xa.xamap);
     }
 
+
     ostream&
     operator<<(ostream &out, const XAttributes &xa)
     {
@@ -194,6 +243,7 @@ namespace snapper
         return out;
     }
 
+
     ostream&
     operator<<(ostream &out, const xa_value_t &xavalue)
     {
@@ -262,14 +312,17 @@ namespace snapper
            y2deb("adding create operation for " << src_cit->first);
             create_vec.push_back(xa_pair_t(src_cit->first, src_cit->second));
        }
+
     }
 
+
     bool
     XAModification::empty() const
     {
        return create_vec.empty() && delete_vec.empty() && replace_vec.empty();
     }
 
+
     bool
     XAModification::serializeTo(const string &dest) const
     {
@@ -336,24 +389,28 @@ namespace snapper
        return true;
     }
 
+
     unsigned int
     XAModification::getXaCreateNum() const
     {
         return create_vec.size();
     }
 
+
     unsigned int
     XAModification::getXaDeleteNum() const
     {
         return delete_vec.size();
     }
 
+
     unsigned int
     XAModification::getXaReplaceNum() const
     {
         return replace_vec.size();
     }
 
+
     void
     XAModification::printTo(ostream& out, bool diff) const
     {
@@ -381,16 +438,46 @@ namespace snapper
        }
     }
 
+
     void
     XAModification::dumpDiffReport(ostream& out) const
     {
        printTo(out, true);
     }
 
+
     ostream&
     operator<<(ostream &out, const XAModification &xa_mod)
     {
        xa_mod.printTo(out, false);
         return out;
     }
+
+
+    CompareAcls::CompareAcls(const XAttributes& xa)
+    {
+       std::for_each(xa.cbegin(), xa.cend(), InsertAclsHelper(xamap, _acl_signatures));
+    }
+
+
+    bool
+    CompareAcls::operator==(const CompareAcls& acls) const
+    {
+       return (this == &acls) ? true : (this->xamap == acls.xamap);
+    }
+
+
+    void
+    XAModification::filterOutAcls()
+    {
+       FilterAclsHelper fhelper(_acl_signatures);
+
+       create_vec.erase(std::remove_if(create_vec.begin(), create_vec.end(), fhelper),
+                        create_vec.end());
+       delete_vec.erase(std::remove_if(delete_vec.begin(), delete_vec.end(), fhelper),
+                        delete_vec.end());
+       replace_vec.erase(std::remove_if(replace_vec.begin(), replace_vec.end(), fhelper),
+                         replace_vec.end());
+    }
+
 }
index 70482108fee7005ae28a165eb8db1d6c88711e2d..2aa88b2ebfea7815bfda7952468aed05af9e768f 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) [2013] Red Hat, Inc.
+ * Copyright (c) [2013-2014] Red Hat, Inc.
  *
  * All Rights Reserved.
  *
@@ -77,6 +77,7 @@ namespace snapper
             XAModification(const XAttributes&, const XAttributes&);
 
             bool empty() const;
+           void filterOutAcls();
             bool serializeTo(const string&) const;
 
             unsigned int getXaCreateNum() const;
@@ -92,6 +93,16 @@ namespace snapper
 
         ostream& operator<<(ostream&, const XAttributes&);
         ostream& operator<<(ostream&, const xa_value_t&);
+
+       class CompareAcls
+       {
+       private:
+           xa_map_t xamap;
+       public:
+           CompareAcls(const XAttributes& xa);
+
+           bool operator==(const CompareAcls&) const;
+       };
 }
 
 
index b183fb77b9e7a601eb174f54017f5f13a48d9a47..ec1de72c68a79a686c368ebb68d0d283f89c81b2 100644 (file)
@@ -9,7 +9,7 @@ INCLUDES = -I$(top_srcdir)
 LDADD = ../snapper/libsnapper.la
 
 if HAVE_XATTRS
-TMP_XATST = xattrs1 xattrs2 xattrs3
+TMP_XATST = xattrs1 xattrs2 xattrs3 xattrs4
 endif
 
 noinst_SCRIPTS = run-all
@@ -38,6 +38,7 @@ error4_SOURCES = error4.cc common.h common.cc
 xattrs1_SOURCES = xattrs1.cc xattrs_utils.cc xattrs_utils.h common.h common.cc
 xattrs2_SOURCES = xattrs2.cc xattrs_utils.cc xattrs_utils.h common.h common.cc
 xattrs3_SOURCES = xattrs3.cc xattrs_utils.cc xattrs_utils.h common.h common.cc
+xattrs4_SOURCES = xattrs4.cc xattrs_utils.cc xattrs_utils.h common.h common.cc
 
 EXTRA_DIST = $(noinst_SCRIPTS)
 
index 0f9b6edad112be2be78e63de29bcaebf0968eeef..51e604794fa5b43392dc01c29813e95165e6798c 100755 (executable)
@@ -38,3 +38,4 @@ run error4
 test -x xattrs1 && run xattrs1
 test -x xattrs2 && run xattrs2
 test -x xattrs3 && run xattrs3
+test -x xattrs4 && run xattrs4
index 2d9f360c60ae56103a19c57948b2b02aa9f76799..b784b25c15b019c2f5d5704a49b448b230a94ae7 100644 (file)
@@ -28,7 +28,8 @@ main()
 
     check_undo_statistics(0, 1, 0);
 
-    check_xa_undo_statistics(2, 1, 1);
+    // do not count ACLs
+    check_xa_undo_statistics(1, 1, 1);
 
     check_undo_errors(0, 0, 0);
 
diff --git a/testsuite-real/xattrs4.cc b/testsuite-real/xattrs4.cc
new file mode 100644 (file)
index 0000000..0d4db29
--- /dev/null
@@ -0,0 +1,41 @@
+
+#include "common.h"
+
+using namespace std;
+
+int
+main()
+{
+    setup();
+
+    run_command("touch file1");
+    run_command("mkdir dir1");
+    run_command("mkdir no_default");
+    run_command("setfacl -b file1");
+    run_command("setfacl -k dir1");
+    run_command("setfacl -k no_default");
+    run_command("setfacl -m u:nobody:rw file1");
+    run_command("setfacl -d -m u:nobody:w dir1");
+
+    first_snapshot();
+
+    run_command("setfacl -b file1");
+    run_command("setfacl -k dir1");
+
+    second_snapshot();
+
+    undo();
+
+    check_undo_statistics(0, 2, 0);
+
+    // do not count ACLs
+    check_xa_undo_statistics(0, 0, 0);
+
+    check_undo_errors(0, 0, 0);
+
+    check_first();
+
+    cleanup();
+
+    exit(EXIT_SUCCESS);
+}