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/. */
9 * contents = 1*( line )
10 * line = method LWS *( param LWS ) CRLF
12 * LWS = 1*( " " | "\t" )
14 * Available methods for the manifest file:
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.
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.
34 * method = "remove" | "rmdir"
37 #include "progressui.h"
38 #include "archivereader.h"
39 #include "readstrings.h"
50 #include <sys/types.h>
58 #include <config_version.h>
60 #include "updatelogging.h"
62 #include <onlineupdate/mozilla/Compiler.h>
63 #include <onlineupdate/mozilla/Types.h>
66 #include <comphelper/windowsStart.hxx>
67 #include "uachelper.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
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
86 #define PARENT_WAIT 5000
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
102 const NS_tchar
** argv
;
107 # define SSIZE_MAX LONG_MAX
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)
116 #if defined(VERIFY_MAR_SIGNATURE) && !defined(_WIN32) && !defined(MACOSX)
122 #ifdef MAINTENANCE_SERVICE
123 #include "registrycertificates.h"
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) { \
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.
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];
155 extern "C" unsigned int BZ2_crc32Table
[256];
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
];
171 //-----------------------------------------------------------------------------
173 // A simple stack based container for a FILE struct that closes the
174 // file descriptor from its destructor.
178 explicit AutoFile(FILE* file
= nullptr)
185 if (mFile
!= nullptr)
189 AutoFile
&operator=(FILE* file
)
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;
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
];
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("\"");
244 mstrtok(const NS_tchar
*delims
, NS_tchar
**str
)
252 // skip leading "whitespace"
253 NS_tchar
*ret
= *str
;
257 for (d
= delims
; *d
!= NS_T('\0'); ++d
)
277 for (d
= delims
; *d
!= NS_T('\0'); ++d
)
294 #if defined(_WIN32) && defined(MAINTENANCE_SERVICE)
296 EnvHasValue(const char *name
)
298 const char *val
= getenv(name
);
299 return (val
&& *val
);
304 * Converts a relative update path to an absolute path related to the working
305 * or install directory. Allocates a new NS_tchar[] based path!
308 * The relative path to convert to a full path.
309 * @return valid filesystem full path or nullptr if memory allocation fails.
312 new_absolute_path(const NS_tchar
*relpath
)
314 NS_tchar
*destpath
= sStagedUpdate
? gWorkingDirPath
: gInstallDirPath
;
315 size_t lendestpath
= NS_tstrlen(destpath
);
316 size_t lenrelpath
= NS_tstrlen(relpath
);
317 NS_tchar
*s
= new NS_tchar
[lendestpath
+ lenrelpath
+ 2];
321 NS_tstrcpy(c
, destpath
);
323 NS_tstrcat(c
, NS_T("/"));
326 NS_tstrcat(c
, relpath
);
334 bool is_userprofile_in_instdir()
339 // 1.) if userprofile path length is smaller than installation dir,
340 // the profile is surely not in instdir
341 // 2.) else comparing the two paths looking only at the installation dir
342 // characters should yield an equal string
343 NS_tchar userprofile[MAXPATHLEN];
344 NS_tstrcpy(userprofile, gPatchDirPath);
345 NS_tchar *slash = (NS_tchar *) NS_tstrrchr(userprofile, NS_T('/'));
349 size_t userprofile_len = NS_tstrlen(userprofile);
350 size_t installdir_len = NS_tstrlen(gInstallDirPath);
352 if (userprofile_len < installdir_len)
355 return NS_tstrncmp(userprofile, gInstallDirPath, installdir_len) == 0;
362 * Get a pointer in the absolute path, relative to the working or install
363 * directory. Returns itself, if not absolute or outside of the directory.
367 * return pointer to the location within fullpath where the relative path starts
368 * or fullpath itself if it already looks relative.
370 static const NS_tchar
*
371 get_relative_offset(const NS_tchar
*abs_path
)
373 // If the path isn't absolute, just return it as-is.
375 if (abs_path
[1] != ':' && abs_path
[2] != '\\')
378 if (abs_path
[0] != '/')
384 NS_tchar
*prefix
= sStagedUpdate
? gWorkingDirPath
: gInstallDirPath
;
386 size_t len
= NS_tstrlen(prefix
);
387 if (NS_tstrlen(abs_path
) <= len
)
389 if (0 != strncmp(abs_path
, prefix
, len
))
391 return abs_path
+ len
+ 1;
395 * Gets the platform specific path and performs simple checks to the path. If
396 * the path checks don't pass nullptr will be returned.
399 * The line from the manifest that contains the path.
401 * Whether the path is a directory path. Defaults to false.
402 * @return valid filesystem path or nullptr if the path checks fail.
405 get_valid_path(NS_tchar
**line
, bool isdir
= false)
407 NS_tchar
*path
= mstrtok(kQuote
, line
);
410 LOG(("get_valid_path: unable to determine path: " LOG_S
, line
));
414 // All paths must be relative from the current working directory
415 if (path
[0] == NS_T('/'))
417 LOG(("get_valid_path: path must be relative: " LOG_S
, path
));
422 // All paths must be relative from the current working directory
423 if (path
[0] == NS_T('\\') || path
[1] == NS_T(':'))
425 LOG(("get_valid_path: path must be relative: " LOG_S
, path
));
432 // Directory paths must have a trailing forward slash.
433 if (path
[NS_tstrlen(path
) - 1] != NS_T('/'))
435 LOG(("get_valid_path: directory paths must have a trailing forward " \
436 "slash: " LOG_S
, path
));
440 // Remove the trailing forward slash because stat on Windows will return
441 // ENOENT if the path has a trailing slash.
442 path
[NS_tstrlen(path
) - 1] = NS_T('\0');
445 // Don't allow relative paths that resolve to a parent directory.
446 if (NS_tstrstr(path
, NS_T("..")) != nullptr)
448 LOG(("get_valid_path: paths must not contain '..': " LOG_S
, path
));
456 get_quoted_path(const NS_tchar
*path
)
458 size_t lenQuote
= NS_tstrlen(kQuote
);
459 size_t lenPath
= NS_tstrlen(path
);
460 size_t len
= lenQuote
+ lenPath
+ lenQuote
+ 1;
462 NS_tchar
*s
= (NS_tchar
*) malloc(len
* sizeof(NS_tchar
));
467 NS_tstrcpy(c
, kQuote
);
471 NS_tstrcat(c
, kQuote
);
478 static void ensure_write_permissions(const NS_tchar
*path
)
481 (void) _wchmod(path
, _S_IREAD
| _S_IWRITE
);
484 if (!stat(path
, &fs
) && !(fs
.st_mode
& S_IWUSR
))
486 (void)chmod(path
, fs
.st_mode
| S_IWUSR
);
491 static int ensure_remove(const NS_tchar
*path
)
493 ensure_write_permissions(path
);
494 int rv
= NS_tremove(path
);
496 LOG(("ensure_remove: failed to remove file: " LOG_S
", rv: %d, err: %d",
501 // Remove the directory pointed to by path and all of its files and sub-directories.
502 static int ensure_remove_recursive(const NS_tchar
*path
,
503 bool continueEnumOnFailure
= false)
505 // We use lstat rather than stat here so that we can successfully remove
507 struct NS_tstat_t sInfo
;
508 int rv
= NS_tlstat(path
, &sInfo
);
511 // This error is benign
514 if (!S_ISDIR(sInfo
.st_mode
))
516 return ensure_remove(path
);
522 dir
= NS_topendir(path
);
525 LOG(("ensure_remove_recursive: unable to open directory: " LOG_S
526 ", rv: %d, err: %d", path
, rv
, errno
));
530 while ((entry
= NS_treaddir(dir
)) != 0)
532 if (NS_tstrcmp(entry
->d_name
, NS_T(".")) &&
533 NS_tstrcmp(entry
->d_name
, NS_T("..")))
535 NS_tchar childPath
[MAXPATHLEN
];
536 NS_tsnprintf(childPath
, sizeof(childPath
)/sizeof(childPath
[0]),
537 NS_T("%s/%s"), path
, entry
->d_name
);
538 rv
= ensure_remove_recursive(childPath
);
539 if (rv
&& !continueEnumOnFailure
)
550 ensure_write_permissions(path
);
551 rv
= NS_trmdir(path
);
554 LOG(("ensure_remove_recursive: unable to remove directory: " LOG_S
555 ", rv: %d, err: %d", path
, rv
, errno
));
561 static bool is_read_only(const NS_tchar
*flags
)
563 size_t length
= NS_tstrlen(flags
);
567 // Make sure the string begins with "r"
568 if (flags
[0] != NS_T('r'))
571 // Look for "r+" or "r+b"
572 if (length
> 1 && flags
[1] == NS_T('+'))
576 if (NS_tstrcmp(flags
, NS_T("rb+")) == 0)
582 static FILE* ensure_open(const NS_tchar
*path
, const NS_tchar
*flags
, unsigned int options
)
584 ensure_write_permissions(path
);
585 FILE* f
= NS_tfopen(path
, flags
);
586 if (is_read_only(flags
))
588 // Don't attempt to modify the file permissions if the file is being opened
589 // in read-only mode.
592 if (NS_tchmod(path
, options
) != 0)
600 struct NS_tstat_t ss
;
601 if (NS_tstat(path
, &ss
) != 0 || ss
.st_mode
!= options
)
612 // Ensure that the directory containing this file exists.
613 static int ensure_parent_dir(const NS_tchar
*path
)
617 NS_tchar
*slash
= (NS_tchar
*) NS_tstrrchr(path
, NS_T('/'));
621 rv
= ensure_parent_dir(path
);
622 // Only attempt to create the directory if we're not at the root
623 if (rv
== OK
&& *path
)
625 rv
= NS_tmkdir(path
, 0755);
626 // If the directory already exists, then ignore the error.
627 if (rv
< 0 && errno
!= EEXIST
)
629 LOG(("ensure_parent_dir: failed to create directory: " LOG_S
", " \
630 "err: %d", path
, errno
));
644 static int ensure_copy_symlink(const NS_tchar
*path
, const NS_tchar
*dest
)
646 // Copy symlinks by creating a new symlink to the same target
647 NS_tchar target
[MAXPATHLEN
+ 1] = {NS_T('\0')};
648 int rv
= readlink(path
, target
, MAXPATHLEN
);
651 LOG(("ensure_copy_symlink: failed to read the link: " LOG_S
", err: %d",
655 rv
= symlink(target
, dest
);
658 LOG(("ensure_copy_symlink: failed to create the new link: " LOG_S
", target: " LOG_S
" err: %d",
659 dest
, target
, errno
));
666 // Copy the file named path onto a new file named dest.
667 static int ensure_copy(const NS_tchar
*path
, const NS_tchar
*dest
)
670 // Fast path for Windows
671 bool result
= CopyFileW(path
, dest
, false);
674 LOG(("ensure_copy: failed to copy the file " LOG_S
" over to " LOG_S
", lasterr: %x",
675 path
, dest
, GetLastError()));
676 return WRITE_ERROR_FILE_COPY
;
680 struct NS_tstat_t ss
;
681 int rv
= NS_tlstat(path
, &ss
);
684 LOG(("ensure_copy: failed to read file status info: " LOG_S
", err: %d",
690 if (S_ISLNK(ss
.st_mode
))
692 return ensure_copy_symlink(path
, dest
);
696 AutoFile
infile(ensure_open(path
, NS_T("rb"), ss
.st_mode
));
699 LOG(("ensure_copy: failed to open the file for reading: " LOG_S
", err: %d",
703 AutoFile
outfile(ensure_open(dest
, NS_T("wb"), ss
.st_mode
));
706 LOG(("ensure_copy: failed to open the file for writing: " LOG_S
", err: %d",
711 // This block size was chosen pretty arbitrarily but seems like a reasonable
712 // compromise. For example, the optimal block size on a modern macOS machine
714 const int blockSize
= 32 * 1024;
715 void* buffer
= malloc(blockSize
);
717 return UPDATER_MEM_ERROR
;
719 while (!feof(infile
.get()))
721 size_t read
= fread(buffer
, 1, blockSize
, infile
);
722 if (ferror(infile
.get()))
724 LOG(("ensure_copy: failed to read the file: " LOG_S
", err: %d",
732 while (written
< read
)
734 size_t nCount
= read
- written
;
735 size_t chunkWritten
= fwrite(buffer
, 1, nCount
, outfile
);
736 if (chunkWritten
!= nCount
)
738 LOG(("ensure_copy: failed to write the file: " LOG_S
", err: %d",
741 return WRITE_ERROR_FILE_COPY
;
744 written
+= chunkWritten
;
748 rv
= NS_tchmod(dest
, ss
.st_mode
);
755 template <unsigned N
>
756 struct copy_recursive_skiplist
758 NS_tchar paths
[N
][MAXPATHLEN
];
760 void append(unsigned index
, const NS_tchar
*path
, const NS_tchar
*suffix
)
762 NS_tsnprintf(paths
[index
], MAXPATHLEN
, NS_T("%s/%s"), path
, suffix
);
765 void append(unsigned index
, const NS_tchar
* path
)
767 NS_tstrcpy(paths
[index
], path
);
770 bool find(const NS_tchar
*path
)
772 for (int i
= 0; i
< static_cast<int>(N
); ++i
)
774 if (!NS_tstricmp(paths
[i
], path
))
783 // Copy all of the files and subdirectories under path to a new directory named dest.
784 // The path names in the skiplist will be skipped and will not be copied.
785 template <unsigned N
>
786 static int ensure_copy_recursive(const NS_tchar
*path
, const NS_tchar
*dest
,
787 copy_recursive_skiplist
<N
>& skiplist
)
789 struct NS_tstat_t sInfo
;
790 int rv
= NS_tlstat(path
, &sInfo
);
793 LOG(("ensure_copy_recursive: path doesn't exist: " LOG_S
", rv: %d, err: %d",
799 if (S_ISLNK(sInfo
.st_mode
))
801 return ensure_copy_symlink(path
, dest
);
805 if (!S_ISDIR(sInfo
.st_mode
))
807 return ensure_copy(path
, dest
);
810 rv
= NS_tmkdir(dest
, sInfo
.st_mode
);
811 if (rv
< 0 && errno
!= EEXIST
)
813 LOG(("ensure_copy_recursive: could not create destination directory: " LOG_S
", rv: %d, err: %d",
821 dir
= NS_topendir(path
);
824 LOG(("ensure_copy_recursive: path is not a directory: " LOG_S
", rv: %d, err: %d",
829 while ((entry
= NS_treaddir(dir
)) != 0)
831 if (NS_tstrcmp(entry
->d_name
, NS_T(".")) &&
832 NS_tstrcmp(entry
->d_name
, NS_T("..")))
834 NS_tchar childPath
[MAXPATHLEN
];
835 NS_tsnprintf(childPath
, sizeof(childPath
)/sizeof(childPath
[0]),
836 NS_T("%s/%s"), path
, entry
->d_name
);
837 if (skiplist
.find(childPath
))
841 NS_tchar childPathDest
[MAXPATHLEN
];
842 NS_tsnprintf(childPathDest
, sizeof(childPathDest
)/sizeof(childPathDest
[0]),
843 NS_T("%s/%s"), dest
, entry
->d_name
);
844 rv
= ensure_copy_recursive(childPath
, childPathDest
, skiplist
);
855 // Renames the specified file to the new file specified. If the destination file
856 // exists it is removed.
857 static int rename_file(const NS_tchar
*spath
, const NS_tchar
*dpath
,
858 bool allowDirs
= false)
860 int rv
= ensure_parent_dir(dpath
);
864 struct NS_tstat_t spathInfo
;
865 rv
= NS_tstat(spath
, &spathInfo
);
868 LOG(("rename_file: failed to read file status info: " LOG_S
", " \
869 "err: %d", spath
, errno
));
873 if (!S_ISREG(spathInfo
.st_mode
))
875 if (allowDirs
&& !S_ISDIR(spathInfo
.st_mode
))
877 LOG(("rename_file: path present, but not a file: " LOG_S
", err: %d",
879 return RENAME_ERROR_EXPECTED_FILE
;
883 LOG(("rename_file: proceeding to rename the directory"));
887 if (!NS_taccess(dpath
, F_OK
))
889 if (ensure_remove(dpath
))
891 LOG(("rename_file: destination file exists and could not be " \
892 "removed: " LOG_S
, dpath
));
893 return WRITE_ERROR_DELETE_FILE
;
897 if (NS_trename(spath
, dpath
) != 0)
899 LOG(("rename_file: failed to rename file - src: " LOG_S
", " \
900 "dst:" LOG_S
", err: %d", spath
, dpath
, errno
));
908 // Remove the directory pointed to by path and all of its files and
909 // sub-directories. If a file is in use move it to the tobedeleted directory
910 // and attempt to schedule removal of the file on reboot
911 static int remove_recursive_on_reboot(const NS_tchar
*path
, const NS_tchar
*deleteDir
)
913 struct NS_tstat_t sInfo
;
914 int rv
= NS_tlstat(path
, &sInfo
);
917 // This error is benign
921 if (!S_ISDIR(sInfo
.st_mode
))
923 NS_tchar tmpDeleteFile
[MAXPATHLEN
];
924 GetTempFileNameW(deleteDir
, L
"rep", 0, tmpDeleteFile
);
925 NS_tremove(tmpDeleteFile
);
926 rv
= rename_file(path
, tmpDeleteFile
, false);
927 if (MoveFileEx(rv
? path
: tmpDeleteFile
, nullptr, MOVEFILE_DELAY_UNTIL_REBOOT
))
929 LOG(("remove_recursive_on_reboot: file will be removed on OS reboot: "
930 LOG_S
, rv
? path
: tmpDeleteFile
));
934 LOG(("remove_recursive_on_reboot: failed to schedule OS reboot removal of "
935 "file: " LOG_S
, rv
? path
: tmpDeleteFile
));
943 dir
= NS_topendir(path
);
946 LOG(("remove_recursive_on_reboot: unable to open directory: " LOG_S
952 while ((entry
= NS_treaddir(dir
)) != 0)
954 if (NS_tstrcmp(entry
->d_name
, NS_T(".")) &&
955 NS_tstrcmp(entry
->d_name
, NS_T("..")))
957 NS_tchar childPath
[MAXPATHLEN
];
958 NS_tsnprintf(childPath
, sizeof(childPath
)/sizeof(childPath
[0]),
959 NS_T("%s/%s"), path
, entry
->d_name
);
960 // There is no need to check the return value of this call since this
961 // function is only called after an update is successful and there is not
962 // much that can be done to recover if it isn't successful. There is also
963 // no need to log the value since it will have already been logged.
964 remove_recursive_on_reboot(childPath
, deleteDir
);
972 ensure_write_permissions(path
);
973 rv
= NS_trmdir(path
);
976 LOG(("remove_recursive_on_reboot: unable to remove directory: " LOG_S
977 ", rv: %d, err: %d", path
, rv
, errno
));
984 //-----------------------------------------------------------------------------
986 // Create a backup of the specified file by renaming it.
987 static int backup_create(const NS_tchar
*path
)
989 NS_tchar backup
[MAXPATHLEN
];
990 NS_tsnprintf(backup
, sizeof(backup
)/sizeof(backup
[0]),
991 NS_T("%s") BACKUP_EXT
, path
);
993 return rename_file(path
, backup
);
996 // Rename the backup of the specified file that was created by renaming it back
997 // to the original file.
998 static int backup_restore(const NS_tchar
*path
, const NS_tchar
*relPath
)
1000 NS_tchar backup
[MAXPATHLEN
];
1001 NS_tsnprintf(backup
, sizeof(backup
)/sizeof(backup
[0]),
1002 NS_T("%s") BACKUP_EXT
, path
);
1004 NS_tchar relBackup
[MAXPATHLEN
];
1005 NS_tsnprintf(relBackup
, sizeof(relBackup
) / sizeof(relBackup
[0]),
1006 NS_T("%s") BACKUP_EXT
, relPath
);
1008 if (NS_taccess(backup
, F_OK
))
1010 LOG(("backup_restore: backup file doesn't exist: " LOG_S
, relBackup
));
1014 return rename_file(backup
, path
);
1017 // Discard the backup of the specified file that was created by renaming it.
1018 static int backup_discard(const NS_tchar
*path
, const NS_tchar
*relPath
)
1020 NS_tchar backup
[MAXPATHLEN
];
1021 NS_tsnprintf(backup
, sizeof(backup
)/sizeof(backup
[0]),
1022 NS_T("%s") BACKUP_EXT
, path
);
1024 NS_tchar relBackup
[MAXPATHLEN
];
1025 NS_tsnprintf(relBackup
, sizeof(relBackup
) / sizeof(relBackup
[0]),
1026 NS_T("%s") BACKUP_EXT
, relPath
);
1028 // Nothing to discard
1029 if (NS_taccess(backup
, F_OK
))
1034 int rv
= ensure_remove(backup
);
1036 if (rv
&& !sStagedUpdate
&& !sReplaceRequest
)
1038 LOG(("backup_discard: unable to remove: " LOG_S
, relBackup
));
1039 NS_tchar path
[MAXPATHLEN
];
1040 GetTempFileNameW(gDeleteDirPath
, L
"moz", 0, path
);
1041 if (rename_file(backup
, path
))
1043 LOG(("backup_discard: failed to rename file:" LOG_S
", dst:" LOG_S
,
1044 relBackup
, relPath
));
1045 return WRITE_ERROR_DELETE_BACKUP
;
1047 // The MoveFileEx call to remove the file on OS reboot will fail if the
1048 // process doesn't have write access to the HKEY_LOCAL_MACHINE registry key
1049 // but this is ok since the installer / uninstaller will delete the
1050 // directory containing the file along with its contents after an update is
1051 // applied, on reinstall, and on uninstall.
1052 if (MoveFileEx(path
, nullptr, MOVEFILE_DELAY_UNTIL_REBOOT
))
1054 LOG(("backup_discard: file renamed and will be removed on OS " \
1055 "reboot: " LOG_S
, relPath
));
1059 LOG(("backup_discard: failed to schedule OS reboot removal of " \
1060 "file: " LOG_S
, relPath
));
1065 return WRITE_ERROR_DELETE_BACKUP
;
1071 // Helper function for post-processing a temporary backup.
1072 static void backup_finish(const NS_tchar
*path
, const NS_tchar
*relPath
,
1076 backup_discard(path
, relPath
);
1078 backup_restore(path
, relPath
);
1081 //-----------------------------------------------------------------------------
1083 static int DoUpdate(ArchiveReader
& ArchiveReader
);
1088 Action() : mProgressCost(1), mNext(nullptr) { }
1089 virtual ~Action() { }
1091 virtual int Parse(NS_tchar
*line
) = 0;
1093 // Do any preprocessing to ensure that the action can be performed. Execute
1094 // will be called if this Action and all others return OK from this method.
1095 virtual int Prepare() = 0;
1097 // Perform the operation. Return OK to indicate success. After all actions
1098 // have been executed, Finish will be called. A requirement of Execute is
1099 // that its operation be reversible from Finish.
1100 virtual int Execute() = 0;
1102 // Finish is called after execution of all actions. If status is OK, then
1103 // all actions were successfully executed. Otherwise, some action failed.
1104 virtual void Finish(int status
) = 0;
1110 friend class ActionList
;
1113 class RemoveFile
: public Action
1116 RemoveFile() : mSkip(0) { }
1118 int Parse(NS_tchar
*line
);
1121 void Finish(int status
);
1124 std::unique_ptr
<const NS_tchar
[]> mFile
;
1125 std::unique_ptr
<NS_tchar
[]> mRelPath
;
1130 RemoveFile::Parse(NS_tchar
*line
)
1132 // format "<deadfile>"
1134 NS_tchar
* validPath
= get_valid_path(&line
);
1138 mRelPath
.reset(new NS_tchar
[MAXPATHLEN
]);
1139 NS_tstrcpy(mRelPath
.get(), validPath
);
1141 mFile
.reset(new_absolute_path(validPath
));
1151 RemoveFile::Prepare()
1153 // Skip the file if it already doesn't exist.
1154 int rv
= NS_taccess(mFile
.get(), F_OK
);
1162 LOG(("PREPARE REMOVEFILE " LOG_S
, mRelPath
.get()));
1164 // Make sure that we're actually a file...
1165 struct NS_tstat_t fileInfo
;
1166 rv
= NS_tstat(mFile
.get(), &fileInfo
);
1169 LOG(("failed to read file status info: " LOG_S
", err: %d", mFile
.get(),
1174 if (!S_ISREG(fileInfo
.st_mode
))
1176 LOG(("path present, but not a file: " LOG_S
, mFile
.get()));
1177 return DELETE_ERROR_EXPECTED_FILE
;
1180 NS_tchar
*slash
= (NS_tchar
*) NS_tstrrchr(mFile
.get(), NS_T('/'));
1183 *slash
= NS_T('\0');
1184 rv
= NS_taccess(mFile
.get(), W_OK
);
1189 rv
= NS_taccess(NS_T("."), W_OK
);
1194 LOG(("access failed: %d", errno
));
1195 return WRITE_ERROR_FILE_ACCESS_DENIED
;
1202 RemoveFile::Execute()
1207 LOG(("EXECUTE REMOVEFILE " LOG_S
, mRelPath
.get()));
1209 // The file is checked for existence here and in Prepare since it might have
1210 // been removed by a separate instruction: bug 311099.
1211 int rv
= NS_taccess(mFile
.get(), F_OK
);
1214 LOG(("file cannot be removed because it does not exist; skipping"));
1219 // Rename the old file. It will be removed in Finish.
1220 rv
= backup_create(mFile
.get());
1223 LOG(("backup_create failed: %d", rv
));
1231 RemoveFile::Finish(int status
)
1236 LOG(("FINISH REMOVEFILE " LOG_S
, mRelPath
.get()));
1238 backup_finish(mFile
.get(), mRelPath
.get(), status
);
1241 class RemoveDir
: public Action
1244 RemoveDir() : mSkip(0) { }
1246 virtual int Parse(NS_tchar
*line
);
1247 virtual int Prepare(); // check that the source dir exists
1248 virtual int Execute();
1249 virtual void Finish(int status
);
1252 std::unique_ptr
<NS_tchar
[]> mDir
;
1253 std::unique_ptr
<NS_tchar
[]> mRelPath
;
1258 RemoveDir::Parse(NS_tchar
*line
)
1260 // format "<deaddir>/"
1262 NS_tchar
* validPath
= get_valid_path(&line
, true);
1266 mRelPath
.reset(new NS_tchar
[MAXPATHLEN
]);
1267 NS_tstrcpy(mRelPath
.get(), validPath
);
1269 mDir
.reset(new_absolute_path(validPath
));
1279 RemoveDir::Prepare()
1281 // We expect the directory to exist if we are to remove it.
1282 int rv
= NS_taccess(mDir
.get(), F_OK
);
1290 LOG(("PREPARE REMOVEDIR " LOG_S
"/", mRelPath
.get()));
1292 // Make sure that we're actually a dir.
1293 struct NS_tstat_t dirInfo
;
1294 rv
= NS_tstat(mDir
.get(), &dirInfo
);
1297 LOG(("failed to read directory status info: " LOG_S
", err: %d", mRelPath
.get(),
1302 if (!S_ISDIR(dirInfo
.st_mode
))
1304 LOG(("path present, but not a directory: " LOG_S
, mRelPath
.get()));
1305 return DELETE_ERROR_EXPECTED_DIR
;
1308 rv
= NS_taccess(mDir
.get(), W_OK
);
1311 LOG(("access failed: %d, %d", rv
, errno
));
1312 return WRITE_ERROR_DIR_ACCESS_DENIED
;
1319 RemoveDir::Execute()
1324 LOG(("EXECUTE REMOVEDIR " LOG_S
"/", mRelPath
.get()));
1326 // The directory is checked for existence at every step since it might have
1327 // been removed by a separate instruction: bug 311099.
1328 int rv
= NS_taccess(mDir
.get(), F_OK
);
1331 LOG(("directory no longer exists; skipping"));
1339 RemoveDir::Finish(int status
)
1341 if (mSkip
|| status
!= OK
)
1344 LOG(("FINISH REMOVEDIR " LOG_S
"/", mRelPath
.get()));
1346 // The directory is checked for existence at every step since it might have
1347 // been removed by a separate instruction: bug 311099.
1348 int rv
= NS_taccess(mDir
.get(), F_OK
);
1351 LOG(("directory no longer exists; skipping"));
1358 if (NS_trmdir(mDir
.get()))
1360 LOG(("non-fatal error removing directory: " LOG_S
"/, rv: %d, err: %d",
1361 mRelPath
.get(), rv
, errno
));
1366 class AddFile
: public Action
1369 AddFile(ArchiveReader
& ar
) : mAdded(false), mArchiveReader(ar
) { }
1371 virtual int Parse(NS_tchar
*line
);
1372 virtual int Prepare();
1373 virtual int Execute();
1374 virtual void Finish(int status
);
1377 std::unique_ptr
<NS_tchar
[]> mFile
;
1378 std::unique_ptr
<NS_tchar
[]> mRelPath
;
1380 ArchiveReader
& mArchiveReader
;
1384 AddFile::Parse(NS_tchar
*line
)
1386 // format "<newfile>"
1388 NS_tchar
* validPath
= get_valid_path(&line
);
1392 mRelPath
.reset(new NS_tchar
[MAXPATHLEN
]);
1393 NS_tstrcpy(mRelPath
.get(), validPath
);
1395 mFile
.reset(new_absolute_path(validPath
));
1407 LOG(("PREPARE ADD " LOG_S
, mRelPath
.get()));
1415 LOG(("EXECUTE ADD " LOG_S
, mRelPath
.get()));
1419 // First make sure that we can actually get rid of any existing file.
1420 rv
= NS_taccess(mFile
.get(), F_OK
);
1423 rv
= backup_create(mFile
.get());
1429 rv
= ensure_parent_dir(mFile
.get());
1435 char sourcefile
[MAXPATHLEN
];
1436 if (!WideCharToMultiByte(CP_UTF8
, 0, mRelPath
.get(), -1, sourcefile
,
1437 MAXPATHLEN
, nullptr, nullptr))
1439 LOG(("error converting wchar to utf8: %d", GetLastError()));
1440 return STRING_CONVERSION_ERROR
;
1443 rv
= mArchiveReader
.ExtractFile(sourcefile
, mFile
.get());
1445 rv
= mArchiveReader
.ExtractFile(mRelPath
.get(), mFile
.get());
1455 AddFile::Finish(int status
)
1457 LOG(("FINISH ADD " LOG_S
, mRelPath
.get()));
1458 // When there is an update failure and a file has been added it is removed
1459 // here since there might not be a backup to replace it.
1460 if (status
&& mAdded
)
1461 NS_tremove(mFile
.get());
1462 backup_finish(mFile
.get(), mRelPath
.get(), status
);
1465 class PatchFile
: public Action
1468 PatchFile(ArchiveReader
& ar
) : mPatchFile(nullptr), mPatchIndex(-1), buf(nullptr), mArchiveReader(ar
) { }
1470 virtual ~PatchFile();
1472 virtual int Parse(NS_tchar
*line
);
1473 virtual int Prepare(); // should check for patch file and for checksum here
1474 virtual int Execute();
1475 virtual void Finish(int status
);
1478 int LoadSourceFile(FILE* ofile
);
1480 static int sPatchIndex
;
1482 const NS_tchar
*mPatchFile
;
1483 std::unique_ptr
<NS_tchar
> mFile
;
1484 std::unique_ptr
<NS_tchar
> mFileRelPath
;
1486 MBSPatchHeader header
;
1488 NS_tchar spath
[MAXPATHLEN
];
1489 AutoFile mPatchStream
;
1490 ArchiveReader
& mArchiveReader
;
1493 int PatchFile::sPatchIndex
= 0;
1495 PatchFile::~PatchFile()
1497 // Make sure mPatchStream gets unlocked on Windows; the system will do that,
1498 // but not until some indeterminate future time, and we want determinism.
1499 // Normally this happens at the end of Execute, when we close the stream;
1500 // this call is here in case Execute errors out.
1504 UnlockFile((HANDLE
)_get_osfhandle(fileno(mPatchStream
)), (DWORD
)0, (DWORD
)0, (DWORD
)-1, (DWORD
)-1);
1508 // delete the temporary patch file
1517 PatchFile::LoadSourceFile(FILE* ofile
)
1520 int rv
= fstat(fileno((FILE *)ofile
), &os
);
1523 LOG(("LoadSourceFile: unable to stat destination file: " LOG_S
", " \
1524 "err: %d", mFileRelPath
.get(), errno
));
1528 if (uint32_t(os
.st_size
) != header
.slen
)
1530 LOG(("LoadSourceFile: destination file size %d does not match expected size %d",
1531 uint32_t(os
.st_size
), header
.slen
));
1532 return LOADSOURCE_ERROR_WRONG_SIZE
;
1535 buf
= (unsigned char *) malloc(header
.slen
);
1537 return UPDATER_MEM_ERROR
;
1539 size_t r
= header
.slen
;
1540 unsigned char *rb
= buf
;
1543 const size_t count
= std::min
<size_t>(SSIZE_MAX
, r
);
1544 size_t c
= fread(rb
, 1, count
, ofile
);
1547 LOG(("LoadSourceFile: error reading destination file: " LOG_S
,
1548 mFileRelPath
.get()));
1556 // Verify that the contents of the source file correspond to what we expect.
1558 unsigned int crc
= crc32(buf
, header
.slen
);
1560 if (crc
!= header
.scrc32
)
1562 LOG(("LoadSourceFile: destination file crc %d does not match expected " \
1563 "crc %d", crc
, header
.scrc32
));
1571 PatchFile::Parse(NS_tchar
*line
)
1573 // format "<patchfile>" "<filetopatch>"
1575 // Get the path to the patch file inside of the mar
1576 mPatchFile
= mstrtok(kQuote
, &line
);
1580 // consume whitespace between args
1581 NS_tchar
*q
= mstrtok(kQuote
, &line
);
1585 NS_tchar
* validPath
= get_valid_path(&line
);
1588 mFileRelPath
.reset(new NS_tchar
[MAXPATHLEN
]);
1589 NS_tstrcpy(mFileRelPath
.get(), validPath
);
1591 mFile
.reset(new_absolute_path(validPath
));
1601 PatchFile::Prepare()
1603 LOG(("PREPARE PATCH " LOG_S
, mFileRelPath
.get()));
1605 // extract the patch to a temporary file
1606 mPatchIndex
= sPatchIndex
++;
1608 int nWrittenBytes
= NS_tsnprintf(spath
, sizeof(spath
)/sizeof(spath
[0]),
1609 NS_T("%s/updating/%d.patch"), gWorkingDirPath
, mPatchIndex
);
1610 (void) nWrittenBytes
;
1614 mPatchStream
= NS_tfopen(spath
, NS_T("wb+"));
1619 // Lock the patch file, so it can't be messed with between
1620 // when we're done creating it and when we go to apply it.
1621 if (!LockFile((HANDLE
)_get_osfhandle(fileno(mPatchStream
)), (DWORD
)0, (DWORD
)0, (DWORD
)-1, (DWORD
)-1))
1623 LOG(("Couldn't lock patch file: %d", GetLastError()));
1624 // TODO: moggi: fix the build problem with LOCK_ERROR_PATCH_FILE
1625 return WRITE_ERROR
; //return LOCK_ERROR_PATCH_FILE;
1627 char sourcefile
[MAXPATHLEN
];
1628 if (!WideCharToMultiByte(CP_UTF8
, 0, mPatchFile
, -1, sourcefile
, MAXPATHLEN
,
1631 LOG(("error converting wchar to utf8: %d", GetLastError()));
1632 return STRING_CONVERSION_ERROR
;
1635 int rv
= mArchiveReader
.ExtractFileToStream(sourcefile
, mPatchStream
);
1637 int rv
= mArchiveReader
.ExtractFileToStream(mPatchFile
, mPatchStream
);
1644 PatchFile::Execute()
1646 LOG(("EXECUTE PATCH " LOG_S
, mFileRelPath
.get()));
1648 fseek(mPatchStream
, 0, SEEK_SET
);
1650 int rv
= MBS_ReadHeader(mPatchStream
, &header
);
1654 FILE *origfile
= nullptr;
1656 if (NS_tstrcmp(mFileRelPath
.get(), gCallbackRelPath
) == 0)
1658 // Read from the copy of the callback when patching since the callback can't
1659 // be opened for reading to prevent the application from being launched.
1660 origfile
= NS_tfopen(gCallbackBackupPath
, NS_T("rb"));
1664 origfile
= NS_tfopen(mFile
.get(), NS_T("rb"));
1667 origfile
= NS_tfopen(mFile
.get(), NS_T("rb"));
1672 LOG(("unable to open destination file: " LOG_S
", err: %d",
1673 mFileRelPath
.get(), errno
));
1677 rv
= LoadSourceFile(origfile
);
1681 LOG(("LoadSourceFile failed"));
1685 // Rename the destination file if it exists before proceeding so it can be
1686 // used to restore the file to its original state if there is an error.
1687 struct NS_tstat_t ss
;
1688 rv
= NS_tstat(mFile
.get(), &ss
);
1691 LOG(("failed to read file status info: " LOG_S
", err: %d",
1692 mFileRelPath
.get(), errno
));
1696 rv
= backup_create(mFile
.get());
1700 #if defined(HAVE_POSIX_FALLOCATE)
1701 AutoFile
ofile(ensure_open(mFile
.get(), NS_T("wb+"), ss
.st_mode
));
1702 posix_fallocate(fileno((FILE *)ofile
), 0, header
.dlen
);
1703 #elif defined(_WIN32)
1704 bool shouldTruncate
= true;
1705 // Creating the file, setting the size, and then closing the file handle
1706 // lessens fragmentation more than any other method tested. Other methods that
1707 // have been tested are:
1708 // 1. _chsize / _chsize_s reduced fragmentation though not completely.
1709 // 2. _get_osfhandle and then setting the size reduced fragmentation though
1710 // not completely. There are also reports of _get_osfhandle failing on
1712 HANDLE hfile
= CreateFileW(mFile
.get(),
1717 FILE_ATTRIBUTE_NORMAL
,
1720 if (hfile
!= INVALID_HANDLE_VALUE
)
1722 if (SetFilePointer(hfile
, header
.dlen
,
1723 nullptr, FILE_BEGIN
) != INVALID_SET_FILE_POINTER
&&
1724 SetEndOfFile(hfile
) != 0)
1726 shouldTruncate
= false;
1731 AutoFile
ofile(ensure_open(mFile
.get(), shouldTruncate
? NS_T("wb+") : NS_T("rb+"),
1733 #elif defined(MACOSX)
1734 AutoFile
ofile(ensure_open(mFile
.get(), NS_T("wb+"), ss
.st_mode
));
1735 // Modified code from FileUtils.cpp
1736 fstore_t store
= {F_ALLOCATECONTIG
, F_PEOFPOSMODE
, 0, header
.dlen
};
1737 // Try to get a continuous chunk of disk space
1738 rv
= fcntl(fileno((FILE *)ofile
), F_PREALLOCATE
, &store
);
1741 // OK, perhaps we are too fragmented, allocate non-continuous
1742 store
.fst_flags
= F_ALLOCATEALL
;
1743 rv
= fcntl(fileno((FILE *)ofile
), F_PREALLOCATE
, &store
);
1748 ftruncate(fileno((FILE *)ofile
), header
.dlen
);
1751 AutoFile
ofile(ensure_open(mFile
.get(), NS_T("wb+"), ss
.st_mode
));
1754 if (ofile
== nullptr)
1756 LOG(("unable to create new file: " LOG_S
", err: %d", mFileRelPath
.get(),
1758 return WRITE_ERROR_OPEN_PATCH_FILE
;
1762 if (!shouldTruncate
)
1764 fseek(ofile
, 0, SEEK_SET
);
1768 rv
= MBS_ApplyPatch(&header
, mPatchStream
, buf
, ofile
);
1770 // Go ahead and do a bit of cleanup now to minimize runtime overhead.
1771 // Make sure mPatchStream gets unlocked on Windows; the system will do that,
1772 // but not until some indeterminate future time, and we want determinism.
1774 UnlockFile((HANDLE
)_get_osfhandle(fileno(mPatchStream
)), (DWORD
)0, (DWORD
)0, (DWORD
)-1, (DWORD
)-1);
1776 // Set mPatchStream to nullptr to make AutoFile close the file,
1777 // so it can be deleted on Windows.
1778 mPatchStream
= nullptr;
1780 spath
[0] = NS_T('\0');
1787 PatchFile::Finish(int status
)
1789 LOG(("FINISH PATCH " LOG_S
, mFileRelPath
.get()));
1791 backup_finish(mFile
.get(), mFileRelPath
.get(), status
);
1794 class AddIfFile
: public AddFile
1797 AddIfFile(ArchiveReader
& archiveReader
);
1799 virtual int Parse(NS_tchar
*line
);
1800 virtual int Prepare();
1801 virtual int Execute();
1802 virtual void Finish(int status
);
1805 std::unique_ptr
<NS_tchar
[]> mTestFile
;
1808 AddIfFile::AddIfFile(ArchiveReader
& archiveReader
):
1809 AddFile(archiveReader
)
1814 AddIfFile::Parse(NS_tchar
*line
)
1816 // format "<testfile>" "<newfile>"
1818 mTestFile
.reset(new_absolute_path(get_valid_path(&line
)));
1822 // consume whitespace between args
1823 NS_tchar
*q
= mstrtok(kQuote
, &line
);
1827 return AddFile::Parse(line
);
1831 AddIfFile::Prepare()
1833 // If the test file does not exist, then skip this action.
1834 if (NS_taccess(mTestFile
.get(), F_OK
))
1836 mTestFile
= nullptr;
1840 return AddFile::Prepare();
1844 AddIfFile::Execute()
1849 return AddFile::Execute();
1853 AddIfFile::Finish(int status
)
1858 AddFile::Finish(status
);
1861 class AddIfNotFile
: public AddFile
1864 AddIfNotFile(ArchiveReader
& archiveReader
);
1866 virtual int Parse(NS_tchar
*line
);
1867 virtual int Prepare();
1868 virtual int Execute();
1869 virtual void Finish(int status
);
1872 std::unique_ptr
<NS_tchar
[]> mTestFile
;
1875 AddIfNotFile::AddIfNotFile(ArchiveReader
& archiveReader
):
1876 AddFile(archiveReader
)
1881 AddIfNotFile::Parse(NS_tchar
*line
)
1883 // format "<testfile>" "<newfile>"
1885 mTestFile
.reset(new_absolute_path(get_valid_path(&line
)));
1889 // consume whitespace between args
1890 NS_tchar
*q
= mstrtok(kQuote
, &line
);
1894 return AddFile::Parse(line
);
1898 AddIfNotFile::Prepare()
1900 // If the test file exists, then skip this action.
1901 if (!NS_taccess(mTestFile
.get(), F_OK
))
1907 return AddFile::Prepare();
1911 AddIfNotFile::Execute()
1916 return AddFile::Execute();
1920 AddIfNotFile::Finish(int status
)
1925 AddFile::Finish(status
);
1928 class PatchIfFile
: public PatchFile
1931 PatchIfFile(ArchiveReader
& archiveReader
);
1933 virtual int Parse(NS_tchar
*line
);
1934 virtual int Prepare(); // should check for patch file and for checksum here
1935 virtual int Execute();
1936 virtual void Finish(int status
);
1939 std::unique_ptr
<NS_tchar
[]> mTestFile
;
1942 PatchIfFile::PatchIfFile(ArchiveReader
& archiveReader
):
1943 PatchFile(archiveReader
)
1948 PatchIfFile::Parse(NS_tchar
*line
)
1950 // format "<testfile>" "<patchfile>" "<filetopatch>"
1952 mTestFile
.reset(new_absolute_path(get_valid_path(&line
)));
1956 // consume whitespace between args
1957 NS_tchar
*q
= mstrtok(kQuote
, &line
);
1961 return PatchFile::Parse(line
);
1965 PatchIfFile::Prepare()
1967 // If the test file does not exist, then skip this action.
1968 if (NS_taccess(mTestFile
.get(), F_OK
))
1970 mTestFile
= nullptr;
1974 return PatchFile::Prepare();
1978 PatchIfFile::Execute()
1983 return PatchFile::Execute();
1987 PatchIfFile::Finish(int status
)
1992 PatchFile::Finish(status
);
1995 //-----------------------------------------------------------------------------
2000 * Launch the post update application (helper.exe). It takes in the path of the
2001 * callback application to calculate the path of helper.exe. For service updates
2002 * this is called from both the system account and the current user account.
2004 * @param installationDir The path to the callback application binary.
2005 * @param updateInfoDir The directory where update info is stored.
2006 * @return true if there was no error starting the process.
2009 LaunchWinPostProcess(const WCHAR
*installationDir
,
2010 const WCHAR
*updateInfoDir
)
2012 WCHAR workingDirectory
[MAX_PATH
+ 1] = { L
'\0' };
2013 wcsncpy(workingDirectory
, installationDir
, MAX_PATH
);
2015 // TODO: moggi: needs adaptation for LibreOffice
2016 // Most likely we don't have the helper method yet. Check if we really need it.
2018 // Launch helper.exe to perform post processing (e.g. registry and log file
2019 // modifications) for the update.
2020 WCHAR inifile
[MAX_PATH
+ 1] = { L
'\0' };
2021 wcsncpy(inifile
, installationDir
, MAX_PATH
);
2022 if (!PathAppendSafe(inifile
, L
"updater.ini"))
2027 WCHAR exefile
[MAX_PATH
+ 1];
2028 WCHAR exearg
[MAX_PATH
+ 1];
2031 if (!GetPrivateProfileStringW(L
"PostUpdateWin", L
"ExeRelPath", nullptr,
2032 exefile
, MAX_PATH
+ 1, inifile
))
2037 if (!GetPrivateProfileStringW(L
"PostUpdateWin", L
"ExeArg", nullptr, exearg
,
2038 MAX_PATH
+ 1, inifile
))
2043 if (!GetPrivateProfileStringW(L
"PostUpdateWin", L
"ExeAsync", L
"TRUE",
2045 sizeof(exeasync
)/sizeof(exeasync
[0]),
2051 // Verify that exeFile doesn't contain relative paths
2052 if (wcsstr(exefile
, L
"..") != nullptr)
2057 WCHAR exefullpath
[MAX_PATH
+ 1] = { L
'\0' };
2058 wcsncpy(exefullpath
, installationDir
, MAX_PATH
);
2059 if (!PathAppendSafe(exefullpath
, exefile
))
2064 #if !defined(TEST_UPDATER) && defined(MAINTENANCE_SERVICE)
2065 if (sUsingService
&&
2066 !DoesBinaryMatchAllowedCertificates(installationDir
, exefullpath
))
2072 WCHAR dlogFile
[MAX_PATH
+ 1];
2073 if (!PathGetSiblingFilePath(dlogFile
, exefullpath
, L
"uninstall.update"))
2078 WCHAR slogFile
[MAX_PATH
+ 1] = { L
'\0' };
2079 wcsncpy(slogFile
, updateInfoDir
, MAX_PATH
);
2080 if (!PathAppendSafe(slogFile
, L
"update.log"))
2085 WCHAR dummyArg
[14] = { L
'\0' };
2086 wcsncpy(dummyArg
, L
"argv0ignored ", sizeof(dummyArg
) / sizeof(dummyArg
[0]) - 1);
2088 size_t len
= wcslen(exearg
) + wcslen(dummyArg
);
2089 WCHAR
*cmdline
= (WCHAR
*) malloc((len
+ 1) * sizeof(WCHAR
));
2095 wcsncpy(cmdline
, dummyArg
, len
);
2096 wcscat(cmdline
, exearg
);
2098 if (sUsingService
||
2099 !_wcsnicmp(exeasync
, L
"false", 6) ||
2100 !_wcsnicmp(exeasync
, L
"0", 2))
2105 // We want to launch the post update helper app to update the Windows
2106 // registry even if there is a failure with removing the uninstall.update
2107 // file or copying the update.log file.
2108 CopyFileW(slogFile
, dlogFile
, false);
2110 STARTUPINFOW si
= {sizeof(si
), 0};
2112 PROCESS_INFORMATION pi
= {0};
2114 bool ok
= CreateProcessW(exefullpath
,
2116 nullptr, // no special security attributes
2117 nullptr, // no special thread attributes
2118 false, // don't inherit filehandles
2119 0, // No special process creation flags
2120 nullptr, // inherit my environment
2129 WaitForSingleObject(pi
.hProcess
, INFINITE
);
2131 CloseHandle(pi
.hProcess
);
2132 CloseHandle(pi
.hThread
);
2140 LaunchCallbackApp(const NS_tchar
*workingDir
,
2145 putenv(const_cast<char*>("NO_EM_RESTART="));
2146 putenv(const_cast<char*>("MOZ_LAUNCHED_CHILD=1"));
2148 // Run from the specified working directory (see bug 312360). This is not
2149 // necessary on Windows CE since the application that launches the updater
2150 // passes the working directory as an --environ: command line argument.
2151 if (NS_tchdir(workingDir
) != 0)
2153 LOG(("Warning: chdir failed"));
2156 #if defined(USE_EXECV)
2158 (void) usingService
; // avoid warnings
2159 execv(argv
[0], argv
);
2160 #elif defined(MACOSX)
2161 LaunchChild(argc
, (const char**)argv
);
2162 #elif defined(_WIN32)
2163 // Do not allow the callback to run when running an update through the
2164 // service as session 0. The unelevated updater.exe will do the launching.
2167 WinLaunchChild(argv
[0], argc
, argv
, nullptr);
2170 # warning "Need implementation of LaunchCallbackApp"
2175 WriteStatusFile(const char* aStatus
)
2177 NS_tchar filename
[MAXPATHLEN
] = {NS_T('\0')};
2179 // The temp file is not removed on failure since there is client code that
2181 GetTempFileNameW(gPatchDirPath
, L
"sta", 0, filename
);
2183 NS_tsnprintf(filename
, sizeof(filename
)/sizeof(filename
[0]),
2184 NS_T("%s/update.status"), gPatchDirPath
);
2187 // Make sure that the directory for the update status file exists
2188 if (ensure_parent_dir(filename
))
2191 // This is scoped to make the AutoFile close the file so it is possible to
2192 // move the temp file to the update.status file on Windows.
2194 AutoFile
file(NS_tfopen(filename
, NS_T("wb+")));
2195 if (file
== nullptr)
2200 if (fwrite(aStatus
, strlen(aStatus
), 1, file
) != 1)
2207 NS_tchar dstfilename
[MAXPATHLEN
] = {NS_T('\0')};
2208 NS_tsnprintf(dstfilename
, sizeof(dstfilename
)/sizeof(dstfilename
[0]),
2209 NS_T("%s\\update.status"), gPatchDirPath
);
2210 if (MoveFileExW(filename
, dstfilename
, MOVEFILE_REPLACE_EXISTING
) == 0)
2220 WriteStatusFile(int status
)
2233 text
= "succeeded\n";
2238 snprintf(buf
, sizeof(buf
)/sizeof(buf
[0]), "failed: %d\n", status
);
2242 WriteStatusFile(text
);
2245 #ifdef MAINTENANCE_SERVICE
2247 * Read the update.status file and sets isPendingService to true if
2248 * the status is set to pending-service.
2250 * @param isPendingService Out parameter for specifying if the status
2251 * is set to pending-service or not.
2252 * @return true if the information was retrieved and it is pending
2253 * or pending-service.
2256 IsUpdateStatusPendingService()
2258 NS_tchar filename
[MAXPATHLEN
];
2259 NS_tsnprintf(filename
, sizeof(filename
)/sizeof(filename
[0]),
2260 NS_T("%s/update.status"), gPatchDirPath
);
2262 AutoFile
file(NS_tfopen(filename
, NS_T("rb")));
2263 if (file
== nullptr)
2266 char buf
[32] = { 0 };
2267 fread(buf
, sizeof(buf
), 1, file
);
2269 const char kPendingService
[] = "pending-service";
2270 const char kAppliedService
[] = "applied-service";
2272 return (strncmp(buf
, kPendingService
,
2273 sizeof(kPendingService
) - 1) == 0) ||
2274 (strncmp(buf
, kAppliedService
,
2275 sizeof(kAppliedService
) - 1) == 0);
2281 * Read the update.status file and sets isSuccess to true if
2282 * the status is set to succeeded.
2284 * @param isSucceeded Out parameter for specifying if the status
2285 * is set to succeeded or not.
2286 * @return true if the information was retrieved and it is succeeded.
2289 IsUpdateStatusSucceeded(bool &isSucceeded
)
2291 isSucceeded
= false;
2292 NS_tchar filename
[MAXPATHLEN
];
2293 NS_tsnprintf(filename
, sizeof(filename
)/sizeof(filename
[0]),
2294 NS_T("%s/update.status"), gPatchDirPath
);
2296 AutoFile
file(NS_tfopen(filename
, NS_T("rb")));
2297 if (file
== nullptr)
2300 char buf
[32] = { 0 };
2301 fread(buf
, sizeof(buf
), 1, file
);
2303 const char kSucceeded
[] = "succeeded";
2304 isSucceeded
= strncmp(buf
, kSucceeded
,
2305 sizeof(kSucceeded
) - 1) == 0;
2311 * Copy the entire contents of the application installation directory to the
2312 * destination directory for the update process.
2314 * @return 0 if successful, an error code otherwise.
2317 CopyInstallDirToDestDir()
2319 // These files should not be copied over to the updated app
2321 #define SKIPLIST_COUNT 4
2322 #elif defined(MACOSX)
2323 #define SKIPLIST_COUNT 1
2325 #define SKIPLIST_COUNT 3
2327 copy_recursive_skiplist
<SKIPLIST_COUNT
> skiplist
;
2329 std::unique_ptr
<NS_tchar
[]> pUserProfile(new NS_tchar
[MAXPATHLEN
]);
2330 NS_tstrcpy(pUserProfile
.get(), gPatchDirPath
);
2331 NS_tchar
*slash
= (NS_tchar
*) NS_tstrrchr(pUserProfile
.get(), NS_T('/'));
2333 *slash
= NS_T('\0');
2335 LOG(("ignore user profile directory during copy: " LOG_S
, pUserProfile
.get()));
2337 skiplist
.append(0, pUserProfile
.get());
2339 skiplist
.append(1, gInstallDirPath
, NS_T("updated"));
2340 skiplist
.append(2, gInstallDirPath
, NS_T("updates/0"));
2342 skiplist
.append(4, gInstallDirPath
, NS_T("updated.update_in_progress.lock"));
2346 return ensure_copy_recursive(gInstallDirPath
, gWorkingDirPath
, skiplist
);
2350 * Replace the application installation directory with the destination
2351 * directory in order to finish a staged update task
2353 * @return 0 if successful, an error code otherwise.
2356 ProcessReplaceRequest()
2358 // TODO: moggi: handle the user profile in the installation dir also
2359 // during the replacement request
2360 // The replacement algorithm is like this:
2361 // 1. Move destDir to tmpDir. In case of failure, abort.
2362 // 2. Move newDir to destDir. In case of failure, revert step 1 and abort.
2363 // 3. Delete tmpDir (or defer it to the next reboot).
2366 NS_tchar destDir
[MAXPATHLEN
];
2367 NS_tsnprintf(destDir
, sizeof(destDir
)/sizeof(destDir
[0]),
2368 NS_T("%s/Contents"), gInstallDirPath
);
2369 #elif defined(_WIN32)
2370 // Windows preserves the case of the file/directory names. We use the
2371 // GetLongPathName API in order to get the correct case for the directory
2372 // name, so that if the user has used a different case when launching the
2373 // application, the installation directory's name does not change.
2374 NS_tchar destDir
[MAXPATHLEN
];
2375 if (!GetLongPathNameW(gInstallDirPath
, destDir
,
2376 sizeof(destDir
)/sizeof(destDir
[0])))
2378 return NO_INSTALLDIR_ERROR
;
2381 NS_tchar
* destDir
= gInstallDirPath
;
2384 NS_tchar tmpDir
[MAXPATHLEN
];
2385 NS_tsnprintf(tmpDir
, sizeof(tmpDir
)/sizeof(tmpDir
[0]),
2386 NS_T("%s.bak"), destDir
);
2388 // First try to remove the possibly existing temp directory, because if this
2389 // directory exists, we will fail to rename destDir.
2390 // No need to error check here because if this fails, we will fail in the
2391 // next step anyways.
2392 ensure_remove_recursive(tmpDir
);
2394 LOG(("Begin moving destDir (" LOG_S
") to tmpDir (" LOG_S
")",
2397 int rv
= rename_file(destDir
, tmpDir
, true);
2399 // On Windows, if Firefox is launched using the shortcut, it will hold a handle
2400 // to its installation directory open, which might not get released in time.
2401 // Therefore we wait a little bit here to see if the handle is released.
2402 // If it's not released, we just fail to perform the replace request.
2403 const int max_retries
= 10;
2405 while (rv
== WRITE_ERROR
&& (retries
++ < max_retries
))
2407 LOG(("PerformReplaceRequest: destDir rename attempt %d failed. " \
2408 "File: " LOG_S
". Last error: %d, err: %d", retries
,
2409 destDir
, GetLastError(), rv
));
2413 rv
= rename_file(destDir
, tmpDir
, true);
2418 // The status file will have 'pending' written to it so there is no value in
2419 // returning an error specific for this failure.
2420 LOG(("Moving destDir to tmpDir failed, err: %d", rv
));
2424 NS_tchar newDir
[MAXPATHLEN
];
2425 if (is_userprofile_in_instdir())
2427 LOG(("user profile in instdir"));
2428 NS_tstrcpy(newDir
, tmpDir
);
2429 NS_tstrcat(newDir
, gWorkingDirPath
+ NS_tstrlen(gInstallDirPath
));
2430 LOG((LOG_S
, newDir
));
2434 NS_tsnprintf(newDir
, sizeof(newDir
)/sizeof(newDir
[0]),
2439 LOG(("Begin moving newDir (" LOG_S
") to destDir (" LOG_S
")",
2441 rv
= rename_file(newDir
, destDir
, true);
2445 LOG(("Moving failed. Begin copying newDir (" LOG_S
") to destDir (" LOG_S
")",
2447 copy_recursive_skiplist
<0> skiplist
;
2448 rv
= ensure_copy_recursive(newDir
, destDir
, skiplist
);
2453 LOG(("Moving newDir to destDir failed, err: %d", rv
));
2454 LOG(("Now, try to move tmpDir back to destDir"));
2455 ensure_remove_recursive(destDir
);
2456 int rv2
= rename_file(tmpDir
, destDir
, true);
2459 LOG(("Moving tmpDir back to destDir failed, err: %d", rv2
));
2461 // The status file will be have 'pending' written to it so there is no value
2462 // in returning an error specific for this failure.
2466 if (is_userprofile_in_instdir())
2468 // 1.) calculate path of the user profile in the backup directory
2469 // 2.) move the user profile from the backup to the install directory
2470 NS_tchar backup_user_profile
[MAXPATHLEN
];
2471 NS_tchar userprofile
[MAXPATHLEN
];
2473 NS_tstrcpy(userprofile
, gPatchDirPath
);
2474 NS_tchar
* slash
= (NS_tchar
*) NS_tstrrchr(userprofile
, NS_T('/'));
2476 *slash
= NS_T('\0');
2477 NS_tstrcpy(backup_user_profile
, tmpDir
);
2478 size_t installdir_len
= NS_tstrlen(destDir
);
2480 NS_tstrcat(backup_user_profile
, userprofile
+ installdir_len
);
2481 LOG(("copy user profile back from " LOG_S
" to " LOG_S
, backup_user_profile
, userprofile
));
2482 int rv2
= rename_file(backup_user_profile
, userprofile
);
2485 LOG(("failed to copy user profile back"));
2491 #if !defined(_WIN32) && !defined(MACOSX)
2492 // Platforms that have their updates directory in the installation directory
2493 // need to have the last-update.log and backup-update.log files moved from the
2494 // old installation directory to the new installation directory.
2495 NS_tchar tmpLog
[MAXPATHLEN
];
2496 int nWrittenBytes
= NS_tsnprintf(tmpLog
, sizeof(tmpLog
)/sizeof(tmpLog
[0]),
2497 NS_T("%s/updates/last-update.log"), tmpDir
);
2498 (void) nWrittenBytes
;
2499 if (!NS_taccess(tmpLog
, F_OK
))
2501 NS_tchar destLog
[MAXPATHLEN
];
2502 NS_tsnprintf(destLog
, sizeof(destLog
)/sizeof(destLog
[0]),
2503 NS_T("%s/updates/last-update.log"), destDir
);
2504 NS_tremove(destLog
);
2505 NS_trename(tmpLog
, destLog
);
2509 LOG(("Now, remove the tmpDir"));
2510 rv
= ensure_remove_recursive(tmpDir
, true);
2513 LOG(("Removing tmpDir failed, err: %d", rv
));
2515 NS_tchar deleteDir
[MAXPATHLEN
];
2516 NS_tsnprintf(deleteDir
, sizeof(deleteDir
)/sizeof(deleteDir
[0]),
2517 NS_T("%s\\%s"), destDir
, DELETE_DIR
);
2518 // Attempt to remove the tobedeleted directory and then recreate it if it
2519 // was successfully removed.
2521 if (NS_taccess(deleteDir
, F_OK
))
2523 NS_tmkdir(deleteDir
, 0755);
2525 remove_recursive_on_reboot(tmpDir
, deleteDir
);
2530 // On macOS, we need to remove the staging directory after its Contents
2531 // directory has been moved.
2532 NS_tchar updatedAppDir
[MAXPATHLEN
];
2533 NS_tsnprintf(updatedAppDir
, sizeof(updatedAppDir
)/sizeof(updatedAppDir
[0]),
2534 NS_T("%s/Updated.app"), gPatchDirPath
);
2535 ensure_remove_recursive(updatedAppDir
);
2545 WaitForServiceFinishThread(void* /*param*/)
2547 // We wait at most 10 minutes, we already waited 5 seconds previously
2548 // before deciding to show this UI.
2549 WaitForServiceStop(SVC_NAME
, 595);
2554 #ifdef VERIFY_MAR_SIGNATURE
2556 * This function reads in the ACCEPTED_MAR_CHANNEL_IDS from update-settings.ini
2558 * @param path The path to the ini file that is to be read
2559 * @param results A pointer to the location to store the read strings
2560 * @return OK on success
2563 ReadMARChannelIDs(const NS_tchar
*path
, MARChannelStringTable
*results
)
2565 // TODO: moggi: needs adaptation for LibreOffice
2566 // Check where this function gets its parameters from
2567 const unsigned int kNumStrings
= 1;
2568 const char *kUpdaterKeys
= "ACCEPTED_MAR_CHANNEL_IDS\0";
2569 char updater_strings
[kNumStrings
][MAX_TEXT_LEN
];
2571 int result
= ReadStrings(path
, kUpdaterKeys
, kNumStrings
,
2572 updater_strings
, "Settings");
2574 strncpy(results
->MARChannelID
, updater_strings
[0], MAX_TEXT_LEN
- 1);
2575 results
->MARChannelID
[MAX_TEXT_LEN
- 1] = 0;
2582 GetUpdateFileNames(std::vector
<tstring
>& fileNames
)
2584 NS_tchar fileName
[MAXPATHLEN
];
2585 NS_tsnprintf(fileName
, MAXPATHLEN
,
2586 NS_T("%s/update.mar"), gPatchDirPath
);
2587 fileNames
.push_back(fileName
);
2589 // add the language packs
2590 NS_tDIR
* dir
= NS_topendir(gPatchDirPath
);
2593 LOG(("Could not open directory " LOG_S
, gPatchDirPath
));
2598 while ((entry
= NS_treaddir(dir
)) != nullptr)
2600 if (NS_tstrcmp(entry
->d_name
, NS_T(".")) &&
2601 NS_tstrcmp(entry
->d_name
, NS_T("..")) &&
2602 NS_tstrcmp(entry
->d_name
, NS_T("update.mar")))
2604 if (NS_tstrncmp(entry
->d_name
, NS_T("update"), 6) == 0)
2606 NS_tchar
*dot
= NS_tstrrchr(entry
->d_name
, NS_T('.'));
2607 if (dot
&& !NS_tstrcmp(dot
, NS_T(".mar")))
2609 NS_tchar updatePath
[MAXPATHLEN
];
2610 NS_tsnprintf(updatePath
, sizeof(updatePath
)/sizeof(updatePath
[0]),
2611 NS_T("%s/%s"), gPatchDirPath
, entry
->d_name
);
2613 LOG (("Found language update file: " LOG_S
, updatePath
));
2614 fileNames
.push_back(updatePath
);
2623 CheckSignature(ArchiveReader
& archiveReader
)
2625 #ifdef VERIFY_MAR_SIGNATURE
2627 HKEY baseKey
= nullptr;
2628 wchar_t valueName
[] = L
"Image Path";
2629 wchar_t rasenh
[] = L
"rsaenh.dll";
2631 if (RegOpenKeyExW(HKEY_LOCAL_MACHINE
,
2632 L
"SOFTWARE\\Microsoft\\Cryptography\\Defaults\\Provider\\Microsoft Enhanced Cryptographic Provider v1.0",
2633 0, KEY_READ
| KEY_WRITE
,
2634 &baseKey
) == ERROR_SUCCESS
)
2636 wchar_t path
[MAX_PATH
+ 1];
2637 DWORD size
= sizeof(path
);
2639 if (RegQueryValueExW(baseKey
, valueName
, 0, &type
,
2640 (LPBYTE
)path
, &size
) == ERROR_SUCCESS
)
2642 if (type
== REG_SZ
&& wcscmp(path
, rasenh
) == 0)
2644 wchar_t rasenhFullPath
[] = L
"%SystemRoot%\\System32\\rsaenh.dll";
2645 if (RegSetValueExW(baseKey
, valueName
, 0, REG_SZ
,
2646 (const BYTE
*)rasenhFullPath
,
2647 sizeof(rasenhFullPath
)) == ERROR_SUCCESS
)
2655 int rv
= archiveReader
.VerifySignature();
2661 RegSetValueExW(baseKey
, valueName
, 0, REG_SZ
,
2662 (const BYTE
*)rasenh
,
2665 RegCloseKey(baseKey
);
2674 NS_tchar updateSettingsPath
[MAX_TEXT_LEN
];
2676 // TODO: moggi: needs adaptation for LibreOffice
2677 // These paths need to be adapted for us.
2678 int nWrittenBytes
= NS_tsnprintf(updateSettingsPath
,
2679 sizeof(updateSettingsPath
) / sizeof(updateSettingsPath
[0]),
2681 NS_T("%s/Contents/Resources/update-settings.ini"),
2683 NS_T("%s/update-settings.ini"),
2686 (void) nWrittenBytes
;
2687 MARChannelStringTable MARStrings
;
2688 if (ReadMARChannelIDs(updateSettingsPath
, &MARStrings
) != OK
)
2690 // If we can't read from update-settings.ini then we shouldn't impose
2691 // a MAR restriction. Some installations won't even include this file.
2692 MARStrings
.MARChannelID
[0] = '\0';
2695 rv
= archiveReader
.VerifyProductInformation(MARStrings
.MARChannelID
,
2696 LIBO_VERSION_DOTTED
);
2705 UpdateThreadFunc(void * /*param*/)
2707 // open ZIP archive and process...
2709 if (sReplaceRequest
)
2711 rv
= ProcessReplaceRequest();
2715 std::vector
<tstring
> fileNames
;
2716 GetUpdateFileNames(fileNames
);
2718 for (auto& fileName
: fileNames
)
2720 ArchiveReader archiveReader
;
2721 rv
= archiveReader
.Open(fileName
.c_str());
2724 LOG(("Could not open " LOG_S
, fileName
.c_str()));
2728 rv
= CheckSignature(archiveReader
);
2731 LOG(("Could not verify the signature of " LOG_S
, fileName
.c_str()));
2736 if (rv
== OK
&& sStagedUpdate
)
2738 rv
= CopyInstallDirToDestDir();
2743 for (auto& fileName
: fileNames
)
2745 ArchiveReader archiveReader
;
2746 archiveReader
.Open(fileName
.c_str());
2747 rv
= DoUpdate(archiveReader
);
2749 NS_tchar updatingDir
[MAXPATHLEN
];
2750 int nWrittenBytes
= NS_tsnprintf(updatingDir
, sizeof(updatingDir
)/sizeof(updatingDir
[0]),
2751 NS_T("%s/updating"), gWorkingDirPath
);
2752 (void) nWrittenBytes
;
2753 ensure_remove_recursive(updatingDir
);
2757 if (rv
&& (sReplaceRequest
|| sStagedUpdate
))
2760 // On Windows, the current working directory of the process should be changed
2761 // so that it's not locked.
2764 NS_tchar sysDir
[MAX_PATH
+ 1] = { L
'\0' };
2765 if (GetSystemDirectoryW(sysDir
, MAX_PATH
+ 1))
2771 ensure_remove_recursive(gWorkingDirPath
);
2772 // When attempting to replace the application, we should fall back
2773 // to non-staged updates in case of a failure. We do this by
2774 // setting the status to pending, exiting the updater, and
2775 // launching the callback application. The callback application's
2776 // startup path will see the pending status, and will start the
2777 // updater application again in order to apply the update without
2779 if (sReplaceRequest
)
2781 WriteStatusFile(sUsingService
? "pending-service" : "pending");
2785 WriteStatusFile(rv
);
2788 // Some tests need to use --test-process-updates again.
2789 putenv(const_cast<char*>("MOZ_TEST_PROCESS_UPDATES="));
2796 LOG(("failed: %d", rv
));
2801 // If the update was successful we need to update the timestamp on the
2802 // top-level macOS bundle directory so that macOS's Launch Services
2803 // picks up any major changes when the bundle is updated.
2804 if (!sStagedUpdate
&& utimes(gInstallDirPath
, nullptr) != 0)
2806 LOG(("Couldn't set access/modification time on application bundle."));
2812 WriteStatusFile(rv
);
2815 LOG(("calling QuitProgressUI"));
2821 ServeElevatedUpdateThreadFunc(void* param
)
2823 UpdateServerThreadArgs
* threadArgs
= (UpdateServerThreadArgs
*)param
;
2824 gSucceeded
= ServeElevatedUpdate(threadArgs
->argc
, threadArgs
->argv
);
2827 WriteStatusFile(ELEVATION_CANCELED
);
2832 void freeArguments(int argc
, char** argv
)
2834 for (int i
= 0; i
< argc
; i
++)
2842 int LaunchCallbackAndPostProcessApps(int argc
, NS_tchar
** argv
,
2845 , const WCHAR
* elevatedLockFilePath
2846 , HANDLE updateLockFileHandle
2847 #elif defined(MACOSX)
2852 if (argc
> callbackIndex
)
2857 if (!LaunchWinPostProcess(gInstallDirPath
, gPatchDirPath
))
2859 fprintf(stderr
, "The post update process was not launched");
2862 // The service update will only be executed if it is already installed.
2863 // For first time installs of the service, the install will happen from
2864 // the PostUpdate process. We do the service update process here
2865 // because it's possible we are updating with updater.exe without the
2866 // service if the service failed to apply the update. We want to update
2867 // the service to a newer version in that case. If we are not running
2868 // through the service, then USING_SERVICE will not exist.
2871 StartServiceUpdate(gInstallDirPath
);
2874 EXIT_WHEN_ELEVATED(elevatedLockFilePath
, updateLockFileHandle
, 0);
2875 #elif defined(MACOSX)
2880 LaunchMacPostProcess(gInstallDirPath
);
2884 LaunchCallbackApp(argv
[5],
2885 argc
- callbackIndex
,
2886 argv
+ callbackIndex
,
2889 } // if (!isElevated)
2890 #endif /* XP_MACOSX */
2895 int NS_main(int argc
, NS_tchar
**argv
)
2897 // The callback is the remaining arguments starting at callbackIndex.
2898 // The argument specified by callbackIndex is the callback executable and the
2899 // argument prior to callbackIndex is the working directory.
2900 const int callbackIndex
= 6;
2903 // TODO: moggi: needs adaptation for LibreOffice
2905 strstr(argv
[0], "/Library/PrivilegedHelperTools/org.mozilla.updater") != 0;
2908 if (!ObtainUpdaterArguments(&argc
, &argv
))
2910 // Won't actually get here because ObtainUpdaterArguments will terminate
2911 // the current process on failure.
2917 #if defined(VERIFY_MAR_SIGNATURE) && !defined(_WIN32) && !defined(MACOSX)
2918 // On Windows and Mac we rely on native APIs to do verifications so we don't
2919 // need to initialize NSS at all there.
2920 // Otherwise, minimize the amount of NSS we depend on by avoiding all the NSS
2922 if (!NSS_IsInitialized())
2924 if (NSS_NoDB_Init(NULL
) != SECSuccess
)
2926 PRErrorCode error
= PR_GetError();
2927 fprintf(stderr
, "Could not initialize NSS: %s (%d)",
2928 PR_ErrorToName(error
), (int) error
);
2938 InitProgressUI(&argc
, &argv
);
2943 // To process an update the updater command line must at a minimum have the
2944 // directory path containing the updater.mar file to process as the first
2945 // argument, the install directory as the second argument, and the directory
2946 // to apply the update to as the third argument. When the updater is launched
2947 // by another process the PID of the parent process should be provided in the
2948 // optional fourth argument and the updater will wait on the parent process to
2949 // exit if the value is non-zero and the process is present. This is necessary
2950 // due to not being able to update files that are in use on Windows. The
2951 // optional fifth argument is the callback's working directory and the
2952 // optional sixth argument is the callback path. The callback is the
2953 // application to launch after updating and it will be launched when these
2954 // arguments are provided whether the update was successful or not. All
2955 // remaining arguments are optional and are passed to the callback when it is
2959 fprintf(stderr
, "Usage: updater patch-dir install-dir apply-to-dir [wait-pid [callback-working-dir callback-path args...]]\n");
2963 freeArguments(argc
, argv
);
2964 CleanupElevatedMacUpdate(true);
2970 // The directory containing the update information.
2971 gPatchDirPath
= argv
[1];
2973 // The directory we're going to update to.
2974 // We copy this string because we need to remove trailing slashes. The C++
2975 // standard says that it's always safe to write to strings pointed to by argv
2976 // elements, but I don't necessarily believe it.
2977 NS_tstrncpy(gInstallDirPath
, argv
[2], MAXPATHLEN
);
2978 gInstallDirPath
[MAXPATHLEN
- 1] = NS_T('\0');
2979 NS_tchar
*slash
= NS_tstrrchr(gInstallDirPath
, NS_SLASH
);
2980 if (slash
&& !slash
[1])
2982 *slash
= NS_T('\0');
2986 bool useService
= false;
2987 bool testOnlyFallbackKeyExists
= false;
2988 bool noServiceFallback
= false;
2990 // We never want the service to be used unless we build with
2991 // the maintenance service.
2992 #ifdef MAINTENANCE_SERVICE
2993 useService
= IsUpdateStatusPendingService();
2994 // Our tests run with a different apply directory for each test.
2995 // We use this registry key on our test slaves to store the
2996 // allowed name/issuers.
2997 testOnlyFallbackKeyExists
= DoesFallbackKeyExist();
3000 // Remove everything except close window from the context menu
3002 // TODO: moggi: needs adaptation for LibreOffice
3003 HKEY hkApp
= nullptr;
3004 RegCreateKeyExW(HKEY_CURRENT_USER
, L
"Software\\Classes\\Applications",
3005 0, nullptr, REG_OPTION_NON_VOLATILE
, KEY_SET_VALUE
, nullptr,
3008 if (RegCreateKeyExW(HKEY_CURRENT_USER
,
3009 L
"Software\\Classes\\Applications\\updater.exe",
3010 0, nullptr, REG_OPTION_VOLATILE
, KEY_SET_VALUE
, nullptr,
3011 &hkApp
, nullptr) == ERROR_SUCCESS
)
3013 RegSetValueExW(hkApp
, L
"IsHostApp", 0, REG_NONE
, 0, 0);
3014 RegSetValueExW(hkApp
, L
"NoOpenWith", 0, REG_NONE
, 0, 0);
3015 RegSetValueExW(hkApp
, L
"NoStartPage", 0, REG_NONE
, 0, 0);
3021 // If there is a PID specified and it is not '0' then wait for the process to exit.
3030 pid
= _wtoi64(argv
[4]);
3032 pid
= atoi(argv
[4]);
3036 // This is a signal from the parent process that the updater should stage
3038 sStagedUpdate
= true;
3040 else if (NS_tstrstr(argv
[4], NS_T("/replace")))
3042 // We're processing a request to replace the application with a staged
3044 sReplaceRequest
= true;
3048 // The directory we're going to update to.
3049 // We copy this string because we need to remove trailing slashes. The C++
3050 // standard says that it's always safe to write to strings pointed to by argv
3051 // elements, but I don't necessarily believe it.
3052 NS_tstrncpy(gWorkingDirPath
, argv
[3], MAXPATHLEN
);
3053 gWorkingDirPath
[MAXPATHLEN
- 1] = NS_T('\0');
3054 slash
= NS_tstrrchr(gWorkingDirPath
, NS_SLASH
);
3055 if (slash
&& !slash
[1])
3057 *slash
= NS_T('\0');
3061 if (!isElevated
&& !IsRecursivelyWritable(argv
[2]))
3063 // If the app directory isn't recursively writeable, an elevated update is
3065 UpdateServerThreadArgs threadArgs
;
3066 threadArgs
.argc
= argc
;
3067 threadArgs
.argv
= const_cast<const NS_tchar
**>(argv
);
3070 if (t1
.Run(ServeElevatedUpdateThreadFunc
, &threadArgs
) == 0)
3072 // Show an indeterminate progress bar while an elevated update is in
3074 ShowProgressUI(true);
3078 LaunchCallbackAndPostProcessApps(argc
, argv
, callbackIndex
, false);
3079 return gSucceeded
? 0 : 1;
3083 LogInit(gPatchDirPath
, NS_T("update.log"));
3085 if (!WriteStatusFile("applying"))
3087 LOG(("failed setting status to 'applying'"));
3091 freeArguments(argc
, argv
);
3092 CleanupElevatedMacUpdate(true);
3100 LOG(("Performing a staged update"));
3102 else if (sReplaceRequest
)
3104 LOG(("Performing a replace request"));
3107 LOG(("PATCH DIRECTORY " LOG_S
, gPatchDirPath
));
3108 LOG(("INSTALLATION DIRECTORY " LOG_S
, gInstallDirPath
));
3109 LOG(("WORKING DIRECTORY " LOG_S
, gWorkingDirPath
));
3112 if (_wcsnicmp(gWorkingDirPath
, gInstallDirPath
, MAX_PATH
) != 0)
3114 if (!sStagedUpdate
&& !sReplaceRequest
)
3116 WriteStatusFile(INVALID_APPLYTO_DIR_ERROR
);
3117 LOG(("Installation directory and working directory must be the same "
3118 "for non-staged updates. Exiting."));
3123 NS_tchar workingDirParent
[MAX_PATH
];
3124 NS_tsnprintf(workingDirParent
,
3125 sizeof(workingDirParent
) / sizeof(workingDirParent
[0]),
3126 NS_T("%s"), gWorkingDirPath
);
3127 if (!PathRemoveFileSpecW(workingDirParent
))
3129 WriteStatusFile(REMOVE_FILE_SPEC_ERROR
);
3130 LOG(("Error calling PathRemoveFileSpecW: %d", GetLastError()));
3135 if (_wcsnicmp(workingDirParent
, gInstallDirPath
, MAX_PATH
) != 0)
3137 WriteStatusFile(INVALID_APPLYTO_DIR_STAGED_ERROR
);
3138 LOG(("The apply-to directory must be the same as or "
3139 "a child of the installation directory! Exiting."));
3150 HANDLE parent
= OpenProcess(SYNCHRONIZE
, false, (DWORD
) pid
);
3151 // May return nullptr if the parent process has already gone away.
3152 // Otherwise, wait for the parent process to exit before starting the
3156 DWORD waitTime
= PARENT_WAIT
;
3157 DWORD result
= WaitForSingleObject(parent
, waitTime
);
3158 CloseHandle(parent
);
3159 if (result
!= WAIT_OBJECT_0
)
3165 waitpid(pid
, nullptr, 0);
3169 #ifdef MAINTENANCE_SERVICE
3170 sUsingService
= EnvHasValue("USING_SERVICE");
3171 putenv(const_cast<char*>("USING_SERVICE="));
3173 // lastFallbackError keeps track of the last error for the service not being
3174 // used, in case of an error when fallback is not enabled we write the
3175 // error to the update.status file.
3176 // When fallback is disabled (MOZ_NO_SERVICE_FALLBACK does not exist) then
3177 // we will instead fallback to not using the service and display a UAC prompt.
3178 int lastFallbackError
= FALLBACKKEY_UNKNOWN_ERROR
;
3180 // Launch a second instance of the updater with the runas verb on Windows
3181 // when write access is denied to the installation directory.
3182 HANDLE updateLockFileHandle
= INVALID_HANDLE_VALUE
;
3183 NS_tchar elevatedLockFilePath
[MAXPATHLEN
] = {NS_T('\0')};
3184 if (!sUsingService
&&
3185 (argc
> callbackIndex
|| sStagedUpdate
|| sReplaceRequest
))
3187 NS_tchar updateLockFilePath
[MAXPATHLEN
];
3190 // When staging an update, the lock file is:
3191 // <install_dir>\updated.update_in_progress.lock
3192 NS_tsnprintf(updateLockFilePath
,
3193 sizeof(updateLockFilePath
)/sizeof(updateLockFilePath
[0]),
3194 NS_T("%s/updated.update_in_progress.lock"), gInstallDirPath
);
3196 else if (sReplaceRequest
)
3198 // When processing a replace request, the lock file is:
3199 // <install_dir>\..\moz_update_in_progress.lock
3200 NS_tchar installDir
[MAXPATHLEN
];
3201 NS_tstrcpy(installDir
, gInstallDirPath
);
3202 NS_tchar
*slash
= (NS_tchar
*) NS_tstrrchr(installDir
, NS_SLASH
);
3203 *slash
= NS_T('\0');
3204 NS_tsnprintf(updateLockFilePath
,
3205 sizeof(updateLockFilePath
)/sizeof(updateLockFilePath
[0]),
3206 NS_T("%s\\moz_update_in_progress.lock"), installDir
);
3210 // In the non-staging update case, the lock file is:
3211 // <install_dir>\<app_name>.exe.update_in_progress.lock
3212 NS_tsnprintf(updateLockFilePath
,
3213 sizeof(updateLockFilePath
)/sizeof(updateLockFilePath
[0]),
3214 NS_T("%s.update_in_progress.lock"), argv
[callbackIndex
]);
3217 // The update_in_progress.lock file should only exist during an update. In
3218 // case it exists attempt to remove it and exit if that fails to prevent
3219 // simultaneous updates occurring.
3220 if (!_waccess(updateLockFilePath
, F_OK
) &&
3221 NS_tremove(updateLockFilePath
) != 0)
3223 // Try to fall back to the old way of doing updates if a staged
3225 if (sStagedUpdate
|| sReplaceRequest
)
3227 // Note that this could fail, but if it does, there isn't too much we
3228 // can do in order to recover anyways.
3229 WriteStatusFile("pending");
3231 LOG(("Update already in progress! Exiting"));
3235 updateLockFileHandle
= CreateFileW(updateLockFilePath
,
3236 GENERIC_READ
| GENERIC_WRITE
,
3240 FILE_FLAG_DELETE_ON_CLOSE
,
3243 NS_tsnprintf(elevatedLockFilePath
,
3244 sizeof(elevatedLockFilePath
)/sizeof(elevatedLockFilePath
[0]),
3245 NS_T("%s/update_elevated.lock"), gPatchDirPath
);
3247 // Even if a file has no sharing access, you can still get its attributes
3248 bool startedFromUnelevatedUpdater
=
3249 GetFileAttributesW(elevatedLockFilePath
) != INVALID_FILE_ATTRIBUTES
;
3251 // If we're running from the service, then we were started with the same
3252 // token as the service so the permissions are already dropped. If we're
3253 // running from an elevated updater that was started from an unelevated
3254 // updater, then we drop the permissions here. We do not drop the
3255 // permissions on the originally called updater because we use its token
3256 // to start the callback application.
3257 if (startedFromUnelevatedUpdater
)
3259 // Disable every privilege we don't need. Processes started using
3260 // CreateProcess will use the same token as this process.
3261 UACHelper::DisablePrivileges(nullptr);
3264 if (updateLockFileHandle
== INVALID_HANDLE_VALUE
||
3265 (useService
&& testOnlyFallbackKeyExists
&& noServiceFallback
))
3267 if (!_waccess(elevatedLockFilePath
, F_OK
) &&
3268 NS_tremove(elevatedLockFilePath
) != 0)
3270 fprintf(stderr
, "Unable to create elevated lock file! Exiting\n");
3274 HANDLE elevatedFileHandle
;
3275 elevatedFileHandle
= CreateFileW(elevatedLockFilePath
,
3276 GENERIC_READ
| GENERIC_WRITE
,
3280 FILE_FLAG_DELETE_ON_CLOSE
,
3283 if (elevatedFileHandle
== INVALID_HANDLE_VALUE
)
3285 LOG(("Unable to create elevated lock file! Exiting"));
3289 wchar_t *cmdLine
= MakeCommandLine(argc
- 1, argv
+ 1);
3292 CloseHandle(elevatedFileHandle
);
3296 // Make sure the path to the updater to use for the update is on local.
3297 // We do this check to make sure that file locking is available for
3298 // race condition security checks.
3301 BOOL isLocal
= FALSE
;
3302 useService
= IsLocalFile(argv
[0], isLocal
) && isLocal
;
3305 // If we have unprompted elevation we should NOT use the service
3306 // for the update. Service updates happen with the SYSTEM account
3307 // which has more privs than we need to update with.
3308 // Windows 8 provides a user interface so users can configure this
3309 // behavior and it can be configured in the registry in all Windows
3310 // versions that support UAC.
3313 BOOL unpromptedElevation
;
3314 if (IsUnpromptedElevation(unpromptedElevation
))
3316 useService
= !unpromptedElevation
;
3320 // Make sure the service registry entries for the installation path
3321 // are available. If not don't use the service.
3324 WCHAR maintenanceServiceKey
[MAX_PATH
+ 1];
3325 // TODO: moggi: needs adaptation for LibreOffice
3326 // Most likely the registry part is not correct yet
3327 if (CalculateRegistryPathFromFilePath(gInstallDirPath
,
3328 maintenanceServiceKey
))
3330 HKEY baseKey
= nullptr;
3331 if (RegOpenKeyExW(HKEY_LOCAL_MACHINE
,
3332 maintenanceServiceKey
, 0,
3333 KEY_READ
| KEY_WOW64_64KEY
,
3334 &baseKey
) == ERROR_SUCCESS
)
3336 RegCloseKey(baseKey
);
3341 useService
= testOnlyFallbackKeyExists
;
3345 lastFallbackError
= FALLBACKKEY_NOKEY_ERROR
;
3352 lastFallbackError
= FALLBACKKEY_REGPATH_ERROR
;
3356 // Originally we used to write "pending" to update.status before
3357 // launching the service command. This is no longer needed now
3358 // since the service command is launched from updater.exe. If anything
3359 // fails in between, we can fall back to using the normal update process
3362 // If we still want to use the service try to launch the service
3363 // command for the update.
3366 // If the update couldn't be started, then set useService to false so
3367 // we do the update the old way.
3368 DWORD ret
= LaunchServiceSoftwareUpdateCommand(argc
, (LPCWSTR
*)argv
);
3369 useService
= (ret
== ERROR_SUCCESS
);
3370 // If the command was launched then wait for the service to be done.
3373 bool showProgressUI
= false;
3374 // Never show the progress UI when staging updates.
3377 // We need to call this separately instead of allowing ShowProgressUI
3378 // to initialize the strings because the service will move the
3379 // ini file out of the way when running updater.
3380 showProgressUI
= !InitProgressUIStrings();
3383 // Wait for the service to stop for 5 seconds. If the service
3384 // has still not stopped then show an indeterminate progress bar.
3385 DWORD lastState
= WaitForServiceStop(SVC_NAME
, 5);
3386 if (lastState
!= SERVICE_STOPPED
)
3388 std::thread
waitThread(WaitForServiceFinishThread
, nullptr);
3391 ShowProgressUI(true, false);
3396 lastState
= WaitForServiceStop(SVC_NAME
, 1);
3397 if (lastState
!= SERVICE_STOPPED
)
3399 // If the service doesn't stop after 10 minutes there is
3400 // something seriously wrong.
3401 lastFallbackError
= FALLBACKKEY_SERVICE_NO_STOP_ERROR
;
3407 lastFallbackError
= FALLBACKKEY_LAUNCH_ERROR
;
3411 // If the service can't be used when staging and update, make sure that
3412 // the UAC prompt is not shown! In this case, just set the status to
3413 // pending and the update will be applied during the next startup.
3414 if (!useService
&& sStagedUpdate
)
3416 if (updateLockFileHandle
!= INVALID_HANDLE_VALUE
)
3418 CloseHandle(updateLockFileHandle
);
3420 WriteStatusFile("pending");
3424 // If we started the service command, and it finished, check the
3425 // update.status file to make sure it succeeded, and if it did
3426 // we need to manually start the PostUpdate process from the
3427 // current user's session of this unelevated updater.exe the
3428 // current process is running as.
3429 // Note that we don't need to do this if we're just staging the update,
3430 // as the PostUpdate step runs when performing the replacing in that case.
3431 if (useService
&& !sStagedUpdate
)
3433 bool updateStatusSucceeded
= false;
3434 if (IsUpdateStatusSucceeded(updateStatusSucceeded
) &&
3435 updateStatusSucceeded
)
3437 if (!LaunchWinPostProcess(gInstallDirPath
, gPatchDirPath
))
3439 fprintf(stderr
, "The post update process which runs as the user"
3440 " for service update could not be launched.");
3445 // If we didn't want to use the service at all, or if an update was
3446 // already happening, or launching the service command failed, then
3447 // launch the elevated updater.exe as we do without the service.
3448 // We don't launch the elevated updater in the case that we did have
3449 // write access all along because in that case the only reason we're
3450 // using the service is because we are testing.
3451 if (!useService
&& !noServiceFallback
&&
3452 updateLockFileHandle
== INVALID_HANDLE_VALUE
)
3454 SHELLEXECUTEINFO sinfo
;
3455 memset(&sinfo
, 0, sizeof(SHELLEXECUTEINFO
));
3456 sinfo
.cbSize
= sizeof(SHELLEXECUTEINFO
);
3457 sinfo
.fMask
= SEE_MASK_FLAG_NO_UI
|
3458 SEE_MASK_FLAG_DDEWAIT
|
3459 SEE_MASK_NOCLOSEPROCESS
;
3460 sinfo
.hwnd
= nullptr;
3461 sinfo
.lpFile
= argv
[0];
3462 sinfo
.lpParameters
= cmdLine
;
3463 sinfo
.lpVerb
= L
"runas";
3464 sinfo
.nShow
= SW_SHOWNORMAL
;
3466 bool result
= ShellExecuteEx(&sinfo
);
3471 WaitForSingleObject(sinfo
.hProcess
, INFINITE
);
3472 CloseHandle(sinfo
.hProcess
);
3476 WriteStatusFile(ELEVATION_CANCELED
);
3480 if (argc
> callbackIndex
)
3482 LaunchCallbackApp(argv
[5], argc
- callbackIndex
,
3483 argv
+ callbackIndex
, sUsingService
);
3486 CloseHandle(elevatedFileHandle
);
3488 if (!useService
&& !noServiceFallback
&&
3489 INVALID_HANDLE_VALUE
== updateLockFileHandle
)
3491 // We didn't use the service and we did run the elevated updater.exe.
3492 // The elevated updater.exe is responsible for writing out the
3493 // update.status file.
3496 else if (useService
)
3498 // The service command was launched. The service is responsible for
3499 // writing out the update.status file.
3500 if (updateLockFileHandle
!= INVALID_HANDLE_VALUE
)
3502 CloseHandle(updateLockFileHandle
);
3508 // Otherwise the service command was not launched at all.
3509 // We are only reaching this code path because we had write access
3510 // all along to the directory and a fallback key existed, and we
3511 // have fallback disabled (MOZ_NO_SERVICE_FALLBACK env var exists).
3512 // We only currently use this env var from XPCShell tests.
3513 CloseHandle(updateLockFileHandle
);
3514 WriteStatusFile(lastFallbackError
);
3523 // When staging updates, blow away the old installation directory and create
3525 ensure_remove_recursive(gWorkingDirPath
);
3527 if (!sReplaceRequest
)
3529 // Try to create the destination directory if it doesn't exist
3530 int rv
= NS_tmkdir(gWorkingDirPath
, 0755);
3531 if (rv
!= OK
&& errno
!= EEXIST
)
3536 freeArguments(argc
, argv
);
3537 CleanupElevatedMacUpdate(true);
3545 // For replace requests, we don't need to do any real updates, so this is not
3547 if (!sReplaceRequest
)
3549 // Allocate enough space for the length of the path an optional additional
3550 // trailing slash and null termination.
3551 NS_tchar
*destpath
= (NS_tchar
*) malloc((NS_tstrlen(gWorkingDirPath
) + 2) * sizeof(NS_tchar
));
3555 NS_tchar
*c
= destpath
;
3556 NS_tstrcpy(c
, gWorkingDirPath
);
3557 c
+= NS_tstrlen(gWorkingDirPath
);
3558 if (gWorkingDirPath
[NS_tstrlen(gWorkingDirPath
) - 1] != NS_T('/') &&
3559 gWorkingDirPath
[NS_tstrlen(gWorkingDirPath
) - 1] != NS_T('\\'))
3561 NS_tstrcat(c
, NS_T("/"));
3562 c
+= NS_tstrlen(NS_T("/"));
3567 gDestPath
= destpath
;
3570 NS_tchar applyDirLongPath
[MAXPATHLEN
];
3571 if (!GetLongPathNameW(gWorkingDirPath
, applyDirLongPath
,
3572 sizeof(applyDirLongPath
)/sizeof(applyDirLongPath
[0])))
3574 LOG(("NS_main: unable to find apply to dir: " LOG_S
, gWorkingDirPath
));
3576 WriteStatusFile(WRITE_ERROR_APPLY_DIR_PATH
);
3577 EXIT_WHEN_ELEVATED(elevatedLockFilePath
, updateLockFileHandle
, 1);
3578 if (argc
> callbackIndex
)
3580 LaunchCallbackApp(argv
[5], argc
- callbackIndex
,
3581 argv
+ callbackIndex
, sUsingService
);
3586 HANDLE callbackFile
= INVALID_HANDLE_VALUE
;
3587 if (argc
> callbackIndex
)
3589 // If the callback executable is specified it must exist for a successful
3590 // update. It is important we null out the whole buffer here because later
3591 // we make the assumption that the callback application is inside the
3592 // apply-to dir. If we don't have a fully null'ed out buffer it can lead
3593 // to stack corruption which causes crashes and other problems.
3594 NS_tchar callbackLongPath
[MAXPATHLEN
];
3595 ZeroMemory(callbackLongPath
, sizeof(callbackLongPath
));
3596 NS_tchar
*targetPath
= argv
[callbackIndex
];
3597 NS_tchar buffer
[MAXPATHLEN
* 2] = { NS_T('\0') };
3598 size_t bufferLeft
= MAXPATHLEN
* 2;
3599 if (sReplaceRequest
)
3601 // In case of replace requests, we should look for the callback file in
3602 // the destination directory.
3603 size_t commonPrefixLength
= PathCommonPrefixW(argv
[callbackIndex
],
3606 NS_tchar
*p
= buffer
;
3607 NS_tstrncpy(p
, argv
[callbackIndex
], commonPrefixLength
);
3608 p
+= commonPrefixLength
;
3609 bufferLeft
-= commonPrefixLength
;
3610 NS_tstrncpy(p
, gInstallDirPath
+ commonPrefixLength
, bufferLeft
);
3612 size_t len
= NS_tstrlen(gInstallDirPath
+ commonPrefixLength
);
3619 NS_tchar installDir
[MAXPATHLEN
];
3620 NS_tstrcpy(installDir
, gInstallDirPath
);
3621 size_t callbackPrefixLength
= PathCommonPrefixW(argv
[callbackIndex
],
3624 NS_tstrncpy(p
, argv
[callbackIndex
] + std::max(callbackPrefixLength
,
3625 commonPrefixLength
), bufferLeft
);
3626 targetPath
= buffer
;
3628 if (!GetLongPathNameW(targetPath
, callbackLongPath
,
3629 sizeof(callbackLongPath
)/sizeof(callbackLongPath
[0])))
3631 LOG(("NS_main: unable to find callback file: " LOG_S
, targetPath
));
3633 WriteStatusFile(WRITE_ERROR_CALLBACK_PATH
);
3634 EXIT_WHEN_ELEVATED(elevatedLockFilePath
, updateLockFileHandle
, 1);
3635 if (argc
> callbackIndex
)
3637 LaunchCallbackApp(argv
[5],
3638 argc
- callbackIndex
,
3639 argv
+ callbackIndex
,
3645 // Doing this is only necessary when we're actually applying a patch.
3646 if (!sReplaceRequest
)
3648 int len
= NS_tstrlen(applyDirLongPath
);
3649 NS_tchar
*s
= callbackLongPath
;
3650 NS_tchar
*d
= gCallbackRelPath
;
3651 // advance to the apply to directory and advance past the trailing backslash
3654 if (*s
== NS_T('\\'))
3657 // Copy the string and replace backslashes with forward slashes along the
3661 if (*s
== NS_T('\\'))
3672 // Make a copy of the callback executable so it can be read when patching.
3673 NS_tsnprintf(gCallbackBackupPath
,
3674 sizeof(gCallbackBackupPath
)/sizeof(gCallbackBackupPath
[0]),
3675 NS_T("%s" CALLBACK_BACKUP_EXT
), argv
[callbackIndex
]);
3676 NS_tremove(gCallbackBackupPath
);
3677 if (!CopyFileW(argv
[callbackIndex
], gCallbackBackupPath
, true))
3679 DWORD copyFileError
= GetLastError();
3680 LOG(("NS_main: failed to copy callback file " LOG_S
3681 " into place at " LOG_S
, argv
[callbackIndex
], gCallbackBackupPath
));
3683 if (copyFileError
== ERROR_ACCESS_DENIED
)
3685 WriteStatusFile(WRITE_ERROR_ACCESS_DENIED
);
3689 WriteStatusFile(WRITE_ERROR_CALLBACK_APP
);
3692 EXIT_WHEN_ELEVATED(elevatedLockFilePath
, updateLockFileHandle
, 1);
3693 LaunchCallbackApp(argv
[callbackIndex
],
3694 argc
- callbackIndex
,
3695 argv
+ callbackIndex
,
3700 // Since the process may be signaled as exited by WaitForSingleObject before
3701 // the release of the executable image try to lock the main executable file
3702 // multiple times before giving up. If we end up giving up, we won't
3704 const int max_retries
= 10;
3706 DWORD lastWriteError
= 0;
3709 // By opening a file handle without FILE_SHARE_READ to the callback
3710 // executable, the OS will prevent launching the process while it is
3712 callbackFile
= CreateFileW(targetPath
,
3713 DELETE
| GENERIC_WRITE
,
3714 // allow delete, rename, and write
3715 FILE_SHARE_DELETE
| FILE_SHARE_WRITE
,
3716 nullptr, OPEN_EXISTING
, 0, nullptr);
3717 if (callbackFile
!= INVALID_HANDLE_VALUE
)
3720 lastWriteError
= GetLastError();
3721 LOG(("NS_main: callback app file open attempt %d failed. " \
3722 "File: " LOG_S
". Last error: %d", retries
,
3723 targetPath
, lastWriteError
));
3727 while (++retries
<= max_retries
);
3729 // CreateFileW will fail if the callback executable is already in use.
3730 if (callbackFile
== INVALID_HANDLE_VALUE
)
3732 // Only fail the update if the last error was not a sharing violation.
3733 if (lastWriteError
!= ERROR_SHARING_VIOLATION
)
3735 LOG(("NS_main: callback app file in use, failed to exclusively open " \
3736 "executable file: " LOG_S
, argv
[callbackIndex
]));
3738 if (lastWriteError
== ERROR_ACCESS_DENIED
)
3740 WriteStatusFile(WRITE_ERROR_ACCESS_DENIED
);
3744 WriteStatusFile(WRITE_ERROR_CALLBACK_APP
);
3747 NS_tremove(gCallbackBackupPath
);
3748 EXIT_WHEN_ELEVATED(elevatedLockFilePath
, updateLockFileHandle
, 1);
3749 LaunchCallbackApp(argv
[5],
3750 argc
- callbackIndex
,
3751 argv
+ callbackIndex
,
3755 LOG(("NS_main: callback app file in use, continuing without " \
3756 "exclusive access for executable file: " LOG_S
,
3757 argv
[callbackIndex
]));
3762 // DELETE_DIR is not required when performing a staged update or replace
3763 // request; it can be used during a replace request but then it doesn't
3764 // use gDeleteDirPath.
3765 if (!sStagedUpdate
&& !sReplaceRequest
)
3767 // The directory to move files that are in use to on Windows. This directory
3768 // will be deleted after the update is finished, on OS reboot using
3769 // MoveFileEx if it contains files that are in use, or by the post update
3770 // process after the update finishes. On Windows when performing a normal
3771 // update (e.g. the update is not a staged update and is not a replace
3772 // request) gWorkingDirPath is the same as gInstallDirPath and
3773 // gWorkingDirPath is used because it is the destination directory.
3774 NS_tsnprintf(gDeleteDirPath
,
3775 sizeof(gDeleteDirPath
) / sizeof(gDeleteDirPath
[0]),
3776 NS_T("%s/%s"), gWorkingDirPath
, DELETE_DIR
);
3778 if (NS_taccess(gDeleteDirPath
, F_OK
))
3780 NS_tmkdir(gDeleteDirPath
, 0755);
3785 // Run update process on a background thread. ShowProgressUI may return
3786 // before QuitProgressUI has been called, so wait for UpdateThreadFunc to
3787 // terminate. Avoid showing the progress UI when staging an update, or if this
3788 // is an elevated process on OSX.
3789 std::thread
t(UpdateThreadFunc
, nullptr);
3790 if (!sStagedUpdate
&& !sReplaceRequest
3801 if (argc
> callbackIndex
&& !sReplaceRequest
)
3803 if (callbackFile
!= INVALID_HANDLE_VALUE
)
3805 CloseHandle(callbackFile
);
3807 // Remove the copy of the callback executable.
3808 NS_tremove(gCallbackBackupPath
);
3811 if (!sStagedUpdate
&& !sReplaceRequest
&& _wrmdir(gDeleteDirPath
))
3813 LOG(("NS_main: unable to remove directory: " LOG_S
", err: %d",
3814 DELETE_DIR
, errno
));
3815 // The directory probably couldn't be removed due to it containing files
3816 // that are in use and will be removed on OS reboot. The call to remove the
3817 // directory on OS reboot is done after the calls to remove the files so the
3818 // files are removed first on OS reboot since the directory must be empty
3819 // for the directory removal to be successful. The MoveFileEx call to remove
3820 // the directory on OS reboot will fail if the process doesn't have write
3821 // access to the HKEY_LOCAL_MACHINE registry key but this is ok since the
3822 // installer / uninstaller will delete the directory along with its contents
3823 // after an update is applied, on reinstall, and on uninstall.
3824 if (MoveFileEx(gDeleteDirPath
, nullptr, MOVEFILE_DELAY_UNTIL_REBOOT
))
3826 LOG(("NS_main: directory will be removed on OS reboot: " LOG_S
,
3831 LOG(("NS_main: failed to schedule OS reboot removal of " \
3832 "directory: " LOG_S
, DELETE_DIR
));
3839 // When the update is successful remove the precomplete file in the root of
3840 // the application bundle and move the distribution directory from
3841 // Contents/MacOS to Contents/Resources and if both exist delete the
3842 // directory under Contents/MacOS (see Bug 1068439).
3843 if (gSucceeded
&& !sStagedUpdate
)
3845 NS_tchar oldPrecomplete
[MAXPATHLEN
];
3846 NS_tsnprintf(oldPrecomplete
, sizeof(oldPrecomplete
)/sizeof(oldPrecomplete
[0]),
3847 NS_T("%s/precomplete"), gInstallDirPath
);
3848 NS_tremove(oldPrecomplete
);
3850 NS_tchar oldDistDir
[MAXPATHLEN
];
3851 NS_tsnprintf(oldDistDir
, sizeof(oldDistDir
)/sizeof(oldDistDir
[0]),
3852 NS_T("%s/Contents/MacOS/distribution"), gInstallDirPath
);
3853 int rv
= NS_taccess(oldDistDir
, F_OK
);
3856 NS_tchar newDistDir
[MAXPATHLEN
];
3857 NS_tsnprintf(newDistDir
, sizeof(newDistDir
)/sizeof(newDistDir
[0]),
3858 NS_T("%s/Contents/Resources/distribution"), gInstallDirPath
);
3859 rv
= NS_taccess(newDistDir
, F_OK
);
3862 LOG(("New distribution directory already exists... removing old " \
3863 "distribution directory: " LOG_S
, oldDistDir
));
3864 rv
= ensure_remove_recursive(oldDistDir
);
3867 LOG(("Removing old distribution directory failed - err: %d", rv
));
3872 LOG(("Moving old distribution directory to new location. src: " LOG_S \
3873 ", dst:" LOG_S
, oldDistDir
, newDistDir
));
3874 rv
= rename_file(oldDistDir
, newDistDir
, true);
3877 LOG(("Moving old distribution directory to new location failed - " \
3886 SetGroupOwnershipAndPermissions(gInstallDirPath
);
3887 freeArguments(argc
, argv
);
3888 CleanupElevatedMacUpdate(false);
3890 else if (IsOwnedByGroupAdmin(gInstallDirPath
))
3892 // If the group ownership of the Firefox .app bundle was set to the "admin"
3893 // group during a previous elevated update, we need to ensure that all files
3894 // in the bundle have group ownership of "admin" as well as write permission
3895 // for the group to not break updates in the future.
3896 SetGroupOwnershipAndPermissions(gInstallDirPath
);
3902 int retVal
= LaunchCallbackAndPostProcessApps(argc
, argv
, callbackIndex
3904 , elevatedLockFilePath
3905 , updateLockFileHandle
3906 #elif defined(MACOSX)
3911 return retVal
? retVal
: (gSucceeded
? 0 : 1);
3917 ActionList() : mFirst(nullptr), mLast(nullptr), mCount(0) { }
3920 void Append(Action
* action
);
3923 void Finish(int status
);
3931 ActionList::~ActionList()
3943 ActionList::Append(Action
*action
)
3946 mLast
->mNext
= action
;
3955 ActionList::Prepare()
3957 // If the action list is empty then we should fail in order to signal that
3958 // something has gone wrong. Otherwise we report success when nothing is
3959 // actually done. See bug 327140.
3962 LOG(("empty action list"));
3963 return MAR_ERROR_EMPTY_ACTION_LIST
;
3970 int rv
= a
->Prepare();
3974 float percent
= float(++i
) / float(mCount
);
3975 UpdateProgressUI(PROGRESS_PREPARE_SIZE
* percent
);
3984 ActionList::Execute()
3986 int currentProgress
= 0, maxProgress
= 0;
3990 maxProgress
+= a
->mProgressCost
;
3997 int rv
= a
->Execute();
4000 LOG(("### execution failed"));
4004 currentProgress
+= a
->mProgressCost
;
4005 float percent
= float(currentProgress
) / float(maxProgress
);
4006 UpdateProgressUI(PROGRESS_PREPARE_SIZE
+
4007 PROGRESS_EXECUTE_SIZE
* percent
);
4016 ActionList::Finish(int status
)
4024 float percent
= float(++i
) / float(mCount
);
4025 UpdateProgressUI(PROGRESS_PREPARE_SIZE
+
4026 PROGRESS_EXECUTE_SIZE
+
4027 PROGRESS_FINISH_SIZE
* percent
);
4038 int add_dir_entries(const NS_tchar
*dirpath
, ActionList
*list
)
4041 WIN32_FIND_DATAW finddata
;
4043 NS_tchar searchspec
[MAXPATHLEN
];
4044 NS_tchar foundpath
[MAXPATHLEN
];
4046 NS_tsnprintf(searchspec
, sizeof(searchspec
)/sizeof(searchspec
[0]),
4047 NS_T("%s*"), dirpath
);
4048 std::unique_ptr
<const NS_tchar
[]> pszSpec(new_absolute_path(searchspec
));
4050 hFindFile
= FindFirstFileW(pszSpec
.get(), &finddata
);
4051 if (hFindFile
!= INVALID_HANDLE_VALUE
)
4055 // Don't process the current or parent directory.
4056 if (NS_tstrcmp(finddata
.cFileName
, NS_T(".")) == 0 ||
4057 NS_tstrcmp(finddata
.cFileName
, NS_T("..")) == 0)
4060 NS_tsnprintf(foundpath
, sizeof(foundpath
)/sizeof(foundpath
[0]),
4061 NS_T("%s%s"), dirpath
, finddata
.cFileName
);
4062 if (finddata
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
)
4064 NS_tsnprintf(foundpath
, sizeof(foundpath
)/sizeof(foundpath
[0]),
4065 NS_T("%s/"), foundpath
);
4066 // Recurse into the directory.
4067 rv
= add_dir_entries(foundpath
, list
);
4070 LOG(("add_dir_entries error: " LOG_S
", err: %d", foundpath
, rv
));
4076 // Add the file to be removed to the ActionList.
4077 NS_tchar
*quotedpath
= get_quoted_path(foundpath
);
4081 Action
*action
= new RemoveFile();
4082 rv
= action
->Parse(quotedpath
);
4085 LOG(("add_dir_entries Parse error on recurse: " LOG_S
", err: %d",
4091 list
->Append(action
);
4094 while (FindNextFileW(hFindFile
, &finddata
) != 0);
4096 FindClose(hFindFile
);
4098 // Add the directory to be removed to the ActionList.
4099 NS_tchar
*quotedpath
= get_quoted_path(dirpath
);
4103 Action
*action
= new RemoveDir();
4104 rv
= action
->Parse(quotedpath
);
4106 LOG(("add_dir_entries Parse error on close: " LOG_S
", err: %d",
4109 list
->Append(action
);
4117 #elif defined(__sun)
4118 int add_dir_entries(const NS_tchar
*dirpath
, ActionList
*list
)
4121 NS_tchar foundpath
[MAXPATHLEN
];
4125 char chars
[MAXNAMLEN
];
4128 std::unique_ptr
<NS_tchar
[]> searchpath(new_absolute_path(dirpath
));
4130 DIR* dir
= opendir(searchpath
.get());
4133 LOG(("add_dir_entries error on opendir: " LOG_S
", err: %d", searchpath
.get(),
4135 return UNEXPECTED_FILE_OPERATION_ERROR
;
4138 while (readdir_r(dir
, (dirent
*)&ent_buf
, &ent
) == 0 && ent
)
4140 if ((strcmp(ent
->d_name
, ".") == 0) ||
4141 (strcmp(ent
->d_name
, "..") == 0))
4144 NS_tsnprintf(foundpath
, sizeof(foundpath
)/sizeof(foundpath
[0]),
4145 NS_T("%s%s"), searchpath
.get(), ent
->d_name
);
4146 struct stat64 st_buf
;
4147 int test
= stat64(foundpath
, &st_buf
);
4151 return UNEXPECTED_FILE_OPERATION_ERROR
;
4153 if (S_ISDIR(st_buf
.st_mode
))
4155 NS_tsnprintf(foundpath
, sizeof(foundpath
)/sizeof(foundpath
[0]),
4156 NS_T("%s/"), foundpath
);
4157 // Recurse into the directory.
4158 rv
= add_dir_entries(foundpath
, list
);
4161 LOG(("add_dir_entries error: " LOG_S
", err: %d", foundpath
, rv
));
4168 // Add the file to be removed to the ActionList.
4169 NS_tchar
*quotedpath
= get_quoted_path(get_relative_offset(foundpath
));
4176 Action
*action
= new RemoveFile();
4177 rv
= action
->Parse(quotedpath
);
4180 LOG(("add_dir_entries Parse error on recurse: " LOG_S
", err: %d",
4186 list
->Append(action
);
4191 // Add the directory to be removed to the ActionList.
4192 NS_tchar
*quotedpath
= get_quoted_path(get_relative_offset(dirpath
));
4196 Action
*action
= new RemoveDir();
4197 rv
= action
->Parse(quotedpath
);
4200 LOG(("add_dir_entries Parse error on close: " LOG_S
", err: %d",
4205 list
->Append(action
);
4213 int add_dir_entries(const NS_tchar
*dirpath
, ActionList
*list
)
4217 FTSENT
*ftsdirEntry
;
4218 std::unique_ptr
<NS_tchar
[]> searchpath(new_absolute_path(dirpath
));
4220 // Remove the trailing slash so the paths don't contain double slashes. The
4221 // existence of the slash has already been checked in DoUpdate.
4222 searchpath
.get()[NS_tstrlen(searchpath
.get()) - 1] = NS_T('\0');
4223 char* const pathargv
[] = {searchpath
.get(), nullptr};
4225 // FTS_NOCHDIR is used so relative paths from the destination directory are
4227 if (!(ftsdir
= fts_open(pathargv
,
4228 FTS_PHYSICAL
| FTS_NOSTAT
| FTS_XDEV
| FTS_NOCHDIR
,
4230 return UNEXPECTED_FILE_OPERATION_ERROR
;
4232 while ((ftsdirEntry
= fts_read(ftsdir
)) != nullptr)
4234 NS_tchar foundpath
[MAXPATHLEN
];
4235 NS_tchar
*quotedpath
= nullptr;
4236 Action
*action
= nullptr;
4238 switch (ftsdirEntry
->fts_info
)
4240 // Filesystem objects that shouldn't be in the application's directories
4244 LOG(("add_dir_entries: found a non-standard file: " LOG_S
,
4245 ftsdirEntry
->fts_path
));
4246 /* Fall through */ // and try to remove as a file
4251 // Add the file to be removed to the ActionList.
4252 NS_tsnprintf(foundpath
, sizeof(foundpath
)/sizeof(foundpath
[0]),
4253 NS_T("%s"), ftsdirEntry
->fts_accpath
);
4254 quotedpath
= get_quoted_path(get_relative_offset(foundpath
));
4257 rv
= UPDATER_QUOTED_PATH_MEM_ERROR
;
4260 action
= new RemoveFile();
4261 rv
= action
->Parse(quotedpath
);
4264 list
->Append(action
);
4270 // Add the directory to be removed to the ActionList.
4271 NS_tsnprintf(foundpath
, sizeof(foundpath
)/sizeof(foundpath
[0]),
4272 NS_T("%s/"), ftsdirEntry
->fts_accpath
);
4273 quotedpath
= get_quoted_path(get_relative_offset(foundpath
));
4276 rv
= UPDATER_QUOTED_PATH_MEM_ERROR
;
4280 action
= new RemoveDir();
4281 rv
= action
->Parse(quotedpath
);
4284 list
->Append(action
);
4290 // ENOENT is an acceptable error for FTS_DNR and FTS_NS and means that
4291 // we're racing with ourselves. Though strange, the entry will be
4293 if (ENOENT
== ftsdirEntry
->fts_errno
)
4301 rv
= UNEXPECTED_FILE_OPERATION_ERROR
;
4302 LOG(("add_dir_entries: fts_read() error: " LOG_S
", err: %d",
4303 ftsdirEntry
->fts_path
, ftsdirEntry
->fts_errno
));
4307 rv
= UNEXPECTED_FILE_OPERATION_ERROR
;
4308 LOG(("add_dir_entries: fts_read() returned FT_DC: " LOG_S
,
4309 ftsdirEntry
->fts_path
));
4313 // FTS_D is ignored and FTS_DP is used instead (post-order).
4329 GetManifestContents(const NS_tchar
*manifest
)
4331 AutoFile
mfile(NS_tfopen(manifest
, NS_T("rb")));
4332 if (mfile
== nullptr)
4334 LOG(("GetManifestContents: error opening manifest file: " LOG_S
, manifest
));
4339 int rv
= fstat(fileno((FILE *)mfile
), &ms
);
4342 LOG(("GetManifestContents: error stating manifest file: " LOG_S
, manifest
));
4346 char *mbuf
= (char *) malloc(ms
.st_size
+ 1);
4350 size_t r
= ms
.st_size
;
4354 const size_t count
= std::min
<size_t>(SSIZE_MAX
, r
);
4355 size_t c
= fread(rb
, 1, count
, mfile
);
4358 LOG(("GetManifestContents: error reading manifest file: " LOG_S
, manifest
));
4366 mbuf
[ms
.st_size
] = '\0';
4372 NS_tchar
*wrb
= (NS_tchar
*) malloc((ms
.st_size
+ 1) * sizeof(NS_tchar
));
4379 if (!MultiByteToWideChar(CP_UTF8
, MB_ERR_INVALID_CHARS
, rb
, -1, wrb
,
4382 LOG(("GetManifestContents: error converting utf8 to utf16le: %d", GetLastError()));
4393 int AddPreCompleteActions(ActionList
*list
)
4396 std::unique_ptr
<NS_tchar
[]> manifestPath(new_absolute_path(
4397 NS_T("Contents/Resources/precomplete")));
4399 std::unique_ptr
<NS_tchar
[]> manifestPath(new_absolute_path(
4400 NS_T("precomplete")));
4403 NS_tchar
*rb
= GetManifestContents(manifestPath
.get());
4406 LOG(("AddPreCompleteActions: error getting contents of precomplete " \
4408 // Applications aren't required to have a precomplete manifest. The mar
4409 // generation scripts enforce the presence of a precomplete manifest.
4415 while ((line
= mstrtok(kNL
, &rb
)) != 0)
4418 if (*line
== NS_T('#'))
4421 NS_tchar
*token
= mstrtok(kWhitespace
, &line
);
4424 LOG(("AddPreCompleteActions: token not found in manifest"));
4428 Action
*action
= nullptr;
4429 if (NS_tstrcmp(token
, NS_T("remove")) == 0) // rm file
4431 action
= new RemoveFile();
4433 else if (NS_tstrcmp(token
, NS_T("remove-cc")) == 0) // no longer supported
4437 else if (NS_tstrcmp(token
, NS_T("rmdir")) == 0) // rmdir if empty
4439 action
= new RemoveDir();
4443 LOG(("AddPreCompleteActions: unknown token: " LOG_S
, token
));
4448 return BAD_ACTION_ERROR
;
4450 rv
= action
->Parse(line
);
4454 list
->Append(action
);
4460 int DoUpdate(ArchiveReader
& archiveReader
)
4462 NS_tchar manifest
[MAXPATHLEN
];
4463 int nWrittenBytes
= NS_tsnprintf(manifest
, sizeof(manifest
)/sizeof(manifest
[0]),
4464 NS_T("%s/updating/update.manifest"), gWorkingDirPath
);
4465 (void) nWrittenBytes
;
4466 ensure_parent_dir(manifest
);
4468 // extract the manifest
4469 // TODO: moggi: needs adaptation for LibreOffice
4470 // Why would we need the manifest? Even if we need it why would we need 2?
4471 int rv
= archiveReader
.ExtractFile("updatev3.manifest", manifest
);
4474 rv
= archiveReader
.ExtractFile("updatev2.manifest", manifest
);
4477 LOG(("DoUpdate: error extracting manifest file"));
4482 NS_tchar
*rb
= GetManifestContents(manifest
);
4483 NS_tremove(manifest
);
4486 LOG(("DoUpdate: error opening manifest file: " LOG_S
, manifest
));
4492 bool isFirstAction
= true;
4494 while ((line
= mstrtok(kNL
, &rb
)) != 0)
4497 if (*line
== NS_T('#'))
4500 NS_tchar
*token
= mstrtok(kWhitespace
, &line
);
4503 LOG(("DoUpdate: token not found in manifest"));
4509 isFirstAction
= false;
4510 // The update manifest isn't required to have a type declaration. The mar
4511 // generation scripts enforce the presence of the type declaration.
4512 if (NS_tstrcmp(token
, NS_T("type")) == 0)
4514 const NS_tchar
*type
= mstrtok(kQuote
, &line
);
4515 LOG(("UPDATE TYPE " LOG_S
, type
));
4516 if (NS_tstrcmp(type
, NS_T("complete")) == 0)
4518 rv
= AddPreCompleteActions(&list
);
4526 Action
*action
= nullptr;
4527 if (NS_tstrcmp(token
, NS_T("remove")) == 0) // rm file
4529 action
= new RemoveFile();
4531 else if (NS_tstrcmp(token
, NS_T("rmdir")) == 0) // rmdir if empty
4533 action
= new RemoveDir();
4535 else if (NS_tstrcmp(token
, NS_T("rmrfdir")) == 0) // rmdir recursive
4537 const NS_tchar
*reldirpath
= mstrtok(kQuote
, &line
);
4541 if (reldirpath
[NS_tstrlen(reldirpath
) - 1] != NS_T('/'))
4544 rv
= add_dir_entries(reldirpath
, &list
);
4550 else if (NS_tstrcmp(token
, NS_T("add")) == 0)
4552 action
= new AddFile(archiveReader
);
4554 else if (NS_tstrcmp(token
, NS_T("patch")) == 0)
4556 action
= new PatchFile(archiveReader
);
4558 else if (NS_tstrcmp(token
, NS_T("add-if")) == 0) // Add if exists
4560 action
= new AddIfFile(archiveReader
);
4562 else if (NS_tstrcmp(token
, NS_T("add-if-not")) == 0) // Add if not exists
4564 action
= new AddIfNotFile(archiveReader
);
4566 else if (NS_tstrcmp(token
, NS_T("patch-if")) == 0) // Patch if exists
4568 action
= new PatchIfFile(archiveReader
);
4572 LOG(("DoUpdate: unknown token: " LOG_S
, token
));
4577 return BAD_ACTION_ERROR
;
4579 rv
= action
->Parse(line
);
4583 list
.Append(action
);
4586 rv
= list
.Prepare();
4590 rv
= list
.Execute();