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 * Coverts a relative update path to a full path.
307 * The relative path to convert to a full path.
308 * @return valid filesystem full path or nullptr if memory allocation fails.
311 get_full_path(const NS_tchar
*relpath
)
313 NS_tchar
*destpath
= sStagedUpdate
? gWorkingDirPath
: gInstallDirPath
;
314 size_t lendestpath
= NS_tstrlen(destpath
);
315 size_t lenrelpath
= NS_tstrlen(relpath
);
316 NS_tchar
*s
= new NS_tchar
[lendestpath
+ lenrelpath
+ 2];
320 NS_tstrcpy(c
, destpath
);
322 NS_tstrcat(c
, NS_T("/"));
325 NS_tstrcat(c
, relpath
);
333 bool is_userprofile_in_instdir()
338 // 1.) if userprofile path length is smaller than installation dir,
339 // the profile is surely not in instdir
340 // 2.) else comparing the two paths looking only at the installation dir
341 // characters should yield an equal string
342 NS_tchar userprofile[MAXPATHLEN];
343 NS_tstrcpy(userprofile, gPatchDirPath);
344 NS_tchar *slash = (NS_tchar *) NS_tstrrchr(userprofile, NS_T('/'));
348 size_t userprofile_len = NS_tstrlen(userprofile);
349 size_t installdir_len = NS_tstrlen(gInstallDirPath);
351 if (userprofile_len < installdir_len)
354 return NS_tstrncmp(userprofile, gInstallDirPath, installdir_len) == 0;
361 * Converts a full update path into a relative path; reverses get_full_path.
364 * The absolute path to convert into a relative path.
365 * return pointer to the location within fullpath where the relative path starts
366 * or fullpath itself if it already looks relative.
368 static const NS_tchar
*
369 get_relative_path(const NS_tchar
*fullpath
)
371 // If the path isn't absolute, just return it as-is.
373 if (fullpath
[1] != ':' && fullpath
[2] != '\\')
376 if (fullpath
[0] != '/')
382 NS_tchar
*prefix
= sStagedUpdate
? gWorkingDirPath
: gInstallDirPath
;
384 // If the path isn't long enough to be absolute, return it as-is.
385 if (NS_tstrlen(fullpath
) <= NS_tstrlen(prefix
))
390 return fullpath
+ NS_tstrlen(prefix
) + 1;
394 * Gets the platform specific path and performs simple checks to the path. If
395 * the path checks don't pass nullptr will be returned.
398 * The line from the manifest that contains the path.
400 * Whether the path is a directory path. Defaults to false.
401 * @return valid filesystem path or nullptr if the path checks fail.
404 get_valid_path(NS_tchar
**line
, bool isdir
= false)
406 NS_tchar
*path
= mstrtok(kQuote
, line
);
409 LOG(("get_valid_path: unable to determine path: " LOG_S
, line
));
413 // All paths must be relative from the current working directory
414 if (path
[0] == NS_T('/'))
416 LOG(("get_valid_path: path must be relative: " LOG_S
, path
));
421 // All paths must be relative from the current working directory
422 if (path
[0] == NS_T('\\') || path
[1] == NS_T(':'))
424 LOG(("get_valid_path: path must be relative: " LOG_S
, path
));
431 // Directory paths must have a trailing forward slash.
432 if (path
[NS_tstrlen(path
) - 1] != NS_T('/'))
434 LOG(("get_valid_path: directory paths must have a trailing forward " \
435 "slash: " LOG_S
, path
));
439 // Remove the trailing forward slash because stat on Windows will return
440 // ENOENT if the path has a trailing slash.
441 path
[NS_tstrlen(path
) - 1] = NS_T('\0');
444 // Don't allow relative paths that resolve to a parent directory.
445 if (NS_tstrstr(path
, NS_T("..")) != nullptr)
447 LOG(("get_valid_path: paths must not contain '..': " LOG_S
, path
));
455 get_quoted_path(const NS_tchar
*path
)
457 size_t lenQuote
= NS_tstrlen(kQuote
);
458 size_t lenPath
= NS_tstrlen(path
);
459 size_t len
= lenQuote
+ lenPath
+ lenQuote
+ 1;
461 NS_tchar
*s
= (NS_tchar
*) malloc(len
* sizeof(NS_tchar
));
466 NS_tstrcpy(c
, kQuote
);
470 NS_tstrcat(c
, kQuote
);
477 static void ensure_write_permissions(const NS_tchar
*path
)
480 (void) _wchmod(path
, _S_IREAD
| _S_IWRITE
);
483 if (!stat(path
, &fs
) && !(fs
.st_mode
& S_IWUSR
))
485 (void)chmod(path
, fs
.st_mode
| S_IWUSR
);
490 static int ensure_remove(const NS_tchar
*path
)
492 ensure_write_permissions(path
);
493 int rv
= NS_tremove(path
);
495 LOG(("ensure_remove: failed to remove file: " LOG_S
", rv: %d, err: %d",
500 // Remove the directory pointed to by path and all of its files and sub-directories.
501 static int ensure_remove_recursive(const NS_tchar
*path
,
502 bool continueEnumOnFailure
= false)
504 // We use lstat rather than stat here so that we can successfully remove
506 struct NS_tstat_t sInfo
;
507 int rv
= NS_tlstat(path
, &sInfo
);
510 // This error is benign
513 if (!S_ISDIR(sInfo
.st_mode
))
515 return ensure_remove(path
);
521 dir
= NS_topendir(path
);
524 LOG(("ensure_remove_recursive: unable to open directory: " LOG_S
525 ", rv: %d, err: %d", path
, rv
, errno
));
529 while ((entry
= NS_treaddir(dir
)) != 0)
531 if (NS_tstrcmp(entry
->d_name
, NS_T(".")) &&
532 NS_tstrcmp(entry
->d_name
, NS_T("..")))
534 NS_tchar childPath
[MAXPATHLEN
];
535 NS_tsnprintf(childPath
, sizeof(childPath
)/sizeof(childPath
[0]),
536 NS_T("%s/%s"), path
, entry
->d_name
);
537 rv
= ensure_remove_recursive(childPath
);
538 if (rv
&& !continueEnumOnFailure
)
549 ensure_write_permissions(path
);
550 rv
= NS_trmdir(path
);
553 LOG(("ensure_remove_recursive: unable to remove directory: " LOG_S
554 ", rv: %d, err: %d", path
, rv
, errno
));
560 static bool is_read_only(const NS_tchar
*flags
)
562 size_t length
= NS_tstrlen(flags
);
566 // Make sure the string begins with "r"
567 if (flags
[0] != NS_T('r'))
570 // Look for "r+" or "r+b"
571 if (length
> 1 && flags
[1] == NS_T('+'))
575 if (NS_tstrcmp(flags
, NS_T("rb+")) == 0)
581 static FILE* ensure_open(const NS_tchar
*path
, const NS_tchar
*flags
, unsigned int options
)
583 ensure_write_permissions(path
);
584 FILE* f
= NS_tfopen(path
, flags
);
585 if (is_read_only(flags
))
587 // Don't attempt to modify the file permissions if the file is being opened
588 // in read-only mode.
591 if (NS_tchmod(path
, options
) != 0)
599 struct NS_tstat_t ss
;
600 if (NS_tstat(path
, &ss
) != 0 || ss
.st_mode
!= options
)
611 // Ensure that the directory containing this file exists.
612 static int ensure_parent_dir(const NS_tchar
*path
)
616 NS_tchar
*slash
= (NS_tchar
*) NS_tstrrchr(path
, NS_T('/'));
620 rv
= ensure_parent_dir(path
);
621 // Only attempt to create the directory if we're not at the root
622 if (rv
== OK
&& *path
)
624 rv
= NS_tmkdir(path
, 0755);
625 // If the directory already exists, then ignore the error.
626 if (rv
< 0 && errno
!= EEXIST
)
628 LOG(("ensure_parent_dir: failed to create directory: " LOG_S
", " \
629 "err: %d", path
, errno
));
643 static int ensure_copy_symlink(const NS_tchar
*path
, const NS_tchar
*dest
)
645 // Copy symlinks by creating a new symlink to the same target
646 NS_tchar target
[MAXPATHLEN
+ 1] = {NS_T('\0')};
647 int rv
= readlink(path
, target
, MAXPATHLEN
);
650 LOG(("ensure_copy_symlink: failed to read the link: " LOG_S
", err: %d",
654 rv
= symlink(target
, dest
);
657 LOG(("ensure_copy_symlink: failed to create the new link: " LOG_S
", target: " LOG_S
" err: %d",
658 dest
, target
, errno
));
665 // Copy the file named path onto a new file named dest.
666 static int ensure_copy(const NS_tchar
*path
, const NS_tchar
*dest
)
669 // Fast path for Windows
670 bool result
= CopyFileW(path
, dest
, false);
673 LOG(("ensure_copy: failed to copy the file " LOG_S
" over to " LOG_S
", lasterr: %x",
674 path
, dest
, GetLastError()));
675 return WRITE_ERROR_FILE_COPY
;
679 struct NS_tstat_t ss
;
680 int rv
= NS_tlstat(path
, &ss
);
683 LOG(("ensure_copy: failed to read file status info: " LOG_S
", err: %d",
689 if (S_ISLNK(ss
.st_mode
))
691 return ensure_copy_symlink(path
, dest
);
695 AutoFile
infile(ensure_open(path
, NS_T("rb"), ss
.st_mode
));
698 LOG(("ensure_copy: failed to open the file for reading: " LOG_S
", err: %d",
702 AutoFile
outfile(ensure_open(dest
, NS_T("wb"), ss
.st_mode
));
705 LOG(("ensure_copy: failed to open the file for writing: " LOG_S
", err: %d",
710 // This block size was chosen pretty arbitrarily but seems like a reasonable
711 // compromise. For example, the optimal block size on a modern OS X machine
713 const int blockSize
= 32 * 1024;
714 void* buffer
= malloc(blockSize
);
716 return UPDATER_MEM_ERROR
;
718 while (!feof(infile
.get()))
720 size_t read
= fread(buffer
, 1, blockSize
, infile
);
721 if (ferror(infile
.get()))
723 LOG(("ensure_copy: failed to read the file: " LOG_S
", err: %d",
731 while (written
< read
)
733 size_t chunkWritten
= fwrite(buffer
, 1, read
- written
, outfile
);
734 if (chunkWritten
<= 0)
736 LOG(("ensure_copy: failed to write the file: " LOG_S
", err: %d",
739 return WRITE_ERROR_FILE_COPY
;
742 written
+= chunkWritten
;
746 rv
= NS_tchmod(dest
, ss
.st_mode
);
753 template <unsigned N
>
754 struct copy_recursive_skiplist
756 NS_tchar paths
[N
][MAXPATHLEN
];
758 void append(unsigned index
, const NS_tchar
*path
, const NS_tchar
*suffix
)
760 NS_tsnprintf(paths
[index
], MAXPATHLEN
, NS_T("%s/%s"), path
, suffix
);
763 void append(unsigned index
, const NS_tchar
* path
)
765 NS_tstrcpy(paths
[index
], path
);
768 bool find(const NS_tchar
*path
)
770 for (int i
= 0; i
< static_cast<int>(N
); ++i
)
772 if (!NS_tstricmp(paths
[i
], path
))
781 // Copy all of the files and subdirectories under path to a new directory named dest.
782 // The path names in the skiplist will be skipped and will not be copied.
783 template <unsigned N
>
784 static int ensure_copy_recursive(const NS_tchar
*path
, const NS_tchar
*dest
,
785 copy_recursive_skiplist
<N
>& skiplist
)
787 struct NS_tstat_t sInfo
;
788 int rv
= NS_tlstat(path
, &sInfo
);
791 LOG(("ensure_copy_recursive: path doesn't exist: " LOG_S
", rv: %d, err: %d",
797 if (S_ISLNK(sInfo
.st_mode
))
799 return ensure_copy_symlink(path
, dest
);
803 if (!S_ISDIR(sInfo
.st_mode
))
805 return ensure_copy(path
, dest
);
808 rv
= NS_tmkdir(dest
, sInfo
.st_mode
);
809 if (rv
< 0 && errno
!= EEXIST
)
811 LOG(("ensure_copy_recursive: could not create destination directory: " LOG_S
", rv: %d, err: %d",
819 dir
= NS_topendir(path
);
822 LOG(("ensure_copy_recursive: path is not a directory: " LOG_S
", rv: %d, err: %d",
827 while ((entry
= NS_treaddir(dir
)) != 0)
829 if (NS_tstrcmp(entry
->d_name
, NS_T(".")) &&
830 NS_tstrcmp(entry
->d_name
, NS_T("..")))
832 NS_tchar childPath
[MAXPATHLEN
];
833 NS_tsnprintf(childPath
, sizeof(childPath
)/sizeof(childPath
[0]),
834 NS_T("%s/%s"), path
, entry
->d_name
);
835 if (skiplist
.find(childPath
))
839 NS_tchar childPathDest
[MAXPATHLEN
];
840 NS_tsnprintf(childPathDest
, sizeof(childPathDest
)/sizeof(childPathDest
[0]),
841 NS_T("%s/%s"), dest
, entry
->d_name
);
842 rv
= ensure_copy_recursive(childPath
, childPathDest
, skiplist
);
853 // Renames the specified file to the new file specified. If the destination file
854 // exists it is removed.
855 static int rename_file(const NS_tchar
*spath
, const NS_tchar
*dpath
,
856 bool allowDirs
= false)
858 int rv
= ensure_parent_dir(dpath
);
862 struct NS_tstat_t spathInfo
;
863 rv
= NS_tstat(spath
, &spathInfo
);
866 LOG(("rename_file: failed to read file status info: " LOG_S
", " \
867 "err: %d", spath
, errno
));
871 if (!S_ISREG(spathInfo
.st_mode
))
873 if (allowDirs
&& !S_ISDIR(spathInfo
.st_mode
))
875 LOG(("rename_file: path present, but not a file: " LOG_S
", err: %d",
877 return RENAME_ERROR_EXPECTED_FILE
;
881 LOG(("rename_file: proceeding to rename the directory"));
885 if (!NS_taccess(dpath
, F_OK
))
887 if (ensure_remove(dpath
))
889 LOG(("rename_file: destination file exists and could not be " \
890 "removed: " LOG_S
, dpath
));
891 return WRITE_ERROR_DELETE_FILE
;
895 if (NS_trename(spath
, dpath
) != 0)
897 LOG(("rename_file: failed to rename file - src: " LOG_S
", " \
898 "dst:" LOG_S
", err: %d", spath
, dpath
, errno
));
906 // Remove the directory pointed to by path and all of its files and
907 // sub-directories. If a file is in use move it to the tobedeleted directory
908 // and attempt to schedule removal of the file on reboot
909 static int remove_recursive_on_reboot(const NS_tchar
*path
, const NS_tchar
*deleteDir
)
911 struct NS_tstat_t sInfo
;
912 int rv
= NS_tlstat(path
, &sInfo
);
915 // This error is benign
919 if (!S_ISDIR(sInfo
.st_mode
))
921 NS_tchar tmpDeleteFile
[MAXPATHLEN
];
922 GetTempFileNameW(deleteDir
, L
"rep", 0, tmpDeleteFile
);
923 NS_tremove(tmpDeleteFile
);
924 rv
= rename_file(path
, tmpDeleteFile
, false);
925 if (MoveFileEx(rv
? path
: tmpDeleteFile
, nullptr, MOVEFILE_DELAY_UNTIL_REBOOT
))
927 LOG(("remove_recursive_on_reboot: file will be removed on OS reboot: "
928 LOG_S
, rv
? path
: tmpDeleteFile
));
932 LOG(("remove_recursive_on_reboot: failed to schedule OS reboot removal of "
933 "file: " LOG_S
, rv
? path
: tmpDeleteFile
));
941 dir
= NS_topendir(path
);
944 LOG(("remove_recursive_on_reboot: unable to open directory: " LOG_S
950 while ((entry
= NS_treaddir(dir
)) != 0)
952 if (NS_tstrcmp(entry
->d_name
, NS_T(".")) &&
953 NS_tstrcmp(entry
->d_name
, NS_T("..")))
955 NS_tchar childPath
[MAXPATHLEN
];
956 NS_tsnprintf(childPath
, sizeof(childPath
)/sizeof(childPath
[0]),
957 NS_T("%s/%s"), path
, entry
->d_name
);
958 // There is no need to check the return value of this call since this
959 // function is only called after an update is successful and there is not
960 // much that can be done to recover if it isn't successful. There is also
961 // no need to log the value since it will have already been logged.
962 remove_recursive_on_reboot(childPath
, deleteDir
);
970 ensure_write_permissions(path
);
971 rv
= NS_trmdir(path
);
974 LOG(("remove_recursive_on_reboot: unable to remove directory: " LOG_S
975 ", rv: %d, err: %d", path
, rv
, errno
));
982 //-----------------------------------------------------------------------------
984 // Create a backup of the specified file by renaming it.
985 static int backup_create(const NS_tchar
*path
)
987 NS_tchar backup
[MAXPATHLEN
];
988 NS_tsnprintf(backup
, sizeof(backup
)/sizeof(backup
[0]),
989 NS_T("%s") BACKUP_EXT
, path
);
991 return rename_file(path
, backup
);
994 // Rename the backup of the specified file that was created by renaming it back
995 // to the original file.
996 static int backup_restore(const NS_tchar
*path
, const NS_tchar
*relPath
)
998 NS_tchar backup
[MAXPATHLEN
];
999 NS_tsnprintf(backup
, sizeof(backup
)/sizeof(backup
[0]),
1000 NS_T("%s") BACKUP_EXT
, path
);
1002 NS_tchar relBackup
[MAXPATHLEN
];
1003 NS_tsnprintf(relBackup
, sizeof(relBackup
) / sizeof(relBackup
[0]),
1004 NS_T("%s") BACKUP_EXT
, relPath
);
1006 if (NS_taccess(backup
, F_OK
))
1008 LOG(("backup_restore: backup file doesn't exist: " LOG_S
, relBackup
));
1012 return rename_file(backup
, path
);
1015 // Discard the backup of the specified file that was created by renaming it.
1016 static int backup_discard(const NS_tchar
*path
, const NS_tchar
*relPath
)
1018 NS_tchar backup
[MAXPATHLEN
];
1019 NS_tsnprintf(backup
, sizeof(backup
)/sizeof(backup
[0]),
1020 NS_T("%s") BACKUP_EXT
, path
);
1022 NS_tchar relBackup
[MAXPATHLEN
];
1023 NS_tsnprintf(relBackup
, sizeof(relBackup
) / sizeof(relBackup
[0]),
1024 NS_T("%s") BACKUP_EXT
, relPath
);
1026 // Nothing to discard
1027 if (NS_taccess(backup
, F_OK
))
1032 int rv
= ensure_remove(backup
);
1034 if (rv
&& !sStagedUpdate
&& !sReplaceRequest
)
1036 LOG(("backup_discard: unable to remove: " LOG_S
, relBackup
));
1037 NS_tchar path
[MAXPATHLEN
];
1038 GetTempFileNameW(gDeleteDirPath
, L
"moz", 0, path
);
1039 if (rename_file(backup
, path
))
1041 LOG(("backup_discard: failed to rename file:" LOG_S
", dst:" LOG_S
,
1042 relBackup
, relPath
));
1043 return WRITE_ERROR_DELETE_BACKUP
;
1045 // The MoveFileEx call to remove the file on OS reboot will fail if the
1046 // process doesn't have write access to the HKEY_LOCAL_MACHINE registry key
1047 // but this is ok since the installer / uninstaller will delete the
1048 // directory containing the file along with its contents after an update is
1049 // applied, on reinstall, and on uninstall.
1050 if (MoveFileEx(path
, nullptr, MOVEFILE_DELAY_UNTIL_REBOOT
))
1052 LOG(("backup_discard: file renamed and will be removed on OS " \
1053 "reboot: " LOG_S
, relPath
));
1057 LOG(("backup_discard: failed to schedule OS reboot removal of " \
1058 "file: " LOG_S
, relPath
));
1063 return WRITE_ERROR_DELETE_BACKUP
;
1069 // Helper function for post-processing a temporary backup.
1070 static void backup_finish(const NS_tchar
*path
, const NS_tchar
*relPath
,
1074 backup_discard(path
, relPath
);
1076 backup_restore(path
, relPath
);
1079 //-----------------------------------------------------------------------------
1081 static int DoUpdate(ArchiveReader
& ArchiveReader
);
1086 Action() : mProgressCost(1), mNext(nullptr) { }
1087 virtual ~Action() { }
1089 virtual int Parse(NS_tchar
*line
) = 0;
1091 // Do any preprocessing to ensure that the action can be performed. Execute
1092 // will be called if this Action and all others return OK from this method.
1093 virtual int Prepare() = 0;
1095 // Perform the operation. Return OK to indicate success. After all actions
1096 // have been executed, Finish will be called. A requirement of Execute is
1097 // that its operation be reversible from Finish.
1098 virtual int Execute() = 0;
1100 // Finish is called after execution of all actions. If status is OK, then
1101 // all actions were successfully executed. Otherwise, some action failed.
1102 virtual void Finish(int status
) = 0;
1108 friend class ActionList
;
1111 class RemoveFile
: public Action
1114 RemoveFile() : mSkip(0) { }
1116 int Parse(NS_tchar
*line
);
1119 void Finish(int status
);
1122 std::unique_ptr
<const NS_tchar
> mFile
;
1123 std::unique_ptr
<NS_tchar
> mRelPath
;
1128 RemoveFile::Parse(NS_tchar
*line
)
1130 // format "<deadfile>"
1132 NS_tchar
* validPath
= get_valid_path(&line
);
1136 mRelPath
.reset(new NS_tchar
[MAXPATHLEN
]);
1137 NS_tstrcpy(mRelPath
.get(), validPath
);
1139 mFile
.reset(get_full_path(validPath
));
1149 RemoveFile::Prepare()
1151 // Skip the file if it already doesn't exist.
1152 int rv
= NS_taccess(mFile
.get(), F_OK
);
1160 LOG(("PREPARE REMOVEFILE " LOG_S
, mRelPath
.get()));
1162 // Make sure that we're actually a file...
1163 struct NS_tstat_t fileInfo
;
1164 rv
= NS_tstat(mFile
.get(), &fileInfo
);
1167 LOG(("failed to read file status info: " LOG_S
", err: %d", mFile
.get(),
1172 if (!S_ISREG(fileInfo
.st_mode
))
1174 LOG(("path present, but not a file: " LOG_S
, mFile
.get()));
1175 return DELETE_ERROR_EXPECTED_FILE
;
1178 NS_tchar
*slash
= (NS_tchar
*) NS_tstrrchr(mFile
.get(), NS_T('/'));
1181 *slash
= NS_T('\0');
1182 rv
= NS_taccess(mFile
.get(), W_OK
);
1187 rv
= NS_taccess(NS_T("."), W_OK
);
1192 LOG(("access failed: %d", errno
));
1193 return WRITE_ERROR_FILE_ACCESS_DENIED
;
1200 RemoveFile::Execute()
1205 LOG(("EXECUTE REMOVEFILE " LOG_S
, mRelPath
.get()));
1207 // The file is checked for existence here and in Prepare since it might have
1208 // been removed by a separate instruction: bug 311099.
1209 int rv
= NS_taccess(mFile
.get(), F_OK
);
1212 LOG(("file cannot be removed because it does not exist; skipping"));
1217 // Rename the old file. It will be removed in Finish.
1218 rv
= backup_create(mFile
.get());
1221 LOG(("backup_create failed: %d", rv
));
1229 RemoveFile::Finish(int status
)
1234 LOG(("FINISH REMOVEFILE " LOG_S
, mRelPath
.get()));
1236 backup_finish(mFile
.get(), mRelPath
.get(), status
);
1239 class RemoveDir
: public Action
1242 RemoveDir() : mSkip(0) { }
1244 virtual int Parse(NS_tchar
*line
);
1245 virtual int Prepare(); // check that the source dir exists
1246 virtual int Execute();
1247 virtual void Finish(int status
);
1250 std::unique_ptr
<NS_tchar
> mDir
;
1251 std::unique_ptr
<NS_tchar
> mRelPath
;
1256 RemoveDir::Parse(NS_tchar
*line
)
1258 // format "<deaddir>/"
1260 NS_tchar
* validPath
= get_valid_path(&line
, true);
1263 mRelPath
.reset(new NS_tchar
[MAXPATHLEN
]);
1264 NS_tstrcpy(mRelPath
.get(), validPath
);
1266 mDir
.reset(get_full_path(validPath
));
1276 RemoveDir::Prepare()
1278 // We expect the directory to exist if we are to remove it.
1279 int rv
= NS_taccess(mDir
.get(), F_OK
);
1287 LOG(("PREPARE REMOVEDIR " LOG_S
"/", mRelPath
.get()));
1289 // Make sure that we're actually a dir.
1290 struct NS_tstat_t dirInfo
;
1291 rv
= NS_tstat(mDir
.get(), &dirInfo
);
1294 LOG(("failed to read directory status info: " LOG_S
", err: %d", mRelPath
.get(),
1299 if (!S_ISDIR(dirInfo
.st_mode
))
1301 LOG(("path present, but not a directory: " LOG_S
, mRelPath
.get()));
1302 return DELETE_ERROR_EXPECTED_DIR
;
1305 rv
= NS_taccess(mDir
.get(), W_OK
);
1308 LOG(("access failed: %d, %d", rv
, errno
));
1309 return WRITE_ERROR_DIR_ACCESS_DENIED
;
1316 RemoveDir::Execute()
1321 LOG(("EXECUTE REMOVEDIR " LOG_S
"/", mRelPath
.get()));
1323 // The directory is checked for existence at every step since it might have
1324 // been removed by a separate instruction: bug 311099.
1325 int rv
= NS_taccess(mDir
.get(), F_OK
);
1328 LOG(("directory no longer exists; skipping"));
1336 RemoveDir::Finish(int status
)
1338 if (mSkip
|| status
!= OK
)
1341 LOG(("FINISH REMOVEDIR " LOG_S
"/", mRelPath
.get()));
1343 // The directory is checked for existence at every step since it might have
1344 // been removed by a separate instruction: bug 311099.
1345 int rv
= NS_taccess(mDir
.get(), F_OK
);
1348 LOG(("directory no longer exists; skipping"));
1355 if (NS_trmdir(mDir
.get()))
1357 LOG(("non-fatal error removing directory: " LOG_S
"/, rv: %d, err: %d",
1358 mRelPath
.get(), rv
, errno
));
1363 class AddFile
: public Action
1366 AddFile(ArchiveReader
& ar
) : mAdded(false), mArchiveReader(ar
) { }
1368 virtual int Parse(NS_tchar
*line
);
1369 virtual int Prepare();
1370 virtual int Execute();
1371 virtual void Finish(int status
);
1374 std::unique_ptr
<NS_tchar
> mFile
;
1375 std::unique_ptr
<NS_tchar
> mRelPath
;
1377 ArchiveReader
& mArchiveReader
;
1381 AddFile::Parse(NS_tchar
*line
)
1383 // format "<newfile>"
1385 NS_tchar
* validPath
= get_valid_path(&line
);
1389 mRelPath
.reset(new NS_tchar
[MAXPATHLEN
]);
1391 NS_tstrcpy(mRelPath
.get(), validPath
);
1393 mFile
.reset(get_full_path(validPath
));
1405 LOG(("PREPARE ADD " LOG_S
, mRelPath
.get()));
1413 LOG(("EXECUTE ADD " LOG_S
, mRelPath
.get()));
1417 // First make sure that we can actually get rid of any existing file.
1418 rv
= NS_taccess(mFile
.get(), F_OK
);
1421 rv
= backup_create(mFile
.get());
1427 rv
= ensure_parent_dir(mFile
.get());
1433 char sourcefile
[MAXPATHLEN
];
1434 if (!WideCharToMultiByte(CP_UTF8
, 0, mRelPath
.get(), -1, sourcefile
,
1435 MAXPATHLEN
, nullptr, nullptr))
1437 LOG(("error converting wchar to utf8: %d", GetLastError()));
1438 return STRING_CONVERSION_ERROR
;
1441 rv
= mArchiveReader
.ExtractFile(sourcefile
, mFile
.get());
1443 rv
= mArchiveReader
.ExtractFile(mRelPath
.get(), mFile
.get());
1453 AddFile::Finish(int status
)
1455 LOG(("FINISH ADD " LOG_S
, mRelPath
.get()));
1456 // When there is an update failure and a file has been added it is removed
1457 // here since there might not be a backup to replace it.
1458 if (status
&& mAdded
)
1459 NS_tremove(mFile
.get());
1460 backup_finish(mFile
.get(), mRelPath
.get(), status
);
1463 class PatchFile
: public Action
1466 PatchFile(ArchiveReader
& ar
) : mPatchFile(nullptr), mPatchIndex(-1), buf(nullptr), mArchiveReader(ar
) { }
1468 virtual ~PatchFile();
1470 virtual int Parse(NS_tchar
*line
);
1471 virtual int Prepare(); // should check for patch file and for checksum here
1472 virtual int Execute();
1473 virtual void Finish(int status
);
1476 int LoadSourceFile(FILE* ofile
);
1478 static int sPatchIndex
;
1480 const NS_tchar
*mPatchFile
;
1481 std::unique_ptr
<NS_tchar
> mFile
;
1482 std::unique_ptr
<NS_tchar
> mFileRelPath
;
1484 MBSPatchHeader header
;
1486 NS_tchar spath
[MAXPATHLEN
];
1487 AutoFile mPatchStream
;
1488 ArchiveReader
& mArchiveReader
;
1491 int PatchFile::sPatchIndex
= 0;
1493 PatchFile::~PatchFile()
1495 // Make sure mPatchStream gets unlocked on Windows; the system will do that,
1496 // but not until some indeterminate future time, and we want determinism.
1497 // Normally this happens at the end of Execute, when we close the stream;
1498 // this call is here in case Execute errors out.
1502 UnlockFile((HANDLE
)_get_osfhandle(fileno(mPatchStream
)), (DWORD
)0, (DWORD
)0, (DWORD
)-1, (DWORD
)-1);
1506 // delete the temporary patch file
1515 PatchFile::LoadSourceFile(FILE* ofile
)
1518 int rv
= fstat(fileno((FILE *)ofile
), &os
);
1521 LOG(("LoadSourceFile: unable to stat destination file: " LOG_S
", " \
1522 "err: %d", mFileRelPath
.get(), errno
));
1526 if (uint32_t(os
.st_size
) != header
.slen
)
1528 LOG(("LoadSourceFile: destination file size %d does not match expected size %d",
1529 uint32_t(os
.st_size
), header
.slen
));
1530 return LOADSOURCE_ERROR_WRONG_SIZE
;
1533 buf
= (unsigned char *) malloc(header
.slen
);
1535 return UPDATER_MEM_ERROR
;
1537 size_t r
= header
.slen
;
1538 unsigned char *rb
= buf
;
1541 const size_t count
= std::min
<size_t>(SSIZE_MAX
, r
);
1542 size_t c
= fread(rb
, 1, count
, ofile
);
1545 LOG(("LoadSourceFile: error reading destination file: " LOG_S
,
1546 mFileRelPath
.get()));
1554 // Verify that the contents of the source file correspond to what we expect.
1556 unsigned int crc
= crc32(buf
, header
.slen
);
1558 if (crc
!= header
.scrc32
)
1560 LOG(("LoadSourceFile: destination file crc %d does not match expected " \
1561 "crc %d", crc
, header
.scrc32
));
1569 PatchFile::Parse(NS_tchar
*line
)
1571 // format "<patchfile>" "<filetopatch>"
1573 // Get the path to the patch file inside of the mar
1574 mPatchFile
= mstrtok(kQuote
, &line
);
1578 // consume whitespace between args
1579 NS_tchar
*q
= mstrtok(kQuote
, &line
);
1583 NS_tchar
* validPath
= get_valid_path(&line
);
1586 mFileRelPath
.reset(new NS_tchar
[MAXPATHLEN
]);
1587 NS_tstrcpy(mFileRelPath
.get(), validPath
);
1589 mFile
.reset(get_full_path(validPath
));
1599 PatchFile::Prepare()
1601 LOG(("PREPARE PATCH " LOG_S
, mFileRelPath
.get()));
1603 // extract the patch to a temporary file
1604 mPatchIndex
= sPatchIndex
++;
1606 NS_tsnprintf(spath
, sizeof(spath
)/sizeof(spath
[0]),
1607 NS_T("%s/updating/%d.patch"), gWorkingDirPath
, mPatchIndex
);
1611 mPatchStream
= NS_tfopen(spath
, NS_T("wb+"));
1616 // Lock the patch file, so it can't be messed with between
1617 // when we're done creating it and when we go to apply it.
1618 if (!LockFile((HANDLE
)_get_osfhandle(fileno(mPatchStream
)), (DWORD
)0, (DWORD
)0, (DWORD
)-1, (DWORD
)-1))
1620 LOG(("Couldn't lock patch file: %d", GetLastError()));
1621 // TODO: moggi: fix the build problem with LOCK_ERROR_PATCH_FILE
1622 return WRITE_ERROR
; //return LOCK_ERROR_PATCH_FILE;
1624 char sourcefile
[MAXPATHLEN
];
1625 if (!WideCharToMultiByte(CP_UTF8
, 0, mPatchFile
, -1, sourcefile
, MAXPATHLEN
,
1628 LOG(("error converting wchar to utf8: %d", GetLastError()));
1629 return STRING_CONVERSION_ERROR
;
1632 int rv
= mArchiveReader
.ExtractFileToStream(sourcefile
, mPatchStream
);
1634 int rv
= mArchiveReader
.ExtractFileToStream(mPatchFile
, mPatchStream
);
1641 PatchFile::Execute()
1643 LOG(("EXECUTE PATCH " LOG_S
, mFileRelPath
.get()));
1645 fseek(mPatchStream
, 0, SEEK_SET
);
1647 int rv
= MBS_ReadHeader(mPatchStream
, &header
);
1651 FILE *origfile
= nullptr;
1653 if (NS_tstrcmp(mFileRelPath
.get(), gCallbackRelPath
) == 0)
1655 // Read from the copy of the callback when patching since the callback can't
1656 // be opened for reading to prevent the application from being launched.
1657 origfile
= NS_tfopen(gCallbackBackupPath
, NS_T("rb"));
1661 origfile
= NS_tfopen(mFile
.get(), NS_T("rb"));
1664 origfile
= NS_tfopen(mFile
.get(), NS_T("rb"));
1669 LOG(("unable to open destination file: " LOG_S
", err: %d",
1670 mFileRelPath
.get(), errno
));
1674 rv
= LoadSourceFile(origfile
);
1678 LOG(("LoadSourceFile failed"));
1682 // Rename the destination file if it exists before proceeding so it can be
1683 // used to restore the file to its original state if there is an error.
1684 struct NS_tstat_t ss
;
1685 rv
= NS_tstat(mFile
.get(), &ss
);
1688 LOG(("failed to read file status info: " LOG_S
", err: %d",
1689 mFileRelPath
.get(), errno
));
1693 rv
= backup_create(mFile
.get());
1697 #if defined(HAVE_POSIX_FALLOCATE)
1698 AutoFile
ofile(ensure_open(mFile
.get(), NS_T("wb+"), ss
.st_mode
));
1699 posix_fallocate(fileno((FILE *)ofile
), 0, header
.dlen
);
1700 #elif defined(_WIN32)
1701 bool shouldTruncate
= true;
1702 // Creating the file, setting the size, and then closing the file handle
1703 // lessens fragmentation more than any other method tested. Other methods that
1704 // have been tested are:
1705 // 1. _chsize / _chsize_s reduced fragmentation though not completely.
1706 // 2. _get_osfhandle and then setting the size reduced fragmentation though
1707 // not completely. There are also reports of _get_osfhandle failing on
1709 HANDLE hfile
= CreateFileW(mFile
.get(),
1714 FILE_ATTRIBUTE_NORMAL
,
1717 if (hfile
!= INVALID_HANDLE_VALUE
)
1719 if (SetFilePointer(hfile
, header
.dlen
,
1720 nullptr, FILE_BEGIN
) != INVALID_SET_FILE_POINTER
&&
1721 SetEndOfFile(hfile
) != 0)
1723 shouldTruncate
= false;
1728 AutoFile
ofile(ensure_open(mFile
.get(), shouldTruncate
? NS_T("wb+") : NS_T("rb+"),
1730 #elif defined(MACOSX)
1731 AutoFile
ofile(ensure_open(mFile
.get(), NS_T("wb+"), ss
.st_mode
));
1732 // Modified code from FileUtils.cpp
1733 fstore_t store
= {F_ALLOCATECONTIG
, F_PEOFPOSMODE
, 0, header
.dlen
};
1734 // Try to get a continuous chunk of disk space
1735 rv
= fcntl(fileno((FILE *)ofile
), F_PREALLOCATE
, &store
);
1738 // OK, perhaps we are too fragmented, allocate non-continuous
1739 store
.fst_flags
= F_ALLOCATEALL
;
1740 rv
= fcntl(fileno((FILE *)ofile
), F_PREALLOCATE
, &store
);
1745 ftruncate(fileno((FILE *)ofile
), header
.dlen
);
1748 AutoFile
ofile(ensure_open(mFile
.get(), NS_T("wb+"), ss
.st_mode
));
1751 if (ofile
== nullptr)
1753 LOG(("unable to create new file: " LOG_S
", err: %d", mFileRelPath
.get(),
1755 return WRITE_ERROR_OPEN_PATCH_FILE
;
1759 if (!shouldTruncate
)
1761 fseek(ofile
, 0, SEEK_SET
);
1765 rv
= MBS_ApplyPatch(&header
, mPatchStream
, buf
, ofile
);
1767 // Go ahead and do a bit of cleanup now to minimize runtime overhead.
1768 // Make sure mPatchStream gets unlocked on Windows; the system will do that,
1769 // but not until some indeterminate future time, and we want determinism.
1771 UnlockFile((HANDLE
)_get_osfhandle(fileno(mPatchStream
)), (DWORD
)0, (DWORD
)0, (DWORD
)-1, (DWORD
)-1);
1773 // Set mPatchStream to nullptr to make AutoFile close the file,
1774 // so it can be deleted on Windows.
1775 mPatchStream
= nullptr;
1777 spath
[0] = NS_T('\0');
1784 PatchFile::Finish(int status
)
1786 LOG(("FINISH PATCH " LOG_S
, mFileRelPath
.get()));
1788 backup_finish(mFile
.get(), mFileRelPath
.get(), status
);
1791 class AddIfFile
: public AddFile
1794 AddIfFile(ArchiveReader
& archiveReader
);
1796 virtual int Parse(NS_tchar
*line
);
1797 virtual int Prepare();
1798 virtual int Execute();
1799 virtual void Finish(int status
);
1802 std::unique_ptr
<NS_tchar
> mTestFile
;
1805 AddIfFile::AddIfFile(ArchiveReader
& archiveReader
):
1806 AddFile(archiveReader
)
1811 AddIfFile::Parse(NS_tchar
*line
)
1813 // format "<testfile>" "<newfile>"
1815 mTestFile
.reset(get_full_path(get_valid_path(&line
)));
1819 // consume whitespace between args
1820 NS_tchar
*q
= mstrtok(kQuote
, &line
);
1824 return AddFile::Parse(line
);
1828 AddIfFile::Prepare()
1830 // If the test file does not exist, then skip this action.
1831 if (NS_taccess(mTestFile
.get(), F_OK
))
1833 mTestFile
= nullptr;
1837 return AddFile::Prepare();
1841 AddIfFile::Execute()
1846 return AddFile::Execute();
1850 AddIfFile::Finish(int status
)
1855 AddFile::Finish(status
);
1858 class AddIfNotFile
: public AddFile
1861 AddIfNotFile(ArchiveReader
& archiveReader
);
1863 virtual int Parse(NS_tchar
*line
);
1864 virtual int Prepare();
1865 virtual int Execute();
1866 virtual void Finish(int status
);
1869 std::unique_ptr
<NS_tchar
> mTestFile
;
1872 AddIfNotFile::AddIfNotFile(ArchiveReader
& archiveReader
):
1873 AddFile(archiveReader
)
1878 AddIfNotFile::Parse(NS_tchar
*line
)
1880 // format "<testfile>" "<newfile>"
1882 mTestFile
.reset(get_full_path(get_valid_path(&line
)));
1886 // consume whitespace between args
1887 NS_tchar
*q
= mstrtok(kQuote
, &line
);
1891 return AddFile::Parse(line
);
1895 AddIfNotFile::Prepare()
1897 // If the test file exists, then skip this action.
1898 if (!NS_taccess(mTestFile
.get(), F_OK
))
1904 return AddFile::Prepare();
1908 AddIfNotFile::Execute()
1913 return AddFile::Execute();
1917 AddIfNotFile::Finish(int status
)
1922 AddFile::Finish(status
);
1925 class PatchIfFile
: public PatchFile
1928 PatchIfFile(ArchiveReader
& archiveReader
);
1930 virtual int Parse(NS_tchar
*line
);
1931 virtual int Prepare(); // should check for patch file and for checksum here
1932 virtual int Execute();
1933 virtual void Finish(int status
);
1936 std::unique_ptr
<NS_tchar
> mTestFile
;
1939 PatchIfFile::PatchIfFile(ArchiveReader
& archiveReader
):
1940 PatchFile(archiveReader
)
1945 PatchIfFile::Parse(NS_tchar
*line
)
1947 // format "<testfile>" "<patchfile>" "<filetopatch>"
1949 mTestFile
.reset(get_full_path(get_valid_path(&line
)));
1953 // consume whitespace between args
1954 NS_tchar
*q
= mstrtok(kQuote
, &line
);
1958 return PatchFile::Parse(line
);
1962 PatchIfFile::Prepare()
1964 // If the test file does not exist, then skip this action.
1965 if (NS_taccess(mTestFile
.get(), F_OK
))
1967 mTestFile
= nullptr;
1971 return PatchFile::Prepare();
1975 PatchIfFile::Execute()
1980 return PatchFile::Execute();
1984 PatchIfFile::Finish(int status
)
1989 PatchFile::Finish(status
);
1992 //-----------------------------------------------------------------------------
1997 * Launch the post update application (helper.exe). It takes in the path of the
1998 * callback application to calculate the path of helper.exe. For service updates
1999 * this is called from both the system account and the current user account.
2001 * @param installationDir The path to the callback application binary.
2002 * @param updateInfoDir The directory where update info is stored.
2003 * @return true if there was no error starting the process.
2006 LaunchWinPostProcess(const WCHAR
*installationDir
,
2007 const WCHAR
*updateInfoDir
)
2009 WCHAR workingDirectory
[MAX_PATH
+ 1] = { L
'\0' };
2010 wcsncpy(workingDirectory
, installationDir
, MAX_PATH
);
2012 // TODO: moggi: needs adaption for LibreOffice
2013 // Most likely we don't have the helper method yet. Check if we really need it.
2015 // Launch helper.exe to perform post processing (e.g. registry and log file
2016 // modifications) for the update.
2017 WCHAR inifile
[MAX_PATH
+ 1] = { L
'\0' };
2018 wcsncpy(inifile
, installationDir
, MAX_PATH
);
2019 if (!PathAppendSafe(inifile
, L
"updater.ini"))
2024 WCHAR exefile
[MAX_PATH
+ 1];
2025 WCHAR exearg
[MAX_PATH
+ 1];
2028 if (!GetPrivateProfileStringW(L
"PostUpdateWin", L
"ExeRelPath", nullptr,
2029 exefile
, MAX_PATH
+ 1, inifile
))
2034 if (!GetPrivateProfileStringW(L
"PostUpdateWin", L
"ExeArg", nullptr, exearg
,
2035 MAX_PATH
+ 1, inifile
))
2040 if (!GetPrivateProfileStringW(L
"PostUpdateWin", L
"ExeAsync", L
"TRUE",
2042 sizeof(exeasync
)/sizeof(exeasync
[0]),
2048 // Verify that exeFile doesn't contain relative paths
2049 if (wcsstr(exefile
, L
"..") != nullptr)
2054 WCHAR exefullpath
[MAX_PATH
+ 1] = { L
'\0' };
2055 wcsncpy(exefullpath
, installationDir
, MAX_PATH
);
2056 if (!PathAppendSafe(exefullpath
, exefile
))
2061 #if !defined(TEST_UPDATER) && defined(MAINTENANCE_SERVICE)
2062 if (sUsingService
&&
2063 !DoesBinaryMatchAllowedCertificates(installationDir
, exefullpath
))
2069 WCHAR dlogFile
[MAX_PATH
+ 1];
2070 if (!PathGetSiblingFilePath(dlogFile
, exefullpath
, L
"uninstall.update"))
2075 WCHAR slogFile
[MAX_PATH
+ 1] = { L
'\0' };
2076 wcsncpy(slogFile
, updateInfoDir
, MAX_PATH
);
2077 if (!PathAppendSafe(slogFile
, L
"update.log"))
2082 WCHAR dummyArg
[14] = { L
'\0' };
2083 wcsncpy(dummyArg
, L
"argv0ignored ", sizeof(dummyArg
) / sizeof(dummyArg
[0]) - 1);
2085 size_t len
= wcslen(exearg
) + wcslen(dummyArg
);
2086 WCHAR
*cmdline
= (WCHAR
*) malloc((len
+ 1) * sizeof(WCHAR
));
2092 wcsncpy(cmdline
, dummyArg
, len
);
2093 wcscat(cmdline
, exearg
);
2095 if (sUsingService
||
2096 !_wcsnicmp(exeasync
, L
"false", 6) ||
2097 !_wcsnicmp(exeasync
, L
"0", 2))
2102 // We want to launch the post update helper app to update the Windows
2103 // registry even if there is a failure with removing the uninstall.update
2104 // file or copying the update.log file.
2105 CopyFileW(slogFile
, dlogFile
, false);
2107 STARTUPINFOW si
= {sizeof(si
), 0};
2109 PROCESS_INFORMATION pi
= {0};
2111 bool ok
= CreateProcessW(exefullpath
,
2113 nullptr, // no special security attributes
2114 nullptr, // no special thread attributes
2115 false, // don't inherit filehandles
2116 0, // No special process creation flags
2117 nullptr, // inherit my environment
2126 WaitForSingleObject(pi
.hProcess
, INFINITE
);
2128 CloseHandle(pi
.hProcess
);
2129 CloseHandle(pi
.hThread
);
2137 LaunchCallbackApp(const NS_tchar
*workingDir
,
2142 putenv(const_cast<char*>("NO_EM_RESTART="));
2143 putenv(const_cast<char*>("MOZ_LAUNCHED_CHILD=1"));
2145 // Run from the specified working directory (see bug 312360). This is not
2146 // necessary on Windows CE since the application that launches the updater
2147 // passes the working directory as an --environ: command line argument.
2148 if (NS_tchdir(workingDir
) != 0)
2150 LOG(("Warning: chdir failed"));
2153 #if defined(USE_EXECV)
2155 (void) usingService
; // avoid warnings
2156 execv(argv
[0], argv
);
2157 #elif defined(MACOSX)
2158 LaunchChild(argc
, (const char**)argv
);
2160 // Do not allow the callback to run when running an update through the
2161 // service as session 0. The unelevated updater.exe will do the launching.
2164 WinLaunchChild(argv
[0], argc
, argv
, nullptr);
2167 # warning "Need implementation of LaunchCallbackApp"
2172 WriteStatusFile(const char* aStatus
)
2174 NS_tchar filename
[MAXPATHLEN
] = {NS_T('\0')};
2176 // The temp file is not removed on failure since there is client code that
2178 GetTempFileNameW(gPatchDirPath
, L
"sta", 0, filename
);
2180 NS_tsnprintf(filename
, sizeof(filename
)/sizeof(filename
[0]),
2181 NS_T("%s/update.status"), gPatchDirPath
);
2184 // Make sure that the directory for the update status file exists
2185 if (ensure_parent_dir(filename
))
2188 // This is scoped to make the AutoFile close the file so it is possible to
2189 // move the temp file to the update.status file on Windows.
2191 AutoFile
file(NS_tfopen(filename
, NS_T("wb+")));
2192 if (file
== nullptr)
2197 if (fwrite(aStatus
, strlen(aStatus
), 1, file
) != 1)
2204 NS_tchar dstfilename
[MAXPATHLEN
] = {NS_T('\0')};
2205 NS_tsnprintf(dstfilename
, sizeof(dstfilename
)/sizeof(dstfilename
[0]),
2206 NS_T("%s\\update.status"), gPatchDirPath
);
2207 if (MoveFileExW(filename
, dstfilename
, MOVEFILE_REPLACE_EXISTING
) == 0)
2217 WriteStatusFile(int status
)
2230 text
= "succeeded\n";
2235 snprintf(buf
, sizeof(buf
)/sizeof(buf
[0]), "failed: %d\n", status
);
2239 WriteStatusFile(text
);
2242 #ifdef MAINTENANCE_SERVICE
2244 * Read the update.status file and sets isPendingService to true if
2245 * the status is set to pending-service.
2247 * @param isPendingService Out parameter for specifying if the status
2248 * is set to pending-service or not.
2249 * @return true if the information was retrieved and it is pending
2250 * or pending-service.
2253 IsUpdateStatusPendingService()
2255 NS_tchar filename
[MAXPATHLEN
];
2256 NS_tsnprintf(filename
, sizeof(filename
)/sizeof(filename
[0]),
2257 NS_T("%s/update.status"), gPatchDirPath
);
2259 AutoFile
file(NS_tfopen(filename
, NS_T("rb")));
2260 if (file
== nullptr)
2263 char buf
[32] = { 0 };
2264 fread(buf
, sizeof(buf
), 1, file
);
2266 const char kPendingService
[] = "pending-service";
2267 const char kAppliedService
[] = "applied-service";
2269 return (strncmp(buf
, kPendingService
,
2270 sizeof(kPendingService
) - 1) == 0) ||
2271 (strncmp(buf
, kAppliedService
,
2272 sizeof(kAppliedService
) - 1) == 0);
2278 * Read the update.status file and sets isSuccess to true if
2279 * the status is set to succeeded.
2281 * @param isSucceeded Out parameter for specifying if the status
2282 * is set to succeeded or not.
2283 * @return true if the information was retrieved and it is succeeded.
2286 IsUpdateStatusSucceeded(bool &isSucceeded
)
2288 isSucceeded
= false;
2289 NS_tchar filename
[MAXPATHLEN
];
2290 NS_tsnprintf(filename
, sizeof(filename
)/sizeof(filename
[0]),
2291 NS_T("%s/update.status"), gPatchDirPath
);
2293 AutoFile
file(NS_tfopen(filename
, NS_T("rb")));
2294 if (file
== nullptr)
2297 char buf
[32] = { 0 };
2298 fread(buf
, sizeof(buf
), 1, file
);
2300 const char kSucceeded
[] = "succeeded";
2301 isSucceeded
= strncmp(buf
, kSucceeded
,
2302 sizeof(kSucceeded
) - 1) == 0;
2308 * Copy the entire contents of the application installation directory to the
2309 * destination directory for the update process.
2311 * @return 0 if successful, an error code otherwise.
2314 CopyInstallDirToDestDir()
2316 // These files should not be copied over to the updated app
2318 #define SKIPLIST_COUNT 4
2319 #elif defined(MACOSX)
2320 #define SKIPLIST_COUNT 1
2322 #define SKIPLIST_COUNT 3
2324 copy_recursive_skiplist
<SKIPLIST_COUNT
> skiplist
;
2326 std::unique_ptr
<NS_tchar
> pUserProfile(new NS_tchar
[MAXPATHLEN
]);
2327 NS_tstrcpy(pUserProfile
.get(), gPatchDirPath
);
2328 NS_tchar
*slash
= (NS_tchar
*) NS_tstrrchr(pUserProfile
.get(), NS_T('/'));
2330 *slash
= NS_T('\0');
2332 LOG(("ignore user profile directory during copy: " LOG_S
, pUserProfile
.get()));
2334 skiplist
.append(0, pUserProfile
.get());
2336 skiplist
.append(1, gInstallDirPath
, NS_T("updated"));
2337 skiplist
.append(2, gInstallDirPath
, NS_T("updates/0"));
2339 skiplist
.append(4, gInstallDirPath
, NS_T("updated.update_in_progress.lock"));
2343 return ensure_copy_recursive(gInstallDirPath
, gWorkingDirPath
, skiplist
);
2347 * Replace the application installation directory with the destination
2348 * directory in order to finish a staged update task
2350 * @return 0 if successful, an error code otherwise.
2353 ProcessReplaceRequest()
2355 // TODO: moggi: handle the user profile in the installation dir also
2356 // during the replacement request
2357 // The replacement algorithm is like this:
2358 // 1. Move destDir to tmpDir. In case of failure, abort.
2359 // 2. Move newDir to destDir. In case of failure, revert step 1 and abort.
2360 // 3. Delete tmpDir (or defer it to the next reboot).
2363 NS_tchar destDir
[MAXPATHLEN
];
2364 NS_tsnprintf(destDir
, sizeof(destDir
)/sizeof(destDir
[0]),
2365 NS_T("%s/Contents"), gInstallDirPath
);
2367 // Windows preserves the case of the file/directory names. We use the
2368 // GetLongPathName API in order to get the correct case for the directory
2369 // name, so that if the user has used a different case when launching the
2370 // application, the installation directory's name does not change.
2371 NS_tchar destDir
[MAXPATHLEN
];
2372 if (!GetLongPathNameW(gInstallDirPath
, destDir
,
2373 sizeof(destDir
)/sizeof(destDir
[0])))
2375 return NO_INSTALLDIR_ERROR
;
2378 NS_tchar
* destDir
= gInstallDirPath
;
2381 NS_tchar tmpDir
[MAXPATHLEN
];
2382 NS_tsnprintf(tmpDir
, sizeof(tmpDir
)/sizeof(tmpDir
[0]),
2383 NS_T("%s.bak"), destDir
);
2385 // First try to remove the possibly existing temp directory, because if this
2386 // directory exists, we will fail to rename destDir.
2387 // No need to error check here because if this fails, we will fail in the
2388 // next step anyways.
2389 ensure_remove_recursive(tmpDir
);
2391 LOG(("Begin moving destDir (" LOG_S
") to tmpDir (" LOG_S
")",
2394 int rv
= rename_file(destDir
, tmpDir
, true);
2396 // On Windows, if Firefox is launched using the shortcut, it will hold a handle
2397 // to its installation directory open, which might not get released in time.
2398 // Therefore we wait a little bit here to see if the handle is released.
2399 // If it's not released, we just fail to perform the replace request.
2400 const int max_retries
= 10;
2402 while (rv
== WRITE_ERROR
&& (retries
++ < max_retries
))
2404 LOG(("PerformReplaceRequest: destDir rename attempt %d failed. " \
2405 "File: " LOG_S
". Last error: %d, err: %d", retries
,
2406 destDir
, GetLastError(), rv
));
2410 rv
= rename_file(destDir
, tmpDir
, true);
2415 // The status file will have 'pending' written to it so there is no value in
2416 // returning an error specific for this failure.
2417 LOG(("Moving destDir to tmpDir failed, err: %d", rv
));
2421 NS_tchar newDir
[MAXPATHLEN
];
2422 if (is_userprofile_in_instdir())
2424 LOG(("user profile in instdir"));
2425 NS_tstrcpy(newDir
, tmpDir
);
2426 NS_tstrcat(newDir
, gWorkingDirPath
+ NS_tstrlen(gInstallDirPath
));
2427 LOG((LOG_S
, newDir
));
2431 NS_tsnprintf(newDir
, sizeof(newDir
)/sizeof(newDir
[0]),
2436 LOG(("Begin moving newDir (" LOG_S
") to destDir (" LOG_S
")",
2438 rv
= rename_file(newDir
, destDir
, true);
2442 LOG(("Moving failed. Begin copying newDir (" LOG_S
") to destDir (" LOG_S
")",
2444 copy_recursive_skiplist
<0> skiplist
;
2445 rv
= ensure_copy_recursive(newDir
, destDir
, skiplist
);
2450 LOG(("Moving newDir to destDir failed, err: %d", rv
));
2451 LOG(("Now, try to move tmpDir back to destDir"));
2452 ensure_remove_recursive(destDir
);
2453 int rv2
= rename_file(tmpDir
, destDir
, true);
2456 LOG(("Moving tmpDir back to destDir failed, err: %d", rv2
));
2458 // The status file will be have 'pending' written to it so there is no value
2459 // in returning an error specific for this failure.
2463 if (is_userprofile_in_instdir())
2465 // 1.) calculate path of the user profile in the backup directory
2466 // 2.) move the user profile from the backup to the install directory
2467 NS_tchar backup_user_profile
[MAXPATHLEN
];
2468 NS_tchar userprofile
[MAXPATHLEN
];
2470 NS_tstrcpy(userprofile
, gPatchDirPath
);
2471 NS_tchar
* slash
= (NS_tchar
*) NS_tstrrchr(userprofile
, NS_T('/'));
2473 *slash
= NS_T('\0');
2474 NS_tstrcpy(backup_user_profile
, tmpDir
);
2475 size_t installdir_len
= NS_tstrlen(destDir
);
2477 NS_tstrcat(backup_user_profile
, userprofile
+ installdir_len
);
2478 LOG(("copy user profile back from " LOG_S
" to " LOG_S
, backup_user_profile
, userprofile
));
2479 int rv2
= rename_file(backup_user_profile
, userprofile
);
2482 LOG(("failed to copy user profile back"));
2488 #if !defined(_WIN32) && !defined(MACOSX)
2489 // Platforms that have their updates directory in the installation directory
2490 // need to have the last-update.log and backup-update.log files moved from the
2491 // old installation directory to the new installation directory.
2492 NS_tchar tmpLog
[MAXPATHLEN
];
2493 NS_tsnprintf(tmpLog
, sizeof(tmpLog
)/sizeof(tmpLog
[0]),
2494 NS_T("%s/updates/last-update.log"), tmpDir
);
2495 if (!NS_taccess(tmpLog
, F_OK
))
2497 NS_tchar destLog
[MAXPATHLEN
];
2498 NS_tsnprintf(destLog
, sizeof(destLog
)/sizeof(destLog
[0]),
2499 NS_T("%s/updates/last-update.log"), destDir
);
2500 NS_tremove(destLog
);
2501 NS_trename(tmpLog
, destLog
);
2505 LOG(("Now, remove the tmpDir"));
2506 rv
= ensure_remove_recursive(tmpDir
, true);
2509 LOG(("Removing tmpDir failed, err: %d", rv
));
2511 NS_tchar deleteDir
[MAXPATHLEN
];
2512 NS_tsnprintf(deleteDir
, sizeof(deleteDir
)/sizeof(deleteDir
[0]),
2513 NS_T("%s\\%s"), destDir
, DELETE_DIR
);
2514 // Attempt to remove the tobedeleted directory and then recreate it if it
2515 // was successfully removed.
2517 if (NS_taccess(deleteDir
, F_OK
))
2519 NS_tmkdir(deleteDir
, 0755);
2521 remove_recursive_on_reboot(tmpDir
, deleteDir
);
2526 // On OS X, we need to remove the staging directory after its Contents
2527 // directory has been moved.
2528 NS_tchar updatedAppDir
[MAXPATHLEN
];
2529 NS_tsnprintf(updatedAppDir
, sizeof(updatedAppDir
)/sizeof(updatedAppDir
[0]),
2530 NS_T("%s/Updated.app"), gPatchDirPath
);
2531 ensure_remove_recursive(updatedAppDir
);
2541 WaitForServiceFinishThread(void* /*param*/)
2543 // We wait at most 10 minutes, we already waited 5 seconds previously
2544 // before deciding to show this UI.
2545 WaitForServiceStop(SVC_NAME
, 595);
2550 #ifdef VERIFY_MAR_SIGNATURE
2552 * This function reads in the ACCEPTED_MAR_CHANNEL_IDS from update-settings.ini
2554 * @param path The path to the ini file that is to be read
2555 * @param results A pointer to the location to store the read strings
2556 * @return OK on success
2559 ReadMARChannelIDs(const NS_tchar
*path
, MARChannelStringTable
*results
)
2561 // TODO: moggi: needs adaption for LibreOffice
2562 // Check where this function gets its parameters from
2563 const unsigned int kNumStrings
= 1;
2564 const char *kUpdaterKeys
= "ACCEPTED_MAR_CHANNEL_IDS\0";
2565 char updater_strings
[kNumStrings
][MAX_TEXT_LEN
];
2567 int result
= ReadStrings(path
, kUpdaterKeys
, kNumStrings
,
2568 updater_strings
, "Settings");
2570 strncpy(results
->MARChannelID
, updater_strings
[0], MAX_TEXT_LEN
- 1);
2571 results
->MARChannelID
[MAX_TEXT_LEN
- 1] = 0;
2578 GetUpdateFileNames(std::vector
<tstring
>& fileNames
)
2580 NS_tchar fileName
[MAXPATHLEN
];
2581 NS_tsnprintf(fileName
, MAXPATHLEN
,
2582 NS_T("%s/update.mar"), gPatchDirPath
);
2583 fileNames
.push_back(fileName
);
2585 // add the language packs
2586 NS_tDIR
* dir
= NS_topendir(gPatchDirPath
);
2589 LOG(("Could not open directory " LOG_S
, gPatchDirPath
));
2594 while ((entry
= NS_treaddir(dir
)) != nullptr)
2596 if (NS_tstrcmp(entry
->d_name
, NS_T(".")) &&
2597 NS_tstrcmp(entry
->d_name
, NS_T("..")) &&
2598 NS_tstrcmp(entry
->d_name
, NS_T("update.mar")))
2600 if (NS_tstrncmp(entry
->d_name
, NS_T("update"), 6) == 0)
2602 NS_tchar
*dot
= NS_tstrrchr(entry
->d_name
, NS_T('.'));
2603 if (dot
&& !NS_tstrcmp(dot
, NS_T(".mar")))
2605 NS_tchar updatePath
[MAXPATHLEN
];
2606 NS_tsnprintf(updatePath
, sizeof(updatePath
)/sizeof(updatePath
[0]),
2607 NS_T("%s/%s"), gPatchDirPath
, entry
->d_name
);
2609 LOG (("Found language update file: " LOG_S
, updatePath
));
2610 fileNames
.push_back(updatePath
);
2619 CheckSignature(ArchiveReader
& archiveReader
)
2621 #ifdef VERIFY_MAR_SIGNATURE
2623 HKEY baseKey
= nullptr;
2624 wchar_t valueName
[] = L
"Image Path";
2625 wchar_t rasenh
[] = L
"rsaenh.dll";
2627 if (RegOpenKeyExW(HKEY_LOCAL_MACHINE
,
2628 L
"SOFTWARE\\Microsoft\\Cryptography\\Defaults\\Provider\\Microsoft Enhanced Cryptographic Provider v1.0",
2629 0, KEY_READ
| KEY_WRITE
,
2630 &baseKey
) == ERROR_SUCCESS
)
2632 wchar_t path
[MAX_PATH
+ 1];
2633 DWORD size
= sizeof(path
);
2635 if (RegQueryValueExW(baseKey
, valueName
, 0, &type
,
2636 (LPBYTE
)path
, &size
) == ERROR_SUCCESS
)
2638 if (type
== REG_SZ
&& wcscmp(path
, rasenh
) == 0)
2640 wchar_t rasenhFullPath
[] = L
"%SystemRoot%\\System32\\rsaenh.dll";
2641 if (RegSetValueExW(baseKey
, valueName
, 0, REG_SZ
,
2642 (const BYTE
*)rasenhFullPath
,
2643 sizeof(rasenhFullPath
)) == ERROR_SUCCESS
)
2651 int rv
= archiveReader
.VerifySignature();
2657 RegSetValueExW(baseKey
, valueName
, 0, REG_SZ
,
2658 (const BYTE
*)rasenh
,
2661 RegCloseKey(baseKey
);
2670 NS_tchar updateSettingsPath
[MAX_TEXT_LEN
];
2672 // TODO: moggi: needs adaption for LibreOffice
2673 // These paths need to be adapted for us.
2674 NS_tsnprintf(updateSettingsPath
,
2675 sizeof(updateSettingsPath
) / sizeof(updateSettingsPath
[0]),
2677 NS_T("%s/Contents/Resources/update-settings.ini"),
2679 NS_T("%s/update-settings.ini"),
2682 MARChannelStringTable MARStrings
;
2683 if (ReadMARChannelIDs(updateSettingsPath
, &MARStrings
) != OK
)
2685 // If we can't read from update-settings.ini then we shouldn't impose
2686 // a MAR restriction. Some installations won't even include this file.
2687 MARStrings
.MARChannelID
[0] = '\0';
2690 rv
= archiveReader
.VerifyProductInformation(MARStrings
.MARChannelID
,
2691 LIBO_VERSION_DOTTED
);
2700 UpdateThreadFunc(void * /*param*/)
2702 // open ZIP archive and process...
2704 if (sReplaceRequest
)
2706 rv
= ProcessReplaceRequest();
2710 std::vector
<tstring
> fileNames
;
2711 GetUpdateFileNames(fileNames
);
2713 for (auto& fileName
: fileNames
)
2715 ArchiveReader archiveReader
;
2716 rv
= archiveReader
.Open(fileName
.c_str());
2719 LOG(("Could not open " LOG_S
, fileName
.c_str()));
2723 rv
= CheckSignature(archiveReader
);
2726 LOG(("Could not verify the signature of " LOG_S
, fileName
.c_str()));
2731 if (rv
== OK
&& sStagedUpdate
)
2733 rv
= CopyInstallDirToDestDir();
2738 for (auto& fileName
: fileNames
)
2740 ArchiveReader archiveReader
;
2741 archiveReader
.Open(fileName
.c_str());
2742 rv
= DoUpdate(archiveReader
);
2744 NS_tchar updatingDir
[MAXPATHLEN
];
2745 NS_tsnprintf(updatingDir
, sizeof(updatingDir
)/sizeof(updatingDir
[0]),
2746 NS_T("%s/updating"), gWorkingDirPath
);
2747 ensure_remove_recursive(updatingDir
);
2751 if (rv
&& (sReplaceRequest
|| sStagedUpdate
))
2754 // On Windows, the current working directory of the process should be changed
2755 // so that it's not locked.
2758 NS_tchar sysDir
[MAX_PATH
+ 1] = { L
'\0' };
2759 if (GetSystemDirectoryW(sysDir
, MAX_PATH
+ 1))
2765 ensure_remove_recursive(gWorkingDirPath
);
2766 // When attempting to replace the application, we should fall back
2767 // to non-staged updates in case of a failure. We do this by
2768 // setting the status to pending, exiting the updater, and
2769 // launching the callback application. The callback application's
2770 // startup path will see the pending status, and will start the
2771 // updater application again in order to apply the update without
2773 if (sReplaceRequest
)
2775 WriteStatusFile(sUsingService
? "pending-service" : "pending");
2779 WriteStatusFile(rv
);
2782 // Some tests need to use --test-process-updates again.
2783 putenv(const_cast<char*>("MOZ_TEST_PROCESS_UPDATES="));
2790 LOG(("failed: %d", rv
));
2795 // If the update was successful we need to update the timestamp on the
2796 // top-level Mac OS X bundle directory so that Mac OS X's Launch Services
2797 // picks up any major changes when the bundle is updated.
2798 if (!sStagedUpdate
&& utimes(gInstallDirPath
, nullptr) != 0)
2800 LOG(("Couldn't set access/modification time on application bundle."));
2806 WriteStatusFile(rv
);
2809 LOG(("calling QuitProgressUI"));
2815 ServeElevatedUpdateThreadFunc(void* param
)
2817 UpdateServerThreadArgs
* threadArgs
= (UpdateServerThreadArgs
*)param
;
2818 gSucceeded
= ServeElevatedUpdate(threadArgs
->argc
, threadArgs
->argv
);
2821 WriteStatusFile(ELEVATION_CANCELED
);
2826 void freeArguments(int argc
, char** argv
)
2828 for (int i
= 0; i
< argc
; i
++)
2836 int LaunchCallbackAndPostProcessApps(int argc
, NS_tchar
** argv
,
2839 , const WCHAR
* elevatedLockFilePath
2840 , HANDLE updateLockFileHandle
2841 #elif defined(MACOSX)
2846 if (argc
> callbackIndex
)
2851 if (!LaunchWinPostProcess(gInstallDirPath
, gPatchDirPath
))
2853 fprintf(stderr
, "The post update process was not launched");
2856 // The service update will only be executed if it is already installed.
2857 // For first time installs of the service, the install will happen from
2858 // the PostUpdate process. We do the service update process here
2859 // because it's possible we are updating with updater.exe without the
2860 // service if the service failed to apply the update. We want to update
2861 // the service to a newer version in that case. If we are not running
2862 // through the service, then USING_SERVICE will not exist.
2865 StartServiceUpdate(gInstallDirPath
);
2868 EXIT_WHEN_ELEVATED(elevatedLockFilePath
, updateLockFileHandle
, 0);
2869 #elif defined(MACOSX)
2874 LaunchMacPostProcess(gInstallDirPath
);
2878 LaunchCallbackApp(argv
[5],
2879 argc
- callbackIndex
,
2880 argv
+ callbackIndex
,
2883 } // if (!isElevated)
2884 #endif /* XP_MACOSX */
2889 int NS_main(int argc
, NS_tchar
**argv
)
2891 // The callback is the remaining arguments starting at callbackIndex.
2892 // The argument specified by callbackIndex is the callback executable and the
2893 // argument prior to callbackIndex is the working directory.
2894 const int callbackIndex
= 6;
2897 // TODO: moggi: needs adaption for LibreOffice
2899 strstr(argv
[0], "/Library/PrivilegedHelperTools/org.mozilla.updater") != 0;
2902 if (!ObtainUpdaterArguments(&argc
, &argv
))
2904 // Won't actually get here because ObtainUpdaterArguments will terminate
2905 // the current process on failure.
2911 #if defined(VERIFY_MAR_SIGNATURE) && !defined(_WIN32) && !defined(MACOSX)
2912 // On Windows and Mac we rely on native APIs to do verifications so we don't
2913 // need to initialize NSS at all there.
2914 // Otherwise, minimize the amount of NSS we depend on by avoiding all the NSS
2916 if (NSS_NoDB_Init(NULL
) != SECSuccess
)
2918 PRErrorCode error
= PR_GetError();
2919 fprintf(stderr
, "Could not initialize NSS: %s (%d)",
2920 PR_ErrorToName(error
), (int) error
);
2929 InitProgressUI(&argc
, &argv
);
2934 // To process an update the updater command line must at a minimum have the
2935 // directory path containing the updater.mar file to process as the first
2936 // argument, the install directory as the second argument, and the directory
2937 // to apply the update to as the third argument. When the updater is launched
2938 // by another process the PID of the parent process should be provided in the
2939 // optional fourth argument and the updater will wait on the parent process to
2940 // exit if the value is non-zero and the process is present. This is necessary
2941 // due to not being able to update files that are in use on Windows. The
2942 // optional fifth argument is the callback's working directory and the
2943 // optional sixth argument is the callback path. The callback is the
2944 // application to launch after updating and it will be launched when these
2945 // arguments are provided whether the update was successful or not. All
2946 // remaining arguments are optional and are passed to the callback when it is
2950 fprintf(stderr
, "Usage: updater patch-dir install-dir apply-to-dir [wait-pid [callback-working-dir callback-path args...]]\n");
2954 freeArguments(argc
, argv
);
2955 CleanupElevatedMacUpdate(true);
2961 // The directory containing the update information.
2962 gPatchDirPath
= argv
[1];
2964 // The directory we're going to update to.
2965 // We copy this string because we need to remove trailing slashes. The C++
2966 // standard says that it's always safe to write to strings pointed to by argv
2967 // elements, but I don't necessarily believe it.
2968 NS_tstrncpy(gInstallDirPath
, argv
[2], MAXPATHLEN
);
2969 gInstallDirPath
[MAXPATHLEN
- 1] = NS_T('\0');
2970 NS_tchar
*slash
= NS_tstrrchr(gInstallDirPath
, NS_SLASH
);
2971 if (slash
&& !slash
[1])
2973 *slash
= NS_T('\0');
2977 bool useService
= false;
2978 bool testOnlyFallbackKeyExists
= false;
2979 bool noServiceFallback
= false;
2981 // We never want the service to be used unless we build with
2982 // the maintenance service.
2983 #ifdef MAINTENANCE_SERVICE
2984 useService
= IsUpdateStatusPendingService();
2985 // Our tests run with a different apply directory for each test.
2986 // We use this registry key on our test slaves to store the
2987 // allowed name/issuers.
2988 testOnlyFallbackKeyExists
= DoesFallbackKeyExist();
2991 // Remove everything except close window from the context menu
2993 // TODO: moggi: needs adaption for LibreOffice
2994 HKEY hkApp
= nullptr;
2995 RegCreateKeyExW(HKEY_CURRENT_USER
, L
"Software\\Classes\\Applications",
2996 0, nullptr, REG_OPTION_NON_VOLATILE
, KEY_SET_VALUE
, nullptr,
2999 if (RegCreateKeyExW(HKEY_CURRENT_USER
,
3000 L
"Software\\Classes\\Applications\\updater.exe",
3001 0, nullptr, REG_OPTION_VOLATILE
, KEY_SET_VALUE
, nullptr,
3002 &hkApp
, nullptr) == ERROR_SUCCESS
)
3004 RegSetValueExW(hkApp
, L
"IsHostApp", 0, REG_NONE
, 0, 0);
3005 RegSetValueExW(hkApp
, L
"NoOpenWith", 0, REG_NONE
, 0, 0);
3006 RegSetValueExW(hkApp
, L
"NoStartPage", 0, REG_NONE
, 0, 0);
3012 // If there is a PID specified and it is not '0' then wait for the process to exit.
3021 pid
= _wtoi64(argv
[4]);
3023 pid
= atoi(argv
[4]);
3027 // This is a signal from the parent process that the updater should stage
3029 sStagedUpdate
= true;
3031 else if (NS_tstrstr(argv
[4], NS_T("/replace")))
3033 // We're processing a request to replace the application with a staged
3035 sReplaceRequest
= true;
3039 // The directory we're going to update to.
3040 // We copy this string because we need to remove trailing slashes. The C++
3041 // standard says that it's always safe to write to strings pointed to by argv
3042 // elements, but I don't necessarily believe it.
3043 NS_tstrncpy(gWorkingDirPath
, argv
[3], MAXPATHLEN
);
3044 gWorkingDirPath
[MAXPATHLEN
- 1] = NS_T('\0');
3045 slash
= NS_tstrrchr(gWorkingDirPath
, NS_SLASH
);
3046 if (slash
&& !slash
[1])
3048 *slash
= NS_T('\0');
3052 if (!isElevated
&& !IsRecursivelyWritable(argv
[2]))
3054 // If the app directory isn't recursively writeable, an elevated update is
3056 UpdateServerThreadArgs threadArgs
;
3057 threadArgs
.argc
= argc
;
3058 threadArgs
.argv
= const_cast<const NS_tchar
**>(argv
);
3061 if (t1
.Run(ServeElevatedUpdateThreadFunc
, &threadArgs
) == 0)
3063 // Show an indeterminate progress bar while an elevated update is in
3065 ShowProgressUI(true);
3069 LaunchCallbackAndPostProcessApps(argc
, argv
, callbackIndex
, false);
3070 return gSucceeded
? 0 : 1;
3074 LogInit(gPatchDirPath
, NS_T("update.log"));
3076 if (!WriteStatusFile("applying"))
3078 LOG(("failed setting status to 'applying'"));
3082 freeArguments(argc
, argv
);
3083 CleanupElevatedMacUpdate(true);
3091 LOG(("Performing a staged update"));
3093 else if (sReplaceRequest
)
3095 LOG(("Performing a replace request"));
3098 LOG(("PATCH DIRECTORY " LOG_S
, gPatchDirPath
));
3099 LOG(("INSTALLATION DIRECTORY " LOG_S
, gInstallDirPath
));
3100 LOG(("WORKING DIRECTORY " LOG_S
, gWorkingDirPath
));
3103 if (_wcsnicmp(gWorkingDirPath
, gInstallDirPath
, MAX_PATH
) != 0)
3105 if (!sStagedUpdate
&& !sReplaceRequest
)
3107 WriteStatusFile(INVALID_APPLYTO_DIR_ERROR
);
3108 LOG(("Installation directory and working directory must be the same "
3109 "for non-staged updates. Exiting."));
3114 NS_tchar workingDirParent
[MAX_PATH
];
3115 NS_tsnprintf(workingDirParent
,
3116 sizeof(workingDirParent
) / sizeof(workingDirParent
[0]),
3117 NS_T("%s"), gWorkingDirPath
);
3118 if (!PathRemoveFileSpecW(workingDirParent
))
3120 WriteStatusFile(REMOVE_FILE_SPEC_ERROR
);
3121 LOG(("Error calling PathRemoveFileSpecW: %d", GetLastError()));
3126 if (_wcsnicmp(workingDirParent
, gInstallDirPath
, MAX_PATH
) != 0)
3128 WriteStatusFile(INVALID_APPLYTO_DIR_STAGED_ERROR
);
3129 LOG(("The apply-to directory must be the same as or "
3130 "a child of the installation directory! Exiting."));
3141 HANDLE parent
= OpenProcess(SYNCHRONIZE
, false, (DWORD
) pid
);
3142 // May return nullptr if the parent process has already gone away.
3143 // Otherwise, wait for the parent process to exit before starting the
3147 DWORD waitTime
= PARENT_WAIT
;
3148 DWORD result
= WaitForSingleObject(parent
, waitTime
);
3149 CloseHandle(parent
);
3150 if (result
!= WAIT_OBJECT_0
)
3156 waitpid(pid
, nullptr, 0);
3160 #ifdef MAINTENANCE_SERVICE
3161 sUsingService
= EnvHasValue("USING_SERVICE");
3162 putenv(const_cast<char*>("USING_SERVICE="));
3164 // lastFallbackError keeps track of the last error for the service not being
3165 // used, in case of an error when fallback is not enabled we write the
3166 // error to the update.status file.
3167 // When fallback is disabled (MOZ_NO_SERVICE_FALLBACK does not exist) then
3168 // we will instead fallback to not using the service and display a UAC prompt.
3169 int lastFallbackError
= FALLBACKKEY_UNKNOWN_ERROR
;
3171 // Launch a second instance of the updater with the runas verb on Windows
3172 // when write access is denied to the installation directory.
3173 HANDLE updateLockFileHandle
= INVALID_HANDLE_VALUE
;
3174 NS_tchar elevatedLockFilePath
[MAXPATHLEN
] = {NS_T('\0')};
3175 if (!sUsingService
&&
3176 (argc
> callbackIndex
|| sStagedUpdate
|| sReplaceRequest
))
3178 NS_tchar updateLockFilePath
[MAXPATHLEN
];
3181 // When staging an update, the lock file is:
3182 // <install_dir>\updated.update_in_progress.lock
3183 NS_tsnprintf(updateLockFilePath
,
3184 sizeof(updateLockFilePath
)/sizeof(updateLockFilePath
[0]),
3185 NS_T("%s/updated.update_in_progress.lock"), gInstallDirPath
);
3187 else if (sReplaceRequest
)
3189 // When processing a replace request, the lock file is:
3190 // <install_dir>\..\moz_update_in_progress.lock
3191 NS_tchar installDir
[MAXPATHLEN
];
3192 NS_tstrcpy(installDir
, gInstallDirPath
);
3193 NS_tchar
*slash
= (NS_tchar
*) NS_tstrrchr(installDir
, NS_SLASH
);
3194 *slash
= NS_T('\0');
3195 NS_tsnprintf(updateLockFilePath
,
3196 sizeof(updateLockFilePath
)/sizeof(updateLockFilePath
[0]),
3197 NS_T("%s\\moz_update_in_progress.lock"), installDir
);
3201 // In the non-staging update case, the lock file is:
3202 // <install_dir>\<app_name>.exe.update_in_progress.lock
3203 NS_tsnprintf(updateLockFilePath
,
3204 sizeof(updateLockFilePath
)/sizeof(updateLockFilePath
[0]),
3205 NS_T("%s.update_in_progress.lock"), argv
[callbackIndex
]);
3208 // The update_in_progress.lock file should only exist during an update. In
3209 // case it exists attempt to remove it and exit if that fails to prevent
3210 // simultaneous updates occurring.
3211 if (!_waccess(updateLockFilePath
, F_OK
) &&
3212 NS_tremove(updateLockFilePath
) != 0)
3214 // Try to fall back to the old way of doing updates if a staged
3216 if (sStagedUpdate
|| sReplaceRequest
)
3218 // Note that this could fail, but if it does, there isn't too much we
3219 // can do in order to recover anyways.
3220 WriteStatusFile("pending");
3222 LOG(("Update already in progress! Exiting"));
3226 updateLockFileHandle
= CreateFileW(updateLockFilePath
,
3227 GENERIC_READ
| GENERIC_WRITE
,
3231 FILE_FLAG_DELETE_ON_CLOSE
,
3234 NS_tsnprintf(elevatedLockFilePath
,
3235 sizeof(elevatedLockFilePath
)/sizeof(elevatedLockFilePath
[0]),
3236 NS_T("%s/update_elevated.lock"), gPatchDirPath
);
3238 // Even if a file has no sharing access, you can still get its attributes
3239 bool startedFromUnelevatedUpdater
=
3240 GetFileAttributesW(elevatedLockFilePath
) != INVALID_FILE_ATTRIBUTES
;
3242 // If we're running from the service, then we were started with the same
3243 // token as the service so the permissions are already dropped. If we're
3244 // running from an elevated updater that was started from an unelevated
3245 // updater, then we drop the permissions here. We do not drop the
3246 // permissions on the originally called updater because we use its token
3247 // to start the callback application.
3248 if (startedFromUnelevatedUpdater
)
3250 // Disable every privilege we don't need. Processes started using
3251 // CreateProcess will use the same token as this process.
3252 UACHelper::DisablePrivileges(nullptr);
3255 if (updateLockFileHandle
== INVALID_HANDLE_VALUE
||
3256 (useService
&& testOnlyFallbackKeyExists
&& noServiceFallback
))
3258 if (!_waccess(elevatedLockFilePath
, F_OK
) &&
3259 NS_tremove(elevatedLockFilePath
) != 0)
3261 fprintf(stderr
, "Unable to create elevated lock file! Exiting\n");
3265 HANDLE elevatedFileHandle
;
3266 elevatedFileHandle
= CreateFileW(elevatedLockFilePath
,
3267 GENERIC_READ
| GENERIC_WRITE
,
3271 FILE_FLAG_DELETE_ON_CLOSE
,
3274 if (elevatedFileHandle
== INVALID_HANDLE_VALUE
)
3276 LOG(("Unable to create elevated lock file! Exiting"));
3280 wchar_t *cmdLine
= MakeCommandLine(argc
- 1, argv
+ 1);
3283 CloseHandle(elevatedFileHandle
);
3287 // Make sure the path to the updater to use for the update is on local.
3288 // We do this check to make sure that file locking is available for
3289 // race condition security checks.
3292 BOOL isLocal
= FALSE
;
3293 useService
= IsLocalFile(argv
[0], isLocal
) && isLocal
;
3296 // If we have unprompted elevation we should NOT use the service
3297 // for the update. Service updates happen with the SYSTEM account
3298 // which has more privs than we need to update with.
3299 // Windows 8 provides a user interface so users can configure this
3300 // behavior and it can be configured in the registry in all Windows
3301 // versions that support UAC.
3304 BOOL unpromptedElevation
;
3305 if (IsUnpromptedElevation(unpromptedElevation
))
3307 useService
= !unpromptedElevation
;
3311 // Make sure the service registry entries for the installation path
3312 // are available. If not don't use the service.
3315 WCHAR maintenanceServiceKey
[MAX_PATH
+ 1];
3316 // TODO: moggi: needs adaption for LibreOffice
3317 // Most likely the registry part is not correct yet
3318 if (CalculateRegistryPathFromFilePath(gInstallDirPath
,
3319 maintenanceServiceKey
))
3321 HKEY baseKey
= nullptr;
3322 if (RegOpenKeyExW(HKEY_LOCAL_MACHINE
,
3323 maintenanceServiceKey
, 0,
3324 KEY_READ
| KEY_WOW64_64KEY
,
3325 &baseKey
) == ERROR_SUCCESS
)
3327 RegCloseKey(baseKey
);
3332 useService
= testOnlyFallbackKeyExists
;
3336 lastFallbackError
= FALLBACKKEY_NOKEY_ERROR
;
3343 lastFallbackError
= FALLBACKKEY_REGPATH_ERROR
;
3347 // Originally we used to write "pending" to update.status before
3348 // launching the service command. This is no longer needed now
3349 // since the service command is launched from updater.exe. If anything
3350 // fails in between, we can fall back to using the normal update process
3353 // If we still want to use the service try to launch the service
3354 // command for the update.
3357 // If the update couldn't be started, then set useService to false so
3358 // we do the update the old way.
3359 DWORD ret
= LaunchServiceSoftwareUpdateCommand(argc
, (LPCWSTR
*)argv
);
3360 useService
= (ret
== ERROR_SUCCESS
);
3361 // If the command was launched then wait for the service to be done.
3364 bool showProgressUI
= false;
3365 // Never show the progress UI when staging updates.
3368 // We need to call this separately instead of allowing ShowProgressUI
3369 // to initialize the strings because the service will move the
3370 // ini file out of the way when running updater.
3371 showProgressUI
= !InitProgressUIStrings();
3374 // Wait for the service to stop for 5 seconds. If the service
3375 // has still not stopped then show an indeterminate progress bar.
3376 DWORD lastState
= WaitForServiceStop(SVC_NAME
, 5);
3377 if (lastState
!= SERVICE_STOPPED
)
3379 std::thread
waitThread(WaitForServiceFinishThread
, nullptr);
3382 ShowProgressUI(true, false);
3387 lastState
= WaitForServiceStop(SVC_NAME
, 1);
3388 if (lastState
!= SERVICE_STOPPED
)
3390 // If the service doesn't stop after 10 minutes there is
3391 // something seriously wrong.
3392 lastFallbackError
= FALLBACKKEY_SERVICE_NO_STOP_ERROR
;
3398 lastFallbackError
= FALLBACKKEY_LAUNCH_ERROR
;
3402 // If the service can't be used when staging and update, make sure that
3403 // the UAC prompt is not shown! In this case, just set the status to
3404 // pending and the update will be applied during the next startup.
3405 if (!useService
&& sStagedUpdate
)
3407 if (updateLockFileHandle
!= INVALID_HANDLE_VALUE
)
3409 CloseHandle(updateLockFileHandle
);
3411 WriteStatusFile("pending");
3415 // If we started the service command, and it finished, check the
3416 // update.status file to make sure it succeeded, and if it did
3417 // we need to manually start the PostUpdate process from the
3418 // current user's session of this unelevated updater.exe the
3419 // current process is running as.
3420 // Note that we don't need to do this if we're just staging the update,
3421 // as the PostUpdate step runs when performing the replacing in that case.
3422 if (useService
&& !sStagedUpdate
)
3424 bool updateStatusSucceeded
= false;
3425 if (IsUpdateStatusSucceeded(updateStatusSucceeded
) &&
3426 updateStatusSucceeded
)
3428 if (!LaunchWinPostProcess(gInstallDirPath
, gPatchDirPath
))
3430 fprintf(stderr
, "The post update process which runs as the user"
3431 " for service update could not be launched.");
3436 // If we didn't want to use the service at all, or if an update was
3437 // already happening, or launching the service command failed, then
3438 // launch the elevated updater.exe as we do without the service.
3439 // We don't launch the elevated updater in the case that we did have
3440 // write access all along because in that case the only reason we're
3441 // using the service is because we are testing.
3442 if (!useService
&& !noServiceFallback
&&
3443 updateLockFileHandle
== INVALID_HANDLE_VALUE
)
3445 SHELLEXECUTEINFO sinfo
;
3446 memset(&sinfo
, 0, sizeof(SHELLEXECUTEINFO
));
3447 sinfo
.cbSize
= sizeof(SHELLEXECUTEINFO
);
3448 sinfo
.fMask
= SEE_MASK_FLAG_NO_UI
|
3449 SEE_MASK_FLAG_DDEWAIT
|
3450 SEE_MASK_NOCLOSEPROCESS
;
3451 sinfo
.hwnd
= nullptr;
3452 sinfo
.lpFile
= argv
[0];
3453 sinfo
.lpParameters
= cmdLine
;
3454 sinfo
.lpVerb
= L
"runas";
3455 sinfo
.nShow
= SW_SHOWNORMAL
;
3457 bool result
= ShellExecuteEx(&sinfo
);
3462 WaitForSingleObject(sinfo
.hProcess
, INFINITE
);
3463 CloseHandle(sinfo
.hProcess
);
3467 WriteStatusFile(ELEVATION_CANCELED
);
3471 if (argc
> callbackIndex
)
3473 LaunchCallbackApp(argv
[5], argc
- callbackIndex
,
3474 argv
+ callbackIndex
, sUsingService
);
3477 CloseHandle(elevatedFileHandle
);
3479 if (!useService
&& !noServiceFallback
&&
3480 INVALID_HANDLE_VALUE
== updateLockFileHandle
)
3482 // We didn't use the service and we did run the elevated updater.exe.
3483 // The elevated updater.exe is responsible for writing out the
3484 // update.status file.
3487 else if (useService
)
3489 // The service command was launched. The service is responsible for
3490 // writing out the update.status file.
3491 if (updateLockFileHandle
!= INVALID_HANDLE_VALUE
)
3493 CloseHandle(updateLockFileHandle
);
3499 // Otherwise the service command was not launched at all.
3500 // We are only reaching this code path because we had write access
3501 // all along to the directory and a fallback key existed, and we
3502 // have fallback disabled (MOZ_NO_SERVICE_FALLBACK env var exists).
3503 // We only currently use this env var from XPCShell tests.
3504 CloseHandle(updateLockFileHandle
);
3505 WriteStatusFile(lastFallbackError
);
3514 // When staging updates, blow away the old installation directory and create
3516 ensure_remove_recursive(gWorkingDirPath
);
3518 if (!sReplaceRequest
)
3520 // Try to create the destination directory if it doesn't exist
3521 int rv
= NS_tmkdir(gWorkingDirPath
, 0755);
3522 if (rv
!= OK
&& errno
!= EEXIST
)
3527 freeArguments(argc
, argv
);
3528 CleanupElevatedMacUpdate(true);
3536 // For replace requests, we don't need to do any real updates, so this is not
3538 if (!sReplaceRequest
)
3540 // Allocate enough space for the length of the path an optional additional
3541 // trailing slash and null termination.
3542 NS_tchar
*destpath
= (NS_tchar
*) malloc((NS_tstrlen(gWorkingDirPath
) + 2) * sizeof(NS_tchar
));
3546 NS_tchar
*c
= destpath
;
3547 NS_tstrcpy(c
, gWorkingDirPath
);
3548 c
+= NS_tstrlen(gWorkingDirPath
);
3549 if (gWorkingDirPath
[NS_tstrlen(gWorkingDirPath
) - 1] != NS_T('/') &&
3550 gWorkingDirPath
[NS_tstrlen(gWorkingDirPath
) - 1] != NS_T('\\'))
3552 NS_tstrcat(c
, NS_T("/"));
3553 c
+= NS_tstrlen(NS_T("/"));
3558 gDestPath
= destpath
;
3561 NS_tchar applyDirLongPath
[MAXPATHLEN
];
3562 if (!GetLongPathNameW(gWorkingDirPath
, applyDirLongPath
,
3563 sizeof(applyDirLongPath
)/sizeof(applyDirLongPath
[0])))
3565 LOG(("NS_main: unable to find apply to dir: " LOG_S
, gWorkingDirPath
));
3567 WriteStatusFile(WRITE_ERROR_APPLY_DIR_PATH
);
3568 EXIT_WHEN_ELEVATED(elevatedLockFilePath
, updateLockFileHandle
, 1);
3569 if (argc
> callbackIndex
)
3571 LaunchCallbackApp(argv
[5], argc
- callbackIndex
,
3572 argv
+ callbackIndex
, sUsingService
);
3577 HANDLE callbackFile
= INVALID_HANDLE_VALUE
;
3578 if (argc
> callbackIndex
)
3580 // If the callback executable is specified it must exist for a successful
3581 // update. It is important we null out the whole buffer here because later
3582 // we make the assumption that the callback application is inside the
3583 // apply-to dir. If we don't have a fully null'ed out buffer it can lead
3584 // to stack corruption which causes crashes and other problems.
3585 NS_tchar callbackLongPath
[MAXPATHLEN
];
3586 ZeroMemory(callbackLongPath
, sizeof(callbackLongPath
));
3587 NS_tchar
*targetPath
= argv
[callbackIndex
];
3588 NS_tchar buffer
[MAXPATHLEN
* 2] = { NS_T('\0') };
3589 size_t bufferLeft
= MAXPATHLEN
* 2;
3590 if (sReplaceRequest
)
3592 // In case of replace requests, we should look for the callback file in
3593 // the destination directory.
3594 size_t commonPrefixLength
= PathCommonPrefixW(argv
[callbackIndex
],
3597 NS_tchar
*p
= buffer
;
3598 NS_tstrncpy(p
, argv
[callbackIndex
], commonPrefixLength
);
3599 p
+= commonPrefixLength
;
3600 bufferLeft
-= commonPrefixLength
;
3601 NS_tstrncpy(p
, gInstallDirPath
+ commonPrefixLength
, bufferLeft
);
3603 size_t len
= NS_tstrlen(gInstallDirPath
+ commonPrefixLength
);
3610 NS_tchar installDir
[MAXPATHLEN
];
3611 NS_tstrcpy(installDir
, gInstallDirPath
);
3612 size_t callbackPrefixLength
= PathCommonPrefixW(argv
[callbackIndex
],
3615 NS_tstrncpy(p
, argv
[callbackIndex
] + std::max(callbackPrefixLength
,
3616 commonPrefixLength
), bufferLeft
);
3617 targetPath
= buffer
;
3619 if (!GetLongPathNameW(targetPath
, callbackLongPath
,
3620 sizeof(callbackLongPath
)/sizeof(callbackLongPath
[0])))
3622 LOG(("NS_main: unable to find callback file: " LOG_S
, targetPath
));
3624 WriteStatusFile(WRITE_ERROR_CALLBACK_PATH
);
3625 EXIT_WHEN_ELEVATED(elevatedLockFilePath
, updateLockFileHandle
, 1);
3626 if (argc
> callbackIndex
)
3628 LaunchCallbackApp(argv
[5],
3629 argc
- callbackIndex
,
3630 argv
+ callbackIndex
,
3636 // Doing this is only necessary when we're actually applying a patch.
3637 if (!sReplaceRequest
)
3639 int len
= NS_tstrlen(applyDirLongPath
);
3640 NS_tchar
*s
= callbackLongPath
;
3641 NS_tchar
*d
= gCallbackRelPath
;
3642 // advance to the apply to directory and advance past the trailing backslash
3645 if (*s
== NS_T('\\'))
3648 // Copy the string and replace backslashes with forward slashes along the
3652 if (*s
== NS_T('\\'))
3663 // Make a copy of the callback executable so it can be read when patching.
3664 NS_tsnprintf(gCallbackBackupPath
,
3665 sizeof(gCallbackBackupPath
)/sizeof(gCallbackBackupPath
[0]),
3666 NS_T("%s" CALLBACK_BACKUP_EXT
), argv
[callbackIndex
]);
3667 NS_tremove(gCallbackBackupPath
);
3668 if (!CopyFileW(argv
[callbackIndex
], gCallbackBackupPath
, true))
3670 DWORD copyFileError
= GetLastError();
3671 LOG(("NS_main: failed to copy callback file " LOG_S
3672 " into place at " LOG_S
, argv
[callbackIndex
], gCallbackBackupPath
));
3674 if (copyFileError
== ERROR_ACCESS_DENIED
)
3676 WriteStatusFile(WRITE_ERROR_ACCESS_DENIED
);
3680 WriteStatusFile(WRITE_ERROR_CALLBACK_APP
);
3683 EXIT_WHEN_ELEVATED(elevatedLockFilePath
, updateLockFileHandle
, 1);
3684 LaunchCallbackApp(argv
[callbackIndex
],
3685 argc
- callbackIndex
,
3686 argv
+ callbackIndex
,
3691 // Since the process may be signaled as exited by WaitForSingleObject before
3692 // the release of the executable image try to lock the main executable file
3693 // multiple times before giving up. If we end up giving up, we won't
3695 const int max_retries
= 10;
3697 DWORD lastWriteError
= 0;
3700 // By opening a file handle without FILE_SHARE_READ to the callback
3701 // executable, the OS will prevent launching the process while it is
3703 callbackFile
= CreateFileW(targetPath
,
3704 DELETE
| GENERIC_WRITE
,
3705 // allow delete, rename, and write
3706 FILE_SHARE_DELETE
| FILE_SHARE_WRITE
,
3707 nullptr, OPEN_EXISTING
, 0, nullptr);
3708 if (callbackFile
!= INVALID_HANDLE_VALUE
)
3711 lastWriteError
= GetLastError();
3712 LOG(("NS_main: callback app file open attempt %d failed. " \
3713 "File: " LOG_S
". Last error: %d", retries
,
3714 targetPath
, lastWriteError
));
3718 while (++retries
<= max_retries
);
3720 // CreateFileW will fail if the callback executable is already in use.
3721 if (callbackFile
== INVALID_HANDLE_VALUE
)
3723 // Only fail the update if the last error was not a sharing violation.
3724 if (lastWriteError
!= ERROR_SHARING_VIOLATION
)
3726 LOG(("NS_main: callback app file in use, failed to exclusively open " \
3727 "executable file: " LOG_S
, argv
[callbackIndex
]));
3729 if (lastWriteError
== ERROR_ACCESS_DENIED
)
3731 WriteStatusFile(WRITE_ERROR_ACCESS_DENIED
);
3735 WriteStatusFile(WRITE_ERROR_CALLBACK_APP
);
3738 NS_tremove(gCallbackBackupPath
);
3739 EXIT_WHEN_ELEVATED(elevatedLockFilePath
, updateLockFileHandle
, 1);
3740 LaunchCallbackApp(argv
[5],
3741 argc
- callbackIndex
,
3742 argv
+ callbackIndex
,
3746 LOG(("NS_main: callback app file in use, continuing without " \
3747 "exclusive access for executable file: " LOG_S
,
3748 argv
[callbackIndex
]));
3753 // DELETE_DIR is not required when performing a staged update or replace
3754 // request; it can be used during a replace request but then it doesn't
3755 // use gDeleteDirPath.
3756 if (!sStagedUpdate
&& !sReplaceRequest
)
3758 // The directory to move files that are in use to on Windows. This directory
3759 // will be deleted after the update is finished, on OS reboot using
3760 // MoveFileEx if it contains files that are in use, or by the post update
3761 // process after the update finishes. On Windows when performing a normal
3762 // update (e.g. the update is not a staged update and is not a replace
3763 // request) gWorkingDirPath is the same as gInstallDirPath and
3764 // gWorkingDirPath is used because it is the destination directory.
3765 NS_tsnprintf(gDeleteDirPath
,
3766 sizeof(gDeleteDirPath
) / sizeof(gDeleteDirPath
[0]),
3767 NS_T("%s/%s"), gWorkingDirPath
, DELETE_DIR
);
3769 if (NS_taccess(gDeleteDirPath
, F_OK
))
3771 NS_tmkdir(gDeleteDirPath
, 0755);
3776 // Run update process on a background thread. ShowProgressUI may return
3777 // before QuitProgressUI has been called, so wait for UpdateThreadFunc to
3778 // terminate. Avoid showing the progress UI when staging an update, or if this
3779 // is an elevated process on OSX.
3780 std::thread
t(UpdateThreadFunc
, nullptr);
3781 if (!sStagedUpdate
&& !sReplaceRequest
3792 if (argc
> callbackIndex
&& !sReplaceRequest
)
3794 if (callbackFile
!= INVALID_HANDLE_VALUE
)
3796 CloseHandle(callbackFile
);
3798 // Remove the copy of the callback executable.
3799 NS_tremove(gCallbackBackupPath
);
3802 if (!sStagedUpdate
&& !sReplaceRequest
&& _wrmdir(gDeleteDirPath
))
3804 LOG(("NS_main: unable to remove directory: " LOG_S
", err: %d",
3805 DELETE_DIR
, errno
));
3806 // The directory probably couldn't be removed due to it containing files
3807 // that are in use and will be removed on OS reboot. The call to remove the
3808 // directory on OS reboot is done after the calls to remove the files so the
3809 // files are removed first on OS reboot since the directory must be empty
3810 // for the directory removal to be successful. The MoveFileEx call to remove
3811 // the directory on OS reboot will fail if the process doesn't have write
3812 // access to the HKEY_LOCAL_MACHINE registry key but this is ok since the
3813 // installer / uninstaller will delete the directory along with its contents
3814 // after an update is applied, on reinstall, and on uninstall.
3815 if (MoveFileEx(gDeleteDirPath
, nullptr, MOVEFILE_DELAY_UNTIL_REBOOT
))
3817 LOG(("NS_main: directory will be removed on OS reboot: " LOG_S
,
3822 LOG(("NS_main: failed to schedule OS reboot removal of " \
3823 "directory: " LOG_S
, DELETE_DIR
));
3830 // When the update is successful remove the precomplete file in the root of
3831 // the application bundle and move the distribution directory from
3832 // Contents/MacOS to Contents/Resources and if both exist delete the
3833 // directory under Contents/MacOS (see Bug 1068439).
3834 if (gSucceeded
&& !sStagedUpdate
)
3836 NS_tchar oldPrecomplete
[MAXPATHLEN
];
3837 NS_tsnprintf(oldPrecomplete
, sizeof(oldPrecomplete
)/sizeof(oldPrecomplete
[0]),
3838 NS_T("%s/precomplete"), gInstallDirPath
);
3839 NS_tremove(oldPrecomplete
);
3841 NS_tchar oldDistDir
[MAXPATHLEN
];
3842 NS_tsnprintf(oldDistDir
, sizeof(oldDistDir
)/sizeof(oldDistDir
[0]),
3843 NS_T("%s/Contents/MacOS/distribution"), gInstallDirPath
);
3844 int rv
= NS_taccess(oldDistDir
, F_OK
);
3847 NS_tchar newDistDir
[MAXPATHLEN
];
3848 NS_tsnprintf(newDistDir
, sizeof(newDistDir
)/sizeof(newDistDir
[0]),
3849 NS_T("%s/Contents/Resources/distribution"), gInstallDirPath
);
3850 rv
= NS_taccess(newDistDir
, F_OK
);
3853 LOG(("New distribution directory already exists... removing old " \
3854 "distribution directory: " LOG_S
, oldDistDir
));
3855 rv
= ensure_remove_recursive(oldDistDir
);
3858 LOG(("Removing old distribution directory failed - err: %d", rv
));
3863 LOG(("Moving old distribution directory to new location. src: " LOG_S \
3864 ", dst:" LOG_S
, oldDistDir
, newDistDir
));
3865 rv
= rename_file(oldDistDir
, newDistDir
, true);
3868 LOG(("Moving old distribution directory to new location failed - " \
3877 SetGroupOwnershipAndPermissions(gInstallDirPath
);
3878 freeArguments(argc
, argv
);
3879 CleanupElevatedMacUpdate(false);
3881 else if (IsOwnedByGroupAdmin(gInstallDirPath
))
3883 // If the group ownership of the Firefox .app bundle was set to the "admin"
3884 // group during a previous elevated update, we need to ensure that all files
3885 // in the bundle have group ownership of "admin" as well as write permission
3886 // for the group to not break updates in the future.
3887 SetGroupOwnershipAndPermissions(gInstallDirPath
);
3893 int retVal
= LaunchCallbackAndPostProcessApps(argc
, argv
, callbackIndex
3895 , elevatedLockFilePath
3896 , updateLockFileHandle
3897 #elif defined(MACOSX)
3902 return retVal
? retVal
: (gSucceeded
? 0 : 1);
3908 ActionList() : mFirst(nullptr), mLast(nullptr), mCount(0) { }
3911 void Append(Action
* action
);
3914 void Finish(int status
);
3922 ActionList::~ActionList()
3934 ActionList::Append(Action
*action
)
3937 mLast
->mNext
= action
;
3946 ActionList::Prepare()
3948 // If the action list is empty then we should fail in order to signal that
3949 // something has gone wrong. Otherwise we report success when nothing is
3950 // actually done. See bug 327140.
3953 LOG(("empty action list"));
3954 return MAR_ERROR_EMPTY_ACTION_LIST
;
3961 int rv
= a
->Prepare();
3965 float percent
= float(++i
) / float(mCount
);
3966 UpdateProgressUI(PROGRESS_PREPARE_SIZE
* percent
);
3975 ActionList::Execute()
3977 int currentProgress
= 0, maxProgress
= 0;
3981 maxProgress
+= a
->mProgressCost
;
3988 int rv
= a
->Execute();
3991 LOG(("### execution failed"));
3995 currentProgress
+= a
->mProgressCost
;
3996 float percent
= float(currentProgress
) / float(maxProgress
);
3997 UpdateProgressUI(PROGRESS_PREPARE_SIZE
+
3998 PROGRESS_EXECUTE_SIZE
* percent
);
4007 ActionList::Finish(int status
)
4015 float percent
= float(++i
) / float(mCount
);
4016 UpdateProgressUI(PROGRESS_PREPARE_SIZE
+
4017 PROGRESS_EXECUTE_SIZE
+
4018 PROGRESS_FINISH_SIZE
* percent
);
4029 int add_dir_entries(const NS_tchar
*dirpath
, ActionList
*list
)
4032 WIN32_FIND_DATAW finddata
;
4034 NS_tchar searchspec
[MAXPATHLEN
];
4035 NS_tchar foundpath
[MAXPATHLEN
];
4037 NS_tsnprintf(searchspec
, sizeof(searchspec
)/sizeof(searchspec
[0]),
4038 NS_T("%s*"), dirpath
);
4039 std::unique_ptr
<const NS_tchar
> pszSpec(get_full_path(searchspec
));
4041 hFindFile
= FindFirstFileW(pszSpec
.get(), &finddata
);
4042 if (hFindFile
!= INVALID_HANDLE_VALUE
)
4046 // Don't process the current or parent directory.
4047 if (NS_tstrcmp(finddata
.cFileName
, NS_T(".")) == 0 ||
4048 NS_tstrcmp(finddata
.cFileName
, NS_T("..")) == 0)
4051 NS_tsnprintf(foundpath
, sizeof(foundpath
)/sizeof(foundpath
[0]),
4052 NS_T("%s%s"), dirpath
, finddata
.cFileName
);
4053 if (finddata
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
)
4055 NS_tsnprintf(foundpath
, sizeof(foundpath
)/sizeof(foundpath
[0]),
4056 NS_T("%s/"), foundpath
);
4057 // Recurse into the directory.
4058 rv
= add_dir_entries(foundpath
, list
);
4061 LOG(("add_dir_entries error: " LOG_S
", err: %d", foundpath
, rv
));
4067 // Add the file to be removed to the ActionList.
4068 NS_tchar
*quotedpath
= get_quoted_path(foundpath
);
4072 Action
*action
= new RemoveFile();
4073 rv
= action
->Parse(quotedpath
);
4076 LOG(("add_dir_entries Parse error on recurse: " LOG_S
", err: %d",
4082 list
->Append(action
);
4085 while (FindNextFileW(hFindFile
, &finddata
) != 0);
4087 FindClose(hFindFile
);
4089 // Add the directory to be removed to the ActionList.
4090 NS_tchar
*quotedpath
= get_quoted_path(dirpath
);
4094 Action
*action
= new RemoveDir();
4095 rv
= action
->Parse(quotedpath
);
4097 LOG(("add_dir_entries Parse error on close: " LOG_S
", err: %d",
4100 list
->Append(action
);
4108 #elif defined(__sun)
4109 int add_dir_entries(const NS_tchar
*dirpath
, ActionList
*list
)
4112 NS_tchar foundpath
[MAXPATHLEN
];
4116 char chars
[MAXNAMLEN
];
4119 std::unique_ptr
<NS_tchar
> searchpath(get_full_path(dirpath
));
4121 DIR* dir
= opendir(searchpath
.get());
4124 LOG(("add_dir_entries error on opendir: " LOG_S
", err: %d", searchpath
.get(),
4126 return UNEXPECTED_FILE_OPERATION_ERROR
;
4129 while (readdir_r(dir
, (dirent
*)&ent_buf
, &ent
) == 0 && ent
)
4131 if ((strcmp(ent
->d_name
, ".") == 0) ||
4132 (strcmp(ent
->d_name
, "..") == 0))
4135 NS_tsnprintf(foundpath
, sizeof(foundpath
)/sizeof(foundpath
[0]),
4136 NS_T("%s%s"), searchpath
.get(), ent
->d_name
);
4137 struct stat64 st_buf
;
4138 int test
= stat64(foundpath
, &st_buf
);
4142 return UNEXPECTED_FILE_OPERATION_ERROR
;
4144 if (S_ISDIR(st_buf
.st_mode
))
4146 NS_tsnprintf(foundpath
, sizeof(foundpath
)/sizeof(foundpath
[0]),
4147 NS_T("%s/"), foundpath
);
4148 // Recurse into the directory.
4149 rv
= add_dir_entries(foundpath
, list
);
4152 LOG(("add_dir_entries error: " LOG_S
", err: %d", foundpath
, rv
));
4159 // Add the file to be removed to the ActionList.
4160 NS_tchar
*quotedpath
= get_quoted_path(get_relative_path(foundpath
));
4167 Action
*action
= new RemoveFile();
4168 rv
= action
->Parse(quotedpath
);
4171 LOG(("add_dir_entries Parse error on recurse: " LOG_S
", err: %d",
4177 list
->Append(action
);
4182 // Add the directory to be removed to the ActionList.
4183 NS_tchar
*quotedpath
= get_quoted_path(get_relative_path(dirpath
));
4187 Action
*action
= new RemoveDir();
4188 rv
= action
->Parse(quotedpath
);
4191 LOG(("add_dir_entries Parse error on close: " LOG_S
", err: %d",
4196 list
->Append(action
);
4204 int add_dir_entries(const NS_tchar
*dirpath
, ActionList
*list
)
4208 FTSENT
*ftsdirEntry
;
4209 std::unique_ptr
<NS_tchar
> searchpath(get_full_path(dirpath
));
4211 // Remove the trailing slash so the paths don't contain double slashes. The
4212 // existence of the slash has already been checked in DoUpdate.
4213 searchpath
.get()[NS_tstrlen(searchpath
.get()) - 1] = NS_T('\0');
4214 char* const pathargv
[] = {searchpath
.get(), nullptr};
4216 // FTS_NOCHDIR is used so relative paths from the destination directory are
4218 if (!(ftsdir
= fts_open(pathargv
,
4219 FTS_PHYSICAL
| FTS_NOSTAT
| FTS_XDEV
| FTS_NOCHDIR
,
4221 return UNEXPECTED_FILE_OPERATION_ERROR
;
4223 while ((ftsdirEntry
= fts_read(ftsdir
)) != nullptr)
4225 NS_tchar foundpath
[MAXPATHLEN
];
4226 NS_tchar
*quotedpath
= nullptr;
4227 Action
*action
= nullptr;
4229 switch (ftsdirEntry
->fts_info
)
4231 // Filesystem objects that shouldn't be in the application's directories
4235 LOG(("add_dir_entries: found a non-standard file: " LOG_S
,
4236 ftsdirEntry
->fts_path
));
4237 /* Fall through */ // and try to remove as a file
4242 // Add the file to be removed to the ActionList.
4243 NS_tsnprintf(foundpath
, sizeof(foundpath
)/sizeof(foundpath
[0]),
4244 NS_T("%s"), ftsdirEntry
->fts_accpath
);
4245 quotedpath
= get_quoted_path(get_relative_path(foundpath
));
4248 rv
= UPDATER_QUOTED_PATH_MEM_ERROR
;
4251 action
= new RemoveFile();
4252 rv
= action
->Parse(quotedpath
);
4255 list
->Append(action
);
4261 // Add the directory to be removed to the ActionList.
4262 NS_tsnprintf(foundpath
, sizeof(foundpath
)/sizeof(foundpath
[0]),
4263 NS_T("%s/"), ftsdirEntry
->fts_accpath
);
4264 quotedpath
= get_quoted_path(get_relative_path(foundpath
));
4267 rv
= UPDATER_QUOTED_PATH_MEM_ERROR
;
4271 action
= new RemoveDir();
4272 rv
= action
->Parse(quotedpath
);
4275 list
->Append(action
);
4281 // ENOENT is an acceptable error for FTS_DNR and FTS_NS and means that
4282 // we're racing with ourselves. Though strange, the entry will be
4284 if (ENOENT
== ftsdirEntry
->fts_errno
)
4292 rv
= UNEXPECTED_FILE_OPERATION_ERROR
;
4293 LOG(("add_dir_entries: fts_read() error: " LOG_S
", err: %d",
4294 ftsdirEntry
->fts_path
, ftsdirEntry
->fts_errno
));
4298 rv
= UNEXPECTED_FILE_OPERATION_ERROR
;
4299 LOG(("add_dir_entries: fts_read() returned FT_DC: " LOG_S
,
4300 ftsdirEntry
->fts_path
));
4304 // FTS_D is ignored and FTS_DP is used instead (post-order).
4320 GetManifestContents(const NS_tchar
*manifest
)
4322 AutoFile
mfile(NS_tfopen(manifest
, NS_T("rb")));
4323 if (mfile
== nullptr)
4325 LOG(("GetManifestContents: error opening manifest file: " LOG_S
, manifest
));
4330 int rv
= fstat(fileno((FILE *)mfile
), &ms
);
4333 LOG(("GetManifestContents: error stating manifest file: " LOG_S
, manifest
));
4337 char *mbuf
= (char *) malloc(ms
.st_size
+ 1);
4341 size_t r
= ms
.st_size
;
4345 const size_t count
= std::min
<size_t>(SSIZE_MAX
, r
);
4346 size_t c
= fread(rb
, 1, count
, mfile
);
4349 LOG(("GetManifestContents: error reading manifest file: " LOG_S
, manifest
));
4357 mbuf
[ms
.st_size
] = '\0';
4363 NS_tchar
*wrb
= (NS_tchar
*) malloc((ms
.st_size
+ 1) * sizeof(NS_tchar
));
4370 if (!MultiByteToWideChar(CP_UTF8
, MB_ERR_INVALID_CHARS
, rb
, -1, wrb
,
4373 LOG(("GetManifestContents: error converting utf8 to utf16le: %d", GetLastError()));
4384 int AddPreCompleteActions(ActionList
*list
)
4387 std::unique_ptr
<NS_tchar
> manifestPath(get_full_path(
4388 NS_T("Contents/Resources/precomplete")));
4390 std::unique_ptr
<NS_tchar
> manifestPath(get_full_path(
4391 NS_T("precomplete")));
4394 NS_tchar
*rb
= GetManifestContents(manifestPath
.get());
4397 LOG(("AddPreCompleteActions: error getting contents of precomplete " \
4399 // Applications aren't required to have a precomplete manifest. The mar
4400 // generation scripts enforce the presence of a precomplete manifest.
4406 while ((line
= mstrtok(kNL
, &rb
)) != 0)
4409 if (*line
== NS_T('#'))
4412 NS_tchar
*token
= mstrtok(kWhitespace
, &line
);
4415 LOG(("AddPreCompleteActions: token not found in manifest"));
4419 Action
*action
= nullptr;
4420 if (NS_tstrcmp(token
, NS_T("remove")) == 0) // rm file
4422 action
= new RemoveFile();
4424 else if (NS_tstrcmp(token
, NS_T("remove-cc")) == 0) // no longer supported
4428 else if (NS_tstrcmp(token
, NS_T("rmdir")) == 0) // rmdir if empty
4430 action
= new RemoveDir();
4434 LOG(("AddPreCompleteActions: unknown token: " LOG_S
, token
));
4439 return BAD_ACTION_ERROR
;
4441 rv
= action
->Parse(line
);
4445 list
->Append(action
);
4451 int DoUpdate(ArchiveReader
& archiveReader
)
4453 NS_tchar manifest
[MAXPATHLEN
];
4454 NS_tsnprintf(manifest
, sizeof(manifest
)/sizeof(manifest
[0]),
4455 NS_T("%s/updating/update.manifest"), gWorkingDirPath
);
4456 ensure_parent_dir(manifest
);
4458 // extract the manifest
4459 // TODO: moggi: needs adaption for LibreOffice
4460 // Why would we need the manifest? Even if we need it why would we need 2?
4461 int rv
= archiveReader
.ExtractFile("updatev3.manifest", manifest
);
4464 rv
= archiveReader
.ExtractFile("updatev2.manifest", manifest
);
4467 LOG(("DoUpdate: error extracting manifest file"));
4472 NS_tchar
*rb
= GetManifestContents(manifest
);
4473 NS_tremove(manifest
);
4476 LOG(("DoUpdate: error opening manifest file: " LOG_S
, manifest
));
4482 bool isFirstAction
= true;
4484 while ((line
= mstrtok(kNL
, &rb
)) != 0)
4487 if (*line
== NS_T('#'))
4490 NS_tchar
*token
= mstrtok(kWhitespace
, &line
);
4493 LOG(("DoUpdate: token not found in manifest"));
4499 isFirstAction
= false;
4500 // The update manifest isn't required to have a type declaration. The mar
4501 // generation scripts enforce the presence of the type declaration.
4502 if (NS_tstrcmp(token
, NS_T("type")) == 0)
4504 const NS_tchar
*type
= mstrtok(kQuote
, &line
);
4505 LOG(("UPDATE TYPE " LOG_S
, type
));
4506 if (NS_tstrcmp(type
, NS_T("complete")) == 0)
4508 rv
= AddPreCompleteActions(&list
);
4516 Action
*action
= nullptr;
4517 if (NS_tstrcmp(token
, NS_T("remove")) == 0) // rm file
4519 action
= new RemoveFile();
4521 else if (NS_tstrcmp(token
, NS_T("rmdir")) == 0) // rmdir if empty
4523 action
= new RemoveDir();
4525 else if (NS_tstrcmp(token
, NS_T("rmrfdir")) == 0) // rmdir recursive
4527 const NS_tchar
*reldirpath
= mstrtok(kQuote
, &line
);
4531 if (reldirpath
[NS_tstrlen(reldirpath
) - 1] != NS_T('/'))
4534 rv
= add_dir_entries(reldirpath
, &list
);
4540 else if (NS_tstrcmp(token
, NS_T("add")) == 0)
4542 action
= new AddFile(archiveReader
);
4544 else if (NS_tstrcmp(token
, NS_T("patch")) == 0)
4546 action
= new PatchFile(archiveReader
);
4548 else if (NS_tstrcmp(token
, NS_T("add-if")) == 0) // Add if exists
4550 action
= new AddIfFile(archiveReader
);
4552 else if (NS_tstrcmp(token
, NS_T("add-if-not")) == 0) // Add if not exists
4554 action
= new AddIfNotFile(archiveReader
);
4556 else if (NS_tstrcmp(token
, NS_T("patch-if")) == 0) // Patch if exists
4558 action
= new PatchIfFile(archiveReader
);
4562 LOG(("DoUpdate: unknown token: " LOG_S
, token
));
4567 return BAD_ACTION_ERROR
;
4569 rv
= action
->Parse(line
);
4573 list
.Append(action
);
4576 rv
= list
.Prepare();
4580 rv
= list
.Execute();