This is the mail archive of the cygwin-apps mailing list for the Cygwin project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]
Other format: [Raw text]

[PATCH 2/2] Add a self-update mechanism for setup.exe


Updating setup.exe has 3 stages:
1) Download updated setup.exe to a temporary location
2) Execute that temporary copy of setup.exe with --copy-to instructing
it to copy itself over the setup.exe to be updated
3) Execute the updated setup.exe with --remove-from instructing it
to delete the temporary copy

A named mutex is used to ensure setup exits from each stage before
the next stage can start.

Unfortunately, at the moment, we don't usefully check the setup version number
until after we have downloaded and parsed setup.ini, which is perhaps a bit
late to offer to update setup.exe

2011-02-02  Jon TURNEY  <jon.turney@dronecode.org.uk>

	* res.rc (IDS_OLD_SETUP_VERSION): Change text to offer to download
	new version of setup.
	* main.cc:
	(self_update_remove_from, self_update_copy_to, WinMain): Add
	--copy-to and --remove-from options for self-update process.
	* ini.cc (self_update_download): New function to
	create download updated setup.
	(do_ini_thread): Prompt to download updated setup if a newer
	version exists.  Return a result indicating what should happen
	next.
	* threebar.h (PropertyPage::OnFinish): Add an implementation of
	OnFinish virtual function for this class.
	* threebar.cc (OnFinish): Ditto.
	 (OnMessageApp): Setup should finish on WM_APP_SETUP_INI_DOWNLOAD_COMPLETE
	if an updated setup was downloaded.

2011-02-02  Jon TURNEY  <jon.turney@dronecode.org.uk>

	* include/getopt++/DefaultFormatter.h (DefaultFormatter): Fix option string
	formatting when it has no short option.

Signed-off-by: Jon TURNEY <jon.turney@dronecode.org.uk>
---
 ini.cc                                          |   70 +++++++++++++--
 libgetopt++/include/getopt++/DefaultFormatter.h |    6 +-
 main.cc                                         |  110 ++++++++++++++++++++++-
 res.rc                                          |    2 +-
 threebar.cc                                     |   14 +++-
 threebar.h                                      |    1 +
 6 files changed, 189 insertions(+), 14 deletions(-)

diff --git a/ini.cc b/ini.cc
index d99bc60..7f265c1 100644
--- a/ini.cc
+++ b/ini.cc
@@ -285,7 +285,60 @@ do_remote_ini (HWND owner)
   return ini_count;
 }
 
-static bool
+static int
+self_update_download(HWND owner)
+{
+  /*
+    XXX: do we need a mechanism for source of setup.exe to be overriden in .ini file
+    for the benefit of people who use a patched version? Or can they just patch this
+    as well? :-)
+  */
+#define SETUP_URL "http://cygwin.com/setup.exe";
+
+  /* Download new setup to a temporary location */
+  std::string downloadedSetup = local_dir + "\\setup.tmp.exe";
+  get_url_to_file (SETUP_URL, downloadedSetup, 0, owner);
+
+  /* Create and claim a mutex so the new version invoked with --copy-to doesn't
+     try to overwrite us before we've exited */
+  HANDLE mutex = CreateMutex(NULL, TRUE, "Global\\Cygwin.Setup");
+  if (!mutex)
+    {
+      log (LOG_PLAIN) << "CreateMutex failed :" << GetLastError () << endLog;
+    }
+
+  /* Invoke setup with same options + --copy-to current location */
+  std::string newArgs = downloadedSetup;
+
+  for (int i = 1; _argv[i]; i++)
+    {
+      newArgs += " ";
+      newArgs += _argv[i];
+    }
+  TCHAR filename[MAX_PATH+1];
+  GetModuleFileName(NULL, filename, MAX_PATH);
+  newArgs += " --copy-to=";
+  newArgs += filename;
+
+#ifdef DEBUG
+  log (LOG_BABBLE) << newArgs << endLog;
+#endif
+
+  PROCESS_INFORMATION processInfo;
+  STARTUPINFO startupInfo;
+  GetStartupInfo(&startupInfo);
+  if (!CreateProcess(downloadedSetup.c_str(), (char *)(newArgs.c_str()),
+                    NULL, NULL, FALSE, 0, NULL, NULL, &startupInfo, &processInfo) > 0)
+    {
+      log (LOG_PLAIN) << "CreateProcess failed :" << GetLastError () << endLog;
+      // XXX: message box saying execute failed
+      return IDD_CHOOSE;
+    }
+
+  return -1;
+}
+
+static int
 do_ini_thread (HINSTANCE h, HWND owner)
 {
   size_t ini_count = 0;
@@ -295,7 +348,7 @@ do_ini_thread (HINSTANCE h, HWND owner)
     ini_count = do_remote_ini (owner);
 
   if (ini_count == 0)
-    return false;
+    return 0;
 
   if (get_root_dir ().c_str())
     {
@@ -339,11 +392,14 @@ do_ini_thread (HINSTANCE h, HWND owner)
   if (ini_setup_version.size())
     {
       if (version_compare(setup_version, ini_setup_version) < 0)
-	note (owner, IDS_OLD_SETUP_VERSION, setup_version,
-	      ini_setup_version.c_str());
+        if (yesno (owner, IDS_OLD_SETUP_VERSION, setup_version,
+                   ini_setup_version.c_str()))
+          {
+            return self_update_download(owner);
+          }
     }
 
-  return true;
+  return IDD_CHOOSE;
 }
 
 static DWORD WINAPI
@@ -354,10 +410,10 @@ do_ini_thread_reflector(void* p)
 
   try
   {
-    bool succeeded = do_ini_thread((HINSTANCE)context[0], (HWND)context[1]);
+    int nextpage = do_ini_thread((HINSTANCE)context[0], (HWND)context[1]);
 
     // Tell the progress page that we're done downloading
-    Progress.PostMessageNow(WM_APP_SETUP_INI_DOWNLOAD_COMPLETE, 0, succeeded);
+    Progress.PostMessageNow(WM_APP_SETUP_INI_DOWNLOAD_COMPLETE, 0, nextpage);
   }
   TOPLEVEL_CATCH("ini");
 
diff --git a/libgetopt++/include/getopt++/DefaultFormatter.h b/libgetopt++/include/getopt++/DefaultFormatter.h
index ced6956..cbf700b 100644
--- a/libgetopt++/include/getopt++/DefaultFormatter.h
+++ b/libgetopt++/include/getopt++/DefaultFormatter.h
@@ -27,7 +27,11 @@ class DefaultFormatter {
   public:
     DefaultFormatter (std::ostream &aStream) : theStream(aStream) {}
     void operator () (Option *anOption) {
-      std::string output = std::string() + " -" + anOption->shortOption ()[0];
+      std::string output;
+      if (anOption->shortOption()[0] != '\0')
+          output +=  std::string() + " -" + anOption->shortOption ()[0];
+      else
+        output += "   ";
       output += " --" ;
       output += anOption->longOption ();
       output += std::string (40 - output.size(), ' ');
diff --git a/main.cc b/main.cc
index bddff6f..e88cf71 100644
--- a/main.cc
+++ b/main.cc
@@ -68,6 +68,7 @@ static const char *cvsid =
 
 #include "getopt++/GetOption.h"
 #include "getopt++/BoolOption.h"
+#include "getopt++/StringOption.h"
 
 #include "Exception.h"
 #include <stdexcept>
@@ -88,6 +89,8 @@ bool is_legacy;
 static BoolOption UnattendedOption (false, 'q', "quiet-mode", "Unattended setup mode");
 static BoolOption PackageManagerOption (false, 'M', "package-manager", "Semi-attended chooser-only mode");
 static BoolOption HelpOption (false, 'h', "help", "print help");
+static StringOption SelfUpdateCopyTo ("", '\0', "copy-to", "Used by self-update.  Copy self to location and invoke.", true);
+static StringOption SelfUpdateRemoveFrom ("", '\0', "remove-from", "Used by self-update.  Remove file after starting.", true);
 static BOOL WINAPI (*dyn_AttachConsole) (DWORD);
 static BOOL WINAPI (*dyn_GetLongPathName) (LPCTSTR, LPTSTR, DWORD);
 
@@ -239,6 +242,86 @@ set_legacy (const char *command)
   is_legacy = strstr (buf, "setup-legacy");
 }
 
+static void
+self_update_remove_from(const std::string &SelfUpdateRemoveFromString)
+{
+  /* Wait on mutex held by previous instance until it exits, so
+     we don't try to remove it before it's exited */
+  HANDLE mutex = CreateMutex(NULL, FALSE, "Global\\Cygwin.Setup");
+  if (!mutex)
+    {
+      log (LOG_PLAIN) << "CreateMutex failed :" << GetLastError () << endLog;
+    }
+  else
+    {
+      log (LOG_PLAIN) << "Waiting on Mutex" << endLog;
+      WaitForSingleObject(mutex, INFINITE);
+    }
+
+  log (LOG_PLAIN) << "Removing temporary copy of self '" << SelfUpdateRemoveFromString << "'" << endLog;
+  if (DeleteFile(SelfUpdateRemoveFromString.c_str()) == 0)
+    {
+      log (LOG_PLAIN) << "DeleteFile failed :" << GetLastError () << endLog;
+    }
+
+  ReleaseMutex(mutex);
+}
+
+static void
+self_update_copy_to(const std::string &SelfUpdateCopyToString, int argc)
+{
+  log (LOG_PLAIN) << "Copying self to  '" << SelfUpdateCopyToString << "' and invoking" << endLog;
+
+  /* Wait on mutex held by previous instance until it exits, so
+     we don't try to overwrite it before it's exited */
+  HANDLE mutex = CreateMutex(NULL, FALSE, "Global\\Cygwin.Setup");
+  if (!mutex)
+    {
+      log (LOG_PLAIN) << "CreateMutex failed :" << GetLastError () << endLog;
+    }
+  else
+    {
+      log (LOG_PLAIN) << "Waiting on Mutex" << endLog;
+      WaitForSingleObject(mutex, INFINITE);
+    }
+
+  /* Copy self to new location */
+  TCHAR filename[MAX_PATH+1];
+  GetModuleFileName(NULL, filename, MAX_PATH);
+  if (CopyFile(filename, SelfUpdateCopyToString.c_str(), FALSE) == 0)
+    {
+      log (LOG_PLAIN) << "CopyFile failed :" << GetLastError () << endLog;
+    }
+
+  /* Now invoke self with in new location with all the same arguments, except --remove-from this temporary
+     copy, rather than --copyto the new location */
+  std::string newArgs = SelfUpdateCopyToString;
+
+  for (int i = 1; i < argc; i++)
+    {
+      if (strncmp(_argv[i],"--copy-to",strlen("--copy-to")) != 0)
+        {
+          newArgs += " ";
+          newArgs += _argv[i];
+        }
+      else
+        {
+          newArgs += " --remove-from=";
+          newArgs += filename;
+        }
+    }
+
+#ifdef DEBUG
+  log (LOG_BABBLE) << newArgs << endLog;
+#endif
+
+  PROCESS_INFORMATION processInfo;
+  STARTUPINFO startupInfo;
+  GetStartupInfo(&startupInfo);
+  CreateProcess(SelfUpdateCopyToString.c_str(), (char *)newArgs.c_str(),
+                NULL, NULL, FALSE, 0, NULL, NULL, &startupInfo, &processInfo);
+}
+
 #ifndef __CYGWIN__
 int WINAPI
 WinMain (HINSTANCE h,
@@ -318,11 +401,32 @@ main (int argc, char **argv)
     log (LOG_PLAIN) << "Starting cygwin install, version " 
                     << setup_version << endLog;
 
-    UserSettings Settings (local_dir);
+#ifdef DEBUG
+    for (int i = 0; i < argc; i++)
+      {
+        log (LOG_BABBLE) << "argv[" << i << "] = " << _argv[i] << endLog;
+      }
+#endif
+
+    std::string SelfUpdateRemoveFromString = SelfUpdateRemoveFrom;
+    if (SelfUpdateRemoveFromString.size())
+      {
+        self_update_remove_from(SelfUpdateRemoveFromString);
+      }
+
+    std::string SelfUpdateCopyToString = SelfUpdateCopyTo;
+    if (SelfUpdateCopyToString.size())
+      {
+        self_update_copy_to(SelfUpdateCopyToString, argc);
+      }
+    else
+      {
+        UserSettings Settings (local_dir);
 
-    main_display ();
+        main_display ();
 
-    Settings.save ();	// Clean exit.. save user options.
+        Settings.save ();	// Clean exit.. save user options.
+      }
 
     if (rebootneeded)
       {
diff --git a/res.rc b/res.rc
index 1a549a2..4b7987c 100644
--- a/res.rc
+++ b/res.rc
@@ -502,7 +502,7 @@ BEGIN
     IDS_UNINSTALL_COMPLETE  "Uninstalls complete."
     IDS_WININET             "Unable to find or load the Internet Explorer 5 DLLs"
     IDS_ERR_CHDIR           "Could not change dir to %s: %s [%.8x]"
-    IDS_OLD_SETUP_VERSION   "This setup is version %s, but setup.ini claims version %s is available.\nYou might want to upgrade to get the latest features and bug fixes."
+    IDS_OLD_SETUP_VERSION   "This setup is version %s, but setup.ini claims version %s is available.\nDo you want to upgrade to get the latest features and bug fixes?"
     IDS_DOWNLOAD_INCOMPLETE "Download Incomplete.  Try again?"
     IDS_INSTALL_ERROR	    "Installation error (%s), Continue with other packages?"
     IDS_INSTALL_INCOMPLETE  "Installation incomplete.  Check %s for details"
diff --git a/threebar.cc b/threebar.cc
index b88dd38..cc4e083 100644
--- a/threebar.cc
+++ b/threebar.cc
@@ -240,9 +240,9 @@ ThreeBarProgressPage::OnMessageApp (UINT uMsg, WPARAM wParam, LPARAM lParam)
       }
     case WM_APP_SETUP_INI_DOWNLOAD_COMPLETE:
       {
-	if (lParam)
+	if (lParam > 0)
 	  GetOwner ()->SetActivePageByID (IDD_CHOOSE);
-	else
+	else if (lParam == 0) /* download failed */
 	  {
 	    if (source == IDC_SOURCE_CWD)
 	      {
@@ -276,6 +276,10 @@ ThreeBarProgressPage::OnMessageApp (UINT uMsg, WPARAM wParam, LPARAM lParam)
 		GetOwner ()->SetActivePageByID (IDD_SITE);
 	      }
 	  }
+        else /* a setup.exe update was downloaded */
+          {
+            PropSheet_PressButton(GetOwner()->GetHWND(), PSBTN_FINISH);
+          }
 	break;
       }
     default:
@@ -288,3 +292,9 @@ ThreeBarProgressPage::OnMessageApp (UINT uMsg, WPARAM wParam, LPARAM lParam)
   return true;
 
 }
+
+bool
+ThreeBarProgressPage::OnFinish ()
+{
+  return true;
+}
diff --git a/threebar.h b/threebar.h
index 225703a..328ccbf 100644
--- a/threebar.h
+++ b/threebar.h
@@ -63,6 +63,7 @@ public:
 
   virtual void OnInit ();
   virtual void OnActivate ();
+  virtual bool OnFinish ();
   virtual bool OnMessageApp (UINT uMsg, WPARAM wParam, LPARAM lParam);
   virtual long OnUnattended ()
   {
-- 
1.7.3.3


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]