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
175 * installing; (note that we must take care not to attempt to
176 * update the database, if passed a NULL setup object).
178 dbase.LoadSystemMap();
179 if( setup != NULL ) setup->UpdateDatabase( dbase );
181 /* We're finished with the setup.xml profile; delete it, and free
182 * the memory which xmlfile() allocated to store its path name.
185 free( (void *)(dfile) );
189 void update_catalogue( HWND owner )
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.
196 pkgXmlDocument dbase( dfile = xmlfile( profile_key ) );
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.
203 setupProgressMeter watch( owner, dbase );
204 dbase.BindRepositories( true );
206 "Catalogue update completed; please check 'Details' pane for errors."
209 /* We're finished with the path name for the profile.xml file;
210 * release the memory which xmlfile() allocated to store it.
212 free( (void *)(dfile) );
216 int run_basic_system_installer( const wchar_t *dll_name )
218 /* Hook to emulate a subset of the mingw-get GUI installer
219 * capabilities, via an embedded subset of its functions.
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.
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 )
232 /* Show this window, and paint its initial content...
234 MainWindow.Show( SW_SHOW );
237 /* ...then invoke its message loop, ultimately returning the
238 * status code which prevails, when the user closes it.
240 return MainWindow.Invoked();
242 catch( dmh_exception &e )
244 /* Here, we handle any fatal exception which has been raised
245 * and identified by the diagnostic message handler...
247 MessageBox( NULL, e.what(), "WinMain", MB_ICONERROR );
250 catch( WTK::runtime_error &e )
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...
256 MessageBox( NULL, e.what(), "WinMain", MB_ICONERROR );
260 { /* ...and here, we diagnose any other error which we weren't
261 * able to explicitly identify.
263 MessageBox( NULL, "Unknown exception", "WinMain", MB_ICONERROR );
268 EXTERN_C __declspec(dllexport)
269 void setup_hook( unsigned int request, va_list argv )
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.
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.
281 dmh_bind( va_arg( argv, dmhTypeGeneric * ) );
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.
289 initialise_profile();
290 update_database( va_arg( argv, pkgSetupAction * ) );
291 update_catalogue( va_arg( argv, HWND ) );
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.
298 run_basic_system_installer( va_arg( argv, const wchar_t * ) );
302 /* We should never get to here; it's a programming error in
303 * the setup tool, if we do.
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 );
312 class pkgXmlNodeStack
314 /* A convenience class for managing a list of pkgXmlNodes
318 inline pkgXmlNodeStack *push( pkgXmlNode * );
319 inline pkgXmlNodeStack *pop( pkgXmlNode ** );
322 pkgXmlNodeStack *next;
326 inline pkgXmlNodeStack *pkgXmlNodeStack::push( pkgXmlNode *node )
328 /* Method to push a pkgXmlNode into a new frame, on the top of
329 * the stack, returning the new stack pointer.
331 pkgXmlNodeStack *rtn;
332 if( (rtn = new pkgXmlNodeStack) != NULL )
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...
338 rtn->entry = node; rtn->next = this;
340 /* ...before returning the new top-of-stack pointer.
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.
350 inline pkgXmlNodeStack *pkgXmlNodeStack::pop( pkgXmlNode **node )
352 /* Method to pop a pkgXmlNode from the top of the stack, while
353 * saving a reference pointer for it, and returning the updated
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.
362 pkgXmlNodeStack *rtn = next; *node = entry;
363 delete this; return rtn;
366 void pkgSetupAction::UpdateDatabase( pkgXmlDocument &dbase )
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.
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
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 )
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.
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 )
394 * The package name is qualified by an explicit component
395 * name; form the composite package name string.
397 sprintf( lookup_name, name_fmt, lookup.GetPackageName(), component );
399 /* There is no explicit component name; just save a copy
400 * of the unqualified package name.
402 strcpy( lookup_name, lookup.GetPackageName() );
404 /* Locate the corresponding component package entry, if any,
405 * in the package catalogue.
407 if( dbase.FindPackageByName( lookup_name, subsystem ) != NULL )
409 /* Lookup was successful; now search the installation records,
410 * if any, for any matching package entry.
412 pkgXmlNodeStack *stack = NULL;
413 pkgXmlNode *sysroot = dbase.GetRoot()->GetSysRoot( subsystem );
414 pkgXmlNode *installed = sysroot->FindFirstAssociate( installed_key );
415 while( installed != NULL )
417 /* There is at least one installation record; walk the chain
418 * of all such records...
420 const char *tarname = installed->GetPropVal( tarname_key, NULL );
421 if( tarname != NULL )
423 /* ...extracting package and component names from the tarname
424 * specification within each...
426 pkgSpecs ref( tarname );
427 char ref_name[1 + strlen( tarname )];
428 if( ((component = ref.GetComponentClass()) != NULL) && *component )
430 * ...once again forming the composite name, when applicable...
432 sprintf( ref_name, name_fmt, ref.GetPackageName(), component );
434 /* ...or simply storing the unqualified package name if not.
436 strcpy( ref_name, ref.GetPackageName() );
438 /* Check for a match between the installed package name, and
439 * the name we wish to record as newly installed...
441 if( (strcasecmp( ref_name, lookup_name ) == 0)
442 && (strcasecmp( tarname, current->package_name ) != 0) )
444 * ...pushing the current installation record on to the
445 * update stack, in case of a match...
447 stack = stack->push( installed );
449 /* ...then move on to the next installation record, if any.
451 installed = installed->FindNextAssociate( installed_key );
454 /* Create a temporary package "release" descriptor...
456 pkgXmlNode *reference_hook = new pkgXmlNode( release_key );
457 if( reference_hook != NULL )
459 /* ...which we may conveniently attach to the root
460 * of the XML catalogue tree.
462 dbase.GetRoot()->AddChild( reference_hook );
463 reference_hook->SetAttribute( tarname_key, current->package_name );
465 /* Run the installer...
467 pkgTarArchiveInstaller registration_server( reference_hook );
468 if( registration_server.IsOk() )
470 /* ...reporting the installation as a "registration" of
471 * the specified package, but...
473 dmh_notify( DMH_INFO, "%s: register %s\n",
474 setup_key, current->package_name
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.
482 registration_server.SaveExtractedFiles( false );
483 registration_server.Process();
485 /* With the installation record safely compiled, we may
486 * discard the temporary "release" descriptor from which
489 dbase.GetRoot()->DeleteChild( reference_hook );
492 /* When the update stack, constructed above, is not empty...
495 { while( stack != NULL )
497 /* ...pop each installation record, which is to be updated,
498 * off the update stack, in turn...
501 pkgXmlNode *installed;
502 stack = stack->pop( &installed );
503 if( (tarname = installed->GetPropVal( tarname_key, NULL )) != NULL )
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).
510 pkgManifest inventory( package_key, tarname );
511 inventory.DetachSysRoot( sysroot->GetPropVal( id_key, subsystem ) );
513 /* Delete the installation record from the current sysroot...
515 sysroot->DeleteChild( installed );
517 /* ...and mark the sysroot record as "modified", as a result of
518 * all preceding updates.
520 sysroot->SetAttribute( modified_key, value_yes );
524 /* Repeat for all packages with an associated setup action...
526 current = current->next;
528 /* ...and finally, report completion of all database updates, while also
529 * committing all recorded changes to disk storage.
531 dmh_notify( DMH_INFO, "%s: installation database updated\n", setup_key );
532 dbase.UpdateSystemMap();
535 /* $RCSfile$: end of file */