blob: 9158b7900b04a6cebaa44dd03d3a13963a19e559 [file] [log] [blame]
// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "login_manager/system_utils_impl.h"
#include <errno.h>
#include <signal.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <time.h>
#include <unistd.h>
#include <limits>
#include <string>
#include <vector>
#include <base/basictypes.h>
#include <base/file_path.h>
#include <base/file_util.h>
#include <base/files/scoped_temp_dir.h>
#include <base/logging.h>
#include <base/time.h>
#include <chromeos/dbus/dbus.h>
#include <chromeos/dbus/service_constants.h>
#include <chromeos/process.h>
#include <dbus/dbus-glib-lowlevel.h>
#include <dbus/dbus-glib.h>
#include <dbus/dbus.h>
#include <glib.h>
#include "login_manager/scoped_dbus_pending_call.h"
using std::string;
using std::vector;
namespace login_manager {
namespace gobject {
struct SessionManager;
}
const char SystemUtilsImpl::kSignalSuccess[] = "success";
const char SystemUtilsImpl::kSignalFailure[] = "failure";
SystemUtilsImpl::SystemUtilsImpl() {}
SystemUtilsImpl::~SystemUtilsImpl() {}
int SystemUtilsImpl::IsDevMode() {
int dev_mode_code = system("crossystem 'cros_debug?0'");
if (WIFEXITED(dev_mode_code)) {
return WEXITSTATUS(dev_mode_code);
}
return -1;
}
int SystemUtilsImpl::kill(pid_t pid, uid_t owner, int signal) {
LOG(INFO) << "Sending " << signal << " to " << pid << " as " << owner;
uid_t uid, euid, suid;
getresuid(&uid, &euid, &suid);
if (setresuid(owner, owner, -1)) {
PLOG(ERROR) << "Couldn't assume uid " << owner;
return -1;
}
int ret = ::kill(pid, signal);
if (setresuid(uid, euid, -1)) {
PLOG(ERROR) << "Couldn't return to root";
return -1;
}
return ret;
}
time_t SystemUtilsImpl::time(time_t* t) {
return ::time(t);
}
pid_t SystemUtilsImpl::fork() {
return ::fork();
}
bool SystemUtilsImpl::ChildIsGone(pid_t child_spec, base::TimeDelta timeout) {
base::TimeTicks start = base::TimeTicks::Now();
base::TimeDelta elapsed;
int ret;
DCHECK(timeout.InSeconds() >= 0);
DCHECK(timeout.InSeconds() <=
static_cast<int64>(std::numeric_limits<int>::max()));
alarm(static_cast<int32>(timeout.InSeconds()));
do {
errno = 0;
ret = ::waitpid(child_spec, NULL, 0);
elapsed = base::TimeTicks::Now() - start;
} while (ret > 0 || (errno == EINTR && elapsed < timeout));
// Once we exit the loop, we know there was an error.
alarm(0);
return errno == ECHILD; // EINTR means we timed out.
}
bool SystemUtilsImpl::EnsureAndReturnSafeFileSize(const base::FilePath& file,
int32* file_size_32) {
// Get the file size (must fit in a 32 bit int for NSS).
int64 file_size;
if (!file_util::GetFileSize(file, &file_size)) {
LOG(ERROR) << "Could not get size of " << file.value();
return false;
}
if (file_size > static_cast<int64>(std::numeric_limits<int>::max())) {
LOG(ERROR) << file.value() << "is "
<< file_size << "bytes!!! Too big!";
return false;
}
*file_size_32 = static_cast<int32>(file_size);
return true;
}
bool SystemUtilsImpl::Exists(const base::FilePath& file) {
return file_util::PathExists(file);
}
bool SystemUtilsImpl::CreateReadOnlyFileInTempDir(base::FilePath* temp_file) {
if (!temp_dir_.IsValid() && !temp_dir_.CreateUniqueTempDir())
return false;
base::FilePath local_temp_file;
if (file_util::CreateTemporaryFileInDir(temp_dir_.path(), &local_temp_file)) {
if (chmod(local_temp_file.value().c_str(), 0644) == 0) {
*temp_file = local_temp_file;
return true;
} else {
PLOG(ERROR) << "Can't chmod " << local_temp_file.value() << " to 0644.";
}
RemoveFile(local_temp_file);
}
return false;
}
bool SystemUtilsImpl::GetUniqueFilenameInWriteOnlyTempDir(
base::FilePath* temp_file_path) {
// Create a temporary directory to put the testing channel in.
// It will be made write-only below; we need to be able to read it
// when trying to create a unique name inside it.
base::FilePath temp_dir_path;
if (!file_util::CreateNewTempDirectory(
FILE_PATH_LITERAL(""), &temp_dir_path)) {
PLOG(ERROR) << "Can't create temp dir";
return false;
}
// Create a temporary file in the temporary directory, to be deleted later.
// This ensures a unique name.
if (!file_util::CreateTemporaryFileInDir(temp_dir_path, temp_file_path)) {
PLOG(ERROR) << "Can't get temp file name in " << temp_dir_path.value();
return false;
}
// Now, allow access to non-root processes.
if (chmod(temp_dir_path.value().c_str(), 0333)) {
PLOG(ERROR) << "Can't chmod " << temp_file_path->value() << " to 0333.";
return false;
}
if (!RemoveFile(*temp_file_path)) {
PLOG(ERROR) << "Can't clear temp file in " << temp_file_path->value();
return false;
}
return true;
}
bool SystemUtilsImpl::RemoveFile(const base::FilePath& filename) {
if (file_util::DirectoryExists(filename))
return false;
return file_util::Delete(filename, false);
}
bool SystemUtilsImpl::AtomicFileWrite(const base::FilePath& filename,
const char* data,
int size) {
base::FilePath scratch_file;
if (!file_util::CreateTemporaryFileInDir(filename.DirName(), &scratch_file))
return false;
if (file_util::WriteFile(scratch_file, data, size) != size)
return false;
return (file_util::ReplaceFile(scratch_file, filename) &&
file_util::SetPosixFilePermissions(filename,
(S_IRUSR | S_IWUSR | S_IROTH)));
}
void SystemUtilsImpl::EmitSignal(const char* signal_name) {
EmitSignalWithStringArgs(signal_name, vector<string>());
}
void SystemUtilsImpl::EmitSignalWithStringArgs(const char* signal_name,
const vector<string>& payload) {
EmitSignalFrom(chromium::kChromiumInterface, signal_name, payload);
EmitSignalFrom(login_manager::kSessionManagerInterface, signal_name, payload);
}
void SystemUtilsImpl::EmitStatusSignal(const char* signal_name, bool status) {
EmitSignalFrom(chromium::kChromiumInterface, signal_name,
vector<string>(1, status ? kSignalSuccess : kSignalFailure));
EmitSignalFrom(login_manager::kSessionManagerInterface, signal_name,
vector<string>(1, status ? kSignalSuccess : kSignalFailure));
}
void SystemUtilsImpl::CallMethodOnPowerManager(const char* method_name) {
CallMethodOn(power_manager::kPowerManagerServiceName,
power_manager::kPowerManagerServicePath,
power_manager::kPowerManagerInterface,
method_name);
}
scoped_ptr<ScopedDBusPendingCall> SystemUtilsImpl::CallAsyncMethodOnChromium(
const char* method_name) {
DBusMessage* method = dbus_message_new_method_call(
chromeos::kLibCrosServiceName,
chromeos::kLibCrosServicePath,
chromeos::kLibCrosServiceInterface,
method_name);
DBusConnection* connection = dbus_g_connection_get_connection(
chromeos::dbus::GetSystemBusConnection().g_connection());
DBusPendingCall* to_return = NULL;
// Only fails on OOM conditions.
CHECK(dbus_connection_send_with_reply(connection, method, &to_return,
DBUS_TIMEOUT_INFINITE));
dbus_message_unref(method);
return ScopedDBusPendingCall::Create(to_return);
}
bool SystemUtilsImpl::CheckAsyncMethodSuccess(DBusPendingCall* pending_call) {
DCHECK(pending_call);
// This returns NULL if |pending_call| hasn't completed yet.
DBusMessage* reply = dbus_pending_call_steal_reply(pending_call);
if (!reply) {
CancelAsyncMethodCall(pending_call);
return false;
}
DBusError error;
dbus_error_init(&error);
// By definition, getting a method_return message means the async
// method completed successfully.
bool to_return =
dbus_message_get_type(reply) == DBUS_MESSAGE_TYPE_METHOD_RETURN;
if (dbus_set_error_from_message(&error, reply))
LOG(ERROR) << "Liveness ping resulted in an error: " << error.message;
if (dbus_error_is_set(&error))
dbus_error_free(&error);
dbus_message_unref(reply);
return to_return;
}
void SystemUtilsImpl::CancelAsyncMethodCall(DBusPendingCall* pending_call) {
DCHECK(pending_call);
dbus_pending_call_cancel(pending_call);
}
void SystemUtilsImpl::EmitSignalFrom(const char* interface,
const char* signal_name,
const vector<string>& payload) {
chromeos::dbus::Proxy proxy(chromeos::dbus::GetSystemBusConnection(),
login_manager::kSessionManagerServicePath,
interface);
if (!proxy) {
LOG(ERROR) << "No proxy; can't signal " << interface;
return;
}
DBusMessage* signal = ::dbus_message_new_signal(
login_manager::kSessionManagerServicePath, interface, signal_name);
if (!payload.empty()) {
for (vector<string>::const_iterator it = payload.begin();
it != payload.end();
++it) {
// Need to be able to take the address of the value to append.
const char* string_arg = it->c_str();
dbus_message_append_args(signal,
DBUS_TYPE_STRING, &string_arg,
DBUS_TYPE_INVALID);
}
}
::dbus_g_proxy_send(proxy.gproxy(), signal, NULL);
::dbus_message_unref(signal);
}
void SystemUtilsImpl::CallMethodOn(const char* destination,
const char* path,
const char* interface,
const char* method_name) {
chromeos::dbus::Proxy proxy(chromeos::dbus::GetSystemBusConnection(),
destination, path, interface);
if (!proxy) {
LOG(ERROR) << "No proxy; can't call " << interface << "." << method_name;
return;
}
GError* error = NULL;
::dbus_g_proxy_call(proxy.gproxy(), method_name, &error, G_TYPE_INVALID,
G_TYPE_INVALID);
if (error)
LOG(ERROR) << interface << "." << path << " failed: " << error->message;
}
void SystemUtilsImpl::AppendToClobberLog(const char* msg) const {
chromeos::ProcessImpl appender;
appender.AddArg("/sbin/clobber-log");
appender.AddArg("--");
appender.AddArg(msg);
appender.Run();
}
void SystemUtilsImpl::SetAndSendGError(ChromeOSLoginError code,
DBusGMethodInvocation* context,
const char* msg) {
GError* error = NULL;
g_set_error(&error, CHROMEOS_LOGIN_ERROR, code, "Login error: %s", msg);
dbus_g_method_return_error(context, error);
g_error_free(error);
}
} // namespace login_manager