| // 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 <dirent.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/bind.h> |
| #include <base/callback.h> |
| #include <base/file_util.h> |
| #include <base/location.h> |
| #include <base/posix/eintr_wrapper.h> |
| #include <base/strings/string_number_conversions.h> |
| #include <base/strings/string_split.h> |
| #include <base/strings/string_util.h> |
| #include <base/strings/stringprintf.h> |
| #include <base/sys_info.h> |
| #include <base/threading/thread.h> |
| #include <base/time/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::FilePath; |
| using base::SplitString; |
| using base::StringPrintf; |
| using std::string; |
| |
| namespace { |
| |
| class ScopedPath { |
| public: |
| ScopedPath(cryptohome::Platform* platform, const string& dir) |
| : platform_(platform), dir_(dir) {} |
| ~ScopedPath() { |
| if (!dir_.empty() && !platform_->DeleteFile(dir_, true)) { |
| PLOG(WARNING) << "Failed to clean up " << dir_; |
| } |
| } |
| void release() { |
| dir_.clear(); |
| } |
| private: |
| cryptohome::Platform* platform_; |
| string dir_; |
| }; |
| |
| bool IsDirectory(const struct stat& file_info) { |
| return !!S_ISDIR(file_info.st_mode); |
| } |
| |
| void TimedSync() { |
| base::Time start = base::Time::Now(); |
| sync(); |
| base::TimeDelta delta = base::Time::Now() - start; |
| if (delta > base::TimeDelta::FromSeconds(10)) { |
| LOG(WARNING) << "Long sync(): " << delta.InSeconds() << " seconds"; |
| } |
| } |
| |
| } // namespace |
| |
| 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 (!base::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 (base::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 (base::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::LazyUnmountAndSync(const std::string& path, bool sync_first) { |
| if (sync_first) { |
| TimedSync(); |
| } |
| if (umount2(path.c_str(), MNT_DETACH | UMOUNT_NOFOLLOW)) { |
| if (errno != EBUSY) { |
| PLOG(ERROR) << "Lazy unmount failed!"; |
| } |
| } |
| TimedSync(); |
| } |
| |
| 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 (base::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_path = file_path.AsEndingWithSeparator(); |
| 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"); |
| |
| base::FileEnumerator fd_dir_enum(fd_dirpath, false, |
| base::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_path = file_path.AsEndingWithSeparator(); |
| std::string path = file_path.value(); |
| |
| // Open /proc |
| base::FileEnumerator proc_dir_enum(FilePath(kProcDir), false, |
| base::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()) { |
| pid_t pid = 0; |
| // Ignore PID 1 and errors |
| if (!base::StringToInt(pid_path.BaseName().value(), &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"); |
| |
| base::FileEnumerator fd_dir_enum(fd_dirpath, false, |
| base::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 base::PathExists(FilePath(path)); |
| } |
| |
| bool Platform::DirectoryExists(const std::string& path) { |
| return base::DirectoryExists(FilePath(path)); |
| } |
| |
| bool Platform::GetFileSize(const std::string& path, int64* size) { |
| return base::GetFileSize(FilePath(path), size); |
| } |
| |
| FILE* Platform::CreateAndOpenTemporaryFile(std::string* path) { |
| FilePath created_path; |
| FILE* f = base::CreateAndOpenTemporaryFile(&created_path); |
| if (f) |
| path->assign(created_path.value()); |
| |
| return f; |
| } |
| |
| FILE* Platform::OpenFile(const std::string& path, const char* mode) { |
| return base::OpenFile(FilePath(path), mode); |
| } |
| |
| bool Platform::CloseFile(FILE* fp) { |
| return base::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 (!base::DirectoryExists(file_path.DirName())) { |
| if (!base::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 (!base::PathExists(file_path)) { |
| return false; |
| } |
| if (!base::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 = base::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 base::ReadFileToString(FilePath(path), string); |
| } |
| |
| bool Platform::CreateDirectory(const std::string& path) { |
| return base::CreateDirectory(FilePath(path)); |
| } |
| |
| bool Platform::DeleteFile(const std::string& path, bool is_recursive) { |
| return base::DeleteFile(FilePath(path), is_recursive); |
| } |
| |
| bool Platform::Move(const std::string& from, const std::string& to) { |
| return base::Move(FilePath(from), FilePath(to)); |
| } |
| |
| bool Platform::EnumerateDirectoryEntries(const std::string& path, |
| bool recursive, |
| std::vector<std::string>* ent_list) { |
| base::FileEnumerator::FileType ft = static_cast<typeof(ft)>( |
| base::FileEnumerator::FILES | base::FileEnumerator::DIRECTORIES | |
| base::FileEnumerator::SHOW_SYM_LINKS); |
| base::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 base::CopyDirectory(from_path, to_path, true); |
| } |
| |
| bool Platform::CopyPermissionsCallback( |
| const std::string& old_base, |
| const std::string& new_base, |
| const std::string& file_path, |
| const struct stat& file_info) { |
| const FilePath old_base_path(old_base); |
| const FilePath new_base_path(new_base); |
| // Find the new path that corresponds with the old path given by file_info. |
| FilePath old_path(file_path); |
| FilePath new_path(new_base_path); |
| if (old_path != old_base_path) { |
| if (old_path.IsAbsolute()) { |
| if (!old_base_path.AppendRelativePath(old_path, &new_path)) { |
| LOG(ERROR) << "AppendRelativePath failed: parent=" |
| << old_base_path.value() << ", child=" << old_path.value(); |
| return false; |
| } |
| } else { |
| new_path = new_base_path.Append(old_path); |
| } |
| } |
| if (!SetOwnership(new_path.value(), |
| file_info.st_uid, |
| file_info.st_gid)) { |
| PLOG(ERROR) << "Failed to set ownership for " << new_path.value(); |
| return false; |
| } |
| const mode_t permissions_mask = 07777; |
| if (!SetPermissions(new_path.value(), |
| file_info.st_mode & permissions_mask)) { |
| PLOG(ERROR) << "Failed to set permissions for " << new_path.value(); |
| return false; |
| } |
| return true; |
| } |
| |
| bool Platform::CopyWithPermissions(const std::string& from_path, |
| const std::string& to_path) { |
| if (!Copy(from_path, to_path)) { |
| PLOG(ERROR) << "Failed to copy " << from_path; |
| return false; |
| } |
| |
| // If something goes wrong we want to blow away the half-baked path. |
| ScopedPath scoped_new_path(this, to_path); |
| |
| // Unfortunately, ownership and permissions are not always retained. |
| // Apply the old ownership / permissions on a per-file basis. |
| FileEnumeratorCallback callback = base::Bind( |
| &Platform::CopyPermissionsCallback, |
| base::Unretained(this), |
| from_path, |
| to_path); |
| if (!WalkPath(from_path, callback)) |
| return false; |
| |
| // The copy is done, keep the new path. |
| scoped_new_path.release(); |
| return true; |
| } |
| |
| bool Platform::ApplyPermissionsCallback( |
| const Permissions& default_file_info, |
| const Permissions& default_dir_info, |
| const std::map<std::string, Permissions>& special_cases, |
| const std::string& file_path, |
| const struct stat& file_info) { |
| Permissions expected; |
| std::map<std::string, Permissions>::const_iterator it = |
| special_cases.find(file_path); |
| if (it != special_cases.end()) { |
| expected = it->second; |
| } else if (IsDirectory(file_info)) { |
| expected = default_dir_info; |
| } else { |
| expected = default_file_info; |
| } |
| if (expected.user != file_info.st_uid || |
| expected.group != file_info.st_gid) { |
| LOG(WARNING) << "Unexpected user/group for " << file_path; |
| if (!SetOwnership(file_path, |
| expected.user, |
| expected.group)) { |
| PLOG(ERROR) << "Failed to fix user/group for " |
| << file_path; |
| return false; |
| } |
| } |
| const mode_t permissions_mask = 07777; |
| if ((expected.mode & permissions_mask) != |
| (file_info.st_mode & permissions_mask)) { |
| LOG(WARNING) << "Unexpected permissions for " << file_path; |
| if (!SetPermissions(file_path, |
| expected.mode & permissions_mask)) { |
| PLOG(ERROR) << "Failed to set permissions for " |
| << file_path; |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| bool Platform::ApplyPermissionsRecursive( |
| const std::string& path, |
| const Permissions& default_file_info, |
| const Permissions& default_dir_info, |
| const std::map<std::string, Permissions>& special_cases) { |
| FileEnumeratorCallback callback = base::Bind( |
| &Platform::ApplyPermissionsCallback, |
| base::Unretained(this), |
| default_file_info, |
| default_dir_info, |
| special_cases); |
| return WalkPath(path, callback); |
| } |
| |
| 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 = -1; |
| DIR* dir = NULL; |
| if (base::DirectoryExists(FilePath(path))) { |
| dir = opendir(path.c_str()); |
| if (!dir) { |
| PLOG(WARNING) << "Could not open directory " << path << " for syncing"; |
| return false; |
| } |
| fd = dirfd(dir); |
| } else { |
| 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); |
| if (dir) { |
| ignore_result(closedir(dir)); |
| } else { |
| ignore_result(HANDLE_EINTR(close(fd))); |
| } |
| if (result < 0) { |
| PLOG(WARNING) << "Failed to sync " << path; |
| return false; |
| } |
| return true; |
| } |
| |
| void Platform::Sync() { |
| TimedSync(); |
| } |
| |
| // 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); |
| } |
| |
| bool Platform::WalkPath(const std::string& path, |
| const FileEnumeratorCallback& callback) { |
| struct stat base_entry_info; |
| if (!Stat(path, &base_entry_info)) { |
| PLOG(ERROR) << "Failed to stat " << path; |
| return false; |
| } |
| if (!callback.Run(path, base_entry_info)) |
| return false; |
| if (IsDirectory(base_entry_info)) { |
| int file_types = base::FileEnumerator::FILES | |
| base::FileEnumerator::DIRECTORIES; |
| scoped_ptr<FileEnumerator> file_enumerator( |
| GetFileEnumerator(path, true, file_types)); |
| std::string entry_path; |
| while (!(entry_path = file_enumerator->Next()).empty()) { |
| if (!callback.Run(entry_path, file_enumerator->GetInfo().stat())) |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| FileEnumerator::FileInfo::FileInfo( |
| const base::FileEnumerator::FileInfo& file_info) { |
| Assign(file_info); |
| } |
| |
| FileEnumerator::FileInfo::FileInfo(const std::string& name, |
| const struct stat& stat) |
| : name_(name), stat_(stat) { |
| } |
| |
| FileEnumerator::FileInfo::FileInfo(const FileEnumerator::FileInfo& other) { |
| if (other.info_.get()) { |
| Assign(*other.info_); |
| } else { |
| info_.reset(); |
| name_ = other.name_; |
| stat_ = other.stat_; |
| } |
| } |
| |
| FileEnumerator::FileInfo::FileInfo() { |
| Assign(base::FileEnumerator::FileInfo()); |
| } |
| |
| FileEnumerator::FileInfo::~FileInfo() {} |
| |
| FileEnumerator::FileInfo& FileEnumerator::FileInfo::operator=( |
| const FileEnumerator::FileInfo& other) { |
| if (other.info_.get()) { |
| Assign(*other.info_); |
| } else { |
| info_.reset(); |
| name_ = other.name_; |
| stat_ = other.stat_; |
| } |
| return *this; |
| } |
| |
| bool FileEnumerator::FileInfo::IsDirectory() const { |
| if (info_.get()) |
| return info_->IsDirectory(); |
| return ::IsDirectory(stat_); |
| } |
| |
| std::string FileEnumerator::FileInfo::GetName() const { |
| if (info_.get()) |
| return info_->GetName().value(); |
| return name_; |
| } |
| |
| int64 FileEnumerator::FileInfo::GetSize() const { |
| if (info_.get()) |
| return info_->GetSize(); |
| return stat_.st_size; |
| } |
| |
| base::Time FileEnumerator::FileInfo::GetLastModifiedTime() const { |
| if (info_.get()) |
| return info_->GetLastModifiedTime(); |
| return base::Time::FromTimeT(stat_.st_mtime); |
| } |
| |
| const struct stat& FileEnumerator::FileInfo::stat() const { |
| if (info_.get()) |
| return info_->stat(); |
| return stat_; |
| } |
| |
| void FileEnumerator::FileInfo::Assign( |
| const base::FileEnumerator::FileInfo& file_info) { |
| info_.reset(new base::FileEnumerator::FileInfo(file_info)); |
| memset(&stat_, 0, sizeof(stat_)); |
| } |
| |
| FileEnumerator::FileEnumerator(const std::string& root_path, |
| bool recursive, |
| int file_type) { |
| enumerator_.reset(new base::FileEnumerator(FilePath(root_path), |
| recursive, |
| file_type)); |
| } |
| |
| FileEnumerator::FileEnumerator(const std::string& root_path, |
| bool recursive, |
| int file_type, |
| const std::string& pattern) { |
| enumerator_.reset(new base::FileEnumerator(FilePath(root_path), |
| recursive, |
| file_type, |
| pattern)); |
| } |
| |
| FileEnumerator::FileEnumerator() {} |
| FileEnumerator::~FileEnumerator() {} |
| |
| std::string FileEnumerator::Next() { |
| if (!enumerator_.get()) |
| return std::string(); |
| return enumerator_->Next().value(); |
| } |
| |
| FileEnumerator::FileInfo FileEnumerator::GetInfo() { |
| return FileInfo(enumerator_->GetInfo()); |
| } |
| |
| } // namespace cryptohome |