blob: 9b434396453b91d41073a9cd7db08427b5d47184 [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.
// Contains the implementation of class Platform
#include "platform.h"
#include <errno.h>
#include <fcntl.h>
#include <grp.h>
#include <limits.h>
#include <mntent.h>
#include <pwd.h>
#include <signal.h>
#include <stdint.h>
#include <stdio.h>
#include <sys/mount.h>
#include <sys/stat.h>
#include <sys/statvfs.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <base/basictypes.h>
#include <base/file_util.h>
#include <base/posix/eintr_wrapper.h>
#include <base/string_number_conversions.h>
#include <base/string_split.h>
#include <base/string_util.h>
#include <base/stringprintf.h>
#include <base/sys_info.h>
#include <base/time.h>
#include <chromeos/process.h>
#include <chromeos/secure_blob.h>
#include <chromeos/utility.h>
// Uses libvboot_host for accessing crossystem variables.
extern "C" {
#include <vboot/crossystem.h>
}
using base::SplitString;
using std::string;
namespace cryptohome {
const int kDefaultMountOptions = MS_NOEXEC | MS_NOSUID | MS_NODEV;
const int kDefaultPwnameLength = 1024;
const int kDefaultUmask = S_IRGRP | S_IWGRP | S_IXGRP | S_IROTH | S_IWOTH
| S_IXOTH;
const std::string kMtab = "/etc/mtab";
const std::string kProcDir = "/proc";
const std::string kPathTune2fs = "/sbin/tune2fs";
Platform::Platform()
: mtab_path_(kMtab) {
}
Platform::~Platform() {
}
bool Platform::GetMountsBySourcePrefix(const std::string& from_prefix,
std::multimap<const std::string, const std::string>* mounts) {
std::string contents;
if (!file_util::ReadFileToString(FilePath(mtab_path_), &contents))
return false;
std::vector<std::string> lines;
SplitString(contents, '\n', &lines);
for (std::vector<std::string>::iterator it = lines.begin();
it < lines.end();
++it) {
if (it->substr(0, from_prefix.size()) != from_prefix)
continue;
// If there is no mounts pointer, we can return true right away.
if (!mounts)
return true;
size_t src_end = it->find(' ');
std::string source = it->substr(0, src_end);
size_t dst_start = src_end + 1;
size_t dst_end = it->find(' ', dst_start);
std::string destination = it->substr(dst_start, dst_end - dst_start);
mounts->insert(
std::pair<const std::string, const std::string>(source, destination));
}
return mounts && mounts->size();
}
bool Platform::IsDirectoryMounted(const std::string& directory) {
// Trivial string match from /etc/mtab to see if the cryptohome mount point is
// listed. This works because Chrome OS is a controlled environment and the
// only way /home/chronos/user should be mounted is if cryptohome mounted it.
string contents;
if (file_util::ReadFileToString(FilePath(mtab_path_), &contents)) {
if (contents.find(StringPrintf(" %s ", directory.c_str()))
!= string::npos) {
return true;
}
}
return false;
}
bool Platform::IsDirectoryMountedWith(const std::string& directory,
const std::string& from) {
// Trivial string match from /etc/mtab to see if the cryptohome mount point
// and the user's vault path are present. Assumes this user is mounted if it
// finds both. This will need to change if simultaneous login is implemented.
string contents;
if (file_util::ReadFileToString(FilePath(mtab_path_), &contents)) {
if ((contents.find(StringPrintf(" %s ", directory.c_str()))
!= string::npos)
&& (contents.find(StringPrintf("%s ",
from.c_str()).c_str())
!= string::npos)) {
return true;
}
}
return false;
}
bool Platform::Mount(const std::string& from, const std::string& to,
const std::string& type,
const std::string& mount_options) {
if (mount(from.c_str(), to.c_str(), type.c_str(), kDefaultMountOptions,
mount_options.c_str())) {
return false;
}
return true;
}
bool Platform::Bind(const std::string& from, const std::string& to) {
if (mount(from.c_str(), to.c_str(), NULL, kDefaultMountOptions | MS_BIND,
NULL))
return false;
return true;
}
bool Platform::Unmount(const std::string& path, bool lazy, bool* was_busy) {
if (lazy) {
if (umount2(path.c_str(), MNT_DETACH)) {
if (was_busy) {
*was_busy = (errno == EBUSY);
}
return false;
}
} else {
if (umount(path.c_str())) {
if (was_busy) {
*was_busy = (errno == EBUSY);
}
return false;
}
}
if (was_busy) {
*was_busy = false;
}
return true;
}
void Platform::GetProcessesWithOpenFiles(
const std::string& path,
std::vector<ProcessInformation>* processes) {
std::vector<pid_t> pids;
LookForOpenFiles(path, &pids);
for (std::vector<pid_t>::iterator it = pids.begin(); it != pids.end(); ++it) {
pid_t pid = static_cast<pid_t>(*it);
processes->push_back(ProcessInformation());
GetProcessOpenFileInformation(pid, path,
&processes->at(processes->size() - 1));
}
}
std::string Platform::ReadLink(const std::string& link_path) {
char link_buf[PATH_MAX];
ssize_t link_length = readlink(link_path.c_str(), link_buf, sizeof(link_buf));
if (link_length > 0) {
return std::string(link_buf, link_length);
}
return std::string();
}
void Platform::GetProcessOpenFileInformation(pid_t pid,
const std::string& path_in,
ProcessInformation* process_info) {
process_info->set_process_id(pid);
FilePath pid_path(StringPrintf("/proc/%d", pid));
FilePath cmdline_file = pid_path.Append("cmdline");
string contents;
std::vector<std::string> cmd_line;
if (file_util::ReadFileToString(cmdline_file, &contents)) {
SplitString(contents, '\0', &cmd_line);
}
process_info->set_cmd_line(&cmd_line);
// Make sure that if we get a directory, it has a trailing separator
FilePath file_path(path_in);
file_util::EnsureEndsWithSeparator(&file_path);
std::string path = file_path.value();
FilePath cwd_path = pid_path.Append("cwd");
std::string link_val = ReadLink(cwd_path.value());
if (IsPathChild(path, link_val)) {
process_info->set_cwd(&link_val);
} else {
link_val.clear();
process_info->set_cwd(&link_val);
}
// Open /proc/<pid>/fd
FilePath fd_dirpath = pid_path.Append("fd");
file_util::FileEnumerator fd_dir_enum(fd_dirpath, false,
file_util::FileEnumerator::FILES);
std::set<std::string> open_files;
// List open file descriptors
for (FilePath fd_path = fd_dir_enum.Next();
!fd_path.empty();
fd_path = fd_dir_enum.Next()) {
link_val = ReadLink(fd_path.value());
if (IsPathChild(path, link_val)) {
open_files.insert(link_val);
}
}
process_info->set_open_files(&open_files);
}
void Platform::LookForOpenFiles(const std::string& path_in,
std::vector<pid_t>* pids) {
// Make sure that if we get a directory, it has a trailing separator
FilePath file_path(path_in);
file_util::EnsureEndsWithSeparator(&file_path);
std::string path = file_path.value();
// Open /proc
file_util::FileEnumerator proc_dir_enum(FilePath(kProcDir), false,
file_util::FileEnumerator::DIRECTORIES);
int linkbuf_length = path.length();
std::vector<char> linkbuf(linkbuf_length);
// List PIDs in /proc
for (FilePath pid_path = proc_dir_enum.Next();
!pid_path.empty();
pid_path = proc_dir_enum.Next()) {
const char* pidstr = pid_path.BaseName().value().c_str();
pid_t pid = 0;
// Ignore PID 1 and errors
if (!base::StringToInt(pidstr, &pid) || pid <= 1) {
continue;
}
FilePath cwd_path = pid_path.Append("cwd");
ssize_t link_length = readlink(cwd_path.value().c_str(),
&linkbuf[0],
linkbuf.size());
if (link_length > 0) {
std::string open_file(&linkbuf[0], link_length);
if (IsPathChild(path, open_file)) {
pids->push_back(pid);
continue;
}
}
// Open /proc/<pid>/fd
FilePath fd_dirpath = pid_path.Append("fd");
file_util::FileEnumerator fd_dir_enum(fd_dirpath, false,
file_util::FileEnumerator::FILES);
// List open file descriptors
for (FilePath fd_path = fd_dir_enum.Next();
!fd_path.empty();
fd_path = fd_dir_enum.Next()) {
link_length = readlink(fd_path.value().c_str(), &linkbuf[0],
linkbuf.size());
if (link_length > 0) {
std::string open_file(&linkbuf[0], link_length);
if (IsPathChild(path, open_file)) {
pids->push_back(pid);
break;
}
}
}
}
}
bool Platform::IsPathChild(const std::string& parent,
const std::string& child) {
if (parent.length() == 0 || child.length() == 0) {
return false;
}
if (child.length() >= parent.length()) {
if (child.compare(0, parent.length(), parent, 0, parent.length()) == 0) {
return true;
}
} else if ((parent[parent.length() - 1] == '/') &&
(child.length() == (parent.length() - 1))) {
if (child.compare(0, child.length(), parent, 0, parent.length() - 1) == 0) {
return true;
}
}
return false;
}
bool Platform::GetOwnership(const string& path,
uid_t* user_id, gid_t* group_id) const {
struct stat path_status;
if (stat(path.c_str(), &path_status) != 0) {
PLOG(ERROR) << "stat() of " << path << " failed.";
return false;
}
if (user_id)
*user_id = path_status.st_uid;
if (group_id)
*group_id = path_status.st_gid;
return true;
}
bool Platform::SetOwnership(const std::string& path, uid_t user_id,
gid_t group_id) const {
if (chown(path.c_str(), user_id, group_id)) {
PLOG(ERROR) << "chown() of " << path.c_str() << " to (" << user_id
<< "," << group_id << ") failed.";
return false;
}
return true;
}
bool Platform::GetPermissions(const string& path, mode_t* mode) const {
struct stat path_status;
if (stat(path.c_str(), &path_status) != 0) {
PLOG(ERROR) << "stat() of " << path << " failed.";
return false;
}
*mode = path_status.st_mode;
return true;
}
bool Platform::SetPermissions(const std::string& path, mode_t mode) const {
if (chmod(path.c_str(), mode)) {
PLOG(ERROR) << "chmod() of " << path.c_str() << " to (" << std::oct << mode
<< ") failed.";
return false;
}
return true;
}
bool Platform::SetGroupAccessible(const string& path, gid_t group_id,
mode_t group_mode) const {
uid_t user_id;
mode_t mode;
if (!GetOwnership(path, &user_id, NULL) ||
!GetPermissions(path, &mode) ||
!SetOwnership(path, user_id, group_id) ||
!SetPermissions(path, (mode & ~S_IRWXG) | (group_mode & S_IRWXG))) {
LOG(ERROR) << "Couldn't set up group access on directory: " << path;
return false;
}
return true;
}
int Platform::SetMask(int new_mask) const {
return umask(new_mask);
}
bool Platform::GetUserId(const std::string& user, uid_t* user_id,
gid_t* group_id) const {
// Load the passwd entry
long user_name_length = sysconf(_SC_GETPW_R_SIZE_MAX); // NOLINT long
if (user_name_length == -1) {
user_name_length = kDefaultPwnameLength;
}
struct passwd user_info, *user_infop;
std::vector<char> user_name_buf(user_name_length);
if (getpwnam_r(user.c_str(), &user_info, &user_name_buf[0],
user_name_length, &user_infop)) {
return false;
}
*user_id = user_info.pw_uid;
*group_id = user_info.pw_gid;
return true;
}
bool Platform::GetGroupId(const std::string& group, gid_t* group_id) const {
// Load the group entry
long group_name_length = sysconf(_SC_GETGR_R_SIZE_MAX); // NOLINT long
if (group_name_length == -1) {
group_name_length = kDefaultPwnameLength;
}
struct group group_info, *group_infop;
std::vector<char> group_name_buf(group_name_length);
if (getgrnam_r(group.c_str(), &group_info, &group_name_buf[0],
group_name_length, &group_infop)) {
return false;
}
*group_id = group_info.gr_gid;
return true;
}
int64 Platform::AmountOfFreeDiskSpace(const string& path) const {
return base::SysInfo::AmountOfFreeDiskSpace(FilePath(path));
}
bool Platform::FileExists(const std::string& path) {
return file_util::PathExists(FilePath(path));
}
bool Platform::DirectoryExists(const std::string& path) {
return file_util::DirectoryExists(FilePath(path));
}
bool Platform::GetFileSize(const std::string& path, int64* size) {
return file_util::GetFileSize(FilePath(path), size);
}
FILE* Platform::CreateAndOpenTemporaryFile(std::string* path) {
FilePath created_path;
FILE* f = file_util::CreateAndOpenTemporaryFile(&created_path);
if (f)
path->assign(created_path.value());
return f;
}
FILE* Platform::OpenFile(const std::string& path, const char* mode) {
return file_util::OpenFile(FilePath(path), mode);
}
bool Platform::CloseFile(FILE* fp) {
return file_util::CloseFile(fp);
}
bool Platform::WriteOpenFile(FILE* fp, const chromeos::Blob& blob) {
return (fwrite(static_cast<const void*>(&blob.at(0)), 1, blob.size(), fp)
!= blob.size());
}
bool Platform::WriteFile(const std::string& path,
const chromeos::Blob& blob) {
return WriteArrayToFile(path,
reinterpret_cast<const char*>(&blob[0]),
blob.size());
}
bool Platform::WriteStringToFile(const std::string& path,
const std::string& data) {
return WriteArrayToFile(path, data.data(), data.size());
}
bool Platform::WriteArrayToFile(const std::string& path, const char* data,
size_t size) {
FilePath file_path(path);
if (!file_util::DirectoryExists(file_path.DirName())) {
if (!file_util::CreateDirectory(file_path.DirName())) {
LOG(ERROR) << "Cannot create directory: " << file_path.DirName().value();
return false;
}
}
// chromeos::Blob::size_type is std::vector::size_type and is unsigned.
if (size > static_cast<std::string::size_type>(INT_MAX)) {
LOG(ERROR) << "Cannot write to " << path
<< ". Data is too large: " << size << " bytes.";
return false;
}
int data_written = file_util::WriteFile(file_path, data, size);
return data_written == static_cast<int>(size);
}
bool Platform::ReadFile(const std::string& path, chromeos::Blob* blob) {
int64 file_size;
FilePath file_path(path);
if (!file_util::PathExists(file_path)) {
return false;
}
if (!file_util::GetFileSize(file_path, &file_size)) {
LOG(ERROR) << "Could not get size of " << path;
return false;
}
// Compare to the max of a signed integer.
if (file_size > static_cast<int64>(INT_MAX)) {
LOG(ERROR) << "File " << path << " is too large: "
<< file_size << " bytes.";
return false;
}
chromeos::Blob buf(file_size);
int data_read = file_util::ReadFile(file_path,
reinterpret_cast<char*>(&buf[0]),
file_size);
// Cast is okay because of comparison to INT_MAX above.
if (data_read != static_cast<int>(file_size)) {
LOG(ERROR) << "Only read " << data_read << " of " << file_size << " bytes.";
return false;
}
blob->swap(buf);
return true;
}
bool Platform::ReadFileToString(const std::string& path, std::string* string) {
return file_util::ReadFileToString(FilePath(path), string);
}
bool Platform::CreateDirectory(const std::string& path) {
return file_util::CreateDirectory(FilePath(path));
}
bool Platform::DeleteFile(const std::string& path, bool is_recursive) {
return file_util::Delete(FilePath(path), is_recursive);
}
bool Platform::Move(const std::string& from, const std::string& to) {
return file_util::Move(FilePath(from), FilePath(to));
}
bool Platform::EnumerateDirectoryEntries(const std::string& path,
bool recursive,
std::vector<std::string>* ent_list) {
file_util::FileEnumerator::FileType ft = static_cast<typeof(ft)>(
file_util::FileEnumerator::FILES | file_util::FileEnumerator::DIRECTORIES |
file_util::FileEnumerator::SHOW_SYM_LINKS);
file_util::FileEnumerator ent_enum(FilePath(path), recursive, ft);
for (FilePath path = ent_enum.Next(); !path.empty(); path = ent_enum.Next())
ent_list->push_back(path.value());
return true;
}
base::Time Platform::GetCurrentTime() const {
return base::Time::Now();
}
bool Platform::Stat(const std::string& path, struct stat *buf) {
return lstat(path.c_str(), buf) == 0;
}
bool Platform::Rename(const std::string& from, const std::string& to) {
return rename(from.c_str(), to.c_str()) == 0;
}
bool Platform::Copy(const std::string& from, const std::string& to) {
FilePath from_path(from);
FilePath to_path(to);
return file_util::CopyDirectory(from_path, to_path, true);
}
bool Platform::StatVFS(const std::string& path, struct statvfs* vfs) {
return statvfs(path.c_str(), vfs) == 0;
}
bool Platform::FindFilesystemDevice(const std::string &filesystem_in,
std::string *device)
{
/* Clear device to indicate failure case. */
device->clear();
/* Removing trailing slashes. */
std::string filesystem = filesystem_in;
size_t offset = filesystem.find_last_not_of('/');
if (offset != std::string::npos)
filesystem.erase(offset+1);
/* If we fail to open mtab, abort immediately. */
FILE *mtab_file = setmntent(mtab_path_.c_str(), "r");
if (!mtab_file)
return false;
/* Copy device of first matching filesystem location. */
struct mntent *entry;
while ((entry = getmntent(mtab_file)) != NULL) {
if (filesystem.compare(entry->mnt_dir) == 0) {
*device = entry->mnt_fsname;
break;
}
}
endmntent(mtab_file);
return (device->length() > 0);
}
bool Platform::ReportFilesystemDetails(const std::string &filesystem,
const std::string &logfile) {
chromeos::ProcessImpl process;
int rc;
std::string device;
if (!FindFilesystemDevice(filesystem, &device)) {
LOG(ERROR) << "Failed to find device for " << filesystem;
return false;
}
process.RedirectOutput(logfile);
process.AddArg(kPathTune2fs);
process.AddArg("-l");
process.AddArg(device);
rc = process.Run();
if (rc == 0)
return true;
LOG(ERROR) << "Failed to run tune2fs on " << device
<< " (" << filesystem << ", exit " << rc << ")";
return false;
}
bool Platform::FirmwareWriteProtected() {
return VbGetSystemPropertyInt("wpsw_boot") != 0;
}
bool Platform::SyncFile(const std::string& path) {
// Sync both the file and its parent directory.
return (SyncPath(path) && SyncPath(FilePath(path).DirName().value()));
}
bool Platform::SyncPath(const std::string& path) {
int fd = HANDLE_EINTR(open(path.c_str(), O_WRONLY));
if (fd < 0) {
PLOG(WARNING) << "Could not open " << path << " for syncing";
return false;
}
int result = fsync(fd);
ignore_result(HANDLE_EINTR(close(fd)));
if (result < 0) {
PLOG(WARNING) << "Failed to sync " << path;
return false;
}
return true;
}
// Encapsulate these helpers to avoid include conflicts.
namespace ecryptfs {
extern "C" {
#include <ecryptfs.h>
#include <keyutils.h>
}
long ClearUserKeyring() {
return keyctl(KEYCTL_CLEAR, KEY_SPEC_USER_KEYRING);
}
long AddEcryptfsAuthToken(const chromeos::SecureBlob& key,
const std::string& key_sig,
const chromeos::SecureBlob& salt) {
DCHECK(key.size() == ECRYPTFS_MAX_KEY_BYTES);
DCHECK(key_sig.length() == (ECRYPTFS_SIG_SIZE * 2));
DCHECK(salt.size() == ECRYPTFS_SALT_SIZE);
struct ecryptfs_auth_tok auth_token;
generate_payload(&auth_token, const_cast<char*>(key_sig.c_str()),
const_cast<char*>(reinterpret_cast<const char*>(&salt[0])),
const_cast<char*>(reinterpret_cast<const char*>(&key[0])));
long ret = ecryptfs_add_auth_tok_to_keyring(&auth_token,
const_cast<char*>(key_sig.c_str()));
return ret;
}
} // namespace ecryptfs
long Platform::ClearUserKeyring() {
return ecryptfs::ClearUserKeyring();
}
long Platform::AddEcryptfsAuthToken(const chromeos::SecureBlob& key,
const std::string& key_sig,
const chromeos::SecureBlob& salt) {
return ecryptfs::AddEcryptfsAuthToken(key, key_sig, salt);
}
FileEnumerator* Platform::GetFileEnumerator(const std::string& root_path,
bool recursive,
int file_type) {
return new FileEnumerator(root_path, recursive, file_type);
}
FileEnumerator::FileEnumerator(const std::string& root_path,
bool recursive,
int file_type) {
enumerator_ = new file_util::FileEnumerator(
FilePath(root_path),
recursive,
static_cast<file_util::FileEnumerator::FileType>(file_type));
}
FileEnumerator::FileEnumerator(const std::string& root_path,
bool recursive,
int file_type,
const std::string& pattern) {
enumerator_ = new file_util::FileEnumerator(
FilePath(root_path),
recursive,
static_cast<file_util::FileEnumerator::FileType>(file_type),
pattern);
}
FileEnumerator::~FileEnumerator() {
if (enumerator_)
delete enumerator_;
}
std::string FileEnumerator::Next() {
if (!enumerator_)
return "";
return enumerator_->Next().value();
}
void FileEnumerator::GetFindInfo(FindInfo* info) {
DCHECK(info);
enumerator_->GetFindInfo(
reinterpret_cast<file_util::FileEnumerator::FindInfo*>(info));
}
// static
bool FileEnumerator::IsDirectory(const FindInfo& info) {
return !!S_ISDIR(info.stat.st_mode);
}
//static
std::string FileEnumerator::GetFilename(const FindInfo& find_info) {
return find_info.filename;
}
// static
int64 FileEnumerator::GetFilesize(const FindInfo& info) {
return file_util::FileEnumerator::GetFilesize(
*(reinterpret_cast<const file_util::FileEnumerator::FindInfo*>(&info)));
}
// static
base::Time FileEnumerator::GetLastModifiedTime(const FindInfo& info) {
return file_util::FileEnumerator::GetLastModifiedTime(
*(reinterpret_cast<const file_util::FileEnumerator::FindInfo*>(&info)));
}
} // namespace cryptohome