Coverage Report

Created: 2018-09-25 14:53

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