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