Skip to content

Commit a759ea1

Browse files
committed
Revise the repo locker.
Now if the process holding the lock has exited without cleanup, it won't block future access.
1 parent 4e84a34 commit a759ea1

File tree

6 files changed

+76
-12
lines changed

6 files changed

+76
-12
lines changed

sources/full_format.cpp

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
#include <absl/container/inlined_vector.h>
1010
#include <absl/strings/numbers.h>
11+
#include <absl/strings/str_cat.h>
1112
#include <absl/strings/str_split.h>
1213

1314
#include <cerrno>
@@ -674,4 +675,42 @@ void FuseHighLevelOps::postprocess_stat(fuse_stat* st)
674675
}
675676
}
676677

678+
void RepoLocker::check_lock_file()
679+
{
680+
lock_stream_ = open_lock_stream_checked();
681+
auto pid = absl::StrCat(OSService::get_current_process_id());
682+
lock_stream_->resize(0);
683+
lock_stream_->write(pid.data(), 0, pid.size());
684+
}
685+
686+
std::shared_ptr<FileStream> RepoLocker::open_lock_stream_checked()
687+
{
688+
try
689+
{
690+
return root_.open_file_stream(kLockFileName, O_RDWR | O_CREAT | O_EXCL, 0644);
691+
}
692+
catch (const ExceptionBase& e)
693+
{
694+
if (e.error_number() != EEXIST)
695+
{
696+
throw;
697+
}
698+
699+
auto result = root_.open_file_stream(kLockFileName, O_RDWR, 0644);
700+
pid_t pid{};
701+
if (absl::SimpleAtoi(result->as_string(), &pid) && OSService::is_process_running(pid))
702+
{
703+
ERROR_LOG("Failed to acquire lock file %s. Process %d is holding "
704+
"the lock.",
705+
root_.norm_path_narrowed(kLockFileName),
706+
pid);
707+
throw;
708+
}
709+
else
710+
{
711+
return result;
712+
}
713+
}
714+
}
715+
677716
} // namespace securefs::full_format

sources/full_format.h

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -27,17 +27,7 @@ class RepoLocker
2727
{
2828
return;
2929
}
30-
try
31-
{
32-
lock_stream_ = root_.open_file_stream(kLockFileName, O_RDONLY | O_CREAT | O_EXCL, 0644);
33-
}
34-
catch (const std::exception& e)
35-
{
36-
ERROR_LOG("Failed to acquire lock file %s. Perhaps another securefs process is holding "
37-
"the lock.",
38-
root_.norm_path_narrowed(kLockFileName));
39-
throw;
40-
}
30+
check_lock_file();
4131
}
4232

4333
~RepoLocker()
@@ -53,6 +43,9 @@ class RepoLocker
5343
private:
5444
DISABLE_COPY_MOVE(RepoLocker)
5545

46+
void check_lock_file();
47+
std::shared_ptr<FileStream> open_lock_stream_checked();
48+
5649
OSService& root_;
5750
std::shared_ptr<FileStream> lock_stream_;
5851
};

sources/platform.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,7 @@ class OSService
253253
static std::string stringify_system_error(int errcode);
254254
static void enter_background();
255255
static bool is_process_running(pid_t pid);
256+
static pid_t get_current_process_id();
256257
};
257258

258259
struct Colour

sources/unix.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -666,6 +666,7 @@ void OSService::read_password_with_confirmation(const char* prompt,
666666
}
667667

668668
void OSService::enter_background() { daemon(true, false); }
669+
pid_t OSService::get_current_process_id() { return getpid(); }
669670

670671
// These two overloads are used to distinguish the GNU and XSI version of strerror_r
671672

sources/win.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1347,6 +1347,8 @@ bool OSService::is_process_running(pid_t pid)
13471347
// we'll assume if it's not ERROR_INVALID_PARAMETER, the PID likely exists.
13481348
return true;
13491349
}
1350+
pid_t OSService::get_current_process_id() { return GetCurrentProcessId(); }
1351+
13501352
} // namespace securefs
13511353

13521354
#endif

test/simple_test.py

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,9 +112,15 @@ def securefs_mount(
112112
)
113113
try:
114114
for _ in range(600):
115-
time.sleep(0.1)
115+
try:
116+
p.wait(timeout=0.1)
117+
except subprocess.TimeoutExpired:
118+
pass
119+
if p.returncode:
120+
raise subprocess.CalledProcessError(p.returncode, p.args)
116121
if is_mount_then_statvfs(mount_point):
117122
return p
123+
118124
raise TimeoutError(f"Failed to mount {repr(mount_point)} after many attempts")
119125
except:
120126
securefs_unmount(p=p, mount_point=mount_point)
@@ -882,6 +888,28 @@ def test_regression(self):
882888
securefs_unmount(p, mount_point)
883889

884890

891+
class RepoLockerTestCase(unittest.TestCase):
892+
def test_locked(self):
893+
data_dir = get_data_dir(fmt=RepoFormat.FULL)
894+
securefs_create(data_dir=data_dir, fmt=RepoFormat.FULL, password="123")
895+
mount_point = get_mount_point()
896+
with open(os.path.join(data_dir, ".securefs.lock"), "w") as f:
897+
f.write(str(os.getpid()))
898+
with self.assertRaises(subprocess.CalledProcessError):
899+
p = securefs_mount(
900+
data_dir=data_dir, mount_point=mount_point, password="123"
901+
)
902+
903+
def test_locker_died(self):
904+
data_dir: str = get_data_dir(fmt=RepoFormat.FULL)
905+
securefs_create(data_dir=data_dir, fmt=RepoFormat.FULL, password="123")
906+
mount_point = get_mount_point()
907+
with open(os.path.join(data_dir, ".securefs.lock"), "w") as f:
908+
f.write(str(2**31 - 1))
909+
p = securefs_mount(data_dir=data_dir, mount_point=mount_point, password="123")
910+
securefs_unmount(p, mount_point)
911+
912+
885913
def list_dir_recursive(dirname: str, relpath=False) -> Set[str]:
886914
# Note: os.walk does not work on Windows when crossing filesystem boundary.
887915
# So we use this crude version instead.

0 commit comments

Comments
 (0)