| // Copyright 2015 Google Inc. All Rights Reserved. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| #include "cobalt/storage/virtual_file_system.h" |
| |
| #include "base/logging.h" |
| #include "base/synchronization/lock.h" |
| #include "cobalt/storage/virtual_file.h" |
| |
| #include "starboard/client_porting/poem/string_poem.h" |
| |
| namespace cobalt { |
| namespace storage { |
| |
| namespace { |
| // Update this any time the serialization format changes. |
| const char kVersion[] = "SAV0"; |
| } // namespace |
| |
| // static |
| int VirtualFileSystem::GetHeaderVersion(const SerializedHeader& header) { |
| // Copy the version int to a char buffer to avoid endian issues. |
| char version[4]; |
| memcpy(version, &header.version, sizeof(version)); |
| |
| if (memcmp(version, kVersion, 3) != 0) { |
| return -1; |
| } else if (header.file_size < static_cast<int>(sizeof(SerializedHeader))) { |
| return -1; |
| } else { |
| return version[3] - '0'; |
| } |
| } |
| |
| // static |
| int VirtualFileSystem::GetCurrentVersion() { |
| COMPILE_ASSERT(sizeof(kVersion) - 1 == 4, Unexpected_version_size); |
| int version; |
| memcpy(&version, kVersion, sizeof(version)); |
| return version; |
| } |
| |
| VirtualFileSystem::VirtualFileSystem() {} |
| |
| VirtualFileSystem::~VirtualFileSystem() { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| base::AutoLock lock(file_table_lock_); |
| ClearFileTable(); |
| } |
| |
| VirtualFile* VirtualFileSystem::Open(const std::string& filename) { |
| base::AutoLock lock(file_table_lock_); |
| VirtualFile* result = NULL; |
| FileTable::iterator it = table_.find(filename); |
| if (it != table_.end()) { |
| result = it->second; |
| } else { |
| result = new VirtualFile(filename); |
| table_.insert(it, FileTable::value_type(filename, result)); |
| } |
| return result; |
| } |
| |
| std::vector<std::string> VirtualFileSystem::ListFiles() { |
| base::AutoLock lock(file_table_lock_); |
| std::vector<std::string> files; |
| for (FileTable::iterator it = table_.begin(); it != table_.end(); ++it) { |
| files.push_back(it->first); |
| } |
| return files; |
| } |
| |
| void VirtualFileSystem::Delete(const std::string& filename) { |
| base::AutoLock lock(file_table_lock_); |
| FileTable::iterator it = table_.find(filename); |
| if (it != table_.end()) { |
| delete it->second; |
| table_.erase(it); |
| } |
| } |
| |
| int VirtualFileSystem::Serialize(uint8* buffer, bool dry_run) { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| base::AutoLock lock(file_table_lock_); |
| uint8* original = buffer; |
| |
| // We don't know the total size of the file yet, so defer writing the header. |
| buffer += sizeof(SerializedHeader); |
| int valid_file_count = 0; |
| |
| // Serialize each file |
| for (FileTable::iterator it = table_.begin(); it != table_.end(); ++it) { |
| if (it->second->Size() == 0) { |
| continue; |
| } |
| int file_bytes = it->second->Serialize(buffer, dry_run); |
| buffer += file_bytes; |
| valid_file_count++; |
| } |
| const int bytes_written = static_cast<int>(buffer - original); |
| if (!dry_run) { |
| // Now we can write the header to the beginning of the buffer. |
| SerializedHeader header; |
| header.version = GetCurrentVersion(); |
| header.file_count = valid_file_count; |
| header.file_size = bytes_written; |
| memcpy(original, &header, sizeof(SerializedHeader)); |
| } |
| |
| return bytes_written; |
| } |
| |
| bool VirtualFileSystem::Deserialize(const uint8* buffer, int buffer_size_in) { |
| const uint8* caller_buffer = buffer; |
| |
| base::AutoLock lock(file_table_lock_); |
| ClearFileTable(); |
| |
| if (buffer_size_in < 0) { |
| DLOG(ERROR) << "Buffer size must be positive: " << buffer_size_in |
| << " < 0."; |
| return false; |
| } |
| |
| size_t buffer_size = static_cast<size_t>(buffer_size_in); |
| |
| // The size of the buffer must be checked before copying the beginning of it |
| // into a SerializedHeader. |
| if (buffer_size < sizeof(SerializedHeader)) { |
| DLOG(ERROR) << "Buffer size " << buffer_size |
| << " is too small to contain a SerializedHeader of size " |
| << sizeof(SerializedHeader) << "; operation aborted."; |
| return false; |
| } |
| |
| // Read in expected number of files |
| SerializedHeader header; |
| memcpy(&header, buffer, sizeof(SerializedHeader)); |
| buffer += sizeof(SerializedHeader); |
| |
| int buffer_version = GetHeaderVersion(header); |
| if (buffer_version != 0) { |
| // Note: We would handle old versions here, if necessary. |
| DLOG(ERROR) << "Attempted to load a different version; operation aborted."; |
| return false; |
| } else if (static_cast<size_t>(header.file_size) != buffer_size) { |
| DLOG(ERROR) << "Buffer size mismatch: " << header.file_size |
| << " != " << buffer_size; |
| return false; |
| } |
| |
| for (int i = 0; i < header.file_count; ++i) { |
| size_t buffer_used = static_cast<size_t>(buffer - caller_buffer); |
| if (buffer_size < buffer_used) { |
| DLOG(ERROR) << "Buffer overrun deserializing files"; |
| ClearFileTable(); |
| return false; |
| } |
| size_t buffer_remaining = buffer_size - buffer_used; |
| |
| VirtualFile* file = new VirtualFile(""); |
| |
| int bytes = file->Deserialize(buffer, buffer_remaining); |
| if (bytes > 0) { |
| buffer += bytes; |
| table_[file->name_] = file; |
| } else { |
| DLOG(WARNING) << "Failed to deserialize virtual file system."; |
| delete file; |
| ClearFileTable(); |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| void VirtualFileSystem::ClearFileTable() { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| file_table_lock_.AssertAcquired(); |
| for (FileTable::iterator it = table_.begin(); it != table_.end(); ++it) { |
| delete it->second; |
| } |
| table_.clear(); |
| } |
| |
| } // namespace storage |
| } // namespace cobalt |