+/* 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,
+ struct stat& stat_buf)
+{
+ int64_t size = 0;
+ list<string> object_list;
+ const char *status = NULL; /* Status indicator printed out */
+
+ /* 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. */
+ bool cached = false;
+
+ if (statcache->Find(path, &stat_buf)) {
+ cached = true;
+ const list<ObjectReference> &blocks = statcache->get_blocks();
+
+ /* If any of the blocks in the object have been expired, then we should
+ * fall back to fully reading in the file. */
+ for (list<ObjectReference>::const_iterator i = blocks.begin();
+ i != blocks.end(); ++i) {
+ const ObjectReference &ref = *i;
+ if (!db->IsAvailable(ref)) {
+ cached = false;
+ status = "repack";
+ break;
+ }
+ }
+
+ /* If everything looks okay, use the cached information */
+ if (cached) {
+ file_info["checksum"] = statcache->get_checksum();
+ for (list<ObjectReference>::const_iterator i = blocks.begin();
+ i != blocks.end(); ++i) {
+ const ObjectReference &ref = *i;
+ object_list.push_back(ref.to_string());
+ segment_list.insert(ref.get_segment());
+ db->UseObject(ref);
+ }
+ size = stat_buf.st_size;
+ }
+ }
+
+ /* If the file is new or changed, we must read in the contents a block at a
+ * time. */
+ if (!cached) {
+ SHA1Checksum hash;
+ while (true) {
+ ssize_t bytes = file_read(fd, block_buf, LBS_BLOCK_SIZE);
+ if (bytes == 0)
+ break;
+ if (bytes < 0) {
+ fprintf(stderr, "Backup contents for %s may be incorrect\n",
+ path.c_str());
+ break;
+ }
+
+ hash.process(block_buf, bytes);
+
+ // Either find a copy of this block in an already-existing segment,
+ // or index it so it can be re-used in the future
+ double block_age = 0.0;
+ SHA1Checksum block_hash;
+ block_hash.process(block_buf, bytes);
+ string block_csum = block_hash.checksum_str();
+ ObjectReference ref = db->FindObject(block_csum, bytes);
+
+ // Store a copy of the object if one does not yet exist
+ if (ref.get_segment().size() == 0) {
+ LbsObject *o = new LbsObject;
+
+ /* We might still have seen this checksum before, if the object
+ * was stored at some time in the past, but we have decided to
+ * clean the segment the object was originally stored in
+ * (FindObject will not return such objects). When rewriting
+ * the object contents, put it in a separate group, so that old
+ * objects get grouped together. The hope is that these old
+ * objects will continue to be used in the future, and we
+ * obtain segments which will continue to be well-utilized.
+ * Additionally, keep track of the age of the data by looking
+ * up the age of the block which was expired and using that
+ * instead of the current time. */
+ if (db->IsOldObject(block_csum, bytes, &block_age)) {
+ o->set_group("compacted");
+ if (status == NULL)
+ status = "partial";
+ } else {
+ o->set_group("data");
+ status = "new";
+ }
+
+ o->set_data(block_buf, bytes);
+ o->write(tss);
+ ref = o->get_ref();
+ db->StoreObject(ref, block_csum, bytes, block_age);
+ delete o;
+ }
+
+ object_list.push_back(ref.to_string());
+ segment_list.insert(ref.get_segment());
+ db->UseObject(ref);
+ size += bytes;
+
+ if (status == NULL)
+ status = "old";
+ }
+
+ file_info["checksum"] = hash.checksum_str();
+ }
+
+ if (status != NULL)
+ printf(" [%s]\n", status);
+
+ statcache->Save(path, &stat_buf, file_info["checksum"], object_list);
+
+ /* For files that only need to be broken apart into a few objects, store
+ * the list of objects directly. For larger files, store the data
+ * out-of-line and provide a pointer to the indrect object. */
+ if (object_list.size() < 8) {
+ string blocklist = "";
+ for (list<string>::iterator i = object_list.begin();
+ i != object_list.end(); ++i) {
+ if (i != object_list.begin())
+ blocklist += " ";
+ blocklist += *i;
+ }
+ file_info["data"] = blocklist;
+ } else {
+ string blocklist = "";
+ for (list<string>::iterator i = object_list.begin();
+ i != object_list.end(); ++i) {
+ blocklist += *i + "\n";
+ }
+
+ LbsObject *i = new LbsObject;
+ i->set_group("metadata");
+ i->set_data(blocklist.data(), blocklist.size());
+ i->write(tss);
+ file_info["data"] = "@" + i->get_name();
+ segment_list.insert(i->get_ref().get_segment());
+ delete i;
+ }
+
+ return size;
+}
+
+/* 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