Reorganize filesystem traversal and dumping code.
authorMichael Vrable <mvrable@cs.ucsd.edu>
Thu, 19 Jul 2007 04:13:20 +0000 (21:13 -0700)
committerMichael Vrable <mvrable@turin.ucsd.edu>
Thu, 19 Jul 2007 04:13:20 +0000 (21:13 -0700)
Work to more cleanly separate the parts of the code that scan the
filesystem from the parts which actually generate dump data to the backup
snapshot.

scandir.cc

index c4a33cf..0820c16 100644 (file)
@@ -68,8 +68,6 @@ std::ostringstream metadata;
 /* Keep track of all segments which are needed to reconstruct the snapshot. */
 std::set<string> segment_list;
 
-void scandir(const string& path, bool include);
-
 /* Selection of files to include/exclude in the snapshot. */
 std::list<string> includes;         // Paths in which files should be saved
 std::list<string> excludes;         // Paths which will not be saved
@@ -132,23 +130,12 @@ ssize_t file_read(int fd, char *buf, size_t maxlen)
 /* Read the contents of a file (specified by an open file descriptor) and copy
  * the data to the store.  Returns the size of the file (number of bytes
  * dumped), or -1 on error. */
-int64_t dumpfile(int fd, dictionary &file_info, const string &path)
+int64_t dumpfile(int fd, dictionary &file_info, const string &path,
+                 struct stat& stat_buf)
 {
-    struct stat stat_buf;
-
-    if (fstat(fd, &stat_buf) < 0) {
-        fprintf(stderr, "fstat: %m\n");
-        return -1;
-    }
-
     int64_t size = 0;
     list<string> object_list;
 
-    if ((stat_buf.st_mode & S_IFMT) != S_IFREG) {
-        fprintf(stderr, "file is no longer a regular file!\n");
-        return -1;
-    }
-
     /* Look up this file in the old stat cache, if we can.  If the stat
      * information indicates that the file has not changed, do not bother
      * re-reading the entire contents. */
@@ -278,64 +265,18 @@ int64_t dumpfile(int fd, dictionary &file_info, const string &path)
     return size;
 }
 
-void scanfile(const string& path, bool include)
+/* Dump a specified filesystem object (file, directory, etc.) based on its
+ * inode information.  If the object is a regular file, an open filehandle is
+ * provided. */
+void dump_inode(const string& path,         // Path within snapshot
+                const string& fullpath,     // Path to object in filesystem
+                struct stat& stat_buf,      // Results of stat() call
+                int fd)                     // Open filehandle if regular file
 {
-    int fd;
-    long flags;
-    struct stat stat_buf;
     char *buf;
-    ssize_t len;
-    int64_t file_size;
-    list<string> refs;
-
-    string true_path;
-    if (relative_paths)
-        true_path = path;
-    else
-        true_path = "/" + path;
-
-    // Set to true if the item is a directory and we should recursively scan
-    bool recurse = false;
-
-    // Set to true if we should scan through the contents of this directory,
-    // but not actually back files up
-    bool scan_only = false;
-
-    // Check this file against the include/exclude list to see if it should be
-    // considered
-    for (list<string>::iterator i = includes.begin();
-         i != includes.end(); ++i) {
-        if (path == *i) {
-            printf("Including %s\n", path.c_str());
-            include = true;
-        }
-    }
-
-    for (list<string>::iterator i = excludes.begin();
-         i != excludes.end(); ++i) {
-        if (path == *i) {
-            printf("Excluding %s\n", path.c_str());
-            include = false;
-        }
-    }
-
-    for (list<string>::iterator i = searches.begin();
-         i != searches.end(); ++i) {
-        if (path == *i) {
-            printf("Scanning %s\n", path.c_str());
-            scan_only = true;
-        }
-    }
-
-    if (!include && !scan_only)
-        return;
-
     dictionary file_info;
-
-    if (lstat(true_path.c_str(), &stat_buf) < 0) {
-        fprintf(stderr, "lstat(%s): %m\n", path.c_str());
-        return;
-    }
+    int64_t file_size;
+    ssize_t len;
 
     printf("%s\n", path.c_str());
 
@@ -376,7 +317,7 @@ void scanfile(const string& path, bool include)
          * the symlink.  Allocate slightly more space, so that we ask for more
          * bytes than we expect and so check for truncation. */
         buf = new char[stat_buf.st_size + 2];
-        len = readlink(true_path.c_str(), buf, stat_buf.st_size + 1);
+        len = readlink(fullpath.c_str(), buf, stat_buf.st_size + 1);
         if (len < 0) {
             fprintf(stderr, "error reading symlink: %m\n");
         } else if (len <= stat_buf.st_size) {
@@ -391,33 +332,7 @@ void scanfile(const string& path, bool include)
     case S_IFREG:
         inode_type = '-';
 
-        /* Be paranoid when opening the file.  We have no guarantee that the
-         * file was not replaced between the stat() call above and the open()
-         * call below, so we might not even be opening a regular file.  That
-         * the file descriptor refers to a regular file is checked in
-         * dumpfile().  But we also supply flags to open to to guard against
-         * various conditions before we can perform that verification:
-         *   - O_NOFOLLOW: in the event the file was replaced by a symlink
-         *   - O_NONBLOCK: prevents open() from blocking if the file was
-         *     replaced by a fifo
-         * We also add in O_NOATIME, since this may reduce disk writes (for
-         * inode updates).  However, O_NOATIME may result in EPERM, so if the
-         * initial open fails, try again without O_NOATIME.  */
-        fd = open(true_path.c_str(), O_RDONLY|O_NOATIME|O_NOFOLLOW|O_NONBLOCK);
-        if (fd < 0) {
-            fd = open(true_path.c_str(), O_RDONLY|O_NOFOLLOW|O_NONBLOCK);
-        }
-        if (fd < 0) {
-            fprintf(stderr, "Unable to open file %s: %m\n", path.c_str());
-            return;
-        }
-
-        /* Drop the use of the O_NONBLOCK flag; we only wanted that for file
-         * open. */
-        flags = fcntl(fd, F_GETFL);
-        fcntl(fd, F_SETFL, flags & ~O_NONBLOCK);
-
-        file_size = dumpfile(fd, file_info, path);
+        file_size = dumpfile(fd, file_info, path, stat_buf);
         file_info["size"] = encode_int(file_size);
         close(fd);
 
@@ -432,7 +347,6 @@ void scanfile(const string& path, bool include)
         break;
     case S_IFDIR:
         inode_type = 'd';
-        recurse = true;
         break;
 
     default:
@@ -449,48 +363,137 @@ void scanfile(const string& path, bool include)
     // Break apart metadata listing if it becomes too large.
     if (metadata.str().size() > LBS_METADATA_BLOCK_SIZE)
         metadata_flush();
-
-    // If we hit a directory, now that we've written the directory itself,
-    // recursively scan the directory.
-    if (recurse)
-        scandir(path, include);
 }
 
-void scandir(const string& path, bool include)
+void scanfile(const string& path, bool include)
 {
+    int fd = -1;
+    long flags;
+    struct stat stat_buf;
+    list<string> refs;
+
     string true_path;
     if (relative_paths)
         true_path = path;
     else
         true_path = "/" + path;
 
-    DIR *dir = opendir(true_path.c_str());
+    // Set to true if we should scan through the contents of this directory,
+    // but not actually back files up
+    bool scan_only = false;
+
+    // Check this file against the include/exclude list to see if it should be
+    // considered
+    for (list<string>::iterator i = includes.begin();
+         i != includes.end(); ++i) {
+        if (path == *i) {
+            printf("Including %s\n", path.c_str());
+            include = true;
+        }
+    }
 
-    if (dir == NULL) {
-        fprintf(stderr, "Error: %m\n");
+    for (list<string>::iterator i = excludes.begin();
+         i != excludes.end(); ++i) {
+        if (path == *i) {
+            printf("Excluding %s\n", path.c_str());
+            include = false;
+        }
+    }
+
+    for (list<string>::iterator i = searches.begin();
+         i != searches.end(); ++i) {
+        if (path == *i) {
+            printf("Scanning %s\n", path.c_str());
+            scan_only = true;
+        }
+    }
+
+    if (!include && !scan_only)
+        return;
+
+    if (lstat(true_path.c_str(), &stat_buf) < 0) {
+        fprintf(stderr, "lstat(%s): %m\n", path.c_str());
         return;
     }
 
-    struct dirent *ent;
-    vector<string> contents;
-    while ((ent = readdir(dir)) != NULL) {
-        string filename(ent->d_name);
-        if (filename == "." || filename == "..")
-            continue;
-        contents.push_back(filename);
+    if ((stat_buf.st_mode & S_IFMT) == S_IFREG) {
+        /* Be paranoid when opening the file.  We have no guarantee that the
+         * file was not replaced between the stat() call above and the open()
+         * call below, so we might not even be opening a regular file.  We
+         * supply flags to open to to guard against various conditions before
+         * we can perform an lstat to check that the file is still a regular
+         * file:
+         *   - O_NOFOLLOW: in the event the file was replaced by a symlink
+         *   - O_NONBLOCK: prevents open() from blocking if the file was
+         *     replaced by a fifo
+         * We also add in O_NOATIME, since this may reduce disk writes (for
+         * inode updates).  However, O_NOATIME may result in EPERM, so if the
+         * initial open fails, try again without O_NOATIME.  */
+        fd = open(true_path.c_str(), O_RDONLY|O_NOATIME|O_NOFOLLOW|O_NONBLOCK);
+        if (fd < 0) {
+            fd = open(true_path.c_str(), O_RDONLY|O_NOFOLLOW|O_NONBLOCK);
+        }
+        if (fd < 0) {
+            fprintf(stderr, "Unable to open file %s: %m\n", path.c_str());
+            return;
+        }
+
+        /* Drop the use of the O_NONBLOCK flag; we only wanted that for file
+         * open. */
+        flags = fcntl(fd, F_GETFL);
+        fcntl(fd, F_SETFL, flags & ~O_NONBLOCK);
+
+        /* Perform the stat call again, and check that we still have a regular
+         * file. */
+        if (fstat(fd, &stat_buf) < 0) {
+            fprintf(stderr, "fstat: %m\n");
+            close(fd);
+            return;
+        }
+
+        if ((stat_buf.st_mode & S_IFMT) != S_IFREG) {
+            fprintf(stderr, "file is no longer a regular file!\n");
+            close(fd);
+            return;
+        }
     }
 
-    closedir(dir);
+    dump_inode(path, true_path, stat_buf, fd);
+
+    if (fd >= 0)
+        close(fd);
+
+    // If we hit a directory, now that we've written the directory itself,
+    // recursively scan the directory.
+    if ((stat_buf.st_mode & S_IFMT) == S_IFDIR) {
+        DIR *dir = opendir(true_path.c_str());
+
+        if (dir == NULL) {
+            fprintf(stderr, "Error: %m\n");
+            return;
+        }
 
-    sort(contents.begin(), contents.end());
+        struct dirent *ent;
+        vector<string> contents;
+        while ((ent = readdir(dir)) != NULL) {
+            string filename(ent->d_name);
+            if (filename == "." || filename == "..")
+                continue;
+            contents.push_back(filename);
+        }
+
+        closedir(dir);
 
-    for (vector<string>::iterator i = contents.begin();
-         i != contents.end(); ++i) {
-        const string& filename = *i;
-        if (path == ".")
-            scanfile(filename, include);
-        else
-            scanfile(path + "/" + filename, include);
+        sort(contents.begin(), contents.end());
+
+        for (vector<string>::iterator i = contents.begin();
+             i != contents.end(); ++i) {
+            const string& filename = *i;
+            if (path == ".")
+                scanfile(filename, include);
+            else
+                scanfile(path + "/" + filename, include);
+        }
     }
 }