PageRenderTime 103ms CodeModel.GetById 34ms app.highlight 60ms RepoModel.GetById 2ms app.codeStats 0ms

/indra/mac_updater/mac_updater.cpp

https://bitbucket.org/lindenlab/viewer-beta/
C++ | 1259 lines | 945 code | 206 blank | 108 comment | 220 complexity | a454910045a2dc291ac2928fdeac867e MD5 | raw file
   1/** 
   2 * @file mac_updater.cpp
   3 * @brief 
   4 *
   5 * $LicenseInfo:firstyear=2006&license=viewerlgpl$
   6 * Second Life Viewer Source Code
   7 * Copyright (C) 2010, Linden Research, Inc.
   8 * 
   9 * This library is free software; you can redistribute it and/or
  10 * modify it under the terms of the GNU Lesser General Public
  11 * License as published by the Free Software Foundation;
  12 * version 2.1 of the License only.
  13 * 
  14 * This library is distributed in the hope that it will be useful,
  15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
  16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  17 * Lesser General Public License for more details.
  18 * 
  19 * You should have received a copy of the GNU Lesser General Public
  20 * License along with this library; if not, write to the Free Software
  21 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
  22 * 
  23 * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA
  24 * $/LicenseInfo$
  25 */
  26
  27#include "linden_common.h"
  28
  29#include <boost/format.hpp>
  30
  31#include <libgen.h>
  32#include <sys/types.h>
  33#include <sys/stat.h>
  34#include <unistd.h>
  35
  36#include <curl/curl.h>
  37#include <pthread.h>
  38
  39#include "llerror.h"
  40#include "lltimer.h"
  41#include "lldir.h"
  42#include "llfile.h"
  43
  44#include "llstring.h"
  45
  46#include <Carbon/Carbon.h>
  47
  48#include "llerrorcontrol.h"
  49
  50enum
  51{
  52	kEventClassCustom = 'Cust',
  53	kEventCustomProgress = 'Prog',
  54	kEventParamCustomCurValue = 'Cur ',
  55	kEventParamCustomMaxValue = 'Max ',
  56	kEventParamCustomText = 'Text',
  57	kEventCustomDone = 'Done',
  58};
  59
  60WindowRef gWindow = NULL;
  61EventHandlerRef gEventHandler = NULL;
  62OSStatus gFailure = noErr;
  63Boolean gCancelled = false;
  64
  65const char *gUpdateURL;
  66const char *gProductName;
  67const char *gBundleID;
  68const char *gDmgFile;
  69const char *gMarkerPath;
  70
  71void *updatethreadproc(void*);
  72
  73pthread_t updatethread;
  74
  75OSStatus setProgress(int cur, int max)
  76{
  77	OSStatus err;
  78	ControlRef progressBar = NULL;
  79	ControlID id;
  80
  81	id.signature = 'prog';
  82	id.id = 0;
  83
  84	err = GetControlByID(gWindow, &id, &progressBar);
  85	if(err == noErr)
  86	{
  87		Boolean indeterminate;
  88		
  89		if(max == 0)
  90		{
  91			indeterminate = true;
  92			err = SetControlData(progressBar, kControlEntireControl, kControlProgressBarIndeterminateTag, sizeof(Boolean), (Ptr)&indeterminate);
  93		}
  94		else
  95		{
  96			double percentage = (double)cur / (double)max;
  97			SetControlMinimum(progressBar, 0);
  98			SetControlMaximum(progressBar, 100);
  99			SetControlValue(progressBar, (SInt16)(percentage * 100));
 100
 101			indeterminate = false;
 102			err = SetControlData(progressBar, kControlEntireControl, kControlProgressBarIndeterminateTag, sizeof(Boolean), (Ptr)&indeterminate);
 103
 104			Draw1Control(progressBar);
 105		}
 106	}
 107
 108	return(err);
 109}
 110
 111OSStatus setProgressText(CFStringRef text)
 112{
 113	OSStatus err;
 114	ControlRef progressText = NULL;
 115	ControlID id;
 116
 117	id.signature = 'what';
 118	id.id = 0;
 119
 120	err = GetControlByID(gWindow, &id, &progressText);
 121	if(err == noErr)
 122	{
 123		err = SetControlData(progressText, kControlEntireControl, kControlStaticTextCFStringTag, sizeof(CFStringRef), (Ptr)&text);
 124		Draw1Control(progressText);
 125	}
 126
 127	return(err);
 128}
 129
 130OSStatus sendProgress(long cur, long max, CFStringRef text = NULL)
 131{
 132	OSStatus result;
 133	EventRef evt;
 134	
 135	result = CreateEvent( 
 136			NULL,
 137			kEventClassCustom, 
 138			kEventCustomProgress,
 139			0, 
 140			kEventAttributeNone, 
 141			&evt);
 142	
 143	// This event needs to be targeted at the window so it goes to the window's handler.
 144	if(result == noErr)
 145	{
 146		EventTargetRef target = GetWindowEventTarget(gWindow);
 147		result = SetEventParameter (
 148			evt,
 149			kEventParamPostTarget,
 150			typeEventTargetRef,
 151			sizeof(target),
 152			&target);
 153	}
 154
 155	if(result == noErr)
 156	{
 157		result = SetEventParameter (
 158			evt,
 159			kEventParamCustomCurValue,
 160			typeLongInteger,
 161			sizeof(cur),
 162			&cur);
 163	}
 164
 165	if(result == noErr)
 166	{
 167		result = SetEventParameter (
 168			evt,
 169			kEventParamCustomMaxValue,
 170			typeLongInteger,
 171			sizeof(max),
 172			&max);
 173	}
 174	
 175	if(result == noErr)
 176	{
 177		if(text != NULL)
 178		{
 179			result = SetEventParameter (
 180				evt,
 181				kEventParamCustomText,
 182				typeCFStringRef,
 183				sizeof(text),
 184				&text);
 185		}
 186	}
 187	
 188	if(result == noErr)
 189	{
 190		// Send the event
 191		PostEventToQueue(
 192			GetMainEventQueue(),
 193			evt,
 194			kEventPriorityStandard);
 195
 196	}
 197	
 198	return(result);
 199}
 200
 201OSStatus sendDone(void)
 202{
 203	OSStatus result;
 204	EventRef evt;
 205	
 206	result = CreateEvent( 
 207			NULL,
 208			kEventClassCustom, 
 209			kEventCustomDone,
 210			0, 
 211			kEventAttributeNone, 
 212			&evt);
 213	
 214	// This event needs to be targeted at the window so it goes to the window's handler.
 215	if(result == noErr)
 216	{
 217		EventTargetRef target = GetWindowEventTarget(gWindow);
 218		result = SetEventParameter (
 219			evt,
 220			kEventParamPostTarget,
 221			typeEventTargetRef,
 222			sizeof(target),
 223			&target);
 224	}
 225
 226	if(result == noErr)
 227	{
 228		// Send the event
 229		PostEventToQueue(
 230			GetMainEventQueue(),
 231			evt,
 232			kEventPriorityStandard);
 233
 234	}
 235	
 236	return(result);
 237}
 238
 239OSStatus dialogHandler(EventHandlerCallRef handler, EventRef event, void *userdata)
 240{
 241	OSStatus result = eventNotHandledErr;
 242	OSStatus err;
 243	UInt32 evtClass = GetEventClass(event);
 244	UInt32 evtKind = GetEventKind(event);
 245	
 246	if((evtClass == kEventClassCommand) && (evtKind == kEventCommandProcess))
 247	{
 248		HICommand cmd;
 249		err = GetEventParameter(event, kEventParamDirectObject, typeHICommand, NULL, sizeof(cmd), NULL, &cmd);
 250		
 251		if(err == noErr)
 252		{
 253			switch(cmd.commandID)
 254			{				
 255				case kHICommandCancel:
 256					gCancelled = true;
 257//					QuitAppModalLoopForWindow(gWindow);
 258					result = noErr;
 259				break;
 260			}
 261		}
 262	}
 263	else if((evtClass == kEventClassCustom) && (evtKind == kEventCustomProgress))
 264	{
 265		// Request to update the progress dialog
 266		long cur = 0;
 267		long max = 0;
 268		CFStringRef text = NULL;
 269		(void) GetEventParameter(event, kEventParamCustomCurValue, typeLongInteger, NULL, sizeof(cur), NULL, &cur);
 270		(void) GetEventParameter(event, kEventParamCustomMaxValue, typeLongInteger, NULL, sizeof(max), NULL, &max);
 271		(void) GetEventParameter(event, kEventParamCustomText, typeCFStringRef, NULL, sizeof(text), NULL, &text);
 272		
 273		err = setProgress(cur, max);
 274		if(err == noErr)
 275		{
 276			if(text != NULL)
 277			{
 278				setProgressText(text);
 279			}
 280		}
 281		
 282		result = noErr;
 283	}
 284	else if((evtClass == kEventClassCustom) && (evtKind == kEventCustomDone))
 285	{
 286		// We're done.  Exit the modal loop.
 287		QuitAppModalLoopForWindow(gWindow);
 288		result = noErr;
 289	}
 290	
 291	return(result);
 292}
 293
 294#if 0
 295size_t curl_download_callback(void *data, size_t size, size_t nmemb,
 296										  void *user_data)
 297{
 298	S32 bytes = size * nmemb;
 299	char *cdata = (char *) data;
 300	for (int i =0; i < bytes; i += 1)
 301	{
 302		gServerResponse.append(cdata[i]);
 303	}
 304	return bytes;
 305}
 306#endif
 307
 308int curl_progress_callback_func(void *clientp,
 309							  double dltotal,
 310							  double dlnow,
 311							  double ultotal,
 312							  double ulnow)
 313{
 314	int max = (int)(dltotal / 1024.0);
 315	int cur = (int)(dlnow / 1024.0);
 316	sendProgress(cur, max);
 317	
 318	if(gCancelled)
 319		return(1);
 320
 321	return(0);
 322}
 323
 324int parse_args(int argc, char **argv)
 325{
 326	int j;
 327
 328	for (j = 1; j < argc; j++) 
 329	{
 330		if ((!strcmp(argv[j], "-url")) && (++j < argc)) 
 331		{
 332			gUpdateURL = argv[j];
 333		}
 334		else if ((!strcmp(argv[j], "-name")) && (++j < argc)) 
 335		{
 336			gProductName = argv[j];
 337		}
 338		else if ((!strcmp(argv[j], "-bundleid")) && (++j < argc)) 
 339		{
 340			gBundleID = argv[j];
 341		}
 342		else if ((!strcmp(argv[j], "-dmg")) && (++j < argc)) 
 343		{
 344			gDmgFile = argv[j];
 345		}
 346		else if ((!strcmp(argv[j], "-marker")) && (++j < argc)) 
 347		{
 348			gMarkerPath = argv[j];;
 349		}
 350	}
 351
 352	return 0;
 353}
 354
 355int main(int argc, char **argv)
 356{
 357	// We assume that all the logs we're looking for reside on the current drive
 358	gDirUtilp->initAppDirs("SecondLife");
 359
 360	LLError::initForApplication( gDirUtilp->getExpandedFilename(LL_PATH_APP_SETTINGS, ""));
 361
 362	// Rename current log file to ".old"
 363	std::string old_log_file = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, "updater.log.old");
 364	std::string log_file = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, "updater.log");
 365	LLFile::rename(log_file.c_str(), old_log_file.c_str());
 366
 367	// Set the log file to updater.log
 368	LLError::logToFile(log_file);
 369
 370	/////////////////////////////////////////
 371	//
 372	// Process command line arguments
 373	//
 374	gUpdateURL  = NULL;
 375	gProductName = NULL;
 376	gBundleID = NULL;
 377	gDmgFile = NULL;
 378	gMarkerPath = NULL;
 379	parse_args(argc, argv);
 380	if ((gUpdateURL == NULL) && (gDmgFile == NULL))
 381	{
 382		llinfos << "Usage: mac_updater -url <url> | -dmg <dmg file> [-name <product_name>] [-program <program_name>]" << llendl;
 383		exit(1);
 384	}
 385	else
 386	{
 387		llinfos << "Update url is: " << gUpdateURL << llendl;
 388		if (gProductName)
 389		{
 390			llinfos << "Product name is: " << gProductName << llendl;
 391		}
 392		else
 393		{
 394			gProductName = "Second Life";
 395		}
 396		if (gBundleID)
 397		{
 398			llinfos << "Bundle ID is: " << gBundleID << llendl;
 399		}
 400		else
 401		{
 402			gBundleID = "com.secondlife.indra.viewer";
 403		}
 404	}
 405	
 406	llinfos << "Starting " << gProductName << " Updater" << llendl;
 407
 408	// Real UI...
 409	OSStatus err;
 410	IBNibRef nib = NULL;
 411	
 412	err = CreateNibReference(CFSTR("AutoUpdater"), &nib);
 413
 414	char windowTitle[MAX_PATH];		/* Flawfinder: ignore */
 415	snprintf(windowTitle, sizeof(windowTitle), "%s Updater", gProductName);		
 416	CFStringRef windowTitleRef = NULL;
 417	windowTitleRef = CFStringCreateWithCString(NULL, windowTitle, kCFStringEncodingUTF8);
 418	
 419	if(err == noErr)
 420	{
 421		err = CreateWindowFromNib(nib, CFSTR("Updater"), &gWindow);
 422	}
 423
 424	if (err == noErr)
 425	{
 426		err = SetWindowTitleWithCFString(gWindow, windowTitleRef);	
 427	}
 428	CFRelease(windowTitleRef);
 429
 430	if(err == noErr)
 431	{
 432		// Set up an event handler for the window.
 433		EventTypeSpec handlerEvents[] = 
 434		{
 435			{ kEventClassCommand, kEventCommandProcess },
 436			{ kEventClassCustom, kEventCustomProgress },
 437			{ kEventClassCustom, kEventCustomDone }
 438		};
 439		InstallStandardEventHandler(GetWindowEventTarget(gWindow));
 440		InstallWindowEventHandler(
 441				gWindow, 
 442				NewEventHandlerUPP(dialogHandler), 
 443				GetEventTypeCount (handlerEvents), 
 444				handlerEvents, 
 445				0, 
 446				&gEventHandler);
 447	}
 448	
 449	if(err == noErr)
 450	{
 451		ShowWindow(gWindow);
 452		SelectWindow(gWindow);
 453	}
 454		
 455	if(err == noErr)
 456	{
 457		pthread_create(&updatethread, 
 458                         NULL,
 459                         &updatethreadproc, 
 460                         NULL);
 461						 
 462	}
 463	
 464	if(err == noErr)
 465	{
 466		RunAppModalLoopForWindow(gWindow);
 467	}
 468
 469	void *threadresult;
 470
 471	pthread_join(updatethread, &threadresult);
 472
 473	if(!gCancelled && (gFailure != noErr))
 474	{
 475		// Something went wrong.  Since we always just tell the user to download a new version, we don't really care what.
 476		AlertStdCFStringAlertParamRec params;
 477		SInt16 retval_mac = 1;
 478		DialogRef alert = NULL;
 479		OSStatus err;
 480
 481		params.version = kStdCFStringAlertVersionOne;
 482		params.movable = false;
 483		params.helpButton = false;
 484		params.defaultText = (CFStringRef)kAlertDefaultOKText;
 485		params.cancelText = 0;
 486		params.otherText = 0;
 487		params.defaultButton = 1;
 488		params.cancelButton = 0;
 489		params.position = kWindowDefaultPosition;
 490		params.flags = 0;
 491
 492		err = CreateStandardAlert(
 493				kAlertStopAlert,
 494				CFSTR("Error"),
 495				CFSTR("An error occurred while updating Second Life.  Please download the latest version from www.secondlife.com."),
 496				&params,
 497				&alert);
 498		
 499		if(err == noErr)
 500		{
 501			err = RunStandardAlert(
 502					alert,
 503					NULL,
 504					&retval_mac);
 505		}
 506		
 507		if(gMarkerPath != 0)
 508		{
 509			// Create a install fail marker that can be used by the viewer to
 510			// detect install problems.
 511			std::ofstream stream(gMarkerPath);
 512			if(stream) stream << -1;
 513		}
 514		exit(-1);
 515	} else {
 516		exit(0);
 517	}
 518
 519	if(gWindow != NULL)
 520	{
 521		DisposeWindow(gWindow);
 522	}
 523	
 524	if(nib != NULL)
 525	{
 526		DisposeNibReference(nib);
 527	}
 528	
 529	return 0;
 530}
 531
 532bool isDirWritable(FSRef &dir)
 533{
 534	bool result = false;
 535	
 536	// Test for a writable directory by creating a directory, then deleting it again.
 537	// This is kinda lame, but will pretty much always give the right answer.
 538	
 539	OSStatus err = noErr;
 540	char temp[PATH_MAX] = "";		/* Flawfinder: ignore */
 541
 542	err = FSRefMakePath(&dir, (UInt8*)temp, sizeof(temp));
 543
 544	if(err == noErr)
 545	{
 546		strncat(temp, "/.test_XXXXXX", (sizeof(temp) - strlen(temp)) - 1);
 547		
 548		if(mkdtemp(temp) != NULL)
 549		{
 550			// We were able to make the directory.  This means the directory is writable.
 551			result = true;
 552			
 553			// Clean up.
 554			rmdir(temp);
 555		}
 556	}
 557
 558#if 0
 559	// This seemed like a good idea, but won't tell us if we're on a volume mounted read-only.
 560	UInt8 perm;
 561	err = FSGetUserPrivilegesPermissions(&targetParentRef, &perm, NULL);
 562	if(err == noErr)
 563	{
 564		if(perm & kioACUserNoMakeChangesMask)
 565		{
 566			// Parent directory isn't writable.
 567			llinfos << "Target parent directory not writable." << llendl;
 568			err = -1;
 569			replacingTarget = false;
 570		}
 571	}
 572#endif
 573
 574	return result;
 575}
 576
 577static std::string HFSUniStr255_to_utf8str(const HFSUniStr255* src)
 578{
 579	llutf16string string16((U16*)&(src->unicode), src->length);
 580	std::string result = utf16str_to_utf8str(string16);
 581	return result;
 582}
 583
 584int restoreObject(const char* aside, const char* target, const char* path, const char* object)
 585{
 586	char source[PATH_MAX] = "";		/* Flawfinder: ignore */
 587	char dest[PATH_MAX] = "";		/* Flawfinder: ignore */
 588	snprintf(source, sizeof(source), "%s/%s/%s", aside, path, object);		
 589	snprintf(dest, sizeof(dest), "%s/%s", target, path);		
 590	FSRef sourceRef;
 591	FSRef destRef;
 592	OSStatus err;
 593	err = FSPathMakeRef((UInt8 *)source, &sourceRef, NULL);
 594	if(err != noErr) return false;
 595	err = FSPathMakeRef((UInt8 *)dest, &destRef, NULL);
 596	if(err != noErr) return false;
 597
 598	llinfos << "Copying " << source << " to " << dest << llendl;
 599
 600	err = FSCopyObjectSync(
 601			&sourceRef,
 602			&destRef,
 603			NULL,
 604			NULL,
 605			kFSFileOperationOverwrite);
 606
 607	if(err != noErr) return false;
 608	return true;
 609}
 610
 611// Replace any mention of "Second Life" with the product name.
 612void filterFile(const char* filename)
 613{
 614	char temp[PATH_MAX] = "";		/* Flawfinder: ignore */
 615	// First copy the target's version, so we can run it through sed.
 616	snprintf(temp, sizeof(temp), "cp '%s' '%s.tmp'", filename, filename);		
 617	system(temp);		/* Flawfinder: ignore */
 618
 619	// Now run it through sed.
 620	snprintf(temp, sizeof(temp), 		
 621			"sed 's/Second Life/%s/g' '%s.tmp' > '%s'", gProductName, filename, filename);
 622	system(temp);		/* Flawfinder: ignore */
 623}
 624
 625static bool isFSRefViewerBundle(FSRef *targetRef)
 626{
 627	bool result = false;
 628	CFURLRef targetURL = NULL;
 629	CFBundleRef targetBundle = NULL;
 630	CFStringRef targetBundleID = NULL;
 631	CFStringRef sourceBundleID = NULL;
 632
 633	targetURL = CFURLCreateFromFSRef(NULL, targetRef);
 634
 635	if(targetURL == NULL)
 636	{
 637		llinfos << "Error creating target URL." << llendl;
 638	}
 639	else
 640	{
 641		targetBundle = CFBundleCreate(NULL, targetURL);
 642	}
 643	
 644	if(targetBundle == NULL)
 645	{
 646		llinfos << "Failed to create target bundle." << llendl;
 647	}
 648	else
 649	{
 650		targetBundleID = CFBundleGetIdentifier(targetBundle);
 651	}
 652	
 653	if(targetBundleID == NULL)
 654	{
 655		llinfos << "Couldn't retrieve target bundle ID." << llendl;
 656	}
 657	else
 658	{
 659		sourceBundleID = CFStringCreateWithCString(NULL, gBundleID, kCFStringEncodingUTF8);
 660		if(CFStringCompare(sourceBundleID, targetBundleID, 0) == kCFCompareEqualTo)
 661		{
 662			// This is the bundle we're looking for.
 663			result = true;
 664		}
 665		else
 666		{
 667			llinfos << "Target bundle ID mismatch." << llendl;
 668		}
 669	}
 670	
 671	// Don't release targetBundleID -- since we don't retain it, it's released when targetBundle is released.
 672	if(targetURL != NULL)
 673		CFRelease(targetURL);
 674	if(targetBundle != NULL)
 675		CFRelease(targetBundle);
 676	
 677	return result;
 678}
 679
 680// Search through the directory specified by 'parent' for an item that appears to be a Second Life viewer.
 681static OSErr findAppBundleOnDiskImage(FSRef *parent, FSRef *app)
 682{
 683	FSIterator		iterator;
 684	bool			found = false;
 685
 686	OSErr err = FSOpenIterator( parent, kFSIterateFlat, &iterator );
 687	if(!err)
 688	{
 689		do
 690		{
 691			ItemCount actualObjects = 0;
 692			Boolean containerChanged = false;
 693			FSCatalogInfo info;
 694			FSRef ref;
 695			HFSUniStr255 unicodeName;
 696			err = FSGetCatalogInfoBulk( 
 697					iterator, 
 698					1, 
 699					&actualObjects, 
 700					&containerChanged,
 701					kFSCatInfoNodeFlags, 
 702					&info, 
 703					&ref,
 704					NULL, 
 705					&unicodeName );
 706			
 707			if(actualObjects == 0)
 708				break;
 709				
 710			if(!err)
 711			{
 712				// Call succeeded and not done with the iteration.
 713				std::string name = HFSUniStr255_to_utf8str(&unicodeName);
 714
 715				llinfos << "Considering \"" << name << "\"" << llendl;
 716
 717				if(info.nodeFlags & kFSNodeIsDirectoryMask)
 718				{
 719					// This is a directory.  See if it's a .app
 720					if(name.find(".app") != std::string::npos)
 721					{
 722						// Looks promising.  Check to see if it has the right bundle identifier.
 723						if(isFSRefViewerBundle(&ref))
 724						{
 725							llinfos << name << " is the one" << llendl;
 726							// This is the one.  Return it.
 727							*app = ref;
 728							found = true;
 729							break;
 730						} else {
 731							llinfos << name << " is not the bundle we are looking for; move along" << llendl;
 732						}
 733
 734					}
 735				}
 736			}
 737		}
 738		while(!err);
 739		
 740		llinfos << "closing the iterator" << llendl;
 741		
 742		FSCloseIterator(iterator);
 743		
 744		llinfos << "closed" << llendl;
 745	}
 746	
 747	if(!err && !found)
 748		err = fnfErr;
 749		
 750	return err;
 751}
 752
 753void *updatethreadproc(void*)
 754{
 755	char tempDir[PATH_MAX] = "";		/* Flawfinder: ignore */
 756	FSRef tempDirRef;
 757	char temp[PATH_MAX] = "";	/* Flawfinder: ignore */
 758	// *NOTE: This buffer length is used in a scanf() below.
 759	char deviceNode[1024] = "";	/* Flawfinder: ignore */
 760	LLFILE *downloadFile = NULL;
 761	OSStatus err;
 762	ProcessSerialNumber psn;
 763	char target[PATH_MAX] = "";		/* Flawfinder: ignore */
 764	FSRef targetRef;
 765	FSRef targetParentRef;
 766	FSVolumeRefNum targetVol;
 767	FSRef trashFolderRef;
 768	Boolean replacingTarget = false;
 769
 770	memset(&tempDirRef, 0, sizeof(tempDirRef));
 771	memset(&targetRef, 0, sizeof(targetRef));
 772	memset(&targetParentRef, 0, sizeof(targetParentRef));
 773	
 774	try
 775	{
 776		// Attempt to get a reference to the Second Life application bundle containing this updater.
 777		// Any failures during this process will cause us to default to updating /Applications/Second Life.app
 778		{
 779			FSRef myBundle;
 780
 781			err = GetCurrentProcess(&psn);
 782			if(err == noErr)
 783			{
 784				err = GetProcessBundleLocation(&psn, &myBundle);
 785			}
 786
 787			if(err == noErr)
 788			{
 789				// Sanity check:  Make sure the name of the item referenced by targetRef is "Second Life.app".
 790				FSRefMakePath(&myBundle, (UInt8*)target, sizeof(target));
 791				
 792				llinfos << "Updater bundle location: " << target << llendl;
 793			}
 794			
 795			// Our bundle should be in Second Life.app/Contents/Resources/AutoUpdater.app
 796			// so we need to go up 3 levels to get the path to the main application bundle.
 797			if(err == noErr)
 798			{
 799				err = FSGetCatalogInfo(&myBundle, kFSCatInfoNone, NULL, NULL, NULL, &targetRef);
 800			}
 801			if(err == noErr)
 802			{
 803				err = FSGetCatalogInfo(&targetRef, kFSCatInfoNone, NULL, NULL, NULL, &targetRef);
 804			}
 805			if(err == noErr)
 806			{
 807				err = FSGetCatalogInfo(&targetRef, kFSCatInfoNone, NULL, NULL, NULL, &targetRef);
 808			}
 809			
 810			// And once more to get the parent of the target
 811			if(err == noErr)
 812			{
 813				err = FSGetCatalogInfo(&targetRef, kFSCatInfoNone, NULL, NULL, NULL, &targetParentRef);
 814			}
 815			
 816			if(err == noErr)
 817			{
 818				FSRefMakePath(&targetRef, (UInt8*)target, sizeof(target));
 819				llinfos << "Path to target: " << target << llendl;
 820			}
 821			
 822			// Sanity check: make sure the target is a bundle with the right identifier
 823			if(err == noErr)
 824			{
 825				// Assume the worst...
 826				err = -1;
 827
 828				if(isFSRefViewerBundle(&targetRef))
 829				{
 830					// This is the bundle we're looking for.
 831					err = noErr;
 832					replacingTarget = true;
 833				}
 834			}
 835			
 836			// Make sure the target's parent directory is writable.
 837			if(err == noErr)
 838			{
 839				if(!isDirWritable(targetParentRef))
 840				{
 841					// Parent directory isn't writable.
 842					llinfos << "Target parent directory not writable." << llendl;
 843					err = -1;
 844					replacingTarget = false;
 845				}
 846			}
 847
 848			if(err != noErr)
 849			{
 850				Boolean isDirectory;
 851				llinfos << "Target search failed, defaulting to /Applications/" << gProductName << ".app." << llendl;
 852				
 853				// Set up the parent directory
 854				err = FSPathMakeRef((UInt8*)"/Applications", &targetParentRef, &isDirectory);
 855				if((err != noErr) || (!isDirectory))
 856				{
 857					// We're so hosed.
 858					llinfos << "Applications directory not found, giving up." << llendl;
 859					throw 0;
 860				}
 861				
 862				snprintf(target, sizeof(target), "/Applications/%s.app", gProductName);		
 863
 864				memset(&targetRef, 0, sizeof(targetRef));
 865				err = FSPathMakeRef((UInt8*)target, &targetRef, NULL);
 866				if(err == fnfErr)
 867				{
 868					// This is fine, just means we're not replacing anything.
 869					err = noErr;
 870					replacingTarget = false;
 871				}
 872				else
 873				{
 874					replacingTarget = true;
 875				}
 876
 877				// Make sure the target's parent directory is writable.
 878				if(err == noErr)
 879				{
 880					if(!isDirWritable(targetParentRef))
 881					{
 882						// Parent directory isn't writable.
 883						llinfos << "Target parent directory not writable." << llendl;
 884						err = -1;
 885						replacingTarget = false;
 886					}
 887				}
 888
 889			}
 890			
 891			// If we haven't fixed all problems by this point, just bail.
 892			if(err != noErr)
 893			{
 894				llinfos << "Unable to pick a target, giving up." << llendl;
 895				throw 0;
 896			}
 897		}
 898		
 899		// Find the volID of the volume the target resides on
 900		{
 901			FSCatalogInfo info;
 902			err = FSGetCatalogInfo(
 903				&targetParentRef,
 904				kFSCatInfoVolume,
 905				&info,
 906				NULL, 
 907				NULL,  
 908				NULL);
 909				
 910			if(err != noErr)
 911				throw 0;
 912			
 913			targetVol = info.volume;
 914		}
 915
 916		// Find the temporary items and trash folders on that volume.
 917		err = FSFindFolder(
 918			targetVol,
 919			kTrashFolderType,
 920			true,
 921			&trashFolderRef);
 922
 923		if(err != noErr)
 924			throw 0;
 925
 926#if 0 // *HACK for DEV-11935 see below for details.
 927
 928		FSRef tempFolderRef;
 929
 930		err = FSFindFolder(
 931			targetVol,
 932			kTemporaryFolderType,
 933			true,
 934			&tempFolderRef);
 935		
 936		if(err != noErr)
 937			throw 0;
 938		
 939		err = FSRefMakePath(&tempFolderRef, (UInt8*)temp, sizeof(temp));
 940
 941		if(err != noErr)
 942			throw 0;
 943
 944#else		
 945
 946		// *HACK for DEV-11935  the above kTemporaryFolderType query was giving
 947		// back results with path names that seem to be too long to be used as
 948		// mount points.  I suspect this incompatibility was introduced in the
 949		// Leopard 10.5.2 update, but I have not verified this. 
 950		char const HARDCODED_TMP[] = "/tmp";
 951		strncpy(temp, HARDCODED_TMP, sizeof(HARDCODED_TMP));
 952
 953#endif // 0 *HACK for DEV-11935
 954		
 955		// Skip downloading the file if the dmg was passed on the command line.
 956		std::string dmgName;
 957		if(gDmgFile != NULL) {
 958			dmgName = basename((char *)gDmgFile);
 959			char * dmgDir = dirname((char *)gDmgFile);
 960			strncpy(tempDir, dmgDir, sizeof(tempDir));
 961			err = FSPathMakeRef((UInt8*)tempDir, &tempDirRef, NULL);
 962			if(err != noErr) throw 0;
 963			chdir(tempDir);
 964			goto begin_install;
 965		} else {
 966			// Continue on to download file.
 967			dmgName = "SecondLife.dmg";
 968		}
 969
 970		
 971		strncat(temp, "/SecondLifeUpdate_XXXXXX", (sizeof(temp) - strlen(temp)) - 1);
 972		if(mkdtemp(temp) == NULL)
 973		{
 974			throw 0;
 975		}
 976		
 977		strncpy(tempDir, temp, sizeof(tempDir));
 978		temp[sizeof(tempDir) - 1] = '\0';
 979		
 980		llinfos << "tempDir is " << tempDir << llendl;
 981
 982		err = FSPathMakeRef((UInt8*)tempDir, &tempDirRef, NULL);
 983
 984		if(err != noErr)
 985			throw 0;
 986				
 987		chdir(tempDir);
 988		
 989		snprintf(temp, sizeof(temp), "SecondLife.dmg");		
 990		
 991		downloadFile = LLFile::fopen(temp, "wb");		/* Flawfinder: ignore */
 992		if(downloadFile == NULL)
 993		{
 994			throw 0;
 995		}
 996
 997		{
 998			CURL *curl = curl_easy_init();
 999
1000			curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1);
1001	//		curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &curl_download_callback);
1002			curl_easy_setopt(curl, CURLOPT_FILE, downloadFile);
1003			curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0);
1004			curl_easy_setopt(curl, CURLOPT_PROGRESSFUNCTION, &curl_progress_callback_func);
1005			curl_easy_setopt(curl, CURLOPT_URL,	gUpdateURL);
1006			curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1);
1007			
1008			sendProgress(0, 1, CFSTR("Downloading..."));
1009			
1010			CURLcode result = curl_easy_perform(curl);
1011			
1012			curl_easy_cleanup(curl);
1013			
1014			if(gCancelled)
1015			{
1016				llinfos << "User cancel, bailing out."<< llendl;
1017				throw 0;
1018			}
1019			
1020			if(result != CURLE_OK)
1021			{
1022				llinfos << "Error " << result << " while downloading disk image."<< llendl;
1023				throw 0;
1024			}
1025			
1026			fclose(downloadFile);
1027			downloadFile = NULL;
1028		}
1029
1030	begin_install:
1031		sendProgress(0, 0, CFSTR("Mounting image..."));
1032		LLFile::mkdir("mnt", 0700);
1033		
1034		// NOTE: we could add -private at the end of this command line to keep the image from showing up in the Finder,
1035		//		but if our cleanup fails, this makes it much harder for the user to unmount the image.
1036		std::string mountOutput;
1037		boost::format cmdFormat("hdiutil attach %s -mountpoint mnt");
1038		cmdFormat % dmgName;
1039		FILE* mounter = popen(cmdFormat.str().c_str(), "r");		/* Flawfinder: ignore */
1040		
1041		if(mounter == NULL)
1042		{
1043			llinfos << "Failed to mount disk image, exiting."<< llendl;
1044			throw 0;
1045		}
1046		
1047		// We need to scan the output from hdiutil to find the device node it uses to attach the disk image.
1048		// If we don't have this information, we can't detach it later.
1049		while(mounter != NULL)
1050		{
1051			size_t len = fread(temp, 1, sizeof(temp)-1, mounter);
1052			temp[len] = 0;
1053			mountOutput.append(temp);
1054			if(len < sizeof(temp)-1)
1055			{
1056				// End of file or error.
1057				int result = pclose(mounter);
1058				if(result != 0)
1059				{
1060					// NOTE: We used to abort here, but pclose() started returning 
1061					// -1, possibly when the size of the DMG passed a certain point 
1062					llinfos << "Unexpected result closing pipe: " << result << llendl; 
1063				}
1064				mounter = NULL;
1065			}
1066		}
1067		
1068		if(!mountOutput.empty())
1069		{
1070			const char *s = mountOutput.c_str();
1071			const char *prefix = "/dev/";
1072			char *sub = strstr(s, prefix);
1073			
1074			if(sub != NULL)
1075			{
1076				sub += strlen(prefix);	/* Flawfinder: ignore */
1077				sscanf(sub, "%1023s", deviceNode);	/* Flawfinder: ignore */
1078			}
1079		}
1080		
1081		if(deviceNode[0] != 0)
1082		{
1083			llinfos << "Disk image attached on /dev/" << deviceNode << llendl;
1084		}
1085		else
1086		{
1087			llinfos << "Disk image device node not found!" << llendl;
1088			throw 0; 
1089		}
1090		
1091		// Get an FSRef to the new application on the disk image
1092		FSRef sourceRef;
1093		FSRef mountRef;
1094		snprintf(temp, sizeof(temp), "%s/mnt", tempDir);		
1095
1096		llinfos << "Disk image mount point is: " << temp << llendl;
1097
1098		err = FSPathMakeRef((UInt8 *)temp, &mountRef, NULL);
1099		if(err != noErr)
1100		{
1101			llinfos << "Couldn't make FSRef to disk image mount point." << llendl;
1102			throw 0;
1103		}
1104
1105		sendProgress(0, 0, CFSTR("Searching for the app bundle..."));
1106		err = findAppBundleOnDiskImage(&mountRef, &sourceRef);
1107		if(err != noErr)
1108		{
1109			llinfos << "Couldn't find application bundle on mounted disk image." << llendl;
1110			throw 0;
1111		}
1112		else
1113		{
1114			llinfos << "found the bundle." << llendl;
1115		}
1116
1117		sendProgress(0, 0, CFSTR("Preparing to copy files..."));
1118		
1119		FSRef asideRef;
1120		char aside[MAX_PATH];		/* Flawfinder: ignore */
1121		
1122		// this will hold the name of the destination target
1123		CFStringRef appNameRef;
1124
1125		if(replacingTarget)
1126		{
1127			// Get the name of the target we're replacing
1128			HFSUniStr255 appNameUniStr;
1129			err = FSGetCatalogInfo(&targetRef, 0, NULL, &appNameUniStr, NULL, NULL);
1130			if(err != noErr)
1131				throw 0;
1132			appNameRef = FSCreateStringFromHFSUniStr(NULL, &appNameUniStr);
1133			
1134			// Move aside old version (into work directory)
1135			err = FSMoveObject(&targetRef, &tempDirRef, &asideRef);
1136			if(err != noErr)
1137			{
1138				llwarns << "failed to move aside old version (error code " << 
1139					err << ")" << llendl;
1140				throw 0;
1141			}
1142
1143			// Grab the path for later use.
1144			err = FSRefMakePath(&asideRef, (UInt8*)aside, sizeof(aside));
1145		}
1146		else
1147		{
1148			// Construct the name of the target based on the product name
1149			char appName[MAX_PATH];		/* Flawfinder: ignore */
1150			snprintf(appName, sizeof(appName), "%s.app", gProductName);		
1151			appNameRef = CFStringCreateWithCString(NULL, appName, kCFStringEncodingUTF8);
1152		}
1153		
1154		sendProgress(0, 0, CFSTR("Copying files..."));
1155		
1156		llinfos << "Starting copy..." << llendl;
1157
1158		// Copy the new version from the disk image to the target location.
1159		err = FSCopyObjectSync(
1160				&sourceRef,
1161				&targetParentRef,
1162				appNameRef,
1163				&targetRef,
1164				kFSFileOperationDefaultOptions);
1165		
1166		// Grab the path for later use.
1167		err = FSRefMakePath(&targetRef, (UInt8*)target, sizeof(target));
1168		if(err != noErr)
1169			throw 0;
1170
1171		llinfos << "Copy complete. Target = " << target << llendl;
1172
1173		if(err != noErr)
1174		{
1175			// Something went wrong during the copy.  Attempt to put the old version back and bail.
1176			(void)FSDeleteObject(&targetRef);
1177			if(replacingTarget)
1178			{
1179				(void)FSMoveObject(&asideRef, &targetParentRef, NULL);
1180			}
1181			throw 0;
1182		}
1183		else
1184		{
1185			// The update has succeeded.  Clear the cache directory.
1186
1187			sendProgress(0, 0, CFSTR("Clearing cache..."));
1188	
1189			llinfos << "Clearing cache..." << llendl;
1190			
1191			char mask[LL_MAX_PATH];		/* Flawfinder: ignore */
1192			snprintf(mask, LL_MAX_PATH, "%s*.*", gDirUtilp->getDirDelimiter().c_str());		
1193			gDirUtilp->deleteFilesInDir(gDirUtilp->getExpandedFilename(LL_PATH_CACHE,""),mask);
1194			
1195			llinfos << "Clear complete." << llendl;
1196
1197		}
1198	}
1199	catch(...)
1200	{
1201		if(!gCancelled)
1202			if(gFailure == noErr)
1203				gFailure = -1;
1204	}
1205
1206	// Failures from here on out are all non-fatal and not reported.
1207	sendProgress(0, 3, CFSTR("Cleaning up..."));
1208
1209	// Close disk image file if necessary
1210	if(downloadFile != NULL)
1211	{
1212		llinfos << "Closing download file." << llendl;
1213
1214		fclose(downloadFile);
1215		downloadFile = NULL;
1216	}
1217
1218	sendProgress(1, 3);
1219	// Unmount image
1220	if(deviceNode[0] != 0)
1221	{
1222		llinfos << "Detaching disk image." << llendl;
1223
1224		snprintf(temp, sizeof(temp), "hdiutil detach '%s'", deviceNode);		
1225		system(temp);		/* Flawfinder: ignore */
1226	}
1227
1228	sendProgress(2, 3);
1229
1230	// Move work directory to the trash
1231	if(tempDir[0] != 0)
1232	{
1233		llinfos << "Moving work directory to the trash." << llendl;
1234
1235		FSRef trashRef;
1236		OSStatus err = FSMoveObjectToTrashSync(&tempDirRef, &trashRef, 0); 
1237		if(err != noErr) {
1238			llwarns << "failed to move files to trash, (error code " <<
1239				err << ")" << llendl;
1240		}
1241	}
1242	
1243	if(!gCancelled  && !gFailure && (target[0] != 0))
1244	{
1245		llinfos << "Touching application bundle." << llendl;
1246
1247		snprintf(temp, sizeof(temp), "touch '%s'", target);		
1248		system(temp);		/* Flawfinder: ignore */
1249
1250		llinfos << "Launching updated application." << llendl;
1251
1252		snprintf(temp, sizeof(temp), "open '%s'", target);		
1253		system(temp);		/* Flawfinder: ignore */
1254	}
1255
1256	sendDone();
1257	
1258	return(NULL);
1259}