/* Cydia - iPhone UIKit Front-End for Debian APT
- * Copyright (C) 2008-2014 Jay Freeman (saurik)
+ * Copyright (C) 2008-2015 Jay Freeman (saurik)
*/
/* GNU General Public License, Version 3 {{{ */
#include <sys/mount.h>
#include <sys/reboot.h>
+#include <dirent.h>
#include <fcntl.h>
#include <notify.h>
#include <dlfcn.h>
[NSString stringWithFormat:@"%@/%s", Cache_, file]
static void (*$SBSSetInterceptsMenuButtonForever)(bool);
+static NSData *(*$SBSCopyIconImagePNGDataForDisplayIdentifier)(NSString *);
static CFStringRef (*$MGCopyAnswer)(CFStringRef);
return [[NSString stringWithUTF8String:page] stringByAppendingString:path];
}
-static void ReapZombie(pid_t pid) {
- int status;
- wait:
- if (waitpid(pid, &status, 0) == -1)
- if (errno == EINTR)
- goto wait;
- else _assert(false);
+static NSString *ShellEscape(NSString *value) {
+ return [NSString stringWithFormat:@"'%@'", [value stringByReplacingOccurrencesOfString:@"'" withString:@"'\\''"]];
}
static _finline void UpdateExternalStatus(uint64_t newStatus) {
static int Finish_;
static bool RestartSubstrate_;
-static bool UpgradeCydia_;
static NSArray *Finishes_;
#define SpringBoard_ "/System/Library/LaunchDaemons/com.apple.SpringBoard.plist"
return [NSString stringWithUTF8String:string];
}
+static NSString *VerifySource(NSString *href) {
+ static RegEx href_r("(http(s?)://|file:///)[^# ]*");
+ if (!href_r(href)) {
+ [[[[UIAlertView alloc]
+ initWithTitle:[NSString stringWithFormat:Colon_, Error_, UCLocalize("INVALID_URL")]
+ message:UCLocalize("INVALID_URL_EX")
+ delegate:nil
+ cancelButtonTitle:UCLocalize("OK")
+ otherButtonTitles:nil
+ ] autorelease] show];
+
+ return nil;
+ }
+
+ if (![href hasSuffix:@"/"])
+ href = [href stringByAppendingString:@"/"];
+ return href;
+}
+
@class Cydia;
/* Delegate Prototypes {{{ */
- (void) _saveConfig;
- (void) syncData;
- (void) addSource:(NSDictionary *)source;
-- (void) addTrivialSource:(NSString *)href;
+- (BOOL) addTrivialSource:(NSString *)href;
- (UIProgressHUD *) addProgressHUD;
- (void) removeProgressHUD:(UIProgressHUD *)hud;
- (void) showActionSheet:(UIActionSheet *)sheet fromItem:(UIBarButtonItem *)item;
- (NSString *) installed;
- (BOOL) uninstalled;
-- (BOOL) valid;
- (BOOL) upgradableAndEssential:(BOOL)essential;
- (BOOL) essential;
- (BOOL) broken;
iterator_ = iterator;
_profile(Package$initWithVersion$Version)
- if (!version_.end())
- file_ = version_.FileList();
- else {
- pkgCache &cache([database_ cache]);
- file_ = pkgCache::VerFileIterator(cache, cache.VerFileP);
- }
+ file_ = version_.FileList();
_end
_profile(Package$initWithVersion$Cache)
return installed_.empty();
}
-- (BOOL) valid {
- return !version_.end();
-}
-
- (BOOL) upgradableAndEssential:(BOOL)essential {
_profile(Package$upgradableAndEssential)
pkgCache::VerIterator current(iterator_.CurrentVer());
if (current.end())
return essential && essential_;
else
- return !version_.end() && version_ != current;
+ return version_ != current;
_end
}
for (NSString *file in files)
if (application_r(file)) {
NSDictionary *info([NSDictionary dictionaryWithContentsOfFile:file]);
+ if (info == nil)
+ continue;
NSString *id([info objectForKey:@"CFBundleIdentifier"]);
- if ([id isEqualToString:me])
+ if (id == nil || [id isEqualToString:me])
continue;
NSString *display([info objectForKey:@"CFBundleDisplayName"]);
- (void) clear {
@synchronized (database_) {
+ if ([database_ era] != era_ || file_.end())
+ return;
+
pkgProblemResolver *resolver = [database_ resolver];
resolver->Clear(iterator_);
- (void) install {
@synchronized (database_) {
+ if ([database_ era] != era_ || file_.end())
+ return;
+
pkgProblemResolver *resolver = [database_ resolver];
resolver->Clear(iterator_);
resolver->Protect(iterator_);
- (void) remove {
@synchronized (database_) {
+ if ([database_ era] != era_ || file_.end())
+ return;
+
pkgProblemResolver *resolver = [database_ resolver];
resolver->Clear(iterator_);
resolver->Remove(iterator_);
- (bool) popErrorWithTitle:(NSString *)title forReadList:(pkgSourceList &)list {
if ([self popErrorWithTitle:title forOperation:list.ReadMainList()])
return true;
- if ([self popErrorWithTitle:title forOperation:list.Read(SOURCES_LIST)])
- return true;
return false;
+
+ list.Reset();
+
+ bool error(false);
+
+ if (access("/etc/apt/sources.list", F_OK) == 0)
+ error |= [self popErrorWithTitle:title forOperation:list.ReadAppend("/etc/apt/sources.list")];
+
+ std::string base("/etc/apt/sources.list.d");
+ if (DIR *sources = opendir(base.c_str())) {
+ while (dirent *source = readdir(sources))
+ if (source->d_name[0] != '.' && source->d_namlen > 5 && strcmp(source->d_name + source->d_namlen - 5, ".list") == 0 && strcmp(source->d_name, "cydia.list") != 0)
+ error |= [self popErrorWithTitle:title forOperation:list.ReadAppend((base + "/" + source->d_name).c_str())];
+ closedir(sources);
+ }
+
+ error |= [self popErrorWithTitle:title forOperation:list.ReadAppend(SOURCES_LIST)];
+
+ return error;
}
- (void) reloadDataWithInvocation:(NSInvocation *)invocation {
}
_end
- delock_ = GetStatusDate();
-
_trace();
OpProgress progress;
bool opened;
open:
+ delock_ = GetStatusDate();
_profile(reloadDataWithInvocation$pkgCacheFile)
opened = cache_.Open(progress, false);
_end
delock_ = nil;
pkgPackageManager::OrderResult result(manager_->DoInstall(statusfd_));
+
+ NSString *oextended(@"/var/lib/apt/extended_states");
+ NSString *nextended(Cache("extended_states"));
+
+ struct stat info;
+ if (stat([nextended UTF8String], &info) != -1 && (info.st_mode & S_IFMT) == S_IFREG) {
+ system([[NSString stringWithFormat:@"/usr/libexec/cydia/cydo /bin/mv -f %@ %@", ShellEscape(nextended), ShellEscape(oextended)] UTF8String]);
+ system([[NSString stringWithFormat:@"/usr/libexec/cydia/cydo /bin/chown 0:0 %@", ShellEscape(oextended)] UTF8String]);
+ }
+
+ unlink([nextended UTF8String]);
+ symlink([oextended UTF8String], [nextended UTF8String]);
+
if ([self popErrorWithTitle:title])
return;
return @"getAllSources";
else if (selector == @selector(getApplicationInfo:value:))
return @"getApplicationInfoValue";
+ else if (selector == @selector(getDisplayIdentifiers))
+ return @"getDisplayIdentifiers";
+ else if (selector == @selector(getLocalizedNameForDisplayIdentifier:))
+ return @"getLocalizedNameForDisplayIdentifier";
else if (selector == @selector(getKernelNumber:))
return @"getKernelNumber";
else if (selector == @selector(getKernelString:))
return [info objectForKey:key];
}
+- (NSArray *) getDisplayIdentifiers {
+ NSSet *set([SBSCopyDisplayIdentifiers() autorelease]);
+ if (set == nil || ![set isKindOfClass:[NSSet class]])
+ return [NSArray array];
+ return [set allObjects];
+}
+
+- (NSString *) getLocalizedNameForDisplayIdentifier:(NSString *)identifier {
+ return [SBSCopyLocalizedApplicationNameForDisplayIdentifier(identifier) autorelease] ?: (id) [NSNull null];
+}
+
- (NSNumber *) getKernelNumber:(NSString *)name {
const char *string([name UTF8String]);
nil] waitUntilDone:NO];
}
-- (void) addTrivialSource:(NSString *)href {
+- (BOOL) addTrivialSource:(NSString *)href {
+ href = VerifySource(href);
+ if (href == nil)
+ return NO;
[delegate_ performSelectorOnMainThread:@selector(addTrivialSource:) withObject:href waitUntilDone:NO];
+ return YES;
}
- (void) refreshSources {
nil];
}
-ssize_t DiskUsage(const char *path);
-
- (NSNumber *) du:(NSString *)path {
- ssize_t usage(DiskUsage([path UTF8String]));
- if (usage != -1)
- usage /= 1024;
- return [NSNumber numberWithUnsignedLong:usage];
+ NSNumber *value(nil);
+
+ FILE *du(popen([[NSString stringWithFormat:@"/usr/libexec/cydia/cydo /usr/libexec/cydia/du -ks %@", ShellEscape(path)] UTF8String], "r"));
+ if (du != NULL) {
+ char line[1024];
+ while (fgets(line, sizeof(line), du) != NULL) {
+ size_t length(strlen(line));
+ while (length != 0 && line[length - 1] == '\n')
+ line[--length] = '\0';
+ if (char *tab = strchr(line, '\t')) {
+ *tab = '\0';
+ value = [NSNumber numberWithUnsignedLong:strtoul(line, NULL, 0)];
+ }
+ }
+ pclose(du);
+ }
+
+ return value;
}
- (void) close {
issues_ = [NSMutableArray arrayWithCapacity:4];
- UpgradeCydia_ = false;
-
for (Package *package in packages) {
pkgCache::PkgIterator iterator([package iterator]);
NSString *name([package id]);
[removes addObject:name];
}
- if ([name isEqualToString:@"cydia"])
- UpgradeCydia_ = true;
-
substrate_ |= DepSubstrate(policy->GetCandidateVer(iterator));
substrate_ |= DepSubstrate(iterator.CurrentVer());
}
[super viewWillAppear:animated];
}
-- (void) reloadSpringBoard {
- if (kCFCoreFoundationVersionNumber > 700) { // XXX: iOS 6.x
- system("/bin/launchctl stop com.apple.backboardd");
- sleep(15);
- system("/usr/bin/killall backboardd SpringBoard sbreload");
- return;
- }
-
- pid_t pid(ExecFork());
- if (pid == 0) {
- if (setsid() == -1)
- perror("setsid");
-
- pid_t pid(ExecFork());
- if (pid == 0) {
- execl("/usr/libexec/cydia/cydo", "cydo", "/usr/bin/sbreload", NULL);
- perror("sbreload");
-
- exit(0);
- } ReapZombie(pid);
-
- exit(0);
- } ReapZombie(pid);
-
- sleep(15);
- system("/usr/bin/killall backboardd SpringBoard sbreload");
-}
-
- (void) close {
UpdateExternalStatus(0);
reload: {
UIProgressHUD *hud([delegate_ addProgressHUD]);
[hud setText:UCLocalize("LOADING")];
- [self performSelector:@selector(reloadSpringBoard) withObject:nil afterDelay:0.5];
+ [delegate_ performSelector:@selector(reloadSpringBoard) withObject:nil afterDelay:0.5];
return;
}
_H<NSString> name_;
bool commercial_;
std::vector<std::pair<_H<NSString>, _H<NSString>>> buttons_;
+ _H<UIActionSheet> sheet_;
_H<UIBarButtonItem> button_;
}
- (void) actionSheet:(UIActionSheet *)sheet clickedButtonAtIndex:(NSInteger)button {
NSString *context([sheet context]);
+ if (sheet_ == sheet)
+ sheet_ = nil;
if ([context isEqualToString:@"modify"]) {
if (button != [sheet cancelButtonIndex]) {
for (const auto &button : buttons_)
[buttons addObject:button.second];
- UIActionSheet *sheet = [[[UIActionSheet alloc]
+ sheet_ = [[[UIActionSheet alloc]
initWithTitle:nil
delegate:self
cancelButtonTitle:nil
otherButtonTitles:nil
] autorelease];
- for (NSString *button in buttons) [sheet addButtonWithTitle:button];
+ for (NSString *button in buttons) [sheet_ addButtonWithTitle:button];
if (!IsWildcat_) {
- [sheet addButtonWithTitle:UCLocalize("CANCEL")];
- [sheet setCancelButtonIndex:[sheet numberOfButtons] - 1];
+ [sheet_ addButtonWithTitle:UCLocalize("CANCEL")];
+ [sheet_ setCancelButtonIndex:[sheet_ numberOfButtons] - 1];
}
- [sheet setContext:@"modify"];
+ [sheet_ setContext:@"modify"];
- [delegate_ showActionSheet:sheet fromItem:[[self navigationItem] rightBarButtonItem]];
+ [delegate_ showActionSheet:sheet_ fromItem:[[self navigationItem] rightBarButtonItem]];
}
}
- (void) reloadData {
[super reloadData];
+ [sheet_ dismissWithClickedButtonIndex:[sheet_ cancelButtonIndex] animated:YES];
+ sheet_ = nil;
+
package_ = [database_ packageWithName:name_];
buttons_.clear();
_profile(PackageTable$reloadData$Filter)
for (Package *package in packages)
- if ([package valid] && filter(package))
+ if (filter(package))
[filtered addObject:package];
_end
[alert setCancelButtonIndex:0];
[alert setMessage:
- @"Copyright \u00a9 2008-2014\n"
+ @"Copyright \u00a9 2008-2015\n"
"SaurikIT, LLC\n"
"\n"
"Jay Freeman (saurik)\n"
Database *database([Database sharedInstance]);
- if ([command isEqualToString:@"package-icon"]) {
+ if (false);
+ else if ([command isEqualToString:@"application-icon"]) {
+ if (path == nil)
+ goto fail;
+ path = [path stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
+
+ UIImage *icon(nil);
+
+ if (icon == nil && $SBSCopyIconImagePNGDataForDisplayIdentifier != NULL) {
+ NSData *data([$SBSCopyIconImagePNGDataForDisplayIdentifier(path) autorelease]);
+ icon = [UIImage imageWithData:data];
+ }
+
+ if (icon == nil)
+ if (NSString *file = SBSCopyIconImagePathForDisplayIdentifier(path))
+ icon = [UIImage imageAtPath:file];
+
+ if (icon == nil)
+ icon = [UIImage imageNamed:@"unknown.png"];
+
+ [self _returnPNGWithImage:icon forRequest:request];
+ } else if ([command isEqualToString:@"package-icon"]) {
if (path == nil)
goto fail;
path = [path stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
[section addToCount];
_profile(SectionsView$reloadData$Filter)
- if (![package valid] || ![package visible])
+ if (![package visible])
continue;
_end
const char *package([name_ UTF8String]);
bool on([ignoredSwitch_ isOn]);
- pid_t pid(ExecFork());
- if (pid == 0) {
- FILE *dpkg(popen("/usr/libexec/cydo --set-selections", "w"));
- fwrite(package, strlen(package), 1, dpkg);
-
- if (on)
- fwrite(" hold\n", 6, 1, dpkg);
- else
- fwrite(" install\n", 9, 1, dpkg);
+ FILE *dpkg(popen("/usr/libexec/cydia/cydo --set-selections", "w"));
+ fwrite(package, strlen(package), 1, dpkg);
- pclose(dpkg);
+ if (on)
+ fwrite(" hold\n", 6, 1, dpkg);
+ else
+ fwrite(" install\n", 9, 1, dpkg);
- exit(0);
- } ReapZombie(pid);
+ pclose(dpkg);
}
- (void) onIgnored:(id)control {
switch (button) {
case 1: {
NSString *href = [[alert textField] text];
-
- static RegEx href_r("(http(s?)://|file:///)[^# ]*");
- if (!href_r(href)) {
- UIAlertView *alert = [[[UIAlertView alloc]
- initWithTitle:[NSString stringWithFormat:Colon_, Error_, UCLocalize("INVALID_URL")]
- message:UCLocalize("INVALID_URL_EX")
- delegate:self
- cancelButtonTitle:UCLocalize("OK")
- otherButtonTitles:nil
- ] autorelease];
-
- [alert setContext:@"badurl"];
- [alert show];
-
+ href = VerifySource(href);
+ if (href == nil)
break;
- }
-
- if (![href hasSuffix:@"/"])
- href_ = [href stringByAppendingString:@"/"];
- else
- href_ = href;
+ href_ = href;
trivial_bz2_ = [[self _requestHRef:[href_ stringByAppendingString:@"Packages.bz2"] method:@"HEAD"] retain];
trivial_gz_ = [[self _requestHRef:[href_ stringByAppendingString:@"Packages.gz"] method:@"HEAD"] retain];
[self _loaded];
}
+- (void) reloadSpringBoard {
+ if (kCFCoreFoundationVersionNumber >= 700) // XXX: iOS 6.x
+ system("/bin/launchctl stop com.apple.backboardd");
+ else
+ system("/bin/launchctl stop com.apple.SpringBoard");
+ sleep(15);
+ system("/usr/bin/killall backboardd SpringBoard");
+}
+
- (void) _saveConfig {
SaveConfig(database_);
}
CydiaAddSource(href, distribution, sections);
}
-- (void) addTrivialSource:(NSString *)href {
+// XXX: this method should not return anything
+- (BOOL) addTrivialSource:(NSString *)href {
CydiaAddSource(href, @"./");
+ return YES;
}
- (void) resolve {
- (void) _uicache {
_trace();
-
- if (UpgradeCydia_ && Finish_ > 0)
- system("/usr/libexec/cydia/cydo /bin/su -c /usr/bin/uicache mobile");
- else
- system("/usr/bin/uicache");
-
+ system("/usr/bin/uicache");
_trace();
}
@synchronized (self) {
for (Package *broken in (id) broken_) {
[broken remove];
- NSString *id = [broken id];
-
+ NSString *id(ShellEscape([broken id]));
system([[NSString stringWithFormat:@"/usr/libexec/cydia/cydo /bin/rm -f"
" /var/lib/dpkg/info/%@.prerm"
" /var/lib/dpkg/info/%@.postrm"
" /var/lib/dpkg/info/%@.preinst"
" /var/lib/dpkg/info/%@.postinst"
" /var/lib/dpkg/info/%@.extrainst_"
- , id, id, id, id, id] UTF8String]);
+ "", id, id, id, id, id] UTF8String]);
}
[self resolve];
controller = [[[SectionController alloc] initWithDatabase:database_ source:nil section:argument] autorelease];
}
- if (!external && [base isEqualToString:@"sources"]) {
+ if ([base isEqualToString:@"sources"]) {
if ([argument isEqualToString:@"add"]) {
controller = [[[SourcesController alloc] initWithDatabase:database_] autorelease];
[(SourcesController *)controller showAddSourcePrompt];
UpdateExternalStatus(0);
[self removeStashController];
-
- pid_t pid(ExecFork());
- if (pid == 0) {
- execlp("launchctl", "launchctl", "stop", "com.apple.SpringBoard", NULL);
- perror("launchctl stop");
-
- exit(0);
- } ReapZombie(pid);
+ [self reloadSpringBoard];
}
- (void) setupViewControllers {
[window_ makeKey:self];
[window_ setHidden:NO];
+ if (access("/.cydia_no_stash", F_OK) == 0);
+ else {
+
if (false) stash: {
[self addStashController];
// XXX: this would be much cleaner as a yieldToSelector:
Stash_("/usr/share");
//Stash_("/var/lib");
+ }
+
database_ = [Database sharedInstance];
[database_ setDelegate:self];
return _NSUserDefaults$objectForKey$(self, _cmd, key);
}
+static NSMutableDictionary *AutoreleaseDeepMutableCopyOfDictionary(CFTypeRef type) {
+ if (type == NULL)
+ return nil;
+ if (CFGetTypeID(type) != CFDictionaryGetTypeID())
+ return nil;
+ CFTypeRef copy(CFPropertyListCreateDeepCopy(kCFAllocatorDefault, type, kCFPropertyListMutableContainers));
+ CFRelease(type);
+ return [(NSMutableDictionary *) copy autorelease];
+}
+
int main(int argc, char *argv[]) {
+ int fd(open("/tmp/cydia.log", O_WRONLY | O_APPEND | O_CREAT, 0644));
+ dup2(fd, 2);
+ close(fd);
+
NSAutoreleasePool *pool([[NSAutoreleasePool alloc] init]);
_trace();
MetaFile_.Open("/var/mobile/Library/Cydia/metadata.cb0");
_trace();
- // XXX: port this to NSUserDefaults when you aren't in such a rush
- Values_ = [[[(NSDictionary *) CFPreferencesCopyAppValue(CFSTR("CydiaValues"), CFSTR("com.saurik.Cydia")) autorelease] mutableCopy] autorelease];
- Sections_ = [[[(NSDictionary *) CFPreferencesCopyAppValue(CFSTR("CydiaSections"), CFSTR("com.saurik.Cydia")) autorelease] mutableCopy] autorelease];
- Sources_ = [[[(NSDictionary *) CFPreferencesCopyAppValue(CFSTR("CydiaSources"), CFSTR("com.saurik.Cydia")) autorelease] mutableCopy] autorelease];
+ Values_ = AutoreleaseDeepMutableCopyOfDictionary(CFPreferencesCopyAppValue(CFSTR("CydiaValues"), CFSTR("com.saurik.Cydia")));
+ Sections_ = AutoreleaseDeepMutableCopyOfDictionary(CFPreferencesCopyAppValue(CFSTR("CydiaSections"), CFSTR("com.saurik.Cydia")));
+ Sources_ = AutoreleaseDeepMutableCopyOfDictionary(CFPreferencesCopyAppValue(CFSTR("CydiaSources"), CFSTR("com.saurik.Cydia")));
Version_ = [(NSNumber *) CFPreferencesCopyAppValue(CFSTR("CydiaVersion"), CFSTR("com.saurik.Cydia")) autorelease];
_trace();
_H<NSMutableArray> broken([NSMutableArray array]);
for (NSString *key in (id) Sources_)
- if ([key rangeOfCharacterFromSet:[NSCharacterSet characterSetWithCharactersInString:@"# "]].location != NSNotFound)
+ if ([key rangeOfCharacterFromSet:[NSCharacterSet characterSetWithCharactersInString:@"# "]].location != NSNotFound || ![([[Sources_ objectForKey:key] objectForKey:@"URI"] ?: @"/") hasSuffix:@"/"])
[broken addObject:key];
if ([broken count] != 0)
for (NSString *key in (id) broken)
_assert(errno == ENOENT);
}
+ system("/usr/libexec/cydia/cydo /bin/ln -sf /var/mobile/Library/Caches/com.saurik.Cydia/sources.list /etc/apt/sources.list.d/cydia.list");
+
/* APT Initialization {{{ */
_assert(pkgInitConfig(*_config));
_assert(pkgInitSystem(*_config, _system));
mkdir([Cache("archives/partial") UTF8String], 0755);
_config->Set("Dir::Cache", [Cache_ UTF8String]);
+ symlink("/var/lib/apt/extended_states", [Cache("extended_states") UTF8String]);
+ _config->Set("Dir::State", [Cache_ UTF8String]);
+
mkdir([Cache("lists") UTF8String], 0755);
mkdir([Cache("lists/partial") UTF8String], 0755);
mkdir([Cache("periodic") UTF8String], 0755);
/* }}} */
$SBSSetInterceptsMenuButtonForever = reinterpret_cast<void (*)(bool)>(dlsym(RTLD_DEFAULT, "SBSSetInterceptsMenuButtonForever"));
+ $SBSCopyIconImagePNGDataForDisplayIdentifier = reinterpret_cast<NSData *(*)(NSString *)>(dlsym(RTLD_DEFAULT, "SBSCopyIconImagePNGDataForDisplayIdentifier"));
const char *symbol(kCFCoreFoundationVersionNumber >= 800 ? "MGGetBoolAnswer" : "GSSystemHasCapability");
BOOL (*GSSystemHasCapability)(CFStringRef) = reinterpret_cast<BOOL (*)(CFStringRef)>(dlsym(RTLD_DEFAULT, symbol));