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