// This file is part of BOINC. // http://boinc.berkeley.edu // 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 // as published by the Free Software Foundation, // either version 3 of the License, or (at your option) any later version. // // BOINC is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. // See the GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public License // along with BOINC. If not, see . // // main.cpp // boinc_Finish_Install // Usage: boinc_Finish_Install [-d] [brandID] // // * Deletes Login Items of all possible branded and unbranded BOINC Managers for current user. // * If first argument is -d then also kills the application specified by the second argument. // * If first argument is the name of a branded or unbranded BOINC Manager, adds it as a Login // Item for the current user and launches it. // // TODO: Do we ned to code sign this app? // #define VERBOSE_TEST 0 /* for debugging callPosixSpawn */ #if VERBOSE_TEST #define CREATE_LOG 0 /* for debugging */ #else #define CREATE_LOG 0 /* for debugging */ #endif #define USE_SPECIAL_LOG_FILE 1 #include #include #include #include // waitpid #include // for MAXPATHLEN #include // for chmod #include #include #include #include #include #include #include // getpwname, getpwuid, getuid #include #include "mac_branding.h" static int callPosixSpawn(const char *cmd); static Boolean MakeLaunchManagerLaunchAgent(long brandID, passwd *pw); static void FixLaunchServicesDataBase(int brandId, bool isUninstall); static Boolean IsUserActive(const char *userName); static char * PersistentFGets(char *buf, size_t buflen, FILE *f); static int compareOSVersionTo(int toMajor, int toMinor); static void print_to_log_file(const char *format, ...); 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"); while (!IsUserActive(userName)) { sleep(1); } pw = getpwuid(getuid()); for (i=0; i= 0) { snprintf(cmd, sizeof(cmd), "launchctl unload \"/Users/%s/Library/LaunchAgents/edu.berkeley.launchboincmanager.plist\"", pw->pw_name); err = callPosixSpawn(cmd); if (err) { print_to_log_file("Command: %s\n", cmd); print_to_log_file("returned error %d\n", err); } sprintf(cmd, "rm -f \"/Users/%s/Library/LaunchAgents/edu.berkeley.launchboincmanager.plist\"", pw->pw_name); callPosixSpawn (cmd); if (err) { print_to_log_file("Command: %s\n", cmd); print_to_log_file("returned error %d\n", err); } } } else { if (compareOSVersionTo(13, 0) >= 0) { bool success = MakeLaunchManagerLaunchAgent(iBrandId, pw); if (!success) { print_to_log_file("Command: %s\n", cmd); print_to_log_file("MakeLaunchManagerLaunchAgent for %s failed\n", appName[iBrandId]); } } else { snprintf(cmd, sizeof(cmd), "osascript -e 'tell application \"System Events\" to make new login item at end with properties {path:\"%s\", hidden:true, name:\"%s\"}'", appPath[iBrandId], appName[iBrandId]); err = callPosixSpawn(cmd); if (err) { print_to_log_file("Command: %s\n", cmd); print_to_log_file("Make new login item for %s returned error %d\n", appName[iBrandId], err); } } if (compareOSVersionTo(13, 0) >= 0) { snprintf(cmd, sizeof(cmd), "launchctl unload \"/Users/%s/Library/LaunchAgents/edu.berkeley.launchboincmanager.plist\"", pw->pw_name); err = callPosixSpawn(cmd); if (err) { print_to_log_file("Command: %s\n", cmd); print_to_log_file("returned error %d\n", err); } snprintf(cmd, sizeof(cmd), "launchctl load \"/Users/%s/Library/LaunchAgents/edu.berkeley.launchboincmanager.plist\"", pw->pw_name); err = callPosixSpawn(cmd); if (err) { print_to_log_file("Command: %s\n", cmd); print_to_log_file("returned error %d\n", err); } } else { snprintf(cmd, sizeof(cmd), "open -jg \"%s\"", appPath[iBrandId]); err = callPosixSpawn(cmd); if (err) { print_to_log_file("Command: %s\n", cmd); print_to_log_file("\"open -jg \"%s\" returned error %d\n", appPath[iBrandId], err); } } } FixLaunchServicesDataBase(iBrandId, isUninstall); 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", brandName[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 -fR \"/Users/%s/Library/Application Support/BOINC/%s_Finish_Install.app\"", pw->pw_name, brandName[iBrandId]); } fclose(f); sprintf(cmd, "sh \"%s\"", scriptName); callPosixSpawn (cmd); return 0; } static Boolean MakeLaunchManagerLaunchAgent(long brandID, passwd *pw) { struct stat sbuf; char s[2048]; // Create a LaunchAgent for the specified user to autostart BOINC Manager on login, replacing // any LaunchAgent created previously (such as by installing a differently branded BOINC.) // Create LaunchAgents directory for this user if it does not yet exist snprintf(s, sizeof(s), "/Users/%s/Library/LaunchAgents", pw->pw_name); if (stat(s, &sbuf) != 0) { mkdir(s, 0755); chown(s, pw->pw_uid, pw->pw_gid); } snprintf(s, sizeof(s), "/Users/%s/Library/LaunchAgents/edu.berkeley.launchboincmanager.plist", pw->pw_name); FILE* f = fopen(s, "w"); if (!f) return false; fprintf(f, "\n"); fprintf(f, "\n"); fprintf(f, "\n"); fprintf(f, "\n"); fprintf(f, "\tLabel\n"); fprintf(f, "\tedu.berkeley.launchBOINCManager\n"); fprintf(f, "\tProgramArguments\n"); fprintf(f, "\t\n"); fprintf(f, "\t\t%s/Contents/MacOS/%s\n", appPath[brandID], appName[brandID]); fprintf(f, "\t\t--autostart\n"); fprintf(f, "\t\n"); if (compareOSVersionTo(13, 0) >= 0) { fprintf(f, "\tAssociatedBundleIdentifiers\n"); fprintf(f, "\tedu.berkeley.boinc\n"); } fprintf(f, "\tRunAtLoad\n"); fprintf(f, "\t\n"); fprintf(f, "\n"); fprintf(f, "\n"); fclose(f); chmod(s, 0644); chown(s, pw->pw_uid, pw->pw_gid); return true; } // 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 // other copies of BOINC Manager as the first entry. Each user has // their own copy of the Launch Services database, so this must be // done for each user. // // This probably will happen only on BOINC development systems where // Xcode has generated copies of BOINC Manager. static void FixLaunchServicesDataBase(int brandID, bool isUninstall) { char boincPath[MAXPATHLEN]; char cmd[MAXPATHLEN+250]; long i, n; CFArrayRef appRefs = NULL; OSStatus err; 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); for (i=0; i toMajor) return 1; // if (major == toMajor) compare minor version numbers if (minor < toMinor) return -1; if (minor > toMinor) return 1; return 0; } #define NOT_IN_TOKEN 0 #define IN_SINGLE_QUOTED_TOKEN 1 #define IN_DOUBLE_QUOTED_TOKEN 2 #define IN_UNQUOTED_TOKEN 3 static int parse_posix_spawn_command_line(char* p, char** argv) { int state = NOT_IN_TOKEN; int argc=0; while (*p) { switch(state) { case NOT_IN_TOKEN: if (isspace(*p)) { } else if (*p == '\'') { p++; argv[argc++] = p; state = IN_SINGLE_QUOTED_TOKEN; break; } else if (*p == '\"') { p++; argv[argc++] = p; state = IN_DOUBLE_QUOTED_TOKEN; break; } else { argv[argc++] = p; state = IN_UNQUOTED_TOKEN; } break; case IN_SINGLE_QUOTED_TOKEN: if (*p == '\'') { if (*(p-1) == '\\') break; *p = 0; state = NOT_IN_TOKEN; } break; case IN_DOUBLE_QUOTED_TOKEN: if (*p == '\"') { if (*(p-1) == '\\') break; *p = 0; state = NOT_IN_TOKEN; } break; case IN_UNQUOTED_TOKEN: if (isspace(*p)) { *p = 0; state = NOT_IN_TOKEN; } break; } p++; } argv[argc] = 0; return argc; } static int callPosixSpawn(const char *cmdline) { char command[1024]; char progName[1024]; char progPath[MAXPATHLEN]; char* argv[100]; int argc __attribute__((unused)) = 0; char *p; pid_t thePid = 0; int result = 0; int status = 0; extern char **environ; // Make a copy of cmdline because parse_posix_spawn_command_line modifies it strlcpy(command, cmdline, sizeof(command)); argc = parse_posix_spawn_command_line(const_cast(command), argv); strlcpy(progPath, argv[0], sizeof(progPath)); strlcpy(progName, argv[0], sizeof(progName)); p = strrchr(progName, '/'); if (p) { argv[0] = p+1; } else { argv[0] = progName; } #if VERBOSE_TEST print_to_log_file("***********"); for (int i=0; i