OSDN Git Service

eed0a62e19f08cf04746982e3c0280f013449d80
[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.
176      */
177     dbase.LoadSystemMap();
178     setup->UpdateDatabase( dbase );
179   }
180   /* We're finished with the setup.xml profile; delete it, and free
181    * the memory which xmlfile() allocated to store its path name.
182    */
183   unlink( dfile );
184   free( (void *)(dfile) );
185 }
186
187 static inline
188 void update_catalogue( HWND owner )
189 {
190   /* Helper function to ensure that all locally installed catalogue
191    * files are synchronised with their latest versions, as hosted on
192    * their respective repository servers.
193    */
194   const char *dfile;
195   pkgXmlDocument dbase( dfile = xmlfile( profile_key ) );
196   if( dbase.IsOk() )
197   {
198     /* The XML package database has been successfully loaded;
199      * ensure that the local catalogue is synchronised with the
200      * with the up-to-date repository representation.
201      */
202     setupProgressMeter watch( owner, dbase );
203     dbase.BindRepositories( true );
204     watch.Annotate(
205         "Catalogue update completed; please check 'Details' pane for errors."
206       );
207   }
208   /* We're finished with the path name for the profile.xml file;
209    * release the memory which xmlfile() allocated to store it.
210    */
211   free( (void *)(dfile) );
212 }
213
214 static inline
215 int run_basic_system_installer( const wchar_t *dll_name )
216 {
217   /* Hook to emulate a subset of the mingw-get GUI installer
218    * capabilities, via an embedded subset of its functions.
219    */
220   try
221   { /* Identify the DLL module, whence we may retrieve resources
222      * similar to those of the free-standing GUI application, and
223      * create a clone of that application's main window.
224      */
225     HINSTANCE instance;
226     AppWindowMaker MainWindow( instance = GetModuleHandleW( dll_name ) );
227     MainWindow.Create( WTK::StringResource( instance, ID_MAIN_WINDOW_CLASS ),
228         WTK::StringResource( instance, ID_MAIN_WINDOW_CAPTION )
229       );
230
231     /* Show this window, and paint its initial content...
232      */
233     MainWindow.Show( SW_SHOW );
234     MainWindow.Update();
235
236     /* ...then invoke its message loop, ultimately returning the
237      * status code which prevails, when the user closes it.
238      */
239     return MainWindow.Invoked();
240   }
241   catch( dmh_exception &e )
242   {
243     /* Here, we handle any fatal exception which has been raised
244      * and identified by the diagnostic message handler...
245      */
246     MessageBox( NULL, e.what(), "WinMain", MB_ICONERROR );
247     return EXIT_FAILURE;
248   }
249   catch( WTK::runtime_error &e )
250   {
251     /* ...while here, we diagnose any other error which was captured
252      * during the creation of the application's window hierarchy, or
253      * processing of its message loop...
254      */
255     MessageBox( NULL, e.what(), "WinMain", MB_ICONERROR );
256     return EXIT_FAILURE;
257   }
258   catch(...)
259   { /* ...and here, we diagnose any other error which we weren't
260      * able to explicitly identify.
261      */
262     MessageBox( NULL, "Unknown exception", "WinMain", MB_ICONERROR );
263     return EXIT_FAILURE;
264   }
265 }
266
267 EXTERN_C __declspec(dllexport)
268 void setup_hook( unsigned int request, va_list argv )
269 {
270   /* Single entry point API function, through which the setup tool
271    * directs all requests to its own delay-loaded DLL, and hence to
272    * mingw-get-0.dll itself.
273    */
274   switch( request )
275   { case SETUP_HOOK_DMH_BIND:
276       /* Initialisation hook, through which the setup tool makes its
277        * already initialised diagnostic message handler available to
278        * to DLL functions which it invokes.
279        */
280       dmh_bind( va_arg( argv, dmhTypeGeneric * ) );
281       break;
282
283     case SETUP_HOOK_POST_INSTALL:
284       /* This is the principal entry point, through which the setup
285        * tool hands off the final phases of mingw-get installation to
286        * the XML database management functions within the DLLs.
287        */
288       initialise_profile();
289       update_database( va_arg( argv, pkgSetupAction * ) );
290       update_catalogue( va_arg( argv, HWND ) );
291       break;
292
293     case SETUP_HOOK_RUN_INSTALLER:
294       /* This hook invokes the mingw-get GUI, in setup tool mode, to
295        * facilitate installation of a user configured basic system.
296        */
297       run_basic_system_installer( va_arg( argv, const wchar_t * ) );
298       break;
299
300     default:
301       /* We should never get to here; it's a programming error in
302        * the setup tool, if we do.
303        */
304       dmh_control( DMH_BEGIN_DIGEST );
305       dmh_notify( DMH_ERROR, MSG_INTERNAL_ERROR( INVALID_REQUEST ) );
306       dmh_notify( DMH_ERROR, MSG_INTERNAL_ERROR( NOTIFY_MAINTAINER ) );
307       dmh_control( DMH_END_DIGEST );
308   }
309 }
310
311 class pkgXmlNodeStack
312 {
313   /* A convenience class for managing a list of pkgXmlNodes
314    * as a LIFO stack.
315    */
316   public:
317     inline pkgXmlNodeStack *push( pkgXmlNode * );
318     inline pkgXmlNodeStack *pop( pkgXmlNode ** );
319
320   private:
321     pkgXmlNodeStack *next;
322     pkgXmlNode *entry;
323 };
324
325 inline pkgXmlNodeStack *pkgXmlNodeStack::push( pkgXmlNode *node )
326 {
327   /* Method to push a pkgXmlNode into a new frame, on the top of
328    * the stack, returning the new stack pointer.
329    */
330   pkgXmlNodeStack *rtn;
331   if( (rtn = new pkgXmlNodeStack) != NULL )
332   {
333     /* We successfully allocated storage space for the new stack
334      * entry; populate it with the specified pkgXmlNode date, and
335      * link it to the existing stack chain...
336      */
337     rtn->entry = node; rtn->next = this;
338
339     /* ...before returning the new top-of-stack pointer.
340      */
341     return rtn;
342   }
343   /* If we get to here, we were unable to expand the stack to
344    * accommodate any new entry; don't move the stack pointer.
345    */
346   return this;
347 }
348
349 inline pkgXmlNodeStack *pkgXmlNodeStack::pop( pkgXmlNode **node )
350 {
351   /* Method to pop a pkgXmlNode from the top of the stack, while
352    * saving a reference pointer for it, and returning the updated
353    * stack pointer.
354    */
355   if( this == NULL )
356     /*
357      * The stack is empty; there's nothing more to do!
358      */
359     return NULL;
360
361   /* When the stack is NOT empty, capture the reference pointer
362    * for the SECOND entry (if any), store the top entry, delete
363    * its stack frame, and return the new stack pointer.
364    */
365   pkgXmlNodeStack *rtn = next; *node = entry;
366   delete this; return rtn;
367 }
368
369 void pkgSetupAction::UpdateDatabase( pkgXmlDocument &dbase )
370 {
371   /* Method to ensure that the mingw-get package components which are
372    * specified in the setup actions list are recorded as "installed", in
373    * the installation database manifests.
374    */
375   if( this != NULL )
376   {
377     /* The setup actions list is not empty; ensure that we commence
378      * processing from its first entry...
379      */
380     pkgSetupAction *current = this;
381     while( current->prev != NULL ) current = current->prev;
382     dmh_notify( DMH_INFO, "%s: updating installation database\n", setup_key );
383     while( current != NULL )
384     {
385       /* ...then processing all entries sequentially, in turn,
386        * parse the package tarname specified in the current action
387        * entry, to identify the associated package name, component
388        * class and subsystem name.
389        */
390       const char *name_fmt = "%s-%s";
391       pkgSpecs lookup( current->package_name );
392       char lookup_name[1 + strlen( current->package_name )];
393       const char *component = lookup.GetComponentClass();
394       const char *subsystem = lookup.GetSubSystemName();
395       if( (component != NULL) && *component )
396         /*
397          * The package name is qualified by an explicit component
398          * name; form the composite package name string.
399          */
400         sprintf( lookup_name, name_fmt, lookup.GetPackageName(), component );
401       else
402         /* There is no explicit component name; just save a copy
403          * of the unqualified package name.
404          */
405         strcpy( lookup_name, lookup.GetPackageName() );
406
407       /* Locate the corresponding component package entry, if any,
408        * in the package catalogue.
409        */
410       if( dbase.FindPackageByName( lookup_name, subsystem ) != NULL )
411       {
412         /* Lookup was successful; now search the installation records,
413          * if any, for any matching package entry.
414          */
415         pkgXmlNodeStack *stack = NULL;
416         pkgXmlNode *sysroot = dbase.GetRoot()->GetSysRoot( subsystem );
417         pkgXmlNode *installed = sysroot->FindFirstAssociate( installed_key );
418         while( installed != NULL )
419         {
420           /* There is at least one installation record; walk the chain
421            * of all such records...
422            */
423           const char *tarname = installed->GetPropVal( tarname_key, NULL );
424           if( tarname != NULL )
425           {
426             /* ...extracting package and component names from the tarname
427              * specification within each...
428              */
429             pkgSpecs ref( tarname );
430             char ref_name[1 + strlen( tarname )];
431             if( ((component = ref.GetComponentClass()) != NULL) && *component )
432               /*
433                * ...once again forming the composite name, when applicable...
434                */
435               sprintf( ref_name, name_fmt, ref.GetPackageName(), component );
436             else
437               /* ...or simply storing the unqualified package name if not.
438                */
439               strcpy( ref_name, ref.GetPackageName() );
440
441             /* Check for a match between the installed package name, and
442              * the name we wish to record as newly installed...
443              */
444             if( (strcasecmp( ref_name, lookup_name ) == 0)
445             &&  (strcasecmp( tarname, current->package_name ) != 0)  )
446               /*
447                * ...pushing the current installation record on to the
448                * update stack, in case of a match...
449                */
450               stack = stack->push( installed );
451           }
452           /* ...then move on to the next installation record, if any.
453            */
454           installed = installed->FindNextAssociate( installed_key );
455         }
456
457         /* Create a temporary package "release" descriptor...
458          */
459         pkgXmlNode *reference_hook = new pkgXmlNode( release_key );
460         if( reference_hook != NULL )
461         {
462           /* ...which we may conveniently attach to the root
463            * of the XML catalogue tree.
464            */
465           dbase.GetRoot()->AddChild( reference_hook );
466           reference_hook->SetAttribute( tarname_key, current->package_name );
467
468           /* Run the installer...
469            */
470           pkgTarArchiveInstaller registration_server( reference_hook );
471           if( registration_server.IsOk() )
472           {
473             /* ...reporting the installation as a "registration" of
474              * the specified package, but...
475              */
476             dmh_notify( DMH_INFO, "%s: register %s\n",
477                 setup_key, current->package_name
478               );
479             /* ...noting that the package content has already been
480              * "installed" by the setup tool, but without recording
481              * any details, we run this without physically extracting
482              * any files, to capture the side effect of compiling an
483              * installation record.
484              */
485             registration_server.SaveExtractedFiles( false );
486             registration_server.Process();
487           }
488           /* With the installation record safely compiled, we may
489            * discard the temporary "release" descriptor from which
490            * we compiled it.
491            */
492           dbase.GetRoot()->DeleteChild( reference_hook );
493         }
494
495         /* When the update stack, constructed above, is not empty...
496          */
497         if( stack != NULL )
498         { while( stack != NULL )
499           {
500             /* ...pop each installation record, which is to be updated,
501              * off the update stack, in turn...
502              */
503             const char *tarname;
504             pkgXmlNode *installed;
505             stack = stack->pop( &installed );
506             if( (tarname = installed->GetPropVal( tarname_key, NULL )) != NULL )
507             {
508               /* ...identify its associated installed files manifest, and
509                * disassociate it from the sysroot of the current installation;
510                * (note that this automatically deletes the manifest itself, if
511                * it is associated with no other sysroots).
512                */
513               pkgManifest inventory( package_key, tarname );
514               inventory.DetachSysRoot( sysroot->GetPropVal( id_key, subsystem ) );
515             }
516             /* Delete the installation record from the current sysroot...
517              */
518             sysroot->DeleteChild( installed );
519           }
520           /* ...and mark the sysroot record as "modified", as a result of
521            * all preceding updates.
522            */
523           sysroot->SetAttribute( modified_key, value_yes );
524         }
525       }
526
527       /* Repeat for all packages with an associated setup action...
528        */
529       current = current->next;
530     }
531     /* ...and finally, report completion of all database updates, while also
532      * committing all recorded changes to disk storage.
533      */
534     dmh_notify( DMH_INFO, "%s: installation database updated\n", setup_key );
535     dbase.UpdateSystemMap();
536   }
537 }
538
539 /* $RCSfile$: end of file */