Version 7.6.3.2-android, tag libreoffice-7.6.3.2-android
[LibreOffice.git] / onlineupdate / source / update / updater / updater.cxx
blob467f0b67cd1fbfc9a63cf4a14fe75680bbcf4c3a
1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 /**
6 * Manifest Format
7 * ---------------
9 * contents = 1*( line )
10 * line = method LWS *( param LWS ) CRLF
11 * CRLF = "\r\n"
12 * LWS = 1*( " " | "\t" )
14 * Available methods for the manifest file:
16 * updatev2.manifest
17 * -----------------
18 * method = "add" | "add-if" | "patch" | "patch-if" | "remove" |
19 * "rmdir" | "rmrfdir" | type
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.
25 * updatev3.manifest
26 * -----------------
27 * method = "add" | "add-if" | "add-if-not" | "patch" | "patch-if" |
28 * "remove" | "rmdir" | "rmrfdir" | type
30 * 'add-if-not' adds a file if it doesn't exist.
32 * precomplete
33 * -----------
34 * method = "remove" | "rmdir"
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>
45 #include <stdio.h>
46 #include <string.h>
47 #include <stdlib.h>
48 #include <stdarg.h>
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>
58 #include <config_version.h>
60 #include "updatelogging.h"
62 #include <onlineupdate/mozilla/Compiler.h>
63 #include <onlineupdate/mozilla/Types.h>
65 #ifdef _WIN32
66 #include <comphelper/windowsStart.hxx>
67 #include "uachelper.h"
68 #include "pathhash.h"
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
75 #endif
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
84 // Amount of time in ms to wait for the parent process to close
85 #ifdef _WIN32
86 #define PARENT_WAIT 5000
87 #endif
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
101 int argc;
102 const NS_tchar** argv;
104 #endif
106 #ifndef SSIZE_MAX
107 # define SSIZE_MAX LONG_MAX
108 #endif
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
116 #if defined(VERIFY_MAR_SIGNATURE) && !defined(_WIN32) && !defined(MACOSX)
117 #include <nss.h>
118 #include <nspr.h>
119 #endif
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"
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) \
136 if (handle != INVALID_HANDLE_VALUE) { \
137 CloseHandle(handle); \
139 if (_waccess(path, F_OK) == 0 && NS_tremove(path) != 0) { \
140 LogFinish(); \
141 return retCode; \
144 #endif
146 //-----------------------------------------------------------------------------
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
158 static unsigned int
159 crc32(const unsigned char *buf, unsigned int len)
161 unsigned int crc = 0xffffffffL;
163 const unsigned char *end = buf + len;
164 for (; buf != end; ++buf)
165 crc = (crc << 8) ^ BZ2_crc32Table[(crc >> 24) ^ *buf];
167 crc = ~crc;
168 return crc;
171 //-----------------------------------------------------------------------------
173 // A simple stack based container for a FILE struct that closes the
174 // file descriptor from its destructor.
175 class AutoFile
177 public:
178 explicit AutoFile(FILE* file = nullptr)
179 : mFile(file)
183 ~AutoFile()
185 if (mFile != nullptr)
186 fclose(mFile);
189 AutoFile &operator=(FILE* file)
191 if (mFile != 0)
192 fclose(mFile);
193 mFile = file;
194 return *this;
197 operator FILE*()
199 return mFile;
202 FILE* get()
204 return mFile;
207 private:
208 FILE* mFile;
211 struct MARChannelStringTable
213 MARChannelStringTable()
215 MARChannelID[0] = '\0';
218 char MARChannelID[MAX_TEXT_LEN];
221 //-----------------------------------------------------------------------------
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;
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
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("\"");
243 static NS_tchar*
244 mstrtok(const NS_tchar *delims, NS_tchar **str)
246 if (!*str || !**str)
248 *str = nullptr;
249 return nullptr;
252 // skip leading "whitespace"
253 NS_tchar *ret = *str;
254 const NS_tchar *d;
257 for (d = delims; *d != NS_T('\0'); ++d)
259 if (*ret == *d)
261 ++ret;
262 break;
266 while (*d);
268 if (!*ret)
270 *str = ret;
271 return nullptr;
274 NS_tchar *i = ret;
277 for (d = delims; *d != NS_T('\0'); ++d)
279 if (*i == *d)
281 *i = NS_T('\0');
282 *str = ++i;
283 return ret;
286 ++i;
288 while (*i);
290 *str = nullptr;
291 return ret;
294 #if defined(_WIN32) && defined(MAINTENANCE_SERVICE)
295 static bool
296 EnvHasValue(const char *name)
298 const char *val = getenv(name);
299 return (val && *val);
301 #endif
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!
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.
311 static NS_tchar*
312 new_absolute_path(const NS_tchar *relpath)
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];
319 NS_tchar *c = s;
321 NS_tstrcpy(c, destpath);
322 c += lendestpath;
323 NS_tstrcat(c, NS_T("/"));
324 c++;
326 NS_tstrcat(c, relpath);
327 c += lenrelpath;
328 *c = NS_T('\0');
329 return s;
332 namespace {
334 bool is_userprofile_in_instdir()
336 return false;
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');
349 size_t userprofile_len = NS_tstrlen(userprofile);
350 size_t installdir_len = NS_tstrlen(gInstallDirPath);
352 if (userprofile_len < installdir_len)
353 return false;
355 return NS_tstrncmp(userprofile, gInstallDirPath, installdir_len) == 0;
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.
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.
370 static const NS_tchar*
371 get_relative_offset(const NS_tchar *abs_path)
373 // If the path isn't absolute, just return it as-is.
374 #ifdef _WIN32
375 if (abs_path[1] != ':' && abs_path[2] != '\\')
377 #else
378 if (abs_path[0] != '/')
380 #endif
381 return abs_path;
384 NS_tchar *prefix = sStagedUpdate ? gWorkingDirPath : gInstallDirPath;
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;
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.
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.
404 static NS_tchar*
405 get_valid_path(NS_tchar **line, bool isdir = false)
407 NS_tchar *path = mstrtok(kQuote, line);
408 if (!path)
410 LOG(("get_valid_path: unable to determine path: " LOG_S, line));
411 return nullptr;
414 // All paths must be relative from the current working directory
415 if (path[0] == NS_T('/'))
417 LOG(("get_valid_path: path must be relative: " LOG_S, path));
418 return nullptr;
421 #ifdef _WIN32
422 // All paths must be relative from the current working directory
423 if (path[0] == NS_T('\\') || path[1] == NS_T(':'))
425 LOG(("get_valid_path: path must be relative: " LOG_S, path));
426 return nullptr;
428 #endif
430 if (isdir)
432 // Directory paths must have a trailing forward slash.
433 if (path[NS_tstrlen(path) - 1] != NS_T('/'))
435 LOG(("get_valid_path: directory paths must have a trailing forward " \
436 "slash: " LOG_S, path));
437 return nullptr;
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');
445 // Don't allow relative paths that resolve to a parent directory.
446 if (NS_tstrstr(path, NS_T("..")) != nullptr)
448 LOG(("get_valid_path: paths must not contain '..': " LOG_S, path));
449 return nullptr;
452 return path;
455 static NS_tchar*
456 get_quoted_path(const NS_tchar *path)
458 size_t lenQuote = NS_tstrlen(kQuote);
459 size_t lenPath = NS_tstrlen(path);
460 size_t len = lenQuote + lenPath + lenQuote + 1;
462 NS_tchar *s = (NS_tchar *) malloc(len * sizeof(NS_tchar));
463 if (!s)
464 return nullptr;
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;
478 static void ensure_write_permissions(const NS_tchar *path)
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))
486 (void)chmod(path, fs.st_mode | S_IWUSR);
488 #endif
491 static int ensure_remove(const NS_tchar *path)
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;
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)
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)
511 // This error is benign
512 return rv;
514 if (!S_ISDIR(sInfo.st_mode))
516 return ensure_remove(path);
519 NS_tDIR *dir;
520 NS_tdirent *entry;
522 dir = NS_topendir(path);
523 if (!dir)
525 LOG(("ensure_remove_recursive: unable to open directory: " LOG_S
526 ", rv: %d, err: %d", path, rv, errno));
527 return rv;
530 while ((entry = NS_treaddir(dir)) != 0)
532 if (NS_tstrcmp(entry->d_name, NS_T(".")) &&
533 NS_tstrcmp(entry->d_name, NS_T("..")))
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)
541 break;
546 NS_tclosedir(dir);
548 if (rv == OK)
550 ensure_write_permissions(path);
551 rv = NS_trmdir(path);
552 if (rv)
554 LOG(("ensure_remove_recursive: unable to remove directory: " LOG_S
555 ", rv: %d, err: %d", path, rv, errno));
558 return rv;
561 static bool is_read_only(const NS_tchar *flags)
563 size_t length = NS_tstrlen(flags);
564 if (length == 0)
565 return false;
567 // Make sure the string begins with "r"
568 if (flags[0] != NS_T('r'))
569 return false;
571 // Look for "r+" or "r+b"
572 if (length > 1 && flags[1] == NS_T('+'))
573 return false;
575 // Look for "rb+"
576 if (NS_tstrcmp(flags, NS_T("rb+")) == 0)
577 return false;
579 return true;
582 static FILE* ensure_open(const NS_tchar *path, const NS_tchar *flags, unsigned int options)
584 ensure_write_permissions(path);
585 FILE* f = NS_tfopen(path, flags);
586 if (is_read_only(flags))
588 // Don't attempt to modify the file permissions if the file is being opened
589 // in read-only mode.
590 return f;
592 if (NS_tchmod(path, options) != 0)
594 if (f != nullptr)
596 fclose(f);
598 return nullptr;
600 struct NS_tstat_t ss;
601 if (NS_tstat(path, &ss) != 0 || ss.st_mode != options)
603 if (f != nullptr)
605 fclose(f);
607 return nullptr;
609 return f;
612 // Ensure that the directory containing this file exists.
613 static int ensure_parent_dir(const NS_tchar *path)
615 int rv = OK;
617 NS_tchar *slash = (NS_tchar *) NS_tstrrchr(path, NS_T('/'));
618 if (slash)
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)
625 rv = NS_tmkdir(path, 0755);
626 // If the directory already exists, then ignore the error.
627 if (rv < 0 && errno != EEXIST)
629 LOG(("ensure_parent_dir: failed to create directory: " LOG_S ", " \
630 "err: %d", path, errno));
631 rv = WRITE_ERROR;
633 else
635 rv = OK;
638 *slash = NS_T('/');
640 return rv;
643 #ifdef UNIX
644 static int ensure_copy_symlink(const NS_tchar *path, const NS_tchar *dest)
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)
651 LOG(("ensure_copy_symlink: failed to read the link: " LOG_S ", err: %d",
652 path, errno));
653 return READ_ERROR;
655 rv = symlink(target, dest);
656 if (rv == -1)
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;
662 return 0;
664 #endif
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)
669 #ifdef _WIN32
670 // Fast path for Windows
671 bool result = CopyFileW(path, dest, false);
672 if (!result)
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;
678 return OK;
679 #else
680 struct NS_tstat_t ss;
681 int rv = NS_tlstat(path, &ss);
682 if (rv)
684 LOG(("ensure_copy: failed to read file status info: " LOG_S ", err: %d",
685 path, errno));
686 return READ_ERROR;
689 #ifdef UNIX
690 if (S_ISLNK(ss.st_mode))
692 return ensure_copy_symlink(path, dest);
694 #endif
696 AutoFile infile(ensure_open(path, NS_T("rb"), ss.st_mode));
697 if (!infile)
699 LOG(("ensure_copy: failed to open the file for reading: " LOG_S ", err: %d",
700 path, errno));
701 return READ_ERROR;
703 AutoFile outfile(ensure_open(dest, NS_T("wb"), ss.st_mode));
704 if (!outfile)
706 LOG(("ensure_copy: failed to open the file for writing: " LOG_S ", err: %d",
707 dest, errno));
708 return WRITE_ERROR;
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;
719 while (!feof(infile.get()))
721 size_t read = fread(buffer, 1, blockSize, infile);
722 if (ferror(infile.get()))
724 LOG(("ensure_copy: failed to read the file: " LOG_S ", err: %d",
725 path, errno));
726 free(buffer);
727 return READ_ERROR;
730 size_t written = 0;
732 while (written < read)
734 size_t nCount = read - written;
735 size_t chunkWritten = fwrite(buffer, 1, nCount, outfile);
736 if (chunkWritten != nCount)
738 LOG(("ensure_copy: failed to write the file: " LOG_S ", err: %d",
739 dest, errno));
740 free(buffer);
741 return WRITE_ERROR_FILE_COPY;
744 written += chunkWritten;
748 rv = NS_tchmod(dest, ss.st_mode);
750 free(buffer);
751 return rv;
752 #endif
755 template <unsigned N>
756 struct copy_recursive_skiplist
758 NS_tchar paths[N][MAXPATHLEN];
760 void append(unsigned index, const NS_tchar *path, const NS_tchar *suffix)
762 NS_tsnprintf(paths[index], MAXPATHLEN, NS_T("%s/%s"), path, suffix);
765 void append(unsigned index, const NS_tchar* path)
767 NS_tstrcpy(paths[index], path);
770 bool find(const NS_tchar *path)
772 for (int i = 0; i < static_cast<int>(N); ++i)
774 if (!NS_tstricmp(paths[i], path))
776 return true;
779 return false;
783 // Copy all of the files and subdirectories under path to a new directory named dest.
784 // The path names in the skiplist will be skipped and will not be copied.
785 template <unsigned N>
786 static int ensure_copy_recursive(const NS_tchar *path, const NS_tchar *dest,
787 copy_recursive_skiplist<N>& skiplist)
789 struct NS_tstat_t sInfo;
790 int rv = NS_tlstat(path, &sInfo);
791 if (rv)
793 LOG(("ensure_copy_recursive: path doesn't exist: " LOG_S ", rv: %d, err: %d",
794 path, rv, errno));
795 return READ_ERROR;
798 #ifdef UNIX
799 if (S_ISLNK(sInfo.st_mode))
801 return ensure_copy_symlink(path, dest);
803 #endif
805 if (!S_ISDIR(sInfo.st_mode))
807 return ensure_copy(path, dest);
810 rv = NS_tmkdir(dest, sInfo.st_mode);
811 if (rv < 0 && errno != EEXIST)
813 LOG(("ensure_copy_recursive: could not create destination directory: " LOG_S ", rv: %d, err: %d",
814 path, rv, errno));
815 return WRITE_ERROR;
818 NS_tDIR *dir;
819 NS_tdirent *entry;
821 dir = NS_topendir(path);
822 if (!dir)
824 LOG(("ensure_copy_recursive: path is not a directory: " LOG_S ", rv: %d, err: %d",
825 path, rv, errno));
826 return READ_ERROR;
829 while ((entry = NS_treaddir(dir)) != 0)
831 if (NS_tstrcmp(entry->d_name, NS_T(".")) &&
832 NS_tstrcmp(entry->d_name, NS_T("..")))
834 NS_tchar childPath[MAXPATHLEN];
835 NS_tsnprintf(childPath, sizeof(childPath)/sizeof(childPath[0]),
836 NS_T("%s/%s"), path, entry->d_name);
837 if (skiplist.find(childPath))
839 continue;
841 NS_tchar childPathDest[MAXPATHLEN];
842 NS_tsnprintf(childPathDest, sizeof(childPathDest)/sizeof(childPathDest[0]),
843 NS_T("%s/%s"), dest, entry->d_name);
844 rv = ensure_copy_recursive(childPath, childPathDest, skiplist);
845 if (rv)
847 break;
851 NS_tclosedir(dir);
852 return rv;
855 // Renames the specified file to the new file specified. If the destination file
856 // exists it is removed.
857 static int rename_file(const NS_tchar *spath, const NS_tchar *dpath,
858 bool allowDirs = false)
860 int rv = ensure_parent_dir(dpath);
861 if (rv)
862 return rv;
864 struct NS_tstat_t spathInfo;
865 rv = NS_tstat(spath, &spathInfo);
866 if (rv)
868 LOG(("rename_file: failed to read file status info: " LOG_S ", " \
869 "err: %d", spath, errno));
870 return READ_ERROR;
873 if (!S_ISREG(spathInfo.st_mode))
875 if (allowDirs && !S_ISDIR(spathInfo.st_mode))
877 LOG(("rename_file: path present, but not a file: " LOG_S ", err: %d",
878 spath, errno));
879 return RENAME_ERROR_EXPECTED_FILE;
881 else
883 LOG(("rename_file: proceeding to rename the directory"));
887 if (!NS_taccess(dpath, F_OK))
889 if (ensure_remove(dpath))
891 LOG(("rename_file: destination file exists and could not be " \
892 "removed: " LOG_S, dpath));
893 return WRITE_ERROR_DELETE_FILE;
897 if (NS_trename(spath, dpath) != 0)
899 LOG(("rename_file: failed to rename file - src: " LOG_S ", " \
900 "dst:" LOG_S ", err: %d", spath, dpath, errno));
901 return WRITE_ERROR;
904 return OK;
907 #ifdef _WIN32
908 // Remove the directory pointed to by path and all of its files and
909 // sub-directories. If a file is in use move it to the tobedeleted directory
910 // and attempt to schedule removal of the file on reboot
911 static int remove_recursive_on_reboot(const NS_tchar *path, const NS_tchar *deleteDir)
913 struct NS_tstat_t sInfo;
914 int rv = NS_tlstat(path, &sInfo);
915 if (rv)
917 // This error is benign
918 return rv;
921 if (!S_ISDIR(sInfo.st_mode))
923 NS_tchar tmpDeleteFile[MAXPATHLEN];
924 GetTempFileNameW(deleteDir, L"rep", 0, tmpDeleteFile);
925 NS_tremove(tmpDeleteFile);
926 rv = rename_file(path, tmpDeleteFile, false);
927 if (MoveFileEx(rv ? path : tmpDeleteFile, nullptr, MOVEFILE_DELAY_UNTIL_REBOOT))
929 LOG(("remove_recursive_on_reboot: file will be removed on OS reboot: "
930 LOG_S, rv ? path : tmpDeleteFile));
932 else
934 LOG(("remove_recursive_on_reboot: failed to schedule OS reboot removal of "
935 "file: " LOG_S, rv ? path : tmpDeleteFile));
937 return rv;
940 NS_tDIR *dir;
941 NS_tdirent *entry;
943 dir = NS_topendir(path);
944 if (!dir)
946 LOG(("remove_recursive_on_reboot: unable to open directory: " LOG_S
947 ", rv: %d, err: %d",
948 path, rv, errno));
949 return rv;
952 while ((entry = NS_treaddir(dir)) != 0)
954 if (NS_tstrcmp(entry->d_name, NS_T(".")) &&
955 NS_tstrcmp(entry->d_name, NS_T("..")))
957 NS_tchar childPath[MAXPATHLEN];
958 NS_tsnprintf(childPath, sizeof(childPath)/sizeof(childPath[0]),
959 NS_T("%s/%s"), path, entry->d_name);
960 // There is no need to check the return value of this call since this
961 // function is only called after an update is successful and there is not
962 // much that can be done to recover if it isn't successful. There is also
963 // no need to log the value since it will have already been logged.
964 remove_recursive_on_reboot(childPath, deleteDir);
968 NS_tclosedir(dir);
970 if (rv == OK)
972 ensure_write_permissions(path);
973 rv = NS_trmdir(path);
974 if (rv)
976 LOG(("remove_recursive_on_reboot: unable to remove directory: " LOG_S
977 ", rv: %d, err: %d", path, rv, errno));
980 return rv;
982 #endif
984 //-----------------------------------------------------------------------------
986 // Create a backup of the specified file by renaming it.
987 static int backup_create(const NS_tchar *path)
989 NS_tchar backup[MAXPATHLEN];
990 NS_tsnprintf(backup, sizeof(backup)/sizeof(backup[0]),
991 NS_T("%s") BACKUP_EXT, path);
993 return rename_file(path, backup);
996 // Rename the backup of the specified file that was created by renaming it back
997 // to the original file.
998 static int backup_restore(const NS_tchar *path, const NS_tchar *relPath)
1000 NS_tchar backup[MAXPATHLEN];
1001 NS_tsnprintf(backup, sizeof(backup)/sizeof(backup[0]),
1002 NS_T("%s") BACKUP_EXT, path);
1004 NS_tchar relBackup[MAXPATHLEN];
1005 NS_tsnprintf(relBackup, sizeof(relBackup) / sizeof(relBackup[0]),
1006 NS_T("%s") BACKUP_EXT, relPath);
1008 if (NS_taccess(backup, F_OK))
1010 LOG(("backup_restore: backup file doesn't exist: " LOG_S, relBackup));
1011 return OK;
1014 return rename_file(backup, path);
1017 // Discard the backup of the specified file that was created by renaming it.
1018 static int backup_discard(const NS_tchar *path, const NS_tchar *relPath)
1020 NS_tchar backup[MAXPATHLEN];
1021 NS_tsnprintf(backup, sizeof(backup)/sizeof(backup[0]),
1022 NS_T("%s") BACKUP_EXT, path);
1024 NS_tchar relBackup[MAXPATHLEN];
1025 NS_tsnprintf(relBackup, sizeof(relBackup) / sizeof(relBackup[0]),
1026 NS_T("%s") BACKUP_EXT, relPath);
1028 // Nothing to discard
1029 if (NS_taccess(backup, F_OK))
1031 return OK;
1034 int rv = ensure_remove(backup);
1035 #if defined(_WIN32)
1036 if (rv && !sStagedUpdate && !sReplaceRequest)
1038 LOG(("backup_discard: unable to remove: " LOG_S, relBackup));
1039 NS_tchar path[MAXPATHLEN];
1040 GetTempFileNameW(gDeleteDirPath, L"moz", 0, path);
1041 if (rename_file(backup, path))
1043 LOG(("backup_discard: failed to rename file:" LOG_S ", dst:" LOG_S,
1044 relBackup, relPath));
1045 return WRITE_ERROR_DELETE_BACKUP;
1047 // The MoveFileEx call to remove the file on OS reboot will fail if the
1048 // process doesn't have write access to the HKEY_LOCAL_MACHINE registry key
1049 // but this is ok since the installer / uninstaller will delete the
1050 // directory containing the file along with its contents after an update is
1051 // applied, on reinstall, and on uninstall.
1052 if (MoveFileEx(path, nullptr, MOVEFILE_DELAY_UNTIL_REBOOT))
1054 LOG(("backup_discard: file renamed and will be removed on OS " \
1055 "reboot: " LOG_S, relPath));
1057 else
1059 LOG(("backup_discard: failed to schedule OS reboot removal of " \
1060 "file: " LOG_S, relPath));
1063 #else
1064 if (rv)
1065 return WRITE_ERROR_DELETE_BACKUP;
1066 #endif
1068 return OK;
1071 // Helper function for post-processing a temporary backup.
1072 static void backup_finish(const NS_tchar *path, const NS_tchar *relPath,
1073 int status)
1075 if (status == OK)
1076 backup_discard(path, relPath);
1077 else
1078 backup_restore(path, relPath);
1081 //-----------------------------------------------------------------------------
1083 static int DoUpdate(ArchiveReader& ArchiveReader);
1085 class Action
1087 public:
1088 Action() : mProgressCost(1), mNext(nullptr) { }
1089 virtual ~Action() { }
1091 virtual int Parse(NS_tchar *line) = 0;
1093 // Do any preprocessing to ensure that the action can be performed. Execute
1094 // will be called if this Action and all others return OK from this method.
1095 virtual int Prepare() = 0;
1097 // Perform the operation. Return OK to indicate success. After all actions
1098 // have been executed, Finish will be called. A requirement of Execute is
1099 // that its operation be reversible from Finish.
1100 virtual int Execute() = 0;
1102 // Finish is called after execution of all actions. If status is OK, then
1103 // all actions were successfully executed. Otherwise, some action failed.
1104 virtual void Finish(int status) = 0;
1106 int mProgressCost;
1107 private:
1108 Action* mNext;
1110 friend class ActionList;
1113 class RemoveFile : public Action
1115 public:
1116 RemoveFile() : mSkip(0) { }
1118 int Parse(NS_tchar *line);
1119 int Prepare();
1120 int Execute();
1121 void Finish(int status);
1123 private:
1124 std::unique_ptr<const NS_tchar[]> mFile;
1125 std::unique_ptr<NS_tchar[]> mRelPath;
1126 int mSkip;
1130 RemoveFile::Parse(NS_tchar *line)
1132 // format "<deadfile>"
1134 NS_tchar* validPath = get_valid_path(&line);
1135 if (!validPath)
1136 return PARSE_ERROR;
1138 mRelPath.reset(new NS_tchar[MAXPATHLEN]);
1139 NS_tstrcpy(mRelPath.get(), validPath);
1141 mFile.reset(new_absolute_path(validPath));
1142 if (!mFile)
1144 return PARSE_ERROR;
1147 return OK;
1151 RemoveFile::Prepare()
1153 // Skip the file if it already doesn't exist.
1154 int rv = NS_taccess(mFile.get(), F_OK);
1155 if (rv)
1157 mSkip = 1;
1158 mProgressCost = 0;
1159 return OK;
1162 LOG(("PREPARE REMOVEFILE " LOG_S, mRelPath.get()));
1164 // Make sure that we're actually a file...
1165 struct NS_tstat_t fileInfo;
1166 rv = NS_tstat(mFile.get(), &fileInfo);
1167 if (rv)
1169 LOG(("failed to read file status info: " LOG_S ", err: %d", mFile.get(),
1170 errno));
1171 return READ_ERROR;
1174 if (!S_ISREG(fileInfo.st_mode))
1176 LOG(("path present, but not a file: " LOG_S, mFile.get()));
1177 return DELETE_ERROR_EXPECTED_FILE;
1180 NS_tchar *slash = (NS_tchar *) NS_tstrrchr(mFile.get(), NS_T('/'));
1181 if (slash)
1183 *slash = NS_T('\0');
1184 rv = NS_taccess(mFile.get(), W_OK);
1185 *slash = NS_T('/');
1187 else
1189 rv = NS_taccess(NS_T("."), W_OK);
1192 if (rv)
1194 LOG(("access failed: %d", errno));
1195 return WRITE_ERROR_FILE_ACCESS_DENIED;
1198 return OK;
1202 RemoveFile::Execute()
1204 if (mSkip)
1205 return OK;
1207 LOG(("EXECUTE REMOVEFILE " LOG_S, mRelPath.get()));
1209 // The file is checked for existence here and in Prepare since it might have
1210 // been removed by a separate instruction: bug 311099.
1211 int rv = NS_taccess(mFile.get(), F_OK);
1212 if (rv)
1214 LOG(("file cannot be removed because it does not exist; skipping"));
1215 mSkip = 1;
1216 return OK;
1219 // Rename the old file. It will be removed in Finish.
1220 rv = backup_create(mFile.get());
1221 if (rv)
1223 LOG(("backup_create failed: %d", rv));
1224 return rv;
1227 return OK;
1230 void
1231 RemoveFile::Finish(int status)
1233 if (mSkip)
1234 return;
1236 LOG(("FINISH REMOVEFILE " LOG_S, mRelPath.get()));
1238 backup_finish(mFile.get(), mRelPath.get(), status);
1241 class RemoveDir : public Action
1243 public:
1244 RemoveDir() : mSkip(0) { }
1246 virtual int Parse(NS_tchar *line);
1247 virtual int Prepare(); // check that the source dir exists
1248 virtual int Execute();
1249 virtual void Finish(int status);
1251 private:
1252 std::unique_ptr<NS_tchar[]> mDir;
1253 std::unique_ptr<NS_tchar[]> mRelPath;
1254 int mSkip;
1258 RemoveDir::Parse(NS_tchar *line)
1260 // format "<deaddir>/"
1262 NS_tchar* validPath = get_valid_path(&line, true);
1263 if (!validPath)
1264 return PARSE_ERROR;
1266 mRelPath.reset(new NS_tchar[MAXPATHLEN]);
1267 NS_tstrcpy(mRelPath.get(), validPath);
1269 mDir.reset(new_absolute_path(validPath));
1270 if (!mDir)
1272 return PARSE_ERROR;
1275 return OK;
1279 RemoveDir::Prepare()
1281 // We expect the directory to exist if we are to remove it.
1282 int rv = NS_taccess(mDir.get(), F_OK);
1283 if (rv)
1285 mSkip = 1;
1286 mProgressCost = 0;
1287 return OK;
1290 LOG(("PREPARE REMOVEDIR " LOG_S "/", mRelPath.get()));
1292 // Make sure that we're actually a dir.
1293 struct NS_tstat_t dirInfo;
1294 rv = NS_tstat(mDir.get(), &dirInfo);
1295 if (rv)
1297 LOG(("failed to read directory status info: " LOG_S ", err: %d", mRelPath.get(),
1298 errno));
1299 return READ_ERROR;
1302 if (!S_ISDIR(dirInfo.st_mode))
1304 LOG(("path present, but not a directory: " LOG_S, mRelPath.get()));
1305 return DELETE_ERROR_EXPECTED_DIR;
1308 rv = NS_taccess(mDir.get(), W_OK);
1309 if (rv)
1311 LOG(("access failed: %d, %d", rv, errno));
1312 return WRITE_ERROR_DIR_ACCESS_DENIED;
1315 return OK;
1319 RemoveDir::Execute()
1321 if (mSkip)
1322 return OK;
1324 LOG(("EXECUTE REMOVEDIR " LOG_S "/", mRelPath.get()));
1326 // The directory is checked for existence at every step since it might have
1327 // been removed by a separate instruction: bug 311099.
1328 int rv = NS_taccess(mDir.get(), F_OK);
1329 if (rv)
1331 LOG(("directory no longer exists; skipping"));
1332 mSkip = 1;
1335 return OK;
1338 void
1339 RemoveDir::Finish(int status)
1341 if (mSkip || status != OK)
1342 return;
1344 LOG(("FINISH REMOVEDIR " LOG_S "/", mRelPath.get()));
1346 // The directory is checked for existence at every step since it might have
1347 // been removed by a separate instruction: bug 311099.
1348 int rv = NS_taccess(mDir.get(), F_OK);
1349 if (rv)
1351 LOG(("directory no longer exists; skipping"));
1352 return;
1356 if (status == OK)
1358 if (NS_trmdir(mDir.get()))
1360 LOG(("non-fatal error removing directory: " LOG_S "/, rv: %d, err: %d",
1361 mRelPath.get(), rv, errno));
1366 class AddFile : public Action
1368 public:
1369 AddFile(ArchiveReader& ar) : mAdded(false), mArchiveReader(ar) { }
1371 virtual int Parse(NS_tchar *line);
1372 virtual int Prepare();
1373 virtual int Execute();
1374 virtual void Finish(int status);
1376 private:
1377 std::unique_ptr<NS_tchar[]> mFile;
1378 std::unique_ptr<NS_tchar[]> mRelPath;
1379 bool mAdded;
1380 ArchiveReader& mArchiveReader;
1384 AddFile::Parse(NS_tchar *line)
1386 // format "<newfile>"
1388 NS_tchar* validPath = get_valid_path(&line);
1389 if (!validPath)
1390 return PARSE_ERROR;
1392 mRelPath.reset(new NS_tchar[MAXPATHLEN]);
1393 NS_tstrcpy(mRelPath.get(), validPath);
1395 mFile.reset(new_absolute_path(validPath));
1396 if (!mFile)
1398 return PARSE_ERROR;
1401 return OK;
1405 AddFile::Prepare()
1407 LOG(("PREPARE ADD " LOG_S, mRelPath.get()));
1409 return OK;
1413 AddFile::Execute()
1415 LOG(("EXECUTE ADD " LOG_S, mRelPath.get()));
1417 int rv;
1419 // First make sure that we can actually get rid of any existing file.
1420 rv = NS_taccess(mFile.get(), F_OK);
1421 if (rv == 0)
1423 rv = backup_create(mFile.get());
1424 if (rv)
1425 return rv;
1427 else
1429 rv = ensure_parent_dir(mFile.get());
1430 if (rv)
1431 return rv;
1434 #ifdef _WIN32
1435 char sourcefile[MAXPATHLEN];
1436 if (!WideCharToMultiByte(CP_UTF8, 0, mRelPath.get(), -1, sourcefile,
1437 MAXPATHLEN, nullptr, nullptr))
1439 LOG(("error converting wchar to utf8: %d", GetLastError()));
1440 return STRING_CONVERSION_ERROR;
1443 rv = mArchiveReader.ExtractFile(sourcefile, mFile.get());
1444 #else
1445 rv = mArchiveReader.ExtractFile(mRelPath.get(), mFile.get());
1446 #endif
1447 if (!rv)
1449 mAdded = true;
1451 return rv;
1454 void
1455 AddFile::Finish(int status)
1457 LOG(("FINISH ADD " LOG_S, mRelPath.get()));
1458 // When there is an update failure and a file has been added it is removed
1459 // here since there might not be a backup to replace it.
1460 if (status && mAdded)
1461 NS_tremove(mFile.get());
1462 backup_finish(mFile.get(), mRelPath.get(), status);
1465 class PatchFile : public Action
1467 public:
1468 PatchFile(ArchiveReader& ar) : mPatchFile(nullptr), mPatchIndex(-1), buf(nullptr), mArchiveReader(ar) { }
1470 virtual ~PatchFile();
1472 virtual int Parse(NS_tchar *line);
1473 virtual int Prepare(); // should check for patch file and for checksum here
1474 virtual int Execute();
1475 virtual void Finish(int status);
1477 private:
1478 int LoadSourceFile(FILE* ofile);
1480 static int sPatchIndex;
1482 const NS_tchar *mPatchFile;
1483 std::unique_ptr<NS_tchar> mFile;
1484 std::unique_ptr<NS_tchar> mFileRelPath;
1485 int mPatchIndex;
1486 MBSPatchHeader header;
1487 unsigned char *buf;
1488 NS_tchar spath[MAXPATHLEN];
1489 AutoFile mPatchStream;
1490 ArchiveReader& mArchiveReader;
1493 int PatchFile::sPatchIndex = 0;
1495 PatchFile::~PatchFile()
1497 // Make sure mPatchStream gets unlocked on Windows; the system will do that,
1498 // but not until some indeterminate future time, and we want determinism.
1499 // Normally this happens at the end of Execute, when we close the stream;
1500 // this call is here in case Execute errors out.
1501 #ifdef _WIN32
1502 if (mPatchStream)
1504 UnlockFile((HANDLE)_get_osfhandle(fileno(mPatchStream)), (DWORD)0, (DWORD)0, (DWORD)-1, (DWORD)-1);
1506 #endif
1508 // delete the temporary patch file
1509 if (spath[0])
1510 NS_tremove(spath);
1512 if (buf)
1513 free(buf);
1517 PatchFile::LoadSourceFile(FILE* ofile)
1519 struct stat os;
1520 int rv = fstat(fileno((FILE *)ofile), &os);
1521 if (rv)
1523 LOG(("LoadSourceFile: unable to stat destination file: " LOG_S ", " \
1524 "err: %d", mFileRelPath.get(), errno));
1525 return READ_ERROR;
1528 if (uint32_t(os.st_size) != header.slen)
1530 LOG(("LoadSourceFile: destination file size %d does not match expected size %d",
1531 uint32_t(os.st_size), header.slen));
1532 return LOADSOURCE_ERROR_WRONG_SIZE;
1535 buf = (unsigned char *) malloc(header.slen);
1536 if (!buf)
1537 return UPDATER_MEM_ERROR;
1539 size_t r = header.slen;
1540 unsigned char *rb = buf;
1541 while (r)
1543 const size_t count = std::min<size_t>(SSIZE_MAX, r);
1544 size_t c = fread(rb, 1, count, ofile);
1545 if (c != count)
1547 LOG(("LoadSourceFile: error reading destination file: " LOG_S,
1548 mFileRelPath.get()));
1549 return READ_ERROR;
1552 r -= c;
1553 rb += c;
1556 // Verify that the contents of the source file correspond to what we expect.
1558 unsigned int crc = crc32(buf, header.slen);
1560 if (crc != header.scrc32)
1562 LOG(("LoadSourceFile: destination file crc %d does not match expected " \
1563 "crc %d", crc, header.scrc32));
1564 return CRC_ERROR;
1567 return OK;
1571 PatchFile::Parse(NS_tchar *line)
1573 // format "<patchfile>" "<filetopatch>"
1575 // Get the path to the patch file inside of the mar
1576 mPatchFile = mstrtok(kQuote, &line);
1577 if (!mPatchFile)
1578 return PARSE_ERROR;
1580 // consume whitespace between args
1581 NS_tchar *q = mstrtok(kQuote, &line);
1582 if (!q)
1583 return PARSE_ERROR;
1585 NS_tchar* validPath = get_valid_path(&line);
1586 if (!validPath)
1587 return PARSE_ERROR;
1588 mFileRelPath.reset(new NS_tchar[MAXPATHLEN]);
1589 NS_tstrcpy(mFileRelPath.get(), validPath);
1591 mFile.reset(new_absolute_path(validPath));
1592 if (!mFile)
1594 return PARSE_ERROR;
1597 return OK;
1601 PatchFile::Prepare()
1603 LOG(("PREPARE PATCH " LOG_S, mFileRelPath.get()));
1605 // extract the patch to a temporary file
1606 mPatchIndex = sPatchIndex++;
1608 int nWrittenBytes = NS_tsnprintf(spath, sizeof(spath)/sizeof(spath[0]),
1609 NS_T("%s/updating/%d.patch"), gWorkingDirPath, mPatchIndex);
1610 (void) nWrittenBytes;
1612 NS_tremove(spath);
1614 mPatchStream = NS_tfopen(spath, NS_T("wb+"));
1615 if (!mPatchStream)
1616 return WRITE_ERROR;
1618 #ifdef _WIN32
1619 // Lock the patch file, so it can't be messed with between
1620 // when we're done creating it and when we go to apply it.
1621 if (!LockFile((HANDLE)_get_osfhandle(fileno(mPatchStream)), (DWORD)0, (DWORD)0, (DWORD)-1, (DWORD)-1))
1623 LOG(("Couldn't lock patch file: %d", GetLastError()));
1624 // TODO: moggi: fix the build problem with LOCK_ERROR_PATCH_FILE
1625 return WRITE_ERROR; //return LOCK_ERROR_PATCH_FILE;
1627 char sourcefile[MAXPATHLEN];
1628 if (!WideCharToMultiByte(CP_UTF8, 0, mPatchFile, -1, sourcefile, MAXPATHLEN,
1629 nullptr, nullptr))
1631 LOG(("error converting wchar to utf8: %d", GetLastError()));
1632 return STRING_CONVERSION_ERROR;
1635 int rv = mArchiveReader.ExtractFileToStream(sourcefile, mPatchStream);
1636 #else
1637 int rv = mArchiveReader.ExtractFileToStream(mPatchFile, mPatchStream);
1638 #endif
1640 return rv;
1644 PatchFile::Execute()
1646 LOG(("EXECUTE PATCH " LOG_S, mFileRelPath.get()));
1648 fseek(mPatchStream, 0, SEEK_SET);
1650 int rv = MBS_ReadHeader(mPatchStream, &header);
1651 if (rv)
1652 return rv;
1654 FILE *origfile = nullptr;
1655 #ifdef _WIN32
1656 if (NS_tstrcmp(mFileRelPath.get(), gCallbackRelPath) == 0)
1658 // Read from the copy of the callback when patching since the callback can't
1659 // be opened for reading to prevent the application from being launched.
1660 origfile = NS_tfopen(gCallbackBackupPath, NS_T("rb"));
1662 else
1664 origfile = NS_tfopen(mFile.get(), NS_T("rb"));
1666 #else
1667 origfile = NS_tfopen(mFile.get(), NS_T("rb"));
1668 #endif
1670 if (!origfile)
1672 LOG(("unable to open destination file: " LOG_S ", err: %d",
1673 mFileRelPath.get(), errno));
1674 return READ_ERROR;
1677 rv = LoadSourceFile(origfile);
1678 fclose(origfile);
1679 if (rv)
1681 LOG(("LoadSourceFile failed"));
1682 return rv;
1685 // Rename the destination file if it exists before proceeding so it can be
1686 // used to restore the file to its original state if there is an error.
1687 struct NS_tstat_t ss;
1688 rv = NS_tstat(mFile.get(), &ss);
1689 if (rv)
1691 LOG(("failed to read file status info: " LOG_S ", err: %d",
1692 mFileRelPath.get(), errno));
1693 return READ_ERROR;
1696 rv = backup_create(mFile.get());
1697 if (rv)
1698 return rv;
1700 #if defined(HAVE_POSIX_FALLOCATE)
1701 AutoFile ofile(ensure_open(mFile.get(), NS_T("wb+"), ss.st_mode));
1702 posix_fallocate(fileno((FILE *)ofile), 0, header.dlen);
1703 #elif defined(_WIN32)
1704 bool shouldTruncate = true;
1705 // Creating the file, setting the size, and then closing the file handle
1706 // lessens fragmentation more than any other method tested. Other methods that
1707 // have been tested are:
1708 // 1. _chsize / _chsize_s reduced fragmentation though not completely.
1709 // 2. _get_osfhandle and then setting the size reduced fragmentation though
1710 // not completely. There are also reports of _get_osfhandle failing on
1711 // mingw.
1712 HANDLE hfile = CreateFileW(mFile.get(),
1713 GENERIC_WRITE,
1715 nullptr,
1716 CREATE_ALWAYS,
1717 FILE_ATTRIBUTE_NORMAL,
1718 nullptr);
1720 if (hfile != INVALID_HANDLE_VALUE)
1722 if (SetFilePointer(hfile, header.dlen,
1723 nullptr, FILE_BEGIN) != INVALID_SET_FILE_POINTER &&
1724 SetEndOfFile(hfile) != 0)
1726 shouldTruncate = false;
1728 CloseHandle(hfile);
1731 AutoFile ofile(ensure_open(mFile.get(), shouldTruncate ? NS_T("wb+") : NS_T("rb+"),
1732 ss.st_mode));
1733 #elif defined(MACOSX)
1734 AutoFile ofile(ensure_open(mFile.get(), NS_T("wb+"), ss.st_mode));
1735 // Modified code from FileUtils.cpp
1736 fstore_t store = {F_ALLOCATECONTIG, F_PEOFPOSMODE, 0, header.dlen};
1737 // Try to get a continuous chunk of disk space
1738 rv = fcntl(fileno((FILE *)ofile), F_PREALLOCATE, &store);
1739 if (rv == -1)
1741 // OK, perhaps we are too fragmented, allocate non-continuous
1742 store.fst_flags = F_ALLOCATEALL;
1743 rv = fcntl(fileno((FILE *)ofile), F_PREALLOCATE, &store);
1746 if (rv != -1)
1748 ftruncate(fileno((FILE *)ofile), header.dlen);
1750 #else
1751 AutoFile ofile(ensure_open(mFile.get(), NS_T("wb+"), ss.st_mode));
1752 #endif
1754 if (ofile == nullptr)
1756 LOG(("unable to create new file: " LOG_S ", err: %d", mFileRelPath.get(),
1757 errno));
1758 return WRITE_ERROR_OPEN_PATCH_FILE;
1761 #ifdef _WIN32
1762 if (!shouldTruncate)
1764 fseek(ofile, 0, SEEK_SET);
1766 #endif
1768 rv = MBS_ApplyPatch(&header, mPatchStream, buf, ofile);
1770 // Go ahead and do a bit of cleanup now to minimize runtime overhead.
1771 // Make sure mPatchStream gets unlocked on Windows; the system will do that,
1772 // but not until some indeterminate future time, and we want determinism.
1773 #ifdef _WIN32
1774 UnlockFile((HANDLE)_get_osfhandle(fileno(mPatchStream)), (DWORD)0, (DWORD)0, (DWORD)-1, (DWORD)-1);
1775 #endif
1776 // Set mPatchStream to nullptr to make AutoFile close the file,
1777 // so it can be deleted on Windows.
1778 mPatchStream = nullptr;
1779 NS_tremove(spath);
1780 spath[0] = NS_T('\0');
1781 free(buf);
1782 buf = nullptr;
1783 return rv;
1786 void
1787 PatchFile::Finish(int status)
1789 LOG(("FINISH PATCH " LOG_S, mFileRelPath.get()));
1791 backup_finish(mFile.get(), mFileRelPath.get(), status);
1794 class AddIfFile : public AddFile
1796 public:
1797 AddIfFile(ArchiveReader& archiveReader);
1799 virtual int Parse(NS_tchar *line);
1800 virtual int Prepare();
1801 virtual int Execute();
1802 virtual void Finish(int status);
1804 protected:
1805 std::unique_ptr<NS_tchar[]> mTestFile;
1808 AddIfFile::AddIfFile(ArchiveReader& archiveReader):
1809 AddFile(archiveReader)
1814 AddIfFile::Parse(NS_tchar *line)
1816 // format "<testfile>" "<newfile>"
1818 mTestFile.reset(new_absolute_path(get_valid_path(&line)));
1819 if (!mTestFile)
1820 return PARSE_ERROR;
1822 // consume whitespace between args
1823 NS_tchar *q = mstrtok(kQuote, &line);
1824 if (!q)
1825 return PARSE_ERROR;
1827 return AddFile::Parse(line);
1831 AddIfFile::Prepare()
1833 // If the test file does not exist, then skip this action.
1834 if (NS_taccess(mTestFile.get(), F_OK))
1836 mTestFile = nullptr;
1837 return OK;
1840 return AddFile::Prepare();
1844 AddIfFile::Execute()
1846 if (!mTestFile)
1847 return OK;
1849 return AddFile::Execute();
1852 void
1853 AddIfFile::Finish(int status)
1855 if (!mTestFile)
1856 return;
1858 AddFile::Finish(status);
1861 class AddIfNotFile : public AddFile
1863 public:
1864 AddIfNotFile(ArchiveReader& archiveReader);
1866 virtual int Parse(NS_tchar *line);
1867 virtual int Prepare();
1868 virtual int Execute();
1869 virtual void Finish(int status);
1871 protected:
1872 std::unique_ptr<NS_tchar[]> mTestFile;
1875 AddIfNotFile::AddIfNotFile(ArchiveReader& archiveReader):
1876 AddFile(archiveReader)
1881 AddIfNotFile::Parse(NS_tchar *line)
1883 // format "<testfile>" "<newfile>"
1885 mTestFile.reset(new_absolute_path(get_valid_path(&line)));
1886 if (!mTestFile)
1887 return PARSE_ERROR;
1889 // consume whitespace between args
1890 NS_tchar *q = mstrtok(kQuote, &line);
1891 if (!q)
1892 return PARSE_ERROR;
1894 return AddFile::Parse(line);
1898 AddIfNotFile::Prepare()
1900 // If the test file exists, then skip this action.
1901 if (!NS_taccess(mTestFile.get(), F_OK))
1903 mTestFile = NULL;
1904 return OK;
1907 return AddFile::Prepare();
1911 AddIfNotFile::Execute()
1913 if (!mTestFile)
1914 return OK;
1916 return AddFile::Execute();
1919 void
1920 AddIfNotFile::Finish(int status)
1922 if (!mTestFile)
1923 return;
1925 AddFile::Finish(status);
1928 class PatchIfFile : public PatchFile
1930 public:
1931 PatchIfFile(ArchiveReader& archiveReader);
1933 virtual int Parse(NS_tchar *line);
1934 virtual int Prepare(); // should check for patch file and for checksum here
1935 virtual int Execute();
1936 virtual void Finish(int status);
1938 private:
1939 std::unique_ptr<NS_tchar[]> mTestFile;
1942 PatchIfFile::PatchIfFile(ArchiveReader& archiveReader):
1943 PatchFile(archiveReader)
1948 PatchIfFile::Parse(NS_tchar *line)
1950 // format "<testfile>" "<patchfile>" "<filetopatch>"
1952 mTestFile.reset(new_absolute_path(get_valid_path(&line)));
1953 if (!mTestFile)
1954 return PARSE_ERROR;
1956 // consume whitespace between args
1957 NS_tchar *q = mstrtok(kQuote, &line);
1958 if (!q)
1959 return PARSE_ERROR;
1961 return PatchFile::Parse(line);
1965 PatchIfFile::Prepare()
1967 // If the test file does not exist, then skip this action.
1968 if (NS_taccess(mTestFile.get(), F_OK))
1970 mTestFile = nullptr;
1971 return OK;
1974 return PatchFile::Prepare();
1978 PatchIfFile::Execute()
1980 if (!mTestFile)
1981 return OK;
1983 return PatchFile::Execute();
1986 void
1987 PatchIfFile::Finish(int status)
1989 if (!mTestFile)
1990 return;
1992 PatchFile::Finish(status);
1995 //-----------------------------------------------------------------------------
1997 #ifdef _WIN32
2000 * Launch the post update application (helper.exe). It takes in the path of the
2001 * callback application to calculate the path of helper.exe. For service updates
2002 * this is called from both the system account and the current user account.
2004 * @param installationDir The path to the callback application binary.
2005 * @param updateInfoDir The directory where update info is stored.
2006 * @return true if there was no error starting the process.
2008 bool
2009 LaunchWinPostProcess(const WCHAR *installationDir,
2010 const WCHAR *updateInfoDir)
2012 WCHAR workingDirectory[MAX_PATH + 1] = { L'\0' };
2013 wcsncpy(workingDirectory, installationDir, MAX_PATH);
2015 // TODO: moggi: needs adaptation for LibreOffice
2016 // Most likely we don't have the helper method yet. Check if we really need it.
2018 // Launch helper.exe to perform post processing (e.g. registry and log file
2019 // modifications) for the update.
2020 WCHAR inifile[MAX_PATH + 1] = { L'\0' };
2021 wcsncpy(inifile, installationDir, MAX_PATH);
2022 if (!PathAppendSafe(inifile, L"updater.ini"))
2024 return false;
2027 WCHAR exefile[MAX_PATH + 1];
2028 WCHAR exearg[MAX_PATH + 1];
2029 WCHAR exeasync[10];
2030 bool async = true;
2031 if (!GetPrivateProfileStringW(L"PostUpdateWin", L"ExeRelPath", nullptr,
2032 exefile, MAX_PATH + 1, inifile))
2034 return false;
2037 if (!GetPrivateProfileStringW(L"PostUpdateWin", L"ExeArg", nullptr, exearg,
2038 MAX_PATH + 1, inifile))
2040 return false;
2043 if (!GetPrivateProfileStringW(L"PostUpdateWin", L"ExeAsync", L"TRUE",
2044 exeasync,
2045 sizeof(exeasync)/sizeof(exeasync[0]),
2046 inifile))
2048 return false;
2051 // Verify that exeFile doesn't contain relative paths
2052 if (wcsstr(exefile, L"..") != nullptr)
2054 return false;
2057 WCHAR exefullpath[MAX_PATH + 1] = { L'\0' };
2058 wcsncpy(exefullpath, installationDir, MAX_PATH);
2059 if (!PathAppendSafe(exefullpath, exefile))
2061 return false;
2064 #if !defined(TEST_UPDATER) && defined(MAINTENANCE_SERVICE)
2065 if (sUsingService &&
2066 !DoesBinaryMatchAllowedCertificates(installationDir, exefullpath))
2068 return false;
2070 #endif
2072 WCHAR dlogFile[MAX_PATH + 1];
2073 if (!PathGetSiblingFilePath(dlogFile, exefullpath, L"uninstall.update"))
2075 return false;
2078 WCHAR slogFile[MAX_PATH + 1] = { L'\0' };
2079 wcsncpy(slogFile, updateInfoDir, MAX_PATH);
2080 if (!PathAppendSafe(slogFile, L"update.log"))
2082 return false;
2085 WCHAR dummyArg[14] = { L'\0' };
2086 wcsncpy(dummyArg, L"argv0ignored ", sizeof(dummyArg) / sizeof(dummyArg[0]) - 1);
2088 size_t len = wcslen(exearg) + wcslen(dummyArg);
2089 WCHAR *cmdline = (WCHAR *) malloc((len + 1) * sizeof(WCHAR));
2090 if (!cmdline)
2092 return false;
2095 wcsncpy(cmdline, dummyArg, len);
2096 wcscat(cmdline, exearg);
2098 if (sUsingService ||
2099 !_wcsnicmp(exeasync, L"false", 6) ||
2100 !_wcsnicmp(exeasync, L"0", 2))
2102 async = false;
2105 // We want to launch the post update helper app to update the Windows
2106 // registry even if there is a failure with removing the uninstall.update
2107 // file or copying the update.log file.
2108 CopyFileW(slogFile, dlogFile, false);
2110 STARTUPINFOW si = {sizeof(si), 0};
2111 si.lpDesktop = L"";
2112 PROCESS_INFORMATION pi = {0};
2114 bool ok = CreateProcessW(exefullpath,
2115 cmdline,
2116 nullptr, // no special security attributes
2117 nullptr, // no special thread attributes
2118 false, // don't inherit filehandles
2119 0, // No special process creation flags
2120 nullptr, // inherit my environment
2121 workingDirectory,
2122 &si,
2123 &pi);
2124 free(cmdline);
2125 if (ok)
2127 if (!async)
2129 WaitForSingleObject(pi.hProcess, INFINITE);
2131 CloseHandle(pi.hProcess);
2132 CloseHandle(pi.hThread);
2134 return ok;
2137 #endif
2139 static void
2140 LaunchCallbackApp(const NS_tchar *workingDir,
2141 int argc,
2142 NS_tchar **argv,
2143 bool usingService)
2145 putenv(const_cast<char*>("NO_EM_RESTART="));
2146 putenv(const_cast<char*>("MOZ_LAUNCHED_CHILD=1"));
2148 // Run from the specified working directory (see bug 312360). This is not
2149 // necessary on Windows CE since the application that launches the updater
2150 // passes the working directory as an --environ: command line argument.
2151 if (NS_tchdir(workingDir) != 0)
2153 LOG(("Warning: chdir failed"));
2156 #if defined(USE_EXECV)
2157 (void) argc;
2158 (void) usingService; // avoid warnings
2159 execv(argv[0], argv);
2160 #elif defined(MACOSX)
2161 LaunchChild(argc, (const char**)argv);
2162 #elif defined(_WIN32)
2163 // Do not allow the callback to run when running an update through the
2164 // service as session 0. The unelevated updater.exe will do the launching.
2165 if (!usingService)
2167 WinLaunchChild(argv[0], argc, argv, nullptr);
2169 #else
2170 # warning "Need implementation of LaunchCallbackApp"
2171 #endif
2174 static bool
2175 WriteStatusFile(const char* aStatus)
2177 NS_tchar filename[MAXPATHLEN] = {NS_T('\0')};
2178 #if defined(_WIN32)
2179 // The temp file is not removed on failure since there is client code that
2180 // will remove it.
2181 GetTempFileNameW(gPatchDirPath, L"sta", 0, filename);
2182 #else
2183 NS_tsnprintf(filename, sizeof(filename)/sizeof(filename[0]),
2184 NS_T("%s/update.status"), gPatchDirPath);
2185 #endif
2187 // Make sure that the directory for the update status file exists
2188 if (ensure_parent_dir(filename))
2189 return false;
2191 // This is scoped to make the AutoFile close the file so it is possible to
2192 // move the temp file to the update.status file on Windows.
2194 AutoFile file(NS_tfopen(filename, NS_T("wb+")));
2195 if (file == nullptr)
2197 return false;
2200 if (fwrite(aStatus, strlen(aStatus), 1, file) != 1)
2202 return false;
2206 #if defined(_WIN32)
2207 NS_tchar dstfilename[MAXPATHLEN] = {NS_T('\0')};
2208 NS_tsnprintf(dstfilename, sizeof(dstfilename)/sizeof(dstfilename[0]),
2209 NS_T("%s\\update.status"), gPatchDirPath);
2210 if (MoveFileExW(filename, dstfilename, MOVEFILE_REPLACE_EXISTING) == 0)
2212 return false;
2214 #endif
2216 return true;
2219 static void
2220 WriteStatusFile(int status)
2222 const char *text;
2224 char buf[32];
2225 if (status == OK)
2227 if (sStagedUpdate)
2229 text = "applied\n";
2231 else
2233 text = "succeeded\n";
2236 else
2238 snprintf(buf, sizeof(buf)/sizeof(buf[0]), "failed: %d\n", status);
2239 text = buf;
2242 WriteStatusFile(text);
2245 #ifdef MAINTENANCE_SERVICE
2247 * Read the update.status file and sets isPendingService to true if
2248 * the status is set to pending-service.
2250 * @param isPendingService Out parameter for specifying if the status
2251 * is set to pending-service or not.
2252 * @return true if the information was retrieved and it is pending
2253 * or pending-service.
2255 static bool
2256 IsUpdateStatusPendingService()
2258 NS_tchar filename[MAXPATHLEN];
2259 NS_tsnprintf(filename, sizeof(filename)/sizeof(filename[0]),
2260 NS_T("%s/update.status"), gPatchDirPath);
2262 AutoFile file(NS_tfopen(filename, NS_T("rb")));
2263 if (file == nullptr)
2264 return false;
2266 char buf[32] = { 0 };
2267 fread(buf, sizeof(buf), 1, file);
2269 const char kPendingService[] = "pending-service";
2270 const char kAppliedService[] = "applied-service";
2272 return (strncmp(buf, kPendingService,
2273 sizeof(kPendingService) - 1) == 0) ||
2274 (strncmp(buf, kAppliedService,
2275 sizeof(kAppliedService) - 1) == 0);
2277 #endif
2279 #ifdef _WIN32
2281 * Read the update.status file and sets isSuccess to true if
2282 * the status is set to succeeded.
2284 * @param isSucceeded Out parameter for specifying if the status
2285 * is set to succeeded or not.
2286 * @return true if the information was retrieved and it is succeeded.
2288 static bool
2289 IsUpdateStatusSucceeded(bool &isSucceeded)
2291 isSucceeded = false;
2292 NS_tchar filename[MAXPATHLEN];
2293 NS_tsnprintf(filename, sizeof(filename)/sizeof(filename[0]),
2294 NS_T("%s/update.status"), gPatchDirPath);
2296 AutoFile file(NS_tfopen(filename, NS_T("rb")));
2297 if (file == nullptr)
2298 return false;
2300 char buf[32] = { 0 };
2301 fread(buf, sizeof(buf), 1, file);
2303 const char kSucceeded[] = "succeeded";
2304 isSucceeded = strncmp(buf, kSucceeded,
2305 sizeof(kSucceeded) - 1) == 0;
2306 return true;
2308 #endif
2311 * Copy the entire contents of the application installation directory to the
2312 * destination directory for the update process.
2314 * @return 0 if successful, an error code otherwise.
2316 static int
2317 CopyInstallDirToDestDir()
2319 // These files should not be copied over to the updated app
2320 #ifdef _WIN32
2321 #define SKIPLIST_COUNT 4
2322 #elif defined(MACOSX)
2323 #define SKIPLIST_COUNT 1
2324 #else
2325 #define SKIPLIST_COUNT 3
2326 #endif
2327 copy_recursive_skiplist<SKIPLIST_COUNT> skiplist;
2329 std::unique_ptr<NS_tchar[]> pUserProfile(new NS_tchar[MAXPATHLEN]);
2330 NS_tstrcpy(pUserProfile.get(), gPatchDirPath);
2331 NS_tchar *slash = (NS_tchar *) NS_tstrrchr(pUserProfile.get(), NS_T('/'));
2332 if (slash)
2333 *slash = NS_T('\0');
2335 LOG(("ignore user profile directory during copy: " LOG_S, pUserProfile.get()));
2337 skiplist.append(0, pUserProfile.get());
2338 #ifndef MACOSX
2339 skiplist.append(1, gInstallDirPath, NS_T("updated"));
2340 skiplist.append(2, gInstallDirPath, NS_T("updates/0"));
2341 #ifdef _WIN32
2342 skiplist.append(4, gInstallDirPath, NS_T("updated.update_in_progress.lock"));
2343 #endif
2344 #endif
2346 return ensure_copy_recursive(gInstallDirPath, gWorkingDirPath, skiplist);
2350 * Replace the application installation directory with the destination
2351 * directory in order to finish a staged update task
2353 * @return 0 if successful, an error code otherwise.
2355 static int
2356 ProcessReplaceRequest()
2358 // TODO: moggi: handle the user profile in the installation dir also
2359 // during the replacement request
2360 // The replacement algorithm is like this:
2361 // 1. Move destDir to tmpDir. In case of failure, abort.
2362 // 2. Move newDir to destDir. In case of failure, revert step 1 and abort.
2363 // 3. Delete tmpDir (or defer it to the next reboot).
2365 #ifdef MACOSX
2366 NS_tchar destDir[MAXPATHLEN];
2367 NS_tsnprintf(destDir, sizeof(destDir)/sizeof(destDir[0]),
2368 NS_T("%s/Contents"), gInstallDirPath);
2369 #elif defined(_WIN32)
2370 // Windows preserves the case of the file/directory names. We use the
2371 // GetLongPathName API in order to get the correct case for the directory
2372 // name, so that if the user has used a different case when launching the
2373 // application, the installation directory's name does not change.
2374 NS_tchar destDir[MAXPATHLEN];
2375 if (!GetLongPathNameW(gInstallDirPath, destDir,
2376 sizeof(destDir)/sizeof(destDir[0])))
2378 return NO_INSTALLDIR_ERROR;
2380 #else
2381 NS_tchar* destDir = gInstallDirPath;
2382 #endif
2384 NS_tchar tmpDir[MAXPATHLEN];
2385 NS_tsnprintf(tmpDir, sizeof(tmpDir)/sizeof(tmpDir[0]),
2386 NS_T("%s.bak"), destDir);
2388 // First try to remove the possibly existing temp directory, because if this
2389 // directory exists, we will fail to rename destDir.
2390 // No need to error check here because if this fails, we will fail in the
2391 // next step anyways.
2392 ensure_remove_recursive(tmpDir);
2394 LOG(("Begin moving destDir (" LOG_S ") to tmpDir (" LOG_S ")",
2395 destDir, tmpDir));
2396 LogFlush();
2397 int rv = rename_file(destDir, tmpDir, true);
2398 #ifdef _WIN32
2399 // On Windows, if Firefox is launched using the shortcut, it will hold a handle
2400 // to its installation directory open, which might not get released in time.
2401 // Therefore we wait a little bit here to see if the handle is released.
2402 // If it's not released, we just fail to perform the replace request.
2403 const int max_retries = 10;
2404 int retries = 0;
2405 while (rv == WRITE_ERROR && (retries++ < max_retries))
2407 LOG(("PerformReplaceRequest: destDir rename attempt %d failed. " \
2408 "File: " LOG_S ". Last error: %d, err: %d", retries,
2409 destDir, GetLastError(), rv));
2411 Sleep(100);
2413 rv = rename_file(destDir, tmpDir, true);
2415 #endif
2416 if (rv)
2418 // The status file will have 'pending' written to it so there is no value in
2419 // returning an error specific for this failure.
2420 LOG(("Moving destDir to tmpDir failed, err: %d", rv));
2421 return rv;
2424 NS_tchar newDir[MAXPATHLEN];
2425 if (is_userprofile_in_instdir())
2427 LOG(("user profile in instdir"));
2428 NS_tstrcpy(newDir, tmpDir);
2429 NS_tstrcat(newDir, gWorkingDirPath + NS_tstrlen(gInstallDirPath));
2430 LOG((LOG_S, newDir));
2432 else
2434 NS_tsnprintf(newDir, sizeof(newDir)/sizeof(newDir[0]),
2435 NS_T("%s"),
2436 gWorkingDirPath);
2439 LOG(("Begin moving newDir (" LOG_S ") to destDir (" LOG_S ")",
2440 newDir, destDir));
2441 rv = rename_file(newDir, destDir, true);
2442 #ifdef MACOSX
2443 if (rv)
2445 LOG(("Moving failed. Begin copying newDir (" LOG_S ") to destDir (" LOG_S ")",
2446 newDir, destDir));
2447 copy_recursive_skiplist<0> skiplist;
2448 rv = ensure_copy_recursive(newDir, destDir, skiplist);
2450 #endif
2451 if (rv)
2453 LOG(("Moving newDir to destDir failed, err: %d", rv));
2454 LOG(("Now, try to move tmpDir back to destDir"));
2455 ensure_remove_recursive(destDir);
2456 int rv2 = rename_file(tmpDir, destDir, true);
2457 if (rv2)
2459 LOG(("Moving tmpDir back to destDir failed, err: %d", rv2));
2461 // The status file will be have 'pending' written to it so there is no value
2462 // in returning an error specific for this failure.
2463 return rv;
2466 if (is_userprofile_in_instdir())
2468 // 1.) calculate path of the user profile in the backup directory
2469 // 2.) move the user profile from the backup to the install directory
2470 NS_tchar backup_user_profile[MAXPATHLEN];
2471 NS_tchar userprofile[MAXPATHLEN];
2473 NS_tstrcpy(userprofile, gPatchDirPath);
2474 NS_tchar* slash = (NS_tchar *) NS_tstrrchr(userprofile, NS_T('/'));
2475 if (slash)
2476 *slash = NS_T('\0');
2477 NS_tstrcpy(backup_user_profile, tmpDir);
2478 size_t installdir_len = NS_tstrlen(destDir);
2480 NS_tstrcat(backup_user_profile, userprofile + installdir_len);
2481 LOG(("copy user profile back from " LOG_S " to " LOG_S, backup_user_profile, userprofile));
2482 int rv2 = rename_file(backup_user_profile, userprofile);
2483 if (rv2)
2485 LOG(("failed to copy user profile back"));
2487 if (slash)
2488 *slash = NS_T('/');
2491 #if !defined(_WIN32) && !defined(MACOSX)
2492 // Platforms that have their updates directory in the installation directory
2493 // need to have the last-update.log and backup-update.log files moved from the
2494 // old installation directory to the new installation directory.
2495 NS_tchar tmpLog[MAXPATHLEN];
2496 int nWrittenBytes = NS_tsnprintf(tmpLog, sizeof(tmpLog)/sizeof(tmpLog[0]),
2497 NS_T("%s/updates/last-update.log"), tmpDir);
2498 (void) nWrittenBytes;
2499 if (!NS_taccess(tmpLog, F_OK))
2501 NS_tchar destLog[MAXPATHLEN];
2502 NS_tsnprintf(destLog, sizeof(destLog)/sizeof(destLog[0]),
2503 NS_T("%s/updates/last-update.log"), destDir);
2504 NS_tremove(destLog);
2505 NS_trename(tmpLog, destLog);
2507 #endif
2509 LOG(("Now, remove the tmpDir"));
2510 rv = ensure_remove_recursive(tmpDir, true);
2511 if (rv)
2513 LOG(("Removing tmpDir failed, err: %d", rv));
2514 #ifdef _WIN32
2515 NS_tchar deleteDir[MAXPATHLEN];
2516 NS_tsnprintf(deleteDir, sizeof(deleteDir)/sizeof(deleteDir[0]),
2517 NS_T("%s\\%s"), destDir, DELETE_DIR);
2518 // Attempt to remove the tobedeleted directory and then recreate it if it
2519 // was successfully removed.
2520 _wrmdir(deleteDir);
2521 if (NS_taccess(deleteDir, F_OK))
2523 NS_tmkdir(deleteDir, 0755);
2525 remove_recursive_on_reboot(tmpDir, deleteDir);
2526 #endif
2529 #ifdef MACOSX
2530 // On macOS, we need to remove the staging directory after its Contents
2531 // directory has been moved.
2532 NS_tchar updatedAppDir[MAXPATHLEN];
2533 NS_tsnprintf(updatedAppDir, sizeof(updatedAppDir)/sizeof(updatedAppDir[0]),
2534 NS_T("%s/Updated.app"), gPatchDirPath);
2535 ensure_remove_recursive(updatedAppDir);
2536 #endif
2538 gSucceeded = true;
2540 return 0;
2543 #ifdef _WIN32
2544 static void
2545 WaitForServiceFinishThread(void* /*param*/)
2547 // We wait at most 10 minutes, we already waited 5 seconds previously
2548 // before deciding to show this UI.
2549 WaitForServiceStop(SVC_NAME, 595);
2550 QuitProgressUI();
2552 #endif
2554 #ifdef VERIFY_MAR_SIGNATURE
2556 * This function reads in the ACCEPTED_MAR_CHANNEL_IDS from update-settings.ini
2558 * @param path The path to the ini file that is to be read
2559 * @param results A pointer to the location to store the read strings
2560 * @return OK on success
2562 static int
2563 ReadMARChannelIDs(const NS_tchar *path, MARChannelStringTable *results)
2565 // TODO: moggi: needs adaptation for LibreOffice
2566 // Check where this function gets its parameters from
2567 const unsigned int kNumStrings = 1;
2568 const char *kUpdaterKeys = "ACCEPTED_MAR_CHANNEL_IDS\0";
2569 char updater_strings[kNumStrings][MAX_TEXT_LEN];
2571 int result = ReadStrings(path, kUpdaterKeys, kNumStrings,
2572 updater_strings, "Settings");
2574 strncpy(results->MARChannelID, updater_strings[0], MAX_TEXT_LEN - 1);
2575 results->MARChannelID[MAX_TEXT_LEN - 1] = 0;
2577 return result;
2579 #endif
2581 static int
2582 GetUpdateFileNames(std::vector<tstring>& fileNames)
2584 NS_tchar fileName[MAXPATHLEN];
2585 NS_tsnprintf(fileName, MAXPATHLEN,
2586 NS_T("%s/update.mar"), gPatchDirPath);
2587 fileNames.push_back(fileName);
2589 // add the language packs
2590 NS_tDIR* dir = NS_topendir(gPatchDirPath);
2591 if (!dir)
2593 LOG(("Could not open directory " LOG_S, gPatchDirPath));
2594 return READ_ERROR;
2597 NS_tdirent* entry;
2598 while ((entry = NS_treaddir(dir)) != nullptr)
2600 if (NS_tstrcmp(entry->d_name, NS_T(".")) &&
2601 NS_tstrcmp(entry->d_name, NS_T("..")) &&
2602 NS_tstrcmp(entry->d_name, NS_T("update.mar")))
2604 if (NS_tstrncmp(entry->d_name, NS_T("update"), 6) == 0)
2606 NS_tchar *dot = NS_tstrrchr(entry->d_name, NS_T('.'));
2607 if (dot && !NS_tstrcmp(dot, NS_T(".mar")))
2609 NS_tchar updatePath[MAXPATHLEN];
2610 NS_tsnprintf(updatePath, sizeof(updatePath)/sizeof(updatePath[0]),
2611 NS_T("%s/%s"), gPatchDirPath, entry->d_name);
2613 LOG (("Found language update file: " LOG_S, updatePath));
2614 fileNames.push_back(updatePath);
2619 return OK;
2622 static int
2623 CheckSignature(ArchiveReader& archiveReader)
2625 #ifdef VERIFY_MAR_SIGNATURE
2626 #ifdef _WIN32
2627 HKEY baseKey = nullptr;
2628 wchar_t valueName[] = L"Image Path";
2629 wchar_t rasenh[] = L"rsaenh.dll";
2630 bool reset = false;
2631 if (RegOpenKeyExW(HKEY_LOCAL_MACHINE,
2632 L"SOFTWARE\\Microsoft\\Cryptography\\Defaults\\Provider\\Microsoft Enhanced Cryptographic Provider v1.0",
2633 0, KEY_READ | KEY_WRITE,
2634 &baseKey) == ERROR_SUCCESS)
2636 wchar_t path[MAX_PATH + 1];
2637 DWORD size = sizeof(path);
2638 DWORD type;
2639 if (RegQueryValueExW(baseKey, valueName, 0, &type,
2640 (LPBYTE)path, &size) == ERROR_SUCCESS)
2642 if (type == REG_SZ && wcscmp(path, rasenh) == 0)
2644 wchar_t rasenhFullPath[] = L"%SystemRoot%\\System32\\rsaenh.dll";
2645 if (RegSetValueExW(baseKey, valueName, 0, REG_SZ,
2646 (const BYTE*)rasenhFullPath,
2647 sizeof(rasenhFullPath)) == ERROR_SUCCESS)
2649 reset = true;
2654 #endif
2655 int rv = archiveReader.VerifySignature();
2656 #ifdef _WIN32
2657 if (baseKey)
2659 if (reset)
2661 RegSetValueExW(baseKey, valueName, 0, REG_SZ,
2662 (const BYTE*)rasenh,
2663 sizeof(rasenh));
2665 RegCloseKey(baseKey);
2667 #endif
2670 if (rv == OK)
2672 if (rv == OK)
2674 NS_tchar updateSettingsPath[MAX_TEXT_LEN];
2676 // TODO: moggi: needs adaptation for LibreOffice
2677 // These paths need to be adapted for us.
2678 int nWrittenBytes = NS_tsnprintf(updateSettingsPath,
2679 sizeof(updateSettingsPath) / sizeof(updateSettingsPath[0]),
2680 #ifdef MACOSX
2681 NS_T("%s/Contents/Resources/update-settings.ini"),
2682 #else
2683 NS_T("%s/update-settings.ini"),
2684 #endif
2685 gWorkingDirPath);
2686 (void) nWrittenBytes;
2687 MARChannelStringTable MARStrings;
2688 if (ReadMARChannelIDs(updateSettingsPath, &MARStrings) != OK)
2690 // If we can't read from update-settings.ini then we shouldn't impose
2691 // a MAR restriction. Some installations won't even include this file.
2692 MARStrings.MARChannelID[0] = '\0';
2695 rv = archiveReader.VerifyProductInformation(MARStrings.MARChannelID,
2696 LIBO_VERSION_DOTTED);
2699 #endif
2701 return rv;
2704 static void
2705 UpdateThreadFunc(void * /*param*/)
2707 // open ZIP archive and process...
2708 int rv = OK;
2709 if (sReplaceRequest)
2711 rv = ProcessReplaceRequest();
2713 else
2715 std::vector<tstring> fileNames;
2716 GetUpdateFileNames(fileNames);
2718 for (auto& fileName: fileNames)
2720 ArchiveReader archiveReader;
2721 rv = archiveReader.Open(fileName.c_str());
2722 if (rv != OK)
2724 LOG(("Could not open " LOG_S, fileName.c_str()));
2725 break;
2728 rv = CheckSignature(archiveReader);
2729 if (rv != OK)
2731 LOG(("Could not verify the signature of " LOG_S, fileName.c_str()));
2732 break;
2736 if (rv == OK && sStagedUpdate)
2738 rv = CopyInstallDirToDestDir();
2741 if (rv == OK)
2743 for (auto& fileName: fileNames)
2745 ArchiveReader archiveReader;
2746 archiveReader.Open(fileName.c_str());
2747 rv = DoUpdate(archiveReader);
2749 NS_tchar updatingDir[MAXPATHLEN];
2750 int nWrittenBytes = NS_tsnprintf(updatingDir, sizeof(updatingDir)/sizeof(updatingDir[0]),
2751 NS_T("%s/updating"), gWorkingDirPath);
2752 (void) nWrittenBytes;
2753 ensure_remove_recursive(updatingDir);
2757 if (rv && (sReplaceRequest || sStagedUpdate))
2759 #ifdef _WIN32
2760 // On Windows, the current working directory of the process should be changed
2761 // so that it's not locked.
2762 if (sStagedUpdate)
2764 NS_tchar sysDir[MAX_PATH + 1] = { L'\0' };
2765 if (GetSystemDirectoryW(sysDir, MAX_PATH + 1))
2767 NS_tchdir(sysDir);
2770 #endif
2771 ensure_remove_recursive(gWorkingDirPath);
2772 // When attempting to replace the application, we should fall back
2773 // to non-staged updates in case of a failure. We do this by
2774 // setting the status to pending, exiting the updater, and
2775 // launching the callback application. The callback application's
2776 // startup path will see the pending status, and will start the
2777 // updater application again in order to apply the update without
2778 // staging.
2779 if (sReplaceRequest)
2781 WriteStatusFile(sUsingService ? "pending-service" : "pending");
2783 else
2785 WriteStatusFile(rv);
2787 #ifdef TEST_UPDATER
2788 // Some tests need to use --test-process-updates again.
2789 putenv(const_cast<char*>("MOZ_TEST_PROCESS_UPDATES="));
2790 #endif
2792 else
2794 if (rv)
2796 LOG(("failed: %d", rv));
2798 else
2800 #ifdef MACOSX
2801 // If the update was successful we need to update the timestamp on the
2802 // top-level macOS bundle directory so that macOS's Launch Services
2803 // picks up any major changes when the bundle is updated.
2804 if (!sStagedUpdate && utimes(gInstallDirPath, nullptr) != 0)
2806 LOG(("Couldn't set access/modification time on application bundle."));
2808 #endif
2810 LOG(("succeeded"));
2812 WriteStatusFile(rv);
2815 LOG(("calling QuitProgressUI"));
2816 QuitProgressUI();
2819 #ifdef MACOSX
2820 static void
2821 ServeElevatedUpdateThreadFunc(void* param)
2823 UpdateServerThreadArgs* threadArgs = (UpdateServerThreadArgs*)param;
2824 gSucceeded = ServeElevatedUpdate(threadArgs->argc, threadArgs->argv);
2825 if (!gSucceeded)
2827 WriteStatusFile(ELEVATION_CANCELED);
2829 QuitProgressUI();
2832 void freeArguments(int argc, char** argv)
2834 for (int i = 0; i < argc; i++)
2836 free(argv[i]);
2838 free(argv);
2840 #endif
2842 int LaunchCallbackAndPostProcessApps(int argc, NS_tchar** argv,
2843 int callbackIndex
2844 #ifdef _WIN32
2845 , const WCHAR* elevatedLockFilePath
2846 , HANDLE updateLockFileHandle
2847 #elif defined(MACOSX)
2848 , bool isElevated
2849 #endif
2852 if (argc > callbackIndex)
2854 #if defined(_WIN32)
2855 if (gSucceeded)
2857 if (!LaunchWinPostProcess(gInstallDirPath, gPatchDirPath))
2859 fprintf(stderr, "The post update process was not launched");
2862 // The service update will only be executed if it is already installed.
2863 // For first time installs of the service, the install will happen from
2864 // the PostUpdate process. We do the service update process here
2865 // because it's possible we are updating with updater.exe without the
2866 // service if the service failed to apply the update. We want to update
2867 // the service to a newer version in that case. If we are not running
2868 // through the service, then USING_SERVICE will not exist.
2869 if (!sUsingService)
2871 StartServiceUpdate(gInstallDirPath);
2874 EXIT_WHEN_ELEVATED(elevatedLockFilePath, updateLockFileHandle, 0);
2875 #elif defined(MACOSX)
2876 if (!isElevated)
2878 if (gSucceeded)
2880 LaunchMacPostProcess(gInstallDirPath);
2882 #endif
2884 LaunchCallbackApp(argv[5],
2885 argc - callbackIndex,
2886 argv + callbackIndex,
2887 sUsingService);
2888 #ifdef XP_MACOSX
2889 } // if (!isElevated)
2890 #endif /* XP_MACOSX */
2892 return 0;
2895 int NS_main(int argc, NS_tchar **argv)
2897 // The callback is the remaining arguments starting at callbackIndex.
2898 // The argument specified by callbackIndex is the callback executable and the
2899 // argument prior to callbackIndex is the working directory.
2900 const int callbackIndex = 6;
2902 #ifdef MACOSX
2903 // TODO: moggi: needs adaptation for LibreOffice
2904 bool isElevated =
2905 strstr(argv[0], "/Library/PrivilegedHelperTools/org.mozilla.updater") != 0;
2906 if (isElevated)
2908 if (!ObtainUpdaterArguments(&argc, &argv))
2910 // Won't actually get here because ObtainUpdaterArguments will terminate
2911 // the current process on failure.
2912 return 1;
2915 #endif
2917 #if defined(VERIFY_MAR_SIGNATURE) && !defined(_WIN32) && !defined(MACOSX)
2918 // On Windows and Mac we rely on native APIs to do verifications so we don't
2919 // need to initialize NSS at all there.
2920 // Otherwise, minimize the amount of NSS we depend on by avoiding all the NSS
2921 // databases.
2922 if (!NSS_IsInitialized())
2924 if (NSS_NoDB_Init(NULL) != SECSuccess)
2926 PRErrorCode error = PR_GetError();
2927 fprintf(stderr, "Could not initialize NSS: %s (%d)",
2928 PR_ErrorToName(error), (int) error);
2929 _exit(1);
2932 #endif
2934 #ifdef MACOSX
2935 if (!isElevated)
2937 #endif
2938 InitProgressUI(&argc, &argv);
2939 #ifdef MACOSX
2941 #endif
2943 // To process an update the updater command line must at a minimum have the
2944 // directory path containing the updater.mar file to process as the first
2945 // argument, the install directory as the second argument, and the directory
2946 // to apply the update to as the third argument. When the updater is launched
2947 // by another process the PID of the parent process should be provided in the
2948 // optional fourth argument and the updater will wait on the parent process to
2949 // exit if the value is non-zero and the process is present. This is necessary
2950 // due to not being able to update files that are in use on Windows. The
2951 // optional fifth argument is the callback's working directory and the
2952 // optional sixth argument is the callback path. The callback is the
2953 // application to launch after updating and it will be launched when these
2954 // arguments are provided whether the update was successful or not. All
2955 // remaining arguments are optional and are passed to the callback when it is
2956 // launched.
2957 if (argc < 4)
2959 fprintf(stderr, "Usage: updater patch-dir install-dir apply-to-dir [wait-pid [callback-working-dir callback-path args...]]\n");
2960 #ifdef MACOSX
2961 if (isElevated)
2963 freeArguments(argc, argv);
2964 CleanupElevatedMacUpdate(true);
2966 #endif
2967 return 1;
2970 // The directory containing the update information.
2971 gPatchDirPath = argv[1];
2973 // The directory we're going to update to.
2974 // We copy this string because we need to remove trailing slashes. The C++
2975 // standard says that it's always safe to write to strings pointed to by argv
2976 // elements, but I don't necessarily believe it.
2977 NS_tstrncpy(gInstallDirPath, argv[2], MAXPATHLEN);
2978 gInstallDirPath[MAXPATHLEN - 1] = NS_T('\0');
2979 NS_tchar *slash = NS_tstrrchr(gInstallDirPath, NS_SLASH);
2980 if (slash && !slash[1])
2982 *slash = NS_T('\0');
2985 #ifdef _WIN32
2986 bool useService = false;
2987 bool testOnlyFallbackKeyExists = false;
2988 bool noServiceFallback = false;
2990 // We never want the service to be used unless we build with
2991 // the maintenance service.
2992 #ifdef MAINTENANCE_SERVICE
2993 useService = IsUpdateStatusPendingService();
2994 // Our tests run with a different apply directory for each test.
2995 // We use this registry key on our test slaves to store the
2996 // allowed name/issuers.
2997 testOnlyFallbackKeyExists = DoesFallbackKeyExist();
2998 #endif
3000 // Remove everything except close window from the context menu
3002 // TODO: moggi: needs adaptation for LibreOffice
3003 HKEY hkApp = nullptr;
3004 RegCreateKeyExW(HKEY_CURRENT_USER, L"Software\\Classes\\Applications",
3005 0, nullptr, REG_OPTION_NON_VOLATILE, KEY_SET_VALUE, nullptr,
3006 &hkApp, nullptr);
3007 RegCloseKey(hkApp);
3008 if (RegCreateKeyExW(HKEY_CURRENT_USER,
3009 L"Software\\Classes\\Applications\\updater.exe",
3010 0, nullptr, REG_OPTION_VOLATILE, KEY_SET_VALUE, nullptr,
3011 &hkApp, nullptr) == ERROR_SUCCESS)
3013 RegSetValueExW(hkApp, L"IsHostApp", 0, REG_NONE, 0, 0);
3014 RegSetValueExW(hkApp, L"NoOpenWith", 0, REG_NONE, 0, 0);
3015 RegSetValueExW(hkApp, L"NoStartPage", 0, REG_NONE, 0, 0);
3016 RegCloseKey(hkApp);
3019 #endif
3021 // If there is a PID specified and it is not '0' then wait for the process to exit.
3022 #ifdef _WIN32
3023 __int64 pid = 0;
3024 #else
3025 int pid = 0;
3026 #endif
3027 if (argc > 4)
3029 #ifdef _WIN32
3030 pid = _wtoi64(argv[4]);
3031 #else
3032 pid = atoi(argv[4]);
3033 #endif
3034 if (pid == -1)
3036 // This is a signal from the parent process that the updater should stage
3037 // the update.
3038 sStagedUpdate = true;
3040 else if (NS_tstrstr(argv[4], NS_T("/replace")))
3042 // We're processing a request to replace the application with a staged
3043 // update.
3044 sReplaceRequest = true;
3048 // The directory we're going to update to.
3049 // We copy this string because we need to remove trailing slashes. The C++
3050 // standard says that it's always safe to write to strings pointed to by argv
3051 // elements, but I don't necessarily believe it.
3052 NS_tstrncpy(gWorkingDirPath, argv[3], MAXPATHLEN);
3053 gWorkingDirPath[MAXPATHLEN - 1] = NS_T('\0');
3054 slash = NS_tstrrchr(gWorkingDirPath, NS_SLASH);
3055 if (slash && !slash[1])
3057 *slash = NS_T('\0');
3060 #ifdef MACOSX
3061 if (!isElevated && !IsRecursivelyWritable(argv[2]))
3063 // If the app directory isn't recursively writeable, an elevated update is
3064 // required.
3065 UpdateServerThreadArgs threadArgs;
3066 threadArgs.argc = argc;
3067 threadArgs.argv = const_cast<const NS_tchar**>(argv);
3069 Thread t1;
3070 if (t1.Run(ServeElevatedUpdateThreadFunc, &threadArgs) == 0)
3072 // Show an indeterminate progress bar while an elevated update is in
3073 // progress.
3074 ShowProgressUI(true);
3076 t1.Join();
3078 LaunchCallbackAndPostProcessApps(argc, argv, callbackIndex, false);
3079 return gSucceeded ? 0 : 1;
3081 #endif
3083 LogInit(gPatchDirPath, NS_T("update.log"));
3085 if (!WriteStatusFile("applying"))
3087 LOG(("failed setting status to 'applying'"));
3088 #ifdef MACOSX
3089 if (isElevated)
3091 freeArguments(argc, argv);
3092 CleanupElevatedMacUpdate(true);
3094 #endif
3095 return 1;
3098 if (sStagedUpdate)
3100 LOG(("Performing a staged update"));
3102 else if (sReplaceRequest)
3104 LOG(("Performing a replace request"));
3107 LOG(("PATCH DIRECTORY " LOG_S, gPatchDirPath));
3108 LOG(("INSTALLATION DIRECTORY " LOG_S, gInstallDirPath));
3109 LOG(("WORKING DIRECTORY " LOG_S, gWorkingDirPath));
3111 #ifdef _WIN32
3112 if (_wcsnicmp(gWorkingDirPath, gInstallDirPath, MAX_PATH) != 0)
3114 if (!sStagedUpdate && !sReplaceRequest)
3116 WriteStatusFile(INVALID_APPLYTO_DIR_ERROR);
3117 LOG(("Installation directory and working directory must be the same "
3118 "for non-staged updates. Exiting."));
3119 LogFinish();
3120 return 1;
3123 NS_tchar workingDirParent[MAX_PATH];
3124 NS_tsnprintf(workingDirParent,
3125 sizeof(workingDirParent) / sizeof(workingDirParent[0]),
3126 NS_T("%s"), gWorkingDirPath);
3127 if (!PathRemoveFileSpecW(workingDirParent))
3129 WriteStatusFile(REMOVE_FILE_SPEC_ERROR);
3130 LOG(("Error calling PathRemoveFileSpecW: %d", GetLastError()));
3131 LogFinish();
3132 return 1;
3135 if (_wcsnicmp(workingDirParent, gInstallDirPath, MAX_PATH) != 0)
3137 WriteStatusFile(INVALID_APPLYTO_DIR_STAGED_ERROR);
3138 LOG(("The apply-to directory must be the same as or "
3139 "a child of the installation directory! Exiting."));
3140 LogFinish();
3141 return 1;
3144 #endif
3147 #ifdef _WIN32
3148 if (pid > 0)
3150 HANDLE parent = OpenProcess(SYNCHRONIZE, false, (DWORD) pid);
3151 // May return nullptr if the parent process has already gone away.
3152 // Otherwise, wait for the parent process to exit before starting the
3153 // update.
3154 if (parent)
3156 DWORD waitTime = PARENT_WAIT;
3157 DWORD result = WaitForSingleObject(parent, waitTime);
3158 CloseHandle(parent);
3159 if (result != WAIT_OBJECT_0)
3160 return 1;
3163 #else
3164 if (pid > 0)
3165 waitpid(pid, nullptr, 0);
3166 #endif
3168 #if defined(_WIN32)
3169 #ifdef MAINTENANCE_SERVICE
3170 sUsingService = EnvHasValue("USING_SERVICE");
3171 putenv(const_cast<char*>("USING_SERVICE="));
3172 #endif
3173 // lastFallbackError keeps track of the last error for the service not being
3174 // used, in case of an error when fallback is not enabled we write the
3175 // error to the update.status file.
3176 // When fallback is disabled (MOZ_NO_SERVICE_FALLBACK does not exist) then
3177 // we will instead fallback to not using the service and display a UAC prompt.
3178 int lastFallbackError = FALLBACKKEY_UNKNOWN_ERROR;
3180 // Launch a second instance of the updater with the runas verb on Windows
3181 // when write access is denied to the installation directory.
3182 HANDLE updateLockFileHandle = INVALID_HANDLE_VALUE;
3183 NS_tchar elevatedLockFilePath[MAXPATHLEN] = {NS_T('\0')};
3184 if (!sUsingService &&
3185 (argc > callbackIndex || sStagedUpdate || sReplaceRequest))
3187 NS_tchar updateLockFilePath[MAXPATHLEN];
3188 if (sStagedUpdate)
3190 // When staging an update, the lock file is:
3191 // <install_dir>\updated.update_in_progress.lock
3192 NS_tsnprintf(updateLockFilePath,
3193 sizeof(updateLockFilePath)/sizeof(updateLockFilePath[0]),
3194 NS_T("%s/updated.update_in_progress.lock"), gInstallDirPath);
3196 else if (sReplaceRequest)
3198 // When processing a replace request, the lock file is:
3199 // <install_dir>\..\moz_update_in_progress.lock
3200 NS_tchar installDir[MAXPATHLEN];
3201 NS_tstrcpy(installDir, gInstallDirPath);
3202 NS_tchar *slash = (NS_tchar *) NS_tstrrchr(installDir, NS_SLASH);
3203 *slash = NS_T('\0');
3204 NS_tsnprintf(updateLockFilePath,
3205 sizeof(updateLockFilePath)/sizeof(updateLockFilePath[0]),
3206 NS_T("%s\\moz_update_in_progress.lock"), installDir);
3208 else
3210 // In the non-staging update case, the lock file is:
3211 // <install_dir>\<app_name>.exe.update_in_progress.lock
3212 NS_tsnprintf(updateLockFilePath,
3213 sizeof(updateLockFilePath)/sizeof(updateLockFilePath[0]),
3214 NS_T("%s.update_in_progress.lock"), argv[callbackIndex]);
3217 // The update_in_progress.lock file should only exist during an update. In
3218 // case it exists attempt to remove it and exit if that fails to prevent
3219 // simultaneous updates occurring.
3220 if (!_waccess(updateLockFilePath, F_OK) &&
3221 NS_tremove(updateLockFilePath) != 0)
3223 // Try to fall back to the old way of doing updates if a staged
3224 // update fails.
3225 if (sStagedUpdate || sReplaceRequest)
3227 // Note that this could fail, but if it does, there isn't too much we
3228 // can do in order to recover anyways.
3229 WriteStatusFile("pending");
3231 LOG(("Update already in progress! Exiting"));
3232 return 1;
3235 updateLockFileHandle = CreateFileW(updateLockFilePath,
3236 GENERIC_READ | GENERIC_WRITE,
3238 nullptr,
3239 OPEN_ALWAYS,
3240 FILE_FLAG_DELETE_ON_CLOSE,
3241 nullptr);
3243 NS_tsnprintf(elevatedLockFilePath,
3244 sizeof(elevatedLockFilePath)/sizeof(elevatedLockFilePath[0]),
3245 NS_T("%s/update_elevated.lock"), gPatchDirPath);
3247 // Even if a file has no sharing access, you can still get its attributes
3248 bool startedFromUnelevatedUpdater =
3249 GetFileAttributesW(elevatedLockFilePath) != INVALID_FILE_ATTRIBUTES;
3251 // If we're running from the service, then we were started with the same
3252 // token as the service so the permissions are already dropped. If we're
3253 // running from an elevated updater that was started from an unelevated
3254 // updater, then we drop the permissions here. We do not drop the
3255 // permissions on the originally called updater because we use its token
3256 // to start the callback application.
3257 if (startedFromUnelevatedUpdater)
3259 // Disable every privilege we don't need. Processes started using
3260 // CreateProcess will use the same token as this process.
3261 UACHelper::DisablePrivileges(nullptr);
3264 if (updateLockFileHandle == INVALID_HANDLE_VALUE ||
3265 (useService && testOnlyFallbackKeyExists && noServiceFallback))
3267 if (!_waccess(elevatedLockFilePath, F_OK) &&
3268 NS_tremove(elevatedLockFilePath) != 0)
3270 fprintf(stderr, "Unable to create elevated lock file! Exiting\n");
3271 return 1;
3274 HANDLE elevatedFileHandle;
3275 elevatedFileHandle = CreateFileW(elevatedLockFilePath,
3276 GENERIC_READ | GENERIC_WRITE,
3278 nullptr,
3279 OPEN_ALWAYS,
3280 FILE_FLAG_DELETE_ON_CLOSE,
3281 nullptr);
3283 if (elevatedFileHandle == INVALID_HANDLE_VALUE)
3285 LOG(("Unable to create elevated lock file! Exiting"));
3286 return 1;
3289 wchar_t *cmdLine = MakeCommandLine(argc - 1, argv + 1);
3290 if (!cmdLine)
3292 CloseHandle(elevatedFileHandle);
3293 return 1;
3296 // Make sure the path to the updater to use for the update is on local.
3297 // We do this check to make sure that file locking is available for
3298 // race condition security checks.
3299 if (useService)
3301 BOOL isLocal = FALSE;
3302 useService = IsLocalFile(argv[0], isLocal) && isLocal;
3305 // If we have unprompted elevation we should NOT use the service
3306 // for the update. Service updates happen with the SYSTEM account
3307 // which has more privs than we need to update with.
3308 // Windows 8 provides a user interface so users can configure this
3309 // behavior and it can be configured in the registry in all Windows
3310 // versions that support UAC.
3311 if (useService)
3313 BOOL unpromptedElevation;
3314 if (IsUnpromptedElevation(unpromptedElevation))
3316 useService = !unpromptedElevation;
3320 // Make sure the service registry entries for the installation path
3321 // are available. If not don't use the service.
3322 if (useService)
3324 WCHAR maintenanceServiceKey[MAX_PATH + 1];
3325 // TODO: moggi: needs adaptation for LibreOffice
3326 // Most likely the registry part is not correct yet
3327 if (CalculateRegistryPathFromFilePath(gInstallDirPath,
3328 maintenanceServiceKey))
3330 HKEY baseKey = nullptr;
3331 if (RegOpenKeyExW(HKEY_LOCAL_MACHINE,
3332 maintenanceServiceKey, 0,
3333 KEY_READ | KEY_WOW64_64KEY,
3334 &baseKey) == ERROR_SUCCESS)
3336 RegCloseKey(baseKey);
3338 else
3340 #ifdef TEST_UPDATER
3341 useService = testOnlyFallbackKeyExists;
3342 #endif
3343 if (!useService)
3345 lastFallbackError = FALLBACKKEY_NOKEY_ERROR;
3349 else
3351 useService = false;
3352 lastFallbackError = FALLBACKKEY_REGPATH_ERROR;
3356 // Originally we used to write "pending" to update.status before
3357 // launching the service command. This is no longer needed now
3358 // since the service command is launched from updater.exe. If anything
3359 // fails in between, we can fall back to using the normal update process
3360 // on our own.
3362 // If we still want to use the service try to launch the service
3363 // command for the update.
3364 if (useService)
3366 // If the update couldn't be started, then set useService to false so
3367 // we do the update the old way.
3368 DWORD ret = LaunchServiceSoftwareUpdateCommand(argc, (LPCWSTR *)argv);
3369 useService = (ret == ERROR_SUCCESS);
3370 // If the command was launched then wait for the service to be done.
3371 if (useService)
3373 bool showProgressUI = false;
3374 // Never show the progress UI when staging updates.
3375 if (!sStagedUpdate)
3377 // We need to call this separately instead of allowing ShowProgressUI
3378 // to initialize the strings because the service will move the
3379 // ini file out of the way when running updater.
3380 showProgressUI = !InitProgressUIStrings();
3383 // Wait for the service to stop for 5 seconds. If the service
3384 // has still not stopped then show an indeterminate progress bar.
3385 DWORD lastState = WaitForServiceStop(SVC_NAME, 5);
3386 if (lastState != SERVICE_STOPPED)
3388 std::thread waitThread(WaitForServiceFinishThread, nullptr);
3389 if (showProgressUI)
3391 ShowProgressUI(true, false);
3393 waitThread.join();
3396 lastState = WaitForServiceStop(SVC_NAME, 1);
3397 if (lastState != SERVICE_STOPPED)
3399 // If the service doesn't stop after 10 minutes there is
3400 // something seriously wrong.
3401 lastFallbackError = FALLBACKKEY_SERVICE_NO_STOP_ERROR;
3402 useService = false;
3405 else
3407 lastFallbackError = FALLBACKKEY_LAUNCH_ERROR;
3411 // If the service can't be used when staging and update, make sure that
3412 // the UAC prompt is not shown! In this case, just set the status to
3413 // pending and the update will be applied during the next startup.
3414 if (!useService && sStagedUpdate)
3416 if (updateLockFileHandle != INVALID_HANDLE_VALUE)
3418 CloseHandle(updateLockFileHandle);
3420 WriteStatusFile("pending");
3421 return 0;
3424 // If we started the service command, and it finished, check the
3425 // update.status file to make sure it succeeded, and if it did
3426 // we need to manually start the PostUpdate process from the
3427 // current user's session of this unelevated updater.exe the
3428 // current process is running as.
3429 // Note that we don't need to do this if we're just staging the update,
3430 // as the PostUpdate step runs when performing the replacing in that case.
3431 if (useService && !sStagedUpdate)
3433 bool updateStatusSucceeded = false;
3434 if (IsUpdateStatusSucceeded(updateStatusSucceeded) &&
3435 updateStatusSucceeded)
3437 if (!LaunchWinPostProcess(gInstallDirPath, gPatchDirPath))
3439 fprintf(stderr, "The post update process which runs as the user"
3440 " for service update could not be launched.");
3445 // If we didn't want to use the service at all, or if an update was
3446 // already happening, or launching the service command failed, then
3447 // launch the elevated updater.exe as we do without the service.
3448 // We don't launch the elevated updater in the case that we did have
3449 // write access all along because in that case the only reason we're
3450 // using the service is because we are testing.
3451 if (!useService && !noServiceFallback &&
3452 updateLockFileHandle == INVALID_HANDLE_VALUE)
3454 SHELLEXECUTEINFO sinfo;
3455 memset(&sinfo, 0, sizeof(SHELLEXECUTEINFO));
3456 sinfo.cbSize = sizeof(SHELLEXECUTEINFO);
3457 sinfo.fMask = SEE_MASK_FLAG_NO_UI |
3458 SEE_MASK_FLAG_DDEWAIT |
3459 SEE_MASK_NOCLOSEPROCESS;
3460 sinfo.hwnd = nullptr;
3461 sinfo.lpFile = argv[0];
3462 sinfo.lpParameters = cmdLine;
3463 sinfo.lpVerb = L"runas";
3464 sinfo.nShow = SW_SHOWNORMAL;
3466 bool result = ShellExecuteEx(&sinfo);
3467 free(cmdLine);
3469 if (result)
3471 WaitForSingleObject(sinfo.hProcess, INFINITE);
3472 CloseHandle(sinfo.hProcess);
3474 else
3476 WriteStatusFile(ELEVATION_CANCELED);
3480 if (argc > callbackIndex)
3482 LaunchCallbackApp(argv[5], argc - callbackIndex,
3483 argv + callbackIndex, sUsingService);
3486 CloseHandle(elevatedFileHandle);
3488 if (!useService && !noServiceFallback &&
3489 INVALID_HANDLE_VALUE == updateLockFileHandle)
3491 // We didn't use the service and we did run the elevated updater.exe.
3492 // The elevated updater.exe is responsible for writing out the
3493 // update.status file.
3494 return 0;
3496 else if (useService)
3498 // The service command was launched. The service is responsible for
3499 // writing out the update.status file.
3500 if (updateLockFileHandle != INVALID_HANDLE_VALUE)
3502 CloseHandle(updateLockFileHandle);
3504 return 0;
3506 else
3508 // Otherwise the service command was not launched at all.
3509 // We are only reaching this code path because we had write access
3510 // all along to the directory and a fallback key existed, and we
3511 // have fallback disabled (MOZ_NO_SERVICE_FALLBACK env var exists).
3512 // We only currently use this env var from XPCShell tests.
3513 CloseHandle(updateLockFileHandle);
3514 WriteStatusFile(lastFallbackError);
3515 return 0;
3519 #endif
3521 if (sStagedUpdate)
3523 // When staging updates, blow away the old installation directory and create
3524 // it from scratch.
3525 ensure_remove_recursive(gWorkingDirPath);
3527 if (!sReplaceRequest)
3529 // Try to create the destination directory if it doesn't exist
3530 int rv = NS_tmkdir(gWorkingDirPath, 0755);
3531 if (rv != OK && errno != EEXIST)
3533 #ifdef MACOSX
3534 if (isElevated)
3536 freeArguments(argc, argv);
3537 CleanupElevatedMacUpdate(true);
3539 #endif
3540 return 1;
3544 #ifdef _WIN32
3545 // For replace requests, we don't need to do any real updates, so this is not
3546 // necessary.
3547 if (!sReplaceRequest)
3549 // Allocate enough space for the length of the path an optional additional
3550 // trailing slash and null termination.
3551 NS_tchar *destpath = (NS_tchar *) malloc((NS_tstrlen(gWorkingDirPath) + 2) * sizeof(NS_tchar));
3552 if (!destpath)
3553 return 1;
3555 NS_tchar *c = destpath;
3556 NS_tstrcpy(c, gWorkingDirPath);
3557 c += NS_tstrlen(gWorkingDirPath);
3558 if (gWorkingDirPath[NS_tstrlen(gWorkingDirPath) - 1] != NS_T('/') &&
3559 gWorkingDirPath[NS_tstrlen(gWorkingDirPath) - 1] != NS_T('\\'))
3561 NS_tstrcat(c, NS_T("/"));
3562 c += NS_tstrlen(NS_T("/"));
3564 *c = NS_T('\0');
3565 c++;
3567 gDestPath = destpath;
3570 NS_tchar applyDirLongPath[MAXPATHLEN];
3571 if (!GetLongPathNameW(gWorkingDirPath, applyDirLongPath,
3572 sizeof(applyDirLongPath)/sizeof(applyDirLongPath[0])))
3574 LOG(("NS_main: unable to find apply to dir: " LOG_S, gWorkingDirPath));
3575 LogFinish();
3576 WriteStatusFile(WRITE_ERROR_APPLY_DIR_PATH);
3577 EXIT_WHEN_ELEVATED(elevatedLockFilePath, updateLockFileHandle, 1);
3578 if (argc > callbackIndex)
3580 LaunchCallbackApp(argv[5], argc - callbackIndex,
3581 argv + callbackIndex, sUsingService);
3583 return 1;
3586 HANDLE callbackFile = INVALID_HANDLE_VALUE;
3587 if (argc > callbackIndex)
3589 // If the callback executable is specified it must exist for a successful
3590 // update. It is important we null out the whole buffer here because later
3591 // we make the assumption that the callback application is inside the
3592 // apply-to dir. If we don't have a fully null'ed out buffer it can lead
3593 // to stack corruption which causes crashes and other problems.
3594 NS_tchar callbackLongPath[MAXPATHLEN];
3595 ZeroMemory(callbackLongPath, sizeof(callbackLongPath));
3596 NS_tchar *targetPath = argv[callbackIndex];
3597 NS_tchar buffer[MAXPATHLEN * 2] = { NS_T('\0') };
3598 size_t bufferLeft = MAXPATHLEN * 2;
3599 if (sReplaceRequest)
3601 // In case of replace requests, we should look for the callback file in
3602 // the destination directory.
3603 size_t commonPrefixLength = PathCommonPrefixW(argv[callbackIndex],
3604 gInstallDirPath,
3605 nullptr);
3606 NS_tchar *p = buffer;
3607 NS_tstrncpy(p, argv[callbackIndex], commonPrefixLength);
3608 p += commonPrefixLength;
3609 bufferLeft -= commonPrefixLength;
3610 NS_tstrncpy(p, gInstallDirPath + commonPrefixLength, bufferLeft);
3612 size_t len = NS_tstrlen(gInstallDirPath + commonPrefixLength);
3613 p += len;
3614 bufferLeft -= len;
3615 *p = NS_T('\\');
3616 ++p;
3617 bufferLeft--;
3618 *p = NS_T('\0');
3619 NS_tchar installDir[MAXPATHLEN];
3620 NS_tstrcpy(installDir, gInstallDirPath);
3621 size_t callbackPrefixLength = PathCommonPrefixW(argv[callbackIndex],
3622 installDir,
3623 nullptr);
3624 NS_tstrncpy(p, argv[callbackIndex] + std::max(callbackPrefixLength,
3625 commonPrefixLength), bufferLeft);
3626 targetPath = buffer;
3628 if (!GetLongPathNameW(targetPath, callbackLongPath,
3629 sizeof(callbackLongPath)/sizeof(callbackLongPath[0])))
3631 LOG(("NS_main: unable to find callback file: " LOG_S, targetPath));
3632 LogFinish();
3633 WriteStatusFile(WRITE_ERROR_CALLBACK_PATH);
3634 EXIT_WHEN_ELEVATED(elevatedLockFilePath, updateLockFileHandle, 1);
3635 if (argc > callbackIndex)
3637 LaunchCallbackApp(argv[5],
3638 argc - callbackIndex,
3639 argv + callbackIndex,
3640 sUsingService);
3642 return 1;
3645 // Doing this is only necessary when we're actually applying a patch.
3646 if (!sReplaceRequest)
3648 int len = NS_tstrlen(applyDirLongPath);
3649 NS_tchar *s = callbackLongPath;
3650 NS_tchar *d = gCallbackRelPath;
3651 // advance to the apply to directory and advance past the trailing backslash
3652 // if present.
3653 s += len;
3654 if (*s == NS_T('\\'))
3655 ++s;
3657 // Copy the string and replace backslashes with forward slashes along the
3658 // way.
3661 if (*s == NS_T('\\'))
3662 *d = NS_T('/');
3663 else
3664 *d = *s;
3665 ++s;
3666 ++d;
3668 while (*s);
3669 *d = NS_T('\0');
3670 ++d;
3672 // Make a copy of the callback executable so it can be read when patching.
3673 NS_tsnprintf(gCallbackBackupPath,
3674 sizeof(gCallbackBackupPath)/sizeof(gCallbackBackupPath[0]),
3675 NS_T("%s" CALLBACK_BACKUP_EXT), argv[callbackIndex]);
3676 NS_tremove(gCallbackBackupPath);
3677 if (!CopyFileW(argv[callbackIndex], gCallbackBackupPath, true))
3679 DWORD copyFileError = GetLastError();
3680 LOG(("NS_main: failed to copy callback file " LOG_S
3681 " into place at " LOG_S, argv[callbackIndex], gCallbackBackupPath));
3682 LogFinish();
3683 if (copyFileError == ERROR_ACCESS_DENIED)
3685 WriteStatusFile(WRITE_ERROR_ACCESS_DENIED);
3687 else
3689 WriteStatusFile(WRITE_ERROR_CALLBACK_APP);
3692 EXIT_WHEN_ELEVATED(elevatedLockFilePath, updateLockFileHandle, 1);
3693 LaunchCallbackApp(argv[callbackIndex],
3694 argc - callbackIndex,
3695 argv + callbackIndex,
3696 sUsingService);
3697 return 1;
3700 // Since the process may be signaled as exited by WaitForSingleObject before
3701 // the release of the executable image try to lock the main executable file
3702 // multiple times before giving up. If we end up giving up, we won't
3703 // fail the update.
3704 const int max_retries = 10;
3705 int retries = 1;
3706 DWORD lastWriteError = 0;
3709 // By opening a file handle without FILE_SHARE_READ to the callback
3710 // executable, the OS will prevent launching the process while it is
3711 // being updated.
3712 callbackFile = CreateFileW(targetPath,
3713 DELETE | GENERIC_WRITE,
3714 // allow delete, rename, and write
3715 FILE_SHARE_DELETE | FILE_SHARE_WRITE,
3716 nullptr, OPEN_EXISTING, 0, nullptr);
3717 if (callbackFile != INVALID_HANDLE_VALUE)
3718 break;
3720 lastWriteError = GetLastError();
3721 LOG(("NS_main: callback app file open attempt %d failed. " \
3722 "File: " LOG_S ". Last error: %d", retries,
3723 targetPath, lastWriteError));
3725 Sleep(100);
3727 while (++retries <= max_retries);
3729 // CreateFileW will fail if the callback executable is already in use.
3730 if (callbackFile == INVALID_HANDLE_VALUE)
3732 // Only fail the update if the last error was not a sharing violation.
3733 if (lastWriteError != ERROR_SHARING_VIOLATION)
3735 LOG(("NS_main: callback app file in use, failed to exclusively open " \
3736 "executable file: " LOG_S, argv[callbackIndex]));
3737 LogFinish();
3738 if (lastWriteError == ERROR_ACCESS_DENIED)
3740 WriteStatusFile(WRITE_ERROR_ACCESS_DENIED);
3742 else
3744 WriteStatusFile(WRITE_ERROR_CALLBACK_APP);
3747 NS_tremove(gCallbackBackupPath);
3748 EXIT_WHEN_ELEVATED(elevatedLockFilePath, updateLockFileHandle, 1);
3749 LaunchCallbackApp(argv[5],
3750 argc - callbackIndex,
3751 argv + callbackIndex,
3752 sUsingService);
3753 return 1;
3755 LOG(("NS_main: callback app file in use, continuing without " \
3756 "exclusive access for executable file: " LOG_S,
3757 argv[callbackIndex]));
3762 // DELETE_DIR is not required when performing a staged update or replace
3763 // request; it can be used during a replace request but then it doesn't
3764 // use gDeleteDirPath.
3765 if (!sStagedUpdate && !sReplaceRequest)
3767 // The directory to move files that are in use to on Windows. This directory
3768 // will be deleted after the update is finished, on OS reboot using
3769 // MoveFileEx if it contains files that are in use, or by the post update
3770 // process after the update finishes. On Windows when performing a normal
3771 // update (e.g. the update is not a staged update and is not a replace
3772 // request) gWorkingDirPath is the same as gInstallDirPath and
3773 // gWorkingDirPath is used because it is the destination directory.
3774 NS_tsnprintf(gDeleteDirPath,
3775 sizeof(gDeleteDirPath) / sizeof(gDeleteDirPath[0]),
3776 NS_T("%s/%s"), gWorkingDirPath, DELETE_DIR);
3778 if (NS_taccess(gDeleteDirPath, F_OK))
3780 NS_tmkdir(gDeleteDirPath, 0755);
3783 #endif /* _WIN32 */
3785 // Run update process on a background thread. ShowProgressUI may return
3786 // before QuitProgressUI has been called, so wait for UpdateThreadFunc to
3787 // terminate. Avoid showing the progress UI when staging an update, or if this
3788 // is an elevated process on OSX.
3789 std::thread t(UpdateThreadFunc, nullptr);
3790 if (!sStagedUpdate && !sReplaceRequest
3791 #ifdef XP_MACOSX
3792 && !isElevated
3793 #endif
3796 ShowProgressUI();
3798 t.join();
3800 #ifdef _WIN32
3801 if (argc > callbackIndex && !sReplaceRequest)
3803 if (callbackFile != INVALID_HANDLE_VALUE)
3805 CloseHandle(callbackFile);
3807 // Remove the copy of the callback executable.
3808 NS_tremove(gCallbackBackupPath);
3811 if (!sStagedUpdate && !sReplaceRequest && _wrmdir(gDeleteDirPath))
3813 LOG(("NS_main: unable to remove directory: " LOG_S ", err: %d",
3814 DELETE_DIR, errno));
3815 // The directory probably couldn't be removed due to it containing files
3816 // that are in use and will be removed on OS reboot. The call to remove the
3817 // directory on OS reboot is done after the calls to remove the files so the
3818 // files are removed first on OS reboot since the directory must be empty
3819 // for the directory removal to be successful. The MoveFileEx call to remove
3820 // the directory on OS reboot will fail if the process doesn't have write
3821 // access to the HKEY_LOCAL_MACHINE registry key but this is ok since the
3822 // installer / uninstaller will delete the directory along with its contents
3823 // after an update is applied, on reinstall, and on uninstall.
3824 if (MoveFileEx(gDeleteDirPath, nullptr, MOVEFILE_DELAY_UNTIL_REBOOT))
3826 LOG(("NS_main: directory will be removed on OS reboot: " LOG_S,
3827 DELETE_DIR));
3829 else
3831 LOG(("NS_main: failed to schedule OS reboot removal of " \
3832 "directory: " LOG_S, DELETE_DIR));
3835 #endif /* _WIN32 */
3838 #ifdef MACOSX
3839 // When the update is successful remove the precomplete file in the root of
3840 // the application bundle and move the distribution directory from
3841 // Contents/MacOS to Contents/Resources and if both exist delete the
3842 // directory under Contents/MacOS (see Bug 1068439).
3843 if (gSucceeded && !sStagedUpdate)
3845 NS_tchar oldPrecomplete[MAXPATHLEN];
3846 NS_tsnprintf(oldPrecomplete, sizeof(oldPrecomplete)/sizeof(oldPrecomplete[0]),
3847 NS_T("%s/precomplete"), gInstallDirPath);
3848 NS_tremove(oldPrecomplete);
3850 NS_tchar oldDistDir[MAXPATHLEN];
3851 NS_tsnprintf(oldDistDir, sizeof(oldDistDir)/sizeof(oldDistDir[0]),
3852 NS_T("%s/Contents/MacOS/distribution"), gInstallDirPath);
3853 int rv = NS_taccess(oldDistDir, F_OK);
3854 if (!rv)
3856 NS_tchar newDistDir[MAXPATHLEN];
3857 NS_tsnprintf(newDistDir, sizeof(newDistDir)/sizeof(newDistDir[0]),
3858 NS_T("%s/Contents/Resources/distribution"), gInstallDirPath);
3859 rv = NS_taccess(newDistDir, F_OK);
3860 if (!rv)
3862 LOG(("New distribution directory already exists... removing old " \
3863 "distribution directory: " LOG_S, oldDistDir));
3864 rv = ensure_remove_recursive(oldDistDir);
3865 if (rv)
3867 LOG(("Removing old distribution directory failed - err: %d", rv));
3870 else
3872 LOG(("Moving old distribution directory to new location. src: " LOG_S \
3873 ", dst:" LOG_S, oldDistDir, newDistDir));
3874 rv = rename_file(oldDistDir, newDistDir, true);
3875 if (rv)
3877 LOG(("Moving old distribution directory to new location failed - " \
3878 "err: %d", rv));
3884 if (isElevated)
3886 SetGroupOwnershipAndPermissions(gInstallDirPath);
3887 freeArguments(argc, argv);
3888 CleanupElevatedMacUpdate(false);
3890 else if (IsOwnedByGroupAdmin(gInstallDirPath))
3892 // If the group ownership of the Firefox .app bundle was set to the "admin"
3893 // group during a previous elevated update, we need to ensure that all files
3894 // in the bundle have group ownership of "admin" as well as write permission
3895 // for the group to not break updates in the future.
3896 SetGroupOwnershipAndPermissions(gInstallDirPath);
3898 #endif /* MACOSX */
3900 LogFinish();
3902 int retVal = LaunchCallbackAndPostProcessApps(argc, argv, callbackIndex
3903 #ifdef _WIN32
3904 , elevatedLockFilePath
3905 , updateLockFileHandle
3906 #elif defined(MACOSX)
3907 , isElevated
3908 #endif
3911 return retVal ? retVal : (gSucceeded ? 0 : 1);
3914 class ActionList
3916 public:
3917 ActionList() : mFirst(nullptr), mLast(nullptr), mCount(0) { }
3918 ~ActionList();
3920 void Append(Action* action);
3921 int Prepare();
3922 int Execute();
3923 void Finish(int status);
3925 private:
3926 Action *mFirst;
3927 Action *mLast;
3928 int mCount;
3931 ActionList::~ActionList()
3933 Action* a = mFirst;
3934 while (a)
3936 Action *b = a;
3937 a = a->mNext;
3938 delete b;
3942 void
3943 ActionList::Append(Action *action)
3945 if (mLast)
3946 mLast->mNext = action;
3947 else
3948 mFirst = action;
3950 mLast = action;
3951 mCount++;
3955 ActionList::Prepare()
3957 // If the action list is empty then we should fail in order to signal that
3958 // something has gone wrong. Otherwise we report success when nothing is
3959 // actually done. See bug 327140.
3960 if (mCount == 0)
3962 LOG(("empty action list"));
3963 return MAR_ERROR_EMPTY_ACTION_LIST;
3966 Action *a = mFirst;
3967 int i = 0;
3968 while (a)
3970 int rv = a->Prepare();
3971 if (rv)
3972 return rv;
3974 float percent = float(++i) / float(mCount);
3975 UpdateProgressUI(PROGRESS_PREPARE_SIZE * percent);
3977 a = a->mNext;
3980 return OK;
3984 ActionList::Execute()
3986 int currentProgress = 0, maxProgress = 0;
3987 Action *a = mFirst;
3988 while (a)
3990 maxProgress += a->mProgressCost;
3991 a = a->mNext;
3994 a = mFirst;
3995 while (a)
3997 int rv = a->Execute();
3998 if (rv)
4000 LOG(("### execution failed"));
4001 return rv;
4004 currentProgress += a->mProgressCost;
4005 float percent = float(currentProgress) / float(maxProgress);
4006 UpdateProgressUI(PROGRESS_PREPARE_SIZE +
4007 PROGRESS_EXECUTE_SIZE * percent);
4009 a = a->mNext;
4012 return OK;
4015 void
4016 ActionList::Finish(int status)
4018 Action *a = mFirst;
4019 int i = 0;
4020 while (a)
4022 a->Finish(status);
4024 float percent = float(++i) / float(mCount);
4025 UpdateProgressUI(PROGRESS_PREPARE_SIZE +
4026 PROGRESS_EXECUTE_SIZE +
4027 PROGRESS_FINISH_SIZE * percent);
4029 a = a->mNext;
4032 if (status == OK)
4033 gSucceeded = true;
4037 #ifdef _WIN32
4038 int add_dir_entries(const NS_tchar *dirpath, ActionList *list)
4040 int rv = OK;
4041 WIN32_FIND_DATAW finddata;
4042 HANDLE hFindFile;
4043 NS_tchar searchspec[MAXPATHLEN];
4044 NS_tchar foundpath[MAXPATHLEN];
4046 NS_tsnprintf(searchspec, sizeof(searchspec)/sizeof(searchspec[0]),
4047 NS_T("%s*"), dirpath);
4048 std::unique_ptr<const NS_tchar[]> pszSpec(new_absolute_path(searchspec));
4050 hFindFile = FindFirstFileW(pszSpec.get(), &finddata);
4051 if (hFindFile != INVALID_HANDLE_VALUE)
4055 // Don't process the current or parent directory.
4056 if (NS_tstrcmp(finddata.cFileName, NS_T(".")) == 0 ||
4057 NS_tstrcmp(finddata.cFileName, NS_T("..")) == 0)
4058 continue;
4060 NS_tsnprintf(foundpath, sizeof(foundpath)/sizeof(foundpath[0]),
4061 NS_T("%s%s"), dirpath, finddata.cFileName);
4062 if (finddata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
4064 NS_tsnprintf(foundpath, sizeof(foundpath)/sizeof(foundpath[0]),
4065 NS_T("%s/"), foundpath);
4066 // Recurse into the directory.
4067 rv = add_dir_entries(foundpath, list);
4068 if (rv)
4070 LOG(("add_dir_entries error: " LOG_S ", err: %d", foundpath, rv));
4071 return rv;
4074 else
4076 // Add the file to be removed to the ActionList.
4077 NS_tchar *quotedpath = get_quoted_path(foundpath);
4078 if (!quotedpath)
4079 return PARSE_ERROR;
4081 Action *action = new RemoveFile();
4082 rv = action->Parse(quotedpath);
4083 if (rv)
4085 LOG(("add_dir_entries Parse error on recurse: " LOG_S ", err: %d",
4086 quotedpath, rv));
4087 return rv;
4089 free(quotedpath);
4091 list->Append(action);
4094 while (FindNextFileW(hFindFile, &finddata) != 0);
4096 FindClose(hFindFile);
4098 // Add the directory to be removed to the ActionList.
4099 NS_tchar *quotedpath = get_quoted_path(dirpath);
4100 if (!quotedpath)
4101 return PARSE_ERROR;
4103 Action *action = new RemoveDir();
4104 rv = action->Parse(quotedpath);
4105 if (rv)
4106 LOG(("add_dir_entries Parse error on close: " LOG_S ", err: %d",
4107 quotedpath, rv));
4108 else
4109 list->Append(action);
4110 free(quotedpath);
4114 return rv;
4117 #elif defined(__sun)
4118 int add_dir_entries(const NS_tchar *dirpath, ActionList *list)
4120 int rv = OK;
4121 NS_tchar foundpath[MAXPATHLEN];
4122 struct
4124 dirent dent_buffer;
4125 char chars[MAXNAMLEN];
4126 } ent_buf;
4127 struct dirent* ent;
4128 std::unique_ptr<NS_tchar[]> searchpath(new_absolute_path(dirpath));
4130 DIR* dir = opendir(searchpath.get());
4131 if (!dir)
4133 LOG(("add_dir_entries error on opendir: " LOG_S ", err: %d", searchpath.get(),
4134 errno));
4135 return UNEXPECTED_FILE_OPERATION_ERROR;
4138 while (readdir_r(dir, (dirent *)&ent_buf, &ent) == 0 && ent)
4140 if ((strcmp(ent->d_name, ".") == 0) ||
4141 (strcmp(ent->d_name, "..") == 0))
4142 continue;
4144 NS_tsnprintf(foundpath, sizeof(foundpath)/sizeof(foundpath[0]),
4145 NS_T("%s%s"), searchpath.get(), ent->d_name);
4146 struct stat64 st_buf;
4147 int test = stat64(foundpath, &st_buf);
4148 if (test)
4150 closedir(dir);
4151 return UNEXPECTED_FILE_OPERATION_ERROR;
4153 if (S_ISDIR(st_buf.st_mode))
4155 NS_tsnprintf(foundpath, sizeof(foundpath)/sizeof(foundpath[0]),
4156 NS_T("%s/"), foundpath);
4157 // Recurse into the directory.
4158 rv = add_dir_entries(foundpath, list);
4159 if (rv)
4161 LOG(("add_dir_entries error: " LOG_S ", err: %d", foundpath, rv));
4162 closedir(dir);
4163 return rv;
4166 else
4168 // Add the file to be removed to the ActionList.
4169 NS_tchar *quotedpath = get_quoted_path(get_relative_offset(foundpath));
4170 if (!quotedpath)
4172 closedir(dir);
4173 return PARSE_ERROR;
4176 Action *action = new RemoveFile();
4177 rv = action->Parse(quotedpath);
4178 if (rv)
4180 LOG(("add_dir_entries Parse error on recurse: " LOG_S ", err: %d",
4181 quotedpath, rv));
4182 closedir(dir);
4183 return rv;
4186 list->Append(action);
4189 closedir(dir);
4191 // Add the directory to be removed to the ActionList.
4192 NS_tchar *quotedpath = get_quoted_path(get_relative_offset(dirpath));
4193 if (!quotedpath)
4194 return PARSE_ERROR;
4196 Action *action = new RemoveDir();
4197 rv = action->Parse(quotedpath);
4198 if (rv)
4200 LOG(("add_dir_entries Parse error on close: " LOG_S ", err: %d",
4201 quotedpath, rv));
4203 else
4205 list->Append(action);
4208 return rv;
4211 #else
4213 int add_dir_entries(const NS_tchar *dirpath, ActionList *list)
4215 int rv = OK;
4216 FTS *ftsdir;
4217 FTSENT *ftsdirEntry;
4218 std::unique_ptr<NS_tchar[]> searchpath(new_absolute_path(dirpath));
4220 // Remove the trailing slash so the paths don't contain double slashes. The
4221 // existence of the slash has already been checked in DoUpdate.
4222 searchpath.get()[NS_tstrlen(searchpath.get()) - 1] = NS_T('\0');
4223 char* const pathargv[] = {searchpath.get(), nullptr};
4225 // FTS_NOCHDIR is used so relative paths from the destination directory are
4226 // returned.
4227 if (!(ftsdir = fts_open(pathargv,
4228 FTS_PHYSICAL | FTS_NOSTAT | FTS_XDEV | FTS_NOCHDIR,
4229 nullptr)))
4230 return UNEXPECTED_FILE_OPERATION_ERROR;
4232 while ((ftsdirEntry = fts_read(ftsdir)) != nullptr)
4234 NS_tchar foundpath[MAXPATHLEN];
4235 NS_tchar *quotedpath = nullptr;
4236 Action *action = nullptr;
4238 switch (ftsdirEntry->fts_info)
4240 // Filesystem objects that shouldn't be in the application's directories
4241 case FTS_SL:
4242 case FTS_SLNONE:
4243 case FTS_DEFAULT:
4244 LOG(("add_dir_entries: found a non-standard file: " LOG_S,
4245 ftsdirEntry->fts_path));
4246 /* Fall through */ // and try to remove as a file
4248 // Files
4249 case FTS_F:
4250 case FTS_NSOK:
4251 // Add the file to be removed to the ActionList.
4252 NS_tsnprintf(foundpath, sizeof(foundpath)/sizeof(foundpath[0]),
4253 NS_T("%s"), ftsdirEntry->fts_accpath);
4254 quotedpath = get_quoted_path(get_relative_offset(foundpath));
4255 if (!quotedpath)
4257 rv = UPDATER_QUOTED_PATH_MEM_ERROR;
4258 break;
4260 action = new RemoveFile();
4261 rv = action->Parse(quotedpath);
4262 free(quotedpath);
4263 if (!rv)
4264 list->Append(action);
4265 break;
4267 // Directories
4268 case FTS_DP:
4269 rv = OK;
4270 // Add the directory to be removed to the ActionList.
4271 NS_tsnprintf(foundpath, sizeof(foundpath)/sizeof(foundpath[0]),
4272 NS_T("%s/"), ftsdirEntry->fts_accpath);
4273 quotedpath = get_quoted_path(get_relative_offset(foundpath));
4274 if (!quotedpath)
4276 rv = UPDATER_QUOTED_PATH_MEM_ERROR;
4277 break;
4280 action = new RemoveDir();
4281 rv = action->Parse(quotedpath);
4282 free(quotedpath);
4283 if (!rv)
4284 list->Append(action);
4285 break;
4287 // Errors
4288 case FTS_DNR:
4289 case FTS_NS:
4290 // ENOENT is an acceptable error for FTS_DNR and FTS_NS and means that
4291 // we're racing with ourselves. Though strange, the entry will be
4292 // removed anyway.
4293 if (ENOENT == ftsdirEntry->fts_errno)
4295 rv = OK;
4296 break;
4298 // Fall through
4300 case FTS_ERR:
4301 rv = UNEXPECTED_FILE_OPERATION_ERROR;
4302 LOG(("add_dir_entries: fts_read() error: " LOG_S ", err: %d",
4303 ftsdirEntry->fts_path, ftsdirEntry->fts_errno));
4304 break;
4306 case FTS_DC:
4307 rv = UNEXPECTED_FILE_OPERATION_ERROR;
4308 LOG(("add_dir_entries: fts_read() returned FT_DC: " LOG_S,
4309 ftsdirEntry->fts_path));
4310 break;
4312 default:
4313 // FTS_D is ignored and FTS_DP is used instead (post-order).
4314 rv = OK;
4315 break;
4318 if (rv != OK)
4319 break;
4322 fts_close(ftsdir);
4324 return rv;
4326 #endif
4328 static NS_tchar*
4329 GetManifestContents(const NS_tchar *manifest)
4331 AutoFile mfile(NS_tfopen(manifest, NS_T("rb")));
4332 if (mfile == nullptr)
4334 LOG(("GetManifestContents: error opening manifest file: " LOG_S, manifest));
4335 return nullptr;
4338 struct stat ms;
4339 int rv = fstat(fileno((FILE *)mfile), &ms);
4340 if (rv)
4342 LOG(("GetManifestContents: error stating manifest file: " LOG_S, manifest));
4343 return nullptr;
4346 char *mbuf = (char *) malloc(ms.st_size + 1);
4347 if (!mbuf)
4348 return nullptr;
4350 size_t r = ms.st_size;
4351 char *rb = mbuf;
4352 while (r)
4354 const size_t count = std::min<size_t>(SSIZE_MAX, r);
4355 size_t c = fread(rb, 1, count, mfile);
4356 if (c != count)
4358 LOG(("GetManifestContents: error reading manifest file: " LOG_S, manifest));
4359 free(mbuf);
4360 return nullptr;
4363 r -= c;
4364 rb += c;
4366 mbuf[ms.st_size] = '\0';
4367 rb = mbuf;
4369 #ifndef _WIN32
4370 return rb;
4371 #else
4372 NS_tchar *wrb = (NS_tchar *) malloc((ms.st_size + 1) * sizeof(NS_tchar));
4373 if (!wrb)
4375 free(mbuf);
4376 return nullptr;
4379 if (!MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, rb, -1, wrb,
4380 ms.st_size + 1))
4382 LOG(("GetManifestContents: error converting utf8 to utf16le: %d", GetLastError()));
4383 free(mbuf);
4384 free(wrb);
4385 return nullptr;
4387 free(mbuf);
4389 return wrb;
4390 #endif
4393 int AddPreCompleteActions(ActionList *list)
4395 #ifdef MACOSX
4396 std::unique_ptr<NS_tchar[]> manifestPath(new_absolute_path(
4397 NS_T("Contents/Resources/precomplete")));
4398 #else
4399 std::unique_ptr<NS_tchar[]> manifestPath(new_absolute_path(
4400 NS_T("precomplete")));
4401 #endif
4403 NS_tchar *rb = GetManifestContents(manifestPath.get());
4404 if (rb == nullptr)
4406 LOG(("AddPreCompleteActions: error getting contents of precomplete " \
4407 "manifest"));
4408 // Applications aren't required to have a precomplete manifest. The mar
4409 // generation scripts enforce the presence of a precomplete manifest.
4410 return OK;
4413 int rv;
4414 NS_tchar *line;
4415 while ((line = mstrtok(kNL, &rb)) != 0)
4417 // skip comments
4418 if (*line == NS_T('#'))
4419 continue;
4421 NS_tchar *token = mstrtok(kWhitespace, &line);
4422 if (!token)
4424 LOG(("AddPreCompleteActions: token not found in manifest"));
4425 return PARSE_ERROR;
4428 Action *action = nullptr;
4429 if (NS_tstrcmp(token, NS_T("remove")) == 0) // rm file
4431 action = new RemoveFile();
4433 else if (NS_tstrcmp(token, NS_T("remove-cc")) == 0) // no longer supported
4435 continue;
4437 else if (NS_tstrcmp(token, NS_T("rmdir")) == 0) // rmdir if empty
4439 action = new RemoveDir();
4441 else
4443 LOG(("AddPreCompleteActions: unknown token: " LOG_S, token));
4444 return PARSE_ERROR;
4447 if (!action)
4448 return BAD_ACTION_ERROR;
4450 rv = action->Parse(line);
4451 if (rv)
4452 return rv;
4454 list->Append(action);
4457 return OK;
4460 int DoUpdate(ArchiveReader& archiveReader)
4462 NS_tchar manifest[MAXPATHLEN];
4463 int nWrittenBytes = NS_tsnprintf(manifest, sizeof(manifest)/sizeof(manifest[0]),
4464 NS_T("%s/updating/update.manifest"), gWorkingDirPath);
4465 (void) nWrittenBytes;
4466 ensure_parent_dir(manifest);
4468 // extract the manifest
4469 // TODO: moggi: needs adaptation for LibreOffice
4470 // Why would we need the manifest? Even if we need it why would we need 2?
4471 int rv = archiveReader.ExtractFile("updatev3.manifest", manifest);
4472 if (rv)
4474 rv = archiveReader.ExtractFile("updatev2.manifest", manifest);
4475 if (rv)
4477 LOG(("DoUpdate: error extracting manifest file"));
4478 return rv;
4482 NS_tchar *rb = GetManifestContents(manifest);
4483 NS_tremove(manifest);
4484 if (rb == nullptr)
4486 LOG(("DoUpdate: error opening manifest file: " LOG_S, manifest));
4487 return READ_ERROR;
4490 ActionList list;
4491 NS_tchar *line;
4492 bool isFirstAction = true;
4494 while ((line = mstrtok(kNL, &rb)) != 0)
4496 // skip comments
4497 if (*line == NS_T('#'))
4498 continue;
4500 NS_tchar *token = mstrtok(kWhitespace, &line);
4501 if (!token)
4503 LOG(("DoUpdate: token not found in manifest"));
4504 return PARSE_ERROR;
4507 if (isFirstAction)
4509 isFirstAction = false;
4510 // The update manifest isn't required to have a type declaration. The mar
4511 // generation scripts enforce the presence of the type declaration.
4512 if (NS_tstrcmp(token, NS_T("type")) == 0)
4514 const NS_tchar *type = mstrtok(kQuote, &line);
4515 LOG(("UPDATE TYPE " LOG_S, type));
4516 if (NS_tstrcmp(type, NS_T("complete")) == 0)
4518 rv = AddPreCompleteActions(&list);
4519 if (rv)
4520 return rv;
4522 continue;
4526 Action *action = nullptr;
4527 if (NS_tstrcmp(token, NS_T("remove")) == 0) // rm file
4529 action = new RemoveFile();
4531 else if (NS_tstrcmp(token, NS_T("rmdir")) == 0) // rmdir if empty
4533 action = new RemoveDir();
4535 else if (NS_tstrcmp(token, NS_T("rmrfdir")) == 0) // rmdir recursive
4537 const NS_tchar *reldirpath = mstrtok(kQuote, &line);
4538 if (!reldirpath)
4539 return PARSE_ERROR;
4541 if (reldirpath[NS_tstrlen(reldirpath) - 1] != NS_T('/'))
4542 return PARSE_ERROR;
4544 rv = add_dir_entries(reldirpath, &list);
4545 if (rv)
4546 return rv;
4548 continue;
4550 else if (NS_tstrcmp(token, NS_T("add")) == 0)
4552 action = new AddFile(archiveReader);
4554 else if (NS_tstrcmp(token, NS_T("patch")) == 0)
4556 action = new PatchFile(archiveReader);
4558 else if (NS_tstrcmp(token, NS_T("add-if")) == 0) // Add if exists
4560 action = new AddIfFile(archiveReader);
4562 else if (NS_tstrcmp(token, NS_T("add-if-not")) == 0) // Add if not exists
4564 action = new AddIfNotFile(archiveReader);
4566 else if (NS_tstrcmp(token, NS_T("patch-if")) == 0) // Patch if exists
4568 action = new PatchIfFile(archiveReader);
4570 else
4572 LOG(("DoUpdate: unknown token: " LOG_S, token));
4573 return PARSE_ERROR;
4576 if (!action)
4577 return BAD_ACTION_ERROR;
4579 rv = action->Parse(line);
4580 if (rv)
4581 return rv;
4583 list.Append(action);
4586 rv = list.Prepare();
4587 if (rv)
4588 return rv;
4590 rv = list.Execute();
4592 list.Finish(rv);
4593 return rv;