michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: /** michael@0: * Manifest Format michael@0: * --------------- michael@0: * michael@0: * contents = 1*( line ) michael@0: * line = method LWS *( param LWS ) CRLF michael@0: * CRLF = "\r\n" michael@0: * LWS = 1*( " " | "\t" ) michael@0: * michael@0: * Available methods for the manifest file: michael@0: * michael@0: * updatev2.manifest michael@0: * ----------------- michael@0: * method = "add" | "add-if" | "patch" | "patch-if" | "remove" | michael@0: * "rmdir" | "rmrfdir" | type michael@0: * michael@0: * 'type' is the update type (e.g. complete or partial) and when present MUST michael@0: * be the first entry in the update manifest. The type is used to support michael@0: * downgrades by causing the actions defined in precomplete to be performed. michael@0: * michael@0: * updatev3.manifest michael@0: * ----------------- michael@0: * method = "add" | "add-if" | "add-if-not" | "patch" | "patch-if" | michael@0: * "remove" | "rmdir" | "rmrfdir" | "addsymlink" | type michael@0: * michael@0: * 'add-if-not' adds a file if it doesn't exist. michael@0: * michael@0: * precomplete michael@0: * ----------- michael@0: * method = "remove" | "rmdir" michael@0: */ michael@0: #include "bspatch.h" michael@0: #include "progressui.h" michael@0: #include "archivereader.h" michael@0: #include "readstrings.h" michael@0: #include "errors.h" michael@0: #include "bzlib.h" michael@0: michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: michael@0: #include "updatelogging.h" michael@0: michael@0: #include "mozilla/Compiler.h" michael@0: michael@0: // Amount of the progress bar to use in each of the 3 update stages, michael@0: // should total 100.0. michael@0: #define PROGRESS_PREPARE_SIZE 20.0f michael@0: #define PROGRESS_EXECUTE_SIZE 75.0f michael@0: #define PROGRESS_FINISH_SIZE 5.0f michael@0: michael@0: // Amount of time in ms to wait for the parent process to close michael@0: #define PARENT_WAIT 5000 michael@0: #define IMMERSIVE_PARENT_WAIT 15000 michael@0: michael@0: #if defined(XP_MACOSX) michael@0: // These functions are defined in launchchild_osx.mm michael@0: void LaunchChild(int argc, char **argv); michael@0: void LaunchMacPostProcess(const char* aAppExe); michael@0: #endif michael@0: michael@0: #ifndef _O_BINARY michael@0: # define _O_BINARY 0 michael@0: #endif michael@0: michael@0: #ifndef NULL michael@0: # define NULL (0) michael@0: #endif michael@0: michael@0: #ifndef SSIZE_MAX michael@0: # define SSIZE_MAX LONG_MAX michael@0: #endif michael@0: michael@0: // We want to use execv to invoke the callback executable on platforms where michael@0: // we were launched using execv. See nsUpdateDriver.cpp. michael@0: #if defined(XP_UNIX) && !defined(XP_MACOSX) michael@0: #define USE_EXECV michael@0: #endif michael@0: michael@0: #if defined(MOZ_WIDGET_GONK) michael@0: # include "automounter_gonk.h" michael@0: # include michael@0: # include michael@0: # include michael@0: # include michael@0: michael@0: // The only header file in bionic which has a function prototype for ioprio_set michael@0: // is libc/include/sys/linux-unistd.h. However, linux-unistd.h conflicts michael@0: // badly with unistd.h, so we declare the prototype for ioprio_set directly. michael@0: extern "C" int ioprio_set(int which, int who, int ioprio); michael@0: michael@0: # define MAYBE_USE_HARD_LINKS 1 michael@0: static bool sUseHardLinks = true; michael@0: #else michael@0: # define MAYBE_USE_HARD_LINKS 0 michael@0: #endif michael@0: michael@0: #ifdef XP_WIN michael@0: #include "updatehelper.h" michael@0: michael@0: // Closes the handle if valid and if the updater is elevated returns with the michael@0: // return code specified. This prevents multiple launches of the callback michael@0: // application by preventing the elevated process from launching the callback. michael@0: #define EXIT_WHEN_ELEVATED(path, handle, retCode) \ michael@0: { \ michael@0: if (handle != INVALID_HANDLE_VALUE) { \ michael@0: CloseHandle(handle); \ michael@0: } \ michael@0: if (_waccess(path, F_OK) == 0 && NS_tremove(path) != 0) { \ michael@0: return retCode; \ michael@0: } \ michael@0: } michael@0: #endif michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: // This variable lives in libbz2. It's declared in bzlib_private.h, so we just michael@0: // declare it here to avoid including that entire header file. michael@0: #define BZ2_CRC32TABLE_UNDECLARED michael@0: michael@0: #if MOZ_IS_GCC michael@0: #if MOZ_GCC_VERSION_AT_LEAST(3, 3, 0) michael@0: extern "C" __attribute__((visibility("default"))) unsigned int BZ2_crc32Table[256]; michael@0: #undef BZ2_CRC32TABLE_UNDECLARED michael@0: #endif michael@0: #elif defined(__SUNPRO_C) || defined(__SUNPRO_CC) michael@0: extern "C" __global unsigned int BZ2_crc32Table[256]; michael@0: #undef BZ2_CRC32TABLE_UNDECLARED michael@0: #endif michael@0: #if defined(BZ2_CRC32TABLE_UNDECLARED) michael@0: extern "C" unsigned int BZ2_crc32Table[256]; michael@0: #undef BZ2_CRC32TABLE_UNDECLARED michael@0: #endif michael@0: michael@0: static unsigned int michael@0: crc32(const unsigned char *buf, unsigned int len) michael@0: { michael@0: unsigned int crc = 0xffffffffL; michael@0: michael@0: const unsigned char *end = buf + len; michael@0: for (; buf != end; ++buf) michael@0: crc = (crc << 8) ^ BZ2_crc32Table[(crc >> 24) ^ *buf]; michael@0: michael@0: crc = ~crc; michael@0: return crc; michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: // A simple stack based container for a FILE struct that closes the michael@0: // file descriptor from its destructor. michael@0: class AutoFile michael@0: { michael@0: public: michael@0: AutoFile(FILE* file = nullptr) michael@0: : mFile(file) { michael@0: } michael@0: michael@0: ~AutoFile() { michael@0: if (mFile != nullptr) michael@0: fclose(mFile); michael@0: } michael@0: michael@0: AutoFile &operator=(FILE* file) { michael@0: if (mFile != 0) michael@0: fclose(mFile); michael@0: mFile = file; michael@0: return *this; michael@0: } michael@0: michael@0: operator FILE*() { michael@0: return mFile; michael@0: } michael@0: michael@0: FILE* get() { michael@0: return mFile; michael@0: } michael@0: michael@0: private: michael@0: FILE* mFile; michael@0: }; michael@0: michael@0: struct MARChannelStringTable { michael@0: MARChannelStringTable() michael@0: { michael@0: MARChannelID[0] = '\0'; michael@0: } michael@0: michael@0: char MARChannelID[MAX_TEXT_LEN]; michael@0: }; michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: typedef void (* ThreadFunc)(void *param); michael@0: michael@0: #ifdef XP_WIN michael@0: #include michael@0: michael@0: class Thread michael@0: { michael@0: public: michael@0: int Run(ThreadFunc func, void *param) michael@0: { michael@0: mThreadFunc = func; michael@0: mThreadParam = param; michael@0: michael@0: unsigned int threadID; michael@0: michael@0: mThread = (HANDLE) _beginthreadex(nullptr, 0, ThreadMain, this, 0, michael@0: &threadID); michael@0: michael@0: return mThread ? 0 : -1; michael@0: } michael@0: int Join() michael@0: { michael@0: WaitForSingleObject(mThread, INFINITE); michael@0: CloseHandle(mThread); michael@0: return 0; michael@0: } michael@0: private: michael@0: static unsigned __stdcall ThreadMain(void *p) michael@0: { michael@0: Thread *self = (Thread *) p; michael@0: self->mThreadFunc(self->mThreadParam); michael@0: return 0; michael@0: } michael@0: HANDLE mThread; michael@0: ThreadFunc mThreadFunc; michael@0: void *mThreadParam; michael@0: }; michael@0: michael@0: #elif defined(XP_UNIX) michael@0: #include michael@0: michael@0: class Thread michael@0: { michael@0: public: michael@0: int Run(ThreadFunc func, void *param) michael@0: { michael@0: return pthread_create(&thr, nullptr, (void* (*)(void *)) func, param); michael@0: } michael@0: int Join() michael@0: { michael@0: void *result; michael@0: return pthread_join(thr, &result); michael@0: } michael@0: private: michael@0: pthread_t thr; michael@0: }; michael@0: michael@0: #else michael@0: #error "Unsupported platform" michael@0: #endif michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: static NS_tchar* gSourcePath; michael@0: static NS_tchar gDestinationPath[MAXPATHLEN]; michael@0: static ArchiveReader gArchiveReader; michael@0: static bool gSucceeded = false; michael@0: static bool sStagedUpdate = false; michael@0: static bool sReplaceRequest = false; michael@0: static bool sUsingService = false; michael@0: static bool sIsOSUpdate = false; michael@0: michael@0: #ifdef XP_WIN michael@0: // The current working directory specified in the command line. michael@0: static NS_tchar* gDestPath; michael@0: static NS_tchar gCallbackRelPath[MAXPATHLEN]; michael@0: static NS_tchar gCallbackBackupPath[MAXPATHLEN]; michael@0: #endif michael@0: michael@0: static const NS_tchar kWhitespace[] = NS_T(" \t"); michael@0: static const NS_tchar kNL[] = NS_T("\r\n"); michael@0: static const NS_tchar kQuote[] = NS_T("\""); michael@0: michael@0: static inline size_t michael@0: mmin(size_t a, size_t b) michael@0: { michael@0: return (a > b) ? b : a; michael@0: } michael@0: michael@0: static NS_tchar* michael@0: mstrtok(const NS_tchar *delims, NS_tchar **str) michael@0: { michael@0: if (!*str || !**str) michael@0: return nullptr; michael@0: michael@0: // skip leading "whitespace" michael@0: NS_tchar *ret = *str; michael@0: const NS_tchar *d; michael@0: do { michael@0: for (d = delims; *d != NS_T('\0'); ++d) { michael@0: if (*ret == *d) { michael@0: ++ret; michael@0: break; michael@0: } michael@0: } michael@0: } while (*d); michael@0: michael@0: if (!*ret) { michael@0: *str = ret; michael@0: return nullptr; michael@0: } michael@0: michael@0: NS_tchar *i = ret; michael@0: do { michael@0: for (d = delims; *d != NS_T('\0'); ++d) { michael@0: if (*i == *d) { michael@0: *i = NS_T('\0'); michael@0: *str = ++i; michael@0: return ret; michael@0: } michael@0: } michael@0: ++i; michael@0: } while (*i); michael@0: michael@0: *str = nullptr; michael@0: return ret; michael@0: } michael@0: michael@0: #ifdef XP_WIN michael@0: /** michael@0: * Coverts a relative update path to a full path for Windows. michael@0: * michael@0: * @param relpath michael@0: * The relative path to convert to a full path. michael@0: * @return valid filesystem full path or nullptr if memory allocation fails. michael@0: */ michael@0: static NS_tchar* michael@0: get_full_path(const NS_tchar *relpath) michael@0: { michael@0: size_t lendestpath = NS_tstrlen(gDestPath); michael@0: size_t lenrelpath = NS_tstrlen(relpath); michael@0: NS_tchar *s = (NS_tchar *) malloc((lendestpath + lenrelpath + 1) * sizeof(NS_tchar)); michael@0: if (!s) michael@0: return nullptr; michael@0: michael@0: NS_tchar *c = s; michael@0: michael@0: NS_tstrcpy(c, gDestPath); michael@0: c += lendestpath; michael@0: NS_tstrcat(c, relpath); michael@0: c += lenrelpath; michael@0: *c = NS_T('\0'); michael@0: c++; michael@0: return s; michael@0: } michael@0: #endif michael@0: michael@0: /** michael@0: * Gets the platform specific path and performs simple checks to the path. If michael@0: * the path checks don't pass nullptr will be returned. michael@0: * michael@0: * @param line michael@0: * The line from the manifest that contains the path. michael@0: * @param isdir michael@0: * Whether the path is a directory path. Defaults to false. michael@0: * @param islinktarget michael@0: * Whether the path is a symbolic link target. Defaults to false. michael@0: * @return valid filesystem path or nullptr if the path checks fail. michael@0: */ michael@0: static NS_tchar* michael@0: get_valid_path(NS_tchar **line, bool isdir = false, bool islinktarget = false) michael@0: { michael@0: NS_tchar *path = mstrtok(kQuote, line); michael@0: if (!path) { michael@0: LOG(("get_valid_path: unable to determine path: " LOG_S, line)); michael@0: return nullptr; michael@0: } michael@0: michael@0: // All paths must be relative from the current working directory michael@0: if (path[0] == NS_T('/')) { michael@0: LOG(("get_valid_path: path must be relative: " LOG_S, path)); michael@0: return nullptr; michael@0: } michael@0: michael@0: #ifdef XP_WIN michael@0: // All paths must be relative from the current working directory michael@0: if (path[0] == NS_T('\\') || path[1] == NS_T(':')) { michael@0: LOG(("get_valid_path: path must be relative: " LOG_S, path)); michael@0: return nullptr; michael@0: } michael@0: #endif michael@0: michael@0: if (isdir) { michael@0: // Directory paths must have a trailing forward slash. michael@0: if (path[NS_tstrlen(path) - 1] != NS_T('/')) { michael@0: LOG(("get_valid_path: directory paths must have a trailing forward " \ michael@0: "slash: " LOG_S, path)); michael@0: return nullptr; michael@0: } michael@0: michael@0: // Remove the trailing forward slash because stat on Windows will return michael@0: // ENOENT if the path has a trailing slash. michael@0: path[NS_tstrlen(path) - 1] = NS_T('\0'); michael@0: } michael@0: michael@0: if (!islinktarget) { michael@0: // Don't allow relative paths that resolve to a parent directory. michael@0: if (NS_tstrstr(path, NS_T("..")) != nullptr) { michael@0: LOG(("get_valid_path: paths must not contain '..': " LOG_S, path)); michael@0: return nullptr; michael@0: } michael@0: } michael@0: michael@0: return path; michael@0: } michael@0: michael@0: static NS_tchar* michael@0: get_quoted_path(const NS_tchar *path) michael@0: { michael@0: size_t lenQuote = NS_tstrlen(kQuote); michael@0: size_t lenPath = NS_tstrlen(path); michael@0: size_t len = lenQuote + lenPath + lenQuote + 1; michael@0: michael@0: NS_tchar *s = (NS_tchar *) malloc(len * sizeof(NS_tchar)); michael@0: if (!s) michael@0: return nullptr; michael@0: michael@0: NS_tchar *c = s; michael@0: NS_tstrcpy(c, kQuote); michael@0: c += lenQuote; michael@0: NS_tstrcat(c, path); michael@0: c += lenPath; michael@0: NS_tstrcat(c, kQuote); michael@0: c += lenQuote; michael@0: *c = NS_T('\0'); michael@0: c++; michael@0: return s; michael@0: } michael@0: michael@0: static void ensure_write_permissions(const NS_tchar *path) michael@0: { michael@0: #ifdef XP_WIN michael@0: (void) _wchmod(path, _S_IREAD | _S_IWRITE); michael@0: #else michael@0: struct stat fs; michael@0: if (!NS_tlstat(path, &fs) && !S_ISLNK(fs.st_mode) michael@0: && !(fs.st_mode & S_IWUSR)) { michael@0: (void)chmod(path, fs.st_mode | S_IWUSR); michael@0: } michael@0: #endif michael@0: } michael@0: michael@0: static int ensure_remove(const NS_tchar *path) michael@0: { michael@0: ensure_write_permissions(path); michael@0: int rv = NS_tremove(path); michael@0: if (rv) michael@0: LOG(("ensure_remove: failed to remove file: " LOG_S ", rv: %d, err: %d", michael@0: path, rv, errno)); michael@0: return rv; michael@0: } michael@0: michael@0: // Remove the directory pointed to by path and all of its files and sub-directories. michael@0: static int ensure_remove_recursive(const NS_tchar *path) michael@0: { michael@0: // We use lstat rather than stat here so that we can successfully remove michael@0: // symlinks. michael@0: struct stat sInfo; michael@0: int rv = NS_tlstat(path, &sInfo); michael@0: if (rv) { michael@0: // This error is benign michael@0: return rv; michael@0: } michael@0: if (!S_ISDIR(sInfo.st_mode)) { michael@0: return ensure_remove(path); michael@0: } michael@0: michael@0: NS_tDIR *dir; michael@0: NS_tdirent *entry; michael@0: michael@0: dir = NS_topendir(path); michael@0: if (!dir) { michael@0: LOG(("ensure_remove_recursive: path is not a directory: " LOG_S ", rv: %d, err: %d", michael@0: path, rv, errno)); michael@0: return rv; michael@0: } michael@0: michael@0: while ((entry = NS_treaddir(dir)) != 0) { michael@0: if (NS_tstrcmp(entry->d_name, NS_T(".")) && michael@0: NS_tstrcmp(entry->d_name, NS_T(".."))) { michael@0: NS_tchar childPath[MAXPATHLEN]; michael@0: NS_tsnprintf(childPath, sizeof(childPath)/sizeof(childPath[0]), michael@0: NS_T("%s/%s"), path, entry->d_name); michael@0: rv = ensure_remove_recursive(childPath); michael@0: if (rv) { michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: michael@0: NS_tclosedir(dir); michael@0: michael@0: if (rv == OK) { michael@0: ensure_write_permissions(path); michael@0: rv = NS_trmdir(path); michael@0: if (rv) { michael@0: LOG(("ensure_remove_recursive: path is not a directory: " LOG_S ", rv: %d, err: %d", michael@0: path, rv, errno)); michael@0: } michael@0: } michael@0: return rv; michael@0: } michael@0: michael@0: static bool is_read_only(const NS_tchar *flags) michael@0: { michael@0: size_t length = NS_tstrlen(flags); michael@0: if (length == 0) michael@0: return false; michael@0: michael@0: // Make sure the string begins with "r" michael@0: if (flags[0] != NS_T('r')) michael@0: return false; michael@0: michael@0: // Look for "r+" or "r+b" michael@0: if (length > 1 && flags[1] == NS_T('+')) michael@0: return false; michael@0: michael@0: // Look for "rb+" michael@0: if (NS_tstrcmp(flags, NS_T("rb+")) == 0) michael@0: return false; michael@0: michael@0: return true; michael@0: } michael@0: michael@0: static FILE* ensure_open(const NS_tchar *path, const NS_tchar *flags, unsigned int options) michael@0: { michael@0: ensure_write_permissions(path); michael@0: FILE* f = NS_tfopen(path, flags); michael@0: if (is_read_only(flags)) { michael@0: // Don't attempt to modify the file permissions if the file is being opened michael@0: // in read-only mode. michael@0: return f; michael@0: } michael@0: if (NS_tchmod(path, options) != 0) { michael@0: if (f != nullptr) { michael@0: fclose(f); michael@0: } michael@0: return nullptr; michael@0: } michael@0: struct stat ss; michael@0: if (NS_tstat(path, &ss) != 0 || ss.st_mode != options) { michael@0: if (f != nullptr) { michael@0: fclose(f); michael@0: } michael@0: return nullptr; michael@0: } michael@0: return f; michael@0: } michael@0: michael@0: // Ensure that the directory containing this file exists. michael@0: static int ensure_parent_dir(const NS_tchar *path) michael@0: { michael@0: int rv = OK; michael@0: michael@0: NS_tchar *slash = (NS_tchar *) NS_tstrrchr(path, NS_T('/')); michael@0: if (slash) { michael@0: *slash = NS_T('\0'); michael@0: rv = ensure_parent_dir(path); michael@0: // Only attempt to create the directory if we're not at the root michael@0: if (rv == OK && *path) { michael@0: rv = NS_tmkdir(path, 0755); michael@0: // If the directory already exists, then ignore the error. michael@0: if (rv < 0 && errno != EEXIST) { michael@0: LOG(("ensure_parent_dir: failed to create directory: " LOG_S ", " \ michael@0: "err: %d", path, errno)); michael@0: rv = WRITE_ERROR; michael@0: } else { michael@0: rv = OK; michael@0: } michael@0: } michael@0: *slash = NS_T('/'); michael@0: } michael@0: return rv; michael@0: } michael@0: michael@0: #ifdef XP_UNIX michael@0: static int ensure_copy_symlink(const NS_tchar *path, const NS_tchar *dest) michael@0: { michael@0: // Copy symlinks by creating a new symlink to the same target michael@0: NS_tchar target[MAXPATHLEN + 1] = {NS_T('\0')}; michael@0: int rv = readlink(path, target, MAXPATHLEN); michael@0: if (rv == -1) { michael@0: LOG(("ensure_copy_symlink: failed to read the link: " LOG_S ", err: %d", michael@0: path, errno)); michael@0: return READ_ERROR; michael@0: } michael@0: rv = symlink(target, dest); michael@0: if (rv == -1) { michael@0: LOG(("ensure_copy_symlink: failed to create the new link: " LOG_S ", target: " LOG_S " err: %d", michael@0: dest, target, errno)); michael@0: return READ_ERROR; michael@0: } michael@0: return 0; michael@0: } michael@0: #endif michael@0: michael@0: #if MAYBE_USE_HARD_LINKS michael@0: /* michael@0: * Creates a hardlink (destFilename) which points to the existing file michael@0: * (srcFilename). michael@0: * michael@0: * @return 0 if successful, an error otherwise michael@0: */ michael@0: michael@0: static int michael@0: create_hard_link(const NS_tchar *srcFilename, const NS_tchar *destFilename) michael@0: { michael@0: if (link(srcFilename, destFilename) < 0) { michael@0: LOG(("link(%s, %s) failed errno = %d", srcFilename, destFilename, errno)); michael@0: return WRITE_ERROR; michael@0: } michael@0: return OK; michael@0: } michael@0: #endif michael@0: michael@0: // Copy the file named path onto a new file named dest. michael@0: static int ensure_copy(const NS_tchar *path, const NS_tchar *dest) michael@0: { michael@0: #ifdef XP_WIN michael@0: // Fast path for Windows michael@0: bool result = CopyFileW(path, dest, false); michael@0: if (!result) { michael@0: LOG(("ensure_copy: failed to copy the file " LOG_S " over to " LOG_S ", lasterr: %x", michael@0: path, dest, GetLastError())); michael@0: return WRITE_ERROR; michael@0: } michael@0: return 0; michael@0: #else michael@0: struct stat ss; michael@0: int rv = NS_tlstat(path, &ss); michael@0: if (rv) { michael@0: LOG(("ensure_copy: failed to read file status info: " LOG_S ", err: %d", michael@0: path, errno)); michael@0: return READ_ERROR; michael@0: } michael@0: michael@0: if (S_ISLNK(ss.st_mode)) { michael@0: return ensure_copy_symlink(path, dest); michael@0: } michael@0: michael@0: #if MAYBE_USE_HARD_LINKS michael@0: if (sUseHardLinks) { michael@0: if (!create_hard_link(path, dest)) { michael@0: return OK; michael@0: } michael@0: // Since we failed to create the hard link, fall through and copy the file. michael@0: sUseHardLinks = false; michael@0: } michael@0: #endif michael@0: michael@0: AutoFile infile = ensure_open(path, NS_T("rb"), ss.st_mode); michael@0: if (!infile) { michael@0: LOG(("ensure_copy: failed to open the file for reading: " LOG_S ", err: %d", michael@0: path, errno)); michael@0: return READ_ERROR; michael@0: } michael@0: AutoFile outfile = ensure_open(dest, NS_T("wb"), ss.st_mode); michael@0: if (!outfile) { michael@0: LOG(("ensure_copy: failed to open the file for writing: " LOG_S ", err: %d", michael@0: dest, errno)); michael@0: return WRITE_ERROR; michael@0: } michael@0: michael@0: // This block size was chosen pretty arbitrarily but seems like a reasonable michael@0: // compromise. For example, the optimal block size on a modern OS X machine michael@0: // is 100k */ michael@0: const int blockSize = 32 * 1024; michael@0: void* buffer = malloc(blockSize); michael@0: if (!buffer) michael@0: return UPDATER_MEM_ERROR; michael@0: michael@0: while (!feof(infile.get())) { michael@0: size_t read = fread(buffer, 1, blockSize, infile); michael@0: if (ferror(infile.get())) { michael@0: LOG(("ensure_copy: failed to read the file: " LOG_S ", err: %d", michael@0: path, errno)); michael@0: free(buffer); michael@0: return READ_ERROR; michael@0: } michael@0: michael@0: size_t written = 0; michael@0: michael@0: while (written < read) { michael@0: size_t chunkWritten = fwrite(buffer, 1, read - written, outfile); michael@0: if (chunkWritten <= 0) { michael@0: LOG(("ensure_copy: failed to write the file: " LOG_S ", err: %d", michael@0: dest, errno)); michael@0: free(buffer); michael@0: return WRITE_ERROR; michael@0: } michael@0: michael@0: written += chunkWritten; michael@0: } michael@0: } michael@0: michael@0: rv = NS_tchmod(dest, ss.st_mode); michael@0: michael@0: free(buffer); michael@0: return rv; michael@0: #endif michael@0: } michael@0: michael@0: template michael@0: struct copy_recursive_skiplist { michael@0: NS_tchar paths[N][MAXPATHLEN]; michael@0: michael@0: void append(unsigned index, const NS_tchar *path, const NS_tchar *suffix) { michael@0: NS_tsnprintf(paths[index], MAXPATHLEN, NS_T("%s/%s"), path, suffix); michael@0: } michael@0: bool find(const NS_tchar *path) { michael@0: for (unsigned i = 0; i < N; ++i) { michael@0: if (!NS_tstricmp(paths[i], path)) { michael@0: return true; michael@0: } michael@0: } michael@0: return false; michael@0: } michael@0: }; michael@0: michael@0: // Copy all of the files and subdirectories under path to a new directory named dest. michael@0: // The path names in the skiplist will be skipped and will not be copied. michael@0: template michael@0: static int ensure_copy_recursive(const NS_tchar *path, const NS_tchar *dest, michael@0: copy_recursive_skiplist& skiplist) michael@0: { michael@0: struct stat sInfo; michael@0: int rv = NS_tlstat(path, &sInfo); michael@0: if (rv) { michael@0: LOG(("ensure_copy_recursive: path doesn't exist: " LOG_S ", rv: %d, err: %d", michael@0: path, rv, errno)); michael@0: return READ_ERROR; michael@0: } michael@0: michael@0: #ifndef XP_WIN michael@0: if (S_ISLNK(sInfo.st_mode)) { michael@0: return ensure_copy_symlink(path, dest); michael@0: } michael@0: #endif michael@0: michael@0: if (!S_ISDIR(sInfo.st_mode)) { michael@0: return ensure_copy(path, dest); michael@0: } michael@0: michael@0: rv = NS_tmkdir(dest, sInfo.st_mode); michael@0: if (rv < 0 && errno != EEXIST) { michael@0: LOG(("ensure_copy_recursive: could not create destination directory: " LOG_S ", rv: %d, err: %d", michael@0: path, rv, errno)); michael@0: return WRITE_ERROR; michael@0: } michael@0: michael@0: NS_tDIR *dir; michael@0: NS_tdirent *entry; michael@0: michael@0: dir = NS_topendir(path); michael@0: if (!dir) { michael@0: LOG(("ensure_copy_recursive: path is not a directory: " LOG_S ", rv: %d, err: %d", michael@0: path, rv, errno)); michael@0: return READ_ERROR; michael@0: } michael@0: michael@0: while ((entry = NS_treaddir(dir)) != 0) { michael@0: if (NS_tstrcmp(entry->d_name, NS_T(".")) && michael@0: NS_tstrcmp(entry->d_name, NS_T(".."))) { michael@0: NS_tchar childPath[MAXPATHLEN]; michael@0: NS_tsnprintf(childPath, sizeof(childPath)/sizeof(childPath[0]), michael@0: NS_T("%s/%s"), path, entry->d_name); michael@0: if (skiplist.find(childPath)) { michael@0: continue; michael@0: } michael@0: NS_tchar childPathDest[MAXPATHLEN]; michael@0: NS_tsnprintf(childPathDest, sizeof(childPathDest)/sizeof(childPathDest[0]), michael@0: NS_T("%s/%s"), dest, entry->d_name); michael@0: rv = ensure_copy_recursive(childPath, childPathDest, skiplist); michael@0: if (rv) { michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: // Renames the specified file to the new file specified. If the destination file michael@0: // exists it is removed. michael@0: static int rename_file(const NS_tchar *spath, const NS_tchar *dpath, michael@0: bool allowDirs = false) michael@0: { michael@0: int rv = ensure_parent_dir(dpath); michael@0: if (rv) michael@0: return rv; michael@0: michael@0: struct stat spathInfo; michael@0: rv = NS_tlstat(spath, &spathInfo); michael@0: if (rv) { michael@0: LOG(("rename_file: failed to read file status info: " LOG_S ", " \ michael@0: "err: %d", spath, errno)); michael@0: return READ_ERROR; michael@0: } michael@0: michael@0: #ifdef XP_WIN michael@0: if (!S_ISREG(spathInfo.st_mode)) michael@0: #else michael@0: if (!S_ISREG(spathInfo.st_mode) && !S_ISLNK(spathInfo.st_mode)) michael@0: #endif michael@0: { michael@0: if (allowDirs && !S_ISDIR(spathInfo.st_mode)) { michael@0: LOG(("rename_file: path present, but not a file: " LOG_S ", err: %d", michael@0: spath, errno)); michael@0: return UNEXPECTED_FILE_OPERATION_ERROR; michael@0: } else { michael@0: LOG(("rename_file: proceeding to rename the directory")); michael@0: } michael@0: } michael@0: michael@0: #ifdef XP_WIN michael@0: if (!NS_taccess(dpath, F_OK)) michael@0: #else michael@0: if (!S_ISLNK(spathInfo.st_mode) && !NS_taccess(dpath, F_OK)) michael@0: #endif michael@0: { michael@0: if (ensure_remove(dpath)) { michael@0: LOG(("rename_file: destination file exists and could not be " \ michael@0: "removed: " LOG_S, dpath)); michael@0: return WRITE_ERROR; michael@0: } michael@0: } michael@0: michael@0: if (NS_trename(spath, dpath) != 0) { michael@0: LOG(("rename_file: failed to rename file - src: " LOG_S ", " \ michael@0: "dst:" LOG_S ", err: %d", spath, dpath, errno)); michael@0: return WRITE_ERROR; michael@0: } michael@0: michael@0: return OK; michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: // Create a backup of the specified file by renaming it. michael@0: static int backup_create(const NS_tchar *path) michael@0: { michael@0: NS_tchar backup[MAXPATHLEN]; michael@0: NS_tsnprintf(backup, sizeof(backup)/sizeof(backup[0]), michael@0: NS_T("%s") BACKUP_EXT, path); michael@0: michael@0: return rename_file(path, backup); michael@0: } michael@0: michael@0: // Rename the backup of the specified file that was created by renaming it back michael@0: // to the original file. michael@0: static int backup_restore(const NS_tchar *path) michael@0: { michael@0: NS_tchar backup[MAXPATHLEN]; michael@0: NS_tsnprintf(backup, sizeof(backup)/sizeof(backup[0]), michael@0: NS_T("%s") BACKUP_EXT, path); michael@0: michael@0: bool isLink = false; michael@0: #ifndef XP_WIN michael@0: struct stat linkInfo; michael@0: int rv = NS_tlstat(path, &linkInfo); michael@0: if (!rv) { michael@0: LOG(("backup_restore: cannot get info for backup file: " LOG_S, backup)); michael@0: return OK; michael@0: } michael@0: isLink = S_ISLNK(linkInfo.st_mode); michael@0: #endif michael@0: michael@0: if (!isLink && NS_taccess(backup, F_OK)) { michael@0: LOG(("backup_restore: backup file doesn't exist: " LOG_S, backup)); michael@0: return OK; michael@0: } michael@0: michael@0: return rename_file(backup, path); michael@0: } michael@0: michael@0: // Discard the backup of the specified file that was created by renaming it. michael@0: static int backup_discard(const NS_tchar *path) michael@0: { michael@0: NS_tchar backup[MAXPATHLEN]; michael@0: NS_tsnprintf(backup, sizeof(backup)/sizeof(backup[0]), michael@0: NS_T("%s") BACKUP_EXT, path); michael@0: michael@0: bool isLink = false; michael@0: #ifndef XP_WIN michael@0: struct stat linkInfo; michael@0: int rv2 = NS_tlstat(backup, &linkInfo); michael@0: if (rv2) { michael@0: return OK; // File does not exist; nothing to do. michael@0: } michael@0: isLink = S_ISLNK(linkInfo.st_mode); michael@0: #endif michael@0: michael@0: // Nothing to discard michael@0: if (!isLink && NS_taccess(backup, F_OK)) { michael@0: return OK; michael@0: } michael@0: michael@0: int rv = ensure_remove(backup); michael@0: #if defined(XP_WIN) michael@0: if (rv && !sStagedUpdate && !sReplaceRequest) { michael@0: LOG(("backup_discard: unable to remove: " LOG_S, backup)); michael@0: NS_tchar path[MAXPATHLEN]; michael@0: GetTempFileNameW(DELETE_DIR, L"moz", 0, path); michael@0: if (rename_file(backup, path)) { michael@0: LOG(("backup_discard: failed to rename file:" LOG_S ", dst:" LOG_S, michael@0: backup, path)); michael@0: return WRITE_ERROR; michael@0: } michael@0: // The MoveFileEx call to remove the file on OS reboot will fail if the michael@0: // process doesn't have write access to the HKEY_LOCAL_MACHINE registry key michael@0: // but this is ok since the installer / uninstaller will delete the michael@0: // directory containing the file along with its contents after an update is michael@0: // applied, on reinstall, and on uninstall. michael@0: if (MoveFileEx(path, nullptr, MOVEFILE_DELAY_UNTIL_REBOOT)) { michael@0: LOG(("backup_discard: file renamed and will be removed on OS " \ michael@0: "reboot: " LOG_S, path)); michael@0: } else { michael@0: LOG(("backup_discard: failed to schedule OS reboot removal of " \ michael@0: "file: " LOG_S, path)); michael@0: } michael@0: } michael@0: #else michael@0: if (rv) michael@0: return WRITE_ERROR; michael@0: #endif michael@0: michael@0: return OK; michael@0: } michael@0: michael@0: // Helper function for post-processing a temporary backup. michael@0: static void backup_finish(const NS_tchar *path, int status) michael@0: { michael@0: if (status == OK) michael@0: backup_discard(path); michael@0: else michael@0: backup_restore(path); michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: static int DoUpdate(); michael@0: michael@0: class Action michael@0: { michael@0: public: michael@0: Action() : mProgressCost(1), mNext(nullptr) { } michael@0: virtual ~Action() { } michael@0: michael@0: virtual int Parse(NS_tchar *line) = 0; michael@0: michael@0: // Do any preprocessing to ensure that the action can be performed. Execute michael@0: // will be called if this Action and all others return OK from this method. michael@0: virtual int Prepare() = 0; michael@0: michael@0: // Perform the operation. Return OK to indicate success. After all actions michael@0: // have been executed, Finish will be called. A requirement of Execute is michael@0: // that its operation be reversable from Finish. michael@0: virtual int Execute() = 0; michael@0: michael@0: // Finish is called after execution of all actions. If status is OK, then michael@0: // all actions were successfully executed. Otherwise, some action failed. michael@0: virtual void Finish(int status) = 0; michael@0: michael@0: int mProgressCost; michael@0: private: michael@0: Action* mNext; michael@0: michael@0: friend class ActionList; michael@0: }; michael@0: michael@0: class RemoveFile : public Action michael@0: { michael@0: public: michael@0: RemoveFile() : mFile(nullptr), mSkip(0), mIsLink(0) { } michael@0: michael@0: int Parse(NS_tchar *line); michael@0: int Prepare(); michael@0: int Execute(); michael@0: void Finish(int status); michael@0: michael@0: private: michael@0: const NS_tchar *mFile; michael@0: int mSkip; michael@0: int mIsLink; michael@0: }; michael@0: michael@0: int michael@0: RemoveFile::Parse(NS_tchar *line) michael@0: { michael@0: // format "" michael@0: michael@0: mFile = get_valid_path(&line); michael@0: if (!mFile) michael@0: return PARSE_ERROR; michael@0: michael@0: return OK; michael@0: } michael@0: michael@0: int michael@0: RemoveFile::Prepare() michael@0: { michael@0: int rv; michael@0: #ifndef XP_WIN michael@0: struct stat linkInfo; michael@0: rv = NS_tlstat(mFile, &linkInfo); michael@0: mIsLink = ((0 == rv) && S_ISLNK(linkInfo.st_mode)); michael@0: #endif michael@0: michael@0: if (!mIsLink) { michael@0: // Skip the file if it already doesn't exist. michael@0: rv = NS_taccess(mFile, F_OK); michael@0: if (rv) { michael@0: mSkip = 1; michael@0: mProgressCost = 0; michael@0: return OK; michael@0: } michael@0: } michael@0: michael@0: LOG(("PREPARE REMOVEFILE " LOG_S, mFile)); michael@0: michael@0: if (!mIsLink) { michael@0: // Make sure that we're actually a file... michael@0: struct stat fileInfo; michael@0: rv = NS_tstat(mFile, &fileInfo); michael@0: if (rv) { michael@0: LOG(("failed to read file status info: " LOG_S ", err: %d", mFile, michael@0: errno)); michael@0: return READ_ERROR; michael@0: } michael@0: michael@0: if (!S_ISREG(fileInfo.st_mode)) { michael@0: LOG(("path present, but not a file: " LOG_S, mFile)); michael@0: return UNEXPECTED_FILE_OPERATION_ERROR; michael@0: } michael@0: } michael@0: michael@0: NS_tchar *slash = (NS_tchar *) NS_tstrrchr(mFile, NS_T('/')); michael@0: if (slash) { michael@0: *slash = NS_T('\0'); michael@0: rv = NS_taccess(mFile, W_OK); michael@0: *slash = NS_T('/'); michael@0: } else { michael@0: rv = NS_taccess(NS_T("."), W_OK); michael@0: } michael@0: michael@0: if (rv) { michael@0: LOG(("access failed: %d", errno)); michael@0: return WRITE_ERROR; michael@0: } michael@0: michael@0: return OK; michael@0: } michael@0: michael@0: int michael@0: RemoveFile::Execute() michael@0: { michael@0: if (mSkip) michael@0: return OK; michael@0: michael@0: LOG(("EXECUTE REMOVEFILE " LOG_S, mFile)); michael@0: michael@0: // The file is checked for existence here and in Prepare since it might have michael@0: // been removed by a separate instruction: bug 311099. michael@0: int rv = 0; michael@0: if (mIsLink) { michael@0: struct stat linkInfo; michael@0: rv = NS_tlstat(mFile, &linkInfo); michael@0: } else { michael@0: rv = NS_taccess(mFile, F_OK); michael@0: } michael@0: if (rv) { michael@0: LOG(("file cannot be removed because it does not exist; skipping")); michael@0: mSkip = 1; michael@0: return OK; michael@0: } michael@0: michael@0: // Rename the old file. It will be removed in Finish. michael@0: rv = backup_create(mFile); michael@0: if (rv) { michael@0: LOG(("backup_create failed: %d", rv)); michael@0: return rv; michael@0: } michael@0: michael@0: return OK; michael@0: } michael@0: michael@0: void michael@0: RemoveFile::Finish(int status) michael@0: { michael@0: if (mSkip) michael@0: return; michael@0: michael@0: LOG(("FINISH REMOVEFILE " LOG_S, mFile)); michael@0: michael@0: backup_finish(mFile, status); michael@0: } michael@0: michael@0: class RemoveDir : public Action michael@0: { michael@0: public: michael@0: RemoveDir() : mDir(nullptr), mSkip(0) { } michael@0: michael@0: virtual int Parse(NS_tchar *line); michael@0: virtual int Prepare(); // check that the source dir exists michael@0: virtual int Execute(); michael@0: virtual void Finish(int status); michael@0: michael@0: private: michael@0: const NS_tchar *mDir; michael@0: int mSkip; michael@0: }; michael@0: michael@0: int michael@0: RemoveDir::Parse(NS_tchar *line) michael@0: { michael@0: // format "/" michael@0: michael@0: mDir = get_valid_path(&line, true); michael@0: if (!mDir) michael@0: return PARSE_ERROR; michael@0: michael@0: return OK; michael@0: } michael@0: michael@0: int michael@0: RemoveDir::Prepare() michael@0: { michael@0: // We expect the directory to exist if we are to remove it. michael@0: int rv = NS_taccess(mDir, F_OK); michael@0: if (rv) { michael@0: mSkip = 1; michael@0: mProgressCost = 0; michael@0: return OK; michael@0: } michael@0: michael@0: LOG(("PREPARE REMOVEDIR " LOG_S "/", mDir)); michael@0: michael@0: // Make sure that we're actually a dir. michael@0: struct stat dirInfo; michael@0: rv = NS_tstat(mDir, &dirInfo); michael@0: if (rv) { michael@0: LOG(("failed to read directory status info: " LOG_S ", err: %d", mDir, michael@0: errno)); michael@0: return READ_ERROR; michael@0: } michael@0: michael@0: if (!S_ISDIR(dirInfo.st_mode)) { michael@0: LOG(("path present, but not a directory: " LOG_S, mDir)); michael@0: return UNEXPECTED_FILE_OPERATION_ERROR; michael@0: } michael@0: michael@0: rv = NS_taccess(mDir, W_OK); michael@0: if (rv) { michael@0: LOG(("access failed: %d, %d", rv, errno)); michael@0: return WRITE_ERROR; michael@0: } michael@0: michael@0: return OK; michael@0: } michael@0: michael@0: int michael@0: RemoveDir::Execute() michael@0: { michael@0: if (mSkip) michael@0: return OK; michael@0: michael@0: LOG(("EXECUTE REMOVEDIR " LOG_S "/", mDir)); michael@0: michael@0: // The directory is checked for existence at every step since it might have michael@0: // been removed by a separate instruction: bug 311099. michael@0: int rv = NS_taccess(mDir, F_OK); michael@0: if (rv) { michael@0: LOG(("directory no longer exists; skipping")); michael@0: mSkip = 1; michael@0: } michael@0: michael@0: return OK; michael@0: } michael@0: michael@0: void michael@0: RemoveDir::Finish(int status) michael@0: { michael@0: if (mSkip || status != OK) michael@0: return; michael@0: michael@0: LOG(("FINISH REMOVEDIR " LOG_S "/", mDir)); michael@0: michael@0: // The directory is checked for existence at every step since it might have michael@0: // been removed by a separate instruction: bug 311099. michael@0: int rv = NS_taccess(mDir, F_OK); michael@0: if (rv) { michael@0: LOG(("directory no longer exists; skipping")); michael@0: return; michael@0: } michael@0: michael@0: michael@0: if (status == OK) { michael@0: if (NS_trmdir(mDir)) { michael@0: LOG(("non-fatal error removing directory: " LOG_S "/, rv: %d, err: %d", michael@0: mDir, rv, errno)); michael@0: } michael@0: } michael@0: } michael@0: michael@0: class AddFile : public Action michael@0: { michael@0: public: michael@0: AddFile() : mFile(nullptr) michael@0: , mAdded(false) michael@0: { } michael@0: michael@0: virtual int Parse(NS_tchar *line); michael@0: virtual int Prepare(); michael@0: virtual int Execute(); michael@0: virtual void Finish(int status); michael@0: michael@0: private: michael@0: const NS_tchar *mFile; michael@0: bool mAdded; michael@0: }; michael@0: michael@0: int michael@0: AddFile::Parse(NS_tchar *line) michael@0: { michael@0: // format "" michael@0: michael@0: mFile = get_valid_path(&line); michael@0: if (!mFile) michael@0: return PARSE_ERROR; michael@0: michael@0: return OK; michael@0: } michael@0: michael@0: int michael@0: AddFile::Prepare() michael@0: { michael@0: LOG(("PREPARE ADD " LOG_S, mFile)); michael@0: michael@0: return OK; michael@0: } michael@0: michael@0: int michael@0: AddFile::Execute() michael@0: { michael@0: LOG(("EXECUTE ADD " LOG_S, mFile)); michael@0: michael@0: int rv; michael@0: michael@0: // First make sure that we can actually get rid of any existing file. michael@0: rv = NS_taccess(mFile, F_OK); michael@0: if (rv == 0) { michael@0: rv = backup_create(mFile); michael@0: if (rv) michael@0: return rv; michael@0: } else { michael@0: rv = ensure_parent_dir(mFile); michael@0: if (rv) michael@0: return rv; michael@0: } michael@0: michael@0: #ifdef XP_WIN michael@0: char sourcefile[MAXPATHLEN]; michael@0: if (!WideCharToMultiByte(CP_UTF8, 0, mFile, -1, sourcefile, MAXPATHLEN, michael@0: nullptr, nullptr)) { michael@0: LOG(("error converting wchar to utf8: %d", GetLastError())); michael@0: return STRING_CONVERSION_ERROR; michael@0: } michael@0: michael@0: rv = gArchiveReader.ExtractFile(sourcefile, mFile); michael@0: #else michael@0: rv = gArchiveReader.ExtractFile(mFile, mFile); michael@0: #endif michael@0: if (!rv) { michael@0: mAdded = true; michael@0: } michael@0: return rv; michael@0: } michael@0: michael@0: void michael@0: AddFile::Finish(int status) michael@0: { michael@0: LOG(("FINISH ADD " LOG_S, mFile)); michael@0: // When there is an update failure and a file has been added it is removed michael@0: // here since there might not be a backup to replace it. michael@0: if (status && mAdded) michael@0: NS_tremove(mFile); michael@0: backup_finish(mFile, status); michael@0: } michael@0: michael@0: class PatchFile : public Action michael@0: { michael@0: public: michael@0: PatchFile() : mPatchIndex(-1), buf(nullptr) { } michael@0: michael@0: virtual ~PatchFile(); michael@0: michael@0: virtual int Parse(NS_tchar *line); michael@0: virtual int Prepare(); // should check for patch file and for checksum here michael@0: virtual int Execute(); michael@0: virtual void Finish(int status); michael@0: michael@0: private: michael@0: int LoadSourceFile(FILE* ofile); michael@0: michael@0: static int sPatchIndex; michael@0: michael@0: const NS_tchar *mPatchFile; michael@0: const NS_tchar *mFile; michael@0: int mPatchIndex; michael@0: MBSPatchHeader header; michael@0: unsigned char *buf; michael@0: NS_tchar spath[MAXPATHLEN]; michael@0: }; michael@0: michael@0: int PatchFile::sPatchIndex = 0; michael@0: michael@0: PatchFile::~PatchFile() michael@0: { michael@0: // delete the temporary patch file michael@0: if (spath[0]) michael@0: NS_tremove(spath); michael@0: michael@0: if (buf) michael@0: free(buf); michael@0: } michael@0: michael@0: int michael@0: PatchFile::LoadSourceFile(FILE* ofile) michael@0: { michael@0: struct stat os; michael@0: int rv = fstat(fileno((FILE *)ofile), &os); michael@0: if (rv) { michael@0: LOG(("LoadSourceFile: unable to stat destination file: " LOG_S ", " \ michael@0: "err: %d", mFile, errno)); michael@0: return READ_ERROR; michael@0: } michael@0: michael@0: if (uint32_t(os.st_size) != header.slen) { michael@0: LOG(("LoadSourceFile: destination file size %d does not match expected size %d", michael@0: uint32_t(os.st_size), header.slen)); michael@0: return UNEXPECTED_FILE_OPERATION_ERROR; michael@0: } michael@0: michael@0: buf = (unsigned char *) malloc(header.slen); michael@0: if (!buf) michael@0: return UPDATER_MEM_ERROR; michael@0: michael@0: size_t r = header.slen; michael@0: unsigned char *rb = buf; michael@0: while (r) { michael@0: const size_t count = mmin(SSIZE_MAX, r); michael@0: size_t c = fread(rb, 1, count, ofile); michael@0: if (c != count) { michael@0: LOG(("LoadSourceFile: error reading destination file: " LOG_S, michael@0: mFile)); michael@0: return READ_ERROR; michael@0: } michael@0: michael@0: r -= c; michael@0: rb += c; michael@0: } michael@0: michael@0: // Verify that the contents of the source file correspond to what we expect. michael@0: michael@0: unsigned int crc = crc32(buf, header.slen); michael@0: michael@0: if (crc != header.scrc32) { michael@0: LOG(("LoadSourceFile: destination file crc %d does not match expected " \ michael@0: "crc %d", crc, header.scrc32)); michael@0: return CRC_ERROR; michael@0: } michael@0: michael@0: return OK; michael@0: } michael@0: michael@0: int michael@0: PatchFile::Parse(NS_tchar *line) michael@0: { michael@0: // format "" "" michael@0: michael@0: // Get the path to the patch file inside of the mar michael@0: mPatchFile = mstrtok(kQuote, &line); michael@0: if (!mPatchFile) michael@0: return PARSE_ERROR; michael@0: michael@0: // consume whitespace between args michael@0: NS_tchar *q = mstrtok(kQuote, &line); michael@0: if (!q) michael@0: return PARSE_ERROR; michael@0: michael@0: mFile = get_valid_path(&line); michael@0: if (!mFile) michael@0: return PARSE_ERROR; michael@0: michael@0: return OK; michael@0: } michael@0: michael@0: int michael@0: PatchFile::Prepare() michael@0: { michael@0: LOG(("PREPARE PATCH " LOG_S, mFile)); michael@0: michael@0: // extract the patch to a temporary file michael@0: mPatchIndex = sPatchIndex++; michael@0: michael@0: NS_tsnprintf(spath, sizeof(spath)/sizeof(spath[0]), michael@0: NS_T("%s/updating/%d.patch"), gDestinationPath, mPatchIndex); michael@0: michael@0: NS_tremove(spath); michael@0: michael@0: FILE *fp = NS_tfopen(spath, NS_T("wb")); michael@0: if (!fp) michael@0: return WRITE_ERROR; michael@0: michael@0: #ifdef XP_WIN michael@0: char sourcefile[MAXPATHLEN]; michael@0: if (!WideCharToMultiByte(CP_UTF8, 0, mPatchFile, -1, sourcefile, MAXPATHLEN, michael@0: nullptr, nullptr)) { michael@0: LOG(("error converting wchar to utf8: %d", GetLastError())); michael@0: return STRING_CONVERSION_ERROR; michael@0: } michael@0: michael@0: int rv = gArchiveReader.ExtractFileToStream(sourcefile, fp); michael@0: #else michael@0: int rv = gArchiveReader.ExtractFileToStream(mPatchFile, fp); michael@0: #endif michael@0: fclose(fp); michael@0: return rv; michael@0: } michael@0: michael@0: int michael@0: PatchFile::Execute() michael@0: { michael@0: LOG(("EXECUTE PATCH " LOG_S, mFile)); michael@0: michael@0: AutoFile pfile = NS_tfopen(spath, NS_T("rb")); michael@0: if (pfile == nullptr) michael@0: return READ_ERROR; michael@0: michael@0: int rv = MBS_ReadHeader(pfile, &header); michael@0: if (rv) michael@0: return rv; michael@0: michael@0: FILE *origfile = nullptr; michael@0: #ifdef XP_WIN michael@0: if (NS_tstrcmp(mFile, gCallbackRelPath) == 0) { michael@0: // Read from the copy of the callback when patching since the callback can't michael@0: // be opened for reading to prevent the application from being launched. michael@0: origfile = NS_tfopen(gCallbackBackupPath, NS_T("rb")); michael@0: } else { michael@0: origfile = NS_tfopen(mFile, NS_T("rb")); michael@0: } michael@0: #else michael@0: origfile = NS_tfopen(mFile, NS_T("rb")); michael@0: #endif michael@0: michael@0: if (!origfile) { michael@0: LOG(("unable to open destination file: " LOG_S ", err: %d", mFile, michael@0: errno)); michael@0: return READ_ERROR; michael@0: } michael@0: michael@0: rv = LoadSourceFile(origfile); michael@0: fclose(origfile); michael@0: if (rv) { michael@0: LOG(("LoadSourceFile failed")); michael@0: return rv; michael@0: } michael@0: michael@0: // Rename the destination file if it exists before proceeding so it can be michael@0: // used to restore the file to its original state if there is an error. michael@0: struct stat ss; michael@0: rv = NS_tstat(mFile, &ss); michael@0: if (rv) { michael@0: LOG(("failed to read file status info: " LOG_S ", err: %d", mFile, michael@0: errno)); michael@0: return READ_ERROR; michael@0: } michael@0: michael@0: rv = backup_create(mFile); michael@0: if (rv) michael@0: return rv; michael@0: michael@0: #if defined(HAVE_POSIX_FALLOCATE) michael@0: AutoFile ofile = ensure_open(mFile, NS_T("wb+"), ss.st_mode); michael@0: posix_fallocate(fileno((FILE *)ofile), 0, header.dlen); michael@0: #elif defined(XP_WIN) michael@0: bool shouldTruncate = true; michael@0: // Creating the file, setting the size, and then closing the file handle michael@0: // lessens fragmentation more than any other method tested. Other methods that michael@0: // have been tested are: michael@0: // 1. _chsize / _chsize_s reduced fragmentation but though not completely. michael@0: // 2. _get_osfhandle and then setting the size reduced fragmentation though michael@0: // not completely. There are also reports of _get_osfhandle failing on michael@0: // mingw. michael@0: HANDLE hfile = CreateFileW(mFile, michael@0: GENERIC_WRITE, michael@0: 0, michael@0: nullptr, michael@0: CREATE_ALWAYS, michael@0: FILE_ATTRIBUTE_NORMAL, michael@0: nullptr); michael@0: michael@0: if (hfile != INVALID_HANDLE_VALUE) { michael@0: if (SetFilePointer(hfile, header.dlen, michael@0: nullptr, FILE_BEGIN) != INVALID_SET_FILE_POINTER && michael@0: SetEndOfFile(hfile) != 0) { michael@0: shouldTruncate = false; michael@0: } michael@0: CloseHandle(hfile); michael@0: } michael@0: michael@0: AutoFile ofile = ensure_open(mFile, shouldTruncate ? NS_T("wb+") : NS_T("rb+"), ss.st_mode); michael@0: #elif defined(XP_MACOSX) michael@0: AutoFile ofile = ensure_open(mFile, NS_T("wb+"), ss.st_mode); michael@0: // Modified code from FileUtils.cpp michael@0: fstore_t store = {F_ALLOCATECONTIG, F_PEOFPOSMODE, 0, header.dlen}; michael@0: // Try to get a continous chunk of disk space michael@0: rv = fcntl(fileno((FILE *)ofile), F_PREALLOCATE, &store); michael@0: if (rv == -1) { michael@0: // OK, perhaps we are too fragmented, allocate non-continuous michael@0: store.fst_flags = F_ALLOCATEALL; michael@0: rv = fcntl(fileno((FILE *)ofile), F_PREALLOCATE, &store); michael@0: } michael@0: michael@0: if (rv != -1) { michael@0: ftruncate(fileno((FILE *)ofile), header.dlen); michael@0: } michael@0: #else michael@0: AutoFile ofile = ensure_open(mFile, NS_T("wb+"), ss.st_mode); michael@0: #endif michael@0: michael@0: if (ofile == nullptr) { michael@0: LOG(("unable to create new file: " LOG_S ", err: %d", mFile, errno)); michael@0: return WRITE_ERROR; michael@0: } michael@0: michael@0: #ifdef XP_WIN michael@0: if (!shouldTruncate) { michael@0: fseek(ofile, 0, SEEK_SET); michael@0: } michael@0: #endif michael@0: michael@0: rv = MBS_ApplyPatch(&header, pfile, buf, ofile); michael@0: michael@0: // Go ahead and do a bit of cleanup now to minimize runtime overhead. michael@0: // Set pfile to nullptr to make AutoFile close the file so it can be deleted michael@0: // on Windows. michael@0: pfile = nullptr; michael@0: NS_tremove(spath); michael@0: spath[0] = NS_T('\0'); michael@0: free(buf); michael@0: buf = nullptr; michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: void michael@0: PatchFile::Finish(int status) michael@0: { michael@0: LOG(("FINISH PATCH " LOG_S, mFile)); michael@0: michael@0: backup_finish(mFile, status); michael@0: } michael@0: michael@0: class AddIfFile : public AddFile michael@0: { michael@0: public: michael@0: AddIfFile() : mTestFile(nullptr) { } michael@0: michael@0: virtual int Parse(NS_tchar *line); michael@0: virtual int Prepare(); michael@0: virtual int Execute(); michael@0: virtual void Finish(int status); michael@0: michael@0: protected: michael@0: const NS_tchar *mTestFile; michael@0: }; michael@0: michael@0: int michael@0: AddIfFile::Parse(NS_tchar *line) michael@0: { michael@0: // format "" "" michael@0: michael@0: mTestFile = get_valid_path(&line); michael@0: if (!mTestFile) michael@0: return PARSE_ERROR; michael@0: michael@0: // consume whitespace between args michael@0: NS_tchar *q = mstrtok(kQuote, &line); michael@0: if (!q) michael@0: return PARSE_ERROR; michael@0: michael@0: return AddFile::Parse(line); michael@0: } michael@0: michael@0: int michael@0: AddIfFile::Prepare() michael@0: { michael@0: // If the test file does not exist, then skip this action. michael@0: if (NS_taccess(mTestFile, F_OK)) { michael@0: mTestFile = nullptr; michael@0: return OK; michael@0: } michael@0: michael@0: return AddFile::Prepare(); michael@0: } michael@0: michael@0: int michael@0: AddIfFile::Execute() michael@0: { michael@0: if (!mTestFile) michael@0: return OK; michael@0: michael@0: return AddFile::Execute(); michael@0: } michael@0: michael@0: void michael@0: AddIfFile::Finish(int status) michael@0: { michael@0: if (!mTestFile) michael@0: return; michael@0: michael@0: AddFile::Finish(status); michael@0: } michael@0: michael@0: class AddIfNotFile : public AddFile michael@0: { michael@0: public: michael@0: AddIfNotFile() : mTestFile(NULL) { } michael@0: michael@0: virtual int Parse(NS_tchar *line); michael@0: virtual int Prepare(); michael@0: virtual int Execute(); michael@0: virtual void Finish(int status); michael@0: michael@0: protected: michael@0: const NS_tchar *mTestFile; michael@0: }; michael@0: michael@0: int michael@0: AddIfNotFile::Parse(NS_tchar *line) michael@0: { michael@0: // format "" "" michael@0: michael@0: mTestFile = get_valid_path(&line); michael@0: if (!mTestFile) michael@0: return PARSE_ERROR; michael@0: michael@0: // consume whitespace between args michael@0: NS_tchar *q = mstrtok(kQuote, &line); michael@0: if (!q) michael@0: return PARSE_ERROR; michael@0: michael@0: return AddFile::Parse(line); michael@0: } michael@0: michael@0: int michael@0: AddIfNotFile::Prepare() michael@0: { michael@0: // If the test file exists, then skip this action. michael@0: if (!NS_taccess(mTestFile, F_OK)) { michael@0: mTestFile = NULL; michael@0: return OK; michael@0: } michael@0: michael@0: return AddFile::Prepare(); michael@0: } michael@0: michael@0: int michael@0: AddIfNotFile::Execute() michael@0: { michael@0: if (!mTestFile) michael@0: return OK; michael@0: michael@0: return AddFile::Execute(); michael@0: } michael@0: michael@0: void michael@0: AddIfNotFile::Finish(int status) michael@0: { michael@0: if (!mTestFile) michael@0: return; michael@0: michael@0: AddFile::Finish(status); michael@0: } michael@0: michael@0: class PatchIfFile : public PatchFile michael@0: { michael@0: public: michael@0: PatchIfFile() : mTestFile(nullptr) { } michael@0: michael@0: virtual int Parse(NS_tchar *line); michael@0: virtual int Prepare(); // should check for patch file and for checksum here michael@0: virtual int Execute(); michael@0: virtual void Finish(int status); michael@0: michael@0: private: michael@0: const NS_tchar *mTestFile; michael@0: }; michael@0: michael@0: int michael@0: PatchIfFile::Parse(NS_tchar *line) michael@0: { michael@0: // format "" "" "" michael@0: michael@0: mTestFile = get_valid_path(&line); michael@0: if (!mTestFile) michael@0: return PARSE_ERROR; michael@0: michael@0: // consume whitespace between args michael@0: NS_tchar *q = mstrtok(kQuote, &line); michael@0: if (!q) michael@0: return PARSE_ERROR; michael@0: michael@0: return PatchFile::Parse(line); michael@0: } michael@0: michael@0: int michael@0: PatchIfFile::Prepare() michael@0: { michael@0: // If the test file does not exist, then skip this action. michael@0: if (NS_taccess(mTestFile, F_OK)) { michael@0: mTestFile = nullptr; michael@0: return OK; michael@0: } michael@0: michael@0: return PatchFile::Prepare(); michael@0: } michael@0: michael@0: int michael@0: PatchIfFile::Execute() michael@0: { michael@0: if (!mTestFile) michael@0: return OK; michael@0: michael@0: return PatchFile::Execute(); michael@0: } michael@0: michael@0: void michael@0: PatchIfFile::Finish(int status) michael@0: { michael@0: if (!mTestFile) michael@0: return; michael@0: michael@0: PatchFile::Finish(status); michael@0: } michael@0: michael@0: #ifndef XP_WIN michael@0: class AddSymlink : public Action michael@0: { michael@0: public: michael@0: AddSymlink() : mLinkName(NULL) michael@0: , mTarget(NULL) michael@0: , mAdded(false) michael@0: { } michael@0: michael@0: virtual int Parse(NS_tchar *line); michael@0: virtual int Prepare(); michael@0: virtual int Execute(); michael@0: virtual void Finish(int status); michael@0: michael@0: private: michael@0: const NS_tchar *mLinkName; michael@0: const NS_tchar *mTarget; michael@0: bool mAdded; michael@0: }; michael@0: michael@0: int michael@0: AddSymlink::Parse(NS_tchar *line) michael@0: { michael@0: // format "" "target" michael@0: michael@0: mLinkName = get_valid_path(&line); michael@0: if (!mLinkName) michael@0: return PARSE_ERROR; michael@0: michael@0: // consume whitespace between args michael@0: NS_tchar *q = mstrtok(kQuote, &line); michael@0: if (!q) michael@0: return PARSE_ERROR; michael@0: michael@0: mTarget = get_valid_path(&line, false, true); michael@0: if (!mTarget) michael@0: return PARSE_ERROR; michael@0: michael@0: return OK; michael@0: } michael@0: michael@0: int michael@0: AddSymlink::Prepare() michael@0: { michael@0: LOG(("PREPARE ADDSYMLINK " LOG_S " -> " LOG_S, mLinkName, mTarget)); michael@0: michael@0: return OK; michael@0: } michael@0: michael@0: int michael@0: AddSymlink::Execute() michael@0: { michael@0: LOG(("EXECUTE ADDSYMLINK " LOG_S " -> " LOG_S, mLinkName, mTarget)); michael@0: michael@0: // First make sure that we can actually get rid of any existing file or link. michael@0: struct stat linkInfo; michael@0: int rv = NS_tlstat(mLinkName, &linkInfo); michael@0: if ((0 == rv) && !S_ISLNK(linkInfo.st_mode)) { michael@0: rv = NS_taccess(mLinkName, F_OK); michael@0: } michael@0: if (rv == 0) { michael@0: rv = backup_create(mLinkName); michael@0: if (rv) michael@0: return rv; michael@0: } else { michael@0: rv = ensure_parent_dir(mLinkName); michael@0: if (rv) michael@0: return rv; michael@0: } michael@0: michael@0: // Create the link. michael@0: rv = symlink(mTarget, mLinkName); michael@0: if (!rv) { michael@0: mAdded = true; michael@0: } michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: void michael@0: AddSymlink::Finish(int status) michael@0: { michael@0: LOG(("FINISH ADDSYMLINK " LOG_S " -> " LOG_S, mLinkName, mTarget)); michael@0: // When there is an update failure and a link has been added it is removed michael@0: // here since there might not be a backup to replace it. michael@0: if (status && mAdded) michael@0: NS_tremove(mLinkName); michael@0: backup_finish(mLinkName, status); michael@0: } michael@0: #endif michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: #ifdef XP_WIN michael@0: #include "nsWindowsRestart.cpp" michael@0: #include "nsWindowsHelpers.h" michael@0: #include "uachelper.h" michael@0: #include "pathhash.h" michael@0: michael@0: #ifdef MOZ_METRO michael@0: /** michael@0: * Determines if the update came from an Immersive browser michael@0: * @return true if the update came from an immersive browser michael@0: */ michael@0: bool michael@0: IsUpdateFromMetro(int argc, NS_tchar **argv) michael@0: { michael@0: for (int i = 0; i < argc; i++) { michael@0: if (!wcsicmp(L"-ServerName:DefaultBrowserServer", argv[i])) { michael@0: return true; michael@0: } michael@0: } michael@0: return false; michael@0: } michael@0: #endif michael@0: #endif michael@0: michael@0: static void michael@0: LaunchCallbackApp(const NS_tchar *workingDir, michael@0: int argc, michael@0: NS_tchar **argv, michael@0: bool usingService) michael@0: { michael@0: putenv(const_cast("NO_EM_RESTART=")); michael@0: putenv(const_cast("MOZ_LAUNCHED_CHILD=1")); michael@0: michael@0: // Run from the specified working directory (see bug 312360). This is not michael@0: // necessary on Windows CE since the application that launches the updater michael@0: // passes the working directory as an --environ: command line argument. michael@0: if (NS_tchdir(workingDir) != 0) { michael@0: LOG(("Warning: chdir failed")); michael@0: } michael@0: michael@0: #if defined(USE_EXECV) michael@0: execv(argv[0], argv); michael@0: #elif defined(XP_MACOSX) michael@0: LaunchChild(argc, argv); michael@0: #elif defined(XP_WIN) michael@0: // Do not allow the callback to run when running an update through the michael@0: // service as session 0. The unelevated updater.exe will do the launching. michael@0: if (!usingService) { michael@0: #if defined(MOZ_METRO) michael@0: // If our callback application is the default metro browser, then michael@0: // launch it now. michael@0: if (IsUpdateFromMetro(argc, argv)) { michael@0: LaunchDefaultMetroBrowser(); michael@0: return; michael@0: } michael@0: #endif michael@0: WinLaunchChild(argv[0], argc, argv, nullptr); michael@0: } michael@0: #else michael@0: # warning "Need implementaton of LaunchCallbackApp" michael@0: #endif michael@0: } michael@0: michael@0: static bool michael@0: WriteStatusFile(const char* aStatus) michael@0: { michael@0: NS_tchar filename[MAXPATHLEN]; michael@0: NS_tsnprintf(filename, sizeof(filename)/sizeof(filename[0]), michael@0: NS_T("%s/update.status"), gSourcePath); michael@0: michael@0: // Make sure that the directory for the update status file exists michael@0: if (ensure_parent_dir(filename)) michael@0: return false; michael@0: michael@0: AutoFile file = NS_tfopen(filename, NS_T("wb+")); michael@0: if (file == nullptr) michael@0: return false; michael@0: michael@0: if (fwrite(aStatus, strlen(aStatus), 1, file) != 1) michael@0: return false; michael@0: michael@0: return true; michael@0: } michael@0: michael@0: static void michael@0: WriteStatusFile(int status) michael@0: { michael@0: const char *text; michael@0: michael@0: char buf[32]; michael@0: if (status == OK) { michael@0: if (sStagedUpdate) { michael@0: text = "applied\n"; michael@0: } else { michael@0: text = "succeeded\n"; michael@0: } michael@0: } else { michael@0: snprintf(buf, sizeof(buf)/sizeof(buf[0]), "failed: %d\n", status); michael@0: text = buf; michael@0: } michael@0: michael@0: WriteStatusFile(text); michael@0: } michael@0: michael@0: #ifdef MOZ_MAINTENANCE_SERVICE michael@0: /* michael@0: * Read the update.status file and sets isPendingService to true if michael@0: * the status is set to pending-service. michael@0: * michael@0: * @param isPendingService Out parameter for specifying if the status michael@0: * is set to pending-service or not. michael@0: * @return true if the information was retrieved and it is pending michael@0: * or pending-service. michael@0: */ michael@0: static bool michael@0: IsUpdateStatusPendingService() michael@0: { michael@0: NS_tchar filename[MAXPATHLEN]; michael@0: NS_tsnprintf(filename, sizeof(filename)/sizeof(filename[0]), michael@0: NS_T("%s/update.status"), gSourcePath); michael@0: michael@0: AutoFile file = NS_tfopen(filename, NS_T("rb")); michael@0: if (file == nullptr) michael@0: return false; michael@0: michael@0: char buf[32] = { 0 }; michael@0: fread(buf, sizeof(buf), 1, file); michael@0: michael@0: const char kPendingService[] = "pending-service"; michael@0: const char kAppliedService[] = "applied-service"; michael@0: michael@0: return (strncmp(buf, kPendingService, michael@0: sizeof(kPendingService) - 1) == 0) || michael@0: (strncmp(buf, kAppliedService, michael@0: sizeof(kAppliedService) - 1) == 0); michael@0: } michael@0: #endif michael@0: michael@0: #ifdef XP_WIN michael@0: /* michael@0: * Read the update.status file and sets isSuccess to true if michael@0: * the status is set to succeeded. michael@0: * michael@0: * @param isSucceeded Out parameter for specifying if the status michael@0: * is set to succeeded or not. michael@0: * @return true if the information was retrieved and it is succeeded. michael@0: */ michael@0: static bool michael@0: IsUpdateStatusSucceeded(bool &isSucceeded) michael@0: { michael@0: isSucceeded = false; michael@0: NS_tchar filename[MAXPATHLEN]; michael@0: NS_tsnprintf(filename, sizeof(filename)/sizeof(filename[0]), michael@0: NS_T("%s/update.status"), gSourcePath); michael@0: michael@0: AutoFile file = NS_tfopen(filename, NS_T("rb")); michael@0: if (file == nullptr) michael@0: return false; michael@0: michael@0: char buf[32] = { 0 }; michael@0: fread(buf, sizeof(buf), 1, file); michael@0: michael@0: const char kSucceeded[] = "succeeded"; michael@0: isSucceeded = strncmp(buf, kSucceeded, michael@0: sizeof(kSucceeded) - 1) == 0; michael@0: return true; michael@0: } michael@0: #endif michael@0: michael@0: /* michael@0: * Get the application installation directory. michael@0: * michael@0: * @param installDir Out parameter for specifying the installation directory. michael@0: * @return true if successful, false otherwise. michael@0: */ michael@0: template michael@0: static bool michael@0: GetInstallationDir(NS_tchar (&installDir)[N]) michael@0: { michael@0: NS_tsnprintf(installDir, N, NS_T("%s"), gDestinationPath); michael@0: if (!sStagedUpdate && !sReplaceRequest) { michael@0: // no need to do any further processing michael@0: return true; michael@0: } michael@0: michael@0: NS_tchar *slash = (NS_tchar *) NS_tstrrchr(installDir, NS_SLASH); michael@0: // Make sure we're not looking at a trailing slash michael@0: if (slash && slash[1] == NS_T('\0')) { michael@0: *slash = NS_T('\0'); michael@0: slash = (NS_tchar *) NS_tstrrchr(installDir, NS_SLASH); michael@0: } michael@0: if (slash) { michael@0: *slash = NS_T('\0'); michael@0: } else { michael@0: return false; michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: /* michael@0: * Copy the entire contents of the application installation directory to the michael@0: * destination directory for the update process. michael@0: * michael@0: * @return 0 if successful, an error code otherwise. michael@0: */ michael@0: static int michael@0: CopyInstallDirToDestDir() michael@0: { michael@0: // First extract the installation directory from gSourcePath by going two michael@0: // levels above it. This is effectively skipping over "updates/0". michael@0: NS_tchar installDir[MAXPATHLEN]; michael@0: if (!GetInstallationDir(installDir)) { michael@0: return NO_INSTALLDIR_ERROR; michael@0: } michael@0: michael@0: // These files should not be copied over to the updated app michael@0: #ifdef XP_WIN michael@0: #ifdef TOR_BROWSER_UPDATE michael@0: #define SKIPLIST_COUNT 5 michael@0: #else michael@0: #define SKIPLIST_COUNT 3 michael@0: #endif michael@0: #else michael@0: #ifdef TOR_BROWSER_UPDATE michael@0: #define SKIPLIST_COUNT 4 michael@0: #else michael@0: #define SKIPLIST_COUNT 2 michael@0: #endif michael@0: #endif michael@0: copy_recursive_skiplist skiplist; michael@0: #ifdef XP_MACOSX michael@0: skiplist.append(0, installDir, NS_T("Updated.app")); michael@0: skiplist.append(1, installDir, NS_T("Contents/MacOS/updates/0")); michael@0: #ifdef TOR_BROWSER_UPDATE michael@0: skiplist.append(2, installDir, NS_T("TorBrowser/Data/Browser/profile.default/.parentlock")); michael@0: skiplist.append(3, installDir, NS_T("TorBrowser/Data/Tor/lock")); michael@0: #endif michael@0: #else michael@0: skiplist.append(0, installDir, NS_T("updated")); michael@0: skiplist.append(1, installDir, NS_T("updates/0")); michael@0: #ifdef TOR_BROWSER_UPDATE michael@0: #ifdef XP_UNIX michael@0: skiplist.append(2, installDir, NS_T("TorBrowser/Data/Browser/profile.default/.parentlock")); michael@0: #else michael@0: skiplist.append(2, installDir, NS_T("TorBrowser/Data/Browser/profile.default/parent.lock")); michael@0: #endif michael@0: skiplist.append(3, installDir, NS_T("TorBrowser/Data/Tor/lock")); michael@0: #endif michael@0: #ifdef XP_WIN michael@0: skiplist.append(SKIPLIST_COUNT - 1, installDir, michael@0: NS_T("updated.update_in_progress.lock")); michael@0: #endif michael@0: #endif michael@0: michael@0: return ensure_copy_recursive(installDir, gDestinationPath, skiplist); michael@0: } michael@0: michael@0: /* michael@0: * Replace the application installation directory with the destination michael@0: * directory in order to finish a staged update task michael@0: * michael@0: * @return 0 if successful, an error code otherwise. michael@0: */ michael@0: static int michael@0: ProcessReplaceRequest() michael@0: { michael@0: // The replacement algorithm is like this: michael@0: // 1. Move sourceDir to tmpDir. In case of failure, abort. michael@0: // 2. Move newDir to sourceDir. In case of failure, revert step 1 and abort. michael@0: // 3. Delete tmpDir (or defer it to the next reboot). michael@0: michael@0: NS_tchar installDir[MAXPATHLEN]; michael@0: if (!GetInstallationDir(installDir)) { michael@0: return NO_INSTALLDIR_ERROR; michael@0: } michael@0: michael@0: #ifdef XP_MACOSX michael@0: NS_tchar sourceDir[MAXPATHLEN]; michael@0: NS_tsnprintf(sourceDir, sizeof(sourceDir)/sizeof(sourceDir[0]), michael@0: NS_T("%s/Contents"), installDir); michael@0: #elif XP_WIN michael@0: // Windows preserves the case of the file/directory names. We use the michael@0: // GetLongPathName API in order to get the correct case for the directory michael@0: // name, so that if the user has used a different case when launching the michael@0: // application, the installation directory's name does not change. michael@0: NS_tchar sourceDir[MAXPATHLEN]; michael@0: if (!GetLongPathNameW(installDir, sourceDir, sizeof(sourceDir)/sizeof(sourceDir[0]))) { michael@0: return NO_INSTALLDIR_ERROR; michael@0: } michael@0: #else michael@0: NS_tchar* sourceDir = installDir; michael@0: #endif michael@0: michael@0: NS_tchar tmpDir[MAXPATHLEN]; michael@0: NS_tsnprintf(tmpDir, sizeof(tmpDir)/sizeof(tmpDir[0]), michael@0: NS_T("%s.bak"), sourceDir); michael@0: michael@0: NS_tchar newDir[MAXPATHLEN]; michael@0: NS_tsnprintf(newDir, sizeof(newDir)/sizeof(newDir[0]), michael@0: #ifdef XP_MACOSX michael@0: NS_T("%s/Updated.app/Contents"), michael@0: #else michael@0: NS_T("%s.bak/updated"), michael@0: #endif michael@0: installDir); michael@0: michael@0: // First try to remove the possibly existing temp directory, because if this michael@0: // directory exists, we will fail to rename sourceDir. michael@0: // No need to error check here because if this fails, we will fail in the michael@0: // next step anyways. michael@0: ensure_remove_recursive(tmpDir); michael@0: michael@0: LOG(("Begin moving sourceDir (" LOG_S ") to tmpDir (" LOG_S ")", michael@0: sourceDir, tmpDir)); michael@0: int rv = rename_file(sourceDir, tmpDir, true); michael@0: #ifdef XP_WIN michael@0: // On Windows, if Firefox is launched using the shortcut, it will hold a handle michael@0: // to its installation directory open, which might not get released in time. michael@0: // Therefore we wait a little bit here to see if the handle is released. michael@0: // If it's not released, we just fail to perform the replace request. michael@0: const int max_retries = 10; michael@0: int retries = 0; michael@0: while (rv == WRITE_ERROR && (retries++ < max_retries)) { michael@0: LOG(("PerformReplaceRequest: sourceDir rename attempt %d failed. " \ michael@0: "File: " LOG_S ". Last error: %d, err: %d", retries, michael@0: sourceDir, GetLastError(), rv)); michael@0: michael@0: Sleep(100); michael@0: michael@0: rv = rename_file(sourceDir, tmpDir, true); michael@0: } michael@0: #endif michael@0: if (rv) { michael@0: LOG(("Moving sourceDir to tmpDir failed, err: %d", rv)); michael@0: return rv; michael@0: } michael@0: michael@0: LOG(("Begin moving newDir (" LOG_S ") to sourceDir (" LOG_S ")", michael@0: newDir, sourceDir)); michael@0: rv = rename_file(newDir, sourceDir, true); michael@0: if (rv) { michael@0: LOG(("Moving newDir to sourceDir failed, err: %d", rv)); michael@0: LOG(("Now, try to move tmpDir back to sourceDir")); michael@0: ensure_remove_recursive(sourceDir); michael@0: int rv2 = rename_file(tmpDir, sourceDir, true); michael@0: if (rv2) { michael@0: LOG(("Moving tmpDir back to sourceDir failed, err: %d", rv2)); michael@0: } michael@0: return rv; michael@0: } michael@0: michael@0: LOG(("Now, remove the tmpDir")); michael@0: rv = ensure_remove_recursive(tmpDir); michael@0: if (rv) { michael@0: LOG(("Removing tmpDir failed, err: %d", rv)); michael@0: #ifdef XP_WIN michael@0: if (MoveFileExW(tmpDir, nullptr, MOVEFILE_DELAY_UNTIL_REBOOT)) { michael@0: LOG(("tmpDir will be removed on OS reboot: " LOG_S, tmpDir)); michael@0: } else { michael@0: LOG(("Failed to schedule OS reboot removal of directory: " LOG_S, michael@0: tmpDir)); michael@0: } michael@0: #endif michael@0: } michael@0: michael@0: #ifdef XP_MACOSX michael@0: // On OS X, we need to copy anything else left over inside the Updated.app michael@0: // directory, and then we need to get rid of it as it's no longer going to michael@0: // be useful. michael@0: NS_tchar updatedAppDir[MAXPATHLEN]; michael@0: NS_tsnprintf(updatedAppDir, sizeof(updatedAppDir)/sizeof(updatedAppDir[0]), michael@0: NS_T("%s/Updated.app"), installDir); michael@0: NS_tDIR *dir = NS_topendir(updatedAppDir); michael@0: if (dir) { michael@0: NS_tdirent *entry; michael@0: while ((entry = NS_treaddir(dir)) != 0) { michael@0: if (NS_tstrcmp(entry->d_name, NS_T(".")) && michael@0: NS_tstrcmp(entry->d_name, NS_T(".."))) { michael@0: NS_tchar childSrcPath[MAXPATHLEN]; michael@0: NS_tsnprintf(childSrcPath, sizeof(childSrcPath)/sizeof(childSrcPath[0]), michael@0: NS_T("%s/%s"), updatedAppDir, entry->d_name); michael@0: NS_tchar childDstPath[MAXPATHLEN]; michael@0: NS_tsnprintf(childDstPath, sizeof(childDstPath)/sizeof(childDstPath[0]), michael@0: NS_T("%s/%s"), installDir, entry->d_name); michael@0: ensure_remove_recursive(childDstPath); michael@0: rv = rename_file(childSrcPath, childDstPath, true); michael@0: if (rv) { michael@0: LOG(("Moving " LOG_S " to " LOG_S " failed, err: %d", michael@0: childSrcPath, childDstPath, errno)); michael@0: } michael@0: } michael@0: } michael@0: michael@0: NS_tclosedir(dir); michael@0: } else { michael@0: LOG(("Updated.app dir can't be found: " LOG_S ", err: %d", michael@0: updatedAppDir, errno)); michael@0: } michael@0: ensure_remove_recursive(updatedAppDir); michael@0: michael@0: LOG(("Moving the precomplete file")); michael@0: michael@0: // We also need to move the precomplete file too. michael@0: NS_tchar precompleteSource[MAXPATHLEN]; michael@0: NS_tsnprintf(precompleteSource, sizeof(precompleteSource)/sizeof(precompleteSource[0]), michael@0: NS_T("%s/precomplete"), installDir); michael@0: michael@0: NS_tchar precompleteTmp[MAXPATHLEN]; michael@0: NS_tsnprintf(precompleteTmp, sizeof(precompleteTmp)/sizeof(precompleteTmp[0]), michael@0: NS_T("%s/precomplete.bak"), installDir); michael@0: michael@0: NS_tchar precompleteNew[MAXPATHLEN]; michael@0: NS_tsnprintf(precompleteNew, sizeof(precompleteNew)/sizeof(precompleteNew[0]), michael@0: NS_T("%s/Updated.app/precomplete"), installDir); michael@0: michael@0: ensure_remove(precompleteTmp); michael@0: LOG(("Begin moving precompleteSrc to precompleteTmp")); michael@0: rv = rename_file(precompleteSource, precompleteTmp); michael@0: LOG(("Moved precompleteSrc to precompleteTmp, err: %d", rv)); michael@0: LOG(("Begin moving precompleteNew to precompleteSrc")); michael@0: int rv2 = rename_file(precompleteNew, precompleteSource); michael@0: LOG(("Moved precompleteNew to precompleteSrc, err: %d", rv2)); michael@0: michael@0: // If new could not be moved to source, we only want to restore tmp to source michael@0: // if the first step succeeded. Note that it is possible for the first michael@0: // rename to have failed as well, for example if the tmpFile exists and we michael@0: // race between the ensure_remove call and the first rename call, but there michael@0: // isn't too much that we can do about that, unfortunately. michael@0: if (!rv && rv2) { michael@0: LOG(("Begin trying to recover precompleteSrc")); michael@0: rv = rename_file(precompleteTmp, precompleteSource); michael@0: LOG(("Moved precompleteTmp to precompleteSrc, err: %d", rv)); michael@0: } michael@0: michael@0: LOG(("Finished moving the precomplete file")); michael@0: #endif michael@0: michael@0: gSucceeded = true; michael@0: michael@0: return 0; michael@0: } michael@0: michael@0: #ifdef XP_WIN michael@0: static void michael@0: WaitForServiceFinishThread(void *param) michael@0: { michael@0: // We wait at most 10 minutes, we already waited 5 seconds previously michael@0: // before deciding to show this UI. michael@0: WaitForServiceStop(SVC_NAME, 595); michael@0: LOG(("calling QuitProgressUI")); michael@0: QuitProgressUI(); michael@0: } michael@0: #endif michael@0: michael@0: #ifdef MOZ_VERIFY_MAR_SIGNATURE michael@0: /** michael@0: * This function reads in the ACCEPTED_MAR_CHANNEL_IDS from update-settings.ini michael@0: * michael@0: * @param path The path to the ini file that is to be read michael@0: * @param results A pointer to the location to store the read strings michael@0: * @return OK on success michael@0: */ michael@0: static int michael@0: ReadMARChannelIDs(const NS_tchar *path, MARChannelStringTable *results) michael@0: { michael@0: const unsigned int kNumStrings = 1; michael@0: const char *kUpdaterKeys = "ACCEPTED_MAR_CHANNEL_IDS\0"; michael@0: char updater_strings[kNumStrings][MAX_TEXT_LEN]; michael@0: michael@0: int result = ReadStrings(path, kUpdaterKeys, kNumStrings, michael@0: updater_strings, "Settings"); michael@0: michael@0: strncpy(results->MARChannelID, updater_strings[0], MAX_TEXT_LEN - 1); michael@0: results->MARChannelID[MAX_TEXT_LEN - 1] = 0; michael@0: michael@0: return result; michael@0: } michael@0: #endif michael@0: michael@0: static int michael@0: GetUpdateFileName(NS_tchar *fileName, int maxChars) michael@0: { michael@0: #if defined(MOZ_WIDGET_GONK) // If an update.link file exists, then it will contain the name michael@0: // of the update file (terminated by a newline). michael@0: michael@0: NS_tchar linkFileName[MAXPATHLEN]; michael@0: NS_tsnprintf(linkFileName, sizeof(linkFileName)/sizeof(linkFileName[0]), michael@0: NS_T("%s/update.link"), gSourcePath); michael@0: AutoFile linkFile = NS_tfopen(linkFileName, NS_T("rb")); michael@0: if (linkFile == nullptr) { michael@0: NS_tsnprintf(fileName, maxChars, michael@0: NS_T("%s/update.mar"), gSourcePath); michael@0: return OK; michael@0: } michael@0: michael@0: char dataFileName[MAXPATHLEN]; michael@0: size_t bytesRead; michael@0: michael@0: if ((bytesRead = fread(dataFileName, 1, sizeof(dataFileName)-1, linkFile)) <= 0) { michael@0: *fileName = NS_T('\0'); michael@0: return READ_ERROR; michael@0: } michael@0: if (dataFileName[bytesRead-1] == '\n') { michael@0: // Strip trailing newline (for \n and \r\n) michael@0: bytesRead--; michael@0: } michael@0: if (dataFileName[bytesRead-1] == '\r') { michael@0: // Strip trailing CR (for \r, \r\n) michael@0: bytesRead--; michael@0: } michael@0: dataFileName[bytesRead] = '\0'; michael@0: michael@0: strncpy(fileName, dataFileName, maxChars-1); michael@0: fileName[maxChars-1] = '\0'; michael@0: #else michael@0: // We currently only support update.link files under GONK michael@0: NS_tsnprintf(fileName, maxChars, michael@0: NS_T("%s/update.mar"), gSourcePath); michael@0: #endif michael@0: return OK; michael@0: } michael@0: michael@0: static void michael@0: UpdateThreadFunc(void *param) michael@0: { michael@0: // open ZIP archive and process... michael@0: int rv; michael@0: if (sReplaceRequest) { michael@0: rv = ProcessReplaceRequest(); michael@0: } else { michael@0: NS_tchar dataFile[MAXPATHLEN]; michael@0: rv = GetUpdateFileName(dataFile, sizeof(dataFile)/sizeof(dataFile[0])); michael@0: if (rv == OK) { michael@0: rv = gArchiveReader.Open(dataFile); michael@0: } michael@0: michael@0: #ifdef MOZ_VERIFY_MAR_SIGNATURE michael@0: if (rv == OK) { michael@0: rv = gArchiveReader.VerifySignature(); michael@0: } michael@0: michael@0: if (rv == OK) { michael@0: NS_tchar installDir[MAXPATHLEN]; michael@0: if (sStagedUpdate) { michael@0: if (!GetInstallationDir(installDir)) { michael@0: rv = NO_INSTALLDIR_ERROR; michael@0: } michael@0: } else { michael@0: NS_tstrcpy(installDir, gDestinationPath); michael@0: } michael@0: if (rv == OK) { michael@0: NS_tchar updateSettingsPath[MAX_TEXT_LEN]; michael@0: NS_tsnprintf(updateSettingsPath, michael@0: sizeof(updateSettingsPath) / sizeof(updateSettingsPath[0]), michael@0: NS_T("%s/update-settings.ini"), installDir); michael@0: MARChannelStringTable MARStrings; michael@0: if (ReadMARChannelIDs(updateSettingsPath, &MARStrings) != OK) { michael@0: // If we can't read from update-settings.ini then we shouldn't impose michael@0: // a MAR restriction. Some installations won't even include this file. michael@0: MARStrings.MARChannelID[0] = '\0'; michael@0: } michael@0: michael@0: rv = gArchiveReader.VerifyProductInformation(MARStrings.MARChannelID, michael@0: MOZ_APP_VERSION); michael@0: } michael@0: } michael@0: #endif michael@0: michael@0: if (rv == OK && sStagedUpdate && !sIsOSUpdate) { michael@0: rv = CopyInstallDirToDestDir(); michael@0: } michael@0: michael@0: if (rv == OK) { michael@0: rv = DoUpdate(); michael@0: gArchiveReader.Close(); michael@0: NS_tchar updatingDir[MAXPATHLEN]; michael@0: NS_tsnprintf(updatingDir, sizeof(updatingDir)/sizeof(updatingDir[0]), michael@0: NS_T("%s/updating"), gDestinationPath); michael@0: ensure_remove_recursive(updatingDir); michael@0: } michael@0: } michael@0: michael@0: bool reportRealResults = true; michael@0: if (sReplaceRequest && rv && !getenv("MOZ_NO_REPLACE_FALLBACK")) { michael@0: // When attempting to replace the application, we should fall back michael@0: // to non-staged updates in case of a failure. We do this by michael@0: // setting the status to pending, exiting the updater, and michael@0: // launching the callback application. The callback application's michael@0: // startup path will see the pending status, and will start the michael@0: // updater application again in order to apply the update without michael@0: // staging. michael@0: // The MOZ_NO_REPLACE_FALLBACK environment variable is used to michael@0: // bypass this fallback, and is used in the updater tests. michael@0: // The only special thing which we should do here is to remove the michael@0: // staged directory as it won't be useful any more. michael@0: NS_tchar installDir[MAXPATHLEN]; michael@0: if (GetInstallationDir(installDir)) { michael@0: NS_tchar stageDir[MAXPATHLEN]; michael@0: NS_tsnprintf(stageDir, sizeof(stageDir)/sizeof(stageDir[0]), michael@0: #ifdef XP_MACOSX michael@0: NS_T("%s/Updated.app"), michael@0: #else michael@0: NS_T("%s/updated"), michael@0: #endif michael@0: installDir); michael@0: michael@0: ensure_remove_recursive(stageDir); michael@0: WriteStatusFile(sUsingService ? "pending-service" : "pending"); michael@0: char processUpdates[] = "MOZ_PROCESS_UPDATES="; michael@0: putenv(processUpdates); // We need to use -process-updates again in the tests michael@0: reportRealResults = false; // pretend success michael@0: } michael@0: } michael@0: michael@0: if (reportRealResults) { michael@0: if (rv) { michael@0: LOG(("failed: %d", rv)); michael@0: } michael@0: else { michael@0: #ifdef XP_MACOSX michael@0: // If the update was successful we need to update the timestamp michael@0: // on the top-level Mac OS X bundle directory so that Mac OS X's michael@0: // Launch Services picks up any major changes. Here we assume that michael@0: // the current working directory is the top-level bundle directory. michael@0: char* cwd = getcwd(nullptr, 0); michael@0: if (cwd) { michael@0: if (utimes(cwd, nullptr) != 0) { michael@0: LOG(("Couldn't set access/modification time on application bundle.")); michael@0: } michael@0: free(cwd); michael@0: } michael@0: else { michael@0: LOG(("Couldn't get current working directory for setting " michael@0: "access/modification time on application bundle.")); michael@0: } michael@0: #endif michael@0: michael@0: LOG(("succeeded")); michael@0: } michael@0: WriteStatusFile(rv); michael@0: } michael@0: michael@0: LOG(("calling QuitProgressUI")); michael@0: QuitProgressUI(); michael@0: } michael@0: michael@0: int NS_main(int argc, NS_tchar **argv) michael@0: { michael@0: #if defined(MOZ_WIDGET_GONK) michael@0: if (getenv("LD_PRELOAD")) { michael@0: // If the updater is launched with LD_PRELOAD set, then we wind up michael@0: // preloading libmozglue.so. Under some circumstances, this can cause michael@0: // the remount of /system to fail when going from rw to ro, so if we michael@0: // detect LD_PRELOAD we unsetenv it and relaunch ourselves without it. michael@0: // This will cause the offending preloaded library to be closed. michael@0: // michael@0: // For a variety of reasons, this is really hard to do in a safe manner michael@0: // in the parent process, so we do it here. michael@0: unsetenv("LD_PRELOAD"); michael@0: execv(argv[0], argv); michael@0: __android_log_print(ANDROID_LOG_INFO, "updater", michael@0: "execve failed: errno: %d. Exiting...", errno); michael@0: _exit(1); michael@0: } michael@0: #endif michael@0: InitProgressUI(&argc, &argv); michael@0: michael@0: // To process an update the updater command line must at a minimum have the michael@0: // directory path containing the updater.mar file to process as the first argument michael@0: // and the directory to apply the update to as the second argument. When the michael@0: // updater is launched by another process the PID of the parent process should be michael@0: // provided in the optional third argument and the updater will wait on the parent michael@0: // process to exit if the value is non-zero and the process is present. This is michael@0: // necessary due to not being able to update files that are in use on Windows. The michael@0: // optional fourth argument is the callback's working directory and the optional michael@0: // fifth argument is the callback path. The callback is the application to launch michael@0: // after updating and it will be launched when these arguments are provided michael@0: // whether the update was successful or not. All remaining arguments are optional michael@0: // and are passed to the callback when it is launched. michael@0: if (argc < 3) { michael@0: fprintf(stderr, "Usage: updater update-dir apply-to-dir [wait-pid [callback-working-dir callback-path args...]]\n"); michael@0: return 1; michael@0: } michael@0: michael@0: // The directory containing the update information. michael@0: gSourcePath = argv[1]; michael@0: // The directory we're going to update to. michael@0: // We copy this string because we need to remove trailing slashes. The C++ michael@0: // standard says that it's always safe to write to strings pointed to by argv michael@0: // elements, but I don't necessarily believe it. michael@0: NS_tstrncpy(gDestinationPath, argv[2], MAXPATHLEN); michael@0: gDestinationPath[MAXPATHLEN - 1] = NS_T('\0'); michael@0: NS_tchar *slash = NS_tstrrchr(gDestinationPath, NS_SLASH); michael@0: if (slash && !slash[1]) { michael@0: *slash = NS_T('\0'); michael@0: } michael@0: michael@0: #ifdef XP_WIN michael@0: bool useService = false; michael@0: bool testOnlyFallbackKeyExists = false; michael@0: bool noServiceFallback = getenv("MOZ_NO_SERVICE_FALLBACK") != nullptr; michael@0: putenv(const_cast("MOZ_NO_SERVICE_FALLBACK=")); michael@0: michael@0: // We never want the service to be used unless we build with michael@0: // the maintenance service. michael@0: #ifdef MOZ_MAINTENANCE_SERVICE michael@0: useService = IsUpdateStatusPendingService(); michael@0: // Our tests run with a different apply directory for each test. michael@0: // We use this registry key on our test slaves to store the michael@0: // allowed name/issuers. michael@0: testOnlyFallbackKeyExists = DoesFallbackKeyExist(); michael@0: #endif michael@0: michael@0: // Remove everything except close window from the context menu michael@0: { michael@0: HKEY hkApp; michael@0: RegCreateKeyExW(HKEY_CURRENT_USER, L"Software\\Classes\\Applications", michael@0: 0, nullptr, REG_OPTION_NON_VOLATILE, KEY_SET_VALUE, nullptr, michael@0: &hkApp, nullptr); michael@0: RegCloseKey(hkApp); michael@0: if (RegCreateKeyExW(HKEY_CURRENT_USER, michael@0: L"Software\\Classes\\Applications\\updater.exe", michael@0: 0, nullptr, REG_OPTION_VOLATILE, KEY_SET_VALUE, nullptr, michael@0: &hkApp, nullptr) == ERROR_SUCCESS) { michael@0: RegSetValueExW(hkApp, L"IsHostApp", 0, REG_NONE, 0, 0); michael@0: RegSetValueExW(hkApp, L"NoOpenWith", 0, REG_NONE, 0, 0); michael@0: RegSetValueExW(hkApp, L"NoStartPage", 0, REG_NONE, 0, 0); michael@0: RegCloseKey(hkApp); michael@0: } michael@0: } michael@0: #endif michael@0: michael@0: // If there is a PID specified and it is not '0' then wait for the process to exit. michael@0: #ifdef XP_WIN michael@0: __int64 pid = 0; michael@0: #else michael@0: int pid = 0; michael@0: #endif michael@0: if (argc > 3) { michael@0: #ifdef XP_WIN michael@0: pid = _wtoi64(argv[3]); michael@0: #else michael@0: pid = atoi(argv[3]); michael@0: #endif michael@0: if (pid == -1) { michael@0: // This is a signal from the parent process that the updater should stage michael@0: // the update. michael@0: sStagedUpdate = true; michael@0: } else if (NS_tstrstr(argv[3], NS_T("/replace"))) { michael@0: // We're processing a request to replace the application with a staged michael@0: // update. michael@0: sReplaceRequest = true; michael@0: } michael@0: } michael@0: michael@0: if (getenv("MOZ_OS_UPDATE")) { michael@0: sIsOSUpdate = true; michael@0: putenv(const_cast("MOZ_OS_UPDATE=")); michael@0: } michael@0: michael@0: if (sReplaceRequest) { michael@0: // If we're attempting to replace the application, try to append to the michael@0: // log generated when staging the staged update. michael@0: NS_tchar installDir[MAXPATHLEN]; michael@0: if (!GetInstallationDir(installDir)) { michael@0: fprintf(stderr, "Could not get the installation directory\n"); michael@0: return 1; michael@0: } michael@0: michael@0: #ifdef XP_WIN michael@0: NS_tchar* logDir = gSourcePath; michael@0: #else michael@0: NS_tchar logDir[MAXPATHLEN]; michael@0: NS_tsnprintf(logDir, sizeof(logDir)/sizeof(logDir[0]), michael@0: #ifdef XP_MACOSX michael@0: NS_T("%s/Updated.app/Contents/MacOS/updates"), michael@0: #else michael@0: NS_T("%s/updated/updates"), michael@0: #endif michael@0: installDir); michael@0: #endif michael@0: michael@0: LogInitAppend(logDir, NS_T("last-update.log"), NS_T("update.log")); michael@0: } else { michael@0: LogInit(gSourcePath, NS_T("update.log")); michael@0: } michael@0: michael@0: if (!WriteStatusFile("applying")) { michael@0: LOG(("failed setting status to 'applying'")); michael@0: return 1; michael@0: } michael@0: michael@0: if (sStagedUpdate) { michael@0: LOG(("Performing a staged update")); michael@0: } else if (sReplaceRequest) { michael@0: LOG(("Performing a replace request")); michael@0: } michael@0: michael@0: #ifdef MOZ_WIDGET_GONK michael@0: const char *prioEnv = getenv("MOZ_UPDATER_PRIO"); michael@0: if (prioEnv) { michael@0: int32_t prioVal; michael@0: int32_t oomScoreAdj; michael@0: int32_t ioprioClass; michael@0: int32_t ioprioLevel; michael@0: if (sscanf(prioEnv, "%d/%d/%d/%d", michael@0: &prioVal, &oomScoreAdj, &ioprioClass, &ioprioLevel) == 4) { michael@0: LOG(("MOZ_UPDATER_PRIO=%s", prioEnv)); michael@0: if (setpriority(PRIO_PROCESS, 0, prioVal)) { michael@0: LOG(("setpriority(%d) failed, errno = %d", prioVal, errno)); michael@0: } michael@0: if (ioprio_set(IOPRIO_WHO_PROCESS, 0, michael@0: IOPRIO_PRIO_VALUE(ioprioClass, ioprioLevel))) { michael@0: LOG(("ioprio_set(%d,%d) failed: errno = %d", michael@0: ioprioClass, ioprioLevel, errno)); michael@0: } michael@0: FILE *fs = fopen("/proc/self/oom_score_adj", "w"); michael@0: if (fs) { michael@0: fprintf(fs, "%d", oomScoreAdj); michael@0: fclose(fs); michael@0: } else { michael@0: LOG(("Unable to open /proc/self/oom_score_adj for writing, errno = %d", errno)); michael@0: } michael@0: } michael@0: } michael@0: #endif michael@0: michael@0: #ifdef XP_WIN michael@0: if (pid > 0) { michael@0: HANDLE parent = OpenProcess(SYNCHRONIZE, false, (DWORD) pid); michael@0: // May return nullptr if the parent process has already gone away. michael@0: // Otherwise, wait for the parent process to exit before starting the michael@0: // update. michael@0: if (parent) { michael@0: bool updateFromMetro = false; michael@0: #ifdef MOZ_METRO michael@0: updateFromMetro = IsUpdateFromMetro(argc, argv); michael@0: #endif michael@0: DWORD waitTime = updateFromMetro ? michael@0: IMMERSIVE_PARENT_WAIT : PARENT_WAIT; michael@0: DWORD result = WaitForSingleObject(parent, waitTime); michael@0: CloseHandle(parent); michael@0: if (result != WAIT_OBJECT_0 && !updateFromMetro) michael@0: return 1; michael@0: } michael@0: } michael@0: #else michael@0: if (pid > 0) michael@0: waitpid(pid, nullptr, 0); michael@0: #endif michael@0: michael@0: if (sReplaceRequest) { michael@0: #ifdef XP_WIN michael@0: // On Windows, the current working directory of the process should be changed michael@0: // so that it's not locked. michael@0: NS_tchar tmpDir[MAXPATHLEN]; michael@0: if (GetTempPathW(MAXPATHLEN, tmpDir)) { michael@0: NS_tchdir(tmpDir); michael@0: } michael@0: #endif michael@0: } michael@0: michael@0: // The callback is the remaining arguments starting at callbackIndex. michael@0: // The argument specified by callbackIndex is the callback executable and the michael@0: // argument prior to callbackIndex is the working directory. michael@0: const int callbackIndex = 5; michael@0: michael@0: #if defined(XP_WIN) michael@0: sUsingService = getenv("MOZ_USING_SERVICE") != nullptr; michael@0: putenv(const_cast("MOZ_USING_SERVICE=")); michael@0: // lastFallbackError keeps track of the last error for the service not being michael@0: // used, in case of an error when fallback is not enabled we write the michael@0: // error to the update.status file. michael@0: // When fallback is disabled (MOZ_NO_SERVICE_FALLBACK does not exist) then michael@0: // we will instead fallback to not using the service and display a UAC prompt. michael@0: int lastFallbackError = FALLBACKKEY_UNKNOWN_ERROR; michael@0: michael@0: // Launch a second instance of the updater with the runas verb on Windows michael@0: // when write access is denied to the installation directory. michael@0: HANDLE updateLockFileHandle = INVALID_HANDLE_VALUE; michael@0: NS_tchar elevatedLockFilePath[MAXPATHLEN] = {NS_T('\0')}; michael@0: if (!sUsingService && michael@0: (argc > callbackIndex || sStagedUpdate || sReplaceRequest)) { michael@0: NS_tchar updateLockFilePath[MAXPATHLEN]; michael@0: if (sStagedUpdate) { michael@0: // When staging an update, the lock file is: michael@0: // $INSTALLDIR\updated.update_in_progress.lock michael@0: NS_tsnprintf(updateLockFilePath, michael@0: sizeof(updateLockFilePath)/sizeof(updateLockFilePath[0]), michael@0: NS_T("%s.update_in_progress.lock"), gDestinationPath); michael@0: } else if (sReplaceRequest) { michael@0: // When processing a replace request, the lock file is: michael@0: // $INSTALLDIR\..\moz_update_in_progress.lock michael@0: NS_tchar installDir[MAXPATHLEN]; michael@0: if (!GetInstallationDir(installDir)) { michael@0: return 1; michael@0: } michael@0: NS_tchar *slash = (NS_tchar *) NS_tstrrchr(installDir, NS_SLASH); michael@0: *slash = NS_T('\0'); michael@0: NS_tsnprintf(updateLockFilePath, michael@0: sizeof(updateLockFilePath)/sizeof(updateLockFilePath[0]), michael@0: NS_T("%s\\moz_update_in_progress.lock"), installDir); michael@0: } else { michael@0: // In the non-staging update case, the lock file is: michael@0: // $INSTALLDIR\$APPNAME.exe.update_in_progress.lock michael@0: NS_tsnprintf(updateLockFilePath, michael@0: sizeof(updateLockFilePath)/sizeof(updateLockFilePath[0]), michael@0: NS_T("%s.update_in_progress.lock"), argv[callbackIndex]); michael@0: } michael@0: michael@0: // The update_in_progress.lock file should only exist during an update. In michael@0: // case it exists attempt to remove it and exit if that fails to prevent michael@0: // simultaneous updates occurring. michael@0: if (!_waccess(updateLockFilePath, F_OK) && michael@0: NS_tremove(updateLockFilePath) != 0) { michael@0: // Try to fall back to the old way of doing updates if a staged michael@0: // update fails. michael@0: if (sStagedUpdate || sReplaceRequest) { michael@0: // Note that this could fail, but if it does, there isn't too much we michael@0: // can do in order to recover anyways. michael@0: WriteStatusFile("pending"); michael@0: } michael@0: LOG(("Update already in progress! Exiting")); michael@0: return 1; michael@0: } michael@0: michael@0: updateLockFileHandle = CreateFileW(updateLockFilePath, michael@0: GENERIC_READ | GENERIC_WRITE, michael@0: 0, michael@0: nullptr, michael@0: OPEN_ALWAYS, michael@0: FILE_FLAG_DELETE_ON_CLOSE, michael@0: nullptr); michael@0: michael@0: NS_tsnprintf(elevatedLockFilePath, michael@0: sizeof(elevatedLockFilePath)/sizeof(elevatedLockFilePath[0]), michael@0: NS_T("%s/update_elevated.lock"), gSourcePath); michael@0: michael@0: michael@0: // Even if a file has no sharing access, you can still get its attributes michael@0: bool startedFromUnelevatedUpdater = michael@0: GetFileAttributesW(elevatedLockFilePath) != INVALID_FILE_ATTRIBUTES; michael@0: michael@0: // If we're running from the service, then we were started with the same michael@0: // token as the service so the permissions are already dropped. If we're michael@0: // running from an elevated updater that was started from an unelevated michael@0: // updater, then we drop the permissions here. We do not drop the michael@0: // permissions on the originally called updater because we use its token michael@0: // to start the callback application. michael@0: if(startedFromUnelevatedUpdater) { michael@0: // Disable every privilege we don't need. Processes started using michael@0: // CreateProcess will use the same token as this process. michael@0: UACHelper::DisablePrivileges(nullptr); michael@0: } michael@0: michael@0: if (updateLockFileHandle == INVALID_HANDLE_VALUE || michael@0: (useService && testOnlyFallbackKeyExists && noServiceFallback)) { michael@0: if (!_waccess(elevatedLockFilePath, F_OK) && michael@0: NS_tremove(elevatedLockFilePath) != 0) { michael@0: fprintf(stderr, "Unable to create elevated lock file! Exiting\n"); michael@0: return 1; michael@0: } michael@0: michael@0: HANDLE elevatedFileHandle; michael@0: elevatedFileHandle = CreateFileW(elevatedLockFilePath, michael@0: GENERIC_READ | GENERIC_WRITE, michael@0: 0, michael@0: nullptr, michael@0: OPEN_ALWAYS, michael@0: FILE_FLAG_DELETE_ON_CLOSE, michael@0: nullptr); michael@0: michael@0: if (elevatedFileHandle == INVALID_HANDLE_VALUE) { michael@0: LOG(("Unable to create elevated lock file! Exiting")); michael@0: return 1; michael@0: } michael@0: michael@0: wchar_t *cmdLine = MakeCommandLine(argc - 1, argv + 1); michael@0: if (!cmdLine) { michael@0: CloseHandle(elevatedFileHandle); michael@0: return 1; michael@0: } michael@0: michael@0: NS_tchar installDir[MAXPATHLEN]; michael@0: if (!GetInstallationDir(installDir)) { michael@0: return 1; michael@0: } michael@0: michael@0: // Make sure the path to the updater to use for the update is on local. michael@0: // We do this check to make sure that file locking is available for michael@0: // race condition security checks. michael@0: if (useService) { michael@0: BOOL isLocal = FALSE; michael@0: useService = IsLocalFile(argv[0], isLocal) && isLocal; michael@0: } michael@0: michael@0: // If we have unprompted elevation we should NOT use the service michael@0: // for the update. Service updates happen with the SYSTEM account michael@0: // which has more privs than we need to update with. michael@0: // Windows 8 provides a user interface so users can configure this michael@0: // behavior and it can be configured in the registry in all Windows michael@0: // versions that support UAC. michael@0: if (useService) { michael@0: BOOL unpromptedElevation; michael@0: if (IsUnpromptedElevation(unpromptedElevation)) { michael@0: useService = !unpromptedElevation; michael@0: } michael@0: } michael@0: michael@0: // Make sure the service registry entries for the instsallation path michael@0: // are available. If not don't use the service. michael@0: if (useService) { michael@0: WCHAR maintenanceServiceKey[MAX_PATH + 1]; michael@0: if (CalculateRegistryPathFromFilePath(installDir, maintenanceServiceKey)) { michael@0: HKEY baseKey; michael@0: if (RegOpenKeyExW(HKEY_LOCAL_MACHINE, michael@0: maintenanceServiceKey, 0, michael@0: KEY_READ | KEY_WOW64_64KEY, michael@0: &baseKey) == ERROR_SUCCESS) { michael@0: RegCloseKey(baseKey); michael@0: } else { michael@0: useService = testOnlyFallbackKeyExists; michael@0: if (!useService) { michael@0: lastFallbackError = FALLBACKKEY_NOKEY_ERROR; michael@0: } michael@0: } michael@0: } else { michael@0: useService = false; michael@0: lastFallbackError = FALLBACKKEY_REGPATH_ERROR; michael@0: } michael@0: } michael@0: michael@0: // Originally we used to write "pending" to update.status before michael@0: // launching the service command. This is no longer needed now michael@0: // since the service command is launched from updater.exe. If anything michael@0: // fails in between, we can fall back to using the normal update process michael@0: // on our own. michael@0: michael@0: // If we still want to use the service try to launch the service michael@0: // comamnd for the update. michael@0: if (useService) { michael@0: // If the update couldn't be started, then set useService to false so michael@0: // we do the update the old way. michael@0: DWORD ret = LaunchServiceSoftwareUpdateCommand(argc, (LPCWSTR *)argv); michael@0: useService = (ret == ERROR_SUCCESS); michael@0: // If the command was launched then wait for the service to be done. michael@0: if (useService) { michael@0: bool showProgressUI = false; michael@0: // Never show the progress UI when staging updates. michael@0: if (!sStagedUpdate) { michael@0: // We need to call this separately instead of allowing ShowProgressUI michael@0: // to initialize the strings because the service will move the michael@0: // ini file out of the way when running updater. michael@0: showProgressUI = !InitProgressUIStrings(); michael@0: } michael@0: michael@0: // Wait for the service to stop for 5 seconds. If the service michael@0: // has still not stopped then show an indeterminate progress bar. michael@0: DWORD lastState = WaitForServiceStop(SVC_NAME, 5); michael@0: if (lastState != SERVICE_STOPPED) { michael@0: Thread t1; michael@0: if (t1.Run(WaitForServiceFinishThread, nullptr) == 0 && michael@0: showProgressUI) { michael@0: ShowProgressUI(true, false); michael@0: } michael@0: t1.Join(); michael@0: } michael@0: michael@0: lastState = WaitForServiceStop(SVC_NAME, 1); michael@0: if (lastState != SERVICE_STOPPED) { michael@0: // If the service doesn't stop after 10 minutes there is michael@0: // something seriously wrong. michael@0: lastFallbackError = FALLBACKKEY_SERVICE_NO_STOP_ERROR; michael@0: useService = false; michael@0: } michael@0: } else { michael@0: lastFallbackError = FALLBACKKEY_LAUNCH_ERROR; michael@0: } michael@0: } michael@0: michael@0: // If the service can't be used when staging and update, make sure that michael@0: // the UAC prompt is not shown! In this case, just set the status to michael@0: // pending and the update will be applied during the next startup. michael@0: if (!useService && sStagedUpdate) { michael@0: if (updateLockFileHandle != INVALID_HANDLE_VALUE) { michael@0: CloseHandle(updateLockFileHandle); michael@0: } michael@0: WriteStatusPending(gSourcePath); michael@0: return 0; michael@0: } michael@0: michael@0: // If we started the service command, and it finished, check the michael@0: // update.status file to make sure it succeeded, and if it did michael@0: // we need to manually start the PostUpdate process from the michael@0: // current user's session of this unelevated updater.exe the michael@0: // current process is running as. michael@0: // Note that we don't need to do this if we're just staging the update, michael@0: // as the PostUpdate step runs when performing the replacing in that case. michael@0: if (useService && !sStagedUpdate) { michael@0: bool updateStatusSucceeded = false; michael@0: if (IsUpdateStatusSucceeded(updateStatusSucceeded) && michael@0: updateStatusSucceeded) { michael@0: if (!LaunchWinPostProcess(installDir, gSourcePath, false, nullptr)) { michael@0: fprintf(stderr, "The post update process which runs as the user" michael@0: " for service update could not be launched."); michael@0: } michael@0: } michael@0: } michael@0: michael@0: // If we didn't want to use the service at all, or if an update was michael@0: // already happening, or launching the service command failed, then michael@0: // launch the elevated updater.exe as we do without the service. michael@0: // We don't launch the elevated updater in the case that we did have michael@0: // write access all along because in that case the only reason we're michael@0: // using the service is because we are testing. michael@0: if (!useService && !noServiceFallback && michael@0: updateLockFileHandle == INVALID_HANDLE_VALUE) { michael@0: SHELLEXECUTEINFO sinfo; michael@0: memset(&sinfo, 0, sizeof(SHELLEXECUTEINFO)); michael@0: sinfo.cbSize = sizeof(SHELLEXECUTEINFO); michael@0: sinfo.fMask = SEE_MASK_FLAG_NO_UI | michael@0: SEE_MASK_FLAG_DDEWAIT | michael@0: SEE_MASK_NOCLOSEPROCESS; michael@0: sinfo.hwnd = nullptr; michael@0: sinfo.lpFile = argv[0]; michael@0: sinfo.lpParameters = cmdLine; michael@0: sinfo.lpVerb = L"runas"; michael@0: sinfo.nShow = SW_SHOWNORMAL; michael@0: michael@0: bool result = ShellExecuteEx(&sinfo); michael@0: free(cmdLine); michael@0: michael@0: if (result) { michael@0: WaitForSingleObject(sinfo.hProcess, INFINITE); michael@0: CloseHandle(sinfo.hProcess); michael@0: } else { michael@0: WriteStatusFile(ELEVATION_CANCELED); michael@0: } michael@0: } michael@0: michael@0: if (argc > callbackIndex) { michael@0: LaunchCallbackApp(argv[4], argc - callbackIndex, michael@0: argv + callbackIndex, sUsingService); michael@0: } michael@0: michael@0: CloseHandle(elevatedFileHandle); michael@0: michael@0: if (!useService && !noServiceFallback && michael@0: INVALID_HANDLE_VALUE == updateLockFileHandle) { michael@0: // We didn't use the service and we did run the elevated updater.exe. michael@0: // The elevated updater.exe is responsible for writing out the michael@0: // update.status file. michael@0: return 0; michael@0: } else if(useService) { michael@0: // The service command was launched. The service is responsible for michael@0: // writing out the update.status file. michael@0: if (updateLockFileHandle != INVALID_HANDLE_VALUE) { michael@0: CloseHandle(updateLockFileHandle); michael@0: } michael@0: return 0; michael@0: } else { michael@0: // Otherwise the service command was not launched at all. michael@0: // We are only reaching this code path because we had write access michael@0: // all along to the directory and a fallback key existed, and we michael@0: // have fallback disabled (MOZ_NO_SERVICE_FALLBACK env var exists). michael@0: // We only currently use this env var from XPCShell tests. michael@0: CloseHandle(updateLockFileHandle); michael@0: WriteStatusFile(lastFallbackError); michael@0: return 0; michael@0: } michael@0: } michael@0: } michael@0: #endif michael@0: michael@0: #if defined(MOZ_WIDGET_GONK) michael@0: // In gonk, the master b2g process sets its umask to 0027 because michael@0: // there's no reason for it to ever create world-readable files. michael@0: // The updater binary, however, needs to do this, and it inherits michael@0: // the master process's cautious umask. So we drop down a bit here. michael@0: umask(0022); michael@0: michael@0: // Remount the /system partition as read-write for gonk. The destructor will michael@0: // remount /system as read-only. We add an extra level of scope here to avoid michael@0: // calling LogFinish() before the GonkAutoMounter destructor has a chance michael@0: // to be called michael@0: { michael@0: GonkAutoMounter mounter; michael@0: if (mounter.GetAccess() != MountAccess::ReadWrite) { michael@0: WriteStatusFile(FILESYSTEM_MOUNT_READWRITE_ERROR); michael@0: return 1; michael@0: } michael@0: #endif michael@0: michael@0: if (sStagedUpdate) { michael@0: // When staging updates, blow away the old installation directory and create michael@0: // it from scratch. michael@0: ensure_remove_recursive(gDestinationPath); michael@0: } michael@0: if (!sReplaceRequest) { michael@0: // Change current directory to the directory where we need to apply the update. michael@0: if (NS_tchdir(gDestinationPath) != 0) { michael@0: // Try to create the destination directory if it doesn't exist michael@0: int rv = NS_tmkdir(gDestinationPath, 0755); michael@0: if (rv == OK && errno != EEXIST) { michael@0: // Try changing the current directory again michael@0: if (NS_tchdir(gDestinationPath) != 0) { michael@0: // OK, time to give up! michael@0: return 1; michael@0: } michael@0: } else { michael@0: // Failed to create the directory, bail out michael@0: return 1; michael@0: } michael@0: } michael@0: } michael@0: michael@0: LOG(("SOURCE DIRECTORY " LOG_S, gSourcePath)); michael@0: LOG(("DESTINATION DIRECTORY " LOG_S, gDestinationPath)); michael@0: michael@0: #ifdef XP_WIN michael@0: // For replace requests, we don't need to do any real updates, so this is not michael@0: // necessary. michael@0: if (!sReplaceRequest) { michael@0: // Allocate enough space for the length of the path an optional additional michael@0: // trailing slash and null termination. michael@0: NS_tchar *destpath = (NS_tchar *) malloc((NS_tstrlen(gDestinationPath) + 2) * sizeof(NS_tchar)); michael@0: if (!destpath) michael@0: return 1; michael@0: michael@0: NS_tchar *c = destpath; michael@0: NS_tstrcpy(c, gDestinationPath); michael@0: c += NS_tstrlen(gDestinationPath); michael@0: if (gDestinationPath[NS_tstrlen(gDestinationPath) - 1] != NS_T('/') && michael@0: gDestinationPath[NS_tstrlen(gDestinationPath) - 1] != NS_T('\\')) { michael@0: NS_tstrcat(c, NS_T("/")); michael@0: c += NS_tstrlen(NS_T("/")); michael@0: } michael@0: *c = NS_T('\0'); michael@0: c++; michael@0: michael@0: gDestPath = destpath; michael@0: } michael@0: michael@0: NS_tchar applyDirLongPath[MAXPATHLEN]; michael@0: if (!GetLongPathNameW(gDestinationPath, applyDirLongPath, michael@0: sizeof(applyDirLongPath)/sizeof(applyDirLongPath[0]))) { michael@0: LOG(("NS_main: unable to find apply to dir: " LOG_S, gDestinationPath)); michael@0: LogFinish(); michael@0: WriteStatusFile(WRITE_ERROR); michael@0: EXIT_WHEN_ELEVATED(elevatedLockFilePath, updateLockFileHandle, 1); michael@0: if (argc > callbackIndex) { michael@0: LaunchCallbackApp(argv[4], argc - callbackIndex, michael@0: argv + callbackIndex, sUsingService); michael@0: } michael@0: return 1; michael@0: } michael@0: michael@0: HANDLE callbackFile = INVALID_HANDLE_VALUE; michael@0: if (argc > callbackIndex) { michael@0: // If the callback executable is specified it must exist for a successful michael@0: // update. It is important we null out the whole buffer here because later michael@0: // we make the assumption that the callback application is inside the michael@0: // apply-to dir. If we don't have a fully null'ed out buffer it can lead michael@0: // to stack corruption which causes crashes and other problems. michael@0: NS_tchar callbackLongPath[MAXPATHLEN]; michael@0: ZeroMemory(callbackLongPath, sizeof(callbackLongPath)); michael@0: NS_tchar *targetPath = argv[callbackIndex]; michael@0: NS_tchar buffer[MAXPATHLEN * 2] = { NS_T('\0') }; michael@0: size_t bufferLeft = MAXPATHLEN * 2; michael@0: if (sReplaceRequest) { michael@0: // In case of replace requests, we should look for the callback file in michael@0: // the destination directory. michael@0: size_t commonPrefixLength = PathCommonPrefixW(argv[callbackIndex], michael@0: gDestinationPath, michael@0: nullptr); michael@0: NS_tchar *p = buffer; michael@0: NS_tstrncpy(p, argv[callbackIndex], commonPrefixLength); michael@0: p += commonPrefixLength; michael@0: bufferLeft -= commonPrefixLength; michael@0: NS_tstrncpy(p, gDestinationPath + commonPrefixLength, bufferLeft); michael@0: michael@0: size_t len = NS_tstrlen(gDestinationPath + commonPrefixLength); michael@0: p += len; michael@0: bufferLeft -= len; michael@0: *p = NS_T('\\'); michael@0: ++p; michael@0: bufferLeft--; michael@0: *p = NS_T('\0'); michael@0: NS_tchar installDir[MAXPATHLEN]; michael@0: if (!GetInstallationDir(installDir)) michael@0: return 1; michael@0: size_t callbackPrefixLength = PathCommonPrefixW(argv[callbackIndex], michael@0: installDir, michael@0: nullptr); michael@0: NS_tstrncpy(p, argv[callbackIndex] + std::max(callbackPrefixLength, commonPrefixLength), bufferLeft); michael@0: targetPath = buffer; michael@0: } michael@0: if (!GetLongPathNameW(targetPath, callbackLongPath, michael@0: sizeof(callbackLongPath)/sizeof(callbackLongPath[0]))) { michael@0: LOG(("NS_main: unable to find callback file: " LOG_S, targetPath)); michael@0: LogFinish(); michael@0: WriteStatusFile(WRITE_ERROR); michael@0: EXIT_WHEN_ELEVATED(elevatedLockFilePath, updateLockFileHandle, 1); michael@0: if (argc > callbackIndex) { michael@0: LaunchCallbackApp(argv[4], michael@0: argc - callbackIndex, michael@0: argv + callbackIndex, michael@0: sUsingService); michael@0: } michael@0: return 1; michael@0: } michael@0: michael@0: // Doing this is only necessary when we're actually applying a patch. michael@0: if (!sReplaceRequest) { michael@0: int len = NS_tstrlen(applyDirLongPath); michael@0: NS_tchar *s = callbackLongPath; michael@0: NS_tchar *d = gCallbackRelPath; michael@0: // advance to the apply to directory and advance past the trailing backslash michael@0: // if present. michael@0: s += len; michael@0: if (*s == NS_T('\\')) michael@0: ++s; michael@0: michael@0: // Copy the string and replace backslashes with forward slashes along the michael@0: // way. michael@0: do { michael@0: if (*s == NS_T('\\')) michael@0: *d = NS_T('/'); michael@0: else michael@0: *d = *s; michael@0: ++s; michael@0: ++d; michael@0: } while (*s); michael@0: *d = NS_T('\0'); michael@0: ++d; michael@0: michael@0: // Make a copy of the callback executable so it can be read when patching. michael@0: NS_tsnprintf(gCallbackBackupPath, michael@0: sizeof(gCallbackBackupPath)/sizeof(gCallbackBackupPath[0]), michael@0: NS_T("%s" CALLBACK_BACKUP_EXT), argv[callbackIndex]); michael@0: NS_tremove(gCallbackBackupPath); michael@0: CopyFileW(argv[callbackIndex], gCallbackBackupPath, false); michael@0: michael@0: // Since the process may be signaled as exited by WaitForSingleObject before michael@0: // the release of the executable image try to lock the main executable file michael@0: // multiple times before giving up. If we end up giving up, we won't michael@0: // fail the update. michael@0: const int max_retries = 10; michael@0: int retries = 1; michael@0: DWORD lastWriteError = 0; michael@0: do { michael@0: // By opening a file handle wihout FILE_SHARE_READ to the callback michael@0: // executable, the OS will prevent launching the process while it is michael@0: // being updated. michael@0: callbackFile = CreateFileW(targetPath, michael@0: DELETE | GENERIC_WRITE, michael@0: // allow delete, rename, and write michael@0: FILE_SHARE_DELETE | FILE_SHARE_WRITE, michael@0: nullptr, OPEN_EXISTING, 0, nullptr); michael@0: if (callbackFile != INVALID_HANDLE_VALUE) michael@0: break; michael@0: michael@0: lastWriteError = GetLastError(); michael@0: LOG(("NS_main: callback app file open attempt %d failed. " \ michael@0: "File: " LOG_S ". Last error: %d", retries, michael@0: targetPath, lastWriteError)); michael@0: michael@0: Sleep(100); michael@0: } while (++retries <= max_retries); michael@0: michael@0: // CreateFileW will fail if the callback executable is already in use. michael@0: if (callbackFile == INVALID_HANDLE_VALUE) { michael@0: // Only fail the update if the last error was not a sharing violation. michael@0: if (lastWriteError != ERROR_SHARING_VIOLATION) { michael@0: LOG(("NS_main: callback app file in use, failed to exclusively open " \ michael@0: "executable file: " LOG_S, argv[callbackIndex])); michael@0: LogFinish(); michael@0: if (lastWriteError == ERROR_ACCESS_DENIED) { michael@0: WriteStatusFile(WRITE_ERROR_ACCESS_DENIED); michael@0: } else { michael@0: WriteStatusFile(WRITE_ERROR_CALLBACK_APP); michael@0: } michael@0: michael@0: NS_tremove(gCallbackBackupPath); michael@0: EXIT_WHEN_ELEVATED(elevatedLockFilePath, updateLockFileHandle, 1); michael@0: LaunchCallbackApp(argv[4], michael@0: argc - callbackIndex, michael@0: argv + callbackIndex, michael@0: sUsingService); michael@0: return 1; michael@0: } michael@0: LOG(("NS_main: callback app file in use, continuing without " \ michael@0: "exclusive access for executable file: " LOG_S, michael@0: argv[callbackIndex])); michael@0: } michael@0: } michael@0: } michael@0: michael@0: // DELETE_DIR is not required when staging an update. michael@0: if (!sStagedUpdate && !sReplaceRequest) { michael@0: // The directory to move files that are in use to on Windows. This directory michael@0: // will be deleted after the update is finished or on OS reboot using michael@0: // MoveFileEx if it contains files that are in use. michael@0: if (NS_taccess(DELETE_DIR, F_OK)) { michael@0: NS_tmkdir(DELETE_DIR, 0755); michael@0: } michael@0: } michael@0: #endif /* XP_WIN */ michael@0: michael@0: // Run update process on a background thread. ShowProgressUI may return michael@0: // before QuitProgressUI has been called, so wait for UpdateThreadFunc to michael@0: // terminate. Avoid showing the progress UI when staging an update. michael@0: Thread t; michael@0: if (t.Run(UpdateThreadFunc, nullptr) == 0) { michael@0: if (!sStagedUpdate && !sReplaceRequest) { michael@0: ShowProgressUI(); michael@0: } michael@0: } michael@0: t.Join(); michael@0: michael@0: #ifdef XP_WIN michael@0: if (argc > callbackIndex && !sReplaceRequest) { michael@0: if (callbackFile != INVALID_HANDLE_VALUE) { michael@0: CloseHandle(callbackFile); michael@0: } michael@0: // Remove the copy of the callback executable. michael@0: NS_tremove(gCallbackBackupPath); michael@0: } michael@0: michael@0: if (!sStagedUpdate && !sReplaceRequest && _wrmdir(DELETE_DIR)) { michael@0: LOG(("NS_main: unable to remove directory: " LOG_S ", err: %d", michael@0: DELETE_DIR, errno)); michael@0: // The directory probably couldn't be removed due to it containing files michael@0: // that are in use and will be removed on OS reboot. The call to remove the michael@0: // directory on OS reboot is done after the calls to remove the files so the michael@0: // files are removed first on OS reboot since the directory must be empty michael@0: // for the directory removal to be successful. The MoveFileEx call to remove michael@0: // the directory on OS reboot will fail if the process doesn't have write michael@0: // access to the HKEY_LOCAL_MACHINE registry key but this is ok since the michael@0: // installer / uninstaller will delete the directory along with its contents michael@0: // after an update is applied, on reinstall, and on uninstall. michael@0: if (MoveFileEx(DELETE_DIR, nullptr, MOVEFILE_DELAY_UNTIL_REBOOT)) { michael@0: LOG(("NS_main: directory will be removed on OS reboot: " LOG_S, michael@0: DELETE_DIR)); michael@0: } else { michael@0: LOG(("NS_main: failed to schedule OS reboot removal of " \ michael@0: "directory: " LOG_S, DELETE_DIR)); michael@0: } michael@0: } michael@0: #endif /* XP_WIN */ michael@0: michael@0: #if defined(MOZ_WIDGET_GONK) michael@0: } // end the extra level of scope for the GonkAutoMounter michael@0: #endif michael@0: michael@0: LogFinish(); michael@0: michael@0: if (argc > callbackIndex) { michael@0: #if defined(XP_WIN) && !defined(TOR_BROWSER_UPDATE) michael@0: if (gSucceeded) { michael@0: // The service update will only be executed if it is already installed. michael@0: // For first time installs of the service, the install will happen from michael@0: // the PostUpdate process. We do the service update process here michael@0: // because it's possible we are updating with updater.exe without the michael@0: // service if the service failed to apply the update. We want to update michael@0: // the service to a newer version in that case. If we are not running michael@0: // through the service, then MOZ_USING_SERVICE will not exist. michael@0: if (!sUsingService) { michael@0: NS_tchar installDir[MAXPATHLEN]; michael@0: if (GetInstallationDir(installDir)) { michael@0: if (!LaunchWinPostProcess(installDir, gSourcePath, false, nullptr)) { michael@0: LOG(("NS_main: The post update process could not be launched.")); michael@0: } michael@0: michael@0: StartServiceUpdate(installDir); michael@0: } michael@0: } michael@0: } michael@0: EXIT_WHEN_ELEVATED(elevatedLockFilePath, updateLockFileHandle, 0); michael@0: #endif /* XP_WIN */ michael@0: #ifdef XP_MACOSX michael@0: if (gSucceeded) { michael@0: LaunchMacPostProcess(argv[callbackIndex]); michael@0: } michael@0: #endif /* XP_MACOSX */ michael@0: michael@0: if (getenv("MOZ_PROCESS_UPDATES") == nullptr) { michael@0: LaunchCallbackApp(argv[4], michael@0: argc - callbackIndex, michael@0: argv + callbackIndex, michael@0: sUsingService); michael@0: } michael@0: } michael@0: michael@0: return gSucceeded ? 0 : 1; michael@0: } michael@0: michael@0: class ActionList michael@0: { michael@0: public: michael@0: ActionList() : mFirst(nullptr), mLast(nullptr), mCount(0) { } michael@0: ~ActionList(); michael@0: michael@0: void Append(Action* action); michael@0: int Prepare(); michael@0: int Execute(); michael@0: void Finish(int status); michael@0: michael@0: private: michael@0: Action *mFirst; michael@0: Action *mLast; michael@0: int mCount; michael@0: }; michael@0: michael@0: ActionList::~ActionList() michael@0: { michael@0: Action* a = mFirst; michael@0: while (a) { michael@0: Action *b = a; michael@0: a = a->mNext; michael@0: delete b; michael@0: } michael@0: } michael@0: michael@0: void michael@0: ActionList::Append(Action *action) michael@0: { michael@0: if (mLast) michael@0: mLast->mNext = action; michael@0: else michael@0: mFirst = action; michael@0: michael@0: mLast = action; michael@0: mCount++; michael@0: } michael@0: michael@0: int michael@0: ActionList::Prepare() michael@0: { michael@0: // If the action list is empty then we should fail in order to signal that michael@0: // something has gone wrong. Otherwise we report success when nothing is michael@0: // actually done. See bug 327140. michael@0: if (mCount == 0) { michael@0: LOG(("empty action list")); michael@0: return UNEXPECTED_MAR_ERROR; michael@0: } michael@0: michael@0: Action *a = mFirst; michael@0: int i = 0; michael@0: while (a) { michael@0: int rv = a->Prepare(); michael@0: if (rv) michael@0: return rv; michael@0: michael@0: float percent = float(++i) / float(mCount); michael@0: UpdateProgressUI(PROGRESS_PREPARE_SIZE * percent); michael@0: michael@0: a = a->mNext; michael@0: } michael@0: michael@0: return OK; michael@0: } michael@0: michael@0: int michael@0: ActionList::Execute() michael@0: { michael@0: int currentProgress = 0, maxProgress = 0; michael@0: Action *a = mFirst; michael@0: while (a) { michael@0: maxProgress += a->mProgressCost; michael@0: a = a->mNext; michael@0: } michael@0: michael@0: a = mFirst; michael@0: while (a) { michael@0: int rv = a->Execute(); michael@0: if (rv) { michael@0: LOG(("### execution failed")); michael@0: return rv; michael@0: } michael@0: michael@0: currentProgress += a->mProgressCost; michael@0: float percent = float(currentProgress) / float(maxProgress); michael@0: UpdateProgressUI(PROGRESS_PREPARE_SIZE + michael@0: PROGRESS_EXECUTE_SIZE * percent); michael@0: michael@0: a = a->mNext; michael@0: } michael@0: michael@0: return OK; michael@0: } michael@0: michael@0: void michael@0: ActionList::Finish(int status) michael@0: { michael@0: Action *a = mFirst; michael@0: int i = 0; michael@0: while (a) { michael@0: a->Finish(status); michael@0: michael@0: float percent = float(++i) / float(mCount); michael@0: UpdateProgressUI(PROGRESS_PREPARE_SIZE + michael@0: PROGRESS_EXECUTE_SIZE + michael@0: PROGRESS_FINISH_SIZE * percent); michael@0: michael@0: a = a->mNext; michael@0: } michael@0: michael@0: if (status == OK) michael@0: gSucceeded = true; michael@0: } michael@0: michael@0: michael@0: #ifdef XP_WIN michael@0: int add_dir_entries(const NS_tchar *dirpath, ActionList *list) michael@0: { michael@0: int rv = OK; michael@0: WIN32_FIND_DATAW finddata; michael@0: HANDLE hFindFile; michael@0: NS_tchar searchspec[MAXPATHLEN]; michael@0: NS_tchar foundpath[MAXPATHLEN]; michael@0: michael@0: NS_tsnprintf(searchspec, sizeof(searchspec)/sizeof(searchspec[0]), michael@0: NS_T("%s*"), dirpath); michael@0: const NS_tchar *pszSpec = get_full_path(searchspec); michael@0: michael@0: hFindFile = FindFirstFileW(pszSpec, &finddata); michael@0: if (hFindFile != INVALID_HANDLE_VALUE) { michael@0: do { michael@0: // Don't process the current or parent directory. michael@0: if (NS_tstrcmp(finddata.cFileName, NS_T(".")) == 0 || michael@0: NS_tstrcmp(finddata.cFileName, NS_T("..")) == 0) michael@0: continue; michael@0: michael@0: NS_tsnprintf(foundpath, sizeof(foundpath)/sizeof(foundpath[0]), michael@0: NS_T("%s%s"), dirpath, finddata.cFileName); michael@0: if (finddata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { michael@0: NS_tsnprintf(foundpath, sizeof(foundpath)/sizeof(foundpath[0]), michael@0: NS_T("%s/"), foundpath); michael@0: // Recurse into the directory. michael@0: rv = add_dir_entries(foundpath, list); michael@0: if (rv) { michael@0: LOG(("add_dir_entries error: " LOG_S ", err: %d", foundpath, rv)); michael@0: return rv; michael@0: } michael@0: } else { michael@0: // Add the file to be removed to the ActionList. michael@0: NS_tchar *quotedpath = get_quoted_path(foundpath); michael@0: if (!quotedpath) michael@0: return PARSE_ERROR; michael@0: michael@0: Action *action = new RemoveFile(); michael@0: rv = action->Parse(quotedpath); michael@0: if (rv) { michael@0: LOG(("add_dir_entries Parse error on recurse: " LOG_S ", err: %d", quotedpath, rv)); michael@0: return rv; michael@0: } michael@0: michael@0: list->Append(action); michael@0: } michael@0: } while (FindNextFileW(hFindFile, &finddata) != 0); michael@0: michael@0: FindClose(hFindFile); michael@0: { michael@0: // Add the directory to be removed to the ActionList. michael@0: NS_tchar *quotedpath = get_quoted_path(dirpath); michael@0: if (!quotedpath) michael@0: return PARSE_ERROR; michael@0: michael@0: Action *action = new RemoveDir(); michael@0: rv = action->Parse(quotedpath); michael@0: if (rv) michael@0: LOG(("add_dir_entries Parse error on close: " LOG_S ", err: %d", quotedpath, rv)); michael@0: else michael@0: list->Append(action); michael@0: } michael@0: } michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: #elif defined(SOLARIS) michael@0: int add_dir_entries(const NS_tchar *dirpath, ActionList *list) michael@0: { michael@0: int rv = OK; michael@0: NS_tchar searchpath[MAXPATHLEN]; michael@0: NS_tchar foundpath[MAXPATHLEN]; michael@0: struct { michael@0: dirent dent_buffer; michael@0: char chars[MAXNAMLEN]; michael@0: } ent_buf; michael@0: struct dirent* ent; michael@0: michael@0: michael@0: NS_tsnprintf(searchpath, sizeof(searchpath)/sizeof(searchpath[0]), NS_T("%s"), michael@0: dirpath); michael@0: // Remove the trailing slash so the paths don't contain double slashes. The michael@0: // existence of the slash has already been checked in DoUpdate. michael@0: searchpath[NS_tstrlen(searchpath) - 1] = NS_T('\0'); michael@0: michael@0: DIR* dir = opendir(searchpath); michael@0: if (!dir) { michael@0: LOG(("add_dir_entries error on opendir: " LOG_S ", err: %d", searchpath, michael@0: errno)); michael@0: return UNEXPECTED_FILE_OPERATION_ERROR; michael@0: } michael@0: michael@0: while (readdir_r(dir, (dirent *)&ent_buf, &ent) == 0 && ent) { michael@0: if ((strcmp(ent->d_name, ".") == 0) || michael@0: (strcmp(ent->d_name, "..") == 0)) michael@0: continue; michael@0: michael@0: NS_tsnprintf(foundpath, sizeof(foundpath)/sizeof(foundpath[0]), michael@0: NS_T("%s%s"), dirpath, ent->d_name); michael@0: struct stat64 st_buf; michael@0: int test = stat64(foundpath, &st_buf); michael@0: if (test) { michael@0: closedir(dir); michael@0: return UNEXPECTED_FILE_OPERATION_ERROR; michael@0: } michael@0: if (S_ISDIR(st_buf.st_mode)) { michael@0: NS_tsnprintf(foundpath, sizeof(foundpath)/sizeof(foundpath[0]), michael@0: NS_T("%s/"), foundpath); michael@0: // Recurse into the directory. michael@0: rv = add_dir_entries(foundpath, list); michael@0: if (rv) { michael@0: LOG(("add_dir_entries error: " LOG_S ", err: %d", foundpath, rv)); michael@0: closedir(dir); michael@0: return rv; michael@0: } michael@0: } else { michael@0: // Add the file to be removed to the ActionList. michael@0: NS_tchar *quotedpath = get_quoted_path(foundpath); michael@0: if (!quotedpath) { michael@0: closedir(dir); michael@0: return PARSE_ERROR; michael@0: } michael@0: michael@0: Action *action = new RemoveFile(); michael@0: rv = action->Parse(quotedpath); michael@0: if (rv) { michael@0: LOG(("add_dir_entries Parse error on recurse: " LOG_S ", err: %d", michael@0: quotedpath, rv)); michael@0: closedir(dir); michael@0: return rv; michael@0: } michael@0: michael@0: list->Append(action); michael@0: } michael@0: } michael@0: closedir(dir); michael@0: michael@0: // Add the directory to be removed to the ActionList. michael@0: NS_tchar *quotedpath = get_quoted_path(dirpath); michael@0: if (!quotedpath) michael@0: return PARSE_ERROR; michael@0: michael@0: Action *action = new RemoveDir(); michael@0: rv = action->Parse(quotedpath); michael@0: if (rv) { michael@0: LOG(("add_dir_entries Parse error on close: " LOG_S ", err: %d", michael@0: quotedpath, rv)); michael@0: } michael@0: else { michael@0: list->Append(action); michael@0: } michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: #else michael@0: michael@0: int add_dir_entries(const NS_tchar *dirpath, ActionList *list) michael@0: { michael@0: int rv = OK; michael@0: FTS *ftsdir; michael@0: FTSENT *ftsdirEntry; michael@0: NS_tchar searchpath[MAXPATHLEN]; michael@0: michael@0: NS_tsnprintf(searchpath, sizeof(searchpath)/sizeof(searchpath[0]), NS_T("%s"), michael@0: dirpath); michael@0: // Remove the trailing slash so the paths don't contain double slashes. The michael@0: // existence of the slash has already been checked in DoUpdate. michael@0: searchpath[NS_tstrlen(searchpath) - 1] = NS_T('\0'); michael@0: char* const pathargv[] = {searchpath, nullptr}; michael@0: michael@0: // FTS_NOCHDIR is used so relative paths from the destination directory are michael@0: // returned. michael@0: if (!(ftsdir = fts_open(pathargv, michael@0: FTS_PHYSICAL | FTS_NOSTAT | FTS_XDEV | FTS_NOCHDIR, michael@0: nullptr))) michael@0: return UNEXPECTED_FILE_OPERATION_ERROR; michael@0: michael@0: while ((ftsdirEntry = fts_read(ftsdir)) != nullptr) { michael@0: NS_tchar foundpath[MAXPATHLEN]; michael@0: NS_tchar *quotedpath; michael@0: Action *action = nullptr; michael@0: michael@0: switch (ftsdirEntry->fts_info) { michael@0: // Filesystem objects that shouldn't be in the application's directories michael@0: case FTS_SL: michael@0: case FTS_SLNONE: michael@0: case FTS_DEFAULT: michael@0: LOG(("add_dir_entries: found a non-standard file: " LOG_S, michael@0: ftsdirEntry->fts_path)); michael@0: // Fall through and try to remove as a file michael@0: michael@0: // Files michael@0: case FTS_F: michael@0: case FTS_NSOK: michael@0: // Add the file to be removed to the ActionList. michael@0: NS_tsnprintf(foundpath, sizeof(foundpath)/sizeof(foundpath[0]), michael@0: NS_T("%s"), ftsdirEntry->fts_accpath); michael@0: quotedpath = get_quoted_path(foundpath); michael@0: if (!quotedpath) { michael@0: rv = UPDATER_QUOTED_PATH_MEM_ERROR; michael@0: break; michael@0: } michael@0: action = new RemoveFile(); michael@0: rv = action->Parse(quotedpath); michael@0: if (!rv) michael@0: list->Append(action); michael@0: break; michael@0: michael@0: // Directories michael@0: case FTS_DP: michael@0: rv = OK; michael@0: // Add the directory to be removed to the ActionList. michael@0: NS_tsnprintf(foundpath, sizeof(foundpath)/sizeof(foundpath[0]), michael@0: NS_T("%s/"), ftsdirEntry->fts_accpath); michael@0: quotedpath = get_quoted_path(foundpath); michael@0: if (!quotedpath) { michael@0: rv = UPDATER_QUOTED_PATH_MEM_ERROR; michael@0: break; michael@0: } michael@0: michael@0: action = new RemoveDir(); michael@0: rv = action->Parse(quotedpath); michael@0: if (!rv) michael@0: list->Append(action); michael@0: break; michael@0: michael@0: // Errors michael@0: case FTS_DNR: michael@0: case FTS_NS: michael@0: // ENOENT is an acceptable error for FTS_DNR and FTS_NS and means that michael@0: // we're racing with ourselves. Though strange, the entry will be michael@0: // removed anyway. michael@0: if (ENOENT == ftsdirEntry->fts_errno) { michael@0: rv = OK; michael@0: break; michael@0: } michael@0: // Fall through michael@0: michael@0: case FTS_ERR: michael@0: rv = UNEXPECTED_FILE_OPERATION_ERROR; michael@0: LOG(("add_dir_entries: fts_read() error: " LOG_S ", err: %d", michael@0: ftsdirEntry->fts_path, ftsdirEntry->fts_errno)); michael@0: break; michael@0: michael@0: case FTS_DC: michael@0: rv = UNEXPECTED_FILE_OPERATION_ERROR; michael@0: LOG(("add_dir_entries: fts_read() returned FT_DC: " LOG_S, michael@0: ftsdirEntry->fts_path)); michael@0: break; michael@0: michael@0: default: michael@0: // FTS_D is ignored and FTS_DP is used instead (post-order). michael@0: rv = OK; michael@0: break; michael@0: } michael@0: michael@0: if (rv != OK) michael@0: break; michael@0: } michael@0: michael@0: fts_close(ftsdir); michael@0: michael@0: return rv; michael@0: } michael@0: #endif michael@0: michael@0: static NS_tchar* michael@0: GetManifestContents(const NS_tchar *manifest) michael@0: { michael@0: AutoFile mfile = NS_tfopen(manifest, NS_T("rb")); michael@0: if (mfile == nullptr) { michael@0: LOG(("GetManifestContents: error opening manifest file: " LOG_S, manifest)); michael@0: return nullptr; michael@0: } michael@0: michael@0: struct stat ms; michael@0: int rv = fstat(fileno((FILE *)mfile), &ms); michael@0: if (rv) { michael@0: LOG(("GetManifestContents: error stating manifest file: " LOG_S, manifest)); michael@0: return nullptr; michael@0: } michael@0: michael@0: char *mbuf = (char *) malloc(ms.st_size + 1); michael@0: if (!mbuf) michael@0: return nullptr; michael@0: michael@0: size_t r = ms.st_size; michael@0: char *rb = mbuf; michael@0: while (r) { michael@0: const size_t count = mmin(SSIZE_MAX, r); michael@0: size_t c = fread(rb, 1, count, mfile); michael@0: if (c != count) { michael@0: LOG(("GetManifestContents: error reading manifest file: " LOG_S, manifest)); michael@0: return nullptr; michael@0: } michael@0: michael@0: r -= c; michael@0: rb += c; michael@0: } michael@0: mbuf[ms.st_size] = '\0'; michael@0: rb = mbuf; michael@0: michael@0: #ifndef XP_WIN michael@0: return rb; michael@0: #else michael@0: NS_tchar *wrb = (NS_tchar *) malloc((ms.st_size + 1) * sizeof(NS_tchar)); michael@0: if (!wrb) michael@0: return nullptr; michael@0: michael@0: if (!MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, rb, -1, wrb, michael@0: ms.st_size + 1)) { michael@0: LOG(("GetManifestContents: error converting utf8 to utf16le: %d", GetLastError())); michael@0: free(mbuf); michael@0: free(wrb); michael@0: return nullptr; michael@0: } michael@0: free(mbuf); michael@0: michael@0: return wrb; michael@0: #endif michael@0: } michael@0: michael@0: int AddPreCompleteActions(ActionList *list) michael@0: { michael@0: if (sIsOSUpdate) { michael@0: return OK; michael@0: } michael@0: michael@0: NS_tchar *rb = GetManifestContents(NS_T("precomplete")); michael@0: if (rb == nullptr) { michael@0: LOG(("AddPreCompleteActions: error getting contents of precomplete " \ michael@0: "manifest")); michael@0: // Applications aren't required to have a precomplete manifest. The mar michael@0: // generation scripts enforce the presence of a precomplete manifest. michael@0: return OK; michael@0: } michael@0: michael@0: int rv; michael@0: NS_tchar *line; michael@0: while((line = mstrtok(kNL, &rb)) != 0) { michael@0: // skip comments michael@0: if (*line == NS_T('#')) michael@0: continue; michael@0: michael@0: NS_tchar *token = mstrtok(kWhitespace, &line); michael@0: if (!token) { michael@0: LOG(("AddPreCompleteActions: token not found in manifest")); michael@0: return PARSE_ERROR; michael@0: } michael@0: michael@0: Action *action = nullptr; michael@0: if (NS_tstrcmp(token, NS_T("remove")) == 0) { // rm file michael@0: action = new RemoveFile(); michael@0: } michael@0: else if (NS_tstrcmp(token, NS_T("remove-cc")) == 0) { // no longer supported michael@0: continue; michael@0: } michael@0: else if (NS_tstrcmp(token, NS_T("rmdir")) == 0) { // rmdir if empty michael@0: action = new RemoveDir(); michael@0: } michael@0: else { michael@0: LOG(("AddPreCompleteActions: unknown token: " LOG_S, token)); michael@0: return PARSE_ERROR; michael@0: } michael@0: michael@0: if (!action) michael@0: return BAD_ACTION_ERROR; michael@0: michael@0: rv = action->Parse(line); michael@0: if (rv) michael@0: return rv; michael@0: michael@0: list->Append(action); michael@0: } michael@0: michael@0: return OK; michael@0: } michael@0: michael@0: int DoUpdate() michael@0: { michael@0: NS_tchar manifest[MAXPATHLEN]; michael@0: NS_tsnprintf(manifest, sizeof(manifest)/sizeof(manifest[0]), michael@0: NS_T("%s/updating/update.manifest"), gDestinationPath); michael@0: ensure_parent_dir(manifest); michael@0: michael@0: // extract the manifest michael@0: int rv = gArchiveReader.ExtractFile("updatev3.manifest", manifest); michael@0: if (rv) { michael@0: rv = gArchiveReader.ExtractFile("updatev2.manifest", manifest); michael@0: if (rv) { michael@0: LOG(("DoUpdate: error extracting manifest file")); michael@0: return rv; michael@0: } michael@0: } michael@0: michael@0: NS_tchar *rb = GetManifestContents(manifest); michael@0: NS_tremove(manifest); michael@0: if (rb == nullptr) { michael@0: LOG(("DoUpdate: error opening manifest file: " LOG_S, manifest)); michael@0: return READ_ERROR; michael@0: } michael@0: michael@0: michael@0: ActionList list; michael@0: NS_tchar *line; michael@0: bool isFirstAction = true; michael@0: michael@0: while((line = mstrtok(kNL, &rb)) != 0) { michael@0: // skip comments michael@0: if (*line == NS_T('#')) michael@0: continue; michael@0: michael@0: NS_tchar *token = mstrtok(kWhitespace, &line); michael@0: if (!token) { michael@0: LOG(("DoUpdate: token not found in manifest")); michael@0: return PARSE_ERROR; michael@0: } michael@0: michael@0: if (isFirstAction) { michael@0: isFirstAction = false; michael@0: // The update manifest isn't required to have a type declaration. The mar michael@0: // generation scripts enforce the presence of the type declaration. michael@0: if (NS_tstrcmp(token, NS_T("type")) == 0) { michael@0: const NS_tchar *type = mstrtok(kQuote, &line); michael@0: LOG(("UPDATE TYPE " LOG_S, type)); michael@0: if (NS_tstrcmp(type, NS_T("complete")) == 0) { michael@0: rv = AddPreCompleteActions(&list); michael@0: if (rv) michael@0: return rv; michael@0: } michael@0: continue; michael@0: } michael@0: } michael@0: michael@0: Action *action = nullptr; michael@0: if (NS_tstrcmp(token, NS_T("remove")) == 0) { // rm file michael@0: action = new RemoveFile(); michael@0: } michael@0: else if (NS_tstrcmp(token, NS_T("rmdir")) == 0) { // rmdir if empty michael@0: action = new RemoveDir(); michael@0: } michael@0: else if (NS_tstrcmp(token, NS_T("rmrfdir")) == 0) { // rmdir recursive michael@0: const NS_tchar *reldirpath = mstrtok(kQuote, &line); michael@0: if (!reldirpath) michael@0: return PARSE_ERROR; michael@0: michael@0: if (reldirpath[NS_tstrlen(reldirpath) - 1] != NS_T('/')) michael@0: return PARSE_ERROR; michael@0: michael@0: rv = add_dir_entries(reldirpath, &list); michael@0: if (rv) michael@0: return rv; michael@0: michael@0: continue; michael@0: } michael@0: else if (NS_tstrcmp(token, NS_T("add")) == 0) { michael@0: action = new AddFile(); michael@0: } michael@0: else if (NS_tstrcmp(token, NS_T("patch")) == 0) { michael@0: action = new PatchFile(); michael@0: } michael@0: else if (NS_tstrcmp(token, NS_T("add-if")) == 0) { // Add if exists michael@0: action = new AddIfFile(); michael@0: } michael@0: else if (NS_tstrcmp(token, NS_T("add-if-not")) == 0) { // Add if not exists michael@0: action = new AddIfNotFile(); michael@0: } michael@0: else if (NS_tstrcmp(token, NS_T("patch-if")) == 0) { // Patch if exists michael@0: action = new PatchIfFile(); michael@0: } michael@0: #ifndef XP_WIN michael@0: else if (NS_tstrcmp(token, NS_T("addsymlink")) == 0) { michael@0: action = new AddSymlink(); michael@0: } michael@0: #endif michael@0: else { michael@0: LOG(("DoUpdate: unknown token: " LOG_S, token)); michael@0: return PARSE_ERROR; michael@0: } michael@0: michael@0: if (!action) michael@0: return BAD_ACTION_ERROR; michael@0: michael@0: rv = action->Parse(line); michael@0: if (rv) michael@0: return rv; michael@0: michael@0: list.Append(action); michael@0: } michael@0: michael@0: rv = list.Prepare(); michael@0: if (rv) michael@0: return rv; michael@0: michael@0: rv = list.Execute(); michael@0: michael@0: list.Finish(rv); michael@0: return rv; michael@0: }