toolkit/mozapps/update/updater/updater.cpp

Sat, 03 Jan 2015 20:18:00 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Sat, 03 Jan 2015 20:18:00 +0100
branch
TOR_BUG_3246
changeset 7
129ffea94266
permissions
-rw-r--r--

Conditionally enable double key logic according to:
private browsing mode or privacy.thirdparty.isolate preference and
implement in GetCookieStringCommon and FindCookie where it counts...
With some reservations of how to convince FindCookie users to test
condition and pass a nullptr when disabling double key logic.

     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/. */
     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  *  updatev2.manifest
    17  *  -----------------
    18  *  method   = "add" | "add-if" | "patch" | "patch-if" | "remove" |
    19  *             "rmdir" | "rmrfdir" | type
    20  *
    21  *  'type' is the update type (e.g. complete or partial) and when present MUST
    22  *  be the first entry in the update manifest. The type is used to support
    23  *  downgrades by causing the actions defined in precomplete to be performed.
    24  *
    25  *  updatev3.manifest
    26  *  -----------------
    27  *  method   = "add" | "add-if" | "add-if-not" | "patch" | "patch-if" |
    28  *             "remove" | "rmdir" | "rmrfdir" | "addsymlink" | type
    29  *
    30  *  'add-if-not' adds a file if it doesn't exist.
    31  *
    32  *  precomplete
    33  *  -----------
    34  *  method   = "remove" | "rmdir"
    35  */
    36 #include "bspatch.h"
    37 #include "progressui.h"
    38 #include "archivereader.h"
    39 #include "readstrings.h"
    40 #include "errors.h"
    41 #include "bzlib.h"
    43 #include <stdio.h>
    44 #include <string.h>
    45 #include <stdlib.h>
    46 #include <stdarg.h>
    48 #include <sys/types.h>
    49 #include <sys/stat.h>
    50 #include <fcntl.h>
    51 #include <limits.h>
    52 #include <errno.h>
    53 #include <algorithm>
    55 #include "updatelogging.h"
    57 #include "mozilla/Compiler.h"
    59 // Amount of the progress bar to use in each of the 3 update stages,
    60 // should total 100.0.
    61 #define PROGRESS_PREPARE_SIZE 20.0f
    62 #define PROGRESS_EXECUTE_SIZE 75.0f
    63 #define PROGRESS_FINISH_SIZE   5.0f
    65 // Amount of time in ms to wait for the parent process to close
    66 #define PARENT_WAIT 5000
    67 #define IMMERSIVE_PARENT_WAIT 15000
    69 #if defined(XP_MACOSX)
    70 // These functions are defined in launchchild_osx.mm
    71 void LaunchChild(int argc, char **argv);
    72 void LaunchMacPostProcess(const char* aAppExe);
    73 #endif
    75 #ifndef _O_BINARY
    76 # define _O_BINARY 0
    77 #endif
    79 #ifndef NULL
    80 # define NULL (0)
    81 #endif
    83 #ifndef SSIZE_MAX
    84 # define SSIZE_MAX LONG_MAX
    85 #endif
    87 // We want to use execv to invoke the callback executable on platforms where
    88 // we were launched using execv.  See nsUpdateDriver.cpp.
    89 #if defined(XP_UNIX) && !defined(XP_MACOSX)
    90 #define USE_EXECV
    91 #endif
    93 #if defined(MOZ_WIDGET_GONK)
    94 # include "automounter_gonk.h"
    95 # include <unistd.h>
    96 # include <android/log.h>
    97 # include <linux/ioprio.h>
    98 # include <sys/resource.h>
   100 // The only header file in bionic which has a function prototype for ioprio_set
   101 // is libc/include/sys/linux-unistd.h. However, linux-unistd.h conflicts
   102 // badly with unistd.h, so we declare the prototype for ioprio_set directly.
   103 extern "C" int ioprio_set(int which, int who, int ioprio);
   105 # define MAYBE_USE_HARD_LINKS 1
   106 static bool sUseHardLinks = true;
   107 #else
   108 # define MAYBE_USE_HARD_LINKS 0
   109 #endif
   111 #ifdef XP_WIN
   112 #include "updatehelper.h"
   114 // Closes the handle if valid and if the updater is elevated returns with the
   115 // return code specified. This prevents multiple launches of the callback
   116 // application by preventing the elevated process from launching the callback.
   117 #define EXIT_WHEN_ELEVATED(path, handle, retCode) \
   118   { \
   119       if (handle != INVALID_HANDLE_VALUE) { \
   120         CloseHandle(handle); \
   121       } \
   122       if (_waccess(path, F_OK) == 0 && NS_tremove(path) != 0) { \
   123         return retCode; \
   124       } \
   125   }
   126 #endif
   128 //-----------------------------------------------------------------------------
   130 // This variable lives in libbz2.  It's declared in bzlib_private.h, so we just
   131 // declare it here to avoid including that entire header file.
   132 #define BZ2_CRC32TABLE_UNDECLARED
   134 #if MOZ_IS_GCC
   135 #if MOZ_GCC_VERSION_AT_LEAST(3, 3, 0)
   136 extern "C"  __attribute__((visibility("default"))) unsigned int BZ2_crc32Table[256];
   137 #undef BZ2_CRC32TABLE_UNDECLARED
   138 #endif
   139 #elif defined(__SUNPRO_C) || defined(__SUNPRO_CC)
   140 extern "C" __global unsigned int BZ2_crc32Table[256];
   141 #undef BZ2_CRC32TABLE_UNDECLARED
   142 #endif
   143 #if defined(BZ2_CRC32TABLE_UNDECLARED)
   144 extern "C" unsigned int BZ2_crc32Table[256];
   145 #undef BZ2_CRC32TABLE_UNDECLARED
   146 #endif
   148 static unsigned int
   149 crc32(const unsigned char *buf, unsigned int len)
   150 {
   151   unsigned int crc = 0xffffffffL;
   153   const unsigned char *end = buf + len;
   154   for (; buf != end; ++buf)
   155     crc = (crc << 8) ^ BZ2_crc32Table[(crc >> 24) ^ *buf];
   157   crc = ~crc;
   158   return crc;
   159 }
   161 //-----------------------------------------------------------------------------
   163 // A simple stack based container for a FILE struct that closes the
   164 // file descriptor from its destructor.
   165 class AutoFile
   166 {
   167 public:
   168   AutoFile(FILE* file = nullptr)
   169     : mFile(file) {
   170   }
   172   ~AutoFile() {
   173     if (mFile != nullptr)
   174       fclose(mFile);
   175   }
   177   AutoFile &operator=(FILE* file) {
   178     if (mFile != 0)
   179       fclose(mFile);
   180     mFile = file;
   181     return *this;
   182   }
   184   operator FILE*() {
   185     return mFile;
   186   }
   188   FILE* get() {
   189     return mFile;
   190   }
   192 private:
   193   FILE* mFile;
   194 };
   196 struct MARChannelStringTable {
   197   MARChannelStringTable() 
   198   {
   199     MARChannelID[0] = '\0';
   200   }
   202   char MARChannelID[MAX_TEXT_LEN];
   203 };
   205 //-----------------------------------------------------------------------------
   207 typedef void (* ThreadFunc)(void *param);
   209 #ifdef XP_WIN
   210 #include <process.h>
   212 class Thread
   213 {
   214 public:
   215   int Run(ThreadFunc func, void *param)
   216   {
   217     mThreadFunc = func;
   218     mThreadParam = param;
   220     unsigned int threadID;
   222     mThread = (HANDLE) _beginthreadex(nullptr, 0, ThreadMain, this, 0,
   223                                       &threadID);
   225     return mThread ? 0 : -1;
   226   }
   227   int Join()
   228   {
   229     WaitForSingleObject(mThread, INFINITE);
   230     CloseHandle(mThread);
   231     return 0;
   232   }
   233 private:
   234   static unsigned __stdcall ThreadMain(void *p)
   235   {
   236     Thread *self = (Thread *) p;
   237     self->mThreadFunc(self->mThreadParam);
   238     return 0;
   239   }
   240   HANDLE     mThread;
   241   ThreadFunc mThreadFunc;
   242   void      *mThreadParam;
   243 };
   245 #elif defined(XP_UNIX)
   246 #include <pthread.h>
   248 class Thread
   249 {
   250 public:
   251   int Run(ThreadFunc func, void *param)
   252   {
   253     return pthread_create(&thr, nullptr, (void* (*)(void *)) func, param);
   254   }
   255   int Join()
   256   {
   257     void *result;
   258     return pthread_join(thr, &result);
   259   }
   260 private:
   261   pthread_t thr;
   262 };
   264 #else
   265 #error "Unsupported platform"
   266 #endif
   268 //-----------------------------------------------------------------------------
   270 static NS_tchar* gSourcePath;
   271 static NS_tchar gDestinationPath[MAXPATHLEN];
   272 static ArchiveReader gArchiveReader;
   273 static bool gSucceeded = false;
   274 static bool sStagedUpdate = false;
   275 static bool sReplaceRequest = false;
   276 static bool sUsingService = false;
   277 static bool sIsOSUpdate = false;
   279 #ifdef XP_WIN
   280 // The current working directory specified in the command line.
   281 static NS_tchar* gDestPath;
   282 static NS_tchar gCallbackRelPath[MAXPATHLEN];
   283 static NS_tchar gCallbackBackupPath[MAXPATHLEN];
   284 #endif
   286 static const NS_tchar kWhitespace[] = NS_T(" \t");
   287 static const NS_tchar kNL[] = NS_T("\r\n");
   288 static const NS_tchar kQuote[] = NS_T("\"");
   290 static inline size_t
   291 mmin(size_t a, size_t b)
   292 {
   293   return (a > b) ? b : a;
   294 }
   296 static NS_tchar*
   297 mstrtok(const NS_tchar *delims, NS_tchar **str)
   298 {
   299   if (!*str || !**str)
   300     return nullptr;
   302   // skip leading "whitespace"
   303   NS_tchar *ret = *str;
   304   const NS_tchar *d;
   305   do {
   306     for (d = delims; *d != NS_T('\0'); ++d) {
   307       if (*ret == *d) {
   308         ++ret;
   309         break;
   310       }
   311     }
   312   } while (*d);
   314   if (!*ret) {
   315     *str = ret;
   316     return nullptr;
   317   }
   319   NS_tchar *i = ret;
   320   do {
   321     for (d = delims; *d != NS_T('\0'); ++d) {
   322       if (*i == *d) {
   323         *i = NS_T('\0');
   324         *str = ++i;
   325         return ret;
   326       }
   327     }
   328     ++i;
   329   } while (*i);
   331   *str = nullptr;
   332   return ret;
   333 }
   335 #ifdef XP_WIN
   336 /**
   337  * Coverts a relative update path to a full path for Windows.
   338  *
   339  * @param  relpath
   340  *         The relative path to convert to a full path.
   341  * @return valid filesystem full path or nullptr if memory allocation fails.
   342  */
   343 static NS_tchar*
   344 get_full_path(const NS_tchar *relpath)
   345 {
   346   size_t lendestpath = NS_tstrlen(gDestPath);
   347   size_t lenrelpath = NS_tstrlen(relpath);
   348   NS_tchar *s = (NS_tchar *) malloc((lendestpath + lenrelpath + 1) * sizeof(NS_tchar));
   349   if (!s)
   350     return nullptr;
   352   NS_tchar *c = s;
   354   NS_tstrcpy(c, gDestPath);
   355   c += lendestpath;
   356   NS_tstrcat(c, relpath);
   357   c += lenrelpath;
   358   *c = NS_T('\0');
   359   c++;
   360   return s;
   361 }
   362 #endif
   364 /**
   365  * Gets the platform specific path and performs simple checks to the path. If
   366  * the path checks don't pass nullptr will be returned.
   367  *
   368  * @param  line
   369  *         The line from the manifest that contains the path.
   370  * @param  isdir
   371  *         Whether the path is a directory path. Defaults to false.
   372  * @param  islinktarget
   373  *         Whether the path is a symbolic link target. Defaults to false.
   374  * @return valid filesystem path or nullptr if the path checks fail.
   375  */
   376 static NS_tchar*
   377 get_valid_path(NS_tchar **line, bool isdir = false, bool islinktarget = false)
   378 {
   379   NS_tchar *path = mstrtok(kQuote, line);
   380   if (!path) {
   381     LOG(("get_valid_path: unable to determine path: " LOG_S, line));
   382     return nullptr;
   383   }
   385   // All paths must be relative from the current working directory
   386   if (path[0] == NS_T('/')) {
   387     LOG(("get_valid_path: path must be relative: " LOG_S, path));
   388     return nullptr;
   389   }
   391 #ifdef XP_WIN
   392   // All paths must be relative from the current working directory
   393   if (path[0] == NS_T('\\') || path[1] == NS_T(':')) {
   394     LOG(("get_valid_path: path must be relative: " LOG_S, path));
   395     return nullptr;
   396   }
   397 #endif
   399   if (isdir) {
   400     // Directory paths must have a trailing forward slash.
   401     if (path[NS_tstrlen(path) - 1] != NS_T('/')) {
   402       LOG(("get_valid_path: directory paths must have a trailing forward " \
   403            "slash: " LOG_S, path));
   404       return nullptr;
   405     }
   407     // Remove the trailing forward slash because stat on Windows will return
   408     // ENOENT if the path has a trailing slash.
   409     path[NS_tstrlen(path) - 1] = NS_T('\0');
   410   }
   412   if (!islinktarget) {
   413     // Don't allow relative paths that resolve to a parent directory.
   414     if (NS_tstrstr(path, NS_T("..")) != nullptr) {
   415       LOG(("get_valid_path: paths must not contain '..': " LOG_S, path));
   416       return nullptr;
   417     }
   418   }
   420   return path;
   421 }
   423 static NS_tchar*
   424 get_quoted_path(const NS_tchar *path)
   425 {
   426   size_t lenQuote = NS_tstrlen(kQuote);
   427   size_t lenPath = NS_tstrlen(path);
   428   size_t len = lenQuote + lenPath + lenQuote + 1;
   430   NS_tchar *s = (NS_tchar *) malloc(len * sizeof(NS_tchar));
   431   if (!s)
   432     return nullptr;
   434   NS_tchar *c = s;
   435   NS_tstrcpy(c, kQuote);
   436   c += lenQuote;
   437   NS_tstrcat(c, path);
   438   c += lenPath;
   439   NS_tstrcat(c, kQuote);
   440   c += lenQuote;
   441   *c = NS_T('\0');
   442   c++;
   443   return s;
   444 }
   446 static void ensure_write_permissions(const NS_tchar *path)
   447 {
   448 #ifdef XP_WIN
   449   (void) _wchmod(path, _S_IREAD | _S_IWRITE);
   450 #else
   451   struct stat fs;
   452   if (!NS_tlstat(path, &fs) && !S_ISLNK(fs.st_mode)
   453       && !(fs.st_mode & S_IWUSR)) {
   454     (void)chmod(path, fs.st_mode | S_IWUSR);
   455   }
   456 #endif
   457 }
   459 static int ensure_remove(const NS_tchar *path)
   460 {
   461   ensure_write_permissions(path);
   462   int rv = NS_tremove(path);
   463   if (rv)
   464     LOG(("ensure_remove: failed to remove file: " LOG_S ", rv: %d, err: %d",
   465          path, rv, errno));
   466   return rv;
   467 }
   469 // Remove the directory pointed to by path and all of its files and sub-directories.
   470 static int ensure_remove_recursive(const NS_tchar *path)
   471 {
   472   // We use lstat rather than stat here so that we can successfully remove
   473   // symlinks.
   474   struct stat sInfo;
   475   int rv = NS_tlstat(path, &sInfo);
   476   if (rv) {
   477     // This error is benign
   478     return rv;
   479   }
   480   if (!S_ISDIR(sInfo.st_mode)) {
   481     return ensure_remove(path);
   482   }
   484   NS_tDIR *dir;
   485   NS_tdirent *entry;
   487   dir = NS_topendir(path);
   488   if (!dir) {
   489     LOG(("ensure_remove_recursive: path is not a directory: " LOG_S ", rv: %d, err: %d",
   490          path, rv, errno));
   491     return rv;
   492   }
   494   while ((entry = NS_treaddir(dir)) != 0) {
   495     if (NS_tstrcmp(entry->d_name, NS_T(".")) &&
   496         NS_tstrcmp(entry->d_name, NS_T(".."))) {
   497       NS_tchar childPath[MAXPATHLEN];
   498       NS_tsnprintf(childPath, sizeof(childPath)/sizeof(childPath[0]),
   499                    NS_T("%s/%s"), path, entry->d_name);
   500       rv = ensure_remove_recursive(childPath);
   501       if (rv) {
   502         break;
   503       }
   504     }
   505   }
   507   NS_tclosedir(dir);
   509   if (rv == OK) {
   510     ensure_write_permissions(path);
   511     rv = NS_trmdir(path);
   512     if (rv) {
   513       LOG(("ensure_remove_recursive: path is not a directory: " LOG_S ", rv: %d, err: %d",
   514            path, rv, errno));
   515     }
   516   }
   517   return rv;
   518 }
   520 static bool is_read_only(const NS_tchar *flags)
   521 {
   522   size_t length = NS_tstrlen(flags);
   523   if (length == 0)
   524     return false;
   526   // Make sure the string begins with "r"
   527   if (flags[0] != NS_T('r'))
   528     return false;
   530   // Look for "r+" or "r+b"
   531   if (length > 1 && flags[1] == NS_T('+'))
   532     return false;
   534   // Look for "rb+"
   535   if (NS_tstrcmp(flags, NS_T("rb+")) == 0)
   536     return false;
   538   return true;
   539 }
   541 static FILE* ensure_open(const NS_tchar *path, const NS_tchar *flags, unsigned int options)
   542 {
   543   ensure_write_permissions(path);
   544   FILE* f = NS_tfopen(path, flags);
   545   if (is_read_only(flags)) {
   546     // Don't attempt to modify the file permissions if the file is being opened
   547     // in read-only mode.
   548     return f;
   549   }
   550   if (NS_tchmod(path, options) != 0) {
   551     if (f != nullptr) {
   552       fclose(f);
   553     }
   554     return nullptr;
   555   }
   556   struct stat ss;
   557   if (NS_tstat(path, &ss) != 0 || ss.st_mode != options) {
   558     if (f != nullptr) {
   559       fclose(f);
   560     }
   561     return nullptr;
   562   }
   563   return f;
   564 }
   566 // Ensure that the directory containing this file exists.
   567 static int ensure_parent_dir(const NS_tchar *path)
   568 {
   569   int rv = OK;
   571   NS_tchar *slash = (NS_tchar *) NS_tstrrchr(path, NS_T('/'));
   572   if (slash) {
   573     *slash = NS_T('\0');
   574     rv = ensure_parent_dir(path);
   575     // Only attempt to create the directory if we're not at the root
   576     if (rv == OK && *path) {
   577       rv = NS_tmkdir(path, 0755);
   578       // If the directory already exists, then ignore the error.
   579       if (rv < 0 && errno != EEXIST) {
   580         LOG(("ensure_parent_dir: failed to create directory: " LOG_S ", " \
   581              "err: %d", path, errno));
   582         rv = WRITE_ERROR;
   583       } else {
   584         rv = OK;
   585       }
   586     }
   587     *slash = NS_T('/');
   588   }
   589   return rv;
   590 }
   592 #ifdef XP_UNIX
   593 static int ensure_copy_symlink(const NS_tchar *path, const NS_tchar *dest)
   594 {
   595   // Copy symlinks by creating a new symlink to the same target
   596   NS_tchar target[MAXPATHLEN + 1] = {NS_T('\0')};
   597   int rv = readlink(path, target, MAXPATHLEN);
   598   if (rv == -1) {
   599     LOG(("ensure_copy_symlink: failed to read the link: " LOG_S ", err: %d",
   600          path, errno));
   601     return READ_ERROR;
   602   }
   603   rv = symlink(target, dest);
   604   if (rv == -1) {
   605     LOG(("ensure_copy_symlink: failed to create the new link: " LOG_S ", target: " LOG_S " err: %d",
   606          dest, target, errno));
   607     return READ_ERROR;
   608   }
   609   return 0;
   610 }
   611 #endif
   613 #if MAYBE_USE_HARD_LINKS
   614 /*
   615  * Creates a hardlink (destFilename) which points to the existing file
   616  * (srcFilename).
   617  *
   618  * @return 0 if successful, an error otherwise
   619  */
   621 static int
   622 create_hard_link(const NS_tchar *srcFilename, const NS_tchar *destFilename)
   623 {
   624   if (link(srcFilename, destFilename) < 0) {
   625     LOG(("link(%s, %s) failed errno = %d", srcFilename, destFilename, errno));
   626     return WRITE_ERROR;
   627   }
   628   return OK;
   629 }
   630 #endif
   632 // Copy the file named path onto a new file named dest.
   633 static int ensure_copy(const NS_tchar *path, const NS_tchar *dest)
   634 {
   635 #ifdef XP_WIN
   636   // Fast path for Windows
   637   bool result = CopyFileW(path, dest, false);
   638   if (!result) {
   639     LOG(("ensure_copy: failed to copy the file " LOG_S " over to " LOG_S ", lasterr: %x",
   640          path, dest, GetLastError()));
   641     return WRITE_ERROR;
   642   }
   643   return 0;
   644 #else
   645   struct stat ss;
   646   int rv = NS_tlstat(path, &ss);
   647   if (rv) {
   648     LOG(("ensure_copy: failed to read file status info: " LOG_S ", err: %d",
   649          path, errno));
   650     return READ_ERROR;
   651   }
   653   if (S_ISLNK(ss.st_mode)) {
   654     return ensure_copy_symlink(path, dest);
   655   }
   657 #if MAYBE_USE_HARD_LINKS
   658   if (sUseHardLinks) {
   659     if (!create_hard_link(path, dest)) {
   660       return OK;
   661     }
   662     // Since we failed to create the hard link, fall through and copy the file.
   663     sUseHardLinks = false;
   664   }
   665 #endif
   667   AutoFile infile = ensure_open(path, NS_T("rb"), ss.st_mode);
   668   if (!infile) {
   669     LOG(("ensure_copy: failed to open the file for reading: " LOG_S ", err: %d",
   670          path, errno));
   671     return READ_ERROR;
   672   }
   673   AutoFile outfile = ensure_open(dest, NS_T("wb"), ss.st_mode);
   674   if (!outfile) {
   675     LOG(("ensure_copy: failed to open the file for writing: " LOG_S ", err: %d",
   676          dest, errno));
   677     return WRITE_ERROR;
   678   }
   680   // This block size was chosen pretty arbitrarily but seems like a reasonable
   681   // compromise. For example, the optimal block size on a modern OS X machine
   682   // is 100k */
   683   const int blockSize = 32 * 1024;
   684   void* buffer = malloc(blockSize);
   685   if (!buffer)
   686     return UPDATER_MEM_ERROR;
   688   while (!feof(infile.get())) {
   689     size_t read = fread(buffer, 1, blockSize, infile);
   690     if (ferror(infile.get())) {
   691       LOG(("ensure_copy: failed to read the file: " LOG_S ", err: %d",
   692            path, errno));
   693       free(buffer);
   694       return READ_ERROR;
   695     }
   697     size_t written = 0;
   699     while (written < read) {
   700       size_t chunkWritten = fwrite(buffer, 1, read - written, outfile);
   701       if (chunkWritten <= 0) {
   702         LOG(("ensure_copy: failed to write the file: " LOG_S ", err: %d",
   703              dest, errno));
   704         free(buffer);
   705         return WRITE_ERROR;
   706       }
   708       written += chunkWritten;
   709     }
   710   }
   712   rv = NS_tchmod(dest, ss.st_mode);
   714   free(buffer);
   715   return rv;
   716 #endif
   717 }
   719 template <unsigned N>
   720 struct copy_recursive_skiplist {
   721   NS_tchar paths[N][MAXPATHLEN];
   723   void append(unsigned index, const NS_tchar *path, const NS_tchar *suffix) {
   724     NS_tsnprintf(paths[index], MAXPATHLEN, NS_T("%s/%s"), path, suffix);
   725   }
   726   bool find(const NS_tchar *path) {
   727     for (unsigned i = 0; i < N; ++i) {
   728       if (!NS_tstricmp(paths[i], path)) {
   729         return true;
   730       }
   731     }
   732     return false;
   733   }
   734 };
   736 // Copy all of the files and subdirectories under path to a new directory named dest.
   737 // The path names in the skiplist will be skipped and will not be copied.
   738 template <unsigned N>
   739 static int ensure_copy_recursive(const NS_tchar *path, const NS_tchar *dest,
   740                                  copy_recursive_skiplist<N>& skiplist)
   741 {
   742   struct stat sInfo;
   743   int rv = NS_tlstat(path, &sInfo);
   744   if (rv) {
   745     LOG(("ensure_copy_recursive: path doesn't exist: " LOG_S ", rv: %d, err: %d",
   746          path, rv, errno));
   747     return READ_ERROR;
   748   }
   750 #ifndef XP_WIN
   751   if (S_ISLNK(sInfo.st_mode)) {
   752     return ensure_copy_symlink(path, dest);
   753   }
   754 #endif
   756   if (!S_ISDIR(sInfo.st_mode)) {
   757     return ensure_copy(path, dest);
   758   }
   760   rv = NS_tmkdir(dest, sInfo.st_mode);
   761   if (rv < 0 && errno != EEXIST) {
   762     LOG(("ensure_copy_recursive: could not create destination directory: " LOG_S ", rv: %d, err: %d",
   763          path, rv, errno));
   764     return WRITE_ERROR;
   765   }
   767   NS_tDIR *dir;
   768   NS_tdirent *entry;
   770   dir = NS_topendir(path);
   771   if (!dir) {
   772     LOG(("ensure_copy_recursive: path is not a directory: " LOG_S ", rv: %d, err: %d",
   773          path, rv, errno));
   774     return READ_ERROR;
   775   }
   777   while ((entry = NS_treaddir(dir)) != 0) {
   778     if (NS_tstrcmp(entry->d_name, NS_T(".")) &&
   779         NS_tstrcmp(entry->d_name, NS_T(".."))) {
   780       NS_tchar childPath[MAXPATHLEN];
   781       NS_tsnprintf(childPath, sizeof(childPath)/sizeof(childPath[0]),
   782                    NS_T("%s/%s"), path, entry->d_name);
   783       if (skiplist.find(childPath)) {
   784         continue;
   785       }
   786       NS_tchar childPathDest[MAXPATHLEN];
   787       NS_tsnprintf(childPathDest, sizeof(childPathDest)/sizeof(childPathDest[0]),
   788                    NS_T("%s/%s"), dest, entry->d_name);
   789       rv = ensure_copy_recursive(childPath, childPathDest, skiplist);
   790       if (rv) {
   791         break;
   792       }
   793     }
   794   }
   796   return rv;
   797 }
   799 // Renames the specified file to the new file specified. If the destination file
   800 // exists it is removed.
   801 static int rename_file(const NS_tchar *spath, const NS_tchar *dpath,
   802                        bool allowDirs = false)
   803 {
   804   int rv = ensure_parent_dir(dpath);
   805   if (rv)
   806     return rv;
   808   struct stat spathInfo;
   809   rv = NS_tlstat(spath, &spathInfo);
   810   if (rv) {
   811     LOG(("rename_file: failed to read file status info: " LOG_S ", " \
   812          "err: %d", spath, errno));
   813     return READ_ERROR;
   814   }
   816 #ifdef XP_WIN
   817   if (!S_ISREG(spathInfo.st_mode))
   818 #else
   819   if (!S_ISREG(spathInfo.st_mode) && !S_ISLNK(spathInfo.st_mode))
   820 #endif
   821   {
   822     if (allowDirs && !S_ISDIR(spathInfo.st_mode)) {
   823       LOG(("rename_file: path present, but not a file: " LOG_S ", err: %d",
   824            spath, errno));
   825       return UNEXPECTED_FILE_OPERATION_ERROR;
   826     } else {
   827       LOG(("rename_file: proceeding to rename the directory"));
   828     }
   829   }
   831 #ifdef XP_WIN
   832   if (!NS_taccess(dpath, F_OK))
   833 #else
   834   if (!S_ISLNK(spathInfo.st_mode) && !NS_taccess(dpath, F_OK))
   835 #endif
   836   {
   837     if (ensure_remove(dpath)) {
   838       LOG(("rename_file: destination file exists and could not be " \
   839            "removed: " LOG_S, dpath));
   840       return WRITE_ERROR;
   841     }
   842   }
   844   if (NS_trename(spath, dpath) != 0) {
   845     LOG(("rename_file: failed to rename file - src: " LOG_S ", " \
   846          "dst:" LOG_S ", err: %d", spath, dpath, errno));
   847     return WRITE_ERROR;
   848   }
   850   return OK;
   851 }
   853 //-----------------------------------------------------------------------------
   855 // Create a backup of the specified file by renaming it.
   856 static int backup_create(const NS_tchar *path)
   857 {
   858   NS_tchar backup[MAXPATHLEN];
   859   NS_tsnprintf(backup, sizeof(backup)/sizeof(backup[0]),
   860                NS_T("%s") BACKUP_EXT, path);
   862   return rename_file(path, backup);
   863 }
   865 // Rename the backup of the specified file that was created by renaming it back
   866 // to the original file.
   867 static int backup_restore(const NS_tchar *path)
   868 {
   869   NS_tchar backup[MAXPATHLEN];
   870   NS_tsnprintf(backup, sizeof(backup)/sizeof(backup[0]),
   871                NS_T("%s") BACKUP_EXT, path);
   873   bool isLink = false;
   874 #ifndef XP_WIN
   875   struct stat linkInfo;
   876   int rv = NS_tlstat(path, &linkInfo);
   877   if (!rv) {
   878     LOG(("backup_restore: cannot get info for backup file: " LOG_S, backup));
   879     return OK;
   880   }
   881   isLink = S_ISLNK(linkInfo.st_mode);
   882 #endif
   884   if (!isLink && NS_taccess(backup, F_OK)) {
   885     LOG(("backup_restore: backup file doesn't exist: " LOG_S, backup));
   886     return OK;
   887   }
   889   return rename_file(backup, path);
   890 }
   892 // Discard the backup of the specified file that was created by renaming it.
   893 static int backup_discard(const NS_tchar *path)
   894 {
   895   NS_tchar backup[MAXPATHLEN];
   896   NS_tsnprintf(backup, sizeof(backup)/sizeof(backup[0]),
   897                NS_T("%s") BACKUP_EXT, path);
   899   bool isLink = false;
   900 #ifndef XP_WIN
   901   struct stat linkInfo;
   902   int rv2 = NS_tlstat(backup, &linkInfo);
   903   if (rv2) {
   904     return OK;	// File does not exist; nothing to do.
   905   }
   906   isLink = S_ISLNK(linkInfo.st_mode);
   907 #endif
   909   // Nothing to discard
   910   if (!isLink && NS_taccess(backup, F_OK)) {
   911     return OK;
   912   }
   914   int rv = ensure_remove(backup);
   915 #if defined(XP_WIN)
   916   if (rv && !sStagedUpdate && !sReplaceRequest) {
   917     LOG(("backup_discard: unable to remove: " LOG_S, backup));
   918     NS_tchar path[MAXPATHLEN];
   919     GetTempFileNameW(DELETE_DIR, L"moz", 0, path);
   920     if (rename_file(backup, path)) {
   921       LOG(("backup_discard: failed to rename file:" LOG_S ", dst:" LOG_S,
   922            backup, path));
   923       return WRITE_ERROR;
   924     }
   925     // The MoveFileEx call to remove the file on OS reboot will fail if the
   926     // process doesn't have write access to the HKEY_LOCAL_MACHINE registry key
   927     // but this is ok since the installer / uninstaller will delete the
   928     // directory containing the file along with its contents after an update is
   929     // applied, on reinstall, and on uninstall.
   930     if (MoveFileEx(path, nullptr, MOVEFILE_DELAY_UNTIL_REBOOT)) {
   931       LOG(("backup_discard: file renamed and will be removed on OS " \
   932            "reboot: " LOG_S, path));
   933     } else {
   934       LOG(("backup_discard: failed to schedule OS reboot removal of " \
   935            "file: " LOG_S, path));
   936     }
   937   }
   938 #else
   939   if (rv)
   940     return WRITE_ERROR;
   941 #endif
   943   return OK;
   944 }
   946 // Helper function for post-processing a temporary backup.
   947 static void backup_finish(const NS_tchar *path, int status)
   948 {
   949   if (status == OK)
   950     backup_discard(path);
   951   else
   952     backup_restore(path);
   953 }
   955 //-----------------------------------------------------------------------------
   957 static int DoUpdate();
   959 class Action
   960 {
   961 public:
   962   Action() : mProgressCost(1), mNext(nullptr) { }
   963   virtual ~Action() { }
   965   virtual int Parse(NS_tchar *line) = 0;
   967   // Do any preprocessing to ensure that the action can be performed.  Execute
   968   // will be called if this Action and all others return OK from this method.
   969   virtual int Prepare() = 0;
   971   // Perform the operation.  Return OK to indicate success.  After all actions
   972   // have been executed, Finish will be called.  A requirement of Execute is
   973   // that its operation be reversable from Finish.
   974   virtual int Execute() = 0;
   976   // Finish is called after execution of all actions.  If status is OK, then
   977   // all actions were successfully executed.  Otherwise, some action failed.
   978   virtual void Finish(int status) = 0;
   980   int mProgressCost;
   981 private:
   982   Action* mNext;
   984   friend class ActionList;
   985 };
   987 class RemoveFile : public Action
   988 {
   989 public:
   990   RemoveFile() : mFile(nullptr), mSkip(0), mIsLink(0) { }
   992   int Parse(NS_tchar *line);
   993   int Prepare();
   994   int Execute();
   995   void Finish(int status);
   997 private:
   998   const NS_tchar *mFile;
   999   int mSkip;
  1000   int mIsLink;
  1001 };
  1003 int
  1004 RemoveFile::Parse(NS_tchar *line)
  1006   // format "<deadfile>"
  1008   mFile = get_valid_path(&line);
  1009   if (!mFile)
  1010     return PARSE_ERROR;
  1012   return OK;
  1015 int
  1016 RemoveFile::Prepare()
  1018   int rv;
  1019 #ifndef XP_WIN
  1020   struct stat linkInfo;
  1021   rv = NS_tlstat(mFile, &linkInfo);
  1022   mIsLink = ((0 == rv) && S_ISLNK(linkInfo.st_mode));
  1023 #endif
  1025   if (!mIsLink) {
  1026     // Skip the file if it already doesn't exist.
  1027     rv = NS_taccess(mFile, F_OK);
  1028     if (rv) {
  1029       mSkip = 1;
  1030       mProgressCost = 0;
  1031       return OK;
  1035   LOG(("PREPARE REMOVEFILE " LOG_S, mFile));
  1037   if (!mIsLink) {
  1038     // Make sure that we're actually a file...
  1039     struct stat fileInfo;
  1040     rv = NS_tstat(mFile, &fileInfo);
  1041     if (rv) {
  1042       LOG(("failed to read file status info: " LOG_S ", err: %d", mFile,
  1043            errno));
  1044       return READ_ERROR;
  1047     if (!S_ISREG(fileInfo.st_mode)) {
  1048       LOG(("path present, but not a file: " LOG_S, mFile));
  1049       return UNEXPECTED_FILE_OPERATION_ERROR;
  1053   NS_tchar *slash = (NS_tchar *) NS_tstrrchr(mFile, NS_T('/'));
  1054   if (slash) {
  1055     *slash = NS_T('\0');
  1056     rv = NS_taccess(mFile, W_OK);
  1057     *slash = NS_T('/');
  1058   } else {
  1059     rv = NS_taccess(NS_T("."), W_OK);
  1062   if (rv) {
  1063     LOG(("access failed: %d", errno));
  1064     return WRITE_ERROR;
  1067   return OK;
  1070 int
  1071 RemoveFile::Execute()
  1073   if (mSkip)
  1074     return OK;
  1076   LOG(("EXECUTE REMOVEFILE " LOG_S, mFile));
  1078   // The file is checked for existence here and in Prepare since it might have
  1079   // been removed by a separate instruction: bug 311099.
  1080   int rv = 0;
  1081   if (mIsLink) {
  1082     struct stat linkInfo;
  1083     rv = NS_tlstat(mFile, &linkInfo);
  1084   } else {
  1085     rv = NS_taccess(mFile, F_OK);
  1087   if (rv) {
  1088     LOG(("file cannot be removed because it does not exist; skipping"));
  1089     mSkip = 1;
  1090     return OK;
  1093   // Rename the old file. It will be removed in Finish.
  1094   rv = backup_create(mFile);
  1095   if (rv) {
  1096     LOG(("backup_create failed: %d", rv));
  1097     return rv;
  1100   return OK;
  1103 void
  1104 RemoveFile::Finish(int status)
  1106   if (mSkip)
  1107     return;
  1109   LOG(("FINISH REMOVEFILE " LOG_S, mFile));
  1111   backup_finish(mFile, status);
  1114 class RemoveDir : public Action
  1116 public:
  1117   RemoveDir() : mDir(nullptr), mSkip(0) { }
  1119   virtual int Parse(NS_tchar *line);
  1120   virtual int Prepare(); // check that the source dir exists
  1121   virtual int Execute();
  1122   virtual void Finish(int status);
  1124 private:
  1125   const NS_tchar *mDir;
  1126   int mSkip;
  1127 };
  1129 int
  1130 RemoveDir::Parse(NS_tchar *line)
  1132   // format "<deaddir>/"
  1134   mDir = get_valid_path(&line, true);
  1135   if (!mDir)
  1136     return PARSE_ERROR;
  1138   return OK;
  1141 int
  1142 RemoveDir::Prepare()
  1144   // We expect the directory to exist if we are to remove it.
  1145   int rv = NS_taccess(mDir, F_OK);
  1146   if (rv) {
  1147     mSkip = 1;
  1148     mProgressCost = 0;
  1149     return OK;
  1152   LOG(("PREPARE REMOVEDIR " LOG_S "/", mDir));
  1154   // Make sure that we're actually a dir.
  1155   struct stat dirInfo;
  1156   rv = NS_tstat(mDir, &dirInfo);
  1157   if (rv) {
  1158     LOG(("failed to read directory status info: " LOG_S ", err: %d", mDir,
  1159          errno));
  1160     return READ_ERROR;
  1163   if (!S_ISDIR(dirInfo.st_mode)) {
  1164     LOG(("path present, but not a directory: " LOG_S, mDir));
  1165     return UNEXPECTED_FILE_OPERATION_ERROR;
  1168   rv = NS_taccess(mDir, W_OK);
  1169   if (rv) {
  1170     LOG(("access failed: %d, %d", rv, errno));
  1171     return WRITE_ERROR;
  1174   return OK;
  1177 int
  1178 RemoveDir::Execute()
  1180   if (mSkip)
  1181     return OK;
  1183   LOG(("EXECUTE REMOVEDIR " LOG_S "/", mDir));
  1185   // The directory is checked for existence at every step since it might have
  1186   // been removed by a separate instruction: bug 311099.
  1187   int rv = NS_taccess(mDir, F_OK);
  1188   if (rv) {
  1189     LOG(("directory no longer exists; skipping"));
  1190     mSkip = 1;
  1193   return OK;
  1196 void
  1197 RemoveDir::Finish(int status)
  1199   if (mSkip || status != OK)
  1200     return;
  1202   LOG(("FINISH REMOVEDIR " LOG_S "/", mDir));
  1204   // The directory is checked for existence at every step since it might have
  1205   // been removed by a separate instruction: bug 311099.
  1206   int rv = NS_taccess(mDir, F_OK);
  1207   if (rv) {
  1208     LOG(("directory no longer exists; skipping"));
  1209     return;
  1213   if (status == OK) {
  1214     if (NS_trmdir(mDir)) {
  1215       LOG(("non-fatal error removing directory: " LOG_S "/, rv: %d, err: %d",
  1216            mDir, rv, errno));
  1221 class AddFile : public Action
  1223 public:
  1224   AddFile() : mFile(nullptr)
  1225             , mAdded(false)
  1226             { }
  1228   virtual int Parse(NS_tchar *line);
  1229   virtual int Prepare();
  1230   virtual int Execute();
  1231   virtual void Finish(int status);
  1233 private:
  1234   const NS_tchar *mFile;
  1235   bool mAdded;
  1236 };
  1238 int
  1239 AddFile::Parse(NS_tchar *line)
  1241   // format "<newfile>"
  1243   mFile = get_valid_path(&line);
  1244   if (!mFile)
  1245     return PARSE_ERROR;
  1247   return OK;
  1250 int
  1251 AddFile::Prepare()
  1253   LOG(("PREPARE ADD " LOG_S, mFile));
  1255   return OK;
  1258 int
  1259 AddFile::Execute()
  1261   LOG(("EXECUTE ADD " LOG_S, mFile));
  1263   int rv;
  1265   // First make sure that we can actually get rid of any existing file.
  1266   rv = NS_taccess(mFile, F_OK);
  1267   if (rv == 0) {
  1268     rv = backup_create(mFile);
  1269     if (rv)
  1270       return rv;
  1271   } else {
  1272     rv = ensure_parent_dir(mFile);
  1273     if (rv)
  1274       return rv;
  1277 #ifdef XP_WIN
  1278   char sourcefile[MAXPATHLEN];
  1279   if (!WideCharToMultiByte(CP_UTF8, 0, mFile, -1, sourcefile, MAXPATHLEN,
  1280                            nullptr, nullptr)) {
  1281     LOG(("error converting wchar to utf8: %d", GetLastError()));
  1282     return STRING_CONVERSION_ERROR;
  1285   rv = gArchiveReader.ExtractFile(sourcefile, mFile);
  1286 #else
  1287   rv = gArchiveReader.ExtractFile(mFile, mFile);
  1288 #endif
  1289   if (!rv) {
  1290     mAdded = true;
  1292   return rv;
  1295 void
  1296 AddFile::Finish(int status)
  1298   LOG(("FINISH ADD " LOG_S, mFile));
  1299   // When there is an update failure and a file has been added it is removed
  1300   // here since there might not be a backup to replace it.
  1301   if (status && mAdded)
  1302     NS_tremove(mFile);
  1303   backup_finish(mFile, status);
  1306 class PatchFile : public Action
  1308 public:
  1309   PatchFile() : mPatchIndex(-1), buf(nullptr) { }
  1311   virtual ~PatchFile();
  1313   virtual int Parse(NS_tchar *line);
  1314   virtual int Prepare(); // should check for patch file and for checksum here
  1315   virtual int Execute();
  1316   virtual void Finish(int status);
  1318 private:
  1319   int LoadSourceFile(FILE* ofile);
  1321   static int sPatchIndex;
  1323   const NS_tchar *mPatchFile;
  1324   const NS_tchar *mFile;
  1325   int mPatchIndex;
  1326   MBSPatchHeader header;
  1327   unsigned char *buf;
  1328   NS_tchar spath[MAXPATHLEN];
  1329 };
  1331 int PatchFile::sPatchIndex = 0;
  1333 PatchFile::~PatchFile()
  1335   // delete the temporary patch file
  1336   if (spath[0])
  1337     NS_tremove(spath);
  1339   if (buf)
  1340     free(buf);
  1343 int
  1344 PatchFile::LoadSourceFile(FILE* ofile)
  1346   struct stat os;
  1347   int rv = fstat(fileno((FILE *)ofile), &os);
  1348   if (rv) {
  1349     LOG(("LoadSourceFile: unable to stat destination file: " LOG_S ", " \
  1350          "err: %d", mFile, errno));
  1351     return READ_ERROR;
  1354   if (uint32_t(os.st_size) != header.slen) {
  1355     LOG(("LoadSourceFile: destination file size %d does not match expected size %d",
  1356          uint32_t(os.st_size), header.slen));
  1357     return UNEXPECTED_FILE_OPERATION_ERROR;
  1360   buf = (unsigned char *) malloc(header.slen);
  1361   if (!buf)
  1362     return UPDATER_MEM_ERROR;
  1364   size_t r = header.slen;
  1365   unsigned char *rb = buf;
  1366   while (r) {
  1367     const size_t count = mmin(SSIZE_MAX, r);
  1368     size_t c = fread(rb, 1, count, ofile);
  1369     if (c != count) {
  1370       LOG(("LoadSourceFile: error reading destination file: " LOG_S,
  1371            mFile));
  1372       return READ_ERROR;
  1375     r -= c;
  1376     rb += c;
  1379   // Verify that the contents of the source file correspond to what we expect.
  1381   unsigned int crc = crc32(buf, header.slen);
  1383   if (crc != header.scrc32) {
  1384     LOG(("LoadSourceFile: destination file crc %d does not match expected " \
  1385          "crc %d", crc, header.scrc32));
  1386     return CRC_ERROR;
  1389   return OK;
  1392 int
  1393 PatchFile::Parse(NS_tchar *line)
  1395   // format "<patchfile>" "<filetopatch>"
  1397   // Get the path to the patch file inside of the mar
  1398   mPatchFile = mstrtok(kQuote, &line);
  1399   if (!mPatchFile)
  1400     return PARSE_ERROR;
  1402   // consume whitespace between args
  1403   NS_tchar *q = mstrtok(kQuote, &line);
  1404   if (!q)
  1405     return PARSE_ERROR;
  1407   mFile = get_valid_path(&line);
  1408   if (!mFile)
  1409     return PARSE_ERROR;
  1411   return OK;
  1414 int
  1415 PatchFile::Prepare()
  1417   LOG(("PREPARE PATCH " LOG_S, mFile));
  1419   // extract the patch to a temporary file
  1420   mPatchIndex = sPatchIndex++;
  1422   NS_tsnprintf(spath, sizeof(spath)/sizeof(spath[0]),
  1423                NS_T("%s/updating/%d.patch"), gDestinationPath, mPatchIndex);
  1425   NS_tremove(spath);
  1427   FILE *fp = NS_tfopen(spath, NS_T("wb"));
  1428   if (!fp)
  1429     return WRITE_ERROR;
  1431 #ifdef XP_WIN
  1432   char sourcefile[MAXPATHLEN];
  1433   if (!WideCharToMultiByte(CP_UTF8, 0, mPatchFile, -1, sourcefile, MAXPATHLEN,
  1434                            nullptr, nullptr)) {
  1435     LOG(("error converting wchar to utf8: %d", GetLastError()));
  1436     return STRING_CONVERSION_ERROR;
  1439   int rv = gArchiveReader.ExtractFileToStream(sourcefile, fp);
  1440 #else
  1441   int rv = gArchiveReader.ExtractFileToStream(mPatchFile, fp);
  1442 #endif
  1443   fclose(fp);
  1444   return rv;
  1447 int
  1448 PatchFile::Execute()
  1450   LOG(("EXECUTE PATCH " LOG_S, mFile));
  1452   AutoFile pfile = NS_tfopen(spath, NS_T("rb"));
  1453   if (pfile == nullptr)
  1454     return READ_ERROR;
  1456   int rv = MBS_ReadHeader(pfile, &header);
  1457   if (rv)
  1458     return rv;
  1460   FILE *origfile = nullptr;
  1461 #ifdef XP_WIN
  1462   if (NS_tstrcmp(mFile, gCallbackRelPath) == 0) {
  1463     // Read from the copy of the callback when patching since the callback can't
  1464     // be opened for reading to prevent the application from being launched.
  1465     origfile = NS_tfopen(gCallbackBackupPath, NS_T("rb"));
  1466   } else {
  1467     origfile = NS_tfopen(mFile, NS_T("rb"));
  1469 #else
  1470   origfile = NS_tfopen(mFile, NS_T("rb"));
  1471 #endif
  1473   if (!origfile) {
  1474     LOG(("unable to open destination file: " LOG_S ", err: %d", mFile,
  1475          errno));
  1476     return READ_ERROR;
  1479   rv = LoadSourceFile(origfile);
  1480   fclose(origfile);
  1481   if (rv) {
  1482     LOG(("LoadSourceFile failed"));
  1483     return rv;
  1486   // Rename the destination file if it exists before proceeding so it can be
  1487   // used to restore the file to its original state if there is an error.
  1488   struct stat ss;
  1489   rv = NS_tstat(mFile, &ss);
  1490   if (rv) {
  1491     LOG(("failed to read file status info: " LOG_S ", err: %d", mFile,
  1492          errno));
  1493     return READ_ERROR;
  1496   rv = backup_create(mFile);
  1497   if (rv)
  1498     return rv;
  1500 #if defined(HAVE_POSIX_FALLOCATE)
  1501   AutoFile ofile = ensure_open(mFile, NS_T("wb+"), ss.st_mode);
  1502   posix_fallocate(fileno((FILE *)ofile), 0, header.dlen);
  1503 #elif defined(XP_WIN)
  1504   bool shouldTruncate = true;
  1505   // Creating the file, setting the size, and then closing the file handle
  1506   // lessens fragmentation more than any other method tested. Other methods that
  1507   // have been tested are:
  1508   // 1. _chsize / _chsize_s reduced fragmentation but though not completely.
  1509   // 2. _get_osfhandle and then setting the size reduced fragmentation though
  1510   //    not completely. There are also reports of _get_osfhandle failing on
  1511   //    mingw.
  1512   HANDLE hfile = CreateFileW(mFile,
  1513                              GENERIC_WRITE,
  1514                              0,
  1515                              nullptr,
  1516                              CREATE_ALWAYS,
  1517                              FILE_ATTRIBUTE_NORMAL,
  1518                              nullptr);
  1520   if (hfile != INVALID_HANDLE_VALUE) {
  1521     if (SetFilePointer(hfile, header.dlen,
  1522                        nullptr, FILE_BEGIN) != INVALID_SET_FILE_POINTER &&
  1523         SetEndOfFile(hfile) != 0) {
  1524       shouldTruncate = false;
  1526     CloseHandle(hfile);
  1529   AutoFile ofile = ensure_open(mFile, shouldTruncate ? NS_T("wb+") : NS_T("rb+"), ss.st_mode);
  1530 #elif defined(XP_MACOSX)
  1531   AutoFile ofile = ensure_open(mFile, NS_T("wb+"), ss.st_mode);
  1532   // Modified code from FileUtils.cpp
  1533   fstore_t store = {F_ALLOCATECONTIG, F_PEOFPOSMODE, 0, header.dlen};
  1534   // Try to get a continous chunk of disk space
  1535   rv = fcntl(fileno((FILE *)ofile), F_PREALLOCATE, &store);
  1536   if (rv == -1) {
  1537     // OK, perhaps we are too fragmented, allocate non-continuous
  1538     store.fst_flags = F_ALLOCATEALL;
  1539     rv = fcntl(fileno((FILE *)ofile), F_PREALLOCATE, &store);
  1542   if (rv != -1) {
  1543     ftruncate(fileno((FILE *)ofile), header.dlen);
  1545 #else
  1546   AutoFile ofile = ensure_open(mFile, NS_T("wb+"), ss.st_mode);
  1547 #endif
  1549   if (ofile == nullptr) {
  1550     LOG(("unable to create new file: " LOG_S ", err: %d", mFile, errno));
  1551     return WRITE_ERROR;
  1554 #ifdef XP_WIN
  1555   if (!shouldTruncate) {
  1556     fseek(ofile, 0, SEEK_SET);
  1558 #endif
  1560   rv = MBS_ApplyPatch(&header, pfile, buf, ofile);
  1562   // Go ahead and do a bit of cleanup now to minimize runtime overhead.
  1563   // Set pfile to nullptr to make AutoFile close the file so it can be deleted
  1564   // on Windows.
  1565   pfile = nullptr;
  1566   NS_tremove(spath);
  1567   spath[0] = NS_T('\0');
  1568   free(buf);
  1569   buf = nullptr;
  1571   return rv;
  1574 void
  1575 PatchFile::Finish(int status)
  1577   LOG(("FINISH PATCH " LOG_S, mFile));
  1579   backup_finish(mFile, status);
  1582 class AddIfFile : public AddFile
  1584 public:
  1585   AddIfFile() : mTestFile(nullptr) { }
  1587   virtual int Parse(NS_tchar *line);
  1588   virtual int Prepare();
  1589   virtual int Execute();
  1590   virtual void Finish(int status);
  1592 protected:
  1593   const NS_tchar *mTestFile;
  1594 };
  1596 int
  1597 AddIfFile::Parse(NS_tchar *line)
  1599   // format "<testfile>" "<newfile>"
  1601   mTestFile = get_valid_path(&line);
  1602   if (!mTestFile)
  1603     return PARSE_ERROR;
  1605   // consume whitespace between args
  1606   NS_tchar *q = mstrtok(kQuote, &line);
  1607   if (!q)
  1608     return PARSE_ERROR;
  1610   return AddFile::Parse(line);
  1613 int
  1614 AddIfFile::Prepare()
  1616   // If the test file does not exist, then skip this action.
  1617   if (NS_taccess(mTestFile, F_OK)) {
  1618     mTestFile = nullptr;
  1619     return OK;
  1622   return AddFile::Prepare();
  1625 int
  1626 AddIfFile::Execute()
  1628   if (!mTestFile)
  1629     return OK;
  1631   return AddFile::Execute();
  1634 void
  1635 AddIfFile::Finish(int status)
  1637   if (!mTestFile)
  1638     return;
  1640   AddFile::Finish(status);
  1643 class AddIfNotFile : public AddFile
  1645 public:
  1646   AddIfNotFile() : mTestFile(NULL) { }
  1648   virtual int Parse(NS_tchar *line);
  1649   virtual int Prepare();
  1650   virtual int Execute();
  1651   virtual void Finish(int status);
  1653 protected:
  1654   const NS_tchar *mTestFile;
  1655 };
  1657 int
  1658 AddIfNotFile::Parse(NS_tchar *line)
  1660   // format "<testfile>" "<newfile>"
  1662   mTestFile = get_valid_path(&line);
  1663   if (!mTestFile)
  1664     return PARSE_ERROR;
  1666   // consume whitespace between args
  1667   NS_tchar *q = mstrtok(kQuote, &line);
  1668   if (!q)
  1669     return PARSE_ERROR;
  1671   return AddFile::Parse(line);
  1674 int
  1675 AddIfNotFile::Prepare()
  1677   // If the test file exists, then skip this action.
  1678   if (!NS_taccess(mTestFile, F_OK)) {
  1679     mTestFile = NULL;
  1680     return OK;
  1683   return AddFile::Prepare();
  1686 int
  1687 AddIfNotFile::Execute()
  1689   if (!mTestFile)
  1690     return OK;
  1692   return AddFile::Execute();
  1695 void
  1696 AddIfNotFile::Finish(int status)
  1698   if (!mTestFile)
  1699     return;
  1701   AddFile::Finish(status);
  1704 class PatchIfFile : public PatchFile
  1706 public:
  1707   PatchIfFile() : mTestFile(nullptr) { }
  1709   virtual int Parse(NS_tchar *line);
  1710   virtual int Prepare(); // should check for patch file and for checksum here
  1711   virtual int Execute();
  1712   virtual void Finish(int status);
  1714 private:
  1715   const NS_tchar *mTestFile;
  1716 };
  1718 int
  1719 PatchIfFile::Parse(NS_tchar *line)
  1721   // format "<testfile>" "<patchfile>" "<filetopatch>"
  1723   mTestFile = get_valid_path(&line);
  1724   if (!mTestFile)
  1725     return PARSE_ERROR;
  1727   // consume whitespace between args
  1728   NS_tchar *q = mstrtok(kQuote, &line);
  1729   if (!q)
  1730     return PARSE_ERROR;
  1732   return PatchFile::Parse(line);
  1735 int
  1736 PatchIfFile::Prepare()
  1738   // If the test file does not exist, then skip this action.
  1739   if (NS_taccess(mTestFile, F_OK)) {
  1740     mTestFile = nullptr;
  1741     return OK;
  1744   return PatchFile::Prepare();
  1747 int
  1748 PatchIfFile::Execute()
  1750   if (!mTestFile)
  1751     return OK;
  1753   return PatchFile::Execute();
  1756 void
  1757 PatchIfFile::Finish(int status)
  1759   if (!mTestFile)
  1760     return;
  1762   PatchFile::Finish(status);
  1765 #ifndef XP_WIN
  1766 class AddSymlink : public Action
  1768 public:
  1769   AddSymlink() : mLinkName(NULL)
  1770                , mTarget(NULL)
  1771                , mAdded(false)
  1772             { }
  1774   virtual int Parse(NS_tchar *line);
  1775   virtual int Prepare();
  1776   virtual int Execute();
  1777   virtual void Finish(int status);
  1779 private:
  1780   const NS_tchar *mLinkName;
  1781   const NS_tchar *mTarget;
  1782   bool mAdded;
  1783 };
  1785 int
  1786 AddSymlink::Parse(NS_tchar *line)
  1788   // format "<linkname>" "target"
  1790   mLinkName = get_valid_path(&line);
  1791   if (!mLinkName)
  1792     return PARSE_ERROR;
  1794   // consume whitespace between args
  1795   NS_tchar *q = mstrtok(kQuote, &line);
  1796   if (!q)
  1797     return PARSE_ERROR;
  1799   mTarget = get_valid_path(&line, false, true);
  1800   if (!mTarget)
  1801     return PARSE_ERROR;
  1803   return OK;
  1806 int
  1807 AddSymlink::Prepare()
  1809   LOG(("PREPARE ADDSYMLINK " LOG_S " -> " LOG_S, mLinkName, mTarget));
  1811   return OK;
  1814 int
  1815 AddSymlink::Execute()
  1817   LOG(("EXECUTE ADDSYMLINK " LOG_S " -> " LOG_S, mLinkName, mTarget));
  1819   // First make sure that we can actually get rid of any existing file or link.
  1820   struct stat linkInfo;
  1821   int rv = NS_tlstat(mLinkName, &linkInfo);
  1822   if ((0 == rv) && !S_ISLNK(linkInfo.st_mode)) {
  1823     rv = NS_taccess(mLinkName, F_OK);
  1825   if (rv == 0) {
  1826     rv = backup_create(mLinkName);
  1827     if (rv)
  1828       return rv;
  1829   } else {
  1830     rv = ensure_parent_dir(mLinkName);
  1831     if (rv)
  1832       return rv;
  1835   // Create the link.
  1836   rv = symlink(mTarget, mLinkName);
  1837   if (!rv) {
  1838     mAdded = true;
  1841   return rv;
  1844 void
  1845 AddSymlink::Finish(int status)
  1847   LOG(("FINISH ADDSYMLINK " LOG_S " -> " LOG_S, mLinkName, mTarget));
  1848   // When there is an update failure and a link has been added it is removed
  1849   // here since there might not be a backup to replace it.
  1850   if (status && mAdded)
  1851     NS_tremove(mLinkName);
  1852   backup_finish(mLinkName, status);
  1854 #endif
  1856 //-----------------------------------------------------------------------------
  1858 #ifdef XP_WIN
  1859 #include "nsWindowsRestart.cpp"
  1860 #include "nsWindowsHelpers.h"
  1861 #include "uachelper.h"
  1862 #include "pathhash.h"
  1864 #ifdef MOZ_METRO
  1865 /**
  1866  * Determines if the update came from an Immersive browser
  1867  * @return true if the update came from an immersive browser
  1868  */
  1869 bool
  1870 IsUpdateFromMetro(int argc, NS_tchar **argv)
  1872   for (int i = 0; i < argc; i++) {
  1873     if (!wcsicmp(L"-ServerName:DefaultBrowserServer", argv[i])) {
  1874       return true;
  1877   return false;
  1879 #endif
  1880 #endif
  1882 static void
  1883 LaunchCallbackApp(const NS_tchar *workingDir, 
  1884                   int argc, 
  1885                   NS_tchar **argv, 
  1886                   bool usingService)
  1888   putenv(const_cast<char*>("NO_EM_RESTART="));
  1889   putenv(const_cast<char*>("MOZ_LAUNCHED_CHILD=1"));
  1891   // Run from the specified working directory (see bug 312360). This is not
  1892   // necessary on Windows CE since the application that launches the updater
  1893   // passes the working directory as an --environ: command line argument.
  1894   if (NS_tchdir(workingDir) != 0) {
  1895     LOG(("Warning: chdir failed"));
  1898 #if defined(USE_EXECV)
  1899   execv(argv[0], argv);
  1900 #elif defined(XP_MACOSX)
  1901   LaunchChild(argc, argv);
  1902 #elif defined(XP_WIN)
  1903   // Do not allow the callback to run when running an update through the
  1904   // service as session 0.  The unelevated updater.exe will do the launching.
  1905   if (!usingService) {
  1906 #if defined(MOZ_METRO)
  1907     // If our callback application is the default metro browser, then
  1908     // launch it now.
  1909     if (IsUpdateFromMetro(argc, argv)) {
  1910       LaunchDefaultMetroBrowser();
  1911       return;
  1913 #endif
  1914     WinLaunchChild(argv[0], argc, argv, nullptr);
  1916 #else
  1917 # warning "Need implementaton of LaunchCallbackApp"
  1918 #endif
  1921 static bool
  1922 WriteStatusFile(const char* aStatus)
  1924   NS_tchar filename[MAXPATHLEN];
  1925   NS_tsnprintf(filename, sizeof(filename)/sizeof(filename[0]),
  1926                NS_T("%s/update.status"), gSourcePath);
  1928   // Make sure that the directory for the update status file exists
  1929   if (ensure_parent_dir(filename))
  1930     return false;
  1932   AutoFile file = NS_tfopen(filename, NS_T("wb+"));
  1933   if (file == nullptr)
  1934     return false;
  1936   if (fwrite(aStatus, strlen(aStatus), 1, file) != 1)
  1937     return false;
  1939   return true;
  1942 static void
  1943 WriteStatusFile(int status)
  1945   const char *text;
  1947   char buf[32];
  1948   if (status == OK) {
  1949     if (sStagedUpdate) {
  1950       text = "applied\n";
  1951     } else {
  1952       text = "succeeded\n";
  1954   } else {
  1955     snprintf(buf, sizeof(buf)/sizeof(buf[0]), "failed: %d\n", status);
  1956     text = buf;
  1959   WriteStatusFile(text);
  1962 #ifdef MOZ_MAINTENANCE_SERVICE
  1963 /* 
  1964  * Read the update.status file and sets isPendingService to true if
  1965  * the status is set to pending-service.
  1967  * @param  isPendingService Out parameter for specifying if the status
  1968  *         is set to pending-service or not.
  1969  * @return true if the information was retrieved and it is pending
  1970  *         or pending-service.
  1971 */
  1972 static bool
  1973 IsUpdateStatusPendingService()
  1975   NS_tchar filename[MAXPATHLEN];
  1976   NS_tsnprintf(filename, sizeof(filename)/sizeof(filename[0]),
  1977                NS_T("%s/update.status"), gSourcePath);
  1979   AutoFile file = NS_tfopen(filename, NS_T("rb"));
  1980   if (file == nullptr)
  1981     return false;
  1983   char buf[32] = { 0 };
  1984   fread(buf, sizeof(buf), 1, file);
  1986   const char kPendingService[] = "pending-service";
  1987   const char kAppliedService[] = "applied-service";
  1989   return (strncmp(buf, kPendingService, 
  1990                   sizeof(kPendingService) - 1) == 0) ||
  1991          (strncmp(buf, kAppliedService,
  1992                   sizeof(kAppliedService) - 1) == 0);
  1994 #endif
  1996 #ifdef XP_WIN
  1997 /* 
  1998  * Read the update.status file and sets isSuccess to true if
  1999  * the status is set to succeeded.
  2001  * @param  isSucceeded Out parameter for specifying if the status
  2002  *         is set to succeeded or not.
  2003  * @return true if the information was retrieved and it is succeeded.
  2004 */
  2005 static bool
  2006 IsUpdateStatusSucceeded(bool &isSucceeded)
  2008   isSucceeded = false;
  2009   NS_tchar filename[MAXPATHLEN];
  2010   NS_tsnprintf(filename, sizeof(filename)/sizeof(filename[0]),
  2011                NS_T("%s/update.status"), gSourcePath);
  2013   AutoFile file = NS_tfopen(filename, NS_T("rb"));
  2014   if (file == nullptr)
  2015     return false;
  2017   char buf[32] = { 0 };
  2018   fread(buf, sizeof(buf), 1, file);
  2020   const char kSucceeded[] = "succeeded";
  2021   isSucceeded = strncmp(buf, kSucceeded, 
  2022                         sizeof(kSucceeded) - 1) == 0;
  2023   return true;
  2025 #endif
  2027 /*
  2028  * Get the application installation directory.
  2030  * @param installDir Out parameter for specifying the installation directory.
  2031  * @return true if successful, false otherwise.
  2032  */
  2033 template <size_t N>
  2034 static bool
  2035 GetInstallationDir(NS_tchar (&installDir)[N])
  2037   NS_tsnprintf(installDir, N, NS_T("%s"), gDestinationPath);
  2038   if (!sStagedUpdate && !sReplaceRequest) {
  2039     // no need to do any further processing
  2040     return true;
  2043   NS_tchar *slash = (NS_tchar *) NS_tstrrchr(installDir, NS_SLASH);
  2044   // Make sure we're not looking at a trailing slash
  2045   if (slash && slash[1] == NS_T('\0')) {
  2046     *slash = NS_T('\0');
  2047     slash = (NS_tchar *) NS_tstrrchr(installDir, NS_SLASH);
  2049   if (slash) {
  2050     *slash = NS_T('\0');
  2051   } else {
  2052     return false;
  2054   return true;
  2057 /*
  2058  * Copy the entire contents of the application installation directory to the
  2059  * destination directory for the update process.
  2061  * @return 0 if successful, an error code otherwise.
  2062  */
  2063 static int
  2064 CopyInstallDirToDestDir()
  2066   // First extract the installation directory from gSourcePath by going two
  2067   // levels above it.  This is effectively skipping over "updates/0".
  2068   NS_tchar installDir[MAXPATHLEN];
  2069   if (!GetInstallationDir(installDir)) {
  2070     return NO_INSTALLDIR_ERROR;
  2073   // These files should not be copied over to the updated app
  2074 #ifdef XP_WIN
  2075   #ifdef TOR_BROWSER_UPDATE
  2076     #define SKIPLIST_COUNT 5
  2077   #else
  2078     #define SKIPLIST_COUNT 3
  2079   #endif
  2080 #else
  2081   #ifdef TOR_BROWSER_UPDATE
  2082     #define SKIPLIST_COUNT 4
  2083   #else
  2084     #define SKIPLIST_COUNT 2
  2085   #endif
  2086 #endif
  2087   copy_recursive_skiplist<SKIPLIST_COUNT> skiplist;
  2088 #ifdef XP_MACOSX
  2089   skiplist.append(0, installDir, NS_T("Updated.app"));
  2090   skiplist.append(1, installDir, NS_T("Contents/MacOS/updates/0"));
  2091 #ifdef TOR_BROWSER_UPDATE
  2092   skiplist.append(2, installDir, NS_T("TorBrowser/Data/Browser/profile.default/.parentlock"));
  2093   skiplist.append(3, installDir, NS_T("TorBrowser/Data/Tor/lock"));
  2094 #endif
  2095 #else
  2096   skiplist.append(0, installDir, NS_T("updated"));
  2097   skiplist.append(1, installDir, NS_T("updates/0"));
  2098 #ifdef TOR_BROWSER_UPDATE
  2099 #ifdef XP_UNIX
  2100   skiplist.append(2, installDir, NS_T("TorBrowser/Data/Browser/profile.default/.parentlock"));
  2101 #else
  2102   skiplist.append(2, installDir, NS_T("TorBrowser/Data/Browser/profile.default/parent.lock"));
  2103 #endif
  2104   skiplist.append(3, installDir, NS_T("TorBrowser/Data/Tor/lock"));
  2105 #endif
  2106 #ifdef XP_WIN
  2107   skiplist.append(SKIPLIST_COUNT - 1, installDir,
  2108                   NS_T("updated.update_in_progress.lock"));
  2109 #endif
  2110 #endif
  2112   return ensure_copy_recursive(installDir, gDestinationPath, skiplist);
  2115 /*
  2116  * Replace the application installation directory with the destination
  2117  * directory in order to finish a staged update task
  2119  * @return 0 if successful, an error code otherwise.
  2120  */
  2121 static int
  2122 ProcessReplaceRequest()
  2124   // The replacement algorithm is like this:
  2125   // 1. Move sourceDir to tmpDir.  In case of failure, abort.
  2126   // 2. Move newDir to sourceDir.  In case of failure, revert step 1 and abort.
  2127   // 3. Delete tmpDir (or defer it to the next reboot).
  2129   NS_tchar installDir[MAXPATHLEN];
  2130   if (!GetInstallationDir(installDir)) {
  2131     return NO_INSTALLDIR_ERROR;
  2134 #ifdef XP_MACOSX
  2135   NS_tchar sourceDir[MAXPATHLEN];
  2136   NS_tsnprintf(sourceDir, sizeof(sourceDir)/sizeof(sourceDir[0]),
  2137                NS_T("%s/Contents"), installDir);
  2138 #elif XP_WIN
  2139   // Windows preserves the case of the file/directory names.  We use the
  2140   // GetLongPathName API in order to get the correct case for the directory
  2141   // name, so that if the user has used a different case when launching the
  2142   // application, the installation directory's name does not change.
  2143   NS_tchar sourceDir[MAXPATHLEN];
  2144   if (!GetLongPathNameW(installDir, sourceDir, sizeof(sourceDir)/sizeof(sourceDir[0]))) {
  2145     return NO_INSTALLDIR_ERROR;
  2147 #else
  2148   NS_tchar* sourceDir = installDir;
  2149 #endif
  2151   NS_tchar tmpDir[MAXPATHLEN];
  2152   NS_tsnprintf(tmpDir, sizeof(tmpDir)/sizeof(tmpDir[0]),
  2153                NS_T("%s.bak"), sourceDir);
  2155   NS_tchar newDir[MAXPATHLEN];
  2156   NS_tsnprintf(newDir, sizeof(newDir)/sizeof(newDir[0]),
  2157 #ifdef XP_MACOSX
  2158                NS_T("%s/Updated.app/Contents"),
  2159 #else
  2160                NS_T("%s.bak/updated"),
  2161 #endif
  2162                installDir);
  2164   // First try to remove the possibly existing temp directory, because if this
  2165   // directory exists, we will fail to rename sourceDir.
  2166   // No need to error check here because if this fails, we will fail in the
  2167   // next step anyways.
  2168   ensure_remove_recursive(tmpDir);
  2170   LOG(("Begin moving sourceDir (" LOG_S ") to tmpDir (" LOG_S ")",
  2171        sourceDir, tmpDir));
  2172   int rv = rename_file(sourceDir, tmpDir, true);
  2173 #ifdef XP_WIN
  2174   // On Windows, if Firefox is launched using the shortcut, it will hold a handle
  2175   // to its installation directory open, which might not get released in time.
  2176   // Therefore we wait a little bit here to see if the handle is released.
  2177   // If it's not released, we just fail to perform the replace request.
  2178   const int max_retries = 10;
  2179   int retries = 0;
  2180   while (rv == WRITE_ERROR && (retries++ < max_retries)) {
  2181     LOG(("PerformReplaceRequest: sourceDir rename attempt %d failed. " \
  2182          "File: " LOG_S ". Last error: %d, err: %d", retries,
  2183          sourceDir, GetLastError(), rv));
  2185     Sleep(100);
  2187     rv = rename_file(sourceDir, tmpDir, true);
  2189 #endif
  2190   if (rv) {
  2191     LOG(("Moving sourceDir to tmpDir failed, err: %d", rv));
  2192     return rv;
  2195   LOG(("Begin moving newDir (" LOG_S ") to sourceDir (" LOG_S ")",
  2196        newDir, sourceDir));
  2197   rv = rename_file(newDir, sourceDir, true);
  2198   if (rv) {
  2199     LOG(("Moving newDir to sourceDir failed, err: %d", rv));
  2200     LOG(("Now, try to move tmpDir back to sourceDir"));
  2201     ensure_remove_recursive(sourceDir);
  2202     int rv2 = rename_file(tmpDir, sourceDir, true);
  2203     if (rv2) {
  2204       LOG(("Moving tmpDir back to sourceDir failed, err: %d", rv2));
  2206     return rv;
  2209   LOG(("Now, remove the tmpDir"));
  2210   rv = ensure_remove_recursive(tmpDir);
  2211   if (rv) {
  2212     LOG(("Removing tmpDir failed, err: %d", rv));
  2213 #ifdef XP_WIN
  2214     if (MoveFileExW(tmpDir, nullptr, MOVEFILE_DELAY_UNTIL_REBOOT)) {
  2215       LOG(("tmpDir will be removed on OS reboot: " LOG_S, tmpDir));
  2216     } else {
  2217       LOG(("Failed to schedule OS reboot removal of directory: " LOG_S,
  2218            tmpDir));
  2220 #endif
  2223 #ifdef XP_MACOSX
  2224   // On OS X, we need to copy anything else left over inside the Updated.app
  2225   // directory, and then we need to get rid of it as it's no longer going to
  2226   // be useful.
  2227   NS_tchar updatedAppDir[MAXPATHLEN];
  2228   NS_tsnprintf(updatedAppDir, sizeof(updatedAppDir)/sizeof(updatedAppDir[0]),
  2229                NS_T("%s/Updated.app"), installDir);
  2230   NS_tDIR *dir = NS_topendir(updatedAppDir);
  2231   if (dir) {
  2232     NS_tdirent *entry;
  2233     while ((entry = NS_treaddir(dir)) != 0) {
  2234       if (NS_tstrcmp(entry->d_name, NS_T(".")) &&
  2235           NS_tstrcmp(entry->d_name, NS_T(".."))) {
  2236         NS_tchar childSrcPath[MAXPATHLEN];
  2237         NS_tsnprintf(childSrcPath, sizeof(childSrcPath)/sizeof(childSrcPath[0]),
  2238                      NS_T("%s/%s"), updatedAppDir, entry->d_name);
  2239         NS_tchar childDstPath[MAXPATHLEN];
  2240         NS_tsnprintf(childDstPath, sizeof(childDstPath)/sizeof(childDstPath[0]),
  2241                      NS_T("%s/%s"), installDir, entry->d_name);
  2242         ensure_remove_recursive(childDstPath);
  2243         rv = rename_file(childSrcPath, childDstPath, true);
  2244         if (rv) {
  2245           LOG(("Moving " LOG_S " to " LOG_S " failed, err: %d",
  2246                childSrcPath, childDstPath, errno));
  2251     NS_tclosedir(dir);
  2252   } else {
  2253     LOG(("Updated.app dir can't be found: " LOG_S ", err: %d",
  2254          updatedAppDir, errno));
  2256   ensure_remove_recursive(updatedAppDir);
  2258   LOG(("Moving the precomplete file"));
  2260   // We also need to move the precomplete file too.
  2261   NS_tchar precompleteSource[MAXPATHLEN];
  2262   NS_tsnprintf(precompleteSource, sizeof(precompleteSource)/sizeof(precompleteSource[0]),
  2263                NS_T("%s/precomplete"), installDir);
  2265   NS_tchar precompleteTmp[MAXPATHLEN];
  2266   NS_tsnprintf(precompleteTmp, sizeof(precompleteTmp)/sizeof(precompleteTmp[0]),
  2267                NS_T("%s/precomplete.bak"), installDir);
  2269   NS_tchar precompleteNew[MAXPATHLEN];
  2270   NS_tsnprintf(precompleteNew, sizeof(precompleteNew)/sizeof(precompleteNew[0]),
  2271                NS_T("%s/Updated.app/precomplete"), installDir);
  2273   ensure_remove(precompleteTmp);
  2274   LOG(("Begin moving precompleteSrc to precompleteTmp"));
  2275   rv = rename_file(precompleteSource, precompleteTmp);
  2276   LOG(("Moved precompleteSrc to precompleteTmp, err: %d", rv));
  2277   LOG(("Begin moving precompleteNew to precompleteSrc"));
  2278   int rv2 = rename_file(precompleteNew, precompleteSource);
  2279   LOG(("Moved precompleteNew to precompleteSrc, err: %d", rv2));
  2281   // If new could not be moved to source, we only want to restore tmp to source
  2282   // if the first step succeeded.  Note that it is possible for the first
  2283   // rename to have failed as well, for example if the tmpFile exists and we
  2284   // race between the ensure_remove call and the first rename call, but there
  2285   // isn't too much that we can do about that, unfortunately.
  2286   if (!rv && rv2) {
  2287     LOG(("Begin trying to recover precompleteSrc"));
  2288     rv = rename_file(precompleteTmp, precompleteSource);
  2289     LOG(("Moved precompleteTmp to precompleteSrc, err: %d", rv));
  2292   LOG(("Finished moving the precomplete file"));
  2293 #endif
  2295   gSucceeded = true;
  2297   return 0;
  2300 #ifdef XP_WIN
  2301 static void 
  2302 WaitForServiceFinishThread(void *param)
  2304   // We wait at most 10 minutes, we already waited 5 seconds previously
  2305   // before deciding to show this UI.
  2306   WaitForServiceStop(SVC_NAME, 595);
  2307   LOG(("calling QuitProgressUI"));
  2308   QuitProgressUI();
  2310 #endif
  2312 #ifdef MOZ_VERIFY_MAR_SIGNATURE
  2313 /**
  2314  * This function reads in the ACCEPTED_MAR_CHANNEL_IDS from update-settings.ini
  2316  * @param path    The path to the ini file that is to be read
  2317  * @param results A pointer to the location to store the read strings
  2318  * @return OK on success
  2319  */
  2320 static int
  2321 ReadMARChannelIDs(const NS_tchar *path, MARChannelStringTable *results)
  2323   const unsigned int kNumStrings = 1;
  2324   const char *kUpdaterKeys = "ACCEPTED_MAR_CHANNEL_IDS\0";
  2325   char updater_strings[kNumStrings][MAX_TEXT_LEN];
  2327   int result = ReadStrings(path, kUpdaterKeys, kNumStrings,
  2328                            updater_strings, "Settings");
  2330   strncpy(results->MARChannelID, updater_strings[0], MAX_TEXT_LEN - 1);
  2331   results->MARChannelID[MAX_TEXT_LEN - 1] = 0;
  2333   return result;
  2335 #endif
  2337 static int
  2338 GetUpdateFileName(NS_tchar *fileName, int maxChars)
  2340 #if defined(MOZ_WIDGET_GONK)  // If an update.link file exists, then it will contain the name
  2341   // of the update file (terminated by a newline).
  2343   NS_tchar linkFileName[MAXPATHLEN];
  2344   NS_tsnprintf(linkFileName, sizeof(linkFileName)/sizeof(linkFileName[0]),
  2345                NS_T("%s/update.link"), gSourcePath);
  2346   AutoFile linkFile = NS_tfopen(linkFileName, NS_T("rb"));
  2347   if (linkFile == nullptr) {
  2348     NS_tsnprintf(fileName, maxChars,
  2349                  NS_T("%s/update.mar"), gSourcePath);
  2350     return OK;
  2353   char dataFileName[MAXPATHLEN];
  2354   size_t bytesRead;
  2356   if ((bytesRead = fread(dataFileName, 1, sizeof(dataFileName)-1, linkFile)) <= 0) {
  2357     *fileName = NS_T('\0');
  2358     return READ_ERROR;
  2360   if (dataFileName[bytesRead-1] == '\n') {
  2361     // Strip trailing newline (for \n and \r\n)
  2362     bytesRead--;
  2364   if (dataFileName[bytesRead-1] == '\r') {
  2365     // Strip trailing CR (for \r, \r\n)
  2366     bytesRead--;
  2368   dataFileName[bytesRead] = '\0';
  2370   strncpy(fileName, dataFileName, maxChars-1);
  2371   fileName[maxChars-1] = '\0';
  2372 #else
  2373   // We currently only support update.link files under GONK
  2374   NS_tsnprintf(fileName, maxChars,
  2375                NS_T("%s/update.mar"), gSourcePath);
  2376 #endif
  2377   return OK;
  2380 static void
  2381 UpdateThreadFunc(void *param)
  2383   // open ZIP archive and process...
  2384   int rv;
  2385   if (sReplaceRequest) {
  2386     rv = ProcessReplaceRequest();
  2387   } else {
  2388     NS_tchar dataFile[MAXPATHLEN];
  2389     rv = GetUpdateFileName(dataFile, sizeof(dataFile)/sizeof(dataFile[0]));
  2390     if (rv == OK) {
  2391       rv = gArchiveReader.Open(dataFile);
  2394 #ifdef MOZ_VERIFY_MAR_SIGNATURE
  2395     if (rv == OK) {
  2396       rv = gArchiveReader.VerifySignature();
  2399     if (rv == OK) {
  2400       NS_tchar installDir[MAXPATHLEN];
  2401       if (sStagedUpdate) {
  2402         if (!GetInstallationDir(installDir)) {
  2403           rv = NO_INSTALLDIR_ERROR;
  2405       } else {
  2406         NS_tstrcpy(installDir, gDestinationPath);
  2408       if (rv == OK) {
  2409         NS_tchar updateSettingsPath[MAX_TEXT_LEN];
  2410         NS_tsnprintf(updateSettingsPath,
  2411                      sizeof(updateSettingsPath) / sizeof(updateSettingsPath[0]),
  2412                      NS_T("%s/update-settings.ini"), installDir);
  2413         MARChannelStringTable MARStrings;
  2414         if (ReadMARChannelIDs(updateSettingsPath, &MARStrings) != OK) {
  2415           // If we can't read from update-settings.ini then we shouldn't impose
  2416           // a MAR restriction.  Some installations won't even include this file.
  2417           MARStrings.MARChannelID[0] = '\0';
  2420         rv = gArchiveReader.VerifyProductInformation(MARStrings.MARChannelID,
  2421                                                      MOZ_APP_VERSION);
  2424 #endif
  2426     if (rv == OK && sStagedUpdate && !sIsOSUpdate) {
  2427       rv = CopyInstallDirToDestDir();
  2430     if (rv == OK) {
  2431       rv = DoUpdate();
  2432       gArchiveReader.Close();
  2433       NS_tchar updatingDir[MAXPATHLEN];
  2434       NS_tsnprintf(updatingDir, sizeof(updatingDir)/sizeof(updatingDir[0]),
  2435                    NS_T("%s/updating"), gDestinationPath);
  2436       ensure_remove_recursive(updatingDir);
  2440   bool reportRealResults = true;
  2441   if (sReplaceRequest && rv && !getenv("MOZ_NO_REPLACE_FALLBACK")) {
  2442     // When attempting to replace the application, we should fall back
  2443     // to non-staged updates in case of a failure.  We do this by
  2444     // setting the status to pending, exiting the updater, and
  2445     // launching the callback application.  The callback application's
  2446     // startup path will see the pending status, and will start the
  2447     // updater application again in order to apply the update without
  2448     // staging.
  2449     // The MOZ_NO_REPLACE_FALLBACK environment variable is used to
  2450     // bypass this fallback, and is used in the updater tests.
  2451     // The only special thing which we should do here is to remove the
  2452     // staged directory as it won't be useful any more.
  2453     NS_tchar installDir[MAXPATHLEN];
  2454     if (GetInstallationDir(installDir)) {
  2455       NS_tchar stageDir[MAXPATHLEN];
  2456       NS_tsnprintf(stageDir, sizeof(stageDir)/sizeof(stageDir[0]),
  2457 #ifdef XP_MACOSX
  2458                    NS_T("%s/Updated.app"),
  2459 #else
  2460                    NS_T("%s/updated"),
  2461 #endif
  2462                    installDir);
  2464       ensure_remove_recursive(stageDir);
  2465       WriteStatusFile(sUsingService ? "pending-service" : "pending");
  2466       char processUpdates[] = "MOZ_PROCESS_UPDATES=";
  2467       putenv(processUpdates); // We need to use -process-updates again in the tests
  2468       reportRealResults = false; // pretend success
  2472   if (reportRealResults) {
  2473     if (rv) {
  2474       LOG(("failed: %d", rv));
  2476     else {
  2477 #ifdef XP_MACOSX
  2478       // If the update was successful we need to update the timestamp
  2479       // on the top-level Mac OS X bundle directory so that Mac OS X's
  2480       // Launch Services picks up any major changes. Here we assume that
  2481       // the current working directory is the top-level bundle directory.
  2482       char* cwd = getcwd(nullptr, 0);
  2483       if (cwd) {
  2484         if (utimes(cwd, nullptr) != 0) {
  2485           LOG(("Couldn't set access/modification time on application bundle."));
  2487         free(cwd);
  2489       else {
  2490         LOG(("Couldn't get current working directory for setting "
  2491              "access/modification time on application bundle."));
  2493 #endif
  2495       LOG(("succeeded"));
  2497     WriteStatusFile(rv);
  2500   LOG(("calling QuitProgressUI"));
  2501   QuitProgressUI();
  2504 int NS_main(int argc, NS_tchar **argv)
  2506 #if defined(MOZ_WIDGET_GONK)
  2507   if (getenv("LD_PRELOAD")) {
  2508     // If the updater is launched with LD_PRELOAD set, then we wind up
  2509     // preloading libmozglue.so. Under some circumstances, this can cause
  2510     // the remount of /system to fail when going from rw to ro, so if we
  2511     // detect LD_PRELOAD we unsetenv it and relaunch ourselves without it.
  2512     // This will cause the offending preloaded library to be closed.
  2513     //
  2514     // For a variety of reasons, this is really hard to do in a safe manner
  2515     // in the parent process, so we do it here.
  2516     unsetenv("LD_PRELOAD");
  2517     execv(argv[0], argv);
  2518     __android_log_print(ANDROID_LOG_INFO, "updater",
  2519                         "execve failed: errno: %d. Exiting...", errno);
  2520     _exit(1);
  2522 #endif
  2523   InitProgressUI(&argc, &argv);
  2525   // To process an update the updater command line must at a minimum have the
  2526   // directory path containing the updater.mar file to process as the first argument
  2527   // and the directory to apply the update to as the second argument. When the
  2528   // updater is launched by another process the PID of the parent process should be
  2529   // provided in the optional third argument and the updater will wait on the parent
  2530   // process to exit if the value is non-zero and the process is present. This is
  2531   // necessary due to not being able to update files that are in use on Windows. The
  2532   // optional fourth argument is the callback's working directory and the optional
  2533   // fifth argument is the callback path. The callback is the application to launch
  2534   // after  updating and it will be launched when these arguments are provided
  2535   // whether the update was successful or not. All remaining arguments are optional
  2536   // and are passed to the callback when it is launched.
  2537   if (argc < 3) {
  2538     fprintf(stderr, "Usage: updater update-dir apply-to-dir [wait-pid [callback-working-dir callback-path args...]]\n");
  2539     return 1;
  2542   // The directory containing the update information.
  2543   gSourcePath = argv[1];
  2544   // The directory we're going to update to.
  2545   // We copy this string because we need to remove trailing slashes.  The C++
  2546   // standard says that it's always safe to write to strings pointed to by argv
  2547   // elements, but I don't necessarily believe it.
  2548   NS_tstrncpy(gDestinationPath, argv[2], MAXPATHLEN);
  2549   gDestinationPath[MAXPATHLEN - 1] = NS_T('\0');
  2550   NS_tchar *slash = NS_tstrrchr(gDestinationPath, NS_SLASH);
  2551   if (slash && !slash[1]) {
  2552     *slash = NS_T('\0');
  2555 #ifdef XP_WIN
  2556   bool useService = false;
  2557   bool testOnlyFallbackKeyExists = false;
  2558   bool noServiceFallback = getenv("MOZ_NO_SERVICE_FALLBACK") != nullptr;
  2559   putenv(const_cast<char*>("MOZ_NO_SERVICE_FALLBACK="));
  2561   // We never want the service to be used unless we build with
  2562   // the maintenance service.
  2563 #ifdef MOZ_MAINTENANCE_SERVICE
  2564   useService = IsUpdateStatusPendingService();
  2565   // Our tests run with a different apply directory for each test.
  2566   // We use this registry key on our test slaves to store the 
  2567   // allowed name/issuers.
  2568   testOnlyFallbackKeyExists = DoesFallbackKeyExist();
  2569 #endif
  2571   // Remove everything except close window from the context menu
  2573     HKEY hkApp;
  2574     RegCreateKeyExW(HKEY_CURRENT_USER, L"Software\\Classes\\Applications",
  2575                     0, nullptr, REG_OPTION_NON_VOLATILE, KEY_SET_VALUE, nullptr,
  2576                     &hkApp, nullptr);
  2577     RegCloseKey(hkApp);
  2578     if (RegCreateKeyExW(HKEY_CURRENT_USER,
  2579                         L"Software\\Classes\\Applications\\updater.exe",
  2580                         0, nullptr, REG_OPTION_VOLATILE, KEY_SET_VALUE, nullptr,
  2581                         &hkApp, nullptr) == ERROR_SUCCESS) {
  2582       RegSetValueExW(hkApp, L"IsHostApp", 0, REG_NONE, 0, 0);
  2583       RegSetValueExW(hkApp, L"NoOpenWith", 0, REG_NONE, 0, 0);
  2584       RegSetValueExW(hkApp, L"NoStartPage", 0, REG_NONE, 0, 0);
  2585       RegCloseKey(hkApp);
  2588 #endif
  2590   // If there is a PID specified and it is not '0' then wait for the process to exit.
  2591 #ifdef XP_WIN
  2592   __int64 pid = 0;
  2593 #else
  2594   int pid = 0;
  2595 #endif
  2596   if (argc > 3) {
  2597 #ifdef XP_WIN
  2598     pid = _wtoi64(argv[3]);
  2599 #else
  2600     pid = atoi(argv[3]);
  2601 #endif
  2602     if (pid == -1) {
  2603       // This is a signal from the parent process that the updater should stage
  2604       // the update.
  2605       sStagedUpdate = true;
  2606     } else if (NS_tstrstr(argv[3], NS_T("/replace"))) {
  2607       // We're processing a request to replace the application with a staged
  2608       // update.
  2609       sReplaceRequest = true;
  2613   if (getenv("MOZ_OS_UPDATE")) {
  2614     sIsOSUpdate = true;
  2615     putenv(const_cast<char*>("MOZ_OS_UPDATE="));
  2618   if (sReplaceRequest) {
  2619     // If we're attempting to replace the application, try to append to the
  2620     // log generated when staging the staged update.
  2621     NS_tchar installDir[MAXPATHLEN];
  2622     if (!GetInstallationDir(installDir)) {
  2623       fprintf(stderr, "Could not get the installation directory\n");
  2624       return 1;
  2627 #ifdef XP_WIN
  2628     NS_tchar* logDir = gSourcePath;
  2629 #else
  2630     NS_tchar logDir[MAXPATHLEN];
  2631     NS_tsnprintf(logDir, sizeof(logDir)/sizeof(logDir[0]),
  2632 #ifdef XP_MACOSX
  2633                  NS_T("%s/Updated.app/Contents/MacOS/updates"),
  2634 #else
  2635                  NS_T("%s/updated/updates"),
  2636 #endif
  2637                  installDir);
  2638 #endif
  2640     LogInitAppend(logDir, NS_T("last-update.log"), NS_T("update.log"));
  2641   } else {
  2642     LogInit(gSourcePath, NS_T("update.log"));
  2645   if (!WriteStatusFile("applying")) {
  2646     LOG(("failed setting status to 'applying'"));
  2647     return 1;
  2650   if (sStagedUpdate) {
  2651     LOG(("Performing a staged update"));
  2652   } else if (sReplaceRequest) {
  2653     LOG(("Performing a replace request"));
  2656 #ifdef MOZ_WIDGET_GONK
  2657   const char *prioEnv = getenv("MOZ_UPDATER_PRIO");
  2658   if (prioEnv) {
  2659     int32_t prioVal;
  2660     int32_t oomScoreAdj;
  2661     int32_t ioprioClass;
  2662     int32_t ioprioLevel;
  2663     if (sscanf(prioEnv, "%d/%d/%d/%d",
  2664                &prioVal, &oomScoreAdj, &ioprioClass, &ioprioLevel) == 4) {
  2665       LOG(("MOZ_UPDATER_PRIO=%s", prioEnv));
  2666       if (setpriority(PRIO_PROCESS, 0, prioVal)) {
  2667         LOG(("setpriority(%d) failed, errno = %d", prioVal, errno));
  2669       if (ioprio_set(IOPRIO_WHO_PROCESS, 0,
  2670                      IOPRIO_PRIO_VALUE(ioprioClass, ioprioLevel))) {
  2671         LOG(("ioprio_set(%d,%d) failed: errno = %d",
  2672              ioprioClass, ioprioLevel, errno));
  2674       FILE *fs = fopen("/proc/self/oom_score_adj", "w");
  2675       if (fs) {
  2676         fprintf(fs, "%d", oomScoreAdj);
  2677         fclose(fs);
  2678       } else {
  2679         LOG(("Unable to open /proc/self/oom_score_adj for writing, errno = %d", errno));
  2683 #endif
  2685 #ifdef XP_WIN
  2686   if (pid > 0) {
  2687     HANDLE parent = OpenProcess(SYNCHRONIZE, false, (DWORD) pid);
  2688     // May return nullptr if the parent process has already gone away.
  2689     // Otherwise, wait for the parent process to exit before starting the
  2690     // update.
  2691     if (parent) {
  2692       bool updateFromMetro = false;
  2693 #ifdef MOZ_METRO
  2694       updateFromMetro = IsUpdateFromMetro(argc, argv);
  2695 #endif
  2696       DWORD waitTime = updateFromMetro ?
  2697                        IMMERSIVE_PARENT_WAIT : PARENT_WAIT;
  2698       DWORD result = WaitForSingleObject(parent, waitTime);
  2699       CloseHandle(parent);
  2700       if (result != WAIT_OBJECT_0 && !updateFromMetro)
  2701         return 1;
  2704 #else
  2705   if (pid > 0)
  2706     waitpid(pid, nullptr, 0);
  2707 #endif
  2709   if (sReplaceRequest) {
  2710 #ifdef XP_WIN
  2711     // On Windows, the current working directory of the process should be changed
  2712     // so that it's not locked.
  2713     NS_tchar tmpDir[MAXPATHLEN];
  2714     if (GetTempPathW(MAXPATHLEN, tmpDir)) {
  2715       NS_tchdir(tmpDir);
  2717 #endif
  2720   // The callback is the remaining arguments starting at callbackIndex.
  2721   // The argument specified by callbackIndex is the callback executable and the
  2722   // argument prior to callbackIndex is the working directory.
  2723   const int callbackIndex = 5;
  2725 #if defined(XP_WIN)
  2726   sUsingService = getenv("MOZ_USING_SERVICE") != nullptr;
  2727   putenv(const_cast<char*>("MOZ_USING_SERVICE="));
  2728   // lastFallbackError keeps track of the last error for the service not being 
  2729   // used, in case of an error when fallback is not enabled we write the 
  2730   // error to the update.status file. 
  2731   // When fallback is disabled (MOZ_NO_SERVICE_FALLBACK does not exist) then
  2732   // we will instead fallback to not using the service and display a UAC prompt.
  2733   int lastFallbackError = FALLBACKKEY_UNKNOWN_ERROR;
  2735   // Launch a second instance of the updater with the runas verb on Windows
  2736   // when write access is denied to the installation directory.
  2737   HANDLE updateLockFileHandle = INVALID_HANDLE_VALUE;
  2738   NS_tchar elevatedLockFilePath[MAXPATHLEN] = {NS_T('\0')};
  2739   if (!sUsingService &&
  2740       (argc > callbackIndex || sStagedUpdate || sReplaceRequest)) {
  2741     NS_tchar updateLockFilePath[MAXPATHLEN];
  2742     if (sStagedUpdate) {
  2743       // When staging an update, the lock file is:
  2744       // $INSTALLDIR\updated.update_in_progress.lock
  2745       NS_tsnprintf(updateLockFilePath,
  2746                    sizeof(updateLockFilePath)/sizeof(updateLockFilePath[0]),
  2747                    NS_T("%s.update_in_progress.lock"), gDestinationPath);
  2748     } else if (sReplaceRequest) {
  2749       // When processing a replace request, the lock file is:
  2750       // $INSTALLDIR\..\moz_update_in_progress.lock
  2751       NS_tchar installDir[MAXPATHLEN];
  2752       if (!GetInstallationDir(installDir)) {
  2753         return 1;
  2755       NS_tchar *slash = (NS_tchar *) NS_tstrrchr(installDir, NS_SLASH);
  2756       *slash = NS_T('\0');
  2757       NS_tsnprintf(updateLockFilePath,
  2758                    sizeof(updateLockFilePath)/sizeof(updateLockFilePath[0]),
  2759                    NS_T("%s\\moz_update_in_progress.lock"), installDir);
  2760     } else {
  2761       // In the non-staging update case, the lock file is:
  2762       // $INSTALLDIR\$APPNAME.exe.update_in_progress.lock
  2763       NS_tsnprintf(updateLockFilePath,
  2764                    sizeof(updateLockFilePath)/sizeof(updateLockFilePath[0]),
  2765                    NS_T("%s.update_in_progress.lock"), argv[callbackIndex]);
  2768     // The update_in_progress.lock file should only exist during an update. In
  2769     // case it exists attempt to remove it and exit if that fails to prevent
  2770     // simultaneous updates occurring.
  2771     if (!_waccess(updateLockFilePath, F_OK) &&
  2772         NS_tremove(updateLockFilePath) != 0) {
  2773       // Try to fall back to the old way of doing updates if a staged
  2774       // update fails.
  2775       if (sStagedUpdate || sReplaceRequest) {
  2776         // Note that this could fail, but if it does, there isn't too much we
  2777         // can do in order to recover anyways.
  2778         WriteStatusFile("pending");
  2780       LOG(("Update already in progress! Exiting"));
  2781       return 1;
  2784     updateLockFileHandle = CreateFileW(updateLockFilePath,
  2785                                        GENERIC_READ | GENERIC_WRITE,
  2786                                        0,
  2787                                        nullptr,
  2788                                        OPEN_ALWAYS,
  2789                                        FILE_FLAG_DELETE_ON_CLOSE,
  2790                                        nullptr);
  2792     NS_tsnprintf(elevatedLockFilePath,
  2793                  sizeof(elevatedLockFilePath)/sizeof(elevatedLockFilePath[0]),
  2794                  NS_T("%s/update_elevated.lock"), gSourcePath);
  2797     // Even if a file has no sharing access, you can still get its attributes
  2798     bool startedFromUnelevatedUpdater =
  2799       GetFileAttributesW(elevatedLockFilePath) != INVALID_FILE_ATTRIBUTES;
  2801     // If we're running from the service, then we were started with the same
  2802     // token as the service so the permissions are already dropped.  If we're
  2803     // running from an elevated updater that was started from an unelevated 
  2804     // updater, then we drop the permissions here. We do not drop the 
  2805     // permissions on the originally called updater because we use its token
  2806     // to start the callback application.
  2807     if(startedFromUnelevatedUpdater) {
  2808       // Disable every privilege we don't need. Processes started using
  2809       // CreateProcess will use the same token as this process.
  2810       UACHelper::DisablePrivileges(nullptr);
  2813     if (updateLockFileHandle == INVALID_HANDLE_VALUE || 
  2814         (useService && testOnlyFallbackKeyExists && noServiceFallback)) {
  2815       if (!_waccess(elevatedLockFilePath, F_OK) &&
  2816           NS_tremove(elevatedLockFilePath) != 0) {
  2817         fprintf(stderr, "Unable to create elevated lock file! Exiting\n");
  2818         return 1;
  2821       HANDLE elevatedFileHandle;
  2822       elevatedFileHandle = CreateFileW(elevatedLockFilePath,
  2823                                        GENERIC_READ | GENERIC_WRITE,
  2824                                        0,
  2825                                        nullptr,
  2826                                        OPEN_ALWAYS,
  2827                                        FILE_FLAG_DELETE_ON_CLOSE,
  2828                                        nullptr);
  2830       if (elevatedFileHandle == INVALID_HANDLE_VALUE) {
  2831         LOG(("Unable to create elevated lock file! Exiting"));
  2832         return 1;
  2835       wchar_t *cmdLine = MakeCommandLine(argc - 1, argv + 1);
  2836       if (!cmdLine) {
  2837         CloseHandle(elevatedFileHandle);
  2838         return 1;
  2841       NS_tchar installDir[MAXPATHLEN];
  2842       if (!GetInstallationDir(installDir)) {
  2843         return 1;
  2846       // Make sure the path to the updater to use for the update is on local.
  2847       // We do this check to make sure that file locking is available for
  2848       // race condition security checks.
  2849       if (useService) {
  2850         BOOL isLocal = FALSE;
  2851         useService = IsLocalFile(argv[0], isLocal) && isLocal;
  2854       // If we have unprompted elevation we should NOT use the service
  2855       // for the update. Service updates happen with the SYSTEM account
  2856       // which has more privs than we need to update with.
  2857       // Windows 8 provides a user interface so users can configure this
  2858       // behavior and it can be configured in the registry in all Windows
  2859       // versions that support UAC.
  2860       if (useService) {
  2861         BOOL unpromptedElevation;
  2862         if (IsUnpromptedElevation(unpromptedElevation)) {
  2863           useService = !unpromptedElevation;
  2867       // Make sure the service registry entries for the instsallation path
  2868       // are available.  If not don't use the service.
  2869       if (useService) {
  2870         WCHAR maintenanceServiceKey[MAX_PATH + 1];
  2871         if (CalculateRegistryPathFromFilePath(installDir, maintenanceServiceKey)) {
  2872           HKEY baseKey;
  2873           if (RegOpenKeyExW(HKEY_LOCAL_MACHINE, 
  2874                             maintenanceServiceKey, 0, 
  2875                             KEY_READ | KEY_WOW64_64KEY, 
  2876                             &baseKey) == ERROR_SUCCESS) {
  2877             RegCloseKey(baseKey);
  2878           } else {
  2879             useService = testOnlyFallbackKeyExists;
  2880             if (!useService) {
  2881               lastFallbackError = FALLBACKKEY_NOKEY_ERROR;
  2884         } else {
  2885           useService = false;
  2886           lastFallbackError = FALLBACKKEY_REGPATH_ERROR;
  2890       // Originally we used to write "pending" to update.status before
  2891       // launching the service command.  This is no longer needed now
  2892       // since the service command is launched from updater.exe.  If anything
  2893       // fails in between, we can fall back to using the normal update process
  2894       // on our own.
  2896       // If we still want to use the service try to launch the service 
  2897       // comamnd for the update.
  2898       if (useService) {
  2899         // If the update couldn't be started, then set useService to false so
  2900         // we do the update the old way.
  2901         DWORD ret = LaunchServiceSoftwareUpdateCommand(argc, (LPCWSTR *)argv);
  2902         useService = (ret == ERROR_SUCCESS);
  2903         // If the command was launched then wait for the service to be done.
  2904         if (useService) {
  2905           bool showProgressUI = false;
  2906           // Never show the progress UI when staging updates.
  2907           if (!sStagedUpdate) {
  2908             // We need to call this separately instead of allowing ShowProgressUI
  2909             // to initialize the strings because the service will move the
  2910             // ini file out of the way when running updater.
  2911             showProgressUI = !InitProgressUIStrings();
  2914           // Wait for the service to stop for 5 seconds.  If the service
  2915           // has still not stopped then show an indeterminate progress bar.
  2916           DWORD lastState = WaitForServiceStop(SVC_NAME, 5);
  2917           if (lastState != SERVICE_STOPPED) {
  2918             Thread t1;
  2919             if (t1.Run(WaitForServiceFinishThread, nullptr) == 0 && 
  2920                 showProgressUI) {
  2921               ShowProgressUI(true, false);
  2923             t1.Join();
  2926           lastState = WaitForServiceStop(SVC_NAME, 1);
  2927           if (lastState != SERVICE_STOPPED) {
  2928             // If the service doesn't stop after 10 minutes there is
  2929             // something seriously wrong.
  2930             lastFallbackError = FALLBACKKEY_SERVICE_NO_STOP_ERROR;
  2931             useService = false;
  2933         } else {
  2934           lastFallbackError = FALLBACKKEY_LAUNCH_ERROR;
  2938       // If the service can't be used when staging and update, make sure that
  2939       // the UAC prompt is not shown! In this case, just set the status to
  2940       // pending and the update will be applied during the next startup.
  2941       if (!useService && sStagedUpdate) {
  2942         if (updateLockFileHandle != INVALID_HANDLE_VALUE) {
  2943           CloseHandle(updateLockFileHandle);
  2945         WriteStatusPending(gSourcePath);
  2946         return 0;
  2949       // If we started the service command, and it finished, check the
  2950       // update.status file to make sure it succeeded, and if it did
  2951       // we need to manually start the PostUpdate process from the
  2952       // current user's session of this unelevated updater.exe the
  2953       // current process is running as.
  2954       // Note that we don't need to do this if we're just staging the update,
  2955       // as the PostUpdate step runs when performing the replacing in that case.
  2956       if (useService && !sStagedUpdate) {
  2957         bool updateStatusSucceeded = false;
  2958         if (IsUpdateStatusSucceeded(updateStatusSucceeded) && 
  2959             updateStatusSucceeded) {
  2960           if (!LaunchWinPostProcess(installDir, gSourcePath, false, nullptr)) {
  2961             fprintf(stderr, "The post update process which runs as the user"
  2962                     " for service update could not be launched.");
  2967       // If we didn't want to use the service at all, or if an update was 
  2968       // already happening, or launching the service command failed, then 
  2969       // launch the elevated updater.exe as we do without the service.
  2970       // We don't launch the elevated updater in the case that we did have
  2971       // write access all along because in that case the only reason we're
  2972       // using the service is because we are testing. 
  2973       if (!useService && !noServiceFallback && 
  2974           updateLockFileHandle == INVALID_HANDLE_VALUE) {
  2975         SHELLEXECUTEINFO sinfo;
  2976         memset(&sinfo, 0, sizeof(SHELLEXECUTEINFO));
  2977         sinfo.cbSize       = sizeof(SHELLEXECUTEINFO);
  2978         sinfo.fMask        = SEE_MASK_FLAG_NO_UI |
  2979                              SEE_MASK_FLAG_DDEWAIT |
  2980                              SEE_MASK_NOCLOSEPROCESS;
  2981         sinfo.hwnd         = nullptr;
  2982         sinfo.lpFile       = argv[0];
  2983         sinfo.lpParameters = cmdLine;
  2984         sinfo.lpVerb       = L"runas";
  2985         sinfo.nShow        = SW_SHOWNORMAL;
  2987         bool result = ShellExecuteEx(&sinfo);
  2988         free(cmdLine);
  2990         if (result) {
  2991           WaitForSingleObject(sinfo.hProcess, INFINITE);
  2992           CloseHandle(sinfo.hProcess);
  2993         } else {
  2994           WriteStatusFile(ELEVATION_CANCELED);
  2998       if (argc > callbackIndex) {
  2999         LaunchCallbackApp(argv[4], argc - callbackIndex,
  3000                           argv + callbackIndex, sUsingService);
  3003       CloseHandle(elevatedFileHandle);
  3005       if (!useService && !noServiceFallback &&
  3006           INVALID_HANDLE_VALUE == updateLockFileHandle) {
  3007         // We didn't use the service and we did run the elevated updater.exe.
  3008         // The elevated updater.exe is responsible for writing out the
  3009         // update.status file.
  3010         return 0;
  3011       } else if(useService) {
  3012         // The service command was launched.  The service is responsible for 
  3013         // writing out the update.status file.
  3014         if (updateLockFileHandle != INVALID_HANDLE_VALUE) {
  3015           CloseHandle(updateLockFileHandle);
  3017         return 0;
  3018       } else {
  3019         // Otherwise the service command was not launched at all.
  3020         // We are only reaching this code path because we had write access
  3021         // all along to the directory and a fallback key existed, and we
  3022         // have fallback disabled (MOZ_NO_SERVICE_FALLBACK env var exists).
  3023         // We only currently use this env var from XPCShell tests.
  3024         CloseHandle(updateLockFileHandle);
  3025         WriteStatusFile(lastFallbackError);
  3026         return 0;
  3030 #endif
  3032 #if defined(MOZ_WIDGET_GONK)
  3033   // In gonk, the master b2g process sets its umask to 0027 because
  3034   // there's no reason for it to ever create world-readable files.
  3035   // The updater binary, however, needs to do this, and it inherits
  3036   // the master process's cautious umask.  So we drop down a bit here.
  3037   umask(0022);
  3039   // Remount the /system partition as read-write for gonk. The destructor will
  3040   // remount /system as read-only. We add an extra level of scope here to avoid
  3041   // calling LogFinish() before the GonkAutoMounter destructor has a chance
  3042   // to be called
  3044     GonkAutoMounter mounter;
  3045     if (mounter.GetAccess() != MountAccess::ReadWrite) {
  3046       WriteStatusFile(FILESYSTEM_MOUNT_READWRITE_ERROR);
  3047       return 1;
  3049 #endif
  3051   if (sStagedUpdate) {
  3052     // When staging updates, blow away the old installation directory and create
  3053     // it from scratch.
  3054     ensure_remove_recursive(gDestinationPath);
  3056   if (!sReplaceRequest) {
  3057     // Change current directory to the directory where we need to apply the update.
  3058     if (NS_tchdir(gDestinationPath) != 0) {
  3059       // Try to create the destination directory if it doesn't exist
  3060       int rv = NS_tmkdir(gDestinationPath, 0755);
  3061       if (rv == OK && errno != EEXIST) {
  3062         // Try changing the current directory again
  3063         if (NS_tchdir(gDestinationPath) != 0) {
  3064           // OK, time to give up!
  3065           return 1;
  3067       } else {
  3068         // Failed to create the directory, bail out
  3069         return 1;
  3074   LOG(("SOURCE DIRECTORY " LOG_S, gSourcePath));
  3075   LOG(("DESTINATION DIRECTORY " LOG_S, gDestinationPath));
  3077 #ifdef XP_WIN
  3078   // For replace requests, we don't need to do any real updates, so this is not
  3079   // necessary.
  3080   if (!sReplaceRequest) {
  3081     // Allocate enough space for the length of the path an optional additional
  3082     // trailing slash and null termination.
  3083     NS_tchar *destpath = (NS_tchar *) malloc((NS_tstrlen(gDestinationPath) + 2) * sizeof(NS_tchar));
  3084     if (!destpath)
  3085       return 1;
  3087     NS_tchar *c = destpath;
  3088     NS_tstrcpy(c, gDestinationPath);
  3089     c += NS_tstrlen(gDestinationPath);
  3090     if (gDestinationPath[NS_tstrlen(gDestinationPath) - 1] != NS_T('/') &&
  3091         gDestinationPath[NS_tstrlen(gDestinationPath) - 1] != NS_T('\\')) {
  3092       NS_tstrcat(c, NS_T("/"));
  3093       c += NS_tstrlen(NS_T("/"));
  3095     *c = NS_T('\0');
  3096     c++;
  3098     gDestPath = destpath;
  3101   NS_tchar applyDirLongPath[MAXPATHLEN];
  3102   if (!GetLongPathNameW(gDestinationPath, applyDirLongPath,
  3103                         sizeof(applyDirLongPath)/sizeof(applyDirLongPath[0]))) {
  3104     LOG(("NS_main: unable to find apply to dir: " LOG_S, gDestinationPath));
  3105     LogFinish();
  3106     WriteStatusFile(WRITE_ERROR);
  3107     EXIT_WHEN_ELEVATED(elevatedLockFilePath, updateLockFileHandle, 1);
  3108     if (argc > callbackIndex) {
  3109       LaunchCallbackApp(argv[4], argc - callbackIndex,
  3110                         argv + callbackIndex, sUsingService);
  3112     return 1;
  3115   HANDLE callbackFile = INVALID_HANDLE_VALUE;
  3116   if (argc > callbackIndex) {
  3117     // If the callback executable is specified it must exist for a successful
  3118     // update.  It is important we null out the whole buffer here because later
  3119     // we make the assumption that the callback application is inside the
  3120     // apply-to dir.  If we don't have a fully null'ed out buffer it can lead
  3121     // to stack corruption which causes crashes and other problems.
  3122     NS_tchar callbackLongPath[MAXPATHLEN];
  3123     ZeroMemory(callbackLongPath, sizeof(callbackLongPath));
  3124     NS_tchar *targetPath = argv[callbackIndex];
  3125     NS_tchar buffer[MAXPATHLEN * 2] = { NS_T('\0') };
  3126     size_t bufferLeft = MAXPATHLEN * 2;
  3127     if (sReplaceRequest) {
  3128       // In case of replace requests, we should look for the callback file in
  3129       // the destination directory.
  3130       size_t commonPrefixLength = PathCommonPrefixW(argv[callbackIndex],
  3131                                                     gDestinationPath,
  3132                                                     nullptr);
  3133       NS_tchar *p = buffer;
  3134       NS_tstrncpy(p, argv[callbackIndex], commonPrefixLength);
  3135       p += commonPrefixLength;
  3136       bufferLeft -= commonPrefixLength;
  3137       NS_tstrncpy(p, gDestinationPath + commonPrefixLength, bufferLeft);
  3139       size_t len = NS_tstrlen(gDestinationPath + commonPrefixLength);
  3140       p += len;
  3141       bufferLeft -= len;
  3142       *p = NS_T('\\');
  3143       ++p;
  3144       bufferLeft--;
  3145       *p = NS_T('\0');
  3146       NS_tchar installDir[MAXPATHLEN];
  3147       if (!GetInstallationDir(installDir))
  3148         return 1;
  3149       size_t callbackPrefixLength = PathCommonPrefixW(argv[callbackIndex],
  3150                                                       installDir,
  3151                                                       nullptr);
  3152       NS_tstrncpy(p, argv[callbackIndex] + std::max(callbackPrefixLength, commonPrefixLength), bufferLeft);
  3153       targetPath = buffer;
  3155     if (!GetLongPathNameW(targetPath, callbackLongPath,
  3156                           sizeof(callbackLongPath)/sizeof(callbackLongPath[0]))) {
  3157       LOG(("NS_main: unable to find callback file: " LOG_S, targetPath));
  3158       LogFinish();
  3159       WriteStatusFile(WRITE_ERROR);
  3160       EXIT_WHEN_ELEVATED(elevatedLockFilePath, updateLockFileHandle, 1);
  3161       if (argc > callbackIndex) {
  3162         LaunchCallbackApp(argv[4], 
  3163                           argc - callbackIndex, 
  3164                           argv + callbackIndex, 
  3165                           sUsingService);
  3167       return 1;
  3170     // Doing this is only necessary when we're actually applying a patch.
  3171     if (!sReplaceRequest) {
  3172       int len = NS_tstrlen(applyDirLongPath);
  3173       NS_tchar *s = callbackLongPath;
  3174       NS_tchar *d = gCallbackRelPath;
  3175       // advance to the apply to directory and advance past the trailing backslash
  3176       // if present.
  3177       s += len;
  3178       if (*s == NS_T('\\'))
  3179         ++s;
  3181       // Copy the string and replace backslashes with forward slashes along the
  3182       // way.
  3183       do {
  3184         if (*s == NS_T('\\'))
  3185           *d = NS_T('/');
  3186         else
  3187           *d = *s;
  3188         ++s;
  3189         ++d;
  3190       } while (*s);
  3191       *d = NS_T('\0');
  3192       ++d;
  3194       // Make a copy of the callback executable so it can be read when patching.
  3195       NS_tsnprintf(gCallbackBackupPath,
  3196                    sizeof(gCallbackBackupPath)/sizeof(gCallbackBackupPath[0]),
  3197                    NS_T("%s" CALLBACK_BACKUP_EXT), argv[callbackIndex]);
  3198       NS_tremove(gCallbackBackupPath);
  3199       CopyFileW(argv[callbackIndex], gCallbackBackupPath, false);
  3201       // Since the process may be signaled as exited by WaitForSingleObject before
  3202       // the release of the executable image try to lock the main executable file
  3203       // multiple times before giving up.  If we end up giving up, we won't
  3204       // fail the update.
  3205       const int max_retries = 10;
  3206       int retries = 1;
  3207       DWORD lastWriteError = 0;
  3208       do {
  3209         // By opening a file handle wihout FILE_SHARE_READ to the callback
  3210         // executable, the OS will prevent launching the process while it is
  3211         // being updated.
  3212         callbackFile = CreateFileW(targetPath,
  3213                                    DELETE | GENERIC_WRITE,
  3214                                    // allow delete, rename, and write
  3215                                    FILE_SHARE_DELETE | FILE_SHARE_WRITE,
  3216                                    nullptr, OPEN_EXISTING, 0, nullptr);
  3217         if (callbackFile != INVALID_HANDLE_VALUE)
  3218           break;
  3220         lastWriteError = GetLastError();
  3221         LOG(("NS_main: callback app file open attempt %d failed. " \
  3222              "File: " LOG_S ". Last error: %d", retries,
  3223              targetPath, lastWriteError));
  3225         Sleep(100);
  3226       } while (++retries <= max_retries);
  3228       // CreateFileW will fail if the callback executable is already in use.
  3229       if (callbackFile == INVALID_HANDLE_VALUE) {
  3230         // Only fail the update if the last error was not a sharing violation.
  3231         if (lastWriteError != ERROR_SHARING_VIOLATION) {
  3232           LOG(("NS_main: callback app file in use, failed to exclusively open " \
  3233                "executable file: " LOG_S, argv[callbackIndex]));
  3234           LogFinish();
  3235           if (lastWriteError == ERROR_ACCESS_DENIED) {
  3236             WriteStatusFile(WRITE_ERROR_ACCESS_DENIED);
  3237           } else {
  3238             WriteStatusFile(WRITE_ERROR_CALLBACK_APP);
  3241           NS_tremove(gCallbackBackupPath);
  3242           EXIT_WHEN_ELEVATED(elevatedLockFilePath, updateLockFileHandle, 1);
  3243           LaunchCallbackApp(argv[4],
  3244                             argc - callbackIndex,
  3245                             argv + callbackIndex,
  3246                             sUsingService);
  3247           return 1;
  3249         LOG(("NS_main: callback app file in use, continuing without " \
  3250              "exclusive access for executable file: " LOG_S,
  3251              argv[callbackIndex]));
  3256   // DELETE_DIR is not required when staging an update.
  3257   if (!sStagedUpdate && !sReplaceRequest) {
  3258     // The directory to move files that are in use to on Windows. This directory
  3259     // will be deleted after the update is finished or on OS reboot using
  3260     // MoveFileEx if it contains files that are in use.
  3261     if (NS_taccess(DELETE_DIR, F_OK)) {
  3262       NS_tmkdir(DELETE_DIR, 0755);
  3265 #endif /* XP_WIN */
  3267   // Run update process on a background thread.  ShowProgressUI may return
  3268   // before QuitProgressUI has been called, so wait for UpdateThreadFunc to
  3269   // terminate.  Avoid showing the progress UI when staging an update.
  3270   Thread t;
  3271   if (t.Run(UpdateThreadFunc, nullptr) == 0) {
  3272     if (!sStagedUpdate && !sReplaceRequest) {
  3273       ShowProgressUI();
  3276   t.Join();
  3278 #ifdef XP_WIN
  3279   if (argc > callbackIndex && !sReplaceRequest) {
  3280     if (callbackFile != INVALID_HANDLE_VALUE) {
  3281       CloseHandle(callbackFile);
  3283     // Remove the copy of the callback executable.
  3284     NS_tremove(gCallbackBackupPath);
  3287   if (!sStagedUpdate && !sReplaceRequest && _wrmdir(DELETE_DIR)) {
  3288     LOG(("NS_main: unable to remove directory: " LOG_S ", err: %d",
  3289          DELETE_DIR, errno));
  3290     // The directory probably couldn't be removed due to it containing files
  3291     // that are in use and will be removed on OS reboot. The call to remove the
  3292     // directory on OS reboot is done after the calls to remove the files so the
  3293     // files are removed first on OS reboot since the directory must be empty
  3294     // for the directory removal to be successful. The MoveFileEx call to remove
  3295     // the directory on OS reboot will fail if the process doesn't have write
  3296     // access to the HKEY_LOCAL_MACHINE registry key but this is ok since the
  3297     // installer / uninstaller will delete the directory along with its contents
  3298     // after an update is applied, on reinstall, and on uninstall.
  3299     if (MoveFileEx(DELETE_DIR, nullptr, MOVEFILE_DELAY_UNTIL_REBOOT)) {
  3300       LOG(("NS_main: directory will be removed on OS reboot: " LOG_S,
  3301            DELETE_DIR));
  3302     } else {
  3303       LOG(("NS_main: failed to schedule OS reboot removal of " \
  3304            "directory: " LOG_S, DELETE_DIR));
  3307 #endif /* XP_WIN */
  3309 #if defined(MOZ_WIDGET_GONK)
  3310   } // end the extra level of scope for the GonkAutoMounter
  3311 #endif
  3313   LogFinish();
  3315   if (argc > callbackIndex) {
  3316 #if defined(XP_WIN) && !defined(TOR_BROWSER_UPDATE)
  3317     if (gSucceeded) {
  3318       // The service update will only be executed if it is already installed.
  3319       // For first time installs of the service, the install will happen from
  3320       // the PostUpdate process. We do the service update process here 
  3321       // because it's possible we are updating with updater.exe without the 
  3322       // service if the service failed to apply the update. We want to update
  3323       // the service to a newer version in that case. If we are not running
  3324       // through the service, then MOZ_USING_SERVICE will not exist.
  3325       if (!sUsingService) {
  3326         NS_tchar installDir[MAXPATHLEN];
  3327         if (GetInstallationDir(installDir)) {
  3328           if (!LaunchWinPostProcess(installDir, gSourcePath, false, nullptr)) {
  3329             LOG(("NS_main: The post update process could not be launched."));
  3332           StartServiceUpdate(installDir);
  3336     EXIT_WHEN_ELEVATED(elevatedLockFilePath, updateLockFileHandle, 0);
  3337 #endif /* XP_WIN */
  3338 #ifdef XP_MACOSX
  3339     if (gSucceeded) {
  3340       LaunchMacPostProcess(argv[callbackIndex]);
  3342 #endif /* XP_MACOSX */
  3344     if (getenv("MOZ_PROCESS_UPDATES") == nullptr) {
  3345       LaunchCallbackApp(argv[4], 
  3346                         argc - callbackIndex, 
  3347                         argv + callbackIndex, 
  3348                         sUsingService);
  3352   return gSucceeded ? 0 : 1;
  3355 class ActionList
  3357 public:
  3358   ActionList() : mFirst(nullptr), mLast(nullptr), mCount(0) { }
  3359   ~ActionList();
  3361   void Append(Action* action);
  3362   int Prepare();
  3363   int Execute();
  3364   void Finish(int status);
  3366 private:
  3367   Action *mFirst;
  3368   Action *mLast;
  3369   int     mCount;
  3370 };
  3372 ActionList::~ActionList()
  3374   Action* a = mFirst;
  3375   while (a) {
  3376     Action *b = a;
  3377     a = a->mNext;
  3378     delete b;
  3382 void
  3383 ActionList::Append(Action *action)
  3385   if (mLast)
  3386     mLast->mNext = action;
  3387   else
  3388     mFirst = action;
  3390   mLast = action;
  3391   mCount++;
  3394 int
  3395 ActionList::Prepare()
  3397   // If the action list is empty then we should fail in order to signal that
  3398   // something has gone wrong. Otherwise we report success when nothing is
  3399   // actually done. See bug 327140.
  3400   if (mCount == 0) {
  3401     LOG(("empty action list"));
  3402     return UNEXPECTED_MAR_ERROR;
  3405   Action *a = mFirst;
  3406   int i = 0;
  3407   while (a) {
  3408     int rv = a->Prepare();
  3409     if (rv)
  3410       return rv;
  3412     float percent = float(++i) / float(mCount);
  3413     UpdateProgressUI(PROGRESS_PREPARE_SIZE * percent);
  3415     a = a->mNext;
  3418   return OK;
  3421 int
  3422 ActionList::Execute()
  3424   int currentProgress = 0, maxProgress = 0;
  3425   Action *a = mFirst;
  3426   while (a) {
  3427     maxProgress += a->mProgressCost;
  3428     a = a->mNext;
  3431   a = mFirst;
  3432   while (a) {
  3433     int rv = a->Execute();
  3434     if (rv) {
  3435       LOG(("### execution failed"));
  3436       return rv;
  3439     currentProgress += a->mProgressCost;
  3440     float percent = float(currentProgress) / float(maxProgress);
  3441     UpdateProgressUI(PROGRESS_PREPARE_SIZE +
  3442                      PROGRESS_EXECUTE_SIZE * percent);
  3444     a = a->mNext;
  3447   return OK;
  3450 void
  3451 ActionList::Finish(int status)
  3453   Action *a = mFirst;
  3454   int i = 0;
  3455   while (a) {
  3456     a->Finish(status);
  3458     float percent = float(++i) / float(mCount);
  3459     UpdateProgressUI(PROGRESS_PREPARE_SIZE +
  3460                      PROGRESS_EXECUTE_SIZE +
  3461                      PROGRESS_FINISH_SIZE * percent);
  3463     a = a->mNext;
  3466   if (status == OK)
  3467     gSucceeded = true;
  3471 #ifdef XP_WIN
  3472 int add_dir_entries(const NS_tchar *dirpath, ActionList *list)
  3474   int rv = OK;
  3475   WIN32_FIND_DATAW finddata;
  3476   HANDLE hFindFile;
  3477   NS_tchar searchspec[MAXPATHLEN];
  3478   NS_tchar foundpath[MAXPATHLEN];
  3480   NS_tsnprintf(searchspec, sizeof(searchspec)/sizeof(searchspec[0]),
  3481                NS_T("%s*"), dirpath);
  3482   const NS_tchar *pszSpec = get_full_path(searchspec);
  3484   hFindFile = FindFirstFileW(pszSpec, &finddata);
  3485   if (hFindFile != INVALID_HANDLE_VALUE) {
  3486     do {
  3487       // Don't process the current or parent directory.
  3488       if (NS_tstrcmp(finddata.cFileName, NS_T(".")) == 0 ||
  3489           NS_tstrcmp(finddata.cFileName, NS_T("..")) == 0)
  3490         continue;
  3492       NS_tsnprintf(foundpath, sizeof(foundpath)/sizeof(foundpath[0]),
  3493                    NS_T("%s%s"), dirpath, finddata.cFileName);
  3494       if (finddata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
  3495         NS_tsnprintf(foundpath, sizeof(foundpath)/sizeof(foundpath[0]),
  3496                      NS_T("%s/"), foundpath);
  3497         // Recurse into the directory.
  3498         rv = add_dir_entries(foundpath, list);
  3499         if (rv) {
  3500           LOG(("add_dir_entries error: " LOG_S ", err: %d", foundpath, rv));
  3501           return rv;
  3503       } else {
  3504         // Add the file to be removed to the ActionList.
  3505         NS_tchar *quotedpath = get_quoted_path(foundpath);
  3506         if (!quotedpath)
  3507           return PARSE_ERROR;
  3509         Action *action = new RemoveFile();
  3510         rv = action->Parse(quotedpath);
  3511         if (rv) {
  3512           LOG(("add_dir_entries Parse error on recurse: " LOG_S ", err: %d", quotedpath, rv));
  3513           return rv;
  3516         list->Append(action);
  3518     } while (FindNextFileW(hFindFile, &finddata) != 0);
  3520     FindClose(hFindFile);
  3522       // Add the directory to be removed to the ActionList.
  3523       NS_tchar *quotedpath = get_quoted_path(dirpath);
  3524       if (!quotedpath)
  3525         return PARSE_ERROR;
  3527       Action *action = new RemoveDir();
  3528       rv = action->Parse(quotedpath);
  3529       if (rv)
  3530         LOG(("add_dir_entries Parse error on close: " LOG_S ", err: %d", quotedpath, rv));
  3531       else
  3532         list->Append(action);
  3536   return rv;
  3539 #elif defined(SOLARIS)
  3540 int add_dir_entries(const NS_tchar *dirpath, ActionList *list)
  3542   int rv = OK;
  3543   NS_tchar searchpath[MAXPATHLEN];
  3544   NS_tchar foundpath[MAXPATHLEN];
  3545   struct {
  3546     dirent dent_buffer;
  3547     char chars[MAXNAMLEN];
  3548   } ent_buf;
  3549   struct dirent* ent;
  3552   NS_tsnprintf(searchpath, sizeof(searchpath)/sizeof(searchpath[0]), NS_T("%s"),
  3553                dirpath);
  3554   // Remove the trailing slash so the paths don't contain double slashes. The
  3555   // existence of the slash has already been checked in DoUpdate.
  3556   searchpath[NS_tstrlen(searchpath) - 1] = NS_T('\0');
  3558   DIR* dir = opendir(searchpath);
  3559   if (!dir) {
  3560     LOG(("add_dir_entries error on opendir: " LOG_S ", err: %d", searchpath,
  3561          errno));
  3562     return UNEXPECTED_FILE_OPERATION_ERROR;
  3565   while (readdir_r(dir, (dirent *)&ent_buf, &ent) == 0 && ent) {
  3566     if ((strcmp(ent->d_name, ".") == 0) ||
  3567         (strcmp(ent->d_name, "..") == 0))
  3568       continue;
  3570     NS_tsnprintf(foundpath, sizeof(foundpath)/sizeof(foundpath[0]),
  3571                  NS_T("%s%s"), dirpath, ent->d_name);
  3572     struct stat64 st_buf;
  3573     int test = stat64(foundpath, &st_buf);
  3574     if (test) {
  3575       closedir(dir);
  3576       return UNEXPECTED_FILE_OPERATION_ERROR;
  3578     if (S_ISDIR(st_buf.st_mode)) {
  3579       NS_tsnprintf(foundpath, sizeof(foundpath)/sizeof(foundpath[0]),
  3580                    NS_T("%s/"), foundpath);
  3581       // Recurse into the directory.
  3582       rv = add_dir_entries(foundpath, list);
  3583       if (rv) {
  3584         LOG(("add_dir_entries error: " LOG_S ", err: %d", foundpath, rv));
  3585         closedir(dir);
  3586         return rv;
  3588     } else {
  3589       // Add the file to be removed to the ActionList.
  3590       NS_tchar *quotedpath = get_quoted_path(foundpath);
  3591       if (!quotedpath) {
  3592         closedir(dir);
  3593         return PARSE_ERROR;
  3596       Action *action = new RemoveFile();
  3597       rv = action->Parse(quotedpath);
  3598       if (rv) {
  3599         LOG(("add_dir_entries Parse error on recurse: " LOG_S ", err: %d",
  3600              quotedpath, rv));
  3601         closedir(dir);
  3602         return rv;
  3605       list->Append(action);
  3608   closedir(dir);
  3610   // Add the directory to be removed to the ActionList.
  3611   NS_tchar *quotedpath = get_quoted_path(dirpath);
  3612   if (!quotedpath)
  3613     return PARSE_ERROR;
  3615   Action *action = new RemoveDir();
  3616   rv = action->Parse(quotedpath);
  3617   if (rv) {
  3618     LOG(("add_dir_entries Parse error on close: " LOG_S ", err: %d",
  3619          quotedpath, rv));
  3621   else {
  3622     list->Append(action);
  3625   return rv;
  3628 #else
  3630 int add_dir_entries(const NS_tchar *dirpath, ActionList *list)
  3632   int rv = OK;
  3633   FTS *ftsdir;
  3634   FTSENT *ftsdirEntry;
  3635   NS_tchar searchpath[MAXPATHLEN];
  3637   NS_tsnprintf(searchpath, sizeof(searchpath)/sizeof(searchpath[0]), NS_T("%s"),
  3638                dirpath);
  3639   // Remove the trailing slash so the paths don't contain double slashes. The
  3640   // existence of the slash has already been checked in DoUpdate.
  3641   searchpath[NS_tstrlen(searchpath) - 1] = NS_T('\0');
  3642   char* const pathargv[] = {searchpath, nullptr};
  3644   // FTS_NOCHDIR is used so relative paths from the destination directory are
  3645   // returned.
  3646   if (!(ftsdir = fts_open(pathargv,
  3647                           FTS_PHYSICAL | FTS_NOSTAT | FTS_XDEV | FTS_NOCHDIR,
  3648                           nullptr)))
  3649     return UNEXPECTED_FILE_OPERATION_ERROR;
  3651   while ((ftsdirEntry = fts_read(ftsdir)) != nullptr) {
  3652     NS_tchar foundpath[MAXPATHLEN];
  3653     NS_tchar *quotedpath;
  3654     Action *action = nullptr;
  3656     switch (ftsdirEntry->fts_info) {
  3657       // Filesystem objects that shouldn't be in the application's directories
  3658       case FTS_SL:
  3659       case FTS_SLNONE:
  3660       case FTS_DEFAULT:
  3661         LOG(("add_dir_entries: found a non-standard file: " LOG_S,
  3662              ftsdirEntry->fts_path));
  3663         // Fall through and try to remove as a file
  3665       // Files
  3666       case FTS_F:
  3667       case FTS_NSOK:
  3668         // Add the file to be removed to the ActionList.
  3669         NS_tsnprintf(foundpath, sizeof(foundpath)/sizeof(foundpath[0]),
  3670                      NS_T("%s"), ftsdirEntry->fts_accpath);
  3671         quotedpath = get_quoted_path(foundpath);
  3672         if (!quotedpath) {
  3673           rv = UPDATER_QUOTED_PATH_MEM_ERROR;
  3674           break;
  3676         action = new RemoveFile();
  3677         rv = action->Parse(quotedpath);
  3678         if (!rv)
  3679           list->Append(action);
  3680         break;
  3682       // Directories
  3683       case FTS_DP:
  3684         rv = OK;
  3685         // Add the directory to be removed to the ActionList.
  3686         NS_tsnprintf(foundpath, sizeof(foundpath)/sizeof(foundpath[0]),
  3687                      NS_T("%s/"), ftsdirEntry->fts_accpath);
  3688         quotedpath = get_quoted_path(foundpath);
  3689         if (!quotedpath) {
  3690           rv = UPDATER_QUOTED_PATH_MEM_ERROR;
  3691           break;
  3694         action = new RemoveDir();
  3695         rv = action->Parse(quotedpath);
  3696         if (!rv)
  3697           list->Append(action);
  3698         break;
  3700       // Errors
  3701       case FTS_DNR:
  3702       case FTS_NS:
  3703         // ENOENT is an acceptable error for FTS_DNR and FTS_NS and means that
  3704         // we're racing with ourselves. Though strange, the entry will be
  3705         // removed anyway.
  3706         if (ENOENT == ftsdirEntry->fts_errno) {
  3707           rv = OK;
  3708           break;
  3710         // Fall through
  3712       case FTS_ERR:
  3713         rv = UNEXPECTED_FILE_OPERATION_ERROR;
  3714         LOG(("add_dir_entries: fts_read() error: " LOG_S ", err: %d",
  3715              ftsdirEntry->fts_path, ftsdirEntry->fts_errno));
  3716         break;
  3718       case FTS_DC:
  3719         rv = UNEXPECTED_FILE_OPERATION_ERROR;
  3720         LOG(("add_dir_entries: fts_read() returned FT_DC: " LOG_S,
  3721              ftsdirEntry->fts_path));
  3722         break;
  3724       default:
  3725         // FTS_D is ignored and FTS_DP is used instead (post-order).
  3726         rv = OK;
  3727         break;
  3730     if (rv != OK)
  3731       break;
  3734   fts_close(ftsdir);
  3736   return rv;
  3738 #endif
  3740 static NS_tchar*
  3741 GetManifestContents(const NS_tchar *manifest)
  3743   AutoFile mfile = NS_tfopen(manifest, NS_T("rb"));
  3744   if (mfile == nullptr) {
  3745     LOG(("GetManifestContents: error opening manifest file: " LOG_S, manifest));
  3746     return nullptr;
  3749   struct stat ms;
  3750   int rv = fstat(fileno((FILE *)mfile), &ms);
  3751   if (rv) {
  3752     LOG(("GetManifestContents: error stating manifest file: " LOG_S, manifest));
  3753     return nullptr;
  3756   char *mbuf = (char *) malloc(ms.st_size + 1);
  3757   if (!mbuf)
  3758     return nullptr;
  3760   size_t r = ms.st_size;
  3761   char *rb = mbuf;
  3762   while (r) {
  3763     const size_t count = mmin(SSIZE_MAX, r);
  3764     size_t c = fread(rb, 1, count, mfile);
  3765     if (c != count) {
  3766       LOG(("GetManifestContents: error reading manifest file: " LOG_S, manifest));
  3767       return nullptr;
  3770     r -= c;
  3771     rb += c;
  3773   mbuf[ms.st_size] = '\0';
  3774   rb = mbuf;
  3776 #ifndef XP_WIN
  3777   return rb;
  3778 #else
  3779   NS_tchar *wrb = (NS_tchar *) malloc((ms.st_size + 1) * sizeof(NS_tchar));
  3780   if (!wrb)
  3781     return nullptr;
  3783   if (!MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, rb, -1, wrb,
  3784                            ms.st_size + 1)) {
  3785     LOG(("GetManifestContents: error converting utf8 to utf16le: %d", GetLastError()));
  3786     free(mbuf);
  3787     free(wrb);
  3788     return nullptr;
  3790   free(mbuf);
  3792   return wrb;
  3793 #endif
  3796 int AddPreCompleteActions(ActionList *list)
  3798   if (sIsOSUpdate) {
  3799     return OK;
  3802   NS_tchar *rb = GetManifestContents(NS_T("precomplete"));
  3803   if (rb == nullptr) {
  3804     LOG(("AddPreCompleteActions: error getting contents of precomplete " \
  3805          "manifest"));
  3806     // Applications aren't required to have a precomplete manifest. The mar
  3807     // generation scripts enforce the presence of a precomplete manifest.
  3808     return OK;
  3811   int rv;
  3812   NS_tchar *line;
  3813   while((line = mstrtok(kNL, &rb)) != 0) {
  3814     // skip comments
  3815     if (*line == NS_T('#'))
  3816       continue;
  3818     NS_tchar *token = mstrtok(kWhitespace, &line);
  3819     if (!token) {
  3820       LOG(("AddPreCompleteActions: token not found in manifest"));
  3821       return PARSE_ERROR;
  3824     Action *action = nullptr;
  3825     if (NS_tstrcmp(token, NS_T("remove")) == 0) { // rm file
  3826       action = new RemoveFile();
  3828     else if (NS_tstrcmp(token, NS_T("remove-cc")) == 0) { // no longer supported
  3829       continue;
  3831     else if (NS_tstrcmp(token, NS_T("rmdir")) == 0) { // rmdir if  empty
  3832       action = new RemoveDir();
  3834     else {
  3835       LOG(("AddPreCompleteActions: unknown token: " LOG_S, token));
  3836       return PARSE_ERROR;
  3839     if (!action)
  3840       return BAD_ACTION_ERROR;
  3842     rv = action->Parse(line);
  3843     if (rv)
  3844       return rv;
  3846     list->Append(action);
  3849   return OK;
  3852 int DoUpdate()
  3854   NS_tchar manifest[MAXPATHLEN];
  3855   NS_tsnprintf(manifest, sizeof(manifest)/sizeof(manifest[0]),
  3856                NS_T("%s/updating/update.manifest"), gDestinationPath);
  3857   ensure_parent_dir(manifest);
  3859   // extract the manifest
  3860   int rv = gArchiveReader.ExtractFile("updatev3.manifest", manifest);
  3861   if (rv) {
  3862     rv = gArchiveReader.ExtractFile("updatev2.manifest", manifest);
  3863     if (rv) {
  3864       LOG(("DoUpdate: error extracting manifest file"));
  3865       return rv;
  3869   NS_tchar *rb = GetManifestContents(manifest);
  3870   NS_tremove(manifest);
  3871   if (rb == nullptr) {
  3872     LOG(("DoUpdate: error opening manifest file: " LOG_S, manifest));
  3873     return READ_ERROR;
  3877   ActionList list;
  3878   NS_tchar *line;
  3879   bool isFirstAction = true;
  3881   while((line = mstrtok(kNL, &rb)) != 0) {
  3882     // skip comments
  3883     if (*line == NS_T('#'))
  3884       continue;
  3886     NS_tchar *token = mstrtok(kWhitespace, &line);
  3887     if (!token) {
  3888       LOG(("DoUpdate: token not found in manifest"));
  3889       return PARSE_ERROR;
  3892     if (isFirstAction) {
  3893       isFirstAction = false;
  3894       // The update manifest isn't required to have a type declaration. The mar
  3895       // generation scripts enforce the presence of the type declaration.
  3896       if (NS_tstrcmp(token, NS_T("type")) == 0) {
  3897         const NS_tchar *type = mstrtok(kQuote, &line);
  3898         LOG(("UPDATE TYPE " LOG_S, type));
  3899         if (NS_tstrcmp(type, NS_T("complete")) == 0) {
  3900           rv = AddPreCompleteActions(&list);
  3901           if (rv)
  3902             return rv;
  3904         continue;
  3908     Action *action = nullptr;
  3909     if (NS_tstrcmp(token, NS_T("remove")) == 0) { // rm file
  3910       action = new RemoveFile();
  3912     else if (NS_tstrcmp(token, NS_T("rmdir")) == 0) { // rmdir if empty
  3913       action = new RemoveDir();
  3915     else if (NS_tstrcmp(token, NS_T("rmrfdir")) == 0) { // rmdir recursive
  3916       const NS_tchar *reldirpath = mstrtok(kQuote, &line);
  3917       if (!reldirpath)
  3918         return PARSE_ERROR;
  3920       if (reldirpath[NS_tstrlen(reldirpath) - 1] != NS_T('/'))
  3921         return PARSE_ERROR;
  3923       rv = add_dir_entries(reldirpath, &list);
  3924       if (rv)
  3925         return rv;
  3927       continue;
  3929     else if (NS_tstrcmp(token, NS_T("add")) == 0) {
  3930       action = new AddFile();
  3932     else if (NS_tstrcmp(token, NS_T("patch")) == 0) {
  3933       action = new PatchFile();
  3935     else if (NS_tstrcmp(token, NS_T("add-if")) == 0) { // Add if exists
  3936       action = new AddIfFile();
  3938     else if (NS_tstrcmp(token, NS_T("add-if-not")) == 0) { // Add if not exists
  3939       action = new AddIfNotFile();
  3941     else if (NS_tstrcmp(token, NS_T("patch-if")) == 0) { // Patch if exists
  3942       action = new PatchIfFile();
  3944 #ifndef XP_WIN
  3945     else if (NS_tstrcmp(token, NS_T("addsymlink")) == 0) {
  3946       action = new AddSymlink();
  3948 #endif
  3949     else {
  3950       LOG(("DoUpdate: unknown token: " LOG_S, token));
  3951       return PARSE_ERROR;
  3954     if (!action)
  3955       return BAD_ACTION_ERROR;
  3957     rv = action->Parse(line);
  3958     if (rv)
  3959       return rv;
  3961     list.Append(action);
  3964   rv = list.Prepare();
  3965   if (rv)
  3966     return rv;
  3968   rv = list.Execute();
  3970   list.Finish(rv);
  3971   return rv;

mercurial