Fix typo in 9b54bd30006c008b4a951331b273613d5bac3abf
[pm.git] / toolkit / xre / nsUpdateDriver.cpp
blob7050e519a60dd575ac9f56d8dccd1d17f2356f18
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 eAppliedUpdate,
256 } UpdateStatus;
259 * Returns a value indicating what needs to be done in order to handle an update.
261 * @param dir the directory in which we should look for an update.status file.
262 * @param statusFile the update.status file found in the directory.
264 * @return the update action to be performed.
266 static UpdateStatus
267 GetUpdateStatus(nsIFile* dir, nsCOMPtr<nsIFile> &statusFile)
269 if (GetStatusFile(dir, statusFile)) {
270 char buf[32];
271 if (GetStatusFileContents(statusFile, buf)) {
272 const char kPending[] = "pending";
273 const char kApplied[] = "applied";
274 if (!strncmp(buf, kPending, sizeof(kPending) - 1)) {
275 return ePendingUpdate;
277 if (!strncmp(buf, kApplied, sizeof(kApplied) - 1)) {
278 return eAppliedUpdate;
282 return eNoUpdateAction;
285 static bool
286 GetVersionFile(nsIFile *dir, nsCOMPtr<nsIFile> &result)
288 return GetFile(dir, NS_LITERAL_CSTRING("update.version"), result);
291 // Compares the current application version with the update's application
292 // version.
293 static bool
294 IsOlderVersion(nsIFile *versionFile, const char *appVersion)
296 PRFileDesc *fd = nullptr;
297 nsresult rv = versionFile->OpenNSPRFileDesc(PR_RDONLY, 0660, &fd);
298 if (NS_FAILED(rv))
299 return true;
301 char buf[32];
302 const int32_t n = PR_Read(fd, buf, sizeof(buf));
303 PR_Close(fd);
305 if (n < 0)
306 return false;
308 // Trim off the trailing newline
309 if (buf[n - 1] == '\n')
310 buf[n - 1] = '\0';
312 // If the update xml doesn't provide the application version the file will
313 // contain the string "null" and it is assumed that the update is not older.
314 const char kNull[] = "null";
315 if (strncmp(buf, kNull, sizeof(kNull) - 1) == 0)
316 return false;
318 if (mozilla::Version(appVersion) > buf)
319 return true;
321 return false;
324 static bool
325 CopyFileIntoUpdateDir(nsIFile *parentDir, const char *leafName, nsIFile *updateDir)
327 nsDependentCString leaf(leafName);
328 nsCOMPtr<nsIFile> file;
330 // Make sure there is not an existing file in the target location.
331 nsresult rv = updateDir->Clone(getter_AddRefs(file));
332 if (NS_FAILED(rv))
333 return false;
334 rv = file->AppendNative(leaf);
335 if (NS_FAILED(rv))
336 return false;
337 file->Remove(true);
339 // Now, copy into the target location.
340 rv = parentDir->Clone(getter_AddRefs(file));
341 if (NS_FAILED(rv))
342 return false;
343 rv = file->AppendNative(leaf);
344 if (NS_FAILED(rv))
345 return false;
346 rv = file->CopyToNative(updateDir, EmptyCString());
347 if (NS_FAILED(rv))
348 return false;
350 return true;
353 static bool
354 CopyUpdaterIntoUpdateDir(nsIFile *greDir, nsIFile *appDir, nsIFile *updateDir,
355 nsCOMPtr<nsIFile> &updater)
357 // Copy the updater application from the GRE and the updater ini from the app
358 #if defined(XP_MACOSX)
359 if (!CopyFileIntoUpdateDir(appDir, kUpdaterApp, updateDir))
360 return false;
361 CopyFileIntoUpdateDir(greDir, kUpdaterINI, updateDir);
362 #else
363 if (!CopyFileIntoUpdateDir(greDir, kUpdaterBin, updateDir))
364 return false;
365 CopyFileIntoUpdateDir(appDir, kUpdaterINI, updateDir);
366 #endif
367 #if defined(XP_UNIX) && !defined(XP_MACOSX) && !defined(ANDROID)
368 nsCOMPtr<nsIFile> iconDir;
369 appDir->Clone(getter_AddRefs(iconDir));
370 iconDir->AppendNative(NS_LITERAL_CSTRING("icons"));
371 if (!CopyFileIntoUpdateDir(iconDir, kUpdaterPNG, updateDir))
372 return false;
373 #endif
374 // Finally, return the location of the updater binary.
375 nsresult rv = updateDir->Clone(getter_AddRefs(updater));
376 if (NS_FAILED(rv))
377 return false;
378 #if defined(XP_MACOSX)
379 rv = updater->AppendNative(NS_LITERAL_CSTRING(kUpdaterApp));
380 nsresult tmp = updater->AppendNative(NS_LITERAL_CSTRING("Contents"));
381 if (NS_FAILED(tmp)) {
382 rv = tmp;
384 tmp = updater->AppendNative(NS_LITERAL_CSTRING("MacOS"));
385 if (NS_FAILED(tmp) || NS_FAILED(rv))
386 return false;
387 #endif
388 rv = updater->AppendNative(NS_LITERAL_CSTRING(kUpdaterBin));
389 return NS_SUCCEEDED(rv);
393 * Switch an existing application directory to an updated version that has been
394 * staged.
396 * @param greDir the GRE dir
397 * @param updateDir the update dir where the mar file is located
398 * @param appDir the app dir
399 * @param appArgc the number of args to the application
400 * @param appArgv the args to the application, used for restarting if needed
402 static void
403 SwitchToUpdatedApp(nsIFile *greDir, nsIFile *updateDir,
404 nsIFile *appDir, int appArgc, char **appArgv)
406 nsresult rv;
408 // Steps:
409 // - copy updater into updates/0/MozUpdater/bgupdate/ dir on all platforms
410 // except Windows
411 // - run updater with the correct arguments
412 #ifndef XP_WIN
413 nsCOMPtr<nsIFile> mozUpdaterDir;
414 rv = updateDir->Clone(getter_AddRefs(mozUpdaterDir));
415 if (NS_FAILED(rv)) {
416 LOG(("failed cloning update dir\n"));
417 return;
420 // Create a new directory named MozUpdater in the updates/0 directory to copy
421 // the updater files to that will be used to replace the installation with the
422 // staged application that has been updated. Note that we don't check for
423 // directory creation errors since the call to CopyUpdaterIntoUpdateDir will
424 // fail if the creation of the directory fails. A unique directory is created
425 // in MozUpdater in case a previous attempt locked the directory or files.
426 mozUpdaterDir->Append(NS_LITERAL_STRING("MozUpdater"));
427 mozUpdaterDir->Append(NS_LITERAL_STRING("bgupdate"));
428 mozUpdaterDir->CreateUnique(nsIFile::DIRECTORY_TYPE, 0755);
430 nsCOMPtr<nsIFile> updater;
431 if (!CopyUpdaterIntoUpdateDir(greDir, appDir, mozUpdaterDir, updater)) {
432 LOG(("failed copying updater\n"));
433 return;
435 #endif
437 // We need to use the value returned from XRE_GetBinaryPath when attempting
438 // to restart the running application.
439 nsCOMPtr<nsIFile> appFile;
441 #if defined(XP_MACOSX)
442 // On OS X we need to pass the location of the xulrunner-stub executable
443 // rather than xulrunner-bin. See bug 349737.
444 GetXULRunnerStubPath(appArgv[0], getter_AddRefs(appFile));
445 #else
446 XRE_GetBinaryPath(appArgv[0], getter_AddRefs(appFile));
447 #endif
449 if (!appFile)
450 return;
452 #ifdef XP_WIN
453 nsAutoString appFilePathW;
454 rv = appFile->GetPath(appFilePathW);
455 if (NS_FAILED(rv)) {
456 return;
458 NS_ConvertUTF16toUTF8 appFilePath(appFilePathW);
460 nsCOMPtr<nsIFile> updater;
461 rv = greDir->Clone(getter_AddRefs(updater));
462 if (NS_FAILED(rv)) {
463 return;
466 nsDependentCString leaf(kUpdaterBin);
467 rv = updater->AppendNative(leaf);
468 if (NS_FAILED(rv)) {
469 return;
472 nsAutoString updaterPathW;
473 rv = updater->GetPath(updaterPathW);
474 if (NS_FAILED(rv)) {
475 return;
477 NS_ConvertUTF16toUTF8 updaterPath(updaterPathW);
478 #else
480 nsAutoCString appFilePath;
481 #if defined(MOZ_WIDGET_GONK)
482 appFilePath.Assign(kB2GServiceArgv[0]);
483 appArgc = kB2GServiceArgc;
484 appArgv = const_cast<char**>(kB2GServiceArgv);
485 #else
486 rv = appFile->GetNativePath(appFilePath);
487 if (NS_FAILED(rv))
488 return;
489 #endif
491 nsAutoCString updaterPath;
492 rv = updater->GetNativePath(updaterPath);
493 if (NS_FAILED(rv))
494 return;
495 #endif
497 nsAutoCString installDirPath;
498 rv = GetInstallDirPath(appDir, installDirPath);
499 if (NS_FAILED(rv)) {
500 return;
503 // Get the directory where the update will be staged.
504 nsAutoCString applyToDir;
505 nsCOMPtr<nsIFile> updatedDir;
506 #ifdef XP_MACOSX
507 if (!GetFile(updateDir, NS_LITERAL_CSTRING("Updated.app"), updatedDir)) {
508 #else
509 if (!GetFile(appDir, NS_LITERAL_CSTRING("updated"), updatedDir)) {
510 #endif
511 return;
513 #ifdef XP_WIN
514 nsAutoString applyToDirW;
515 rv = updatedDir->GetPath(applyToDirW);
516 if (NS_FAILED(rv)) {
517 return;
519 applyToDir = NS_ConvertUTF16toUTF8(applyToDirW);
520 #else
521 rv = updatedDir->GetNativePath(applyToDir);
522 #endif
523 if (NS_FAILED(rv)) {
524 return;
527 // Make sure that the updated directory exists
528 bool updatedDirExists = false;
529 updatedDir->Exists(&updatedDirExists);
530 if (!updatedDirExists) {
531 return;
534 #if defined(XP_WIN)
535 nsAutoString updateDirPathW;
536 rv = updateDir->GetPath(updateDirPathW);
537 NS_ConvertUTF16toUTF8 updateDirPath(updateDirPathW);
538 #else
539 nsAutoCString updateDirPath;
540 rv = updateDir->GetNativePath(updateDirPath);
541 #endif
542 if (NS_FAILED(rv))
543 return;
545 // Get the current working directory.
546 char workingDirPath[MAXPATHLEN];
547 rv = GetCurrentWorkingDir(workingDirPath, sizeof(workingDirPath));
548 if (NS_FAILED(rv))
549 return;
551 // Construct the PID argument for this process. If we are using execv, then
552 // we pass "0" which is then ignored by the updater.
553 #if defined(USE_EXECV)
554 nsAutoCString pid("0");
555 #else
556 nsAutoCString pid;
557 pid.AppendInt((int32_t) getpid());
558 #endif
560 // Append a special token to the PID in order to let the updater know that it
561 // just needs to replace the update directory.
562 pid.AppendLiteral("/replace");
564 int argc = appArgc + 6;
565 char **argv = new char*[argc + 1];
566 if (!argv)
567 return;
568 argv[0] = (char*) updaterPath.get();
569 argv[1] = (char*) updateDirPath.get();
570 argv[2] = (char*) installDirPath.get();
571 argv[3] = (char*) applyToDir.get();
572 argv[4] = (char*) pid.get();
573 if (appArgc) {
574 argv[5] = workingDirPath;
575 argv[6] = (char*) appFilePath.get();
576 for (int i = 1; i < appArgc; ++i)
577 argv[6 + i] = appArgv[i];
578 argv[argc] = nullptr;
579 } else {
580 argc = 5;
581 argv[5] = nullptr;
584 if (gSafeMode) {
585 PR_SetEnv("MOZ_SAFE_MODE_RESTART=1");
588 LOG(("spawning updater process for replacing [%s]\n", updaterPath.get()));
590 #if defined(USE_EXECV)
591 # if defined(MOZ_WIDGET_GONK)
592 // In Gonk, we preload libmozglue, which the updater process doesn't need.
593 // Since the updater will move and delete libmozglue.so, this can actually
594 // stop the /system mount from correctly being remounted as read-only.
595 unsetenv("LD_PRELOAD");
596 # endif
597 execv(updaterPath.get(), argv);
598 #elif defined(XP_WIN)
599 // Switch the application using updater.exe
600 if (!WinLaunchChild(updaterPathW.get(), argc, argv)) {
601 return;
603 _exit(0);
604 #elif defined(XP_MACOSX)
605 CommandLineServiceMac::SetupMacCommandLine(argc, argv, true);
606 // LaunchChildMac uses posix_spawnp and prefers the current
607 // architecture when launching. It doesn't require a
608 // null-terminated string but it doesn't matter if we pass one.
609 LaunchChildMac(argc, argv);
610 exit(0);
611 #else
612 PR_CreateProcessDetached(updaterPath.get(), argv, nullptr, nullptr);
613 exit(0);
614 #endif
617 #if defined(MOZ_WIDGET_GONK)
618 static nsresult
619 GetOSApplyToDir(nsACString& applyToDir)
621 nsCOMPtr<nsIProperties> ds =
622 do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID);
623 NS_ASSERTION(ds, "Can't get directory service");
625 nsCOMPtr<nsIFile> osApplyToDir;
626 nsresult rv = ds->Get(XRE_OS_UPDATE_APPLY_TO_DIR, NS_GET_IID(nsIFile),
627 getter_AddRefs(osApplyToDir));
628 if (NS_FAILED(rv)) {
629 LOG(("Can't get the OS applyTo dir"));
630 return rv;
633 return osApplyToDir->GetNativePath(applyToDir);
636 static void
637 SetOSApplyToDir(nsIUpdate* update, const nsACString& osApplyToDir)
639 nsresult rv;
640 nsCOMPtr<nsIWritablePropertyBag> updateProperties =
641 do_QueryInterface(update, &rv);
643 if (NS_FAILED(rv)) {
644 return;
647 nsCOMPtr<nsIWritableVariant> variant =
648 do_CreateInstance("@mozilla.org/variant;1", &rv);
649 if (NS_FAILED(rv)) {
650 return;
653 rv = variant->SetAsACString(osApplyToDir);
654 if (NS_FAILED(rv)) {
655 return;
658 updateProperties->SetProperty(NS_LITERAL_STRING("osApplyToDir"), variant);
660 #endif
663 * Apply an update. This applies to both normal and staged updates.
665 * @param greDir the GRE dir
666 * @param updateDir the update root dir
667 * @param statusFile the update.status file
668 * @param appDir the app dir
669 * @param appArgc the number of args to the application
670 * @param appArgv the args to the application, used for restarting if needed
671 * @param restart if true, apply the update in the foreground and restart the
672 * application when done. otherwise, stage the update and don't
673 * restart the application.
674 * @param outpid out parameter holding the handle to the updater application for
675 * staging updates.
677 static void
678 ApplyUpdate(nsIFile *greDir, nsIFile *updateDir, nsIFile *statusFile,
679 nsIFile *appDir, int appArgc, char **appArgv,
680 bool restart, bool isOSUpdate, nsIFile *osApplyToDir,
681 ProcessType *outpid)
683 nsresult rv;
685 // Steps:
686 // - mark update as 'applying'
687 // - copy updater into update dir on all platforms except Windows
688 // - run updater w/ appDir as the current working dir
689 #ifndef XP_WIN
690 nsCOMPtr<nsIFile> updater;
691 if (!CopyUpdaterIntoUpdateDir(greDir, appDir, updateDir, updater)) {
692 LOG(("failed copying updater\n"));
693 return;
695 #endif
697 // We need to use the value returned from XRE_GetBinaryPath when attempting
698 // to restart the running application.
699 nsCOMPtr<nsIFile> appFile;
701 #if defined(XP_MACOSX)
702 // On OS X we need to pass the location of the xulrunner-stub executable
703 // rather than xulrunner-bin. See bug 349737.
704 GetXULRunnerStubPath(appArgv[0], getter_AddRefs(appFile));
705 #else
706 XRE_GetBinaryPath(appArgv[0], getter_AddRefs(appFile));
707 #endif
709 if (!appFile)
710 return;
712 #ifdef XP_WIN
713 nsAutoString appFilePathW;
714 rv = appFile->GetPath(appFilePathW);
715 if (NS_FAILED(rv)) {
716 return;
718 NS_ConvertUTF16toUTF8 appFilePath(appFilePathW);
720 nsCOMPtr<nsIFile> updater;
721 rv = greDir->Clone(getter_AddRefs(updater));
722 if (NS_FAILED(rv)) {
723 return;
726 nsDependentCString leaf(kUpdaterBin);
727 rv = updater->AppendNative(leaf);
728 if (NS_FAILED(rv)) {
729 return;
732 nsAutoString updaterPathW;
733 rv = updater->GetPath(updaterPathW);
734 if (NS_FAILED(rv)) {
735 return;
737 NS_ConvertUTF16toUTF8 updaterPath(updaterPathW);
738 #else
739 nsAutoCString appFilePath;
740 rv = appFile->GetNativePath(appFilePath);
741 if (NS_FAILED(rv))
742 return;
744 nsAutoCString updaterPath;
745 rv = updater->GetNativePath(updaterPath);
746 if (NS_FAILED(rv))
747 return;
749 #endif
751 nsAutoCString installDirPath;
752 rv = GetInstallDirPath(appDir, installDirPath);
753 if (NS_FAILED(rv))
754 return;
756 // Get the directory where the update was staged for replace and GONK OS
757 // Updates or where it will be applied.
758 #ifndef MOZ_WIDGET_GONK
759 // OS Updates are only supported on GONK so force it to false on everything
760 // but GONK to simplify the following logic.
761 isOSUpdate = false;
762 #endif
763 nsAutoCString applyToDir;
764 nsCOMPtr<nsIFile> updatedDir;
765 if (restart && !isOSUpdate) {
766 applyToDir.Assign(installDirPath);
767 } else {
768 #ifdef XP_MACOSX
769 if (!GetFile(updateDir, NS_LITERAL_CSTRING("Updated.app"), updatedDir)) {
770 #else
771 if (!GetFile(appDir, NS_LITERAL_CSTRING("updated"), updatedDir)) {
772 #endif
773 return;
775 #ifdef XP_WIN
776 nsAutoString applyToDirW;
777 rv = updatedDir->GetPath(applyToDirW);
778 if (NS_FAILED(rv)) {
779 return;
781 applyToDir = NS_ConvertUTF16toUTF8(applyToDirW);
782 #elif MOZ_WIDGET_GONK
783 if (isOSUpdate) {
784 if (!osApplyToDir) {
785 return;
787 rv = osApplyToDir->GetNativePath(applyToDir);
788 } else {
789 rv = updatedDir->GetNativePath(applyToDir);
791 #else
792 rv = updatedDir->GetNativePath(applyToDir);
793 #endif
795 if (NS_FAILED(rv))
796 return;
798 #if defined(XP_WIN)
799 nsAutoString updateDirPathW;
800 rv = updateDir->GetPath(updateDirPathW);
801 NS_ConvertUTF16toUTF8 updateDirPath(updateDirPathW);
802 #else
803 nsAutoCString updateDirPath;
804 rv = updateDir->GetNativePath(updateDirPath);
805 #endif
806 if (NS_FAILED(rv)) {
807 return;
810 // Get the current working directory.
811 char workingDirPath[MAXPATHLEN];
812 rv = GetCurrentWorkingDir(workingDirPath, sizeof(workingDirPath));
813 if (NS_FAILED(rv))
814 return;
816 // We used to write out "Applying" to the update.status file here.
817 // Instead we do this from within the updater application now.
818 // This is so that we don't overwrite the status of pending-service
819 // in the Windows case. This change was made for all platforms so
820 // that it stays consistent across all OS.
822 // Construct the PID argument for this process. If we are using execv, then
823 // we pass "0" which is then ignored by the updater.
824 nsAutoCString pid;
825 if (!restart) {
826 // Signal the updater application that it should stage the update.
827 pid.AssignASCII("-1");
828 } else {
829 #if defined(USE_EXECV)
830 pid.AssignASCII("0");
831 #else
832 pid.AppendInt((int32_t) getpid());
833 #endif
836 int argc = appArgc + 6;
837 char **argv = new char*[argc + 1 ];
838 if (!argv)
839 return;
840 argv[0] = (char*) updaterPath.get();
841 argv[1] = (char*) updateDirPath.get();
842 argv[2] = (char*) installDirPath.get();
843 argv[3] = (char*) applyToDir.get();
844 argv[4] = (char*) pid.get();
845 if (restart && appArgc) {
846 argv[5] = workingDirPath;
847 argv[6] = (char*) appFilePath.get();
848 for (int i = 1; i < appArgc; ++i)
849 argv[6 + i] = appArgv[i];
850 argv[argc] = nullptr;
851 } else {
852 argc = 5;
853 argv[5] = nullptr;
856 if (gSafeMode) {
857 PR_SetEnv("MOZ_SAFE_MODE_RESTART=1");
860 if (isOSUpdate) {
861 PR_SetEnv("MOZ_OS_UPDATE=1");
863 #if defined(MOZ_WIDGET_GONK)
864 // We want the updater to be CPU friendly and not subject to being killed by
865 // the low memory killer, so we pass in some preferences to allow it to
866 // adjust its priority.
868 int32_t prioVal = Preferences::GetInt(kAppUpdaterPrio,
869 kAppUpdaterPrioDefault);
870 int32_t oomScoreAdj = Preferences::GetInt(kAppUpdaterOomScoreAdj,
871 kAppUpdaterOomScoreAdjDefault);
872 int32_t ioprioClass = Preferences::GetInt(kAppUpdaterIOPrioClass,
873 kAppUpdaterIOPrioClassDefault);
874 int32_t ioprioLevel = Preferences::GetInt(kAppUpdaterIOPrioLevel,
875 kAppUpdaterIOPrioLevelDefault);
876 nsPrintfCString prioEnv("MOZ_UPDATER_PRIO=%d/%d/%d/%d",
877 prioVal, oomScoreAdj, ioprioClass, ioprioLevel);
878 PR_SetEnv(prioEnv.get());
879 #endif
881 LOG(("spawning updater process [%s]\n", updaterPath.get()));
883 #if defined(USE_EXECV)
884 // Don't use execv when staging updates.
885 if (restart) {
886 execv(updaterPath.get(), argv);
887 } else {
888 *outpid = PR_CreateProcess(updaterPath.get(), argv, nullptr, nullptr);
890 #elif defined(XP_WIN)
891 // Launch the update using updater.exe
892 if (!WinLaunchChild(updaterPathW.get(), argc, argv, nullptr, outpid)) {
893 return;
896 if (restart) {
897 // We are going to process an update so we should exit now
898 _exit(0);
900 #elif defined(XP_MACOSX)
901 CommandLineServiceMac::SetupMacCommandLine(argc, argv, true);
902 // LaunchChildMac uses posix_spawnp and prefers the current
903 // architecture when launching. It doesn't require a
904 // null-terminated string but it doesn't matter if we pass one.
905 LaunchChildMac(argc, argv, 0, outpid);
906 if (restart) {
907 exit(0);
909 #else
910 *outpid = PR_CreateProcess(updaterPath.get(), argv, nullptr, nullptr);
911 if (restart) {
912 exit(0);
914 #endif
918 * Wait for a process until it terminates. This call is blocking.
920 static void
921 WaitForProcess(ProcessType pt)
923 #if defined(XP_WIN)
924 WaitForSingleObject(pt, INFINITE);
925 CloseHandle(pt);
926 #elif defined(XP_MACOSX)
927 waitpid(pt, 0, 0);
928 #else
929 int32_t exitCode;
930 PR_WaitProcess(pt, &exitCode);
931 if (exitCode != 0) {
932 LOG(("Error while running the updater process, check update.log"));
934 #endif
937 nsresult
938 ProcessUpdates(nsIFile *greDir, nsIFile *appDir, nsIFile *updRootDir,
939 int argc, char **argv, const char *appVersion,
940 bool restart, bool isOSUpdate, nsIFile *osApplyToDir,
941 ProcessType *pid)
943 nsresult rv;
945 nsCOMPtr<nsIFile> updatesDir;
946 rv = updRootDir->Clone(getter_AddRefs(updatesDir));
947 if (NS_FAILED(rv))
948 return rv;
950 rv = updatesDir->AppendNative(NS_LITERAL_CSTRING("updates"));
951 if (NS_FAILED(rv))
952 return rv;
954 rv = updatesDir->AppendNative(NS_LITERAL_CSTRING("0"));
955 if (NS_FAILED(rv))
956 return rv;
958 ProcessType dummyPID; // this will only be used for MOZ_UPDATE_STAGING
959 const char *processingUpdates = PR_GetEnv("MOZ_PROCESS_UPDATES");
960 if (processingUpdates && *processingUpdates) {
961 // Enable the tests to request an update to be staged.
962 const char *stagingUpdate = PR_GetEnv("MOZ_UPDATE_STAGING");
963 if (stagingUpdate && *stagingUpdate) {
964 restart = false;
965 pid = &dummyPID;
969 nsCOMPtr<nsIFile> statusFile;
970 UpdateStatus status = GetUpdateStatus(updatesDir, statusFile);
971 switch (status) {
972 case ePendingUpdate: {
973 nsCOMPtr<nsIFile> versionFile;
974 // Remove the update if the update application version file doesn't exist
975 // or if the update's application version is less than the current
976 // application version.
977 if (!GetVersionFile(updatesDir, versionFile) ||
978 IsOlderVersion(versionFile, appVersion)) {
979 updatesDir->Remove(true);
980 } else {
981 ApplyUpdate(greDir, updatesDir, statusFile,
982 appDir, argc, argv, restart, isOSUpdate, osApplyToDir, pid);
984 break;
986 case eAppliedUpdate:
987 // An update was staged and needs to be switched so the updated application
988 // is used.
989 SwitchToUpdatedApp(greDir, updatesDir, appDir, argc, argv);
990 break;
991 case eNoUpdateAction:
992 // We don't need to do any special processing here, we'll just continue to
993 // startup the application.
994 break;
997 return NS_OK;
1002 NS_IMPL_ISUPPORTS(nsUpdateProcessor, nsIUpdateProcessor)
1004 nsUpdateProcessor::nsUpdateProcessor()
1005 : mUpdaterPID(0)
1009 nsUpdateProcessor::~nsUpdateProcessor()
1013 NS_IMETHODIMP
1014 nsUpdateProcessor::ProcessUpdate(nsIUpdate* aUpdate)
1016 nsCOMPtr<nsIFile> greDir, appDir, updRoot;
1017 nsAutoCString appVersion;
1018 int argc;
1019 char **argv;
1021 nsAutoCString binPath;
1022 nsXREDirProvider* dirProvider = nsXREDirProvider::GetSingleton();
1023 if (dirProvider) { // Normal code path
1024 // Check for and process any available updates
1025 bool persistent;
1026 nsresult rv = NS_ERROR_FAILURE; // Take the NS_FAILED path when non-GONK
1027 #ifdef MOZ_WIDGET_GONK
1028 // Check in the sdcard for updates first, since that's our preferred
1029 // download location.
1030 rv = dirProvider->GetFile(XRE_UPDATE_ARCHIVE_DIR, &persistent,
1031 getter_AddRefs(updRoot));
1032 #endif
1033 if (NS_FAILED(rv)) {
1034 rv = dirProvider->GetFile(XRE_UPDATE_ROOT_DIR, &persistent,
1035 getter_AddRefs(updRoot));
1037 // XRE_UPDATE_ROOT_DIR may fail. Fallback to appDir if failed
1038 if (NS_FAILED(rv))
1039 updRoot = dirProvider->GetAppDir();
1041 greDir = dirProvider->GetGREDir();
1042 nsCOMPtr<nsIFile> exeFile;
1043 rv = dirProvider->GetFile(XRE_EXECUTABLE_FILE, &persistent,
1044 getter_AddRefs(exeFile));
1045 if (NS_SUCCEEDED(rv))
1046 rv = exeFile->GetParent(getter_AddRefs(appDir));
1048 if (NS_FAILED(rv))
1049 appDir = dirProvider->GetAppDir();
1051 appVersion = gAppData->version;
1052 argc = gRestartArgc;
1053 argv = gRestartArgv;
1054 } else {
1055 // In the xpcshell environment, the usual XRE_main is not run, so things
1056 // like dirProvider and gAppData do not exist. This code path accesses
1057 // XPCOM (which is not available in the previous code path) in order to get
1058 // the same information.
1059 nsCOMPtr<nsIProperties> ds =
1060 do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID);
1061 if (!ds) {
1062 NS_ABORT(); // There's nothing which we can do if this fails!
1065 nsresult rv = ds->Get(NS_GRE_DIR, NS_GET_IID(nsIFile),
1066 getter_AddRefs(greDir));
1067 NS_ASSERTION(NS_SUCCEEDED(rv), "Can't get the GRE dir");
1069 nsCOMPtr<nsIFile> exeFile;
1070 rv = ds->Get(XRE_EXECUTABLE_FILE, NS_GET_IID(nsIFile),
1071 getter_AddRefs(exeFile));
1072 if (NS_SUCCEEDED(rv))
1073 rv = exeFile->GetParent(getter_AddRefs(appDir));
1075 NS_ASSERTION(NS_SUCCEEDED(rv), "Can't get the XREExeF parent dir");
1077 rv = ds->Get(XRE_UPDATE_ROOT_DIR, NS_GET_IID(nsIFile),
1078 getter_AddRefs(updRoot));
1079 NS_ASSERTION(NS_SUCCEEDED(rv), "Can't get the UpdRootD dir");
1081 nsCOMPtr<nsIXULAppInfo> appInfo =
1082 do_GetService("@mozilla.org/xre/app-info;1");
1083 if (appInfo) {
1084 rv = appInfo->GetVersion(appVersion);
1085 NS_ENSURE_SUCCESS(rv, rv);
1086 } else {
1087 appVersion = MOZ_APP_VERSION;
1090 // We need argv[0] to point to the current executable's name. The rest of
1091 // the entries in this array will be ignored if argc<2. Therefore, for
1092 // xpcshell, we only fill out that item, and leave the rest empty.
1093 argc = 1;
1094 nsCOMPtr<nsIFile> binary;
1095 rv = ds->Get(XRE_EXECUTABLE_FILE, NS_GET_IID(nsIFile),
1096 getter_AddRefs(binary));
1097 NS_ASSERTION(NS_SUCCEEDED(rv), "Can't get the binary path");
1098 binary->GetNativePath(binPath);
1101 // Copy the parameters to the StagedUpdateInfo structure shared with the
1102 // watcher thread.
1103 mInfo.mGREDir = greDir;
1104 mInfo.mAppDir = appDir;
1105 mInfo.mUpdateRoot = updRoot;
1106 mInfo.mArgc = argc;
1107 mInfo.mArgv = new char*[argc];
1108 if (dirProvider) {
1109 for (int i = 0; i < argc; ++i) {
1110 const size_t length = strlen(argv[i]);
1111 mInfo.mArgv[i] = new char[length + 1];
1112 strcpy(mInfo.mArgv[i], argv[i]);
1114 } else {
1115 MOZ_ASSERT(argc == 1); // see above
1116 const size_t length = binPath.Length();
1117 mInfo.mArgv[0] = new char[length + 1];
1118 strcpy(mInfo.mArgv[0], binPath.get());
1120 mInfo.mAppVersion = appVersion;
1122 #if defined(MOZ_WIDGET_GONK)
1123 NS_ENSURE_ARG_POINTER(aUpdate);
1125 bool isOSUpdate;
1126 if (NS_SUCCEEDED(aUpdate->GetIsOSUpdate(&isOSUpdate)) &&
1127 isOSUpdate) {
1128 nsAutoCString osApplyToDir;
1130 // This needs to be done on the main thread, so we pass it along in
1131 // BackgroundThreadInfo
1132 nsresult rv = GetOSApplyToDir(osApplyToDir);
1133 if (NS_FAILED(rv)) {
1134 LOG(("Can't get the OS apply to dir"));
1135 return rv;
1138 SetOSApplyToDir(aUpdate, osApplyToDir);
1140 mInfo.mIsOSUpdate = true;
1141 rv = NS_NewNativeLocalFile(osApplyToDir, false,
1142 getter_AddRefs(mInfo.mOSApplyToDir));
1143 if (NS_FAILED(rv)) {
1144 LOG(("Can't create nsIFile for OS apply to dir"));
1145 return rv;
1148 #endif
1150 MOZ_ASSERT(NS_IsMainThread(), "not main thread");
1151 return NS_NewThread(getter_AddRefs(mProcessWatcher),
1152 NS_NewRunnableMethod(this, &nsUpdateProcessor::StartStagedUpdate));
1157 void
1158 nsUpdateProcessor::StartStagedUpdate()
1160 MOZ_ASSERT(!NS_IsMainThread(), "main thread");
1162 nsresult rv = ProcessUpdates(mInfo.mGREDir,
1163 mInfo.mAppDir,
1164 mInfo.mUpdateRoot,
1165 mInfo.mArgc,
1166 mInfo.mArgv,
1167 mInfo.mAppVersion.get(),
1168 false,
1169 mInfo.mIsOSUpdate,
1170 mInfo.mOSApplyToDir,
1171 &mUpdaterPID);
1172 NS_ENSURE_SUCCESS_VOID(rv);
1174 if (mUpdaterPID) {
1175 // Track the state of the updater process while it is staging an update.
1176 rv = NS_DispatchToCurrentThread(NS_NewRunnableMethod(this, &nsUpdateProcessor::WaitForProcess));
1177 NS_ENSURE_SUCCESS_VOID(rv);
1178 } else {
1179 // Failed to launch the updater process for some reason.
1180 // We need to shutdown the current thread as there isn't anything more for
1181 // us to do...
1182 rv = NS_DispatchToMainThread(NS_NewRunnableMethod(this, &nsUpdateProcessor::ShutdownWatcherThread));
1183 NS_ENSURE_SUCCESS_VOID(rv);
1187 void
1188 nsUpdateProcessor::ShutdownWatcherThread()
1190 MOZ_ASSERT(NS_IsMainThread(), "not main thread");
1191 mProcessWatcher->Shutdown();
1192 mProcessWatcher = nullptr;
1195 void
1196 nsUpdateProcessor::WaitForProcess()
1198 MOZ_ASSERT(!NS_IsMainThread(), "main thread");
1199 ::WaitForProcess(mUpdaterPID);
1200 NS_DispatchToMainThread(NS_NewRunnableMethod(this, &nsUpdateProcessor::UpdateDone));
1203 void
1204 nsUpdateProcessor::UpdateDone()
1206 MOZ_ASSERT(NS_IsMainThread(), "not main thread");
1208 nsCOMPtr<nsIUpdateManager> um =
1209 do_GetService("@mozilla.org/updates/update-manager;1");
1210 if (um) {
1211 nsCOMPtr<nsIUpdate> update;
1212 um->GetActiveUpdate(getter_AddRefs(update));
1213 if (update) {
1214 um->RefreshUpdateStatus(update);
1218 ShutdownWatcherThread();