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