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