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