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