Back to index

lightning-sunbird  0.9+nobinonly
nsUpdateDriver.cpp
Go to the documentation of this file.
00001 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
00002 /* vim:set ts=2 sw=2 sts=2 et cindent: */
00003 /* ***** BEGIN LICENSE BLOCK *****
00004  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
00005  *
00006  * The contents of this file are subject to the Mozilla Public License Version
00007  * 1.1 (the "License"); you may not use this file except in compliance with
00008  * the License. You may obtain a copy of the License at
00009  * http://www.mozilla.org/MPL/
00010  *
00011  * Software distributed under the License is distributed on an "AS IS" basis,
00012  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
00013  * for the specific language governing rights and limitations under the
00014  * License.
00015  *
00016  * The Original Code is mozilla.org code.
00017  *
00018  * The Initial Developer of the Original Code is Google Inc.
00019  * Portions created by the Initial Developer are Copyright (C) 2005
00020  * the Initial Developer. All Rights Reserved.
00021  *
00022  * Contributor(s):
00023  *  Darin Fisher <darin@meer.net>
00024  *  Ben Turner <mozilla@songbirdnest.com>
00025  *
00026  * Alternatively, the contents of this file may be used under the terms of
00027  * either the GNU General Public License Version 2 or later (the "GPL"), or
00028  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
00029  * in which case the provisions of the GPL or the LGPL are applicable instead
00030  * of those above. If you wish to allow use of your version of this file only
00031  * under the terms of either the GPL or the LGPL, and not to allow others to
00032  * use your version of this file under the terms of the MPL, indicate your
00033  * decision by deleting the provisions above and replace them with the notice
00034  * and other provisions required by the GPL or the LGPL. If you do not delete
00035  * the provisions above, a recipient may use your version of this file under
00036  * the terms of any one of the MPL, the GPL or the LGPL.
00037  *
00038  * ***** END LICENSE BLOCK ***** */
00039 
00040 #include <stdlib.h>
00041 #include <stdio.h>
00042 #include "nsUpdateDriver.h"
00043 #include "nsXULAppAPI.h"
00044 #include "nsAppRunner.h"
00045 #include "nsILocalFile.h"
00046 #include "nsISimpleEnumerator.h"
00047 #include "nsIDirectoryEnumerator.h"
00048 #include "nsCOMPtr.h"
00049 #include "nsCOMArray.h"
00050 #include "nsString.h"
00051 #include "nsPrintfCString.h"
00052 #include "prproces.h"
00053 #include "prlog.h"
00054 
00055 #ifdef XP_MACOSX
00056 #include "nsILocalFileMac.h"
00057 #include "nsCommandLineServiceMac.h"
00058 #endif
00059 
00060 #if defined(XP_WIN)
00061 # include <direct.h>
00062 # include <process.h>
00063 # include <windows.h>
00064 # define getcwd(path, size) _getcwd(path, size)
00065 # define getpid() GetCurrentProcessId()
00066 #elif defined(XP_OS2)
00067 # include <unistd.h>
00068 # define INCL_DOSFILEMGR
00069 # include <os2.h>
00070 #elif defined(XP_UNIX)
00071 # include <unistd.h>
00072 #endif
00073 
00074 //
00075 // We use execv to spawn the updater process on all UNIX systems except Mac OSX
00076 // since it is known to cause problems on the Mac.  Windows has execv, but it
00077 // is a faked implementation that doesn't really replace the current process.
00078 // Instead it spawns a new process, so we gain nothing from using execv on
00079 // Windows.
00080 //
00081 // On platforms where we are not calling execv, we may need to make the
00082 // udpaterfail executable wait for the calling process to exit.  Otherwise, the
00083 // updater may have trouble modifying our executable image (because it might
00084 // still be in use).  This is accomplished by passing our PID to the updater so
00085 // that it can wait for us to exit.  This is not perfect as there is a race
00086 // condition that could bite us.  It's possible that the calling process could
00087 // exit before the updater waits on the specified PID, and in the meantime a
00088 // new process with the same PID could be created.  This situation is unlikely,
00089 // however, given the way most operating systems recycle PIDs.  We'll take our
00090 // chances ;-)
00091 //
00092 // A similar #define lives in updater.cpp and should be kept in sync with this.
00093 //
00094 #if defined(XP_UNIX) && !defined(XP_MACOSX)
00095 #define USE_EXECV
00096 #endif
00097 
00098 #ifdef PR_LOGGING
00099 static PRLogModuleInfo *sUpdateLog = PR_NewLogModule("updatedriver");
00100 #endif
00101 #define LOG(args) PR_LOG(sUpdateLog, PR_LOG_DEBUG, args)
00102 
00103 #ifdef XP_WIN
00104 static const char kUpdaterBin[] = "updater.exe";
00105 #else
00106 static const char kUpdaterBin[] = "updater";
00107 #endif
00108 static const char kUpdaterINI[] = "updater.ini";
00109 #ifdef XP_MACOSX
00110 static const char kUpdaterApp[] = "updater.app";
00111 #endif
00112 
00113 static nsresult
00114 GetCurrentWorkingDir(char *buf, size_t size)
00115 {
00116   // Cannot use NS_GetSpecialDirectory because XPCOM is not yet initialized.
00117   // This code is duplicated from xpcom/io/SpecialSystemDirectory.cpp:
00118 
00119 #if defined(XP_OS2)
00120   if (DosQueryPathInfo( ".", FIL_QUERYFULLNAME, buf, size))
00121     return NS_ERROR_FAILURE;
00122 #else
00123   if(!getcwd(buf, size))
00124     return NS_ERROR_FAILURE;
00125 #endif
00126   return NS_OK;
00127 }
00128 
00129 #if defined(MOZ_XULRUNNER) && defined(XP_MACOSX)
00130 
00131 // This is a copy of OS X's XRE_GetBinaryPath from nsAppRunner.cpp with the
00132 // gBinaryPath check removed so that the updater can reload the stub executable
00133 // instead of xulrunner-bin. See bug 349737.
00134 static nsresult
00135 GetXULRunnerStubPath(const char* argv0, nsILocalFile* *aResult)
00136 {
00137   nsresult rv;
00138   nsCOMPtr<nsILocalFile> lf;
00139 
00140   NS_NewNativeLocalFile(EmptyCString(), PR_TRUE, getter_AddRefs(lf));
00141   nsCOMPtr<nsILocalFileMac> lfm (do_QueryInterface(lf));
00142   if (!lfm)
00143     return NS_ERROR_FAILURE;
00144 
00145   // Works even if we're not bundled.
00146   CFBundleRef appBundle = CFBundleGetMainBundle();
00147   if (!appBundle)
00148     return NS_ERROR_FAILURE;
00149 
00150   CFURLRef bundleURL = CFBundleCopyExecutableURL(appBundle);
00151   if (!bundleURL)
00152     return NS_ERROR_FAILURE;
00153 
00154   FSRef fileRef;
00155   if (!CFURLGetFSRef(bundleURL, &fileRef)) {
00156     CFRelease(bundleURL);
00157     return NS_ERROR_FAILURE;
00158   }
00159 
00160   rv = lfm->InitWithFSRef(&fileRef);
00161   CFRelease(bundleURL);
00162 
00163   if (NS_FAILED(rv))
00164     return rv;
00165 
00166   NS_ADDREF(*aResult = lf);
00167   return NS_OK;
00168 }
00169 #endif /* MOZ_XULRUNNER && XP_MACOSX */
00170 
00171 PR_STATIC_CALLBACK(int)
00172 ScanDirComparator(nsIFile *a, nsIFile *b, void *unused)
00173 {
00174   // lexically compare the leaf names of these two files
00175   nsCAutoString a_name, b_name;
00176   a->GetNativeLeafName(a_name);
00177   b->GetNativeLeafName(b_name);
00178   return Compare(a_name, b_name);
00179 }
00180 
00181 static nsresult
00182 ScanDir(nsIFile *dir, nsCOMArray<nsIFile> *result)
00183 {
00184   nsresult rv;
00185 
00186   nsCOMPtr<nsISimpleEnumerator> simpEnum;
00187   rv = dir->GetDirectoryEntries(getter_AddRefs(simpEnum));
00188   if (NS_FAILED(rv))
00189     return rv;
00190 
00191   nsCOMPtr<nsIDirectoryEnumerator> dirEnum = do_QueryInterface(simpEnum, &rv);
00192   if (NS_FAILED(rv))
00193     return rv;
00194 
00195   nsCOMPtr<nsIFile> file;
00196   for (;;) {
00197     rv = dirEnum->GetNextFile(getter_AddRefs(file));
00198     if (NS_FAILED(rv))
00199       return rv;
00200 
00201     // enumeration complete when null file is returned
00202     if (!file)
00203       break;
00204 
00205     if (!result->AppendObject(file))
00206       return NS_ERROR_OUT_OF_MEMORY;
00207   }
00208 
00209   result->Sort(ScanDirComparator, nsnull);
00210   return NS_OK;
00211 }
00212 
00213 static PRBool
00214 GetFile(nsIFile *dir, const nsCSubstring &name, nsCOMPtr<nsILocalFile> &result)
00215 {
00216   nsresult rv;
00217   
00218   nsCOMPtr<nsIFile> statusFile;
00219   rv = dir->Clone(getter_AddRefs(statusFile));
00220   if (NS_FAILED(rv))
00221     return PR_FALSE;
00222 
00223   rv = statusFile->AppendNative(name);
00224   if (NS_FAILED(rv))
00225     return PR_FALSE;
00226 
00227   result = do_QueryInterface(statusFile, &rv);
00228   return NS_SUCCEEDED(rv);
00229 }
00230 
00231 static PRBool
00232 GetStatusFile(nsIFile *dir, nsCOMPtr<nsILocalFile> &result)
00233 {
00234   return GetFile(dir, NS_LITERAL_CSTRING("update.status"), result);
00235 }
00236 
00237 static PRBool
00238 IsPending(nsILocalFile *statusFile)
00239 {
00240   nsresult rv;
00241 
00242   FILE *fp;
00243   rv = statusFile->OpenANSIFileDesc("r", &fp);
00244   if (NS_FAILED(rv))
00245     return PR_FALSE;
00246 
00247   char buf[32];
00248   char *result = fgets(buf, sizeof(buf), fp);
00249   fclose(fp);
00250   if (!result)
00251     return PR_FALSE;
00252   
00253   const char kPending[] = "pending";
00254   return (strncmp(buf, kPending, sizeof(kPending) - 1) == 0);
00255 }
00256 
00257 static PRBool
00258 SetStatus(nsILocalFile *statusFile, const char *status)
00259 {
00260   FILE *fp;
00261   nsresult rv = statusFile->OpenANSIFileDesc("w", &fp);
00262   if (NS_FAILED(rv))
00263     return PR_FALSE;
00264 
00265   fprintf(fp, "%s\n", status);
00266   fclose(fp);
00267   return PR_TRUE;
00268 }
00269 
00270 static PRBool
00271 CopyUpdaterIntoUpdateDir(nsIFile *appDir, nsIFile *updateDir,
00272                          nsCOMPtr<nsIFile> &updater)
00273 {
00274   // We have to move the updater binary and its resource file.
00275   const char *filesToMove[] = {
00276 #if defined(XP_MACOSX)
00277     kUpdaterApp,
00278 #else
00279     kUpdaterINI,
00280     kUpdaterBin,
00281 #endif
00282     nsnull
00283   };
00284 
00285   nsresult rv;
00286 
00287   for (const char **leafName = filesToMove; *leafName; ++leafName) {
00288     nsDependentCString leaf(*leafName);
00289     nsCOMPtr<nsIFile> file;
00290 
00291     // Make sure there is not an existing file in the target location.
00292     rv = updateDir->Clone(getter_AddRefs(file));
00293     if (NS_FAILED(rv))
00294       return PR_FALSE;
00295     rv = file->AppendNative(leaf);
00296     if (NS_FAILED(rv))
00297       return PR_FALSE;
00298     file->Remove(PR_FALSE);
00299 
00300     // Now, copy into the target location.
00301     rv = appDir->Clone(getter_AddRefs(file));
00302     if (NS_FAILED(rv))
00303       return PR_FALSE;
00304     rv = file->AppendNative(leaf);
00305     if (NS_FAILED(rv))
00306       return PR_FALSE;
00307     rv = file->CopyToNative(updateDir, EmptyCString());
00308     if (*leafName != kUpdaterINI && NS_FAILED(rv))
00309       return PR_FALSE;
00310   }
00311   
00312   // Finally, return the location of the updater binary.
00313   rv = updateDir->Clone(getter_AddRefs(updater));
00314   if (NS_FAILED(rv))
00315     return PR_FALSE;
00316 #if defined(XP_MACOSX)
00317   rv  = updater->AppendNative(NS_LITERAL_CSTRING(kUpdaterApp));
00318   rv |= updater->AppendNative(NS_LITERAL_CSTRING("Contents"));
00319   rv |= updater->AppendNative(NS_LITERAL_CSTRING("MacOS"));
00320   if (NS_FAILED(rv))
00321     return PR_FALSE;
00322 #endif
00323   rv = updater->AppendNative(NS_LITERAL_CSTRING(kUpdaterBin));
00324   return NS_SUCCEEDED(rv); 
00325 }
00326 
00327 static void
00328 ApplyUpdate(nsIFile *greDir, nsIFile *updateDir, nsILocalFile *statusFile,
00329             nsIFile *appDir, int appArgc, char **appArgv)
00330 {
00331   // Steps:
00332   //  - mark update as 'applying'
00333   //  - copy updater into update dir
00334   //  - run updater w/ appDir as the current working dir
00335 
00336   nsCOMPtr<nsIFile> updater;
00337   if (!CopyUpdaterIntoUpdateDir(greDir, updateDir, updater)) {
00338     LOG(("failed copying updater\n"));
00339     return;
00340   }
00341 
00342   // We need to use the value returned from XRE_GetBinaryPath when attempting
00343   // to restart the running application.
00344   nsCOMPtr<nsILocalFile> appFile;
00345 
00346 #if defined(MOZ_XULRUNNER) && defined(XP_MACOSX)
00347   // On OS X we need to pass the location of the xulrunner-stub executable
00348   // rather than xulrunner-bin. See bug 349737.
00349   GetXULRunnerStubPath(appArgv[0], getter_AddRefs(appFile));
00350 #else
00351   XRE_GetBinaryPath(appArgv[0], getter_AddRefs(appFile));
00352 #endif
00353 
00354   if (!appFile)
00355     return;
00356   nsCAutoString appFilePath;
00357   nsresult rv = appFile->GetNativePath(appFilePath);
00358   if (NS_FAILED(rv))
00359     return;
00360   
00361   nsCAutoString updaterPath;
00362   rv = updater->GetNativePath(updaterPath);
00363   if (NS_FAILED(rv))
00364     return;
00365 
00366   // Get the directory to which the update will be applied. On Mac OSX we need
00367   // to apply the update to the Foo.app directory which is the parent of the
00368   // parent of the appDir. On other platforms we will just apply to the appDir.
00369   nsCAutoString applyToDir;
00370 #if defined(XP_MACOSX)
00371   {
00372     nsCOMPtr<nsIFile> parentDir1, parentDir2;
00373     rv = appDir->GetParent(getter_AddRefs(parentDir1));
00374     if (NS_FAILED(rv))
00375       return;
00376     rv = parentDir1->GetParent(getter_AddRefs(parentDir2));
00377     if (NS_FAILED(rv))
00378       return;
00379     rv = parentDir2->GetNativePath(applyToDir);
00380   }
00381 #else
00382   rv = appDir->GetNativePath(applyToDir);
00383 #endif
00384   if (NS_FAILED(rv))
00385     return;
00386 
00387   nsCAutoString updateDirPath;
00388   rv = updateDir->GetNativePath(updateDirPath);
00389   if (NS_FAILED(rv))
00390     return;
00391 
00392   // Get the current working directory.
00393   char workingDirPath[MAXPATHLEN];
00394   rv = GetCurrentWorkingDir(workingDirPath, sizeof(workingDirPath));
00395   if (NS_FAILED(rv))
00396     return;
00397 
00398   if (!SetStatus(statusFile, "applying")) {
00399     LOG(("failed setting status to 'applying'\n"));
00400     return;
00401   }
00402 
00403   // Construct the PID argument for this process.  If we are using execv, then
00404   // we pass "0" which is then ignored by the updater.
00405 #if defined(USE_EXECV)
00406   NS_NAMED_LITERAL_CSTRING(pid, "0");
00407 #else
00408   nsCAutoString pid;
00409   pid.AppendInt((PRInt32) getpid());
00410 #endif
00411 
00412   int argc = appArgc + 4;
00413   char **argv = new char*[argc + 1];
00414   if (!argv)
00415     return;
00416   argv[0] = (char*) updaterPath.get();
00417   argv[1] = (char*) updateDirPath.get();
00418   argv[2] = (char*) pid.get();
00419   if (appArgc) {
00420     argv[3] = workingDirPath;
00421     argv[4] = (char*) appFilePath.get();
00422     for (int i = 1; i < appArgc; ++i)
00423       argv[4 + i] = appArgv[i];
00424     argv[4 + appArgc] = nsnull;
00425   } else {
00426     argv[3] = nsnull;
00427     argc = 3;
00428   }
00429 
00430   LOG(("spawning updater process [%s]\n", updaterPath.get()));
00431 
00432 #if defined(USE_EXECV)
00433   chdir(applyToDir.get());
00434   execv(updaterPath.get(), argv);
00435 #elif defined(XP_WIN)
00436   _chdir(applyToDir.get());
00437 
00438   if (!WinLaunchChild(updaterPath.get(), appArgc + 4, argv, 1))
00439     return;
00440   _exit(0);
00441 #else
00442   PRStatus status;
00443   PRProcessAttr *attr;
00444   
00445   attr = PR_NewProcessAttr();
00446   if (!attr)
00447     goto end;
00448 
00449   status = PR_ProcessAttrSetCurrentDirectory(attr, applyToDir.get());
00450   if (status != PR_SUCCESS)
00451     goto end;
00452 
00453 #ifdef XP_MACOSX
00454   SetupMacCommandLine(argc, argv);
00455 #endif
00456 
00457   PR_CreateProcessDetached(updaterPath.get(), argv, nsnull, attr);
00458   exit(0);
00459 
00460 end:
00461   PR_DestroyProcessAttr(attr); 
00462   delete[] argv;
00463 #endif
00464 }
00465 
00466 nsresult
00467 ProcessUpdates(nsIFile *greDir, nsIFile *appDir, nsIFile *updRootDir,
00468                int argc, char **argv)
00469 {
00470   nsresult rv;
00471 
00472   nsCOMPtr<nsIFile> updatesDir;
00473   rv = updRootDir->Clone(getter_AddRefs(updatesDir));
00474   if (NS_FAILED(rv))
00475     return rv;
00476   rv = updatesDir->AppendNative(NS_LITERAL_CSTRING("updates"));
00477   if (NS_FAILED(rv))
00478     return rv;
00479 
00480   PRBool exists;
00481   rv = updatesDir->Exists(&exists);
00482   if (NS_FAILED(rv) || !exists)
00483     return rv;
00484 
00485   nsCOMArray<nsIFile> dirEntries;
00486   rv = ScanDir(updatesDir, &dirEntries);
00487   if (NS_FAILED(rv))
00488     return rv;
00489   if (dirEntries.Count() == 0)
00490     return NS_OK;
00491 
00492   // look for the first update subdirectory with a status of pending
00493   for (int i = 0; i < dirEntries.Count(); ++i) {
00494     nsCOMPtr<nsILocalFile> statusFile;
00495     if (GetStatusFile(dirEntries[i], statusFile) && IsPending(statusFile)) {
00496       ApplyUpdate(greDir, dirEntries[i], statusFile, appDir, argc, argv);
00497       break;
00498     }
00499   }
00500 
00501   return NS_OK;
00502 }