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