diff options
author | William Todd Stinson <stinson@lindenlab.com> | 2012-12-17 13:31:47 -0800 |
---|---|---|
committer | William Todd Stinson <stinson@lindenlab.com> | 2012-12-17 13:31:47 -0800 |
commit | 2e015caf26fcba6ef44000588b5bad89118b63a9 (patch) | |
tree | fb09d0e5d110175ebe14abd5b1e2bf2e4710bdb2 /indra/mac_updater/mac_updater.cpp | |
parent | 6710b60b104a119b91ffc468424f8c8fe25ef2da (diff) | |
parent | 36222cfa8c6960372acf10938a16fefe2a210259 (diff) |
Pull and merge from https://bitbucket.org/lindenlab/viewer-development.
Diffstat (limited to 'indra/mac_updater/mac_updater.cpp')
-rw-r--r-- | indra/mac_updater/mac_updater.cpp | 1594 |
1 files changed, 505 insertions, 1089 deletions
diff --git a/indra/mac_updater/mac_updater.cpp b/indra/mac_updater/mac_updater.cpp index aa45c5d23f..fc9565d63d 100644 --- a/indra/mac_updater/mac_updater.cpp +++ b/indra/mac_updater/mac_updater.cpp @@ -27,6 +27,8 @@ #include "linden_common.h" #include <boost/format.hpp> +#include <boost/filesystem/operations.hpp> +#include <boost/filesystem.hpp> #include <libgen.h> #include <sys/types.h> @@ -34,7 +36,6 @@ #include <unistd.h> #include <curl/curl.h> -#include <pthread.h> #include "llerror.h" #include "lltimer.h" @@ -43,1177 +44,566 @@ #include "llstring.h" -#include <Carbon/Carbon.h> - #include "llerrorcontrol.h" - -enum -{ - kEventClassCustom = 'Cust', - kEventCustomProgress = 'Prog', - kEventParamCustomCurValue = 'Cur ', - kEventParamCustomMaxValue = 'Max ', - kEventParamCustomText = 'Text', - kEventCustomDone = 'Done', -}; - -WindowRef gWindow = NULL; -EventHandlerRef gEventHandler = NULL; -OSStatus gFailure = noErr; -Boolean gCancelled = false; - -const char *gUpdateURL; -const char *gProductName; -const char *gBundleID; -const char *gDmgFile; -const char *gMarkerPath; - -void *updatethreadproc(void*); +#include "mac_updater.h" +#include <sstream> pthread_t updatethread; -OSStatus setProgress(int cur, int max) -{ - OSStatus err; - ControlRef progressBar = NULL; - ControlID id; - - id.signature = 'prog'; - id.id = 0; - - err = GetControlByID(gWindow, &id, &progressBar); - if(err == noErr) - { - Boolean indeterminate; - - if(max == 0) - { - indeterminate = true; - err = SetControlData(progressBar, kControlEntireControl, kControlProgressBarIndeterminateTag, sizeof(Boolean), (Ptr)&indeterminate); - } - else - { - double percentage = (double)cur / (double)max; - SetControlMinimum(progressBar, 0); - SetControlMaximum(progressBar, 100); - SetControlValue(progressBar, (SInt16)(percentage * 100)); - - indeterminate = false; - err = SetControlData(progressBar, kControlEntireControl, kControlProgressBarIndeterminateTag, sizeof(Boolean), (Ptr)&indeterminate); - - Draw1Control(progressBar); - } - } - - return(err); -} - -OSStatus setProgressText(CFStringRef text) -{ - OSStatus err; - ControlRef progressText = NULL; - ControlID id; - - id.signature = 'what'; - id.id = 0; - - err = GetControlByID(gWindow, &id, &progressText); - if(err == noErr) - { - err = SetControlData(progressText, kControlEntireControl, kControlStaticTextCFStringTag, sizeof(CFStringRef), (Ptr)&text); - Draw1Control(progressText); - } - - return(err); -} - -OSStatus sendProgress(long cur, long max, CFStringRef text = NULL) -{ - OSStatus result; - EventRef evt; - - result = CreateEvent( - NULL, - kEventClassCustom, - kEventCustomProgress, - 0, - kEventAttributeNone, - &evt); - - // This event needs to be targeted at the window so it goes to the window's handler. - if(result == noErr) - { - EventTargetRef target = GetWindowEventTarget(gWindow); - result = SetEventParameter ( - evt, - kEventParamPostTarget, - typeEventTargetRef, - sizeof(target), - &target); - } - - if(result == noErr) - { - result = SetEventParameter ( - evt, - kEventParamCustomCurValue, - typeLongInteger, - sizeof(cur), - &cur); - } - - if(result == noErr) - { - result = SetEventParameter ( - evt, - kEventParamCustomMaxValue, - typeLongInteger, - sizeof(max), - &max); - } - - if(result == noErr) - { - if(text != NULL) - { - result = SetEventParameter ( - evt, - kEventParamCustomText, - typeCFStringRef, - sizeof(text), - &text); - } - } - - if(result == noErr) - { - // Send the event - PostEventToQueue( - GetMainEventQueue(), - evt, - kEventPriorityStandard); - - } - - return(result); -} - -OSStatus sendDone(void) -{ - OSStatus result; - EventRef evt; - - result = CreateEvent( - NULL, - kEventClassCustom, - kEventCustomDone, - 0, - kEventAttributeNone, - &evt); - - // This event needs to be targeted at the window so it goes to the window's handler. - if(result == noErr) - { - EventTargetRef target = GetWindowEventTarget(gWindow); - result = SetEventParameter ( - evt, - kEventParamPostTarget, - typeEventTargetRef, - sizeof(target), - &target); - } - - if(result == noErr) - { - // Send the event - PostEventToQueue( - GetMainEventQueue(), - evt, - kEventPriorityStandard); +LLMacUpdater* LLMacUpdater::sInstance = NULL; - } - - return(result); -} - -OSStatus dialogHandler(EventHandlerCallRef handler, EventRef event, void *userdata) +LLMacUpdater::LLMacUpdater(): + mUpdateURL (NULL), + mProductName (NULL), + mBundleID (NULL), + mDmgFile (NULL), + mMarkerPath (NULL) { - OSStatus result = eventNotHandledErr; - OSStatus err; - UInt32 evtClass = GetEventClass(event); - UInt32 evtKind = GetEventKind(event); - - if((evtClass == kEventClassCommand) && (evtKind == kEventCommandProcess)) - { - HICommand cmd; - err = GetEventParameter(event, kEventParamDirectObject, typeHICommand, NULL, sizeof(cmd), NULL, &cmd); - - if(err == noErr) - { - switch(cmd.commandID) - { - case kHICommandCancel: - gCancelled = true; -// QuitAppModalLoopForWindow(gWindow); - result = noErr; - break; - } - } - } - else if((evtClass == kEventClassCustom) && (evtKind == kEventCustomProgress)) - { - // Request to update the progress dialog - long cur = 0; - long max = 0; - CFStringRef text = NULL; - (void) GetEventParameter(event, kEventParamCustomCurValue, typeLongInteger, NULL, sizeof(cur), NULL, &cur); - (void) GetEventParameter(event, kEventParamCustomMaxValue, typeLongInteger, NULL, sizeof(max), NULL, &max); - (void) GetEventParameter(event, kEventParamCustomText, typeCFStringRef, NULL, sizeof(text), NULL, &text); - - err = setProgress(cur, max); - if(err == noErr) - { - if(text != NULL) - { - setProgressText(text); - } - } - - result = noErr; - } - else if((evtClass == kEventClassCustom) && (evtKind == kEventCustomDone)) - { - // We're done. Exit the modal loop. - QuitAppModalLoopForWindow(gWindow); - result = noErr; - } - - return(result); + sInstance = this; } -#if 0 -size_t curl_download_callback(void *data, size_t size, size_t nmemb, - void *user_data) +void LLMacUpdater::doUpdate() { - S32 bytes = size * nmemb; - char *cdata = (char *) data; - for (int i =0; i < bytes; i += 1) - { - gServerResponse.append(cdata[i]); - } - return bytes; -} -#endif - -int curl_progress_callback_func(void *clientp, - double dltotal, - double dlnow, - double ultotal, - double ulnow) -{ - int max = (int)(dltotal / 1024.0); - int cur = (int)(dlnow / 1024.0); - sendProgress(cur, max); - - if(gCancelled) - return(1); - - return(0); -} - -int parse_args(int argc, char **argv) -{ - int j; - - for (j = 1; j < argc; j++) - { - if ((!strcmp(argv[j], "-url")) && (++j < argc)) - { - gUpdateURL = argv[j]; - } - else if ((!strcmp(argv[j], "-name")) && (++j < argc)) - { - gProductName = argv[j]; - } - else if ((!strcmp(argv[j], "-bundleid")) && (++j < argc)) - { - gBundleID = argv[j]; - } - else if ((!strcmp(argv[j], "-dmg")) && (++j < argc)) - { - gDmgFile = argv[j]; - } - else if ((!strcmp(argv[j], "-marker")) && (++j < argc)) - { - gMarkerPath = argv[j];; - } - } - - return 0; -} - -int main(int argc, char **argv) -{ - // We assume that all the logs we're looking for reside on the current drive + // We assume that all the logs we're looking for reside on the current drive gDirUtilp->initAppDirs("SecondLife"); - + LLError::initForApplication( gDirUtilp->getExpandedFilename(LL_PATH_APP_SETTINGS, "")); - + // Rename current log file to ".old" std::string old_log_file = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, "updater.log.old"); std::string log_file = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, "updater.log"); LLFile::rename(log_file.c_str(), old_log_file.c_str()); - + // Set the log file to updater.log LLError::logToFile(log_file); - - ///////////////////////////////////////// - // - // Process command line arguments - // - gUpdateURL = NULL; - gProductName = NULL; - gBundleID = NULL; - gDmgFile = NULL; - gMarkerPath = NULL; - parse_args(argc, argv); - if ((gUpdateURL == NULL) && (gDmgFile == NULL)) + + if ((mUpdateURL == NULL) && (mDmgFile == NULL)) { llinfos << "Usage: mac_updater -url <url> | -dmg <dmg file> [-name <product_name>] [-program <program_name>]" << llendl; exit(1); } else { - llinfos << "Update url is: " << gUpdateURL << llendl; - if (gProductName) + llinfos << "Update url is: " << mUpdateURL << llendl; + if (mProductName) { - llinfos << "Product name is: " << gProductName << llendl; + llinfos << "Product name is: " << *mProductName << llendl; } else { - gProductName = "Second Life"; + mProductName = new std::string("Second Life"); } - if (gBundleID) + if (mBundleID) { - llinfos << "Bundle ID is: " << gBundleID << llendl; + llinfos << "Bundle ID is: " << *mBundleID << llendl; } else { - gBundleID = "com.secondlife.indra.viewer"; + mBundleID = new std::string("com.secondlife.indra.viewer"); } } - llinfos << "Starting " << gProductName << " Updater" << llendl; - - // Real UI... - OSStatus err; - IBNibRef nib = NULL; - - err = CreateNibReference(CFSTR("AutoUpdater"), &nib); - - char windowTitle[MAX_PATH]; /* Flawfinder: ignore */ - snprintf(windowTitle, sizeof(windowTitle), "%s Updater", gProductName); - CFStringRef windowTitleRef = NULL; - windowTitleRef = CFStringCreateWithCString(NULL, windowTitle, kCFStringEncodingUTF8); - - if(err == noErr) - { - err = CreateWindowFromNib(nib, CFSTR("Updater"), &gWindow); - } - - if (err == noErr) - { - err = SetWindowTitleWithCFString(gWindow, windowTitleRef); - } - CFRelease(windowTitleRef); - - if(err == noErr) - { - // Set up an event handler for the window. - EventTypeSpec handlerEvents[] = - { - { kEventClassCommand, kEventCommandProcess }, - { kEventClassCustom, kEventCustomProgress }, - { kEventClassCustom, kEventCustomDone } - }; - InstallStandardEventHandler(GetWindowEventTarget(gWindow)); - InstallWindowEventHandler( - gWindow, - NewEventHandlerUPP(dialogHandler), - GetEventTypeCount (handlerEvents), - handlerEvents, - 0, - &gEventHandler); - } - - if(err == noErr) - { - ShowWindow(gWindow); - SelectWindow(gWindow); - } - - if(err == noErr) - { - pthread_create(&updatethread, - NULL, - &updatethreadproc, - NULL); - - } - - if(err == noErr) - { - RunAppModalLoopForWindow(gWindow); - } - + llinfos << "Starting " << *mProductName << " Updater" << llendl; + + pthread_create(&updatethread, + NULL, + &sUpdatethreadproc, + NULL); + + void *threadresult; - + pthread_join(updatethread, &threadresult); - - if(!gCancelled && (gFailure != noErr)) + + if(gCancelled || gFailure) { - // Something went wrong. Since we always just tell the user to download a new version, we don't really care what. - AlertStdCFStringAlertParamRec params; - SInt16 retval_mac = 1; - DialogRef alert = NULL; - OSStatus err; - - params.version = kStdCFStringAlertVersionOne; - params.movable = false; - params.helpButton = false; - params.defaultText = (CFStringRef)kAlertDefaultOKText; - params.cancelText = 0; - params.otherText = 0; - params.defaultButton = 1; - params.cancelButton = 0; - params.position = kWindowDefaultPosition; - params.flags = 0; - - err = CreateStandardAlert( - kAlertStopAlert, - CFSTR("Error"), - CFSTR("An error occurred while updating Second Life. Please download the latest version from www.secondlife.com."), - ¶ms, - &alert); - - if(err == noErr) - { - err = RunStandardAlert( - alert, - NULL, - &retval_mac); - } - - if(gMarkerPath != 0) + sendStopAlert(); + + if(mMarkerPath != 0) { // Create a install fail marker that can be used by the viewer to // detect install problems. - std::ofstream stream(gMarkerPath); + std::ofstream stream(mMarkerPath->c_str()); if(stream) stream << -1; } exit(-1); } else { exit(0); } - - if(gWindow != NULL) - { - DisposeWindow(gWindow); - } - if(nib != NULL) - { - DisposeNibReference(nib); - } - - return 0; + return; } -bool isDirWritable(FSRef &dir) +//SPATTERS TODO this should be moved to lldir_mac.cpp +const std::string LLMacUpdater::walkParents( signed int depth, const std::string& childpath ) { - bool result = false; - - // Test for a writable directory by creating a directory, then deleting it again. - // This is kinda lame, but will pretty much always give the right answer. - - OSStatus err = noErr; - char temp[PATH_MAX] = ""; /* Flawfinder: ignore */ + boost::filesystem::path fullpath(childpath.c_str()); + + while (depth > 0 && fullpath.has_parent_path()) + { + fullpath = boost::filesystem::path(fullpath.parent_path()); + --depth; + } + + return fullpath.string(); +} - err = FSRefMakePath(&dir, (UInt8*)temp, sizeof(temp)); +//#if 0 +//size_t curl_download_callback(void *data, size_t size, size_t nmemb, +// void *user_data) +//{ +// S32 bytes = size * nmemb; +// char *cdata = (char *) data; +// for (int i =0; i < bytes; i += 1) +// { +// gServerResponse.append(cdata[i]); +// } +// return bytes; +//} +//#endif - if(err == noErr) - { - strncat(temp, "/.test_XXXXXX", (sizeof(temp) - strlen(temp)) - 1); - - if(mkdtemp(temp) != NULL) - { - // We were able to make the directory. This means the directory is writable. - result = true; - - // Clean up. - rmdir(temp); - } - } - -#if 0 - // This seemed like a good idea, but won't tell us if we're on a volume mounted read-only. - UInt8 perm; - err = FSGetUserPrivilegesPermissions(&targetParentRef, &perm, NULL); - if(err == noErr) - { - if(perm & kioACUserNoMakeChangesMask) - { - // Parent directory isn't writable. - llinfos << "Target parent directory not writable." << llendl; - err = -1; - replacingTarget = false; - } - } -#endif +int curl_progress_callback_func(void *clientp, + double dltotal, + double dlnow, + double ultotal, + double ulnow) +{ + int max = (int)(dltotal / 1024.0); + int cur = (int)(dlnow / 1024.0); + setProgress(cur, max); + + if(gCancelled) + return(1); - return result; + return(0); } -static std::string HFSUniStr255_to_utf8str(const HFSUniStr255* src) +bool LLMacUpdater::isApplication(const std::string& app_str) { - llutf16string string16((U16*)&(src->unicode), src->length); - std::string result = utf16str_to_utf8str(string16); - return result; + return !(bool) app_str.compare( app_str.length()-4, 4, ".app"); } - -int restoreObject(const char* aside, const char* target, const char* path, const char* object) + +// Search through the directory specified by 'parent' for an item that appears to be a Second Life viewer. +bool LLMacUpdater::findAppBundleOnDiskImage(const boost::filesystem::path& dir_path, + boost::filesystem::path& path_found) { - char source[PATH_MAX] = ""; /* Flawfinder: ignore */ - char dest[PATH_MAX] = ""; /* Flawfinder: ignore */ - snprintf(source, sizeof(source), "%s/%s/%s", aside, path, object); - snprintf(dest, sizeof(dest), "%s/%s", target, path); - FSRef sourceRef; - FSRef destRef; - OSStatus err; - err = FSPathMakeRef((UInt8 *)source, &sourceRef, NULL); - if(err != noErr) return false; - err = FSPathMakeRef((UInt8 *)dest, &destRef, NULL); - if(err != noErr) return false; - - llinfos << "Copying " << source << " to " << dest << llendl; - - err = FSCopyObjectSync( - &sourceRef, - &destRef, - NULL, - NULL, - kFSFileOperationOverwrite); - - if(err != noErr) return false; - return true; + if ( !boost::filesystem::exists( dir_path ) ) return false; + + boost::filesystem::directory_iterator end_itr; + + for ( boost::filesystem::directory_iterator itr( dir_path ); + itr != end_itr; + ++itr ) + { + if ( boost::filesystem::is_directory(itr->status()) ) + { + std::string dir_name = itr->path().string(); + if ( isApplication(dir_name) ) + { + if(isFSRefViewerBundle(dir_name)) + { + llinfos << dir_name << " is the one" << llendl; + + path_found = itr->path(); + return true; + } + } + } + } + return false; } -// Replace any mention of "Second Life" with the product name. -void filterFile(const char* filename) +bool LLMacUpdater::verifyDirectory(const boost::filesystem::path* directory, bool isParent) { - char temp[PATH_MAX] = ""; /* Flawfinder: ignore */ - // First copy the target's version, so we can run it through sed. - snprintf(temp, sizeof(temp), "cp '%s' '%s.tmp'", filename, filename); - system(temp); /* Flawfinder: ignore */ - - // Now run it through sed. - snprintf(temp, sizeof(temp), - "sed 's/Second Life/%s/g' '%s.tmp' > '%s'", gProductName, filename, filename); - system(temp); /* Flawfinder: ignore */ + bool replacingTarget; + std::string app_str = directory->string(); + + if (boost::filesystem::is_directory(*directory)) + { + // This is fine, just means we're not replacing anything. + replacingTarget = true; + } + else + { + replacingTarget = isParent; + } + + //Check that the directory is writeable. + if(!isDirWritable(app_str)) + { + // Parent directory isn't writable. + llinfos << "Target directory not writable." << llendl; + replacingTarget = false; + } + return replacingTarget; } - -static bool isFSRefViewerBundle(FSRef *targetRef) + +bool LLMacUpdater::getViewerDir(boost::filesystem::path &app_dir) { - bool result = false; - CFURLRef targetURL = NULL; - CFBundleRef targetBundle = NULL; - CFStringRef targetBundleID = NULL; - CFStringRef sourceBundleID = NULL; + std::string app_dir_str; + + //Walk up 6 levels from the App Updater's installation point. + app_dir_str = walkParents( 6, *mApplicationPath ); + + app_dir = boost::filesystem::path(app_dir_str); + + //Check to see that the directory's name ends in .app Lame but it's the best thing we have to go on. + //If it's not there, we're going to default to /Applications/VIEWERNAME + if (!isApplication(app_dir_str)) + { + llinfos << "Target search failed, defaulting to /Applications/" << *mProductName << ".app." << llendl; + std::string newpath = std::string("/Applications/") + mProductName->c_str(); + app_dir = boost::filesystem::path(newpath); + } + return verifyDirectory(&app_dir); +} - targetURL = CFURLCreateFromFSRef(NULL, targetRef); +bool LLMacUpdater::downloadDMG(const std::string& dmgName, boost::filesystem::path* temp_dir) +{ + LLFILE *downloadFile = NULL; + char temp[PATH_MAX] = ""; /* Flawfinder: ignore */ - if(targetURL == NULL) - { - llinfos << "Error creating target URL." << llendl; - } - else - { - targetBundle = CFBundleCreate(NULL, targetURL); - } - - if(targetBundle == NULL) - { - llinfos << "Failed to create target bundle." << llendl; - } - else - { - targetBundleID = CFBundleGetIdentifier(targetBundle); - } - - if(targetBundleID == NULL) - { - llinfos << "Couldn't retrieve target bundle ID." << llendl; - } - else + chdir(temp_dir->string().c_str()); + + snprintf(temp, sizeof(temp), "SecondLife.dmg"); + + downloadFile = LLFile::fopen(temp, "wb"); /* Flawfinder: ignore */ + if(downloadFile == NULL) + { + return false; + } + + bool success = false; + + CURL *curl = curl_easy_init(); + + curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1); + // curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &curl_download_callback); + curl_easy_setopt(curl, CURLOPT_FILE, downloadFile); + curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0); + curl_easy_setopt(curl, CURLOPT_PROGRESSFUNCTION, &curl_progress_callback_func); + curl_easy_setopt(curl, CURLOPT_URL, mUpdateURL); + curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1); + + sendProgress(0, 1, std::string("Downloading...")); + + CURLcode result = curl_easy_perform(curl); + + curl_easy_cleanup(curl); + + if(gCancelled) + { + llinfos << "User cancel, bailing out."<< llendl; + goto close_file; + } + + if(result != CURLE_OK) + { + llinfos << "Error " << result << " while downloading disk image."<< llendl; + goto close_file; + } + + fclose(downloadFile); + downloadFile = NULL; + + success = true; + +close_file: + // Close disk image file if necessary + if(downloadFile != NULL) { - sourceBundleID = CFStringCreateWithCString(NULL, gBundleID, kCFStringEncodingUTF8); - if(CFStringCompare(sourceBundleID, targetBundleID, 0) == kCFCompareEqualTo) - { - // This is the bundle we're looking for. - result = true; - } - else - { - llinfos << "Target bundle ID mismatch." << llendl; - } + llinfos << "Closing download file." << llendl; + + fclose(downloadFile); + downloadFile = NULL; } - - // Don't release targetBundleID -- since we don't retain it, it's released when targetBundle is released. - if(targetURL != NULL) - CFRelease(targetURL); - if(targetBundle != NULL) - CFRelease(targetBundle); - - return result; -} -// Search through the directory specified by 'parent' for an item that appears to be a Second Life viewer. -static OSErr findAppBundleOnDiskImage(FSRef *parent, FSRef *app) + return success; +} + +bool LLMacUpdater::doMount(const std::string& dmgName, char* deviceNode, const boost::filesystem::path& temp_dir) { - FSIterator iterator; - bool found = false; - - OSErr err = FSOpenIterator( parent, kFSIterateFlat, &iterator ); - if(!err) - { - do - { - ItemCount actualObjects = 0; - Boolean containerChanged = false; - FSCatalogInfo info; - FSRef ref; - HFSUniStr255 unicodeName; - err = FSGetCatalogInfoBulk( - iterator, - 1, - &actualObjects, - &containerChanged, - kFSCatInfoNodeFlags, - &info, - &ref, - NULL, - &unicodeName ); - - if(actualObjects == 0) - break; - - if(!err) - { - // Call succeeded and not done with the iteration. - std::string name = HFSUniStr255_to_utf8str(&unicodeName); + char temp[PATH_MAX] = ""; /* Flawfinder: ignore */ + + sendProgress(0, 0, std::string("Mounting image...")); + chdir(temp_dir.string().c_str()); + std::string mnt_dir = temp_dir.string() + std::string("/mnt"); + LLFile::mkdir(mnt_dir.c_str(), 0700); + + // NOTE: we could add -private at the end of this command line to keep the image from showing up in the Finder, + // but if our cleanup fails, this makes it much harder for the user to unmount the image. + std::string mountOutput; + boost::format cmdFormat("hdiutil attach %s -mountpoint mnt"); + cmdFormat % dmgName; + FILE* mounter = popen(cmdFormat.str().c_str(), "r"); /* Flawfinder: ignore */ + + if(mounter == NULL) + { + llinfos << "Failed to mount disk image, exiting."<< llendl; + return false; + } + + // We need to scan the output from hdiutil to find the device node it uses to attach the disk image. + // If we don't have this information, we can't detach it later. + while(mounter != NULL) + { + size_t len = fread(temp, 1, sizeof(temp)-1, mounter); + temp[len] = 0; + mountOutput.append(temp); + if(len < sizeof(temp)-1) + { + // End of file or error. + int result = pclose(mounter); + if(result != 0) + { + // NOTE: We used to abort here, but pclose() started returning + // -1, possibly when the size of the DMG passed a certain point + llinfos << "Unexpected result closing pipe: " << result << llendl; + } + mounter = NULL; + } + } + + if(!mountOutput.empty()) + { + const char *s = mountOutput.c_str(); + const char *prefix = "/dev/"; + char *sub = strstr(s, prefix); + + if(sub != NULL) + { + sub += strlen(prefix); /* Flawfinder: ignore */ + sscanf(sub, "%1023s", deviceNode); /* Flawfinder: ignore */ + } + } + + if(deviceNode[0] != 0) + { + llinfos << "Disk image attached on /dev/" << deviceNode << llendl; + } + else + { + llinfos << "Disk image device node not found!" << llendl; + return false; + } + + return true; +} - llinfos << "Considering \"" << name << "\"" << llendl; +bool LLMacUpdater::moveApplication (const boost::filesystem::path& app_dir, + const boost::filesystem::path& temp_dir, + boost::filesystem::path& aside_dir) +{ + try + { + //Grab filename from installdir append to tempdir move set aside_dir to moved path. + std::string install_str = app_dir.parent_path().string(); + std::string temp_str = temp_dir.string(); + std::string app_str = app_dir.filename().string(); + aside_dir = boost::filesystem::path( boost::filesystem::operator/(temp_dir,app_str) ); + std::cout << "Attempting to move " << app_dir.string() << " to " << aside_dir.string() << std::endl; + + boost::filesystem::rename(app_dir, aside_dir); + } + catch(boost::filesystem::filesystem_error e) + { + llinfos << "Application move failed." << llendl; + return false; + } + return true; +} - if(info.nodeFlags & kFSNodeIsDirectoryMask) - { - // This is a directory. See if it's a .app - if(name.find(".app") != std::string::npos) - { - // Looks promising. Check to see if it has the right bundle identifier. - if(isFSRefViewerBundle(&ref)) - { - llinfos << name << " is the one" << llendl; - // This is the one. Return it. - *app = ref; - found = true; - break; - } else { - llinfos << name << " is not the bundle we are looking for; move along" << llendl; - } +bool LLMacUpdater::doInstall(const boost::filesystem::path& app_dir, + const boost::filesystem::path& temp_dir, + boost::filesystem::path& mount_dir, + bool replacingTarget) +{ + std::string temp_name = temp_dir.string() + std::string("/mnt"); + + llinfos << "Disk image mount point is: " << temp_name << llendl; + + mount_dir = boost::filesystem::path(temp_name.c_str()); + + if (! boost::filesystem::exists ( mount_dir ) ) + { + llinfos << "Couldn't make FSRef to disk image mount point." << llendl; + return false; + } + + sendProgress(0, 0, std::string("Searching for the app bundle...")); + + boost::filesystem::path source_dir; + + if ( !findAppBundleOnDiskImage(mount_dir, source_dir) ) + { + llinfos << "Couldn't find application bundle on mounted disk image." << llendl; + return false; + } + else + { + llinfos << "found the bundle." << llendl; + } + + sendProgress(0, 0, std::string("Preparing to copy files...")); + + // this will hold the name of the destination target + boost::filesystem::path aside_dir; + + if(replacingTarget) + { + + if (! moveApplication (app_dir, temp_dir, aside_dir) ) + { + llwarns << "failed to move aside old version." << llendl; + return false; + } + } + + sendProgress(0, 0, std::string("Copying files...")); + + llinfos << "Starting copy..." << llendl; + // If we were replacingTarget, we've moved the app to a temp directory. + // Otherwise the destination should be empty. + // We have mounted the DMG as a volume so we should be able to just + // move the app from the volume to the destination and everything will just work. + + + // Copy the new version from the disk image to the target location. + + //The installer volume is mounted read-only so we can't move. Instead copy and then unmount. + if (! copyDir(source_dir.string(), app_dir.string()) ) + { + llwarns << "Failed to copy " << source_dir.string() << " to " << app_dir.string() << llendl; + + // Something went wrong during the copy. Attempt to put the old version back and bail. + boost::filesystem::rename(app_dir, aside_dir); + return false; + + } + + // The update has succeeded. Clear the cache directory. + + sendProgress(0, 0, std::string("Clearing cache...")); + + llinfos << "Clearing cache..." << llendl; + + gDirUtilp->deleteFilesInDir(gDirUtilp->getExpandedFilename(LL_PATH_CACHE,""), "*.*"); + + llinfos << "Clear complete." << llendl; + + return true; +} - } - } - } - } - while(!err); - - llinfos << "closing the iterator" << llendl; - - FSCloseIterator(iterator); - - llinfos << "closed" << llendl; - } - - if(!err && !found) - err = fnfErr; - - return err; +bool mkTempDir(boost::filesystem::path& temp_dir) +{ + char temp_str[PATH_MAX] = "/tmp/SecondLifeUpdate_XXXXXX"; + + if(mkdtemp(temp_str) == NULL) + { + return false; + } + + temp_dir = boost::filesystem::path(temp_str); + + return true; } -void *updatethreadproc(void*) +void* LLMacUpdater::updatethreadproc(void*) { char tempDir[PATH_MAX] = ""; /* Flawfinder: ignore */ - FSRef tempDirRef; char temp[PATH_MAX] = ""; /* Flawfinder: ignore */ // *NOTE: This buffer length is used in a scanf() below. char deviceNode[1024] = ""; /* Flawfinder: ignore */ - LLFILE *downloadFile = NULL; - OSStatus err; - ProcessSerialNumber psn; - char target[PATH_MAX] = ""; /* Flawfinder: ignore */ - FSRef targetRef; - FSRef targetParentRef; - FSVolumeRefNum targetVol; - FSRef trashFolderRef; - Boolean replacingTarget = false; - - memset(&tempDirRef, 0, sizeof(tempDirRef)); - memset(&targetRef, 0, sizeof(targetRef)); - memset(&targetParentRef, 0, sizeof(targetParentRef)); - - try - { - // Attempt to get a reference to the Second Life application bundle containing this updater. - // Any failures during this process will cause us to default to updating /Applications/Second Life.app - { - FSRef myBundle; - - err = GetCurrentProcess(&psn); - if(err == noErr) - { - err = GetProcessBundleLocation(&psn, &myBundle); - } - - if(err == noErr) - { - // Sanity check: Make sure the name of the item referenced by targetRef is "Second Life.app". - FSRefMakePath(&myBundle, (UInt8*)target, sizeof(target)); - - llinfos << "Updater bundle location: " << target << llendl; - } - - // Our bundle should be in Second Life.app/Contents/Resources/AutoUpdater.app - // so we need to go up 3 levels to get the path to the main application bundle. - if(err == noErr) - { - err = FSGetCatalogInfo(&myBundle, kFSCatInfoNone, NULL, NULL, NULL, &targetRef); - } - if(err == noErr) - { - err = FSGetCatalogInfo(&targetRef, kFSCatInfoNone, NULL, NULL, NULL, &targetRef); - } - if(err == noErr) - { - err = FSGetCatalogInfo(&targetRef, kFSCatInfoNone, NULL, NULL, NULL, &targetRef); - } - - // And once more to get the parent of the target - if(err == noErr) - { - err = FSGetCatalogInfo(&targetRef, kFSCatInfoNone, NULL, NULL, NULL, &targetParentRef); - } - - if(err == noErr) - { - FSRefMakePath(&targetRef, (UInt8*)target, sizeof(target)); - llinfos << "Path to target: " << target << llendl; - } - - // Sanity check: make sure the target is a bundle with the right identifier - if(err == noErr) - { - // Assume the worst... - err = -1; - - if(isFSRefViewerBundle(&targetRef)) - { - // This is the bundle we're looking for. - err = noErr; - replacingTarget = true; - } - } - - // Make sure the target's parent directory is writable. - if(err == noErr) - { - if(!isDirWritable(targetParentRef)) - { - // Parent directory isn't writable. - llinfos << "Target parent directory not writable." << llendl; - err = -1; - replacingTarget = false; - } - } - - if(err != noErr) - { - Boolean isDirectory; - llinfos << "Target search failed, defaulting to /Applications/" << gProductName << ".app." << llendl; - - // Set up the parent directory - err = FSPathMakeRef((UInt8*)"/Applications", &targetParentRef, &isDirectory); - if((err != noErr) || (!isDirectory)) - { - // We're so hosed. - llinfos << "Applications directory not found, giving up." << llendl; - throw 0; - } - - snprintf(target, sizeof(target), "/Applications/%s.app", gProductName); - - memset(&targetRef, 0, sizeof(targetRef)); - err = FSPathMakeRef((UInt8*)target, &targetRef, NULL); - if(err == fnfErr) - { - // This is fine, just means we're not replacing anything. - err = noErr; - replacingTarget = false; - } - else - { - replacingTarget = true; - } - - // Make sure the target's parent directory is writable. - if(err == noErr) - { - if(!isDirWritable(targetParentRef)) - { - // Parent directory isn't writable. - llinfos << "Target parent directory not writable." << llendl; - err = -1; - replacingTarget = false; - } - } - - } - - // If we haven't fixed all problems by this point, just bail. - if(err != noErr) - { - llinfos << "Unable to pick a target, giving up." << llendl; - throw 0; - } - } - - // Find the volID of the volume the target resides on - { - FSCatalogInfo info; - err = FSGetCatalogInfo( - &targetParentRef, - kFSCatInfoVolume, - &info, - NULL, - NULL, - NULL); - - if(err != noErr) - throw 0; - - targetVol = info.volume; - } - - // Find the temporary items and trash folders on that volume. - err = FSFindFolder( - targetVol, - kTrashFolderType, - true, - &trashFolderRef); - - if(err != noErr) - throw 0; - -#if 0 // *HACK for DEV-11935 see below for details. - - FSRef tempFolderRef; - - err = FSFindFolder( - targetVol, - kTemporaryFolderType, - true, - &tempFolderRef); - - if(err != noErr) - throw 0; - - err = FSRefMakePath(&tempFolderRef, (UInt8*)temp, sizeof(temp)); - - if(err != noErr) - throw 0; + + bool replacingTarget = false; -#else + boost::filesystem::path app_dir; + boost::filesystem::path temp_dir; + boost::filesystem::path mount_dir; + + // Attempt to get a reference to the Second Life application bundle containing this updater. + // Any failures during this process will cause us to default to updating /Applications/Second Life.app - // *HACK for DEV-11935 the above kTemporaryFolderType query was giving - // back results with path names that seem to be too long to be used as - // mount points. I suspect this incompatibility was introduced in the - // Leopard 10.5.2 update, but I have not verified this. - char const HARDCODED_TMP[] = "/tmp"; - strncpy(temp, HARDCODED_TMP, sizeof(HARDCODED_TMP)); - -#endif // 0 *HACK for DEV-11935 + try + { + replacingTarget = getViewerDir( app_dir ); + + if (!mkTempDir(temp_dir)) + { + throw 0; + } + + //In case the dir doesn't exist, try to create it. If create fails, verify it exists. + if (! boost::filesystem::create_directory(app_dir)) + { + + + if(isFSRefViewerBundle(app_dir.string())) + { + // This is the bundle we're looking for. + replacingTarget = true; + } + else + { + throw 0; + } + } + + if ( !verifyDirectory(&app_dir, true) ) + { + // We're so hosed. + llinfos << "Applications directory not found, giving up." << llendl; + throw 0; + } // Skip downloading the file if the dmg was passed on the command line. std::string dmgName; - if(gDmgFile != NULL) { - dmgName = basename((char *)gDmgFile); - char * dmgDir = dirname((char *)gDmgFile); - strncpy(tempDir, dmgDir, sizeof(tempDir)); - err = FSPathMakeRef((UInt8*)tempDir, &tempDirRef, NULL); - if(err != noErr) throw 0; - chdir(tempDir); - goto begin_install; + if(mDmgFile != NULL) { + //Create a string from the mDmgFile then a dir reference to that. + //change to that directory and begin install. + + boost::filesystem::path dmg_path(*mDmgFile); + + dmgName = dmg_path.string(); + std::string* dmgPath = new std::string(dmg_path.parent_path().string()); + if ( !boost::filesystem::exists( dmg_path.parent_path() ) ) { + llinfos << "Path " << *dmgPath << " is not writeable. Aborting." << llendl; + throw 0; + } + + chdir(dmgPath->c_str()); } else { // Continue on to download file. dmgName = "SecondLife.dmg"; - } - - - strncat(temp, "/SecondLifeUpdate_XXXXXX", (sizeof(temp) - strlen(temp)) - 1); - if(mkdtemp(temp) == NULL) - { - throw 0; - } - - strncpy(tempDir, temp, sizeof(tempDir)); - temp[sizeof(tempDir) - 1] = '\0'; - - llinfos << "tempDir is " << tempDir << llendl; - - err = FSPathMakeRef((UInt8*)tempDir, &tempDirRef, NULL); - - if(err != noErr) - throw 0; - - chdir(tempDir); - - snprintf(temp, sizeof(temp), "SecondLife.dmg"); - - downloadFile = LLFile::fopen(temp, "wb"); /* Flawfinder: ignore */ - if(downloadFile == NULL) - { - throw 0; - } - - { - CURL *curl = curl_easy_init(); - - curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1); - // curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &curl_download_callback); - curl_easy_setopt(curl, CURLOPT_FILE, downloadFile); - curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0); - curl_easy_setopt(curl, CURLOPT_PROGRESSFUNCTION, &curl_progress_callback_func); - curl_easy_setopt(curl, CURLOPT_URL, gUpdateURL); - curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1); - - sendProgress(0, 1, CFSTR("Downloading...")); - - CURLcode result = curl_easy_perform(curl); - - curl_easy_cleanup(curl); - - if(gCancelled) - { - llinfos << "User cancel, bailing out."<< llendl; - throw 0; - } - - if(result != CURLE_OK) - { - llinfos << "Error " << result << " while downloading disk image."<< llendl; - throw 0; - } - - fclose(downloadFile); - downloadFile = NULL; - } - - begin_install: - sendProgress(0, 0, CFSTR("Mounting image...")); - LLFile::mkdir("mnt", 0700); - - // NOTE: we could add -private at the end of this command line to keep the image from showing up in the Finder, - // but if our cleanup fails, this makes it much harder for the user to unmount the image. - std::string mountOutput; - boost::format cmdFormat("hdiutil attach %s -mountpoint mnt"); - cmdFormat % dmgName; - FILE* mounter = popen(cmdFormat.str().c_str(), "r"); /* Flawfinder: ignore */ - - if(mounter == NULL) - { - llinfos << "Failed to mount disk image, exiting."<< llendl; - throw 0; - } - - // We need to scan the output from hdiutil to find the device node it uses to attach the disk image. - // If we don't have this information, we can't detach it later. - while(mounter != NULL) - { - size_t len = fread(temp, 1, sizeof(temp)-1, mounter); - temp[len] = 0; - mountOutput.append(temp); - if(len < sizeof(temp)-1) - { - // End of file or error. - int result = pclose(mounter); - if(result != 0) - { - // NOTE: We used to abort here, but pclose() started returning - // -1, possibly when the size of the DMG passed a certain point - llinfos << "Unexpected result closing pipe: " << result << llendl; - } - mounter = NULL; - } - } - - if(!mountOutput.empty()) - { - const char *s = mountOutput.c_str(); - const char *prefix = "/dev/"; - char *sub = strstr(s, prefix); - - if(sub != NULL) - { - sub += strlen(prefix); /* Flawfinder: ignore */ - sscanf(sub, "%1023s", deviceNode); /* Flawfinder: ignore */ - } - } - - if(deviceNode[0] != 0) - { - llinfos << "Disk image attached on /dev/" << deviceNode << llendl; - } - else - { - llinfos << "Disk image device node not found!" << llendl; - throw 0; - } - - // Get an FSRef to the new application on the disk image - FSRef sourceRef; - FSRef mountRef; - snprintf(temp, sizeof(temp), "%s/mnt", tempDir); - - llinfos << "Disk image mount point is: " << temp << llendl; + + + if (!downloadDMG(dmgName, &temp_dir)) + { + throw 0; + } + } + + if (!doMount(dmgName, deviceNode, temp_dir)) + { + throw 0; + } + + if (!doInstall( app_dir, temp_dir, mount_dir, replacingTarget )) + { + throw 0; + } - err = FSPathMakeRef((UInt8 *)temp, &mountRef, NULL); - if(err != noErr) - { - llinfos << "Couldn't make FSRef to disk image mount point." << llendl; - throw 0; - } - - sendProgress(0, 0, CFSTR("Searching for the app bundle...")); - err = findAppBundleOnDiskImage(&mountRef, &sourceRef); - if(err != noErr) - { - llinfos << "Couldn't find application bundle on mounted disk image." << llendl; - throw 0; - } - else - { - llinfos << "found the bundle." << llendl; - } - - sendProgress(0, 0, CFSTR("Preparing to copy files...")); - - FSRef asideRef; - char aside[MAX_PATH]; /* Flawfinder: ignore */ - - // this will hold the name of the destination target - CFStringRef appNameRef; - - if(replacingTarget) - { - // Get the name of the target we're replacing - HFSUniStr255 appNameUniStr; - err = FSGetCatalogInfo(&targetRef, 0, NULL, &appNameUniStr, NULL, NULL); - if(err != noErr) - throw 0; - appNameRef = FSCreateStringFromHFSUniStr(NULL, &appNameUniStr); - - // Move aside old version (into work directory) - err = FSMoveObject(&targetRef, &tempDirRef, &asideRef); - if(err != noErr) - { - llwarns << "failed to move aside old version (error code " << - err << ")" << llendl; - throw 0; - } - - // Grab the path for later use. - err = FSRefMakePath(&asideRef, (UInt8*)aside, sizeof(aside)); - } - else - { - // Construct the name of the target based on the product name - char appName[MAX_PATH]; /* Flawfinder: ignore */ - snprintf(appName, sizeof(appName), "%s.app", gProductName); - appNameRef = CFStringCreateWithCString(NULL, appName, kCFStringEncodingUTF8); - } - - sendProgress(0, 0, CFSTR("Copying files...")); - - llinfos << "Starting copy..." << llendl; - - // Copy the new version from the disk image to the target location. - err = FSCopyObjectSync( - &sourceRef, - &targetParentRef, - appNameRef, - &targetRef, - kFSFileOperationDefaultOptions); - - // Grab the path for later use. - err = FSRefMakePath(&targetRef, (UInt8*)target, sizeof(target)); - if(err != noErr) - throw 0; - - llinfos << "Copy complete. Target = " << target << llendl; - - if(err != noErr) - { - // Something went wrong during the copy. Attempt to put the old version back and bail. - (void)FSDeleteObject(&targetRef); - if(replacingTarget) - { - (void)FSMoveObject(&asideRef, &targetParentRef, NULL); - } - throw 0; - } - else - { - // The update has succeeded. Clear the cache directory. - - sendProgress(0, 0, CFSTR("Clearing cache...")); - - llinfos << "Clearing cache..." << llendl; - - gDirUtilp->deleteFilesInDir(gDirUtilp->getExpandedFilename(LL_PATH_CACHE,""), "*.*"); - - llinfos << "Clear complete." << llendl; - - } } catch(...) { if(!gCancelled) - if(gFailure == noErr) - gFailure = -1; + gFailure = true; } // Failures from here on out are all non-fatal and not reported. - sendProgress(0, 3, CFSTR("Cleaning up...")); + sendProgress(0, 3, std::string("Cleaning up...")); - // Close disk image file if necessary - if(downloadFile != NULL) - { - llinfos << "Closing download file." << llendl; - - fclose(downloadFile); - downloadFile = NULL; - } - - sendProgress(1, 3); + setProgress(1, 3); // Unmount image if(deviceNode[0] != 0) { @@ -1223,35 +613,61 @@ void *updatethreadproc(void*) system(temp); /* Flawfinder: ignore */ } - sendProgress(2, 3); + setProgress(2, 3); + std::string *trash_str=getUserTrashFolder(); // Move work directory to the trash if(tempDir[0] != 0) { llinfos << "Moving work directory to the trash." << llendl; - - FSRef trashRef; - OSStatus err = FSMoveObjectToTrashSync(&tempDirRef, &trashRef, 0); - if(err != noErr) { - llwarns << "failed to move files to trash, (error code " << - err << ")" << llendl; - } + + try + { + boost::filesystem::path trash_dir(*trash_str); + boost::filesystem::rename(mount_dir, trash_dir); + } + catch(boost::filesystem::filesystem_error e) + { + llwarns << "Failed to move " << mount_dir.string() << " to " << *trash_str << llendl; + return (NULL); + } } - if(!gCancelled && !gFailure && (target[0] != 0)) + std::string app_name_str = app_dir.string(); + + if(!gCancelled && !gFailure && !app_name_str.empty()) { + //SPATTERS todo is there no better way to do this than system calls? llinfos << "Touching application bundle." << llendl; + + std::stringstream touch_str; - snprintf(temp, sizeof(temp), "touch '%s'", target); - system(temp); /* Flawfinder: ignore */ + touch_str << "touch '" << app_name_str << "'"; + + system(touch_str.str().c_str()); /* Flawfinder: ignore */ llinfos << "Launching updated application." << llendl; + + std::stringstream open_str; + + open_str << "open '" << app_name_str << "'"; - snprintf(temp, sizeof(temp), "open '%s'", target); - system(temp); /* Flawfinder: ignore */ + system(open_str.str().c_str()); /* Flawfinder: ignore */ } sendDone(); - return(NULL); + return (NULL); } + +//static +void* LLMacUpdater::sUpdatethreadproc(void* vptr) +{ + if (!sInstance) + { + llerrs << "LLMacUpdater not instantiated before use. Aborting." << llendl; + return (NULL); + } + return sInstance->updatethreadproc(vptr); +} + |