OSDN Git Service

Incorporate foundation for setup tool implementation.
[mingw/mingw-get.git] / src / dllhook.cpp
1 /*
2  * dllhook.cpp
3  *
4  * $Id$
5  *
6  * Written by Keith Marshall <keithmarshall@users.sourceforge.net>
7  * Copyright (C) 2013, 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 static const char *internal_error = "internal error";
52 #define MSG_INTERNAL_ERROR(MSG) "%s: "MSG_##MSG"\n", internal_error
53
54 #define MSG_INVALID_REQUEST     "invalid request code specified"
55 #define MSG_NOTIFY_MAINTAINER   "please report this to the mingw-get maintainer"
56
57 #define PROGRESS_METER_CLASS    setupProgressMeter
58
59 class PROGRESS_METER_CLASS: public pkgProgressMeter
60 {
61   /* Specialisation of the pkgProgressMeter class, through which
62    * the pkgXmlDocument::BindRepositories() method sends progress
63    * reports to the setup tool dialogue.
64    */
65   public:
66     PROGRESS_METER_CLASS( HWND, pkgXmlDocument & );
67     ~PROGRESS_METER_CLASS(){ dbase.DetachProgressMeter( this ); }
68
69     virtual int Annotate( const char *, ... );
70     virtual void SetRange( int, int );
71     virtual void SetValue( int );
72
73   private:
74     pkgXmlDocument &dbase;
75     HWND annotation, count, lim, frac, progress_bar;
76     void PutVal( HWND, const char *, ... );
77     int total;
78 };
79
80 /* This class requires a specialised constructor...
81  */
82 #define IDD( DLG, ITEM ) GetDlgItem( DLG, IDD_PROGRESS_##ITEM )
83 PROGRESS_METER_CLASS::PROGRESS_METER_CLASS( HWND owner, pkgXmlDocument &db ):
84 dbase( db ), annotation( IDD( owner, MSG ) ), progress_bar( IDD( owner, BAR ) ),
85 count( IDD( owner, VAL ) ), lim( IDD( owner, MAX ) ), frac( IDD( owner, PCT ) )
86 {
87   /* ...which binds the progress repporter directly to the
88    * invoking pkgXmlDocument class object, before setting the
89    * initial item count to zero, from a minimal anticipated
90    * total of one.
91    */
92   dbase.AttachProgressMeter( this );
93   SetRange( 0, 1 ); SetValue( 0 );
94 };
95
96 /* The remaining methods of the class are abstracted from the
97  * generic progress metering implementation.
98  */
99 #include "pmihook.cpp"
100
101 static inline
102 void initialise_profile( void )
103 {
104   /* Helper function to ensure that profile.xml exists in the
105    * mingw-get database directory, either as a pre-existing file,
106    * or by creating it as a copy of defaults.xml
107    */
108   int copyin, copyout;
109   const char *profile = xmlfile( profile_key, NULL );
110   if(  (profile != NULL) && (access( profile, F_OK ) != 0)
111   &&  ((copyout = set_output_stream( profile, 0644 )) >= 0)  )
112   {
113     /* No profile.xml file currently exists, but we are able
114      * to create one...
115      */
116     const char *default_profile;
117     if( ((default_profile = xmlfile( defaults_key, NULL )) != NULL)
118     &&  ((copyin = open( default_profile, _O_RDONLY | _O_BINARY )) >= 0)  )
119     {
120       /* ...whereas the defaults.xml DOES exist, and we are able
121        * to read it, so set up a transfer buffer, through which we
122        * may copy its content to profile.xml
123        */
124       char buf[BUFSIZ]; ssize_t count;
125       while( (count = read( copyin, buf, BUFSIZ )) > 0 )
126         write( copyout, buf, count );
127
128       /* When the entire content of defaults.xml has been copied,
129        * close both file streams, saving both files.
130        */
131       close( copyout );
132       close( copyin );
133     }
134     else
135     { /* We were unable to open defaults.xml for copying, but we
136        * already hold an open stream handle for profile.xml; close
137        * it, then unlink, to discard the resulting empty file.
138        */
139       close( copyout );
140       unlink( profile );
141     }
142     /* Attempting to open defaults.xml, whether successful or not,
143      * has left us with a heap memory allocation for its path name.
144      * We don't need it any more; free it so we don't leak memory.
145      */
146     free( (void *)(default_profile) );
147   }
148   /* Similarly, we have a heap memory allocation for the path name
149    * of profile.xml; we must also avoid leaking it.
150    */
151   free( (void *)(profile) );
152 }
153
154 static inline
155 void update_database( pkgSetupAction *setup )
156 {
157   /* Helper function to initiate an XML database update, based on
158    * a restricted (custom) profile, to ensure that the installation
159    * of mingw-get itself is properly recorded.
160    */
161   const char *dfile;
162   pkgXmlDocument dbase( dfile = xmlfile( setup_key ) );
163   if( dbase.IsOk() && (dbase.BindRepositories( false ) != NULL) )
164   {
165     /* Having successfully loaded the restricted profile, load the
166      * map of the installation it specifies, and update it to reflect
167      * the installation of the mingw-get packages we are currently
168      * installing.
169      */
170     dbase.LoadSystemMap();
171     setup->UpdateDatabase( dbase );
172   }
173   /* We're finished with the setup.xml profile; delete it, and free
174    * the memory which xmlfile() allocated to store its path name.
175    */
176   unlink( dfile );
177   free( (void *)(dfile) );
178 }
179
180 static inline
181 void update_catalogue( HWND owner )
182 {
183   /* Helper function to ensure that all locally installed catalogue
184    * files are synchronised with their latest versions, as hosted on
185    * their respective repository servers.
186    */
187   const char *dfile;
188   pkgXmlDocument dbase( dfile = xmlfile( profile_key ) );
189   if( dbase.IsOk() )
190   {
191     /* The XML package database has been successfully loaded;
192      * ensure that the local catalogue is synchronised with the
193      * with the up-to-date repository representation.
194      */
195     setupProgressMeter watch( owner, dbase );
196     dbase.BindRepositories( true );
197     watch.Annotate(
198         "Catalogue update completed; please check 'Details' pane for errors."
199       );
200   }
201   /* We're finished with the path name for the profile.xml file;
202    * release the memory which xmlfile() allocated to store it.
203    */
204   free( (void *)(dfile) );
205 }
206
207 EXTERN_C __declspec(dllexport)
208 void setup_hook( unsigned int request, va_list argv )
209 {
210   /* Single entry point API function, through which the setup tool
211    * directs all requests to its own delay-loaded DLL, and hence to
212    * mingw-get-0.dll itself.
213    */
214   switch( request )
215   { case SETUP_HOOK_DMH_BIND:
216       /* Initialisation hook, through which the setup tool makes its
217        * already initialised diagnostic message handler available to
218        * to DLL functions which it invokes.
219        */
220       dmh_bind( va_arg( argv, dmhTypeGeneric * ) );
221       break;
222
223     case SETUP_HOOK_POST_INSTALL:
224       /* This is the principal entry point, through which the setup
225        * tool hands off the final phases of mingw-get installation to
226        * the XML database management functions within the DLLs.
227        */
228       initialise_profile();
229       update_database( va_arg( argv, pkgSetupAction * ) );
230       update_catalogue( va_arg( argv, HWND ) );
231       break;
232
233     default:
234       /* We should never get to here; it's a programming error in
235        * the setup tool, if we do.
236        */
237       dmh_notify( DMH_ERROR, MSG_INTERNAL_ERROR( INVALID_REQUEST ) );
238       dmh_notify( DMH_ERROR, MSG_INTERNAL_ERROR( NOTIFY_MAINTAINER ) );
239   }
240 }
241
242 class pkgXmlNodeStack
243 {
244   /* A convenience class for managing a list of pkgXmlNodes
245    * as a LIFO stack.
246    */
247   public:
248     inline pkgXmlNodeStack *push( pkgXmlNode * );
249     inline pkgXmlNodeStack *pop( pkgXmlNode ** );
250
251   private:
252     pkgXmlNodeStack *next;
253     pkgXmlNode *entry;
254 };
255
256 inline pkgXmlNodeStack *pkgXmlNodeStack::push( pkgXmlNode *node )
257 {
258   /* Method to push a pkgXmlNode into a new frame, on the top of
259    * the stack, returning the new stack pointer.
260    */
261   pkgXmlNodeStack *rtn;
262   if( (rtn = new pkgXmlNodeStack) != NULL )
263   {
264     /* We successfully allocated storage space for the new stack
265      * entry; populate it with the specified pkgXmlNode date, and
266      * link it to the existing stack chain...
267      */
268     rtn->entry = node; rtn->next = this;
269
270     /* ...before returning the new top-of-stack pointer.
271      */
272     return rtn;
273   }
274   /* If we get to here, we were unable to expand the stack to
275    * accommodate any new entry; don't move the stack pointer.
276    */
277   return this;
278 }
279
280 inline pkgXmlNodeStack *pkgXmlNodeStack::pop( pkgXmlNode **node )
281 {
282   /* Method to pop a pkgXmlNode from the top of the stack, while
283    * saving a reference pointer for it, and returning the updated
284    * stack pointer.
285    */
286   if( this == NULL )
287     /*
288      * The stack is empty; there's nothing more to do!
289      */
290     return NULL;
291
292   /* When the stack is NOT empty, capture the reference pointer
293    * for the SECOND entry (if any), store the top entry, delete
294    * its stack frame, and return the new stack pointer.
295    */
296   pkgXmlNodeStack *rtn = next; *node = entry;
297   delete this; return rtn;
298 }
299
300 void pkgSetupAction::UpdateDatabase( pkgXmlDocument &dbase )
301 {
302   /* Method to ensure that the mingw-get package components which are
303    * specified in the setup actions list are recorded as "installed", in
304    * the installation database manifests.
305    */
306   if( this != NULL )
307   {
308     /* The setup actions list is not empty; ensure that we commence
309      * processing from its first entry...
310      */
311     pkgSetupAction *current = this;
312     while( current->prev != NULL ) current = current->prev;
313     dmh_notify( DMH_INFO, "%s: updating installation database\n", setup_key );
314     while( current != NULL )
315     {
316       /* ...then processing all entries sequentially, in turn,
317        * parse the package tarname specified in the current action
318        * entry, to identify the associated package name, component
319        * class and subsystem name.
320        */
321       const char *name_fmt = "%s-%s";
322       pkgSpecs lookup( current->package_name );
323       char lookup_name[1 + strlen( current->package_name )];
324       const char *component = lookup.GetComponentClass();
325       const char *subsystem = lookup.GetSubSystemName();
326       if( (component != NULL) && *component )
327         /*
328          * The package name is qualified by an explicit component
329          * name; form the composite package name string.
330          */
331         sprintf( lookup_name, name_fmt, lookup.GetPackageName(), component );
332       else
333         /* There is no explicit component name; just save a copy
334          * of the unqualified package name.
335          */
336         strcpy( lookup_name, lookup.GetPackageName() );
337
338       /* Locate the corresponding component package entry, if any,
339        * in the package catalogue.
340        */
341       if( dbase.FindPackageByName( lookup_name, subsystem ) != NULL )
342       {
343         /* Lookup was successful; now search the installation records,
344          * if any, for any matching package entry.
345          */
346         pkgXmlNodeStack *stack = NULL;
347         pkgXmlNode *sysroot = dbase.GetRoot()->GetSysRoot( subsystem );
348         pkgXmlNode *installed = sysroot->FindFirstAssociate( installed_key );
349         while( installed != NULL )
350         {
351           /* There is at least one installation record; walk the chain
352            * of all such records...
353            */
354           const char *tarname = installed->GetPropVal( tarname_key, NULL );
355           if( tarname != NULL )
356           {
357             /* ...extracting package and component names from the tarname
358              * specification within each...
359              */
360             pkgSpecs ref( tarname );
361             char ref_name[1 + strlen( tarname )];
362             if( ((component = ref.GetComponentClass()) != NULL) && *component )
363               /*
364                * ...once again forming the composite name, when applicable...
365                */
366               sprintf( ref_name, name_fmt, ref.GetPackageName(), component );
367             else
368               /* ...or simply storing the unqualified package name if not.
369                */
370               strcpy( ref_name, ref.GetPackageName() );
371
372             /* Check for a match between the installed package name, and
373              * the name we wish to record as newly installed...
374              */
375             if( (strcasecmp( ref_name, lookup_name ) == 0)
376             &&  (strcasecmp( tarname, current->package_name ) != 0)  )
377               /*
378                * ...pushing the current installation record on to the
379                * update stack, in case of a match...
380                */
381               stack = stack->push( installed );
382           }
383           /* ...then move on to the next installation record, if any.
384            */
385           installed = installed->FindNextAssociate( installed_key );
386         }
387
388         /* Create a temporary package "release" descriptor...
389          */
390         pkgXmlNode *reference_hook = new pkgXmlNode( release_key );
391         if( reference_hook != NULL )
392         {
393           /* ...which we may conveniently attach to the root
394            * of the XML catalogue tree.
395            */
396           dbase.GetRoot()->AddChild( reference_hook );
397           reference_hook->SetAttribute( tarname_key, current->package_name );
398
399           /* Run the installer...
400            */
401           pkgTarArchiveInstaller registration_server( reference_hook );
402           if( registration_server.IsOk() )
403           {
404             /* ...reporting the installation as a "registration" of
405              * the specified package, but...
406              */
407             dmh_notify( DMH_INFO, "%s: register %s\n",
408                 setup_key, current->package_name
409               );
410             /* ...noting that the package content has already been
411              * "installed" by the setup tool, but without recording
412              * any details, we run this without physically extracting
413              * any files, to capture the side effect of compiling an
414              * installation record.
415              */
416             registration_server.SaveExtractedFiles( false );
417             registration_server.Process();
418           }
419           /* With the installation record safely compiled, we may
420            * discard the temporary "release" descriptor from which
421            * we compiled it.
422            */
423           dbase.GetRoot()->DeleteChild( reference_hook );
424         }
425
426         /* When the update stack, constructed above, is not empty...
427          */
428         if( stack != NULL )
429         { while( stack != NULL )
430           {
431             /* ...pop each installation record, which is to be updated,
432              * off the update stack, in turn...
433              */
434             const char *tarname;
435             pkgXmlNode *installed;
436             stack = stack->pop( &installed );
437             if( (tarname = installed->GetPropVal( tarname_key, NULL )) != NULL )
438             {
439               /* ...identify its associated installed files manifest, and
440                * disassociate it from the sysroot of the current installation;
441                * (note that this automatically deletes the manifest itself, if
442                * it is associated with no other sysroots).
443                */
444               pkgManifest inventory( package_key, tarname );
445               inventory.DetachSysRoot( sysroot->GetPropVal( id_key, subsystem ) );
446             }
447             /* Delete the installation record from the current sysroot...
448              */
449             sysroot->DeleteChild( installed );
450           }
451           /* ...and mark the sysroot record as "modified", as a result of
452            * all preceding updates.
453            */
454           sysroot->SetAttribute( modified_key, value_yes );
455         }
456       }
457
458       /* Repeat for all packages with an associated setup action...
459        */
460       current = current->next;
461     }
462     /* ...and finally, report completion of all database updates, while also
463      * committing all recorded changes to disk storage.
464      */
465     dmh_notify( DMH_INFO, "%s: installation database updated\n", setup_key );
466     dbase.UpdateSystemMap();
467   }
468 }
469
470 /* $RCSfile$: end of file */