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