blob: f2384bdd0842f3ff0c831ccd22b28c441fd7f3a1 [file] [log] [blame]
// Copyright 2023 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Helper for cryptohome.SequentialConsistency tast test. This program
// sequentially writes a number of files, conveniently named
// SequentialConsistencyTest.0.txt, SequentialConsistencyTest.1.txt, etc, to
// a directory specified by the command line. This is useful for testing that
// one process sees files created by another process being created in the same
// order.
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <string>
#include <base/files/file.h>
#include <base/files/file_path.h>
#include <base/logging.h>
#include <base/strings/stringprintf.h>
#include <brillo/flag_helper.h>
#include <brillo/syslog_logging.h>
namespace {
// Both of these should match sequential_consistency.go.
const int kNumFiles = 9;
const char kBasePhrase[] = "This is file #%d";
// makeFilePath here should match makeFilePath in sequential_consistency.go.
base::FilePath makeFilePath(const base::FilePath &path, int file_num) {
return path.Append(
base::StringPrintf("SequentialConsistencyTest.%d.txt", file_num));
}
bool createFile(const base::FilePath &path, int file_num, bool slowly = false) {
base::FilePath file_path = makeFilePath(path, file_num);
base::File f(file_path,
base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE);
if (!f.IsValid()) {
LOG(ERROR) << "file open(" << file_path.value()
<< "): " << base::File::ErrorToString(f.error_details());
return false;
}
std::string phrase = base::StringPrintf(kBasePhrase, file_num);
if (slowly) {
for (int i = 0; i < phrase.length(); i++) {
char c = phrase[i];
int result = f.WriteAtCurrentPos(&c, 1);
if (result != 1) {
LOG(ERROR) << file_path.value() << " WriteAtCurrentPos() returned "
<< result << ": "
<< base::File::ErrorToString(base::File::GetLastFileError());
return false;
}
}
} else {
int result = f.WriteAtCurrentPos(phrase.c_str(), phrase.length());
if (result != phrase.length()) {
LOG(ERROR) << file_path.value() << " WriteAtCurrentPos() returned "
<< result << ": "
<< base::File::ErrorToString(base::File::GetLastFileError());
return false;
}
}
return true;
}
int createFiles(bool create_dir, const base::FilePath &path) {
// sleep briefly to increase the odds that the Go half of the
// cryptohome.SequentialConsistency test is in its testing.Poll loop. This is
// not necessary for the test to be correct -- if, say, the machine is
// overloaded and the Go process doesn't schedule for 10 seconds, the test
// will still pass. It just increases the odds that if there is a problem in
// the depths of the file system driver, a particular test run will catch it.
// We don't do more careful synchronization because the ideal test run has
// the function inside the testing.Poll code running on one CPU at the
// same instant the file creation code below is running on another CPU.
// There's no way to force that with synchronization. In fact, careful
// synchronization will decrease the odds of it happening. In many cases,
// when one process releases a lock that another process is waiting for, the
// OS will context-switch to the waiting process and (if there's not enough
// CPUs available) have the releasing process end up going into a wait.
sleep(2);
if (create_dir) {
// base::CreateDirectory doesn't support permissions
if (mkdir(path.value().c_str(), 0755) != 0) {
PLOG(ERROR) << "mkdir(" << path.value() << "): ";
return EXIT_FAILURE;
}
}
int file_num = 0;
// Immediately create first three files.
for (; file_num < 3; ++file_num) {
if (!createFile(path, file_num)) {
return EXIT_FAILURE;
}
}
// Sleep briefly and create the next three. The sleep is just to exercise a
// few different scenarios (multiple rapid-fire file creation vs a delay
// between the file creations); it's not necessary for the correctness of the
// test.
sleep(2);
for (; file_num < 6; ++file_num) {
if (!createFile(path, file_num)) {
return EXIT_FAILURE;
}
}
// Write out the remaining three slowly.
for (; file_num < kNumFiles; ++file_num) {
if (!createFile(path, file_num, true)) {
return EXIT_FAILURE;
}
}
return EXIT_SUCCESS;
}
} // namespace
int main(int argc, char **argv) {
brillo::InitLog(brillo::kLogToSyslog | brillo::kLogToStderrIfTty);
DEFINE_bool(create_dir, false, "Create directory given by path");
DEFINE_string(path, "", "Directory to put files in");
brillo::FlagHelper::Init(argc, argv,
"cryptohome.SequentialConsistency tast test helper");
CHECK_NE(FLAGS_path, "");
// Fork to detach process from parent. This more closely replicates the
// conditions where errors have been seen in the wild.
pid_t fork_result = fork();
if (fork_result == -1) {
perror("fork");
return EXIT_FAILURE;
}
if (fork_result == 0) {
// Child.
return createFiles(FLAGS_create_dir, base::FilePath(FLAGS_path));
} else {
return EXIT_SUCCESS;
}
}