1 : /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 : /* vim:set ts=2 sw=2 sts=2 et cindent: */
3 : /* ***** BEGIN LICENSE BLOCK *****
4 : * Version: MPL 1.1/GPL 2.0/LGPL 2.1
5 : *
6 : * The contents of this file are subject to the Mozilla Public License Version
7 : * 1.1 (the "License"); you may not use this file except in compliance with
8 : * the License. You may obtain a copy of the License at
9 : * http://www.mozilla.org/MPL/
10 : *
11 : * Software distributed under the License is distributed on an "AS IS" basis,
12 : * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
13 : * for the specific language governing rights and limitations under the
14 : * License.
15 : *
16 : * The Original Code is mozilla.org code.
17 : *
18 : * The Initial Developer of the Original Code is Google Inc.
19 : * Portions created by the Initial Developer are Copyright (C) 2005
20 : * the Initial Developer. All Rights Reserved.
21 : *
22 : * Contributor(s):
23 : * Darin Fisher <darin@meer.net>
24 : * Ben Turner <mozilla@songbirdnest.com>
25 : * Robert Strong <robert.bugzilla@gmail.com>
26 : * Josh Aas <josh@mozilla.com>
27 : * Brian R. Bondy <netzen@gmail.com>
28 : *
29 : * Alternatively, the contents of this file may be used under the terms of
30 : * either the GNU General Public License Version 2 or later (the "GPL"), or
31 : * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
32 : * in which case the provisions of the GPL or the LGPL are applicable instead
33 : * of those above. If you wish to allow use of your version of this file only
34 : * under the terms of either the GPL or the LGPL, and not to allow others to
35 : * use your version of this file under the terms of the MPL, indicate your
36 : * decision by deleting the provisions above and replace them with the notice
37 : * and other provisions required by the GPL or the LGPL. If you do not delete
38 : * the provisions above, a recipient may use your version of this file under
39 : * the terms of any one of the MPL, the GPL or the LGPL.
40 : *
41 : * ***** END LICENSE BLOCK ***** */
42 :
43 : #include <stdlib.h>
44 : #include <stdio.h>
45 : #include "nsUpdateDriver.h"
46 : #include "nsXULAppAPI.h"
47 : #include "nsAppRunner.h"
48 : #include "nsILocalFile.h"
49 : #include "nsCOMPtr.h"
50 : #include "nsString.h"
51 : #include "prproces.h"
52 : #include "prlog.h"
53 : #include "prenv.h"
54 : #include "nsVersionComparator.h"
55 :
56 : #ifdef XP_MACOSX
57 : #include "nsILocalFileMac.h"
58 : #include "nsCommandLineServiceMac.h"
59 : #include "MacLaunchHelper.h"
60 : #endif
61 :
62 : #if defined(XP_WIN)
63 : # include <direct.h>
64 : # include <process.h>
65 : # include <windows.h>
66 : # define getcwd(path, size) _getcwd(path, size)
67 : # define getpid() GetCurrentProcessId()
68 : #elif defined(XP_OS2)
69 : # include <unistd.h>
70 : # define INCL_DOSFILEMGR
71 : # include <os2.h>
72 : #elif defined(XP_UNIX)
73 : # include <unistd.h>
74 : #endif
75 :
76 : //
77 : // We use execv to spawn the updater process on all UNIX systems except Mac OSX
78 : // since it is known to cause problems on the Mac. Windows has execv, but it
79 : // is a faked implementation that doesn't really replace the current process.
80 : // Instead it spawns a new process, so we gain nothing from using execv on
81 : // Windows.
82 : //
83 : // On platforms where we are not calling execv, we may need to make the
84 : // updater executable wait for the calling process to exit. Otherwise, the
85 : // updater may have trouble modifying our executable image (because it might
86 : // still be in use). This is accomplished by passing our PID to the updater so
87 : // that it can wait for us to exit. This is not perfect as there is a race
88 : // condition that could bite us. It's possible that the calling process could
89 : // exit before the updater waits on the specified PID, and in the meantime a
90 : // new process with the same PID could be created. This situation is unlikely,
91 : // however, given the way most operating systems recycle PIDs. We'll take our
92 : // chances ;-)
93 : //
94 : // A similar #define lives in updater.cpp and should be kept in sync with this.
95 : //
96 : #if defined(XP_UNIX) && !defined(XP_MACOSX)
97 : #define USE_EXECV
98 : #endif
99 :
100 : #ifdef PR_LOGGING
101 1464 : static PRLogModuleInfo *sUpdateLog = PR_NewLogModule("updatedriver");
102 : #endif
103 : #define LOG(args) PR_LOG(sUpdateLog, PR_LOG_DEBUG, args)
104 :
105 : #ifdef XP_WIN
106 : static const char kUpdaterBin[] = "updater.exe";
107 : #else
108 : static const char kUpdaterBin[] = "updater";
109 : #endif
110 : static const char kUpdaterINI[] = "updater.ini";
111 : #ifdef XP_MACOSX
112 : static const char kUpdaterApp[] = "updater.app";
113 : #endif
114 : #if defined(XP_UNIX) && !defined(XP_MACOSX)
115 : static const char kUpdaterPNG[] = "updater.png";
116 : #endif
117 :
118 : static nsresult
119 0 : GetCurrentWorkingDir(char *buf, size_t size)
120 : {
121 : // Cannot use NS_GetSpecialDirectory because XPCOM is not yet initialized.
122 : // This code is duplicated from xpcom/io/SpecialSystemDirectory.cpp:
123 :
124 : #if defined(XP_OS2)
125 : if (DosQueryPathInfo( ".", FIL_QUERYFULLNAME, buf, size))
126 : return NS_ERROR_FAILURE;
127 : #elif defined(XP_WIN)
128 : wchar_t wpath[MAX_PATH];
129 : if (!_wgetcwd(wpath, size))
130 : return NS_ERROR_FAILURE;
131 : NS_ConvertUTF16toUTF8 path(wpath);
132 : strncpy(buf, path.get(), size);
133 : #else
134 0 : if(!getcwd(buf, size))
135 0 : return NS_ERROR_FAILURE;
136 : #endif
137 0 : return NS_OK;
138 : }
139 :
140 : #if defined(XP_MACOSX)
141 : // This is a copy of OS X's XRE_GetBinaryPath from nsAppRunner.cpp with the
142 : // gBinaryPath check removed so that the updater can reload the stub executable
143 : // instead of xulrunner-bin. See bug 349737.
144 : static nsresult
145 : GetXULRunnerStubPath(const char* argv0, nsILocalFile* *aResult)
146 : {
147 : // Works even if we're not bundled.
148 : CFBundleRef appBundle = ::CFBundleGetMainBundle();
149 : if (!appBundle)
150 : return NS_ERROR_FAILURE;
151 :
152 : CFURLRef bundleURL = ::CFBundleCopyExecutableURL(appBundle);
153 : if (!bundleURL)
154 : return NS_ERROR_FAILURE;
155 :
156 : nsCOMPtr<nsILocalFileMac> lfm;
157 : nsresult rv = NS_NewLocalFileWithCFURL(bundleURL, true, getter_AddRefs(lfm));
158 :
159 : ::CFRelease(bundleURL);
160 :
161 : if (NS_FAILED(rv))
162 : return rv;
163 :
164 : NS_ADDREF(*aResult = static_cast<nsILocalFile*>(lfm.get()));
165 : return NS_OK;
166 : }
167 : #endif /* XP_MACOSX */
168 :
169 : static bool
170 0 : GetFile(nsIFile *dir, const nsCSubstring &name, nsCOMPtr<nsILocalFile> &result)
171 : {
172 : nsresult rv;
173 :
174 0 : nsCOMPtr<nsIFile> file;
175 0 : rv = dir->Clone(getter_AddRefs(file));
176 0 : if (NS_FAILED(rv))
177 0 : return false;
178 :
179 0 : rv = file->AppendNative(name);
180 0 : if (NS_FAILED(rv))
181 0 : return false;
182 :
183 0 : result = do_QueryInterface(file, &rv);
184 0 : return NS_SUCCEEDED(rv);
185 : }
186 :
187 : static bool
188 0 : GetStatusFile(nsIFile *dir, nsCOMPtr<nsILocalFile> &result)
189 : {
190 0 : return GetFile(dir, NS_LITERAL_CSTRING("update.status"), result);
191 : }
192 :
193 : static bool
194 0 : IsPending(nsILocalFile *statusFile)
195 : {
196 0 : PRFileDesc *fd = nsnull;
197 0 : nsresult rv = statusFile->OpenNSPRFileDesc(PR_RDONLY, 0660, &fd);
198 0 : if (NS_FAILED(rv))
199 0 : return false;
200 :
201 : char buf[32];
202 0 : const PRInt32 n = PR_Read(fd, buf, sizeof(buf));
203 0 : PR_Close(fd);
204 :
205 0 : if (n < 0)
206 0 : return false;
207 :
208 0 : const char kPending[] = "pending";
209 0 : bool isPending = (strncmp(buf, kPending, sizeof(kPending) - 1) == 0);
210 0 : return isPending;
211 : }
212 :
213 : static bool
214 0 : GetVersionFile(nsIFile *dir, nsCOMPtr<nsILocalFile> &result)
215 : {
216 0 : return GetFile(dir, NS_LITERAL_CSTRING("update.version"), result);
217 : }
218 :
219 : // Compares the current application version with the update's application
220 : // version.
221 : static bool
222 0 : IsOlderVersion(nsILocalFile *versionFile, const char *appVersion)
223 : {
224 0 : PRFileDesc *fd = nsnull;
225 0 : nsresult rv = versionFile->OpenNSPRFileDesc(PR_RDONLY, 0660, &fd);
226 0 : if (NS_FAILED(rv))
227 0 : return true;
228 :
229 : char buf[32];
230 0 : const PRInt32 n = PR_Read(fd, buf, sizeof(buf));
231 0 : PR_Close(fd);
232 :
233 0 : if (n < 0)
234 0 : return false;
235 :
236 : // Trim off the trailing newline
237 0 : if (buf[n - 1] == '\n')
238 0 : buf[n - 1] = '\0';
239 :
240 : // If the update xml doesn't provide the application version the file will
241 : // contain the string "null" and it is assumed that the update is not older.
242 0 : const char kNull[] = "null";
243 0 : if (strncmp(buf, kNull, sizeof(kNull) - 1) == 0)
244 0 : return false;
245 :
246 0 : if (NS_CompareVersions(appVersion, buf) > 0)
247 0 : return true;
248 :
249 0 : return false;
250 : }
251 :
252 : static bool
253 0 : CopyFileIntoUpdateDir(nsIFile *parentDir, const char *leafName, nsIFile *updateDir)
254 : {
255 0 : nsDependentCString leaf(leafName);
256 0 : nsCOMPtr<nsIFile> file;
257 :
258 : // Make sure there is not an existing file in the target location.
259 0 : nsresult rv = updateDir->Clone(getter_AddRefs(file));
260 0 : if (NS_FAILED(rv))
261 0 : return false;
262 0 : rv = file->AppendNative(leaf);
263 0 : if (NS_FAILED(rv))
264 0 : return false;
265 0 : file->Remove(false);
266 :
267 : // Now, copy into the target location.
268 0 : rv = parentDir->Clone(getter_AddRefs(file));
269 0 : if (NS_FAILED(rv))
270 0 : return false;
271 0 : rv = file->AppendNative(leaf);
272 0 : if (NS_FAILED(rv))
273 0 : return false;
274 0 : rv = file->CopyToNative(updateDir, EmptyCString());
275 0 : if (NS_FAILED(rv))
276 0 : return false;
277 :
278 0 : return true;
279 : }
280 :
281 : static bool
282 0 : CopyUpdaterIntoUpdateDir(nsIFile *greDir, nsIFile *appDir, nsIFile *updateDir,
283 : nsCOMPtr<nsIFile> &updater)
284 : {
285 : // Copy the updater application from the GRE and the updater ini from the app
286 : #if defined(XP_MACOSX)
287 : if (!CopyFileIntoUpdateDir(greDir, kUpdaterApp, updateDir))
288 : return false;
289 : #else
290 0 : if (!CopyFileIntoUpdateDir(greDir, kUpdaterBin, updateDir))
291 0 : return false;
292 : #endif
293 0 : CopyFileIntoUpdateDir(appDir, kUpdaterINI, updateDir);
294 : #if defined(XP_UNIX) && !defined(XP_MACOSX)
295 0 : nsCOMPtr<nsIFile> iconDir;
296 0 : appDir->Clone(getter_AddRefs(iconDir));
297 0 : iconDir->AppendNative(NS_LITERAL_CSTRING("icons"));
298 0 : if (!CopyFileIntoUpdateDir(iconDir, kUpdaterPNG, updateDir))
299 0 : return false;
300 : #endif
301 : // Finally, return the location of the updater binary.
302 0 : nsresult rv = updateDir->Clone(getter_AddRefs(updater));
303 0 : if (NS_FAILED(rv))
304 0 : return false;
305 : #if defined(XP_MACOSX)
306 : rv = updater->AppendNative(NS_LITERAL_CSTRING(kUpdaterApp));
307 : rv |= updater->AppendNative(NS_LITERAL_CSTRING("Contents"));
308 : rv |= updater->AppendNative(NS_LITERAL_CSTRING("MacOS"));
309 : if (NS_FAILED(rv))
310 : return false;
311 : #endif
312 0 : rv = updater->AppendNative(NS_LITERAL_CSTRING(kUpdaterBin));
313 0 : return NS_SUCCEEDED(rv);
314 : }
315 :
316 : static void
317 0 : ApplyUpdate(nsIFile *greDir, nsIFile *updateDir, nsILocalFile *statusFile,
318 : nsIFile *appDir, int appArgc, char **appArgv)
319 : {
320 : nsresult rv;
321 :
322 : // Steps:
323 : // - mark update as 'applying'
324 : // - copy updater into update dir
325 : // - run updater w/ appDir as the current working dir
326 :
327 0 : nsCOMPtr<nsIFile> updater;
328 0 : if (!CopyUpdaterIntoUpdateDir(greDir, appDir, updateDir, updater)) {
329 0 : LOG(("failed copying updater\n"));
330 : return;
331 : }
332 :
333 : // We need to use the value returned from XRE_GetBinaryPath when attempting
334 : // to restart the running application.
335 0 : nsCOMPtr<nsILocalFile> appFile;
336 :
337 : #if defined(XP_MACOSX)
338 : // On OS X we need to pass the location of the xulrunner-stub executable
339 : // rather than xulrunner-bin. See bug 349737.
340 : GetXULRunnerStubPath(appArgv[0], getter_AddRefs(appFile));
341 : #else
342 0 : XRE_GetBinaryPath(appArgv[0], getter_AddRefs(appFile));
343 : #endif
344 :
345 0 : if (!appFile)
346 : return;
347 :
348 : #ifdef XP_WIN
349 : nsAutoString appFilePathW;
350 : rv = appFile->GetPath(appFilePathW);
351 : if (NS_FAILED(rv))
352 : return;
353 : NS_ConvertUTF16toUTF8 appFilePath(appFilePathW);
354 :
355 : nsAutoString updaterPathW;
356 : rv = updater->GetPath(updaterPathW);
357 : if (NS_FAILED(rv))
358 : return;
359 :
360 : NS_ConvertUTF16toUTF8 updaterPath(updaterPathW);
361 :
362 : #else
363 0 : nsCAutoString appFilePath;
364 0 : rv = appFile->GetNativePath(appFilePath);
365 0 : if (NS_FAILED(rv))
366 : return;
367 :
368 0 : nsCAutoString updaterPath;
369 0 : rv = updater->GetNativePath(updaterPath);
370 0 : if (NS_FAILED(rv))
371 : return;
372 :
373 : #endif
374 :
375 : // Get the directory to which the update will be applied. On Mac OSX we need
376 : // to apply the update to the Foo.app directory which is the parent of the
377 : // parent of the appDir. On other platforms we will just apply to the appDir.
378 : #if defined(XP_MACOSX)
379 : nsCAutoString applyToDir;
380 : {
381 : nsCOMPtr<nsIFile> parentDir1, parentDir2;
382 : rv = appDir->GetParent(getter_AddRefs(parentDir1));
383 : if (NS_FAILED(rv))
384 : return;
385 : rv = parentDir1->GetParent(getter_AddRefs(parentDir2));
386 : if (NS_FAILED(rv))
387 : return;
388 : rv = parentDir2->GetNativePath(applyToDir);
389 : }
390 : #elif defined(XP_WIN)
391 : nsAutoString applyToDirW;
392 : rv = appDir->GetPath(applyToDirW);
393 :
394 : NS_ConvertUTF16toUTF8 applyToDir(applyToDirW);
395 : #else
396 0 : nsCAutoString applyToDir;
397 0 : rv = appDir->GetNativePath(applyToDir);
398 : #endif
399 0 : if (NS_FAILED(rv))
400 : return;
401 :
402 : #if defined(XP_WIN)
403 : nsAutoString updateDirPathW;
404 : rv = updateDir->GetPath(updateDirPathW);
405 :
406 : NS_ConvertUTF16toUTF8 updateDirPath(updateDirPathW);
407 : #else
408 0 : nsCAutoString updateDirPath;
409 0 : rv = updateDir->GetNativePath(updateDirPath);
410 : #endif
411 :
412 0 : if (NS_FAILED(rv))
413 : return;
414 :
415 : // Get the current working directory.
416 : char workingDirPath[MAXPATHLEN];
417 0 : rv = GetCurrentWorkingDir(workingDirPath, sizeof(workingDirPath));
418 0 : if (NS_FAILED(rv))
419 : return;
420 :
421 : // We used to write out "Applying" to the update.status file here.
422 : // Instead we do this from within the updater application now.
423 : // This is so that we don't overwrite the status of pending-service
424 : // in the Windows case. This change was made for all platforms so
425 : // that it stays consistent across all OS.
426 :
427 : // Construct the PID argument for this process. If we are using execv, then
428 : // we pass "0" which is then ignored by the updater.
429 : #if defined(USE_EXECV)
430 0 : NS_NAMED_LITERAL_CSTRING(pid, "0");
431 : #else
432 : nsCAutoString pid;
433 : pid.AppendInt((PRInt32) getpid());
434 : #endif
435 :
436 0 : int argc = appArgc + 5;
437 0 : char **argv = new char*[argc + 1];
438 0 : if (!argv)
439 : return;
440 0 : argv[0] = (char*) updaterPath.get();
441 0 : argv[1] = (char*) updateDirPath.get();
442 0 : argv[2] = (char*) applyToDir.get();
443 0 : argv[3] = (char*) pid.get();
444 0 : if (appArgc) {
445 0 : argv[4] = workingDirPath;
446 0 : argv[5] = (char*) appFilePath.get();
447 0 : for (int i = 1; i < appArgc; ++i)
448 0 : argv[5 + i] = appArgv[i];
449 0 : argc = 5 + appArgc;
450 0 : argv[argc] = NULL;
451 : } else {
452 0 : argc = 4;
453 0 : argv[4] = NULL;
454 : }
455 :
456 0 : if (gSafeMode) {
457 0 : PR_SetEnv("MOZ_SAFE_MODE_RESTART=1");
458 : }
459 :
460 0 : LOG(("spawning updater process [%s]\n", updaterPath.get()));
461 :
462 : #if defined(USE_EXECV)
463 0 : execv(updaterPath.get(), argv);
464 : #elif defined(XP_WIN)
465 :
466 : // Launch the update using updater.exe
467 : if (!WinLaunchChild(updaterPathW.get(), argc, argv)) {
468 : return;
469 : }
470 :
471 : // We are going to process an update so we should exit now
472 : _exit(0);
473 : #elif defined(XP_MACOSX)
474 : CommandLineServiceMac::SetupMacCommandLine(argc, argv, true);
475 : // LaunchChildMac uses posix_spawnp and prefers the current
476 : // architecture when launching. It doesn't require a
477 : // null-terminated string but it doesn't matter if we pass one.
478 : LaunchChildMac(argc, argv);
479 : exit(0);
480 : #else
481 : PR_CreateProcessDetached(updaterPath.get(), argv, NULL, NULL);
482 : exit(0);
483 : #endif
484 : }
485 :
486 : nsresult
487 0 : ProcessUpdates(nsIFile *greDir, nsIFile *appDir, nsIFile *updRootDir,
488 : int argc, char **argv, const char *appVersion)
489 : {
490 : nsresult rv;
491 :
492 0 : nsCOMPtr<nsIFile> updatesDir;
493 0 : rv = updRootDir->Clone(getter_AddRefs(updatesDir));
494 0 : if (NS_FAILED(rv))
495 0 : return rv;
496 :
497 0 : rv = updatesDir->AppendNative(NS_LITERAL_CSTRING("updates"));
498 0 : if (NS_FAILED(rv))
499 0 : return rv;
500 :
501 0 : rv = updatesDir->AppendNative(NS_LITERAL_CSTRING("0"));
502 0 : if (NS_FAILED(rv))
503 0 : return rv;
504 :
505 0 : const char *processingUpdates = PR_GetEnv("MOZ_PROCESS_UPDATES");
506 0 : if (processingUpdates && *processingUpdates) {
507 : // Enable the tests to request us to use a different update root directory
508 0 : const char *updRootOverride = PR_GetEnv("MOZ_UPDATE_ROOT_OVERRIDE");
509 0 : if (updRootOverride && *updRootOverride) {
510 0 : nsCOMPtr<nsILocalFile> overrideDir;
511 0 : nsCAutoString path(updRootOverride);
512 0 : rv = NS_NewNativeLocalFile(path, false, getter_AddRefs(overrideDir));
513 0 : if (NS_FAILED(rv)) {
514 0 : return rv;
515 : }
516 0 : updatesDir = do_QueryInterface(overrideDir);
517 : }
518 : // Enable the tests to request us to use a different app directory
519 0 : const char *appDirOverride = PR_GetEnv("MOZ_UPDATE_APPDIR_OVERRIDE");
520 0 : if (appDirOverride && *appDirOverride) {
521 0 : nsCOMPtr<nsILocalFile> overrideDir;
522 0 : nsCAutoString path(appDirOverride);
523 0 : rv = NS_NewNativeLocalFile(path, false, getter_AddRefs(overrideDir));
524 0 : if (NS_FAILED(rv)) {
525 0 : return rv;
526 : }
527 0 : NS_RELEASE(appDir);
528 0 : NS_ADDREF(appDir = overrideDir);
529 : }
530 : }
531 :
532 0 : nsCOMPtr<nsILocalFile> statusFile;
533 0 : if (GetStatusFile(updatesDir, statusFile) &&
534 0 : IsPending(statusFile)) {
535 0 : nsCOMPtr<nsILocalFile> versionFile;
536 : // Remove the update if the update application version file doesn't exist
537 : // or if the update's application version is less than the current
538 : // application version.
539 0 : if (!GetVersionFile(updatesDir, versionFile) ||
540 0 : IsOlderVersion(versionFile, appVersion)) {
541 0 : updatesDir->Remove(true);
542 : } else {
543 : ApplyUpdate(greDir, updatesDir, statusFile, appDir,
544 0 : argc, argv);
545 : }
546 : }
547 :
548 0 : return NS_OK;
549 4392 : }
|