Version 6.1.4.1, tag libreoffice-6.1.4.1
[LibreOffice.git] / onlineupdate / source / update / updater / updater.cxx
blob1782804eff154726acec9b065aa6ed767d5979c0
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 * Coverts a relative update path to a full path.
306 * @param relpath
307 * The relative path to convert to a full path.
308 * @return valid filesystem full path or nullptr if memory allocation fails.
310 static NS_tchar*
311 get_full_path(const NS_tchar *relpath)
313 NS_tchar *destpath = sStagedUpdate ? gWorkingDirPath : gInstallDirPath;
314 size_t lendestpath = NS_tstrlen(destpath);
315 size_t lenrelpath = NS_tstrlen(relpath);
316 NS_tchar *s = new NS_tchar[lendestpath + lenrelpath + 2];
318 NS_tchar *c = s;
320 NS_tstrcpy(c, destpath);
321 c += lendestpath;
322 NS_tstrcat(c, NS_T("/"));
323 c++;
325 NS_tstrcat(c, relpath);
326 c += lenrelpath;
327 *c = NS_T('\0');
328 return s;
331 namespace {
333 bool is_userprofile_in_instdir()
335 return false;
337 // the algorithm is:
338 // 1.) if userprofile path length is smaller than installation dir,
339 // the profile is surely not in instdir
340 // 2.) else comparing the two paths looking only at the installation dir
341 // characters should yield an equal string
342 NS_tchar userprofile[MAXPATHLEN];
343 NS_tstrcpy(userprofile, gPatchDirPath);
344 NS_tchar *slash = (NS_tchar *) NS_tstrrchr(userprofile, NS_T('/'));
345 if (slash)
346 *slash = NS_T('\0');
348 size_t userprofile_len = NS_tstrlen(userprofile);
349 size_t installdir_len = NS_tstrlen(gInstallDirPath);
351 if (userprofile_len < installdir_len)
352 return false;
354 return NS_tstrncmp(userprofile, gInstallDirPath, installdir_len) == 0;
361 * Converts a full update path into a relative path; reverses get_full_path.
363 * @param fullpath
364 * The absolute path to convert into a relative path.
365 * return pointer to the location within fullpath where the relative path starts
366 * or fullpath itself if it already looks relative.
368 static const NS_tchar*
369 get_relative_path(const NS_tchar *fullpath)
371 // If the path isn't absolute, just return it as-is.
372 #ifdef _WIN32
373 if (fullpath[1] != ':' && fullpath[2] != '\\')
375 #else
376 if (fullpath[0] != '/')
378 #endif
379 return fullpath;
382 NS_tchar *prefix = sStagedUpdate ? gWorkingDirPath : gInstallDirPath;
384 // If the path isn't long enough to be absolute, return it as-is.
385 if (NS_tstrlen(fullpath) <= NS_tstrlen(prefix))
387 return fullpath;
390 return fullpath + NS_tstrlen(prefix) + 1;
394 * Gets the platform specific path and performs simple checks to the path. If
395 * the path checks don't pass nullptr will be returned.
397 * @param line
398 * The line from the manifest that contains the path.
399 * @param isdir
400 * Whether the path is a directory path. Defaults to false.
401 * @return valid filesystem path or nullptr if the path checks fail.
403 static NS_tchar*
404 get_valid_path(NS_tchar **line, bool isdir = false)
406 NS_tchar *path = mstrtok(kQuote, line);
407 if (!path)
409 LOG(("get_valid_path: unable to determine path: " LOG_S, line));
410 return nullptr;
413 // All paths must be relative from the current working directory
414 if (path[0] == NS_T('/'))
416 LOG(("get_valid_path: path must be relative: " LOG_S, path));
417 return nullptr;
420 #ifdef _WIN32
421 // All paths must be relative from the current working directory
422 if (path[0] == NS_T('\\') || path[1] == NS_T(':'))
424 LOG(("get_valid_path: path must be relative: " LOG_S, path));
425 return nullptr;
427 #endif
429 if (isdir)
431 // Directory paths must have a trailing forward slash.
432 if (path[NS_tstrlen(path) - 1] != NS_T('/'))
434 LOG(("get_valid_path: directory paths must have a trailing forward " \
435 "slash: " LOG_S, path));
436 return nullptr;
439 // Remove the trailing forward slash because stat on Windows will return
440 // ENOENT if the path has a trailing slash.
441 path[NS_tstrlen(path) - 1] = NS_T('\0');
444 // Don't allow relative paths that resolve to a parent directory.
445 if (NS_tstrstr(path, NS_T("..")) != nullptr)
447 LOG(("get_valid_path: paths must not contain '..': " LOG_S, path));
448 return nullptr;
451 return path;
454 static NS_tchar*
455 get_quoted_path(const NS_tchar *path)
457 size_t lenQuote = NS_tstrlen(kQuote);
458 size_t lenPath = NS_tstrlen(path);
459 size_t len = lenQuote + lenPath + lenQuote + 1;
461 NS_tchar *s = (NS_tchar *) malloc(len * sizeof(NS_tchar));
462 if (!s)
463 return nullptr;
465 NS_tchar *c = s;
466 NS_tstrcpy(c, kQuote);
467 c += lenQuote;
468 NS_tstrcat(c, path);
469 c += lenPath;
470 NS_tstrcat(c, kQuote);
471 c += lenQuote;
472 *c = NS_T('\0');
473 c++;
474 return s;
477 static void ensure_write_permissions(const NS_tchar *path)
479 #ifdef _WIN32
480 (void) _wchmod(path, _S_IREAD | _S_IWRITE);
481 #else
482 struct stat fs;
483 if (!stat(path, &fs) && !(fs.st_mode & S_IWUSR))
485 (void)chmod(path, fs.st_mode | S_IWUSR);
487 #endif
490 static int ensure_remove(const NS_tchar *path)
492 ensure_write_permissions(path);
493 int rv = NS_tremove(path);
494 if (rv)
495 LOG(("ensure_remove: failed to remove file: " LOG_S ", rv: %d, err: %d",
496 path, rv, errno));
497 return rv;
500 // Remove the directory pointed to by path and all of its files and sub-directories.
501 static int ensure_remove_recursive(const NS_tchar *path,
502 bool continueEnumOnFailure = false)
504 // We use lstat rather than stat here so that we can successfully remove
505 // symlinks.
506 struct NS_tstat_t sInfo;
507 int rv = NS_tlstat(path, &sInfo);
508 if (rv)
510 // This error is benign
511 return rv;
513 if (!S_ISDIR(sInfo.st_mode))
515 return ensure_remove(path);
518 NS_tDIR *dir;
519 NS_tdirent *entry;
521 dir = NS_topendir(path);
522 if (!dir)
524 LOG(("ensure_remove_recursive: unable to open directory: " LOG_S
525 ", rv: %d, err: %d", path, rv, errno));
526 return rv;
529 while ((entry = NS_treaddir(dir)) != 0)
531 if (NS_tstrcmp(entry->d_name, NS_T(".")) &&
532 NS_tstrcmp(entry->d_name, NS_T("..")))
534 NS_tchar childPath[MAXPATHLEN];
535 NS_tsnprintf(childPath, sizeof(childPath)/sizeof(childPath[0]),
536 NS_T("%s/%s"), path, entry->d_name);
537 rv = ensure_remove_recursive(childPath);
538 if (rv && !continueEnumOnFailure)
540 break;
545 NS_tclosedir(dir);
547 if (rv == OK)
549 ensure_write_permissions(path);
550 rv = NS_trmdir(path);
551 if (rv)
553 LOG(("ensure_remove_recursive: unable to remove directory: " LOG_S
554 ", rv: %d, err: %d", path, rv, errno));
557 return rv;
560 static bool is_read_only(const NS_tchar *flags)
562 size_t length = NS_tstrlen(flags);
563 if (length == 0)
564 return false;
566 // Make sure the string begins with "r"
567 if (flags[0] != NS_T('r'))
568 return false;
570 // Look for "r+" or "r+b"
571 if (length > 1 && flags[1] == NS_T('+'))
572 return false;
574 // Look for "rb+"
575 if (NS_tstrcmp(flags, NS_T("rb+")) == 0)
576 return false;
578 return true;
581 static FILE* ensure_open(const NS_tchar *path, const NS_tchar *flags, unsigned int options)
583 ensure_write_permissions(path);
584 FILE* f = NS_tfopen(path, flags);
585 if (is_read_only(flags))
587 // Don't attempt to modify the file permissions if the file is being opened
588 // in read-only mode.
589 return f;
591 if (NS_tchmod(path, options) != 0)
593 if (f != nullptr)
595 fclose(f);
597 return nullptr;
599 struct NS_tstat_t ss;
600 if (NS_tstat(path, &ss) != 0 || ss.st_mode != options)
602 if (f != nullptr)
604 fclose(f);
606 return nullptr;
608 return f;
611 // Ensure that the directory containing this file exists.
612 static int ensure_parent_dir(const NS_tchar *path)
614 int rv = OK;
616 NS_tchar *slash = (NS_tchar *) NS_tstrrchr(path, NS_T('/'));
617 if (slash)
619 *slash = NS_T('\0');
620 rv = ensure_parent_dir(path);
621 // Only attempt to create the directory if we're not at the root
622 if (rv == OK && *path)
624 rv = NS_tmkdir(path, 0755);
625 // If the directory already exists, then ignore the error.
626 if (rv < 0 && errno != EEXIST)
628 LOG(("ensure_parent_dir: failed to create directory: " LOG_S ", " \
629 "err: %d", path, errno));
630 rv = WRITE_ERROR;
632 else
634 rv = OK;
637 *slash = NS_T('/');
639 return rv;
642 #ifdef UNIX
643 static int ensure_copy_symlink(const NS_tchar *path, const NS_tchar *dest)
645 // Copy symlinks by creating a new symlink to the same target
646 NS_tchar target[MAXPATHLEN + 1] = {NS_T('\0')};
647 int rv = readlink(path, target, MAXPATHLEN);
648 if (rv == -1)
650 LOG(("ensure_copy_symlink: failed to read the link: " LOG_S ", err: %d",
651 path, errno));
652 return READ_ERROR;
654 rv = symlink(target, dest);
655 if (rv == -1)
657 LOG(("ensure_copy_symlink: failed to create the new link: " LOG_S ", target: " LOG_S " err: %d",
658 dest, target, errno));
659 return READ_ERROR;
661 return 0;
663 #endif
665 // Copy the file named path onto a new file named dest.
666 static int ensure_copy(const NS_tchar *path, const NS_tchar *dest)
668 #ifdef _WIN32
669 // Fast path for Windows
670 bool result = CopyFileW(path, dest, false);
671 if (!result)
673 LOG(("ensure_copy: failed to copy the file " LOG_S " over to " LOG_S ", lasterr: %x",
674 path, dest, GetLastError()));
675 return WRITE_ERROR_FILE_COPY;
677 return OK;
678 #else
679 struct NS_tstat_t ss;
680 int rv = NS_tlstat(path, &ss);
681 if (rv)
683 LOG(("ensure_copy: failed to read file status info: " LOG_S ", err: %d",
684 path, errno));
685 return READ_ERROR;
688 #ifdef UNIX
689 if (S_ISLNK(ss.st_mode))
691 return ensure_copy_symlink(path, dest);
693 #endif
695 AutoFile infile(ensure_open(path, NS_T("rb"), ss.st_mode));
696 if (!infile)
698 LOG(("ensure_copy: failed to open the file for reading: " LOG_S ", err: %d",
699 path, errno));
700 return READ_ERROR;
702 AutoFile outfile(ensure_open(dest, NS_T("wb"), ss.st_mode));
703 if (!outfile)
705 LOG(("ensure_copy: failed to open the file for writing: " LOG_S ", err: %d",
706 dest, errno));
707 return WRITE_ERROR;
710 // This block size was chosen pretty arbitrarily but seems like a reasonable
711 // compromise. For example, the optimal block size on a modern OS X machine
712 // is 100k */
713 const int blockSize = 32 * 1024;
714 void* buffer = malloc(blockSize);
715 if (!buffer)
716 return UPDATER_MEM_ERROR;
718 while (!feof(infile.get()))
720 size_t read = fread(buffer, 1, blockSize, infile);
721 if (ferror(infile.get()))
723 LOG(("ensure_copy: failed to read the file: " LOG_S ", err: %d",
724 path, errno));
725 free(buffer);
726 return READ_ERROR;
729 size_t written = 0;
731 while (written < read)
733 size_t chunkWritten = fwrite(buffer, 1, read - written, outfile);
734 if (chunkWritten <= 0)
736 LOG(("ensure_copy: failed to write the file: " LOG_S ", err: %d",
737 dest, errno));
738 free(buffer);
739 return WRITE_ERROR_FILE_COPY;
742 written += chunkWritten;
746 rv = NS_tchmod(dest, ss.st_mode);
748 free(buffer);
749 return rv;
750 #endif
753 template <unsigned N>
754 struct copy_recursive_skiplist
756 NS_tchar paths[N][MAXPATHLEN];
758 void append(unsigned index, const NS_tchar *path, const NS_tchar *suffix)
760 NS_tsnprintf(paths[index], MAXPATHLEN, NS_T("%s/%s"), path, suffix);
763 void append(unsigned index, const NS_tchar* path)
765 NS_tstrcpy(paths[index], path);
768 bool find(const NS_tchar *path)
770 for (int i = 0; i < static_cast<int>(N); ++i)
772 if (!NS_tstricmp(paths[i], path))
774 return true;
777 return false;
781 // Copy all of the files and subdirectories under path to a new directory named dest.
782 // The path names in the skiplist will be skipped and will not be copied.
783 template <unsigned N>
784 static int ensure_copy_recursive(const NS_tchar *path, const NS_tchar *dest,
785 copy_recursive_skiplist<N>& skiplist)
787 struct NS_tstat_t sInfo;
788 int rv = NS_tlstat(path, &sInfo);
789 if (rv)
791 LOG(("ensure_copy_recursive: path doesn't exist: " LOG_S ", rv: %d, err: %d",
792 path, rv, errno));
793 return READ_ERROR;
796 #ifdef UNIX
797 if (S_ISLNK(sInfo.st_mode))
799 return ensure_copy_symlink(path, dest);
801 #endif
803 if (!S_ISDIR(sInfo.st_mode))
805 return ensure_copy(path, dest);
808 rv = NS_tmkdir(dest, sInfo.st_mode);
809 if (rv < 0 && errno != EEXIST)
811 LOG(("ensure_copy_recursive: could not create destination directory: " LOG_S ", rv: %d, err: %d",
812 path, rv, errno));
813 return WRITE_ERROR;
816 NS_tDIR *dir;
817 NS_tdirent *entry;
819 dir = NS_topendir(path);
820 if (!dir)
822 LOG(("ensure_copy_recursive: path is not a directory: " LOG_S ", rv: %d, err: %d",
823 path, rv, errno));
824 return READ_ERROR;
827 while ((entry = NS_treaddir(dir)) != 0)
829 if (NS_tstrcmp(entry->d_name, NS_T(".")) &&
830 NS_tstrcmp(entry->d_name, NS_T("..")))
832 NS_tchar childPath[MAXPATHLEN];
833 NS_tsnprintf(childPath, sizeof(childPath)/sizeof(childPath[0]),
834 NS_T("%s/%s"), path, entry->d_name);
835 if (skiplist.find(childPath))
837 continue;
839 NS_tchar childPathDest[MAXPATHLEN];
840 NS_tsnprintf(childPathDest, sizeof(childPathDest)/sizeof(childPathDest[0]),
841 NS_T("%s/%s"), dest, entry->d_name);
842 rv = ensure_copy_recursive(childPath, childPathDest, skiplist);
843 if (rv)
845 break;
849 NS_tclosedir(dir);
850 return rv;
853 // Renames the specified file to the new file specified. If the destination file
854 // exists it is removed.
855 static int rename_file(const NS_tchar *spath, const NS_tchar *dpath,
856 bool allowDirs = false)
858 int rv = ensure_parent_dir(dpath);
859 if (rv)
860 return rv;
862 struct NS_tstat_t spathInfo;
863 rv = NS_tstat(spath, &spathInfo);
864 if (rv)
866 LOG(("rename_file: failed to read file status info: " LOG_S ", " \
867 "err: %d", spath, errno));
868 return READ_ERROR;
871 if (!S_ISREG(spathInfo.st_mode))
873 if (allowDirs && !S_ISDIR(spathInfo.st_mode))
875 LOG(("rename_file: path present, but not a file: " LOG_S ", err: %d",
876 spath, errno));
877 return RENAME_ERROR_EXPECTED_FILE;
879 else
881 LOG(("rename_file: proceeding to rename the directory"));
885 if (!NS_taccess(dpath, F_OK))
887 if (ensure_remove(dpath))
889 LOG(("rename_file: destination file exists and could not be " \
890 "removed: " LOG_S, dpath));
891 return WRITE_ERROR_DELETE_FILE;
895 if (NS_trename(spath, dpath) != 0)
897 LOG(("rename_file: failed to rename file - src: " LOG_S ", " \
898 "dst:" LOG_S ", err: %d", spath, dpath, errno));
899 return WRITE_ERROR;
902 return OK;
905 #ifdef _WIN32
906 // Remove the directory pointed to by path and all of its files and
907 // sub-directories. If a file is in use move it to the tobedeleted directory
908 // and attempt to schedule removal of the file on reboot
909 static int remove_recursive_on_reboot(const NS_tchar *path, const NS_tchar *deleteDir)
911 struct NS_tstat_t sInfo;
912 int rv = NS_tlstat(path, &sInfo);
913 if (rv)
915 // This error is benign
916 return rv;
919 if (!S_ISDIR(sInfo.st_mode))
921 NS_tchar tmpDeleteFile[MAXPATHLEN];
922 GetTempFileNameW(deleteDir, L"rep", 0, tmpDeleteFile);
923 NS_tremove(tmpDeleteFile);
924 rv = rename_file(path, tmpDeleteFile, false);
925 if (MoveFileEx(rv ? path : tmpDeleteFile, nullptr, MOVEFILE_DELAY_UNTIL_REBOOT))
927 LOG(("remove_recursive_on_reboot: file will be removed on OS reboot: "
928 LOG_S, rv ? path : tmpDeleteFile));
930 else
932 LOG(("remove_recursive_on_reboot: failed to schedule OS reboot removal of "
933 "file: " LOG_S, rv ? path : tmpDeleteFile));
935 return rv;
938 NS_tDIR *dir;
939 NS_tdirent *entry;
941 dir = NS_topendir(path);
942 if (!dir)
944 LOG(("remove_recursive_on_reboot: unable to open directory: " LOG_S
945 ", rv: %d, err: %d",
946 path, rv, errno));
947 return rv;
950 while ((entry = NS_treaddir(dir)) != 0)
952 if (NS_tstrcmp(entry->d_name, NS_T(".")) &&
953 NS_tstrcmp(entry->d_name, NS_T("..")))
955 NS_tchar childPath[MAXPATHLEN];
956 NS_tsnprintf(childPath, sizeof(childPath)/sizeof(childPath[0]),
957 NS_T("%s/%s"), path, entry->d_name);
958 // There is no need to check the return value of this call since this
959 // function is only called after an update is successful and there is not
960 // much that can be done to recover if it isn't successful. There is also
961 // no need to log the value since it will have already been logged.
962 remove_recursive_on_reboot(childPath, deleteDir);
966 NS_tclosedir(dir);
968 if (rv == OK)
970 ensure_write_permissions(path);
971 rv = NS_trmdir(path);
972 if (rv)
974 LOG(("remove_recursive_on_reboot: unable to remove directory: " LOG_S
975 ", rv: %d, err: %d", path, rv, errno));
978 return rv;
980 #endif
982 //-----------------------------------------------------------------------------
984 // Create a backup of the specified file by renaming it.
985 static int backup_create(const NS_tchar *path)
987 NS_tchar backup[MAXPATHLEN];
988 NS_tsnprintf(backup, sizeof(backup)/sizeof(backup[0]),
989 NS_T("%s") BACKUP_EXT, path);
991 return rename_file(path, backup);
994 // Rename the backup of the specified file that was created by renaming it back
995 // to the original file.
996 static int backup_restore(const NS_tchar *path, const NS_tchar *relPath)
998 NS_tchar backup[MAXPATHLEN];
999 NS_tsnprintf(backup, sizeof(backup)/sizeof(backup[0]),
1000 NS_T("%s") BACKUP_EXT, path);
1002 NS_tchar relBackup[MAXPATHLEN];
1003 NS_tsnprintf(relBackup, sizeof(relBackup) / sizeof(relBackup[0]),
1004 NS_T("%s") BACKUP_EXT, relPath);
1006 if (NS_taccess(backup, F_OK))
1008 LOG(("backup_restore: backup file doesn't exist: " LOG_S, relBackup));
1009 return OK;
1012 return rename_file(backup, path);
1015 // Discard the backup of the specified file that was created by renaming it.
1016 static int backup_discard(const NS_tchar *path, const NS_tchar *relPath)
1018 NS_tchar backup[MAXPATHLEN];
1019 NS_tsnprintf(backup, sizeof(backup)/sizeof(backup[0]),
1020 NS_T("%s") BACKUP_EXT, path);
1022 NS_tchar relBackup[MAXPATHLEN];
1023 NS_tsnprintf(relBackup, sizeof(relBackup) / sizeof(relBackup[0]),
1024 NS_T("%s") BACKUP_EXT, relPath);
1026 // Nothing to discard
1027 if (NS_taccess(backup, F_OK))
1029 return OK;
1032 int rv = ensure_remove(backup);
1033 #if defined(_WIN32)
1034 if (rv && !sStagedUpdate && !sReplaceRequest)
1036 LOG(("backup_discard: unable to remove: " LOG_S, relBackup));
1037 NS_tchar path[MAXPATHLEN];
1038 GetTempFileNameW(gDeleteDirPath, L"moz", 0, path);
1039 if (rename_file(backup, path))
1041 LOG(("backup_discard: failed to rename file:" LOG_S ", dst:" LOG_S,
1042 relBackup, relPath));
1043 return WRITE_ERROR_DELETE_BACKUP;
1045 // The MoveFileEx call to remove the file on OS reboot will fail if the
1046 // process doesn't have write access to the HKEY_LOCAL_MACHINE registry key
1047 // but this is ok since the installer / uninstaller will delete the
1048 // directory containing the file along with its contents after an update is
1049 // applied, on reinstall, and on uninstall.
1050 if (MoveFileEx(path, nullptr, MOVEFILE_DELAY_UNTIL_REBOOT))
1052 LOG(("backup_discard: file renamed and will be removed on OS " \
1053 "reboot: " LOG_S, relPath));
1055 else
1057 LOG(("backup_discard: failed to schedule OS reboot removal of " \
1058 "file: " LOG_S, relPath));
1061 #else
1062 if (rv)
1063 return WRITE_ERROR_DELETE_BACKUP;
1064 #endif
1066 return OK;
1069 // Helper function for post-processing a temporary backup.
1070 static void backup_finish(const NS_tchar *path, const NS_tchar *relPath,
1071 int status)
1073 if (status == OK)
1074 backup_discard(path, relPath);
1075 else
1076 backup_restore(path, relPath);
1079 //-----------------------------------------------------------------------------
1081 static int DoUpdate(ArchiveReader& ArchiveReader);
1083 class Action
1085 public:
1086 Action() : mProgressCost(1), mNext(nullptr) { }
1087 virtual ~Action() { }
1089 virtual int Parse(NS_tchar *line) = 0;
1091 // Do any preprocessing to ensure that the action can be performed. Execute
1092 // will be called if this Action and all others return OK from this method.
1093 virtual int Prepare() = 0;
1095 // Perform the operation. Return OK to indicate success. After all actions
1096 // have been executed, Finish will be called. A requirement of Execute is
1097 // that its operation be reversible from Finish.
1098 virtual int Execute() = 0;
1100 // Finish is called after execution of all actions. If status is OK, then
1101 // all actions were successfully executed. Otherwise, some action failed.
1102 virtual void Finish(int status) = 0;
1104 int mProgressCost;
1105 private:
1106 Action* mNext;
1108 friend class ActionList;
1111 class RemoveFile : public Action
1113 public:
1114 RemoveFile() : mSkip(0) { }
1116 int Parse(NS_tchar *line);
1117 int Prepare();
1118 int Execute();
1119 void Finish(int status);
1121 private:
1122 std::unique_ptr<const NS_tchar> mFile;
1123 std::unique_ptr<NS_tchar> mRelPath;
1124 int mSkip;
1128 RemoveFile::Parse(NS_tchar *line)
1130 // format "<deadfile>"
1132 NS_tchar* validPath = get_valid_path(&line);
1133 if (!validPath)
1134 return PARSE_ERROR;
1136 mRelPath.reset(new NS_tchar[MAXPATHLEN]);
1137 NS_tstrcpy(mRelPath.get(), validPath);
1139 mFile.reset(get_full_path(validPath));
1140 if (!mFile)
1142 return PARSE_ERROR;
1145 return OK;
1149 RemoveFile::Prepare()
1151 // Skip the file if it already doesn't exist.
1152 int rv = NS_taccess(mFile.get(), F_OK);
1153 if (rv)
1155 mSkip = 1;
1156 mProgressCost = 0;
1157 return OK;
1160 LOG(("PREPARE REMOVEFILE " LOG_S, mRelPath.get()));
1162 // Make sure that we're actually a file...
1163 struct NS_tstat_t fileInfo;
1164 rv = NS_tstat(mFile.get(), &fileInfo);
1165 if (rv)
1167 LOG(("failed to read file status info: " LOG_S ", err: %d", mFile.get(),
1168 errno));
1169 return READ_ERROR;
1172 if (!S_ISREG(fileInfo.st_mode))
1174 LOG(("path present, but not a file: " LOG_S, mFile.get()));
1175 return DELETE_ERROR_EXPECTED_FILE;
1178 NS_tchar *slash = (NS_tchar *) NS_tstrrchr(mFile.get(), NS_T('/'));
1179 if (slash)
1181 *slash = NS_T('\0');
1182 rv = NS_taccess(mFile.get(), W_OK);
1183 *slash = NS_T('/');
1185 else
1187 rv = NS_taccess(NS_T("."), W_OK);
1190 if (rv)
1192 LOG(("access failed: %d", errno));
1193 return WRITE_ERROR_FILE_ACCESS_DENIED;
1196 return OK;
1200 RemoveFile::Execute()
1202 if (mSkip)
1203 return OK;
1205 LOG(("EXECUTE REMOVEFILE " LOG_S, mRelPath.get()));
1207 // The file is checked for existence here and in Prepare since it might have
1208 // been removed by a separate instruction: bug 311099.
1209 int rv = NS_taccess(mFile.get(), F_OK);
1210 if (rv)
1212 LOG(("file cannot be removed because it does not exist; skipping"));
1213 mSkip = 1;
1214 return OK;
1217 // Rename the old file. It will be removed in Finish.
1218 rv = backup_create(mFile.get());
1219 if (rv)
1221 LOG(("backup_create failed: %d", rv));
1222 return rv;
1225 return OK;
1228 void
1229 RemoveFile::Finish(int status)
1231 if (mSkip)
1232 return;
1234 LOG(("FINISH REMOVEFILE " LOG_S, mRelPath.get()));
1236 backup_finish(mFile.get(), mRelPath.get(), status);
1239 class RemoveDir : public Action
1241 public:
1242 RemoveDir() : mSkip(0) { }
1244 virtual int Parse(NS_tchar *line);
1245 virtual int Prepare(); // check that the source dir exists
1246 virtual int Execute();
1247 virtual void Finish(int status);
1249 private:
1250 std::unique_ptr<NS_tchar> mDir;
1251 std::unique_ptr<NS_tchar> mRelPath;
1252 int mSkip;
1256 RemoveDir::Parse(NS_tchar *line)
1258 // format "<deaddir>/"
1260 NS_tchar* validPath = get_valid_path(&line, true);
1261 if (!validPath)
1262 return PARSE_ERROR;
1263 mRelPath.reset(new NS_tchar[MAXPATHLEN]);
1264 NS_tstrcpy(mRelPath.get(), validPath);
1266 mDir.reset(get_full_path(validPath));
1267 if (!mDir)
1269 return PARSE_ERROR;
1272 return OK;
1276 RemoveDir::Prepare()
1278 // We expect the directory to exist if we are to remove it.
1279 int rv = NS_taccess(mDir.get(), F_OK);
1280 if (rv)
1282 mSkip = 1;
1283 mProgressCost = 0;
1284 return OK;
1287 LOG(("PREPARE REMOVEDIR " LOG_S "/", mRelPath.get()));
1289 // Make sure that we're actually a dir.
1290 struct NS_tstat_t dirInfo;
1291 rv = NS_tstat(mDir.get(), &dirInfo);
1292 if (rv)
1294 LOG(("failed to read directory status info: " LOG_S ", err: %d", mRelPath.get(),
1295 errno));
1296 return READ_ERROR;
1299 if (!S_ISDIR(dirInfo.st_mode))
1301 LOG(("path present, but not a directory: " LOG_S, mRelPath.get()));
1302 return DELETE_ERROR_EXPECTED_DIR;
1305 rv = NS_taccess(mDir.get(), W_OK);
1306 if (rv)
1308 LOG(("access failed: %d, %d", rv, errno));
1309 return WRITE_ERROR_DIR_ACCESS_DENIED;
1312 return OK;
1316 RemoveDir::Execute()
1318 if (mSkip)
1319 return OK;
1321 LOG(("EXECUTE REMOVEDIR " LOG_S "/", mRelPath.get()));
1323 // The directory is checked for existence at every step since it might have
1324 // been removed by a separate instruction: bug 311099.
1325 int rv = NS_taccess(mDir.get(), F_OK);
1326 if (rv)
1328 LOG(("directory no longer exists; skipping"));
1329 mSkip = 1;
1332 return OK;
1335 void
1336 RemoveDir::Finish(int status)
1338 if (mSkip || status != OK)
1339 return;
1341 LOG(("FINISH REMOVEDIR " LOG_S "/", mRelPath.get()));
1343 // The directory is checked for existence at every step since it might have
1344 // been removed by a separate instruction: bug 311099.
1345 int rv = NS_taccess(mDir.get(), F_OK);
1346 if (rv)
1348 LOG(("directory no longer exists; skipping"));
1349 return;
1353 if (status == OK)
1355 if (NS_trmdir(mDir.get()))
1357 LOG(("non-fatal error removing directory: " LOG_S "/, rv: %d, err: %d",
1358 mRelPath.get(), rv, errno));
1363 class AddFile : public Action
1365 public:
1366 AddFile(ArchiveReader& ar) : mAdded(false), mArchiveReader(ar) { }
1368 virtual int Parse(NS_tchar *line);
1369 virtual int Prepare();
1370 virtual int Execute();
1371 virtual void Finish(int status);
1373 private:
1374 std::unique_ptr<NS_tchar> mFile;
1375 std::unique_ptr<NS_tchar> mRelPath;
1376 bool mAdded;
1377 ArchiveReader& mArchiveReader;
1381 AddFile::Parse(NS_tchar *line)
1383 // format "<newfile>"
1385 NS_tchar* validPath = get_valid_path(&line);
1386 if (!validPath)
1387 return PARSE_ERROR;
1389 mRelPath.reset(new NS_tchar[MAXPATHLEN]);
1391 NS_tstrcpy(mRelPath.get(), validPath);
1393 mFile.reset(get_full_path(validPath));
1394 if (!mFile)
1396 return PARSE_ERROR;
1399 return OK;
1403 AddFile::Prepare()
1405 LOG(("PREPARE ADD " LOG_S, mRelPath.get()));
1407 return OK;
1411 AddFile::Execute()
1413 LOG(("EXECUTE ADD " LOG_S, mRelPath.get()));
1415 int rv;
1417 // First make sure that we can actually get rid of any existing file.
1418 rv = NS_taccess(mFile.get(), F_OK);
1419 if (rv == 0)
1421 rv = backup_create(mFile.get());
1422 if (rv)
1423 return rv;
1425 else
1427 rv = ensure_parent_dir(mFile.get());
1428 if (rv)
1429 return rv;
1432 #ifdef _WIN32
1433 char sourcefile[MAXPATHLEN];
1434 if (!WideCharToMultiByte(CP_UTF8, 0, mRelPath.get(), -1, sourcefile,
1435 MAXPATHLEN, nullptr, nullptr))
1437 LOG(("error converting wchar to utf8: %d", GetLastError()));
1438 return STRING_CONVERSION_ERROR;
1441 rv = mArchiveReader.ExtractFile(sourcefile, mFile.get());
1442 #else
1443 rv = mArchiveReader.ExtractFile(mRelPath.get(), mFile.get());
1444 #endif
1445 if (!rv)
1447 mAdded = true;
1449 return rv;
1452 void
1453 AddFile::Finish(int status)
1455 LOG(("FINISH ADD " LOG_S, mRelPath.get()));
1456 // When there is an update failure and a file has been added it is removed
1457 // here since there might not be a backup to replace it.
1458 if (status && mAdded)
1459 NS_tremove(mFile.get());
1460 backup_finish(mFile.get(), mRelPath.get(), status);
1463 class PatchFile : public Action
1465 public:
1466 PatchFile(ArchiveReader& ar) : mPatchFile(nullptr), mPatchIndex(-1), buf(nullptr), mArchiveReader(ar) { }
1468 virtual ~PatchFile();
1470 virtual int Parse(NS_tchar *line);
1471 virtual int Prepare(); // should check for patch file and for checksum here
1472 virtual int Execute();
1473 virtual void Finish(int status);
1475 private:
1476 int LoadSourceFile(FILE* ofile);
1478 static int sPatchIndex;
1480 const NS_tchar *mPatchFile;
1481 std::unique_ptr<NS_tchar> mFile;
1482 std::unique_ptr<NS_tchar> mFileRelPath;
1483 int mPatchIndex;
1484 MBSPatchHeader header;
1485 unsigned char *buf;
1486 NS_tchar spath[MAXPATHLEN];
1487 AutoFile mPatchStream;
1488 ArchiveReader& mArchiveReader;
1491 int PatchFile::sPatchIndex = 0;
1493 PatchFile::~PatchFile()
1495 // Make sure mPatchStream gets unlocked on Windows; the system will do that,
1496 // but not until some indeterminate future time, and we want determinism.
1497 // Normally this happens at the end of Execute, when we close the stream;
1498 // this call is here in case Execute errors out.
1499 #ifdef _WIN32
1500 if (mPatchStream)
1502 UnlockFile((HANDLE)_get_osfhandle(fileno(mPatchStream)), (DWORD)0, (DWORD)0, (DWORD)-1, (DWORD)-1);
1504 #endif
1506 // delete the temporary patch file
1507 if (spath[0])
1508 NS_tremove(spath);
1510 if (buf)
1511 free(buf);
1515 PatchFile::LoadSourceFile(FILE* ofile)
1517 struct stat os;
1518 int rv = fstat(fileno((FILE *)ofile), &os);
1519 if (rv)
1521 LOG(("LoadSourceFile: unable to stat destination file: " LOG_S ", " \
1522 "err: %d", mFileRelPath.get(), errno));
1523 return READ_ERROR;
1526 if (uint32_t(os.st_size) != header.slen)
1528 LOG(("LoadSourceFile: destination file size %d does not match expected size %d",
1529 uint32_t(os.st_size), header.slen));
1530 return LOADSOURCE_ERROR_WRONG_SIZE;
1533 buf = (unsigned char *) malloc(header.slen);
1534 if (!buf)
1535 return UPDATER_MEM_ERROR;
1537 size_t r = header.slen;
1538 unsigned char *rb = buf;
1539 while (r)
1541 const size_t count = std::min<size_t>(SSIZE_MAX, r);
1542 size_t c = fread(rb, 1, count, ofile);
1543 if (c != count)
1545 LOG(("LoadSourceFile: error reading destination file: " LOG_S,
1546 mFileRelPath.get()));
1547 return READ_ERROR;
1550 r -= c;
1551 rb += c;
1554 // Verify that the contents of the source file correspond to what we expect.
1556 unsigned int crc = crc32(buf, header.slen);
1558 if (crc != header.scrc32)
1560 LOG(("LoadSourceFile: destination file crc %d does not match expected " \
1561 "crc %d", crc, header.scrc32));
1562 return CRC_ERROR;
1565 return OK;
1569 PatchFile::Parse(NS_tchar *line)
1571 // format "<patchfile>" "<filetopatch>"
1573 // Get the path to the patch file inside of the mar
1574 mPatchFile = mstrtok(kQuote, &line);
1575 if (!mPatchFile)
1576 return PARSE_ERROR;
1578 // consume whitespace between args
1579 NS_tchar *q = mstrtok(kQuote, &line);
1580 if (!q)
1581 return PARSE_ERROR;
1583 NS_tchar* validPath = get_valid_path(&line);
1584 if (!validPath)
1585 return PARSE_ERROR;
1586 mFileRelPath.reset(new NS_tchar[MAXPATHLEN]);
1587 NS_tstrcpy(mFileRelPath.get(), validPath);
1589 mFile.reset(get_full_path(validPath));
1590 if (!mFile)
1592 return PARSE_ERROR;
1595 return OK;
1599 PatchFile::Prepare()
1601 LOG(("PREPARE PATCH " LOG_S, mFileRelPath.get()));
1603 // extract the patch to a temporary file
1604 mPatchIndex = sPatchIndex++;
1606 NS_tsnprintf(spath, sizeof(spath)/sizeof(spath[0]),
1607 NS_T("%s/updating/%d.patch"), gWorkingDirPath, mPatchIndex);
1609 NS_tremove(spath);
1611 mPatchStream = NS_tfopen(spath, NS_T("wb+"));
1612 if (!mPatchStream)
1613 return WRITE_ERROR;
1615 #ifdef _WIN32
1616 // Lock the patch file, so it can't be messed with between
1617 // when we're done creating it and when we go to apply it.
1618 if (!LockFile((HANDLE)_get_osfhandle(fileno(mPatchStream)), (DWORD)0, (DWORD)0, (DWORD)-1, (DWORD)-1))
1620 LOG(("Couldn't lock patch file: %d", GetLastError()));
1621 // TODO: moggi: fix the build problem with LOCK_ERROR_PATCH_FILE
1622 return WRITE_ERROR; //return LOCK_ERROR_PATCH_FILE;
1624 char sourcefile[MAXPATHLEN];
1625 if (!WideCharToMultiByte(CP_UTF8, 0, mPatchFile, -1, sourcefile, MAXPATHLEN,
1626 nullptr, nullptr))
1628 LOG(("error converting wchar to utf8: %d", GetLastError()));
1629 return STRING_CONVERSION_ERROR;
1632 int rv = mArchiveReader.ExtractFileToStream(sourcefile, mPatchStream);
1633 #else
1634 int rv = mArchiveReader.ExtractFileToStream(mPatchFile, mPatchStream);
1635 #endif
1637 return rv;
1641 PatchFile::Execute()
1643 LOG(("EXECUTE PATCH " LOG_S, mFileRelPath.get()));
1645 fseek(mPatchStream, 0, SEEK_SET);
1647 int rv = MBS_ReadHeader(mPatchStream, &header);
1648 if (rv)
1649 return rv;
1651 FILE *origfile = nullptr;
1652 #ifdef _WIN32
1653 if (NS_tstrcmp(mFileRelPath.get(), gCallbackRelPath) == 0)
1655 // Read from the copy of the callback when patching since the callback can't
1656 // be opened for reading to prevent the application from being launched.
1657 origfile = NS_tfopen(gCallbackBackupPath, NS_T("rb"));
1659 else
1661 origfile = NS_tfopen(mFile.get(), NS_T("rb"));
1663 #else
1664 origfile = NS_tfopen(mFile.get(), NS_T("rb"));
1665 #endif
1667 if (!origfile)
1669 LOG(("unable to open destination file: " LOG_S ", err: %d",
1670 mFileRelPath.get(), errno));
1671 return READ_ERROR;
1674 rv = LoadSourceFile(origfile);
1675 fclose(origfile);
1676 if (rv)
1678 LOG(("LoadSourceFile failed"));
1679 return rv;
1682 // Rename the destination file if it exists before proceeding so it can be
1683 // used to restore the file to its original state if there is an error.
1684 struct NS_tstat_t ss;
1685 rv = NS_tstat(mFile.get(), &ss);
1686 if (rv)
1688 LOG(("failed to read file status info: " LOG_S ", err: %d",
1689 mFileRelPath.get(), errno));
1690 return READ_ERROR;
1693 rv = backup_create(mFile.get());
1694 if (rv)
1695 return rv;
1697 #if defined(HAVE_POSIX_FALLOCATE)
1698 AutoFile ofile(ensure_open(mFile.get(), NS_T("wb+"), ss.st_mode));
1699 posix_fallocate(fileno((FILE *)ofile), 0, header.dlen);
1700 #elif defined(_WIN32)
1701 bool shouldTruncate = true;
1702 // Creating the file, setting the size, and then closing the file handle
1703 // lessens fragmentation more than any other method tested. Other methods that
1704 // have been tested are:
1705 // 1. _chsize / _chsize_s reduced fragmentation though not completely.
1706 // 2. _get_osfhandle and then setting the size reduced fragmentation though
1707 // not completely. There are also reports of _get_osfhandle failing on
1708 // mingw.
1709 HANDLE hfile = CreateFileW(mFile.get(),
1710 GENERIC_WRITE,
1712 nullptr,
1713 CREATE_ALWAYS,
1714 FILE_ATTRIBUTE_NORMAL,
1715 nullptr);
1717 if (hfile != INVALID_HANDLE_VALUE)
1719 if (SetFilePointer(hfile, header.dlen,
1720 nullptr, FILE_BEGIN) != INVALID_SET_FILE_POINTER &&
1721 SetEndOfFile(hfile) != 0)
1723 shouldTruncate = false;
1725 CloseHandle(hfile);
1728 AutoFile ofile(ensure_open(mFile.get(), shouldTruncate ? NS_T("wb+") : NS_T("rb+"),
1729 ss.st_mode));
1730 #elif defined(MACOSX)
1731 AutoFile ofile(ensure_open(mFile.get(), NS_T("wb+"), ss.st_mode));
1732 // Modified code from FileUtils.cpp
1733 fstore_t store = {F_ALLOCATECONTIG, F_PEOFPOSMODE, 0, header.dlen};
1734 // Try to get a continuous chunk of disk space
1735 rv = fcntl(fileno((FILE *)ofile), F_PREALLOCATE, &store);
1736 if (rv == -1)
1738 // OK, perhaps we are too fragmented, allocate non-continuous
1739 store.fst_flags = F_ALLOCATEALL;
1740 rv = fcntl(fileno((FILE *)ofile), F_PREALLOCATE, &store);
1743 if (rv != -1)
1745 ftruncate(fileno((FILE *)ofile), header.dlen);
1747 #else
1748 AutoFile ofile(ensure_open(mFile.get(), NS_T("wb+"), ss.st_mode));
1749 #endif
1751 if (ofile == nullptr)
1753 LOG(("unable to create new file: " LOG_S ", err: %d", mFileRelPath.get(),
1754 errno));
1755 return WRITE_ERROR_OPEN_PATCH_FILE;
1758 #ifdef _WIN32
1759 if (!shouldTruncate)
1761 fseek(ofile, 0, SEEK_SET);
1763 #endif
1765 rv = MBS_ApplyPatch(&header, mPatchStream, buf, ofile);
1767 // Go ahead and do a bit of cleanup now to minimize runtime overhead.
1768 // Make sure mPatchStream gets unlocked on Windows; the system will do that,
1769 // but not until some indeterminate future time, and we want determinism.
1770 #ifdef _WIN32
1771 UnlockFile((HANDLE)_get_osfhandle(fileno(mPatchStream)), (DWORD)0, (DWORD)0, (DWORD)-1, (DWORD)-1);
1772 #endif
1773 // Set mPatchStream to nullptr to make AutoFile close the file,
1774 // so it can be deleted on Windows.
1775 mPatchStream = nullptr;
1776 NS_tremove(spath);
1777 spath[0] = NS_T('\0');
1778 free(buf);
1779 buf = nullptr;
1780 return rv;
1783 void
1784 PatchFile::Finish(int status)
1786 LOG(("FINISH PATCH " LOG_S, mFileRelPath.get()));
1788 backup_finish(mFile.get(), mFileRelPath.get(), status);
1791 class AddIfFile : public AddFile
1793 public:
1794 AddIfFile(ArchiveReader& archiveReader);
1796 virtual int Parse(NS_tchar *line);
1797 virtual int Prepare();
1798 virtual int Execute();
1799 virtual void Finish(int status);
1801 protected:
1802 std::unique_ptr<NS_tchar> mTestFile;
1805 AddIfFile::AddIfFile(ArchiveReader& archiveReader):
1806 AddFile(archiveReader)
1811 AddIfFile::Parse(NS_tchar *line)
1813 // format "<testfile>" "<newfile>"
1815 mTestFile.reset(get_full_path(get_valid_path(&line)));
1816 if (!mTestFile)
1817 return PARSE_ERROR;
1819 // consume whitespace between args
1820 NS_tchar *q = mstrtok(kQuote, &line);
1821 if (!q)
1822 return PARSE_ERROR;
1824 return AddFile::Parse(line);
1828 AddIfFile::Prepare()
1830 // If the test file does not exist, then skip this action.
1831 if (NS_taccess(mTestFile.get(), F_OK))
1833 mTestFile = nullptr;
1834 return OK;
1837 return AddFile::Prepare();
1841 AddIfFile::Execute()
1843 if (!mTestFile)
1844 return OK;
1846 return AddFile::Execute();
1849 void
1850 AddIfFile::Finish(int status)
1852 if (!mTestFile)
1853 return;
1855 AddFile::Finish(status);
1858 class AddIfNotFile : public AddFile
1860 public:
1861 AddIfNotFile(ArchiveReader& archiveReader);
1863 virtual int Parse(NS_tchar *line);
1864 virtual int Prepare();
1865 virtual int Execute();
1866 virtual void Finish(int status);
1868 protected:
1869 std::unique_ptr<NS_tchar> mTestFile;
1872 AddIfNotFile::AddIfNotFile(ArchiveReader& archiveReader):
1873 AddFile(archiveReader)
1878 AddIfNotFile::Parse(NS_tchar *line)
1880 // format "<testfile>" "<newfile>"
1882 mTestFile.reset(get_full_path(get_valid_path(&line)));
1883 if (!mTestFile)
1884 return PARSE_ERROR;
1886 // consume whitespace between args
1887 NS_tchar *q = mstrtok(kQuote, &line);
1888 if (!q)
1889 return PARSE_ERROR;
1891 return AddFile::Parse(line);
1895 AddIfNotFile::Prepare()
1897 // If the test file exists, then skip this action.
1898 if (!NS_taccess(mTestFile.get(), F_OK))
1900 mTestFile = NULL;
1901 return OK;
1904 return AddFile::Prepare();
1908 AddIfNotFile::Execute()
1910 if (!mTestFile)
1911 return OK;
1913 return AddFile::Execute();
1916 void
1917 AddIfNotFile::Finish(int status)
1919 if (!mTestFile)
1920 return;
1922 AddFile::Finish(status);
1925 class PatchIfFile : public PatchFile
1927 public:
1928 PatchIfFile(ArchiveReader& archiveReader);
1930 virtual int Parse(NS_tchar *line);
1931 virtual int Prepare(); // should check for patch file and for checksum here
1932 virtual int Execute();
1933 virtual void Finish(int status);
1935 private:
1936 std::unique_ptr<NS_tchar> mTestFile;
1939 PatchIfFile::PatchIfFile(ArchiveReader& archiveReader):
1940 PatchFile(archiveReader)
1945 PatchIfFile::Parse(NS_tchar *line)
1947 // format "<testfile>" "<patchfile>" "<filetopatch>"
1949 mTestFile.reset(get_full_path(get_valid_path(&line)));
1950 if (!mTestFile)
1951 return PARSE_ERROR;
1953 // consume whitespace between args
1954 NS_tchar *q = mstrtok(kQuote, &line);
1955 if (!q)
1956 return PARSE_ERROR;
1958 return PatchFile::Parse(line);
1962 PatchIfFile::Prepare()
1964 // If the test file does not exist, then skip this action.
1965 if (NS_taccess(mTestFile.get(), F_OK))
1967 mTestFile = nullptr;
1968 return OK;
1971 return PatchFile::Prepare();
1975 PatchIfFile::Execute()
1977 if (!mTestFile)
1978 return OK;
1980 return PatchFile::Execute();
1983 void
1984 PatchIfFile::Finish(int status)
1986 if (!mTestFile)
1987 return;
1989 PatchFile::Finish(status);
1992 //-----------------------------------------------------------------------------
1994 #ifdef _WIN32
1997 * Launch the post update application (helper.exe). It takes in the path of the
1998 * callback application to calculate the path of helper.exe. For service updates
1999 * this is called from both the system account and the current user account.
2001 * @param installationDir The path to the callback application binary.
2002 * @param updateInfoDir The directory where update info is stored.
2003 * @return true if there was no error starting the process.
2005 bool
2006 LaunchWinPostProcess(const WCHAR *installationDir,
2007 const WCHAR *updateInfoDir)
2009 WCHAR workingDirectory[MAX_PATH + 1] = { L'\0' };
2010 wcsncpy(workingDirectory, installationDir, MAX_PATH);
2012 // TODO: moggi: needs adaption for LibreOffice
2013 // Most likely we don't have the helper method yet. Check if we really need it.
2015 // Launch helper.exe to perform post processing (e.g. registry and log file
2016 // modifications) for the update.
2017 WCHAR inifile[MAX_PATH + 1] = { L'\0' };
2018 wcsncpy(inifile, installationDir, MAX_PATH);
2019 if (!PathAppendSafe(inifile, L"updater.ini"))
2021 return false;
2024 WCHAR exefile[MAX_PATH + 1];
2025 WCHAR exearg[MAX_PATH + 1];
2026 WCHAR exeasync[10];
2027 bool async = true;
2028 if (!GetPrivateProfileStringW(L"PostUpdateWin", L"ExeRelPath", nullptr,
2029 exefile, MAX_PATH + 1, inifile))
2031 return false;
2034 if (!GetPrivateProfileStringW(L"PostUpdateWin", L"ExeArg", nullptr, exearg,
2035 MAX_PATH + 1, inifile))
2037 return false;
2040 if (!GetPrivateProfileStringW(L"PostUpdateWin", L"ExeAsync", L"TRUE",
2041 exeasync,
2042 sizeof(exeasync)/sizeof(exeasync[0]),
2043 inifile))
2045 return false;
2048 // Verify that exeFile doesn't contain relative paths
2049 if (wcsstr(exefile, L"..") != nullptr)
2051 return false;
2054 WCHAR exefullpath[MAX_PATH + 1] = { L'\0' };
2055 wcsncpy(exefullpath, installationDir, MAX_PATH);
2056 if (!PathAppendSafe(exefullpath, exefile))
2058 return false;
2061 #if !defined(TEST_UPDATER) && defined(MAINTENANCE_SERVICE)
2062 if (sUsingService &&
2063 !DoesBinaryMatchAllowedCertificates(installationDir, exefullpath))
2065 return false;
2067 #endif
2069 WCHAR dlogFile[MAX_PATH + 1];
2070 if (!PathGetSiblingFilePath(dlogFile, exefullpath, L"uninstall.update"))
2072 return false;
2075 WCHAR slogFile[MAX_PATH + 1] = { L'\0' };
2076 wcsncpy(slogFile, updateInfoDir, MAX_PATH);
2077 if (!PathAppendSafe(slogFile, L"update.log"))
2079 return false;
2082 WCHAR dummyArg[14] = { L'\0' };
2083 wcsncpy(dummyArg, L"argv0ignored ", sizeof(dummyArg) / sizeof(dummyArg[0]) - 1);
2085 size_t len = wcslen(exearg) + wcslen(dummyArg);
2086 WCHAR *cmdline = (WCHAR *) malloc((len + 1) * sizeof(WCHAR));
2087 if (!cmdline)
2089 return false;
2092 wcsncpy(cmdline, dummyArg, len);
2093 wcscat(cmdline, exearg);
2095 if (sUsingService ||
2096 !_wcsnicmp(exeasync, L"false", 6) ||
2097 !_wcsnicmp(exeasync, L"0", 2))
2099 async = false;
2102 // We want to launch the post update helper app to update the Windows
2103 // registry even if there is a failure with removing the uninstall.update
2104 // file or copying the update.log file.
2105 CopyFileW(slogFile, dlogFile, false);
2107 STARTUPINFOW si = {sizeof(si), 0};
2108 si.lpDesktop = L"";
2109 PROCESS_INFORMATION pi = {0};
2111 bool ok = CreateProcessW(exefullpath,
2112 cmdline,
2113 nullptr, // no special security attributes
2114 nullptr, // no special thread attributes
2115 false, // don't inherit filehandles
2116 0, // No special process creation flags
2117 nullptr, // inherit my environment
2118 workingDirectory,
2119 &si,
2120 &pi);
2121 free(cmdline);
2122 if (ok)
2124 if (!async)
2126 WaitForSingleObject(pi.hProcess, INFINITE);
2128 CloseHandle(pi.hProcess);
2129 CloseHandle(pi.hThread);
2131 return ok;
2134 #endif
2136 static void
2137 LaunchCallbackApp(const NS_tchar *workingDir,
2138 int argc,
2139 NS_tchar **argv,
2140 bool usingService)
2142 putenv(const_cast<char*>("NO_EM_RESTART="));
2143 putenv(const_cast<char*>("MOZ_LAUNCHED_CHILD=1"));
2145 // Run from the specified working directory (see bug 312360). This is not
2146 // necessary on Windows CE since the application that launches the updater
2147 // passes the working directory as an --environ: command line argument.
2148 if (NS_tchdir(workingDir) != 0)
2150 LOG(("Warning: chdir failed"));
2153 #if defined(USE_EXECV)
2154 (void) argc;
2155 (void) usingService; // avoid warnings
2156 execv(argv[0], argv);
2157 #elif defined(MACOSX)
2158 LaunchChild(argc, (const char**)argv);
2159 #elif defined(WNT)
2160 // Do not allow the callback to run when running an update through the
2161 // service as session 0. The unelevated updater.exe will do the launching.
2162 if (!usingService)
2164 WinLaunchChild(argv[0], argc, argv, nullptr);
2166 #else
2167 # warning "Need implementation of LaunchCallbackApp"
2168 #endif
2171 static bool
2172 WriteStatusFile(const char* aStatus)
2174 NS_tchar filename[MAXPATHLEN] = {NS_T('\0')};
2175 #if defined(_WIN32)
2176 // The temp file is not removed on failure since there is client code that
2177 // will remove it.
2178 GetTempFileNameW(gPatchDirPath, L"sta", 0, filename);
2179 #else
2180 NS_tsnprintf(filename, sizeof(filename)/sizeof(filename[0]),
2181 NS_T("%s/update.status"), gPatchDirPath);
2182 #endif
2184 // Make sure that the directory for the update status file exists
2185 if (ensure_parent_dir(filename))
2186 return false;
2188 // This is scoped to make the AutoFile close the file so it is possible to
2189 // move the temp file to the update.status file on Windows.
2191 AutoFile file(NS_tfopen(filename, NS_T("wb+")));
2192 if (file == nullptr)
2194 return false;
2197 if (fwrite(aStatus, strlen(aStatus), 1, file) != 1)
2199 return false;
2203 #if defined(_WIN32)
2204 NS_tchar dstfilename[MAXPATHLEN] = {NS_T('\0')};
2205 NS_tsnprintf(dstfilename, sizeof(dstfilename)/sizeof(dstfilename[0]),
2206 NS_T("%s\\update.status"), gPatchDirPath);
2207 if (MoveFileExW(filename, dstfilename, MOVEFILE_REPLACE_EXISTING) == 0)
2209 return false;
2211 #endif
2213 return true;
2216 static void
2217 WriteStatusFile(int status)
2219 const char *text;
2221 char buf[32];
2222 if (status == OK)
2224 if (sStagedUpdate)
2226 text = "applied\n";
2228 else
2230 text = "succeeded\n";
2233 else
2235 snprintf(buf, sizeof(buf)/sizeof(buf[0]), "failed: %d\n", status);
2236 text = buf;
2239 WriteStatusFile(text);
2242 #ifdef MAINTENANCE_SERVICE
2244 * Read the update.status file and sets isPendingService to true if
2245 * the status is set to pending-service.
2247 * @param isPendingService Out parameter for specifying if the status
2248 * is set to pending-service or not.
2249 * @return true if the information was retrieved and it is pending
2250 * or pending-service.
2252 static bool
2253 IsUpdateStatusPendingService()
2255 NS_tchar filename[MAXPATHLEN];
2256 NS_tsnprintf(filename, sizeof(filename)/sizeof(filename[0]),
2257 NS_T("%s/update.status"), gPatchDirPath);
2259 AutoFile file(NS_tfopen(filename, NS_T("rb")));
2260 if (file == nullptr)
2261 return false;
2263 char buf[32] = { 0 };
2264 fread(buf, sizeof(buf), 1, file);
2266 const char kPendingService[] = "pending-service";
2267 const char kAppliedService[] = "applied-service";
2269 return (strncmp(buf, kPendingService,
2270 sizeof(kPendingService) - 1) == 0) ||
2271 (strncmp(buf, kAppliedService,
2272 sizeof(kAppliedService) - 1) == 0);
2274 #endif
2276 #ifdef _WIN32
2278 * Read the update.status file and sets isSuccess to true if
2279 * the status is set to succeeded.
2281 * @param isSucceeded Out parameter for specifying if the status
2282 * is set to succeeded or not.
2283 * @return true if the information was retrieved and it is succeeded.
2285 static bool
2286 IsUpdateStatusSucceeded(bool &isSucceeded)
2288 isSucceeded = false;
2289 NS_tchar filename[MAXPATHLEN];
2290 NS_tsnprintf(filename, sizeof(filename)/sizeof(filename[0]),
2291 NS_T("%s/update.status"), gPatchDirPath);
2293 AutoFile file(NS_tfopen(filename, NS_T("rb")));
2294 if (file == nullptr)
2295 return false;
2297 char buf[32] = { 0 };
2298 fread(buf, sizeof(buf), 1, file);
2300 const char kSucceeded[] = "succeeded";
2301 isSucceeded = strncmp(buf, kSucceeded,
2302 sizeof(kSucceeded) - 1) == 0;
2303 return true;
2305 #endif
2308 * Copy the entire contents of the application installation directory to the
2309 * destination directory for the update process.
2311 * @return 0 if successful, an error code otherwise.
2313 static int
2314 CopyInstallDirToDestDir()
2316 // These files should not be copied over to the updated app
2317 #ifdef _WIN32
2318 #define SKIPLIST_COUNT 4
2319 #elif defined(MACOSX)
2320 #define SKIPLIST_COUNT 1
2321 #else
2322 #define SKIPLIST_COUNT 3
2323 #endif
2324 copy_recursive_skiplist<SKIPLIST_COUNT> skiplist;
2326 std::unique_ptr<NS_tchar> pUserProfile(new NS_tchar[MAXPATHLEN]);
2327 NS_tstrcpy(pUserProfile.get(), gPatchDirPath);
2328 NS_tchar *slash = (NS_tchar *) NS_tstrrchr(pUserProfile.get(), NS_T('/'));
2329 if (slash)
2330 *slash = NS_T('\0');
2332 LOG(("ignore user profile directory during copy: " LOG_S, pUserProfile.get()));
2334 skiplist.append(0, pUserProfile.get());
2335 #ifndef MACOSX
2336 skiplist.append(1, gInstallDirPath, NS_T("updated"));
2337 skiplist.append(2, gInstallDirPath, NS_T("updates/0"));
2338 #ifdef _WIN32
2339 skiplist.append(4, gInstallDirPath, NS_T("updated.update_in_progress.lock"));
2340 #endif
2341 #endif
2343 return ensure_copy_recursive(gInstallDirPath, gWorkingDirPath, skiplist);
2347 * Replace the application installation directory with the destination
2348 * directory in order to finish a staged update task
2350 * @return 0 if successful, an error code otherwise.
2352 static int
2353 ProcessReplaceRequest()
2355 // TODO: moggi: handle the user profile in the installation dir also
2356 // during the replacement request
2357 // The replacement algorithm is like this:
2358 // 1. Move destDir to tmpDir. In case of failure, abort.
2359 // 2. Move newDir to destDir. In case of failure, revert step 1 and abort.
2360 // 3. Delete tmpDir (or defer it to the next reboot).
2362 #ifdef MACOSX
2363 NS_tchar destDir[MAXPATHLEN];
2364 NS_tsnprintf(destDir, sizeof(destDir)/sizeof(destDir[0]),
2365 NS_T("%s/Contents"), gInstallDirPath);
2366 #elif defined(WNT)
2367 // Windows preserves the case of the file/directory names. We use the
2368 // GetLongPathName API in order to get the correct case for the directory
2369 // name, so that if the user has used a different case when launching the
2370 // application, the installation directory's name does not change.
2371 NS_tchar destDir[MAXPATHLEN];
2372 if (!GetLongPathNameW(gInstallDirPath, destDir,
2373 sizeof(destDir)/sizeof(destDir[0])))
2375 return NO_INSTALLDIR_ERROR;
2377 #else
2378 NS_tchar* destDir = gInstallDirPath;
2379 #endif
2381 NS_tchar tmpDir[MAXPATHLEN];
2382 NS_tsnprintf(tmpDir, sizeof(tmpDir)/sizeof(tmpDir[0]),
2383 NS_T("%s.bak"), destDir);
2385 // First try to remove the possibly existing temp directory, because if this
2386 // directory exists, we will fail to rename destDir.
2387 // No need to error check here because if this fails, we will fail in the
2388 // next step anyways.
2389 ensure_remove_recursive(tmpDir);
2391 LOG(("Begin moving destDir (" LOG_S ") to tmpDir (" LOG_S ")",
2392 destDir, tmpDir));
2393 LogFlush();
2394 int rv = rename_file(destDir, tmpDir, true);
2395 #ifdef _WIN32
2396 // On Windows, if Firefox is launched using the shortcut, it will hold a handle
2397 // to its installation directory open, which might not get released in time.
2398 // Therefore we wait a little bit here to see if the handle is released.
2399 // If it's not released, we just fail to perform the replace request.
2400 const int max_retries = 10;
2401 int retries = 0;
2402 while (rv == WRITE_ERROR && (retries++ < max_retries))
2404 LOG(("PerformReplaceRequest: destDir rename attempt %d failed. " \
2405 "File: " LOG_S ". Last error: %d, err: %d", retries,
2406 destDir, GetLastError(), rv));
2408 Sleep(100);
2410 rv = rename_file(destDir, tmpDir, true);
2412 #endif
2413 if (rv)
2415 // The status file will have 'pending' written to it so there is no value in
2416 // returning an error specific for this failure.
2417 LOG(("Moving destDir to tmpDir failed, err: %d", rv));
2418 return rv;
2421 NS_tchar newDir[MAXPATHLEN];
2422 if (is_userprofile_in_instdir())
2424 LOG(("user profile in instdir"));
2425 NS_tstrcpy(newDir, tmpDir);
2426 NS_tstrcat(newDir, gWorkingDirPath + NS_tstrlen(gInstallDirPath));
2427 LOG((LOG_S, newDir));
2429 else
2431 NS_tsnprintf(newDir, sizeof(newDir)/sizeof(newDir[0]),
2432 NS_T("%s"),
2433 gWorkingDirPath);
2436 LOG(("Begin moving newDir (" LOG_S ") to destDir (" LOG_S ")",
2437 newDir, destDir));
2438 rv = rename_file(newDir, destDir, true);
2439 #ifdef MACOSX
2440 if (rv)
2442 LOG(("Moving failed. Begin copying newDir (" LOG_S ") to destDir (" LOG_S ")",
2443 newDir, destDir));
2444 copy_recursive_skiplist<0> skiplist;
2445 rv = ensure_copy_recursive(newDir, destDir, skiplist);
2447 #endif
2448 if (rv)
2450 LOG(("Moving newDir to destDir failed, err: %d", rv));
2451 LOG(("Now, try to move tmpDir back to destDir"));
2452 ensure_remove_recursive(destDir);
2453 int rv2 = rename_file(tmpDir, destDir, true);
2454 if (rv2)
2456 LOG(("Moving tmpDir back to destDir failed, err: %d", rv2));
2458 // The status file will be have 'pending' written to it so there is no value
2459 // in returning an error specific for this failure.
2460 return rv;
2463 if (is_userprofile_in_instdir())
2465 // 1.) calculate path of the user profile in the backup directory
2466 // 2.) move the user profile from the backup to the install directory
2467 NS_tchar backup_user_profile[MAXPATHLEN];
2468 NS_tchar userprofile[MAXPATHLEN];
2470 NS_tstrcpy(userprofile, gPatchDirPath);
2471 NS_tchar* slash = (NS_tchar *) NS_tstrrchr(userprofile, NS_T('/'));
2472 if (slash)
2473 *slash = NS_T('\0');
2474 NS_tstrcpy(backup_user_profile, tmpDir);
2475 size_t installdir_len = NS_tstrlen(destDir);
2477 NS_tstrcat(backup_user_profile, userprofile + installdir_len);
2478 LOG(("copy user profile back from " LOG_S " to " LOG_S, backup_user_profile, userprofile));
2479 int rv2 = rename_file(backup_user_profile, userprofile);
2480 if (rv2)
2482 LOG(("failed to copy user profile back"));
2484 if (slash)
2485 *slash = NS_T('/');
2488 #if !defined(_WIN32) && !defined(MACOSX)
2489 // Platforms that have their updates directory in the installation directory
2490 // need to have the last-update.log and backup-update.log files moved from the
2491 // old installation directory to the new installation directory.
2492 NS_tchar tmpLog[MAXPATHLEN];
2493 NS_tsnprintf(tmpLog, sizeof(tmpLog)/sizeof(tmpLog[0]),
2494 NS_T("%s/updates/last-update.log"), tmpDir);
2495 if (!NS_taccess(tmpLog, F_OK))
2497 NS_tchar destLog[MAXPATHLEN];
2498 NS_tsnprintf(destLog, sizeof(destLog)/sizeof(destLog[0]),
2499 NS_T("%s/updates/last-update.log"), destDir);
2500 NS_tremove(destLog);
2501 NS_trename(tmpLog, destLog);
2503 #endif
2505 LOG(("Now, remove the tmpDir"));
2506 rv = ensure_remove_recursive(tmpDir, true);
2507 if (rv)
2509 LOG(("Removing tmpDir failed, err: %d", rv));
2510 #ifdef _WIN32
2511 NS_tchar deleteDir[MAXPATHLEN];
2512 NS_tsnprintf(deleteDir, sizeof(deleteDir)/sizeof(deleteDir[0]),
2513 NS_T("%s\\%s"), destDir, DELETE_DIR);
2514 // Attempt to remove the tobedeleted directory and then recreate it if it
2515 // was successfully removed.
2516 _wrmdir(deleteDir);
2517 if (NS_taccess(deleteDir, F_OK))
2519 NS_tmkdir(deleteDir, 0755);
2521 remove_recursive_on_reboot(tmpDir, deleteDir);
2522 #endif
2525 #ifdef MACOSX
2526 // On OS X, we need to remove the staging directory after its Contents
2527 // directory has been moved.
2528 NS_tchar updatedAppDir[MAXPATHLEN];
2529 NS_tsnprintf(updatedAppDir, sizeof(updatedAppDir)/sizeof(updatedAppDir[0]),
2530 NS_T("%s/Updated.app"), gPatchDirPath);
2531 ensure_remove_recursive(updatedAppDir);
2532 #endif
2534 gSucceeded = true;
2536 return 0;
2539 #ifdef _WIN32
2540 static void
2541 WaitForServiceFinishThread(void* /*param*/)
2543 // We wait at most 10 minutes, we already waited 5 seconds previously
2544 // before deciding to show this UI.
2545 WaitForServiceStop(SVC_NAME, 595);
2546 QuitProgressUI();
2548 #endif
2550 #ifdef VERIFY_MAR_SIGNATURE
2552 * This function reads in the ACCEPTED_MAR_CHANNEL_IDS from update-settings.ini
2554 * @param path The path to the ini file that is to be read
2555 * @param results A pointer to the location to store the read strings
2556 * @return OK on success
2558 static int
2559 ReadMARChannelIDs(const NS_tchar *path, MARChannelStringTable *results)
2561 // TODO: moggi: needs adaption for LibreOffice
2562 // Check where this function gets its parameters from
2563 const unsigned int kNumStrings = 1;
2564 const char *kUpdaterKeys = "ACCEPTED_MAR_CHANNEL_IDS\0";
2565 char updater_strings[kNumStrings][MAX_TEXT_LEN];
2567 int result = ReadStrings(path, kUpdaterKeys, kNumStrings,
2568 updater_strings, "Settings");
2570 strncpy(results->MARChannelID, updater_strings[0], MAX_TEXT_LEN - 1);
2571 results->MARChannelID[MAX_TEXT_LEN - 1] = 0;
2573 return result;
2575 #endif
2577 static int
2578 GetUpdateFileNames(std::vector<tstring>& fileNames)
2580 NS_tchar fileName[MAXPATHLEN];
2581 NS_tsnprintf(fileName, MAXPATHLEN,
2582 NS_T("%s/update.mar"), gPatchDirPath);
2583 fileNames.push_back(fileName);
2585 // add the language packs
2586 NS_tDIR* dir = NS_topendir(gPatchDirPath);
2587 if (!dir)
2589 LOG(("Could not open directory " LOG_S, gPatchDirPath));
2590 return READ_ERROR;
2593 NS_tdirent* entry;
2594 while ((entry = NS_treaddir(dir)) != nullptr)
2596 if (NS_tstrcmp(entry->d_name, NS_T(".")) &&
2597 NS_tstrcmp(entry->d_name, NS_T("..")) &&
2598 NS_tstrcmp(entry->d_name, NS_T("update.mar")))
2600 if (NS_tstrncmp(entry->d_name, NS_T("update"), 6) == 0)
2602 NS_tchar *dot = NS_tstrrchr(entry->d_name, NS_T('.'));
2603 if (dot && !NS_tstrcmp(dot, NS_T(".mar")))
2605 NS_tchar updatePath[MAXPATHLEN];
2606 NS_tsnprintf(updatePath, sizeof(updatePath)/sizeof(updatePath[0]),
2607 NS_T("%s/%s"), gPatchDirPath, entry->d_name);
2609 LOG (("Found language update file: " LOG_S, updatePath));
2610 fileNames.push_back(updatePath);
2615 return OK;
2618 static int
2619 CheckSignature(ArchiveReader& archiveReader)
2621 #ifdef VERIFY_MAR_SIGNATURE
2622 #ifdef _WIN32
2623 HKEY baseKey = nullptr;
2624 wchar_t valueName[] = L"Image Path";
2625 wchar_t rasenh[] = L"rsaenh.dll";
2626 bool reset = false;
2627 if (RegOpenKeyExW(HKEY_LOCAL_MACHINE,
2628 L"SOFTWARE\\Microsoft\\Cryptography\\Defaults\\Provider\\Microsoft Enhanced Cryptographic Provider v1.0",
2629 0, KEY_READ | KEY_WRITE,
2630 &baseKey) == ERROR_SUCCESS)
2632 wchar_t path[MAX_PATH + 1];
2633 DWORD size = sizeof(path);
2634 DWORD type;
2635 if (RegQueryValueExW(baseKey, valueName, 0, &type,
2636 (LPBYTE)path, &size) == ERROR_SUCCESS)
2638 if (type == REG_SZ && wcscmp(path, rasenh) == 0)
2640 wchar_t rasenhFullPath[] = L"%SystemRoot%\\System32\\rsaenh.dll";
2641 if (RegSetValueExW(baseKey, valueName, 0, REG_SZ,
2642 (const BYTE*)rasenhFullPath,
2643 sizeof(rasenhFullPath)) == ERROR_SUCCESS)
2645 reset = true;
2650 #endif
2651 int rv = archiveReader.VerifySignature();
2652 #ifdef _WIN32
2653 if (baseKey)
2655 if (reset)
2657 RegSetValueExW(baseKey, valueName, 0, REG_SZ,
2658 (const BYTE*)rasenh,
2659 sizeof(rasenh));
2661 RegCloseKey(baseKey);
2663 #endif
2666 if (rv == OK)
2668 if (rv == OK)
2670 NS_tchar updateSettingsPath[MAX_TEXT_LEN];
2672 // TODO: moggi: needs adaption for LibreOffice
2673 // These paths need to be adapted for us.
2674 NS_tsnprintf(updateSettingsPath,
2675 sizeof(updateSettingsPath) / sizeof(updateSettingsPath[0]),
2676 #ifdef MACOSX
2677 NS_T("%s/Contents/Resources/update-settings.ini"),
2678 #else
2679 NS_T("%s/update-settings.ini"),
2680 #endif
2681 gWorkingDirPath);
2682 MARChannelStringTable MARStrings;
2683 if (ReadMARChannelIDs(updateSettingsPath, &MARStrings) != OK)
2685 // If we can't read from update-settings.ini then we shouldn't impose
2686 // a MAR restriction. Some installations won't even include this file.
2687 MARStrings.MARChannelID[0] = '\0';
2690 rv = archiveReader.VerifyProductInformation(MARStrings.MARChannelID,
2691 LIBO_VERSION_DOTTED);
2694 #endif
2696 return rv;
2699 static void
2700 UpdateThreadFunc(void * /*param*/)
2702 // open ZIP archive and process...
2703 int rv = OK;
2704 if (sReplaceRequest)
2706 rv = ProcessReplaceRequest();
2708 else
2710 std::vector<tstring> fileNames;
2711 GetUpdateFileNames(fileNames);
2713 for (auto& fileName: fileNames)
2715 ArchiveReader archiveReader;
2716 rv = archiveReader.Open(fileName.c_str());
2717 if (rv != OK)
2719 LOG(("Could not open " LOG_S, fileName.c_str()));
2720 break;
2723 rv = CheckSignature(archiveReader);
2724 if (rv != OK)
2726 LOG(("Could not verify the signature of " LOG_S, fileName.c_str()));
2727 break;
2731 if (rv == OK && sStagedUpdate)
2733 rv = CopyInstallDirToDestDir();
2736 if (rv == OK)
2738 for (auto& fileName: fileNames)
2740 ArchiveReader archiveReader;
2741 archiveReader.Open(fileName.c_str());
2742 rv = DoUpdate(archiveReader);
2744 NS_tchar updatingDir[MAXPATHLEN];
2745 NS_tsnprintf(updatingDir, sizeof(updatingDir)/sizeof(updatingDir[0]),
2746 NS_T("%s/updating"), gWorkingDirPath);
2747 ensure_remove_recursive(updatingDir);
2751 if (rv && (sReplaceRequest || sStagedUpdate))
2753 #ifdef _WIN32
2754 // On Windows, the current working directory of the process should be changed
2755 // so that it's not locked.
2756 if (sStagedUpdate)
2758 NS_tchar sysDir[MAX_PATH + 1] = { L'\0' };
2759 if (GetSystemDirectoryW(sysDir, MAX_PATH + 1))
2761 NS_tchdir(sysDir);
2764 #endif
2765 ensure_remove_recursive(gWorkingDirPath);
2766 // When attempting to replace the application, we should fall back
2767 // to non-staged updates in case of a failure. We do this by
2768 // setting the status to pending, exiting the updater, and
2769 // launching the callback application. The callback application's
2770 // startup path will see the pending status, and will start the
2771 // updater application again in order to apply the update without
2772 // staging.
2773 if (sReplaceRequest)
2775 WriteStatusFile(sUsingService ? "pending-service" : "pending");
2777 else
2779 WriteStatusFile(rv);
2781 #ifdef TEST_UPDATER
2782 // Some tests need to use --test-process-updates again.
2783 putenv(const_cast<char*>("MOZ_TEST_PROCESS_UPDATES="));
2784 #endif
2786 else
2788 if (rv)
2790 LOG(("failed: %d", rv));
2792 else
2794 #ifdef MACOSX
2795 // If the update was successful we need to update the timestamp on the
2796 // top-level Mac OS X bundle directory so that Mac OS X's Launch Services
2797 // picks up any major changes when the bundle is updated.
2798 if (!sStagedUpdate && utimes(gInstallDirPath, nullptr) != 0)
2800 LOG(("Couldn't set access/modification time on application bundle."));
2802 #endif
2804 LOG(("succeeded"));
2806 WriteStatusFile(rv);
2809 LOG(("calling QuitProgressUI"));
2810 QuitProgressUI();
2813 #ifdef MACOSX
2814 static void
2815 ServeElevatedUpdateThreadFunc(void* param)
2817 UpdateServerThreadArgs* threadArgs = (UpdateServerThreadArgs*)param;
2818 gSucceeded = ServeElevatedUpdate(threadArgs->argc, threadArgs->argv);
2819 if (!gSucceeded)
2821 WriteStatusFile(ELEVATION_CANCELED);
2823 QuitProgressUI();
2826 void freeArguments(int argc, char** argv)
2828 for (int i = 0; i < argc; i++)
2830 free(argv[i]);
2832 free(argv);
2834 #endif
2836 int LaunchCallbackAndPostProcessApps(int argc, NS_tchar** argv,
2837 int callbackIndex
2838 #ifdef _WIN32
2839 , const WCHAR* elevatedLockFilePath
2840 , HANDLE updateLockFileHandle
2841 #elif defined(MACOSX)
2842 , bool isElevated
2843 #endif
2846 if (argc > callbackIndex)
2848 #if defined(_WIN32)
2849 if (gSucceeded)
2851 if (!LaunchWinPostProcess(gInstallDirPath, gPatchDirPath))
2853 fprintf(stderr, "The post update process was not launched");
2856 // The service update will only be executed if it is already installed.
2857 // For first time installs of the service, the install will happen from
2858 // the PostUpdate process. We do the service update process here
2859 // because it's possible we are updating with updater.exe without the
2860 // service if the service failed to apply the update. We want to update
2861 // the service to a newer version in that case. If we are not running
2862 // through the service, then USING_SERVICE will not exist.
2863 if (!sUsingService)
2865 StartServiceUpdate(gInstallDirPath);
2868 EXIT_WHEN_ELEVATED(elevatedLockFilePath, updateLockFileHandle, 0);
2869 #elif defined(MACOSX)
2870 if (!isElevated)
2872 if (gSucceeded)
2874 LaunchMacPostProcess(gInstallDirPath);
2876 #endif
2878 LaunchCallbackApp(argv[5],
2879 argc - callbackIndex,
2880 argv + callbackIndex,
2881 sUsingService);
2882 #ifdef XP_MACOSX
2883 } // if (!isElevated)
2884 #endif /* XP_MACOSX */
2886 return 0;
2889 int NS_main(int argc, NS_tchar **argv)
2891 // The callback is the remaining arguments starting at callbackIndex.
2892 // The argument specified by callbackIndex is the callback executable and the
2893 // argument prior to callbackIndex is the working directory.
2894 const int callbackIndex = 6;
2896 #ifdef MACOSX
2897 // TODO: moggi: needs adaption for LibreOffice
2898 bool isElevated =
2899 strstr(argv[0], "/Library/PrivilegedHelperTools/org.mozilla.updater") != 0;
2900 if (isElevated)
2902 if (!ObtainUpdaterArguments(&argc, &argv))
2904 // Won't actually get here because ObtainUpdaterArguments will terminate
2905 // the current process on failure.
2906 return 1;
2909 #endif
2911 #if defined(VERIFY_MAR_SIGNATURE) && !defined(_WIN32) && !defined(MACOSX)
2912 // On Windows and Mac we rely on native APIs to do verifications so we don't
2913 // need to initialize NSS at all there.
2914 // Otherwise, minimize the amount of NSS we depend on by avoiding all the NSS
2915 // databases.
2916 if (NSS_NoDB_Init(NULL) != SECSuccess)
2918 PRErrorCode error = PR_GetError();
2919 fprintf(stderr, "Could not initialize NSS: %s (%d)",
2920 PR_ErrorToName(error), (int) error);
2921 _exit(1);
2923 #endif
2925 #ifdef MACOSX
2926 if (!isElevated)
2928 #endif
2929 InitProgressUI(&argc, &argv);
2930 #ifdef MACOSX
2932 #endif
2934 // To process an update the updater command line must at a minimum have the
2935 // directory path containing the updater.mar file to process as the first
2936 // argument, the install directory as the second argument, and the directory
2937 // to apply the update to as the third argument. When the updater is launched
2938 // by another process the PID of the parent process should be provided in the
2939 // optional fourth argument and the updater will wait on the parent process to
2940 // exit if the value is non-zero and the process is present. This is necessary
2941 // due to not being able to update files that are in use on Windows. The
2942 // optional fifth argument is the callback's working directory and the
2943 // optional sixth argument is the callback path. The callback is the
2944 // application to launch after updating and it will be launched when these
2945 // arguments are provided whether the update was successful or not. All
2946 // remaining arguments are optional and are passed to the callback when it is
2947 // launched.
2948 if (argc < 4)
2950 fprintf(stderr, "Usage: updater patch-dir install-dir apply-to-dir [wait-pid [callback-working-dir callback-path args...]]\n");
2951 #ifdef MACOSX
2952 if (isElevated)
2954 freeArguments(argc, argv);
2955 CleanupElevatedMacUpdate(true);
2957 #endif
2958 return 1;
2961 // The directory containing the update information.
2962 gPatchDirPath = argv[1];
2964 // The directory we're going to update to.
2965 // We copy this string because we need to remove trailing slashes. The C++
2966 // standard says that it's always safe to write to strings pointed to by argv
2967 // elements, but I don't necessarily believe it.
2968 NS_tstrncpy(gInstallDirPath, argv[2], MAXPATHLEN);
2969 gInstallDirPath[MAXPATHLEN - 1] = NS_T('\0');
2970 NS_tchar *slash = NS_tstrrchr(gInstallDirPath, NS_SLASH);
2971 if (slash && !slash[1])
2973 *slash = NS_T('\0');
2976 #ifdef _WIN32
2977 bool useService = false;
2978 bool testOnlyFallbackKeyExists = false;
2979 bool noServiceFallback = false;
2981 // We never want the service to be used unless we build with
2982 // the maintenance service.
2983 #ifdef MAINTENANCE_SERVICE
2984 useService = IsUpdateStatusPendingService();
2985 // Our tests run with a different apply directory for each test.
2986 // We use this registry key on our test slaves to store the
2987 // allowed name/issuers.
2988 testOnlyFallbackKeyExists = DoesFallbackKeyExist();
2989 #endif
2991 // Remove everything except close window from the context menu
2993 // TODO: moggi: needs adaption for LibreOffice
2994 HKEY hkApp = nullptr;
2995 RegCreateKeyExW(HKEY_CURRENT_USER, L"Software\\Classes\\Applications",
2996 0, nullptr, REG_OPTION_NON_VOLATILE, KEY_SET_VALUE, nullptr,
2997 &hkApp, nullptr);
2998 RegCloseKey(hkApp);
2999 if (RegCreateKeyExW(HKEY_CURRENT_USER,
3000 L"Software\\Classes\\Applications\\updater.exe",
3001 0, nullptr, REG_OPTION_VOLATILE, KEY_SET_VALUE, nullptr,
3002 &hkApp, nullptr) == ERROR_SUCCESS)
3004 RegSetValueExW(hkApp, L"IsHostApp", 0, REG_NONE, 0, 0);
3005 RegSetValueExW(hkApp, L"NoOpenWith", 0, REG_NONE, 0, 0);
3006 RegSetValueExW(hkApp, L"NoStartPage", 0, REG_NONE, 0, 0);
3007 RegCloseKey(hkApp);
3010 #endif
3012 // If there is a PID specified and it is not '0' then wait for the process to exit.
3013 #ifdef _WIN32
3014 __int64 pid = 0;
3015 #else
3016 int pid = 0;
3017 #endif
3018 if (argc > 4)
3020 #ifdef _WIN32
3021 pid = _wtoi64(argv[4]);
3022 #else
3023 pid = atoi(argv[4]);
3024 #endif
3025 if (pid == -1)
3027 // This is a signal from the parent process that the updater should stage
3028 // the update.
3029 sStagedUpdate = true;
3031 else if (NS_tstrstr(argv[4], NS_T("/replace")))
3033 // We're processing a request to replace the application with a staged
3034 // update.
3035 sReplaceRequest = true;
3039 // The directory we're going to update to.
3040 // We copy this string because we need to remove trailing slashes. The C++
3041 // standard says that it's always safe to write to strings pointed to by argv
3042 // elements, but I don't necessarily believe it.
3043 NS_tstrncpy(gWorkingDirPath, argv[3], MAXPATHLEN);
3044 gWorkingDirPath[MAXPATHLEN - 1] = NS_T('\0');
3045 slash = NS_tstrrchr(gWorkingDirPath, NS_SLASH);
3046 if (slash && !slash[1])
3048 *slash = NS_T('\0');
3051 #ifdef MACOSX
3052 if (!isElevated && !IsRecursivelyWritable(argv[2]))
3054 // If the app directory isn't recursively writeable, an elevated update is
3055 // required.
3056 UpdateServerThreadArgs threadArgs;
3057 threadArgs.argc = argc;
3058 threadArgs.argv = const_cast<const NS_tchar**>(argv);
3060 Thread t1;
3061 if (t1.Run(ServeElevatedUpdateThreadFunc, &threadArgs) == 0)
3063 // Show an indeterminate progress bar while an elevated update is in
3064 // progress.
3065 ShowProgressUI(true);
3067 t1.Join();
3069 LaunchCallbackAndPostProcessApps(argc, argv, callbackIndex, false);
3070 return gSucceeded ? 0 : 1;
3072 #endif
3074 LogInit(gPatchDirPath, NS_T("update.log"));
3076 if (!WriteStatusFile("applying"))
3078 LOG(("failed setting status to 'applying'"));
3079 #ifdef MACOSX
3080 if (isElevated)
3082 freeArguments(argc, argv);
3083 CleanupElevatedMacUpdate(true);
3085 #endif
3086 return 1;
3089 if (sStagedUpdate)
3091 LOG(("Performing a staged update"));
3093 else if (sReplaceRequest)
3095 LOG(("Performing a replace request"));
3098 LOG(("PATCH DIRECTORY " LOG_S, gPatchDirPath));
3099 LOG(("INSTALLATION DIRECTORY " LOG_S, gInstallDirPath));
3100 LOG(("WORKING DIRECTORY " LOG_S, gWorkingDirPath));
3102 #ifdef _WIN32
3103 if (_wcsnicmp(gWorkingDirPath, gInstallDirPath, MAX_PATH) != 0)
3105 if (!sStagedUpdate && !sReplaceRequest)
3107 WriteStatusFile(INVALID_APPLYTO_DIR_ERROR);
3108 LOG(("Installation directory and working directory must be the same "
3109 "for non-staged updates. Exiting."));
3110 LogFinish();
3111 return 1;
3114 NS_tchar workingDirParent[MAX_PATH];
3115 NS_tsnprintf(workingDirParent,
3116 sizeof(workingDirParent) / sizeof(workingDirParent[0]),
3117 NS_T("%s"), gWorkingDirPath);
3118 if (!PathRemoveFileSpecW(workingDirParent))
3120 WriteStatusFile(REMOVE_FILE_SPEC_ERROR);
3121 LOG(("Error calling PathRemoveFileSpecW: %d", GetLastError()));
3122 LogFinish();
3123 return 1;
3126 if (_wcsnicmp(workingDirParent, gInstallDirPath, MAX_PATH) != 0)
3128 WriteStatusFile(INVALID_APPLYTO_DIR_STAGED_ERROR);
3129 LOG(("The apply-to directory must be the same as or "
3130 "a child of the installation directory! Exiting."));
3131 LogFinish();
3132 return 1;
3135 #endif
3138 #ifdef _WIN32
3139 if (pid > 0)
3141 HANDLE parent = OpenProcess(SYNCHRONIZE, false, (DWORD) pid);
3142 // May return nullptr if the parent process has already gone away.
3143 // Otherwise, wait for the parent process to exit before starting the
3144 // update.
3145 if (parent)
3147 DWORD waitTime = PARENT_WAIT;
3148 DWORD result = WaitForSingleObject(parent, waitTime);
3149 CloseHandle(parent);
3150 if (result != WAIT_OBJECT_0)
3151 return 1;
3154 #else
3155 if (pid > 0)
3156 waitpid(pid, nullptr, 0);
3157 #endif
3159 #if defined(_WIN32)
3160 #ifdef MAINTENANCE_SERVICE
3161 sUsingService = EnvHasValue("USING_SERVICE");
3162 putenv(const_cast<char*>("USING_SERVICE="));
3163 #endif
3164 // lastFallbackError keeps track of the last error for the service not being
3165 // used, in case of an error when fallback is not enabled we write the
3166 // error to the update.status file.
3167 // When fallback is disabled (MOZ_NO_SERVICE_FALLBACK does not exist) then
3168 // we will instead fallback to not using the service and display a UAC prompt.
3169 int lastFallbackError = FALLBACKKEY_UNKNOWN_ERROR;
3171 // Launch a second instance of the updater with the runas verb on Windows
3172 // when write access is denied to the installation directory.
3173 HANDLE updateLockFileHandle = INVALID_HANDLE_VALUE;
3174 NS_tchar elevatedLockFilePath[MAXPATHLEN] = {NS_T('\0')};
3175 if (!sUsingService &&
3176 (argc > callbackIndex || sStagedUpdate || sReplaceRequest))
3178 NS_tchar updateLockFilePath[MAXPATHLEN];
3179 if (sStagedUpdate)
3181 // When staging an update, the lock file is:
3182 // <install_dir>\updated.update_in_progress.lock
3183 NS_tsnprintf(updateLockFilePath,
3184 sizeof(updateLockFilePath)/sizeof(updateLockFilePath[0]),
3185 NS_T("%s/updated.update_in_progress.lock"), gInstallDirPath);
3187 else if (sReplaceRequest)
3189 // When processing a replace request, the lock file is:
3190 // <install_dir>\..\moz_update_in_progress.lock
3191 NS_tchar installDir[MAXPATHLEN];
3192 NS_tstrcpy(installDir, gInstallDirPath);
3193 NS_tchar *slash = (NS_tchar *) NS_tstrrchr(installDir, NS_SLASH);
3194 *slash = NS_T('\0');
3195 NS_tsnprintf(updateLockFilePath,
3196 sizeof(updateLockFilePath)/sizeof(updateLockFilePath[0]),
3197 NS_T("%s\\moz_update_in_progress.lock"), installDir);
3199 else
3201 // In the non-staging update case, the lock file is:
3202 // <install_dir>\<app_name>.exe.update_in_progress.lock
3203 NS_tsnprintf(updateLockFilePath,
3204 sizeof(updateLockFilePath)/sizeof(updateLockFilePath[0]),
3205 NS_T("%s.update_in_progress.lock"), argv[callbackIndex]);
3208 // The update_in_progress.lock file should only exist during an update. In
3209 // case it exists attempt to remove it and exit if that fails to prevent
3210 // simultaneous updates occurring.
3211 if (!_waccess(updateLockFilePath, F_OK) &&
3212 NS_tremove(updateLockFilePath) != 0)
3214 // Try to fall back to the old way of doing updates if a staged
3215 // update fails.
3216 if (sStagedUpdate || sReplaceRequest)
3218 // Note that this could fail, but if it does, there isn't too much we
3219 // can do in order to recover anyways.
3220 WriteStatusFile("pending");
3222 LOG(("Update already in progress! Exiting"));
3223 return 1;
3226 updateLockFileHandle = CreateFileW(updateLockFilePath,
3227 GENERIC_READ | GENERIC_WRITE,
3229 nullptr,
3230 OPEN_ALWAYS,
3231 FILE_FLAG_DELETE_ON_CLOSE,
3232 nullptr);
3234 NS_tsnprintf(elevatedLockFilePath,
3235 sizeof(elevatedLockFilePath)/sizeof(elevatedLockFilePath[0]),
3236 NS_T("%s/update_elevated.lock"), gPatchDirPath);
3238 // Even if a file has no sharing access, you can still get its attributes
3239 bool startedFromUnelevatedUpdater =
3240 GetFileAttributesW(elevatedLockFilePath) != INVALID_FILE_ATTRIBUTES;
3242 // If we're running from the service, then we were started with the same
3243 // token as the service so the permissions are already dropped. If we're
3244 // running from an elevated updater that was started from an unelevated
3245 // updater, then we drop the permissions here. We do not drop the
3246 // permissions on the originally called updater because we use its token
3247 // to start the callback application.
3248 if (startedFromUnelevatedUpdater)
3250 // Disable every privilege we don't need. Processes started using
3251 // CreateProcess will use the same token as this process.
3252 UACHelper::DisablePrivileges(nullptr);
3255 if (updateLockFileHandle == INVALID_HANDLE_VALUE ||
3256 (useService && testOnlyFallbackKeyExists && noServiceFallback))
3258 if (!_waccess(elevatedLockFilePath, F_OK) &&
3259 NS_tremove(elevatedLockFilePath) != 0)
3261 fprintf(stderr, "Unable to create elevated lock file! Exiting\n");
3262 return 1;
3265 HANDLE elevatedFileHandle;
3266 elevatedFileHandle = CreateFileW(elevatedLockFilePath,
3267 GENERIC_READ | GENERIC_WRITE,
3269 nullptr,
3270 OPEN_ALWAYS,
3271 FILE_FLAG_DELETE_ON_CLOSE,
3272 nullptr);
3274 if (elevatedFileHandle == INVALID_HANDLE_VALUE)
3276 LOG(("Unable to create elevated lock file! Exiting"));
3277 return 1;
3280 wchar_t *cmdLine = MakeCommandLine(argc - 1, argv + 1);
3281 if (!cmdLine)
3283 CloseHandle(elevatedFileHandle);
3284 return 1;
3287 // Make sure the path to the updater to use for the update is on local.
3288 // We do this check to make sure that file locking is available for
3289 // race condition security checks.
3290 if (useService)
3292 BOOL isLocal = FALSE;
3293 useService = IsLocalFile(argv[0], isLocal) && isLocal;
3296 // If we have unprompted elevation we should NOT use the service
3297 // for the update. Service updates happen with the SYSTEM account
3298 // which has more privs than we need to update with.
3299 // Windows 8 provides a user interface so users can configure this
3300 // behavior and it can be configured in the registry in all Windows
3301 // versions that support UAC.
3302 if (useService)
3304 BOOL unpromptedElevation;
3305 if (IsUnpromptedElevation(unpromptedElevation))
3307 useService = !unpromptedElevation;
3311 // Make sure the service registry entries for the installation path
3312 // are available. If not don't use the service.
3313 if (useService)
3315 WCHAR maintenanceServiceKey[MAX_PATH + 1];
3316 // TODO: moggi: needs adaption for LibreOffice
3317 // Most likely the registry part is not correct yet
3318 if (CalculateRegistryPathFromFilePath(gInstallDirPath,
3319 maintenanceServiceKey))
3321 HKEY baseKey = nullptr;
3322 if (RegOpenKeyExW(HKEY_LOCAL_MACHINE,
3323 maintenanceServiceKey, 0,
3324 KEY_READ | KEY_WOW64_64KEY,
3325 &baseKey) == ERROR_SUCCESS)
3327 RegCloseKey(baseKey);
3329 else
3331 #ifdef TEST_UPDATER
3332 useService = testOnlyFallbackKeyExists;
3333 #endif
3334 if (!useService)
3336 lastFallbackError = FALLBACKKEY_NOKEY_ERROR;
3340 else
3342 useService = false;
3343 lastFallbackError = FALLBACKKEY_REGPATH_ERROR;
3347 // Originally we used to write "pending" to update.status before
3348 // launching the service command. This is no longer needed now
3349 // since the service command is launched from updater.exe. If anything
3350 // fails in between, we can fall back to using the normal update process
3351 // on our own.
3353 // If we still want to use the service try to launch the service
3354 // command for the update.
3355 if (useService)
3357 // If the update couldn't be started, then set useService to false so
3358 // we do the update the old way.
3359 DWORD ret = LaunchServiceSoftwareUpdateCommand(argc, (LPCWSTR *)argv);
3360 useService = (ret == ERROR_SUCCESS);
3361 // If the command was launched then wait for the service to be done.
3362 if (useService)
3364 bool showProgressUI = false;
3365 // Never show the progress UI when staging updates.
3366 if (!sStagedUpdate)
3368 // We need to call this separately instead of allowing ShowProgressUI
3369 // to initialize the strings because the service will move the
3370 // ini file out of the way when running updater.
3371 showProgressUI = !InitProgressUIStrings();
3374 // Wait for the service to stop for 5 seconds. If the service
3375 // has still not stopped then show an indeterminate progress bar.
3376 DWORD lastState = WaitForServiceStop(SVC_NAME, 5);
3377 if (lastState != SERVICE_STOPPED)
3379 std::thread waitThread(WaitForServiceFinishThread, nullptr);
3380 if (showProgressUI)
3382 ShowProgressUI(true, false);
3384 waitThread.join();
3387 lastState = WaitForServiceStop(SVC_NAME, 1);
3388 if (lastState != SERVICE_STOPPED)
3390 // If the service doesn't stop after 10 minutes there is
3391 // something seriously wrong.
3392 lastFallbackError = FALLBACKKEY_SERVICE_NO_STOP_ERROR;
3393 useService = false;
3396 else
3398 lastFallbackError = FALLBACKKEY_LAUNCH_ERROR;
3402 // If the service can't be used when staging and update, make sure that
3403 // the UAC prompt is not shown! In this case, just set the status to
3404 // pending and the update will be applied during the next startup.
3405 if (!useService && sStagedUpdate)
3407 if (updateLockFileHandle != INVALID_HANDLE_VALUE)
3409 CloseHandle(updateLockFileHandle);
3411 WriteStatusFile("pending");
3412 return 0;
3415 // If we started the service command, and it finished, check the
3416 // update.status file to make sure it succeeded, and if it did
3417 // we need to manually start the PostUpdate process from the
3418 // current user's session of this unelevated updater.exe the
3419 // current process is running as.
3420 // Note that we don't need to do this if we're just staging the update,
3421 // as the PostUpdate step runs when performing the replacing in that case.
3422 if (useService && !sStagedUpdate)
3424 bool updateStatusSucceeded = false;
3425 if (IsUpdateStatusSucceeded(updateStatusSucceeded) &&
3426 updateStatusSucceeded)
3428 if (!LaunchWinPostProcess(gInstallDirPath, gPatchDirPath))
3430 fprintf(stderr, "The post update process which runs as the user"
3431 " for service update could not be launched.");
3436 // If we didn't want to use the service at all, or if an update was
3437 // already happening, or launching the service command failed, then
3438 // launch the elevated updater.exe as we do without the service.
3439 // We don't launch the elevated updater in the case that we did have
3440 // write access all along because in that case the only reason we're
3441 // using the service is because we are testing.
3442 if (!useService && !noServiceFallback &&
3443 updateLockFileHandle == INVALID_HANDLE_VALUE)
3445 SHELLEXECUTEINFO sinfo;
3446 memset(&sinfo, 0, sizeof(SHELLEXECUTEINFO));
3447 sinfo.cbSize = sizeof(SHELLEXECUTEINFO);
3448 sinfo.fMask = SEE_MASK_FLAG_NO_UI |
3449 SEE_MASK_FLAG_DDEWAIT |
3450 SEE_MASK_NOCLOSEPROCESS;
3451 sinfo.hwnd = nullptr;
3452 sinfo.lpFile = argv[0];
3453 sinfo.lpParameters = cmdLine;
3454 sinfo.lpVerb = L"runas";
3455 sinfo.nShow = SW_SHOWNORMAL;
3457 bool result = ShellExecuteEx(&sinfo);
3458 free(cmdLine);
3460 if (result)
3462 WaitForSingleObject(sinfo.hProcess, INFINITE);
3463 CloseHandle(sinfo.hProcess);
3465 else
3467 WriteStatusFile(ELEVATION_CANCELED);
3471 if (argc > callbackIndex)
3473 LaunchCallbackApp(argv[5], argc - callbackIndex,
3474 argv + callbackIndex, sUsingService);
3477 CloseHandle(elevatedFileHandle);
3479 if (!useService && !noServiceFallback &&
3480 INVALID_HANDLE_VALUE == updateLockFileHandle)
3482 // We didn't use the service and we did run the elevated updater.exe.
3483 // The elevated updater.exe is responsible for writing out the
3484 // update.status file.
3485 return 0;
3487 else if (useService)
3489 // The service command was launched. The service is responsible for
3490 // writing out the update.status file.
3491 if (updateLockFileHandle != INVALID_HANDLE_VALUE)
3493 CloseHandle(updateLockFileHandle);
3495 return 0;
3497 else
3499 // Otherwise the service command was not launched at all.
3500 // We are only reaching this code path because we had write access
3501 // all along to the directory and a fallback key existed, and we
3502 // have fallback disabled (MOZ_NO_SERVICE_FALLBACK env var exists).
3503 // We only currently use this env var from XPCShell tests.
3504 CloseHandle(updateLockFileHandle);
3505 WriteStatusFile(lastFallbackError);
3506 return 0;
3510 #endif
3512 if (sStagedUpdate)
3514 // When staging updates, blow away the old installation directory and create
3515 // it from scratch.
3516 ensure_remove_recursive(gWorkingDirPath);
3518 if (!sReplaceRequest)
3520 // Try to create the destination directory if it doesn't exist
3521 int rv = NS_tmkdir(gWorkingDirPath, 0755);
3522 if (rv != OK && errno != EEXIST)
3524 #ifdef MACOSX
3525 if (isElevated)
3527 freeArguments(argc, argv);
3528 CleanupElevatedMacUpdate(true);
3530 #endif
3531 return 1;
3535 #ifdef _WIN32
3536 // For replace requests, we don't need to do any real updates, so this is not
3537 // necessary.
3538 if (!sReplaceRequest)
3540 // Allocate enough space for the length of the path an optional additional
3541 // trailing slash and null termination.
3542 NS_tchar *destpath = (NS_tchar *) malloc((NS_tstrlen(gWorkingDirPath) + 2) * sizeof(NS_tchar));
3543 if (!destpath)
3544 return 1;
3546 NS_tchar *c = destpath;
3547 NS_tstrcpy(c, gWorkingDirPath);
3548 c += NS_tstrlen(gWorkingDirPath);
3549 if (gWorkingDirPath[NS_tstrlen(gWorkingDirPath) - 1] != NS_T('/') &&
3550 gWorkingDirPath[NS_tstrlen(gWorkingDirPath) - 1] != NS_T('\\'))
3552 NS_tstrcat(c, NS_T("/"));
3553 c += NS_tstrlen(NS_T("/"));
3555 *c = NS_T('\0');
3556 c++;
3558 gDestPath = destpath;
3561 NS_tchar applyDirLongPath[MAXPATHLEN];
3562 if (!GetLongPathNameW(gWorkingDirPath, applyDirLongPath,
3563 sizeof(applyDirLongPath)/sizeof(applyDirLongPath[0])))
3565 LOG(("NS_main: unable to find apply to dir: " LOG_S, gWorkingDirPath));
3566 LogFinish();
3567 WriteStatusFile(WRITE_ERROR_APPLY_DIR_PATH);
3568 EXIT_WHEN_ELEVATED(elevatedLockFilePath, updateLockFileHandle, 1);
3569 if (argc > callbackIndex)
3571 LaunchCallbackApp(argv[5], argc - callbackIndex,
3572 argv + callbackIndex, sUsingService);
3574 return 1;
3577 HANDLE callbackFile = INVALID_HANDLE_VALUE;
3578 if (argc > callbackIndex)
3580 // If the callback executable is specified it must exist for a successful
3581 // update. It is important we null out the whole buffer here because later
3582 // we make the assumption that the callback application is inside the
3583 // apply-to dir. If we don't have a fully null'ed out buffer it can lead
3584 // to stack corruption which causes crashes and other problems.
3585 NS_tchar callbackLongPath[MAXPATHLEN];
3586 ZeroMemory(callbackLongPath, sizeof(callbackLongPath));
3587 NS_tchar *targetPath = argv[callbackIndex];
3588 NS_tchar buffer[MAXPATHLEN * 2] = { NS_T('\0') };
3589 size_t bufferLeft = MAXPATHLEN * 2;
3590 if (sReplaceRequest)
3592 // In case of replace requests, we should look for the callback file in
3593 // the destination directory.
3594 size_t commonPrefixLength = PathCommonPrefixW(argv[callbackIndex],
3595 gInstallDirPath,
3596 nullptr);
3597 NS_tchar *p = buffer;
3598 NS_tstrncpy(p, argv[callbackIndex], commonPrefixLength);
3599 p += commonPrefixLength;
3600 bufferLeft -= commonPrefixLength;
3601 NS_tstrncpy(p, gInstallDirPath + commonPrefixLength, bufferLeft);
3603 size_t len = NS_tstrlen(gInstallDirPath + commonPrefixLength);
3604 p += len;
3605 bufferLeft -= len;
3606 *p = NS_T('\\');
3607 ++p;
3608 bufferLeft--;
3609 *p = NS_T('\0');
3610 NS_tchar installDir[MAXPATHLEN];
3611 NS_tstrcpy(installDir, gInstallDirPath);
3612 size_t callbackPrefixLength = PathCommonPrefixW(argv[callbackIndex],
3613 installDir,
3614 nullptr);
3615 NS_tstrncpy(p, argv[callbackIndex] + std::max(callbackPrefixLength,
3616 commonPrefixLength), bufferLeft);
3617 targetPath = buffer;
3619 if (!GetLongPathNameW(targetPath, callbackLongPath,
3620 sizeof(callbackLongPath)/sizeof(callbackLongPath[0])))
3622 LOG(("NS_main: unable to find callback file: " LOG_S, targetPath));
3623 LogFinish();
3624 WriteStatusFile(WRITE_ERROR_CALLBACK_PATH);
3625 EXIT_WHEN_ELEVATED(elevatedLockFilePath, updateLockFileHandle, 1);
3626 if (argc > callbackIndex)
3628 LaunchCallbackApp(argv[5],
3629 argc - callbackIndex,
3630 argv + callbackIndex,
3631 sUsingService);
3633 return 1;
3636 // Doing this is only necessary when we're actually applying a patch.
3637 if (!sReplaceRequest)
3639 int len = NS_tstrlen(applyDirLongPath);
3640 NS_tchar *s = callbackLongPath;
3641 NS_tchar *d = gCallbackRelPath;
3642 // advance to the apply to directory and advance past the trailing backslash
3643 // if present.
3644 s += len;
3645 if (*s == NS_T('\\'))
3646 ++s;
3648 // Copy the string and replace backslashes with forward slashes along the
3649 // way.
3652 if (*s == NS_T('\\'))
3653 *d = NS_T('/');
3654 else
3655 *d = *s;
3656 ++s;
3657 ++d;
3659 while (*s);
3660 *d = NS_T('\0');
3661 ++d;
3663 // Make a copy of the callback executable so it can be read when patching.
3664 NS_tsnprintf(gCallbackBackupPath,
3665 sizeof(gCallbackBackupPath)/sizeof(gCallbackBackupPath[0]),
3666 NS_T("%s" CALLBACK_BACKUP_EXT), argv[callbackIndex]);
3667 NS_tremove(gCallbackBackupPath);
3668 if (!CopyFileW(argv[callbackIndex], gCallbackBackupPath, true))
3670 DWORD copyFileError = GetLastError();
3671 LOG(("NS_main: failed to copy callback file " LOG_S
3672 " into place at " LOG_S, argv[callbackIndex], gCallbackBackupPath));
3673 LogFinish();
3674 if (copyFileError == ERROR_ACCESS_DENIED)
3676 WriteStatusFile(WRITE_ERROR_ACCESS_DENIED);
3678 else
3680 WriteStatusFile(WRITE_ERROR_CALLBACK_APP);
3683 EXIT_WHEN_ELEVATED(elevatedLockFilePath, updateLockFileHandle, 1);
3684 LaunchCallbackApp(argv[callbackIndex],
3685 argc - callbackIndex,
3686 argv + callbackIndex,
3687 sUsingService);
3688 return 1;
3691 // Since the process may be signaled as exited by WaitForSingleObject before
3692 // the release of the executable image try to lock the main executable file
3693 // multiple times before giving up. If we end up giving up, we won't
3694 // fail the update.
3695 const int max_retries = 10;
3696 int retries = 1;
3697 DWORD lastWriteError = 0;
3700 // By opening a file handle without FILE_SHARE_READ to the callback
3701 // executable, the OS will prevent launching the process while it is
3702 // being updated.
3703 callbackFile = CreateFileW(targetPath,
3704 DELETE | GENERIC_WRITE,
3705 // allow delete, rename, and write
3706 FILE_SHARE_DELETE | FILE_SHARE_WRITE,
3707 nullptr, OPEN_EXISTING, 0, nullptr);
3708 if (callbackFile != INVALID_HANDLE_VALUE)
3709 break;
3711 lastWriteError = GetLastError();
3712 LOG(("NS_main: callback app file open attempt %d failed. " \
3713 "File: " LOG_S ". Last error: %d", retries,
3714 targetPath, lastWriteError));
3716 Sleep(100);
3718 while (++retries <= max_retries);
3720 // CreateFileW will fail if the callback executable is already in use.
3721 if (callbackFile == INVALID_HANDLE_VALUE)
3723 // Only fail the update if the last error was not a sharing violation.
3724 if (lastWriteError != ERROR_SHARING_VIOLATION)
3726 LOG(("NS_main: callback app file in use, failed to exclusively open " \
3727 "executable file: " LOG_S, argv[callbackIndex]));
3728 LogFinish();
3729 if (lastWriteError == ERROR_ACCESS_DENIED)
3731 WriteStatusFile(WRITE_ERROR_ACCESS_DENIED);
3733 else
3735 WriteStatusFile(WRITE_ERROR_CALLBACK_APP);
3738 NS_tremove(gCallbackBackupPath);
3739 EXIT_WHEN_ELEVATED(elevatedLockFilePath, updateLockFileHandle, 1);
3740 LaunchCallbackApp(argv[5],
3741 argc - callbackIndex,
3742 argv + callbackIndex,
3743 sUsingService);
3744 return 1;
3746 LOG(("NS_main: callback app file in use, continuing without " \
3747 "exclusive access for executable file: " LOG_S,
3748 argv[callbackIndex]));
3753 // DELETE_DIR is not required when performing a staged update or replace
3754 // request; it can be used during a replace request but then it doesn't
3755 // use gDeleteDirPath.
3756 if (!sStagedUpdate && !sReplaceRequest)
3758 // The directory to move files that are in use to on Windows. This directory
3759 // will be deleted after the update is finished, on OS reboot using
3760 // MoveFileEx if it contains files that are in use, or by the post update
3761 // process after the update finishes. On Windows when performing a normal
3762 // update (e.g. the update is not a staged update and is not a replace
3763 // request) gWorkingDirPath is the same as gInstallDirPath and
3764 // gWorkingDirPath is used because it is the destination directory.
3765 NS_tsnprintf(gDeleteDirPath,
3766 sizeof(gDeleteDirPath) / sizeof(gDeleteDirPath[0]),
3767 NS_T("%s/%s"), gWorkingDirPath, DELETE_DIR);
3769 if (NS_taccess(gDeleteDirPath, F_OK))
3771 NS_tmkdir(gDeleteDirPath, 0755);
3774 #endif /* _WIN32 */
3776 // Run update process on a background thread. ShowProgressUI may return
3777 // before QuitProgressUI has been called, so wait for UpdateThreadFunc to
3778 // terminate. Avoid showing the progress UI when staging an update, or if this
3779 // is an elevated process on OSX.
3780 std::thread t(UpdateThreadFunc, nullptr);
3781 if (!sStagedUpdate && !sReplaceRequest
3782 #ifdef XP_MACOSX
3783 && !isElevated
3784 #endif
3787 ShowProgressUI();
3789 t.join();
3791 #ifdef _WIN32
3792 if (argc > callbackIndex && !sReplaceRequest)
3794 if (callbackFile != INVALID_HANDLE_VALUE)
3796 CloseHandle(callbackFile);
3798 // Remove the copy of the callback executable.
3799 NS_tremove(gCallbackBackupPath);
3802 if (!sStagedUpdate && !sReplaceRequest && _wrmdir(gDeleteDirPath))
3804 LOG(("NS_main: unable to remove directory: " LOG_S ", err: %d",
3805 DELETE_DIR, errno));
3806 // The directory probably couldn't be removed due to it containing files
3807 // that are in use and will be removed on OS reboot. The call to remove the
3808 // directory on OS reboot is done after the calls to remove the files so the
3809 // files are removed first on OS reboot since the directory must be empty
3810 // for the directory removal to be successful. The MoveFileEx call to remove
3811 // the directory on OS reboot will fail if the process doesn't have write
3812 // access to the HKEY_LOCAL_MACHINE registry key but this is ok since the
3813 // installer / uninstaller will delete the directory along with its contents
3814 // after an update is applied, on reinstall, and on uninstall.
3815 if (MoveFileEx(gDeleteDirPath, nullptr, MOVEFILE_DELAY_UNTIL_REBOOT))
3817 LOG(("NS_main: directory will be removed on OS reboot: " LOG_S,
3818 DELETE_DIR));
3820 else
3822 LOG(("NS_main: failed to schedule OS reboot removal of " \
3823 "directory: " LOG_S, DELETE_DIR));
3826 #endif /* WNT */
3829 #ifdef MACOSX
3830 // When the update is successful remove the precomplete file in the root of
3831 // the application bundle and move the distribution directory from
3832 // Contents/MacOS to Contents/Resources and if both exist delete the
3833 // directory under Contents/MacOS (see Bug 1068439).
3834 if (gSucceeded && !sStagedUpdate)
3836 NS_tchar oldPrecomplete[MAXPATHLEN];
3837 NS_tsnprintf(oldPrecomplete, sizeof(oldPrecomplete)/sizeof(oldPrecomplete[0]),
3838 NS_T("%s/precomplete"), gInstallDirPath);
3839 NS_tremove(oldPrecomplete);
3841 NS_tchar oldDistDir[MAXPATHLEN];
3842 NS_tsnprintf(oldDistDir, sizeof(oldDistDir)/sizeof(oldDistDir[0]),
3843 NS_T("%s/Contents/MacOS/distribution"), gInstallDirPath);
3844 int rv = NS_taccess(oldDistDir, F_OK);
3845 if (!rv)
3847 NS_tchar newDistDir[MAXPATHLEN];
3848 NS_tsnprintf(newDistDir, sizeof(newDistDir)/sizeof(newDistDir[0]),
3849 NS_T("%s/Contents/Resources/distribution"), gInstallDirPath);
3850 rv = NS_taccess(newDistDir, F_OK);
3851 if (!rv)
3853 LOG(("New distribution directory already exists... removing old " \
3854 "distribution directory: " LOG_S, oldDistDir));
3855 rv = ensure_remove_recursive(oldDistDir);
3856 if (rv)
3858 LOG(("Removing old distribution directory failed - err: %d", rv));
3861 else
3863 LOG(("Moving old distribution directory to new location. src: " LOG_S \
3864 ", dst:" LOG_S, oldDistDir, newDistDir));
3865 rv = rename_file(oldDistDir, newDistDir, true);
3866 if (rv)
3868 LOG(("Moving old distribution directory to new location failed - " \
3869 "err: %d", rv));
3875 if (isElevated)
3877 SetGroupOwnershipAndPermissions(gInstallDirPath);
3878 freeArguments(argc, argv);
3879 CleanupElevatedMacUpdate(false);
3881 else if (IsOwnedByGroupAdmin(gInstallDirPath))
3883 // If the group ownership of the Firefox .app bundle was set to the "admin"
3884 // group during a previous elevated update, we need to ensure that all files
3885 // in the bundle have group ownership of "admin" as well as write permission
3886 // for the group to not break updates in the future.
3887 SetGroupOwnershipAndPermissions(gInstallDirPath);
3889 #endif /* MACOSX */
3891 LogFinish();
3893 int retVal = LaunchCallbackAndPostProcessApps(argc, argv, callbackIndex
3894 #ifdef _WIN32
3895 , elevatedLockFilePath
3896 , updateLockFileHandle
3897 #elif defined(MACOSX)
3898 , isElevated
3899 #endif
3902 return retVal ? retVal : (gSucceeded ? 0 : 1);
3905 class ActionList
3907 public:
3908 ActionList() : mFirst(nullptr), mLast(nullptr), mCount(0) { }
3909 ~ActionList();
3911 void Append(Action* action);
3912 int Prepare();
3913 int Execute();
3914 void Finish(int status);
3916 private:
3917 Action *mFirst;
3918 Action *mLast;
3919 int mCount;
3922 ActionList::~ActionList()
3924 Action* a = mFirst;
3925 while (a)
3927 Action *b = a;
3928 a = a->mNext;
3929 delete b;
3933 void
3934 ActionList::Append(Action *action)
3936 if (mLast)
3937 mLast->mNext = action;
3938 else
3939 mFirst = action;
3941 mLast = action;
3942 mCount++;
3946 ActionList::Prepare()
3948 // If the action list is empty then we should fail in order to signal that
3949 // something has gone wrong. Otherwise we report success when nothing is
3950 // actually done. See bug 327140.
3951 if (mCount == 0)
3953 LOG(("empty action list"));
3954 return MAR_ERROR_EMPTY_ACTION_LIST;
3957 Action *a = mFirst;
3958 int i = 0;
3959 while (a)
3961 int rv = a->Prepare();
3962 if (rv)
3963 return rv;
3965 float percent = float(++i) / float(mCount);
3966 UpdateProgressUI(PROGRESS_PREPARE_SIZE * percent);
3968 a = a->mNext;
3971 return OK;
3975 ActionList::Execute()
3977 int currentProgress = 0, maxProgress = 0;
3978 Action *a = mFirst;
3979 while (a)
3981 maxProgress += a->mProgressCost;
3982 a = a->mNext;
3985 a = mFirst;
3986 while (a)
3988 int rv = a->Execute();
3989 if (rv)
3991 LOG(("### execution failed"));
3992 return rv;
3995 currentProgress += a->mProgressCost;
3996 float percent = float(currentProgress) / float(maxProgress);
3997 UpdateProgressUI(PROGRESS_PREPARE_SIZE +
3998 PROGRESS_EXECUTE_SIZE * percent);
4000 a = a->mNext;
4003 return OK;
4006 void
4007 ActionList::Finish(int status)
4009 Action *a = mFirst;
4010 int i = 0;
4011 while (a)
4013 a->Finish(status);
4015 float percent = float(++i) / float(mCount);
4016 UpdateProgressUI(PROGRESS_PREPARE_SIZE +
4017 PROGRESS_EXECUTE_SIZE +
4018 PROGRESS_FINISH_SIZE * percent);
4020 a = a->mNext;
4023 if (status == OK)
4024 gSucceeded = true;
4028 #ifdef _WIN32
4029 int add_dir_entries(const NS_tchar *dirpath, ActionList *list)
4031 int rv = OK;
4032 WIN32_FIND_DATAW finddata;
4033 HANDLE hFindFile;
4034 NS_tchar searchspec[MAXPATHLEN];
4035 NS_tchar foundpath[MAXPATHLEN];
4037 NS_tsnprintf(searchspec, sizeof(searchspec)/sizeof(searchspec[0]),
4038 NS_T("%s*"), dirpath);
4039 std::unique_ptr<const NS_tchar> pszSpec(get_full_path(searchspec));
4041 hFindFile = FindFirstFileW(pszSpec.get(), &finddata);
4042 if (hFindFile != INVALID_HANDLE_VALUE)
4046 // Don't process the current or parent directory.
4047 if (NS_tstrcmp(finddata.cFileName, NS_T(".")) == 0 ||
4048 NS_tstrcmp(finddata.cFileName, NS_T("..")) == 0)
4049 continue;
4051 NS_tsnprintf(foundpath, sizeof(foundpath)/sizeof(foundpath[0]),
4052 NS_T("%s%s"), dirpath, finddata.cFileName);
4053 if (finddata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
4055 NS_tsnprintf(foundpath, sizeof(foundpath)/sizeof(foundpath[0]),
4056 NS_T("%s/"), foundpath);
4057 // Recurse into the directory.
4058 rv = add_dir_entries(foundpath, list);
4059 if (rv)
4061 LOG(("add_dir_entries error: " LOG_S ", err: %d", foundpath, rv));
4062 return rv;
4065 else
4067 // Add the file to be removed to the ActionList.
4068 NS_tchar *quotedpath = get_quoted_path(foundpath);
4069 if (!quotedpath)
4070 return PARSE_ERROR;
4072 Action *action = new RemoveFile();
4073 rv = action->Parse(quotedpath);
4074 if (rv)
4076 LOG(("add_dir_entries Parse error on recurse: " LOG_S ", err: %d",
4077 quotedpath, rv));
4078 return rv;
4080 free(quotedpath);
4082 list->Append(action);
4085 while (FindNextFileW(hFindFile, &finddata) != 0);
4087 FindClose(hFindFile);
4089 // Add the directory to be removed to the ActionList.
4090 NS_tchar *quotedpath = get_quoted_path(dirpath);
4091 if (!quotedpath)
4092 return PARSE_ERROR;
4094 Action *action = new RemoveDir();
4095 rv = action->Parse(quotedpath);
4096 if (rv)
4097 LOG(("add_dir_entries Parse error on close: " LOG_S ", err: %d",
4098 quotedpath, rv));
4099 else
4100 list->Append(action);
4101 free(quotedpath);
4105 return rv;
4108 #elif defined(__sun)
4109 int add_dir_entries(const NS_tchar *dirpath, ActionList *list)
4111 int rv = OK;
4112 NS_tchar foundpath[MAXPATHLEN];
4113 struct
4115 dirent dent_buffer;
4116 char chars[MAXNAMLEN];
4117 } ent_buf;
4118 struct dirent* ent;
4119 std::unique_ptr<NS_tchar> searchpath(get_full_path(dirpath));
4121 DIR* dir = opendir(searchpath.get());
4122 if (!dir)
4124 LOG(("add_dir_entries error on opendir: " LOG_S ", err: %d", searchpath.get(),
4125 errno));
4126 return UNEXPECTED_FILE_OPERATION_ERROR;
4129 while (readdir_r(dir, (dirent *)&ent_buf, &ent) == 0 && ent)
4131 if ((strcmp(ent->d_name, ".") == 0) ||
4132 (strcmp(ent->d_name, "..") == 0))
4133 continue;
4135 NS_tsnprintf(foundpath, sizeof(foundpath)/sizeof(foundpath[0]),
4136 NS_T("%s%s"), searchpath.get(), ent->d_name);
4137 struct stat64 st_buf;
4138 int test = stat64(foundpath, &st_buf);
4139 if (test)
4141 closedir(dir);
4142 return UNEXPECTED_FILE_OPERATION_ERROR;
4144 if (S_ISDIR(st_buf.st_mode))
4146 NS_tsnprintf(foundpath, sizeof(foundpath)/sizeof(foundpath[0]),
4147 NS_T("%s/"), foundpath);
4148 // Recurse into the directory.
4149 rv = add_dir_entries(foundpath, list);
4150 if (rv)
4152 LOG(("add_dir_entries error: " LOG_S ", err: %d", foundpath, rv));
4153 closedir(dir);
4154 return rv;
4157 else
4159 // Add the file to be removed to the ActionList.
4160 NS_tchar *quotedpath = get_quoted_path(get_relative_path(foundpath));
4161 if (!quotedpath)
4163 closedir(dir);
4164 return PARSE_ERROR;
4167 Action *action = new RemoveFile();
4168 rv = action->Parse(quotedpath);
4169 if (rv)
4171 LOG(("add_dir_entries Parse error on recurse: " LOG_S ", err: %d",
4172 quotedpath, rv));
4173 closedir(dir);
4174 return rv;
4177 list->Append(action);
4180 closedir(dir);
4182 // Add the directory to be removed to the ActionList.
4183 NS_tchar *quotedpath = get_quoted_path(get_relative_path(dirpath));
4184 if (!quotedpath)
4185 return PARSE_ERROR;
4187 Action *action = new RemoveDir();
4188 rv = action->Parse(quotedpath);
4189 if (rv)
4191 LOG(("add_dir_entries Parse error on close: " LOG_S ", err: %d",
4192 quotedpath, rv));
4194 else
4196 list->Append(action);
4199 return rv;
4202 #else
4204 int add_dir_entries(const NS_tchar *dirpath, ActionList *list)
4206 int rv = OK;
4207 FTS *ftsdir;
4208 FTSENT *ftsdirEntry;
4209 std::unique_ptr<NS_tchar> searchpath(get_full_path(dirpath));
4211 // Remove the trailing slash so the paths don't contain double slashes. The
4212 // existence of the slash has already been checked in DoUpdate.
4213 searchpath.get()[NS_tstrlen(searchpath.get()) - 1] = NS_T('\0');
4214 char* const pathargv[] = {searchpath.get(), nullptr};
4216 // FTS_NOCHDIR is used so relative paths from the destination directory are
4217 // returned.
4218 if (!(ftsdir = fts_open(pathargv,
4219 FTS_PHYSICAL | FTS_NOSTAT | FTS_XDEV | FTS_NOCHDIR,
4220 nullptr)))
4221 return UNEXPECTED_FILE_OPERATION_ERROR;
4223 while ((ftsdirEntry = fts_read(ftsdir)) != nullptr)
4225 NS_tchar foundpath[MAXPATHLEN];
4226 NS_tchar *quotedpath = nullptr;
4227 Action *action = nullptr;
4229 switch (ftsdirEntry->fts_info)
4231 // Filesystem objects that shouldn't be in the application's directories
4232 case FTS_SL:
4233 case FTS_SLNONE:
4234 case FTS_DEFAULT:
4235 LOG(("add_dir_entries: found a non-standard file: " LOG_S,
4236 ftsdirEntry->fts_path));
4237 /* Fall through */ // and try to remove as a file
4239 // Files
4240 case FTS_F:
4241 case FTS_NSOK:
4242 // Add the file to be removed to the ActionList.
4243 NS_tsnprintf(foundpath, sizeof(foundpath)/sizeof(foundpath[0]),
4244 NS_T("%s"), ftsdirEntry->fts_accpath);
4245 quotedpath = get_quoted_path(get_relative_path(foundpath));
4246 if (!quotedpath)
4248 rv = UPDATER_QUOTED_PATH_MEM_ERROR;
4249 break;
4251 action = new RemoveFile();
4252 rv = action->Parse(quotedpath);
4253 free(quotedpath);
4254 if (!rv)
4255 list->Append(action);
4256 break;
4258 // Directories
4259 case FTS_DP:
4260 rv = OK;
4261 // Add the directory to be removed to the ActionList.
4262 NS_tsnprintf(foundpath, sizeof(foundpath)/sizeof(foundpath[0]),
4263 NS_T("%s/"), ftsdirEntry->fts_accpath);
4264 quotedpath = get_quoted_path(get_relative_path(foundpath));
4265 if (!quotedpath)
4267 rv = UPDATER_QUOTED_PATH_MEM_ERROR;
4268 break;
4271 action = new RemoveDir();
4272 rv = action->Parse(quotedpath);
4273 free(quotedpath);
4274 if (!rv)
4275 list->Append(action);
4276 break;
4278 // Errors
4279 case FTS_DNR:
4280 case FTS_NS:
4281 // ENOENT is an acceptable error for FTS_DNR and FTS_NS and means that
4282 // we're racing with ourselves. Though strange, the entry will be
4283 // removed anyway.
4284 if (ENOENT == ftsdirEntry->fts_errno)
4286 rv = OK;
4287 break;
4289 // Fall through
4291 case FTS_ERR:
4292 rv = UNEXPECTED_FILE_OPERATION_ERROR;
4293 LOG(("add_dir_entries: fts_read() error: " LOG_S ", err: %d",
4294 ftsdirEntry->fts_path, ftsdirEntry->fts_errno));
4295 break;
4297 case FTS_DC:
4298 rv = UNEXPECTED_FILE_OPERATION_ERROR;
4299 LOG(("add_dir_entries: fts_read() returned FT_DC: " LOG_S,
4300 ftsdirEntry->fts_path));
4301 break;
4303 default:
4304 // FTS_D is ignored and FTS_DP is used instead (post-order).
4305 rv = OK;
4306 break;
4309 if (rv != OK)
4310 break;
4313 fts_close(ftsdir);
4315 return rv;
4317 #endif
4319 static NS_tchar*
4320 GetManifestContents(const NS_tchar *manifest)
4322 AutoFile mfile(NS_tfopen(manifest, NS_T("rb")));
4323 if (mfile == nullptr)
4325 LOG(("GetManifestContents: error opening manifest file: " LOG_S, manifest));
4326 return nullptr;
4329 struct stat ms;
4330 int rv = fstat(fileno((FILE *)mfile), &ms);
4331 if (rv)
4333 LOG(("GetManifestContents: error stating manifest file: " LOG_S, manifest));
4334 return nullptr;
4337 char *mbuf = (char *) malloc(ms.st_size + 1);
4338 if (!mbuf)
4339 return nullptr;
4341 size_t r = ms.st_size;
4342 char *rb = mbuf;
4343 while (r)
4345 const size_t count = std::min<size_t>(SSIZE_MAX, r);
4346 size_t c = fread(rb, 1, count, mfile);
4347 if (c != count)
4349 LOG(("GetManifestContents: error reading manifest file: " LOG_S, manifest));
4350 free(mbuf);
4351 return nullptr;
4354 r -= c;
4355 rb += c;
4357 mbuf[ms.st_size] = '\0';
4358 rb = mbuf;
4360 #ifndef _WIN32
4361 return rb;
4362 #else
4363 NS_tchar *wrb = (NS_tchar *) malloc((ms.st_size + 1) * sizeof(NS_tchar));
4364 if (!wrb)
4366 free(mbuf);
4367 return nullptr;
4370 if (!MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, rb, -1, wrb,
4371 ms.st_size + 1))
4373 LOG(("GetManifestContents: error converting utf8 to utf16le: %d", GetLastError()));
4374 free(mbuf);
4375 free(wrb);
4376 return nullptr;
4378 free(mbuf);
4380 return wrb;
4381 #endif
4384 int AddPreCompleteActions(ActionList *list)
4386 #ifdef MACOSX
4387 std::unique_ptr<NS_tchar> manifestPath(get_full_path(
4388 NS_T("Contents/Resources/precomplete")));
4389 #else
4390 std::unique_ptr<NS_tchar> manifestPath(get_full_path(
4391 NS_T("precomplete")));
4392 #endif
4394 NS_tchar *rb = GetManifestContents(manifestPath.get());
4395 if (rb == nullptr)
4397 LOG(("AddPreCompleteActions: error getting contents of precomplete " \
4398 "manifest"));
4399 // Applications aren't required to have a precomplete manifest. The mar
4400 // generation scripts enforce the presence of a precomplete manifest.
4401 return OK;
4404 int rv;
4405 NS_tchar *line;
4406 while ((line = mstrtok(kNL, &rb)) != 0)
4408 // skip comments
4409 if (*line == NS_T('#'))
4410 continue;
4412 NS_tchar *token = mstrtok(kWhitespace, &line);
4413 if (!token)
4415 LOG(("AddPreCompleteActions: token not found in manifest"));
4416 return PARSE_ERROR;
4419 Action *action = nullptr;
4420 if (NS_tstrcmp(token, NS_T("remove")) == 0) // rm file
4422 action = new RemoveFile();
4424 else if (NS_tstrcmp(token, NS_T("remove-cc")) == 0) // no longer supported
4426 continue;
4428 else if (NS_tstrcmp(token, NS_T("rmdir")) == 0) // rmdir if empty
4430 action = new RemoveDir();
4432 else
4434 LOG(("AddPreCompleteActions: unknown token: " LOG_S, token));
4435 return PARSE_ERROR;
4438 if (!action)
4439 return BAD_ACTION_ERROR;
4441 rv = action->Parse(line);
4442 if (rv)
4443 return rv;
4445 list->Append(action);
4448 return OK;
4451 int DoUpdate(ArchiveReader& archiveReader)
4453 NS_tchar manifest[MAXPATHLEN];
4454 NS_tsnprintf(manifest, sizeof(manifest)/sizeof(manifest[0]),
4455 NS_T("%s/updating/update.manifest"), gWorkingDirPath);
4456 ensure_parent_dir(manifest);
4458 // extract the manifest
4459 // TODO: moggi: needs adaption for LibreOffice
4460 // Why would we need the manifest? Even if we need it why would we need 2?
4461 int rv = archiveReader.ExtractFile("updatev3.manifest", manifest);
4462 if (rv)
4464 rv = archiveReader.ExtractFile("updatev2.manifest", manifest);
4465 if (rv)
4467 LOG(("DoUpdate: error extracting manifest file"));
4468 return rv;
4472 NS_tchar *rb = GetManifestContents(manifest);
4473 NS_tremove(manifest);
4474 if (rb == nullptr)
4476 LOG(("DoUpdate: error opening manifest file: " LOG_S, manifest));
4477 return READ_ERROR;
4480 ActionList list;
4481 NS_tchar *line;
4482 bool isFirstAction = true;
4484 while ((line = mstrtok(kNL, &rb)) != 0)
4486 // skip comments
4487 if (*line == NS_T('#'))
4488 continue;
4490 NS_tchar *token = mstrtok(kWhitespace, &line);
4491 if (!token)
4493 LOG(("DoUpdate: token not found in manifest"));
4494 return PARSE_ERROR;
4497 if (isFirstAction)
4499 isFirstAction = false;
4500 // The update manifest isn't required to have a type declaration. The mar
4501 // generation scripts enforce the presence of the type declaration.
4502 if (NS_tstrcmp(token, NS_T("type")) == 0)
4504 const NS_tchar *type = mstrtok(kQuote, &line);
4505 LOG(("UPDATE TYPE " LOG_S, type));
4506 if (NS_tstrcmp(type, NS_T("complete")) == 0)
4508 rv = AddPreCompleteActions(&list);
4509 if (rv)
4510 return rv;
4512 continue;
4516 Action *action = nullptr;
4517 if (NS_tstrcmp(token, NS_T("remove")) == 0) // rm file
4519 action = new RemoveFile();
4521 else if (NS_tstrcmp(token, NS_T("rmdir")) == 0) // rmdir if empty
4523 action = new RemoveDir();
4525 else if (NS_tstrcmp(token, NS_T("rmrfdir")) == 0) // rmdir recursive
4527 const NS_tchar *reldirpath = mstrtok(kQuote, &line);
4528 if (!reldirpath)
4529 return PARSE_ERROR;
4531 if (reldirpath[NS_tstrlen(reldirpath) - 1] != NS_T('/'))
4532 return PARSE_ERROR;
4534 rv = add_dir_entries(reldirpath, &list);
4535 if (rv)
4536 return rv;
4538 continue;
4540 else if (NS_tstrcmp(token, NS_T("add")) == 0)
4542 action = new AddFile(archiveReader);
4544 else if (NS_tstrcmp(token, NS_T("patch")) == 0)
4546 action = new PatchFile(archiveReader);
4548 else if (NS_tstrcmp(token, NS_T("add-if")) == 0) // Add if exists
4550 action = new AddIfFile(archiveReader);
4552 else if (NS_tstrcmp(token, NS_T("add-if-not")) == 0) // Add if not exists
4554 action = new AddIfNotFile(archiveReader);
4556 else if (NS_tstrcmp(token, NS_T("patch-if")) == 0) // Patch if exists
4558 action = new PatchIfFile(archiveReader);
4560 else
4562 LOG(("DoUpdate: unknown token: " LOG_S, token));
4563 return PARSE_ERROR;
4566 if (!action)
4567 return BAD_ACTION_ERROR;
4569 rv = action->Parse(line);
4570 if (rv)
4571 return rv;
4573 list.Append(action);
4576 rv = list.Prepare();
4577 if (rv)
4578 return rv;
4580 rv = list.Execute();
4582 list.Finish(rv);
4583 return rv;