Include manifest size when presizing ijar output

PiperOrigin-RevId: 190010996
diff --git a/third_party/ijar/ijar.cc b/third_party/ijar/ijar.cc
index d7742ee..2ed559d 100644
--- a/third_party/ijar/ijar.cc
+++ b/third_party/ijar/ijar.cc
@@ -36,13 +36,20 @@
 const char* CLASS_EXTENSION = ".class";
 const size_t CLASS_EXTENSION_LENGTH = strlen(CLASS_EXTENSION);
 
+const char *MANIFEST_DIR_PATH = "META-INF/";
+const size_t MANIFEST_DIR_PATH_LENGTH = strlen(MANIFEST_DIR_PATH);
+const char *MANIFEST_PATH = "META-INF/MANIFEST.MF";
+const size_t MANIFEST_PATH_LENGTH = strlen(MANIFEST_PATH);
 const char *MANIFEST_HEADER =
-    "Manifest-Version: 1.0\n"
-    "Created-By: bazel\n";
+    "Manifest-Version: 1.0\r\n"
+    "Created-By: bazel\r\n";
+const size_t MANIFEST_HEADER_LENGTH = strlen(MANIFEST_HEADER);
 // These attributes are used by JavaBuilder, Turbine, and ijar.
 // They must all be kept in sync.
 const char *TARGET_LABEL_KEY = "Target-Label: ";
+const size_t TARGET_LABEL_KEY_LENGTH = strlen(TARGET_LABEL_KEY);
 const char *INJECTING_RULE_KIND_KEY = "Injecting-Rule-Kind: ";
+const size_t INJECTING_RULE_KIND_KEY_LENGTH = strlen(INJECTING_RULE_KIND_KEY);
 
 // ZipExtractorProcessor that select only .class file and use
 // StripClass to generate an interface class, storing as a new file
@@ -120,21 +127,48 @@
   return len;
 }
 
+// Computes the size of zip file content for the manifest created by
+// WriteManifest, including zip file format overhead.
+static size_t EstimateManifestOutputSize(const char *target_label,
+                                         const char *injecting_rule_kind) {
+  if (target_label == NULL) {
+    return 0;
+  }
+  // local headers
+  size_t length = 30 * 2 + MANIFEST_DIR_PATH_LENGTH + MANIFEST_PATH_LENGTH;
+  // central directory
+  length += 46 * 2 + MANIFEST_DIR_PATH_LENGTH + MANIFEST_PATH_LENGTH;
+  // zip64 EOCD entries
+  length += 56 * 2;
+
+  // manifest content
+  length += MANIFEST_HEADER_LENGTH;
+  // target label manifest entry, including newline
+  length += TARGET_LABEL_KEY_LENGTH + strlen(target_label) + 2;
+  if (injecting_rule_kind) {
+    // injecting rule kind manifest entry, including newline
+    length += INJECTING_RULE_KIND_KEY_LENGTH + strlen(injecting_rule_kind) + 2;
+  }
+  return length;
+}
+
 static void WriteManifest(ZipBuilder *out, const char *target_label,
                           const char *injecting_rule_kind) {
   if (target_label == NULL) {
     return;
   }
-  out->WriteEmptyFile("META-INF/");
-  u1 *start = out->NewFile("META-INF/MANIFEST.MF", 0);
+  out->WriteEmptyFile(MANIFEST_DIR_PATH);
+  u1 *start = out->NewFile(MANIFEST_PATH, 0);
   u1 *buf = start;
   buf += WriteStr(buf, MANIFEST_HEADER);
   buf += WriteStr(buf, TARGET_LABEL_KEY);
   buf += WriteStr(buf, target_label);
+  *buf++ = '\r';
   *buf++ = '\n';
   if (injecting_rule_kind) {
     buf += WriteStr(buf, INJECTING_RULE_KIND_KEY);
     buf += WriteStr(buf, injecting_rule_kind);
+    *buf++ = '\r';
     *buf++ = '\n';
   }
   size_t total_len = buf - start;
@@ -153,7 +187,9 @@
             strerror(errno));
     abort();
   }
-  u8 output_length = in->CalculateOutputLength();
+  u8 output_length =
+      in->CalculateOutputLength() +
+      EstimateManifestOutputSize(target_label, injecting_rule_kind);
   std::unique_ptr<ZipBuilder> out(ZipBuilder::Create(file_out, output_length));
   if (out.get() == NULL) {
     fprintf(stderr, "Unable to open output file %s: %s\n", file_out,
diff --git a/third_party/ijar/mapped_file.h b/third_party/ijar/mapped_file.h
index 6db0673..7653638 100644
--- a/third_party/ijar/mapped_file.h
+++ b/third_party/ijar/mapped_file.h
@@ -62,6 +62,7 @@
   const char* errmsg_;
   bool opened_;
   u1* buffer_;
+  u8 estimated_size_;
 
  public:
   MappedOutputFile(const char* name, u8 estimated_size);
diff --git a/third_party/ijar/mapped_file_unix.cc b/third_party/ijar/mapped_file_unix.cc
index d5a0b57..ccb8787 100644
--- a/third_party/ijar/mapped_file_unix.cc
+++ b/third_party/ijar/mapped_file_unix.cc
@@ -90,7 +90,8 @@
   int mmap_length_;
 };
 
-MappedOutputFile::MappedOutputFile(const char* name, u8 estimated_size) {
+MappedOutputFile::MappedOutputFile(const char* name, u8 estimated_size)
+    : estimated_size_(estimated_size) {
   impl_ = NULL;
   opened_ = false;
   int fd = open(name, O_CREAT|O_RDWR|O_TRUNC, 0644);
@@ -131,6 +132,12 @@
 }
 
 int MappedOutputFile::Close(int size) {
+  if (size > estimated_size_) {
+    snprintf(errmsg, MAX_ERROR, "size %d > estimated size %lld", size,
+             estimated_size_);
+    errmsg_ = errmsg;
+    return -1;
+  }
   munmap(buffer_, impl_->mmap_length_);
   if (ftruncate(impl_->fd_, size) < 0) {
     snprintf(errmsg, MAX_ERROR, "ftruncate(): %s", strerror(errno));
diff --git a/third_party/ijar/test/BUILD b/third_party/ijar/test/BUILD
index d9ab5f7..62b6edd 100644
--- a/third_party/ijar/test/BUILD
+++ b/third_party/ijar/test/BUILD
@@ -109,6 +109,14 @@
     tools = ["//third_party/ijar"],
 )
 
+genrule(
+    name = "empty_with_target_label",
+    srcs = [":empty_zip.jar"],
+    outs = ["empty_with_target_label.jar"],
+    cmd = "$(location //third_party/ijar) $< $@ --target_label //empty",
+    tools = ["//third_party/ijar"],
+)
+
 java_library(
     name = "typeannotations2",
     srcs = glob(["typeannotations2/**/*.java"]),
@@ -187,6 +195,7 @@
         "UseDeprecatedParts.java",
         "UseRestrictedAnnotation.java",
         "package-info.java",
+        ":empty_with_target_label",
         ":interface_ijar_testlib",
         ":interface_ijar_testlib_with_target_label",
         ":liblocal_and_anonymous_lib.jar",
@@ -234,6 +243,7 @@
         ("largest_regular", 65535),
         ("smallest_zip64", 65536),
         ("definitely_zip64", 70000),
+        ("empty_zip", 1),
     ]
 ]
 
diff --git a/third_party/ijar/test/IjarTests.java b/third_party/ijar/test/IjarTests.java
index a2e10db..dc6c020 100644
--- a/third_party/ijar/test/IjarTests.java
+++ b/third_party/ijar/test/IjarTests.java
@@ -292,4 +292,16 @@
               LocalDateTime.of(2010, 1, 1, 0, 0, 0).atZone(ZoneOffset.systemDefault()).toInstant());
     }
   }
+
+  @Test
+  public void testEmptyWithTargetLabel() throws Exception {
+    try (JarFile jf = new JarFile("third_party/ijar/test/empty_with_target_label.jar")) {
+      Manifest manifest = jf.getManifest();
+      Attributes attributes = manifest.getMainAttributes();
+      assertThat(attributes.getValue("Target-Label")).isEqualTo("//empty");
+      assertThat(jf.getEntry(JarFile.MANIFEST_NAME).getLastModifiedTime().toInstant())
+          .isEqualTo(
+              LocalDateTime.of(2010, 1, 1, 0, 0, 0).atZone(ZoneOffset.systemDefault()).toInstant());
+    }
+  }
 }