Line data Source code
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/. */
6 :
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 "nsVariant.h"
15 : #include "nsCOMPtr.h"
16 : #include "nsString.h"
17 : #include "prproces.h"
18 : #include "mozilla/Logging.h"
19 : #include "prenv.h"
20 : #include "nsVersionComparator.h"
21 : #include "nsDirectoryServiceDefs.h"
22 : #include "nsThreadUtils.h"
23 : #include "nsIXULAppInfo.h"
24 : #include "mozilla/Preferences.h"
25 : #include "nsPrintfCString.h"
26 : #include "mozilla/DebugOnly.h"
27 : #include "mozilla/Printf.h"
28 :
29 : #ifdef XP_MACOSX
30 : #include "nsILocalFileMac.h"
31 : #include "nsCommandLineServiceMac.h"
32 : #include "MacLaunchHelper.h"
33 : #include "updaterfileutils_osx.h"
34 : #include "mozilla/Monitor.h"
35 : #endif
36 :
37 : #if defined(XP_WIN)
38 : # include <direct.h>
39 : # include <process.h>
40 : # include <windows.h>
41 : # include <shlwapi.h>
42 : # define getcwd(path, size) _getcwd(path, size)
43 : # define getpid() GetCurrentProcessId()
44 : #elif defined(XP_UNIX)
45 : # include <unistd.h>
46 : # include <sys/wait.h>
47 : #endif
48 :
49 : using namespace mozilla;
50 :
51 : static LazyLogModule sUpdateLog("updatedriver");
52 : #define LOG(args) MOZ_LOG(sUpdateLog, mozilla::LogLevel::Debug, args)
53 :
54 : #ifdef XP_WIN
55 : #define UPDATER_BIN "updater.exe"
56 : #elif XP_MACOSX
57 : #define UPDATER_BIN "org.mozilla.updater"
58 : #else
59 : #define UPDATER_BIN "updater"
60 : #endif
61 : #define UPDATER_INI "updater.ini"
62 : #ifdef XP_MACOSX
63 : #define UPDATER_APP "updater.app"
64 : #endif
65 : #if defined(XP_UNIX) && !defined(XP_MACOSX)
66 : #define UPDATER_PNG "updater.png"
67 : #endif
68 :
69 : #ifdef XP_MACOSX
70 : static void
71 : UpdateDriverSetupMacCommandLine(int& argc, char**& argv, bool restart)
72 : {
73 : if (NS_IsMainThread()) {
74 : CommandLineServiceMac::SetupMacCommandLine(argc, argv, restart);
75 : return;
76 : }
77 : // Bug 1335916: SetupMacCommandLine calls a CoreFoundation function that
78 : // asserts that it was called from the main thread, so if we are not the main
79 : // thread, we have to dispatch that call to there. But we also have to get the
80 : // result from it, so we can't just dispatch and return, we have to wait
81 : // until the dispatched operation actually completes. So we also set up a
82 : // monitor to signal us when that happens, and block until then.
83 : Monitor monitor("nsUpdateDriver SetupMacCommandLine");
84 :
85 : nsresult rv = NS_DispatchToMainThread(
86 : NS_NewRunnableFunction("UpdateDriverSetupMacCommandLine",
87 : [&argc, &argv, restart, &monitor]() -> void
88 : {
89 : CommandLineServiceMac::SetupMacCommandLine(argc, argv, restart);
90 : MonitorAutoLock(monitor).Notify();
91 : }));
92 :
93 : if (NS_FAILED(rv)) {
94 : LOG(("Update driver error dispatching SetupMacCommandLine to main thread: %d\n", rv));
95 : return;
96 : }
97 :
98 : // The length of this wait is arbitrary, but should be long enough that having
99 : // it expire means something is seriously wrong.
100 : rv = MonitorAutoLock(monitor).Wait(PR_SecondsToInterval(60));
101 : if (NS_FAILED(rv)) {
102 : LOG(("Update driver timed out waiting for SetupMacCommandLine: %d\n", rv));
103 : }
104 : }
105 : #endif
106 :
107 : static nsresult
108 0 : GetCurrentWorkingDir(char *buf, size_t size)
109 : {
110 : // Cannot use NS_GetSpecialDirectory because XPCOM is not yet initialized.
111 : // This code is duplicated from xpcom/io/SpecialSystemDirectory.cpp:
112 :
113 : #if defined(XP_WIN)
114 : wchar_t wpath[MAX_PATH];
115 : if (!_wgetcwd(wpath, size))
116 : return NS_ERROR_FAILURE;
117 : NS_ConvertUTF16toUTF8 path(wpath);
118 : strncpy(buf, path.get(), size);
119 : #else
120 0 : if(!getcwd(buf, size))
121 0 : return NS_ERROR_FAILURE;
122 : #endif
123 0 : return NS_OK;
124 : }
125 :
126 : /**
127 : * Get the path to the installation directory. For Mac OS X this will be the
128 : * bundle directory.
129 : *
130 : * @param appDir the application directory file object
131 : * @param installDirPath the path to the installation directory
132 : */
133 : static nsresult
134 0 : GetInstallDirPath(nsIFile *appDir, nsACString& installDirPath)
135 : {
136 : nsresult rv;
137 : #ifdef XP_MACOSX
138 : nsCOMPtr<nsIFile> parentDir1, parentDir2;
139 : rv = appDir->GetParent(getter_AddRefs(parentDir1));
140 : if (NS_FAILED(rv)) {
141 : return rv;
142 : }
143 : rv = parentDir1->GetParent(getter_AddRefs(parentDir2));
144 : if (NS_FAILED(rv)) {
145 : return rv;
146 : }
147 : rv = parentDir2->GetNativePath(installDirPath);
148 : #elif XP_WIN
149 : nsAutoString installDirPathW;
150 : rv = appDir->GetPath(installDirPathW);
151 : if (NS_FAILED(rv)) {
152 : return rv;
153 : }
154 : installDirPath = NS_ConvertUTF16toUTF8(installDirPathW);
155 : #else
156 0 : rv = appDir->GetNativePath(installDirPath);
157 : #endif
158 0 : if (NS_FAILED(rv)) {
159 0 : return rv;
160 : }
161 0 : return NS_OK;
162 : }
163 :
164 : #if defined(XP_MACOSX)
165 : // This is a copy of OS X's XRE_GetBinaryPath from nsAppRunner.cpp with the
166 : // gBinaryPath check removed so that the updater can reload the stub executable
167 : // instead of xulrunner-bin. See bug 349737.
168 : static nsresult
169 : GetXULRunnerStubPath(const char* argv0, nsIFile* *aResult)
170 : {
171 : // Works even if we're not bundled.
172 : CFBundleRef appBundle = ::CFBundleGetMainBundle();
173 : if (!appBundle)
174 : return NS_ERROR_FAILURE;
175 :
176 : CFURLRef bundleURL = ::CFBundleCopyExecutableURL(appBundle);
177 : if (!bundleURL)
178 : return NS_ERROR_FAILURE;
179 :
180 : nsCOMPtr<nsILocalFileMac> lfm;
181 : nsresult rv = NS_NewLocalFileWithCFURL(bundleURL, true, getter_AddRefs(lfm));
182 :
183 : ::CFRelease(bundleURL);
184 :
185 : if (NS_FAILED(rv))
186 : return rv;
187 :
188 : lfm.forget(aResult);
189 : return NS_OK;
190 : }
191 : #endif /* XP_MACOSX */
192 :
193 : static bool
194 1 : GetFile(nsIFile* dir, const nsACString& name, nsCOMPtr<nsIFile>& result)
195 : {
196 : nsresult rv;
197 :
198 2 : nsCOMPtr<nsIFile> file;
199 1 : rv = dir->Clone(getter_AddRefs(file));
200 1 : if (NS_FAILED(rv))
201 0 : return false;
202 :
203 1 : rv = file->AppendNative(name);
204 1 : if (NS_FAILED(rv))
205 0 : return false;
206 :
207 1 : result = do_QueryInterface(file, &rv);
208 1 : return NS_SUCCEEDED(rv);
209 : }
210 :
211 : static bool
212 0 : GetStatusFile(nsIFile *dir, nsCOMPtr<nsIFile> &result)
213 : {
214 0 : return GetFile(dir, NS_LITERAL_CSTRING("update.status"), result);
215 : }
216 :
217 : /**
218 : * Get the contents of the update.status file.
219 : *
220 : * @param statusFile the status file object.
221 : * @param buf the buffer holding the file contents
222 : *
223 : * @return true if successful, false otherwise.
224 : */
225 : template <size_t Size>
226 : static bool
227 0 : GetStatusFileContents(nsIFile *statusFile, char (&buf)[Size])
228 : {
229 : static_assert(Size > 16, "Buffer needs to be large enough to hold the known status codes");
230 :
231 0 : PRFileDesc *fd = nullptr;
232 0 : nsresult rv = statusFile->OpenNSPRFileDesc(PR_RDONLY, 0660, &fd);
233 0 : if (NS_FAILED(rv))
234 0 : return false;
235 :
236 0 : const int32_t n = PR_Read(fd, buf, Size);
237 0 : PR_Close(fd);
238 :
239 0 : return (n >= 0);
240 : }
241 :
242 : typedef enum {
243 : eNoUpdateAction,
244 : ePendingUpdate,
245 : ePendingService,
246 : ePendingElevate,
247 : eAppliedUpdate,
248 : eAppliedService,
249 : } UpdateStatus;
250 :
251 : /**
252 : * Returns a value indicating what needs to be done in order to handle an update.
253 : *
254 : * @param dir the directory in which we should look for an update.status file.
255 : * @param statusFile the update.status file found in the directory.
256 : *
257 : * @return the update action to be performed.
258 : */
259 : static UpdateStatus
260 0 : GetUpdateStatus(nsIFile* dir, nsCOMPtr<nsIFile> &statusFile)
261 : {
262 0 : if (GetStatusFile(dir, statusFile)) {
263 : char buf[32];
264 0 : if (GetStatusFileContents(statusFile, buf)) {
265 0 : const char kPending[] = "pending";
266 0 : const char kPendingService[] = "pending-service";
267 0 : const char kPendingElevate[] = "pending-elevate";
268 0 : const char kApplied[] = "applied";
269 0 : const char kAppliedService[] = "applied-service";
270 0 : if (!strncmp(buf, kPendingElevate, sizeof(kPendingElevate) - 1)) {
271 0 : return ePendingElevate;
272 : }
273 0 : if (!strncmp(buf, kPendingService, sizeof(kPendingService) - 1)) {
274 0 : return ePendingService;
275 : }
276 0 : if (!strncmp(buf, kPending, sizeof(kPending) - 1)) {
277 0 : return ePendingUpdate;
278 : }
279 0 : if (!strncmp(buf, kAppliedService, sizeof(kAppliedService) - 1)) {
280 0 : return eAppliedService;
281 : }
282 0 : if (!strncmp(buf, kApplied, sizeof(kApplied) - 1)) {
283 0 : return eAppliedUpdate;
284 : }
285 : }
286 : }
287 0 : return eNoUpdateAction;
288 : }
289 :
290 : static bool
291 1 : GetVersionFile(nsIFile *dir, nsCOMPtr<nsIFile> &result)
292 : {
293 1 : return GetFile(dir, NS_LITERAL_CSTRING("update.version"), result);
294 : }
295 :
296 : // Compares the current application version with the update's application
297 : // version.
298 : static bool
299 1 : IsOlderVersion(nsIFile *versionFile, const char *appVersion)
300 : {
301 1 : PRFileDesc *fd = nullptr;
302 1 : nsresult rv = versionFile->OpenNSPRFileDesc(PR_RDONLY, 0660, &fd);
303 1 : if (NS_FAILED(rv))
304 1 : return true;
305 :
306 : char buf[32];
307 0 : const int32_t n = PR_Read(fd, buf, sizeof(buf));
308 0 : PR_Close(fd);
309 :
310 0 : if (n < 0)
311 0 : return false;
312 :
313 : // Trim off the trailing newline
314 0 : if (buf[n - 1] == '\n')
315 0 : buf[n - 1] = '\0';
316 :
317 : // If the update xml doesn't provide the application version the file will
318 : // contain the string "null" and it is assumed that the update is not older.
319 0 : const char kNull[] = "null";
320 0 : if (strncmp(buf, kNull, sizeof(kNull) - 1) == 0)
321 0 : return false;
322 :
323 0 : if (mozilla::Version(appVersion) > buf)
324 0 : return true;
325 :
326 0 : return false;
327 : }
328 :
329 : static bool
330 0 : CopyFileIntoUpdateDir(nsIFile *parentDir, const nsACString& leaf, nsIFile *updateDir)
331 : {
332 0 : nsCOMPtr<nsIFile> file;
333 :
334 : // Make sure there is not an existing file in the target location.
335 0 : nsresult rv = updateDir->Clone(getter_AddRefs(file));
336 0 : if (NS_FAILED(rv))
337 0 : return false;
338 0 : rv = file->AppendNative(leaf);
339 0 : if (NS_FAILED(rv))
340 0 : return false;
341 0 : file->Remove(true);
342 :
343 : // Now, copy into the target location.
344 0 : rv = parentDir->Clone(getter_AddRefs(file));
345 0 : if (NS_FAILED(rv))
346 0 : return false;
347 0 : rv = file->AppendNative(leaf);
348 0 : if (NS_FAILED(rv))
349 0 : return false;
350 0 : rv = file->CopyToNative(updateDir, EmptyCString());
351 0 : if (NS_FAILED(rv))
352 0 : return false;
353 :
354 0 : return true;
355 : }
356 :
357 : static bool
358 0 : CopyUpdaterIntoUpdateDir(nsIFile *greDir, nsIFile *appDir, nsIFile *updateDir,
359 : nsCOMPtr<nsIFile> &updater)
360 : {
361 : // Copy the updater application from the GRE and the updater ini from the app.
362 : #if defined(XP_MACOSX)
363 : if (!CopyFileIntoUpdateDir(appDir, NS_LITERAL_CSTRING(UPDATER_APP), updateDir))
364 : return false;
365 : CopyFileIntoUpdateDir(greDir, NS_LITERAL_CSTRING(UPDATER_INI), updateDir);
366 : #else
367 0 : if (!CopyFileIntoUpdateDir(greDir, NS_LITERAL_CSTRING(UPDATER_BIN), updateDir))
368 0 : return false;
369 0 : CopyFileIntoUpdateDir(appDir, NS_LITERAL_CSTRING(UPDATER_INI), updateDir);
370 : #endif
371 : #if defined(XP_UNIX) && !defined(XP_MACOSX) && !defined(ANDROID)
372 0 : nsCOMPtr<nsIFile> iconDir;
373 0 : appDir->Clone(getter_AddRefs(iconDir));
374 0 : iconDir->AppendNative(NS_LITERAL_CSTRING("icons"));
375 0 : if (!CopyFileIntoUpdateDir(iconDir, NS_LITERAL_CSTRING(UPDATER_PNG), updateDir))
376 0 : return false;
377 : #endif
378 : // Finally, return the location of the updater binary.
379 0 : nsresult rv = updateDir->Clone(getter_AddRefs(updater));
380 0 : if (NS_FAILED(rv))
381 0 : return false;
382 : #if defined(XP_MACOSX)
383 : rv = updater->AppendNative(NS_LITERAL_CSTRING(UPDATER_APP));
384 : nsresult tmp = updater->AppendNative(NS_LITERAL_CSTRING("Contents"));
385 : if (NS_FAILED(tmp)) {
386 : rv = tmp;
387 : }
388 : tmp = updater->AppendNative(NS_LITERAL_CSTRING("MacOS"));
389 : if (NS_FAILED(tmp) || NS_FAILED(rv))
390 : return false;
391 : #endif
392 0 : rv = updater->AppendNative(NS_LITERAL_CSTRING(UPDATER_BIN));
393 0 : return NS_SUCCEEDED(rv);
394 : }
395 :
396 : /**
397 : * Appends the specified path to the library path.
398 : * This is used so that updater can find libmozsqlite3.so and other shared libs.
399 : *
400 : * @param pathToAppend A new library path to prepend to LD_LIBRARY_PATH
401 : */
402 : #if defined(MOZ_VERIFY_MAR_SIGNATURE) && !defined(XP_WIN) && !defined(XP_MACOSX)
403 : #include "prprf.h"
404 : #define PATH_SEPARATOR ":"
405 : #define LD_LIBRARY_PATH_ENVVAR_NAME "LD_LIBRARY_PATH"
406 : static void
407 : AppendToLibPath(const char *pathToAppend)
408 : {
409 : char *pathValue = getenv(LD_LIBRARY_PATH_ENVVAR_NAME);
410 : if (nullptr == pathValue || '\0' == *pathValue) {
411 : // Leak the string because that is required by PR_SetEnv.
412 : char *s = Smprintf("%s=%s", LD_LIBRARY_PATH_ENVVAR_NAME, pathToAppend).release();
413 : PR_SetEnv(s);
414 : } else if (!strstr(pathValue, pathToAppend)) {
415 : // Leak the string because that is required by PR_SetEnv.
416 : char *s = Smprintf("%s=%s" PATH_SEPARATOR "%s",
417 : LD_LIBRARY_PATH_ENVVAR_NAME, pathToAppend, pathValue).release();
418 : PR_SetEnv(s);
419 : }
420 : }
421 : #endif
422 :
423 : /**
424 : * Applies, switches, or stages an update.
425 : *
426 : * @param greDir the GRE directory
427 : * @param updateDir the update root directory
428 : * @param appDir the application directory
429 : * @param appArgc the number of args passed to the application
430 : * @param appArgv the args passed to the application
431 : * (used for restarting the application when necessary)
432 : * @param restart true when a restart is necessary.
433 : * @param isStaged true when the update has already been staged
434 : * @param outpid (out) parameter holding the handle to the updater application
435 : * when staging updates
436 : */
437 : static void
438 0 : ApplyUpdate(nsIFile *greDir, nsIFile *updateDir, nsIFile *appDir, int appArgc,
439 : char **appArgv, bool restart, bool isStaged, ProcessType *outpid)
440 : {
441 : // The following determines the update operation to perform.
442 : // 1. When restart is false the update will be staged.
443 : // 2. When restart is true and isStaged is false the update will apply the mar
444 : // file to the installation directory.
445 : // 3. When restart is true and isStaged is true the update will switch the
446 : // staged update with the installation directory.
447 :
448 : nsresult rv;
449 :
450 0 : nsCOMPtr<nsIFile> updater;
451 0 : nsAutoCString updaterPath;
452 0 : nsAutoCString updateDirPath;
453 : #if defined(XP_WIN)
454 : // Get an nsIFile reference for the updater in the installation dir.
455 : if (!GetFile(greDir, NS_LITERAL_CSTRING(UPDATER_BIN), updater)) {
456 : return;
457 : }
458 :
459 : // Get the path to the updater.
460 : nsAutoString updaterPathW;
461 : rv = updater->GetPath(updaterPathW);
462 : if (NS_FAILED(rv)) {
463 : return;
464 : }
465 : updaterPath = NS_ConvertUTF16toUTF8(updaterPathW);
466 :
467 : // Get the path to the update dir.
468 : nsAutoString updateDirPathW;
469 : rv = updateDir->GetPath(updateDirPathW);
470 : if (NS_FAILED(rv)) {
471 : return;
472 : }
473 : updateDirPath = NS_ConvertUTF16toUTF8(updateDirPathW);
474 : #else
475 0 : if (isStaged) {
476 0 : nsCOMPtr<nsIFile> mozUpdaterDir;
477 0 : rv = updateDir->Clone(getter_AddRefs(mozUpdaterDir));
478 0 : if (NS_FAILED(rv)) {
479 0 : LOG(("failed cloning update dir\n"));
480 0 : return;
481 : }
482 :
483 : // Create a new directory named MozUpdater in the updates/0 directory to copy
484 : // the updater files to that will be used to replace the installation with the
485 : // staged application that has been updated. Note that we don't check for
486 : // directory creation errors since the call to CopyUpdaterIntoUpdateDir will
487 : // fail if the creation of the directory fails. A unique directory is created
488 : // in MozUpdater in case a previous attempt locked the directory or files.
489 0 : mozUpdaterDir->Append(NS_LITERAL_STRING("MozUpdater"));
490 0 : mozUpdaterDir->Append(NS_LITERAL_STRING("bgupdate"));
491 0 : rv = mozUpdaterDir->CreateUnique(nsIFile::DIRECTORY_TYPE, 0755);
492 0 : if (NS_FAILED(rv)) {
493 0 : LOG(("failed creating unique dir\n"));
494 0 : return;
495 : }
496 :
497 : // Copy the updater and files needed to update into the MozUpdater/bgupdate
498 : // directory in the update dir and get an nsIFile reference to the copied
499 : // updater.
500 0 : if (!CopyUpdaterIntoUpdateDir(greDir, appDir, mozUpdaterDir, updater)) {
501 0 : LOG(("failed copying updater\n"));
502 0 : return;
503 : }
504 : } else {
505 : // Copy the updater and files needed to update into the update directory and
506 : // get an nsIFile reference to the copied updater.
507 0 : if (!CopyUpdaterIntoUpdateDir(greDir, appDir, updateDir, updater)) {
508 0 : LOG(("failed copying updater\n"));
509 0 : return;
510 : }
511 : }
512 :
513 : // Get the path to the updater that will be used.
514 0 : rv = updater->GetNativePath(updaterPath);
515 0 : if (NS_FAILED(rv)) {
516 0 : return;
517 : }
518 :
519 : // Get the path to the update dir.
520 0 : rv = updateDir->GetNativePath(updateDirPath);
521 0 : if (NS_FAILED(rv)) {
522 0 : return;
523 : }
524 : #endif
525 :
526 : // appFilePath and workingDirPath are only used when the application will be
527 : // restarted.
528 0 : nsAutoCString appFilePath;
529 : char workingDirPath[MAXPATHLEN];
530 0 : if (restart) {
531 : // Get the path to the current working directory.
532 0 : rv = GetCurrentWorkingDir(workingDirPath, sizeof(workingDirPath));
533 0 : if (NS_FAILED(rv)) {
534 0 : return;
535 : }
536 :
537 : // Get the application file path used by the updater to restart the
538 : // application after the update has finished.
539 0 : nsCOMPtr<nsIFile> appFile;
540 : #if defined(XP_MACOSX)
541 : // On OS X we need to pass the location of the xulrunner-stub executable
542 : // rather than xulrunner-bin. See bug 349737.
543 : GetXULRunnerStubPath(appArgv[0], getter_AddRefs(appFile));
544 : #else
545 0 : XRE_GetBinaryPath(appArgv[0], getter_AddRefs(appFile));
546 : #endif
547 0 : if (!appFile) {
548 0 : return;
549 : }
550 :
551 : #if defined(XP_WIN)
552 : nsAutoString appFilePathW;
553 : rv = appFile->GetPath(appFilePathW);
554 : if (NS_FAILED(rv)) {
555 : return;
556 : }
557 : appFilePath = NS_ConvertUTF16toUTF8(appFilePathW);
558 : #else
559 0 : rv = appFile->GetNativePath(appFilePath);
560 0 : if (NS_FAILED(rv)) {
561 0 : return;
562 : }
563 : #endif
564 : }
565 :
566 : // Get the installation directory path.
567 0 : nsAutoCString installDirPath;
568 0 : rv = GetInstallDirPath(appDir, installDirPath);
569 0 : if (NS_FAILED(rv)) {
570 0 : return;
571 : }
572 :
573 0 : nsAutoCString applyToDirPath;
574 0 : nsCOMPtr<nsIFile> updatedDir;
575 0 : if (restart && !isStaged) {
576 : // The install directory is the same as the apply to directory.
577 0 : applyToDirPath.Assign(installDirPath);
578 : } else {
579 : // Get the directory where the update is staged or will be staged.
580 : #if defined(XP_MACOSX)
581 : if (!GetFile(updateDir, NS_LITERAL_CSTRING("Updated.app"), updatedDir)) {
582 : #else
583 0 : if (!GetFile(appDir, NS_LITERAL_CSTRING("updated"), updatedDir)) {
584 : #endif
585 0 : return;
586 : }
587 : #if defined(XP_WIN)
588 : nsAutoString applyToDirPathW;
589 : rv = updatedDir->GetPath(applyToDirPathW);
590 : if (NS_FAILED(rv)) {
591 : return;
592 : }
593 : applyToDirPath = NS_ConvertUTF16toUTF8(applyToDirPathW);
594 : #else
595 0 : rv = updatedDir->GetNativePath(applyToDirPath);
596 : #endif
597 : }
598 0 : if (NS_FAILED(rv)) {
599 0 : return;
600 : }
601 :
602 0 : if (restart && isStaged) {
603 : // When the update should already be staged make sure that the updated
604 : // directory exists.
605 0 : bool updatedDirExists = false;
606 0 : if (NS_FAILED(updatedDir->Exists(&updatedDirExists)) || !updatedDirExists) {
607 0 : return;
608 : }
609 : }
610 :
611 : // On platforms where we are not calling execv, we may need to make the
612 : // updater executable wait for the calling process to exit. Otherwise, the
613 : // updater may have trouble modifying our executable image (because it might
614 : // still be in use). This is accomplished by passing our PID to the updater so
615 : // that it can wait for us to exit. This is not perfect as there is a race
616 : // condition that could bite us. It's possible that the calling process could
617 : // exit before the updater waits on the specified PID, and in the meantime a
618 : // new process with the same PID could be created. This situation is unlikely,
619 : // however, given the way most operating systems recycle PIDs. We'll take our
620 : // chances ;-)
621 : // Construct the PID argument for this process to pass to the updater.
622 0 : nsAutoCString pid;
623 0 : if (restart) {
624 : #if defined(XP_UNIX) & !defined(XP_MACOSX)
625 : // When execv is used for an update that requires a restart 0 is passed
626 : // which is ignored by the updater.
627 0 : pid.AssignASCII("0");
628 : #else
629 : pid.AppendInt((int32_t) getpid());
630 : #endif
631 0 : if (isStaged) {
632 : // Append a special token to the PID in order to inform the updater that
633 : // it should replace install with the updated directory.
634 0 : pid.AppendLiteral("/replace");
635 : }
636 : } else {
637 : // Signal the updater application that it should stage the update.
638 0 : pid.AssignASCII("-1");
639 : }
640 :
641 0 : int argc = 5;
642 0 : if (restart) {
643 0 : argc = appArgc + 6;
644 : }
645 0 : char **argv = new char*[argc + 1];
646 0 : if (!argv) {
647 0 : return;
648 : }
649 0 : argv[0] = (char*) updaterPath.get();
650 0 : argv[1] = (char*) updateDirPath.get();
651 0 : argv[2] = (char*) installDirPath.get();
652 0 : argv[3] = (char*) applyToDirPath.get();
653 0 : argv[4] = (char*) pid.get();
654 0 : if (restart && appArgc) {
655 0 : argv[5] = workingDirPath;
656 0 : argv[6] = (char*) appFilePath.get();
657 0 : for (int i = 1; i < appArgc; ++i) {
658 0 : argv[6 + i] = appArgv[i];
659 : }
660 0 : argv[argc] = nullptr;
661 : } else {
662 0 : argv[5] = nullptr;
663 : }
664 :
665 0 : if (restart && gSafeMode) {
666 0 : PR_SetEnv("MOZ_SAFE_MODE_RESTART=1");
667 : }
668 :
669 : #if defined(MOZ_VERIFY_MAR_SIGNATURE) && !defined(XP_WIN) && !defined(XP_MACOSX)
670 : AppendToLibPath(installDirPath.get());
671 : #endif
672 :
673 0 : LOG(("spawning updater process [%s]\n", updaterPath.get()));
674 :
675 : #if defined(XP_UNIX) && !defined(XP_MACOSX)
676 : // We use execv to spawn the updater process on all UNIX systems except Mac OSX
677 : // since it is known to cause problems on the Mac. Windows has execv, but it
678 : // is a faked implementation that doesn't really replace the current process.
679 : // Instead it spawns a new process, so we gain nothing from using execv on
680 : // Windows.
681 0 : if (restart) {
682 0 : exit(execv(updaterPath.get(), argv));
683 : }
684 0 : *outpid = fork();
685 0 : if (*outpid == -1) {
686 0 : return;
687 0 : } else if (*outpid == 0) {
688 0 : exit(execv(updaterPath.get(), argv));
689 : }
690 : #elif defined(XP_WIN)
691 : if (isStaged) {
692 : // Launch the updater to replace the installation with the staged updated.
693 : if (!WinLaunchChild(updaterPathW.get(), argc, argv)) {
694 : return;
695 : }
696 : } else {
697 : // Launch the updater to either stage or apply an update.
698 : if (!WinLaunchChild(updaterPathW.get(), argc, argv, nullptr, outpid)) {
699 : return;
700 : }
701 : }
702 : #elif defined(XP_MACOSX)
703 : UpdateDriverSetupMacCommandLine(argc, argv, restart);
704 : // We need to detect whether elevation is required for this update. This can
705 : // occur when an admin user installs the application, but another admin
706 : // user attempts to update (see bug 394984).
707 : if (restart && !IsRecursivelyWritable(installDirPath.get())) {
708 : if (!LaunchElevatedUpdate(argc, argv, outpid)) {
709 : LOG(("Failed to launch elevated update!"));
710 : exit(1);
711 : }
712 : exit(0);
713 : }
714 :
715 : if (isStaged) {
716 : // Launch the updater to replace the installation with the staged updated.
717 : LaunchChildMac(argc, argv);
718 : } else {
719 : // Launch the updater to either stage or apply an update.
720 : LaunchChildMac(argc, argv, outpid);
721 : }
722 : if (restart) {
723 : exit(0);
724 : }
725 : #else
726 : if (isStaged) {
727 : // Launch the updater to replace the installation with the staged updated.
728 : PR_CreateProcessDetached(updaterPath.get(), argv, nullptr, nullptr);
729 : } else {
730 : // Launch the updater to either stage or apply an update.
731 : *outpid = PR_CreateProcess(updaterPath.get(), argv, nullptr, nullptr);
732 : }
733 : #endif
734 : #if !defined(USE_EXECV)
735 0 : if (restart) {
736 0 : exit(0);
737 : }
738 : #endif
739 : }
740 :
741 : /**
742 : * Wait briefly to see if a process terminates, then return true if it has.
743 : */
744 : static bool
745 0 : ProcessHasTerminated(ProcessType pt)
746 : {
747 : #if defined(XP_WIN)
748 : if (WaitForSingleObject(pt, 1000)) {
749 : return false;
750 : }
751 : CloseHandle(pt);
752 : return true;
753 : #elif defined(XP_MACOSX)
754 : // We're waiting for the process to terminate in LaunchChildMac.
755 : return true;
756 : #elif defined(XP_UNIX)
757 : int exitStatus;
758 0 : pid_t exited = waitpid(pt, &exitStatus, WNOHANG);
759 0 : if (exited == 0) {
760 : // Process is still running.
761 0 : sleep(1);
762 0 : return false;
763 : }
764 0 : if (exited == -1) {
765 0 : LOG(("Error while checking if the updater process is finished"));
766 : // This shouldn't happen, but if it does, the updater process is lost to us,
767 : // so the best we can do is pretend that it's exited.
768 0 : return true;
769 : }
770 : // If we get here, the process has exited; make sure it exited normally.
771 0 : if (WIFEXITED(exitStatus) && (WEXITSTATUS(exitStatus) != 0)) {
772 0 : LOG(("Error while running the updater process, check update.log"));
773 : }
774 0 : return true;
775 : #else
776 : // No way to have a non-blocking implementation on these platforms,
777 : // because we're using NSPR and it only provides a blocking wait.
778 : int32_t exitCode;
779 : PR_WaitProcess(pt, &exitCode);
780 : if (exitCode != 0) {
781 : LOG(("Error while running the updater process, check update.log"));
782 : }
783 : return true;
784 : #endif
785 : }
786 :
787 : nsresult
788 1 : ProcessUpdates(nsIFile *greDir, nsIFile *appDir, nsIFile *updRootDir,
789 : int argc, char **argv, const char *appVersion,
790 : bool restart, ProcessType *pid)
791 : {
792 : nsresult rv;
793 :
794 2 : nsCOMPtr<nsIFile> updatesDir;
795 1 : rv = updRootDir->Clone(getter_AddRefs(updatesDir));
796 1 : if (NS_FAILED(rv)) {
797 0 : return rv;
798 : }
799 :
800 1 : rv = updatesDir->AppendNative(NS_LITERAL_CSTRING("updates"));
801 1 : if (NS_FAILED(rv)) {
802 0 : return rv;
803 : }
804 :
805 1 : rv = updatesDir->AppendNative(NS_LITERAL_CSTRING("0"));
806 1 : if (NS_FAILED(rv)) {
807 0 : return rv;
808 : }
809 :
810 : // Return early since there isn't a valid update when the update application
811 : // version file doesn't exist or if the update's application version is less
812 : // than the current application version. The cleanup of the update will happen
813 : // during post update processing in nsUpdateService.js.
814 2 : nsCOMPtr<nsIFile> versionFile;
815 2 : if (!GetVersionFile(updatesDir, versionFile) ||
816 1 : IsOlderVersion(versionFile, appVersion)) {
817 1 : return NS_OK;
818 : }
819 :
820 0 : nsCOMPtr<nsIFile> statusFile;
821 0 : UpdateStatus status = GetUpdateStatus(updatesDir, statusFile);
822 0 : switch (status) {
823 : case ePendingElevate: {
824 0 : if (NS_IsMainThread()) {
825 : // Only do this if we're called from the main thread.
826 : nsCOMPtr<nsIUpdatePrompt> up =
827 0 : do_GetService("@mozilla.org/updates/update-prompt;1");
828 0 : if (up) {
829 0 : up->ShowUpdateElevationRequired();
830 : }
831 0 : break;
832 : }
833 : // Intentional fallthrough to ePendingUpdate and ePendingService.
834 : MOZ_FALLTHROUGH;
835 : }
836 : case ePendingUpdate:
837 : case ePendingService: {
838 0 : ApplyUpdate(greDir, updatesDir, appDir, argc, argv, restart, false, pid);
839 0 : break;
840 : }
841 : case eAppliedUpdate:
842 : case eAppliedService:
843 : // An update was staged and needs to be switched so the updated application
844 : // is used.
845 0 : ApplyUpdate(greDir, updatesDir, appDir, argc, argv, restart, true, pid);
846 0 : break;
847 : case eNoUpdateAction:
848 : // We don't need to do any special processing here, we'll just continue to
849 : // startup the application.
850 0 : break;
851 : }
852 :
853 0 : return NS_OK;
854 : }
855 :
856 :
857 :
858 0 : NS_IMPL_ISUPPORTS(nsUpdateProcessor, nsIUpdateProcessor)
859 :
860 0 : nsUpdateProcessor::nsUpdateProcessor()
861 0 : : mUpdaterPID(0)
862 : {
863 0 : }
864 :
865 0 : nsUpdateProcessor::~nsUpdateProcessor()
866 : {
867 0 : }
868 :
869 : NS_IMETHODIMP
870 0 : nsUpdateProcessor::ProcessUpdate(nsIUpdate* aUpdate)
871 : {
872 : nsresult rv;
873 :
874 : nsCOMPtr<nsIProperties> ds =
875 0 : do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID, &rv);
876 0 : NS_ENSURE_SUCCESS(rv, rv);
877 :
878 0 : nsCOMPtr<nsIFile> exeFile;
879 0 : rv = ds->Get(XRE_EXECUTABLE_FILE, NS_GET_IID(nsIFile),
880 0 : getter_AddRefs(exeFile));
881 0 : NS_ENSURE_SUCCESS(rv, rv);
882 :
883 0 : nsCOMPtr<nsIFile> appDir;
884 0 : rv = exeFile->GetParent(getter_AddRefs(appDir));
885 0 : NS_ENSURE_SUCCESS(rv, rv);
886 :
887 0 : nsCOMPtr<nsIFile> greDir;
888 0 : rv = ds->Get(NS_GRE_DIR, NS_GET_IID(nsIFile), getter_AddRefs(greDir));
889 0 : NS_ENSURE_SUCCESS(rv, rv);
890 :
891 0 : nsCOMPtr<nsIFile> updRoot;
892 0 : rv = ds->Get(XRE_UPDATE_ROOT_DIR, NS_GET_IID(nsIFile),
893 0 : getter_AddRefs(updRoot));
894 0 : NS_ASSERTION(NS_SUCCEEDED(rv), "Can't get the UpdRootD dir");
895 :
896 : // XRE_UPDATE_ROOT_DIR should not fail but if it does fallback to the
897 : // application directory just to be safe.
898 0 : if (NS_FAILED(rv)) {
899 0 : rv = appDir->Clone(getter_AddRefs(updRoot));
900 0 : NS_ENSURE_SUCCESS(rv, rv);
901 : }
902 :
903 : nsCOMPtr<nsIXULAppInfo> appInfo =
904 0 : do_GetService("@mozilla.org/xre/app-info;1", &rv);
905 0 : NS_ENSURE_SUCCESS(rv, rv);
906 :
907 0 : nsAutoCString appVersion;
908 0 : rv = appInfo->GetVersion(appVersion);
909 0 : NS_ENSURE_SUCCESS(rv, rv);
910 :
911 : // Copy the parameters to the StagedUpdateInfo structure shared with the
912 : // watcher thread.
913 0 : mInfo.mGREDir = greDir;
914 0 : mInfo.mAppDir = appDir;
915 0 : mInfo.mUpdateRoot = updRoot;
916 0 : mInfo.mArgc = 0;
917 0 : mInfo.mArgv = nullptr;
918 0 : mInfo.mAppVersion = appVersion;
919 :
920 0 : MOZ_ASSERT(NS_IsMainThread(), "not main thread");
921 : nsCOMPtr<nsIRunnable> r =
922 0 : NewRunnableMethod("nsUpdateProcessor::StartStagedUpdate",
923 : this,
924 0 : &nsUpdateProcessor::StartStagedUpdate);
925 0 : return NS_NewNamedThread("Update Watcher", getter_AddRefs(mProcessWatcher),
926 0 : r);
927 : }
928 :
929 :
930 :
931 : void
932 0 : nsUpdateProcessor::StartStagedUpdate()
933 : {
934 0 : MOZ_ASSERT(!NS_IsMainThread(), "main thread");
935 :
936 0 : nsresult rv = ProcessUpdates(mInfo.mGREDir,
937 : mInfo.mAppDir,
938 : mInfo.mUpdateRoot,
939 : mInfo.mArgc,
940 : mInfo.mArgv,
941 : mInfo.mAppVersion.get(),
942 : false,
943 0 : &mUpdaterPID);
944 0 : NS_ENSURE_SUCCESS_VOID(rv);
945 :
946 0 : if (mUpdaterPID) {
947 : // Track the state of the updater process while it is staging an update.
948 0 : rv = NS_DispatchToCurrentThread(
949 0 : NewRunnableMethod("nsUpdateProcessor::WaitForProcess",
950 : this,
951 0 : &nsUpdateProcessor::WaitForProcess));
952 0 : NS_ENSURE_SUCCESS_VOID(rv);
953 : } else {
954 : // Failed to launch the updater process for some reason.
955 : // We need to shutdown the current thread as there isn't anything more for
956 : // us to do...
957 0 : rv = NS_DispatchToMainThread(
958 0 : NewRunnableMethod("nsUpdateProcessor::ShutdownWatcherThread",
959 : this,
960 0 : &nsUpdateProcessor::ShutdownWatcherThread));
961 0 : NS_ENSURE_SUCCESS_VOID(rv);
962 : }
963 : }
964 :
965 : void
966 0 : nsUpdateProcessor::ShutdownWatcherThread()
967 : {
968 0 : MOZ_ASSERT(NS_IsMainThread(), "not main thread");
969 0 : mProcessWatcher->Shutdown();
970 0 : mProcessWatcher = nullptr;
971 0 : }
972 :
973 : void
974 0 : nsUpdateProcessor::WaitForProcess()
975 : {
976 0 : MOZ_ASSERT(!NS_IsMainThread(), "main thread");
977 0 : if (ProcessHasTerminated(mUpdaterPID)) {
978 0 : NS_DispatchToMainThread(NewRunnableMethod(
979 0 : "nsUpdateProcessor::UpdateDone", this, &nsUpdateProcessor::UpdateDone));
980 : } else {
981 0 : NS_DispatchToCurrentThread(
982 0 : NewRunnableMethod("nsUpdateProcessor::WaitForProcess",
983 : this,
984 0 : &nsUpdateProcessor::WaitForProcess));
985 : }
986 0 : }
987 :
988 : void
989 0 : nsUpdateProcessor::UpdateDone()
990 : {
991 0 : MOZ_ASSERT(NS_IsMainThread(), "not main thread");
992 :
993 : nsCOMPtr<nsIUpdateManager> um =
994 0 : do_GetService("@mozilla.org/updates/update-manager;1");
995 0 : if (um) {
996 0 : um->RefreshUpdateStatus();
997 : }
998 :
999 0 : ShutdownWatcherThread();
1000 0 : }
|