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