From 9696b1a80fa020f0ab508c6d7b3598998abc418e Mon Sep 17 00:00:00 2001 From: Oscar Lesta Date: Sat, 30 Mar 2024 00:21:46 -0300 Subject: [PATCH] Add basic SquashFS support. Only listing/extracting is implemented. --- Docs/UserGuide/addons/SquashFS.rst | 15 + Docs/UserGuide/index.rst | 1 + Source/Beezer/Beezer.rdef | 3 +- Source/Beezer/RuleDefaults.h | 4 + Source/CMakeLists.txt | 1 + Source/SquashFSArchiver/CMakeLists.txt | 13 + Source/SquashFSArchiver/SquashFSArchiver.cpp | 311 ++++++++++++++++++ Source/SquashFSArchiver/SquashFSArchiver.h | 42 +++ Source/SquashFSArchiver/SquashFSArchiver.rdef | 76 +++++ Source/SquashFSArchiver/locales/en.catkeys | 2 + 10 files changed, 467 insertions(+), 1 deletion(-) create mode 100644 Docs/UserGuide/addons/SquashFS.rst create mode 100644 Source/SquashFSArchiver/CMakeLists.txt create mode 100644 Source/SquashFSArchiver/SquashFSArchiver.cpp create mode 100644 Source/SquashFSArchiver/SquashFSArchiver.h create mode 100644 Source/SquashFSArchiver/SquashFSArchiver.rdef create mode 100644 Source/SquashFSArchiver/locales/en.catkeys diff --git a/Docs/UserGuide/addons/SquashFS.rst b/Docs/UserGuide/addons/SquashFS.rst new file mode 100644 index 0000000..7154174 --- /dev/null +++ b/Docs/UserGuide/addons/SquashFS.rst @@ -0,0 +1,15 @@ + +============== +SquashFS AddOn +============== + + +The squashfs archiver gives you the following options: + +**Process attributes** + + Allows adding/extracting of extended file attributes ("xattr"). + Its generally best to leave this option turned ON. + +For information on saving these settings to the archive or as defaults +read the :ref:`ArchiveWindow:\< archiver \>` menu information. diff --git a/Docs/UserGuide/index.rst b/Docs/UserGuide/index.rst index 8d366f0..99ba6ec 100644 --- a/Docs/UserGuide/index.rst +++ b/Docs/UserGuide/index.rst @@ -69,6 +69,7 @@ Welcome to the Beezer User Guide Hpkg Lha Rar + SquashFS Xz Zip Zstd diff --git a/Source/Beezer/Beezer.rdef b/Source/Beezer/Beezer.rdef index 8d5bfa7..367533e 100644 --- a/Source/Beezer/Beezer.rdef +++ b/Source/Beezer/Beezer.rdef @@ -1631,7 +1631,8 @@ resource file_types message { "types" = "application/x-xz", "types" = "application/x-7zip-compressed", "types" = "application/x-7z-compressed", - "types" = "application/x-arj-compressed" + "types" = "application/x-arj-compressed", + "types" = "application/x-squashfs-image" }; resource(3038, "Img:Background") #'PNG ' array { diff --git a/Source/Beezer/RuleDefaults.h b/Source/Beezer/RuleDefaults.h index b295a0b..1206d99 100644 --- a/Source/Beezer/RuleDefaults.h +++ b/Source/Beezer/RuleDefaults.h @@ -54,6 +54,10 @@ const char* kDefaultRules = "\ #application/x-zstd=.zstd\n\ \n\ #application/x-vnd.haiku-package=.hpkg\n\ +\n\ +#application/x-squashfs-image=.sfs\n\ +#application/x-squashfs-image=.sqfs\n\ +#application/x-squashfs-image=.squashfs\n\ "; #endif // _RULE_DEFAULTS_H_ diff --git a/Source/CMakeLists.txt b/Source/CMakeLists.txt index f270df1..bf7ff55 100644 --- a/Source/CMakeLists.txt +++ b/Source/CMakeLists.txt @@ -59,6 +59,7 @@ add_subdirectory(BZipArchiver) add_subdirectory(GZipArchiver) add_subdirectory(LhaArchiver) add_subdirectory(RarArchiver) +add_subdirectory(SquashFSArchiver) add_subdirectory(TarArchiver) add_subdirectory(XzArchiver) add_subdirectory(z7Archiver) diff --git a/Source/SquashFSArchiver/CMakeLists.txt b/Source/SquashFSArchiver/CMakeLists.txt new file mode 100644 index 0000000..a07881a --- /dev/null +++ b/Source/SquashFSArchiver/CMakeLists.txt @@ -0,0 +1,13 @@ + +haiku_add_addon(ark_squashfs SquashFSArchiver.cpp SquashFSArchiver.rdef) + +#target_link_libraries(ark_squashfs) + +set_property(TARGET ark_squashfs PROPERTY LIBRARY_OUTPUT_DIRECTORY ${BEEZER_BUILD_ADDONS_DIR}) + +if(HAIKU_ENABLE_I18N) + set("ark_squashfs-APP_MIME_SIG" "x-vnd.BeezerAddOn-SquashFSArchiver") + set("ark_squashfs-LOCALES" "en") + target_link_libraries(ark_squashfs "localestub") + haiku_add_i18n(ark_squashfs) +endif() diff --git a/Source/SquashFSArchiver/SquashFSArchiver.cpp b/Source/SquashFSArchiver/SquashFSArchiver.cpp new file mode 100644 index 0000000..018385a --- /dev/null +++ b/Source/SquashFSArchiver/SquashFSArchiver.cpp @@ -0,0 +1,311 @@ +// SPDX-License-Identifier: BSD-3-Clause +// Copyright (c) 2002 Ramshankar (aka Teknomancer). +// Copyright (c) 2011 Chris Roberts. +// Copyright (c) 2024 Oscar Lesta. +// All rights reserved. + +#include "SquashFSArchiver.h" +#include "AppUtils.h" +#include "ArchiveEntry.h" +#include "KeyedMenuItem.h" + +#include +#include +#include + +#ifdef HAIKU_ENABLE_I18N +#include + +#undef B_TRANSLATION_CONTEXT +#define B_TRANSLATION_CONTEXT "SquashFSArchiver" +#else +#define B_TRANSLATE(x) x +#define B_TRANSLATE_MARK(x) x +#define B_TRANSLATE_NOCOLLECT(x) x +#endif + +#include // gcc2 + + +// keep track of our custom options/menuitems +static const char* kProcessAttrs = B_TRANSLATE_MARK("Process attributes"); + + +Archiver* load_archiver(BMessage* metaDataMsg) +{ + return new SquashFSArchiver(metaDataMsg); +} + + +// #pragma mark - + + +SquashFSArchiver::SquashFSArchiver(BMessage* metaDataMsg) + : Archiver(metaDataMsg) +{ + m_error = BZR_BINARY_MISSING; + if (GetBinaryPath(m_unsquashfsPath, "unsquashfs") == true) + m_error = BZR_DONE; +} + + +void SquashFSArchiver::BuildMenu(BMessage& message) +{ + m_settingsMenu = new BMenu(m_typeStr); + m_settingsMenu->AddItem( + new KeyedMenuItem("bzr:ProcessAttrs", B_TRANSLATE_NOCOLLECT(kProcessAttrs), + message, true, new BMessage(BZR_MENUITEM_SELECTED))); +} + + +BList SquashFSArchiver::HiddenColumns(BList const& columns) const +{ + // Indices are: 0-name 1-size 2-packed 3-ratio 4-path 5-date 6-method 7-crc + BList hiddenColumns(columns); + hiddenColumns.RemoveItems(0, 2); // Remove name and size + + // Now list has 0-packed 1-ratio 2-path 3-date 4-method 5-crc + hiddenColumns.RemoveItems(2, 2); // Remove path and date + + // Now list has 0-packed 1-ratio 2-method 3-crc <-- these columns are to be hidden + return hiddenColumns; +} + + +// #pragma mark - + + +status_t SquashFSArchiver::Open(entry_ref* ref, BMessage* /*fileList*/) +{ + m_archiveRef = *ref; + m_archivePath.SetTo(ref); + + m_pipeMgr.FlushArgs(); + m_pipeMgr << m_unsquashfsPath << "-d" << "" << "-llc" << m_archivePath.Path(); + + FILE* out, *err; + int outdes[2], errdes[2]; + thread_id tid = m_pipeMgr.Pipe(outdes, errdes); + + if (tid == B_ERROR || tid == B_NO_MEMORY) + return B_ERROR; + + resume_thread(tid); + + close(errdes[1]); + close(outdes[1]); + + out = fdopen(outdes[0], "r"); + status_t exitCode = ReadOpen(out); + + close(outdes[0]); + fclose(out); + + err = fdopen(errdes[0], "r"); + exitCode = Archiver::ReadErrStream(err, NULL); + close(errdes[0]); + fclose(err); + + return exitCode; +} + + +status_t SquashFSArchiver::ReadOpen(FILE* fp) +{ + char lineString[B_PATH_NAME_LENGTH + 512]; + char permStr[15]; + char ownerStr[100]; + char sizeStr[15]; + char dayStr[5]; + char monthStr[5]; + char yearStr[8]; + char hourStr[5]; + char minuteStr[5]; + char pathStr[2 * B_PATH_NAME_LENGTH + 10]; + uint16 const len = sizeof(lineString); + + while (fgets(lineString, len, fp)) + { + lineString[strlen(lineString) - 1] = '\0'; + + sscanf(lineString, + "%[^ ] %[^ ] %[0-9] %[0-9]-%[0-9]-%[0-9] %[0-9]:%[0-9] %[^\n]", + permStr, ownerStr, sizeStr, yearStr, monthStr, dayStr, hourStr, minuteStr, pathStr); + + struct tm timeStruct; time_t timeValue; + MakeTime(&timeStruct, &timeValue, dayStr, monthStr, yearStr, hourStr, minuteStr, "00"); + + BString pathString = pathStr; + pathString.RemoveFirst("/"); // avoids having an empty first-level dir. + + // Handle linked files/folders + if (permStr[0] == 'l') + { + BString fullPath = pathString; + uint16 foundIndex = fullPath.FindLast(" -> "); + fullPath.Remove(foundIndex, fullPath.Length() - foundIndex); + pathString = fullPath.String(); + } + + // Check for emtpy dirs: + bool isDir = permStr[0] == 'd'; + + if (isDir) + pathString.Append("/"); // Without this Beezer doesn't shows the entry for some reason. + + m_entriesList.AddItem( + new ArchiveEntry(isDir, pathString.String(), sizeStr, "-", timeValue, "-", "-")); + } + + return BZR_DONE; +} + + +status_t SquashFSArchiver::Extract(entry_ref* refToDir, BMessage* message, BMessenger* progress, + volatile bool* cancel) +{ + if (progress) + { + BEntry dirEntry(refToDir); + if (dirEntry.Exists() == false || dirEntry.IsDirectory() == false) + return BZR_EXTRACT_DIR_INIT_ERROR; + } + + BPath dirPath(refToDir); + BEntry archiveEntry(&m_archiveRef, true); + if (archiveEntry.Exists() == false) + return BZR_ARCHIVE_PATH_INIT_ERROR; + + int32 count = 0L; + if (message) + { + uint32 type; + message->GetInfo(kPath, &type, &count); + if (type != B_STRING_TYPE) + return BZR_UNKNOWN; + } + + m_pipeMgr.FlushArgs(); + + // Arguments we use here: + // quiet, no percentage, print info, overwrite files (needed if the dirPath already exists). + m_pipeMgr << m_unsquashfsPath << "-q" << "-n" << "-i" << "-f" << "-d" << dirPath.Path(); + + if (m_settingsMenu->FindItem(B_TRANSLATE_NOCOLLECT(kProcessAttrs))->IsMarked() == false) + m_pipeMgr << "-no-xattrs"; + + m_pipeMgr << m_archivePath.Path(); + + for (int32 i = 0; i < count; i ++) + { + const char* pathString = NULL; + if (message->FindString(kPath, i, &pathString) == B_OK) + m_pipeMgr << SupressWildcards(pathString); + } + + int outdes[2], errdes[2]; + thread_id tid = m_pipeMgr.Pipe(outdes, errdes); + + if (tid == B_ERROR || tid == B_NO_MEMORY) + return B_ERROR; + + if (progress) + resume_thread(tid); + else + { + status_t threadExitCode; + wait_for_thread(tid, &threadExitCode); + } + + close(errdes[1]); + close(outdes[1]); + + status_t exitCode = BZR_DONE; + if (progress) + { + FILE* outFile = fdopen(outdes[0], "r"); + exitCode = ReadExtract(outFile, progress, cancel); + fclose(outFile); + } + + close(outdes[0]); + close(errdes[0]); + + // Send signal to quit archiver only AFTER pipes are closed + if (exitCode == BZR_CANCEL_ARCHIVER) + TerminateThread(tid); + + m_pipeMgr.FlushArgs(); + return exitCode; +} + + +status_t SquashFSArchiver::ReadExtract(FILE* fp, BMessenger* progress, volatile bool* cancel) +{ + // Reads output while extracting files and updates progress window (thru messenger) + char lineString[999]; + + // Prepare message to update the progress bar + BMessage updateMessage(BZR_UPDATE_PROGRESS), reply('DUMB'); + updateMessage.AddFloat("delta", 1.0f); + + while (fgets(lineString, 998, fp)) + { + if (cancel && *cancel == true) + return BZR_CANCEL_ARCHIVER; + + int32 len = strlen(lineString); + lineString[--len] = '\0'; + if (len >= 1 && lineString[len - 1] != '/') + { + updateMessage.RemoveName("text"); + updateMessage.AddString("text", LeafFromPath(lineString)); + + progress->SendMessage(&updateMessage, &reply); + } + } + + return BZR_DONE; +} + + +// #pragma mark - + + +status_t SquashFSArchiver::Test(char*& /*outputStr*/, BMessenger* /*progress*/, volatile bool* /*cancel*/) +{ + return BZR_NOT_SUPPORTED; +} + + +status_t SquashFSArchiver::Add(bool /*createMode*/, const char* /*relativePath*/, BMessage* /*message*/, + BMessage* /*addedPaths*/, BMessenger* /*progress*/, volatile bool* /*cancel*/) +{ + return BZR_NOT_SUPPORTED; +} + + +status_t SquashFSArchiver::Delete(char*& /*outputStr*/, BMessage* /*message*/, BMessenger* /*progress*/, + volatile bool* /*cancel*/) +{ + return BZR_NOT_SUPPORTED; +} + + +status_t SquashFSArchiver::Create(BPath* /*archivePath*/, const char* /*relPath*/, BMessage* /*fileList*/, + BMessage* /*addedPaths*/, BMessenger* /*progress*/, volatile bool* /*cancel*/) +{ + return BZR_NOT_SUPPORTED; +} + + +bool SquashFSArchiver::CanAddFiles() const +{ + return false; +} + + +bool SquashFSArchiver::CanDeleteFiles() const +{ + return false; +} diff --git a/Source/SquashFSArchiver/SquashFSArchiver.h b/Source/SquashFSArchiver/SquashFSArchiver.h new file mode 100644 index 0000000..9c80317 --- /dev/null +++ b/Source/SquashFSArchiver/SquashFSArchiver.h @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: BSD-3-Clause +// Copyright (c) 2002 Ramshankar (aka Teknomancer). +// Copyright (c) 2024 Oscar Lesta. +// All rights reserved. + +#ifndef _SQUASHFS_ARCHIVER_H +#define _SQUASHFS_ARCHIVER_H + +#include "Archiver.h" + +class BMessenger; + +class SquashFSArchiver: public Archiver +{ + public: + SquashFSArchiver(BMessage* metaDataMsg); + + void BuildMenu(BMessage& message); + BList HiddenColumns(BList const& columnList) const; + + status_t Open(entry_ref* ref, BMessage* fileList); + status_t Extract(entry_ref* dir, BMessage* list, BMessenger* progress, + volatile bool* cancel); + + status_t Test(char*& outputStr, BMessenger* progress, volatile bool* cancel); + status_t Add(bool createMode, const char* relPath, BMessage* list, BMessage* addedPaths, + BMessenger* progress, volatile bool* cancel); + status_t Create(BPath* archivePath, const char* relPath, BMessage* fileList, + BMessage* addedPaths, BMessenger* progress, volatile bool* cancel); + status_t Delete(char*& outputStr, BMessage* list, BMessenger* progress, volatile bool* cancel); + + bool CanAddFiles() const; + bool CanDeleteFiles() const; + + private: + status_t ReadOpen(FILE* fp); + status_t ReadExtract(FILE* fp, BMessenger* progress, volatile bool* cancel); + + char m_unsquashfsPath[B_PATH_NAME_LENGTH]; +}; + +#endif diff --git a/Source/SquashFSArchiver/SquashFSArchiver.rdef b/Source/SquashFSArchiver/SquashFSArchiver.rdef new file mode 100644 index 0000000..8959d44 --- /dev/null +++ b/Source/SquashFSArchiver/SquashFSArchiver.rdef @@ -0,0 +1,76 @@ +resource app_signature "application/x-vnd.BeezerAddOn-SquashFSArchiver"; + +resource app_version { + major = 0, + middle = 1, + minor = 0, + + variety = B_APPV_ALPHA, + internal = 0, + + short_info = "SquashFSArchiver", + long_info = "Beezer SquashFS AddOn" +}; + +// resource id isn't important as long as it doesn't conflict with others +// the 'ArchiverMetaData' name of the resource is required though +resource(42, "ArchiverMetaData") message { + "ArchiverName" = "SquashFS", + "DefaultExtension" = ".squashfs", + "FileTypes" = message { + "application/x-squashfs-image" = message { + "Extension" = ".sfs", + "Extension" = ".sqfs", + "Extension" = ".squashfs" + } + } +}; + +resource vector_icon { + $"6E6369660B03010000020006023CC7EE389BC0BA16573E39B04977C842ADC700" + $"FFF8EAFFF5DEAC020006023C96323A4D3FBAFC013D5A974B57A549844D00983F" + $"04FFE6C276020006023A492400000000000040000047000000000000FFEFCEFF" + $"FFD16E02000602BB8A46BA62453C0CE4BD0B7C487ECB4B908500EEBF5AFFFFEB" + $"C0020006023A75293B1661BC4A333BA5424832E349A3B900696363FFFFDCDC03" + $"A7FF0003FF00000401880500020016023C335B3AD33ABAF8933C50734975EB49" + $"B9A100FDFF8D0F0608B2AB445B49C62DC7C1C521C92AC755C631C7C0C48DCAEC" + $"C15AC9BCC2F4CC1CBFC15F3C5B3A593F060AEEEA0E233C22C04022BF2422C1B0" + $"B40BC32FB647C4A6B4FBC3F6B7DDC57DB99EC604B99FC07FBEC1BB44BBA1B9FE" + $"BD24BA9CBA49B972B905B954B623BB6AB766BA27B4CABCC30606BA0B3EBADEC2" + $"83B702BF85B5ECC0FAB673BE35B5723926BB83B670BC45B5AEBA98B75BB992B8" + $"61060AEAEE0EBF8DBB84C6133BC618C3B8C8A9C0DCC7A7C22FC96EBFD859BECD" + $"5ABBEA5ABD575ABAC9C969B9F9C5DCB83AC7CDB8EFC472B7B6C33AB778C181B8" + $"E5C269B80BC064B9F2060CEEEEEEC5933DC221BCF0C3E6BDB342BC42BF27BBDD" + $"BCE0BDF9BDFFBCC2BB9DBF57BA6BC0CBBA45C34ABA45C1FABA45C4ECBA85C6B6" + $"BD7AC876BBD3C796BF59C97344C9CFC3C8C74AC297C8DDC4E2C5D7C5A3C467C5" + $"CEC17CC5D2C2F8C5CABFFF0606BA0B444ABA6BC0CBBA45C34ABA45C1FABA45C4" + $"ECBA85C6B6BD7AC876BBD3C796BF59C97344C9CF06076E3BC5933DC3DFC121C4" + $"ECBFD6C2CFC26D444AC9CFC3C8C74AC297C8DDC4E2C5D7C5A3C467C5CEC17CC5" + $"D2C2F8C5CABFFF0607EE3AC5933DC221BCF0C3E6BDB342BC42BF27BBDDBCE0BD" + $"F9BDFFBCC2BB9DBF57BA6BC0CB444AC3DFC121C2CFC26DC4ECBFD60606EE0A23" + $"3C22C04022BF2422C1B0B40BC32FB647C4A6B4FBC3F6B7DDC57DB99EC604B99F" + $"C07F0606EA0E233CB99FC07FBEC1BB44BBA1B9FEBD24BA9CBA49B972B905B954" + $"B623BB6AB766BA27B4CABCC30607BA3BBF8DBB84C6133BC85BBB9FC775BCA4C9" + $"1DBAC3C969B9F9C5DCB83AC7CDB8EFC472B7B6C33AB778C181B8E5C269B80BC0" + $"64B9F20607BA3BC6133BC618C3B8C8A9C0DCC7A7C22FC96EBFD859BECD5ABBEA" + $"5ABD575ABAC9C969B9F9C85BBB9FC91DBAC3C775BCA40408FEBB453F3B2F3F31" + $"372D32352B3439364E314A2E52344F355335C519353F3438BC43BE4EBB3DBAEB" + $"BD3A2FBDDB0221B97FBE29B97FBE29B891BE35B6F7BEFAB7B4BE80B6F7BEFAB7" + $"28BFCEB6F2BF78B728BFCEB798C07BB798C07BB70AC0E7B661C228B69DC17BB6" + $"61C228B59EC1F5B59EC1F5B53BC1DBB4C3C204B4C3C204B4A8C26EB49AC351B4" + $"9AC2DEB49AC3CBB4C7C4AFB4A9C440B4C7C4AFB5A1C4BDB53EC4D8B5A1C4BDB6" + $"65C486B665C486B6A1C52FB79BC62CB70EC5C2B79BC62CB72CC6D6B72CC6D6B6" + $"F5C72CB6FAC7ABB6FAC7ABB7B6C824B980C87AB892C86EB980C87AB9D0C7B3B9" + $"CBC818B9D0C7B3B9D9C6E4B9D9C6E433C6E1BBCEC644BB3EC6A6BBCEC644BC4D" + $"C6E3BC4DC6E3BC8CC732BD05C757BD05C757BDB6C6C7BE97C531BE42C60ABE97" + $"C531BDF2C4A8BE50C4CEBDF2C4A8BD33C45CBD33C45CBD4CC408BD59C352BD59" + $"C3AEBD59C2FABD36C252BD4DC2A4BD36C252BDF3C20BBDF3C20BBE53C1E7BE9C" + $"C17EBE9CC17EBE49C0A3BD0ABF51BDBDBFE3BD0ABF51BC53BFC6BC94BF76BC53" + $"BFC6BBD4C065BBD4C065BB44C001B9D940BA96BFC4B9D940B9D0BEF1B9D0BEF1" + $"B9CCBE8BB980BE29B980BE29B980BE290204344934C2AC34C3FE314CBA6F4CB9" + $"1D4C2E492EC3FF2EC2AD3146B91C46BA6E460A0A080100000A00040102030410" + $"01178400040A01040709020A000A0202060B000A040105000A00010C1815FF01" + $"178200040A030108000A09020D0E1A409242000000000000408FF248B6AEC682" + $"0415FF01178400040A09020D0E1A409242000000000000408FF248B6AEC68204" + $"001501178600040A0A020D0E02409242000000000000408FF248B6AEC68204" +}; diff --git a/Source/SquashFSArchiver/locales/en.catkeys b/Source/SquashFSArchiver/locales/en.catkeys new file mode 100644 index 0000000..89380b9 --- /dev/null +++ b/Source/SquashFSArchiver/locales/en.catkeys @@ -0,0 +1,2 @@ +1 English x-vnd.BeezerAddOn-SquashFSArchiver +Process attributes SquashFSArchiver Process attributes