6 * Written by Keith Marshall <keith@users.osdn.me>
7 * Copyright (C) 2013, 2020, MinGW.org Project
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.
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.
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.
29 static const char *setup_key = "setup";
31 #define WIN32_LEAN_AND_MEAN
32 #define IMPLEMENTATION_LEVEL PACKAGE_BASE_COMPONENT
47 #include <sys/types.h>
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.
56 bool AppWindowMaker::SetupToolInvoked = true;
58 static const char *internal_error = "internal error";
59 #define MSG_INTERNAL_ERROR(MSG) "%s: " MSG_ ## MSG "\n", internal_error
61 #define MSG_INVALID_REQUEST "invalid request code specified"
62 #define MSG_NOTIFY_MAINTAINER "please report this to the mingw-get maintainer"
64 #define PROGRESS_METER_CLASS setupProgressMeter
66 class PROGRESS_METER_CLASS: public pkgProgressMeter
68 /* Specialisation of the pkgProgressMeter class, through which
69 * the pkgXmlDocument::BindRepositories() method sends progress
70 * reports to the setup tool dialogue.
73 PROGRESS_METER_CLASS( HWND, pkgXmlDocument & );
74 ~PROGRESS_METER_CLASS(){ dbase.DetachProgressMeter( this ); }
76 virtual int Annotate( const char *, ... );
77 virtual void SetRange( int, int );
78 virtual void SetValue( int );
81 pkgXmlDocument &dbase;
82 HWND annotation, count, lim, frac, progress_bar;
83 void PutVal( HWND, const char *, ... );
87 /* This class requires a specialised constructor...
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 ) )
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
99 dbase.AttachProgressMeter( this );
100 SetRange( 0, 1 ); SetValue( 0 );
103 /* The remaining methods of the class are abstracted from the
104 * generic progress metering implementation.
106 #include "pmihook.cpp"
109 void initialise_profile( void )
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
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) )
120 /* No profile.xml file currently exists, but we are able
123 const char *default_profile;
124 if( ((default_profile = xmlfile( defaults_key, NULL )) != NULL)
125 && ((copyin = open( default_profile, _O_RDONLY | _O_BINARY )) >= 0) )
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
131 char buf[BUFSIZ]; ssize_t count;
132 while( (count = read( copyin, buf, BUFSIZ )) > 0 )
133 write( copyout, buf, count );
135 /* When the entire content of defaults.xml has been copied,
136 * close both file streams, saving both files.
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.
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.
153 free( (void *)(default_profile) );
155 /* Similarly, we have a heap memory allocation for the path name
156 * of profile.xml; we must also avoid leaking it.
158 free( (void *)(profile) );
162 void update_database( pkgSetupAction *setup )
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.
169 pkgXmlDocument dbase( dfile = xmlfile( setup_key ) );
170 if( dbase.IsOk() && (dbase.BindRepositories( false ) != NULL) )
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
177 dbase.LoadSystemMap();
178 setup->UpdateDatabase( dbase );
180 /* We're finished with the setup.xml profile; delete it, and free
181 * the memory which xmlfile() allocated to store its path name.
184 free( (void *)(dfile) );
188 void update_catalogue( HWND owner )
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.
195 pkgXmlDocument dbase( dfile = xmlfile( profile_key ) );
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.
202 setupProgressMeter watch( owner, dbase );
203 dbase.BindRepositories( true );
205 "Catalogue update completed; please check 'Details' pane for errors."
208 /* We're finished with the path name for the profile.xml file;
209 * release the memory which xmlfile() allocated to store it.
211 free( (void *)(dfile) );
215 int run_basic_system_installer( const wchar_t *dll_name )
217 /* Hook to emulate a subset of the mingw-get GUI installer
218 * capabilities, via an embedded subset of its functions.
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.
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 )
231 /* Show this window, and paint its initial content...
233 MainWindow.Show( SW_SHOW );
236 /* ...then invoke its message loop, ultimately returning the
237 * status code which prevails, when the user closes it.
239 return MainWindow.Invoked();
241 catch( dmh_exception &e )
243 /* Here, we handle any fatal exception which has been raised
244 * and identified by the diagnostic message handler...
246 MessageBox( NULL, e.what(), "WinMain", MB_ICONERROR );
249 catch( WTK::runtime_error &e )
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...
255 MessageBox( NULL, e.what(), "WinMain", MB_ICONERROR );
259 { /* ...and here, we diagnose any other error which we weren't
260 * able to explicitly identify.
262 MessageBox( NULL, "Unknown exception", "WinMain", MB_ICONERROR );
267 EXTERN_C __declspec(dllexport)
268 void setup_hook( unsigned int request, va_list argv )
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.
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.
280 dmh_bind( va_arg( argv, dmhTypeGeneric * ) );
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.
288 initialise_profile();
289 update_database( va_arg( argv, pkgSetupAction * ) );
290 update_catalogue( va_arg( argv, HWND ) );
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.
297 run_basic_system_installer( va_arg( argv, const wchar_t * ) );
301 /* We should never get to here; it's a programming error in
302 * the setup tool, if we do.
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 );
311 class pkgXmlNodeStack
313 /* A convenience class for managing a list of pkgXmlNodes
317 inline pkgXmlNodeStack *push( pkgXmlNode * );
318 inline pkgXmlNodeStack *pop( pkgXmlNode ** );
321 pkgXmlNodeStack *next;
325 inline pkgXmlNodeStack *pkgXmlNodeStack::push( pkgXmlNode *node )
327 /* Method to push a pkgXmlNode into a new frame, on the top of
328 * the stack, returning the new stack pointer.
330 pkgXmlNodeStack *rtn;
331 if( (rtn = new pkgXmlNodeStack) != NULL )
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...
337 rtn->entry = node; rtn->next = this;
339 /* ...before returning the new top-of-stack pointer.
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.
349 inline pkgXmlNodeStack *pkgXmlNodeStack::pop( pkgXmlNode **node )
351 /* Method to pop a pkgXmlNode from the top of the stack, while
352 * saving a reference pointer for it, and returning the updated
357 * The stack is empty; there's nothing more to do!
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.
365 pkgXmlNodeStack *rtn = next; *node = entry;
366 delete this; return rtn;
369 void pkgSetupAction::UpdateDatabase( pkgXmlDocument &dbase )
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.
377 /* The setup actions list is not empty; ensure that we commence
378 * processing from its first entry...
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 )
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.
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 )
397 * The package name is qualified by an explicit component
398 * name; form the composite package name string.
400 sprintf( lookup_name, name_fmt, lookup.GetPackageName(), component );
402 /* There is no explicit component name; just save a copy
403 * of the unqualified package name.
405 strcpy( lookup_name, lookup.GetPackageName() );
407 /* Locate the corresponding component package entry, if any,
408 * in the package catalogue.
410 if( dbase.FindPackageByName( lookup_name, subsystem ) != NULL )
412 /* Lookup was successful; now search the installation records,
413 * if any, for any matching package entry.
415 pkgXmlNodeStack *stack = NULL;
416 pkgXmlNode *sysroot = dbase.GetRoot()->GetSysRoot( subsystem );
417 pkgXmlNode *installed = sysroot->FindFirstAssociate( installed_key );
418 while( installed != NULL )
420 /* There is at least one installation record; walk the chain
421 * of all such records...
423 const char *tarname = installed->GetPropVal( tarname_key, NULL );
424 if( tarname != NULL )
426 /* ...extracting package and component names from the tarname
427 * specification within each...
429 pkgSpecs ref( tarname );
430 char ref_name[1 + strlen( tarname )];
431 if( ((component = ref.GetComponentClass()) != NULL) && *component )
433 * ...once again forming the composite name, when applicable...
435 sprintf( ref_name, name_fmt, ref.GetPackageName(), component );
437 /* ...or simply storing the unqualified package name if not.
439 strcpy( ref_name, ref.GetPackageName() );
441 /* Check for a match between the installed package name, and
442 * the name we wish to record as newly installed...
444 if( (strcasecmp( ref_name, lookup_name ) == 0)
445 && (strcasecmp( tarname, current->package_name ) != 0) )
447 * ...pushing the current installation record on to the
448 * update stack, in case of a match...
450 stack = stack->push( installed );
452 /* ...then move on to the next installation record, if any.
454 installed = installed->FindNextAssociate( installed_key );
457 /* Create a temporary package "release" descriptor...
459 pkgXmlNode *reference_hook = new pkgXmlNode( release_key );
460 if( reference_hook != NULL )
462 /* ...which we may conveniently attach to the root
463 * of the XML catalogue tree.
465 dbase.GetRoot()->AddChild( reference_hook );
466 reference_hook->SetAttribute( tarname_key, current->package_name );
468 /* Run the installer...
470 pkgTarArchiveInstaller registration_server( reference_hook );
471 if( registration_server.IsOk() )
473 /* ...reporting the installation as a "registration" of
474 * the specified package, but...
476 dmh_notify( DMH_INFO, "%s: register %s\n",
477 setup_key, current->package_name
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.
485 registration_server.SaveExtractedFiles( false );
486 registration_server.Process();
488 /* With the installation record safely compiled, we may
489 * discard the temporary "release" descriptor from which
492 dbase.GetRoot()->DeleteChild( reference_hook );
495 /* When the update stack, constructed above, is not empty...
498 { while( stack != NULL )
500 /* ...pop each installation record, which is to be updated,
501 * off the update stack, in turn...
504 pkgXmlNode *installed;
505 stack = stack->pop( &installed );
506 if( (tarname = installed->GetPropVal( tarname_key, NULL )) != NULL )
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).
513 pkgManifest inventory( package_key, tarname );
514 inventory.DetachSysRoot( sysroot->GetPropVal( id_key, subsystem ) );
516 /* Delete the installation record from the current sysroot...
518 sysroot->DeleteChild( installed );
520 /* ...and mark the sysroot record as "modified", as a result of
521 * all preceding updates.
523 sysroot->SetAttribute( modified_key, value_yes );
527 /* Repeat for all packages with an associated setup action...
529 current = current->next;
531 /* ...and finally, report completion of all database updates, while also
532 * committing all recorded changes to disk storage.
534 dmh_notify( DMH_INFO, "%s: installation database updated\n", setup_key );
535 dbase.UpdateSystemMap();
539 /* $RCSfile$: end of file */