Sat, 03 Jan 2015 20:18:00 +0100
Conditionally enable double key logic according to:
private browsing mode or privacy.thirdparty.isolate preference and
implement in GetCookieStringCommon and FindCookie where it counts...
With some reservations of how to convince FindCookie users to test
condition and pass a nullptr when disabling double key logic.
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/. */
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"
43 #include <stdio.h>
44 #include <string.h>
45 #include <stdlib.h>
46 #include <stdarg.h>
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>
55 #include "updatelogging.h"
57 #include "mozilla/Compiler.h"
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
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
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
75 #ifndef _O_BINARY
76 # define _O_BINARY 0
77 #endif
79 #ifndef NULL
80 # define NULL (0)
81 #endif
83 #ifndef SSIZE_MAX
84 # define SSIZE_MAX LONG_MAX
85 #endif
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
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>
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);
105 # define MAYBE_USE_HARD_LINKS 1
106 static bool sUseHardLinks = true;
107 #else
108 # define MAYBE_USE_HARD_LINKS 0
109 #endif
111 #ifdef XP_WIN
112 #include "updatehelper.h"
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
128 //-----------------------------------------------------------------------------
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
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
148 static unsigned int
149 crc32(const unsigned char *buf, unsigned int len)
150 {
151 unsigned int crc = 0xffffffffL;
153 const unsigned char *end = buf + len;
154 for (; buf != end; ++buf)
155 crc = (crc << 8) ^ BZ2_crc32Table[(crc >> 24) ^ *buf];
157 crc = ~crc;
158 return crc;
159 }
161 //-----------------------------------------------------------------------------
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 }
172 ~AutoFile() {
173 if (mFile != nullptr)
174 fclose(mFile);
175 }
177 AutoFile &operator=(FILE* file) {
178 if (mFile != 0)
179 fclose(mFile);
180 mFile = file;
181 return *this;
182 }
184 operator FILE*() {
185 return mFile;
186 }
188 FILE* get() {
189 return mFile;
190 }
192 private:
193 FILE* mFile;
194 };
196 struct MARChannelStringTable {
197 MARChannelStringTable()
198 {
199 MARChannelID[0] = '\0';
200 }
202 char MARChannelID[MAX_TEXT_LEN];
203 };
205 //-----------------------------------------------------------------------------
207 typedef void (* ThreadFunc)(void *param);
209 #ifdef XP_WIN
210 #include <process.h>
212 class Thread
213 {
214 public:
215 int Run(ThreadFunc func, void *param)
216 {
217 mThreadFunc = func;
218 mThreadParam = param;
220 unsigned int threadID;
222 mThread = (HANDLE) _beginthreadex(nullptr, 0, ThreadMain, this, 0,
223 &threadID);
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 };
245 #elif defined(XP_UNIX)
246 #include <pthread.h>
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 };
264 #else
265 #error "Unsupported platform"
266 #endif
268 //-----------------------------------------------------------------------------
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;
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
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("\"");
290 static inline size_t
291 mmin(size_t a, size_t b)
292 {
293 return (a > b) ? b : a;
294 }
296 static NS_tchar*
297 mstrtok(const NS_tchar *delims, NS_tchar **str)
298 {
299 if (!*str || !**str)
300 return nullptr;
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);
314 if (!*ret) {
315 *str = ret;
316 return nullptr;
317 }
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);
331 *str = nullptr;
332 return ret;
333 }
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;
352 NS_tchar *c = s;
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
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 }
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 }
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
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 }
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 }
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 }
420 return path;
421 }
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;
430 NS_tchar *s = (NS_tchar *) malloc(len * sizeof(NS_tchar));
431 if (!s)
432 return nullptr;
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 }
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 }
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 }
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 }
484 NS_tDIR *dir;
485 NS_tdirent *entry;
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 }
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 }
507 NS_tclosedir(dir);
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 }
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;
526 // Make sure the string begins with "r"
527 if (flags[0] != NS_T('r'))
528 return false;
530 // Look for "r+" or "r+b"
531 if (length > 1 && flags[1] == NS_T('+'))
532 return false;
534 // Look for "rb+"
535 if (NS_tstrcmp(flags, NS_T("rb+")) == 0)
536 return false;
538 return true;
539 }
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 }
566 // Ensure that the directory containing this file exists.
567 static int ensure_parent_dir(const NS_tchar *path)
568 {
569 int rv = OK;
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 }
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
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 */
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
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 }
653 if (S_ISLNK(ss.st_mode)) {
654 return ensure_copy_symlink(path, dest);
655 }
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
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 }
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;
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 }
697 size_t written = 0;
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 }
708 written += chunkWritten;
709 }
710 }
712 rv = NS_tchmod(dest, ss.st_mode);
714 free(buffer);
715 return rv;
716 #endif
717 }
719 template <unsigned N>
720 struct copy_recursive_skiplist {
721 NS_tchar paths[N][MAXPATHLEN];
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 };
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 }
750 #ifndef XP_WIN
751 if (S_ISLNK(sInfo.st_mode)) {
752 return ensure_copy_symlink(path, dest);
753 }
754 #endif
756 if (!S_ISDIR(sInfo.st_mode)) {
757 return ensure_copy(path, dest);
758 }
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 }
767 NS_tDIR *dir;
768 NS_tdirent *entry;
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 }
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 }
796 return rv;
797 }
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;
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 }
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 }
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 }
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 }
850 return OK;
851 }
853 //-----------------------------------------------------------------------------
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);
862 return rename_file(path, backup);
863 }
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);
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
884 if (!isLink && NS_taccess(backup, F_OK)) {
885 LOG(("backup_restore: backup file doesn't exist: " LOG_S, backup));
886 return OK;
887 }
889 return rename_file(backup, path);
890 }
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);
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
909 // Nothing to discard
910 if (!isLink && NS_taccess(backup, F_OK)) {
911 return OK;
912 }
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
943 return OK;
944 }
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 }
955 //-----------------------------------------------------------------------------
957 static int DoUpdate();
959 class Action
960 {
961 public:
962 Action() : mProgressCost(1), mNext(nullptr) { }
963 virtual ~Action() { }
965 virtual int Parse(NS_tchar *line) = 0;
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;
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;
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;
980 int mProgressCost;
981 private:
982 Action* mNext;
984 friend class ActionList;
985 };
987 class RemoveFile : public Action
988 {
989 public:
990 RemoveFile() : mFile(nullptr), mSkip(0), mIsLink(0) { }
992 int Parse(NS_tchar *line);
993 int Prepare();
994 int Execute();
995 void Finish(int status);
997 private:
998 const NS_tchar *mFile;
999 int mSkip;
1000 int mIsLink;
1001 };
1003 int
1004 RemoveFile::Parse(NS_tchar *line)
1005 {
1006 // format "<deadfile>"
1008 mFile = get_valid_path(&line);
1009 if (!mFile)
1010 return PARSE_ERROR;
1012 return OK;
1013 }
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
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 }
1035 LOG(("PREPARE REMOVEFILE " LOG_S, mFile));
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 }
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 }
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 }
1062 if (rv) {
1063 LOG(("access failed: %d", errno));
1064 return WRITE_ERROR;
1065 }
1067 return OK;
1068 }
1070 int
1071 RemoveFile::Execute()
1072 {
1073 if (mSkip)
1074 return OK;
1076 LOG(("EXECUTE REMOVEFILE " LOG_S, mFile));
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 }
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 }
1100 return OK;
1101 }
1103 void
1104 RemoveFile::Finish(int status)
1105 {
1106 if (mSkip)
1107 return;
1109 LOG(("FINISH REMOVEFILE " LOG_S, mFile));
1111 backup_finish(mFile, status);
1112 }
1114 class RemoveDir : public Action
1115 {
1116 public:
1117 RemoveDir() : mDir(nullptr), mSkip(0) { }
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);
1124 private:
1125 const NS_tchar *mDir;
1126 int mSkip;
1127 };
1129 int
1130 RemoveDir::Parse(NS_tchar *line)
1131 {
1132 // format "<deaddir>/"
1134 mDir = get_valid_path(&line, true);
1135 if (!mDir)
1136 return PARSE_ERROR;
1138 return OK;
1139 }
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 }
1152 LOG(("PREPARE REMOVEDIR " LOG_S "/", mDir));
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 }
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 }
1168 rv = NS_taccess(mDir, W_OK);
1169 if (rv) {
1170 LOG(("access failed: %d, %d", rv, errno));
1171 return WRITE_ERROR;
1172 }
1174 return OK;
1175 }
1177 int
1178 RemoveDir::Execute()
1179 {
1180 if (mSkip)
1181 return OK;
1183 LOG(("EXECUTE REMOVEDIR " LOG_S "/", mDir));
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 }
1193 return OK;
1194 }
1196 void
1197 RemoveDir::Finish(int status)
1198 {
1199 if (mSkip || status != OK)
1200 return;
1202 LOG(("FINISH REMOVEDIR " LOG_S "/", mDir));
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 }
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 }
1221 class AddFile : public Action
1222 {
1223 public:
1224 AddFile() : mFile(nullptr)
1225 , mAdded(false)
1226 { }
1228 virtual int Parse(NS_tchar *line);
1229 virtual int Prepare();
1230 virtual int Execute();
1231 virtual void Finish(int status);
1233 private:
1234 const NS_tchar *mFile;
1235 bool mAdded;
1236 };
1238 int
1239 AddFile::Parse(NS_tchar *line)
1240 {
1241 // format "<newfile>"
1243 mFile = get_valid_path(&line);
1244 if (!mFile)
1245 return PARSE_ERROR;
1247 return OK;
1248 }
1250 int
1251 AddFile::Prepare()
1252 {
1253 LOG(("PREPARE ADD " LOG_S, mFile));
1255 return OK;
1256 }
1258 int
1259 AddFile::Execute()
1260 {
1261 LOG(("EXECUTE ADD " LOG_S, mFile));
1263 int rv;
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 }
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 }
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 }
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 }
1306 class PatchFile : public Action
1307 {
1308 public:
1309 PatchFile() : mPatchIndex(-1), buf(nullptr) { }
1311 virtual ~PatchFile();
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);
1318 private:
1319 int LoadSourceFile(FILE* ofile);
1321 static int sPatchIndex;
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 };
1331 int PatchFile::sPatchIndex = 0;
1333 PatchFile::~PatchFile()
1334 {
1335 // delete the temporary patch file
1336 if (spath[0])
1337 NS_tremove(spath);
1339 if (buf)
1340 free(buf);
1341 }
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 }
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 }
1360 buf = (unsigned char *) malloc(header.slen);
1361 if (!buf)
1362 return UPDATER_MEM_ERROR;
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 }
1375 r -= c;
1376 rb += c;
1377 }
1379 // Verify that the contents of the source file correspond to what we expect.
1381 unsigned int crc = crc32(buf, header.slen);
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 }
1389 return OK;
1390 }
1392 int
1393 PatchFile::Parse(NS_tchar *line)
1394 {
1395 // format "<patchfile>" "<filetopatch>"
1397 // Get the path to the patch file inside of the mar
1398 mPatchFile = mstrtok(kQuote, &line);
1399 if (!mPatchFile)
1400 return PARSE_ERROR;
1402 // consume whitespace between args
1403 NS_tchar *q = mstrtok(kQuote, &line);
1404 if (!q)
1405 return PARSE_ERROR;
1407 mFile = get_valid_path(&line);
1408 if (!mFile)
1409 return PARSE_ERROR;
1411 return OK;
1412 }
1414 int
1415 PatchFile::Prepare()
1416 {
1417 LOG(("PREPARE PATCH " LOG_S, mFile));
1419 // extract the patch to a temporary file
1420 mPatchIndex = sPatchIndex++;
1422 NS_tsnprintf(spath, sizeof(spath)/sizeof(spath[0]),
1423 NS_T("%s/updating/%d.patch"), gDestinationPath, mPatchIndex);
1425 NS_tremove(spath);
1427 FILE *fp = NS_tfopen(spath, NS_T("wb"));
1428 if (!fp)
1429 return WRITE_ERROR;
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 }
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 }
1447 int
1448 PatchFile::Execute()
1449 {
1450 LOG(("EXECUTE PATCH " LOG_S, mFile));
1452 AutoFile pfile = NS_tfopen(spath, NS_T("rb"));
1453 if (pfile == nullptr)
1454 return READ_ERROR;
1456 int rv = MBS_ReadHeader(pfile, &header);
1457 if (rv)
1458 return rv;
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
1473 if (!origfile) {
1474 LOG(("unable to open destination file: " LOG_S ", err: %d", mFile,
1475 errno));
1476 return READ_ERROR;
1477 }
1479 rv = LoadSourceFile(origfile);
1480 fclose(origfile);
1481 if (rv) {
1482 LOG(("LoadSourceFile failed"));
1483 return rv;
1484 }
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 }
1496 rv = backup_create(mFile);
1497 if (rv)
1498 return rv;
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);
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 }
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 }
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
1549 if (ofile == nullptr) {
1550 LOG(("unable to create new file: " LOG_S ", err: %d", mFile, errno));
1551 return WRITE_ERROR;
1552 }
1554 #ifdef XP_WIN
1555 if (!shouldTruncate) {
1556 fseek(ofile, 0, SEEK_SET);
1557 }
1558 #endif
1560 rv = MBS_ApplyPatch(&header, pfile, buf, ofile);
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;
1571 return rv;
1572 }
1574 void
1575 PatchFile::Finish(int status)
1576 {
1577 LOG(("FINISH PATCH " LOG_S, mFile));
1579 backup_finish(mFile, status);
1580 }
1582 class AddIfFile : public AddFile
1583 {
1584 public:
1585 AddIfFile() : mTestFile(nullptr) { }
1587 virtual int Parse(NS_tchar *line);
1588 virtual int Prepare();
1589 virtual int Execute();
1590 virtual void Finish(int status);
1592 protected:
1593 const NS_tchar *mTestFile;
1594 };
1596 int
1597 AddIfFile::Parse(NS_tchar *line)
1598 {
1599 // format "<testfile>" "<newfile>"
1601 mTestFile = get_valid_path(&line);
1602 if (!mTestFile)
1603 return PARSE_ERROR;
1605 // consume whitespace between args
1606 NS_tchar *q = mstrtok(kQuote, &line);
1607 if (!q)
1608 return PARSE_ERROR;
1610 return AddFile::Parse(line);
1611 }
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 }
1622 return AddFile::Prepare();
1623 }
1625 int
1626 AddIfFile::Execute()
1627 {
1628 if (!mTestFile)
1629 return OK;
1631 return AddFile::Execute();
1632 }
1634 void
1635 AddIfFile::Finish(int status)
1636 {
1637 if (!mTestFile)
1638 return;
1640 AddFile::Finish(status);
1641 }
1643 class AddIfNotFile : public AddFile
1644 {
1645 public:
1646 AddIfNotFile() : mTestFile(NULL) { }
1648 virtual int Parse(NS_tchar *line);
1649 virtual int Prepare();
1650 virtual int Execute();
1651 virtual void Finish(int status);
1653 protected:
1654 const NS_tchar *mTestFile;
1655 };
1657 int
1658 AddIfNotFile::Parse(NS_tchar *line)
1659 {
1660 // format "<testfile>" "<newfile>"
1662 mTestFile = get_valid_path(&line);
1663 if (!mTestFile)
1664 return PARSE_ERROR;
1666 // consume whitespace between args
1667 NS_tchar *q = mstrtok(kQuote, &line);
1668 if (!q)
1669 return PARSE_ERROR;
1671 return AddFile::Parse(line);
1672 }
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 }
1683 return AddFile::Prepare();
1684 }
1686 int
1687 AddIfNotFile::Execute()
1688 {
1689 if (!mTestFile)
1690 return OK;
1692 return AddFile::Execute();
1693 }
1695 void
1696 AddIfNotFile::Finish(int status)
1697 {
1698 if (!mTestFile)
1699 return;
1701 AddFile::Finish(status);
1702 }
1704 class PatchIfFile : public PatchFile
1705 {
1706 public:
1707 PatchIfFile() : mTestFile(nullptr) { }
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);
1714 private:
1715 const NS_tchar *mTestFile;
1716 };
1718 int
1719 PatchIfFile::Parse(NS_tchar *line)
1720 {
1721 // format "<testfile>" "<patchfile>" "<filetopatch>"
1723 mTestFile = get_valid_path(&line);
1724 if (!mTestFile)
1725 return PARSE_ERROR;
1727 // consume whitespace between args
1728 NS_tchar *q = mstrtok(kQuote, &line);
1729 if (!q)
1730 return PARSE_ERROR;
1732 return PatchFile::Parse(line);
1733 }
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 }
1744 return PatchFile::Prepare();
1745 }
1747 int
1748 PatchIfFile::Execute()
1749 {
1750 if (!mTestFile)
1751 return OK;
1753 return PatchFile::Execute();
1754 }
1756 void
1757 PatchIfFile::Finish(int status)
1758 {
1759 if (!mTestFile)
1760 return;
1762 PatchFile::Finish(status);
1763 }
1765 #ifndef XP_WIN
1766 class AddSymlink : public Action
1767 {
1768 public:
1769 AddSymlink() : mLinkName(NULL)
1770 , mTarget(NULL)
1771 , mAdded(false)
1772 { }
1774 virtual int Parse(NS_tchar *line);
1775 virtual int Prepare();
1776 virtual int Execute();
1777 virtual void Finish(int status);
1779 private:
1780 const NS_tchar *mLinkName;
1781 const NS_tchar *mTarget;
1782 bool mAdded;
1783 };
1785 int
1786 AddSymlink::Parse(NS_tchar *line)
1787 {
1788 // format "<linkname>" "target"
1790 mLinkName = get_valid_path(&line);
1791 if (!mLinkName)
1792 return PARSE_ERROR;
1794 // consume whitespace between args
1795 NS_tchar *q = mstrtok(kQuote, &line);
1796 if (!q)
1797 return PARSE_ERROR;
1799 mTarget = get_valid_path(&line, false, true);
1800 if (!mTarget)
1801 return PARSE_ERROR;
1803 return OK;
1804 }
1806 int
1807 AddSymlink::Prepare()
1808 {
1809 LOG(("PREPARE ADDSYMLINK " LOG_S " -> " LOG_S, mLinkName, mTarget));
1811 return OK;
1812 }
1814 int
1815 AddSymlink::Execute()
1816 {
1817 LOG(("EXECUTE ADDSYMLINK " LOG_S " -> " LOG_S, mLinkName, mTarget));
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 }
1835 // Create the link.
1836 rv = symlink(mTarget, mLinkName);
1837 if (!rv) {
1838 mAdded = true;
1839 }
1841 return rv;
1842 }
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
1856 //-----------------------------------------------------------------------------
1858 #ifdef XP_WIN
1859 #include "nsWindowsRestart.cpp"
1860 #include "nsWindowsHelpers.h"
1861 #include "uachelper.h"
1862 #include "pathhash.h"
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
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"));
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 }
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 }
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);
1928 // Make sure that the directory for the update status file exists
1929 if (ensure_parent_dir(filename))
1930 return false;
1932 AutoFile file = NS_tfopen(filename, NS_T("wb+"));
1933 if (file == nullptr)
1934 return false;
1936 if (fwrite(aStatus, strlen(aStatus), 1, file) != 1)
1937 return false;
1939 return true;
1940 }
1942 static void
1943 WriteStatusFile(int status)
1944 {
1945 const char *text;
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 }
1959 WriteStatusFile(text);
1960 }
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);
1979 AutoFile file = NS_tfopen(filename, NS_T("rb"));
1980 if (file == nullptr)
1981 return false;
1983 char buf[32] = { 0 };
1984 fread(buf, sizeof(buf), 1, file);
1986 const char kPendingService[] = "pending-service";
1987 const char kAppliedService[] = "applied-service";
1989 return (strncmp(buf, kPendingService,
1990 sizeof(kPendingService) - 1) == 0) ||
1991 (strncmp(buf, kAppliedService,
1992 sizeof(kAppliedService) - 1) == 0);
1993 }
1994 #endif
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);
2013 AutoFile file = NS_tfopen(filename, NS_T("rb"));
2014 if (file == nullptr)
2015 return false;
2017 char buf[32] = { 0 };
2018 fread(buf, sizeof(buf), 1, file);
2020 const char kSucceeded[] = "succeeded";
2021 isSucceeded = strncmp(buf, kSucceeded,
2022 sizeof(kSucceeded) - 1) == 0;
2023 return true;
2024 }
2025 #endif
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 }
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 }
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 }
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
2112 return ensure_copy_recursive(installDir, gDestinationPath, skiplist);
2113 }
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).
2129 NS_tchar installDir[MAXPATHLEN];
2130 if (!GetInstallationDir(installDir)) {
2131 return NO_INSTALLDIR_ERROR;
2132 }
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
2151 NS_tchar tmpDir[MAXPATHLEN];
2152 NS_tsnprintf(tmpDir, sizeof(tmpDir)/sizeof(tmpDir[0]),
2153 NS_T("%s.bak"), sourceDir);
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);
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);
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));
2185 Sleep(100);
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 }
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 }
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 }
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 }
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);
2258 LOG(("Moving the precomplete file"));
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);
2265 NS_tchar precompleteTmp[MAXPATHLEN];
2266 NS_tsnprintf(precompleteTmp, sizeof(precompleteTmp)/sizeof(precompleteTmp[0]),
2267 NS_T("%s/precomplete.bak"), installDir);
2269 NS_tchar precompleteNew[MAXPATHLEN];
2270 NS_tsnprintf(precompleteNew, sizeof(precompleteNew)/sizeof(precompleteNew[0]),
2271 NS_T("%s/Updated.app/precomplete"), installDir);
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));
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 }
2292 LOG(("Finished moving the precomplete file"));
2293 #endif
2295 gSucceeded = true;
2297 return 0;
2298 }
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
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];
2327 int result = ReadStrings(path, kUpdaterKeys, kNumStrings,
2328 updater_strings, "Settings");
2330 strncpy(results->MARChannelID, updater_strings[0], MAX_TEXT_LEN - 1);
2331 results->MARChannelID[MAX_TEXT_LEN - 1] = 0;
2333 return result;
2334 }
2335 #endif
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).
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 }
2353 char dataFileName[MAXPATHLEN];
2354 size_t bytesRead;
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';
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 }
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 }
2394 #ifdef MOZ_VERIFY_MAR_SIGNATURE
2395 if (rv == OK) {
2396 rv = gArchiveReader.VerifySignature();
2397 }
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 }
2420 rv = gArchiveReader.VerifyProductInformation(MARStrings.MARChannelID,
2421 MOZ_APP_VERSION);
2422 }
2423 }
2424 #endif
2426 if (rv == OK && sStagedUpdate && !sIsOSUpdate) {
2427 rv = CopyInstallDirToDestDir();
2428 }
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 }
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);
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 }
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
2495 LOG(("succeeded"));
2496 }
2497 WriteStatusFile(rv);
2498 }
2500 LOG(("calling QuitProgressUI"));
2501 QuitProgressUI();
2502 }
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);
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 }
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 }
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="));
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
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
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 }
2613 if (getenv("MOZ_OS_UPDATE")) {
2614 sIsOSUpdate = true;
2615 putenv(const_cast<char*>("MOZ_OS_UPDATE="));
2616 }
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 }
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
2640 LogInitAppend(logDir, NS_T("last-update.log"), NS_T("update.log"));
2641 } else {
2642 LogInit(gSourcePath, NS_T("update.log"));
2643 }
2645 if (!WriteStatusFile("applying")) {
2646 LOG(("failed setting status to 'applying'"));
2647 return 1;
2648 }
2650 if (sStagedUpdate) {
2651 LOG(("Performing a staged update"));
2652 } else if (sReplaceRequest) {
2653 LOG(("Performing a replace request"));
2654 }
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
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
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 }
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;
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;
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 }
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 }
2784 updateLockFileHandle = CreateFileW(updateLockFilePath,
2785 GENERIC_READ | GENERIC_WRITE,
2786 0,
2787 nullptr,
2788 OPEN_ALWAYS,
2789 FILE_FLAG_DELETE_ON_CLOSE,
2790 nullptr);
2792 NS_tsnprintf(elevatedLockFilePath,
2793 sizeof(elevatedLockFilePath)/sizeof(elevatedLockFilePath[0]),
2794 NS_T("%s/update_elevated.lock"), gSourcePath);
2797 // Even if a file has no sharing access, you can still get its attributes
2798 bool startedFromUnelevatedUpdater =
2799 GetFileAttributesW(elevatedLockFilePath) != INVALID_FILE_ATTRIBUTES;
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 }
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 }
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);
2830 if (elevatedFileHandle == INVALID_HANDLE_VALUE) {
2831 LOG(("Unable to create elevated lock file! Exiting"));
2832 return 1;
2833 }
2835 wchar_t *cmdLine = MakeCommandLine(argc - 1, argv + 1);
2836 if (!cmdLine) {
2837 CloseHandle(elevatedFileHandle);
2838 return 1;
2839 }
2841 NS_tchar installDir[MAXPATHLEN];
2842 if (!GetInstallationDir(installDir)) {
2843 return 1;
2844 }
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 }
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 }
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 }
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.
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 }
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 }
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 }
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 }
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 }
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;
2987 bool result = ShellExecuteEx(&sinfo);
2988 free(cmdLine);
2990 if (result) {
2991 WaitForSingleObject(sinfo.hProcess, INFINITE);
2992 CloseHandle(sinfo.hProcess);
2993 } else {
2994 WriteStatusFile(ELEVATION_CANCELED);
2995 }
2996 }
2998 if (argc > callbackIndex) {
2999 LaunchCallbackApp(argv[4], argc - callbackIndex,
3000 argv + callbackIndex, sUsingService);
3001 }
3003 CloseHandle(elevatedFileHandle);
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
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);
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
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 }
3074 LOG(("SOURCE DIRECTORY " LOG_S, gSourcePath));
3075 LOG(("DESTINATION DIRECTORY " LOG_S, gDestinationPath));
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;
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++;
3098 gDestPath = destpath;
3099 }
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 }
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);
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 }
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;
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;
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);
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;
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));
3225 Sleep(100);
3226 } while (++retries <= max_retries);
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 }
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 }
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 */
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();
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 }
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 */
3309 #if defined(MOZ_WIDGET_GONK)
3310 } // end the extra level of scope for the GonkAutoMounter
3311 #endif
3313 LogFinish();
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 }
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 */
3344 if (getenv("MOZ_PROCESS_UPDATES") == nullptr) {
3345 LaunchCallbackApp(argv[4],
3346 argc - callbackIndex,
3347 argv + callbackIndex,
3348 sUsingService);
3349 }
3350 }
3352 return gSucceeded ? 0 : 1;
3353 }
3355 class ActionList
3356 {
3357 public:
3358 ActionList() : mFirst(nullptr), mLast(nullptr), mCount(0) { }
3359 ~ActionList();
3361 void Append(Action* action);
3362 int Prepare();
3363 int Execute();
3364 void Finish(int status);
3366 private:
3367 Action *mFirst;
3368 Action *mLast;
3369 int mCount;
3370 };
3372 ActionList::~ActionList()
3373 {
3374 Action* a = mFirst;
3375 while (a) {
3376 Action *b = a;
3377 a = a->mNext;
3378 delete b;
3379 }
3380 }
3382 void
3383 ActionList::Append(Action *action)
3384 {
3385 if (mLast)
3386 mLast->mNext = action;
3387 else
3388 mFirst = action;
3390 mLast = action;
3391 mCount++;
3392 }
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 }
3405 Action *a = mFirst;
3406 int i = 0;
3407 while (a) {
3408 int rv = a->Prepare();
3409 if (rv)
3410 return rv;
3412 float percent = float(++i) / float(mCount);
3413 UpdateProgressUI(PROGRESS_PREPARE_SIZE * percent);
3415 a = a->mNext;
3416 }
3418 return OK;
3419 }
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 }
3431 a = mFirst;
3432 while (a) {
3433 int rv = a->Execute();
3434 if (rv) {
3435 LOG(("### execution failed"));
3436 return rv;
3437 }
3439 currentProgress += a->mProgressCost;
3440 float percent = float(currentProgress) / float(maxProgress);
3441 UpdateProgressUI(PROGRESS_PREPARE_SIZE +
3442 PROGRESS_EXECUTE_SIZE * percent);
3444 a = a->mNext;
3445 }
3447 return OK;
3448 }
3450 void
3451 ActionList::Finish(int status)
3452 {
3453 Action *a = mFirst;
3454 int i = 0;
3455 while (a) {
3456 a->Finish(status);
3458 float percent = float(++i) / float(mCount);
3459 UpdateProgressUI(PROGRESS_PREPARE_SIZE +
3460 PROGRESS_EXECUTE_SIZE +
3461 PROGRESS_FINISH_SIZE * percent);
3463 a = a->mNext;
3464 }
3466 if (status == OK)
3467 gSucceeded = true;
3468 }
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];
3480 NS_tsnprintf(searchspec, sizeof(searchspec)/sizeof(searchspec[0]),
3481 NS_T("%s*"), dirpath);
3482 const NS_tchar *pszSpec = get_full_path(searchspec);
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;
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;
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 }
3516 list->Append(action);
3517 }
3518 } while (FindNextFileW(hFindFile, &finddata) != 0);
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;
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 }
3536 return rv;
3537 }
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;
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');
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 }
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;
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 }
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 }
3605 list->Append(action);
3606 }
3607 }
3608 closedir(dir);
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;
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 }
3625 return rv;
3626 }
3628 #else
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];
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};
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;
3651 while ((ftsdirEntry = fts_read(ftsdir)) != nullptr) {
3652 NS_tchar foundpath[MAXPATHLEN];
3653 NS_tchar *quotedpath;
3654 Action *action = nullptr;
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
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;
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 }
3694 action = new RemoveDir();
3695 rv = action->Parse(quotedpath);
3696 if (!rv)
3697 list->Append(action);
3698 break;
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
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;
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;
3724 default:
3725 // FTS_D is ignored and FTS_DP is used instead (post-order).
3726 rv = OK;
3727 break;
3728 }
3730 if (rv != OK)
3731 break;
3732 }
3734 fts_close(ftsdir);
3736 return rv;
3737 }
3738 #endif
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 }
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 }
3756 char *mbuf = (char *) malloc(ms.st_size + 1);
3757 if (!mbuf)
3758 return nullptr;
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 }
3770 r -= c;
3771 rb += c;
3772 }
3773 mbuf[ms.st_size] = '\0';
3774 rb = mbuf;
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;
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);
3792 return wrb;
3793 #endif
3794 }
3796 int AddPreCompleteActions(ActionList *list)
3797 {
3798 if (sIsOSUpdate) {
3799 return OK;
3800 }
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 }
3811 int rv;
3812 NS_tchar *line;
3813 while((line = mstrtok(kNL, &rb)) != 0) {
3814 // skip comments
3815 if (*line == NS_T('#'))
3816 continue;
3818 NS_tchar *token = mstrtok(kWhitespace, &line);
3819 if (!token) {
3820 LOG(("AddPreCompleteActions: token not found in manifest"));
3821 return PARSE_ERROR;
3822 }
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 }
3839 if (!action)
3840 return BAD_ACTION_ERROR;
3842 rv = action->Parse(line);
3843 if (rv)
3844 return rv;
3846 list->Append(action);
3847 }
3849 return OK;
3850 }
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);
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 }
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 }
3877 ActionList list;
3878 NS_tchar *line;
3879 bool isFirstAction = true;
3881 while((line = mstrtok(kNL, &rb)) != 0) {
3882 // skip comments
3883 if (*line == NS_T('#'))
3884 continue;
3886 NS_tchar *token = mstrtok(kWhitespace, &line);
3887 if (!token) {
3888 LOG(("DoUpdate: token not found in manifest"));
3889 return PARSE_ERROR;
3890 }
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 }
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;
3920 if (reldirpath[NS_tstrlen(reldirpath) - 1] != NS_T('/'))
3921 return PARSE_ERROR;
3923 rv = add_dir_entries(reldirpath, &list);
3924 if (rv)
3925 return rv;
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 }
3954 if (!action)
3955 return BAD_ACTION_ERROR;
3957 rv = action->Parse(line);
3958 if (rv)
3959 return rv;
3961 list.Append(action);
3962 }
3964 rv = list.Prepare();
3965 if (rv)
3966 return rv;
3968 rv = list.Execute();
3970 list.Finish(rv);
3971 return rv;
3972 }