| // Copyright 2017 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 "cros-disks/rename_manager.h" |
| |
| #include <linux/capability.h> |
| |
| #include <string> |
| |
| #include <base/bind.h> |
| #include <base/files/file_util.h> |
| #include <base/logging.h> |
| #include <brillo/process.h> |
| |
| #include "cros-disks/filesystem_label.h" |
| #include "cros-disks/platform.h" |
| #include "cros-disks/quote.h" |
| #include "cros-disks/rename_manager_observer_interface.h" |
| |
| namespace cros_disks { |
| |
| namespace { |
| |
| struct RenameParameters { |
| const char* filesystem_type; |
| const char* program_path; |
| const char* rename_group; |
| }; |
| |
| const char kRenameUser[] = "cros-disks"; |
| |
| // Supported file systems and their parameters |
| const RenameParameters kSupportedRenameParameters[] = { |
| {"vfat", "/usr/sbin/fatlabel", "disk"}, |
| {"exfat", "/usr/sbin/exfatlabel", "fuse-exfat"}, |
| {"ntfs", "/usr/sbin/ntfslabel", "ntfs-3g"}}; |
| |
| const RenameParameters* FindRenameParameters( |
| const std::string& filesystem_type) { |
| for (const auto& parameters : kSupportedRenameParameters) { |
| if (filesystem_type == parameters.filesystem_type) { |
| return ¶meters; |
| } |
| } |
| |
| return nullptr; |
| } |
| |
| RenameErrorType LabelErrorToRenameError(LabelErrorType error_code) { |
| switch (error_code) { |
| case LabelErrorType::kLabelErrorNone: |
| return RENAME_ERROR_NONE; |
| case LabelErrorType::kLabelErrorUnsupportedFilesystem: |
| return RENAME_ERROR_UNSUPPORTED_FILESYSTEM; |
| case LabelErrorType::kLabelErrorLongName: |
| return RENAME_ERROR_LONG_NAME; |
| case LabelErrorType::kLabelErrorInvalidCharacter: |
| return RENAME_ERROR_INVALID_CHARACTER; |
| } |
| } |
| |
| } // namespace |
| |
| RenameManager::RenameManager(Platform* platform, |
| brillo::ProcessReaper* process_reaper) |
| : platform_(platform), |
| process_reaper_(process_reaper), |
| weak_ptr_factory_(this) {} |
| |
| RenameManager::~RenameManager() = default; |
| |
| RenameErrorType RenameManager::StartRenaming( |
| const std::string& device_path, |
| const std::string& device_file, |
| const std::string& volume_name, |
| const std::string& filesystem_type) { |
| std::string source_path; |
| if (!platform_->GetRealPath(device_path, &source_path) || |
| !CanRename(source_path)) { |
| LOG(WARNING) << "Device with path " << quote(device_path) |
| << " is not allowed for renaming"; |
| return RENAME_ERROR_DEVICE_NOT_ALLOWED; |
| } |
| |
| LabelErrorType label_error = |
| ValidateVolumeLabel(volume_name, filesystem_type); |
| if (label_error != LabelErrorType::kLabelErrorNone) { |
| return LabelErrorToRenameError(label_error); |
| } |
| |
| const RenameParameters* parameters = FindRenameParameters(filesystem_type); |
| // Check if tool for renaming exists |
| if (!base::PathExists(base::FilePath(parameters->program_path))) { |
| LOG(WARNING) << "Cannot find a rename program for filesystem " |
| << quote(filesystem_type); |
| return RENAME_ERROR_RENAME_PROGRAM_NOT_FOUND; |
| } |
| |
| if (base::ContainsKey(rename_process_, device_path)) { |
| LOG(WARNING) << "Device " << quote(device_path) |
| << " is already being renamed"; |
| return RENAME_ERROR_DEVICE_BEING_RENAMED; |
| } |
| |
| uid_t rename_user_id; |
| gid_t rename_group_id; |
| if (!platform_->GetUserAndGroupId(kRenameUser, &rename_user_id, nullptr) || |
| !platform_->GetGroupId(parameters->rename_group, &rename_group_id)) { |
| LOG(WARNING) << "Cannot find a user with name " << quote(kRenameUser) |
| << " or a group with name " << quote(parameters->rename_group); |
| return RENAME_ERROR_INTERNAL; |
| } |
| |
| // TODO(klemenko): Further restrict the capabilities |
| SandboxedProcess* process = &rename_process_[device_path]; |
| process->SetUserId(rename_user_id); |
| process->SetGroupId(rename_group_id); |
| process->SetNoNewPrivileges(); |
| process->NewMountNamespace(); |
| process->NewIpcNamespace(); |
| process->NewNetworkNamespace(); |
| process->SetCapabilities(0); |
| |
| process->AddArgument(parameters->program_path); |
| |
| // TODO(klemenko): To improve and provide more general solution, the |
| // per-filesystem argument setup should be parameterized with RenameParameter. |
| // Construct program-name arguments |
| // Example: dosfslabel /dev/sdb1 "NEWNAME" |
| // Example: exfatlabel /dev/sdb1 "NEWNAME" |
| if (filesystem_type == "vfat" || filesystem_type == "exfat" || |
| filesystem_type == "ntfs") { |
| process->AddArgument(device_file); |
| process->AddArgument(volume_name); |
| } |
| |
| if (!process->Start()) { |
| LOG(WARNING) << "Cannot start a process for renaming " << quote(device_path) |
| << " as filesystem " << quote(filesystem_type) |
| << " and volume name " << quote(volume_name); |
| rename_process_.erase(device_path); |
| return RENAME_ERROR_RENAME_PROGRAM_FAILED; |
| } |
| |
| process_reaper_->WatchForChild( |
| FROM_HERE, process->pid(), |
| base::BindOnce(&RenameManager::OnRenameProcessTerminated, |
| weak_ptr_factory_.GetWeakPtr(), device_path)); |
| return RENAME_ERROR_NONE; |
| } |
| |
| void RenameManager::OnRenameProcessTerminated(const std::string& device_path, |
| const siginfo_t& info) { |
| rename_process_.erase(device_path); |
| RenameErrorType error_type = RENAME_ERROR_UNKNOWN; |
| switch (info.si_code) { |
| case CLD_EXITED: |
| if (info.si_status == 0) { |
| error_type = RENAME_ERROR_NONE; |
| LOG(INFO) << "Process " << info.si_pid << " for renaming " |
| << quote(device_path) << " completed successfully"; |
| } else { |
| error_type = RENAME_ERROR_RENAME_PROGRAM_FAILED; |
| LOG(ERROR) << "Process " << info.si_pid << " for renaming " |
| << quote(device_path) << " exited with a status " |
| << info.si_status; |
| } |
| break; |
| |
| case CLD_DUMPED: |
| case CLD_KILLED: |
| error_type = RENAME_ERROR_RENAME_PROGRAM_FAILED; |
| LOG(ERROR) << "Process " << info.si_pid << " for renaming " |
| << quote(device_path) << " killed by a signal " |
| << info.si_status; |
| break; |
| |
| default: |
| break; |
| } |
| |
| if (observer_) |
| observer_->OnRenameCompleted(device_path, error_type); |
| } |
| |
| bool RenameManager::CanRename(const std::string& source_path) const { |
| return base::StartsWith(source_path, "/sys/", base::CompareCase::SENSITIVE) || |
| base::StartsWith(source_path, "/devices/", |
| base::CompareCase::SENSITIVE) || |
| base::StartsWith(source_path, "/dev/", base::CompareCase::SENSITIVE); |
| } |
| |
| } // namespace cros_disks |