Filza 27.2 worse_query: public and developer builds
This commit is contained in:
commit
91a6adf723
267 files changed
+42015
No files matched your search
+757
@@ -0,0 +1,757 @@
|
||||
//
|
||||
// WQPackages.m
|
||||
// FilzaSlop
|
||||
//
|
||||
// In-process dpkg/apt for the normally signed build. Packages are unpacked
|
||||
// under Documents/.terminal/root; this is a plugin/utility prefix, not the
|
||||
// system root, because the app is sandboxed.
|
||||
//
|
||||
|
||||
#import "WQPackages.h"
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <UIKit/UIKit.h>
|
||||
#import <errno.h>
|
||||
#import <fcntl.h>
|
||||
#import <sys/stat.h>
|
||||
#import <unistd.h>
|
||||
|
||||
typedef struct archive WQArchive;
|
||||
typedef struct archive_entry WQArchiveEntry;
|
||||
|
||||
#define WQ_ARCHIVE_OK 0
|
||||
#define ARCHIVE_OK 0
|
||||
#define WQ_AE_IFMT 0170000
|
||||
#define WQ_AE_IFREG 0100000
|
||||
#define WQ_AE_IFDIR 0040000
|
||||
#define WQ_AE_IFLNK 0120000
|
||||
|
||||
extern WQArchive *archive_read_new(void);
|
||||
extern int archive_read_support_filter_all(WQArchive *);
|
||||
extern int archive_read_support_format_all(WQArchive *);
|
||||
extern int archive_read_support_format_raw(WQArchive *);
|
||||
extern int archive_read_open_memory(WQArchive *, const void *, size_t);
|
||||
extern int archive_read_next_header(WQArchive *, WQArchiveEntry **);
|
||||
extern const char *archive_entry_pathname(WQArchiveEntry *);
|
||||
extern const char *archive_entry_symlink(WQArchiveEntry *);
|
||||
extern mode_t archive_entry_perm(WQArchiveEntry *);
|
||||
extern mode_t archive_entry_filetype(WQArchiveEntry *);
|
||||
extern long long archive_entry_size(WQArchiveEntry *);
|
||||
extern ssize_t archive_read_data(WQArchive *, void *, size_t);
|
||||
extern int archive_read_data_skip(WQArchive *);
|
||||
extern int archive_read_free(WQArchive *);
|
||||
extern const char *archive_error_string(WQArchive *);
|
||||
|
||||
static NSString *WQPackageRoot(void)
|
||||
{
|
||||
NSString *configured = NSProcessInfo.processInfo.environment[@"TERM_ROOT"];
|
||||
if (configured.length) return configured;
|
||||
NSString *documents = NSSearchPathForDirectoriesInDomains(
|
||||
NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
|
||||
return [documents stringByAppendingPathComponent:@".terminal"];
|
||||
}
|
||||
|
||||
static NSString *WQInstallRoot(void)
|
||||
{
|
||||
return [WQPackageRoot() stringByAppendingPathComponent:@"root"];
|
||||
}
|
||||
|
||||
static NSString *WQStatusPath(void)
|
||||
{
|
||||
return [WQPackageRoot() stringByAppendingPathComponent:@"var/lib/dpkg/status"];
|
||||
}
|
||||
|
||||
static NSString *WQInfoDirectory(void)
|
||||
{
|
||||
return [WQPackageRoot() stringByAppendingPathComponent:@"var/lib/dpkg/info"];
|
||||
}
|
||||
|
||||
static void WQEnsurePackageDirectories(void)
|
||||
{
|
||||
NSFileManager *fm = NSFileManager.defaultManager;
|
||||
NSArray *directories = @[
|
||||
WQPackageRoot(),
|
||||
WQInstallRoot(),
|
||||
WQInfoDirectory(),
|
||||
[WQPackageRoot() stringByAppendingPathComponent:@"var/lib/apt/lists"],
|
||||
[WQPackageRoot() stringByAppendingPathComponent:@"var/cache/apt/archives"],
|
||||
[WQPackageRoot() stringByAppendingPathComponent:@"etc/apt"],
|
||||
[WQPackageRoot() stringByAppendingPathComponent:@"tmp"],
|
||||
];
|
||||
for (NSString *directory in directories)
|
||||
[fm createDirectoryAtPath:directory withIntermediateDirectories:YES
|
||||
attributes:nil error:nil];
|
||||
NSString *status = WQStatusPath();
|
||||
if (![fm fileExistsAtPath:status])
|
||||
[@"" writeToFile:status atomically:YES encoding:NSUTF8StringEncoding error:nil];
|
||||
}
|
||||
|
||||
#pragma mark - ar / deb
|
||||
|
||||
static NSData *WQArMember(NSData *archive, NSString *namePrefix)
|
||||
{
|
||||
const uint8_t *bytes = archive.bytes;
|
||||
NSUInteger length = archive.length;
|
||||
if (length < 8 || memcmp(bytes, "!<arch>\n", 8) != 0) return nil;
|
||||
NSUInteger offset = 8;
|
||||
while (offset + 60 <= length) {
|
||||
const uint8_t *header = bytes + offset;
|
||||
NSString *name = [[[NSString alloc] initWithBytes:header length:16
|
||||
encoding:NSASCIIStringEncoding] stringByTrimmingCharactersInSet:
|
||||
NSCharacterSet.whitespaceCharacterSet];
|
||||
name = [name stringByTrimmingCharactersInSet:
|
||||
[NSCharacterSet characterSetWithCharactersInString:@"/"]];
|
||||
NSString *sizeText = [[[NSString alloc] initWithBytes:header + 48 length:10
|
||||
encoding:NSASCIIStringEncoding] stringByTrimmingCharactersInSet:
|
||||
NSCharacterSet.whitespaceCharacterSet];
|
||||
NSUInteger size = (NSUInteger)sizeText.longLongValue;
|
||||
offset += 60;
|
||||
if (offset + size > length) break;
|
||||
if ([name hasPrefix:namePrefix])
|
||||
return [archive subdataWithRange:NSMakeRange(offset, size)];
|
||||
offset += size + (size & 1);
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
static NSString *WQArchivePath(NSString *raw)
|
||||
{
|
||||
NSString *path = raw;
|
||||
while ([path hasPrefix:@"./"]) path = [path substringFromIndex:2];
|
||||
while ([path hasPrefix:@"/"]) path = [path substringFromIndex:1];
|
||||
if ([path hasPrefix:@"var/jb/"]) path = [path substringFromIndex:7];
|
||||
if (path.length == 0 || [path containsString:@".."]) return nil;
|
||||
return path;
|
||||
}
|
||||
|
||||
static BOOL WQExtractArchiveData(NSData *archiveData, NSString *destination,
|
||||
NSMutableArray<NSString *> *installedFiles)
|
||||
{
|
||||
if (!archiveData.length) return NO;
|
||||
WQArchive *archive = archive_read_new();
|
||||
if (!archive) return NO;
|
||||
archive_read_support_filter_all(archive);
|
||||
archive_read_support_format_all(archive);
|
||||
if (archive_read_open_memory(archive, archiveData.bytes,
|
||||
archiveData.length) != ARCHIVE_OK) {
|
||||
archive_read_free(archive);
|
||||
return NO;
|
||||
}
|
||||
|
||||
NSFileManager *fm = NSFileManager.defaultManager;
|
||||
struct archive_entry *entry;
|
||||
BOOL success = YES;
|
||||
while (archive_read_next_header(archive, &entry) == WQ_ARCHIVE_OK) {
|
||||
const char *rawName = archive_entry_pathname(entry);
|
||||
if (!rawName) { archive_read_data_skip(archive); continue; }
|
||||
NSString *relative = WQArchivePath([NSString stringWithUTF8String:rawName]);
|
||||
if (!relative.length) { archive_read_data_skip(archive); continue; }
|
||||
NSString *target = [destination stringByAppendingPathComponent:relative];
|
||||
mode_t filetype = archive_entry_filetype(entry);
|
||||
mode_t permissions = archive_entry_perm(entry);
|
||||
if (filetype == WQ_AE_IFDIR) {
|
||||
[fm createDirectoryAtPath:target withIntermediateDirectories:YES
|
||||
attributes:@{ NSFilePosixPermissions: @(permissions ?: 0755) }
|
||||
error:nil];
|
||||
continue;
|
||||
}
|
||||
[fm createDirectoryAtPath:target.stringByDeletingLastPathComponent
|
||||
withIntermediateDirectories:YES attributes:nil error:nil];
|
||||
if (filetype == WQ_AE_IFLNK) {
|
||||
const char *link = archive_entry_symlink(entry);
|
||||
[fm removeItemAtPath:target error:nil];
|
||||
if (link && symlink(link, target.fileSystemRepresentation) == 0)
|
||||
[installedFiles addObject:relative];
|
||||
} else if (filetype == WQ_AE_IFREG) {
|
||||
int descriptor = open(target.fileSystemRepresentation,
|
||||
O_WRONLY | O_CREAT | O_TRUNC, permissions ?: 0644);
|
||||
if (descriptor < 0) { success = NO; archive_read_data_skip(archive); continue; }
|
||||
uint8_t buffer[65536];
|
||||
ssize_t got;
|
||||
while ((got = archive_read_data(archive, buffer, sizeof(buffer))) > 0) {
|
||||
ssize_t written = 0;
|
||||
while (written < got) {
|
||||
ssize_t step = write(descriptor, buffer + written,
|
||||
(size_t)(got - written));
|
||||
if (step <= 0) break;
|
||||
written += step;
|
||||
}
|
||||
}
|
||||
close(descriptor);
|
||||
chmod(target.fileSystemRepresentation, permissions ?: 0644);
|
||||
[installedFiles addObject:relative];
|
||||
} else {
|
||||
archive_read_data_skip(archive);
|
||||
}
|
||||
}
|
||||
archive_read_free(archive);
|
||||
return success;
|
||||
}
|
||||
|
||||
static NSData *WQArchiveEntryNamed(NSData *archiveData, NSString *wanted)
|
||||
{
|
||||
WQArchive *archive = archive_read_new();
|
||||
if (!archive) return nil;
|
||||
archive_read_support_filter_all(archive);
|
||||
archive_read_support_format_all(archive);
|
||||
if (archive_read_open_memory(archive, archiveData.bytes,
|
||||
archiveData.length) != ARCHIVE_OK) {
|
||||
archive_read_free(archive);
|
||||
return nil;
|
||||
}
|
||||
struct archive_entry *entry;
|
||||
NSData *result = nil;
|
||||
while (archive_read_next_header(archive, &entry) == WQ_ARCHIVE_OK) {
|
||||
const char *name = archive_entry_pathname(entry);
|
||||
if (name && (strcmp(name, wanted.UTF8String) == 0 ||
|
||||
strcmp(name + (strlen(name) > 2 && name[0] == '.' &&
|
||||
name[1] == '/' ? 2 : 0), wanted.UTF8String) == 0)) {
|
||||
NSMutableData *data = [NSMutableData data];
|
||||
uint8_t buffer[65536];
|
||||
ssize_t got;
|
||||
while ((got = archive_read_data(archive, buffer, sizeof(buffer))) > 0)
|
||||
[data appendBytes:buffer length:(NSUInteger)got];
|
||||
result = data;
|
||||
break;
|
||||
}
|
||||
archive_read_data_skip(archive);
|
||||
}
|
||||
archive_read_free(archive);
|
||||
return result;
|
||||
}
|
||||
|
||||
static NSDictionary *WQParseControl(NSString *control)
|
||||
{
|
||||
NSMutableDictionary *fields = [NSMutableDictionary dictionary];
|
||||
NSString *currentKey = nil;
|
||||
NSMutableString *currentValue = nil;
|
||||
for (NSString *line in [control componentsSeparatedByString:@"\n"]) {
|
||||
if (line.length == 0) continue;
|
||||
if ([line hasPrefix:@" "] || [line hasPrefix:@"\t"]) {
|
||||
if (currentKey && currentValue)
|
||||
[currentValue appendFormat:@"\n%@", [line stringByTrimmingCharactersInSet:
|
||||
NSCharacterSet.whitespaceCharacterSet]];
|
||||
continue;
|
||||
}
|
||||
NSRange colon = [line rangeOfString:@":"];
|
||||
if (colon.location == NSNotFound) continue;
|
||||
if (currentKey && currentValue) fields[currentKey] = currentValue.copy;
|
||||
currentKey = [[line substringToIndex:colon.location]
|
||||
stringByTrimmingCharactersInSet:NSCharacterSet.whitespaceCharacterSet];
|
||||
currentValue = [[[line substringFromIndex:colon.location + 1]
|
||||
stringByTrimmingCharactersInSet:NSCharacterSet.whitespaceCharacterSet] mutableCopy];
|
||||
}
|
||||
if (currentKey && currentValue) fields[currentKey] = currentValue.copy;
|
||||
return fields;
|
||||
}
|
||||
|
||||
NSString *WQDebInfoText(NSString *path)
|
||||
{
|
||||
NSData *archive = [NSData dataWithContentsOfFile:path];
|
||||
if (!archive.length) return nil;
|
||||
NSData *controlArchive = WQArMember(archive, @"control.tar");
|
||||
if (!controlArchive.length) return nil;
|
||||
NSData *controlData = WQArchiveEntryNamed(controlArchive, @"control");
|
||||
if (!controlData.length) return nil;
|
||||
NSString *control = [[NSString alloc] initWithData:controlData
|
||||
encoding:NSUTF8StringEncoding];
|
||||
if (!control.length) return nil;
|
||||
NSMutableString *text = [NSMutableString stringWithFormat:
|
||||
@" new Debian package, version 2.0.\n"
|
||||
@" size %lu bytes: control archive %lu bytes.\n",
|
||||
(unsigned long)archive.length, (unsigned long)controlArchive.length];
|
||||
[text appendString:control];
|
||||
if (![text hasSuffix:@"\n"]) [text appendString:@"\n"];
|
||||
return text;
|
||||
}
|
||||
|
||||
static NSString *WQStanzaForKey(NSString *text, NSString *key, NSString *value)
|
||||
{
|
||||
NSArray *stanzas = [text componentsSeparatedByString:@"\n\n"];
|
||||
for (NSString *stanza in stanzas) {
|
||||
BOOL match = NO;
|
||||
for (NSString *line in [stanza componentsSeparatedByString:@"\n"]) {
|
||||
if ([line hasPrefix:[key stringByAppendingString:@": "]]) {
|
||||
match = [[line substringFromIndex:key.length + 2]
|
||||
isEqualToString:value];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (match) return stanza;
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
static NSString *WQFieldFromStanza(NSString *stanza, NSString *key)
|
||||
{
|
||||
for (NSString *line in [stanza componentsSeparatedByString:@"\n"])
|
||||
if ([line hasPrefix:[key stringByAppendingString:@": "]])
|
||||
return [line substringFromIndex:key.length + 2];
|
||||
return nil;
|
||||
}
|
||||
|
||||
static void WQRemoveStatusStanza(NSString *package)
|
||||
{
|
||||
NSString *text = [NSString stringWithContentsOfFile:WQStatusPath()
|
||||
encoding:NSUTF8StringEncoding error:nil] ?: @"";
|
||||
NSMutableArray *keep = [NSMutableArray array];
|
||||
for (NSString *stanza in [text componentsSeparatedByString:@"\n\n"]) {
|
||||
if (stanza.length == 0) continue;
|
||||
NSString *name = WQFieldFromStanza(stanza, @"Package");
|
||||
if (![name isEqualToString:package]) [keep addObject:stanza];
|
||||
}
|
||||
[[keep componentsJoinedByString:@"\n\n"] writeToFile:WQStatusPath()
|
||||
atomically:YES encoding:NSUTF8StringEncoding error:nil];
|
||||
}
|
||||
|
||||
static BOOL WQInstallDeb(NSString *path, FILE *out, FILE *err,
|
||||
NSDictionary *options)
|
||||
{
|
||||
WQEnsurePackageDirectories();
|
||||
NSData *archive = [NSData dataWithContentsOfFile:path];
|
||||
if (!archive.length) {
|
||||
fprintf(err, "dpkg: cannot open %s\n", path.UTF8String);
|
||||
return NO;
|
||||
}
|
||||
NSData *controlArchive = WQArMember(archive, @"control.tar");
|
||||
NSData *dataArchive = WQArMember(archive, @"data.tar");
|
||||
if (!controlArchive || !dataArchive) {
|
||||
fprintf(err, "dpkg: %s is not a Debian archive\n", path.UTF8String);
|
||||
return NO;
|
||||
}
|
||||
NSData *controlData = WQArchiveEntryNamed(controlArchive, @"control");
|
||||
if (!controlData) {
|
||||
fprintf(err, "dpkg: missing control file in %s\n", path.UTF8String);
|
||||
return NO;
|
||||
}
|
||||
NSString *control = [[NSString alloc] initWithData:controlData
|
||||
encoding:NSUTF8StringEncoding];
|
||||
NSDictionary *fields = WQParseControl(control ?: @"");
|
||||
NSString *package = fields[@"Package"] ?: @"unknown";
|
||||
NSString *version = fields[@"Version"] ?: @"0";
|
||||
NSString *architecture = fields[@"Architecture"] ?: @"iphoneos-arm64";
|
||||
BOOL force = options[@"force"] != nil;
|
||||
if (!force &&
|
||||
![architecture isEqualToString:@"iphoneos-arm64"] &&
|
||||
![architecture isEqualToString:@"iphoneos-arm"] &&
|
||||
![architecture isEqualToString:@"darwin-arm64"] &&
|
||||
![architecture isEqualToString:@"all"]) {
|
||||
fprintf(err, "dpkg: package architecture %s is not native "
|
||||
"(use --force-architecture)\n", architecture.UTF8String);
|
||||
return NO;
|
||||
}
|
||||
|
||||
fprintf(out, "Selecting previously unselected package %s.\n",
|
||||
package.UTF8String);
|
||||
NSMutableArray<NSString *> *installedFiles = [NSMutableArray array];
|
||||
if (!WQExtractArchiveData(dataArchive, WQInstallRoot(), installedFiles)) {
|
||||
fprintf(err, "dpkg: failed to unpack %s\n", path.UTF8String);
|
||||
return NO;
|
||||
}
|
||||
|
||||
WQRemoveStatusStanza(package);
|
||||
NSMutableString *status = [NSMutableString stringWithContentsOfFile:WQStatusPath()
|
||||
encoding:NSUTF8StringEncoding error:nil] ?: [NSMutableString string];
|
||||
if (status.length && ![status hasSuffix:@"\n\n"]) [status appendString:@"\n\n"];
|
||||
[status appendFormat:
|
||||
@"Package: %@\n"
|
||||
@"Status: install ok installed\n"
|
||||
@"Priority: %@\n"
|
||||
@"Section: %@\n"
|
||||
@"Installed-Size: %@\n"
|
||||
@"Maintainer: %@\n"
|
||||
@"Architecture: %@\n"
|
||||
@"Version: %@\n"
|
||||
@"Description: %@\n\n",
|
||||
package,
|
||||
fields[@"Priority"] ?: @"optional",
|
||||
fields[@"Section"] ?: @"utility",
|
||||
fields[@"Installed-Size"] ?: @"0",
|
||||
fields[@"Maintainer"] ?: @"unknown",
|
||||
architecture,
|
||||
version,
|
||||
fields[@"Description"] ?: package];
|
||||
[status writeToFile:WQStatusPath() atomically:YES
|
||||
encoding:NSUTF8StringEncoding error:nil];
|
||||
NSString *listPath = [WQInfoDirectory()
|
||||
stringByAppendingPathComponent:[package stringByAppendingString:@".list"]];
|
||||
[[installedFiles componentsJoinedByString:@"\n"] writeToFile:listPath
|
||||
atomically:YES encoding:NSUTF8StringEncoding error:nil];
|
||||
fprintf(out, "Unpacking %s (%s) ...\n", package.UTF8String, version.UTF8String);
|
||||
fprintf(out, "Setting up %s (%s) ...\n", package.UTF8String, version.UTF8String);
|
||||
fprintf(out, "Installed to %s\n", WQInstallRoot().UTF8String);
|
||||
return YES;
|
||||
}
|
||||
|
||||
#pragma mark - dpkg
|
||||
|
||||
static void WQDPKGUsage(FILE *out)
|
||||
{
|
||||
fprintf(out,
|
||||
"Usage: dpkg <action>\n"
|
||||
" -i, --install <file.deb> Install a package\n"
|
||||
" -r, --remove <package> Remove a package\n"
|
||||
" -P, --purge <package> Remove a package\n"
|
||||
" -l, --list List installed packages\n"
|
||||
" -s, --status <package> Show package status\n"
|
||||
" -L, --listfiles <package> List package files\n"
|
||||
" -x, --extract <deb> <dir> Extract data archive\n"
|
||||
" --contents <deb> List .deb contents\n"
|
||||
" --print-architecture Print iphoneos-arm64\n"
|
||||
" --force-architecture Allow non-native packages\n"
|
||||
" --version Show version\n");
|
||||
}
|
||||
|
||||
int WQDPKGMain(int argc, char **argv, FILE *out, FILE *err)
|
||||
{
|
||||
WQEnsurePackageDirectories();
|
||||
if (argc < 2) { WQDPKGUsage(out); return 1; }
|
||||
NSString *action = [NSString stringWithUTF8String:argv[1]];
|
||||
BOOL force = NO;
|
||||
int index = 2;
|
||||
for (int i = 2; i < argc; i++) {
|
||||
NSString *arg = [NSString stringWithUTF8String:argv[i]];
|
||||
if ([arg isEqualToString:@"--force-architecture"]) force = YES;
|
||||
}
|
||||
NSDictionary *options = force ? @{ @"force": @YES } : @{};
|
||||
|
||||
if ([action isEqualToString:@"--version"]) {
|
||||
fprintf(out, "dpkg 1.22 (FilzaSlop in-process)\n");
|
||||
return 0;
|
||||
}
|
||||
if ([action isEqualToString:@"--print-architecture"]) {
|
||||
fprintf(out, "iphoneos-arm64\n");
|
||||
return 0;
|
||||
}
|
||||
if ([action isEqualToString:@"-l"] || [action isEqualToString:@"--list"]) {
|
||||
NSString *text = [NSString stringWithContentsOfFile:WQStatusPath()
|
||||
encoding:NSUTF8StringEncoding error:nil] ?: @"";
|
||||
fprintf(out, "Desired=Unknown/Install/Remove/Purge/Hold\n"
|
||||
"||/ Name Version Architecture Description\n");
|
||||
for (NSString *stanza in [text componentsSeparatedByString:@"\n\n"]) {
|
||||
if (stanza.length == 0) continue;
|
||||
NSString *name = WQFieldFromStanza(stanza, @"Package");
|
||||
NSString *version = WQFieldFromStanza(stanza, @"Version") ?: @"";
|
||||
NSString *arch = WQFieldFromStanza(stanza, @"Architecture") ?: @"";
|
||||
NSString *description = WQFieldFromStanza(stanza, @"Description") ?: @"";
|
||||
fprintf(out, "ii %-20s %-14s %-14s %s\n",
|
||||
(name ?: @"").UTF8String, version.UTF8String,
|
||||
arch.UTF8String, description.UTF8String);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
if ([action isEqualToString:@"-s"] || [action isEqualToString:@"--status"]) {
|
||||
if (index >= argc) { fprintf(err, "dpkg: missing package\n"); return 1; }
|
||||
NSString *package = [NSString stringWithUTF8String:argv[index]];
|
||||
NSString *text = [NSString stringWithContentsOfFile:WQStatusPath()
|
||||
encoding:NSUTF8StringEncoding error:nil] ?: @"";
|
||||
NSString *stanza = WQStanzaForKey(text, @"Package", package);
|
||||
if (!stanza) { fprintf(err, "dpkg: package %s is not installed\n",
|
||||
package.UTF8String); return 1; }
|
||||
fprintf(out, "%s\n", stanza.UTF8String);
|
||||
return 0;
|
||||
}
|
||||
if ([action isEqualToString:@"-L"] || [action isEqualToString:@"--listfiles"]) {
|
||||
if (index >= argc) { fprintf(err, "dpkg: missing package\n"); return 1; }
|
||||
NSString *package = [NSString stringWithUTF8String:argv[index]];
|
||||
NSString *list = [NSString stringWithContentsOfFile:
|
||||
[WQInfoDirectory() stringByAppendingPathComponent:
|
||||
[package stringByAppendingString:@".list"]]
|
||||
encoding:NSUTF8StringEncoding error:nil];
|
||||
if (!list) { fprintf(err, "dpkg: package %s has no file list\n",
|
||||
package.UTF8String); return 1; }
|
||||
fprintf(out, "%s\n", list.UTF8String);
|
||||
return 0;
|
||||
}
|
||||
if ([action isEqualToString:@"-r"] || [action isEqualToString:@"--remove"] ||
|
||||
[action isEqualToString:@"-P"] || [action isEqualToString:@"--purge"]) {
|
||||
if (index >= argc) { fprintf(err, "dpkg: missing package\n"); return 1; }
|
||||
NSString *package = [NSString stringWithUTF8String:argv[index]];
|
||||
NSString *listPath = [WQInfoDirectory()
|
||||
stringByAppendingPathComponent:[package stringByAppendingString:@".list"]];
|
||||
NSString *list = [NSString stringWithContentsOfFile:listPath
|
||||
encoding:NSUTF8StringEncoding error:nil];
|
||||
NSFileManager *fm = NSFileManager.defaultManager;
|
||||
for (NSString *relative in [list componentsSeparatedByString:@"\n"]) {
|
||||
if (!relative.length) continue;
|
||||
NSString *target = [WQInstallRoot() stringByAppendingPathComponent:relative];
|
||||
[fm removeItemAtPath:target error:nil];
|
||||
}
|
||||
[fm removeItemAtPath:listPath error:nil];
|
||||
WQRemoveStatusStanza(package);
|
||||
fprintf(out, "Removing %s ...\n", package.UTF8String);
|
||||
return 0;
|
||||
}
|
||||
if ([action isEqualToString:@"-x"] || [action isEqualToString:@"--extract"] ||
|
||||
[action isEqualToString:@"--contents"]) {
|
||||
if (index >= argc) { fprintf(err, "dpkg: missing .deb\n"); return 1; }
|
||||
NSString *path = [NSString stringWithUTF8String:argv[index]];
|
||||
NSData *archive = [NSData dataWithContentsOfFile:path];
|
||||
NSData *data = WQArMember(archive, @"data.tar");
|
||||
if (!data) { fprintf(err, "dpkg: no data archive\n"); return 1; }
|
||||
if ([action isEqualToString:@"--contents"]) {
|
||||
NSMutableArray<NSString *> *files = [NSMutableArray array];
|
||||
WQExtractArchiveData(data, NSTemporaryDirectory(), files);
|
||||
for (NSString *file in files) fprintf(out, "%s\n", file.UTF8String);
|
||||
return 0;
|
||||
}
|
||||
NSString *destination = index + 1 < argc
|
||||
? [NSString stringWithUTF8String:argv[index + 1]]
|
||||
: WQInstallRoot();
|
||||
NSMutableArray *files = [NSMutableArray array];
|
||||
return WQExtractArchiveData(data, destination, files) ? 0 : 1;
|
||||
}
|
||||
if ([action isEqualToString:@"-i"] || [action isEqualToString:@"--install"]) {
|
||||
if (index >= argc) { fprintf(err, "dpkg: missing .deb\n"); return 1; }
|
||||
BOOL all = YES;
|
||||
for (; index < argc; index++) {
|
||||
NSString *path = [NSString stringWithUTF8String:argv[index]];
|
||||
if ([path isEqualToString:@"--force-architecture"]) continue;
|
||||
if (!WQInstallDeb(path, out, err, options)) all = NO;
|
||||
}
|
||||
return all ? 0 : 1;
|
||||
}
|
||||
WQDPKGUsage(out);
|
||||
return 1;
|
||||
}
|
||||
|
||||
#pragma mark - apt
|
||||
|
||||
static NSString *WQAptIndexPath(void)
|
||||
{
|
||||
return [WQPackageRoot() stringByAppendingPathComponent:
|
||||
@"var/lib/apt/lists/Packages"];
|
||||
}
|
||||
|
||||
static NSString *WQAptSourcesPath(void)
|
||||
{
|
||||
return [WQPackageRoot() stringByAppendingPathComponent:@"etc/apt/sources.list"];
|
||||
}
|
||||
|
||||
static NSArray<NSString *> *WQAptStanzas(void)
|
||||
{
|
||||
NSString *text = [NSString stringWithContentsOfFile:WQAptIndexPath()
|
||||
encoding:NSUTF8StringEncoding error:nil] ?: @"";
|
||||
NSMutableArray *stanzas = [NSMutableArray array];
|
||||
for (NSString *stanza in [text componentsSeparatedByString:@"\n\n"])
|
||||
if (stanza.length) [stanzas addObject:stanza];
|
||||
return stanzas;
|
||||
}
|
||||
|
||||
static NSData *WQDownloadURL(NSString *urlString)
|
||||
{
|
||||
NSURL *url = [NSURL URLWithString:urlString];
|
||||
if (!url) return nil;
|
||||
if ([url isFileURL]) return [NSData dataWithContentsOfURL:url];
|
||||
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
|
||||
__block NSData *result = nil;
|
||||
NSURLSessionDataTask *task = [NSURLSession.sharedSession dataTaskWithURL:url
|
||||
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
|
||||
if (!error && [(NSHTTPURLResponse *)response statusCode] / 100 == 2)
|
||||
result = data;
|
||||
dispatch_semaphore_signal(semaphore);
|
||||
}];
|
||||
[task resume];
|
||||
dispatch_semaphore_wait(semaphore, dispatch_time(DISPATCH_TIME_NOW, 30 * NSEC_PER_SEC));
|
||||
return result;
|
||||
}
|
||||
|
||||
static void WQEnsureSources(void)
|
||||
{
|
||||
NSString *path = WQAptSourcesPath();
|
||||
if ([[NSFileManager defaultManager] fileExistsAtPath:path]) return;
|
||||
NSString *root = WQPackageRoot();
|
||||
NSString *line = [NSString stringWithFormat:@"deb [trusted=yes] file://%@/repo ./\n", root];
|
||||
[line writeToFile:path atomically:YES encoding:NSUTF8StringEncoding error:nil];
|
||||
}
|
||||
|
||||
static void WQAptUsage(FILE *out)
|
||||
{
|
||||
fprintf(out,
|
||||
"Usage: apt <command>\n"
|
||||
" update Refresh package indexes\n"
|
||||
" list List packages in the index\n"
|
||||
" search <text> Search package names/descriptions\n"
|
||||
" show <package> Show package details\n"
|
||||
" install <package|deb> Install a package\n"
|
||||
" remove|purge <package> Remove a package\n"
|
||||
" clean Clear downloaded archives\n"
|
||||
" --version Show version\n"
|
||||
"Root: %s\n", WQPackageRoot().UTF8String);
|
||||
}
|
||||
|
||||
int WQAptMain(int argc, char **argv, FILE *out, FILE *err)
|
||||
{
|
||||
WQEnsurePackageDirectories();
|
||||
WQEnsureSources();
|
||||
if (argc < 2) { WQAptUsage(out); return 1; }
|
||||
NSString *action = [NSString stringWithUTF8String:argv[1]];
|
||||
if ([action isEqualToString:@"--version"]) {
|
||||
fprintf(out, "apt 2.7 (FilzaSlop in-process)\n");
|
||||
return 0;
|
||||
}
|
||||
if ([action isEqualToString:@"update"]) {
|
||||
fprintf(out, "apt: updating package lists...\n");
|
||||
NSString *sources = [NSString stringWithContentsOfFile:WQAptSourcesPath()
|
||||
encoding:NSUTF8StringEncoding error:nil] ?: @"";
|
||||
NSMutableString *index = [NSMutableString string];
|
||||
for (NSString *line in [sources componentsSeparatedByString:@"\n"]) {
|
||||
if (![line hasPrefix:@"deb "]) continue;
|
||||
NSArray *parts = [line componentsSeparatedByString:@" "];
|
||||
NSString *uri = nil;
|
||||
for (NSString *part in parts) {
|
||||
if ([part hasPrefix:@"http://"] || [part hasPrefix:@"https://"] ||
|
||||
[part hasPrefix:@"file://"]) { uri = part; break; }
|
||||
}
|
||||
if (!uri.length) continue;
|
||||
NSArray *names = @[@"Packages", @"Packages.gz"];
|
||||
BOOL loaded = NO;
|
||||
for (NSString *name in names) {
|
||||
NSString *url = [uri stringByAppendingPathComponent:name];
|
||||
NSData *data = WQDownloadURL(url);
|
||||
if (!data.length) continue;
|
||||
if ([name hasSuffix:@".gz"]) {
|
||||
// Decompress gzip through libarchive's raw reader.
|
||||
WQArchive *archive = archive_read_new();
|
||||
archive_read_support_filter_all(archive);
|
||||
archive_read_support_format_raw(archive);
|
||||
if (archive_read_open_memory(archive, data.bytes, data.length) != ARCHIVE_OK) {
|
||||
archive_read_free(archive);
|
||||
continue;
|
||||
}
|
||||
struct archive_entry *entry;
|
||||
NSMutableData *plain = [NSMutableData data];
|
||||
if (archive_read_next_header(archive, &entry) == WQ_ARCHIVE_OK) {
|
||||
uint8_t buffer[65536];
|
||||
ssize_t got;
|
||||
while ((got = archive_read_data(archive, buffer, sizeof(buffer))) > 0)
|
||||
[plain appendBytes:buffer length:(NSUInteger)got];
|
||||
}
|
||||
archive_read_free(archive);
|
||||
data = plain;
|
||||
}
|
||||
NSString *text = [[NSString alloc] initWithData:data
|
||||
encoding:NSUTF8StringEncoding];
|
||||
if (text.length) {
|
||||
[index appendString:text];
|
||||
if (![index hasSuffix:@"\n"]) [index appendString:@"\n"];
|
||||
loaded = YES;
|
||||
break;
|
||||
}
|
||||
}
|
||||
fprintf(out, "%s: %s\n", loaded ? "Get" : "Err", uri.UTF8String);
|
||||
}
|
||||
[index writeToFile:WQAptIndexPath() atomically:YES
|
||||
encoding:NSUTF8StringEncoding error:nil];
|
||||
fprintf(out, "Reading package lists... Done\n");
|
||||
return 0;
|
||||
}
|
||||
if ([action isEqualToString:@"list"]) {
|
||||
for (NSString *stanza in WQAptStanzas()) {
|
||||
NSString *name = WQFieldFromStanza(stanza, @"Package") ?: @"";
|
||||
NSString *version = WQFieldFromStanza(stanza, @"Version") ?: @"";
|
||||
NSString *description = WQFieldFromStanza(stanza, @"Description") ?: @"";
|
||||
fprintf(out, "%s/%s %s %s\n", name.UTF8String,
|
||||
(WQFieldFromStanza(stanza, @"Architecture") ?: @"iphoneos-arm64").UTF8String,
|
||||
version.UTF8String, description.UTF8String);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
if ([action isEqualToString:@"search"]) {
|
||||
NSString *text = argc > 2 ? [NSString stringWithUTF8String:argv[2]] : @"";
|
||||
for (NSString *stanza in WQAptStanzas())
|
||||
if (text.length == 0 || [stanza.lowercaseString containsString:text.lowercaseString]) {
|
||||
NSString *name = WQFieldFromStanza(stanza, @"Package") ?: @"";
|
||||
NSString *description = WQFieldFromStanza(stanza, @"Description") ?: @"";
|
||||
fprintf(out, "%s - %s\n", name.UTF8String, description.UTF8String);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
if ([action isEqualToString:@"show"]) {
|
||||
if (argc < 3) { fprintf(err, "apt: missing package\n"); return 1; }
|
||||
NSString *package = [NSString stringWithUTF8String:argv[2]];
|
||||
for (NSString *stanza in WQAptStanzas())
|
||||
if ([WQFieldFromStanza(stanza, @"Package") isEqualToString:package]) {
|
||||
fprintf(out, "%s\n", stanza.UTF8String);
|
||||
return 0;
|
||||
}
|
||||
fprintf(err, "apt: package %s not found\n", package.UTF8String);
|
||||
return 1;
|
||||
}
|
||||
if ([action isEqualToString:@"install"]) {
|
||||
if (argc < 3) { fprintf(err, "apt: missing package\n"); return 1; }
|
||||
NSString *target = [NSString stringWithUTF8String:argv[2]];
|
||||
if ([[NSFileManager defaultManager] fileExistsAtPath:target] ||
|
||||
[target hasSuffix:@".deb"])
|
||||
return WQDPKGMain(3, (char *[]){(char *)"dpkg", (char *)"-i",
|
||||
(char *)target.UTF8String}, out, err) ? 0 : 1;
|
||||
for (NSString *stanza in WQAptStanzas()) {
|
||||
if (![WQFieldFromStanza(stanza, @"Package") isEqualToString:target]) continue;
|
||||
NSString *filename = WQFieldFromStanza(stanza, @"Filename");
|
||||
NSString *source = @"";
|
||||
NSString *sources = [NSString stringWithContentsOfFile:WQAptSourcesPath()
|
||||
encoding:NSUTF8StringEncoding error:nil] ?: @"";
|
||||
for (NSString *line in [sources componentsSeparatedByString:@"\n"])
|
||||
for (NSString *part in [line componentsSeparatedByString:@" "])
|
||||
if ([part hasPrefix:@"http"] || [part hasPrefix:@"file://"]) {
|
||||
source = part; break;
|
||||
}
|
||||
NSString *url = [source stringByAppendingPathComponent:filename ?: @""];
|
||||
NSData *deb = WQDownloadURL(url);
|
||||
NSString *cache = [[WQPackageRoot()
|
||||
stringByAppendingPathComponent:@"var/cache/apt/archives"]
|
||||
stringByAppendingPathComponent:filename.lastPathComponent ?: @"package.deb"];
|
||||
if (!deb.length || ![deb writeToFile:cache atomically:YES]) {
|
||||
fprintf(err, "apt: unable to download %s\n", url.UTF8String);
|
||||
return 1;
|
||||
}
|
||||
return WQDPKGMain(3, (char *[]){(char *)"dpkg", (char *)"-i",
|
||||
(char *)cache.UTF8String}, out, err) ? 0 : 1;
|
||||
}
|
||||
fprintf(err, "apt: package %s not found; run 'apt update'\n", target.UTF8String);
|
||||
return 1;
|
||||
}
|
||||
if ([action isEqualToString:@"remove"] || [action isEqualToString:@"purge"]) {
|
||||
if (argc < 3) { fprintf(err, "apt: missing package\n"); return 1; }
|
||||
return WQDPKGMain(argc, (char *[]){(char *)"dpkg", (char *)"-r",
|
||||
argv[2], NULL}, out, err) ? 0 : 1;
|
||||
}
|
||||
if ([action isEqualToString:@"clean"]) {
|
||||
NSString *archives = [WQPackageRoot()
|
||||
stringByAppendingPathComponent:@"var/cache/apt/archives"];
|
||||
for (NSString *name in [NSFileManager.defaultManager
|
||||
contentsOfDirectoryAtPath:archives error:nil])
|
||||
[NSFileManager.defaultManager removeItemAtPath:
|
||||
[archives stringByAppendingPathComponent:name] error:nil];
|
||||
fprintf(out, "apt: cache cleaned\n");
|
||||
return 0;
|
||||
}
|
||||
WQAptUsage(out);
|
||||
return 1;
|
||||
}
|
||||
|
||||
BOOL WQInstallDebPackage(NSString *path, NSString **message)
|
||||
{
|
||||
FILE *out = tmpfile();
|
||||
FILE *err = tmpfile();
|
||||
BOOL result = WQInstallDeb(path, out, err, @{});
|
||||
NSMutableString *text = [NSMutableString string];
|
||||
if (out) {
|
||||
fflush(out); rewind(out);
|
||||
char buffer[4096]; size_t got;
|
||||
while ((got = fread(buffer, 1, sizeof(buffer), out)) > 0)
|
||||
[text appendString:[[NSString alloc] initWithBytes:buffer
|
||||
length:got encoding:NSUTF8StringEncoding] ?: @""];
|
||||
fclose(out);
|
||||
}
|
||||
if (err) {
|
||||
fflush(err); rewind(err);
|
||||
char buffer[4096]; size_t got;
|
||||
while ((got = fread(buffer, 1, sizeof(buffer), err)) > 0)
|
||||
[text appendString:[[NSString alloc] initWithBytes:buffer
|
||||
length:got encoding:NSUTF8StringEncoding] ?: @""];
|
||||
fclose(err);
|
||||
}
|
||||
if (message) *message = text;
|
||||
return result;
|
||||
}
|
||||
Reference in new issue
Block a user