@@ -126,6 +126,64 @@ class offline_compaction_test : public compaction_test {
126126 }
127127 }
128128
129+ // Shared driver for the cross-filesystem working-directory tests. Prepares a log
130+ // directory with blob data, then runs offline compaction with --working_dir on a
131+ // different filesystem (tmpfs under /dev/shm), optionally with --make_backup.
132+ // Compaction must be rejected up front (before any destructive step) and must leave the
133+ // original log directory fully intact. Skips when /dev/shm is unavailable or happens to
134+ // be on the same filesystem as the location.
135+ void run_cross_filesystem_working_dir_case (bool make_backup) {
136+ if (!boost::filesystem::exists (" /dev/shm" )) {
137+ GTEST_SKIP () << " /dev/shm is not available on this system" ;
138+ }
139+
140+ gen_datastore ();
141+ datastore_->switch_epoch (1 );
142+ lc0_->begin_session ();
143+ lc0_->add_entry (1 , " blob_key" , " blob_value" , {1 , 0 }, {1001 });
144+ lc0_->end_session ();
145+ create_dummy_blob_files (1001 );
146+ datastore_->set_next_blob_id (1002 );
147+ datastore_->switch_epoch (2 );
148+ datastore_->shutdown ();
149+ datastore_ = nullptr ;
150+
151+ std::map<std::string, std::string> before = snapshot_tree (location);
152+
153+ // Prepare a working directory on another filesystem (tmpfs).
154+ std::string wd_template = " /dev/shm/offline_compaction_wd_XXXXXX" ;
155+ ASSERT_NE (::mkdtemp (wd_template.data ()), nullptr ) << strerror (errno);
156+ boost::filesystem::path working_dir{wd_template};
157+
158+ // Skip when the test location happens to live on the same filesystem as /dev/shm
159+ // (e.g. /tmp mounted on the same tmpfs); the cross-filesystem path is unreachable.
160+ struct stat st_location {};
161+ struct stat st_working_dir {};
162+ ASSERT_EQ (::stat (location, &st_location), 0 ) << strerror (errno);
163+ ASSERT_EQ (::stat (working_dir.c_str (), &st_working_dir), 0 ) << strerror (errno);
164+ if (st_location.st_dev == st_working_dir.st_dev ) {
165+ boost::filesystem::remove_all (working_dir);
166+ GTEST_SKIP () << " test location and /dev/shm are on the same filesystem" ;
167+ }
168+
169+ std::string out;
170+ std::string command = std::string (util_command) + " compaction --force" +
171+ (make_backup ? " --make_backup" : " " ) + " --working_dir=" +
172+ working_dir.string () + " " + std::string (location) + " 2>&1" ;
173+ int rc = invoke (command, out);
174+ EXPECT_NE (rc, 0 ) << " compaction should fail on a cross-filesystem working directory" ;
175+ EXPECT_NE (out.find (" working directory must be on the same filesystem as the dblogdir" ),
176+ std::string::npos)
177+ << " tglogutil output:\n " << out;
178+
179+ // The upfront rejection must leave the original log directory fully intact, and no
180+ // backup directory must have been created.
181+ EXPECT_EQ (snapshot_tree (location), before);
182+ EXPECT_TRUE (find_backup_dirs ().empty ());
183+
184+ boost::filesystem::remove_all (working_dir);
185+ }
186+
129187 // Collect the set of top-level entry names (files and directories) directly under dir.
130188 static std::set<std::string> list_top_level_entries (boost::filesystem::path const & dir) {
131189 std::set<std::string> names;
@@ -519,58 +577,16 @@ TEST_F(offline_compaction_test, offline_compaction_backup_matches_pre_compaction
519577 remove_backup_dirs ();
520578}
521579
522- // When --working_dir points to a different filesystem, moving the blob directory by
523- // rename cannot work (EXDEV), and neither can the final rename(tmp, from_dir); compaction
524- // must fail fast with a clear message and leave the original log directory untouched.
580+ // A cross-filesystem --working_dir must be rejected up front (default / move mode).
525581TEST_F (offline_compaction_test, offline_compaction_fails_when_working_dir_is_on_different_filesystem) {
526- if (!boost::filesystem::exists (" /dev/shm" )) {
527- GTEST_SKIP () << " /dev/shm is not available on this system" ;
528- }
529-
530- gen_datastore ();
531- datastore_->switch_epoch (1 );
532- lc0_->begin_session ();
533- lc0_->add_entry (1 , " blob_key" , " blob_value" , {1 , 0 }, {1001 });
534- lc0_->end_session ();
535- boost::filesystem::path blob_path = create_dummy_blob_files (1001 );
536- datastore_->set_next_blob_id (1002 );
537- datastore_->switch_epoch (2 );
538- datastore_->shutdown ();
539- datastore_ = nullptr ;
540-
541- // Prepare a working directory on another filesystem (tmpfs).
542- std::string wd_template = " /dev/shm/offline_compaction_wd_XXXXXX" ;
543- ASSERT_NE (::mkdtemp (wd_template.data ()), nullptr ) << strerror (errno);
544- boost::filesystem::path working_dir{wd_template};
545-
546- // Skip when the test location happens to live on the same filesystem as /dev/shm
547- // (e.g. /tmp mounted on the same tmpfs); rename would then succeed and the error
548- // path under test would not be reachable.
549- struct stat st_location {};
550- struct stat st_working_dir {};
551- ASSERT_EQ (::stat (location, &st_location), 0 ) << strerror (errno);
552- ASSERT_EQ (::stat (working_dir.c_str (), &st_working_dir), 0 ) << strerror (errno);
553- if (st_location.st_dev == st_working_dir.st_dev ) {
554- boost::filesystem::remove_all (working_dir);
555- GTEST_SKIP () << " test location and /dev/shm are on the same filesystem" ;
556- }
557-
558- std::string out;
559- std::string command = std::string (util_command) + " compaction --force --working_dir=" +
560- working_dir.string () + " " + std::string (location) + " 2>&1" ;
561- int rc = invoke (command, out);
562- // Compaction must fail: a cross-filesystem working directory cannot work, because both
563- // the carry-over of the log directory contents and the final rename(tmp, from_dir)
564- // require the same filesystem. We only assert the failure and that the original log
565- // directory is left intact, not a specific message: which cross-device operation trips
566- // first (carrying over the manifest, moving the blob directory, or the final rename)
567- // depends on the platform's copy/rename behavior.
568- EXPECT_NE (rc, 0 ) << " compaction should fail on a cross-filesystem working directory" ;
569-
570- // The failure must leave the original log directory untouched.
571- EXPECT_TRUE (boost::filesystem::exists (blob_path));
582+ run_cross_filesystem_working_dir_case (/* make_backup=*/ false );
583+ }
572584
573- boost::filesystem::remove_all (working_dir);
585+ // A cross-filesystem --working_dir must be rejected up front in --make_backup mode too.
586+ // Otherwise from_dir would be renamed away to the backup and the final rename(tmp, from_dir)
587+ // would fail, leaving no from_dir restored.
588+ TEST_F (offline_compaction_test, offline_compaction_with_backup_fails_when_working_dir_is_on_different_filesystem) {
589+ run_cross_filesystem_working_dir_case (/* make_backup=*/ true );
574590}
575591
576592// When the blob directory contains a broken entry that cannot be copied, the backup-mode
0 commit comments