// This file is part of BOINC. // http://boinc.berkeley.edu // Copyright (C) 2008 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 . /* Installer.cpp */ #define CREATE_LOG 1 /* for debugging */ #include #include #include // getlogin #include // getpwname, getpwuid, getuid #include // getpwname, getpwuid, getuid #include // waitpid #include #include // for MAXPATHLEN #include #include "str_util.h" #include "str_replace.h" #include "translate.h" #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 IsUserMemberOfGroup(const char *userName, const char *groupName); Boolean IsRestartNeeded(); static void GetPreferredLanguages(char * pkgPath); static void LoadPreferredLanguages(); static void ShowMessage(const char *format, ...); static OSErr QuitAppleEventHandler(const AppleEvent *appleEvt, AppleEvent* reply, UInt32 refcon); void print_to_log_file(const char *format, ...); void strip_cr(char *buf); // We can't use translation because the translation catalogs // have not yet been installed when this application is run. #define MAX_LANGUAGES_TO_TRY 5 static char * Catalog_Name = (char *)"BOINC-Setup"; static char * Catalogs_Dir = (char *)"/tmp/BOINC_PAX/Library/Application Support/BOINC Data/locale/"; Boolean gQuitFlag = false; /* global */ #if 0 CFStringRef valueRestartRequired = CFSTR("RequiredRestart"); CFStringRef valueLogoutRequired = CFSTR("RequiredLogout"); CFStringRef valueNoRestart = CFSTR("NoRestart"); #endif int main(int argc, char *argv[]) { char pkgPath[MAXPATHLEN], pkgRestartPath[MAXPATHLEN]; char infoPlistPath[MAXPATHLEN]; char MetaPkgPath[MAXPATHLEN], MetaPkgRestartPath[MAXPATHLEN]; char brand[64], s[256]; char *p; ProcessSerialNumber ourPSN; FSRef ourFSRef; long response; OSStatus err = noErr; struct stat stat_buf; Boolean restartNeeded = true; FILE *restartNeededFile; Initialize(); // Get the full path to Installer package inside this application's bundle err = GetCurrentProcess (&ourPSN); if (err == noErr) err = GetProcessBundleLocation(&ourPSN, &ourFSRef); if (err == noErr) err = FSRefMakePath (&ourFSRef, (UInt8*)pkgPath, sizeof(pkgPath)); strlcpy(infoPlistPath, pkgPath, sizeof(infoPlistPath)); strlcat(pkgPath, "/Contents/Resources/", sizeof(pkgPath)); // To allow for branding, assume name of installer package inside bundle corresponds to name of this application p = strrchr(infoPlistPath, '/'); // Point to name of this application (e.g., "BOINC Installer.app") if (p == NULL) p = infoPlistPath - 1; strlcpy(brand, p+1, sizeof(brand)); strlcat(pkgPath, p+1, sizeof(pkgPath)); p = strrchr(pkgPath, ' '); // Strip off last space character and everything following if (p) *p = '\0'; p = strstr(brand, " Installer.app"); // Strip off trailing " Installer.app" if (p) *p = '\0'; strlcpy(MetaPkgPath, pkgPath, sizeof(MetaPkgPath)); strlcat(MetaPkgPath, "+VirtualBox.mpkg", sizeof(MetaPkgPath)); strlcpy(MetaPkgRestartPath, pkgPath, sizeof(MetaPkgRestartPath)); strlcat(MetaPkgRestartPath, " + VirtualBox.mpkg", sizeof(MetaPkgRestartPath)); strlcpy(pkgRestartPath, pkgPath, sizeof(MetaPkgPath)); strlcat(pkgRestartPath, ".mpkg", sizeof(pkgPath)); strlcat(pkgPath, ".pkg", sizeof(pkgPath)); err = Gestalt(gestaltSystemVersion, &response); if (err != noErr) return err; GetPreferredLanguages(pkgPath); if (response < 0x1040) { LoadPreferredLanguages(); ::SetFrontProcess(&ourPSN); p = strrchr(brand, ' '); // Strip off last space character and everything following if (p) *p = '\0'; ShowMessage((char *)_("Sorry, this version of %s requires system 10.4 or higher."), brand); } else { // Remove previous installer package receipt so we can run installer again // (affects only older versions of OS X and fixes a bug in those versions) // "rm -rf /Library/Receipts/GridRepublic.pkg" sprintf(s, "rm -rf \"/Library/Receipts/%s.pkg\"", brand); system (s); restartNeeded = IsRestartNeeded(); // Write a temp file to tell our PostInstall.app whether restart is needed restartNeededFile = fopen("/tmp/BOINC_restart_flag", "w"); if (restartNeededFile) { fputs(restartNeeded ? "1\n" : "0\n", restartNeededFile); fclose(restartNeededFile); } err = Gestalt(gestaltSystemVersion, &response); if (err != noErr) return err; if (err == noErr) { if ((response < 0x1050) || stat(MetaPkgPath, &stat_buf)) { // stat() returns zero on success sprintf(infoPlistPath, "open \"%s\" &", restartNeeded ? pkgRestartPath : pkgPath); } else { sprintf(infoPlistPath, "open \"%s\" &", restartNeeded ? MetaPkgRestartPath : MetaPkgPath); } system(infoPlistPath); } } system("rm -dfR /tmp/BOINC_PAX"); return err; } Boolean IsUserMemberOfGroup(const char *userName, const char *groupName) { group *grp; short i = 0; char *p; grp = getgrnam(groupName); if (!grp) { printf("getgrnam(%s) failed\n", groupName); fflush(stdout); return false; // Group not found } while ((p = grp->gr_mem[i]) != NULL) { // Step through all users in group admin if (strcmp(p, userName) == 0) { return true; } ++i; } return false; } Boolean IsRestartNeeded() { 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; OSStatus err = noErr; long response; char loginName[256]; grp = getgrnam(boinc_master_group_name); if (grp == NULL) return true; // Group boinc_master does not exist boinc_master_gid = grp->gr_gid; grp = getgrnam(boinc_project_group_name); if (grp == NULL) return true; // Group boinc_project does not exist boinc_project_gid = grp->gr_gid; pw = getpwnam(boinc_master_user_name); if (pw == NULL) return true; // User boinc_master does not exist boinc_master_uid = pw->pw_uid; if (pw->pw_gid != boinc_master_gid) return true; // User boinc_master does not have group boinc_master as its primary group pw = getpwnam(boinc_project_user_name); if (pw == NULL) return true; // User boinc_project does not exist boinc_project_uid = pw->pw_uid; if (pw->pw_gid != boinc_project_gid) return true; // User boinc_project does not have group boinc_project as its primary group err = Gestalt(gestaltSystemVersion, &response); if ((err == noErr) && (response >= 0x1050)) { if (boinc_master_gid < 501) return true; // We will change boinc_master_gid to a value > 501 if (boinc_project_gid < 501) return true; // We will change boinc_project_gid to a value > 501 if (boinc_master_uid < 501) return true; // We will change boinc_master_uid to a value > 501 if (boinc_project_uid < 501) return true; // We will change boinc_project_uid to a value > 501 } #ifdef SANDBOX strncpy(loginName, getenv("USER"), sizeof(loginName)-1); if (loginName[0]) { if (IsUserMemberOfGroup(loginName, boinc_master_group_name)) { return false; // Logged in user is already a member of group boinc_master } } #endif // SANDBOX return true; } void Initialize() /* Initialize some managers */ { OSErr err; // InitCursor(); err = AEInstallEventHandler( kCoreEventClass, kAEQuitApplication, NewAEEventHandlerUPP((AEEventHandlerProcPtr)QuitAppleEventHandler), 0, false ); if (err != noErr) ExitToShell(); } // Because language preferences are set on a per-user basis, we // must get the preferred languages while set to the current // user, before the Apple Installer switches us to root. // So we get the preferred languages here and write them to a // temporary file to be retrieved by our PostInstall app. static void GetPreferredLanguages(char * pkgPath) { DIR *dirp; struct dirent *dp; char searchPath[MAXPATHLEN]; char savedWD[MAXPATHLEN]; char cmd[MAXPATHLEN+64]; struct stat sbuf; CFMutableArrayRef supportedLanguages; CFStringRef aLanguage; char shortLanguage[32]; CFArrayRef preferredLanguages; int i, j, k; int retval; char * language; char *uscore; FILE *f; getcwd(savedWD, sizeof(savedWD)); system("rm -dfR /tmp/BOINC_PAX"); mkdir("/tmp/BOINC_PAX", 0777); chdir("/tmp/BOINC_PAX"); strlcpy(searchPath, pkgPath, sizeof(searchPath)); strlcat(searchPath, "/Contents/Archive.pax.gz", sizeof(searchPath)); snprintf(cmd, sizeof(cmd), "pax -r -z -f \"%s\"", searchPath); retval = system(cmd); chdir(savedWD); if (retval) { system("rm -dfR /tmp/BOINC_PAX"); return; } // Create an array of all our supported languages supportedLanguages = CFArrayCreateMutable(NULL, 100, NULL); aLanguage = CFStringCreateWithCString(NULL, "en", kCFStringEncodingMacRoman); CFArrayAppendValue(supportedLanguages, aLanguage); CFRelease(aLanguage); aLanguage = NULL; dirp = opendir(Catalogs_Dir); if (!dirp) goto cleanup; while (true) { dp = readdir(dirp); if (dp == NULL) break; // End of list if (dp->d_name[0] == '.') continue; // Ignore names beginning with '.' strlcpy(searchPath, Catalogs_Dir, sizeof(searchPath)); strlcat(searchPath, dp->d_name, sizeof(searchPath)); strlcat(searchPath, "/", sizeof(searchPath)); strlcat(searchPath, Catalog_Name, sizeof(searchPath)); strlcat(searchPath, ".mo", sizeof(searchPath)); if (stat(searchPath, &sbuf) != 0) continue; // printf("Adding %s to supportedLanguages array\n", dp->d_name); aLanguage = CFStringCreateWithCString(NULL, dp->d_name, kCFStringEncodingMacRoman); CFArrayAppendValue(supportedLanguages, aLanguage); CFRelease(aLanguage); aLanguage = NULL; // If it has a region code ("it_IT") also try without region code ("it") // TODO: Find a more general solution strlcpy(shortLanguage, dp->d_name, sizeof(shortLanguage)); uscore = strchr(shortLanguage, '_'); if (uscore) { *uscore = '\0'; aLanguage = CFStringCreateWithCString(NULL, shortLanguage, kCFStringEncodingMacRoman); CFArrayAppendValue(supportedLanguages, aLanguage); aLanguage = NULL; } } closedir(dirp); // Write a temp file to tell our PostInstall.app our preferred languages f = fopen("/tmp/BOINC_preferred_languages", "w"); for (i=0; i=0; --k) { if (CFStringCompare(aLanguage, (CFStringRef)CFArrayGetValueAtIndex(supportedLanguages, k), 0) == kCFCompareEqualTo) { CFArrayRemoveValueAtIndex(supportedLanguages, k); } } // Since the original strings are English, no // further translation is needed for language en. if (!strcmp(language, "en")) { fclose(f); CFRelease(preferredLanguages); preferredLanguages = NULL; goto cleanup; } } CFRelease(preferredLanguages); preferredLanguages = NULL; } fclose(f); cleanup: CFArrayRemoveAllValues(supportedLanguages); CFRelease(supportedLanguages); supportedLanguages = NULL; } static void LoadPreferredLanguages(){ FILE *f; int i; char *p; char language[32]; BOINCTranslationInit(); // GetPreferredLanguages() wrote a list of our preferred languages to a temp file f = fopen("/tmp/BOINC_preferred_languages", "r"); if (!f) return; for (i=0; i