blob: 2868b7419b4517e2a368be3deb65cb20e950245f [file] [log] [blame]
// Copyright 2015 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 "settingsd/blob_store.h"
#include <algorithm>
#include <base/logging.h>
#include <base/strings/stringprintf.h>
#include "settingsd/file_utils.h"
#include "settingsd/key.h"
namespace settingsd {
namespace {
// Defines the maximum length of source ids. This requirement stems from the
// constraints on the maximum length of file sytem entries from the underlying
// operating system.
const unsigned int kMaxSourceIdLength = 255u;
// Defines the length of a blob filename. Note that this must be compatible with
// |kFormatBlobFilename|.
const unsigned int kBlobFilenameLength = 10u;
// Defines the file name format for blobs.
const char kFormatBlobFilename[] = "blob_%05u";
// Defines the path format of the directory for blobs belonging to a source.
const char kFormatSourcePath[] = "%s/%s/";
// Defines the maximum supported size of SettingsBlobs in bytes.
const unsigned int kMaxSettingsBlobSizeBytes = 1024u * 1024u;
} // namespace
BlobStore::Handle::Handle() : blob_id_(0) {}
BlobStore::Handle::Handle(unsigned int blob_id, const std::string& source_id)
: blob_id_(blob_id), source_id_(source_id) {}
bool BlobStore::Handle::IsValid() const {
return blob_id_ != 0 && !source_id_.empty();
}
BlobStore::BlobStore(const std::string& storage_path)
: storage_path_(storage_path) {
DCHECK_EQ(base::StringPrintf(kFormatBlobFilename, 0).length(),
kBlobFilenameLength);
}
BlobStore::Handle BlobStore::Store(const std::string& source_id,
BlobRef blob) const {
DCHECK(!source_id.empty());
// Check if the directory for |source_id| exists, if it doesn't, create it.
std::string source_path = GetSourcePath(source_id);
if (source_path.empty())
return Handle();
if (!utils::PathExists(source_path))
utils::CreateDirectory(source_path);
// Determine the next unused blob id, construct the blob path and write blob.
unsigned int blob_id = GetNextUnusedBlobId(source_id);
std::string blob_path = GetBlobPath(blob_id, source_id);
if (blob_path.empty())
return Handle();
if (utils::WriteFileAtomically(blob_path, blob.data(), blob.size()))
return Handle(blob_id, source_id);
// Failed to write the file. Return an invalid Handle.
return Handle();
}
const std::vector<uint8_t> BlobStore::Load(Handle handle) const {
std::vector<uint8_t> blob;
std::string blob_path = GetBlobPath(handle.blob_id_, handle.source_id_);
if (blob_path.empty())
return std::vector<uint8_t>();
utils::ReadFile(blob_path, &blob, kMaxSettingsBlobSizeBytes);
return blob;
}
std::vector<BlobStore::Handle> BlobStore::List(
const std::string& source_id) const {
std::vector<BlobStore::Handle> handles;
std::string source_path = GetSourcePath(source_id);
if (source_path.empty())
return std::vector<BlobStore::Handle>();
std::vector<std::string> files = utils::ListFiles(source_path);
for (auto& file : files) {
Handle handle(FilenameToBlobId(file), source_id);
if (handle.IsValid())
handles.push_back(handle);
}
return handles;
}
bool BlobStore::Purge(Handle handle) const {
if (!handle.IsValid())
return false;
std::string blob_path = GetBlobPath(handle.blob_id_, handle.source_id_);
if (blob_path.empty())
return false;
return utils::DeleteFile(blob_path);
}
std::string BlobStore::GetBlobPath(unsigned int blob_id,
const std::string& source_id) const {
std::string source_path = GetSourcePath(source_id);
if (source_path.empty())
return std::string();
std::string filename = base::StringPrintf(kFormatBlobFilename, blob_id);
if (kBlobFilenameLength != filename.size()) {
LOG(ERROR) << "Invalid blob id: " << blob_id;
return std::string();
}
return source_path + filename;
}
std::string BlobStore::GetSourcePath(const std::string& source_id) const {
if (source_id.empty() || source_id.length() > kMaxSourceIdLength ||
!Key::IsValidKey(source_id)) {
LOG(ERROR) << "Invalid source id: " << source_id;
return std::string();
}
return base::StringPrintf(kFormatSourcePath, storage_path_.c_str(),
source_id.c_str());
}
unsigned int BlobStore::FilenameToBlobId(const std::string& filename) const {
unsigned int id = 0;
if (filename.size() != kBlobFilenameLength) {
LOG(ERROR) << "Not a blob filename: " << filename;
return id;
}
sscanf(filename.c_str(), kFormatBlobFilename, &id);
return id;
}
unsigned int BlobStore::GetNextUnusedBlobId(
const std::string& source_id) const {
std::vector<std::string> files = utils::ListFiles(GetSourcePath(source_id));
// Sort the filenames lexicographically and reverse iterate over them. Note
// that due to the filename format <<kPrefix>>_XXXXX filenames lexicographic
// sorting and blob id sorting are the same.
std::sort(files.begin(), files.end());
for (auto it = files.rbegin(); it != files.rend(); ++it) {
// Attempt to extract the blob id from the filename.
unsigned int id = FilenameToBlobId(*it);
// If this file does not follow our naming scheme, go to next file.
if (!id)
continue;
return id + 1;
}
// No previous blob for |source_id| found.
return 1;
}
} // namespace settingsd