Bug Summary

File:var/lib/jenkins/workspace/firefox-scan-build/toolkit/mozapps/update/updater/updater.cpp
Warning:line 4725, column 7
Potential leak of memory pointed to by 'action'

Annotated Source Code

Press '?' to see keyboard shortcuts

clang -cc1 -cc1 -triple x86_64-pc-linux-gnu -analyze -disable-free -clear-ast-before-backend -disable-llvm-verifier -discard-value-names -main-file-name updater.cpp -analyzer-checker=core -analyzer-checker=apiModeling -analyzer-checker=unix -analyzer-checker=deadcode -analyzer-checker=cplusplus -analyzer-checker=security.insecureAPI.UncheckedReturn -analyzer-checker=security.insecureAPI.getpw -analyzer-checker=security.insecureAPI.gets -analyzer-checker=security.insecureAPI.mktemp -analyzer-checker=security.insecureAPI.mkstemp -analyzer-checker=security.insecureAPI.vfork -analyzer-checker=nullability.NullPassedToNonnull -analyzer-checker=nullability.NullReturnedFromNonnull -analyzer-output plist -w -setup-static-analyzer -analyzer-config-compatibility-mode=true -mrelocation-model pic -pic-level 2 -fhalf-no-semantic-interposition -mframe-pointer=all -relaxed-aliasing -ffp-contract=off -fno-rounding-math -mconstructor-aliases -funwind-tables=2 -target-cpu x86-64 -tune-cpu generic -debugger-tuning=gdb -fdebug-compilation-dir=/var/lib/jenkins/workspace/firefox-scan-build/obj-x86_64-pc-linux-gnu/toolkit/mozapps/update/updater/updater-dep -fcoverage-compilation-dir=/var/lib/jenkins/workspace/firefox-scan-build/obj-x86_64-pc-linux-gnu/toolkit/mozapps/update/updater/updater-dep -resource-dir /usr/lib/llvm-18/lib/clang/18 -include /var/lib/jenkins/workspace/firefox-scan-build/config/gcc_hidden.h -include /var/lib/jenkins/workspace/firefox-scan-build/obj-x86_64-pc-linux-gnu/mozilla-config.h -I /var/lib/jenkins/workspace/firefox-scan-build/obj-x86_64-pc-linux-gnu/dist/system_wrappers -U _FORTIFY_SOURCE -D _FORTIFY_SOURCE=2 -D DEBUG=1 -D DEP_UPDATER -D MAR_NSS -D SPRINTF_H_USES_VSNPRINTF -D NS_NO_XPCOM -D MAR_CHANNEL_ID="" -D MOZ_APP_VERSION="120.0a1" -I /var/lib/jenkins/workspace/firefox-scan-build/toolkit/mozapps/update/updater/updater-dep -I /var/lib/jenkins/workspace/firefox-scan-build/obj-x86_64-pc-linux-gnu/toolkit/mozapps/update/updater/updater-dep -I /var/lib/jenkins/workspace/firefox-scan-build/toolkit/mozapps/update/common -I /var/lib/jenkins/workspace/firefox-scan-build/xpcom/base -I /var/lib/jenkins/workspace/firefox-scan-build/obj-x86_64-pc-linux-gnu/dist/include -I /var/lib/jenkins/workspace/firefox-scan-build/obj-x86_64-pc-linux-gnu/dist/include/nspr -I /var/lib/jenkins/workspace/firefox-scan-build/obj-x86_64-pc-linux-gnu/dist/include/nss -D MOZILLA_CLIENT -I /usr/include/gtk-3.0 -I /usr/include/pango-1.0 -I /usr/include/glib-2.0 -I /usr/lib/x86_64-linux-gnu/glib-2.0/include -I /usr/include/harfbuzz -I /usr/include/freetype2 -I /usr/include/libpng16 -I /usr/include/libmount -I /usr/include/blkid -I /usr/include/fribidi -I /usr/include/cairo -I /usr/include/pixman-1 -I /usr/include/gdk-pixbuf-2.0 -I /usr/include/x86_64-linux-gnu -I /usr/include/webp -I /usr/include/gio-unix-2.0 -I /usr/include/cloudproviders -I /usr/include/atk-1.0 -I /usr/include/at-spi2-atk/2.0 -I /usr/include/at-spi-2.0 -I /usr/include/dbus-1.0 -I /usr/lib/x86_64-linux-gnu/dbus-1.0/include -I /usr/include/gtk-3.0/unix-print -internal-isystem /usr/bin/../lib/gcc/x86_64-linux-gnu/13/../../../../include/c++/13 -internal-isystem /usr/bin/../lib/gcc/x86_64-linux-gnu/13/../../../../include/x86_64-linux-gnu/c++/13 -internal-isystem /usr/bin/../lib/gcc/x86_64-linux-gnu/13/../../../../include/c++/13/backward -internal-isystem /usr/lib/llvm-18/lib/clang/18/include -internal-isystem /usr/local/include -internal-isystem /usr/bin/../lib/gcc/x86_64-linux-gnu/13/../../../../x86_64-linux-gnu/include -internal-externc-isystem /usr/include/x86_64-linux-gnu -internal-externc-isystem /include -internal-externc-isystem /usr/include -O2 -Wno-error=tautological-type-limit-compare -Wno-invalid-offsetof -Wno-range-loop-analysis -Wno-error=deprecated -Wno-error=deprecated-anon-enum-enum-conversion -Wno-error=deprecated-enum-enum-conversion -Wno-error=deprecated-pragma -Wno-error=deprecated-this-capture -Wno-inline-new-delete -Wno-error=deprecated-declarations -Wno-error=array-bounds -Wno-error=free-nonheap-object -Wno-error=atomic-alignment -Wno-error=deprecated-builtins -Wno-psabi -Wno-error=builtin-macro-redefined -Wno-unknown-warning-option -fdeprecated-macro -ferror-limit 19 -stack-protector 2 -fstack-clash-protection -ftrivial-auto-var-init=pattern -fno-rtti -fgnuc-version=4.2.1 -fno-aligned-allocation -vectorize-loops -vectorize-slp -analyzer-checker optin.performance.Padding -analyzer-output=html -analyzer-config stable-report-filename=true -faddrsig -D__GCC_HAVE_DWARF2_CFI_ASM=1 -o /tmp/scan-build-2023-09-28-025625-16803-1 -x c++ /var/lib/jenkins/workspace/firefox-scan-build/toolkit/mozapps/update/updater/updater.cpp
1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5/**
6 * Manifest Format
7 * ---------------
8 *
9 * contents = 1*( line )
10 * line = method LWS *( param LWS ) CRLF
11 * CRLF = "\r\n"
12 * LWS = 1*( " " | "\t" )
13 *
14 * Available methods for the manifest file:
15 *
16 * updatev3.manifest
17 * -----------------
18 * method = "add" | "add-if" | "add-if-not" | "patch" | "patch-if" |
19 * "remove" | "rmdir" | "rmrfdir" | type
20 *
21 * 'add-if-not' adds a file if it doesn't exist.
22 *
23 * 'type' is the update type (e.g. complete or partial) and when present MUST
24 * be the first entry in the update manifest. The type is used to support
25 * removing files that no longer exist when when applying a complete update by
26 * causing the actions defined in the precomplete file to be performed.
27 *
28 * precomplete
29 * -----------
30 * method = "remove" | "rmdir"
31 */
32#include "bspatch.h"
33#include "progressui.h"
34#include "archivereader.h"
35#include "readstrings.h"
36#include "updatererrors.h"
37
38#include <stdio.h>
39#include <string.h>
40#include <stdlib.h>
41
42#include <sys/stat.h>
43#include <fcntl.h>
44#include <limits.h>
45#include <errno(*__errno_location ()).h>
46
47#include "updatecommon.h"
48#ifdef XP_MACOSX
49# include "updaterfileutils_osx.h"
50#endif // XP_MACOSX
51
52#include "mozilla/CmdLineAndEnvUtils.h"
53#include "mozilla/UniquePtr.h"
54#ifdef XP_WIN
55# include "mozilla/Maybe.h"
56# include "mozilla/WinHeaderOnlyUtils.h"
57# include <climits>
58#endif // XP_WIN
59
60// Amount of the progress bar to use in each of the 3 update stages,
61// should total 100.0.
62#define PROGRESS_PREPARE_SIZE20.0f 20.0f
63#define PROGRESS_EXECUTE_SIZE75.0f 75.0f
64#define PROGRESS_FINISH_SIZE5.0f 5.0f
65
66// Maximum amount of time in ms to wait for the parent process to close. The 30
67// seconds is rather long but there have been bug reports where the parent
68// process has exited after 10 seconds and it is better to give it a chance.
69#define PARENT_WAIT30000 30000
70
71#if defined(XP_MACOSX)
72// These functions are defined in launchchild_osx.mm
73void CleanupElevatedMacUpdate(bool aFailureOccurred);
74bool IsOwnedByGroupAdmin(const char* aAppBundle);
75bool IsRecursivelyWritable(const char* aPath);
76void LaunchChild(int argc, const char** argv);
77void LaunchMacPostProcess(const char* aAppBundle);
78bool ObtainUpdaterArguments(int* argc, char*** argv);
79bool ServeElevatedUpdate(int argc, const char** argv);
80void SetGroupOwnershipAndPermissions(const char* aAppBundle);
81bool PerformInstallationFromDMG(int argc, char** argv);
82struct UpdateServerThreadArgs {
83 int argc;
84 const NS_tchar** argv;
85};
86#endif
87
88#ifndef _O_BINARY0
89# define _O_BINARY0 0
90#endif
91
92#ifndef NULL__null
93# define NULL__null (0)
94#endif
95
96#ifndef SSIZE_MAX9223372036854775807L
97# define SSIZE_MAX9223372036854775807L LONG_MAX9223372036854775807L
98#endif
99
100// We want to use execv to invoke the callback executable on platforms where
101// we were launched using execv. See nsUpdateDriver.cpp.
102#if defined(XP_UNIX1) && !defined(XP_MACOSX)
103# define USE_EXECV
104#endif
105
106#if defined(XP_OPENBSD)
107# define stat64 stat
108#endif
109
110#if defined(MOZ_VERIFY_MAR_SIGNATURE1) && defined(MAR_NSS1)
111# include "nss.h"
112# include "prerror.h"
113#endif
114
115#include "crctable.h"
116
117#ifdef XP_WIN
118# ifdef MOZ_MAINTENANCE_SERVICE
119# include "registrycertificates.h"
120# endif
121BOOL PathAppendSafe(LPWSTR base, LPCWSTR extra);
122BOOL PathGetSiblingFilePath(LPWSTR destinationBuffer, LPCWSTR siblingFilePath,
123 LPCWSTR newFileName);
124# include "updatehelper.h"
125
126// Closes the handle if valid and if the updater is elevated returns with the
127// return code specified. This prevents multiple launches of the callback
128// application by preventing the elevated process from launching the callback.
129# define EXIT_WHEN_ELEVATED(path, handle, retCode) \
130 { \
131 if (handle != INVALID_HANDLE_VALUE) { \
132 CloseHandle(handle); \
133 } \
134 if (NS_tremoveremove(path) && errno(*__errno_location ()) != ENOENT2) { \
135 return retCode; \
136 } \
137 }
138#endif
139
140//-----------------------------------------------------------------------------
141
142// This BZ2_crc32Table variable lives in libbz2. We just took the
143// data structure from bz2 and created crctables.h
144
145static unsigned int crc32(const unsigned char* buf, unsigned int len) {
146 unsigned int crc = 0xffffffffL;
147
148 const unsigned char* end = buf + len;
149 for (; buf != end; ++buf)
150 crc = (crc << 8) ^ BZ2_crc32Table[(crc >> 24) ^ *buf];
151
152 crc = ~crc;
153 return crc;
154}
155
156//-----------------------------------------------------------------------------
157
158// A simple stack based container for a FILE struct that closes the
159// file descriptor from its destructor.
160class AutoFile {
161 public:
162 explicit AutoFile(FILE* file = nullptr) : mFile(file) {}
163
164 ~AutoFile() {
165 if (mFile != nullptr) {
166 fclose(mFile);
167 }
168 }
169
170 AutoFile& operator=(FILE* file) {
171 if (mFile != 0) {
172 fclose(mFile);
173 }
174 mFile = file;
175 return *this;
176 }
177
178 operator FILE*() { return mFile; }
179
180 FILE* get() { return mFile; }
181
182 private:
183 FILE* mFile;
184};
185
186struct MARChannelStringTable {
187 MARChannelStringTable() {
188 MARChannelID = mozilla::MakeUnique<char[]>(1);
189 MARChannelID[0] = '\0';
190 }
191
192 mozilla::UniquePtr<char[]> MARChannelID;
193};
194
195//-----------------------------------------------------------------------------
196
197#ifdef XP_MACOSX
198
199// Just a simple class that sets a umask value in its constructor and resets
200// it in its destructor.
201class UmaskContext {
202 public:
203 explicit UmaskContext(mode_t umaskToSet);
204 ~UmaskContext();
205
206 private:
207 mode_t mPreviousUmask;
208};
209
210UmaskContext::UmaskContext(mode_t umaskToSet) {
211 mPreviousUmask = umask(umaskToSet);
212}
213
214UmaskContext::~UmaskContext() { umask(mPreviousUmask); }
215
216#endif
217
218//-----------------------------------------------------------------------------
219
220typedef void (*ThreadFunc)(void* param);
221
222#ifdef XP_WIN
223# include <process.h>
224
225class Thread {
226 public:
227 int Run(ThreadFunc func, void* param) {
228 mThreadFunc = func;
229 mThreadParam = param;
230
231 unsigned int threadID;
232
233 mThread =
234 (HANDLE)_beginthreadex(nullptr, 0, ThreadMain, this, 0, &threadID);
235
236 return mThread ? 0 : -1;
237 }
238 int Join() {
239 WaitForSingleObject(mThread, INFINITE);
240 CloseHandle(mThread);
241 return 0;
242 }
243
244 private:
245 static unsigned __stdcall ThreadMain(void* p) {
246 Thread* self = (Thread*)p;
247 self->mThreadFunc(self->mThreadParam);
248 return 0;
249 }
250 HANDLE mThread;
251 ThreadFunc mThreadFunc;
252 void* mThreadParam;
253};
254
255#elif defined(XP_UNIX1)
256# include <pthread.h>
257
258class Thread {
259 public:
260 int Run(ThreadFunc func, void* param) {
261 return pthread_create(&thr, nullptr, (void* (*)(void*))func, param);
262 }
263 int Join() {
264 void* result;
265 return pthread_join(thr, &result);
266 }
267
268 private:
269 pthread_t thr;
270};
271
272#else
273# error "Unsupported platform"
274#endif
275
276//-----------------------------------------------------------------------------
277
278static NS_tchar gPatchDirPath[MAXPATHLEN4096];
279static NS_tchar gInstallDirPath[MAXPATHLEN4096];
280static NS_tchar gWorkingDirPath[MAXPATHLEN4096];
281static ArchiveReader gArchiveReader;
282static bool gSucceeded = false;
283static bool sStagedUpdate = false;
284static bool sReplaceRequest = false;
285static bool sUsingService = false;
286
287// Normally, we run updates as a result of user action (the user started Firefox
288// or clicked a "Restart to Update" button). But there are some cases when
289// we are not:
290// a) The callback app is a background task. If true then the updater is
291// likely being run as part of a background task.
292// The updater could be run with no callback, but this only happens
293// when performing a staged update (see calls to ProcessUpdates), and there
294// are already checks for sStagedUpdate when showing UI or elevating.
295// b) The environment variable MOZ_APP_SILENT_START is set and not empty. This
296// is set, for instance, on macOS when Firefox had no windows open for a
297// while and restarted to apply updates.
298//
299// In these cases, the update should be installed silently, so we shouldn't:
300// a) show progress UI
301// b) prompt for elevation
302static bool sUpdateSilently = false;
303
304#ifdef XP_WIN
305static NS_tchar gCallbackRelPath[MAXPATHLEN4096];
306static NS_tchar gCallbackBackupPath[MAXPATHLEN4096];
307static NS_tchar gDeleteDirPath[MAXPATHLEN4096];
308
309// Whether to copy the update.log and update.status file to the update patch
310// directory from a secure directory.
311static bool gCopyOutputFiles = false;
312// Whether to write the update.log and update.status file to a secure directory.
313static bool gUseSecureOutputPath = false;
314#endif
315
316static const NS_tchar kWhitespace[] = NS_T(" \t")" \t";
317static const NS_tchar kNL[] = NS_T("\r\n")"\r\n";
318static const NS_tchar kQuote[] = NS_T("\"")"\"";
319
320static inline size_t mmin(size_t a, size_t b) { return (a > b) ? b : a; }
321
322static NS_tchar* mstrtok(const NS_tchar* delims, NS_tchar** str) {
323 if (!*str || !**str) {
324 *str = nullptr;
325 return nullptr;
326 }
327
328 // skip leading "whitespace"
329 NS_tchar* ret = *str;
330 const NS_tchar* d;
331 do {
332 for (d = delims; *d != NS_T('\0')'\0'; ++d) {
333 if (*ret == *d) {
334 ++ret;
335 break;
336 }
337 }
338 } while (*d);
339
340 if (!*ret) {
341 *str = ret;
342 return nullptr;
343 }
344
345 NS_tchar* i = ret;
346 do {
347 for (d = delims; *d != NS_T('\0')'\0'; ++d) {
348 if (*i == *d) {
349 *i = NS_T('\0')'\0';
350 *str = ++i;
351 return ret;
352 }
353 }
354 ++i;
355 } while (*i);
356
357 *str = nullptr;
358 return ret;
359}
360
361#if defined(TEST_UPDATER) || defined(XP_WIN) || defined(XP_MACOSX)
362static bool EnvHasValue(const char* name) {
363 const char* val = getenv(name);
364 return (val && *val);
365}
366#endif
367
368#ifdef XP_WIN
369/**
370 * Obtains the update ID from the secure id file located in secure output
371 * directory.
372 *
373 * @param outBuf
374 * A buffer of size UUID_LEN (e.g. 37) to store the result. The uuid is
375 * 36 characters in length and 1 more for null termination.
376 * @return true if successful
377 */
378bool GetSecureID(char* outBuf) {
379 NS_tchar idFilePath[MAX_PATH + 1] = {L'\0'};
380 if (!GetSecureOutputFilePath(gPatchDirPath, L".id", idFilePath)) {
381 return false;
382 }
383
384 AutoFile idFile(NS_tfopenfopen(idFilePath, NS_T("rb")"rb"));
385 if (idFile == nullptr) {
386 return false;
387 }
388
389 size_t read = fread(outBuf, UUID_LEN - 1, 1, idFile);
390 if (read != 1) {
391 return false;
392 }
393
394 outBuf[UUID_LEN - 1] = '\0';
395 return true;
396}
397#endif
398
399/**
400 * Calls LogFinish for the update log. On Windows, the unelevated updater copies
401 * the update status file and the update log file that were written by the
402 * elevated updater from the secure directory to the update patch directory.
403 *
404 * NOTE: All calls to WriteStatusFile MUST happen before calling output_finish
405 * because this function copies the update status file for the elevated
406 * updater and writing the status file after calling output_finish will
407 * overwrite it.
408 */
409static void output_finish() {
410 LogFinish()UpdateLog::GetPrimaryLog().Finish();
411#ifdef XP_WIN
412 if (gCopyOutputFiles) {
413 NS_tchar srcStatusPath[MAXPATHLEN4096 + 1] = {NS_T('\0')'\0'};
414 if (GetSecureOutputFilePath(gPatchDirPath, L".status", srcStatusPath)) {
415 NS_tchar dstStatusPath[MAXPATHLEN4096 + 1] = {NS_T('\0')'\0'};
416 NS_tsnprintfsnprintf(dstStatusPath,
417 sizeof(dstStatusPath) / sizeof(dstStatusPath[0]),
418 NS_T("%s\\update.status")"%s\\update.status", gPatchDirPath);
419 CopyFileW(srcStatusPath, dstStatusPath, false);
420 }
421
422 NS_tchar srcLogPath[MAXPATHLEN4096 + 1] = {NS_T('\0')'\0'};
423 if (GetSecureOutputFilePath(gPatchDirPath, L".log", srcLogPath)) {
424 NS_tchar dstLogPath[MAXPATHLEN4096 + 1] = {NS_T('\0')'\0'};
425 NS_tsnprintfsnprintf(dstLogPath, sizeof(dstLogPath) / sizeof(dstLogPath[0]),
426 NS_T("%s\\update.log")"%s\\update.log", gPatchDirPath);
427 CopyFileW(srcLogPath, dstLogPath, false);
428 }
429 }
430#endif
431}
432
433/**
434 * Coverts a relative update path to a full path.
435 *
436 * @param relpath
437 * The relative path to convert to a full path.
438 * @return valid filesystem full path or nullptr if memory allocation fails.
439 */
440static NS_tchar* get_full_path(const NS_tchar* relpath) {
441 NS_tchar* destpath = sStagedUpdate ? gWorkingDirPath : gInstallDirPath;
442 size_t lendestpath = NS_tstrlenstrlen(destpath);
443 size_t lenrelpath = NS_tstrlenstrlen(relpath);
444 NS_tchar* s = new NS_tchar[lendestpath + lenrelpath + 2];
445
446 NS_tchar* c = s;
447
448 NS_tstrcpystrcpy(c, destpath);
449 c += lendestpath;
450 NS_tstrcatstrcat(c, NS_T("/")"/");
451 c++;
452
453 NS_tstrcatstrcat(c, relpath);
454 c += lenrelpath;
455 *c = NS_T('\0')'\0';
456 return s;
457}
458
459/**
460 * Converts a full update path into a relative path; reverses get_full_path.
461 *
462 * @param fullpath
463 * The absolute path to convert into a relative path.
464 * return pointer to the location within fullpath where the relative path starts
465 * or fullpath itself if it already looks relative.
466 */
467#ifndef XP_WIN
468static const NS_tchar* get_relative_path(const NS_tchar* fullpath) {
469 if (fullpath[0] != '/') {
470 return fullpath;
471 }
472
473 NS_tchar* prefix = sStagedUpdate ? gWorkingDirPath : gInstallDirPath;
474
475 // If the path isn't long enough to be absolute, return it as-is.
476 if (NS_tstrlenstrlen(fullpath) <= NS_tstrlenstrlen(prefix)) {
477 return fullpath;
478 }
479
480 return fullpath + NS_tstrlenstrlen(prefix) + 1;
481}
482#endif
483
484/**
485 * Gets the platform specific path and performs simple checks to the path. If
486 * the path checks don't pass nullptr will be returned.
487 *
488 * @param line
489 * The line from the manifest that contains the path.
490 * @param isdir
491 * Whether the path is a directory path. Defaults to false.
492 * @return valid filesystem path or nullptr if the path checks fail.
493 */
494static NS_tchar* get_valid_path(NS_tchar** line, bool isdir = false) {
495 NS_tchar* path = mstrtok(kQuote, line);
496 if (!path) {
497 LOG(("get_valid_path: unable to determine path: " LOG_S, *line))UpdateLog::GetPrimaryLog().Printf ("get_valid_path: unable to determine path: "
"%s", *line)
;
498 return nullptr;
499 }
500
501 // All paths must be relative from the current working directory
502 if (path[0] == NS_T('/')'/') {
503 LOG(("get_valid_path: path must be relative: " LOG_S, path))UpdateLog::GetPrimaryLog().Printf ("get_valid_path: path must be relative: "
"%s", path)
;
504 return nullptr;
505 }
506
507#ifdef XP_WIN
508 // All paths must be relative from the current working directory
509 if (path[0] == NS_T('\\')'\\' || path[1] == NS_T(':')':') {
510 LOG(("get_valid_path: path must be relative: " LOG_S, path))UpdateLog::GetPrimaryLog().Printf ("get_valid_path: path must be relative: "
"%s", path)
;
511 return nullptr;
512 }
513#endif
514
515 if (isdir) {
516 // Directory paths must have a trailing forward slash.
517 if (path[NS_tstrlenstrlen(path) - 1] != NS_T('/')'/') {
518 LOG(UpdateLog::GetPrimaryLog().Printf ("get_valid_path: directory paths must have a trailing forward "
"slash: " "%s", path)
519 ("get_valid_path: directory paths must have a trailing forward "UpdateLog::GetPrimaryLog().Printf ("get_valid_path: directory paths must have a trailing forward "
"slash: " "%s", path)
520 "slash: " LOG_S,UpdateLog::GetPrimaryLog().Printf ("get_valid_path: directory paths must have a trailing forward "
"slash: " "%s", path)
521 path))UpdateLog::GetPrimaryLog().Printf ("get_valid_path: directory paths must have a trailing forward "
"slash: " "%s", path)
;
522 return nullptr;
523 }
524
525 // Remove the trailing forward slash because stat on Windows will return
526 // ENOENT if the path has a trailing slash.
527 path[NS_tstrlenstrlen(path) - 1] = NS_T('\0')'\0';
528 }
529
530 // Don't allow relative paths that resolve to a parent directory.
531 if (NS_tstrstrstrstr(path, NS_T("..")"..") != nullptr) {
532 LOG(("get_valid_path: paths must not contain '..': " LOG_S, path))UpdateLog::GetPrimaryLog().Printf ("get_valid_path: paths must not contain '..': "
"%s", path)
;
533 return nullptr;
534 }
535
536 return path;
537}
538
539/*
540 * Gets a quoted path. The return value is malloc'd and it is the responsibility
541 * of the caller to free it.
542 *
543 * @param path
544 * The path to quote.
545 * @return On success the quoted path and nullptr otherwise.
546 */
547static NS_tchar* get_quoted_path(const NS_tchar* path) {
548 size_t lenQuote = NS_tstrlenstrlen(kQuote);
549 size_t lenPath = NS_tstrlenstrlen(path);
550 size_t len = lenQuote + lenPath + lenQuote + 1;
551
552 NS_tchar* s = (NS_tchar*)malloc(len * sizeof(NS_tchar));
553 if (!s) {
554 return nullptr;
555 }
556
557 NS_tchar* c = s;
558 NS_tstrcpystrcpy(c, kQuote);
559 c += lenQuote;
560 NS_tstrcatstrcat(c, path);
561 c += lenPath;
562 NS_tstrcatstrcat(c, kQuote);
563 c += lenQuote;
564 *c = NS_T('\0')'\0';
565 return s;
566}
567
568static void ensure_write_permissions(const NS_tchar* path) {
569#ifdef XP_WIN
570 (void)_wchmod(path, _S_IREAD | _S_IWRITE);
571#else
572 struct stat fs;
573 if (!stat(path, &fs) && !(fs.st_mode & S_IWUSR0200)) {
574 (void)chmod(path, fs.st_mode | S_IWUSR0200);
575 }
576#endif
577}
578
579static int ensure_remove(const NS_tchar* path) {
580 ensure_write_permissions(path);
581 int rv = NS_tremoveremove(path);
582 if (rv) {
583 LOG(("ensure_remove: failed to remove file: " LOG_S ", rv: %d, err: %d",UpdateLog::GetPrimaryLog().Printf ("ensure_remove: failed to remove file: "
"%s" ", rv: %d, err: %d", path, rv, (*__errno_location ()))
584 path, rv, errno))UpdateLog::GetPrimaryLog().Printf ("ensure_remove: failed to remove file: "
"%s" ", rv: %d, err: %d", path, rv, (*__errno_location ()))
;
585 }
586 return rv;
587}
588
589// Remove the directory pointed to by path and all of its files and
590// sub-directories.
591static int ensure_remove_recursive(const NS_tchar* path,
592 bool continueEnumOnFailure = false) {
593 // We use lstat rather than stat here so that we can successfully remove
594 // symlinks.
595 struct NS_tstat_tstat sInfo;
596 int rv = NS_tlstatlstat(path, &sInfo);
597 if (rv) {
598 // This error is benign
599 return rv;
600 }
601 if (!S_ISDIR(sInfo.st_mode)((((sInfo.st_mode)) & 0170000) == (0040000))) {
602 return ensure_remove(path);
603 }
604
605 NS_tDIRDIR* dir;
606 NS_tdirentdirent* entry;
607
608 dir = NS_topendiropendir(path);
609 if (!dir) {
610 LOG(("ensure_remove_recursive: unable to open directory: " LOG_SUpdateLog::GetPrimaryLog().Printf ("ensure_remove_recursive: unable to open directory: "
"%s" ", rv: %d, err: %d", path, rv, (*__errno_location ()))
611 ", rv: %d, err: %d",UpdateLog::GetPrimaryLog().Printf ("ensure_remove_recursive: unable to open directory: "
"%s" ", rv: %d, err: %d", path, rv, (*__errno_location ()))
612 path, rv, errno))UpdateLog::GetPrimaryLog().Printf ("ensure_remove_recursive: unable to open directory: "
"%s" ", rv: %d, err: %d", path, rv, (*__errno_location ()))
;
613 return rv;
614 }
615
616 while ((entry = NS_treaddirreaddir(dir)) != 0) {
617 if (NS_tstrcmpstrcmp(entry->d_name, NS_T(".")".") &&
618 NS_tstrcmpstrcmp(entry->d_name, NS_T("..")"..")) {
619 NS_tchar childPath[MAXPATHLEN4096];
620 NS_tsnprintfsnprintf(childPath, sizeof(childPath) / sizeof(childPath[0]),
621 NS_T("%s/%s")"%s/%s", path, entry->d_name);
622 rv = ensure_remove_recursive(childPath);
623 if (rv && !continueEnumOnFailure) {
624 break;
625 }
626 }
627 }
628
629 NS_tclosedirclosedir(dir);
630
631 if (rv == OK0) {
632 ensure_write_permissions(path);
633 rv = NS_trmdirrmdir(path);
634 if (rv) {
635 LOG(("ensure_remove_recursive: unable to remove directory: " LOG_SUpdateLog::GetPrimaryLog().Printf ("ensure_remove_recursive: unable to remove directory: "
"%s" ", rv: %d, err: %d", path, rv, (*__errno_location ()))
636 ", rv: %d, err: %d",UpdateLog::GetPrimaryLog().Printf ("ensure_remove_recursive: unable to remove directory: "
"%s" ", rv: %d, err: %d", path, rv, (*__errno_location ()))
637 path, rv, errno))UpdateLog::GetPrimaryLog().Printf ("ensure_remove_recursive: unable to remove directory: "
"%s" ", rv: %d, err: %d", path, rv, (*__errno_location ()))
;
638 }
639 }
640 return rv;
641}
642
643static bool is_read_only(const NS_tchar* flags) {
644 size_t length = NS_tstrlenstrlen(flags);
645 if (length == 0) {
646 return false;
647 }
648
649 // Make sure the string begins with "r"
650 if (flags[0] != NS_T('r')'r') {
651 return false;
652 }
653
654 // Look for "r+" or "r+b"
655 if (length > 1 && flags[1] == NS_T('+')'+') {
656 return false;
657 }
658
659 // Look for "rb+"
660 if (NS_tstrcmpstrcmp(flags, NS_T("rb+")"rb+") == 0) {
661 return false;
662 }
663
664 return true;
665}
666
667static FILE* ensure_open(const NS_tchar* path, const NS_tchar* flags,
668 unsigned int options) {
669 ensure_write_permissions(path);
670 FILE* f = NS_tfopenfopen(path, flags);
671 if (is_read_only(flags)) {
672 // Don't attempt to modify the file permissions if the file is being opened
673 // in read-only mode.
674 return f;
675 }
676 if (NS_tchmodchmod(path, options) != 0) {
677 if (f != nullptr) {
678 fclose(f);
679 }
680 return nullptr;
681 }
682 struct NS_tstat_tstat ss;
683 if (NS_tstatstat(path, &ss) != 0 || ss.st_mode != options) {
684 if (f != nullptr) {
685 fclose(f);
686 }
687 return nullptr;
688 }
689 return f;
690}
691
692// Ensure that the directory containing this file exists.
693static int ensure_parent_dir(const NS_tchar* path) {
694 int rv = OK0;
695
696 NS_tchar* slash = (NS_tchar*)NS_tstrrchrstrrchr(path, NS_T('/')'/');
697 if (slash) {
698 *slash = NS_T('\0')'\0';
699 rv = ensure_parent_dir(path);
700 // Only attempt to create the directory if we're not at the root
701 if (rv == OK0 && *path) {
702 rv = NS_tmkdirmkdir(path, 0755);
703 // If the directory already exists, then ignore the error.
704 if (rv < 0 && errno(*__errno_location ()) != EEXIST17) {
705 LOG(("ensure_parent_dir: failed to create directory: " LOG_S ", "UpdateLog::GetPrimaryLog().Printf ("ensure_parent_dir: failed to create directory: "
"%s" ", " "err: %d", path, (*__errno_location ()))
706 "err: %d",UpdateLog::GetPrimaryLog().Printf ("ensure_parent_dir: failed to create directory: "
"%s" ", " "err: %d", path, (*__errno_location ()))
707 path, errno))UpdateLog::GetPrimaryLog().Printf ("ensure_parent_dir: failed to create directory: "
"%s" ", " "err: %d", path, (*__errno_location ()))
;
708 rv = WRITE_ERROR7;
709 } else {
710 rv = OK0;
711 }
712 }
713 *slash = NS_T('/')'/';
714 }
715 return rv;
716}
717
718#ifdef XP_UNIX1
719static int ensure_copy_symlink(const NS_tchar* path, const NS_tchar* dest) {
720 // Copy symlinks by creating a new symlink to the same target
721 NS_tchar target[MAXPATHLEN4096 + 1] = {NS_T('\0')'\0'};
722 int rv = readlink(path, target, MAXPATHLEN4096);
723 if (rv == -1) {
724 LOG(("ensure_copy_symlink: failed to read the link: " LOG_S ", err: %d",UpdateLog::GetPrimaryLog().Printf ("ensure_copy_symlink: failed to read the link: "
"%s" ", err: %d", path, (*__errno_location ()))
725 path, errno))UpdateLog::GetPrimaryLog().Printf ("ensure_copy_symlink: failed to read the link: "
"%s" ", err: %d", path, (*__errno_location ()))
;
726 return READ_ERROR6;
727 }
728 rv = symlink(target, dest);
729 if (rv == -1) {
730 LOG(("ensure_copy_symlink: failed to create the new link: " LOG_SUpdateLog::GetPrimaryLog().Printf ("ensure_copy_symlink: failed to create the new link: "
"%s" ", target: " "%s" " err: %d", dest, target, (*__errno_location
()))
731 ", target: " LOG_S " err: %d",UpdateLog::GetPrimaryLog().Printf ("ensure_copy_symlink: failed to create the new link: "
"%s" ", target: " "%s" " err: %d", dest, target, (*__errno_location
()))
732 dest, target, errno))UpdateLog::GetPrimaryLog().Printf ("ensure_copy_symlink: failed to create the new link: "
"%s" ", target: " "%s" " err: %d", dest, target, (*__errno_location
()))
;
733 return READ_ERROR6;
734 }
735 return 0;
736}
737#endif
738
739// Copy the file named path onto a new file named dest.
740static int ensure_copy(const NS_tchar* path, const NS_tchar* dest) {
741#ifdef XP_WIN
742 // Fast path for Windows
743 bool result = CopyFileW(path, dest, false);
744 if (!result) {
745 LOG(("ensure_copy: failed to copy the file " LOG_S " over to " LOG_SUpdateLog::GetPrimaryLog().Printf ("ensure_copy: failed to copy the file "
"%s" " over to " "%s" ", lasterr: %lx", path, dest, GetLastError
())
746 ", lasterr: %lx",UpdateLog::GetPrimaryLog().Printf ("ensure_copy: failed to copy the file "
"%s" " over to " "%s" ", lasterr: %lx", path, dest, GetLastError
())
747 path, dest, GetLastError()))UpdateLog::GetPrimaryLog().Printf ("ensure_copy: failed to copy the file "
"%s" " over to " "%s" ", lasterr: %lx", path, dest, GetLastError
())
;
748 return WRITE_ERROR_FILE_COPY61;
749 }
750 return OK0;
751#else
752 struct NS_tstat_tstat ss;
753 int rv = NS_tlstatlstat(path, &ss);
754 if (rv) {
755 LOG(("ensure_copy: failed to read file status info: " LOG_S ", err: %d",UpdateLog::GetPrimaryLog().Printf ("ensure_copy: failed to read file status info: "
"%s" ", err: %d", path, (*__errno_location ()))
756 path, errno))UpdateLog::GetPrimaryLog().Printf ("ensure_copy: failed to read file status info: "
"%s" ", err: %d", path, (*__errno_location ()))
;
757 return READ_ERROR6;
758 }
759
760# ifdef XP_UNIX1
761 if (S_ISLNK(ss.st_mode)((((ss.st_mode)) & 0170000) == (0120000))) {
762 return ensure_copy_symlink(path, dest);
763 }
764# endif
765
766 AutoFile infile(ensure_open(path, NS_T("rb")"rb", ss.st_mode));
767 if (!infile) {
768 LOG(("ensure_copy: failed to open the file for reading: " LOG_S ", err: %d",UpdateLog::GetPrimaryLog().Printf ("ensure_copy: failed to open the file for reading: "
"%s" ", err: %d", path, (*__errno_location ()))
769 path, errno))UpdateLog::GetPrimaryLog().Printf ("ensure_copy: failed to open the file for reading: "
"%s" ", err: %d", path, (*__errno_location ()))
;
770 return READ_ERROR6;
771 }
772 AutoFile outfile(ensure_open(dest, NS_T("wb")"wb", ss.st_mode));
773 if (!outfile) {
774 LOG(("ensure_copy: failed to open the file for writing: " LOG_S ", err: %d",UpdateLog::GetPrimaryLog().Printf ("ensure_copy: failed to open the file for writing: "
"%s" ", err: %d", dest, (*__errno_location ()))
775 dest, errno))UpdateLog::GetPrimaryLog().Printf ("ensure_copy: failed to open the file for writing: "
"%s" ", err: %d", dest, (*__errno_location ()))
;
776 return WRITE_ERROR7;
777 }
778
779 // This block size was chosen pretty arbitrarily but seems like a reasonable
780 // compromise. For example, the optimal block size on a modern OS X machine
781 // is 100k */
782 const int blockSize = 32 * 1024;
783 void* buffer = malloc(blockSize);
784 if (!buffer) {
785 return UPDATER_MEM_ERROR13;
786 }
787
788 while (!feof(infile.get())) {
789 size_t read = fread(buffer, 1, blockSize, infile);
790 if (ferror(infile.get())) {
791 LOG(("ensure_copy: failed to read the file: " LOG_S ", err: %d", path,UpdateLog::GetPrimaryLog().Printf ("ensure_copy: failed to read the file: "
"%s" ", err: %d", path, (*__errno_location ()))
792 errno))UpdateLog::GetPrimaryLog().Printf ("ensure_copy: failed to read the file: "
"%s" ", err: %d", path, (*__errno_location ()))
;
793 free(buffer);
794 return READ_ERROR6;
795 }
796
797 size_t written = 0;
798
799 while (written < read) {
800 size_t chunkWritten = fwrite(buffer, 1, read - written, outfile);
801 if (chunkWritten <= 0) {
802 LOG(("ensure_copy: failed to write the file: " LOG_S ", err: %d", dest,UpdateLog::GetPrimaryLog().Printf ("ensure_copy: failed to write the file: "
"%s" ", err: %d", dest, (*__errno_location ()))
803 errno))UpdateLog::GetPrimaryLog().Printf ("ensure_copy: failed to write the file: "
"%s" ", err: %d", dest, (*__errno_location ()))
;
804 free(buffer);
805 return WRITE_ERROR_FILE_COPY61;
806 }
807
808 written += chunkWritten;
809 }
810 }
811
812 rv = NS_tchmodchmod(dest, ss.st_mode);
813
814 free(buffer);
815 return rv;
816#endif
817}
818
819template <unsigned N>
820struct copy_recursive_skiplist {
821 NS_tchar paths[N][MAXPATHLEN4096];
822
823 void append(unsigned index, const NS_tchar* path, const NS_tchar* suffix) {
824 NS_tsnprintfsnprintf(paths[index], MAXPATHLEN4096, NS_T("%s/%s")"%s/%s", path, suffix);
825 }
826
827 bool find(const NS_tchar* path) {
828 for (int i = 0; i < static_cast<int>(N); ++i) {
829 if (!NS_tstricmpstrcasecmp(paths[i], path)) {
830 return true;
831 }
832 }
833 return false;
834 }
835};
836
837// Copy all of the files and subdirectories under path to a new directory named
838// dest. The path names in the skiplist will be skipped and will not be copied.
839template <unsigned N>
840static int ensure_copy_recursive(const NS_tchar* path, const NS_tchar* dest,
841 copy_recursive_skiplist<N>& skiplist) {
842 struct NS_tstat_tstat sInfo;
843 int rv = NS_tlstatlstat(path, &sInfo);
844 if (rv) {
845 LOG(("ensure_copy_recursive: path doesn't exist: " LOG_SUpdateLog::GetPrimaryLog().Printf ("ensure_copy_recursive: path doesn't exist: "
"%s" ", rv: %d, err: %d", path, rv, (*__errno_location ()))
846 ", rv: %d, err: %d",UpdateLog::GetPrimaryLog().Printf ("ensure_copy_recursive: path doesn't exist: "
"%s" ", rv: %d, err: %d", path, rv, (*__errno_location ()))
847 path, rv, errno))UpdateLog::GetPrimaryLog().Printf ("ensure_copy_recursive: path doesn't exist: "
"%s" ", rv: %d, err: %d", path, rv, (*__errno_location ()))
;
848 return READ_ERROR6;
849 }
850
851#ifdef XP_UNIX1
852 if (S_ISLNK(sInfo.st_mode)((((sInfo.st_mode)) & 0170000) == (0120000))) {
853 return ensure_copy_symlink(path, dest);
854 }
855#endif
856
857 if (!S_ISDIR(sInfo.st_mode)((((sInfo.st_mode)) & 0170000) == (0040000))) {
858 return ensure_copy(path, dest);
859 }
860
861 rv = NS_tmkdirmkdir(dest, sInfo.st_mode);
862 if (rv < 0 && errno(*__errno_location ()) != EEXIST17) {
863 LOG(("ensure_copy_recursive: could not create destination directory: " LOG_SUpdateLog::GetPrimaryLog().Printf ("ensure_copy_recursive: could not create destination directory: "
"%s" ", rv: %d, err: %d", path, rv, (*__errno_location ()))
864 ", rv: %d, err: %d",UpdateLog::GetPrimaryLog().Printf ("ensure_copy_recursive: could not create destination directory: "
"%s" ", rv: %d, err: %d", path, rv, (*__errno_location ()))
865 path, rv, errno))UpdateLog::GetPrimaryLog().Printf ("ensure_copy_recursive: could not create destination directory: "
"%s" ", rv: %d, err: %d", path, rv, (*__errno_location ()))
;
866 return WRITE_ERROR7;
867 }
868
869 NS_tDIRDIR* dir;
870 NS_tdirentdirent* entry;
871
872 dir = NS_topendiropendir(path);
873 if (!dir) {
874 LOG(("ensure_copy_recursive: path is not a directory: " LOG_SUpdateLog::GetPrimaryLog().Printf ("ensure_copy_recursive: path is not a directory: "
"%s" ", rv: %d, err: %d", path, rv, (*__errno_location ()))
875 ", rv: %d, err: %d",UpdateLog::GetPrimaryLog().Printf ("ensure_copy_recursive: path is not a directory: "
"%s" ", rv: %d, err: %d", path, rv, (*__errno_location ()))
876 path, rv, errno))UpdateLog::GetPrimaryLog().Printf ("ensure_copy_recursive: path is not a directory: "
"%s" ", rv: %d, err: %d", path, rv, (*__errno_location ()))
;
877 return READ_ERROR6;
878 }
879
880 while ((entry = NS_treaddirreaddir(dir)) != 0) {
881 if (NS_tstrcmpstrcmp(entry->d_name, NS_T(".")".") &&
882 NS_tstrcmpstrcmp(entry->d_name, NS_T("..")"..")) {
883 NS_tchar childPath[MAXPATHLEN4096];
884 NS_tsnprintfsnprintf(childPath, sizeof(childPath) / sizeof(childPath[0]),
885 NS_T("%s/%s")"%s/%s", path, entry->d_name);
886 if (skiplist.find(childPath)) {
887 continue;
888 }
889 NS_tchar childPathDest[MAXPATHLEN4096];
890 NS_tsnprintfsnprintf(childPathDest,
891 sizeof(childPathDest) / sizeof(childPathDest[0]),
892 NS_T("%s/%s")"%s/%s", dest, entry->d_name);
893 rv = ensure_copy_recursive(childPath, childPathDest, skiplist);
894 if (rv) {
895 break;
896 }
897 }
898 }
899 NS_tclosedirclosedir(dir);
900 return rv;
901}
902
903// Renames the specified file to the new file specified. If the destination file
904// exists it is removed.
905static int rename_file(const NS_tchar* spath, const NS_tchar* dpath,
906 bool allowDirs = false) {
907 int rv = ensure_parent_dir(dpath);
908 if (rv) {
909 return rv;
910 }
911
912 struct NS_tstat_tstat spathInfo;
913 rv = NS_tstatstat(spath, &spathInfo);
914 if (rv) {
915 LOG(("rename_file: failed to read file status info: " LOG_S ", "UpdateLog::GetPrimaryLog().Printf ("rename_file: failed to read file status info: "
"%s" ", " "err: %d", spath, (*__errno_location ()))
916 "err: %d",UpdateLog::GetPrimaryLog().Printf ("rename_file: failed to read file status info: "
"%s" ", " "err: %d", spath, (*__errno_location ()))
917 spath, errno))UpdateLog::GetPrimaryLog().Printf ("rename_file: failed to read file status info: "
"%s" ", " "err: %d", spath, (*__errno_location ()))
;
918 return READ_ERROR6;
919 }
920
921 if (!S_ISREG(spathInfo.st_mode)((((spathInfo.st_mode)) & 0170000) == (0100000))) {
922 if (allowDirs && !S_ISDIR(spathInfo.st_mode)((((spathInfo.st_mode)) & 0170000) == (0040000))) {
923 LOG(("rename_file: path present, but not a file: " LOG_S ", err: %d",UpdateLog::GetPrimaryLog().Printf ("rename_file: path present, but not a file: "
"%s" ", err: %d", spath, (*__errno_location ()))
924 spath, errno))UpdateLog::GetPrimaryLog().Printf ("rename_file: path present, but not a file: "
"%s" ", err: %d", spath, (*__errno_location ()))
;
925 return RENAME_ERROR_EXPECTED_FILE48;
926 }
927 LOG(("rename_file: proceeding to rename the directory"))UpdateLog::GetPrimaryLog().Printf ("rename_file: proceeding to rename the directory"
)
;
928 }
929
930 if (!NS_taccessaccess(dpath, F_OK0)) {
931 if (ensure_remove(dpath)) {
932 LOG(UpdateLog::GetPrimaryLog().Printf ("rename_file: destination file exists and could not be "
"removed: " "%s", dpath)
933 ("rename_file: destination file exists and could not be "UpdateLog::GetPrimaryLog().Printf ("rename_file: destination file exists and could not be "
"removed: " "%s", dpath)
934 "removed: " LOG_S,UpdateLog::GetPrimaryLog().Printf ("rename_file: destination file exists and could not be "
"removed: " "%s", dpath)
935 dpath))UpdateLog::GetPrimaryLog().Printf ("rename_file: destination file exists and could not be "
"removed: " "%s", dpath)
;
936 return WRITE_ERROR_DELETE_FILE62;
937 }
938 }
939
940 if (NS_trenamerename(spath, dpath) != 0) {
941 LOG(("rename_file: failed to rename file - src: " LOG_S ", "UpdateLog::GetPrimaryLog().Printf ("rename_file: failed to rename file - src: "
"%s" ", " "dst:" "%s" ", err: %d", spath, dpath, (*__errno_location
()))
942 "dst:" LOG_S ", err: %d",UpdateLog::GetPrimaryLog().Printf ("rename_file: failed to rename file - src: "
"%s" ", " "dst:" "%s" ", err: %d", spath, dpath, (*__errno_location
()))
943 spath, dpath, errno))UpdateLog::GetPrimaryLog().Printf ("rename_file: failed to rename file - src: "
"%s" ", " "dst:" "%s" ", err: %d", spath, dpath, (*__errno_location
()))
;
944 return WRITE_ERROR7;
945 }
946
947 return OK0;
948}
949
950#ifdef XP_WIN
951// Remove the directory pointed to by path and all of its files and
952// sub-directories. If a file is in use move it to the tobedeleted directory
953// and attempt to schedule removal of the file on reboot
954static int remove_recursive_on_reboot(const NS_tchar* path,
955 const NS_tchar* deleteDir) {
956 struct NS_tstat_tstat sInfo;
957 int rv = NS_tlstatlstat(path, &sInfo);
958 if (rv) {
959 // This error is benign
960 return rv;
961 }
962
963 if (!S_ISDIR(sInfo.st_mode)((((sInfo.st_mode)) & 0170000) == (0040000))) {
964 NS_tchar tmpDeleteFile[MAXPATHLEN4096 + 1];
965 GetUUIDTempFilePath(deleteDir, L"rep", tmpDeleteFile);
966 if (NS_tremoveremove(tmpDeleteFile) && errno(*__errno_location ()) != ENOENT2) {
967 LOG(("remove_recursive_on_reboot: failed to remove temporary file: " LOG_SUpdateLog::GetPrimaryLog().Printf ("remove_recursive_on_reboot: failed to remove temporary file: "
"%s" ", err: %d", tmpDeleteFile, (*__errno_location ()))
968 ", err: %d",UpdateLog::GetPrimaryLog().Printf ("remove_recursive_on_reboot: failed to remove temporary file: "
"%s" ", err: %d", tmpDeleteFile, (*__errno_location ()))
969 tmpDeleteFile, errno))UpdateLog::GetPrimaryLog().Printf ("remove_recursive_on_reboot: failed to remove temporary file: "
"%s" ", err: %d", tmpDeleteFile, (*__errno_location ()))
;
970 }
971 rv = rename_file(path, tmpDeleteFile, false);
972 if (MoveFileEx(rv ? path : tmpDeleteFile, nullptr,
973 MOVEFILE_DELAY_UNTIL_REBOOT)) {
974 LOG(UpdateLog::GetPrimaryLog().Printf ("remove_recursive_on_reboot: file will be removed on OS "
"reboot: " "%s", rv ? path : tmpDeleteFile)
975 ("remove_recursive_on_reboot: file will be removed on OS "UpdateLog::GetPrimaryLog().Printf ("remove_recursive_on_reboot: file will be removed on OS "
"reboot: " "%s", rv ? path : tmpDeleteFile)
976 "reboot: " LOG_S,UpdateLog::GetPrimaryLog().Printf ("remove_recursive_on_reboot: file will be removed on OS "
"reboot: " "%s", rv ? path : tmpDeleteFile)
977 rv ? path : tmpDeleteFile))UpdateLog::GetPrimaryLog().Printf ("remove_recursive_on_reboot: file will be removed on OS "
"reboot: " "%s", rv ? path : tmpDeleteFile)
;
978 } else {
979 LOG((UpdateLog::GetPrimaryLog().Printf ( "remove_recursive_on_reboot: failed to schedule OS reboot removal of "
"file: " "%s", rv ? path : tmpDeleteFile)
980 "remove_recursive_on_reboot: failed to schedule OS reboot removal of "UpdateLog::GetPrimaryLog().Printf ( "remove_recursive_on_reboot: failed to schedule OS reboot removal of "
"file: " "%s", rv ? path : tmpDeleteFile)
981 "file: " LOG_S,UpdateLog::GetPrimaryLog().Printf ( "remove_recursive_on_reboot: failed to schedule OS reboot removal of "
"file: " "%s", rv ? path : tmpDeleteFile)
982 rv ? path : tmpDeleteFile))UpdateLog::GetPrimaryLog().Printf ( "remove_recursive_on_reboot: failed to schedule OS reboot removal of "
"file: " "%s", rv ? path : tmpDeleteFile)
;
983 }
984 return rv;
985 }
986
987 NS_tDIRDIR* dir;
988 NS_tdirentdirent* entry;
989
990 dir = NS_topendiropendir(path);
991 if (!dir) {
992 LOG(("remove_recursive_on_reboot: unable to open directory: " LOG_SUpdateLog::GetPrimaryLog().Printf ("remove_recursive_on_reboot: unable to open directory: "
"%s" ", rv: %d, err: %d", path, rv, (*__errno_location ()))
993 ", rv: %d, err: %d",UpdateLog::GetPrimaryLog().Printf ("remove_recursive_on_reboot: unable to open directory: "
"%s" ", rv: %d, err: %d", path, rv, (*__errno_location ()))
994 path, rv, errno))UpdateLog::GetPrimaryLog().Printf ("remove_recursive_on_reboot: unable to open directory: "
"%s" ", rv: %d, err: %d", path, rv, (*__errno_location ()))
;
995 return rv;
996 }
997
998 while ((entry = NS_treaddirreaddir(dir)) != 0) {
999 if (NS_tstrcmpstrcmp(entry->d_name, NS_T(".")".") &&
1000 NS_tstrcmpstrcmp(entry->d_name, NS_T("..")"..")) {
1001 NS_tchar childPath[MAXPATHLEN4096];
1002 NS_tsnprintfsnprintf(childPath, sizeof(childPath) / sizeof(childPath[0]),
1003 NS_T("%s/%s")"%s/%s", path, entry->d_name);
1004 // There is no need to check the return value of this call since this
1005 // function is only called after an update is successful and there is not
1006 // much that can be done to recover if it isn't successful. There is also
1007 // no need to log the value since it will have already been logged.
1008 remove_recursive_on_reboot(childPath, deleteDir);
1009 }
1010 }
1011
1012 NS_tclosedirclosedir(dir);
1013
1014 if (rv == OK0) {
1015 ensure_write_permissions(path);
1016 rv = NS_trmdirrmdir(path);
1017 if (rv) {
1018 LOG(("remove_recursive_on_reboot: unable to remove directory: " LOG_SUpdateLog::GetPrimaryLog().Printf ("remove_recursive_on_reboot: unable to remove directory: "
"%s" ", rv: %d, err: %d", path, rv, (*__errno_location ()))
1019 ", rv: %d, err: %d",UpdateLog::GetPrimaryLog().Printf ("remove_recursive_on_reboot: unable to remove directory: "
"%s" ", rv: %d, err: %d", path, rv, (*__errno_location ()))
1020 path, rv, errno))UpdateLog::GetPrimaryLog().Printf ("remove_recursive_on_reboot: unable to remove directory: "
"%s" ", rv: %d, err: %d", path, rv, (*__errno_location ()))
;
1021 }
1022 }
1023 return rv;
1024}
1025#endif
1026
1027//-----------------------------------------------------------------------------
1028
1029// Create a backup of the specified file by renaming it.
1030static int backup_create(const NS_tchar* path) {
1031 NS_tchar backup[MAXPATHLEN4096];
1032 NS_tsnprintfsnprintf(backup, sizeof(backup) / sizeof(backup[0]),
1033 NS_T("%s")"%s" BACKUP_EXT".moz-backup", path);
1034
1035 return rename_file(path, backup);
1036}
1037
1038// Rename the backup of the specified file that was created by renaming it back
1039// to the original file.
1040static int backup_restore(const NS_tchar* path, const NS_tchar* relPath) {
1041 NS_tchar backup[MAXPATHLEN4096];
1042 NS_tsnprintfsnprintf(backup, sizeof(backup) / sizeof(backup[0]),
1043 NS_T("%s")"%s" BACKUP_EXT".moz-backup", path);
1044
1045 NS_tchar relBackup[MAXPATHLEN4096];
1046 NS_tsnprintfsnprintf(relBackup, sizeof(relBackup) / sizeof(relBackup[0]),
1047 NS_T("%s")"%s" BACKUP_EXT".moz-backup", relPath);
1048
1049 if (NS_taccessaccess(backup, F_OK0)) {
1050 LOG(("backup_restore: backup file doesn't exist: " LOG_S, relBackup))UpdateLog::GetPrimaryLog().Printf ("backup_restore: backup file doesn't exist: "
"%s", relBackup)
;
1051 return OK0;
1052 }
1053
1054 return rename_file(backup, path);
1055}
1056
1057// Discard the backup of the specified file that was created by renaming it.
1058static int backup_discard(const NS_tchar* path, const NS_tchar* relPath) {
1059 NS_tchar backup[MAXPATHLEN4096];
1060 NS_tsnprintfsnprintf(backup, sizeof(backup) / sizeof(backup[0]),
1061 NS_T("%s")"%s" BACKUP_EXT".moz-backup", path);
1062
1063 NS_tchar relBackup[MAXPATHLEN4096];
1064 NS_tsnprintfsnprintf(relBackup, sizeof(relBackup) / sizeof(relBackup[0]),
1065 NS_T("%s")"%s" BACKUP_EXT".moz-backup", relPath);
1066
1067 // Nothing to discard
1068 if (NS_taccessaccess(backup, F_OK0)) {
1069 return OK0;
1070 }
1071
1072 int rv = ensure_remove(backup);
1073#if defined(XP_WIN)
1074 if (rv && !sStagedUpdate && !sReplaceRequest) {
1075 LOG(("backup_discard: unable to remove: " LOG_S, relBackup))UpdateLog::GetPrimaryLog().Printf ("backup_discard: unable to remove: "
"%s", relBackup)
;
1076 NS_tchar path[MAXPATHLEN4096 + 1];
1077 GetUUIDTempFilePath(gDeleteDirPath, L"moz", path);
1078 if (rename_file(backup, path)) {
1079 LOG(("backup_discard: failed to rename file:" LOG_S ", dst:" LOG_S,UpdateLog::GetPrimaryLog().Printf ("backup_discard: failed to rename file:"
"%s" ", dst:" "%s", relBackup, relPath)
1080 relBackup, relPath))UpdateLog::GetPrimaryLog().Printf ("backup_discard: failed to rename file:"
"%s" ", dst:" "%s", relBackup, relPath)
;
1081 return WRITE_ERROR_DELETE_BACKUP69;
1082 }
1083 // The MoveFileEx call to remove the file on OS reboot will fail if the
1084 // process doesn't have write access to the HKEY_LOCAL_MACHINE registry key
1085 // but this is ok since the installer / uninstaller will delete the
1086 // directory containing the file along with its contents after an update is
1087 // applied, on reinstall, and on uninstall.
1088 if (MoveFileEx(path, nullptr, MOVEFILE_DELAY_UNTIL_REBOOT)) {
1089 LOG(UpdateLog::GetPrimaryLog().Printf ("backup_discard: file renamed and will be removed on OS "
"reboot: " "%s", relPath)
1090 ("backup_discard: file renamed and will be removed on OS "UpdateLog::GetPrimaryLog().Printf ("backup_discard: file renamed and will be removed on OS "
"reboot: " "%s", relPath)
1091 "reboot: " LOG_S,UpdateLog::GetPrimaryLog().Printf ("backup_discard: file renamed and will be removed on OS "
"reboot: " "%s", relPath)
1092 relPath))UpdateLog::GetPrimaryLog().Printf ("backup_discard: file renamed and will be removed on OS "
"reboot: " "%s", relPath)
;
1093 } else {
1094 LOG(UpdateLog::GetPrimaryLog().Printf ("backup_discard: failed to schedule OS reboot removal of "
"file: " "%s", relPath)
1095 ("backup_discard: failed to schedule OS reboot removal of "UpdateLog::GetPrimaryLog().Printf ("backup_discard: failed to schedule OS reboot removal of "
"file: " "%s", relPath)
1096 "file: " LOG_S,UpdateLog::GetPrimaryLog().Printf ("backup_discard: failed to schedule OS reboot removal of "
"file: " "%s", relPath)
1097 relPath))UpdateLog::GetPrimaryLog().Printf ("backup_discard: failed to schedule OS reboot removal of "
"file: " "%s", relPath)
;
1098 }
1099 }
1100#else
1101 if (rv) {
1102 return WRITE_ERROR_DELETE_BACKUP69;
1103 }
1104#endif
1105
1106 return OK0;
1107}
1108
1109// Helper function for post-processing a temporary backup.
1110static void backup_finish(const NS_tchar* path, const NS_tchar* relPath,
1111 int status) {
1112 if (status == OK0) {
1113 backup_discard(path, relPath);
1114 } else {
1115 backup_restore(path, relPath);
1116 }
1117}
1118
1119//-----------------------------------------------------------------------------
1120
1121static int DoUpdate();
1122
1123class Action {
1124 public:
1125 Action() : mProgressCost(1), mNext(nullptr) {}
1126 virtual ~Action() = default;
1127
1128 virtual int Parse(NS_tchar* line) = 0;
1129
1130 // Do any preprocessing to ensure that the action can be performed. Execute
1131 // will be called if this Action and all others return OK from this method.
1132 virtual int Prepare() = 0;
1133
1134 // Perform the operation. Return OK to indicate success. After all actions
1135 // have been executed, Finish will be called. A requirement of Execute is
1136 // that its operation be reversable from Finish.
1137 virtual int Execute() = 0;
1138
1139 // Finish is called after execution of all actions. If status is OK, then
1140 // all actions were successfully executed. Otherwise, some action failed.
1141 virtual void Finish(int status) = 0;
1142
1143 int mProgressCost;
1144
1145 private:
1146 Action* mNext;
1147
1148 friend class ActionList;
1149};
1150
1151class RemoveFile : public Action {
1152 public:
1153 RemoveFile() : mSkip(0) {}
1154
1155 int Parse(NS_tchar* line) override;
1156 int Prepare() override;
1157 int Execute() override;
1158 void Finish(int status) override;
1159
1160 private:
1161 mozilla::UniquePtr<NS_tchar[]> mFile;
1162 mozilla::UniquePtr<NS_tchar[]> mRelPath;
1163 int mSkip;
1164};
1165
1166int RemoveFile::Parse(NS_tchar* line) {
1167 // format "<deadfile>"
1168
1169 NS_tchar* validPath = get_valid_path(&line);
1170 if (!validPath) {
1171 return PARSE_ERROR5;
1172 }
1173
1174 mRelPath = mozilla::MakeUnique<NS_tchar[]>(MAXPATHLEN4096);
1175 NS_tstrcpystrcpy(mRelPath.get(), validPath);
1176
1177 mFile.reset(get_full_path(validPath));
1178 if (!mFile) {
1179 return PARSE_ERROR5;
1180 }
1181
1182 return OK0;
1183}
1184
1185int RemoveFile::Prepare() {
1186 // Skip the file if it already doesn't exist.
1187 int rv = NS_taccessaccess(mFile.get(), F_OK0);
1188 if (rv) {
1189 mSkip = 1;
1190 mProgressCost = 0;
1191 return OK0;
1192 }
1193
1194 LOG(("PREPARE REMOVEFILE " LOG_S, mRelPath.get()))UpdateLog::GetPrimaryLog().Printf ("PREPARE REMOVEFILE " "%s"
, mRelPath.get())
;
1195
1196 // Make sure that we're actually a file...
1197 struct NS_tstat_tstat fileInfo;
1198 rv = NS_tstatstat(mFile.get(), &fileInfo);
1199 if (rv) {
1200 LOG(("failed to read file status info: " LOG_S ", err: %d", mFile.get(),UpdateLog::GetPrimaryLog().Printf ("failed to read file status info: "
"%s" ", err: %d", mFile.get(), (*__errno_location ()))
1201 errno))UpdateLog::GetPrimaryLog().Printf ("failed to read file status info: "
"%s" ", err: %d", mFile.get(), (*__errno_location ()))
;
1202 return READ_ERROR6;
1203 }
1204
1205 if (!S_ISREG(fileInfo.st_mode)((((fileInfo.st_mode)) & 0170000) == (0100000))) {
1206 LOG(("path present, but not a file: " LOG_S, mFile.get()))UpdateLog::GetPrimaryLog().Printf ("path present, but not a file: "
"%s", mFile.get())
;
1207 return DELETE_ERROR_EXPECTED_FILE47;
1208 }
1209
1210 NS_tchar* slash = (NS_tchar*)NS_tstrrchrstrrchr(mFile.get(), NS_T('/')'/');
1211 if (slash) {
1212 *slash = NS_T('\0')'\0';
1213 rv = NS_taccessaccess(mFile.get(), W_OK2);
1214 *slash = NS_T('/')'/';
1215 } else {
1216 rv = NS_taccessaccess(NS_T(".")".", W_OK2);
1217 }
1218
1219 if (rv) {
1220 LOG(("access failed: %d", errno))UpdateLog::GetPrimaryLog().Printf ("access failed: %d", (*__errno_location
()))
;
1221 return WRITE_ERROR_FILE_ACCESS_DENIED67;
1222 }
1223
1224 return OK0;
1225}
1226
1227int RemoveFile::Execute() {
1228 if (mSkip) {
1229 return OK0;
1230 }
1231
1232 LOG(("EXECUTE REMOVEFILE " LOG_S, mRelPath.get()))UpdateLog::GetPrimaryLog().Printf ("EXECUTE REMOVEFILE " "%s"
, mRelPath.get())
;
1233
1234 // The file is checked for existence here and in Prepare since it might have
1235 // been removed by a separate instruction: bug 311099.
1236 int rv = NS_taccessaccess(mFile.get(), F_OK0);
1237 if (rv) {
1238 LOG(("file cannot be removed because it does not exist; skipping"))UpdateLog::GetPrimaryLog().Printf ("file cannot be removed because it does not exist; skipping"
)
;
1239 mSkip = 1;
1240 return OK0;
1241 }
1242
1243 if (sStagedUpdate) {
1244 // Staged updates don't need backup files so just remove it.
1245 rv = ensure_remove(mFile.get());
1246 if (rv) {
1247 return rv;
1248 }
1249 } else {
1250 // Rename the old file. It will be removed in Finish.
1251 rv = backup_create(mFile.get());
1252 if (rv) {
1253 LOG(("backup_create failed: %d", rv))UpdateLog::GetPrimaryLog().Printf ("backup_create failed: %d"
, rv)
;
1254 return rv;
1255 }
1256 }
1257
1258 return OK0;
1259}
1260
1261void RemoveFile::Finish(int status) {
1262 if (mSkip) {
1263 return;
1264 }
1265
1266 LOG(("FINISH REMOVEFILE " LOG_S, mRelPath.get()))UpdateLog::GetPrimaryLog().Printf ("FINISH REMOVEFILE " "%s",
mRelPath.get())
;
1267
1268 // Staged updates don't create backup files.
1269 if (!sStagedUpdate) {
1270 backup_finish(mFile.get(), mRelPath.get(), status);
1271 }
1272}
1273
1274class RemoveDir : public Action {
1275 public:
1276 RemoveDir() : mSkip(0) {}
1277
1278 int Parse(NS_tchar* line) override;
1279 int Prepare() override; // check that the source dir exists
1280 int Execute() override;
1281 void Finish(int status) override;
1282
1283 private:
1284 mozilla::UniquePtr<NS_tchar[]> mDir;
1285 mozilla::UniquePtr<NS_tchar[]> mRelPath;
1286 int mSkip;
1287};
1288
1289int RemoveDir::Parse(NS_tchar* line) {
1290 // format "<deaddir>/"
1291
1292 NS_tchar* validPath = get_valid_path(&line, true);
1293 if (!validPath) {
1294 return PARSE_ERROR5;
1295 }
1296
1297 mRelPath = mozilla::MakeUnique<NS_tchar[]>(MAXPATHLEN4096);
1298 NS_tstrcpystrcpy(mRelPath.get(), validPath);
1299
1300 mDir.reset(get_full_path(validPath));
1301 if (!mDir) {
1302 return PARSE_ERROR5;
1303 }
1304
1305 return OK0;
1306}
1307
1308int RemoveDir::Prepare() {
1309 // We expect the directory to exist if we are to remove it.
1310 int rv = NS_taccessaccess(mDir.get(), F_OK0);
1311 if (rv) {
1312 mSkip = 1;
1313 mProgressCost = 0;
1314 return OK0;
1315 }
1316
1317 LOG(("PREPARE REMOVEDIR " LOG_S "/", mRelPath.get()))UpdateLog::GetPrimaryLog().Printf ("PREPARE REMOVEDIR " "%s" "/"
, mRelPath.get())
;
1318
1319 // Make sure that we're actually a dir.
1320 struct NS_tstat_tstat dirInfo;
1321 rv = NS_tstatstat(mDir.get(), &dirInfo);
1322 if (rv) {
1323 LOG(("failed to read directory status info: " LOG_S ", err: %d",UpdateLog::GetPrimaryLog().Printf ("failed to read directory status info: "
"%s" ", err: %d", mRelPath.get(), (*__errno_location ()))
1324 mRelPath.get(), errno))UpdateLog::GetPrimaryLog().Printf ("failed to read directory status info: "
"%s" ", err: %d", mRelPath.get(), (*__errno_location ()))
;
1325 return READ_ERROR6;
1326 }
1327
1328 if (!S_ISDIR(dirInfo.st_mode)((((dirInfo.st_mode)) & 0170000) == (0040000))) {
1329 LOG(("path present, but not a directory: " LOG_S, mRelPath.get()))UpdateLog::GetPrimaryLog().Printf ("path present, but not a directory: "
"%s", mRelPath.get())
;
1330 return DELETE_ERROR_EXPECTED_DIR46;
1331 }
1332
1333 rv = NS_taccessaccess(mDir.get(), W_OK2);
1334 if (rv) {
1335 LOG(("access failed: %d, %d", rv, errno))UpdateLog::GetPrimaryLog().Printf ("access failed: %d, %d", rv
, (*__errno_location ()))
;
1336 return WRITE_ERROR_DIR_ACCESS_DENIED68;
1337 }
1338
1339 return OK0;
1340}
1341
1342int RemoveDir::Execute() {
1343 if (mSkip) {
1344 return OK0;
1345 }
1346
1347 LOG(("EXECUTE REMOVEDIR " LOG_S "/", mRelPath.get()))UpdateLog::GetPrimaryLog().Printf ("EXECUTE REMOVEDIR " "%s" "/"
, mRelPath.get())
;
1348
1349 // The directory is checked for existence at every step since it might have
1350 // been removed by a separate instruction: bug 311099.
1351 int rv = NS_taccessaccess(mDir.get(), F_OK0);
1352 if (rv) {
1353 LOG(("directory no longer exists; skipping"))UpdateLog::GetPrimaryLog().Printf ("directory no longer exists; skipping"
)
;
1354 mSkip = 1;
1355 }
1356
1357 return OK0;
1358}
1359
1360void RemoveDir::Finish(int status) {
1361 if (mSkip || status != OK0) {
1362 return;
1363 }
1364
1365 LOG(("FINISH REMOVEDIR " LOG_S "/", mRelPath.get()))UpdateLog::GetPrimaryLog().Printf ("FINISH REMOVEDIR " "%s" "/"
, mRelPath.get())
;
1366
1367 // The directory is checked for existence at every step since it might have
1368 // been removed by a separate instruction: bug 311099.
1369 int rv = NS_taccessaccess(mDir.get(), F_OK0);
1370 if (rv) {
1371 LOG(("directory no longer exists; skipping"))UpdateLog::GetPrimaryLog().Printf ("directory no longer exists; skipping"
)
;
1372 return;
1373 }
1374
1375 if (status == OK0) {
1376 if (NS_trmdirrmdir(mDir.get())) {
1377 LOG(("non-fatal error removing directory: " LOG_S "/, rv: %d, err: %d",UpdateLog::GetPrimaryLog().Printf ("non-fatal error removing directory: "
"%s" "/, rv: %d, err: %d", mRelPath.get(), rv, (*__errno_location
()))
1378 mRelPath.get(), rv, errno))UpdateLog::GetPrimaryLog().Printf ("non-fatal error removing directory: "
"%s" "/, rv: %d, err: %d", mRelPath.get(), rv, (*__errno_location
()))
;
1379 }
1380 }
1381}
1382
1383class AddFile : public Action {
1384 public:
1385 AddFile() : mAdded(false) {}
1386
1387 int Parse(NS_tchar* line) override;
1388 int Prepare() override;
1389 int Execute() override;
1390 void Finish(int status) override;
1391
1392 private:
1393 mozilla::UniquePtr<NS_tchar[]> mFile;
1394 mozilla::UniquePtr<NS_tchar[]> mRelPath;
1395 bool mAdded;
1396};
1397
1398int AddFile::Parse(NS_tchar* line) {
1399 // format "<newfile>"
1400
1401 NS_tchar* validPath = get_valid_path(&line);
1402 if (!validPath) {
1403 return PARSE_ERROR5;
1404 }
1405
1406 mRelPath = mozilla::MakeUnique<NS_tchar[]>(MAXPATHLEN4096);
1407 NS_tstrcpystrcpy(mRelPath.get(), validPath);
1408
1409 mFile.reset(get_full_path(validPath));
1410 if (!mFile) {
1411 return PARSE_ERROR5;
1412 }
1413
1414 return OK0;
1415}
1416
1417int AddFile::Prepare() {
1418 LOG(("PREPARE ADD " LOG_S, mRelPath.get()))UpdateLog::GetPrimaryLog().Printf ("PREPARE ADD " "%s", mRelPath
.get())
;
1419
1420 return OK0;
1421}
1422
1423int AddFile::Execute() {
1424 LOG(("EXECUTE ADD " LOG_S, mRelPath.get()))UpdateLog::GetPrimaryLog().Printf ("EXECUTE ADD " "%s", mRelPath
.get())
;
1425
1426 int rv;
1427
1428 // First make sure that we can actually get rid of any existing file.
1429 rv = NS_taccessaccess(mFile.get(), F_OK0);
1430 if (rv == 0) {
1431 if (sStagedUpdate) {
1432 // Staged updates don't need backup files so just remove it.
1433 rv = ensure_remove(mFile.get());
1434 } else {
1435 rv = backup_create(mFile.get());
1436 }
1437 if (rv) {
1438 return rv;
1439 }
1440 } else {
1441 rv = ensure_parent_dir(mFile.get());
1442 if (rv) {
1443 return rv;
1444 }
1445 }
1446
1447#ifdef XP_WIN
1448 char sourcefile[MAXPATHLEN4096];
1449 if (!WideCharToMultiByte(CP_UTF8, 0, mRelPath.get(), -1, sourcefile,
1450 MAXPATHLEN4096, nullptr, nullptr)) {
1451 LOG(("error converting wchar to utf8: %lu", GetLastError()))UpdateLog::GetPrimaryLog().Printf ("error converting wchar to utf8: %lu"
, GetLastError())
;
1452 return STRING_CONVERSION_ERROR16;
1453 }
1454
1455 rv = gArchiveReader.ExtractFile(sourcefile, mFile.get());
1456#else
1457 rv = gArchiveReader.ExtractFile(mRelPath.get(), mFile.get());
1458#endif
1459 if (!rv) {
1460 mAdded = true;
1461 }
1462 return rv;
1463}
1464
1465void AddFile::Finish(int status) {
1466 LOG(("FINISH ADD " LOG_S, mRelPath.get()))UpdateLog::GetPrimaryLog().Printf ("FINISH ADD " "%s", mRelPath
.get())
;
1467 // Staged updates don't create backup files.
1468 if (!sStagedUpdate) {
1469 // When there is an update failure and a file has been added it is removed
1470 // here since there might not be a backup to replace it.
1471 if (status && mAdded) {
1472 if (NS_tremoveremove(mFile.get()) && errno(*__errno_location ()) != ENOENT2) {
1473 LOG(("non-fatal error after update failure removing added file: " LOG_SUpdateLog::GetPrimaryLog().Printf ("non-fatal error after update failure removing added file: "
"%s" ", err: %d", mFile.get(), (*__errno_location ()))
1474 ", err: %d",UpdateLog::GetPrimaryLog().Printf ("non-fatal error after update failure removing added file: "
"%s" ", err: %d", mFile.get(), (*__errno_location ()))
1475 mFile.get(), errno))UpdateLog::GetPrimaryLog().Printf ("non-fatal error after update failure removing added file: "
"%s" ", err: %d", mFile.get(), (*__errno_location ()))
;
1476 }
1477 }
1478 backup_finish(mFile.get(), mRelPath.get(), status);
1479 }
1480}
1481
1482class PatchFile : public Action {
1483 public:
1484 PatchFile() : mPatchFile(nullptr), mPatchIndex(-1), buf(nullptr) {}
1485
1486 ~PatchFile() override;
1487
1488 int Parse(NS_tchar* line) override;
1489 int Prepare() override; // should check for patch file and for checksum here
1490 int Execute() override;
1491 void Finish(int status) override;
1492
1493 private:
1494 int LoadSourceFile(FILE* ofile);
1495
1496 static int sPatchIndex;
1497
1498 const NS_tchar* mPatchFile;
1499 mozilla::UniquePtr<NS_tchar[]> mFile;
1500 mozilla::UniquePtr<NS_tchar[]> mFileRelPath;
1501 int mPatchIndex;
1502 MBSPatchHeader header;
1503 unsigned char* buf;
1504 NS_tchar spath[MAXPATHLEN4096];
1505 AutoFile mPatchStream;
1506};
1507
1508int PatchFile::sPatchIndex = 0;
1509
1510PatchFile::~PatchFile() {
1511 // Make sure mPatchStream gets unlocked on Windows; the system will do that,
1512 // but not until some indeterminate future time, and we want determinism.
1513 // Normally this happens at the end of Execute, when we close the stream;
1514 // this call is here in case Execute errors out.
1515#ifdef XP_WIN
1516 if (mPatchStream) {
1517 UnlockFile((HANDLE)_get_osfhandle(fileno(mPatchStream)), 0, 0, -1, -1);
1518 }
1519#endif
1520 // Patch files are written to the <working_dir>/updating directory which is
1521 // removed after the update has finished so don't delete patch files here.
1522
1523 if (buf) {
1524 free(buf);
1525 }
1526}
1527
1528int PatchFile::LoadSourceFile(FILE* ofile) {
1529 struct stat os;
1530 int rv = fstat(fileno((FILE*)ofile), &os);
1531 if (rv) {
1532 LOG(("LoadSourceFile: unable to stat destination file: " LOG_S ", "UpdateLog::GetPrimaryLog().Printf ("LoadSourceFile: unable to stat destination file: "
"%s" ", " "err: %d", mFileRelPath.get(), (*__errno_location (
)))
1533 "err: %d",UpdateLog::GetPrimaryLog().Printf ("LoadSourceFile: unable to stat destination file: "
"%s" ", " "err: %d", mFileRelPath.get(), (*__errno_location (
)))
1534 mFileRelPath.get(), errno))UpdateLog::GetPrimaryLog().Printf ("LoadSourceFile: unable to stat destination file: "
"%s" ", " "err: %d", mFileRelPath.get(), (*__errno_location (
)))
;
1535 return READ_ERROR6;
1536 }
1537
1538 if (uint32_t(os.st_size) != header.slen) {
1539 LOG(UpdateLog::GetPrimaryLog().Printf ("LoadSourceFile: destination file size %d does not match expected "
"size %d", uint32_t(os.st_size), header.slen)
1540 ("LoadSourceFile: destination file size %d does not match expected "UpdateLog::GetPrimaryLog().Printf ("LoadSourceFile: destination file size %d does not match expected "
"size %d", uint32_t(os.st_size), header.slen)
1541 "size %d",UpdateLog::GetPrimaryLog().Printf ("LoadSourceFile: destination file size %d does not match expected "
"size %d", uint32_t(os.st_size), header.slen)
1542 uint32_t(os.st_size), header.slen))UpdateLog::GetPrimaryLog().Printf ("LoadSourceFile: destination file size %d does not match expected "
"size %d", uint32_t(os.st_size), header.slen)
;
1543 return LOADSOURCE_ERROR_WRONG_SIZE2;
1544 }
1545
1546 buf = (unsigned char*)malloc(header.slen);
1547 if (!buf) {
1548 return UPDATER_MEM_ERROR13;
1549 }
1550
1551 size_t r = header.slen;
1552 unsigned char* rb = buf;
1553 while (r) {
1554 const size_t count = mmin(SSIZE_MAX9223372036854775807L, r);
1555 size_t c = fread(rb, 1, count, ofile);
1556 if (c != count) {
1557 LOG(("LoadSourceFile: error reading destination file: " LOG_S,UpdateLog::GetPrimaryLog().Printf ("LoadSourceFile: error reading destination file: "
"%s", mFileRelPath.get())
1558 mFileRelPath.get()))UpdateLog::GetPrimaryLog().Printf ("LoadSourceFile: error reading destination file: "
"%s", mFileRelPath.get())
;
1559 return READ_ERROR6;
1560 }
1561
1562 r -= c;
1563 rb += c;
1564 }
1565
1566 // Verify that the contents of the source file correspond to what we expect.
1567
1568 unsigned int crc = crc32(buf, header.slen);
1569
1570 if (crc != header.scrc32) {
1571 LOG(UpdateLog::GetPrimaryLog().Printf ("LoadSourceFile: destination file crc %d does not match expected "
"crc %d", crc, header.scrc32)
1572 ("LoadSourceFile: destination file crc %d does not match expected "UpdateLog::GetPrimaryLog().Printf ("LoadSourceFile: destination file crc %d does not match expected "
"crc %d", crc, header.scrc32)
1573 "crc %d",UpdateLog::GetPrimaryLog().Printf ("LoadSourceFile: destination file crc %d does not match expected "
"crc %d", crc, header.scrc32)
1574 crc, header.scrc32))UpdateLog::GetPrimaryLog().Printf ("LoadSourceFile: destination file crc %d does not match expected "
"crc %d", crc, header.scrc32)
;
1575 return CRC_ERROR4;
1576 }
1577
1578 return OK0;
1579}
1580
1581int PatchFile::Parse(NS_tchar* line) {
1582 // format "<patchfile>" "<filetopatch>"
1583
1584 // Get the path to the patch file inside of the mar
1585 mPatchFile = mstrtok(kQuote, &line);
1586 if (!mPatchFile) {
1587 return PARSE_ERROR5;
1588 }
1589
1590 // consume whitespace between args
1591 NS_tchar* q = mstrtok(kQuote, &line);
1592 if (!q) {
1593 return PARSE_ERROR5;
1594 }
1595
1596 NS_tchar* validPath = get_valid_path(&line);
1597 if (!validPath) {
1598 return PARSE_ERROR5;
1599 }
1600
1601 mFileRelPath = mozilla::MakeUnique<NS_tchar[]>(MAXPATHLEN4096);
1602 NS_tstrcpystrcpy(mFileRelPath.get(), validPath);
1603
1604 mFile.reset(get_full_path(validPath));
1605 if (!mFile) {
1606 return PARSE_ERROR5;
1607 }
1608
1609 return OK0;
1610}
1611
1612int PatchFile::Prepare() {
1613 LOG(("PREPARE PATCH " LOG_S, mFileRelPath.get()))UpdateLog::GetPrimaryLog().Printf ("PREPARE PATCH " "%s", mFileRelPath
.get())
;
1614
1615 // extract the patch to a temporary file
1616 mPatchIndex = sPatchIndex++;
1617
1618 NS_tsnprintfsnprintf(spath, sizeof(spath) / sizeof(spath[0]),
1619 NS_T("%s/updating/%d.patch")"%s/updating/%d.patch", gWorkingDirPath, mPatchIndex);
1620
1621 // The removal of pre-existing patch files here is in case a previous update
1622 // crashed and left these files behind.
1623 if (NS_tremoveremove(spath) && errno(*__errno_location ()) != ENOENT2) {
1624 LOG(("failure removing pre-existing patch file: " LOG_S ", err: %d", spath,UpdateLog::GetPrimaryLog().Printf ("failure removing pre-existing patch file: "
"%s" ", err: %d", spath, (*__errno_location ()))
1625 errno))UpdateLog::GetPrimaryLog().Printf ("failure removing pre-existing patch file: "
"%s" ", err: %d", spath, (*__errno_location ()))
;
1626 return WRITE_ERROR7;
1627 }
1628
1629 mPatchStream = NS_tfopenfopen(spath, NS_T("wb+")"wb+");
1630 if (!mPatchStream) {
1631 return WRITE_ERROR7;
1632 }
1633
1634#ifdef XP_WIN
1635 // Lock the patch file, so it can't be messed with between
1636 // when we're done creating it and when we go to apply it.
1637 if (!LockFile((HANDLE)_get_osfhandle(fileno(mPatchStream)), 0, 0, -1, -1)) {
1638 LOG(("Couldn't lock patch file: %lu", GetLastError()))UpdateLog::GetPrimaryLog().Printf ("Couldn't lock patch file: %lu"
, GetLastError())
;
1639 return LOCK_ERROR_PATCH_FILE73;
1640 }
1641
1642 char sourcefile[MAXPATHLEN4096];
1643 if (!WideCharToMultiByte(CP_UTF8, 0, mPatchFile, -1, sourcefile, MAXPATHLEN4096,
1644 nullptr, nullptr)) {
1645 LOG(("error converting wchar to utf8: %lu", GetLastError()))UpdateLog::GetPrimaryLog().Printf ("error converting wchar to utf8: %lu"
, GetLastError())
;
1646 return STRING_CONVERSION_ERROR16;
1647 }
1648
1649 int rv = gArchiveReader.ExtractFileToStream(sourcefile, mPatchStream);
1650#else
1651 int rv = gArchiveReader.ExtractFileToStream(mPatchFile, mPatchStream);
1652#endif
1653
1654 return rv;
1655}
1656
1657int PatchFile::Execute() {
1658 LOG(("EXECUTE PATCH " LOG_S, mFileRelPath.get()))UpdateLog::GetPrimaryLog().Printf ("EXECUTE PATCH " "%s", mFileRelPath
.get())
;
1659
1660 fseek(mPatchStream, 0, SEEK_SET0);
1661
1662 int rv = MBS_ReadHeader(mPatchStream, &header);
1663 if (rv) {
1664 return rv;
1665 }
1666
1667 FILE* origfile = nullptr;
1668#ifdef XP_WIN
1669 if (NS_tstrcmpstrcmp(mFileRelPath.get(), gCallbackRelPath) == 0) {
1670 // Read from the copy of the callback when patching since the callback can't
1671 // be opened for reading to prevent the application from being launched.
1672 origfile = NS_tfopenfopen(gCallbackBackupPath, NS_T("rb")"rb");
1673 } else {
1674 origfile = NS_tfopenfopen(mFile.get(), NS_T("rb")"rb");
1675 }
1676#else
1677 origfile = NS_tfopenfopen(mFile.get(), NS_T("rb")"rb");
1678#endif
1679
1680 if (!origfile) {
1681 LOG(("unable to open destination file: " LOG_S ", err: %d",UpdateLog::GetPrimaryLog().Printf ("unable to open destination file: "
"%s" ", err: %d", mFileRelPath.get(), (*__errno_location ())
)
1682 mFileRelPath.get(), errno))UpdateLog::GetPrimaryLog().Printf ("unable to open destination file: "
"%s" ", err: %d", mFileRelPath.get(), (*__errno_location ())
)
;
1683 return READ_ERROR6;
1684 }
1685
1686 rv = LoadSourceFile(origfile);
1687 fclose(origfile);
1688 if (rv) {
1689 LOG(("LoadSourceFile failed"))UpdateLog::GetPrimaryLog().Printf ("LoadSourceFile failed");
1690 return rv;
1691 }
1692
1693 // Rename the destination file if it exists before proceeding so it can be
1694 // used to restore the file to its original state if there is an error.
1695 struct NS_tstat_tstat ss;
1696 rv = NS_tstatstat(mFile.get(), &ss);
1697 if (rv) {
1698 LOG(("failed to read file status info: " LOG_S ", err: %d",UpdateLog::GetPrimaryLog().Printf ("failed to read file status info: "
"%s" ", err: %d", mFileRelPath.get(), (*__errno_location ())
)
1699 mFileRelPath.get(), errno))UpdateLog::GetPrimaryLog().Printf ("failed to read file status info: "
"%s" ", err: %d", mFileRelPath.get(), (*__errno_location ())
)
;
1700 return READ_ERROR6;
1701 }
1702
1703 // Staged updates don't need backup files.
1704 if (!sStagedUpdate) {
1705 rv = backup_create(mFile.get());
1706 if (rv) {
1707 return rv;
1708 }
1709 }
1710
1711#if defined(HAVE_POSIX_FALLOCATE1)
1712 AutoFile ofile(ensure_open(mFile.get(), NS_T("wb+")"wb+", ss.st_mode));
1713 posix_fallocate(fileno((FILE*)ofile), 0, header.dlen);
1714#elif defined(XP_WIN)
1715 bool shouldTruncate = true;
1716 // Creating the file, setting the size, and then closing the file handle
1717 // lessens fragmentation more than any other method tested. Other methods that
1718 // have been tested are:
1719 // 1. _chsize / _chsize_s reduced fragmentation though not completely.
1720 // 2. _get_osfhandle and then setting the size reduced fragmentation though
1721 // not completely. There are also reports of _get_osfhandle failing on
1722 // mingw.
1723 HANDLE hfile = CreateFileW(mFile.get(), GENERIC_WRITE, 0, nullptr,
1724 CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
1725
1726 if (hfile != INVALID_HANDLE_VALUE) {
1727 if (SetFilePointer(hfile, header.dlen, nullptr, FILE_BEGIN) !=
1728 INVALID_SET_FILE_POINTER &&
1729 SetEndOfFile(hfile) != 0) {
1730 shouldTruncate = false;
1731 }
1732 CloseHandle(hfile);
1733 }
1734
1735 AutoFile ofile(ensure_open(
1736 mFile.get(), shouldTruncate ? NS_T("wb+")"wb+" : NS_T("rb+")"rb+", ss.st_mode));
1737#elif defined(XP_MACOSX)
1738 AutoFile ofile(ensure_open(mFile.get(), NS_T("wb+")"wb+", ss.st_mode));
1739 // Modified code from FileUtils.cpp
1740 fstore_t store = {F_ALLOCATECONTIG, F_PEOFPOSMODE, 0, header.dlen};
1741 // Try to get a continous chunk of disk space
1742 rv = fcntl(fileno((FILE*)ofile), F_PREALLOCATE, &store);
1743 if (rv == -1) {
1744 // OK, perhaps we are too fragmented, allocate non-continuous
1745 store.fst_flags = F_ALLOCATEALL;
1746 rv = fcntl(fileno((FILE*)ofile), F_PREALLOCATE, &store);
1747 }
1748
1749 if (rv != -1) {
1750 ftruncate(fileno((FILE*)ofile), header.dlen);
1751 }
1752#else
1753 AutoFile ofile(ensure_open(mFile.get(), NS_T("wb+")"wb+", ss.st_mode));
1754#endif
1755
1756 if (ofile == nullptr) {
1757 LOG(("unable to create new file: " LOG_S ", err: %d", mFileRelPath.get(),UpdateLog::GetPrimaryLog().Printf ("unable to create new file: "
"%s" ", err: %d", mFileRelPath.get(), (*__errno_location ())
)
1758 errno))UpdateLog::GetPrimaryLog().Printf ("unable to create new file: "
"%s" ", err: %d", mFileRelPath.get(), (*__errno_location ())
)
;
1759 return WRITE_ERROR_OPEN_PATCH_FILE63;
1760 }
1761
1762#ifdef XP_WIN
1763 if (!shouldTruncate) {
1764 fseek(ofile, 0, SEEK_SET0);
1765 }
1766#endif
1767
1768 rv = MBS_ApplyPatch(&header, mPatchStream, buf, ofile);
1769
1770 // Go ahead and do a bit of cleanup now to minimize runtime overhead.
1771 // Make sure mPatchStream gets unlocked on Windows; the system will do that,
1772 // but not until some indeterminate future time, and we want determinism.
1773#ifdef XP_WIN
1774 UnlockFile((HANDLE)_get_osfhandle(fileno(mPatchStream)), 0, 0, -1, -1);
1775#endif
1776 // Set mPatchStream to nullptr to make AutoFile close the file,
1777 // so it can be deleted on Windows.
1778 mPatchStream = nullptr;
1779 // Patch files are written to the <working_dir>/updating directory which is
1780 // removed after the update has finished so don't delete patch files here.
1781 spath[0] = NS_T('\0')'\0';
1782 free(buf);
1783 buf = nullptr;
1784
1785 return rv;
1786}
1787
1788void PatchFile::Finish(int status) {
1789 LOG(("FINISH PATCH " LOG_S, mFileRelPath.get()))UpdateLog::GetPrimaryLog().Printf ("FINISH PATCH " "%s", mFileRelPath
.get())
;
1790
1791 // Staged updates don't create backup files.
1792 if (!sStagedUpdate) {
1793 backup_finish(mFile.get(), mFileRelPath.get(), status);
1794 }
1795}
1796
1797class AddIfFile : public AddFile {
1798 public:
1799 int Parse(NS_tchar* line) override;
1800 int Prepare() override;
1801 int Execute() override;
1802 void Finish(int status) override;
1803
1804 protected:
1805 mozilla::UniquePtr<NS_tchar[]> mTestFile;
1806};
1807
1808int AddIfFile::Parse(NS_tchar* line) {
1809 // format "<testfile>" "<newfile>"
1810
1811 mTestFile.reset(get_full_path(get_valid_path(&line)));
1812 if (!mTestFile) {
1813 return PARSE_ERROR5;
1814 }
1815
1816 // consume whitespace between args
1817 NS_tchar* q = mstrtok(kQuote, &line);
1818 if (!q) {
1819 return PARSE_ERROR5;
1820 }
1821
1822 return AddFile::Parse(line);
1823}
1824
1825int AddIfFile::Prepare() {
1826 // If the test file does not exist, then skip this action.
1827 if (NS_taccessaccess(mTestFile.get(), F_OK0)) {
1828 mTestFile = nullptr;
1829 return OK0;
1830 }
1831
1832 return AddFile::Prepare();
1833}
1834
1835int AddIfFile::Execute() {
1836 if (!mTestFile) {
1837 return OK0;
1838 }
1839
1840 return AddFile::Execute();
1841}
1842
1843void AddIfFile::Finish(int status) {
1844 if (!mTestFile) {
1845 return;
1846 }
1847
1848 AddFile::Finish(status);
1849}
1850
1851class AddIfNotFile : public AddFile {
1852 public:
1853 int Parse(NS_tchar* line) override;
1854 int Prepare() override;
1855 int Execute() override;
1856 void Finish(int status) override;
1857
1858 protected:
1859 mozilla::UniquePtr<NS_tchar[]> mTestFile;
1860};
1861
1862int AddIfNotFile::Parse(NS_tchar* line) {
1863 // format "<testfile>" "<newfile>"
1864
1865 mTestFile.reset(get_full_path(get_valid_path(&line)));
1866 if (!mTestFile) {
1867 return PARSE_ERROR5;
1868 }
1869
1870 // consume whitespace between args
1871 NS_tchar* q = mstrtok(kQuote, &line);
1872 if (!q) {
1873 return PARSE_ERROR5;
1874 }
1875
1876 return AddFile::Parse(line);
1877}
1878
1879int AddIfNotFile::Prepare() {
1880 // If the test file exists, then skip this action.
1881 if (!NS_taccessaccess(mTestFile.get(), F_OK0)) {
1882 mTestFile = NULL__null;
1883 return OK0;
1884 }
1885
1886 return AddFile::Prepare();
1887}
1888
1889int AddIfNotFile::Execute() {
1890 if (!mTestFile) {
1891 return OK0;
1892 }
1893
1894 return AddFile::Execute();
1895}
1896
1897void AddIfNotFile::Finish(int status) {
1898 if (!mTestFile) {
1899 return;
1900 }
1901
1902 AddFile::Finish(status);
1903}
1904
1905class PatchIfFile : public PatchFile {
1906 public:
1907 int Parse(NS_tchar* line) override;
1908 int Prepare() override; // should check for patch file and for checksum here
1909 int Execute() override;
1910 void Finish(int status) override;
1911
1912 private:
1913 mozilla::UniquePtr<NS_tchar[]> mTestFile;
1914};
1915
1916int PatchIfFile::Parse(NS_tchar* line) {
1917 // format "<testfile>" "<patchfile>" "<filetopatch>"
1918
1919 mTestFile.reset(get_full_path(get_valid_path(&line)));
1920 if (!mTestFile) {
1921 return PARSE_ERROR5;
1922 }
1923
1924 // consume whitespace between args
1925 NS_tchar* q = mstrtok(kQuote, &line);
1926 if (!q) {
1927 return PARSE_ERROR5;
1928 }
1929
1930 return PatchFile::Parse(line);
1931}
1932
1933int PatchIfFile::Prepare() {
1934 // If the test file does not exist, then skip this action.
1935 if (NS_taccessaccess(mTestFile.get(), F_OK0)) {
1936 mTestFile = nullptr;
1937 return OK0;
1938 }
1939
1940 return PatchFile::Prepare();
1941}
1942
1943int PatchIfFile::Execute() {
1944 if (!mTestFile) {
1945 return OK0;
1946 }
1947
1948 return PatchFile::Execute();
1949}
1950
1951void PatchIfFile::Finish(int status) {
1952 if (!mTestFile) {
1953 return;
1954 }
1955
1956 PatchFile::Finish(status);
1957}
1958
1959//-----------------------------------------------------------------------------
1960
1961#ifdef XP_WIN
1962# include "nsWindowsRestart.cpp"
1963# include "nsWindowsHelpers.h"
1964# include "uachelper.h"
1965# ifdef MOZ_MAINTENANCE_SERVICE
1966# include "pathhash.h"
1967# endif
1968
1969/**
1970 * Launch the post update application (helper.exe). It takes in the path of the
1971 * callback application to calculate the path of helper.exe. For service updates
1972 * this is called from both the system account and the current user account.
1973 *
1974 * @param installationDir The path to the callback application binary.
1975 * @param updateInfoDir The directory where update info is stored.
1976 * @return true if there was no error starting the process.
1977 */
1978bool LaunchWinPostProcess(const WCHAR* installationDir,
1979 const WCHAR* updateInfoDir) {
1980 WCHAR workingDirectory[MAX_PATH + 1] = {L'\0'};
1981 wcsncpy(workingDirectory, installationDir, MAX_PATH);
1982
1983 // Launch helper.exe to perform post processing (e.g. registry and log file
1984 // modifications) for the update.
1985 WCHAR inifile[MAX_PATH + 1] = {L'\0'};
1986 wcsncpy(inifile, installationDir, MAX_PATH);
1987 if (!PathAppendSafe(inifile, L"updater.ini")) {
1988 return false;
1989 }
1990
1991 WCHAR exefile[MAX_PATH + 1];
1992 WCHAR exearg[MAX_PATH + 1];
1993 if (!GetPrivateProfileStringW(L"PostUpdateWin", L"ExeRelPath", nullptr,
1994 exefile, MAX_PATH + 1, inifile)) {
1995 return false;
1996 }
1997
1998 if (!GetPrivateProfileStringW(L"PostUpdateWin", L"ExeArg", nullptr, exearg,
1999 MAX_PATH + 1, inifile)) {
2000 return false;
2001 }
2002
2003 // The relative path must not contain directory traversals, current directory,
2004 // or colons.
2005 if (wcsstr(exefile, L"..") != nullptr || wcsstr(exefile, L"./") != nullptr ||
2006 wcsstr(exefile, L".\\") != nullptr || wcsstr(exefile, L":") != nullptr) {
2007 return false;
2008 }
2009
2010 // The relative path must not start with a decimal point, backslash, or
2011 // forward slash.
2012 if (exefile[0] == L'.' || exefile[0] == L'\\' || exefile[0] == L'/') {
2013 return false;
2014 }
2015
2016 WCHAR exefullpath[MAX_PATH + 1] = {L'\0'};
2017 wcsncpy(exefullpath, installationDir, MAX_PATH);
2018 if (!PathAppendSafe(exefullpath, exefile)) {
2019 return false;
2020 }
2021
2022 if (!IsValidFullPath(exefullpath)) {
2023 return false;
2024 }
2025
2026# if !defined(TEST_UPDATER) && defined(MOZ_MAINTENANCE_SERVICE)
2027 if (sUsingService &&
2028 !DoesBinaryMatchAllowedCertificates(installationDir, exefullpath)) {
2029 return false;
2030 }
2031# endif
2032
2033 WCHAR dlogFile[MAX_PATH + 1];
2034 if (!PathGetSiblingFilePath(dlogFile, exefullpath, L"uninstall.update")) {
2035 return false;
2036 }
2037
2038 WCHAR slogFile[MAX_PATH + 1] = {L'\0'};
2039 if (gCopyOutputFiles) {
2040 if (!GetSecureOutputFilePath(gPatchDirPath, L".log", slogFile)) {
2041 return false;
2042 }
2043 } else {
2044 wcsncpy(slogFile, updateInfoDir, MAX_PATH);
2045 if (!PathAppendSafe(slogFile, L"update.log")) {
2046 return false;
2047 }
2048 }
2049
2050 WCHAR dummyArg[14] = {L'\0'};
2051 wcsncpy(dummyArg, L"argv0ignored ",
2052 sizeof(dummyArg) / sizeof(dummyArg[0]) - 1);
2053
2054 size_t len = wcslen(exearg) + wcslen(dummyArg);
2055 WCHAR* cmdline = (WCHAR*)malloc((len + 1) * sizeof(WCHAR));
2056 if (!cmdline) {
2057 return false;
2058 }
2059
2060 wcsncpy(cmdline, dummyArg, len);
2061 wcscat(cmdline, exearg);
2062
2063 // We want to launch the post update helper app to update the Windows
2064 // registry even if there is a failure with removing the uninstall.update
2065 // file or copying the update.log file.
2066 CopyFileW(slogFile, dlogFile, false);
2067
2068 STARTUPINFOW si = {sizeof(si), 0};
2069 si.lpDesktop = const_cast<LPWSTR>(L""); // -Wwritable-strings
2070 PROCESS_INFORMATION pi = {0};
2071
2072 bool ok = CreateProcessW(exefullpath, cmdline,
2073 nullptr, // no special security attributes
2074 nullptr, // no special thread attributes
2075 false, // don't inherit filehandles
2076 0, // No special process creation flags
2077 nullptr, // inherit my environment
2078 workingDirectory, &si, &pi);
2079 free(cmdline);
2080 if (ok) {
2081 WaitForSingleObject(pi.hProcess, INFINITE);
2082 CloseHandle(pi.hProcess);
2083 CloseHandle(pi.hThread);
2084 }
2085 return ok;
2086}
2087
2088#endif
2089
2090static void LaunchCallbackApp(const NS_tchar* workingDir, int argc,
2091 NS_tchar** argv, bool usingService) {
2092 putenv(const_cast<char*>("MOZ_LAUNCHED_CHILD=1"));
2093
2094 // Run from the specified working directory (see bug 312360).
2095 if (NS_tchdirchdir(workingDir) != 0) {
2096 LOG(("Warning: chdir failed"))UpdateLog::GetPrimaryLog().Printf ("Warning: chdir failed");
2097 }
2098
2099#if defined(USE_EXECV)
2100 execv(argv[0], argv);
2101#elif defined(XP_MACOSX)
2102 LaunchChild(argc, (const char**)argv);
2103#elif defined(XP_WIN)
2104 // Do not allow the callback to run when running an update through the
2105 // service as session 0. The unelevated updater.exe will do the launching.
2106 if (!usingService) {
2107 HANDLE hProcess;
2108 if (WinLaunchChild(argv[0], argc, argv, nullptr, &hProcess)) {
2109 // Keep the current process around until the callback process has created
2110 // its message queue, to avoid the launched process's windows being forced
2111 // into the background.
2112 mozilla::WaitForInputIdle(hProcess);
2113 CloseHandle(hProcess);
2114 }
2115 }
2116#else
2117# warning "Need implementaton of LaunchCallbackApp"
2118#endif
2119}
2120
2121static bool WriteToFile(const NS_tchar* aFilename, const char* aStatus) {
2122 NS_tchar statusFilePath[MAXPATHLEN4096 + 1] = {NS_T('\0')'\0'};
2123#if defined(XP_WIN)
2124 if (gUseSecureOutputPath) {
2125 if (!GetSecureOutputFilePath(gPatchDirPath, L".status", statusFilePath)) {
2126 return false;
2127 }
2128 } else {
2129 NS_tsnprintfsnprintf(statusFilePath,
2130 sizeof(statusFilePath) / sizeof(statusFilePath[0]),
2131 NS_T("%s\\%s")"%s\\%s", gPatchDirPath, aFilename);
2132 }
2133#else
2134 NS_tsnprintfsnprintf(statusFilePath,
2135 sizeof(statusFilePath) / sizeof(statusFilePath[0]),
2136 NS_T("%s/%s")"%s/%s", gPatchDirPath, aFilename);
2137 // Make sure that the directory for the update status file exists
2138 if (ensure_parent_dir(statusFilePath)) {
2139 return false;
2140 }
2141#endif
2142
2143 AutoFile statusFile(NS_tfopenfopen(statusFilePath, NS_T("wb+")"wb+"));
2144 if (statusFile == nullptr) {
2145 return false;
2146 }
2147
2148 if (fwrite(aStatus, strlen(aStatus), 1, statusFile) != 1) {
2149 return false;
2150 }
2151
2152#if defined(XP_WIN)
2153 if (gUseSecureOutputPath) {
2154 // This is done after the update status file has been written so if the
2155 // write to the update status file fails an existing update status file
2156 // won't be used.
2157 if (!WriteSecureIDFile(gPatchDirPath)) {
2158 return false;
2159 }
2160 }
2161#endif
2162
2163 return true;
2164}
2165
2166/**
2167 * Writes a string to the update.status file.
2168 *
2169 * NOTE: All calls to WriteStatusFile MUST happen before calling output_finish
2170 * because the output_finish function copies the update status file for
2171 * the elevated updater and writing the status file after calling
2172 * output_finish will overwrite it.
2173 *
2174 * @param aStatus
2175 * The string to write to the update.status file.
2176 * @return true on success.
2177 */
2178static bool WriteStatusFile(const char* aStatus) {
2179 return WriteToFile(NS_T("update.status")"update.status", aStatus);
2180}
2181
2182/**
2183 * Writes a string to the update.status file based on the status param.
2184 *
2185 * NOTE: All calls to WriteStatusFile MUST happen before calling output_finish
2186 * because the output_finish function copies the update status file for
2187 * the elevated updater and writing the status file after calling
2188 * output_finish will overwrite it.
2189 *
2190 * @param status
2191 * A status code used to determine what string to write to the
2192 * update.status file (see code).
2193 */
2194static void WriteStatusFile(int status) {
2195 const char* text;
2196
2197 char buf[32];
2198 if (status == OK0) {
2199 if (sStagedUpdate) {
2200 text = "applied\n";
2201 } else {
2202 text = "succeeded\n";
2203 }
2204 } else {
2205 snprintf(buf, sizeof(buf) / sizeof(buf[0]), "failed: %d\n", status);
2206 text = buf;
2207 }
2208
2209 WriteStatusFile(text);
2210}
2211
2212#if defined(XP_WIN)
2213/*
2214 * Parses the passed contents of an update status file and checks if the
2215 * contained status matches the expected status.
2216 *
2217 * @param statusString The status file contents.
2218 * @param expectedStatus The status to compare the update status file's
2219 * contents against.
2220 * @param errorCode Optional out parameter. If a pointer is passed and the
2221 * update status file contains an error code, the code
2222 * will be returned via the out parameter. If a pointer is
2223 * passed and the update status file does not contain an error
2224 * code, or any error code after the status could not be
2225 * parsed, mozilla::Nothing will be returned via this
2226 * parameter.
2227 * @return true if the status is set to the value indicated by expectedStatus.
2228 */
2229static bool UpdateStatusIs(const char* statusString, const char* expectedStatus,
2230 mozilla::Maybe<int>* errorCode = nullptr) {
2231 if (errorCode) {
2232 *errorCode = mozilla::Nothing();
2233 }
2234
2235 // Parse the update status file. Expected format is:
2236 // Update status string
2237 // Optionally followed by:
2238 // Colon character (':')
2239 // Space character (' ')
2240 // Integer error code
2241 // Newline character
2242 const char* statusEnd = strchr(statusString, ':');
2243 if (statusEnd == nullptr) {
2244 statusEnd = strchr(statusString, '\n');
2245 }
2246 if (statusEnd == nullptr) {
2247 statusEnd = strchr(statusString, '\0');
2248 }
2249 size_t statusLen = statusEnd - statusString;
2250 size_t expectedStatusLen = strlen(expectedStatus);
2251
2252 bool statusMatch =
2253 statusLen == expectedStatusLen &&
2254 strncmp(statusString, expectedStatus, expectedStatusLen) == 0;
2255
2256 // We only need to continue parsing if (a) there is a place to store the error
2257 // code if we parse it, and (b) there is a status code to parse. If the status
2258 // string didn't end with a ':', there won't be an error code after it.
2259 if (!errorCode || *statusEnd != ':') {
2260 return statusMatch;
2261 }
2262
2263 const char* errorCodeStart = statusEnd + 1;
2264 char* errorCodeEnd = nullptr;
2265 // strtol skips an arbitrary number of leading whitespace characters. This
2266 // technically allows us to successfully consume slightly misformatted status
2267 // files, since the expected format is for there to be a single space only.
2268 long longErrorCode = strtol(errorCodeStart, &errorCodeEnd, 10);
2269 if (errorCodeEnd != errorCodeStart && longErrorCode < INT_MAX2147483647 &&
2270 longErrorCode > INT_MIN(-2147483647 -1)) {
2271 // We don't allow equality with INT_MAX/INT_MIN for two reasons. It could
2272 // be that, on this platform, INT_MAX/INT_MIN equal LONG_MAX/LONG_MIN, which
2273 // is what strtol gives us if the parsed value was out of bounds. And those
2274 // values are already way, way outside the set of valid update error codes
2275 // anyways.
2276 errorCode->emplace(static_cast<int>(longErrorCode));
2277 }
2278 return statusMatch;
2279}
2280
2281/*
2282 * Reads the secure update status file and sets statusMatch to true if the
2283 * status matches the expected status that was passed.
2284 *
2285 * @param expectedStatus The status to compare the update status file's
2286 * contents against.
2287 * @param statusMatch Out parameter for specifying if the status is set to
2288 * the value indicated by expectedStatus
2289 * @param errorCode Optional out parameter. If a pointer is passed and the
2290 * update status file contains an error code, the code
2291 * will be returned via the out parameter. If a pointer is
2292 * passed and the update status file does not contain an error
2293 * code, or any error code after the status could not be
2294 * parsed, mozilla::Nothing will be returned via this
2295 * parameter.
2296 * @return true if the information was retrieved successfully.
2297 */
2298static bool CompareSecureUpdateStatus(
2299 const char* expectedStatus, bool& statusMatch,
2300 mozilla::Maybe<int>* errorCode = nullptr) {
2301 NS_tchar statusFilePath[MAX_PATH + 1] = {L'\0'};
2302 if (!GetSecureOutputFilePath(gPatchDirPath, L".status", statusFilePath)) {
2303 return false;
2304 }
2305
2306 AutoFile file(NS_tfopenfopen(statusFilePath, NS_T("rb")"rb"));
2307 if (file == nullptr) {
2308 return false;
2309 }
2310
2311 const size_t bufferLength = 32;
2312 char buf[bufferLength] = {0};
2313 size_t charsRead = fread(buf, sizeof(buf[0]), bufferLength - 1, file);
2314 if (ferror(file)) {
2315 return false;
2316 }
2317 buf[charsRead] = '\0';
2318
2319 statusMatch = UpdateStatusIs(buf, expectedStatus, errorCode);
2320 return true;
2321}
2322
2323/*
2324 * Reads the secure update status file and sets isSucceeded to true if the
2325 * status is set to succeeded.
2326 *
2327 * @param isSucceeded Out parameter for specifying if the status
2328 * is set to succeeded or not.
2329 * @return true if the information was retrieved successfully.
2330 */
2331static bool IsSecureUpdateStatusSucceeded(bool& isSucceeded) {
2332 return CompareSecureUpdateStatus("succeeded", isSucceeded);
2333}
2334#endif
2335
2336#ifdef MOZ_MAINTENANCE_SERVICE
2337/*
2338 * Read the update.status file and sets isPendingService to true if
2339 * the status is set to pending-service.
2340 *
2341 * @param isPendingService Out parameter for specifying if the status
2342 * is set to pending-service or not.
2343 * @return true if the information was retrieved and it is pending
2344 * or pending-service.
2345 */
2346static bool IsUpdateStatusPendingService() {
2347 NS_tchar filename[MAXPATHLEN4096];
2348 NS_tsnprintfsnprintf(filename, sizeof(filename) / sizeof(filename[0]),
2349 NS_T("%s/update.status")"%s/update.status", gPatchDirPath);
2350
2351 AutoFile file(NS_tfopenfopen(filename, NS_T("rb")"rb"));
2352 if (file == nullptr) {
2353 return false;
2354 }
2355
2356 const size_t bufferLength = 32;
2357 char buf[bufferLength] = {0};
2358 size_t charsRead = fread(buf, sizeof(buf[0]), bufferLength - 1, file);
2359 if (ferror(file)) {
2360 return false;
2361 }
2362 buf[charsRead] = '\0';
2363
2364 return UpdateStatusIs(buf, "pending-service") ||
2365 UpdateStatusIs(buf, "applied-service");
2366}
2367
2368/*
2369 * Reads the secure update status file and sets isFailed to true if the
2370 * status is set to failed.
2371 *
2372 * @param isFailed Out parameter for specifying if the status
2373 * is set to failed or not.
2374 * @param errorCode Optional out parameter. If a pointer is passed and the
2375 * update status file contains an error code, the code
2376 * will be returned via the out parameter. If a pointer is
2377 * passed and the update status file does not contain an error
2378 * code, or any error code after the status could not be
2379 * parsed, mozilla::Nothing will be returned via this
2380 * parameter.
2381 * @return true if the information was retrieved successfully.
2382 */
2383static bool IsSecureUpdateStatusFailed(
2384 bool& isFailed, mozilla::Maybe<int>* errorCode = nullptr) {
2385 return CompareSecureUpdateStatus("failed", isFailed, errorCode);
2386}
2387
2388/**
2389 * This function determines whether the error represented by the passed error
2390 * code could potentially be recovered from or bypassed by updating without
2391 * using the Maintenance Service (i.e. by showing a UAC prompt).
2392 * We don't really want to show a UAC prompt, but it's preferable over the
2393 * manual update doorhanger
2394 *
2395 * @param errorCode An integer error code from the update.status file. Should
2396 * be one of the codes enumerated in updatererrors.h.
2397 * @returns true if the code represents a Maintenance Service specific error.
2398 * Otherwise, false.
2399 */
2400static bool IsServiceSpecificErrorCode(int errorCode) {
2401 return ((errorCode >= 24 && errorCode <= 33) ||
2402 (errorCode >= 49 && errorCode <= 58));
2403}
2404#endif
2405
2406/*
2407 * Copy the entire contents of the application installation directory to the
2408 * destination directory for the update process.
2409 *
2410 * @return 0 if successful, an error code otherwise.
2411 */
2412static int CopyInstallDirToDestDir() {
2413 // These files should not be copied over to the updated app
2414#ifdef XP_WIN
2415# define SKIPLIST_COUNT2 3
2416#elif XP_MACOSX
2417# define SKIPLIST_COUNT2 0
2418#else
2419# define SKIPLIST_COUNT2 2
2420#endif
2421 copy_recursive_skiplist<SKIPLIST_COUNT2> skiplist;
2422#ifndef XP_MACOSX
2423 skiplist.append(0, gInstallDirPath, NS_T("updated")"updated");
2424 skiplist.append(1, gInstallDirPath, NS_T("updates/0")"updates/0");
2425# ifdef XP_WIN
2426 skiplist.append(2, gInstallDirPath, NS_T("updated.update_in_progress.lock")"updated.update_in_progress.lock");
2427# endif
2428#endif
2429
2430 return ensure_copy_recursive(gInstallDirPath, gWorkingDirPath, skiplist);
2431}
2432
2433/*
2434 * Replace the application installation directory with the destination
2435 * directory in order to finish a staged update task
2436 *
2437 * @return 0 if successful, an error code otherwise.
2438 */
2439static int ProcessReplaceRequest() {
2440 // The replacement algorithm is like this:
2441 // 1. Move destDir to tmpDir. In case of failure, abort.
2442 // 2. Move newDir to destDir. In case of failure, revert step 1 and abort.
2443 // 3. Delete tmpDir (or defer it to the next reboot).
2444
2445#ifdef XP_MACOSX
2446 NS_tchar destDir[MAXPATHLEN4096];
2447 NS_tsnprintfsnprintf(destDir, sizeof(destDir) / sizeof(destDir[0]),
2448 NS_T("%s/Contents")"%s/Contents", gInstallDirPath);
2449#elif XP_WIN
2450 // Windows preserves the case of the file/directory names. We use the
2451 // GetLongPathName API in order to get the correct case for the directory
2452 // name, so that if the user has used a different case when launching the
2453 // application, the installation directory's name does not change.
2454 NS_tchar destDir[MAXPATHLEN4096];
2455 if (!GetLongPathNameW(gInstallDirPath, destDir,
2456 sizeof(destDir) / sizeof(destDir[0]))) {
2457 return NO_INSTALLDIR_ERROR34;
2458 }
2459#else
2460 NS_tchar* destDir = gInstallDirPath;
2461#endif
2462
2463 NS_tchar tmpDir[MAXPATHLEN4096];
2464 NS_tsnprintfsnprintf(tmpDir, sizeof(tmpDir) / sizeof(tmpDir[0]), NS_T("%s.bak")"%s.bak",
2465 destDir);
2466
2467 NS_tchar newDir[MAXPATHLEN4096];
2468 NS_tsnprintfsnprintf(newDir, sizeof(newDir) / sizeof(newDir[0]),
2469#ifdef XP_MACOSX
2470 NS_T("%s/Contents")"%s/Contents", gWorkingDirPath);
2471#else
2472 NS_T("%s.bak/updated")"%s.bak/updated", gInstallDirPath);
2473#endif
2474
2475 // First try to remove the possibly existing temp directory, because if this
2476 // directory exists, we will fail to rename destDir.
2477 // No need to error check here because if this fails, we will fail in the
2478 // next step anyways.
2479 ensure_remove_recursive(tmpDir);
2480
2481 LOG(("Begin moving destDir (" LOG_S ") to tmpDir (" LOG_S ")", destDir,UpdateLog::GetPrimaryLog().Printf ("Begin moving destDir (" "%s"
") to tmpDir (" "%s" ")", destDir, tmpDir)
2482 tmpDir))UpdateLog::GetPrimaryLog().Printf ("Begin moving destDir (" "%s"
") to tmpDir (" "%s" ")", destDir, tmpDir)
;
2483 int rv = rename_file(destDir, tmpDir, true);
2484#ifdef XP_WIN
2485 // On Windows, if Firefox is launched using the shortcut, it will hold a
2486 // handle to its installation directory open, which might not get released in
2487 // time. Therefore we wait a little bit here to see if the handle is released.
2488 // If it's not released, we just fail to perform the replace request.
2489 const int max_retries = 10;
2490 int retries = 0;
2491 while (rv == WRITE_ERROR7 && (retries++ < max_retries)) {
2492 LOG(UpdateLog::GetPrimaryLog().Printf ("PerformReplaceRequest: destDir rename attempt %d failed. "
"File: " "%s" ". Last error: %lu, err: %d", retries, destDir
, GetLastError(), rv)
2493 ("PerformReplaceRequest: destDir rename attempt %d failed. "UpdateLog::GetPrimaryLog().Printf ("PerformReplaceRequest: destDir rename attempt %d failed. "
"File: " "%s" ". Last error: %lu, err: %d", retries, destDir
, GetLastError(), rv)
2494 "File: " LOG_S ". Last error: %lu, err: %d",UpdateLog::GetPrimaryLog().Printf ("PerformReplaceRequest: destDir rename attempt %d failed. "
"File: " "%s" ". Last error: %lu, err: %d", retries, destDir
, GetLastError(), rv)
2495 retries, destDir, GetLastError(), rv))UpdateLog::GetPrimaryLog().Printf ("PerformReplaceRequest: destDir rename attempt %d failed. "
"File: " "%s" ". Last error: %lu, err: %d", retries, destDir
, GetLastError(), rv)
;
2496
2497 Sleep(100);
2498
2499 rv = rename_file(destDir, tmpDir, true);
2500 }
2501#endif
2502 if (rv) {
2503 // The status file will have 'pending' written to it so there is no value in
2504 // returning an error specific for this failure.
2505 LOG(("Moving destDir to tmpDir failed, err: %d", rv))UpdateLog::GetPrimaryLog().Printf ("Moving destDir to tmpDir failed, err: %d"
, rv)
;
2506 return rv;
2507 }
2508
2509 LOG(("Begin moving newDir (" LOG_S ") to destDir (" LOG_S ")", newDir,UpdateLog::GetPrimaryLog().Printf ("Begin moving newDir (" "%s"
") to destDir (" "%s" ")", newDir, destDir)
2510 destDir))UpdateLog::GetPrimaryLog().Printf ("Begin moving newDir (" "%s"
") to destDir (" "%s" ")", newDir, destDir)
;
2511 rv = rename_file(newDir, destDir, true);
2512#ifdef XP_MACOSX
2513 if (rv) {
2514 LOG(("Moving failed. Begin copying newDir (" LOG_S ") to destDir (" LOG_SUpdateLog::GetPrimaryLog().Printf ("Moving failed. Begin copying newDir ("
"%s" ") to destDir (" "%s" ")", newDir, destDir)
2515 ")",UpdateLog::GetPrimaryLog().Printf ("Moving failed. Begin copying newDir ("
"%s" ") to destDir (" "%s" ")", newDir, destDir)
2516 newDir, destDir))UpdateLog::GetPrimaryLog().Printf ("Moving failed. Begin copying newDir ("
"%s" ") to destDir (" "%s" ")", newDir, destDir)
;
2517 copy_recursive_skiplist<0> skiplist;
2518 rv = ensure_copy_recursive(newDir, destDir, skiplist);
2519 }
2520#endif
2521 if (rv) {
2522 LOG(("Moving newDir to destDir failed, err: %d", rv))UpdateLog::GetPrimaryLog().Printf ("Moving newDir to destDir failed, err: %d"
, rv)
;
2523 LOG(("Now, try to move tmpDir back to destDir"))UpdateLog::GetPrimaryLog().Printf ("Now, try to move tmpDir back to destDir"
)
;
2524 ensure_remove_recursive(destDir);
2525 int rv2 = rename_file(tmpDir, destDir, true);
2526 if (rv2) {
2527 LOG(("Moving tmpDir back to destDir failed, err: %d", rv2))UpdateLog::GetPrimaryLog().Printf ("Moving tmpDir back to destDir failed, err: %d"
, rv2)
;
2528 }
2529 // The status file will be have 'pending' written to it so there is no value
2530 // in returning an error specific for this failure.
2531 return rv;
2532 }
2533
2534#if !defined(XP_WIN) && !defined(XP_MACOSX)
2535 // Platforms that have their updates directory in the installation directory
2536 // need to have the last-update.log and backup-update.log files moved from the
2537 // old installation directory to the new installation directory.
2538 NS_tchar tmpLog[MAXPATHLEN4096];
2539 NS_tsnprintfsnprintf(tmpLog, sizeof(tmpLog) / sizeof(tmpLog[0]),
2540 NS_T("%s/updates/last-update.log")"%s/updates/last-update.log", tmpDir);
2541 if (!NS_taccessaccess(tmpLog, F_OK0)) {
2542 NS_tchar destLog[MAXPATHLEN4096];
2543 NS_tsnprintfsnprintf(destLog, sizeof(destLog) / sizeof(destLog[0]),
2544 NS_T("%s/updates/last-update.log")"%s/updates/last-update.log", destDir);
2545 if (NS_tremoveremove(destLog) && errno(*__errno_location ()) != ENOENT2) {
2546 LOG(("non-fatal error removing log file: " LOG_S ", err: %d", destLog,UpdateLog::GetPrimaryLog().Printf ("non-fatal error removing log file: "
"%s" ", err: %d", destLog, (*__errno_location ()))
2547 errno))UpdateLog::GetPrimaryLog().Printf ("non-fatal error removing log file: "
"%s" ", err: %d", destLog, (*__errno_location ()))
;
2548 }
2549 NS_trenamerename(tmpLog, destLog);
2550 }
2551#endif
2552
2553 LOG(("Now, remove the tmpDir"))UpdateLog::GetPrimaryLog().Printf ("Now, remove the tmpDir");
2554 rv = ensure_remove_recursive(tmpDir, true);
2555 if (rv) {
2556 LOG(("Removing tmpDir failed, err: %d", rv))UpdateLog::GetPrimaryLog().Printf ("Removing tmpDir failed, err: %d"
, rv)
;
2557#ifdef XP_WIN
2558 NS_tchar deleteDir[MAXPATHLEN4096];
2559 NS_tsnprintfsnprintf(deleteDir, sizeof(deleteDir) / sizeof(deleteDir[0]),
2560 NS_T("%s\\%s")"%s\\%s", destDir, DELETE_DIR);
2561 // Attempt to remove the tobedeleted directory and then recreate it if it
2562 // was successfully removed.
2563 _wrmdir(deleteDir);
2564 if (NS_taccessaccess(deleteDir, F_OK0)) {
2565 NS_tmkdirmkdir(deleteDir, 0755);
2566 }
2567 remove_recursive_on_reboot(tmpDir, deleteDir);
2568#endif
2569 }
2570
2571#ifdef XP_MACOSX
2572 // On OS X, we we need to remove the staging directory after its Contents
2573 // directory has been moved.
2574 NS_tchar updatedAppDir[MAXPATHLEN4096];
2575 NS_tsnprintfsnprintf(updatedAppDir, sizeof(updatedAppDir) / sizeof(updatedAppDir[0]),
2576 NS_T("%s/Updated.app")"%s/Updated.app", gPatchDirPath);
2577 ensure_remove_recursive(updatedAppDir);
2578#endif
2579
2580 gSucceeded = true;
2581
2582 return 0;
2583}
2584
2585#if defined(XP_WIN) && defined(MOZ_MAINTENANCE_SERVICE)
2586static void WaitForServiceFinishThread(void* param) {
2587 // We wait at most 10 minutes, we already waited 5 seconds previously
2588 // before deciding to show this UI.
2589 WaitForServiceStop(SVC_NAME, 595);
2590 QuitProgressUI();
2591}
2592#endif
2593
2594#ifdef MOZ_VERIFY_MAR_SIGNATURE1
2595/**
2596 * This function reads in the ACCEPTED_MAR_CHANNEL_IDS from update-settings.ini
2597 *
2598 * @param path The path to the ini file that is to be read
2599 * @param results A pointer to the location to store the read strings
2600 * @return OK on success
2601 */
2602static int ReadMARChannelIDs(const NS_tchar* path,
2603 MARChannelStringTable* results) {
2604 const unsigned int kNumStrings = 1;
2605 const char* kUpdaterKeys = "ACCEPTED_MAR_CHANNEL_IDS\0";
2606 int result = ReadStrings(path, kUpdaterKeys, kNumStrings,
2607 &results->MARChannelID, "Settings");
2608
2609 return result;
2610}
2611#endif
2612
2613static int GetUpdateFileName(NS_tchar* fileName, int maxChars) {
2614 NS_tsnprintfsnprintf(fileName, maxChars, NS_T("%s/update.mar")"%s/update.mar", gPatchDirPath);
2615 return OK0;
2616}
2617
2618static void UpdateThreadFunc(void* param) {
2619 // open ZIP archive and process...
2620 int rv;
2621 if (sReplaceRequest) {
1
Assuming 'sReplaceRequest' is false
2
Taking false branch
2622 rv = ProcessReplaceRequest();
2623 } else {
2624 NS_tchar dataFile[MAXPATHLEN4096];
2625 rv = GetUpdateFileName(dataFile, sizeof(dataFile) / sizeof(dataFile[0]));
2626 if (rv
2.1
'rv' is equal to OK
== OK0) {
3
Taking true branch
2627 rv = gArchiveReader.Open(dataFile);
2628 }
2629
2630#ifdef MOZ_VERIFY_MAR_SIGNATURE1
2631 if (rv == OK0) {
4
Assuming 'rv' is equal to OK
5
Taking true branch
2632 rv = gArchiveReader.VerifySignature();
2633 }
2634
2635 if (rv == OK0) {
6
Assuming 'rv' is equal to OK
7
Taking true branch
2636 NS_tchar updateSettingsPath[MAXPATHLEN4096];
2637 NS_tsnprintfsnprintf(updateSettingsPath,
2638 sizeof(updateSettingsPath) / sizeof(updateSettingsPath[0]),
2639# ifdef XP_MACOSX
2640 NS_T("%s/Contents/Resources/update-settings.ini")"%s/Contents/Resources/update-settings.ini",
2641# else
2642 NS_T("%s/update-settings.ini")"%s/update-settings.ini",
2643# endif
2644 gInstallDirPath);
2645 MARChannelStringTable MARStrings;
2646 if (ReadMARChannelIDs(updateSettingsPath, &MARStrings) != OK0) {
8
Assuming the condition is false
9
Taking false branch
2647 rv = UPDATE_SETTINGS_FILE_CHANNEL38;
2648 } else {
2649 rv = gArchiveReader.VerifyProductInformation(
2650 MARStrings.MARChannelID.get(), MOZ_APP_VERSION"120.0a1");
2651 }
2652 }
2653#endif
2654
2655 if (rv == OK0 && sStagedUpdate) {
10
Assuming 'rv' is equal to OK
11
Assuming 'sStagedUpdate' is false
12
Taking false branch
2656#ifdef TEST_UPDATER
2657 // The MOZ_TEST_SKIP_UPDATE_STAGE environment variable prevents copying
2658 // the files in dist/bin in the test updater when staging an update since
2659 // this can cause tests to timeout.
2660 if (EnvHasValue("MOZ_TEST_SKIP_UPDATE_STAGE")) {
2661 rv = OK0;
2662 } else if (EnvHasValue("MOZ_TEST_SLOW_SKIP_UPDATE_STAGE")) {
2663 // The following is to simulate staging so the UI tests have time to
2664 // show that the update is being staged.
2665 NS_tchar continueFilePath[MAXPATHLEN4096] = {NS_T('\0')'\0'};
2666 NS_tsnprintfsnprintf(continueFilePath,
2667 sizeof(continueFilePath) / sizeof(continueFilePath[0]),
2668 NS_T("%s/continueStaging")"%s/continueStaging", gInstallDirPath);
2669 // Use 300 retries for staging requests to lessen the likelihood of
2670 // tests intermittently failing on verify tasks due to launching the
2671 // updater. The total time to wait with the default interval of 100 ms
2672 // is approximately 30 seconds. The total time for tests is longer to
2673 // account for the extra time it takes for the updater to launch.
2674 const int max_retries = 300;
2675 int retries = 0;
2676 while (retries++ < max_retries) {
2677# ifdef XP_WIN
2678 Sleep(100);
2679# else
2680 usleep(100000);
2681# endif
2682 // Continue after the continue file exists and is removed.
2683 if (!NS_tremoveremove(continueFilePath)) {
2684 break;
2685 }
2686 }
2687 rv = OK0;
2688 } else {
2689 rv = CopyInstallDirToDestDir();
2690 }
2691#else
2692 rv = CopyInstallDirToDestDir();
2693#endif
2694 }
2695
2696 if (rv
12.1
'rv' is equal to OK
== OK0) {
13
Taking true branch
2697 rv = DoUpdate();
14
Calling 'DoUpdate'
2698 gArchiveReader.Close();
2699 NS_tchar updatingDir[MAXPATHLEN4096];
2700 NS_tsnprintfsnprintf(updatingDir, sizeof(updatingDir) / sizeof(updatingDir[0]),
2701 NS_T("%s/updating")"%s/updating", gWorkingDirPath);
2702 ensure_remove_recursive(updatingDir);
2703 }
2704 }
2705
2706 if (rv && (sReplaceRequest || sStagedUpdate)) {
2707 ensure_remove_recursive(gWorkingDirPath);
2708 // When attempting to replace the application, we should fall back
2709 // to non-staged updates in case of a failure. We do this by
2710 // setting the status to pending, exiting the updater, and
2711 // launching the callback application. The callback application's
2712 // startup path will see the pending status, and will start the
2713 // updater application again in order to apply the update without
2714 // staging.
2715 if (sReplaceRequest) {
2716 WriteStatusFile(sUsingService ? "pending-service" : "pending");
2717 } else {
2718 WriteStatusFile(rv);
2719 }
2720 LOG(("failed: %d", rv))UpdateLog::GetPrimaryLog().Printf ("failed: %d", rv);
2721#ifdef TEST_UPDATER
2722 // Some tests need to use --test-process-updates again.
2723 putenv(const_cast<char*>("MOZ_TEST_PROCESS_UPDATES="));
2724#endif
2725 } else {
2726#ifdef TEST_UPDATER
2727 const char* forceErrorCodeString = getenv("MOZ_FORCE_ERROR_CODE");
2728 if (forceErrorCodeString && *forceErrorCodeString) {
2729 rv = atoi(forceErrorCodeString);
2730 }
2731#endif
2732 if (rv) {
2733 LOG(("failed: %d", rv))UpdateLog::GetPrimaryLog().Printf ("failed: %d", rv);
2734 } else {
2735#ifdef XP_MACOSX
2736 // If the update was successful we need to update the timestamp on the
2737 // top-level Mac OS X bundle directory so that Mac OS X's Launch Services
2738 // picks up any major changes when the bundle is updated.
2739 if (!sStagedUpdate && utimes(gInstallDirPath, nullptr) != 0) {
2740 LOG(("Couldn't set access/modification time on application bundle."))UpdateLog::GetPrimaryLog().Printf ("Couldn't set access/modification time on application bundle."
)
;
2741 }
2742#endif
2743 LOG(("succeeded"))UpdateLog::GetPrimaryLog().Printf ("succeeded");
2744 }
2745 WriteStatusFile(rv);
2746 }
2747
2748 LOG(("calling QuitProgressUI"))UpdateLog::GetPrimaryLog().Printf ("calling QuitProgressUI");
2749 QuitProgressUI();
2750}
2751
2752#ifdef XP_MACOSX
2753static void ServeElevatedUpdateThreadFunc(void* param) {
2754 UpdateServerThreadArgs* threadArgs = (UpdateServerThreadArgs*)param;
2755 gSucceeded = ServeElevatedUpdate(threadArgs->argc, threadArgs->argv);
2756 if (!gSucceeded) {
2757 WriteStatusFile(ELEVATION_CANCELED9);
2758 }
2759 QuitProgressUI();
2760}
2761
2762void freeArguments(int argc, char** argv) {
2763 for (int i = 0; i < argc; i++) {
2764 free(argv[i]);
2765 }
2766 free(argv);
2767}
2768#endif
2769
2770int LaunchCallbackAndPostProcessApps(int argc, NS_tchar** argv,
2771 int callbackIndex
2772#ifdef XP_WIN
2773 ,
2774 const WCHAR* elevatedLockFilePath,
2775 HANDLE updateLockFileHandle
2776#elif XP_MACOSX
2777 ,
2778 bool isElevated,
2779 mozilla::UniquePtr<UmaskContext>
2780 umaskContext
2781#endif
2782) {
2783#ifdef XP_MACOSX
2784 umaskContext.reset();
2785#endif
2786
2787 if (argc > callbackIndex) {
2788#if defined(XP_WIN)
2789 if (gSucceeded) {
2790 if (!LaunchWinPostProcess(gInstallDirPath, gPatchDirPath)) {
2791 fprintf(stderrstderr, "The post update process was not launched");
2792 }
2793
2794# ifdef MOZ_MAINTENANCE_SERVICE
2795 // The service update will only be executed if it is already installed.
2796 // For first time installs of the service, the install will happen from
2797 // the PostUpdate process. We do the service update process here
2798 // because it's possible we are updating with updater.exe without the
2799 // service if the service failed to apply the update. We want to update
2800 // the service to a newer version in that case. If we are not running
2801 // through the service, then MOZ_USING_SERVICE will not exist.
2802 if (!sUsingService) {
2803 StartServiceUpdate(gInstallDirPath);
2804 }
2805# endif
2806 }
2807 EXIT_WHEN_ELEVATED(elevatedLockFilePath, updateLockFileHandle, 0);
2808#elif XP_MACOSX
2809 if (!isElevated) {
2810 if (gSucceeded) {
2811 LaunchMacPostProcess(gInstallDirPath);
2812 }
2813#endif
2814
2815 LaunchCallbackApp(argv[5], argc - callbackIndex, argv + callbackIndex,
2816 sUsingService);
2817#ifdef XP_MACOSX
2818 } // if (!isElevated)
2819#endif /* XP_MACOSX */
2820}
2821return 0;
2822}
2823
2824bool ShouldRunSilently(int argc, NS_tchar** argv) {
2825#ifdef MOZ_BACKGROUNDTASKS1
2826 // If the callback has a --backgroundtask switch, consider it a background
2827 // task. The CheckArg semantics aren't reproduced in full here,
2828 // there's e.g. no check for a parameter and no case-insensitive comparison.
2829 for (int i = 1; i < argc; ++i) {
2830 if (const auto option = mozilla::internal::ReadAsOption(argv[i])) {
2831 const NS_tchar* arg = option.value();
2832 if (NS_tstrcmpstrcmp(arg, NS_T("backgroundtask")"backgroundtask") == 0) {
2833 return true;
2834 }
2835 }
2836 }
2837#endif // MOZ_BACKGROUNDTASKS
2838
2839#if defined(XP_WIN) || defined(XP_MACOSX)
2840 if (EnvHasValue("MOZ_APP_SILENT_START")) {
2841 return true;
2842 }
2843#endif
2844
2845 return false;
2846}
2847
2848int NS_mainmain(int argc, NS_tchar** argv) {
2849#ifdef MOZ_MAINTENANCE_SERVICE
2850 sUsingService = EnvHasValue("MOZ_USING_SERVICE");
2851 putenv(const_cast<char*>("MOZ_USING_SERVICE="));
2852#endif
2853
2854 // The callback is the remaining arguments starting at callbackIndex.
2855 // The argument specified by callbackIndex is the callback executable and the
2856 // argument prior to callbackIndex is the working directory.
2857 const int callbackIndex = 6;
2858
2859 // `isDMGInstall` is only ever true for macOS, but we are declaring it here
2860 // to avoid a ton of extra #ifdef's.
2861 bool isDMGInstall = false;
2862
2863#ifdef XP_MACOSX
2864 // We want to control file permissions explicitly, or else we could end up
2865 // corrupting installs for other users on the system. Accordingly, set the
2866 // umask to 0 for all file creations below and reset it on exit. See Bug
2867 // 1337007
2868 mozilla::UniquePtr<UmaskContext> umaskContext(new UmaskContext(0));
2869
2870 bool isElevated =
2871 strstr(argv[0], "/Library/PrivilegedHelperTools/org.mozilla.updater") !=
2872 0;
2873 if (isElevated) {
2874 if (!ObtainUpdaterArguments(&argc, &argv)) {
2875 // Won't actually get here because ObtainUpdaterArguments will terminate
2876 // the current process on failure.
2877 return 1;
2878 }
2879 }
2880
2881 if (argc == 4 && (strstr(argv[1], "-dmgInstall") != 0)) {
2882 isDMGInstall = true;
2883 if (isElevated) {
2884 PerformInstallationFromDMG(argc, argv);
2885 freeArguments(argc, argv);
2886 CleanupElevatedMacUpdate(true);
2887 return 0;
2888 }
2889 }
2890#endif
2891
2892 if (!isDMGInstall) {
2893 // Skip update-related code path for DMG installs.
2894
2895#if defined(MOZ_VERIFY_MAR_SIGNATURE1) && defined(MAR_NSS1)
2896 // If using NSS for signature verification, initialize NSS but minimize
2897 // the portion we depend on by avoiding all of the NSS databases.
2898 if (NSS_NoDB_Init(nullptr) != SECSuccess) {
2899 PRErrorCode error = PR_GetError();
2900 fprintf(stderrstderr, "Could not initialize NSS: %s (%d)",
2901 PR_ErrorToName(error), (int)error);
2902 _exit(1);
2903 }
2904#endif
2905
2906 // To process an update the updater command line must at a minimum have the
2907 // directory path containing the updater.mar file to process as the first
2908 // argument, the install directory as the second argument, and the directory
2909 // to apply the update to as the third argument. When the updater is
2910 // launched by another process the PID of the parent process should be
2911 // provided in the optional fourth argument and the updater will wait on the
2912 // parent process to exit if the value is non-zero and the process is
2913 // present. This is necessary due to not being able to update files that are
2914 // in use on Windows. The optional fifth argument is the callback's working
2915 // directory and the optional sixth argument is the callback path. The
2916 // callback is the application to launch after updating and it will be
2917 // launched when these arguments are provided whether the update was
2918 // successful or not. All remaining arguments are optional and are passed to
2919 // the callback when it is launched.
2920 if (argc < 4) {
2921 fprintf(stderrstderr,
2922 "Usage: updater patch-dir install-dir apply-to-dir [wait-pid "
2923 "[callback-working-dir callback-path args...]]\n");
2924#ifdef XP_MACOSX
2925 if (isElevated) {
2926 freeArguments(argc, argv);
2927 CleanupElevatedMacUpdate(true);
2928 }
2929#endif
2930 return 1;
2931 }
2932
2933#if defined(TEST_UPDATER) && defined(XP_WIN)
2934 // The tests use nsIProcess to launch the updater and it is simpler for the
2935 // tests to just set an environment variable and have the test updater set
2936 // the current working directory than it is to set the current working
2937 // directory in the test itself.
2938 if (EnvHasValue("CURWORKDIRPATH")) {
2939 const WCHAR* val = _wgetenv(L"CURWORKDIRPATH");
2940 NS_tchdirchdir(val);
2941 }
2942#endif
2943
2944 } // if (!isDMGInstall)
2945
2946 // The directory containing the update information.
2947 NS_tstrncpystrncpy(gPatchDirPath, argv[1], MAXPATHLEN4096);
2948 gPatchDirPath[MAXPATHLEN4096 - 1] = NS_T('\0')'\0';
2949
2950#ifdef XP_WIN
2951 NS_tchar elevatedLockFilePath[MAXPATHLEN4096] = {NS_T('\0')'\0'};
2952 NS_tsnprintfsnprintf(elevatedLockFilePath,
2953 sizeof(elevatedLockFilePath) / sizeof(elevatedLockFilePath[0]),
2954 NS_T("%s\\update_elevated.lock")"%s\\update_elevated.lock", gPatchDirPath);
2955 gUseSecureOutputPath =
2956 sUsingService || (NS_tremoveremove(elevatedLockFilePath) && errno(*__errno_location ()) != ENOENT2);
2957#endif
2958
2959 if (!isDMGInstall) {
2960 // This check is also performed in workmonitor.cpp since the maintenance
2961 // service can be called directly.
2962 if (!IsValidFullPath(argv[1])) {
2963 // Since the status file is written to the patch directory and the patch
2964 // directory is invalid don't write the status file.
2965 fprintf(stderrstderr,
2966 "The patch directory path is not valid for this "
2967 "application (" LOG_S"%s" ")\n",
2968 argv[1]);
2969#ifdef XP_MACOSX
2970 if (isElevated) {
2971 freeArguments(argc, argv);
2972 CleanupElevatedMacUpdate(true);
2973 }
2974#endif
2975 return 1;
2976 }
2977
2978 // This check is also performed in workmonitor.cpp since the maintenance
2979 // service can be called directly.
2980 if (!IsValidFullPath(argv[2])) {
2981 WriteStatusFile(INVALID_INSTALL_DIR_PATH_ERROR75);
2982 fprintf(stderrstderr,
2983 "The install directory path is not valid for this "
2984 "application (" LOG_S"%s" ")\n",
2985 argv[2]);
2986#ifdef XP_MACOSX
2987 if (isElevated) {
2988 freeArguments(argc, argv);
2989 CleanupElevatedMacUpdate(true);
2990 }
2991#endif
2992 return 1;
2993 }
2994
2995 } // if (!isDMGInstall)
2996
2997 // The directory we're going to update to.
2998 // We copy this string because we need to remove trailing slashes. The C++
2999 // standard says that it's always safe to write to strings pointed to by argv
3000 // elements, but I don't necessarily believe it.
3001 NS_tstrncpystrncpy(gInstallDirPath, argv[2], MAXPATHLEN4096);
3002 gInstallDirPath[MAXPATHLEN4096 - 1] = NS_T('\0')'\0';
3003 NS_tchar* slash = NS_tstrrchrstrrchr(gInstallDirPath, NS_SLASH'/');
3004 if (slash && !slash[1]) {
3005 *slash = NS_T('\0')'\0';
3006 }
3007
3008#ifdef XP_WIN
3009 bool useService = false;
3010 bool testOnlyFallbackKeyExists = false;
3011 // Prevent the updater from falling back from updating with the Maintenance
3012 // Service to updating without the Service. Used for Service tests.
3013 // This is set below via the MOZ_NO_SERVICE_FALLBACK environment variable.
3014 bool noServiceFallback = false;
3015 // Force the updater to use the Maintenance Service incorrectly, causing it
3016 // to fail. Used to test the mechanism that allows the updater to fall back
3017 // from using the Maintenance Service to updating without it.
3018 // This is set below via the MOZ_FORCE_SERVICE_FALLBACK environment variable.
3019 bool forceServiceFallback = false;
3020#endif
3021
3022 if (!isDMGInstall) {
3023#ifdef XP_WIN
3024 // We never want the service to be used unless we build with
3025 // the maintenance service.
3026# ifdef MOZ_MAINTENANCE_SERVICE
3027 useService = IsUpdateStatusPendingService();
3028# ifdef TEST_UPDATER
3029 noServiceFallback = EnvHasValue("MOZ_NO_SERVICE_FALLBACK");
3030 putenv(const_cast<char*>("MOZ_NO_SERVICE_FALLBACK="));
3031 forceServiceFallback = EnvHasValue("MOZ_FORCE_SERVICE_FALLBACK");
3032 putenv(const_cast<char*>("MOZ_FORCE_SERVICE_FALLBACK="));
3033 // Our tests run with a different apply directory for each test.
3034 // We use this registry key on our test machines to store the
3035 // allowed name/issuers.
3036 testOnlyFallbackKeyExists = DoesFallbackKeyExist();
3037# endif
3038# endif
3039
3040 // Remove everything except close window from the context menu
3041 {
3042 HKEY hkApp = nullptr;
3043 RegCreateKeyExW(HKEY_CURRENT_USER, L"Software\\Classes\\Applications", 0,
3044 nullptr, REG_OPTION_NON_VOLATILE, KEY_SET_VALUE, nullptr,
3045 &hkApp, nullptr);
3046 RegCloseKey(hkApp);
3047 if (RegCreateKeyExW(HKEY_CURRENT_USER,
3048 L"Software\\Classes\\Applications\\updater.exe", 0,
3049 nullptr, REG_OPTION_VOLATILE, KEY_SET_VALUE, nullptr,
3050 &hkApp, nullptr) == ERROR_SUCCESS) {
3051 RegSetValueExW(hkApp, L"IsHostApp", 0, REG_NONE, 0, 0);
3052 RegSetValueExW(hkApp, L"NoOpenWith", 0, REG_NONE, 0, 0);
3053 RegSetValueExW(hkApp, L"NoStartPage", 0, REG_NONE, 0, 0);
3054 RegCloseKey(hkApp);
3055 }
3056 }
3057#endif
3058
3059 } // if (!isDMGInstall)
3060
3061 // If there is a PID specified and it is not '0' then wait for the process to
3062 // exit.
3063 NS_tpidint pid = 0;
3064 if (argc > 4) {
3065 pid = NS_tatoiatoi(argv[4]);
3066 if (pid == -1) {
3067 // This is a signal from the parent process that the updater should stage
3068 // the update.
3069 sStagedUpdate = true;
3070 } else if (NS_tstrstrstrstr(argv[4], NS_T("/replace")"/replace")) {
3071 // We're processing a request to replace the application with a staged
3072 // update.
3073 sReplaceRequest = true;
3074 }
3075 }
3076
3077 if (!isDMGInstall) {
3078 // This check is also performed in workmonitor.cpp since the maintenance
3079 // service can be called directly.
3080 if (!IsValidFullPath(argv[3])) {
3081 WriteStatusFile(INVALID_WORKING_DIR_PATH_ERROR76);
3082 fprintf(stderrstderr,
3083 "The working directory path is not valid for this "
3084 "application (" LOG_S"%s" ")\n",
3085 argv[3]);
3086#ifdef XP_MACOSX
3087 if (isElevated) {
3088 freeArguments(argc, argv);
3089 CleanupElevatedMacUpdate(true);
3090 }
3091#endif
3092 return 1;
3093 }
3094 // The directory we're going to update to.
3095 // We copy this string because we need to remove trailing slashes. The C++
3096 // standard says that it's always safe to write to strings pointed to by
3097 // argv elements, but I don't necessarily believe it.
3098 NS_tstrncpystrncpy(gWorkingDirPath, argv[3], MAXPATHLEN4096);
3099 gWorkingDirPath[MAXPATHLEN4096 - 1] = NS_T('\0')'\0';
3100 slash = NS_tstrrchrstrrchr(gWorkingDirPath, NS_SLASH'/');
3101 if (slash && !slash[1]) {
3102 *slash = NS_T('\0')'\0';
3103 }
3104
3105 if (argc > callbackIndex) {
3106 if (!IsValidFullPath(argv[callbackIndex])) {
3107 WriteStatusFile(INVALID_CALLBACK_PATH_ERROR77);
3108 fprintf(stderrstderr,
3109 "The callback file path is not valid for this "
3110 "application (" LOG_S"%s" ")\n",
3111 argv[callbackIndex]);
3112#ifdef XP_MACOSX
3113 if (isElevated) {
3114 freeArguments(argc, argv);
3115 CleanupElevatedMacUpdate(true);
3116 }
3117#endif
3118 return 1;
3119 }
3120
3121 size_t len = NS_tstrlenstrlen(gInstallDirPath);
3122 NS_tchar callbackInstallDir[MAXPATHLEN4096] = {NS_T('\0')'\0'};
3123 NS_tstrncpystrncpy(callbackInstallDir, argv[callbackIndex], len);
3124 if (NS_tstrcmpstrcmp(gInstallDirPath, callbackInstallDir) != 0) {
3125 WriteStatusFile(INVALID_CALLBACK_DIR_ERROR78);
3126 fprintf(stderrstderr,
3127 "The callback file must be located in the "
3128 "installation directory (" LOG_S"%s" ")\n",
3129 argv[callbackIndex]);
3130#ifdef XP_MACOSX
3131 if (isElevated) {
3132 freeArguments(argc, argv);
3133 CleanupElevatedMacUpdate(true);
3134 }
3135#endif
3136 return 1;
3137 }
3138
3139 sUpdateSilently =
3140 ShouldRunSilently(argc - callbackIndex, argv + callbackIndex);
3141 }
3142
3143 } // if (!isDMGInstall)
3144
3145 if (!sUpdateSilently && !isDMGInstall
3146#ifdef XP_MACOSX
3147 && !isElevated
3148#endif
3149 ) {
3150 InitProgressUI(&argc, &argv);
3151 }
3152
3153#ifdef XP_MACOSX
3154 if (!isElevated && (!IsRecursivelyWritable(argv[2]) || isDMGInstall)) {
3155 // If the app directory isn't recursively writeable or if this is a DMG
3156 // install, an elevated helper process is required.
3157 if (sUpdateSilently) {
3158 // An elevated update always requires an elevation dialog, so if we are
3159 // updating silently, don't do an elevated update.
3160 // This means that we cannot successfully perform silent updates from
3161 // non-admin accounts on a Mac.
3162 // It also means that we cannot silently perform the first update by an
3163 // admin who was not the installing user. Once the first update has been
3164 // installed, the permissions of the installation directory should be
3165 // changed such that we don't need to elevate in the future.
3166 // Firefox shouldn't actually launch the updater at all in this case. This
3167 // is defense in depth.
3168 WriteStatusFile(SILENT_UPDATE_NEEDED_ELEVATION_ERROR105);
3169 fprintf(stderrstderr,
3170 "Skipping update to avoid elevation prompt from silent update.");
3171 } else {
3172 UpdateServerThreadArgs threadArgs;
3173 threadArgs.argc = argc;
3174 threadArgs.argv = const_cast<const NS_tchar**>(argv);
3175
3176 Thread t1;
3177 if (t1.Run(ServeElevatedUpdateThreadFunc, &threadArgs) == 0) {
3178 // Show an indeterminate progress bar while an elevated update is in
3179 // progress.
3180 if (!isDMGInstall) {
3181 ShowProgressUI(true);
3182 }
3183 }
3184 t1.Join();
3185 }
3186
3187 LaunchCallbackAndPostProcessApps(argc, argv, callbackIndex, false,
3188 std::move(umaskContext));
3189 return gSucceeded ? 0 : 1;
3190 }
3191#endif
3192
3193#ifdef XP_WIN
3194 HANDLE updateLockFileHandle = INVALID_HANDLE_VALUE;
3195#endif
3196
3197 if (!isDMGInstall) {
3198 NS_tchar logFilePath[MAXPATHLEN4096 + 1] = {L'\0'};
3199#ifdef XP_WIN
3200 if (gUseSecureOutputPath) {
3201 // Remove the secure output files so it is easier to determine when new
3202 // files are created in the unelevated updater.
3203 RemoveSecureOutputFiles(gPatchDirPath);
3204
3205 (void)GetSecureOutputFilePath(gPatchDirPath, L".log", logFilePath);
3206 } else {
3207 NS_tsnprintfsnprintf(logFilePath, sizeof(logFilePath) / sizeof(logFilePath[0]),
3208 NS_T("%s\\update.log")"%s\\update.log", gPatchDirPath);
3209 }
3210#else
3211 NS_tsnprintfsnprintf(logFilePath, sizeof(logFilePath) / sizeof(logFilePath[0]),
3212 NS_T("%s/update.log")"%s/update.log", gPatchDirPath);
3213#endif
3214 LogInit(logFilePath)UpdateLog::GetPrimaryLog().Init(logFilePath);
3215
3216 if (!WriteStatusFile("applying")) {
3217 LOG(("failed setting status to 'applying'"))UpdateLog::GetPrimaryLog().Printf ("failed setting status to 'applying'"
)
;
3218#ifdef XP_MACOSX
3219 if (isElevated) {
3220 freeArguments(argc, argv);
3221 CleanupElevatedMacUpdate(true);
3222 }
3223#endif
3224 output_finish();
3225 return 1;
3226 }
3227
3228 if (sStagedUpdate) {
3229 LOG(("Performing a staged update"))UpdateLog::GetPrimaryLog().Printf ("Performing a staged update"
)
;
3230 } else if (sReplaceRequest) {
3231 LOG(("Performing a replace request"))UpdateLog::GetPrimaryLog().Printf ("Performing a replace request"
)
;
3232 }
3233
3234 LOG(("PATCH DIRECTORY " LOG_S, gPatchDirPath))UpdateLog::GetPrimaryLog().Printf ("PATCH DIRECTORY " "%s", gPatchDirPath
)
;
3235 LOG(("INSTALLATION DIRECTORY " LOG_S, gInstallDirPath))UpdateLog::GetPrimaryLog().Printf ("INSTALLATION DIRECTORY " "%s"
, gInstallDirPath)
;
3236 LOG(("WORKING DIRECTORY " LOG_S, gWorkingDirPath))UpdateLog::GetPrimaryLog().Printf ("WORKING DIRECTORY " "%s",
gWorkingDirPath)
;
3237
3238#if defined(XP_WIN)
3239 // These checks are also performed in workmonitor.cpp since the maintenance
3240 // service can be called directly.
3241 if (_wcsnicmp(gWorkingDirPath, gInstallDirPath, MAX_PATH) != 0) {
3242 if (!sStagedUpdate && !sReplaceRequest) {
3243 WriteStatusFile(INVALID_APPLYTO_DIR_ERROR74);
3244 LOG(UpdateLog::GetPrimaryLog().Printf ("Installation directory and working directory must be the same "
"for non-staged updates. Exiting.")
3245 ("Installation directory and working directory must be the same "UpdateLog::GetPrimaryLog().Printf ("Installation directory and working directory must be the same "
"for non-staged updates. Exiting.")
3246 "for non-staged updates. Exiting."))UpdateLog::GetPrimaryLog().Printf ("Installation directory and working directory must be the same "
"for non-staged updates. Exiting.")
;
3247 output_finish();
3248 return 1;
3249 }
3250
3251 NS_tchar workingDirParent[MAX_PATH];
3252 NS_tsnprintfsnprintf(workingDirParent,
3253 sizeof(workingDirParent) / sizeof(workingDirParent[0]),
3254 NS_T("%s")"%s", gWorkingDirPath);
3255 if (!PathRemoveFileSpecW(workingDirParent)) {
3256 WriteStatusFile(REMOVE_FILE_SPEC_ERROR71);
3257 LOG(("Error calling PathRemoveFileSpecW: %lu", GetLastError()))UpdateLog::GetPrimaryLog().Printf ("Error calling PathRemoveFileSpecW: %lu"
, GetLastError())
;
3258 output_finish();
3259 return 1;
3260 }
3261
3262 if (_wcsnicmp(workingDirParent, gInstallDirPath, MAX_PATH) != 0) {
3263 WriteStatusFile(INVALID_APPLYTO_DIR_STAGED_ERROR72);
3264 LOG(UpdateLog::GetPrimaryLog().Printf ("The apply-to directory must be the same as or "
"a child of the installation directory! Exiting.")
3265 ("The apply-to directory must be the same as or "UpdateLog::GetPrimaryLog().Printf ("The apply-to directory must be the same as or "
"a child of the installation directory! Exiting.")
3266 "a child of the installation directory! Exiting."))UpdateLog::GetPrimaryLog().Printf ("The apply-to directory must be the same as or "
"a child of the installation directory! Exiting.")
;
3267 output_finish();
3268 return 1;
3269 }
3270 }
3271#endif
3272
3273#ifdef XP_WIN
3274 if (pid > 0) {
3275 HANDLE parent = OpenProcess(SYNCHRONIZE, false, (DWORD)pid);
3276 // May return nullptr if the parent process has already gone away.
3277 // Otherwise, wait for the parent process to exit before starting the
3278 // update.
3279 if (parent) {
3280 DWORD waitTime = PARENT_WAIT30000;
3281# ifdef TEST_UPDATER
3282 if (EnvHasValue("MOZ_TEST_SHORTER_WAIT_PID")) {
3283 // Use a shorter time to wait for the PID to exit for the test.
3284 waitTime = 100;
3285 }
3286# endif
3287 DWORD result = WaitForSingleObject(parent, waitTime);
3288 CloseHandle(parent);
3289 if (result != WAIT_OBJECT_0) {
3290 // Continue to update since the parent application sometimes doesn't
3291 // exit (see bug 1375242) so any fixes to the parent application will
3292 // be applied instead of leaving the client in a broken state.
3293 LOG(("The parent process didn't exit! Continuing with update."))UpdateLog::GetPrimaryLog().Printf ("The parent process didn't exit! Continuing with update."
)
;
3294 }
3295 }
3296 }
3297#endif
3298
3299#ifdef XP_WIN
3300 if (sReplaceRequest || sStagedUpdate) {
3301 // On Windows, when performing a stage or replace request the current
3302 // working directory for the process must be changed so it isn't locked.
3303 NS_tchar sysDir[MAX_PATH + 1] = {L'\0'};
3304 if (GetSystemDirectoryW(sysDir, MAX_PATH + 1)) {
3305 NS_tchdirchdir(sysDir);
3306 }
3307 }
3308
3309 // lastFallbackError keeps track of the last error for the service not being
3310 // used, in case of an error when fallback is not enabled we write the
3311 // error to the update.status file.
3312 // When fallback is disabled (MOZ_NO_SERVICE_FALLBACK does not exist) then
3313 // we will instead fallback to not using the service and display a UAC
3314 // prompt.
3315 int lastFallbackError = FALLBACKKEY_UNKNOWN_ERROR100;
3316
3317 // Check whether a second instance of the updater should be launched by the
3318 // maintenance service or with the 'runas' verb when write access is denied
3319 // to the installation directory.
3320 if (!sUsingService &&
3321 (argc > callbackIndex || sStagedUpdate || sReplaceRequest)) {
3322 NS_tchar updateLockFilePath[MAXPATHLEN4096];
3323 if (sStagedUpdate) {
3324 // When staging an update, the lock file is:
3325 // <install_dir>\updated.update_in_progress.lock
3326 NS_tsnprintfsnprintf(updateLockFilePath,
3327 sizeof(updateLockFilePath) / sizeof(updateLockFilePath[0]),
3328 NS_T("%s/updated.update_in_progress.lock")"%s/updated.update_in_progress.lock",
3329 gInstallDirPath);
3330 } else if (sReplaceRequest) {
3331 // When processing a replace request, the lock file is:
3332 // <install_dir>\..\moz_update_in_progress.lock
3333 NS_tchar installDir[MAXPATHLEN4096];
3334 NS_tstrcpystrcpy(installDir, gInstallDirPath);
3335 NS_tchar* slash = (NS_tchar*)NS_tstrrchrstrrchr(installDir, NS_SLASH'/');
3336 *slash = NS_T('\0')'\0';
3337 NS_tsnprintfsnprintf(updateLockFilePath,
3338 sizeof(updateLockFilePath) / sizeof(updateLockFilePath[0]),
3339 NS_T("%s\\moz_update_in_progress.lock")"%s\\moz_update_in_progress.lock", installDir);
3340 } else {
3341 // In the non-staging update case, the lock file is:
3342 // <install_dir>\<app_name>.exe.update_in_progress.lock
3343 NS_tsnprintfsnprintf(updateLockFilePath,
3344 sizeof(updateLockFilePath) / sizeof(updateLockFilePath[0]),
3345 NS_T("%s.update_in_progress.lock")"%s.update_in_progress.lock", argv[callbackIndex]);
3346 }
3347
3348 // The update_in_progress.lock file should only exist during an update. In
3349 // case it exists attempt to remove it and exit if that fails to prevent
3350 // simultaneous updates occurring.
3351 if (NS_tremoveremove(updateLockFilePath) && errno(*__errno_location ()) != ENOENT2) {
3352 // Try to fall back to the old way of doing updates if a staged
3353 // update fails.
3354 if (sReplaceRequest) {
3355 // Note that this could fail, but if it does, there isn't too much we
3356 // can do in order to recover anyways.
3357 WriteStatusFile("pending");
3358 } else if (sStagedUpdate) {
3359 WriteStatusFile(DELETE_ERROR_STAGING_LOCK_FILE44);
3360 }
3361 LOG(("Update already in progress! Exiting"))UpdateLog::GetPrimaryLog().Printf ("Update already in progress! Exiting"
)
;
3362 output_finish();
3363 return 1;
3364 }
3365
3366 updateLockFileHandle =
3367 CreateFileW(updateLockFilePath, GENERIC_READ | GENERIC_WRITE, 0,
3368 nullptr, OPEN_ALWAYS, FILE_FLAG_DELETE_ON_CLOSE, nullptr);
3369
3370 // Even if a file has no sharing access, you can still get its attributes
3371 bool startedFromUnelevatedUpdater =
3372 GetFileAttributesW(elevatedLockFilePath) != INVALID_FILE_ATTRIBUTES;
3373
3374 // If we're running from the service, then we were started with the same
3375 // token as the service so the permissions are already dropped. If we're
3376 // running from an elevated updater that was started from an unelevated
3377 // updater, then we drop the permissions here. We do not drop the
3378 // permissions on the originally called updater because we use its token
3379 // to start the callback application.
3380 if (startedFromUnelevatedUpdater) {
3381 // Disable every privilege we don't need. Processes started using
3382 // CreateProcess will use the same token as this process.
3383 UACHelper::DisablePrivileges(nullptr);
3384 }
3385
3386 if (updateLockFileHandle == INVALID_HANDLE_VALUE ||
3387 (useService && testOnlyFallbackKeyExists &&
3388 (noServiceFallback || forceServiceFallback))) {
3389 HANDLE elevatedFileHandle;
3390 if (NS_tremoveremove(elevatedLockFilePath) && errno(*__errno_location ()) != ENOENT2) {
3391 LOG(("Unable to create elevated lock file! Exiting"))UpdateLog::GetPrimaryLog().Printf ("Unable to create elevated lock file! Exiting"
)
;
3392 output_finish();
3393 return 1;
3394 }
3395
3396 elevatedFileHandle = CreateFileW(
3397 elevatedLockFilePath, GENERIC_READ | GENERIC_WRITE, 0, nullptr,
3398 OPEN_ALWAYS, FILE_FLAG_DELETE_ON_CLOSE, nullptr);
3399 if (elevatedFileHandle == INVALID_HANDLE_VALUE) {
3400 LOG(("Unable to create elevated lock file! Exiting"))UpdateLog::GetPrimaryLog().Printf ("Unable to create elevated lock file! Exiting"
)
;
3401 output_finish();
3402 return 1;
3403 }
3404
3405 auto cmdLine = mozilla::MakeCommandLine(argc - 1, argv + 1);
3406 if (!cmdLine) {
3407 CloseHandle(elevatedFileHandle);
3408 output_finish();
3409 return 1;
3410 }
3411
3412# ifdef MOZ_MAINTENANCE_SERVICE
3413// Only invoke the service for installations in Program Files.
3414// This check is duplicated in workmonitor.cpp because the service can
3415// be invoked directly without going through the updater.
3416# ifndef TEST_UPDATER
3417 if (useService) {
3418 useService = IsProgramFilesPath(gInstallDirPath);
3419 }
3420# endif
3421
3422 // Make sure the path to the updater to use for the update is on local.
3423 // We do this check to make sure that file locking is available for
3424 // race condition security checks.
3425 if (useService) {
3426 BOOL isLocal = FALSE;
3427 useService = IsLocalFile(argv[0], isLocal) && isLocal;
3428 }
3429
3430 // If we have unprompted elevation we should NOT use the service
3431 // for the update. Service updates happen with the SYSTEM account
3432 // which has more privs than we need to update with.
3433 // Windows 8 provides a user interface so users can configure this
3434 // behavior and it can be configured in the registry in all Windows
3435 // versions that support UAC.
3436 if (useService) {
3437 BOOL unpromptedElevation;
3438 if (IsUnpromptedElevation(unpromptedElevation)) {
3439 useService = !unpromptedElevation;
3440 }
3441 }
3442
3443 // Make sure the service registry entries for the instsallation path
3444 // are available. If not don't use the service.
3445 if (useService) {
3446 WCHAR maintenanceServiceKey[MAX_PATH + 1];
3447 if (CalculateRegistryPathFromFilePath(gInstallDirPath,
3448 maintenanceServiceKey)) {
3449 HKEY baseKey = nullptr;
3450 if (RegOpenKeyExW(HKEY_LOCAL_MACHINE, maintenanceServiceKey, 0,
3451 KEY_READ | KEY_WOW64_64KEY,
3452 &baseKey) == ERROR_SUCCESS) {
3453 RegCloseKey(baseKey);
3454 } else {
3455# ifdef TEST_UPDATER
3456 useService = testOnlyFallbackKeyExists;
3457# endif
3458 if (!useService) {
3459 lastFallbackError = FALLBACKKEY_NOKEY_ERROR102;
3460 }
3461 }
3462 } else {
3463 useService = false;
3464 lastFallbackError = FALLBACKKEY_REGPATH_ERROR101;
3465 }
3466 }
3467
3468 // Originally we used to write "pending" to update.status before
3469 // launching the service command. This is no longer needed now
3470 // since the service command is launched from updater.exe. If anything
3471 // fails in between, we can fall back to using the normal update process
3472 // on our own.
3473
3474 // If we still want to use the service try to launch the service
3475 // comamnd for the update.
3476 if (useService) {
3477 // Get the secure ID before trying to update so it is possible to
3478 // determine if the updater or the maintenance service has created a
3479 // new one.
3480 char uuidStringBefore[UUID_LEN] = {'\0'};
3481 bool checkID = GetSecureID(uuidStringBefore);
3482 // Write a catchall service failure status in case it fails without
3483 // changing the status.
3484 WriteStatusFile(SERVICE_UPDATE_STATUS_UNCHANGED58);
3485
3486 int serviceArgc = argc;
3487 if (forceServiceFallback && serviceArgc > 2) {
3488 // To force the service to fail, we can just pass it too few
3489 // arguments. However, we don't want to pass it no arguments,
3490 // because then it won't have enough information to write out the
3491 // update status file telling us that it failed.
3492 serviceArgc = 2;
3493 }
3494
3495 // If the update couldn't be started, then set useService to false so
3496 // we do the update the old way.
3497 DWORD ret =
3498 LaunchServiceSoftwareUpdateCommand(serviceArgc, (LPCWSTR*)argv);
3499 useService = (ret == ERROR_SUCCESS);
3500 // If the command was launched then wait for the service to be done.
3501 if (useService) {
3502 bool showProgressUI = false;
3503 // Never show the progress UI when staging updates or in a
3504 // background task.
3505 if (!sStagedUpdate && !sUpdateSilently) {
3506 // We need to call this separately instead of allowing
3507 // ShowProgressUI to initialize the strings because the service
3508 // will move the ini file out of the way when running updater.
3509 showProgressUI = !InitProgressUIStrings();
3510 }
3511
3512 // Wait for the service to stop for 5 seconds. If the service
3513 // has still not stopped then show an indeterminate progress bar.
3514 DWORD lastState = WaitForServiceStop(SVC_NAME, 5);
3515 if (lastState != SERVICE_STOPPED) {
3516 Thread t1;
3517 if (t1.Run(WaitForServiceFinishThread, nullptr) == 0 &&
3518 showProgressUI) {
3519 ShowProgressUI(true, false);
3520 }
3521 t1.Join();
3522 }
3523
3524 lastState = WaitForServiceStop(SVC_NAME, 1);
3525 if (lastState != SERVICE_STOPPED) {
3526 // If the service doesn't stop after 10 minutes there is
3527 // something seriously wrong.
3528 lastFallbackError = FALLBACKKEY_SERVICE_NO_STOP_ERROR103;
3529 useService = false;
3530 } else {
3531 // Copy the secure output files if the secure ID has changed.
3532 gCopyOutputFiles = true;
3533 char uuidStringAfter[UUID_LEN] = {'\0'};
3534 if (checkID && GetSecureID(uuidStringAfter) &&
3535 strncmp(uuidStringBefore, uuidStringAfter,
3536 sizeof(uuidStringBefore)) == 0) {
3537 LOG(UpdateLog::GetPrimaryLog().Printf ("The secure ID hasn't changed after launching the updater "
"using the service")
3538 ("The secure ID hasn't changed after launching the updater "UpdateLog::GetPrimaryLog().Printf ("The secure ID hasn't changed after launching the updater "
"using the service")
3539 "using the service"))UpdateLog::GetPrimaryLog().Printf ("The secure ID hasn't changed after launching the updater "
"using the service")
;
3540 gCopyOutputFiles = false;
3541 }
3542 if (gCopyOutputFiles && !sStagedUpdate && !noServiceFallback) {
3543 // If the Maintenance Service fails for a Service-specific
3544 // reason, we ought to fall back to attempting to update
3545 // without the Service.
3546 // However, we need the secure output files to be able to be
3547 // check the error code, and we can't fall back when we are
3548 // staging, because we will need to show a UAC.
3549 bool updateFailed;
3550 mozilla::Maybe<int> maybeErrorCode;
3551 bool success =
3552 IsSecureUpdateStatusFailed(updateFailed, &maybeErrorCode);
3553 if (success && updateFailed && maybeErrorCode.isSome() &&
3554 IsServiceSpecificErrorCode(maybeErrorCode.value())) {
3555 useService = false;
3556 }
3557 }
3558 }
3559 } else {
3560 lastFallbackError = FALLBACKKEY_LAUNCH_ERROR104;
3561 }
3562 }
3563# endif
3564
3565 // If the service can't be used when staging an update, make sure that
3566 // the UAC prompt is not shown!
3567 if (!useService && sStagedUpdate) {
3568 if (updateLockFileHandle != INVALID_HANDLE_VALUE) {
3569 CloseHandle(updateLockFileHandle);
3570 }
3571 // Set an error so the failure is reported. This will be reset
3572 // to pending so the update can be applied during the next startup,
3573 // see bug 1552853.
3574 WriteStatusFile(UNEXPECTED_STAGING_ERROR43);
3575 LOG(UpdateLog::GetPrimaryLog().Printf ("Non-critical update staging error! Falling back to non-staged "
"updates and exiting")
3576 ("Non-critical update staging error! Falling back to non-staged "UpdateLog::GetPrimaryLog().Printf ("Non-critical update staging error! Falling back to non-staged "
"updates and exiting")
3577 "updates and exiting"))UpdateLog::GetPrimaryLog().Printf ("Non-critical update staging error! Falling back to non-staged "
"updates and exiting")
;
3578 output_finish();
3579 // We don't have a callback when staging so we can just exit.
3580 return 0;
3581 }
3582
3583 // If the service can't be used when in a background task, make sure
3584 // that the UAC prompt is not shown!
3585 if (!useService && sUpdateSilently) {
3586 if (updateLockFileHandle != INVALID_HANDLE_VALUE) {
3587 CloseHandle(updateLockFileHandle);
3588 }
3589 // Set an error so we don't get into an update loop when the callback
3590 // runs. This will be reset to pending by handleUpdateFailure in
3591 // UpdateService.jsm.
3592 WriteStatusFile(SILENT_UPDATE_NEEDED_ELEVATION_ERROR105);
3593 LOG(("Skipping update to avoid UAC prompt from background task."))UpdateLog::GetPrimaryLog().Printf ("Skipping update to avoid UAC prompt from background task."
)
;
3594 output_finish();
3595
3596 LaunchCallbackApp(argv[5], argc - callbackIndex, argv + callbackIndex,
3597 sUsingService);
3598 return 0;
3599 }
3600
3601 // If we didn't want to use the service at all, or if an update was
3602 // already happening, or launching the service command failed, then
3603 // launch the elevated updater.exe as we do without the service.
3604 // We don't launch the elevated updater in the case that we did have
3605 // write access all along because in that case the only reason we're
3606 // using the service is because we are testing.
3607 if (!useService && !noServiceFallback &&
3608 (updateLockFileHandle == INVALID_HANDLE_VALUE ||
3609 forceServiceFallback)) {
3610 // Get the secure ID before trying to update so it is possible to
3611 // determine if the updater has created a new one.
3612 char uuidStringBefore[UUID_LEN] = {'\0'};
3613 bool checkID = GetSecureID(uuidStringBefore);
3614 // Write a catchall failure status in case it fails without changing
3615 // the status.
3616 WriteStatusFile(UPDATE_STATUS_UNCHANGED79);
3617
3618 SHELLEXECUTEINFO sinfo;
3619 memset(&sinfo, 0, sizeof(SHELLEXECUTEINFO));
3620 sinfo.cbSize = sizeof(SHELLEXECUTEINFO);
3621 sinfo.fMask = SEE_MASK_FLAG_NO_UI | SEE_MASK_FLAG_DDEWAIT |
3622 SEE_MASK_NOCLOSEPROCESS;
3623 sinfo.hwnd = nullptr;
3624 sinfo.lpFile = argv[0];
3625 sinfo.lpParameters = cmdLine.get();
3626 if (forceServiceFallback) {
3627 // In testing, we don't actually want a UAC prompt. We should
3628 // already have the permissions such that we shouldn't need it.
3629 // And we don't have a good way of accepting the prompt in
3630 // automation.
3631 sinfo.lpVerb = L"open";
3632 // This handle is what lets the updater that we spawn below know
3633 // that it's the elevated updater. We are going to close it so that
3634 // it doesn't know that and will run un-elevated. Doing this make
3635 // this makes for an imperfect test of the service fallback
3636 // functionality because it changes how the (usually) elevated
3637 // updater runs. One of the effects of this is that the secure
3638 // output files will not be used. So that functionality won't really
3639 // be covered by testing. But we can't really have the updater run
3640 // elevated, because that would require a UAC, which we have no way
3641 // to deal with in automation.
3642 CloseHandle(elevatedFileHandle);
3643 // We need to let go of the update lock to let the un-elevated
3644 // updater we are about to spawn update.
3645 if (updateLockFileHandle != INVALID_HANDLE_VALUE) {
3646 CloseHandle(updateLockFileHandle);
3647 }
3648 } else {
3649 sinfo.lpVerb = L"runas";
3650 }
3651 sinfo.nShow = SW_SHOWNORMAL;
3652
3653 bool result = ShellExecuteEx(&sinfo);
3654
3655 if (result) {
3656 WaitForSingleObject(sinfo.hProcess, INFINITE);
3657 CloseHandle(sinfo.hProcess);
3658
3659 // Copy the secure output files if the secure ID has changed.
3660 gCopyOutputFiles = true;
3661 char uuidStringAfter[UUID_LEN] = {'\0'};
3662 if (checkID && GetSecureID(uuidStringAfter) &&
3663 strncmp(uuidStringBefore, uuidStringAfter,
3664 sizeof(uuidStringBefore)) == 0) {
3665 LOG(UpdateLog::GetPrimaryLog().Printf ("The secure ID hasn't changed after launching the updater "
"using runas")
3666 ("The secure ID hasn't changed after launching the updater "UpdateLog::GetPrimaryLog().Printf ("The secure ID hasn't changed after launching the updater "
"using runas")
3667 "using runas"))UpdateLog::GetPrimaryLog().Printf ("The secure ID hasn't changed after launching the updater "
"using runas")
;
3668 gCopyOutputFiles = false;
3669 }
3670 } else {
3671 // Don't copy the secure output files if the elevation request was
3672 // canceled since the status file written below is in the patch
3673 // directory. At this point it should already be set to false and
3674 // this is set here to make it clear that it should be false at this
3675 // point and to prevent future changes from regressing this code.
3676 gCopyOutputFiles = false;
3677 WriteStatusFile(ELEVATION_CANCELED9);
3678 }
3679 }
3680
3681 // If we started the elevated updater, and it finished, check the secure
3682 // update status file to make sure that it succeeded, and if it did we
3683 // need to launch the PostUpdate process in the unelevated updater which
3684 // is running in the current user's session. Note that we don't need to
3685 // do this when staging an update since the PostUpdate step runs during
3686 // the replace request.
3687 if (!sStagedUpdate) {
3688 bool updateStatusSucceeded = false;
3689 if (IsSecureUpdateStatusSucceeded(updateStatusSucceeded) &&
3690 updateStatusSucceeded) {
3691 if (!LaunchWinPostProcess(gInstallDirPath, gPatchDirPath)) {
3692 fprintf(stderrstderr,
3693 "The post update process which runs as the user"
3694 " for service update could not be launched.");
3695 }
3696 }
3697 }
3698
3699 CloseHandle(elevatedFileHandle);
3700
3701 if (updateLockFileHandle != INVALID_HANDLE_VALUE) {
3702 CloseHandle(updateLockFileHandle);
3703 }
3704
3705 if (!useService && noServiceFallback) {
3706 // When the service command was not launched at all.
3707 // We should only reach this code path because we had write access
3708 // all along to the directory and a fallback key existed, and we
3709 // have fallback disabled (MOZ_NO_SERVICE_FALLBACK env var exists).
3710 // We only currently use this env var from XPCShell tests.
3711 gCopyOutputFiles = false;
3712 WriteStatusFile(lastFallbackError);
3713 }
3714
3715 // The logging output needs to be finished before launching the callback
3716 // application so the update status file contains the value from the
3717 // secure directory used by the maintenance service and the elevated
3718 // updater.
3719 output_finish();
3720 if (argc > callbackIndex) {
3721 LaunchCallbackApp(argv[5], argc - callbackIndex, argv + callbackIndex,
3722 sUsingService);
3723 }
3724 return 0;
3725
3726 // This is the end of the code block for launching another instance of
3727 // the updater using either the maintenance service or with the 'runas'
3728 // verb when the updater doesn't have write access to the installation
3729 // directory.
3730 }
3731 // This is the end of the code block when the updater was not launched by
3732 // the service that checks whether the updater has write access to the
3733 // installation directory.
3734 }
3735 // If we made it this far this is the updater instance that will perform the
3736 // actual update and gCopyOutputFiles will be false (e.g. the default
3737 // value).
3738#endif
3739
3740 if (sStagedUpdate) {
3741#ifdef TEST_UPDATER
3742 // This allows testing that the correct UI after an update staging failure
3743 // that falls back to applying the update on startup. It is simulated due
3744 // to the difficulty of creating the conditions for this type of staging
3745 // failure.
3746 if (EnvHasValue("MOZ_TEST_STAGING_ERROR")) {
3747# ifdef XP_WIN
3748 if (updateLockFileHandle != INVALID_HANDLE_VALUE) {
3749 CloseHandle(updateLockFileHandle);
3750 }
3751# endif
3752 // WRITE_ERROR is one of the cases where the staging failure falls back
3753 // to applying the update on startup.
3754 WriteStatusFile(WRITE_ERROR7);
3755 output_finish();
3756 return 0;
3757 }
3758#endif
3759 // When staging updates, blow away the old installation directory and
3760 // create it from scratch.
3761 ensure_remove_recursive(gWorkingDirPath);
3762 }
3763 if (!sReplaceRequest) {
3764 // Try to create the destination directory if it doesn't exist
3765 int rv = NS_tmkdirmkdir(gWorkingDirPath, 0755);
3766 if (rv != OK0 && errno(*__errno_location ()) != EEXIST17) {
3767#ifdef XP_MACOSX
3768 if (isElevated) {
3769 freeArguments(argc, argv);
3770 CleanupElevatedMacUpdate(true);
3771 }
3772#endif
3773 output_finish();
3774 return 1;
3775 }
3776 }
3777
3778#ifdef XP_WIN
3779 NS_tchar applyDirLongPath[MAXPATHLEN4096];
3780 if (!GetLongPathNameW(
3781 gWorkingDirPath, applyDirLongPath,
3782 sizeof(applyDirLongPath) / sizeof(applyDirLongPath[0]))) {
3783 WriteStatusFile(WRITE_ERROR_APPLY_DIR_PATH65);
3784 LOG(("NS_main: unable to find apply to dir: " LOG_S, gWorkingDirPath))UpdateLog::GetPrimaryLog().Printf ("NS_main: unable to find apply to dir: "
"%s", gWorkingDirPath)
;
3785 output_finish();
3786 EXIT_WHEN_ELEVATED(elevatedLockFilePath, updateLockFileHandle, 1);
3787 if (argc > callbackIndex) {
3788 LaunchCallbackApp(argv[5], argc - callbackIndex, argv + callbackIndex,
3789 sUsingService);
3790 }
3791 return 1;
3792 }
3793
3794 HANDLE callbackFile = INVALID_HANDLE_VALUE;
3795 if (argc > callbackIndex) {
3796 // If the callback executable is specified it must exist for a successful
3797 // update. It is important we null out the whole buffer here because
3798 // later we make the assumption that the callback application is inside
3799 // the apply-to dir. If we don't have a fully null'ed out buffer it can
3800 // lead to stack corruption which causes crashes and other problems.
3801 NS_tchar callbackLongPath[MAXPATHLEN4096];
3802 ZeroMemory(callbackLongPath, sizeof(callbackLongPath));
3803 NS_tchar* targetPath = argv[callbackIndex];
3804 NS_tchar buffer[MAXPATHLEN4096 * 2] = {NS_T('\0')'\0'};
3805 size_t bufferLeft = MAXPATHLEN4096 * 2;
3806 if (sReplaceRequest) {
3807 // In case of replace requests, we should look for the callback file in
3808 // the destination directory.
3809 size_t commonPrefixLength =
3810 PathCommonPrefixW(argv[callbackIndex], gInstallDirPath, nullptr);
3811 NS_tchar* p = buffer;
3812 NS_tstrncpystrncpy(p, argv[callbackIndex], commonPrefixLength);
3813 p += commonPrefixLength;
3814 bufferLeft -= commonPrefixLength;
3815 NS_tstrncpystrncpy(p, gInstallDirPath + commonPrefixLength, bufferLeft);
3816
3817 size_t len = NS_tstrlenstrlen(gInstallDirPath + commonPrefixLength);
3818 p += len;
3819 bufferLeft -= len;
3820 *p = NS_T('\\')'\\';
3821 ++p;
3822 bufferLeft--;
3823 *p = NS_T('\0')'\0';
3824 NS_tchar installDir[MAXPATHLEN4096];
3825 NS_tstrcpystrcpy(installDir, gInstallDirPath);
3826 size_t callbackPrefixLength =
3827 PathCommonPrefixW(argv[callbackIndex], installDir, nullptr);
3828 NS_tstrncpystrncpy(p,
3829 argv[callbackIndex] +
3830 std::max(callbackPrefixLength, commonPrefixLength),
3831 bufferLeft);
3832 targetPath = buffer;
3833 }
3834 if (!GetLongPathNameW(
3835 targetPath, callbackLongPath,
3836 sizeof(callbackLongPath) / sizeof(callbackLongPath[0]))) {
3837 WriteStatusFile(WRITE_ERROR_CALLBACK_PATH66);
3838 LOG(("NS_main: unable to find callback file: " LOG_S, targetPath))UpdateLog::GetPrimaryLog().Printf ("NS_main: unable to find callback file: "
"%s", targetPath)
;
3839 output_finish();
3840 EXIT_WHEN_ELEVATED(elevatedLockFilePath, updateLockFileHandle, 1);
3841 if (argc > callbackIndex) {
3842 LaunchCallbackApp(argv[5], argc - callbackIndex, argv + callbackIndex,
3843 sUsingService);
3844 }
3845 return 1;
3846 }
3847
3848 // Doing this is only necessary when we're actually applying a patch.
3849 if (!sReplaceRequest) {
3850 int len = NS_tstrlenstrlen(applyDirLongPath);
3851 NS_tchar* s = callbackLongPath;
3852 NS_tchar* d = gCallbackRelPath;
3853 // advance to the apply to directory and advance past the trailing
3854 // backslash if present.
3855 s += len;
3856 if (*s == NS_T('\\')'\\') {
3857 ++s;
3858 }
3859
3860 // Copy the string and replace backslashes with forward slashes along
3861 // the way.
3862 do {
3863 if (*s == NS_T('\\')'\\') {
3864 *d = NS_T('/')'/';
3865 } else {
3866 *d = *s;
3867 }
3868 ++s;
3869 ++d;
3870 } while (*s);
3871 *d = NS_T('\0')'\0';
3872 ++d;
3873
3874 const size_t callbackBackupPathBufSize =
3875 sizeof(gCallbackBackupPath) / sizeof(gCallbackBackupPath[0]);
3876 const int callbackBackupPathLen =
3877 NS_tsnprintfsnprintf(gCallbackBackupPath, callbackBackupPathBufSize,
3878 NS_T("%s" CALLBACK_BACKUP_EXT)"%s" CALLBACK_BACKUP_EXT, argv[callbackIndex]);
3879
3880 if (callbackBackupPathLen < 0 ||
3881 callbackBackupPathLen >=
3882 static_cast<int>(callbackBackupPathBufSize)) {
3883 WriteStatusFile(USAGE_ERROR3);
3884 LOG(("NS_main: callback backup path truncated"))UpdateLog::GetPrimaryLog().Printf ("NS_main: callback backup path truncated"
)
;
3885 output_finish();
3886
3887 // Don't attempt to launch the callback when the callback path is
3888 // longer than expected.
3889 EXIT_WHEN_ELEVATED(elevatedLockFilePath, updateLockFileHandle, 1);
3890 return 1;
3891 }
3892
3893 // Make a copy of the callback executable so it can be read when
3894 // patching.
3895 if (!CopyFileW(argv[callbackIndex], gCallbackBackupPath, false)) {
3896 DWORD copyFileError = GetLastError();
3897 if (copyFileError == ERROR_ACCESS_DENIED) {
3898 WriteStatusFile(WRITE_ERROR_ACCESS_DENIED35);
3899 } else {
3900 WriteStatusFile(WRITE_ERROR_CALLBACK_APP37);
3901 }
3902 LOG(("NS_main: failed to copy callback file " LOG_SUpdateLog::GetPrimaryLog().Printf ("NS_main: failed to copy callback file "
"%s" " into place at " "%s", argv[callbackIndex], gCallbackBackupPath
)
3903 " into place at " LOG_S,UpdateLog::GetPrimaryLog().Printf ("NS_main: failed to copy callback file "
"%s" " into place at " "%s", argv[callbackIndex], gCallbackBackupPath
)
3904 argv[callbackIndex], gCallbackBackupPath))UpdateLog::GetPrimaryLog().Printf ("NS_main: failed to copy callback file "
"%s" " into place at " "%s", argv[callbackIndex], gCallbackBackupPath
)
;
3905 output_finish();
3906 EXIT_WHEN_ELEVATED(elevatedLockFilePath, updateLockFileHandle, 1);
3907 LaunchCallbackApp(argv[callbackIndex], argc - callbackIndex,
3908 argv + callbackIndex, sUsingService);
3909 return 1;
3910 }
3911
3912 // Since the process may be signaled as exited by WaitForSingleObject
3913 // before the release of the executable image try to lock the main
3914 // executable file multiple times before giving up. If we end up giving
3915 // up, we won't fail the update.
3916 const int max_retries = 10;
3917 int retries = 1;
3918 DWORD lastWriteError = 0;
3919 do {
3920 // By opening a file handle wihout FILE_SHARE_READ to the callback
3921 // executable, the OS will prevent launching the process while it is
3922 // being updated.
3923 callbackFile = CreateFileW(targetPath, DELETE | GENERIC_WRITE,
3924 // allow delete, rename, and write
3925 FILE_SHARE_DELETE | FILE_SHARE_WRITE,
3926 nullptr, OPEN_EXISTING, 0, nullptr);
3927 if (callbackFile != INVALID_HANDLE_VALUE) {
3928 break;
3929 }
3930
3931 lastWriteError = GetLastError();
3932 LOG(UpdateLog::GetPrimaryLog().Printf ("NS_main: callback app file open attempt %d failed. "
"File: " "%s" ". Last error: %lu", retries, targetPath, lastWriteError
)
3933 ("NS_main: callback app file open attempt %d failed. "UpdateLog::GetPrimaryLog().Printf ("NS_main: callback app file open attempt %d failed. "
"File: " "%s" ". Last error: %lu", retries, targetPath, lastWriteError
)
3934 "File: " LOG_S ". Last error: %lu",UpdateLog::GetPrimaryLog().Printf ("NS_main: callback app file open attempt %d failed. "
"File: " "%s" ". Last error: %lu", retries, targetPath, lastWriteError
)
3935 retries, targetPath, lastWriteError))UpdateLog::GetPrimaryLog().Printf ("NS_main: callback app file open attempt %d failed. "
"File: " "%s" ". Last error: %lu", retries, targetPath, lastWriteError
)
;
3936
3937 Sleep(100);
3938 } while (++retries <= max_retries);
3939
3940 // CreateFileW will fail if the callback executable is already in use.
3941 if (callbackFile == INVALID_HANDLE_VALUE) {
3942 bool proceedWithoutExclusive = true;
3943
3944 // Fail the update if the last error was not a sharing violation.
3945 if (lastWriteError != ERROR_SHARING_VIOLATION) {
3946 LOG((UpdateLog::GetPrimaryLog().Printf ( "NS_main: callback app file in use, failed to exclusively open "
"executable file: " "%s", argv[callbackIndex])
3947 "NS_main: callback app file in use, failed to exclusively open "UpdateLog::GetPrimaryLog().Printf ( "NS_main: callback app file in use, failed to exclusively open "
"executable file: " "%s", argv[callbackIndex])
3948 "executable file: " LOG_S,UpdateLog::GetPrimaryLog().Printf ( "NS_main: callback app file in use, failed to exclusively open "
"executable file: " "%s", argv[callbackIndex])
3949 argv[callbackIndex]))UpdateLog::GetPrimaryLog().Printf ( "NS_main: callback app file in use, failed to exclusively open "
"executable file: " "%s", argv[callbackIndex])
;
3950 if (lastWriteError == ERROR_ACCESS_DENIED) {
3951 WriteStatusFile(WRITE_ERROR_ACCESS_DENIED35);
3952 } else {
3953 WriteStatusFile(WRITE_ERROR_CALLBACK_APP37);
3954 }
3955
3956 proceedWithoutExclusive = false;
3957 }
3958
3959 // Fail even on sharing violation from a background task, since a
3960 // background task has a higher risk of interfering with a running
3961 // app. Note that this does not apply when staging (when an exclusive
3962 // lock isn't necessary), as there is no callback.
3963 if (lastWriteError == ERROR_SHARING_VIOLATION && sUpdateSilently) {
3964 LOG((UpdateLog::GetPrimaryLog().Printf ( "NS_main: callback app file in use, failed to exclusively open "
"executable file from background task: " "%s", argv[callbackIndex
])
3965 "NS_main: callback app file in use, failed to exclusively open "UpdateLog::GetPrimaryLog().Printf ( "NS_main: callback app file in use, failed to exclusively open "
"executable file from background task: " "%s", argv[callbackIndex
])
3966 "executable file from background task: " LOG_S,UpdateLog::GetPrimaryLog().Printf ( "NS_main: callback app file in use, failed to exclusively open "
"executable file from background task: " "%s", argv[callbackIndex
])
3967 argv[callbackIndex]))UpdateLog::GetPrimaryLog().Printf ( "NS_main: callback app file in use, failed to exclusively open "
"executable file from background task: " "%s", argv[callbackIndex
])
;
3968 WriteStatusFile(WRITE_ERROR_BACKGROUND_TASK_SHARING_VIOLATION106);
3969
3970 proceedWithoutExclusive = false;
3971 }
3972
3973 if (!proceedWithoutExclusive) {
3974 if (NS_tremoveremove(gCallbackBackupPath) && errno(*__errno_location ()) != ENOENT2) {
3975 LOG(UpdateLog::GetPrimaryLog().Printf ("NS_main: unable to remove backup of callback app file, "
"path: " "%s", gCallbackBackupPath)
3976 ("NS_main: unable to remove backup of callback app file, "UpdateLog::GetPrimaryLog().Printf ("NS_main: unable to remove backup of callback app file, "
"path: " "%s", gCallbackBackupPath)
3977 "path: " LOG_S,UpdateLog::GetPrimaryLog().Printf ("NS_main: unable to remove backup of callback app file, "
"path: " "%s", gCallbackBackupPath)
3978 gCallbackBackupPath))UpdateLog::GetPrimaryLog().Printf ("NS_main: unable to remove backup of callback app file, "
"path: " "%s", gCallbackBackupPath)
;
3979 }
3980 output_finish();
3981 EXIT_WHEN_ELEVATED(elevatedLockFilePath, updateLockFileHandle, 1);
3982 LaunchCallbackApp(argv[5], argc - callbackIndex,
3983 argv + callbackIndex, sUsingService);
3984 return 1;
3985 }
3986
3987 LOG(UpdateLog::GetPrimaryLog().Printf ("NS_main: callback app file in use, continuing without "
"exclusive access for executable file: " "%s", argv[callbackIndex
])
3988 ("NS_main: callback app file in use, continuing without "UpdateLog::GetPrimaryLog().Printf ("NS_main: callback app file in use, continuing without "
"exclusive access for executable file: " "%s", argv[callbackIndex
])
3989 "exclusive access for executable file: " LOG_S,UpdateLog::GetPrimaryLog().Printf ("NS_main: callback app file in use, continuing without "
"exclusive access for executable file: " "%s", argv[callbackIndex
])
3990 argv[callbackIndex]))UpdateLog::GetPrimaryLog().Printf ("NS_main: callback app file in use, continuing without "
"exclusive access for executable file: " "%s", argv[callbackIndex
])
;
3991 }
3992 }
3993 }
3994
3995 // DELETE_DIR is not required when performing a staged update or replace
3996 // request; it can be used during a replace request but then it doesn't
3997 // use gDeleteDirPath.
3998 if (!sStagedUpdate && !sReplaceRequest) {
3999 // The directory to move files that are in use to on Windows. This
4000 // directory will be deleted after the update is finished, on OS reboot
4001 // using MoveFileEx if it contains files that are in use, or by the post
4002 // update process after the update finishes. On Windows when performing a
4003 // normal update (e.g. the update is not a staged update and is not a
4004 // replace request) gWorkingDirPath is the same as gInstallDirPath and
4005 // gWorkingDirPath is used because it is the destination directory.
4006 NS_tsnprintfsnprintf(gDeleteDirPath,
4007 sizeof(gDeleteDirPath) / sizeof(gDeleteDirPath[0]),
4008 NS_T("%s/%s")"%s/%s", gWorkingDirPath, DELETE_DIR);
4009
4010 if (NS_taccessaccess(gDeleteDirPath, F_OK0)) {
4011 NS_tmkdirmkdir(gDeleteDirPath, 0755);
4012 }
4013 }
4014#endif /* XP_WIN */
4015
4016 // Run update process on a background thread. ShowProgressUI may return
4017 // before QuitProgressUI has been called, so wait for UpdateThreadFunc to
4018 // terminate. Avoid showing the progress UI when staging an update, or if
4019 // this is an elevated process on OSX.
4020 Thread t;
4021 if (t.Run(UpdateThreadFunc, nullptr) == 0) {
4022 if (!sStagedUpdate && !sReplaceRequest && !sUpdateSilently
4023#ifdef XP_MACOSX
4024 && !isElevated
4025#endif
4026 ) {
4027 ShowProgressUI();
4028 }
4029 }
4030 t.Join();
4031
4032#ifdef XP_WIN
4033 if (argc > callbackIndex && !sReplaceRequest) {
4034 if (callbackFile != INVALID_HANDLE_VALUE) {
4035 CloseHandle(callbackFile);
4036 }
4037 // Remove the copy of the callback executable.
4038 if (NS_tremoveremove(gCallbackBackupPath) && errno(*__errno_location ()) != ENOENT2) {
4039 LOG(UpdateLog::GetPrimaryLog().Printf ("NS_main: non-fatal error removing backup of callback app file, "
"path: " "%s", gCallbackBackupPath)
4040 ("NS_main: non-fatal error removing backup of callback app file, "UpdateLog::GetPrimaryLog().Printf ("NS_main: non-fatal error removing backup of callback app file, "
"path: " "%s", gCallbackBackupPath)
4041 "path: " LOG_S,UpdateLog::GetPrimaryLog().Printf ("NS_main: non-fatal error removing backup of callback app file, "
"path: " "%s", gCallbackBackupPath)
4042 gCallbackBackupPath))UpdateLog::GetPrimaryLog().Printf ("NS_main: non-fatal error removing backup of callback app file, "
"path: " "%s", gCallbackBackupPath)
;
4043 }
4044 }
4045
4046 if (!sStagedUpdate && !sReplaceRequest && _wrmdir(gDeleteDirPath)) {
4047 LOG(("NS_main: unable to remove directory: " LOG_S ", err: %d",UpdateLog::GetPrimaryLog().Printf ("NS_main: unable to remove directory: "
"%s" ", err: %d", DELETE_DIR, (*__errno_location ()))
4048 DELETE_DIR, errno))UpdateLog::GetPrimaryLog().Printf ("NS_main: unable to remove directory: "
"%s" ", err: %d", DELETE_DIR, (*__errno_location ()))
;
4049 // The directory probably couldn't be removed due to it containing files
4050 // that are in use and will be removed on OS reboot. The call to remove
4051 // the directory on OS reboot is done after the calls to remove the files
4052 // so the files are removed first on OS reboot since the directory must be
4053 // empty for the directory removal to be successful. The MoveFileEx call
4054 // to remove the directory on OS reboot will fail if the process doesn't
4055 // have write access to the HKEY_LOCAL_MACHINE registry key but this is ok
4056 // since the installer / uninstaller will delete the directory along with
4057 // its contents after an update is applied, on reinstall, and on
4058 // uninstall.
4059 if (MoveFileEx(gDeleteDirPath, nullptr, MOVEFILE_DELAY_UNTIL_REBOOT)) {
4060 LOG(("NS_main: directory will be removed on OS reboot: " LOG_S,UpdateLog::GetPrimaryLog().Printf ("NS_main: directory will be removed on OS reboot: "
"%s", DELETE_DIR)
4061 DELETE_DIR))UpdateLog::GetPrimaryLog().Printf ("NS_main: directory will be removed on OS reboot: "
"%s", DELETE_DIR)
;
4062 } else {
4063 LOG(UpdateLog::GetPrimaryLog().Printf ("NS_main: failed to schedule OS reboot removal of "
"directory: " "%s", DELETE_DIR)
4064 ("NS_main: failed to schedule OS reboot removal of "UpdateLog::GetPrimaryLog().Printf ("NS_main: failed to schedule OS reboot removal of "
"directory: " "%s", DELETE_DIR)
4065 "directory: " LOG_S,UpdateLog::GetPrimaryLog().Printf ("NS_main: failed to schedule OS reboot removal of "
"directory: " "%s", DELETE_DIR)
4066 DELETE_DIR))UpdateLog::GetPrimaryLog().Printf ("NS_main: failed to schedule OS reboot removal of "
"directory: " "%s", DELETE_DIR)
;
4067 }
4068 }
4069#endif /* XP_WIN */
4070
4071 } // if (!isDMGInstall)
4072
4073#ifdef XP_MACOSX
4074 if (isElevated) {
4075 SetGroupOwnershipAndPermissions(gInstallDirPath);
4076 freeArguments(argc, argv);
4077 CleanupElevatedMacUpdate(false);
4078 } else if (IsOwnedByGroupAdmin(gInstallDirPath)) {
4079 // If the group ownership of the Firefox .app bundle was set to the "admin"
4080 // group during a previous elevated update, we need to ensure that all files
4081 // in the bundle have group ownership of "admin" as well as write permission
4082 // for the group to not break updates in the future.
4083 SetGroupOwnershipAndPermissions(gInstallDirPath);
4084 }
4085#endif /* XP_MACOSX */
4086
4087 output_finish();
4088
4089 int retVal = LaunchCallbackAndPostProcessApps(argc, argv, callbackIndex
4090#ifdef XP_WIN
4091 ,
4092 elevatedLockFilePath,
4093 updateLockFileHandle
4094#elif XP_MACOSX
4095 ,
4096 isElevated,
4097 std::move(umaskContext)
4098#endif
4099 );
4100
4101 return retVal ? retVal : (gSucceeded ? 0 : 1);
4102}
4103
4104class ActionList {
4105 public:
4106 ActionList() : mFirst(nullptr), mLast(nullptr), mCount(0) {}
4107 ~ActionList();
4108
4109 void Append(Action* action);
4110 int Prepare();
4111 int Execute();
4112 void Finish(int status);
4113
4114 private:
4115 Action* mFirst;
4116 Action* mLast;
4117 int mCount;
4118};
4119
4120ActionList::~ActionList() {
4121 Action* a = mFirst;
4122 while (a) {
4123 Action* b = a;
4124 a = a->mNext;
4125 delete b;
4126 }
4127}
4128
4129void ActionList::Append(Action* action) {
4130 if (mLast) {
4131 mLast->mNext = action;
4132 } else {
4133 mFirst = action;
4134 }
4135
4136 mLast = action;
4137 mCount++;
4138}
4139
4140int ActionList::Prepare() {
4141 // If the action list is empty then we should fail in order to signal that
4142 // something has gone wrong. Otherwise we report success when nothing is
4143 // actually done. See bug 327140.
4144 if (mCount == 0) {
4145 LOG(("empty action list"))UpdateLog::GetPrimaryLog().Printf ("empty action list");
4146 return MAR_ERROR_EMPTY_ACTION_LIST1;
4147 }
4148
4149 Action* a = mFirst;
4150 int i = 0;
4151 while (a) {
4152 int rv = a->Prepare();
4153 if (rv) {
4154 return rv;
4155 }
4156
4157 float percent = float(++i) / float(mCount);
4158 UpdateProgressUI(PROGRESS_PREPARE_SIZE20.0f * percent);
4159
4160 a = a->mNext;
4161 }
4162
4163 return OK0;
4164}
4165
4166int ActionList::Execute() {
4167 int currentProgress = 0, maxProgress = 0;
4168 Action* a = mFirst;
4169 while (a) {
4170 maxProgress += a->mProgressCost;
4171 a = a->mNext;
4172 }
4173
4174 a = mFirst;
4175 while (a) {
4176 int rv = a->Execute();
4177 if (rv) {
4178 LOG(("### execution failed"))UpdateLog::GetPrimaryLog().Printf ("### execution failed");
4179 return rv;
4180 }
4181
4182 currentProgress += a->mProgressCost;
4183 float percent = float(currentProgress) / float(maxProgress);
4184 UpdateProgressUI(PROGRESS_PREPARE_SIZE20.0f + PROGRESS_EXECUTE_SIZE75.0f * percent);
4185
4186 a = a->mNext;
4187 }
4188
4189 return OK0;
4190}
4191
4192void ActionList::Finish(int status) {
4193 Action* a = mFirst;
4194 int i = 0;
4195 while (a) {
4196 a->Finish(status);
4197
4198 float percent = float(++i) / float(mCount);
4199 UpdateProgressUI(PROGRESS_PREPARE_SIZE20.0f + PROGRESS_EXECUTE_SIZE75.0f +
4200 PROGRESS_FINISH_SIZE5.0f * percent);
4201
4202 a = a->mNext;
4203 }
4204
4205 if (status == OK0) {
4206 gSucceeded = true;
4207 }
4208}
4209
4210#ifdef XP_WIN
4211int add_dir_entries(const NS_tchar* dirpath, ActionList* list) {
4212 int rv = OK0;
4213 WIN32_FIND_DATAW finddata;
4214 HANDLE hFindFile;
4215 NS_tchar searchspec[MAXPATHLEN4096];
4216 NS_tchar foundpath[MAXPATHLEN4096];
4217
4218 NS_tsnprintfsnprintf(searchspec, sizeof(searchspec) / sizeof(searchspec[0]),
4219 NS_T("%s*")"%s*", dirpath);
4220 mozilla::UniquePtr<const NS_tchar> pszSpec(get_full_path(searchspec));
4221
4222 hFindFile = FindFirstFileW(pszSpec.get(), &finddata);
4223 if (hFindFile != INVALID_HANDLE_VALUE) {
4224 do {
4225 // Don't process the current or parent directory.
4226 if (NS_tstrcmpstrcmp(finddata.cFileName, NS_T(".")".") == 0 ||
4227 NS_tstrcmpstrcmp(finddata.cFileName, NS_T("..")"..") == 0) {
4228 continue;
4229 }
4230
4231 NS_tsnprintfsnprintf(foundpath, sizeof(foundpath) / sizeof(foundpath[0]),
4232 NS_T("%s%s")"%s%s", dirpath, finddata.cFileName);
4233 if (finddata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
4234 NS_tsnprintfsnprintf(foundpath, sizeof(foundpath) / sizeof(foundpath[0]),
4235 NS_T("%s/")"%s/", foundpath);
4236 // Recurse into the directory.
4237 rv = add_dir_entries(foundpath, list);
4238 if (rv) {
4239 LOG(("add_dir_entries error: " LOG_S ", err: %d", foundpath, rv))UpdateLog::GetPrimaryLog().Printf ("add_dir_entries error: " "%s"
", err: %d", foundpath, rv)
;
4240 return rv;
4241 }
4242 } else {
4243 // Add the file to be removed to the ActionList.
4244 NS_tchar* quotedpath = get_quoted_path(foundpath);
4245 if (!quotedpath) {
4246 return PARSE_ERROR5;
4247 }
4248
4249 mozilla::UniquePtr<Action> action(new RemoveFile());
4250 rv = action->Parse(quotedpath);
4251 if (rv) {
4252 LOG(("add_dir_entries Parse error on recurse: " LOG_S ", err: %d",UpdateLog::GetPrimaryLog().Printf ("add_dir_entries Parse error on recurse: "
"%s" ", err: %d", quotedpath, rv)
4253 quotedpath, rv))UpdateLog::GetPrimaryLog().Printf ("add_dir_entries Parse error on recurse: "
"%s" ", err: %d", quotedpath, rv)
;
4254 free(quotedpath);
4255 return rv;
4256 }
4257 free(quotedpath);
4258
4259 list->Append(action.release());
4260 }
4261 } while (FindNextFileW(hFindFile, &finddata) != 0);
4262
4263 FindClose(hFindFile);
4264 {
4265 // Add the directory to be removed to the ActionList.
4266 NS_tchar* quotedpath = get_quoted_path(dirpath);
4267 if (!quotedpath) {
4268 return PARSE_ERROR5;
4269 }
4270
4271 mozilla::UniquePtr<Action> action(new RemoveDir());
4272 rv = action->Parse(quotedpath);
4273 if (rv) {
4274 LOG(("add_dir_entries Parse error on close: " LOG_S ", err: %d",UpdateLog::GetPrimaryLog().Printf ("add_dir_entries Parse error on close: "
"%s" ", err: %d", quotedpath, rv)
4275 quotedpath, rv))UpdateLog::GetPrimaryLog().Printf ("add_dir_entries Parse error on close: "
"%s" ", err: %d", quotedpath, rv)
;
4276 } else {
4277 list->Append(action.release());
4278 }
4279 free(quotedpath);
4280 }
4281 }
4282
4283 return rv;
4284}
4285
4286#elif defined(HAVE_FTS_H1)
4287 int add_dir_entries(const NS_tchar* dirpath, ActionList* list) {
4288 int rv = OK0;
4289 FTS* ftsdir;
4290 FTSENT* ftsdirEntry;
4291 mozilla::UniquePtr<NS_tchar[]> searchpath(get_full_path(dirpath));
4292
4293 // Remove the trailing slash so the paths don't contain double slashes. The
4294 // existence of the slash has already been checked in DoUpdate.
4295 searchpath[NS_tstrlenstrlen(searchpath.get()) - 1] = NS_T('\0')'\0';
4296 char* const pathargv[] = {searchpath.get(), nullptr};
4297
4298 // FTS_NOCHDIR is used so relative paths from the destination directory are
4299 // returned.
4300 if (!(ftsdir = fts_open(pathargv,
4301 FTS_PHYSICAL0x0010 | FTS_NOSTAT0x0008 | FTS_XDEV0x0040 | FTS_NOCHDIR0x0004,
4302 nullptr))) {
4303 return UNEXPECTED_FILE_OPERATION_ERROR42;
4304 }
4305
4306 while ((ftsdirEntry = fts_read(ftsdir)) != nullptr) {
4307 NS_tchar foundpath[MAXPATHLEN4096];
4308 NS_tchar* quotedpath = nullptr;
4309 mozilla::UniquePtr<Action> action;
4310
4311 switch (ftsdirEntry->fts_info) {
4312 // Filesystem objects that shouldn't be in the application's directories
4313 case FTS_SL12:
4314 case FTS_SLNONE13:
4315 case FTS_DEFAULT3:
4316 LOG(("add_dir_entries: found a non-standard file: " LOG_S,UpdateLog::GetPrimaryLog().Printf ("add_dir_entries: found a non-standard file: "
"%s", ftsdirEntry->fts_path)
4317 ftsdirEntry->fts_path))UpdateLog::GetPrimaryLog().Printf ("add_dir_entries: found a non-standard file: "
"%s", ftsdirEntry->fts_path)
;
4318 // Fall through and try to remove as a file
4319 [[fallthrough]];
4320
4321 // Files
4322 case FTS_F8:
4323 case FTS_NSOK11:
4324 // Add the file to be removed to the ActionList.
4325 NS_tsnprintfsnprintf(foundpath, sizeof(foundpath) / sizeof(foundpath[0]),
4326 NS_T("%s")"%s", ftsdirEntry->fts_accpath);
4327 quotedpath = get_quoted_path(get_relative_path(foundpath));
4328 if (!quotedpath) {
4329 rv = UPDATER_QUOTED_PATH_MEM_ERROR14;
4330 break;
4331 }
4332 action.reset(new RemoveFile());
4333 rv = action->Parse(quotedpath);
4334 free(quotedpath);
4335 if (!rv) {
4336 list->Append(action.release());
4337 }
4338 break;
4339
4340 // Directories
4341 case FTS_DP6:
4342 // Add the directory to be removed to the ActionList.
4343 NS_tsnprintfsnprintf(foundpath, sizeof(foundpath) / sizeof(foundpath[0]),
4344 NS_T("%s/")"%s/", ftsdirEntry->fts_accpath);
4345 quotedpath = get_quoted_path(get_relative_path(foundpath));
4346 if (!quotedpath) {
4347 rv = UPDATER_QUOTED_PATH_MEM_ERROR14;
4348 break;
4349 }
4350
4351 action.reset(new RemoveDir());
4352 rv = action->Parse(quotedpath);
4353 free(quotedpath);
4354 if (!rv) {
4355 list->Append(action.release());
4356 }
4357 break;
4358
4359 // Errors
4360 case FTS_DNR4:
4361 case FTS_NS10:
4362 // ENOENT is an acceptable error for FTS_DNR and FTS_NS and means that
4363 // we're racing with ourselves. Though strange, the entry will be
4364 // removed anyway.
4365 if (ENOENT2 == ftsdirEntry->fts_errno) {
4366 rv = OK0;
4367 break;
4368 }
4369 [[fallthrough]];
4370
4371 case FTS_ERR7:
4372 rv = UNEXPECTED_FILE_OPERATION_ERROR42;
4373 LOG(("add_dir_entries: fts_read() error: " LOG_S ", err: %d",UpdateLog::GetPrimaryLog().Printf ("add_dir_entries: fts_read() error: "
"%s" ", err: %d", ftsdirEntry->fts_path, ftsdirEntry->
fts_errno)
4374 ftsdirEntry->fts_path, ftsdirEntry->fts_errno))UpdateLog::GetPrimaryLog().Printf ("add_dir_entries: fts_read() error: "
"%s" ", err: %d", ftsdirEntry->fts_path, ftsdirEntry->
fts_errno)
;
4375 break;
4376
4377 case FTS_DC2:
4378 rv = UNEXPECTED_FILE_OPERATION_ERROR42;
4379 LOG(("add_dir_entries: fts_read() returned FT_DC: " LOG_S,UpdateLog::GetPrimaryLog().Printf ("add_dir_entries: fts_read() returned FT_DC: "
"%s", ftsdirEntry->fts_path)
4380 ftsdirEntry->fts_path))UpdateLog::GetPrimaryLog().Printf ("add_dir_entries: fts_read() returned FT_DC: "
"%s", ftsdirEntry->fts_path)
;
4381 break;
4382
4383 default:
4384 // FTS_D is ignored and FTS_DP is used instead (post-order).
4385 rv = OK0;
4386 break;
4387 }
4388
4389 if (rv != OK0) {
4390 break;
4391 }
4392 }
4393
4394 fts_close(ftsdir);
4395
4396 return rv;
4397 }
4398
4399#else
4400
4401int add_dir_entries(const NS_tchar* dirpath, ActionList* list) {
4402 int rv = OK0;
4403 NS_tchar foundpath[PATH_MAX4096];
4404 struct {
4405 dirent dent_buffer;
4406 char chars[NAME_MAX255];
4407 } ent_buf;
4408 struct dirent* ent;
4409 mozilla::UniquePtr<NS_tchar[]> searchpath(get_full_path(dirpath));
4410
4411 DIR* dir = opendir(searchpath.get());
4412 if (!dir) {
4413 LOG(("add_dir_entries error on opendir: " LOG_S ", err: %d",UpdateLog::GetPrimaryLog().Printf ("add_dir_entries error on opendir: "
"%s" ", err: %d", searchpath.get(), (*__errno_location ()))
4414 searchpath.get(), errno))UpdateLog::GetPrimaryLog().Printf ("add_dir_entries error on opendir: "
"%s" ", err: %d", searchpath.get(), (*__errno_location ()))
;
4415 return UNEXPECTED_FILE_OPERATION_ERROR42;
4416 }
4417
4418 while (readdir_r(dir, (dirent*)&ent_buf, &ent) == 0 && ent) {
4419 if ((strcmp(ent->d_name, ".") == 0) || (strcmp(ent->d_name, "..") == 0)) {
4420 continue;
4421 }
4422
4423 NS_tsnprintfsnprintf(foundpath, sizeof(foundpath) / sizeof(foundpath[0]),
4424 NS_T("%s%s")"%s%s", searchpath.get(), ent->d_name);
4425 struct stat64 st_buf;
4426 int test = stat64(foundpath, &st_buf);
4427 if (test) {
4428 closedir(dir);
4429 return UNEXPECTED_FILE_OPERATION_ERROR42;
4430 }
4431 if (S_ISDIR(st_buf.st_mode)((((st_buf.st_mode)) & 0170000) == (0040000))) {
4432 NS_tsnprintfsnprintf(foundpath, sizeof(foundpath) / sizeof(foundpath[0]),
4433 NS_T("%s%s/")"%s%s/", dirpath, ent->d_name);
4434 // Recurse into the directory.
4435 rv = add_dir_entries(foundpath, list);
4436 if (rv) {
4437 LOG(("add_dir_entries error: " LOG_S ", err: %d", foundpath, rv))UpdateLog::GetPrimaryLog().Printf ("add_dir_entries error: " "%s"
", err: %d", foundpath, rv)
;
4438 closedir(dir);
4439 return rv;
4440 }
4441 } else {
4442 // Add the file to be removed to the ActionList.
4443 NS_tchar* quotedpath = get_quoted_path(get_relative_path(foundpath));
4444 if (!quotedpath) {
4445 closedir(dir);
4446 return PARSE_ERROR5;
4447 }
4448
4449 mozilla::UniquePtr<Action> action(new RemoveFile());
4450 rv = action->Parse(quotedpath);
4451 if (rv) {
4452 LOG(("add_dir_entries Parse error on recurse: " LOG_S ", err: %d",UpdateLog::GetPrimaryLog().Printf ("add_dir_entries Parse error on recurse: "
"%s" ", err: %d", quotedpath, rv)
4453 quotedpath, rv))UpdateLog::GetPrimaryLog().Printf ("add_dir_entries Parse error on recurse: "
"%s" ", err: %d", quotedpath, rv)
;
4454 free(quotedpath);
4455 closedir(dir);
4456 return rv;
4457 }
4458 free(quotedpath);
4459
4460 list->Append(action.release());
4461 }
4462 }
4463 closedir(dir);
4464
4465 // Add the directory to be removed to the ActionList.
4466 NS_tchar* quotedpath = get_quoted_path(get_relative_path(dirpath));
4467 if (!quotedpath) {
4468 return PARSE_ERROR5;
4469 }
4470
4471 mozilla::UniquePtr<Action> action(new RemoveDir());
4472 rv = action->Parse(quotedpath);
4473 if (rv) {
4474 LOG(("add_dir_entries Parse error on close: " LOG_S ", err: %d", quotedpath,UpdateLog::GetPrimaryLog().Printf ("add_dir_entries Parse error on close: "
"%s" ", err: %d", quotedpath, rv)
4475 rv))UpdateLog::GetPrimaryLog().Printf ("add_dir_entries Parse error on close: "
"%s" ", err: %d", quotedpath, rv)
;
4476 } else {
4477 list->Append(action.release());
4478 }
4479 free(quotedpath);
4480
4481 return rv;
4482}
4483
4484#endif
4485
4486/*
4487 * Gets the contents of an update manifest file. The return value is malloc'd
4488 * and it is the responsibility of the caller to free it.
4489 *
4490 * @param manifest
4491 * The full path to the manifest file.
4492 * @return On success the contents of the manifest and nullptr otherwise.
4493 */
4494static NS_tchar* GetManifestContents(const NS_tchar* manifest) {
4495 AutoFile mfile(NS_tfopenfopen(manifest, NS_T("rb")"rb"));
4496 if (mfile == nullptr) {
4497 LOG(("GetManifestContents: error opening manifest file: " LOG_S, manifest))UpdateLog::GetPrimaryLog().Printf ("GetManifestContents: error opening manifest file: "
"%s", manifest)
;
4498 return nullptr;
4499 }
4500
4501 struct stat ms;
4502 int rv = fstat(fileno((FILE*)mfile), &ms);
4503 if (rv) {
4504 LOG(("GetManifestContents: error stating manifest file: " LOG_S, manifest))UpdateLog::GetPrimaryLog().Printf ("GetManifestContents: error stating manifest file: "
"%s", manifest)
;
4505 return nullptr;
4506 }
4507
4508 char* mbuf = (char*)malloc(ms.st_size + 1);
4509 if (!mbuf) {
4510 return nullptr;
4511 }
4512
4513 size_t r = ms.st_size;
4514 char* rb = mbuf;
4515 while (r) {
4516 const size_t count = mmin(SSIZE_MAX9223372036854775807L, r);
4517 size_t c = fread(rb, 1, count, mfile);
4518 if (c != count) {
4519 LOG(("GetManifestContents: error reading manifest file: " LOG_S,UpdateLog::GetPrimaryLog().Printf ("GetManifestContents: error reading manifest file: "
"%s", manifest)
4520 manifest))UpdateLog::GetPrimaryLog().Printf ("GetManifestContents: error reading manifest file: "
"%s", manifest)
;
4521 free(mbuf);
4522 return nullptr;
4523 }
4524
4525 r -= c;
4526 rb += c;
4527 }
4528 *rb = '\0';
4529
4530#ifndef XP_WIN
4531 return mbuf;
4532#else
4533 NS_tchar* wrb = (NS_tchar*)malloc((ms.st_size + 1) * sizeof(NS_tchar));
4534 if (!wrb) {
4535 free(mbuf);
4536 return nullptr;
4537 }
4538
4539 if (!MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, mbuf, -1, wrb,
4540 ms.st_size + 1)) {
4541 LOG(("GetManifestContents: error converting utf8 to utf16le: %lu",UpdateLog::GetPrimaryLog().Printf ("GetManifestContents: error converting utf8 to utf16le: %lu"
, GetLastError())
4542 GetLastError()))UpdateLog::GetPrimaryLog().Printf ("GetManifestContents: error converting utf8 to utf16le: %lu"
, GetLastError())
;
4543 free(mbuf);
4544 free(wrb);
4545 return nullptr;
4546 }
4547 free(mbuf);
4548
4549 return wrb;
4550#endif
4551}
4552
4553int AddPreCompleteActions(ActionList* list) {
4554#ifdef XP_MACOSX
4555 mozilla::UniquePtr<NS_tchar[]> manifestPath(
4556 get_full_path(NS_T("Contents/Resources/precomplete")"Contents/Resources/precomplete"));
4557#else
4558 mozilla::UniquePtr<NS_tchar[]> manifestPath(
4559 get_full_path(NS_T("precomplete")"precomplete"));
4560#endif
4561
4562 NS_tchar* buf = GetManifestContents(manifestPath.get());
4563 if (!buf) {
4564 LOG(UpdateLog::GetPrimaryLog().Printf ("AddPreCompleteActions: error getting contents of precomplete "
"manifest")
4565 ("AddPreCompleteActions: error getting contents of precomplete "UpdateLog::GetPrimaryLog().Printf ("AddPreCompleteActions: error getting contents of precomplete "
"manifest")
4566 "manifest"))UpdateLog::GetPrimaryLog().Printf ("AddPreCompleteActions: error getting contents of precomplete "
"manifest")
;
4567 // Applications aren't required to have a precomplete manifest. The mar
4568 // generation scripts enforce the presence of a precomplete manifest.
4569 return OK0;
4570 }
4571 NS_tchar* rb = buf;
4572
4573 int rv;
4574 NS_tchar* line;
4575 while ((line = mstrtok(kNL, &rb)) != 0) {
4576 // skip comments
4577 if (*line == NS_T('#')'#') {
4578 continue;
4579 }
4580
4581 NS_tchar* token = mstrtok(kWhitespace, &line);
4582 if (!token) {
4583 LOG(("AddPreCompleteActions: token not found in manifest"))UpdateLog::GetPrimaryLog().Printf ("AddPreCompleteActions: token not found in manifest"
)
;
4584 free(buf);
4585 return PARSE_ERROR5;
4586 }
4587
4588 Action* action = nullptr;
4589 if (NS_tstrcmpstrcmp(token, NS_T("remove")"remove") == 0) { // rm file
4590 action = new RemoveFile();
4591 } else if (NS_tstrcmpstrcmp(token, NS_T("remove-cc")"remove-cc") ==
4592 0) { // no longer supported
4593 continue;
4594 } else if (NS_tstrcmpstrcmp(token, NS_T("rmdir")"rmdir") == 0) { // rmdir if empty
4595 action = new RemoveDir();
4596 } else {
4597 LOG(("AddPreCompleteActions: unknown token: " LOG_S, token))UpdateLog::GetPrimaryLog().Printf ("AddPreCompleteActions: unknown token: "
"%s", token)
;
4598 free(buf);
4599 return PARSE_ERROR5;
4600 }
4601
4602 if (!action) {
4603 free(buf);
4604 return BAD_ACTION_ERROR15;
4605 }
4606
4607 rv = action->Parse(line);
4608 if (rv) {
4609 delete action;
4610 free(buf);
4611 return rv;
4612 }
4613
4614 list->Append(action);
4615 }
4616
4617 free(buf);
4618 return OK0;
4619}
4620
4621int DoUpdate() {
4622 NS_tchar manifest[MAXPATHLEN4096];
4623 NS_tsnprintfsnprintf(manifest, sizeof(manifest) / sizeof(manifest[0]),
4624 NS_T("%s/updating/update.manifest")"%s/updating/update.manifest", gWorkingDirPath);
4625 ensure_parent_dir(manifest);
4626
4627 // extract the manifest
4628 int rv = gArchiveReader.ExtractFile("updatev3.manifest", manifest);
4629 if (rv) {
15
Assuming 'rv' is 0
16
Taking false branch
4630 LOG(("DoUpdate: error extracting manifest file"))UpdateLog::GetPrimaryLog().Printf ("DoUpdate: error extracting manifest file"
)
;
4631 return rv;
4632 }
4633
4634 NS_tchar* buf = GetManifestContents(manifest);
4635 // The manifest is located in the <working_dir>/updating directory which is
4636 // removed after the update has finished so don't delete it here.
4637 if (!buf
16.1
'buf' is non-null
) {
17
Taking false branch
4638 LOG(("DoUpdate: error opening manifest file: " LOG_S, manifest))UpdateLog::GetPrimaryLog().Printf ("DoUpdate: error opening manifest file: "
"%s", manifest)
;
4639 return READ_ERROR6;
4640 }
4641 NS_tchar* rb = buf;
4642
4643 ActionList list;
4644 NS_tchar* line;
4645 bool isFirstAction = true;
4646 while ((line = mstrtok(kNL, &rb)) != 0) {
18
Assuming the condition is true
19
Loop condition is true. Entering loop body
4647 // skip comments
4648 if (*line == NS_T('#')'#') {
20
Assuming the condition is false
21
Taking false branch
4649 continue;
4650 }
4651
4652 NS_tchar* token = mstrtok(kWhitespace, &line);
4653 if (!token) {
22
Assuming 'token' is non-null
23
Taking false branch
4654 LOG(("DoUpdate: token not found in manifest"))UpdateLog::GetPrimaryLog().Printf ("DoUpdate: token not found in manifest"
)
;
4655 free(buf);
4656 return PARSE_ERROR5;
4657 }
4658
4659 if (isFirstAction
23.1
'isFirstAction' is true
) {
24
Taking true branch
4660 isFirstAction = false;
4661 // The update manifest isn't required to have a type declaration. The mar
4662 // generation scripts enforce the presence of the type declaration.
4663 if (NS_tstrcmpstrcmp(token, NS_T("type")"type") == 0) {
25
Assuming the condition is false
26
Taking false branch
4664 const NS_tchar* type = mstrtok(kQuote, &line);
4665 LOG(("UPDATE TYPE " LOG_S, type))UpdateLog::GetPrimaryLog().Printf ("UPDATE TYPE " "%s", type);
4666 if (NS_tstrcmpstrcmp(type, NS_T("complete")"complete") == 0) {
4667 rv = AddPreCompleteActions(&list);
4668 if (rv) {
4669 free(buf);
4670 return rv;
4671 }
4672 }
4673 continue;
4674 }
4675 }
4676
4677 Action* action = nullptr;
4678 if (NS_tstrcmpstrcmp(token, NS_T("remove")"remove") == 0) { // rm file
27
Assuming the condition is false
28
Taking false branch
4679 action = new RemoveFile();
4680 } else if (NS_tstrcmpstrcmp(token, NS_T("rmdir")"rmdir") == 0) { // rmdir if empty
29
Taking true branch
4681 action = new RemoveDir();
30
Memory is allocated
4682 } else if (NS_tstrcmpstrcmp(token, NS_T("rmrfdir")"rmrfdir") == 0) { // rmdir recursive
4683 const NS_tchar* reldirpath = mstrtok(kQuote, &line);
4684 if (!reldirpath) {
4685 free(buf);
4686 return PARSE_ERROR5;
4687 }
4688
4689 if (reldirpath[NS_tstrlenstrlen(reldirpath) - 1] != NS_T('/')'/') {
4690 free(buf);
4691 return PARSE_ERROR5;
4692 }
4693
4694 rv = add_dir_entries(reldirpath, &list);
4695 if (rv) {
4696 free(buf);
4697 return rv;
4698 }
4699
4700 continue;
4701 } else if (NS_tstrcmpstrcmp(token, NS_T("add")"add") == 0) {
4702 action = new AddFile();
4703 } else if (NS_tstrcmpstrcmp(token, NS_T("patch")"patch") == 0) {
4704 action = new PatchFile();
4705 } else if (NS_tstrcmpstrcmp(token, NS_T("add-if")"add-if") == 0) { // Add if exists
4706 action = new AddIfFile();
4707 } else if (NS_tstrcmpstrcmp(token, NS_T("add-if-not")"add-if-not") ==
4708 0) { // Add if not exists
4709 action = new AddIfNotFile();
4710 } else if (NS_tstrcmpstrcmp(token, NS_T("patch-if")"patch-if") == 0) { // Patch if exists
4711 action = new PatchIfFile();
4712 } else {
4713 LOG(("DoUpdate: unknown token: " LOG_S, token))UpdateLog::GetPrimaryLog().Printf ("DoUpdate: unknown token: "
"%s", token)
;
4714 free(buf);
4715 return PARSE_ERROR5;
4716 }
4717
4718 if (!action
30.1
'action' is non-null
) {
31
Taking false branch
4719 free(buf);
4720 return BAD_ACTION_ERROR15;
4721 }
4722
4723 rv = action->Parse(line);
4724 if (rv
31.1
'rv' is 5
) {
32
Taking true branch
4725 free(buf);
33
Potential leak of memory pointed to by 'action'
4726 return rv;
4727 }
4728
4729 list.Append(action);
4730 }
4731
4732 rv = list.Prepare();
4733 if (rv) {
4734 free(buf);
4735 return rv;
4736 }
4737
4738 rv = list.Execute();
4739
4740 list.Finish(rv);
4741 free(buf);
4742 return rv;
4743}