Bumping manifests a=b2g-bump
[gecko.git] / toolkit / xre / nsUpdateDriver.cpp
blob0e48e598ffccd7805e01a386aa58542a864a3a3a
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim:set ts=2 sw=2 sts=2 et cindent: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include <stdlib.h>
8 #include <stdio.h>
9 #include "nsUpdateDriver.h"
10 #include "nsXULAppAPI.h"
11 #include "nsAppRunner.h"
12 #include "nsIWritablePropertyBag.h"
13 #include "nsIFile.h"
14 #include "nsIVariant.h"
15 #include "nsCOMPtr.h"
16 #include "nsString.h"
17 #include "prproces.h"
18 #include "prlog.h"
19 #include "prenv.h"
20 #include "nsVersionComparator.h"
21 #include "nsXREDirProvider.h"
22 #include "SpecialSystemDirectory.h"
23 #include "nsDirectoryServiceDefs.h"
24 #include "nsThreadUtils.h"
25 #include "nsIXULAppInfo.h"
26 #include "mozilla/Preferences.h"
27 #include "nsPrintfCString.h"
28 #include "mozilla/DebugOnly.h"
30 #ifdef XP_MACOSX
31 #include "nsILocalFileMac.h"
32 #include "nsCommandLineServiceMac.h"
33 #include "MacLaunchHelper.h"
34 #endif
36 #if defined(XP_WIN)
37 # include <direct.h>
38 # include <process.h>
39 # include <windows.h>
40 # include <shlwapi.h>
41 # include "nsWindowsHelpers.h"
42 # define getcwd(path, size) _getcwd(path, size)
43 # define getpid() GetCurrentProcessId()
44 #elif defined(XP_UNIX)
45 # include <unistd.h>
46 #endif
48 using namespace mozilla;
51 // We use execv to spawn the updater process on all UNIX systems except Mac OSX
52 // since it is known to cause problems on the Mac. Windows has execv, but it
53 // is a faked implementation that doesn't really replace the current process.
54 // Instead it spawns a new process, so we gain nothing from using execv on
55 // Windows.
57 // On platforms where we are not calling execv, we may need to make the
58 // updater executable wait for the calling process to exit. Otherwise, the
59 // updater may have trouble modifying our executable image (because it might
60 // still be in use). This is accomplished by passing our PID to the updater so
61 // that it can wait for us to exit. This is not perfect as there is a race
62 // condition that could bite us. It's possible that the calling process could
63 // exit before the updater waits on the specified PID, and in the meantime a
64 // new process with the same PID could be created. This situation is unlikely,
65 // however, given the way most operating systems recycle PIDs. We'll take our
66 // chances ;-)
68 // A similar #define lives in updater.cpp and should be kept in sync with this.
70 #if defined(XP_UNIX) && !defined(XP_MACOSX)
71 #define USE_EXECV
72 #endif
74 #ifdef PR_LOGGING
75 static PRLogModuleInfo *
76 GetUpdateLog()
78 static PRLogModuleInfo *sUpdateLog;
79 if (!sUpdateLog)
80 sUpdateLog = PR_NewLogModule("updatedriver");
81 return sUpdateLog;
83 #endif
84 #define LOG(args) PR_LOG(GetUpdateLog(), PR_LOG_DEBUG, args)
86 #ifdef XP_WIN
87 static const char kUpdaterBin[] = "updater.exe";
88 #else
89 static const char kUpdaterBin[] = "updater";
90 #endif
91 static const char kUpdaterINI[] = "updater.ini";
92 #ifdef XP_MACOSX
93 static const char kUpdaterApp[] = "updater.app";
94 #endif
95 #if defined(XP_UNIX) && !defined(XP_MACOSX)
96 static const char kUpdaterPNG[] = "updater.png";
97 #endif
99 #if defined(MOZ_WIDGET_GONK)
100 #include <linux/ioprio.h>
102 static const int kB2GServiceArgc = 2;
103 static const char *kB2GServiceArgv[] = { "/system/bin/start", "b2g" };
105 static const char kAppUpdaterPrio[] = "app.update.updater.prio";
106 static const char kAppUpdaterOomScoreAdj[] = "app.update.updater.oom_score_adj";
107 static const char kAppUpdaterIOPrioClass[] = "app.update.updater.ioprio.class";
108 static const char kAppUpdaterIOPrioLevel[] = "app.update.updater.ioprio.level";
110 static const int kAppUpdaterPrioDefault = 19; // -20..19 where 19 = lowest priority
111 static const int kAppUpdaterOomScoreAdjDefault = -1000; // -1000 = Never kill
112 static const int kAppUpdaterIOPrioClassDefault = IOPRIO_CLASS_IDLE;
113 static const int kAppUpdaterIOPrioLevelDefault = 0; // Doesn't matter for CLASS IDLE
114 #endif
116 static nsresult
117 GetCurrentWorkingDir(char *buf, size_t size)
119 // Cannot use NS_GetSpecialDirectory because XPCOM is not yet initialized.
120 // This code is duplicated from xpcom/io/SpecialSystemDirectory.cpp:
122 #if defined(XP_WIN)
123 wchar_t wpath[MAX_PATH];
124 if (!_wgetcwd(wpath, size))
125 return NS_ERROR_FAILURE;
126 NS_ConvertUTF16toUTF8 path(wpath);
127 strncpy(buf, path.get(), size);
128 #else
129 if(!getcwd(buf, size))
130 return NS_ERROR_FAILURE;
131 #endif
132 return NS_OK;
136 * Get the path to the installation directory. For Mac OS X this will be the
137 * bundle directory.
139 * @param appDir the application directory file object
140 * @param installDirPath the path to the installation directory
142 static nsresult
143 GetInstallDirPath(nsIFile *appDir, nsACString& installDirPath)
145 nsresult rv;
146 #ifdef XP_MACOSX
147 nsCOMPtr<nsIFile> parentDir1, parentDir2;
148 rv = appDir->GetParent(getter_AddRefs(parentDir1));
149 if (NS_FAILED(rv)) {
150 return rv;
152 rv = parentDir1->GetParent(getter_AddRefs(parentDir2));
153 if (NS_FAILED(rv)) {
154 return rv;
156 rv = parentDir2->GetNativePath(installDirPath);
157 #elif XP_WIN
158 nsAutoString installDirPathW;
159 rv = appDir->GetPath(installDirPathW);
160 if (NS_FAILED(rv)) {
161 return rv;
163 installDirPath = NS_ConvertUTF16toUTF8(installDirPathW);
164 #else
165 rv = appDir->GetNativePath(installDirPath);
166 #endif
167 if (NS_FAILED(rv)) {
168 return rv;
170 return NS_OK;
173 #if defined(XP_MACOSX)
174 // This is a copy of OS X's XRE_GetBinaryPath from nsAppRunner.cpp with the
175 // gBinaryPath check removed so that the updater can reload the stub executable
176 // instead of xulrunner-bin. See bug 349737.
177 static nsresult
178 GetXULRunnerStubPath(const char* argv0, nsIFile* *aResult)
180 // Works even if we're not bundled.
181 CFBundleRef appBundle = ::CFBundleGetMainBundle();
182 if (!appBundle)
183 return NS_ERROR_FAILURE;
185 CFURLRef bundleURL = ::CFBundleCopyExecutableURL(appBundle);
186 if (!bundleURL)
187 return NS_ERROR_FAILURE;
189 nsCOMPtr<nsILocalFileMac> lfm;
190 nsresult rv = NS_NewLocalFileWithCFURL(bundleURL, true, getter_AddRefs(lfm));
192 ::CFRelease(bundleURL);
194 if (NS_FAILED(rv))
195 return rv;
197 NS_ADDREF(*aResult = static_cast<nsIFile*>(lfm.get()));
198 return NS_OK;
200 #endif /* XP_MACOSX */
202 static bool
203 GetFile(nsIFile *dir, const nsCSubstring &name, nsCOMPtr<nsIFile> &result)
205 nsresult rv;
207 nsCOMPtr<nsIFile> file;
208 rv = dir->Clone(getter_AddRefs(file));
209 if (NS_FAILED(rv))
210 return false;
212 rv = file->AppendNative(name);
213 if (NS_FAILED(rv))
214 return false;
216 result = do_QueryInterface(file, &rv);
217 return NS_SUCCEEDED(rv);
220 static bool
221 GetStatusFile(nsIFile *dir, nsCOMPtr<nsIFile> &result)
223 return GetFile(dir, NS_LITERAL_CSTRING("update.status"), result);
227 * Get the contents of the update.status file.
229 * @param statusFile the status file object.
230 * @param buf the buffer holding the file contents
232 * @return true if successful, false otherwise.
234 template <size_t Size>
235 static bool
236 GetStatusFileContents(nsIFile *statusFile, char (&buf)[Size])
238 // The buffer needs to be large enough to hold the known status codes
239 PR_STATIC_ASSERT(Size > 16);
241 PRFileDesc *fd = nullptr;
242 nsresult rv = statusFile->OpenNSPRFileDesc(PR_RDONLY, 0660, &fd);
243 if (NS_FAILED(rv))
244 return false;
246 const int32_t n = PR_Read(fd, buf, Size);
247 PR_Close(fd);
249 return (n >= 0);
252 typedef enum {
253 eNoUpdateAction,
254 ePendingUpdate,
255 ePendingService,
256 eAppliedUpdate,
257 eAppliedService
258 } UpdateStatus;
261 * Returns a value indicating what needs to be done in order to handle an update.
263 * @param dir the directory in which we should look for an update.status file.
264 * @param statusFile the update.status file found in the directory.
266 * @return the update action to be performed.
268 static UpdateStatus
269 GetUpdateStatus(nsIFile* dir, nsCOMPtr<nsIFile> &statusFile)
271 if (GetStatusFile(dir, statusFile)) {
272 char buf[32];
273 if (GetStatusFileContents(statusFile, buf)) {
274 const char kPending[] = "pending";
275 const char kPendingService[] = "pending-service";
276 const char kApplied[] = "applied";
277 const char kAppliedService[] = "applied-service";
278 if (!strncmp(buf, kPendingService, sizeof(kPendingService) - 1)) {
279 return ePendingService;
281 if (!strncmp(buf, kPending, sizeof(kPending) - 1)) {
282 return ePendingUpdate;
284 if (!strncmp(buf, kAppliedService, sizeof(kAppliedService) - 1)) {
285 return eAppliedService;
287 if (!strncmp(buf, kApplied, sizeof(kApplied) - 1)) {
288 return eAppliedUpdate;
292 return eNoUpdateAction;
295 static bool
296 GetVersionFile(nsIFile *dir, nsCOMPtr<nsIFile> &result)
298 return GetFile(dir, NS_LITERAL_CSTRING("update.version"), result);
301 // Compares the current application version with the update's application
302 // version.
303 static bool
304 IsOlderVersion(nsIFile *versionFile, const char *appVersion)
306 PRFileDesc *fd = nullptr;
307 nsresult rv = versionFile->OpenNSPRFileDesc(PR_RDONLY, 0660, &fd);
308 if (NS_FAILED(rv))
309 return true;
311 char buf[32];
312 const int32_t n = PR_Read(fd, buf, sizeof(buf));
313 PR_Close(fd);
315 if (n < 0)
316 return false;
318 // Trim off the trailing newline
319 if (buf[n - 1] == '\n')
320 buf[n - 1] = '\0';
322 // If the update xml doesn't provide the application version the file will
323 // contain the string "null" and it is assumed that the update is not older.
324 const char kNull[] = "null";
325 if (strncmp(buf, kNull, sizeof(kNull) - 1) == 0)
326 return false;
328 if (mozilla::Version(appVersion) > buf)
329 return true;
331 return false;
334 #if defined(XP_WIN) && defined(MOZ_METRO)
335 static bool
336 IsWindowsMetroUpdateRequest(int appArgc, char **appArgv)
338 for (int index = 0; index < appArgc; index++) {
339 if (!strcmp(appArgv[index], "--metro-update")) {
340 return true;
343 return false;
345 #endif
347 static bool
348 CopyFileIntoUpdateDir(nsIFile *parentDir, const char *leafName, nsIFile *updateDir)
350 nsDependentCString leaf(leafName);
351 nsCOMPtr<nsIFile> file;
353 // Make sure there is not an existing file in the target location.
354 nsresult rv = updateDir->Clone(getter_AddRefs(file));
355 if (NS_FAILED(rv))
356 return false;
357 rv = file->AppendNative(leaf);
358 if (NS_FAILED(rv))
359 return false;
360 file->Remove(true);
362 // Now, copy into the target location.
363 rv = parentDir->Clone(getter_AddRefs(file));
364 if (NS_FAILED(rv))
365 return false;
366 rv = file->AppendNative(leaf);
367 if (NS_FAILED(rv))
368 return false;
369 rv = file->CopyToNative(updateDir, EmptyCString());
370 if (NS_FAILED(rv))
371 return false;
373 return true;
376 static bool
377 CopyUpdaterIntoUpdateDir(nsIFile *greDir, nsIFile *appDir, nsIFile *updateDir,
378 nsCOMPtr<nsIFile> &updater)
380 // Copy the updater application from the GRE and the updater ini from the app
381 #if defined(XP_MACOSX)
382 if (!CopyFileIntoUpdateDir(appDir, kUpdaterApp, updateDir))
383 return false;
384 CopyFileIntoUpdateDir(greDir, kUpdaterINI, updateDir);
385 #else
386 if (!CopyFileIntoUpdateDir(greDir, kUpdaterBin, updateDir))
387 return false;
388 CopyFileIntoUpdateDir(appDir, kUpdaterINI, updateDir);
389 #endif
390 #if defined(XP_UNIX) && !defined(XP_MACOSX) && !defined(ANDROID)
391 nsCOMPtr<nsIFile> iconDir;
392 appDir->Clone(getter_AddRefs(iconDir));
393 iconDir->AppendNative(NS_LITERAL_CSTRING("icons"));
394 if (!CopyFileIntoUpdateDir(iconDir, kUpdaterPNG, updateDir))
395 return false;
396 #endif
397 // Finally, return the location of the updater binary.
398 nsresult rv = updateDir->Clone(getter_AddRefs(updater));
399 if (NS_FAILED(rv))
400 return false;
401 #if defined(XP_MACOSX)
402 rv = updater->AppendNative(NS_LITERAL_CSTRING(kUpdaterApp));
403 nsresult tmp = updater->AppendNative(NS_LITERAL_CSTRING("Contents"));
404 if (NS_FAILED(tmp)) {
405 rv = tmp;
407 tmp = updater->AppendNative(NS_LITERAL_CSTRING("MacOS"));
408 if (NS_FAILED(tmp) || NS_FAILED(rv))
409 return false;
410 #endif
411 rv = updater->AppendNative(NS_LITERAL_CSTRING(kUpdaterBin));
412 return NS_SUCCEEDED(rv);
416 * Switch an existing application directory to an updated version that has been
417 * staged.
419 * @param greDir the GRE dir
420 * @param updateDir the update root dir
421 * @param statusFile the update.status file
422 * @param appDir the app dir
423 * @param appArgc the number of args to the application
424 * @param appArgv the args to the application, used for restarting if needed
426 static void
427 SwitchToUpdatedApp(nsIFile *greDir, nsIFile *updateDir, nsIFile *statusFile,
428 nsIFile *appDir, int appArgc, char **appArgv)
430 nsresult rv;
432 // Steps:
433 // - copy updater into temp dir
434 // - run updater with the correct arguments
436 nsCOMPtr<nsIFile> tmpDir;
437 GetSpecialSystemDirectory(OS_TemporaryDirectory,
438 getter_AddRefs(tmpDir));
439 if (!tmpDir) {
440 LOG(("failed getting a temp dir\n"));
441 return;
444 // Try to create our own new temp directory in case there is already an
445 // updater binary in the OS temporary location which we cannot write to.
446 // Note that we don't check for errors here, as if this directory can't
447 // be created, the following CopyUpdaterIntoUpdateDir call will fail.
448 // We create the unique directory inside a subfolder of MozUpdater instead
449 // of directly in the temp directory so we can efficiently delete everything
450 // after updates.
451 tmpDir->Append(NS_LITERAL_STRING("MozUpdater"));
452 tmpDir->Append(NS_LITERAL_STRING("bgupdate"));
453 tmpDir->CreateUnique(nsIFile::DIRECTORY_TYPE, 0755);
455 nsCOMPtr<nsIFile> updater;
456 if (!CopyUpdaterIntoUpdateDir(greDir, appDir, tmpDir, updater)) {
457 LOG(("failed copying updater\n"));
458 return;
461 // We need to use the value returned from XRE_GetBinaryPath when attempting
462 // to restart the running application.
463 nsCOMPtr<nsIFile> appFile;
465 #if defined(XP_MACOSX)
466 // On OS X we need to pass the location of the xulrunner-stub executable
467 // rather than xulrunner-bin. See bug 349737.
468 GetXULRunnerStubPath(appArgv[0], getter_AddRefs(appFile));
469 #else
470 XRE_GetBinaryPath(appArgv[0], getter_AddRefs(appFile));
471 #endif
473 if (!appFile)
474 return;
476 #ifdef XP_WIN
477 nsAutoString appFilePathW;
478 rv = appFile->GetPath(appFilePathW);
479 if (NS_FAILED(rv))
480 return;
481 NS_ConvertUTF16toUTF8 appFilePath(appFilePathW);
483 nsAutoString updaterPathW;
484 rv = updater->GetPath(updaterPathW);
485 if (NS_FAILED(rv))
486 return;
488 NS_ConvertUTF16toUTF8 updaterPath(updaterPathW);
489 #else
491 nsAutoCString appFilePath;
492 #if defined(MOZ_WIDGET_GONK)
493 appFilePath.Assign(kB2GServiceArgv[0]);
494 appArgc = kB2GServiceArgc;
495 appArgv = const_cast<char**>(kB2GServiceArgv);
496 #else
497 rv = appFile->GetNativePath(appFilePath);
498 if (NS_FAILED(rv))
499 return;
500 #endif
502 nsAutoCString updaterPath;
503 rv = updater->GetNativePath(updaterPath);
504 if (NS_FAILED(rv))
505 return;
506 #endif
508 nsAutoCString installDirPath;
509 rv = GetInstallDirPath(appDir, installDirPath);
510 if (NS_FAILED(rv)) {
511 return;
514 // Get the directory where the update will be staged.
515 nsAutoCString applyToDir;
516 nsCOMPtr<nsIFile> updatedDir;
517 #ifdef XP_MACOSX
518 if (!GetFile(updateDir, NS_LITERAL_CSTRING("Updated.app"), updatedDir)) {
519 #else
520 if (!GetFile(appDir, NS_LITERAL_CSTRING("updated"), updatedDir)) {
521 #endif
522 return;
524 #ifdef XP_WIN
525 nsAutoString applyToDirW;
526 rv = updatedDir->GetPath(applyToDirW);
527 if (NS_FAILED(rv)) {
528 return;
530 applyToDir = NS_ConvertUTF16toUTF8(applyToDirW);
531 #else
532 rv = updatedDir->GetNativePath(applyToDir);
533 #endif
534 if (NS_FAILED(rv)) {
535 return;
538 // Make sure that the updated directory exists
539 bool updatedDirExists = false;
540 updatedDir->Exists(&updatedDirExists);
541 if (!updatedDirExists) {
542 return;
545 #if defined(XP_WIN)
546 nsAutoString updateDirPathW;
547 rv = updateDir->GetPath(updateDirPathW);
548 NS_ConvertUTF16toUTF8 updateDirPath(updateDirPathW);
549 #else
550 nsAutoCString updateDirPath;
551 rv = updateDir->GetNativePath(updateDirPath);
552 #endif
553 if (NS_FAILED(rv))
554 return;
556 // Get the current working directory.
557 char workingDirPath[MAXPATHLEN];
558 rv = GetCurrentWorkingDir(workingDirPath, sizeof(workingDirPath));
559 if (NS_FAILED(rv))
560 return;
562 // Construct the PID argument for this process. If we are using execv, then
563 // we pass "0" which is then ignored by the updater.
564 #if defined(USE_EXECV)
565 nsAutoCString pid("0");
566 #else
567 nsAutoCString pid;
568 pid.AppendInt((int32_t) getpid());
569 #endif
571 // Append a special token to the PID in order to let the updater know that it
572 // just needs to replace the update directory.
573 pid.AppendLiteral("/replace");
575 int immersiveArgc = 0;
576 #if defined(XP_WIN) && defined(MOZ_METRO)
577 // If this is desktop doing an update for metro, or if we're the metro browser
578 // we want to launch the metro browser after we're finished.
579 if (IsWindowsMetroUpdateRequest(appArgc, appArgv) || IsRunningInWindowsMetro()) {
580 immersiveArgc = 1;
582 #endif
583 int argc = appArgc + 6 + immersiveArgc;
584 char **argv = new char*[argc + 1];
585 if (!argv)
586 return;
587 argv[0] = (char*) updaterPath.get();
588 argv[1] = (char*) updateDirPath.get();
589 argv[2] = (char*) installDirPath.get();
590 argv[3] = (char*) applyToDir.get();
591 argv[4] = (char*) pid.get();
592 if (appArgc) {
593 argv[5] = workingDirPath;
594 argv[6] = (char*) appFilePath.get();
595 for (int i = 1; i < appArgc; ++i)
596 argv[6 + i] = appArgv[i];
597 #ifdef XP_WIN
598 if (immersiveArgc) {
599 argv[argc - 1] = "-ServerName:DefaultBrowserServer";
601 #endif
602 argv[argc] = nullptr;
603 } else {
604 argc = 5;
605 argv[5] = nullptr;
608 if (gSafeMode) {
609 PR_SetEnv("MOZ_SAFE_MODE_RESTART=1");
612 LOG(("spawning updater process for replacing [%s]\n", updaterPath.get()));
614 #if defined(USE_EXECV)
615 # if defined(MOZ_WIDGET_GONK)
616 // In Gonk, we preload libmozglue, which the updater process doesn't need.
617 // Since the updater will move and delete libmozglue.so, this can actually
618 // stop the /system mount from correctly being remounted as read-only.
619 unsetenv("LD_PRELOAD");
620 # endif
621 execv(updaterPath.get(), argv);
622 #elif defined(XP_WIN)
623 // Switch the application using updater.exe
624 if (!WinLaunchChild(updaterPathW.get(), argc, argv)) {
625 return;
627 _exit(0);
628 #elif defined(XP_MACOSX)
629 CommandLineServiceMac::SetupMacCommandLine(argc, argv, true);
630 // LaunchChildMac uses posix_spawnp and prefers the current
631 // architecture when launching. It doesn't require a
632 // null-terminated string but it doesn't matter if we pass one.
633 LaunchChildMac(argc, argv);
634 exit(0);
635 #else
636 PR_CreateProcessDetached(updaterPath.get(), argv, nullptr, nullptr);
637 exit(0);
638 #endif
641 #if defined(MOZ_WIDGET_GONK)
642 static nsresult
643 GetOSApplyToDir(nsACString& applyToDir)
645 nsCOMPtr<nsIProperties> ds =
646 do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID);
647 NS_ASSERTION(ds, "Can't get directory service");
649 nsCOMPtr<nsIFile> osApplyToDir;
650 nsresult rv = ds->Get(XRE_OS_UPDATE_APPLY_TO_DIR, NS_GET_IID(nsIFile),
651 getter_AddRefs(osApplyToDir));
652 if (NS_FAILED(rv)) {
653 LOG(("Can't get the OS applyTo dir"));
654 return rv;
657 return osApplyToDir->GetNativePath(applyToDir);
660 static void
661 SetOSApplyToDir(nsIUpdate* update, const nsACString& osApplyToDir)
663 nsresult rv;
664 nsCOMPtr<nsIWritablePropertyBag> updateProperties =
665 do_QueryInterface(update, &rv);
667 if (NS_FAILED(rv)) {
668 return;
671 nsCOMPtr<nsIWritableVariant> variant =
672 do_CreateInstance("@mozilla.org/variant;1", &rv);
673 if (NS_FAILED(rv)) {
674 return;
677 rv = variant->SetAsACString(osApplyToDir);
678 if (NS_FAILED(rv)) {
679 return;
682 updateProperties->SetProperty(NS_LITERAL_STRING("osApplyToDir"), variant);
684 #endif
687 * Apply an update. This applies to both normal and staged updates.
689 * @param greDir the GRE dir
690 * @param updateDir the update root dir
691 * @param statusFile the update.status file
692 * @param appDir the app dir
693 * @param appArgc the number of args to the application
694 * @param appArgv the args to the application, used for restarting if needed
695 * @param restart if true, apply the update in the foreground and restart the
696 * application when done. otherwise, stage the update and don't
697 * restart the application.
698 * @param outpid out parameter holding the handle to the updater application for
699 * staging updates.
701 static void
702 ApplyUpdate(nsIFile *greDir, nsIFile *updateDir, nsIFile *statusFile,
703 nsIFile *appDir, int appArgc, char **appArgv,
704 bool restart, bool isOSUpdate, nsIFile *osApplyToDir,
705 ProcessType *outpid)
707 nsresult rv;
709 // Steps:
710 // - mark update as 'applying'
711 // - copy updater into update dir
712 // - run updater w/ appDir as the current working dir
714 nsCOMPtr<nsIFile> updater;
715 if (!CopyUpdaterIntoUpdateDir(greDir, appDir, updateDir, updater)) {
716 LOG(("failed copying updater\n"));
717 return;
720 // We need to use the value returned from XRE_GetBinaryPath when attempting
721 // to restart the running application.
722 nsCOMPtr<nsIFile> appFile;
724 #if defined(XP_MACOSX)
725 // On OS X we need to pass the location of the xulrunner-stub executable
726 // rather than xulrunner-bin. See bug 349737.
727 GetXULRunnerStubPath(appArgv[0], getter_AddRefs(appFile));
728 #else
729 XRE_GetBinaryPath(appArgv[0], getter_AddRefs(appFile));
730 #endif
732 if (!appFile)
733 return;
735 #ifdef XP_WIN
736 nsAutoString appFilePathW;
737 rv = appFile->GetPath(appFilePathW);
738 if (NS_FAILED(rv))
739 return;
740 NS_ConvertUTF16toUTF8 appFilePath(appFilePathW);
742 nsAutoString updaterPathW;
743 rv = updater->GetPath(updaterPathW);
744 if (NS_FAILED(rv))
745 return;
747 NS_ConvertUTF16toUTF8 updaterPath(updaterPathW);
749 #else
750 nsAutoCString appFilePath;
751 rv = appFile->GetNativePath(appFilePath);
752 if (NS_FAILED(rv))
753 return;
755 nsAutoCString updaterPath;
756 rv = updater->GetNativePath(updaterPath);
757 if (NS_FAILED(rv))
758 return;
760 #endif
762 nsAutoCString installDirPath;
763 rv = GetInstallDirPath(appDir, installDirPath);
764 if (NS_FAILED(rv))
765 return;
767 // Get the directory where the update was staged for replace and GONK OS
768 // Updates or where it will be applied.
769 #ifndef MOZ_WIDGET_GONK
770 // OS Updates are only supported on GONK so force it to false on everything
771 // but GONK to simplify the following logic.
772 isOSUpdate = false;
773 #endif
774 nsAutoCString applyToDir;
775 nsCOMPtr<nsIFile> updatedDir;
776 if (restart && !isOSUpdate) {
777 applyToDir.Assign(installDirPath);
778 } else {
779 #ifdef XP_MACOSX
780 if (!GetFile(updateDir, NS_LITERAL_CSTRING("Updated.app"), updatedDir)) {
781 #else
782 if (!GetFile(appDir, NS_LITERAL_CSTRING("updated"), updatedDir)) {
783 #endif
784 return;
786 #ifdef XP_WIN
787 nsAutoString applyToDirW;
788 rv = updatedDir->GetPath(applyToDirW);
789 if (NS_FAILED(rv)) {
790 return;
792 applyToDir = NS_ConvertUTF16toUTF8(applyToDirW);
793 #elif MOZ_WIDGET_GONK
794 if (isOSUpdate) {
795 if (!osApplyToDir) {
796 return;
798 rv = osApplyToDir->GetNativePath(applyToDir);
799 } else {
800 rv = updatedDir->GetNativePath(applyToDir);
802 #else
803 rv = updatedDir->GetNativePath(applyToDir);
804 #endif
806 if (NS_FAILED(rv))
807 return;
809 #if defined(XP_WIN)
810 nsAutoString updateDirPathW;
811 rv = updateDir->GetPath(updateDirPathW);
812 NS_ConvertUTF16toUTF8 updateDirPath(updateDirPathW);
813 #else
814 nsAutoCString updateDirPath;
815 rv = updateDir->GetNativePath(updateDirPath);
816 #endif
817 if (NS_FAILED(rv)) {
818 return;
821 // Get the current working directory.
822 char workingDirPath[MAXPATHLEN];
823 rv = GetCurrentWorkingDir(workingDirPath, sizeof(workingDirPath));
824 if (NS_FAILED(rv))
825 return;
827 // We used to write out "Applying" to the update.status file here.
828 // Instead we do this from within the updater application now.
829 // This is so that we don't overwrite the status of pending-service
830 // in the Windows case. This change was made for all platforms so
831 // that it stays consistent across all OS.
833 // Construct the PID argument for this process. If we are using execv, then
834 // we pass "0" which is then ignored by the updater.
835 nsAutoCString pid;
836 if (!restart) {
837 // Signal the updater application that it should stage the update.
838 pid.AssignASCII("-1");
839 } else {
840 #if defined(USE_EXECV)
841 pid.AssignASCII("0");
842 #else
843 pid.AppendInt((int32_t) getpid());
844 #endif
847 int immersiveArgc = 0;
848 #if defined(XP_WIN) && defined(MOZ_METRO)
849 // If this is desktop doing an update for metro, or if we're the metro browser
850 // we want to launch the metro browser after we're finished.
851 if (IsWindowsMetroUpdateRequest(appArgc, appArgv) || IsRunningInWindowsMetro()) {
852 immersiveArgc = 1;
854 #endif
855 int argc = appArgc + 6 + immersiveArgc;
856 char **argv = new char*[argc + 1 ];
857 if (!argv)
858 return;
859 argv[0] = (char*) updaterPath.get();
860 argv[1] = (char*) updateDirPath.get();
861 argv[2] = (char*) installDirPath.get();
862 argv[3] = (char*) applyToDir.get();
863 argv[4] = (char*) pid.get();
864 if (restart && appArgc) {
865 argv[5] = workingDirPath;
866 argv[6] = (char*) appFilePath.get();
867 for (int i = 1; i < appArgc; ++i)
868 argv[6 + i] = appArgv[i];
869 #ifdef XP_WIN
870 if (immersiveArgc) {
871 argv[argc - 1] = "-ServerName:DefaultBrowserServer";
873 #endif
874 argv[argc] = nullptr;
875 } else {
876 argc = 5;
877 argv[5] = nullptr;
880 if (gSafeMode) {
881 PR_SetEnv("MOZ_SAFE_MODE_RESTART=1");
884 if (isOSUpdate) {
885 PR_SetEnv("MOZ_OS_UPDATE=1");
887 #if defined(MOZ_WIDGET_GONK)
888 // We want the updater to be CPU friendly and not subject to being killed by
889 // the low memory killer, so we pass in some preferences to allow it to
890 // adjust its priority.
892 int32_t prioVal = Preferences::GetInt(kAppUpdaterPrio,
893 kAppUpdaterPrioDefault);
894 int32_t oomScoreAdj = Preferences::GetInt(kAppUpdaterOomScoreAdj,
895 kAppUpdaterOomScoreAdjDefault);
896 int32_t ioprioClass = Preferences::GetInt(kAppUpdaterIOPrioClass,
897 kAppUpdaterIOPrioClassDefault);
898 int32_t ioprioLevel = Preferences::GetInt(kAppUpdaterIOPrioLevel,
899 kAppUpdaterIOPrioLevelDefault);
900 nsPrintfCString prioEnv("MOZ_UPDATER_PRIO=%d/%d/%d/%d",
901 prioVal, oomScoreAdj, ioprioClass, ioprioLevel);
902 PR_SetEnv(prioEnv.get());
903 #endif
905 LOG(("spawning updater process [%s]\n", updaterPath.get()));
907 #if defined(USE_EXECV)
908 // Don't use execv when staging updates.
909 if (restart) {
910 execv(updaterPath.get(), argv);
911 } else {
912 *outpid = PR_CreateProcess(updaterPath.get(), argv, nullptr, nullptr);
914 #elif defined(XP_WIN)
915 // Launch the update using updater.exe
916 if (!WinLaunchChild(updaterPathW.get(), argc, argv, nullptr, outpid)) {
917 return;
920 if (restart) {
921 // We are going to process an update so we should exit now
922 _exit(0);
924 #elif defined(XP_MACOSX)
925 CommandLineServiceMac::SetupMacCommandLine(argc, argv, true);
926 // LaunchChildMac uses posix_spawnp and prefers the current
927 // architecture when launching. It doesn't require a
928 // null-terminated string but it doesn't matter if we pass one.
929 LaunchChildMac(argc, argv, 0, outpid);
930 if (restart) {
931 exit(0);
933 #else
934 *outpid = PR_CreateProcess(updaterPath.get(), argv, nullptr, nullptr);
935 if (restart) {
936 exit(0);
938 #endif
942 * Wait for a process until it terminates. This call is blocking.
944 static void
945 WaitForProcess(ProcessType pt)
947 #if defined(XP_WIN)
948 WaitForSingleObject(pt, INFINITE);
949 CloseHandle(pt);
950 #elif defined(XP_MACOSX)
951 waitpid(pt, 0, 0);
952 #else
953 int32_t exitCode;
954 PR_WaitProcess(pt, &exitCode);
955 if (exitCode != 0) {
956 LOG(("Error while running the updater process, check update.log"));
958 #endif
961 nsresult
962 ProcessUpdates(nsIFile *greDir, nsIFile *appDir, nsIFile *updRootDir,
963 int argc, char **argv, const char *appVersion,
964 bool restart, bool isOSUpdate, nsIFile *osApplyToDir,
965 ProcessType *pid)
967 nsresult rv;
969 nsCOMPtr<nsIFile> updatesDir;
970 rv = updRootDir->Clone(getter_AddRefs(updatesDir));
971 if (NS_FAILED(rv))
972 return rv;
974 rv = updatesDir->AppendNative(NS_LITERAL_CSTRING("updates"));
975 if (NS_FAILED(rv))
976 return rv;
978 rv = updatesDir->AppendNative(NS_LITERAL_CSTRING("0"));
979 if (NS_FAILED(rv))
980 return rv;
982 ProcessType dummyPID; // this will only be used for MOZ_UPDATE_STAGING
983 const char *processingUpdates = PR_GetEnv("MOZ_PROCESS_UPDATES");
984 if (processingUpdates && *processingUpdates) {
985 // Enable the tests to request an update to be staged.
986 const char *stagingUpdate = PR_GetEnv("MOZ_UPDATE_STAGING");
987 if (stagingUpdate && *stagingUpdate) {
988 restart = false;
989 pid = &dummyPID;
993 nsCOMPtr<nsIFile> statusFile;
994 UpdateStatus status = GetUpdateStatus(updatesDir, statusFile);
995 switch (status) {
996 case ePendingUpdate:
997 case ePendingService: {
998 nsCOMPtr<nsIFile> versionFile;
999 // Remove the update if the update application version file doesn't exist
1000 // or if the update's application version is less than the current
1001 // application version.
1002 if (!GetVersionFile(updatesDir, versionFile) ||
1003 IsOlderVersion(versionFile, appVersion)) {
1004 updatesDir->Remove(true);
1005 } else {
1006 ApplyUpdate(greDir, updatesDir, statusFile,
1007 appDir, argc, argv, restart, isOSUpdate, osApplyToDir, pid);
1009 break;
1011 case eAppliedUpdate:
1012 case eAppliedService:
1013 // An update was staged and needs to be switched so the updated application
1014 // is used.
1015 SwitchToUpdatedApp(greDir, updatesDir, statusFile,
1016 appDir, argc, argv);
1017 break;
1018 case eNoUpdateAction:
1019 // We don't need to do any special processing here, we'll just continue to
1020 // startup the application.
1021 break;
1024 return NS_OK;
1029 NS_IMPL_ISUPPORTS(nsUpdateProcessor, nsIUpdateProcessor)
1031 nsUpdateProcessor::nsUpdateProcessor()
1032 : mUpdaterPID(0)
1036 nsUpdateProcessor::~nsUpdateProcessor()
1040 NS_IMETHODIMP
1041 nsUpdateProcessor::ProcessUpdate(nsIUpdate* aUpdate)
1043 nsCOMPtr<nsIFile> greDir, appDir, updRoot;
1044 nsAutoCString appVersion;
1045 int argc;
1046 char **argv;
1048 nsAutoCString binPath;
1049 nsXREDirProvider* dirProvider = nsXREDirProvider::GetSingleton();
1050 if (dirProvider) { // Normal code path
1051 // Check for and process any available updates
1052 bool persistent;
1053 nsresult rv = NS_ERROR_FAILURE; // Take the NS_FAILED path when non-GONK
1054 #ifdef MOZ_WIDGET_GONK
1055 // Check in the sdcard for updates first, since that's our preferred
1056 // download location.
1057 rv = dirProvider->GetFile(XRE_UPDATE_ARCHIVE_DIR, &persistent,
1058 getter_AddRefs(updRoot));
1059 #endif
1060 if (NS_FAILED(rv)) {
1061 rv = dirProvider->GetFile(XRE_UPDATE_ROOT_DIR, &persistent,
1062 getter_AddRefs(updRoot));
1064 // XRE_UPDATE_ROOT_DIR may fail. Fallback to appDir if failed
1065 if (NS_FAILED(rv))
1066 updRoot = dirProvider->GetAppDir();
1068 greDir = dirProvider->GetGREDir();
1069 nsCOMPtr<nsIFile> exeFile;
1070 rv = dirProvider->GetFile(XRE_EXECUTABLE_FILE, &persistent,
1071 getter_AddRefs(exeFile));
1072 if (NS_SUCCEEDED(rv))
1073 rv = exeFile->GetParent(getter_AddRefs(appDir));
1075 if (NS_FAILED(rv))
1076 appDir = dirProvider->GetAppDir();
1078 appVersion = gAppData->version;
1079 argc = gRestartArgc;
1080 argv = gRestartArgv;
1081 } else {
1082 // In the xpcshell environment, the usual XRE_main is not run, so things
1083 // like dirProvider and gAppData do not exist. This code path accesses
1084 // XPCOM (which is not available in the previous code path) in order to get
1085 // the same information.
1086 nsCOMPtr<nsIProperties> ds =
1087 do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID);
1088 if (!ds) {
1089 NS_ABORT(); // There's nothing which we can do if this fails!
1092 nsresult rv = ds->Get(NS_GRE_DIR, NS_GET_IID(nsIFile),
1093 getter_AddRefs(greDir));
1094 NS_ASSERTION(NS_SUCCEEDED(rv), "Can't get the GRE dir");
1096 nsCOMPtr<nsIFile> exeFile;
1097 rv = ds->Get(XRE_EXECUTABLE_FILE, NS_GET_IID(nsIFile),
1098 getter_AddRefs(exeFile));
1099 if (NS_SUCCEEDED(rv))
1100 rv = exeFile->GetParent(getter_AddRefs(appDir));
1102 NS_ASSERTION(NS_SUCCEEDED(rv), "Can't get the XREExeF parent dir");
1104 rv = ds->Get(XRE_UPDATE_ROOT_DIR, NS_GET_IID(nsIFile),
1105 getter_AddRefs(updRoot));
1106 NS_ASSERTION(NS_SUCCEEDED(rv), "Can't get the UpdRootD dir");
1108 nsCOMPtr<nsIXULAppInfo> appInfo =
1109 do_GetService("@mozilla.org/xre/app-info;1");
1110 if (appInfo) {
1111 rv = appInfo->GetVersion(appVersion);
1112 NS_ENSURE_SUCCESS(rv, rv);
1113 } else {
1114 appVersion = MOZ_APP_VERSION;
1117 // We need argv[0] to point to the current executable's name. The rest of
1118 // the entries in this array will be ignored if argc<2. Therefore, for
1119 // xpcshell, we only fill out that item, and leave the rest empty.
1120 argc = 1;
1121 nsCOMPtr<nsIFile> binary;
1122 rv = ds->Get(XRE_EXECUTABLE_FILE, NS_GET_IID(nsIFile),
1123 getter_AddRefs(binary));
1124 NS_ASSERTION(NS_SUCCEEDED(rv), "Can't get the binary path");
1125 binary->GetNativePath(binPath);
1128 // Copy the parameters to the StagedUpdateInfo structure shared with the
1129 // watcher thread.
1130 mInfo.mGREDir = greDir;
1131 mInfo.mAppDir = appDir;
1132 mInfo.mUpdateRoot = updRoot;
1133 mInfo.mArgc = argc;
1134 mInfo.mArgv = new char*[argc];
1135 if (dirProvider) {
1136 for (int i = 0; i < argc; ++i) {
1137 const size_t length = strlen(argv[i]);
1138 mInfo.mArgv[i] = new char[length + 1];
1139 strcpy(mInfo.mArgv[i], argv[i]);
1141 } else {
1142 MOZ_ASSERT(argc == 1); // see above
1143 const size_t length = binPath.Length();
1144 mInfo.mArgv[0] = new char[length + 1];
1145 strcpy(mInfo.mArgv[0], binPath.get());
1147 mInfo.mAppVersion = appVersion;
1149 #if defined(MOZ_WIDGET_GONK)
1150 NS_ENSURE_ARG_POINTER(aUpdate);
1152 bool isOSUpdate;
1153 if (NS_SUCCEEDED(aUpdate->GetIsOSUpdate(&isOSUpdate)) &&
1154 isOSUpdate) {
1155 nsAutoCString osApplyToDir;
1157 // This needs to be done on the main thread, so we pass it along in
1158 // BackgroundThreadInfo
1159 nsresult rv = GetOSApplyToDir(osApplyToDir);
1160 if (NS_FAILED(rv)) {
1161 LOG(("Can't get the OS apply to dir"));
1162 return rv;
1165 SetOSApplyToDir(aUpdate, osApplyToDir);
1167 mInfo.mIsOSUpdate = true;
1168 rv = NS_NewNativeLocalFile(osApplyToDir, false,
1169 getter_AddRefs(mInfo.mOSApplyToDir));
1170 if (NS_FAILED(rv)) {
1171 LOG(("Can't create nsIFile for OS apply to dir"));
1172 return rv;
1175 #endif
1177 mUpdate = aUpdate;
1179 NS_ABORT_IF_FALSE(NS_IsMainThread(), "not main thread");
1180 return NS_NewThread(getter_AddRefs(mProcessWatcher),
1181 NS_NewRunnableMethod(this, &nsUpdateProcessor::StartStagedUpdate));
1186 void
1187 nsUpdateProcessor::StartStagedUpdate()
1189 NS_ABORT_IF_FALSE(!NS_IsMainThread(), "main thread");
1191 nsresult rv = ProcessUpdates(mInfo.mGREDir,
1192 mInfo.mAppDir,
1193 mInfo.mUpdateRoot,
1194 mInfo.mArgc,
1195 mInfo.mArgv,
1196 mInfo.mAppVersion.get(),
1197 false,
1198 mInfo.mIsOSUpdate,
1199 mInfo.mOSApplyToDir,
1200 &mUpdaterPID);
1201 NS_ENSURE_SUCCESS_VOID(rv);
1203 if (mUpdaterPID) {
1204 // Track the state of the updater process while it is staging an update.
1205 rv = NS_DispatchToCurrentThread(NS_NewRunnableMethod(this, &nsUpdateProcessor::WaitForProcess));
1206 NS_ENSURE_SUCCESS_VOID(rv);
1207 } else {
1208 // Failed to launch the updater process for some reason.
1209 // We need to shutdown the current thread as there isn't anything more for
1210 // us to do...
1211 rv = NS_DispatchToMainThread(NS_NewRunnableMethod(this, &nsUpdateProcessor::ShutdownWatcherThread));
1212 NS_ENSURE_SUCCESS_VOID(rv);
1216 void
1217 nsUpdateProcessor::ShutdownWatcherThread()
1219 NS_ABORT_IF_FALSE(NS_IsMainThread(), "not main thread");
1220 mProcessWatcher->Shutdown();
1221 mProcessWatcher = nullptr;
1222 mUpdate = nullptr;
1225 void
1226 nsUpdateProcessor::WaitForProcess()
1228 NS_ABORT_IF_FALSE(!NS_IsMainThread(), "main thread");
1229 ::WaitForProcess(mUpdaterPID);
1230 NS_DispatchToMainThread(NS_NewRunnableMethod(this, &nsUpdateProcessor::UpdateDone));
1233 void
1234 nsUpdateProcessor::UpdateDone()
1236 NS_ABORT_IF_FALSE(NS_IsMainThread(), "not main thread");
1238 nsCOMPtr<nsIUpdateManager> um =
1239 do_GetService("@mozilla.org/updates/update-manager;1");
1240 if (um && mUpdate) {
1241 um->RefreshUpdateStatus(mUpdate);
1244 ShutdownWatcherThread();