Mac: make uninstaller work even if user deleted BOINC Data directory

This commit is contained in:
Charlie Fenton 2023-02-24 04:39:43 -08:00
parent 64d6df402e
commit ae5e68b2d7
3 changed files with 195 additions and 131 deletions

View File

@ -1,6 +1,6 @@
// This file is part of BOINC.
// http://boinc.berkeley.edu
// Copyright (C) 2022 University of California
// Copyright (C) 2023 University of California
//
// BOINC is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License
@ -202,7 +202,6 @@ int main(int argc, char *argv[])
OSStatus err;
FILE *f;
char s[2048];
char path[MAXPATHLEN];
#ifndef SANDBOX
group *grp;
@ -298,10 +297,13 @@ int main(int argc, char *argv[])
err = callPosixSpawn (s);
REPORT_ERROR(err);
// We don't customize BOINC Data directory name for branding
err = callPosixSpawn ("rm -rf \"/Library/Application Support/BOINC Data\"");
REPORT_ERROR(err);
// Don't remove BOINC Data directory if previously present
if (!boinc_file_exists("/Library/Application Support/BOINC Data/stdoutdae.txt")){
// We don't customize BOINC Data directory name for branding
err = callPosixSpawn ("rm -rf \"/Library/Application Support/BOINC Data\"");
REPORT_ERROR(err);
}
err = kill(installerPID, SIGKILL);
return 0;
@ -472,28 +474,6 @@ int main(int argc, char *argv[])
}
}
if (compareOSVersionTo(10, 13) >= 0) {
getPathToThisApp(path, sizeof(path));
strncat(path, "/Contents/Resources/boinc_Finish_Install", sizeof(path)-1);
snprintf(s, sizeof(s), "cp -f \"%s\" \"/Library/Application Support/BOINC Data/%s_Finish_Install\"", path, appName[brandID]);
err = callPosixSpawn(s);
REPORT_ERROR(err);
if (err) {
printf("Command %s returned error %d\n", s, err);
fflush(stdout);
}
snprintf(s, sizeof(s), "/Library/Application Support/BOINC Data/%s_Finish_Install\"</string>\n", appName[brandID]);
chmod(s, 0755);
#ifdef SANDBOX
group *bmgrp = getgrnam(boinc_master_group_name);
passwd *bmpw = getpwnam(boinc_master_user_name);
if (bmgrp && bmpw) {
chown(s, bmpw->pw_uid, bmgrp->gr_gid);
}
#endif
}
err = UpdateAllVisibleUsers(brandID, oldBrandID);
if (err != noErr) {
REPORT_ERROR(true);
@ -1129,7 +1109,7 @@ Boolean SetLoginItemLaunchAgent(long brandID, long oldBrandID, Boolean deleteLog
fprintf(f, "\t<string>edu.berkeley.fix_login_items</string>\n");
fprintf(f, "\t<key>ProgramArguments</key>\n");
fprintf(f, "\t<array>\n");
fprintf(f, "\t\t<string>/Library/Application Support/BOINC Data/%s_Finish_Install</string>\n", appName[brandID]);
fprintf(f, "\t\t<string>/Users/%s/Library/Application Support/BOINC/%s_Finish_Install</string>\n", pw->pw_name, appName[brandID]);
if (deleteLogInItem || (brandID != oldBrandID)) {
// If this user was previously authorized to run the Manager, there
// may still be a Login Item for this user, and the Login Item may
@ -1138,11 +1118,10 @@ Boolean SetLoginItemLaunchAgent(long brandID, long oldBrandID, Boolean deleteLog
// (for this user only) if it is running.
//
fprintf(f, "\t\t<string>-d</string>\n");
fprintf(f, "\t\t<string>%s</string>\n", appName[oldBrandID]);
}
if (!deleteLogInItem) {
fprintf(f, "\t\t<string>%d</string>\n", (int)oldBrandID);
} else {
fprintf(f, "\t\t<string>-a</string>\n");
fprintf(f, "\t\t<string>%s</string>\n", appName[brandID]);
fprintf(f, "\t\t<string>%d</string>\n", (int)brandID);
}
fprintf(f, "\t</array>\n");
fprintf(f, "\t<key>RunAtLoad</key>\n");
@ -1565,7 +1544,7 @@ OSErr UpdateAllVisibleUsers(long brandID, long oldBrandID)
uid_t saved_uid;
Boolean deleteLoginItem;
char human_user_name[256];
char s[256];
char s[2*MAXPATHLEN];
Boolean saverAlreadySetForAll = true;
Boolean setSaverForAllUsers = false;
Boolean allNonAdminUsersAreSet = true;
@ -1573,12 +1552,13 @@ OSErr UpdateAllVisibleUsers(long brandID, long oldBrandID)
int err;
Boolean isAdminGroupMember, isBMGroupMember;
struct stat sbuf;
char cmd[256];
char cmd[2*MAXPATHLEN];
#ifdef SANDBOX
int BMGroupMembershipCount, BPGroupMembershipCount;
int i;
#endif
int i;
int userIndex;
char path[MAXPATHLEN];
// char nameOfSkin[256];
// FindSkinName(nameOfSkin, sizeof(nameOfSkin));
@ -1811,21 +1791,78 @@ OSErr UpdateAllVisibleUsers(long brandID, long oldBrandID)
if (useOSASript) {
snprintf(s, sizeof(s), "/Users/%s/Library/LaunchAgents/edu.berkeley.boinc.plist", pw->pw_name);
boinc_delete_file(s);
for (i=0; i< NUMBRANDS; i++) {
snprintf(s, sizeof(s), "rm -f \"/Users/%s/Library/Application Support/BOINC/%s_Finish_Install\"", pw->pw_name, appName[i]);
err = callPosixSpawn(s);
REPORT_ERROR(err);
if (err) {
printf("Command %s returned error %d\n", s, err);
fflush(stdout);
}
snprintf(s, sizeof(s), "rm -f \"/Users/%s/Library/Application Support/BOINC/%s_Finish_Uninstall\"", pw->pw_name, appName[i]);
err = callPosixSpawn(s);
REPORT_ERROR(err);
if (err) {
printf("Command %s returned error %d\n", s, err);
fflush(stdout);
}
}
printf("[2] calling SetLoginItemOSAScript for user %s, euid = %d, deleteLoginItem = %d\n",
pw->pw_name, geteuid(), deleteLoginItem);
fflush(stdout);
SetLoginItemOSAScript(brandID, deleteLoginItem, pw->pw_name);
printf("[2] calling FixLaunchServicesDataBase for user %s\n", pw->pw_name);
fflush(stdout);
FixLaunchServicesDataBase(pw->pw_uid, brandID);
} else {
snprintf(s, sizeof(s), "mkdir -p \"/Users/%s/Library/Application Support/BOINC/\"", pw->pw_name);
err = callPosixSpawn(s);
REPORT_ERROR(err);
if (err) {
printf("Command %s returned error %d\n", s, err);
fflush(stdout);
}
snprintf(s, sizeof(s), "/Users/%s/Library/Application Support/BOINC/", pw->pw_name);
chmod(s, 0771);
chown(s, pw->pw_uid, pw->pw_gid);
getPathToThisApp(path, sizeof(path));
strncat(path, "/Contents/Resources/boinc_finish_install", sizeof(path)-1);
snprintf(s, sizeof(s), "cp -f \"%s\" \"/Users/%s/Library/Application Support/BOINC/%s_Finish_Install\"", path, pw->pw_name, appName[brandID]);
err = callPosixSpawn(s);
REPORT_ERROR(err);
if (err) {
printf("Command %s returned error %d\n", s, err);
fflush(stdout);
}
snprintf(s, sizeof(s), "/Users/%s/Library/Application Support/BOINC/%s_Finish_Install", pw->pw_name, appName[brandID]);
chmod(s, 0755);
chown(s, pw->pw_uid, pw->pw_gid);
for (i=0; i< NUMBRANDS; i++) {
// If we previously ran the uninstaller but did not log in to this user,
// remove the user's unused BOINC_Manager_Finish_Uninstall file.
snprintf(s, sizeof(s), "rm -f \"/Users/%s/Library/Application Support/BOINC/%s_Finish_Uninstall\"", pw->pw_name, appName[i]);
err = callPosixSpawn(s);
REPORT_ERROR(err);
if (err) {
printf("Command %s returned error %d\n", s, err);
fflush(stdout);
}
}
printf("[2] calling SetLoginItemLaunchAgent for user %s, euid = %d, deleteLoginItem = %d\n",
pw->pw_name, geteuid(), deleteLoginItem);
fflush(stdout);
// SetLoginItemLaunchAgent will run helper app which will call FixLaunchServicesDataBase()
SetLoginItemLaunchAgent(brandID, oldBrandID, deleteLoginItem, pw);
}
printf("[2] calling FixLaunchServicesDataBase for user %s\n", pw->pw_name);
fflush(stdout);
FixLaunchServicesDataBase(pw->pw_uid, brandID);
if (isBMGroupMember) {
// For some reason we need to call getpwnam again on OS 10.5
pw = getpwnam(human_user_name);

View File

@ -1,6 +1,6 @@
// This file is part of BOINC.
// http://boinc.berkeley.edu
// Copyright (C) 2022 University of California
// Copyright (C) 2023 University of California
//
// BOINC is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License
@ -56,8 +56,7 @@
#include "mac_branding.h"
int callPosixSpawn(const char *cmd);
long GetBrandID(char *path);
static void FixLaunchServicesDataBase(void);
static void FixLaunchServicesDataBase(int brandId, bool isUninstall);
static Boolean IsUserActive(const char *userName);
static char * PersistentFGets(char *buf, size_t buflen, FILE *f);
void print_to_log_file(const char *format, ...);
@ -66,8 +65,11 @@ void strip_cr(char *buf);
int main(int argc, const char * argv[]) {
int i, err;
char cmd[2048];
char scriptName[1024];
char *userName;
passwd *pw;
bool isUninstall = false;
int iBrandId = 0;
// Wait until we are the active login (in case of fast user switching)
userName = getenv("USER");
@ -85,67 +87,72 @@ int main(int argc, const char * argv[]) {
}
}
FixLaunchServicesDataBase();
for (i=1; i<argc; i+=2) {
if (strcmp(argv[i], "-d") == 0) {
// If this user was previously authorized to run the Manager, the Login Item
// may have launched the Manager before this app deleted that Login Item. To
// guard against this, we kill the Manager (for this user only) if it is running.
//
snprintf(cmd, sizeof(cmd), "killall -u %d -9 \"%s\"", getuid(), argv[i+1]);
err = callPosixSpawn(cmd);
if (err) {
fprintf(stderr, "Command: %s\n", cmd);
fprintf(stderr, "killall %s returned error %d\n", argv[i+1], err);
fflush(stderr);
}
} else if (strcmp(argv[i], "-a") == 0) {
snprintf(cmd, sizeof(cmd), "osascript -e 'tell application \"System Events\" to make new login item at end with properties {path:\"/Applications/%s.app\", hidden:true, name:\"%s\"}'", argv[i+1], argv[i+1]);
err = callPosixSpawn(cmd);
if (err) {
fprintf(stderr, "Command: %s\n", cmd);
fprintf(stderr, "Make new login item for %s returned error %d\n", argv[i+1], err);
fflush(stderr);
}
snprintf(cmd, sizeof(cmd), "open -jg \"/Applications/%s.app\"", argv[i+1]);
err = callPosixSpawn(cmd);
if (err) {
fprintf(stderr, "Command: %s\n", cmd);
fprintf(stderr, "Make login item for %s returned error %d\n", argv[i+1], err);
fflush(stderr);
}
} // end if (strcmp(argv[i], "-a") == 0)
isUninstall = true;
} else if (strcmp(argv[i], "-a") != 0) {
iBrandId = atoi(argv[i]);
}
} // end for (i=i; i<argc; i+=2)
if (isUninstall) {
// If this user was previously authorized to run the Manager, the Login Item
// may have launched the Manager before this app deleted that Login Item. To
// guard against this, we kill the Manager (for this user only) if it is running.
//
snprintf(cmd, sizeof(cmd), "killall -u %d -9 \"%s\"", getuid(), appName[iBrandId]);
err = callPosixSpawn(cmd);
if (err) {
fprintf(stderr, "Command: %s\n", cmd);
fprintf(stderr, "killall %s returned error %d\n", appName[iBrandId], err);
fflush(stderr);
}
} else {
snprintf(cmd, sizeof(cmd), "osascript -e 'tell application \"System Events\" to make new login item at end with properties {path:\"/Applications/%s.app\", hidden:true, name:\"%s\"}'", appName[iBrandId], appName[iBrandId]);
err = callPosixSpawn(cmd);
if (err) {
fprintf(stderr, "Command: %s\n", cmd);
fprintf(stderr, "Make new login item for %s returned error %d\n", appName[iBrandId], err);
fflush(stderr);
}
snprintf(cmd, sizeof(cmd), "open -jg \"/Applications/%s.app\"", appName[iBrandId]);
err = callPosixSpawn(cmd);
if (err) {
fprintf(stderr, "Command: %s\n", cmd);
fprintf(stderr, "\"open -jg \"/Applications/%s.app\" returned error %d\n", appName[iBrandId], err);
fflush(stderr);
}
}
FixLaunchServicesDataBase(iBrandId, isUninstall);
pw = getpwuid(getuid());
snprintf(cmd, sizeof(cmd), "rm -f \"/Users/%s/Library/LaunchAgents/edu.berkeley.boinc.plist\"", pw->pw_name);
callPosixSpawn(cmd);
// We can't delete ourselves while we are running,
// so launch a shell script to do it after we exit.
sprintf(scriptName, "/tmp/%s_Finish_%s_%s", appName[iBrandId], isUninstall ? "Uninstall" : "Install", pw->pw_name);
FILE* f = fopen(scriptName, "w");
fprintf(f, "#!/bin/bash\n\n");
fprintf(f, "sleep 3\n");
if (isUninstall) {
// Delete per-user BOINC Manager and screensaver files, including this executable
fprintf(f, "rm -fR \"/Users/%s/Library/Application Support/BOINC\"\n", pw->pw_name);
} else {
// Delete only this executable
fprintf(f, "rm -f \"/Users/%s/Library/Application Support/BOINC/%s_Finish_Install\"", pw->pw_name, appName[iBrandId]);
}
fclose(f);
sprintf(cmd, "sh %s", scriptName);
callPosixSpawn (cmd);
return 0;
}
long GetBrandID(char *path)
{
long iBrandId;
iBrandId = 0; // Default value
FILE *f = fopen(path, "r");
if (f) {
fscanf(f, "BrandId=%ld\n", &iBrandId);
fclose(f);
}
if ((iBrandId < 0) || (iBrandId > (NUMBRANDS-1))) {
iBrandId = 0;
}
return iBrandId;
}
// If there are other copies of BOINC Manager with different branding
// on the system, Notifications may display the icon for the wrong
// branding, due to the Launch Services database having one of the
@ -155,27 +162,24 @@ long GetBrandID(char *path)
//
// This probably will happen only on BOINC development systems where
// Xcode has generated copies of BOINC Manager.
static void FixLaunchServicesDataBase() {
long brandID = 0;
static void FixLaunchServicesDataBase(int brandID, bool isUninstall) {
char boincPath[MAXPATHLEN];
char cmd[MAXPATHLEN+250];
long i, n;
CFArrayRef appRefs = NULL;
OSStatus err;
brandID = GetBrandID("/Library/Application Support/BOINC Data/Branding");
CFStringRef bundleID = CFSTR("edu.berkeley.boinc");
// LSCopyApplicationURLsForBundleIdentifier is not available before OS 10.10,
// but this app is used only for OS 10.13 and later
appRefs = LSCopyApplicationURLsForBundleIdentifier(bundleID, NULL);
if (appRefs == NULL) {
print_to_log_file("Call to LSCopyApplicationURLsForBundleIdentifier returned NULL");
goto registerOurApp;
}
n = CFArrayGetCount(appRefs); // Returns all results at once, in database order
print_to_log_file("LSCopyApplicationURLsForBundleIdentifier returned %ld results", n);
appRefs = LSCopyApplicationURLsForBundleIdentifier(bundleID, NULL);
if (appRefs == NULL) {
print_to_log_file("Call to LSCopyApplicationURLsForBundleIdentifier returned NULL");
goto registerOurApp;
}
n = CFArrayGetCount(appRefs); // Returns all results at once, in database order
print_to_log_file("LSCopyApplicationURLsForBundleIdentifier returned %ld results", n);
for (i=0; i<n; ++i) { // Prevent infinite loop
CFURLRef appURL = (CFURLRef)CFArrayGetValueAtIndex(appRefs, i);
@ -188,10 +192,12 @@ static void FixLaunchServicesDataBase() {
CFRelease(appURL);
appURL = NULL;
}
if (strncmp(boincPath, appPath[brandID], sizeof(boincPath)) == 0) {
print_to_log_file("**** Keeping %s", boincPath);
if (appRefs) CFRelease(appRefs);
return; // Our (possibly branded) BOINC Manager app is now at top of database
if (! isUninstall) {
if (strncmp(boincPath, appPath[brandID], sizeof(boincPath)) == 0) {
print_to_log_file("**** Keeping %s", boincPath);
if (appRefs) CFRelease(appRefs);
return; // Our (possibly branded) BOINC Manager app is now at top of database
}
}
print_to_log_file("Unregistering %3ld: %s", i, boincPath);
// Remove this entry from the Launch Services database
@ -205,6 +211,8 @@ static void FixLaunchServicesDataBase() {
registerOurApp:
if (appRefs) CFRelease(appRefs);
if (isUninstall) return;
// We have exhausted the Launch Services database without finding our
// (possibly branded) BOINC Manager app, so add it to the dataabase
print_to_log_file("%s was not found in Launch Services database; registering it now", appPath[brandID]);

View File

@ -1,6 +1,6 @@
// This file is part of BOINC.
// http://boinc.berkeley.edu
// Copyright (C) 2022 University of California
// Copyright (C) 2023 University of California
//
// BOINC is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License
@ -625,12 +625,13 @@ static OSStatus CleanupAllVisibleUsers(void)
int userIndex;
int flag;
char buf[256];
char s[1024];
char s[2*MAXPATHLEN];
FILE *f;
char *p;
int id;
OSStatus err;
Boolean changeSaver;
Boolean isCatalinaOrLater = (compareOSVersionTo(10, 15) >= 0);
// saved_uid = getuid();
saved_euid = geteuid();
@ -749,6 +750,14 @@ static OSStatus CleanupAllVisibleUsers(void)
pw->pw_name, geteuid());
#endif
DeleteLoginItemOSAScript(pw->pw_name);
// Under OS 10.13 High Sierra or later, this code deletes the per-user BOINC
// Manager files only for the user running this app. For each user other than
// one running this app, we put BOINCManager_Finish_Uninstall in its per-user
// BOINC directory, so we can't delete it now. BOINCManager_Finish_Uninstall
// will delete that user's per-user BOINC directory as its final task.
sprintf(s, "rm -fR \"/Users/%s/Library/Application Support/BOINC\"", pw->pw_name);
callPosixSpawn (s);
} else {
#if TESTING
showDebugMsg("calling DeleteLoginItemLaunchAgent for user %s, euid = %d\n",
@ -767,14 +776,18 @@ static OSStatus CleanupAllVisibleUsers(void)
// sprintf(s, "rm -f \"/Users/%s/Library/Preferences/BOINC Manager Preferences\"", human_user_name);
// callPosixSpawn (s);
// Delete per-user BOINC Manager and screensaver files
sprintf(s, "rm -fR \"/Users/%s/Library/Application Support/BOINC\"", human_user_name);
callPosixSpawn (s);
// Set screensaver to "Flurry" screensaver only
// if it was BOINC unbranded or branded screensaver.
changeSaver = false;
if (isCatalinaOrLater) {
// As of Catalina, Screensaver output files are put in the user's Containers
// directory.
snprintf(s, sizeof(s), "rm -fR \"/Users/%s/Library/Containers/com.apple.ScreenSaver.Engine.legacyScreenSaver/Data/Library/Application Support/BOINC\"",
pw->pw_name);
callPosixSpawn(s);
}
err = GetCurrentScreenSaverSelection(pw, s, sizeof(s) -1);
if (err == noErr) {
for (i=0; i<NUMBRANDS; ++i) {
@ -953,7 +966,7 @@ cleanupSystemEvents:
}
// Under OS 10.13 High Sierra, telling System Events to modify Login Items for
// As of OS 10.13 High Sierra, telling System Events to modify Login Items for
// users who are not currently logged in no longer works, even when System Events
// is running as that user.
// So we create a LaunchAgent for that user. The next time that user logs in, the
@ -969,28 +982,34 @@ cleanupSystemEvents:
//
Boolean DeleteLoginItemLaunchAgent(long brandID, passwd *pw)
{
static bool alreadyCopied = false;
struct stat sbuf;
char path[MAXPATHLEN];
char s[2048];
char s[2*MAXPATHLEN];
OSErr err;
if (!alreadyCopied) {
getPathToThisApp(path, sizeof(path));
strncat(path, "/Contents/Resources/boinc_finish_install", sizeof(path)-1);
snprintf(s, sizeof(s), "cp -f \"%s\" \"/Library/Application Support/BOINC Data/%s_Finish_Uninstall\"", path, appName[brandID]);
err = callPosixSpawn(s);
if (err) {
printf("[2] Command %s returned error %d\n", s, err);
fflush(stdout);
} else {
alreadyCopied = true;
}
snprintf(s, sizeof(s), "/Library/Application Support/BOINC Data/%s_Finish_Install\"</string>\n", appName[brandID]);
chmod(s, 0755);
chown(s, pw->pw_uid, pw->pw_gid);
snprintf(s, sizeof(s), "mkdir -p \"/Users/%s/Library/Application Support/BOINC/\"", pw->pw_name);
err = callPosixSpawn(s);
if (err) {
printf("[2] Command %s returned error %d\n", s, err);
fflush(stdout);
}
snprintf(s, sizeof(s), "/Users/%s/Library/Application Support/BOINC/", pw->pw_name);
chmod(s, 0771);
chown(s, pw->pw_uid, pw->pw_gid);
getPathToThisApp(path, sizeof(path));
strncat(path, "/Contents/Resources/boinc_finish_install", sizeof(path)-1);
snprintf(s, sizeof(s), "cp -f \"%s\" \"/Users/%s/Library/Application Support/BOINC/%s_Finish_Uninstall\"",
path, pw->pw_name, appName[brandID]);
err = callPosixSpawn(s);
if (err) {
printf("[2] Command %s returned error %d\n", s, err);
fflush(stdout);
}
snprintf(s, sizeof(s), "/Users/%s/Library/Application Support/BOINC/%s_Finish_Uninstall", pw->pw_name, appName[brandID]);
chmod(s, 0755);
chown(s, pw->pw_uid, pw->pw_gid);
// Create a LaunchAgent for the specified user, replacing any LaunchAgent created
// previously (such as by Installer or by installing a differently branded BOINC.)
@ -1013,7 +1032,7 @@ Boolean DeleteLoginItemLaunchAgent(long brandID, passwd *pw)
fprintf(f, "\t<string>edu.berkeley.fix_login_items</string>\n");
fprintf(f, "\t<key>ProgramArguments</key>\n");
fprintf(f, "\t<array>\n");
fprintf(f, "\t\t<string>/Library/Application Support/BOINC Data/%s_Finish_Uninstall</string>\n", appName[brandID]);
fprintf(f, "\t\t<string>/Users/%s/Library/Application Support/BOINC/%s_Finish_Uninstall</string>\n", pw->pw_name, appName[brandID]);
// If this user was previously authorized to run the Manager, there
// may still be a Login Item for this user, and the Login Item may
// launch the Manager before the LaunchAgent deletes the Login Item.
@ -1024,7 +1043,7 @@ Boolean DeleteLoginItemLaunchAgent(long brandID, passwd *pw)
// that could happen, so this step is probably unnecessary.
//
fprintf(f, "\t\t<string>-d</string>\n");
fprintf(f, "\t\t<string>%s</string>\n", appName[brandID]);
fprintf(f, "\t\t<string>%d</string>\n", (int)brandID);
fprintf(f, "\t</array>\n");
fprintf(f, "\t<key>RunAtLoad</key>\n");
fprintf(f, "\t<true/>\n");