Serenity Operating System
at master 200 lines 7.4 kB view raw
1/* 2 * Copyright (c) 2022, Andrew Kaster <akaster@serenityos.org> 3 * 4 * SPDX-License-Identifier: BSD-2-Clause 5 */ 6 7#define AK_DONT_REPLACE_STD 8 9#include <AK/DeprecatedString.h> 10#include <AK/LexicalPath.h> 11#include <AK/Platform.h> 12#include <AK/ScopeGuard.h> 13#include <LibArchive/Tar.h> 14#include <LibArchive/TarStream.h> 15#include <LibCore/Directory.h> 16#include <LibCore/FileStream.h> 17#include <LibCore/System.h> 18#include <LibMain/Main.h> 19 20#include <QCoreApplication> 21#include <QJniObject> 22#include <QSslSocket> 23 24#ifndef AK_OS_ANDROID 25# error This file is for Android only, check CMake config! 26#endif 27 28// HACK ALERT, we need to include LibMain manually here because the Qt build system doesn't include LibMain.a in the actual executable, 29// nor include it in libladybird_<arch>.so 30#include <LibMain/Main.cpp> // NOLINT(bugprone-suspicious-include) 31 32extern DeprecatedString s_serenity_resource_root; 33 34void android_platform_init(); 35static void extract_ladybird_resources(); 36static ErrorOr<void> extract_tar_archive(DeprecatedString archive_file, DeprecatedString output_directory); 37 38void android_platform_init() 39{ 40 qDebug() << "Device supports OpenSSL: " << QSslSocket::supportsSsl(); 41 42 QJniObject res = QJniObject::callStaticMethod<jstring>("org/serenityos/ladybird/TransferAssets", 43 "transferAssets", 44 "(Landroid/content/Context;)Ljava/lang/String;", 45 QNativeInterface::QAndroidApplication::context()); 46 s_serenity_resource_root = res.toString().toUtf8().data(); 47 48 extract_ladybird_resources(); 49} 50 51void extract_ladybird_resources() 52{ 53 qDebug() << "serenity resource root is " << s_serenity_resource_root.characters(); 54 auto file_or_error = Core::System::open(DeprecatedString::formatted("{}/res/icons/16x16/app-browser.png", s_serenity_resource_root), O_RDONLY); 55 if (file_or_error.is_error()) { 56 qDebug() << "Unable to open test file file as expected, extracting asssets..."; 57 58 MUST(extract_tar_archive(DeprecatedString::formatted("{}/ladybird-assets.tar", s_serenity_resource_root), s_serenity_resource_root)); 59 } else { 60 qDebug() << "Opened app-browser.png test file, good to go!"; 61 qDebug() << "Hopefully no developer changed the asset files and expected them to be re-extracted!"; 62 } 63} 64 65ErrorOr<void> extract_tar_archive(DeprecatedString archive_file, DeprecatedString output_directory) 66{ 67 constexpr size_t buffer_size = 4096; 68 69 auto file = TRY(Core::DeprecatedFile::open(archive_file, Core::OpenMode::ReadOnly)); 70 71 DeprecatedString old_pwd = TRY(Core::System::getcwd()); 72 73 TRY(Core::System::chdir(output_directory)); 74 ScopeGuard go_back = [&old_pwd] { MUST(Core::System::chdir(old_pwd)); }; 75 76 Core::InputFileStream file_stream(file); 77 78 Archive::TarInputStream tar_stream(file_stream); 79 if (!tar_stream.valid()) { 80 qDebug() << "the provided file is not a well-formatted ustar file"; 81 return Error::from_errno(EINVAL); 82 } 83 84 HashMap<DeprecatedString, DeprecatedString> global_overrides; 85 HashMap<DeprecatedString, DeprecatedString> local_overrides; 86 87 auto get_override = [&](StringView key) -> Optional<DeprecatedString> { 88 Optional<DeprecatedString> maybe_local = local_overrides.get(key); 89 90 if (maybe_local.has_value()) 91 return maybe_local; 92 93 Optional<DeprecatedString> maybe_global = global_overrides.get(key); 94 95 if (maybe_global.has_value()) 96 return maybe_global; 97 98 return {}; 99 }; 100 101 for (; !tar_stream.finished(); tar_stream.advance()) { 102 Archive::TarFileHeader const& header = tar_stream.header(); 103 104 // Handle meta-entries earlier to avoid consuming the file content stream. 105 if (header.content_is_like_extended_header()) { 106 switch (header.type_flag()) { 107 case Archive::TarFileType::GlobalExtendedHeader: { 108 TRY(tar_stream.for_each_extended_header([&](StringView key, StringView value) { 109 if (value.length() == 0) 110 global_overrides.remove(key); 111 else 112 global_overrides.set(key, value); 113 })); 114 break; 115 } 116 case Archive::TarFileType::ExtendedHeader: { 117 TRY(tar_stream.for_each_extended_header([&](StringView key, StringView value) { 118 local_overrides.set(key, value); 119 })); 120 break; 121 } 122 default: 123 warnln("Unknown extended header type '{}' of {}", (char)header.type_flag(), header.filename()); 124 VERIFY_NOT_REACHED(); 125 } 126 127 continue; 128 } 129 130 Archive::TarFileStream file_stream = tar_stream.file_contents(); 131 132 // Handle other header types that don't just have an effect on extraction. 133 switch (header.type_flag()) { 134 case Archive::TarFileType::LongName: { 135 StringBuilder long_name; 136 137 Array<u8, buffer_size> buffer; 138 size_t bytes_read; 139 140 while ((bytes_read = file_stream.read(buffer)) > 0) 141 long_name.append(reinterpret_cast<char*>(buffer.data()), bytes_read); 142 143 local_overrides.set("path", long_name.to_deprecated_string()); 144 continue; 145 } 146 default: 147 // None of the relevant headers, so continue as normal. 148 break; 149 } 150 151 LexicalPath path = LexicalPath(header.filename()); 152 if (!header.prefix().is_empty()) 153 path = path.prepend(header.prefix()); 154 DeprecatedString filename = get_override("path"sv).value_or(path.string()); 155 156 DeprecatedString absolute_path = Core::DeprecatedFile::absolute_path(filename); 157 auto parent_path = LexicalPath(absolute_path).parent(); 158 159 switch (header.type_flag()) { 160 case Archive::TarFileType::NormalFile: 161 case Archive::TarFileType::AlternateNormalFile: { 162 MUST(Core::Directory::create(parent_path, Core::Directory::CreateDirectories::Yes)); 163 164 int fd = TRY(Core::System::open(absolute_path, O_CREAT | O_WRONLY, header.mode())); 165 166 Array<u8, buffer_size> buffer; 167 size_t bytes_read; 168 while ((bytes_read = file_stream.read(buffer)) > 0) 169 TRY(Core::System::write(fd, buffer.span().slice(0, bytes_read))); 170 171 TRY(Core::System::close(fd)); 172 break; 173 } 174 case Archive::TarFileType::SymLink: { 175 MUST(Core::Directory::create(parent_path, Core::Directory::CreateDirectories::Yes)); 176 177 TRY(Core::System::symlink(header.link_name(), absolute_path)); 178 break; 179 } 180 case Archive::TarFileType::Directory: { 181 MUST(Core::Directory::create(parent_path, Core::Directory::CreateDirectories::Yes)); 182 183 auto result_or_error = Core::System::mkdir(absolute_path, header.mode()); 184 if (result_or_error.is_error() && result_or_error.error().code() != EEXIST) 185 return result_or_error.error(); 186 break; 187 } 188 default: 189 // FIXME: Implement other file types 190 warnln("file type '{}' of {} is not yet supported", (char)header.type_flag(), header.filename()); 191 VERIFY_NOT_REACHED(); 192 } 193 194 // Non-global headers should be cleared after every file. 195 local_overrides.clear(); 196 } 197 file_stream.close(); 198 199 return {}; 200}