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