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