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