TBR: derat@chromium.org
diff --git a/delta_diff_generator.cc b/delta_diff_generator.cc
index 6f352c7..45872bc 100644
--- a/delta_diff_generator.cc
+++ b/delta_diff_generator.cc
@@ -72,6 +72,8 @@
       : mode(0),
         uid(0),
         gid(0),
+        nlink(0),
+        inode(0),
         compressed(false),
         offset(-1),
         length(0),
@@ -81,6 +83,14 @@
   uid_t uid;
   gid_t gid;
 
+  // a file may be a potential hardlink if it's not a directory
+  // and it has a link count > 1.
+  bool IsPotentialHardlink() const {
+    return !S_ISDIR(mode) && nlink > 1;
+  }
+  nlink_t nlink;  // number of hard links
+  ino_t inode;
+
   // data
   bool compressed;
   int offset;  // -1 means no data
@@ -102,6 +112,8 @@
   node->mode = stbuf.st_mode;
   node->uid = stbuf.st_uid;
   node->gid = stbuf.st_gid;
+  node->nlink = stbuf.st_nlink;
+  node->inode = stbuf.st_ino;
   if (!S_ISDIR(node->mode)) {
     return true;
   }
@@ -153,11 +165,20 @@
 }
 
 // This converts a Node tree rooted at n into a DeltaArchiveManifest.
-void NodeToDeltaArchiveManifest(Node* n, DeltaArchiveManifest* archive) {
+void NodeToDeltaArchiveManifest(Node* n, DeltaArchiveManifest* archive,
+                                map<ino_t, string>* hard_links,
+                                const string& path) {
   DeltaArchiveManifest_File *f = archive->add_files();
   f->set_mode(n->mode);
   f->set_uid(n->uid);
   f->set_gid(n->gid);
+  if (utils::MapContainsKey(*hard_links, n->inode)) {
+    // We have a hard link
+    CHECK(!S_ISDIR(n->mode));
+    f->set_hardlink_path((*hard_links)[n->inode]);
+  } else if (n->IsPotentialHardlink()) {
+    (*hard_links)[n->inode] = path;
+  }
   if (!S_ISDIR(n->mode))
     return;
   for (unsigned int i = 0; i < n->children.size(); i++) {
@@ -166,7 +187,8 @@
     child->set_index(n->children[i]->node->idx);
   }
   for (unsigned int i = 0; i < n->children.size(); i++) {
-    NodeToDeltaArchiveManifest(n->children[i]->node.get(), archive);
+    NodeToDeltaArchiveManifest(n->children[i]->node.get(), archive, hard_links,
+                               path + "/" + n->children[i]->name);
   }
 }
 
@@ -182,7 +204,8 @@
     const std::string& old_path,
     const std::string& new_path,
     FileWriter* out_file_writer,
-    int* out_file_length) {
+    int* out_file_length,
+    const std::string& force_compress_dev_path) {
   TEST_AND_RETURN_FALSE(file->has_mode());
 
   // Stat the actual file, too
@@ -204,12 +227,14 @@
           old_path + "/" + file_name,
           new_path + "/" + file_name,
           out_file_writer,
-          out_file_length));
+          out_file_length,
+          force_compress_dev_path));
     }
     return true;
   }
 
-  if (S_ISFIFO(file->mode()) || S_ISSOCK(file->mode())) {
+  if (S_ISFIFO(file->mode()) || S_ISSOCK(file->mode()) ||
+      file->has_hardlink_path()) {
     // These don't store any additional data
     return true;
   }
@@ -219,13 +244,13 @@
   bool format_set = false;
   DeltaArchiveManifest_File_DataFormat format;
   if (S_ISLNK(file->mode())) {
-    LOG(INFO) << "link";
     TEST_AND_RETURN_FALSE(EncodeLink(new_path + "/" + file_name, &data));
   } else if (S_ISCHR(file->mode()) || S_ISBLK(file->mode())) {
-    LOG(INFO) << "dev";
-    TEST_AND_RETURN_FALSE(EncodeDev(stbuf, &data));
+    TEST_AND_RETURN_FALSE(EncodeDev(stbuf, &data, &format,
+                                    new_path + "/" + file_name ==
+                                    force_compress_dev_path));
+    format_set = true;
   } else if (S_ISREG(file->mode())) {
-    LOG(INFO) << "reg";
     // regular file. We may use a delta here.
     TEST_AND_RETURN_FALSE(EncodeFile(old_path, new_path, file_name, &format,
                                      &data));
@@ -239,7 +264,7 @@
     LOG(ERROR) << "Unhandled mode type: " << file->mode();
     return false;
   }
-  LOG(INFO) << "data len: " << data.size();
+
   if (!format_set) {
     // Pick a format now
     vector<char> compressed_data;
@@ -276,13 +301,24 @@
   return true;
 }
 
-bool DeltaDiffGenerator::EncodeDev(const struct stat& stbuf,
-                                   std::vector<char>* out) {
+bool DeltaDiffGenerator::EncodeDev(
+    const struct stat& stbuf,
+    vector<char>* out,
+    DeltaArchiveManifest_File_DataFormat* format,
+    bool force_compression) {
   LinuxDevice dev;
   dev.set_major(major(stbuf.st_rdev));
   dev.set_minor(minor(stbuf.st_rdev));
   out->resize(dev.ByteSize());
   TEST_AND_RETURN_FALSE(dev.SerializeToArray(&(*out)[0], out->size()));
+  if (force_compression) {
+    vector<char> compressed;
+    TEST_AND_RETURN_FALSE(GzipCompress(*out, &compressed));
+    out->swap(compressed);
+    *format = DeltaArchiveManifest_File_DataFormat_FULL_GZ;
+  } else {
+    *format = DeltaArchiveManifest_File_DataFormat_FULL;
+  }
   return true;
 }
 
@@ -354,14 +390,17 @@
   int index = 0;
   PopulateChildIndexes(&node, &index);
   DeltaArchiveManifest *ret = new DeltaArchiveManifest;
-  NodeToDeltaArchiveManifest(&node, ret);
+  map<ino_t, string> hard_links;  // inode -> first found path for inode
+  NodeToDeltaArchiveManifest(&node, ret, &hard_links, "");
   return ret;
 }
 
-bool DeltaDiffGenerator::EncodeDataToDeltaFile(DeltaArchiveManifest* archive,
-                                               const std::string& old_path,
-                                               const std::string& new_path,
-                                               const std::string& out_file) {
+bool DeltaDiffGenerator::EncodeDataToDeltaFile(
+    DeltaArchiveManifest* archive,
+    const std::string& old_path,
+    const std::string& new_path,
+    const std::string& out_file,
+    const std::string& force_compress_dev_path) {
   DirectFileWriter out_writer;
   int r = out_writer.Open(out_file.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0666);
   TEST_AND_RETURN_FALSE_ERRNO(r >= 0);
@@ -386,7 +425,8 @@
                                                   old_path,
                                                   new_path,
                                                   &out_writer,
-                                                  &out_file_length));
+                                                  &out_file_length,
+                                                  force_compress_dev_path));
 
   // Finally, write the protobuf to the end of the file
   string encoded_archive;