diff --git a/core/io/file_access.cpp b/core/io/file_access.cpp index 4e1bcb712ea..e3a65326c71 100644 --- a/core/io/file_access.cpp +++ b/core/io/file_access.cpp @@ -771,6 +771,83 @@ Error FileAccess::set_read_only_attribute(const String &p_file, bool p_ro) { return err; } +PackedByteArray FileAccess::get_extended_attribute(const String &p_file, const String &p_attribute_name) { + if (PackedData::get_singleton() && !PackedData::get_singleton()->is_disabled() && (PackedData::get_singleton()->has_path(p_file) || PackedData::get_singleton()->has_directory(p_file))) { + return PackedByteArray(); + } + + Ref fa = create_for_path(p_file); + ERR_FAIL_COND_V_MSG(fa.is_null(), PackedByteArray(), vformat("Cannot create FileAccess for path '%s'.", p_file)); + + return fa->_get_extended_attribute(p_file, p_attribute_name); +} + +String FileAccess::get_extended_attribute_string(const String &p_file, const String &p_attribute_name) { + if (PackedData::get_singleton() && !PackedData::get_singleton()->is_disabled() && (PackedData::get_singleton()->has_path(p_file) || PackedData::get_singleton()->has_directory(p_file))) { + return String(); + } + + Ref fa = create_for_path(p_file); + ERR_FAIL_COND_V_MSG(fa.is_null(), String(), vformat("Cannot create FileAccess for path '%s'.", p_file)); + + PackedByteArray data = fa->_get_extended_attribute(p_file, p_attribute_name); + if (data.is_empty()) { + return String(); + } + return String::utf8((const char *)data.ptr(), data.size()); +} + +Error FileAccess::set_extended_attribute(const String &p_file, const String &p_attribute_name, const PackedByteArray &p_data) { + if (PackedData::get_singleton() && !PackedData::get_singleton()->is_disabled() && (PackedData::get_singleton()->has_path(p_file) || PackedData::get_singleton()->has_directory(p_file))) { + return ERR_UNAVAILABLE; + } + + Ref fa = create_for_path(p_file); + ERR_FAIL_COND_V_MSG(fa.is_null(), ERR_CANT_CREATE, vformat("Cannot create FileAccess for path '%s'.", p_file)); + + return fa->_set_extended_attribute(p_file, p_attribute_name, p_data); +} + +Error FileAccess::set_extended_attribute_string(const String &p_file, const String &p_attribute_name, const String &p_data) { + if (PackedData::get_singleton() && !PackedData::get_singleton()->is_disabled() && (PackedData::get_singleton()->has_path(p_file) || PackedData::get_singleton()->has_directory(p_file))) { + return ERR_UNAVAILABLE; + } + + Ref fa = create_for_path(p_file); + ERR_FAIL_COND_V_MSG(fa.is_null(), ERR_CANT_CREATE, vformat("Cannot create FileAccess for path '%s'.", p_file)); + + PackedByteArray data; + CharString cs = p_data.utf8(); + data.resize(cs.size()); + if (cs.size() > 0) { + memcpy(data.ptrw(), cs.get_data(), cs.size()); + } + + return fa->_set_extended_attribute(p_file, p_attribute_name, data); +} + +Error FileAccess::remove_extended_attribute(const String &p_file, const String &p_attribute_name) { + if (PackedData::get_singleton() && !PackedData::get_singleton()->is_disabled() && (PackedData::get_singleton()->has_path(p_file) || PackedData::get_singleton()->has_directory(p_file))) { + return ERR_UNAVAILABLE; + } + + Ref fa = create_for_path(p_file); + ERR_FAIL_COND_V_MSG(fa.is_null(), ERR_CANT_CREATE, vformat("Cannot create FileAccess for path '%s'.", p_file)); + + return fa->_remove_extended_attribute(p_file, p_attribute_name); +} + +PackedStringArray FileAccess::get_extended_attributes_list(const String &p_file) { + if (PackedData::get_singleton() && !PackedData::get_singleton()->is_disabled() && (PackedData::get_singleton()->has_path(p_file) || PackedData::get_singleton()->has_directory(p_file))) { + return PackedStringArray(); + } + + Ref fa = create_for_path(p_file); + ERR_FAIL_COND_V_MSG(fa.is_null(), PackedStringArray(), vformat("Cannot create FileAccess for path '%s'.", p_file)); + + return fa->_get_extended_attributes_list(p_file); +} + bool FileAccess::store_string(const String &p_string) { if (p_string.length() == 0) { return true; @@ -1042,6 +1119,13 @@ void FileAccess::_bind_methods() { ClassDB::bind_static_method("FileAccess", D_METHOD("set_read_only_attribute", "file", "ro"), &FileAccess::set_read_only_attribute); ClassDB::bind_static_method("FileAccess", D_METHOD("get_read_only_attribute", "file"), &FileAccess::get_read_only_attribute); + ClassDB::bind_static_method("FileAccess", D_METHOD("get_extended_attribute", "file", "attribute_name"), &FileAccess::get_extended_attribute); + ClassDB::bind_static_method("FileAccess", D_METHOD("get_extended_attribute_string", "file", "attribute_name"), &FileAccess::get_extended_attribute_string); + ClassDB::bind_static_method("FileAccess", D_METHOD("set_extended_attribute", "file", "attribute_name", "data"), &FileAccess::set_extended_attribute); + ClassDB::bind_static_method("FileAccess", D_METHOD("set_extended_attribute_string", "file", "attribute_name", "_data"), &FileAccess::set_extended_attribute_string); + ClassDB::bind_static_method("FileAccess", D_METHOD("remove_extended_attribute", "file", "attribute_name"), &FileAccess::remove_extended_attribute); + ClassDB::bind_static_method("FileAccess", D_METHOD("get_extended_attributes_list", "file"), &FileAccess::get_extended_attributes_list); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "big_endian"), "set_big_endian", "is_big_endian"); BIND_ENUM_CONSTANT(READ); diff --git a/core/io/file_access.h b/core/io/file_access.h index 3c53c0300bd..4b7fa787a56 100644 --- a/core/io/file_access.h +++ b/core/io/file_access.h @@ -102,6 +102,11 @@ public: virtual bool _get_read_only_attribute(const String &p_file) = 0; virtual Error _set_read_only_attribute(const String &p_file, bool p_ro) = 0; + virtual PackedByteArray _get_extended_attribute(const String &p_file, const String &p_attribute_name) { return PackedByteArray(); } + virtual Error _set_extended_attribute(const String &p_file, const String &p_attribute_name, const PackedByteArray &p_data) { return ERR_UNAVAILABLE; } + virtual Error _remove_extended_attribute(const String &p_file, const String &p_attribute_name) { return ERR_UNAVAILABLE; } + virtual PackedStringArray _get_extended_attributes_list(const String &p_file) { return PackedStringArray(); } + protected: static void _bind_methods(); @@ -256,6 +261,13 @@ public: static bool get_read_only_attribute(const String &p_file); static Error set_read_only_attribute(const String &p_file, bool p_ro); + static PackedByteArray get_extended_attribute(const String &p_file, const String &p_attribute_name); + static String get_extended_attribute_string(const String &p_file, const String &p_attribute_name); + static Error set_extended_attribute(const String &p_file, const String &p_attribute_name, const PackedByteArray &p_data); + static Error set_extended_attribute_string(const String &p_file, const String &p_attribute_name, const String &p_data); + static Error remove_extended_attribute(const String &p_file, const String &p_attribute_name); + static PackedStringArray get_extended_attributes_list(const String &p_file); + static void set_backup_save(bool p_enable) { backup_save = p_enable; } static bool is_backup_save_enabled() { return backup_save; } diff --git a/doc/classes/FileAccess.xml b/doc/classes/FileAccess.xml index 74e6426cdff..8d0b80bfb5e 100644 --- a/doc/classes/FileAccess.xml +++ b/doc/classes/FileAccess.xml @@ -171,6 +171,41 @@ Returns the last error that happened when trying to perform operations. Compare with the [code]ERR_FILE_*[/code] constants from [enum Error]. + + + + + + Reads the file extended attribute with name [param attribute_name] as a byte array. + [b]Note:[/b] This method is implemented on Linux, macOS, and Windows. + [b]Note:[/b] Extended attributes support depends on the file system. Attributes will be lost when the file is moved between incompatible file systems. + [b]Note:[/b] On Linux, only "user" namespace attributes are accessible, namespace prefix should not be included. + [b]Note:[/b] On Windows, alternate data streams are used to store extended attributes. + + + + + + + + Reads the file extended attribute with name [param attribute_name] as a UTF-8 encoded string. + [b]Note:[/b] This method is implemented on Linux, macOS, and Windows. + [b]Note:[/b] Extended attributes support depends on the file system. Attributes will be lost when the file is moved between incompatible file systems. + [b]Note:[/b] On Linux, only "user" namespace attributes are accessible, namespace prefix should not be included. + [b]Note:[/b] On Windows, alternate data streams are used to store extended attributes. + + + + + + + Returns a list of file extended attributes. + [b]Note:[/b] This method is implemented on Linux, macOS, and Windows. + [b]Note:[/b] Extended attributes support depends on the file system. Attributes will be lost when the file is moved between incompatible file systems. + [b]Note:[/b] On Linux, only "user" namespace attributes are accessible, namespace prefix should not be included. + [b]Note:[/b] On Windows, alternate data streams are used to store extended attributes. + + @@ -359,6 +394,18 @@ Returns [code]null[/code] if opening the file failed. You can use [method get_open_error] to check the error that occurred. + + + + + + Removes file extended attribute with name [param attribute_name]. + [b]Note:[/b] This method is implemented on Linux, macOS, and Windows. + [b]Note:[/b] Extended attributes support depends on the file system. Attributes will be lost when the file is moved between incompatible file systems. + [b]Note:[/b] On Linux, only "user" namespace attributes are accessible, namespace prefix should not be included. + [b]Note:[/b] On Windows, alternate data streams are used to store extended attributes. + + @@ -381,6 +428,32 @@ [b]Note:[/b] This is an offset, so you should use negative numbers or the file cursor will be at the end of the file. + + + + + + + Writes file extended attribute with name [param attribute_name] as a byte array. + [b]Note:[/b] This method is implemented on Linux, macOS, and Windows. + [b]Note:[/b] Extended attributes support depends on the file system. Attributes will be lost when the file is moved between incompatible file systems. + [b]Note:[/b] On Linux, only "user" namespace attributes are accessible, namespace prefix should not be included. + [b]Note:[/b] On Windows, alternate data streams are used to store extended attributes. + + + + + + + + + Writes file extended attribute with name [param attribute_name] as a UTF-8 encoded string. + [b]Note:[/b] This method is implemented on Linux, macOS, and Windows. + [b]Note:[/b] Extended attributes support depends on the file system. Attributes will be lost when the file is moved between incompatible file systems. + [b]Note:[/b] On Linux, only "user" namespace attributes are accessible, namespace prefix should not be included. + [b]Note:[/b] On Windows, alternate data streams are used to store extended attributes. + + diff --git a/drivers/unix/file_access_unix.cpp b/drivers/unix/file_access_unix.cpp index d818f41efed..303d4174098 100644 --- a/drivers/unix/file_access_unix.cpp +++ b/drivers/unix/file_access_unix.cpp @@ -38,6 +38,9 @@ #include #include #include +#if !defined(__FreeBSD__) && !defined(__OpenBSD__) && !defined(__NetBSD__) && !defined(WEB_ENABLED) +#include +#endif #include #include @@ -506,6 +509,117 @@ Error FileAccessUnix::_set_read_only_attribute(const String &p_file, bool p_ro) #endif } +PackedByteArray FileAccessUnix::_get_extended_attribute(const String &p_file, const String &p_attribute_name) { + ERR_FAIL_COND_V(p_attribute_name.is_empty(), PackedByteArray()); + + String file = fix_path(p_file); + PackedByteArray data; +#if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(WEB_ENABLED) + // Not supported. +#elif defined(__APPLE__) + CharString attr_name = p_attribute_name.utf8(); + ssize_t attr_size = getxattr(file.utf8().get_data(), attr_name.get_data(), nullptr, 0, 0, 0); + if (attr_size <= 0) { + return PackedByteArray(); + } + + data.resize(attr_size); + attr_size = getxattr(file.utf8().get_data(), attr_name.get_data(), (void *)data.ptrw(), data.size(), 0, 0); + ERR_FAIL_COND_V_MSG(attr_size != data.size(), PackedByteArray(), "Failed to set extended attributes for: " + p_file); +#else + CharString attr_name = ("user." + p_attribute_name).utf8(); + ssize_t attr_size = getxattr(file.utf8().get_data(), attr_name.get_data(), nullptr, 0); + if (attr_size <= 0) { + return PackedByteArray(); + } + + data.resize(attr_size); + attr_size = getxattr(file.utf8().get_data(), attr_name.get_data(), (void *)data.ptrw(), data.size()); + ERR_FAIL_COND_V_MSG(attr_size != data.size(), PackedByteArray(), "Failed to set extended attributes for: " + p_file); +#endif + return data; +} + +Error FileAccessUnix::_set_extended_attribute(const String &p_file, const String &p_attribute_name, const PackedByteArray &p_data) { + ERR_FAIL_COND_V(p_attribute_name.is_empty(), FAILED); + + String file = fix_path(p_file); +#if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(WEB_ENABLED) + // Not supported. +#elif defined(__APPLE__) + int err = setxattr(file.utf8().get_data(), p_attribute_name.utf8().get_data(), (const void *)p_data.ptr(), p_data.size(), 0, 0); + if (err != 0) { + return FAILED; + } +#else + int err = setxattr(file.utf8().get_data(), ("user." + p_attribute_name).utf8().get_data(), (const void *)p_data.ptr(), p_data.size(), 0); + if (err != 0) { + return FAILED; + } +#endif + return OK; +} + +Error FileAccessUnix::_remove_extended_attribute(const String &p_file, const String &p_attribute_name) { + ERR_FAIL_COND_V(p_attribute_name.is_empty(), FAILED); + + String file = fix_path(p_file); +#if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(WEB_ENABLED) + // Not supported. +#elif defined(__APPLE__) + int err = removexattr(file.utf8().get_data(), p_attribute_name.utf8().get_data(), 0); + if (err != 0) { + return FAILED; + } +#else + int err = removexattr(file.utf8().get_data(), ("user." + p_attribute_name).utf8().get_data()); + if (err != 0) { + return FAILED; + } +#endif + return OK; +} + +PackedStringArray FileAccessUnix::_get_extended_attributes_list(const String &p_file) { + PackedStringArray ret; + String file = fix_path(p_file); +#if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(WEB_ENABLED) + // Not supported. +#elif defined(__APPLE__) + size_t size = listxattr(file.utf8().get_data(), nullptr, 0, 0); + if (size > 0) { + PackedByteArray data; + data.resize(size); + listxattr(file.utf8().get_data(), (char *)data.ptrw(), data.size(), 0); + int64_t start = 0; + for (int64_t x = 0; x < data.size(); x++) { + if (x != start && data[x] == 0) { + ret.push_back(String::utf8((const char *)(data.ptr() + start), x - start)); + start = x + 1; + } + } + } +#else + size_t size = listxattr(file.utf8().get_data(), nullptr, 0); + if (size > 0) { + PackedByteArray data; + data.resize(size); + listxattr(file.utf8().get_data(), (char *)data.ptrw(), data.size()); + int64_t start = 0; + for (int64_t x = 0; x < data.size(); x++) { + if (x != start && data[x] == 0) { + String name = String::utf8((const char *)(data.ptr() + start), x - start); + if (name.begins_with("user.")) { + ret.push_back(name.trim_prefix("user.")); + } + start = x + 1; + } + } + } +#endif + return ret; +} + void FileAccessUnix::close() { _close(); } diff --git a/drivers/unix/file_access_unix.h b/drivers/unix/file_access_unix.h index 6e89be0e227..3e200a71cdc 100644 --- a/drivers/unix/file_access_unix.h +++ b/drivers/unix/file_access_unix.h @@ -91,6 +91,11 @@ public: virtual bool _get_read_only_attribute(const String &p_file) override; virtual Error _set_read_only_attribute(const String &p_file, bool p_ro) override; + virtual PackedByteArray _get_extended_attribute(const String &p_file, const String &p_attribute_name) override; + virtual Error _set_extended_attribute(const String &p_file, const String &p_attribute_name, const PackedByteArray &p_data) override; + virtual Error _remove_extended_attribute(const String &p_file, const String &p_attribute_name) override; + virtual PackedStringArray _get_extended_attributes_list(const String &p_file) override; + virtual void close() override; FileAccessUnix() {} diff --git a/drivers/windows/file_access_windows.cpp b/drivers/windows/file_access_windows.cpp index e183840dbbf..6d305ae395a 100644 --- a/drivers/windows/file_access_windows.cpp +++ b/drivers/windows/file_access_windows.cpp @@ -583,6 +583,104 @@ Error FileAccessWindows::_set_read_only_attribute(const String &p_file, bool p_r return OK; } +PackedByteArray FileAccessWindows::_get_extended_attribute(const String &p_file, const String &p_attribute_name) { + ERR_FAIL_COND_V(p_attribute_name.is_empty(), PackedByteArray()); + + String file = fix_path(p_file); + file += ":" + p_attribute_name; + const Char16String &file_utf16 = file.utf16(); + + PackedByteArray data; + HANDLE h = CreateFileW((LPCWSTR)file_utf16.get_data(), GENERIC_READ, 0, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); + if (h != INVALID_HANDLE_VALUE) { + size_t bytes_in_buffer = 0; + const int CHUNK_SIZE = 4096; + + DWORD read = 0; + for (;;) { + data.resize(bytes_in_buffer + CHUNK_SIZE); + bool success = ReadFile(h, data.ptrw() + bytes_in_buffer, CHUNK_SIZE, &read, nullptr); + if (!success || read == 0) { + break; + } + bytes_in_buffer += read; + } + data.resize(bytes_in_buffer); + CloseHandle(h); + } + return data; +} + +Error FileAccessWindows::_set_extended_attribute(const String &p_file, const String &p_attribute_name, const PackedByteArray &p_data) { + ERR_FAIL_COND_V(p_attribute_name.is_empty(), FAILED); + + String file = fix_path(p_file); + file += ":" + p_attribute_name; + const Char16String &file_utf16 = file.utf16(); + + PackedByteArray data; + HANDLE h = CreateFileW((LPCWSTR)file_utf16.get_data(), GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr); + if (h == INVALID_HANDLE_VALUE) { + return FAILED; + } + + DWORD written = 0; + bool ok = true; + if (p_data.size() > 0) { + ok = WriteFile(h, p_data.ptr(), p_data.size(), &written, nullptr); + } + CloseHandle(h); + + ERR_FAIL_COND_V_MSG(!ok || written != p_data.size(), FAILED, "Failed to set extended attributes for: " + p_file); + + return OK; +} + +Error FileAccessWindows::_remove_extended_attribute(const String &p_file, const String &p_attribute_name) { + ERR_FAIL_COND_V(p_attribute_name.is_empty(), FAILED); + + String file = fix_path(p_file); + file += ":" + p_attribute_name; + const Char16String &file_utf16 = file.utf16(); + + return DeleteFileW((LPCWSTR)(file_utf16.get_data())) != 0 ? OK : FAILED; +} + +PackedStringArray FileAccessWindows::_get_extended_attributes_list(const String &p_file) { + PackedStringArray ret; + String file = fix_path(p_file); + + char info_block[65536] = {}; + PFILE_STREAM_INFO stream_info = (PFILE_STREAM_INFO)info_block; + + HANDLE h = CreateFileW((LPCWSTR)file.utf16().get_data(), 0, FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, 0, nullptr); + if (h == INVALID_HANDLE_VALUE) { + return ret; + } + BOOL out = GetFileInformationByHandleEx(h, FileStreamInfo, info_block, sizeof(info_block)); + CloseHandle(h); + if (!out) { + return ret; + } + while (true) { + if (stream_info->StreamNameLength != 0) { + String name = String::utf16((const char16_t *)stream_info->StreamName, stream_info->StreamNameLength / sizeof(WCHAR)); + if (name.ends_with(":$DATA")) { + name = name.trim_prefix(":").trim_suffix(":$DATA"); + if (!name.is_empty()) { + ret.push_back(name); + } + } + } + if (stream_info->NextEntryOffset == 0) { + break; + } + stream_info = (PFILE_STREAM_INFO)((LPBYTE)stream_info + stream_info->NextEntryOffset); + } + + return ret; +} + void FileAccessWindows::close() { _close(); } diff --git a/drivers/windows/file_access_windows.h b/drivers/windows/file_access_windows.h index 7d78518b2db..7142c7652a0 100644 --- a/drivers/windows/file_access_windows.h +++ b/drivers/windows/file_access_windows.h @@ -90,6 +90,11 @@ public: virtual bool _get_read_only_attribute(const String &p_file) override; virtual Error _set_read_only_attribute(const String &p_file, bool p_ro) override; + virtual PackedByteArray _get_extended_attribute(const String &p_file, const String &p_attribute_name) override; + virtual Error _set_extended_attribute(const String &p_file, const String &p_attribute_name, const PackedByteArray &p_data) override; + virtual Error _remove_extended_attribute(const String &p_file, const String &p_attribute_name) override; + virtual PackedStringArray _get_extended_attributes_list(const String &p_file) override; + virtual void close() override; static void initialize();