| // 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 "vm_tools/launcher/nfs_export.h" |
| |
| #include <base/files/file_util.h> |
| #include <base/logging.h> |
| #include <base/process/process_iterator.h> |
| #include <base/strings/string_number_conversions.h> |
| #include <base/strings/string_split.h> |
| #include <base/strings/stringprintf.h> |
| #include <brillo/process.h> |
| #include <brillo/syslog_logging.h> |
| #include <sys/signal.h> |
| |
| #include "vm_tools/launcher/constants.h" |
| #include "vm_tools/launcher/subnet.h" |
| |
| namespace vm_tools { |
| namespace launcher { |
| |
| namespace { |
| |
| // Path to ganesha's temporary config and log directory. |
| constexpr char kGaneshaConfigDirectory[] = "/run/ganesha"; |
| |
| // Name of nfs-ganesha's upstart job. |
| constexpr char kGaneshaJobName[] = "nfs-ganesha"; |
| |
| } // namespace |
| |
| bool NfsExport::StartGanesha() { |
| brillo::ProcessImpl nfs_upstart; |
| nfs_upstart.AddArg("/sbin/start"); |
| nfs_upstart.AddArg(kGaneshaJobName); |
| |
| LOG(INFO) << "Starting NFS server"; |
| |
| return true; |
| } |
| |
| bool NfsExport::StopGanesha() { |
| brillo::ProcessImpl nfs_upstart; |
| nfs_upstart.AddArg("/sbin/stop"); |
| nfs_upstart.AddArg(kGaneshaJobName); |
| |
| LOG(INFO) << "Stopping NFS server"; |
| if (!nfs_upstart.Run()) { |
| PLOG(ERROR) << "Unable to stop NFS server"; |
| return false; |
| } |
| return true; |
| } |
| |
| bool NfsExport::ReloadGanesha() { |
| brillo::ProcessImpl nfs_upstart; |
| nfs_upstart.AddArg("/sbin/reload"); |
| nfs_upstart.AddArg(kGaneshaJobName); |
| |
| LOG(INFO) << "Reloading NFS config"; |
| if (!nfs_upstart.Run()) { |
| PLOG(ERROR) << "Unable to reload NFS config"; |
| return false; |
| } |
| return true; |
| } |
| |
| bool NfsExport::ConfigureGanesha() { |
| std::string config = R"XXX( |
| NFS_Core_Param { |
| MNT_Port = 2050; |
| } |
| )XXX"; |
| |
| constexpr char export_template[] = R"XXX( |
| EXPORT |
| { |
| Export_Id = %u; |
| # Minijail-relative path. |
| Path = %s; |
| Squash = Root; |
| # chronos uid/gid. |
| Anonymous_Uid = 1000; |
| Anonymous_Gid = 1000; |
| Access_Type = NONE; |
| Protocols = 3; |
| FSAL { |
| Name = VFS; |
| } |
| CLIENT { |
| Clients = %s; |
| Access_Type = RW; |
| } |
| } |
| )XXX"; |
| |
| base::FilePath config_directory(launcher::kGaneshaConfigDirectory); |
| if (!base::DirectoryExists(config_directory)) { |
| LOG(INFO) << "Config directory " << launcher::kGaneshaConfigDirectory |
| << " does not exist, creating."; |
| if (!base::CreateDirectory(config_directory)) { |
| PLOG(ERROR) << "Unable to create config directory"; |
| return false; |
| } |
| } |
| base::FilePath config_file_path = |
| base::FilePath(launcher::kGaneshaConfigDirectory) |
| .Append(base::FilePath("ganesha.conf")); |
| |
| // Assemble ganesha config. |
| for (const auto& pair : allocated_exports_) { |
| base::StringAppendF(&config, export_template, pair.first, |
| pair.second.value().c_str(), |
| subnet_->GetIpAddress().c_str()); |
| } |
| |
| int write_len = |
| base::WriteFile(config_file_path, config.c_str(), config.length()); |
| if (write_len != config.length()) { |
| PLOG(ERROR) << "Unable to write config file " << config_file_path.value(); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| unsigned int NfsExport::GetExportID() const { |
| return export_id_; |
| } |
| |
| const base::FilePath& NfsExport::GetExportPath() const { |
| return export_path_; |
| } |
| |
| NfsExport::NfsExport(const base::FilePath& instance_runtime_dir, |
| const base::FilePath& export_path, |
| const std::shared_ptr<Subnet>& subnet, |
| bool release_on_destruction) |
| : PooledResource(instance_runtime_dir, release_on_destruction), |
| export_path_(export_path), |
| subnet_(subnet) {} |
| |
| NfsExport::~NfsExport() { |
| if (ShouldReleaseOnDestruction() && !Release()) |
| LOG(ERROR) << "Failed to Release() NFS export ID"; |
| } |
| |
| std::unique_ptr<NfsExport> NfsExport::Create( |
| const base::FilePath& instance_runtime_dir, |
| const base::FilePath& export_path, |
| const std::shared_ptr<Subnet>& subnet) { |
| auto nfs_export = std::unique_ptr<NfsExport>( |
| new NfsExport(instance_runtime_dir, export_path, subnet, true)); |
| if (!nfs_export->Allocate()) |
| return nullptr; |
| |
| return nfs_export; |
| } |
| |
| std::unique_ptr<NfsExport> NfsExport::Load( |
| const base::FilePath& instance_runtime_dir, |
| const std::shared_ptr<Subnet>& subnet) { |
| auto nfs_export = std::unique_ptr<NfsExport>( |
| new NfsExport(instance_runtime_dir, base::FilePath(), subnet, false)); |
| if (!nfs_export->LoadInstance()) |
| return nullptr; |
| |
| return nfs_export; |
| } |
| |
| const char* NfsExport::GetName() const { |
| return "nfs_export"; |
| } |
| |
| const std::string NfsExport::GetResourceID() const { |
| return base::StringPrintf("%u", export_id_); |
| } |
| |
| bool NfsExport::LoadGlobalResources(const std::string& resources) { |
| allocated_exports_.clear(); |
| |
| for (const auto& line : base::SplitStringPiece( |
| resources, "\n", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY)) { |
| unsigned int id; |
| std::vector<std::string> parts = base::SplitString( |
| line, ":", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY); |
| |
| if (parts.size() != 2) { |
| LOG(ERROR) << "Failed to parse export ID/path"; |
| allocated_exports_.clear(); |
| return false; |
| } |
| |
| if (!base::StringToUint(parts[0], &id)) { |
| LOG(ERROR) << "Failed to read export ID"; |
| allocated_exports_.clear(); |
| return false; |
| } |
| if (allocated_exports_.find(id) != allocated_exports_.end()) |
| LOG(WARNING) << "Export " << id << " was used twice"; |
| |
| base::FilePath export_path(parts[1]); |
| if (!base::DirectoryExists(export_path)) { |
| LOG(ERROR) << "Export path doesn't exist: " << export_path.value(); |
| allocated_exports_.clear(); |
| return false; |
| } |
| |
| allocated_exports_[id] = export_path; |
| } |
| |
| return true; |
| } |
| |
| std::string NfsExport::PersistGlobalResources() { |
| std::string resources; |
| for (const auto& pair : allocated_exports_) { |
| base::StringAppendF(&resources, "%u:%s\n", pair.first, |
| pair.second.value().c_str()); |
| } |
| |
| return resources; |
| } |
| |
| bool NfsExport::LoadInstanceResource(const std::string& resource) { |
| unsigned int id; |
| |
| if (!base::StringToUint(resource, &id)) |
| return false; |
| |
| if (!IsExportIdAllocated(id)) |
| return false; |
| |
| export_id_ = id; |
| export_path_ = allocated_exports_[id]; |
| return true; |
| } |
| |
| bool NfsExport::AllocateResource() { |
| unsigned int export_id = 1; |
| |
| // std::map rbegin is the largest value in the map. Increment this by one |
| // to get our next export id. |
| auto it = allocated_exports_.rbegin(); |
| if (it != allocated_exports_.rend()) |
| export_id = it->first + 1; |
| |
| export_id_ = export_id; |
| allocated_exports_[export_id_] = export_path_; |
| |
| if (!ConfigureGanesha()) |
| return false; |
| |
| if (allocated_exports_.size() == 1) { |
| if (!StartGanesha()) |
| return false; |
| } else { |
| if (!ReloadGanesha()) |
| return false; |
| } |
| |
| return true; |
| } |
| |
| bool NfsExport::ReleaseResource() { |
| allocated_exports_.erase(export_id_); |
| |
| if (!ConfigureGanesha()) |
| return false; |
| |
| if (allocated_exports_.empty()) { |
| if (!StopGanesha()) |
| return false; |
| } else { |
| if (!ReloadGanesha()) |
| return false; |
| } |
| |
| return true; |
| } |
| |
| bool NfsExport::IsExportIdAllocated(const unsigned int export_id) const { |
| return allocated_exports_.find(export_id) != allocated_exports_.end(); |
| } |
| |
| } // namespace launcher |
| } // namespace vm_tools |