// This file is part of BOINC. // http://boinc.berkeley.edu // Copyright (C) 2009 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 . /* PostInstall.cpp */ // Notes on command-line installation to a remote Mac: // // When the installer is run from the Finder, this Postinstall.app will // display up to two dialogs, asking the user whether or not to: // [1] allow non-administrative users to run the BOINC Manager // (asked only if this Mac has any non-administrative users) // [2] set BOINC as the screensaver for all users who can run BOINC // (asked only if BOINC screensaver is not already set for them) // // The installer can also be run from the command line. This is useful // for installation on remote Macs. However, there is no way to respond // to dialogs during a command-line install. // // The command-line installer sets the following environment variable: // COMMAND_LINE_INSTALL=1 // The postinstall script, postupgrade script, and this Postinstall.app // detect this environment variable and do the following: // * Redirect the Postinstall.app log output to a file // /tmp/BOINCInstallLog.txt // * Suppress the 2 dialogs // * test for the existence of a file /tmp/nonadminusersok.txt; if the // file exists, allow non-administrative users to run BOINC Manager // * test for the existence of a file /tmp/setboincsaver.txt; if the // file exists, set BOINC as the screensaver for all BOINC users. // // Example: To install on a remote Mac from the command line, allowing // non-admin users to run the BOINC Manager and setting BOINC as the // screensaver: // * First SCP the "BOINC Installer.pkg" to the remote Mac's /tmp // directory, then SSh into the remote mac and enter the following // $ touch /tmp/nonadminusersok.txt // $ touch /tmp/setboincsaver.txt // $ sudo installer -pkg "/tmp/BOINC Installer.pkg" -tgt / // $ sudo reboot // #define CREATE_LOG 1 /* for debugging */ #include #include #include // getlogin #include // getpwname, getpwuid, getuid #include // getpwname, getpwuid, getuid #include // getgrnam #include // waitpid #include #include // for MAXPATHLEN #include // for chmod #include #include #include #include // for time() #include #include #include "url.h" using std::vector; using std::string; #include "LoginItemAPI.h" #include "SetupSecurity.h" #include "translate.h" #define admin_group_name "admin" #define boinc_master_user_name "boinc_master" #define boinc_master_group_name "boinc_master" #define boinc_project_user_name "boinc_project" #define boinc_project_group_name "boinc_project" void Initialize(void); /* function prototypes */ Boolean myFilterProc(DialogRef theDialog, EventRecord *theEvent, DialogItemIndex *itemHit); int DeleteReceipt(void); Boolean IsRestartNeeded(); void CheckUserAndGroupConflicts(); Boolean SetLoginItemOSAScript(long brandID, Boolean deleteLogInItem, char *userName); Boolean SetLoginItemAPI(long brandID, Boolean deleteLogInItem); OSErr GetCurrentScreenSaverSelection(char *moduleName, size_t maxLen); OSErr SetScreenSaverSelection(char *moduleName, char *modulePath, int type); void SetSkinInUserPrefs(char *userName, char *nameOfSkin); Boolean CheckDeleteFile(char *name); void SetEUIDBackToUser (void); static char * PersistentFGets(char *buf, size_t buflen, FILE *f); static void LoadPreferredLanguages(); static Boolean ShowMessage(Boolean allowCancel, const char *format, ...); Boolean IsUserMemberOfGroup(const char *userName, const char *groupName); int CountGroupMembershipEntries(const char *userName, const char *groupName); OSErr UpdateAllVisibleUsers(long brandID); long GetBrandID(void); int TestRPCBind(void); static int compareOSVersionTo(int toMajor, int toMinor); static OSStatus ResynchSystem(void); OSErr FindProcess (OSType typeToFind, OSType creatorToFind, ProcessSerialNumberPtr processSN); pid_t FindProcessPID(char* name, pid_t thePID); static void SleepTicks(UInt32 ticksToSleep); static OSErr QuitOneProcess(OSType signature); static OSErr QuitAppleEventHandler(const AppleEvent *appleEvt, AppleEvent* reply, UInt32 refcon); void print_to_log_file(const char *format, ...); void strip_cr(char *buf); extern int check_security( char *bundlePath, char *dataPath, int use_sandbox, int isManager, char* path_to_error, int len ); /* BEGIN TEMPORARY ITEMS TO ALLOW TRANSLATORS TO START WORK */ void notused() { ShowMessage(true, (char *)_("Yes")); ShowMessage(true, (char *)_("No")); // Future feature ShowMessage(true, (char *)_("Should BOINC run even when no user is logged in?")); } /* END TEMPORARY ITEMS TO ALLOW TRANSLATORS TO START WORK */ #define NUMBRANDS 4 #define MAX_LANGUAGES_TO_TRY 5 static char * Catalog_Name = (char *)"BOINC-Setup"; static char * Catalogs_Dir = (char *)"/Library/Application Support/BOINC Data/locale/"; /* globals */ static Boolean gCommandLineInstall = false; static Boolean gQuitFlag = false; static Boolean currentUserCanRunBOINC = false; static char loginName[256]; static time_t waitPermissionsStartTime; static char *saverName[NUMBRANDS]; static char *saverNameEscaped[NUMBRANDS]; static char *brandName[NUMBRANDS]; static char *appName[NUMBRANDS]; static char *appPath[NUMBRANDS]; static char *appPathEscaped[NUMBRANDS]; static char *receiptNameEscaped[NUMBRANDS]; static char *skinName[NUMBRANDS]; enum { launchWhenDone, logoutRequired, restartRequired, nothingrequired }; /****************************************************************** *** *** *** NOTE: *** *** *** *** On entry, the postinstall or postupgrade script has set the *** *** current directory to the top level of our installer package *** *** *** ******************************************************************/ int main(int argc, char *argv[]) { Boolean Success; ProcessSerialNumber ourProcess, installerPSN; short itemHit; long brandID = 0; int i; pid_t installerPID = 0, coreClientPID = 0; FSRef fileRef; OSStatus err, err_fsref; FILE *f; char s[256]; #ifndef SANDBOX group *grp; #endif // SANDBOX appName[0] = "BOINCManager"; appPath[0] = "/Applications/BOINCManager.app"; appPathEscaped[0] = "/Applications/BOINCManager.app"; brandName[0] = "BOINC"; saverName[0] = "BOINCSaver"; saverNameEscaped[0] = "BOINCSaver"; receiptNameEscaped[0] = "/Library/Receipts/BOINC\\ Installer.pkg"; skinName[0] = "Default"; appName[1] = "GridRepublic Desktop"; appPath[1] = "/Applications/GridRepublic Desktop.app"; appPathEscaped[1] = "/Applications/GridRepublic\\ Desktop.app"; brandName[1] = "GridRepublic"; saverName[1] = "GridRepublic"; saverNameEscaped[1] = "GridRepublic"; receiptNameEscaped[1] = "/Library/Receipts/GridRepublic\\ Installer.pkg"; skinName[1] = "GridRepublic"; appName[2] = "Progress Thru Processors Desktop"; appPath[2] = "/Applications/Progress Thru Processors Desktop.app"; appPathEscaped[2] = "/Applications/Progress\\ Thru\\ Processors\\ Desktop.app"; brandName[2] = "Progress Thru Processors"; saverName[2] = "Progress Thru Processors"; saverNameEscaped[2] = "Progress\\ Thru\\ Processors"; receiptNameEscaped[2] = "/Library/Receipts/Progress\\ Thru\\ Processors\\ Installer.pkg"; skinName[2] = "ProgressThruProcessors"; appName[3] = "Charity Engine Desktop"; appPath[3] = "/Applications/Charity Engine Desktop.app"; appPathEscaped[3] = "/Applications/Charity\\ Engine\\ Desktop.app"; brandName[3] = "Charity Engine"; saverName[3] = "Charity Engine"; saverNameEscaped[3] = "Charity\\ Engine"; receiptNameEscaped[3] = "/Library/Receipts/Charity\\ Engine\\ Installer.pkg"; skinName[3] = "Charity Engine"; ::GetCurrentProcess (&ourProcess); puts("Starting PostInstall app\n"); fflush(stdout); // getlogin() gives unreliable results under OS 10.6.2, so use environment strncpy(loginName, getenv("USER"), sizeof(loginName)-1); printf("login name = %s\n", loginName); fflush(stdout); if (getenv("COMMAND_LINE_INSTALL") != NULL) { gCommandLineInstall = true; puts("command-line install\n"); fflush(stdout); } for (i=0; i= NUMBRANDS)) { // Safety check brandID = 0; } LoadPreferredLanguages(); if (compareOSVersionTo(10, 5) < 0) { ::SetFrontProcess(&ourProcess); // Remove everything we've installed // "\pSorry, this version of GridRepublic requires system 10.5 or higher." s[0] = sprintf(s+1, "Sorry, this version of %s requires system 10.5 or higher.", brandName[brandID]); StandardAlert (kAlertStopAlert, (StringPtr)s, NULL, NULL, &itemHit); // "rm -rf /Applications/GridRepublic\\ Desktop.app" sprintf(s, "rm -rf %s", appPathEscaped[brandID]); system (s); // "rm -rf /Library/Screen\\ Savers/GridRepublic.saver" sprintf(s, "rm -rf /Library/Screen\\ Savers/%s.saver", saverNameEscaped[brandID]); system (s); // "rm -rf /Library/Receipts/GridRepublic.pkg" sprintf(s, "rm -rf %s", receiptNameEscaped[brandID]); system (s); // We don't customize BOINC Data directory name for branding system ("rm -rf /Library/Application\\ Support/BOINC\\ Data"); err = kill(installerPID, SIGKILL); ExitToShell(); } sleep (2); // Install all_projects_list.xml file, but only if one doesn't // already exist, since a pre-existing one is probably newer. f = fopen("/Library/Application Support/BOINC Data/all_projects_list.xml", "r"); if (f) { fclose(f); // Already exists unlink("/Library/Application Support/BOINC Data/installer_projects_list.xml"); } else { unlink("/Library/Application Support/BOINC Data/all_projects_list.xml"); rename("/Library/Application Support/BOINC Data/installer_projects_list.xml", "/Library/Application Support/BOINC Data/all_projects_list.xml"); } Success = false; #ifdef SANDBOX CheckUserAndGroupConflicts(); for (i=0; i<5; ++i) { err = CreateBOINCUsersAndGroups(); if (err != noErr) { printf("CreateBOINCUsersAndGroups returned %ld (repetition=%d)", err, i); fflush(stdout); continue; } // err = SetBOINCAppOwnersGroupsAndPermissions("/Applications/GridRepublic Desktop.app"); err = SetBOINCAppOwnersGroupsAndPermissions(appPath[brandID]); if (err != noErr) { printf("SetBOINCAppOwnersGroupsAndPermissions returned %ld (repetition=%d)", err, i); fflush(stdout); continue; } err = SetBOINCDataOwnersGroupsAndPermissions(); if (err != noErr) { printf("SetBOINCDataOwnersGroupsAndPermissions returned %ld (repetition=%d)", err, i); fflush(stdout); continue; } err = check_security( appPath[brandID], "/Library/Application Support/BOINC Data", true, false, NULL, 0 ); if (err != noErr) { printf("check_security returned %ld (repetition=%d)", err, i); fflush(stdout); } else { break; } } #else // ! defined(SANDBOX) // The BOINC Manager and Core Client have the set-user-ID-on-execution // flag set, so their ownership is important and must match the // ownership of the BOINC Data directory. // Find an appropriate admin user to set as owner of installed files // First, try the user currently logged in grp = getgrnam(admin_group_name); i = 0; while ((p = grp->gr_mem[i]) != NULL) { // Step through all users in group admin if (strcmp(p, loginName) == 0) { Success = true; // Logged in user is a member of group admin break; } ++i; } // If currently logged in user is not admin, use first non-root admin user if (!Success) { i = 0; while ((p = grp->gr_mem[i]) != NULL) { // Step through all users in group admin if (strcmp(p, "root") != 0) break; ++i; } } // Set owner of branded BOINCManager and contents, including core client // "chown -Rf username /Applications/GridRepublic\\ Desktop.app" sprintf(s, "chown -Rf %s %s", p, appPathEscaped[brandID]); system (s); // Set owner of BOINC Screen Saver // "chown -Rf username /Library/Screen\\ Savers/GridRepublic.saver" sprintf(s, "chown -Rf %s /Library/Screen\\ Savers/%s.saver", p, saverNameEscaped[brandID]); system (s); // We don't customize BOINC Data directory name for branding // "chown -Rf username /Library/Application\\ Support/BOINC\\ Data" sprintf(s, "chown -Rf %s /Library/Application\\ Support/BOINC\\ Data", p); system (s); // "chmod -R a+s /Applications/GridRepublic\\ Desktop.app" sprintf(s, "chmod -R a+s %s", appPathEscaped[brandID]); system (s); #endif // ! defined(SANDBOX) // Remove any branded versions of BOINC other than ours (i.e., old versions) for (i=0; i< NUMBRANDS; i++) { if (i == brandID) continue; // "rm -rf /Applications/GridRepublic\\ Desktop.app" sprintf(s, "rm -rf %s", appPathEscaped[i]); system (s); // "rm -rf /Library/Screen\\ Savers/GridRepublic.saver" sprintf(s, "rm -rf /Library/Screen\\ Savers/%s.saver", saverNameEscaped[i]); system (s); } if (brandID == 0) { // Installing generic BOINC system ("rm -f /Library/Application\\ Support/BOINC\\ Data/Branding"); } // err_fsref = FSPathMakeRef((StringPtr)"/Applications/GridRepublic Desktop.app", &fileRef, NULL); err_fsref = FSPathMakeRef((StringPtr)appPath[brandID], &fileRef, NULL); if (err_fsref == noErr) err = LSRegisterFSRef(&fileRef, true); err = UpdateAllVisibleUsers(brandID); if (err != noErr) return err; #if 0 // WaitPermissions is not needed when using wrapper #ifdef SANDBOX pid_t waitPermissionsPID = 0; uid_t saved_euid, saved_uid, b_m_uid; passwd *pw; Boolean restartNeeded; DialogRef theWin; restartNeeded = IsRestartNeeded(); printf("IsRestartNeeded() returned %d\n", (int)restartNeeded); fflush(stdout); if (!restartNeeded) { // Wait for BOINC's RPC socket address to become available to user boinc_master, in // case we are upgrading from a version which did not run as user boinc_master. saved_uid = getuid(); saved_euid = geteuid(); pw = getpwnam(boinc_master_user_name); b_m_uid = pw->pw_uid; seteuid(b_m_uid); for (i=0; i<120; i++) { err = TestRPCBind(); if (err == noErr) break; sleep(1); } seteuid(saved_euid); FSRef theFSRef; err = FSPathMakeRef((StringPtr)"/Library/Application Support/BOINC Data/WaitPermissions.app", &theFSRef, NULL); if (err) { printf("FSPathMakeRef(WaitPermissions) returned error %ld\n", err); fflush(stdout); } // When we first create the boinc_master group and add the current user to the // new group, there is a delay before the new group membership is recognized. // If we launch the BOINC Manager too soon, it will fail with a -1037 permissions // error, so we wait until the current user can access the switcher application. // Apparently, in order to get the changed permissions / group membership, we must // launch a new process belonging to the user. It may also need to be in a new // process group or new session. Neither system() nor popen() works, even after // setting the uid and euid back to the logged in user, but LSOpenFSRef() does. // The WaitPermissions application loops until it can access the switcher // application. err = LSOpenFSRef(&theFSRef, NULL); if (err) { printf("LSOpenFSRef(WaitPermissions) returned error %ld\n", err); fflush(stdout); } waitPermissionsStartTime = time(NULL); for (i=0; i<15; i++) { // Show "Please wait..." alert after 15 seconds waitPermissionsPID = FindProcessPID("WaitPermissions", 0); if (waitPermissionsPID == 0) { return 0; } sleep(1); } if (gCommandLineInstall) { printf("Finishing install. Please wait ...\n"); printf("This may take a few more minutes.\n"); fflush(stdout); } else { CreateStandardAlert(kAlertNoteAlert, CFSTR("Finishing install. Please wait ..."), CFSTR("This may take a few more minutes."), NULL, &theWin); HideDialogItem(theWin, kStdOkItemIndex); RemoveDialogItems(theWin, kStdOkItemIndex, 1, false); RunStandardAlert(theWin, &myFilterProc, &itemHit); } } #endif // SANDBOX #endif // WaitPermissions is not needed when using wrapper return 0; } Boolean myFilterProc(DialogRef theDialog, EventRecord *theEvent, DialogItemIndex *itemHit) { static time_t lastCheckTime = 0; time_t now = time(NULL); pid_t waitPermissionsPID = 0; if (now != lastCheckTime) { waitPermissionsPID = FindProcessPID("WaitPermissions", 0); if (waitPermissionsPID == 0) { *itemHit = kStdOkItemIndex; return true; } lastCheckTime = now; // Limit delay to 3 minutes if ((now - waitPermissionsStartTime) > 180) { *itemHit = kStdOkItemIndex; return true; } } return false; } // After installation has completed, delete the installer receipt. // If we don't need to logout the user, also launch BOINC Manager. int DeleteReceipt() { ProcessSerialNumber installerPSN; long brandID = 0; int i; pid_t installerPID = 0; OSStatus err; Boolean restartNeeded = true; FSRef fileRef; char s[256]; struct stat sbuf; OSStatus err_fsref; Initialize(); restartNeeded = IsRestartNeeded(); // print_to_log_file("IsRestartNeeded() returned %d\n", (int)restartNeeded); brandID = GetBrandID(); // Remove installer package receipt so we can run installer again if needed to fix permissions // "rm -rf /Library/Receipts/GridRepublic.pkg" sprintf(s, "rm -rf %s", receiptNameEscaped[brandID]); system (s); // err_fsref = FSPathMakeRef((StringPtr)"/Applications/GridRepublic Desktop.app", &fileRef, NULL); err_fsref = FSPathMakeRef((StringPtr)appPath[brandID], &fileRef, NULL); if (!restartNeeded) { err = FindProcess ('APPL', 'xins', &installerPSN); if (err == noErr) { err = GetProcessPID(&installerPSN , &installerPID); // Launch BOINC Manager when user closes installer or after 15 seconds for (i=0; i<15; i++) { // Wait 15 seconds max for installer to quit sleep (1); if (err == noErr) if (FindProcessPID(NULL, installerPID) == 0) break; } } // If system is set up to run BOINC Client as a daemon using launchd, launch it // as a daemon and allow time for client to start before launching BOINC Manager. err = stat("/Library/LaunchDaemons/edu.berkeley.boinc.plist", &sbuf); if (err == noErr) { system("launchctl unload /Library/LaunchDaemons/edu.berkeley.boinc.plist"); i = system("launchctl load /Library/LaunchDaemons/edu.berkeley.boinc.plist"); if (i == 0) sleep (2); } err = LSOpenFSRef(&fileRef, NULL); } return 0; } // BOINC Installer.app wrote a file to tell us whether a restart is required Boolean IsRestartNeeded() { FILE *restartNeededFile; int value; restartNeededFile = fopen("/tmp/BOINC_restart_flag", "r"); if (restartNeededFile) { fscanf(restartNeededFile,"%d", &value); fclose(restartNeededFile); return (value != 0); } return true; } // Some newer versions of the OS define users and groups which may conflict with // our previously created boinc_master or boinc_project user or group. This could // also happen when the user installs new software. So we must check for such // duplicate UserIDs and groupIDs; if found, we delete our user or group so that // the PostInstall application will create a new one that does not conflict. // // Older versions of the installer created our users and groups at the first // unused IDs at or above 25. Apple now recommends using IDs at or above 501, // to reduce the likelihood of conflicts with future UserIDs and groupIDs. // If we have previously created UserIDs and / or groupIDs below 501, this code // now removes them so we can create new ones above 500. void CheckUserAndGroupConflicts() { #ifdef SANDBOX passwd *pw = NULL; group *grp = NULL; gid_t boinc_master_gid = 0, boinc_project_gid = 0; uid_t boinc_master_uid = 0, boinc_project_uid = 0; FILE *f; char cmd[256], buf[256]; int entryCount; OSErr err = noErr; if (compareOSVersionTo(10, 5) < 0) { // This fails under OS 10.4, but should not be needed under OS 10.4 return; } printf("Checking user and group conflicts\n"); fflush(stdout); entryCount = 0; grp = getgrnam(boinc_master_group_name); if (grp) { boinc_master_gid = grp->gr_gid; printf("boinc_master group ID = %d\n", (int)boinc_master_gid); fflush(stdout); if (boinc_master_gid > 500) { sprintf(cmd, "dscl . -search /Groups PrimaryGroupID %d", boinc_master_gid); f = popen(cmd, "r"); if (f) { while (PersistentFGets(buf, sizeof(buf), f)) { if (strstr(buf, "PrimaryGroupID")) { ++entryCount; } } pclose(f); } } } if ((boinc_master_gid < 501) || (entryCount > 1)) { err = system ("dscl . -delete /groups/boinc_master"); // User boinc_master must have group boinc_master as its primary group. // Since this group no longer exists, delete the user as well. if (err) { fprintf(stdout, "dscl . -delete /groups/boinc_master returned %d\n", err); fflush(stdout); } err = system ("dscl . -delete /users/boinc_master"); if (err) { fprintf(stdout, "dscl . -delete /users/boinc_master returned %d\n", err); fflush(stdout); } ResynchSystem(); } entryCount = 0; grp = getgrnam(boinc_project_group_name); if (grp) { boinc_project_gid = grp->gr_gid; printf("boinc_project group ID = %d\n", (int)boinc_project_gid); fflush(stdout); if (boinc_project_gid > 500) { sprintf(cmd, "dscl . -search /Groups PrimaryGroupID %d", boinc_project_gid); f = popen(cmd, "r"); if (f) { while (PersistentFGets(buf, sizeof(buf), f)) { if (strstr(buf, "PrimaryGroupID")) { ++entryCount; } } pclose(f); } } } if ((boinc_project_gid < 501) || (entryCount > 1)) { err = system ("dscl . -delete /groups/boinc_project"); if (err) { fprintf(stdout, "dscl . -delete /groups/boinc_project returned %d\n", err); fflush(stdout); } // User boinc_project must have group boinc_project as its primary group. // Since this group no longer exists, delete the user as well. err = system ("dscl . -delete /users/boinc_project"); if (err) { fprintf(stdout, "dscl . -delete /users/boinc_project returned %d\n", err); fflush(stdout); } ResynchSystem(); } if ((boinc_master_gid < 500) && (boinc_project_gid < 500)) { return; } entryCount = 0; pw = getpwnam(boinc_master_user_name); if (pw) { boinc_master_uid = pw->pw_uid; printf("boinc_master user ID = %d\n", (int)boinc_master_uid); fflush(stdout); sprintf(cmd, "dscl . -search /Users UniqueID %d", boinc_master_uid); f = popen(cmd, "r"); if (f) { while (PersistentFGets(buf, sizeof(buf), f)) { if (strstr(buf, "UniqueID")) { ++entryCount; } } pclose(f); } } if (entryCount > 1) { err = system ("dscl . -delete /users/boinc_master"); if (err) { fprintf(stdout, "dscl . -delete /users/boinc_master returned %d\n", err); fflush(stdout); } ResynchSystem(); } entryCount = 0; pw = getpwnam(boinc_project_user_name); if (pw) { boinc_project_uid = pw->pw_uid; printf("boinc_project user ID = %d\n", (int)boinc_project_uid); fflush(stdout); sprintf(cmd, "dscl . -search /Users UniqueID %d", boinc_project_uid); f = popen(cmd, "r"); if (f) { while (PersistentFGets(buf, sizeof(buf), f)) { if (strstr(buf, "UniqueID")) { ++entryCount; } } pclose(f); } } if (entryCount > 1) { system ("dscl . -delete /users/boinc_project"); if (err) { fprintf(stdout, "dscl . -delete /users/boinc_project returned %d\n", err); fflush(stdout); } ResynchSystem(); } #endif // SANDBOX } enum { kSystemEventsCreator = 'sevs' }; Boolean SetLoginItemOSAScript(long brandID, Boolean deleteLogInItem, char *userName) { int i; char cmd[2048]; char systemEventsPath[1024]; ProcessSerialNumber SystemEventsPSN; FSRef appRef; OSErr err, err2; fprintf(stdout, "Adjusting login items for user %s\n", userName); fflush(stdout); // We must launch the System Events application for the target user err = FindProcess ('APPL', kSystemEventsCreator, &SystemEventsPSN); if (err == noErr) { // Find SystemEvents process. If found, quit it in case // it is running under a different user. fprintf(stdout, "Telling System Events to quit (at start of SetLoginItemOSAScript)\n"); fflush(stdout); err = QuitOneProcess(kSystemEventsCreator); if (err != noErr) { fprintf(stdout, "QuitOneProcess(kSystemEventsCreator) returned error %d \n", (int) err); fflush(stdout); } // Wait for the process to be gone for (i=0; i<50; ++i) { // 5 seconds max delay SleepTicks(6); // 6 Ticks == 1/10 second err = FindProcess ('APPL', kSystemEventsCreator, &SystemEventsPSN); if (err != noErr) break; } if (i >= 50) { fprintf(stdout, "Failed to make System Events quit\n"); fflush(stdout); err = noErr; goto cleanupSystemEvents; } sleep(2); } err = LSFindApplicationForInfo(kSystemEventsCreator, NULL, NULL, &appRef, NULL); if (err != noErr) { fprintf(stdout, "LSFindApplicationForInfo(kSystemEventsCreator) returned error %d \n", (int) err); fflush(stdout); } else { FSRefMakePath(&appRef, (UInt8*)systemEventsPath, sizeof(systemEventsPath)); fprintf(stdout, "SystemEvents is at %s\n", systemEventsPath); fprintf(stdout, "Launching SystemEvents for user %s\n", userName); fflush(stdout); sprintf(cmd, "sudo -iu \"%s\" \\\"%s/Contents/MacOS/System Events\\\" &", userName, systemEventsPath); err = system(cmd); if (err) { fprintf(stdout, "[2] Command: %s returned error %d\n", cmd, (int) err); } } // Wait for the process to start for (i=0; i<50; ++i) { // 5 seconds max delay SleepTicks(6); // 6 Ticks == 1/10 second err = FindProcess ('APPL', kSystemEventsCreator, &SystemEventsPSN); if (err == noErr) break; } if (i >= 50) { fprintf(stdout, "Failed to launch System Events for user %s\n", userName); fflush(stdout); err = noErr; goto cleanupSystemEvents; } sleep(2); for (i=0; i