]> git.ipfire.org Git - thirdparty/snapper.git/commitdiff
- added functions to compare files and directories
authorArvin Schnell <aschnell@suse.de>
Wed, 12 Jan 2011 13:45:30 +0000 (14:45 +0100)
committerArvin Schnell <aschnell@suse.de>
Wed, 12 Jan 2011 13:45:30 +0000 (14:45 +0100)
examples/.gitignore
examples/CmpDirs.cc [new file with mode: 0644]
examples/Makefile.am
snapper/AppUtil.h
snapper/Files.cc [new file with mode: 0644]
snapper/Files.h [new file with mode: 0644]
snapper/Makefile.am
snapper/SnapperInterface.h

index 648188c8202213edb76e3790091596cefeb240b2..629d65b3b5d865778b05fcdde5809ea3041771f0 100644 (file)
@@ -2,3 +2,5 @@ List.o
 List
 Create.o
 Create
+CmpDirs.o
+CmpDirs
diff --git a/examples/CmpDirs.cc b/examples/CmpDirs.cc
new file mode 100644 (file)
index 0000000..4200e22
--- /dev/null
@@ -0,0 +1,30 @@
+
+#include <stdlib.h>
+#include <iostream>
+
+#include <snapper/Files.h>
+
+using namespace snapper;
+using namespace std;
+
+
+void
+log(const string& name, unsigned int status)
+{
+    cout << statusToString(status) << " " << name << endl;
+}
+
+
+int
+main(int argc, char** argv)
+{
+    if (argc != 3)
+    {
+       cerr << "usage: CmdDirs path1 path2\n";
+       exit(EXIT_FAILURE);
+    }
+
+    cmpDirs(argv[1], argv[2], log);
+
+    exit(EXIT_SUCCESS);
+}
index d8df7443e6c32bdeb0688102443fcce8929d1a37..10dac5c9c29680c2178886af8cc82b3bf68925de 100644 (file)
@@ -1,20 +1,22 @@
 #
-# Makefile.am for libsnapper/examples
+# Makefile.am for snapper/examples
 #
 
 INCLUDES = -I$(top_srcdir)
 
 LDADD = ../snapper/libsnapper.la
 
-noinst_PROGRAMS = List Create
+noinst_PROGRAMS = List Create CmpDirs
 
 List_SOURCES = List.cc
 
 Create_SOURCES = Create.cc
 
+CmpDirs_SOURCES = CmpDirs.cc
+
 exampledir = $(docdir)/examples
 
-example_DATA = List.cc Create.cc
+example_DATA = List.cc Create.cc CmpDirs.cc
 
 EXTRA_DIST = $(example_DATA) Makefile.example
 
index 7cbaf19b938ac6e99e8d5b80b2cb36747ddf91a8..20a2528770446dd8917819f37736fdb0cfe63210 100644 (file)
@@ -48,6 +48,8 @@ bool setStatMode(const string& Path_Cv, mode_t val );
 
     list<string> glob(const string& path, int flags);
 
+    bool readlink(const string& path, string& buf);
+
 template<class StreamType>
 void classic(StreamType& stream)
 {
diff --git a/snapper/Files.cc b/snapper/Files.cc
new file mode 100644 (file)
index 0000000..0ba7739
--- /dev/null
@@ -0,0 +1,395 @@
+/*
+ * Copyright (c) 2011 Novell, 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, contact Novell, Inc.
+ *
+ * To contact Novell about this file by physical or electronic mail, you may
+ * find current contact information at www.novell.com.
+ */
+
+
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <dirent.h>
+#include <unistd.h>
+#include <assert.h>
+#include <vector>
+#include <algorithm>
+
+#include "snapper/AppUtil.h"
+#include "snapper/Files.h"
+#include "snapper/SnapperInterface.h"
+
+
+namespace snapper
+{
+    using namespace std;
+
+
+    bool
+    cmpFilesContentReg(const string& fullname1, struct stat stat1, const string& fullname2,
+                      struct stat stat2)
+    {
+       if (stat1.st_mtime == stat2.st_mtime)
+           return true;
+
+       if (stat1.st_size != stat2.st_size)
+           return false;
+
+       if (stat1.st_size == 0 && stat2.st_size == 0)
+           return true;
+
+       if ((stat1.st_dev == stat2.st_dev) && (stat1.st_ino == stat2.st_ino))
+           return true;
+
+       int fd1 = open(fullname1.c_str(), O_RDONLY | O_NOATIME);
+       assert(fd1 >= 0);
+       posix_fadvise(fd1, 0, stat1.st_size, POSIX_FADV_SEQUENTIAL);
+
+       int fd2 = open(fullname2.c_str(), O_RDONLY | O_NOATIME);
+       assert(fd2 >= 0);
+       posix_fadvise(fd2, 0, stat2.st_size, POSIX_FADV_SEQUENTIAL);
+
+       static_assert(sizeof(off_t) >= 8, "off_t is too small");
+
+       const off_t block_size = 4096;
+
+       char block1[block_size];
+       char block2[block_size];
+
+       bool equal = true;
+
+       off_t length = stat1.st_size;
+       while (length > 0)
+       {
+           off_t t = min(block_size, length);
+
+           int r1 = read(fd1, block1, t);
+           assert(r1 == t);
+
+           int r2 = read(fd2, block2, t);
+           assert(r2 == t);
+
+           if (memcmp(block1, block2, t) != 0)
+           {
+               equal = false;
+               break;
+           }
+
+           length -= t;
+       }
+
+       close(fd1);
+       close(fd2);
+
+       return equal;
+    }
+
+
+    bool
+    cmpFilesContentLnk(const string& fullname1, struct stat stat1, const string& fullname2,
+                      struct stat stat2)
+    {
+       if (stat1.st_mtime == stat2.st_mtime)
+           return true;
+
+       string tmp1;
+       bool r1 = readlink(fullname1, tmp1);
+       assert(r1);
+
+       string tmp2;
+       bool r2 = readlink(fullname2, tmp2);
+       assert(r2);
+
+       return tmp1 == tmp2;
+    }
+
+
+    bool
+    cmpFilesContent(const string& fullname1, struct stat stat1, const string& fullname2,
+                   struct stat stat2)
+    {
+       assert((stat1.st_mode & S_IFMT) == (stat2.st_mode & S_IFMT));
+
+       switch (stat1.st_mode & S_IFMT)
+       {
+           case S_IFREG:
+               return cmpFilesContentReg(fullname1, stat1, fullname2, stat2);
+
+           case S_IFLNK:
+               return cmpFilesContentLnk(fullname1, stat1, fullname2, stat2);
+
+           default:
+               return true;
+       }
+    }
+
+
+    unsigned int
+    cmpFiles(const string& fullname1, struct stat stat1, const string& fullname2, struct stat stat2)
+    {
+       unsigned int status = 0;
+
+       if ((stat1.st_mode & S_IFMT) != (stat2.st_mode & S_IFMT))
+       {
+           status |= TYPE;
+       }
+       else
+       {
+           if (!cmpFilesContent(fullname1, stat1, fullname2, stat2))
+               status |= CONTENT;
+       }
+
+       if ((stat1.st_mode ^ stat2.st_mode) & (S_IRWXU | S_IRWXG | S_IRWXO | S_ISUID |
+                                              S_ISGID | S_ISVTX))
+       {
+           status |= PERMISSIONS;
+       }
+
+       if (stat1.st_uid != stat2.st_uid)
+       {
+           status |= USER;
+       }
+
+       if (stat1.st_gid != stat2.st_gid)
+       {
+           status |= GROUP;
+       }
+
+       return status;
+    }
+
+
+    unsigned int
+    cmpFiles(const string& fullname1, const string& fullname2)
+    {
+       struct stat stat1;
+       int r1 = stat(fullname1.c_str(), &stat1);
+
+       struct stat stat2;
+       int r2 = stat(fullname2.c_str(), &stat2);
+
+       if (r1 != 0 && r2 == 0)
+           return CREATED;
+
+       if (r1 == 0 && r2 != 0)
+           return DELETED;
+
+       return cmpFiles(fullname1, stat1, fullname2, stat2);
+    }
+
+
+    bool
+    filter(const string& name)
+    {
+       if (name == "/snapshots")
+           return true;
+
+       return false;
+    }
+
+
+    vector<string>
+    readDirectory(const string& base_path, const string& path)
+    {
+       vector<string> ret;
+
+       int fd = open((base_path + path).c_str(), O_RDONLY | O_NOATIME);
+       assert(fd >= 0);
+
+       DIR* dp = fdopendir(fd);
+       assert(dp != NULL);
+
+       struct dirent* ep;
+       while ((ep = readdir(dp)))
+       {
+           if (strcmp(ep->d_name, ".") == 0 || strcmp(ep->d_name, "..") == 0)
+               continue;
+
+           string fullname = path + "/" + ep->d_name;
+           if (filter(fullname))
+               continue;
+
+           ret.push_back(ep->d_name);
+       }
+
+       closedir(dp);
+
+       sort(ret.begin(), ret.end());
+
+       return ret;
+    }
+
+
+    void
+    listSubdirs(const string& base_path, const string& path, unsigned int status,
+               void(*cb)(const string& name, unsigned int status))
+    {
+       vector<string> tmp = readDirectory(base_path, path);
+
+       for (vector<string>::const_iterator it1 = tmp.begin(); it1 != tmp.end(); ++it1)
+       {
+           (*cb)(path + "/" + *it1, status);
+
+           struct stat stat;
+           int r = lstat((base_path + path + "/" + *it1).c_str(), &stat);
+           assert(r == 0);
+
+           if (S_ISDIR(stat.st_mode))
+               listSubdirs(base_path, path + "/" + *it1, status, cb);
+       }
+    }
+
+
+    void
+    cmpDirsWorker(const string& base_path1, const string& base_path2, const string& path,
+                 void(*cb)(const string& name, unsigned int status));
+
+
+    void
+    lonesome(const string& base_path, const string& path, const string& name, unsigned int status,
+            void(*cb)(const string& name, unsigned int status))
+    {
+       (*cb)(path + "/" + name, status);
+
+       struct stat stat;
+       int r = lstat((base_path + path + "/" + name).c_str(), &stat);
+       assert(r == 0);
+
+       if (S_ISDIR(stat.st_mode))
+           listSubdirs(base_path, path + "/" + name, status, cb);
+    }
+
+
+    void
+    twosome(const string& base_path1, const string& base_path2, const string& path,
+           const string& name, void(*cb)(const string& name, unsigned int status))
+    {
+       string fullname1 = base_path1 + path + "/" + name;
+       struct stat stat1;
+       int r1 = lstat(fullname1.c_str(), &stat1);
+       assert(r1 == 0);
+
+       string fullname2 = base_path2 + path + "/" + name;
+       struct stat stat2;
+       int r2 = lstat(fullname2.c_str(), &stat2);
+       assert(r2 == 0);
+
+       unsigned int status = cmpFiles(fullname1, stat1, fullname2, stat2);
+
+       if (status != 0)
+       {
+           (*cb)(path + "/" + name, status);
+       }
+
+       if (!(status & TYPE))
+       {
+           if (S_ISDIR(stat1.st_mode))
+               cmpDirsWorker(base_path1, base_path2, path + "/" + name, cb);
+       }
+       else
+       {
+           if (S_ISDIR(stat1.st_mode))
+               listSubdirs(base_path1, path + "/" + name, DELETED, cb);
+
+           if (S_ISDIR(stat2.st_mode))
+               listSubdirs(base_path2, path + "/" + name, CREATED, cb);
+       }
+    }
+
+
+    void
+    cmpDirsWorker(const string& base_path1, const string& base_path2, const string& path,
+                 void(*cb)(const string& name, unsigned int status))
+    {
+       const vector<string> pre = readDirectory(base_path1, path);
+       vector<string>::const_iterator first1 = pre.begin();
+       vector<string>::const_iterator last1 = pre.end();
+
+       const vector<string> post = readDirectory(base_path2, path);
+       vector<string>::const_iterator first2 = post.begin();
+       vector<string>::const_iterator last2 = post.end();
+
+       while (first1 != last1 || first2 != last2)
+       {
+           if (first1 == last1)
+           {
+               assert(*first1 != *first2);
+               lonesome(base_path2, path, *first2, CREATED, cb);
+               ++first2;
+           }
+           else if (first2 == last2)
+           {
+               assert(*first1 != *first2);
+               lonesome(base_path1, path, *first1, DELETED, cb);
+               ++first1;
+           }
+           else if (*first2 < *first1)
+           {
+               assert(*first1 != *first2);
+               lonesome(base_path2, path, *first2, CREATED, cb);
+               ++first2;
+           }
+           else if (*first1 < *first2)
+           {
+               assert(*first1 != *first2);
+               lonesome(base_path1, path, *first1, DELETED, cb);
+               ++first1;
+           }
+           else
+           {
+               assert(*first1 == *first2);
+               twosome(base_path1, base_path2, path, *first1, cb);
+               ++first1;
+               ++first2;
+           }
+       }
+    }
+
+
+    void
+    cmpDirs(const string& path1, const string& path2, void(*cb)(const string& name,
+                                                               unsigned int status))
+    {
+       cmpDirsWorker(path1, path2, "", cb);
+    }
+
+
+    string
+    statusToString(unsigned int status)
+    {
+       string ret;
+
+       if (status & CREATED)
+           ret += "+";
+       else if (status & DELETED)
+           ret += "-";
+       else if (status & TYPE)
+           ret += "t";
+       else if (status & CONTENT)
+           ret += "c";
+       else
+           ret += ".";
+
+       ret += status & PERMISSIONS ? "p" : ".";
+       ret += status & USER ? "u" : ".";
+       ret += status & GROUP ? "g" : ".";
+
+       return ret;
+    }
+
+}
diff --git a/snapper/Files.h b/snapper/Files.h
new file mode 100644 (file)
index 0000000..1672ad2
--- /dev/null
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2011 Novell, 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, contact Novell, Inc.
+ *
+ * To contact Novell about this file by physical or electronic mail, you may
+ * find current contact information at www.novell.com.
+ */
+
+
+#ifndef FILES_H
+#define FILES_H
+
+
+#include <string>
+
+
+namespace snapper
+{
+    using std::string;
+
+
+    unsigned int
+    cmpFiles(const string& name1, const string& name2);
+
+    void
+    cmpDirs(const string& path1, const string& path2, void(*cb)(const string& name,
+                                                               unsigned int status));
+
+    string
+    statusToString(unsigned int status);
+
+}
+
+
+#endif
index d1cbbbe21d209394fa2a8630dbd2bf1603f6daec..d6b85f0cbb90bff3538e0de2849720b7e123ec7c 100644 (file)
@@ -1,15 +1,18 @@
 #
-# Makefile.am for libsnapper/snapper
+# Makefile.am for snapper/snapper
 #
 
 lib_LTLIBRARIES = libsnapper.la
 
+AM_CXXFLAGS = -D_FILE_OFFSET_BITS=64
+
 libsnapper_la_SOURCES =                                        \
        SnapperInterface.h                              \
        Snapper.cc              Snapper.h               \
        XmlFile.cc              XmlFile.h               \
        Enum.cc                 Enum.h                  \
        AppUtil.cc              AppUtil.h               \
+       Files.cc                Files.h                 \
        SnapperTmpl.h
 
 libsnapper_la_LDFLAGS = -version-info 2:0
index 94b9ec5c6c62d48fc515b17afad4f0659e6115c7..7a4f68b3aa51991637df5d77b556c38255c1484a 100644 (file)
@@ -75,7 +75,8 @@ namespace snapper
 
     enum StatusFlags
     {
-       CREATED = 1, DELETED = 2, TYPE = 4, CONTENT = 8, PERMISSIONS = 16, OWNER = 32
+       CREATED = 1, DELETED = 2, TYPE = 4, CONTENT = 8, PERMISSIONS = 16, USER = 32,
+       GROUP = 64
     };
 
     enum Cmp