OSDN Git Service

Add runtime hooks to support self-upgrade for future releases.
authorKeith Marshall <keithmarshall@users.sourceforge.net>
Fri, 27 Aug 2010 22:08:03 +0000 (22:08 +0000)
committerKeith Marshall <keithmarshall@users.sourceforge.net>
Fri, 27 Aug 2010 22:08:03 +0000 (22:08 +0000)
ChangeLog
Makefile.in
src/clistub.c
src/debug.h
src/rites.c [new file with mode: 0644]

index 2d9264c..1fa8881 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,29 @@
+2010-08-27  Keith Marshall  <keithmarshall@users.sourceforge.net>
+
+       Add runtime hooks to support self-upgrade for future releases.
+
+       * src/rites.c: New file; compile it free-standing, to provide...
+       (lastrites.exe): ...this helper application for context clean-up;
+       alternatively, use it as an include file, with pre-definition of...
+       (IMPLEMENT_INITIATION_RITES): ...this, to implement...
+       (pkgInitRites, pkgLastRites): ...these inline functions.
+
+       * src/clistub.c (main) [argc > 1]: Use them.
+       (progname): New local variable within `main'; set it once, then use it
+       instead of repeated references to `basename(*argv)' in diagnostics.
+       (MINGW_GET_DLL, MINGW_GET_GUI): Relocate to `src/rites.c'.
+
+       * src/debug.h (DEBUG_INHIBIT_RITES_OF_PASSAGE):
+       (DEBUG_FAIL_FILE_RENAME_RITE, DEBUG_FAIL_FILE_UNLINK_RITE): New
+       defines; they facilitate debugging of the src/rites.c code.
+
+       * Makefile.in (lastrites$EXEEXT): New target; build as prerequisite...
+       (all): ...of this primary target.
+       (install, install-strip): Install it.
+       (SRCDIST_FILES): Remove install-sh; it is now included in...
+       (build-aux): ...this directory; add it to...
+       (SRCDIST_SUBDIRS): ...this.
+
 2010-08-24  Keith Marshall  <keithmarshall@users.sourceforge.net>
 
        Set default sysroots relative to mingw-get installation directory.
index 4329157..358dc9f 100644 (file)
@@ -54,7 +54,7 @@ EXEEXT = @EXEEXT@
 LDFLAGS = @LDFLAGS@
 LIBS = -Wl,-Bstatic -lz -lbz2 -llzma -Wl,-Bdynamic -lwininet
 
-CORE_DLL_OBJECTS = climain.$(OBJEXT) \
+CORE_DLL_OBJECTS  =  climain.$(OBJEXT) \
    pkgbind.$(OBJEXT) pkginet.$(OBJEXT) pkgstrm.$(OBJEXT) pkgname.$(OBJEXT) \
    pkgexec.$(OBJEXT) pkgfind.$(OBJEXT) pkginfo.$(OBJEXT) pkgspec.$(OBJEXT) \
    sysroot.$(OBJEXT) pkghash.$(OBJEXT) pkgkeys.$(OBJEXT) pkgdeps.$(OBJEXT) \
@@ -63,7 +63,7 @@ CORE_DLL_OBJECTS = climain.$(OBJEXT) \
    tinyxml.$(OBJEXT) tinyxmlparser.$(OBJEXT) \
    tinystr.$(OBJEXT) tinyxmlerror.$(OBJEXT)
 
-all: pkginfo$(EXEEXT) mingw-get$(EXEEXT) mingw-get-0.dll
+all: pkginfo$(EXEEXT) mingw-get$(EXEEXT) mingw-get-0.dll lastrites$(EXEEXT)
 
 pkginfo$(EXEEXT):  driver.$(OBJEXT) pkginfo.$(OBJEXT)
        $(CC) -o $@ $(CFLAGS) $(LDFLAGS) $+
@@ -71,6 +71,9 @@ pkginfo$(EXEEXT):  driver.$(OBJEXT) pkginfo.$(OBJEXT)
 mingw-get$(EXEEXT): clistub.$(OBJEXT) version.$(OBJEXT)
        $(CXX) -o $@ $(CXXFLAGS) $(LDFLAGS) $+
 
+lastrites$(EXEEXT): rites.$(OBJEXT)
+       $(CC) -o $@ $(CFLAGS) $(LDFLAGS) $+
+
 mingw-get-0.dll: $(CORE_DLL_OBJECTS)
        $(CXX) -shared -o $@ $(CXXFLAGS) $(LDFLAGS) $+ $(LIBS)
 
@@ -117,6 +120,7 @@ installdirs:
 install: installdirs install-profile
        $(INSTALL_PROGRAM) pkginfo$(EXEEXT) ${bindir}
        $(INSTALL_PROGRAM) mingw-get$(EXEEXT) ${bindir}
+       $(INSTALL_PROGRAM) lastrites$(EXEEXT) ${libexecdir}/${PACKAGE_TARNAME}
        $(INSTALL_DATA) mingw-get-0.dll ${libexecdir}/${PACKAGE_TARNAME}
 
 install-profile:
@@ -126,15 +130,16 @@ install-profile:
 install-strip: install
        $(STRIP) ${bindir}/pkginfo$(EXEEXT)
        $(STRIP) ${bindir}/mingw-get$(EXEEXT)
+       $(STRIP) ${libexecdir}/${PACKAGE_TARNAME}/lastrites$(EXEEXT)
        $(STRIP) ${libexecdir}/${PACKAGE_TARNAME}/mingw-get-0.dll
 
 # Packaging and distribution...
 #
 LICENCE_FILES = README COPYING
-SRCDIST_FILES = $(LICENCE_FILES) ChangeLog version.c.in \
-  configure.ac configure Makefile.in install-sh
+SRCDIST_FILES = $(LICENCE_FILES) ChangeLog \
+  configure.ac configure Makefile.in version.c.in
 
-SRCDIST_SUBDIRS = src src/pkginfo tinyxml xml
+SRCDIST_SUBDIRS = build-aux src src/pkginfo tinyxml xml
 
 # The names of distributed pacakge archive files incorporate version
 # information, derived from PACKAGE_VERSION; this is decomposed, so that
index 8c28b12..ee45e56 100644 (file)
@@ -27,9 +27,6 @@
 #define _WIN32_WINNT 0x500
 #define WIN32_LEAN_AND_MEAN
 
-#define MINGW_GET_DLL  L"libexec/mingw-get/mingw-get-0.dll"
-#define MINGW_GET_GUI  L"libexec/mingw-get/gui.exe"
-
 #include <windows.h>
 #include <stdio.h>
 #include <stdlib.h>
@@ -157,9 +154,15 @@ wchar_t *AppPathNameW( const wchar_t *relpath )
 
 extern const char *version_identification;
 
+#define  IMPLEMENT_INITIATION_RITES
+#include "rites.c"
+
 int main( int argc, char **argv )
 {
-  wchar_t *approot;            /* where this application is installed */
+  /* Make a note of...
+   */
+  const char *progname = basename( *argv );    /* ...this program's name    */
+  wchar_t *approot;                            /* and where it is installed */
 
   if( argc > 1 )
   {
@@ -188,14 +191,14 @@ int main( int argc, char **argv )
           * emit the requisite informational message, and quit.
           */
          printf( version_identification );
-         return 0;
+         return EXIT_SUCCESS;
 
        default:
          /* User specified an invalid or unsupported option...
           */
          if( opt != '?' )
            fprintf( stderr, "%s: option '-%s' not yet supported\n",
-               basename( *argv ), options[offset].name
+               progname, options[offset].name
              );
          return EXIT_FAILURE;
       }
@@ -220,6 +223,7 @@ int main( int argc, char **argv )
      * then, remaining in command line mode, we jump to its main
      * command line processing routine...
      */
+    int lock;
     typedef int (*dll_entry)( int, char ** );
     HMODULE my_dll = LoadLibraryW( AppPathNameW( MINGW_GET_DLL ) );
     dll_entry climain = (dll_entry)(GetProcAddress( my_dll, "climain" ));
@@ -228,11 +232,35 @@ int main( int argc, char **argv )
       /* ...bailing out, on failure to load the DLL.
        */
       fprintf( stderr, "%s: %S: shared library load failed\n", 
-         basename( *argv ), MINGW_GET_DLL
+         progname, MINGW_GET_DLL
        );
       return EXIT_FATAL;
     }
-    return climain( argc, argv );
+
+    /* We want only one mingw-get process accessing the XML database
+     * at any time; attempt to acquire an exclusive access lock...
+     */
+    if( (lock = pkgInitRites( progname )) >= 0 )
+    {
+      /* ...and proceed, only if successful.
+       * (Note that we don't capture the exit status from "climain()";
+       *  MS-Windows degenerate process model provides us with no viable
+       *  mechanism to pass it back to our parent, when the last rites
+       *  handler exits with a successful "exec()" call).
+       */
+      (void) climain( argc, argv );
+
+      /* We must release the mingw-get DLL code, BEFORE we invoke
+       * last rites processing, (otherwise the last rites clean-up
+       * handler exhibits abnormal behaviour when it is exec'd).
+       */
+      FreeLibrary( my_dll );
+      return pkgLastRites( lock, progname );
+    }
+    /* If we get to here, then we failed to acquire a lock;
+     * we MUST abort!
+     */
+    return EXIT_FATAL;
   }
 
   else
@@ -248,7 +276,7 @@ int main( int argc, char **argv )
      * Issue a diagnostic message, before abnormal termination.
      */
     fprintf( stderr, "%s: %S: unable to start application; status = %d\n",
-       basename( *argv ), MINGW_GET_GUI, status
+       progname, MINGW_GET_GUI, status
       );
     return EXIT_FATAL;
   }
index 52181f7..862bd4a 100644 (file)
  *
  */
 #define DEBUG_H  1
+
 # if DEBUGLEVEL
-#  ifndef DEBUG_TRACE_INIT
-#   define DEBUG_TRACE_INIT    0x0001
-#  endif
+  /* Here, we provide definitions and declarations which allow us
+   * to selectively enable compilation of (specific class of) debug
+   * specific code.
+   */
+#  define DEBUG_TRACE_INIT                     0x0001
+
+#  define DEBUG_INHIBIT_RITES_OF_PASSAGE       0x7000
+#  define DEBUG_FAIL_FILE_RENAME_RITE          0x1000
+#  define DEBUG_FAIL_FILE_UNLINK_RITE          0x2000
+
 # else /* DEBUGLEVEL == 0 */
+  /* We use this space to provide any declarations which may be
+   * necessary to disable compilation of debug specific code...
+   * (currently, there are none).
+   */
 # endif /* DEBUGLEVEL */
+
 #endif /* DEBUG_H: $RCSfile$: end of file */
diff --git a/src/rites.c b/src/rites.c
new file mode 100644 (file)
index 0000000..e0301c5
--- /dev/null
@@ -0,0 +1,332 @@
+#ifndef BEGIN_RITES_IMPLEMENTATION
+/*
+ * rites.c
+ *
+ * $Id$
+ *
+ * Written by Keith Marshall <keithmarshall@users.sourceforge.net>
+ * Copyright (C) 2009, 2010, MinGW Project
+ *
+ *
+ * Implementation of the main program function for the "lastrites.exe"
+ * helper application; also, when included within another compilation
+ * module, which pre-defines IMPLEMENT_INITIATION_RITES, it furnishes
+ * the complementary "pkgInitRites()" function.
+ *
+ * The combination of a call to pkgInitRites() at program start-up,
+ * followed by eventual termination by means of an "execl()" call to
+ * invoke "lastrites.exe", equips "mingw-get" with the capability to
+ * work around the MS-Windows limitation which prohibits replacement
+ * of the image files for a running application, and so enables the
+ * "mingw-get" application to upgrade itself.
+ *
+ *
+ * This is free software.  Permission is granted to copy, modify and
+ * redistribute this software, under the provisions of the GNU General
+ * Public License, Version 3, (or, at your option, any later version),
+ * as published by the Free Software Foundation; see the file COPYING
+ * for licensing details.
+ *
+ * Note, in particular, that this software is provided "as is", in the
+ * hope that it may prove useful, but WITHOUT WARRANTY OF ANY KIND; not
+ * even an implied WARRANTY OF MERCHANTABILITY, nor of FITNESS FOR ANY
+ * PARTICULAR PURPOSE.  Under no circumstances will the author, or the
+ * MinGW Project, accept liability for any damages, however caused,
+ * arising from the use of this software.
+ *
+ */
+# include <stdio.h>
+# include <stdlib.h>
+# include <errno.h>
+
+#define MINGW_GET_EXE  L"bin/mingw-get.exe"
+#define MINGW_GET_LCK  L"var/lib/mingw-get/lock"
+#define MINGW_GET_DLL  L"libexec/mingw-get/mingw-get-0.dll"
+#define MINGW_GET_GUI  L"libexec/mingw-get/gui.exe"
+
+/* We wish to define a number of helper functions, which we will prefer
+ * to always compile to inline code; this convenience macro may be used
+ * to facilitate achievement of this objective.
+ */
+#define RITES_INLINE  static __inline__ __attribute__((__always_inline__))
+
+#ifndef EOK
+ /*
+  * Typically, errno.h does not define a code for "no error", but
+  * it's convenient for us to have one, so that we may clear "errno"
+  * to ensure that we don't inadvertently react to a stale value.
+  */
+# define EOK   0
+#endif
+
+#include "debug.h"
+
+#if DEBUGLEVEL & DEBUG_INHIBIT_RITES_OF_PASSAGE
+ /*
+  * When debugging, (with rites of passage selected for debugging),
+  * then we substitute debug message handlers for the real "rename()"
+  * and "unlink()" functions used to implement the rites.
+  */
+RITES_INLINE int mingw_get_rename( const char *from, const char *to )
+{
+  /* Inline debugging message handler to report requests to perform
+   * the file rename rite, with optional success/failure result.
+   */
+  fprintf( stderr, "rename: %s to %s\n", from, to );
+  errno = (DEBUGLEVEL & DEBUG_FAIL_FILE_RENAME_RITE) ? EEXIST : EOK;
+  return (DEBUGLEVEL & DEBUG_FAIL_FILE_RENAME_RITE) ? -1 : 0;
+}
+
+RITES_INLINE int mingw_get_unlink( const char *name )
+{
+  /* Inline debugging message handler to report requests to perform
+   * the file "unlink" rite.
+   */
+  fprintf( stderr, "unlink: %s\n", name );
+  return (DEBUGLEVEL & DEBUG_FAIL_FILE_UNLINK_RITE) ? -1 : 0;
+}
+
+#else
+ /* We are doing it for real...
+  * Simply map the "rename" and "unlink" requests through to the
+  * appropriate system calls.
+  */
+# define mingw_get_rename( N1, N2 )    rename( (N1), (N2) )
+# define mingw_get_unlink( N1 )        unlink( (N1) )
+#endif
+
+RITES_INLINE const char *approot_path( void )
+{
+  /* Inline helper to identify the root directory path for the running
+   * application, (which "mingw-get" passes through the APPROOT variable
+   * in the process environment)...
+   */
+  static const char *approot = NULL;
+  return ((approot == NULL) && ((approot = getenv( "APPROOT" )) == NULL))
+
+    ? "c:/mingw/"      /* default, for failed environment look-up */
+    : approot;         /* normal return value */
+}
+
+#ifdef IMPLEMENT_INITIATION_RITES
+ /*
+  * Normally achieved by including this source file within another,
+  * in this case we wish to implement the inline "pkgInitRites()" and
+  * "pkgLastRites()" functions, together with the "do_init_rites()"
+  * static function required to support them.
+  */
+# include <process.h>
+# include <sys/types.h>
+# include <sys/stat.h>
+# include <fcntl.h>
+
+/* Provide a facility for clearing a stale lock; for Win32, we may
+ * simply refer this to the "unlink()" function, because the system
+ * will not permit us to unlink a lock file which is owned by any
+ * active process; (i.e. it is NOT a stale lock).
+ *
+ * FIXME: This will NOT work on Linux (or other Unixes), where it
+ *        IS permitted to unlink an active lock file; to support
+ *        such systems, we will need to provide a more robust
+ *        implementation for "unlink_if_stale()".
+ */
+#define unlink_if_stale  mingw_get_unlink
+
+/* When IMPLEMENT_INITIATION_RITES is selected, we must implement
+ * the inline function, "void do_init_rites( void )"...
+ */
+static void do_init_rites( void );
+# define BEGIN_RITES_IMPLEMENTATION    static void do_init_rites( void )
+ /*
+  * For the "pkgInitRites()" implementation, the idea is to move
+  * the running executable and its associated DLL out of the way,
+  * so that we may install upgraded versions while the application
+  * is still running.  Thus, the first step is to destroy any
+  * previously created backup copy which may still exist...
+  */
+# define first_act( SOURCE, BACKUP )   mingw_get_unlink( (BACKUP) )
+ /*
+  * ...then, we schedule a potential pending removal of the running
+  * program files, by initially renaming them with their designated
+  * backup names...
+  */
+# define final_act( SOURCE, BACKUP )   mingw_get_rename( (SOURCE), (BACKUP) )
+ /*
+  * ...and finally, since the "do_init_rites()" function has nothing
+  * to return, we declare a "do nothing" wrap-up hook.
+  */
+# define END_RITES_IMPLEMENTATION
+
+/* do_init_rites() also requires the following three supporting
+ * API "entry" points...
+ */
+RITES_INLINE const char *lockfile_name( void )
+{
+  /* Helper to identify the absolute path for the lock file...
+   */
+  static char *lockfile = NULL;
+
+  if( lockfile == NULL )
+  {
+    /* We resolve this only once; this is the first reference,
+     * (or all prior references were unsuccessfully resolved), so
+     * we must resolve it now.
+     */
+    const char *lockpath = approot_path();
+    const wchar_t *lockname = MINGW_GET_LCK;
+    size_t wanted = 1 + snprintf( NULL, 0, "%s%S", lockpath, lockname );
+    if( (lockfile = malloc( wanted )) != NULL )
+      snprintf( lockfile, wanted, "%s%S", lockpath, lockname );
+  }
+  /* In any case, we return the pointer as resolved on first call.
+   */
+  return lockfile;
+}
+
+RITES_INLINE int pkgInitRites( const char *progname )
+{
+  /* Helper to acquire an exclusive execution lock, and if sucessful,
+   * to establish pre-conditions to permit self-upgrade.
+   */
+  int lock;
+  const char *lockfile;
+
+  /* First, attempt to clear any prior (stale) lock, then create
+   * a new one.  (Note that we DON'T use O_TEMPORARY here; on Win2K,
+   * it leads to strange behaviour when another process attempts to
+   * unlink a stale lock file).
+   */
+  unlink_if_stale( lockfile = lockfile_name() );
+  if( (lock = open( lockfile, O_RDWR | O_CREAT | O_EXCL, S_IWRITE )) < 0 )
+  {
+    /* We failed to acquire the lock; diagnose failure...
+     */
+    fprintf( stderr, "%s: cannot acquire lock for exclusive execution\n",
+       progname
+      );
+    fprintf( stderr, "%s: ", progname ); perror( lockfile );
+    if( errno == EEXIST )
+      fprintf( stderr, "%s: another mingw-get process appears to be running\n",
+         progname
+       );
+  }
+  else
+    /* We successfully acquired the lock; continue initialisation...
+     */
+    do_init_rites();
+
+  /* Return the lock, indicating success or failure as appropriate.
+   */
+  return lock;
+}
+
+RITES_INLINE int pkgLastRites( int lock, const char *progname )
+{
+  /* Inline helper to clear the lock acquired by "pkgInitRites()",
+   * and to initiate clean-up of the changes made by "do_init_rites()".
+   */
+  const char *approot = approot_path();
+  const char *lastrites = "libexec/mingw-get/lastrites.exe";
+  char rites[1 + snprintf( NULL, 0, "%s%s", approot, lastrites )];
+
+  /* Clear the lock; note that we must both close AND unlink the
+   * lock file, because we didn't open it as O_TEMPORARY.
+   */
+  close( lock );
+  unlink( lockfile_name() );
+
+  /* Initiate clean-up; we hand this off to a free-standing process,
+   * so that it may delete the old EXE and DLL image files belonging to
+   * this process, if they were upgraded since acquiring the lock.
+   */
+  snprintf( rites, sizeof( rites ), "%s%s", approot, lastrites );
+  execl( rites, "lastrites", NULL );
+
+  /* We should never get to here; if we do...
+   * Diagnose a problem, and bail out.
+   */
+  fprintf( stderr, "%s: ", progname ); perror( "execl" );
+  return EXIT_FATAL;
+}
+
+#else /* ! defined IMPLEMENT_INITIATION_RITES */
+ /*
+  * This alternative case is normally achieved by free-standing
+  * compilation of rites.c; it implements the "main()" function
+  * for the "lastrites.exe" helper program.
+  */
+# define BEGIN_RITES_IMPLEMENTATION    int main()
+ /*
+  * In this case, we assume that the originally running process
+  * has called "pkgInitRites()", so moving its executable and its
+  * associated DLL out of the way; we aim to move them back again,
+  * by changing the backup name back to the original...
+  */
+# define first_act( SOURCE, BACKUP )   mingw_get_rename( (BACKUP), (SOURCE) )
+ /*
+  * We expect the preceding "rename" to succeed; if it didn't, then
+  * the most probable reason is that an upgrade has been installed,
+  * in which case we may remove the obsolete backup version.
+  */
+# define final_act( SOURCE, BACKUP )   mingw_get_remove( (BACKUP) )
+ /*
+  * Finally, in this case, we must return an exit code; we simply
+  * assume that, for us. there is no meaningful concept of process
+  * failure, so we always report success.
+  */
+# define END_RITES_IMPLEMENTATION      return EXIT_SUCCESS;
+#endif
+
+RITES_INLINE void mingw_get_remove( const char *name )
+{
+  /* Inline helper to perform the "unlink" rite, provided a preceding
+   * request, (presumably "rename"), has not failed with EEXIST status.
+   */
+  if( errno == EEXIST ) mingw_get_unlink( name );
+}
+
+RITES_INLINE void perform_rites_of_passage( const wchar_t *name )
+{
+  /* Local helper function, to perform the required rite of passage
+   * for a single specified process image file, as specified by its
+   * relative path name within the application directory tree.
+   */
+  const char *approot = approot_path();
+
+  /* We begin by allocating stack space for the absolute path names
+   * for both the original file name and its backup name.
+   */
+  size_t buflen = snprintf( NULL, 0, "%s%S~", approot, name );
+  char normal_name[ buflen ], backup_name[ 1 + buflen ];
+
+  /* Fill out this buffer pair with the requisite path names,
+   * noting that the backup name is the same as the original
+   * name, with a single tilde appended.
+   */
+  snprintf( normal_name, buflen, "%s%S", approot, name );
+  snprintf( backup_name, ++buflen, "%s~", normal_name );
+
+  /* Clear any pre-existing error condition, then perform the
+   * requisite "rename" and "unlink" rites, in the pre-scheduled
+   * order appropriate to the build context.
+   */
+  errno = EOK;
+  first_act( normal_name, backup_name );
+  final_act( normal_name, backup_name );
+}
+
+/* Here, we specify the variant portion of the implementation...
+ */
+BEGIN_RITES_IMPLEMENTATION
+{
+  /* ...where the requisite "rites of passage" are initiated
+   * for each process image file affected, specifying each by
+   * its path name relative to the root of the application's
+   * installation directory tree.
+   */
+  perform_rites_of_passage( MINGW_GET_EXE );
+  perform_rites_of_passage( MINGW_GET_DLL );
+  END_RITES_IMPLEMENTATION;
+}
+
+#endif /* BEGIN_RITES_IMPLEMENTATION: $RCSfile$: end of file */