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