Add the workdir_root configuration option.

This is so that we can keep the workdir separate from the corpus database
if needed.

PiperOrigin-RevId: 736642485
diff --git a/centipede/centipede_interface.cc b/centipede/centipede_interface.cc
index a2d9653..53f8d59 100644
--- a/centipede/centipede_interface.cc
+++ b/centipede/centipede_interface.cc
@@ -435,6 +435,11 @@
           ? std::filesystem::path()
           : std::filesystem::path(fuzztest_config.stats_root) /
                 fuzztest_config.binary_identifier;
+  const auto workdir_root_path =
+      fuzztest_config.workdir_root.empty()
+          ? corpus_database_path
+          : std::filesystem::path(fuzztest_config.workdir_root) /
+                fuzztest_config.binary_identifier;
   const auto execution_stamp = [] {
     std::string stamp =
         absl::FormatTime("%Y-%m-%d-%H-%M-%S", absl::Now(), absl::UTCTimeZone());
@@ -463,7 +468,7 @@
   const auto base_workdir_path =
       is_workdir_specified
           ? std::filesystem::path{}  // Will not be used.
-          : corpus_database_path /
+          : workdir_root_path /
                 absl::StrFormat("workdir%s.%03d",
                                 fuzztest_config.only_replay ? "-replay" : "",
                                 test_shard_index);
diff --git a/fuzztest/init_fuzztest.cc b/fuzztest/init_fuzztest.cc
index 14ff2dd..da0a717 100644
--- a/fuzztest/init_fuzztest.cc
+++ b/fuzztest/init_fuzztest.cc
@@ -307,6 +307,7 @@
   return internal::Configuration{
       absl::GetFlag(FUZZTEST_FLAG(corpus_database)),
       /*stats_root=*/"",
+      /*workdir_root=*/"",
       std::string(binary_identifier),
       /*fuzz_tests=*/ListRegisteredTests(),
       /*fuzz_tests_in_current_shard=*/ListRegisteredTests(),
diff --git a/fuzztest/internal/configuration.cc b/fuzztest/internal/configuration.cc
index 3dea32f..ccb933c 100644
--- a/fuzztest/internal/configuration.cc
+++ b/fuzztest/internal/configuration.cc
@@ -203,8 +203,8 @@
   std::string time_budget_type_str = AbslUnparseFlag(time_budget_type);
   std::string out;
   out.resize(SpaceFor(corpus_database) + SpaceFor(stats_root) +
-             SpaceFor(binary_identifier) + SpaceFor(fuzz_tests) +
-             SpaceFor(fuzz_tests_in_current_shard) +
+             SpaceFor(workdir_root) + SpaceFor(binary_identifier) +
+             SpaceFor(fuzz_tests) + SpaceFor(fuzz_tests_in_current_shard) +
              SpaceFor(reproduce_findings_as_separate_tests) +
              SpaceFor(replay_coverage_inputs) + SpaceFor(only_replay) +
              SpaceFor(execution_id) + SpaceFor(print_subprocess_log) +
@@ -216,6 +216,7 @@
   size_t offset = 0;
   offset = WriteString(out, offset, corpus_database);
   offset = WriteString(out, offset, stats_root);
+  offset = WriteString(out, offset, workdir_root);
   offset = WriteString(out, offset, binary_identifier);
   offset = WriteVectorOfStrings(out, offset, fuzz_tests);
   offset = WriteVectorOfStrings(out, offset, fuzz_tests_in_current_shard);
@@ -241,6 +242,7 @@
   return [=]() mutable -> absl::StatusOr<Configuration> {
     ASSIGN_OR_RETURN(corpus_database, ConsumeString(serialized));
     ASSIGN_OR_RETURN(stats_root, ConsumeString(serialized));
+    ASSIGN_OR_RETURN(workdir_root, ConsumeString(serialized));
     ASSIGN_OR_RETURN(binary_identifier, ConsumeString(serialized));
     ASSIGN_OR_RETURN(fuzz_tests, ConsumeVectorOfStrings(serialized));
     ASSIGN_OR_RETURN(fuzz_tests_in_current_shard,
@@ -272,6 +274,7 @@
                      ParseTimeBudgetType(*time_budget_type_str));
     return Configuration{*std::move(corpus_database),
                          *std::move(stats_root),
+                         *std::move(workdir_root),
                          *std::move(binary_identifier),
                          *std::move(fuzz_tests),
                          *std::move(fuzz_tests_in_current_shard),
diff --git a/fuzztest/internal/configuration.h b/fuzztest/internal/configuration.h
index cfe82b9..fed9518 100644
--- a/fuzztest/internal/configuration.h
+++ b/fuzztest/internal/configuration.h
@@ -49,6 +49,9 @@
   // The directory path to export stats with a layout similar to
   // `corpus_database`.
   std::string stats_root;
+  // The root directory for Centipede workdirs, with a layout similar to
+  // `corpus_database`.
+  std::string workdir_root;
   // The identifier of the test binary in the corpus database (eg.,
   // relative/path/to/binary).
   std::string binary_identifier;
diff --git a/fuzztest/internal/configuration_test.cc b/fuzztest/internal/configuration_test.cc
index cc26617..16982e9 100644
--- a/fuzztest/internal/configuration_test.cc
+++ b/fuzztest/internal/configuration_test.cc
@@ -15,6 +15,7 @@
   const absl::StatusOr<Configuration>& other = arg;
   return other.ok() && config.corpus_database == other->corpus_database &&
          config.stats_root == other->stats_root &&
+         config.workdir_root == other->workdir_root &&
          config.binary_identifier == other->binary_identifier &&
          config.fuzz_tests == other->fuzz_tests &&
          config.fuzz_tests_in_current_shard ==
@@ -41,6 +42,7 @@
      DeserializeYieldsSerializedConfigurationWithoutOptionalValues) {
   Configuration configuration{"corpus_database",
                               "stats_root",
+                              "workdir_root",
                               "binary_identifier",
                               /*fuzz_tests=*/{},
                               /*fuzz_tests_in_current_shard=*/{},
@@ -66,6 +68,7 @@
      DeserializeYieldsSerializedConfigurationWithOptionalValues) {
   Configuration configuration{"corpus_database",
                               "stats_root",
+                              "workdir_root",
                               "binary_identifier",
                               {"FuzzTest1", "FuzzTest2"},
                               {"FuzzTest1"},