blob: 6a9e0b6ca6c1c9995b2541efbc7926ac95765857 [file] [log] [blame]
// Copyright (c) 2011 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/mount_options.h"
#include <algorithm>
#include <base/containers/adapters.h>
#include <base/stl_util.h>
#include <base/strings/string_util.h>
#include <base/strings/strcat.h>
#include "cros-disks/quote.h"
namespace cros_disks {
const char MountOptions::kOptionBind[] = "bind";
const char MountOptions::kOptionDirSync[] = "dirsync";
const char MountOptions::kOptionFlush[] = "flush";
const char MountOptions::kOptionNoDev[] = "nodev";
const char MountOptions::kOptionNoExec[] = "noexec";
const char MountOptions::kOptionNoSuid[] = "nosuid";
const char MountOptions::kOptionNoSymFollow[] = "nosymfollow";
const char MountOptions::kOptionReadOnly[] = "ro";
const char MountOptions::kOptionReadWrite[] = "rw";
const char MountOptions::kOptionRemount[] = "remount";
const char MountOptions::kOptionSynchronous[] = "sync";
const char MountOptions::kOptionUtf8[] = "utf8";
namespace {
const char kOptionUidPrefix[] = "uid=";
const char kOptionGidPrefix[] = "gid=";
const char kOptionShortNamePrefix[] = "shortname=";
const char kOptionTimeOffsetPrefix[] = "time_offset=";
bool FindLastElementStartingWith(const std::vector<std::string>& container,
base::StringPiece prefix,
std::string* result) {
for (const auto& element : base::Reversed(container)) {
if (base::StartsWith(element, prefix, base::CompareCase::SENSITIVE)) {
*result = element;
return true;
}
}
return false;
}
} // namespace
MountOptions::MountOptions()
: allow_exact_(
{kOptionDirSync, kOptionFlush, kOptionSynchronous, kOptionUtf8}),
allow_prefix_({kOptionShortNamePrefix, kOptionTimeOffsetPrefix}),
enforced_options_({kOptionNoDev, kOptionNoExec, kOptionNoSuid}) {}
MountOptions::~MountOptions() = default;
void MountOptions::Initialize(const std::vector<std::string>& options,
bool set_user_and_group_id,
const std::string& default_user_id,
const std::string& default_group_id) {
options_.clear();
options_.reserve(options.size());
bool option_read_only = false, option_read_write = false;
bool option_remount = false;
std::string option_user_id, option_group_id;
for (const auto& option : options) {
// Skip early if |option| contains a comma.
if (option.find(",") != std::string::npos) {
LOG(WARNING) << "Ignoring invalid mount option " << quote(option);
continue;
}
if (option == kOptionReadOnly) {
option_read_only = true;
} else if (option == kOptionReadWrite) {
option_read_write = true;
} else if (option == kOptionRemount) {
option_remount = true;
} else if (base::StartsWith(option, kOptionUidPrefix,
base::CompareCase::INSENSITIVE_ASCII)) {
option_user_id = option;
} else if (base::StartsWith(option, kOptionGidPrefix,
base::CompareCase::INSENSITIVE_ASCII)) {
option_group_id = option;
} else if (base::Contains(enforced_options_, option)) {
// We'll add these options unconditionally below.
continue;
} else if (base::Contains(allow_exact_, option)) {
// Only add options in the allowlist.
options_.push_back(option);
} else if (std::find_if(allow_prefix_.begin(), allow_prefix_.end(),
[option](const auto& s) {
return base::StartsWith(
option, s,
base::CompareCase::INSENSITIVE_ASCII);
}) != allow_prefix_.end()) {
// Only add options in the allowlist.
options_.push_back(option);
} else {
// Never add unknown/non-allowed options.
LOG(WARNING) << "Ignoring unsupported mount option " << quote(option);
}
}
if (option_read_only || !option_read_write) {
options_.push_back(kOptionReadOnly);
} else {
options_.push_back(kOptionReadWrite);
}
if (option_remount) {
options_.push_back(kOptionRemount);
}
if (set_user_and_group_id) {
if (!option_user_id.empty()) {
options_.push_back(option_user_id);
} else if (!default_user_id.empty()) {
options_.push_back(kOptionUidPrefix + default_user_id);
}
if (!option_group_id.empty()) {
options_.push_back(option_group_id);
} else if (!default_group_id.empty()) {
options_.push_back(kOptionGidPrefix + default_group_id);
}
}
std::copy(enforced_options_.begin(), enforced_options_.end(),
std::back_inserter(options_));
}
bool MountOptions::IsReadOnlyOptionSet() const {
for (const std::string& option : base::Reversed(options_)) {
if (option == kOptionReadOnly)
return true;
if (option == kOptionReadWrite)
return false;
}
return true;
}
void MountOptions::SetReadOnlyOption() {
std::replace(options_.begin(), options_.end(), kOptionReadWrite,
kOptionReadOnly);
}
std::pair<MountOptions::Flags, std::string> MountOptions::ToMountFlagsAndData()
const {
Flags flags = MS_RDONLY;
std::vector<std::string> data;
data.reserve(options_.size());
for (const auto& option : options_) {
if (option == kOptionReadOnly) {
flags |= MS_RDONLY;
} else if (option == kOptionReadWrite) {
flags &= ~static_cast<Flags>(MS_RDONLY);
} else if (option == kOptionRemount) {
flags |= MS_REMOUNT;
} else if (option == kOptionBind) {
flags |= MS_BIND;
} else if (option == kOptionDirSync) {
flags |= MS_DIRSYNC;
} else if (option == kOptionNoDev) {
flags |= MS_NODEV;
} else if (option == kOptionNoExec) {
flags |= MS_NOEXEC;
} else if (option == kOptionNoSuid) {
flags |= MS_NOSUID;
} else if (option == kOptionSynchronous) {
flags |= MS_SYNCHRONOUS;
} else if (option == kOptionNoSymFollow) {
flags |= MS_NOSYMFOLLOW;
// Pass the nosymfollow option as both a flag and a string option for
// compatibility across kernels. The mount syscall ignores unknown flags,
// so kernels that don't have MS_NOSYMFOLLOW will pick up nosymfollow from
// the data parameter through the chromiumos LSM. Kernels that do have
// MS_NOSYMFOLLOW will pick up the same behavior directly from the flag;
// our LSM ignores the string option in that case.
//
// TODO(b/152074038): Remove the string option once all devices have been
// upreved to a kernel that supports MS_NOSYMFOLLOW (currently 5.4+).
data.push_back(option);
} else {
data.push_back(option);
}
}
return std::make_pair(flags, base::JoinString(data, ","));
}
std::string MountOptions::ToFuseMounterOptions() const {
std::string result;
const char* sep = "";
for (const std::string& option : options_) {
// Do not pass the nosymfollow option to the FUSE mounter.
if (option == MountOptions::kOptionNoSymFollow)
continue;
result += sep;
result += option;
sep = ",";
}
if (result.empty())
result = MountOptions::kOptionReadOnly;
return result;
}
std::string MountOptions::ToString() const {
return options_.empty() ? kOptionReadOnly : base::JoinString(options_, ",");
}
void MountOptions::AllowOption(const std::string& option) {
allow_exact_.push_back(option);
}
void MountOptions::AllowOptionPrefix(const std::string& prefix) {
allow_prefix_.push_back(prefix);
}
void MountOptions::EnforceOption(const std::string& option) {
enforced_options_.push_back(option);
}
bool MountOptions::HasOption(const std::string& option) const {
return base::Contains(options_, option);
}
bool IsReadOnlyMount(const std::vector<std::string>& options) {
for (const auto& option : base::Reversed(options)) {
if (option == MountOptions::kOptionReadOnly)
return true;
if (option == MountOptions::kOptionReadWrite)
return false;
}
return false;
}
bool GetParamValue(const std::vector<std::string>& params,
base::StringPiece name,
std::string* value) {
if (!FindLastElementStartingWith(params, base::StrCat({name, "="}), value)) {
return false;
}
*value = value->substr(name.length() + 1);
return true;
}
void SetParamValue(std::vector<std::string>* params,
base::StringPiece name,
base::StringPiece value) {
params->emplace_back(base::StrCat({name, "=", value}));
}
} // namespace cros_disks