OSDN Git Service

Streamline the installation procedure.
[mingw/mingw-get.git] / src / dllhook.cpp
1 /*
2  * dllhook.cpp
3  *
4  * $Id$
5  *
6  * Written by Keith Marshall <keith@users.osdn.me>
7  * Copyright (C) 2013, 2020, MinGW.org Project
8  *
9  *
10  * Implementation of the processing redirector hook, to be provided
11  * in the form of a free-standing bridge DLL, through which the setup
12  * tool requests services from the main mingw-get DLL.
13  *
14  *
15  * This is free software.  Permission is granted to copy, modify and
16  * redistribute this software, under the provisions of the GNU General
17  * Public License, Version 3, (or, at your option, any later version),
18  * as published by the Free Software Foundation; see the file COPYING
19  * for licensing details.
20  *
21  * Note, in particular, that this software is provided "as is", in the
22  * hope that it may prove useful, but WITHOUT WARRANTY OF ANY KIND; not
23  * even an implied WARRANTY OF MERCHANTABILITY, nor of FITNESS FOR ANY
24  * PARTICULAR PURPOSE.  Under no circumstances will the author, or the
25  * MinGW Project, accept liability for any damages, however caused,
26  * arising from the use of this software.
27  *
28  */
29 static const char *setup_key = "setup";
30
31 #define  WIN32_LEAN_AND_MEAN
32 #define  IMPLEMENTATION_LEVEL  PACKAGE_BASE_COMPONENT
33
34 #include "setup.h"
35 #include "guimain.h"
36 #include "pkgbase.h"
37 #include "pkginfo.h"
38 #include "pkgkeys.h"
39 #include "pkgproc.h"
40 #include "dmhcore.h"
41 #include "mkpath.h"
42
43 #include <stdlib.h>
44 #include <windows.h>
45 #include <commctrl.h>
46 #include <stdarg.h>
47 #include <sys/types.h>
48 #include <sys/stat.h>
49 #include <fcntl.h>
50
51 /* This file implements the plugin variant of the mingw-get
52  * GUI installer, for use by the setup tool; in this context
53  * this AppWindowMaker static attribute must be initialised
54  * to indicate this mode of use.
55  */
56 bool AppWindowMaker::SetupToolInvoked = true;
57
58 static const char *internal_error = "internal error";
59 #define MSG_INTERNAL_ERROR(MSG) "%s: " MSG_ ## MSG "\n", internal_error
60
61 #define MSG_INVALID_REQUEST     "invalid request code specified"
62 #define MSG_NOTIFY_MAINTAINER   "please report this to the mingw-get maintainer"
63
64 #define PROGRESS_METER_CLASS    setupProgressMeter
65
66 class PROGRESS_METER_CLASS: public pkgProgressMeter
67 {
68   /* Specialisation of the pkgProgressMeter class, through which
69    * the pkgXmlDocument::BindRepositories() method sends progress
70    * reports to the setup tool dialogue.
71    */
72   public:
73     PROGRESS_METER_CLASS( HWND, pkgXmlDocument & );
74     ~PROGRESS_METER_CLASS(){ dbase.DetachProgressMeter( this ); }
75
76     virtual int Annotate( const char *, ... );
77     virtual void SetRange( int, int );
78     virtual void SetValue( int );
79
80   private:
81     pkgXmlDocument &dbase;
82     HWND annotation, count, lim, frac, progress_bar;
83     void PutVal( HWND, const char *, ... );
84     int total;
85 };
86
87 /* This class requires a specialised constructor...
88  */
89 #define IDD( DLG, ITEM ) GetDlgItem( DLG, IDD_PROGRESS_##ITEM )
90 PROGRESS_METER_CLASS::PROGRESS_METER_CLASS( HWND owner, pkgXmlDocument &db ):
91 dbase( db ), annotation( IDD( owner, MSG ) ), progress_bar( IDD( owner, BAR ) ),
92 count( IDD( owner, VAL ) ), lim( IDD( owner, MAX ) ), frac( IDD( owner, PCT ) )
93 {
94   /* ...which binds the progress repporter directly to the
95    * invoking pkgXmlDocument class object, before setting the
96    * initial item count to zero, from a minimal anticipated
97    * total of one.
98    */
99   dbase.AttachProgressMeter( this );
100   SetRange( 0, 1 ); SetValue( 0 );
101 };
102
103 /* The remaining methods of the class are abstracted from the
104  * generic progress metering implementation.
105  */
106 #include "pmihook.cpp"
107
108 static inline
109 void initialise_profile( void )
110 {
111   /* Helper function to ensure that profile.xml exists in the
112    * mingw-get database directory, either as a pre-existing file,
113    * or by creating it as a copy of defaults.xml
114    */
115   int copyin, copyout;
116   const char *profile = xmlfile( profile_key, NULL );
117   if(  (profile != NULL) && (access( profile, F_OK ) != 0)
118   &&  ((copyout = set_output_stream( profile, 0644 )) >= 0)  )
119   {
120     /* No profile.xml file currently exists, but we are able
121      * to create one...
122      */
123     const char *default_profile;
124     if( ((default_profile = xmlfile( defaults_key, NULL )) != NULL)
125     &&  ((copyin = open( default_profile, _O_RDONLY | _O_BINARY )) >= 0)  )
126     {
127       /* ...whereas the defaults.xml DOES exist, and we are able
128        * to read it, so set up a transfer buffer, through which we
129        * may copy its content to profile.xml
130        */
131       char buf[BUFSIZ]; ssize_t count;
132       while( (count = read( copyin, buf, BUFSIZ )) > 0 )
133         write( copyout, buf, count );
134
135       /* When the entire content of defaults.xml has been copied,
136        * close both file streams, saving both files.
137        */
138       close( copyout );
139       close( copyin );
140     }
141     else
142     { /* We were unable to open defaults.xml for copying, but we
143        * already hold an open stream handle for profile.xml; close
144        * it, then unlink, to discard the resulting empty file.
145        */
146       close( copyout );
147       unlink( profile );
148     }
149     /* Attempting to open defaults.xml, whether successful or not,
150      * has left us with a heap memory allocation for its path name.
151      * We don't need it any more; free it so we don't leak memory.
152      */
153     free( (void *)(default_profile) );
154   }
155   /* Similarly, we have a heap memory allocation for the path name
156    * of profile.xml; we must also avoid leaking it.
157    */
158   free( (void *)(profile) );
159 }
160
161 static inline
162 void update_database( pkgSetupAction *setup )
163 {
164   /* Helper function to initiate an XML database update, based on
165    * a restricted (custom) profile, to ensure that the installation
166    * of mingw-get itself is properly recorded.
167    */
168   const char *dfile;
169   pkgXmlDocument dbase( dfile = xmlfile( setup_key ) );
170   if( dbase.IsOk() && (dbase.BindRepositories( false ) != NULL) )
171   {
172     /* Having successfully loaded the restricted profile, load the
173      * map of the installation it specifies, and update it to reflect
174      * the installation of the mingw-get packages we are currently
175      * installing; (note that we must take care not to attempt to
176      * update the database, if passed a NULL setup object).
177      */
178     dbase.LoadSystemMap();
179     if( setup != NULL ) setup->UpdateDatabase( dbase );
180   }
181   /* We're finished with the setup.xml profile; delete it, and free
182    * the memory which xmlfile() allocated to store its path name.
183    */
184   unlink( dfile );
185   free( (void *)(dfile) );
186 }
187
188 static inline
189 void update_catalogue( HWND owner )
190 {
191   /* Helper function to ensure that all locally installed catalogue
192    * files are synchronised with their latest versions, as hosted on
193    * their respective repository servers.
194    */
195   const char *dfile;
196   pkgXmlDocument dbase( dfile = xmlfile( profile_key ) );
197   if( dbase.IsOk() )
198   {
199     /* The XML package database has been successfully loaded;
200      * ensure that the local catalogue is synchronised with the
201      * with the up-to-date repository representation.
202      */
203     setupProgressMeter watch( owner, dbase );
204     dbase.BindRepositories( true );
205     watch.Annotate(
206         "Catalogue update completed; please check 'Details' pane for errors."
207       );
208   }
209   /* We're finished with the path name for the profile.xml file;
210    * release the memory which xmlfile() allocated to store it.
211    */
212   free( (void *)(dfile) );
213 }
214
215 static inline
216 int run_basic_system_installer( const wchar_t *dll_name )
217 {
218   /* Hook to emulate a subset of the mingw-get GUI installer
219    * capabilities, via an embedded subset of its functions.
220    */
221   try
222   { /* Identify the DLL module, whence we may retrieve resources
223      * similar to those of the free-standing GUI application, and
224      * create a clone of that application's main window.
225      */
226     HINSTANCE instance;
227     AppWindowMaker MainWindow( instance = GetModuleHandleW( dll_name ) );
228     MainWindow.Create( WTK::StringResource( instance, ID_MAIN_WINDOW_CLASS ),
229         WTK::StringResource( instance, ID_MAIN_WINDOW_CAPTION )
230       );
231
232     /* Show this window, and paint its initial content...
233      */
234     MainWindow.Show( SW_SHOW );
235     MainWindow.Update();
236
237     /* ...then invoke its message loop, ultimately returning the
238      * status code which prevails, when the user closes it.
239      */
240     return MainWindow.Invoked();
241   }
242   catch( dmh_exception &e )
243   {
244     /* Here, we handle any fatal exception which has been raised
245      * and identified by the diagnostic message handler...
246      */
247     MessageBox( NULL, e.what(), "WinMain", MB_ICONERROR );
248     return EXIT_FAILURE;
249   }
250   catch( WTK::runtime_error &e )
251   {
252     /* ...while here, we diagnose any other error which was captured
253      * during the creation of the application's window hierarchy, or
254      * processing of its message loop...
255      */
256     MessageBox( NULL, e.what(), "WinMain", MB_ICONERROR );
257     return EXIT_FAILURE;
258   }
259   catch(...)
260   { /* ...and here, we diagnose any other error which we weren't
261      * able to explicitly identify.
262      */
263     MessageBox( NULL, "Unknown exception", "WinMain", MB_ICONERROR );
264     return EXIT_FAILURE;
265   }
266 }
267
268 EXTERN_C __declspec(dllexport)
269 void setup_hook( unsigned int request, va_list argv )
270 {
271   /* Single entry point API function, through which the setup tool
272    * directs all requests to its own delay-loaded DLL, and hence to
273    * mingw-get-0.dll itself.
274    */
275   switch( request )
276   { case SETUP_HOOK_DMH_BIND:
277       /* Initialisation hook, through which the setup tool makes its
278        * already initialised diagnostic message handler available to
279        * to DLL functions which it invokes.
280        */
281       dmh_bind( va_arg( argv, dmhTypeGeneric * ) );
282       break;
283
284     case SETUP_HOOK_POST_INSTALL:
285       /* This is the principal entry point, through which the setup
286        * tool hands off the final phases of mingw-get installation to
287        * the XML database management functions within the DLLs.
288        */
289       initialise_profile();
290       update_database( va_arg( argv, pkgSetupAction * ) );
291       update_catalogue( va_arg( argv, HWND ) );
292       break;
293
294     case SETUP_HOOK_RUN_INSTALLER:
295       /* This hook invokes the mingw-get GUI, in setup tool mode, to
296        * facilitate installation of a user configured basic system.
297        */
298       run_basic_system_installer( va_arg( argv, const wchar_t * ) );
299       break;
300
301     default:
302       /* We should never get to here; it's a programming error in
303        * the setup tool, if we do.
304        */
305       dmh_control( DMH_BEGIN_DIGEST );
306       dmh_notify( DMH_ERROR, MSG_INTERNAL_ERROR( INVALID_REQUEST ) );
307       dmh_notify( DMH_ERROR, MSG_INTERNAL_ERROR( NOTIFY_MAINTAINER ) );
308       dmh_control( DMH_END_DIGEST );
309   }
310 }
311
312 class pkgXmlNodeStack
313 {
314   /* A convenience class for managing a list of pkgXmlNodes
315    * as a LIFO stack.
316    */
317   public:
318     inline pkgXmlNodeStack *push( pkgXmlNode * );
319     inline pkgXmlNodeStack *pop( pkgXmlNode ** );
320
321   private:
322     pkgXmlNodeStack *next;
323     pkgXmlNode *entry;
324 };
325
326 inline pkgXmlNodeStack *pkgXmlNodeStack::push( pkgXmlNode *node )
327 {
328   /* Method to push a pkgXmlNode into a new frame, on the top of
329    * the stack, returning the new stack pointer.
330    */
331   pkgXmlNodeStack *rtn;
332   if( (rtn = new pkgXmlNodeStack) != NULL )
333   {
334     /* We successfully allocated storage space for the new stack
335      * entry; populate it with the specified pkgXmlNode date, and
336      * link it to the existing stack chain...
337      */
338     rtn->entry = node; rtn->next = this;
339
340     /* ...before returning the new top-of-stack pointer.
341      */
342     return rtn;
343   }
344   /* If we get to here, we were unable to expand the stack to
345    * accommodate any new entry; don't move the stack pointer.
346    */
347   return this;
348 }
349
350 inline pkgXmlNodeStack *pkgXmlNodeStack::pop( pkgXmlNode **node )
351 {
352   /* Method to pop a pkgXmlNode from the top of the stack, while
353    * saving a reference pointer for it, and returning the updated
354    * stack pointer.
355    *
356    * This should never be called when the stack is empty; (this
357    * must be checked at the call site).  When the stack is known
358    * to NOT be empty, capture the reference pointer for the SECOND
359    * entry (if any), store the top entry, delete its stack frame,
360    * and return the new stack pointer.
361    */
362   pkgXmlNodeStack *rtn = next; *node = entry;
363   delete this; return rtn;
364 }
365
366 void pkgSetupAction::UpdateDatabase( pkgXmlDocument &dbase )
367 {
368   /* Method to ensure that the mingw-get package components which are
369    * specified in the setup actions list are recorded as "installed", in
370    * the installation database manifests.
371    *
372    * This method should NEVER be invoked on an empty setup actions list;
373    * (this must be verified at the call site).  When it is known that the
374    * list is NOT empty, we must ensure that we commence processing from
375    * its first entry...
376    */
377   pkgSetupAction *current = this;
378   while( current->prev != NULL ) current = current->prev;
379   dmh_notify( DMH_INFO, "%s: updating installation database\n", setup_key );
380   while( current != NULL )
381   {
382     /* ...then processing all entries sequentially, in turn,
383      * parse the package tarname specified in the current action
384      * entry, to identify the associated package name, component
385      * class and subsystem name.
386      */
387     const char *name_fmt = "%s-%s";
388     pkgSpecs lookup( current->package_name );
389     char lookup_name[1 + strlen( current->package_name )];
390     const char *component = lookup.GetComponentClass();
391     const char *subsystem = lookup.GetSubSystemName();
392     if( (component != NULL) && *component )
393       /*
394        * The package name is qualified by an explicit component
395        * name; form the composite package name string.
396        */
397       sprintf( lookup_name, name_fmt, lookup.GetPackageName(), component );
398     else
399       /* There is no explicit component name; just save a copy
400        * of the unqualified package name.
401        */
402       strcpy( lookup_name, lookup.GetPackageName() );
403
404     /* Locate the corresponding component package entry, if any,
405      * in the package catalogue.
406      */
407     if( dbase.FindPackageByName( lookup_name, subsystem ) != NULL )
408     {
409       /* Lookup was successful; now search the installation records,
410        * if any, for any matching package entry.
411        */
412       pkgXmlNodeStack *stack = NULL;
413       pkgXmlNode *sysroot = dbase.GetRoot()->GetSysRoot( subsystem );
414       pkgXmlNode *installed = sysroot->FindFirstAssociate( installed_key );
415       while( installed != NULL )
416       {
417         /* There is at least one installation record; walk the chain
418          * of all such records...
419          */
420         const char *tarname = installed->GetPropVal( tarname_key, NULL );
421         if( tarname != NULL )
422         {
423           /* ...extracting package and component names from the tarname
424            * specification within each...
425            */
426           pkgSpecs ref( tarname );
427           char ref_name[1 + strlen( tarname )];
428           if( ((component = ref.GetComponentClass()) != NULL) && *component )
429             /*
430              * ...once again forming the composite name, when applicable...
431              */
432             sprintf( ref_name, name_fmt, ref.GetPackageName(), component );
433           else
434             /* ...or simply storing the unqualified package name if not.
435              */
436             strcpy( ref_name, ref.GetPackageName() );
437
438           /* Check for a match between the installed package name, and
439            * the name we wish to record as newly installed...
440            */
441           if( (strcasecmp( ref_name, lookup_name ) == 0)
442           &&  (strcasecmp( tarname, current->package_name ) != 0)  )
443             /*
444              * ...pushing the current installation record on to the
445              * update stack, in case of a match...
446              */
447             stack = stack->push( installed );
448         }
449         /* ...then move on to the next installation record, if any.
450          */
451         installed = installed->FindNextAssociate( installed_key );
452       }
453
454       /* Create a temporary package "release" descriptor...
455        */
456       pkgXmlNode *reference_hook = new pkgXmlNode( release_key );
457       if( reference_hook != NULL )
458       {
459         /* ...which we may conveniently attach to the root
460          * of the XML catalogue tree.
461          */
462         dbase.GetRoot()->AddChild( reference_hook );
463         reference_hook->SetAttribute( tarname_key, current->package_name );
464
465         /* Run the installer...
466          */
467         pkgTarArchiveInstaller registration_server( reference_hook );
468         if( registration_server.IsOk() )
469         {
470           /* ...reporting the installation as a "registration" of
471            * the specified package, but...
472            */
473           dmh_notify( DMH_INFO, "%s: register %s\n",
474               setup_key, current->package_name
475             );
476           /* ...noting that the package content has already been
477            * "installed" by the setup tool, but without recording
478            * any details, we run this without physically extracting
479            * any files, to capture the side effect of compiling an
480            * installation record.
481            */
482           registration_server.SaveExtractedFiles( false );
483           registration_server.Process();
484         }
485         /* With the installation record safely compiled, we may
486          * discard the temporary "release" descriptor from which
487          * we compiled it.
488          */
489         dbase.GetRoot()->DeleteChild( reference_hook );
490       }
491
492       /* When the update stack, constructed above, is not empty...
493        */
494       if( stack != NULL )
495       { while( stack != NULL )
496         {
497           /* ...pop each installation record, which is to be updated,
498            * off the update stack, in turn...
499            */
500           const char *tarname;
501           pkgXmlNode *installed;
502           stack = stack->pop( &installed );
503           if( (tarname = installed->GetPropVal( tarname_key, NULL )) != NULL )
504           {
505             /* ...identify its associated installed files manifest, and
506              * disassociate it from the sysroot of the current installation;
507              * (note that this automatically deletes the manifest itself, if
508              * it is associated with no other sysroots).
509              */
510             pkgManifest inventory( package_key, tarname );
511             inventory.DetachSysRoot( sysroot->GetPropVal( id_key, subsystem ) );
512           }
513           /* Delete the installation record from the current sysroot...
514            */
515           sysroot->DeleteChild( installed );
516         }
517         /* ...and mark the sysroot record as "modified", as a result of
518          * all preceding updates.
519          */
520         sysroot->SetAttribute( modified_key, value_yes );
521       }
522     }
523
524     /* Repeat for all packages with an associated setup action...
525      */
526     current = current->next;
527   }
528   /* ...and finally, report completion of all database updates, while also
529    * committing all recorded changes to disk storage.
530    */
531   dmh_notify( DMH_INFO, "%s: installation database updated\n", setup_key );
532   dbase.UpdateSystemMap();
533 }
534
535 /* $RCSfile$: end of file */