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