unify the StartupLogger code on Windows and POSIX

Summary:
Update the Windows code to use the normal StartupLogger.h and .cpp files.
Previously the Windows code defined its own separate StartupLogger.h file.
The class declaration looked largely like a copy of the POSIX
DaemonStartupLogger code, but most of the methods were not actually defined
anywhere.  The actual functionality appears identical to the POSIX
ForegroundStartupLogger, so this code just switches to using that in most
cases.

Reviewed By: xavierd

Differential Revision: D21332570

fbshipit-source-id: 0585a38d8f37dc93459d380aa277082c35cbadfc
This commit is contained in:
Adam Simpkins 2020-04-30 23:34:57 -07:00 committed by Facebook GitHub Bot
parent e648c26999
commit 268d64cf3f
7 changed files with 91 additions and 236 deletions

View File

@ -52,8 +52,6 @@ if (WIN32)
list(
REMOVE_ITEM SERVICE_SRCS
${CMAKE_CURRENT_SOURCE_DIR}/EdenMain.cpp
${CMAKE_CURRENT_SOURCE_DIR}/StartupLogger.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Systemd.cpp
)
endif()

View File

@ -213,8 +213,7 @@ int runEdenMain(EdenMain&& main, int argc, char** argv) {
}
auto logPath = getLogPath(edenConfig->edenDir.getValue());
auto startupLogger =
std::shared_ptr<StartupLogger>{daemonizeIfRequested(logPath)};
auto startupLogger = daemonizeIfRequested(logPath);
XLOG(DBG3) << edenConfig->toString();
std::optional<EdenServer> server;
auto prepareFuture = folly::Future<folly::Unit>::makeEmpty();

View File

@ -35,6 +35,7 @@
#include "eden/fs/service/EdenCPUThreadPool.h"
#include "eden/fs/service/EdenError.h"
#include "eden/fs/service/EdenServiceHandler.h"
#include "eden/fs/service/StartupLogger.h"
#include "eden/fs/service/gen-cpp2/eden_types.h"
#include "eden/fs/store/BlobCache.h"
#include "eden/fs/store/EmptyBackingStore.h"
@ -57,7 +58,6 @@
#ifdef _WIN32
#include "eden/fs/win/mount/PrjfsChannel.h" // @manual
#include "eden/fs/win/service/StartupLogger.h" // @manual
#include "eden/fs/win/utils/FileUtils.h" // @manual
#include "eden/fs/win/utils/Stub.h" // @manual
#else
@ -67,7 +67,6 @@
#include "eden/fs/inodes/InodeMap.h"
#include "eden/fs/inodes/Overlay.h"
#include "eden/fs/inodes/TreeInode.h"
#include "eden/fs/service/StartupLogger.h"
#include "eden/fs/store/RocksDbLocalStore.h"
#include "eden/fs/takeover/TakeoverClient.h"
#include "eden/fs/takeover/TakeoverData.h"

View File

@ -13,15 +13,15 @@
#include <folly/FileUtil.h>
#include <folly/String.h>
#include <folly/logging/xlog.h>
#include <folly/portability/Unistd.h>
#include <gflags/gflags.h>
#include <sys/types.h>
#ifdef _WIN32
#include <folly/portability/Unistd.h>
#include "eden/fs/win/utils/WinError.h" // @manual
#else
#include <sys/wait.h>
#include <sysexits.h>
#include <unistd.h>
#endif
#include "eden/fs/eden-config.h"
@ -42,18 +42,16 @@ DEFINE_string(
namespace {
void writeMessageToFile(folly::File&, folly::StringPiece);
}
#ifdef _WIN32
void redirectOutput(folly::StringPiece);
#endif // _WIN32
} // namespace
std::unique_ptr<StartupLogger> daemonizeIfRequested(
std::shared_ptr<StartupLogger> daemonizeIfRequested(
folly::StringPiece logPath) {
if (FLAGS_foreground) {
if (!FLAGS_startupLogPath.empty()) {
return std::make_unique<FileStartupLogger>(FLAGS_startupLogPath);
}
auto startupLogger = std::make_unique<ForegroundStartupLogger>();
return startupLogger;
} else {
auto startupLogger = std::make_unique<DaemonStartupLogger>();
#ifndef _WIN32
if (!FLAGS_foreground) {
auto startupLogger = std::make_shared<DaemonStartupLogger>();
if (!FLAGS_startupLogPath.empty()) {
startupLogger->warn(
"Ignoring --startupLogPath because --foreground was not specified");
@ -61,6 +59,20 @@ std::unique_ptr<StartupLogger> daemonizeIfRequested(
startupLogger->daemonize(logPath);
return startupLogger;
}
#else
// On Windows we always run as a foreground process, regardless of the value
// of FLAGS_foreground. The main difference is that FLAGS_foreground will
// force logPath to be empty, which causes us to log to stderr. Without
// FLAGS_foreground set we redirect our output to the specified log file.
if (!logPath.empty()) {
redirectOutput(logPath);
}
#endif // _WIN32
if (!FLAGS_startupLogPath.empty()) {
return std::make_shared<FileStartupLogger>(FLAGS_startupLogPath);
}
return std::make_shared<ForegroundStartupLogger>();
}
StartupLogger::~StartupLogger() = default;
@ -85,6 +97,7 @@ void StartupLogger::writeMessage(folly::LogLevel level, StringPiece message) {
writeMessageImpl(level, message);
}
#ifndef _WIN32
void DaemonStartupLogger::successImpl() {
if (!logPath_.empty()) {
writeMessage(
@ -341,6 +354,7 @@ DaemonStartupLogger::ParentResult DaemonStartupLogger::handleChildCrash(
return ParentResult(EX_SOFTWARE, msg);
}
}
#endif // !_WIN32
void ForegroundStartupLogger::writeMessageImpl(folly::LogLevel, StringPiece) {}
@ -368,6 +382,7 @@ void FileStartupLogger::successImpl() {}
}
namespace {
void writeMessageToFile(folly::File& file, folly::StringPiece message) {
std::array<iovec, 2> iov;
iov[0].iov_base = const_cast<char*>(message.data());
@ -380,6 +395,52 @@ void writeMessageToFile(folly::File& file, folly::StringPiece message) {
// There is not much we can do if it fails.
(void)folly::writevFull(file.fd(), iov.data(), iov.size());
}
#ifdef _WIN32
void redirectOutput(folly::StringPiece logPath) {
auto logPathStr = logPath.str();
HANDLE newHandle = CreateFileA(
logPathStr.c_str(),
FILE_APPEND_DATA,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
nullptr,
OPEN_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
nullptr);
if (newHandle == INVALID_HANDLE_VALUE) {
throw makeWin32ErrorExplicit(
GetLastError(),
folly::sformat("Unable to open the log file {}\n", logPath));
}
// Don't close the previous handles here, it will be closed as part of _dup2
// call.
SetStdHandle(STD_OUTPUT_HANDLE, newHandle);
SetStdHandle(STD_ERROR_HANDLE, newHandle);
int fd = _open_osfhandle(reinterpret_cast<intptr_t>(newHandle), _O_APPEND);
if (fd == -1) {
throw std::runtime_error(
"_open_osfhandle() returned -1 while opening logfile");
}
SCOPE_EXIT {
_close(fd);
};
if (_dup2(fd, _fileno(stderr)) == -1) {
throw std::runtime_error(
folly::format("Dup failed to update stderr. errno: {}", errno).str());
}
if (_dup2(fd, _fileno(stdout)) == -1) {
throw std::runtime_error(
folly::format("Dup failed to update stdout. errno: {}", errno).str());
}
}
#endif // _WIN32
} // namespace
} // namespace eden

View File

@ -23,7 +23,7 @@ namespace eden {
class StartupLogger;
std::unique_ptr<StartupLogger> daemonizeIfRequested(folly::StringPiece logPath);
std::shared_ptr<StartupLogger> daemonizeIfRequested(folly::StringPiece logPath);
/**
* StartupLogger provides an API for logging messages that should be displayed
@ -94,6 +94,8 @@ class StartupLogger {
[[noreturn]] virtual void failAndExitImpl(uint8_t exitCode) = 0;
};
#ifndef _WIN32
class DaemonStartupLogger : public StartupLogger {
public:
DaemonStartupLogger() = default;
@ -177,6 +179,7 @@ class DaemonStartupLogger : public StartupLogger {
// completed daemon startup.
folly::File pipe_;
};
#endif // !_WIN32
class ForegroundStartupLogger : public StartupLogger {
public:

View File

@ -1,158 +0,0 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This software may be used and distributed according to the terms of the
* GNU General Public License version 2.
*/
#pragma once
#include <folly/Conv.h>
#include <folly/File.h>
#include <folly/Range.h>
#include <folly/logging/LogLevel.h>
#include <memory>
namespace facebook {
namespace eden {
/**
* StartupLogger provides an API for logging messages that should be displayed
* to the user while edenfs is starting.
*
* If edenfs is daemonizing, the original foreground process will not exit until
* success() or fail() is called. Any messages logged with log() or warn() will
* be shown printed in the original foreground process.
*/
class StartupLogger {
public:
StartupLogger() {} //= default;
/**
* daemonize the current process.
*
* This method returns in a new process. This method will never return in the
* parent process that originally called daemonize(). Instead the parent
* waits for the child process to either call StartupLogger::success() or
* StartupLogger::fail(), and exits with a status code based on which of these
* was called.
*
* If logPath is non-empty the child process will redirect its stdout and
* stderr file descriptors to the specified log file before returning.
*/
void daemonize(folly::StringPiece logPath) {}
/**
* Log an informational message.
*/
template <typename... Args>
void log(Args&&... args) {
// writeMessage(
// origStdout_,
// folly::LogLevel::DBG2,
// folly::to<std::string>(std::forward<Args>(args)...));
}
/**
* Log a warning message.
*/
template <typename... Args>
void warn(Args&&... args) {
// writeMessage(
// origStderr_,
// folly::LogLevel::WARN,
// folly::to<std::string>(std::forward<Args>(args)...));
}
/**
* Indicate that startup has failed.
*
* This exits the current process, and also causes the original foreground
* process to exit if edenfs has daemonized.
*/
template <typename... Args>
[[noreturn]] void exitUnsuccessfully(uint8_t exitCode, Args&&... args) {
writeMessage(
origStderr_,
folly::LogLevel::ERR,
folly::to<std::string>(std::forward<Args>(args)...));
failAndExit(exitCode);
}
/**
* Indicate that startup has succeeded.
*
* If edenfs has daemonized this will cause the original foreground edenfs
* process to exit successfully.
*/
void success() {}
private:
friend class StartupLoggerTest;
using ResultType = uint8_t;
struct ParentResult {
template <typename... Args>
explicit ParentResult(uint8_t code, Args&&... args)
: exitCode(code),
errorMessage(folly::to<std::string>(std::forward<Args>(args)...)) {}
int exitCode;
std::string errorMessage;
};
[[noreturn]] void failAndExit(uint8_t exitCode);
std::optional<std::pair<pid_t, folly::File>> daemonizeImpl(
folly::StringPiece logPath);
/**
* Create the pipe for communication between the parent process and the
* daemonized child. Stores the write end in pipe_ and returns the read end.
*/
folly::File createPipe();
[[noreturn]] void runParentProcess(
folly::File readPipe,
pid_t childPid,
folly::StringPiece logPath);
void prepareChildProcess(folly::StringPiece logPath);
void redirectOutput(folly::StringPiece logPath);
/**
* Wait for the child process to write its initialization status.
*/
ParentResult waitForChildStatus(
const folly::File& pipe,
pid_t childPid,
folly::StringPiece logPath);
ParentResult handleChildCrash(pid_t childPid);
void writeMessage(
const folly::File& file,
folly::LogLevel level,
folly::StringPiece message) {}
void sendResult(ResultType result);
// If stdout and stderr have been redirected during process daemonization,
// origStdout_ and origStderr_ contain file descriptors referencing the
// original stdout and stderr. These are used to continue to print
// informational messages directly to the user during startup even after
// normal log redirection.
//
// If log redirection has not occurred these will simply be closed File
// objects. The normal logging mechanism is sufficient to show messages to
// the user in this case.
folly::File origStdout_;
folly::File origStderr_;
std::string logPath_;
// If we have daemonized, pipe_ is a pipe connected to the original foreground
// process. We use this to inform the original process when we have fully
// completed daemon startup.
folly::File pipe_;
};
} // namespace eden
} // namespace facebook

View File

@ -18,8 +18,8 @@
#include "eden/fs/model/Tree.h"
#include "eden/fs/service/EdenInit.h"
#include "eden/fs/service/EdenServer.h"
#include "eden/fs/service/StartupLogger.h"
#include "eden/fs/telemetry/SessionInfo.h"
#include "eden/fs/win/service/StartupLogger.h"
#include "eden/fs/win/utils/StringConv.h"
#include "folly/io/IOBuf.h"
#include "folly/portability/Windows.h"
@ -56,50 +56,6 @@ namespace {
constexpr StringPiece kEdenVersion = "edenwin";
void redirectLogOutput(const char* logPath) {
HANDLE newHandle = CreateFileA(
logPath,
FILE_APPEND_DATA,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
nullptr,
OPEN_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
nullptr);
if (newHandle == INVALID_HANDLE_VALUE) {
throw makeWin32ErrorExplicit(
GetLastError(),
folly::sformat("Unable to open the log file {}\n", logPath));
}
// Don't close the previous handles here, it will be closed as part of _dup2
// call.
SetStdHandle(STD_OUTPUT_HANDLE, newHandle);
SetStdHandle(STD_ERROR_HANDLE, newHandle);
int fd = _open_osfhandle(reinterpret_cast<intptr_t>(newHandle), _O_APPEND);
if (fd == -1) {
throw std::runtime_error(
"_open_osfhandle() returned -1 while opening logfile");
}
if (_dup2(fd, _fileno(stderr)) == -1) {
throw std::runtime_error(
folly::format("Dup failed to update stderr. errno: {}", errno).str());
}
if (_dup2(fd, _fileno(stdout)) == -1) {
throw std::runtime_error(
folly::format("Dup failed to update stdout. errno: {}", errno).str());
}
SCOPE_EXIT {
_close(fd);
};
}
} // namespace
int __cdecl main(int argc, char** argv) {
@ -120,19 +76,6 @@ int __cdecl main(int argc, char** argv) {
return -1;
}
try {
auto logPath = getLogPath(edenConfig->edenDir.getValue());
if (!logPath.empty()) {
redirectLogOutput(logPath.c_str());
}
} catch (std::exception& ex) {
// If the log redirection fails this error will show up on stderr. When Eden
// is running in the background, this error will be lost. If the log file is
// empty we should run the edenfs.exe on the console to get the error.
fprintf(stderr, "%s\n", ex.what());
return -1;
}
// Set some default glog settings, to be applied unless overridden on the
// command line
gflags::SetCommandLineOptionWithMode(
@ -141,7 +84,17 @@ int __cdecl main(int argc, char** argv) {
"minloglevel", "0", gflags::SET_FLAGS_DEFAULT);
auto prepareFuture = folly::Future<folly::Unit>::makeEmpty();
auto startupLogger = std::make_shared<StartupLogger>();
std::shared_ptr<StartupLogger> startupLogger;
try {
auto logPath = getLogPath(edenConfig->edenDir.getValue());
startupLogger = daemonizeIfRequested(logPath);
} catch (std::exception& ex) {
// If the log redirection fails this error will show up on stderr. When Eden
// is running in the background, this error will be lost. If the log file is
// empty we should run the edenfs.exe on the console to get the error.
fprintf(stderr, "%s\n", ex.what());
return -1;
}
SessionInfo sessionInfo;
sessionInfo.username = identity.getUsername();