diff --git a/troposphere/haze/include/haze/file_system_proxy.hpp b/troposphere/haze/include/haze/file_system_proxy.hpp index 5550fb994..0497a4c8a 100644 --- a/troposphere/haze/include/haze/file_system_proxy.hpp +++ b/troposphere/haze/include/haze/file_system_proxy.hpp @@ -71,6 +71,10 @@ namespace haze { R_RETURN(this->ForwardResult(fsFsDeleteFile, m_filesystem, path)); } + Result RenameFile(const char *old_path, const char *new_path) { + R_RETURN(this->ForwardResult(fsFsRenameFile, m_filesystem, old_path, new_path)); + } + Result OpenFile(const char *path, u32 mode, FsFile *out_file) { R_RETURN(this->ForwardResult(fsFsOpenFile, m_filesystem, path, mode, out_file)); } @@ -103,6 +107,10 @@ namespace haze { R_RETURN(this->ForwardResult(fsFsDeleteDirectoryRecursively, m_filesystem, path)); } + Result RenameDirectory(const char *old_path, const char *new_path) { + R_RETURN(this->ForwardResult(fsFsRenameDirectory, m_filesystem, old_path, new_path)); + } + Result OpenDirectory(const char *path, u32 mode, FsDir *out_dir) { R_RETURN(this->ForwardResult(fsFsOpenDirectory, m_filesystem, path, mode, out_dir)); } diff --git a/troposphere/haze/include/haze/ptp_object_database.hpp b/troposphere/haze/include/haze/ptp_object_database.hpp index 72156bff4..54c787791 100644 --- a/troposphere/haze/include/haze/ptp_object_database.hpp +++ b/troposphere/haze/include/haze/ptp_object_database.hpp @@ -21,62 +21,63 @@ namespace haze { - class PtpObjectDatabase { + struct PtpObject { public: - struct ObjectNode { - public: - util::IntrusiveRedBlackTreeNode m_object_name_to_id_node; - util::IntrusiveRedBlackTreeNode m_object_id_to_name_node; - u32 m_parent_id; - u32 m_object_id; - char m_name[]; - public: - explicit ObjectNode(char *name, u32 parent_id, u32 object_id) : m_object_name_to_id_node(), m_object_id_to_name_node(), m_parent_id(parent_id), m_object_id(object_id) { /* ... */ } + util::IntrusiveRedBlackTreeNode m_object_name_to_id_node; + util::IntrusiveRedBlackTreeNode m_object_id_to_name_node; + u32 m_parent_id; + u32 m_object_id; + char m_name[]; + public: + explicit PtpObject(char *name, u32 parent_id, u32 object_id) : m_object_name_to_id_node(), m_object_id_to_name_node(), m_parent_id(parent_id), m_object_id(object_id) { /* ... */ } - const char *GetName() const { return m_name; } - u32 GetParentId() const { return m_parent_id; } - u32 GetObjectId() const { return m_object_id; } + const char *GetName() const { return m_name; } + u32 GetParentId() const { return m_parent_id; } + u32 GetObjectId() const { return m_object_id; } + bool GetIsRegistered() const { return m_object_id != 0; } - struct NameComparator { - struct RedBlackKeyType { - const char *m_name; + struct NameComparator { + struct RedBlackKeyType { + const char *m_name; - constexpr RedBlackKeyType(const char *name) : m_name(name) { /* ... */ } + constexpr RedBlackKeyType(const char *name) : m_name(name) { /* ... */ } - constexpr const char *GetName() const { - return m_name; - } - }; + constexpr const char *GetName() const { + return m_name; + } + }; - template requires (std::same_as || std::same_as) - static constexpr int Compare(const T &lhs, const ObjectNode &rhs) { - return std::strcmp(lhs.GetName(), rhs.GetName()); - } - }; - - struct IdComparator { - struct RedBlackKeyType { - u32 m_object_id; - - constexpr RedBlackKeyType(u32 object_id) : m_object_id(object_id) { /* ... */ } - - constexpr u32 GetObjectId() const { - return m_object_id; - } - }; - - template requires (std::same_as || std::same_as) - static constexpr int Compare(const T &lhs, const ObjectNode &rhs) { - return lhs.GetObjectId() - rhs.GetObjectId(); - } - }; + template requires (std::same_as || std::same_as) + static constexpr int Compare(const T &lhs, const PtpObject &rhs) { + return std::strcmp(lhs.GetName(), rhs.GetName()); + } }; - private: - using ObjectNameToIdTreeTraits = util::IntrusiveRedBlackTreeMemberTraitsDeferredAssert<&ObjectNode::m_object_name_to_id_node>; - using ObjectNameToIdTree = ObjectNameToIdTreeTraits::TreeType; - using ObjectIdToNameTreeTraits = util::IntrusiveRedBlackTreeMemberTraitsDeferredAssert<&ObjectNode::m_object_id_to_name_node>; - using ObjectIdToNameTree = ObjectIdToNameTreeTraits::TreeType; + struct IdComparator { + struct RedBlackKeyType { + u32 m_object_id; + + constexpr RedBlackKeyType(u32 object_id) : m_object_id(object_id) { /* ... */ } + + constexpr u32 GetObjectId() const { + return m_object_id; + } + }; + + template requires (std::same_as || std::same_as) + static constexpr int Compare(const T &lhs, const PtpObject &rhs) { + return lhs.GetObjectId() - rhs.GetObjectId(); + } + }; + }; + + class PtpObjectDatabase { + private: + using ObjectNameToIdTreeTraits = util::IntrusiveRedBlackTreeMemberTraitsDeferredAssert<&PtpObject::m_object_name_to_id_node>; + using ObjectNameToIdTree = ObjectNameToIdTreeTraits::TreeType; + + using ObjectIdToNameTreeTraits = util::IntrusiveRedBlackTreeMemberTraitsDeferredAssert<&PtpObject::m_object_id_to_name_node>; + using ObjectIdToNameTree = ObjectIdToNameTreeTraits::TreeType; PtpObjectHeap *m_object_heap; ObjectNameToIdTree m_name_to_object_id; @@ -89,9 +90,15 @@ namespace haze { void Finalize(); public: /* Object database API. */ - Result AddObjectId(const char *parent_name, const char *name, u32 *out_object_id, u32 parent_id, u32 desired_object_id = 0); - void RemoveObjectId(u32 object_id); - ObjectNode *GetObject(u32 object_id); + Result CreateOrFindObject(const char *parent_name, const char *name, u32 parent_id, PtpObject **out_object); + void RegisterObject(PtpObject *object, u32 desired_id = 0); + void UnregisterObject(PtpObject *object); + void DeleteObject(PtpObject *obj); + + Result CreateAndRegisterObjectId(const char *parent_name, const char *name, u32 parent_id, u32 *out_object_id); + public: + PtpObject *GetObjectById(u32 object_id); + PtpObject *GetObjectByName(const char *name); }; } diff --git a/troposphere/haze/include/haze/results.hpp b/troposphere/haze/include/haze/results.hpp index 33bd36ef1..7056e4c4d 100644 --- a/troposphere/haze/include/haze/results.hpp +++ b/troposphere/haze/include/haze/results.hpp @@ -30,11 +30,12 @@ namespace haze { R_DEFINE_ERROR_RESULT(UnknownPacketType, 6); R_DEFINE_ERROR_RESULT(SessionNotOpen, 7); R_DEFINE_ERROR_RESULT(OutOfMemory, 8); - R_DEFINE_ERROR_RESULT(ObjectNotFound, 9); - R_DEFINE_ERROR_RESULT(StorageNotFound, 10); + R_DEFINE_ERROR_RESULT(InvalidObjectId, 9); + R_DEFINE_ERROR_RESULT(InvalidStorageId, 10); R_DEFINE_ERROR_RESULT(OperationNotSupported, 11); R_DEFINE_ERROR_RESULT(UnknownRequestType, 12); R_DEFINE_ERROR_RESULT(UnknownPropertyCode, 13); - R_DEFINE_ERROR_RESULT(GeneralFailure, 14); + R_DEFINE_ERROR_RESULT(InvalidPropertyValue, 14); + R_DEFINE_ERROR_RESULT(GeneralFailure, 15); } diff --git a/troposphere/haze/source/ptp_object_database.cpp b/troposphere/haze/source/ptp_object_database.cpp index 30aa80e3e..b94b8f61e 100644 --- a/troposphere/haze/source/ptp_object_database.cpp +++ b/troposphere/haze/source/ptp_object_database.cpp @@ -37,31 +37,32 @@ namespace haze { m_object_heap = nullptr; } - Result PtpObjectDatabase::AddObjectId(const char *parent_name, const char *name, u32 *out_object_id, u32 parent_id, u32 desired_object_id) { - /* Calculate length of the name. */ - const size_t parent_name_len = std::strlen(parent_name); - const size_t name_len = std::strlen(name) + 1; - const size_t alloc_len = parent_name_len + 1 + name_len; + Result PtpObjectDatabase::CreateOrFindObject(const char *parent_name, const char *name, u32 parent_id, PtpObject **out_object) { + constexpr auto separator = "/"; + + /* Calculate length of the new name with null terminator. */ + const size_t parent_name_len = util::Strlen(parent_name); + const size_t separator_len = util::Strlen(separator); + const size_t name_len = util::Strlen(name); + const size_t terminator_len = 1; + const size_t alloc_len = sizeof(PtpObject) + parent_name_len + separator_len + name_len + terminator_len; /* Allocate memory for the node. */ - ObjectNode * const node = m_object_heap->Allocate(sizeof(ObjectNode) + alloc_len); - R_UNLESS(node != nullptr, haze::ResultOutOfMemory()); + PtpObject * const object = m_object_heap->Allocate(alloc_len); + R_UNLESS(object != nullptr, haze::ResultOutOfMemory()); { /* Ensure we maintain a clean state on failure. */ - auto guard = SCOPE_GUARD { m_object_heap->Deallocate(node, sizeof(ObjectNode) + alloc_len); }; + auto guard = SCOPE_GUARD { m_object_heap->Deallocate(object, alloc_len); }; /* Take ownership of the name. */ - std::strncpy(node->m_name, parent_name, parent_name_len + 1); - node->m_name[parent_name_len] = '/'; - std::strncpy(node->m_name + parent_name_len + 1, name, name_len); + std::strncpy(object->m_name, parent_name, parent_name_len + terminator_len); + std::strncpy(object->m_name + parent_name_len, separator, separator_len + terminator_len); + std::strncpy(object->m_name + parent_name_len + separator_len, name, name_len + terminator_len); /* Check if an object with this name already exists. If it does, we can just return it here. */ - auto it = m_name_to_object_id.find_key(node->m_name); - if (it != m_name_to_object_id.end()) { - if (out_object_id) { - *out_object_id = it->GetObjectId(); - } + if (auto * const existing = this->GetObjectByName(object->GetName()); existing != nullptr) { + *out_object = existing; R_SUCCEED(); } @@ -69,37 +70,70 @@ namespace haze { guard.Cancel(); } - /* Insert node into trees. */ - node->m_parent_id = parent_id; - node->m_object_id = desired_object_id == 0 ? m_next_object_id++ : desired_object_id; - m_name_to_object_id.insert(*node); - m_object_id_to_name.insert(*node); + /* Set node properties. */ + object->m_parent_id = parent_id; + object->m_object_id = 0; /* Set output. */ - if (out_object_id) { - *out_object_id = node->GetObjectId(); - } + *out_object = object; /* We succeeded. */ R_SUCCEED(); } - void PtpObjectDatabase::RemoveObjectId(u32 object_id) { - /* Find in forward mapping. */ - auto it = m_object_id_to_name.find_key(object_id); - if (it == m_object_id_to_name.end()) { + void PtpObjectDatabase::RegisterObject(PtpObject *object, u32 desired_id) { + /* If the object is already registered, skip registration. */ + if (object->GetIsRegistered()) { return; } - /* Free the node. */ - ObjectNode *node = std::addressof(*it); - m_object_id_to_name.erase(m_object_id_to_name.iterator_to(*node)); - m_name_to_object_id.erase(m_name_to_object_id.iterator_to(*node)); - m_object_heap->Deallocate(node, sizeof(ObjectNode) + std::strlen(node->GetName()) + 1); + /* Set desired object ID. */ + if (desired_id == 0) { + desired_id = m_next_object_id++; + } + + /* Insert node into trees. */ + object->m_object_id = desired_id; + m_name_to_object_id.insert(*object); + m_object_id_to_name.insert(*object); } - PtpObjectDatabase::ObjectNode *PtpObjectDatabase::GetObject(u32 object_id) { - /* Find in forward mapping. */ + void PtpObjectDatabase::UnregisterObject(PtpObject *object) { + /* If the object is not registered, skip trying to unregister. */ + if (!object->GetIsRegistered()) { + return; + } + + /* Remove node from trees. */ + m_object_id_to_name.erase(m_object_id_to_name.iterator_to(*object)); + m_name_to_object_id.erase(m_name_to_object_id.iterator_to(*object)); + object->m_object_id = 0; + } + + void PtpObjectDatabase::DeleteObject(PtpObject *object) { + /* Unregister the object as required. */ + this->UnregisterObject(object); + + /* Free the object. */ + m_object_heap->Deallocate(object, sizeof(PtpObject) + std::strlen(object->GetName()) + 1); + } + + Result PtpObjectDatabase::CreateAndRegisterObjectId(const char *parent_name, const char *name, u32 parent_id, u32 *out_object_id) { + /* Try to create the object. */ + PtpObject *object; + R_TRY(this->CreateOrFindObject(parent_name, name, parent_id, std::addressof(object))); + + /* We succeeded, so register it. */ + this->RegisterObject(object); + + /* Set the output ID. */ + *out_object_id = object->GetObjectId(); + + R_SUCCEED(); + } + + PtpObject *PtpObjectDatabase::GetObjectById(u32 object_id) { + /* Find in ID mapping. */ auto it = m_object_id_to_name.find_key(object_id); if (it == m_object_id_to_name.end()) { return nullptr; @@ -108,4 +142,13 @@ namespace haze { return std::addressof(*it); } + PtpObject *PtpObjectDatabase::GetObjectByName(const char *name) { + /* Find in name mapping. */ + auto it = m_name_to_object_id.find_key(name); + if (it == m_name_to_object_id.end()) { + return nullptr; + } + + return std::addressof(*it); + } } diff --git a/troposphere/haze/source/ptp_responder.cpp b/troposphere/haze/source/ptp_responder.cpp index 2cce682cf..bbb47f9a9 100644 --- a/troposphere/haze/source/ptp_responder.cpp +++ b/troposphere/haze/source/ptp_responder.cpp @@ -205,15 +205,18 @@ namespace haze { R_CATCH(haze::ResultOperationNotSupported) { R_TRY(this->WriteResponse(PtpResponseCode_OperationNotSupported)); } - R_CATCH(haze::ResultStorageNotFound) { + R_CATCH(haze::ResultInvalidStorageId) { R_TRY(this->WriteResponse(PtpResponseCode_InvalidStorageId)); } - R_CATCH(haze::ResultObjectNotFound) { + R_CATCH(haze::ResultInvalidObjectId) { R_TRY(this->WriteResponse(PtpResponseCode_InvalidObjectHandle)); } R_CATCH(haze::ResultUnknownPropertyCode) { R_TRY(this->WriteResponse(PtpResponseCode_MtpObjectPropNotSupported)); } + R_CATCH(haze::ResultInvalidPropertyValue) { + R_TRY(this->WriteResponse(PtpResponseCode_MtpInvalidObjectPropValue)); + } R_CATCH_MODULE(fs) { /* Errors from fs are typically recoverable. */ R_TRY(this->WriteResponse(PtpResponseCode_GeneralError)); @@ -313,11 +316,18 @@ namespace haze { /* Close, if we're already open. */ this->ForceCloseSession(); - /* Initialize the database with hardcoded storage IDs. */ + /* Initialize the database. */ m_session_open = true; m_object_database.Initialize(m_object_heap); - R_TRY(m_object_database.AddObjectId("", "", nullptr, PtpGetObjectHandles_RootParent, StorageId_SdmcFs)); + /* Create the root storages. */ + PtpObject *object; + R_TRY(m_object_database.CreateOrFindObject("", "", PtpGetObjectHandles_RootParent, std::addressof(object))); + + /* Register the root storages. */ + m_object_database.RegisterObject(object, StorageId_SdmcFs); + + /* We succeeded. */ R_RETURN(this->WriteResponse(PtpResponseCode_Ok)); } @@ -365,7 +375,7 @@ namespace haze { } break; default: - R_THROW(haze::ResultStorageNotFound()); + R_THROW(haze::ResultInvalidStorageId()); } /* Write the result. */ @@ -406,12 +416,12 @@ namespace haze { } /* Check if we know about the object. If we don't, it's an error. */ - auto * const fileobj = m_object_database.GetObject(association_object_handle); - R_UNLESS(fileobj != nullptr, haze::ResultObjectNotFound()); + auto * const obj = m_object_database.GetObjectById(association_object_handle); + R_UNLESS(obj != nullptr, haze::ResultInvalidObjectId()); /* Try to read the object as a directory. */ FsDir dir; - R_TRY(m_fs.OpenDirectory(fileobj->GetName(), FsDirOpenMode_ReadDirs | FsDirOpenMode_ReadFiles, std::addressof(dir))); + R_TRY(m_fs.OpenDirectory(obj->GetName(), FsDirOpenMode_ReadDirs | FsDirOpenMode_ReadFiles, std::addressof(dir))); /* Ensure we maintain a clean state on exit. */ ON_SCOPE_EXIT { m_fs.CloseDirectory(std::addressof(dir)); }; @@ -435,7 +445,7 @@ namespace haze { /* Write to output. */ for (s64 i = 0; i < read_count; i++) { u32 handle; - R_TRY(m_object_database.AddObjectId(fileobj->GetName(), g_dir_entries[i].name, std::addressof(handle), fileobj->GetObjectId())); + R_TRY(m_object_database.CreateAndRegisterObjectId(obj->GetName(), g_dir_entries[i].name, obj->GetObjectId(), std::addressof(handle))); R_TRY(db.Add(handle)); } @@ -459,8 +469,8 @@ namespace haze { R_TRY(dp.Finalize()); /* Check if we know about the object. If we don't, it's an error. */ - auto * const fileobj = m_object_database.GetObject(object_id); - R_UNLESS(fileobj != nullptr, haze::ResultObjectNotFound()); + auto * const obj = m_object_database.GetObjectById(object_id); + R_UNLESS(obj != nullptr, haze::ResultInvalidObjectId()); /* Build info about the object. */ PtpObjectInfo object_info(DefaultObjectInfo); @@ -473,13 +483,13 @@ namespace haze { } else { /* Figure out what type of object this is. */ FsDirEntryType entry_type; - R_TRY(m_fs.GetEntryType(fileobj->GetName(), std::addressof(entry_type))); + R_TRY(m_fs.GetEntryType(obj->GetName(), std::addressof(entry_type))); /* Get the size, if we are requesting info about a file. */ s64 size = 0; if (entry_type == FsDirEntryType_File) { FsFile file; - R_TRY(m_fs.OpenFile(fileobj->GetName(), FsOpenMode_Read, std::addressof(file))); + R_TRY(m_fs.OpenFile(obj->GetName(), FsOpenMode_Read, std::addressof(file))); /* Ensure we maintain a clean state on exit. */ ON_SCOPE_EXIT { m_fs.CloseFile(std::addressof(file)); }; @@ -487,9 +497,9 @@ namespace haze { R_TRY(m_fs.GetFileSize(std::addressof(file), std::addressof(size))); } - object_info.filename = std::strrchr(fileobj->GetName(), '/') + 1; + object_info.filename = std::strrchr(obj->GetName(), '/') + 1; object_info.object_compressed_size = size; - object_info.parent_object = fileobj->GetParentId(); + object_info.parent_object = obj->GetParentId(); if (entry_type == FsDirEntryType_Dir) { object_info.object_format = PtpObjectFormatCode_Association; @@ -536,12 +546,12 @@ namespace haze { R_TRY(dp.Finalize()); /* Check if we know about the object. If we don't, it's an error. */ - auto * const fileobj = m_object_database.GetObject(object_id); - R_UNLESS(fileobj != nullptr, haze::ResultObjectNotFound()); + auto * const obj = m_object_database.GetObjectById(object_id); + R_UNLESS(obj != nullptr, haze::ResultInvalidObjectId()); /* Lock the object as a file. */ FsFile file; - R_TRY(m_fs.OpenFile(fileobj->GetName(), FsOpenMode_Read, std::addressof(file))); + R_TRY(m_fs.OpenFile(obj->GetName(), FsOpenMode_Read, std::addressof(file))); /* Ensure we maintain a clean state on exit. */ ON_SCOPE_EXIT { m_fs.CloseFile(std::addressof(file)); }; @@ -585,7 +595,7 @@ namespace haze { PtpDataParser dp(g_bulk_read_buffer, std::addressof(m_usb_server)); PtpObjectInfo info(DefaultObjectInfo); - /* Ensure that we have a data header. */ + /* Ensure we have a data header. */ PtpUsbBulkContainer data_header; R_TRY(dp.Read(std::addressof(data_header))); R_UNLESS(data_header.type == PtpUsbBulkContainerType_Data, haze::ResultUnknownRequestType()); @@ -620,29 +630,31 @@ namespace haze { } /* Check if we know about the parent object. If we don't, it's an error. */ - auto * const parentobj = m_object_database.GetObject(parent_object); - R_UNLESS(parentobj != nullptr, haze::ResultObjectNotFound()); + auto * const parentobj = m_object_database.GetObjectById(parent_object); + R_UNLESS(parentobj != nullptr, haze::ResultInvalidObjectId()); /* Make a new object with the intended name. */ PtpNewObjectInfo new_object_info; new_object_info.storage_id = StorageId_SdmcFs; new_object_info.parent_object_id = parent_object == storage_id ? 0 : parent_object; - R_TRY(m_object_database.AddObjectId(parentobj->GetName(), g_filename_str, std::addressof(new_object_info.object_id), parentobj->GetObjectId())); + /* Create the object in the database. */ + PtpObject *obj; + R_TRY(m_object_database.CreateOrFindObject(parentobj->GetName(), g_filename_str, parentobj->GetObjectId(), std::addressof(obj))); /* Ensure we maintain a clean state on failure. */ - ON_RESULT_FAILURE { m_object_database.RemoveObjectId(new_object_info.object_id); }; + ON_RESULT_FAILURE { m_object_database.DeleteObject(obj); }; - /* Get the object name we just built. */ - auto * const fileobj = m_object_database.GetObject(new_object_info.object_id); - R_UNLESS(fileobj != nullptr, haze::ResultGeneralFailure()); + /* Register the object with a new ID. */ + m_object_database.RegisterObject(obj); + new_object_info.object_id = obj->GetObjectId(); /* Create the object on the filesystem. */ if (info.object_format == PtpObjectFormatCode_Association) { - R_TRY(m_fs.CreateDirectory(fileobj->GetName())); + R_TRY(m_fs.CreateDirectory(obj->GetName())); m_send_object_id = 0; } else { - R_TRY(m_fs.CreateFile(fileobj->GetName(), 0, 0)); + R_TRY(m_fs.CreateFile(obj->GetName(), 0, 0)); m_send_object_id = new_object_info.object_id; } @@ -658,7 +670,7 @@ namespace haze { PtpDataParser dp(g_bulk_read_buffer, std::addressof(m_usb_server)); - /* Ensure that we have a data header. */ + /* Ensure we have a data header. */ PtpUsbBulkContainer data_header; R_TRY(dp.Read(std::addressof(data_header))); R_UNLESS(data_header.type == PtpUsbBulkContainerType_Data, haze::ResultUnknownRequestType()); @@ -666,12 +678,12 @@ namespace haze { R_UNLESS(data_header.trans_id == m_request_header.trans_id, haze::ResultOperationNotSupported()); /* Check if we know about the object. If we don't, it's an error. */ - auto * const fileobj = m_object_database.GetObject(m_send_object_id); - R_UNLESS(fileobj != nullptr, haze::ResultObjectNotFound()); + auto * const obj = m_object_database.GetObjectById(m_send_object_id); + R_UNLESS(obj != nullptr, haze::ResultInvalidObjectId()); /* Lock the object as a file. */ FsFile file; - R_TRY(m_fs.OpenFile(fileobj->GetName(), FsOpenMode_Write | FsOpenMode_Append, std::addressof(file))); + R_TRY(m_fs.OpenFile(obj->GetName(), FsOpenMode_Write | FsOpenMode_Append, std::addressof(file))); /* Ensure we maintain a clean state on exit. */ ON_SCOPE_EXIT { m_fs.CloseFile(std::addressof(file)); }; @@ -708,23 +720,26 @@ namespace haze { R_TRY(dp.Read(std::addressof(object_id))); R_TRY(dp.Finalize()); + /* Disallow deleting the storage root. */ + R_UNLESS(object_id != StorageId_SdmcFs, haze::ResultInvalidObjectId()); + /* Check if we know about the object. If we don't, it's an error. */ - auto * const fileobj = m_object_database.GetObject(object_id); - R_UNLESS(fileobj != nullptr, haze::ResultObjectNotFound()); + auto * const obj = m_object_database.GetObjectById(object_id); + R_UNLESS(obj != nullptr, haze::ResultInvalidObjectId()); /* Figure out what type of object this is. */ FsDirEntryType entry_type; - R_TRY(m_fs.GetEntryType(fileobj->GetName(), std::addressof(entry_type))); + R_TRY(m_fs.GetEntryType(obj->GetName(), std::addressof(entry_type))); /* Remove the object from the filesystem. */ if (entry_type == FsDirEntryType_Dir) { - R_TRY(m_fs.DeleteDirectoryRecursively(fileobj->GetName())); + R_TRY(m_fs.DeleteDirectoryRecursively(obj->GetName())); } else { - R_TRY(m_fs.DeleteFile(fileobj->GetName())); + R_TRY(m_fs.DeleteFile(obj->GetName())); } - /* Remove the object from tracking. */ - m_object_database.RemoveObjectId(fileobj->GetObjectId()); + /* Remove the object from the database. */ + m_object_database.DeleteObject(obj); /* We succeeded. */ R_RETURN(this->WriteResponse(PtpResponseCode_Ok)); @@ -816,22 +831,25 @@ namespace haze { R_TRY(dp.Read(std::addressof(property_code))); R_TRY(dp.Finalize()); + /* Disallow renaming the storage root. */ + R_UNLESS(object_id != StorageId_SdmcFs, haze::ResultInvalidObjectId()); + /* Ensure we have a valid property code before continuing. */ R_UNLESS(IsSupportedObjectPropertyCode(property_code), haze::ResultUnknownPropertyCode()); /* Check if we know about the object. If we don't, it's an error. */ - auto * const fileobj = m_object_database.GetObject(object_id); - R_UNLESS(fileobj != nullptr, haze::ResultObjectNotFound()); + auto * const obj = m_object_database.GetObjectById(object_id); + R_UNLESS(obj != nullptr, haze::ResultInvalidObjectId()); /* Get the object type. */ FsDirEntryType entry_type; - R_TRY(m_fs.GetEntryType(fileobj->GetName(), std::addressof(entry_type))); + R_TRY(m_fs.GetEntryType(obj->GetName(), std::addressof(entry_type))); /* Get the object size. */ s64 size = 0; if (entry_type == FsDirEntryType_File) { FsFile file; - R_TRY(m_fs.OpenFile(fileobj->GetName(), FsOpenMode_Read, std::addressof(file))); + R_TRY(m_fs.OpenFile(obj->GetName(), FsOpenMode_Read, std::addressof(file))); /* Ensure we maintain a clean state on exit. */ ON_SCOPE_EXIT { m_fs.CloseFile(std::addressof(file)); }; @@ -854,7 +872,7 @@ namespace haze { R_TRY(db.Add(entry_type == FsDirEntryType_File ? PtpObjectFormatCode_Undefined : PtpObjectFormatCode_Association)); break; case PtpObjectPropertyCode_ObjectFileName: - R_TRY(db.AddString(std::strrchr(fileobj->GetName(), '/') + 1)); + R_TRY(db.AddString(std::strrchr(obj->GetName(), '/') + 1)); break; HAZE_UNREACHABLE_DEFAULT_CASE(); } @@ -866,7 +884,82 @@ namespace haze { R_RETURN(this->WriteResponse(PtpResponseCode_Ok)); } - Result PtpResponder::SetObjectPropValue(PtpDataParser &dp) { - R_THROW(haze::ResultOperationNotSupported()); + Result PtpResponder::SetObjectPropValue(PtpDataParser &rdp) { + u32 object_id; + PtpObjectPropertyCode property_code; + + R_TRY(rdp.Read(std::addressof(object_id))); + R_TRY(rdp.Read(std::addressof(property_code))); + R_TRY(rdp.Finalize()); + + PtpDataParser dp(g_bulk_read_buffer, std::addressof(m_usb_server)); + + /* Ensure we have a data header. */ + PtpUsbBulkContainer data_header; + R_TRY(dp.Read(std::addressof(data_header))); + R_UNLESS(data_header.type == PtpUsbBulkContainerType_Data, haze::ResultUnknownRequestType()); + R_UNLESS(data_header.code == m_request_header.code, haze::ResultOperationNotSupported()); + R_UNLESS(data_header.trans_id == m_request_header.trans_id, haze::ResultOperationNotSupported()); + + /* Ensure we have a valid property code before continuing. */ + R_UNLESS(property_code == PtpObjectPropertyCode_ObjectFileName, haze::ResultUnknownPropertyCode()); + + /* Check if we know about the object. If we don't, it's an error. */ + auto * const obj = m_object_database.GetObjectById(object_id); + R_UNLESS(obj != nullptr, haze::ResultInvalidObjectId()); + + /* We are reading a file name. */ + R_TRY(dp.ReadString(g_filename_str)); + R_TRY(dp.Finalize()); + + /* Ensure we can actually process the new name. */ + const bool is_empty = g_filename_str[0] == '\x00'; + const bool contains_slashes = std::strchr(g_filename_str, '/') != nullptr; + R_UNLESS(!is_empty && !contains_slashes, haze::ResultInvalidPropertyValue()); + + /* Add a new object in the database with the new name. */ + PtpObject *newobj; + { + /* Find the last path separator in the existing object name. */ + char *pathsep = std::strrchr(obj->m_name, '/'); + HAZE_ASSERT(pathsep != nullptr); + + /* Temporarily mark the path separator as null to facilitate processing. */ + *pathsep = '\x00'; + ON_SCOPE_EXIT { *pathsep = '/'; }; + + R_TRY(m_object_database.CreateOrFindObject(obj->GetName(), g_filename_str, obj->GetParentId(), std::addressof(newobj))); + } + + { + /* Ensure we maintain a clean state on failure. */ + ON_RESULT_FAILURE { + if (!newobj->GetIsRegistered()) { + /* Only delete if the object was not registered. */ + /* Otherwise, we would remove an object that still exists. */ + m_object_database.DeleteObject(newobj); + } + }; + + /* Get the old object type. */ + FsDirEntryType entry_type; + R_TRY(m_fs.GetEntryType(obj->GetName(), std::addressof(entry_type))); + + /* Attempt to rename the object on the filesystem. */ + if (entry_type == FsDirEntryType_Dir) { + R_TRY(m_fs.RenameDirectory(obj->GetName(), newobj->GetName())); + } else { + R_TRY(m_fs.RenameFile(obj->GetName(), newobj->GetName())); + } + } + + /* Unregister and free the old object. */ + m_object_database.DeleteObject(obj); + + /* Register the new object. */ + m_object_database.RegisterObject(newobj, object_id); + + /* We succeeded. */ + R_RETURN(this->WriteResponse(PtpResponseCode_Ok)); } }