Drop the Python six module dependency since 2.x is no longer supported.
[cumulus.git] / main.cc
diff --git a/main.cc b/main.cc
index 0d02727..140178e 100644 (file)
--- a/main.cc
+++ b/main.cc
 #include <iostream>
 #include <list>
 #include <map>
+#include <memory>
 #include <set>
 #include <sstream>
 #include <string>
 #include <vector>
 
-#include "cumulus.h"
 #include "exclude.h"
 #include "hash.h"
 #include "localdb.h"
@@ -62,6 +62,7 @@ using std::map;
 using std::string;
 using std::vector;
 using std::ostream;
+using std::unique_ptr;
 
 /* Version information.  This will be filled in by the Makefile. */
 #ifndef CUMULUS_VERSION
@@ -227,7 +228,7 @@ int64_t dumpfile(int fd, dictionary &file_info, const string &path,
     /* If the file is new or changed, we must read in the contents a block at a
      * time. */
     if (!cached) {
-        scoped_ptr<Hash> file_hash(Hash::New());
+        unique_ptr<Hash> file_hash(Hash::New());
         Subfile subfile(db);
         subfile.load_old_blocks(old_blocks);
 
@@ -258,7 +259,7 @@ int64_t dumpfile(int fd, dictionary &file_info, const string &path,
             double block_age = 0.0;
             ObjectReference ref;
 
-            scoped_ptr<Hash> block_hash(Hash::New());
+            unique_ptr<Hash> block_hash(Hash::New());
             block_hash->update(block_buf, bytes);
             string block_csum = block_hash->digest_str();
 
@@ -535,6 +536,48 @@ void try_merge_filter(const string& path, const string& basedir)
                                 string(block_buf, bytes));
 }
 
+/* Tests whether the specified file identifies a directory as a cache
+ * directory, by the rules of the Cache Directory Tagging Specification
+ * (https://bford.info/cachedir/).
+ *
+ * Conditions that must be met:
+ *  1. File is named "CACHEDIR.TAG".
+ *  2. File is a regular file (not a symlink or other type).
+ *  3. First bytes of the file must be identical to CACHEDIR_SIGNATURE.
+ * Remaining bytes of the file are ignored and can have any contents.
+ *
+ * It is up to the caller to check the file name; other conditions are checked
+ * in this function. */
+bool is_cachedir_tag_file(const string& path) {
+    struct stat stat_buf;
+    if (lstat(path.c_str(), &stat_buf) < 0) {
+        return false;
+    }
+    if ((stat_buf.st_mode & S_IFMT) != S_IFREG) {
+        return false;
+    }
+
+    static const char CACHEDIR_SIGNATURE[]
+        = "Signature: 8a477f597d28d172789f06886806bc55";
+    ssize_t CACHEDIR_SIGLEN = strlen(CACHEDIR_SIGNATURE);
+
+    int fd = safe_open(path, NULL);
+    if (fd < 0)
+        return false;
+
+    ssize_t bytes = file_read(fd, block_buf, CACHEDIR_SIGLEN);
+    close(fd);
+    if (bytes != CACHEDIR_SIGLEN) {
+        return false;
+    }
+
+    if (memcmp(block_buf, CACHEDIR_SIGNATURE, CACHEDIR_SIGLEN) != 0) {
+        return false;
+    }
+
+    return true;
+}
+
 void scanfile(const string& path)
 {
     int fd = -1;
@@ -607,6 +650,13 @@ void scanfile(const string& path)
                 }
                 try_merge_filter(filename, output_path);
             }
+            if (*i == CACHEDIR_TAG_FILE && is_cachedir_tag_file(filename)) {
+                if (verbose) {
+                    printf("Cache directory found at %s\n",
+                           output_path.c_str());
+                }
+                filter_rules.activate_cachedir(output_path);
+            }
         }
 
         /* Second pass: recursively scan all items in the directory for backup;
@@ -641,6 +691,7 @@ void usage(const char *program)
         "  --exclude=PATTERN    exclude files matching PATTERN from snapshot\n"
         "  --include=PATTERN    include files matching PATTERN in snapshot\n"
         "  --dir-merge=PATTERN  parse files matching PATTERN to read additional\n"
+        "  --cachedir-check     insert a CACHEDIR.TAG check in filter rules\n"
         "                       subtree-specific include/exclude rules during backup\n"
         "  --localdb=PATH       local backup metadata is stored in PATH\n"
         "  --tmpdir=PATH        path for temporarily storing backup files\n"
@@ -692,6 +743,7 @@ int main(int argc, char *argv[])
             {"include", 1, 0, 0},           // 11
             {"exclude", 1, 0, 0},           // 12
             {"dir-merge", 1, 0, 0},         // 13
+            {"cachedir-check", 0, 0, 0},    // 14
             // Aliases for short options
             {"verbose", 0, 0, 'v'},
             {NULL, 0, 0, 0},
@@ -749,6 +801,10 @@ int main(int argc, char *argv[])
             case 13:    // --dir-merge
                 filter_rules.add_pattern(PathFilterList::DIRMERGE, optarg, "");
                 break;
+            case 14:    // --cachedir-check
+                filter_rules.add_pattern(PathFilterList::CACHEDIR_CHECK, "",
+                                         "");
+                break;
             default:
                 fprintf(stderr, "Unhandled long option!\n");
                 return 1;
@@ -853,7 +909,7 @@ int main(int argc, char *argv[])
         dbmeta_filename += backup_scheme + "-";
     dbmeta_filename += timestamp + ".meta" + filter_extension;
     RemoteFile *dbmeta_file = remote->alloc_file(dbmeta_filename, "meta");
-    scoped_ptr<FileFilter> dbmeta_filter(FileFilter::New(dbmeta_file->get_fd(),
+    unique_ptr<FileFilter> dbmeta_filter(FileFilter::New(dbmeta_file->get_fd(),
                                                          filter_program));
     if (dbmeta_filter == NULL) {
         fprintf(stderr, "Unable to open descriptor output file: %m\n");
@@ -902,7 +958,7 @@ int main(int argc, char *argv[])
 
     RemoteFile *descriptor_file = remote->alloc_file(desc_filename,
                                                      "snapshots");
-    scoped_ptr<FileFilter> descriptor_filter(
+    unique_ptr<FileFilter> descriptor_filter(
         FileFilter::New(descriptor_file->get_fd(), signature_filter.c_str()));
     if (descriptor_filter == NULL) {
         fprintf(stderr, "Unable to open descriptor output file: %m\n");