toolkit/mozapps/update/updater/updater.cpp

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

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

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

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

mercurial