OSDN Git Service

ef86aac05ef913ec4dde7687ac6d89852a746002
[mingw/mingw-get.git] / src / pkgexec.cpp
1 /*
2  * pkgexec.cpp
3  *
4  * $Id$
5  *
6  * Written by Keith Marshall <keith@users.osdn.me>
7  * Copyright (C) 2009-2013, 2020, MinGW.org Project
8  *
9  *
10  * Implementation of package management task scheduler and executive.
11  *
12  *
13  * This is free software.  Permission is granted to copy, modify and
14  * redistribute this software, under the provisions of the GNU General
15  * Public License, Version 3, (or, at your option, any later version),
16  * as published by the Free Software Foundation; see the file COPYING
17  * for licensing details.
18  *
19  * Note, in particular, that this software is provided "as is", in the
20  * hope that it may prove useful, but WITHOUT WARRANTY OF ANY KIND; not
21  * even an implied WARRANTY OF MERCHANTABILITY, nor of FITNESS FOR ANY
22  * PARTICULAR PURPOSE.  Under no circumstances will the author, or the
23  * MinGW Project, accept liability for any damages, however caused,
24  * arising from the use of this software.
25  *
26  */
27 #include "dmh.h"
28 #include "mkpath.h"
29
30 #include "pkgbase.h"
31 #include "pkgkeys.h"
32 #include "pkginfo.h"
33 #include "pkgtask.h"
34 #include "pkgstat.h"
35 #include "pkgopts.h"
36 #include "pkgproc.h"
37
38 /* The following static member of the pkgSpinWait class provides
39  * the access hook through which the static core implementation of
40  * the base class methods may pass reports back to any derivative
41  * class object, while retaining the capability to issue reports
42  * even when no such object exists.
43  */
44 pkgSpinWait *pkgSpinWait::referrer = NULL;
45
46 int pkgSpinWait::Report( const char *fmt, ... )
47 {
48   /* Also declared as static, this directs printf() style reports
49    * to any existing derivative class object, while behaving as a
50    * no-op in the absence of any such object.
51    */
52   int count = 0;
53   if( referrer != NULL )
54   {
55     va_list argv;
56     va_start( argv, fmt );
57     count = referrer->DispatchReport( fmt, argv );
58     va_end( argv );
59   }
60   return count;
61 }
62
63 int pkgSpinWait::Indicator( void )
64 {
65   /* Once again, declared as static, this method provides a
66    * mechanism for spin-wait animation of any "%c" formatted
67    * field within a progress reporting message.
68    */
69   static const char *marker = "|/-\\";
70   return marker[ referrer->UpdateIndex() ];
71 }
72
73 EXTERN_C const char *action_name( unsigned long index )
74 {
75   /* Define the keywords used on the mingw-get command line,
76    * to specify the package management actions to be performed,
77    * mapping each to a unique action code index.
78    */
79   static const char* action_id[] =
80   {
81     "no change",        /* unused; zero cannot test true in a bitwise test  */
82     "remove",           /* remove a previously installed package            */
83     "install",          /* install a new package                            */
84     "upgrade",          /* upgrade previously installed packages            */
85
86     "list",             /* list packages and display related information    */
87     "show",             /* a synonym for "list"                             */
88
89     "update",           /* update local copy of repository catalogues       */
90     "licence",          /* retrieve licence sources from repository         */
91     "source"            /* retrieve package sources from repository         */
92   };
93
94   /* For specified "index", return a pointer to the associated keyword,
95    * or NULL, if "index" is outside the defined action code range.
96    */
97   return ((index >= 0) && (index < end_of_actions))
98     ? action_id[ index ]
99     : NULL;
100 }
101
102 EXTERN_C int action_code( const char* request )
103 {
104   /* Match an action keyword specified on the command line
105    * to an entry from the above list...
106    */
107   if( request != NULL )
108   {
109     int lencode = strlen( request );
110
111     int index, retval, matched;
112     for( index = matched = 0; index < end_of_actions; index++ )
113     {
114       /* Try all defined keywords in turn, until we run out
115        * of definitions.
116        */
117       if( (strncmp( request, action_name( index ), lencode ) == 0)
118         /*
119          * When we find a match, and it is the first...
120          */
121       &&  (++matched == 1)  )
122         /*
123          * ...then we record as the probable index to return.
124          */
125         retval = index;
126     }
127     if( matched > 1 )
128       /*
129        * We matched more than one valid keyword; reject them all.
130        */
131       dmh_notify( DMH_ERROR, "%s: action keyword is ambiguous\n", request );
132     
133     else if( matched == 1 )
134       /*
135        * We matched exactly one keyword; return its index value.
136        */
137       return retval;
138   }
139   /* If we get to here, the specified keyword was not uniquely matched;
140    * signal this, by returning -1.
141    */
142   return -1;
143 }
144
145 /* To circumvent MS-Windows restrictions on deletion and/or overwriting
146  * executable and shared object files, while their respective code is in
147  * use by a running application, and to facilitate upgrade of mingw-get
148  * itself, while it is the running application performing the upgrade,
149  * we introduce a "rites of passage" work around.  The first phase of
150  * this is invoked immediately on process start up, but the second
151  * phase is deferred...
152  */
153 #define IMPLEMENT_INITIATION_RITES  PHASE_TWO_RITES
154 #include "rites.c"
155 /*
156  * ...until we know for sure that a self-upgrade has been scheduled...
157  */
158 RITES_INLINE bool self_upgrade_rites( const char *name )
159 {
160   /* ...as determined by inspection of package names, and deferring
161    * the rite as "pending" until a request to process "mingw-get-bin"
162    * is actually received...
163    */
164   pkgSpecs pkg( name );
165   bool pending = ((name = pkg.GetComponentClass()) == NULL)
166     || (strcmp( name, "bin" ) != 0) || ((name = pkg.GetPackageName()) == NULL)
167     || (strcmp( name, "mingw-get" ) != 0);
168
169   if( ! pending )
170     /*
171      * We've just identified a request to process "mingw-get-bin";
172      * thus the requirement to invoke the "self upgrade rites" has
173      * now become immediate, so do it...
174      */
175     invoke_rites();
176
177   /* Finally, return the requirement state as it now is, whether it
178    * remains "pending" or not, so that the caller may avoid checking
179    * the requirement for invoking the "self upgrade rites" process,
180    * after it has already been requested.
181    */
182   return pending;
183 }
184
185 pkgActionItem::pkgActionItem( pkgActionItem *after, pkgActionItem *before )
186 {
187   /* Construct an appropriately initialised non-specific pkgActionItem...
188    */
189   flags = 0;            /* no specific action yet assigned */
190
191   min_wanted = NULL;    /* no minimum package version constraint... */
192   max_wanted = NULL;    /* nor any maximum version */
193
194   /* Initialise package selection to NONE, for this action... */
195   selection[to_remove] = selection[to_install] = NULL;
196
197   /* Insert this item at a specified location in the actions list.
198    */
199   prev = after;
200   next = before;
201 }
202
203 pkgActionItem*
204 pkgActionItem::Append( pkgActionItem *item )
205 {
206   /* Add an "item" to an ActionItems list, attaching it immediately
207    * after the item referenced by the "this" pointer; nominally "this"
208    * refers to the last entry in the list, resulting in a new item
209    * being appended to the list, but the implementation preserves
210    * integrity of any following list items, thus also fulfilling
211    * an "insert after this" function.
212    */
213   if( this == NULL )
214     /*
215      * No list exists yet;
216      * return "item" as first and only entry in new list.
217      */
218     return item;
219
220   /* Ensure "item" physically exists, or if not, create a generic
221    * placeholder in which to construct it...
222    */
223   if( (item == NULL) && ((item = new pkgActionItem()) == NULL) )
224     /*
225      * ...bailing out if no such placeholder can be created.
226      */
227     return NULL;
228
229   /* Maintain list integrity...
230    */
231   if( (item->next = next) != NULL )
232     /*
233      * ...moving any existing items which already follow the insertion
234      * point in the list structure, to follow the newly added "item".
235      */
236     next->prev = item;
237
238   /* Set the new item's own reference pointer, to establish its list
239    * attachment point...
240    */
241   item->prev = this;
242
243   /* ...and attach it immediately after that point.
244    */
245   return next = item;
246 }
247
248 pkgActionItem*
249 pkgActionItem::Insert( pkgActionItem *item )
250 {
251   /* Add an "item" to an ActionItems list, inserting it immediately
252    * before the item referenced by the "this" pointer.
253    */
254   if( this == NULL )
255     /*
256      * No list exists yet;
257      * return "item" as first and only entry in new list.
258      */
259     return item;
260
261   /* Ensure "item" physically exists, or if not, create a generic
262    * placeholder in which to construct it...
263    */
264   if( (item == NULL) && ((item = new pkgActionItem()) == NULL) )
265     /*
266      * ...bailing out if no such placeholder can be created.
267      */
268     return NULL;
269
270   /* Maintain list integrity...
271    */
272   if( (item->prev = prev) != NULL )
273     /*
274      * ...moving any existing items which already precede the insertion
275      * point in the list structure, to precede the newly added "item".
276      */
277     prev->next = item;
278
279   /* Set the new item's own reference pointer, to establish the item
280    * currently at the attachment point, as its immediate successor...
281    */
282   item->next = this;
283
284   /* ...and attach it, immediately preceding that point.
285    */
286   return prev = item;
287 }
288
289 pkgActionItem*
290 pkgActionItem::Schedule( unsigned long action, pkgActionItem& item )
291 {
292   /* Make a copy of an action item template (which may exist in
293    * a volatile scope) on the heap, assign the requested action,
294    * and return it for inclusion in the task schedule.
295    */
296   pkgActionItem *rtn = new pkgActionItem(); *rtn = item;
297   if( pkgOptions()->Test( OPTION_REINSTALL ) == OPTION_REINSTALL )
298     /*
299      * When the user specified the "--reinstall" option, either
300      * explicitly, or implied by "--download-only", (or even as a
301      * side effect of "--print-uris"), we MUST enable a download
302      * action, in case it is required to complete the request.
303      */
304     action |= ACTION_DOWNLOAD;
305   rtn->flags = action | (item.flags & ~ACTION_MASK);
306
307   /* The min_wanted and max_wanted properties, if defined, refer
308    * to dynamically allocated memory blocks, (on the heap); these
309    * must have only one action item owner; currently, the original
310    * item and the copy we've just made are both effective owners,
311    * and we want only the copy to retain this ownership, we must
312    * detach them from the original item.
313    */
314   item.min_wanted = item.max_wanted = NULL;
315
316   /* Similarly, we must transfer any linkage into the schedule of
317    * actions from the original item to the copy.
318    */
319   if( item.prev != NULL ) (item.prev)->next = rtn;
320   if( item.next != NULL ) (item.next)->prev = rtn;
321   item.prev = item.next = NULL;
322
323   /* Finally, we return the copy, leaving the ultimate disposal
324    * of the original to the caller's discretion.
325    */
326   return rtn;
327 }
328
329 void pkgActionItem::Assert
330 ( unsigned long set, unsigned long mask, pkgActionItem *schedule )
331 {
332   /* A method to manipulate the control, error trapping, and state
333    * flags for all items in the specified schedule of actions.
334    *
335    * Starting at the specified item, or the invoking class object
336    * item if no starting point is specified...
337    */
338   if( (schedule != NULL) || ((schedule = this) != NULL) )
339   {
340     /* ...and provided this starting point is not NULL, walk back
341      * to the first item in the associated task schedule...
342      */
343     while( schedule->prev != NULL ) schedule = schedule->prev;
344     while( schedule != NULL )
345     {
346       /* ...then, processing each scheduled task item in sequence,
347        * update the flags according to the specified mask and new
348        * bits to be set...
349        */
350       schedule->flags = (schedule->flags & mask) | set;
351       /*
352        * ...before moving on to the next item in the sequence.
353        */
354       schedule = schedule->next;
355     }
356   }
357 }
358
359 pkgActionItem*
360 pkgActionItem::GetReference( pkgActionItem& item )
361 {
362   /* Check for a prior reference, within the task schedule,
363    * for the package specified for processing by "item".
364    */
365   pkgXmlNode* pkg;
366   if( (pkg = item.Selection()->GetParent()) != NULL )
367   {
368     /* We have a pointer to the XML database entry which identifies
369      * the package containing the release specified as the selection
370      * associated with "item"; walk the chain of prior entries in
371      * the schedule...
372      */
373     for( pkgActionItem* item = this; item != NULL; item = item->prev )
374     {
375       /* ...and if we find another item holding an identical pointer,
376        * (i.e. to the same package), we return it...
377        */
378       if( item->Selection()->GetParent() == pkg )
379         return item;
380     }
381   }
382
383   /* If we get to here, there is no prior action scheduled for the
384    * specified package, so we return a NULL pointer...
385    */
386   return NULL;
387 }
388
389 pkgXmlNode *pkgActionItem::SelectIfMostRecentFit( pkgXmlNode *package )
390 {
391   /* Assign "package" as the "selection" for the referring action item,
392    * provided it matches the specified selection criteria and it represents
393    * a more recent release than any current selection.
394    */
395   pkgSpecs test( package );
396
397   /* Establish the selection criteria...
398    */
399   pkgSpecs min_fit( min_wanted );
400   pkgSpecs max_fit( max_wanted );
401
402   /* Choose one of the above, as a basis for identification of
403    * a correct package-component match...
404    */
405   pkgSpecs& fit = min_wanted ? min_fit : max_fit;
406
407   /* Initially assuming that it may not...
408    */
409   flags &= ~ACTION_MAY_SELECT;
410
411   /* ...verify that "package" fulfills the selection criteria...
412    */
413   if(  match_if_explicit( test.GetComponentClass(), fit.GetComponentClass() )
414   &&   match_if_explicit( test.GetComponentVersion(), fit.GetComponentVersion() )
415   && ((max_wanted == NULL) || ((flags & STRICTLY_LT) ? (test < max_fit) : (test <= max_fit)))
416   && ((min_wanted == NULL) || ((flags & STRICTLY_GT) ? (test > min_fit) : (test >= min_fit)))  )
417   {
418     /* We have the correct package component, and it fits within
419      * the allowed range of release versions...
420      */
421     pkgSpecs last( Selection() );
422     if( test > last )
423       /*
424        * It is also more recent than the current selection,
425        * so we now replace that...
426        */
427       selection[to_install] = package;
428
429     /* Regardless of whether we selected it, or not,
430      * mark "package" as a viable selection.
431      */
432     flags |= ACTION_MAY_SELECT;
433   }
434
435   /* Whatever choice we make, we return the resultant selection...
436    */
437   return Selection();
438 }
439
440 inline void pkgActionItem::SetPrimary( pkgActionItem* ref )
441 {
442   flags = ref->flags;
443   selection[ to_install ] = ref->selection[ to_install ];
444   selection[ to_remove ] = ref->selection[ to_remove ];
445 }
446
447 pkgActionItem* pkgXmlDocument::Schedule
448 ( unsigned long action, pkgActionItem& item, pkgActionItem* rank )
449 {
450   /* Schedule an action item with a specified ranking order in
451    * the action list, (or at the end of the list if no ranking
452    * position is specified)...
453    */
454   pkgActionItem *ref = rank ? rank : actions;
455
456   /* If we already have a prior matching item...
457    */
458   pkgActionItem *prior;
459   if( (prior = actions->GetReference( item )) != NULL )
460   {
461     /* ...then, when the current request refers to a primary action,
462      * we update the already scheduled request to reflect this...
463      */
464     if( (action & ACTION_PRIMARY) == ACTION_PRIMARY )
465       prior->SetPrimary( rank = ref->Schedule( action /* & ACTION_MASK */, item ) );
466 #   if 0
467       dmh_printf( "Schedule(0x%08x):%s(prior)\n",
468           prior->HasAttribute((unsigned long)(-1)),
469           prior->Selection()->ArchiveName()
470         );
471 #   endif
472     return prior;
473   }
474   /* ...otherwise, when this request produces a valid package reference,
475    * we raise a new scheduling request...
476    */
477   else if( ((ref = ref->Schedule( action, item )) != NULL)
478   &&   ((ref->Selection() != NULL) || (ref->Selection( to_remove ) != NULL)) )
479   {
480 #   if 0
481       dmh_printf( "Schedule(0x%08x):%s(new)\n",
482           ref->HasAttribute((unsigned long)(-1)),
483           ref->Selection()->ArchiveName()
484         );
485 #   endif
486     /* ...and, when successfully raised, add it to the task list...
487      */
488     if( rank )
489       /*
490        * ...at the specified ranking position, if any...
491        */
492       return rank->Insert( ref );
493
494     else
495       /* ...otherwise, at the end of the list.
496        */
497       return actions = actions->Append( ref );
498   }
499
500   /* If we get to here, then no new action was scheduled; we simply
501    * return the current insertion point in the task list.
502    */
503   return rank;
504 }
505
506 static __inline__ __attribute__((__always_inline__))
507 int reinstall_action_scheduled( pkgActionItem *package )
508 {
509   /* Helper function to identify scheduled actions which will
510    * result in reinstallation of the associated package.
511    */
512   return
513     ( pkgOptions()->Test( OPTION_REINSTALL )
514       && (package->Selection() == package->Selection( to_remove ))
515     );
516 }
517
518 void pkgActionItem::Execute( bool with_download )
519 {
520   if( this != NULL )
521   { pkgActionItem *current = this;
522     bool init_rites_pending = true;
523     while( current->prev != NULL ) current = current->prev;
524
525     /* Unless normal operations have been suppressed by the
526      * --print-uris option, (in order to obtain a list of all
527      * package URIs which the operation would access)...
528      */
529     if( pkgOptions()->Test( OPTION_PRINT_URIS ) < OPTION_PRINT_URIS )
530       do {
531            /* ...we initiate any download requests which may
532             * be necessary to fetch all required archives into
533             * the local package cache.
534             */
535            if( with_download )
536              DownloadArchiveFiles( current );
537          } while( SetAuthorities( current ) > 0 );
538
539     else while( current != NULL )
540     {
541       /* The --print-uris option is in effect: we simply loop
542        * over all packages with an assigned action, printing
543        * the associated download URI for each; (note that this
544        * will print the URI regardless of prior existence of
545        * the associated package in the local cache).
546        */
547       current->PrintURI( current->Selection()->ArchiveName() );
548       current = current->next;
549     }
550
551     /* If the --download-only option is in effect, then we have
552      * nothing more to do...
553      */
554     if( pkgOptions()->Test( OPTION_DOWNLOAD_ONLY ) != OPTION_DOWNLOAD_ONLY )
555     {
556       /* ...otherwise...
557        */
558       while( current != NULL )
559       {
560         /* ...processing only those packages with assigned actions...
561          */
562         if( (current->flags & ACTION_MASK) != 0 )
563         {
564           /* ...print a notification of the installation process to
565            * be performed, identifying the package to be processed.
566            */
567           const char *tarname;
568           pkgXmlNode *ref = current->Selection();
569           if( (tarname = ref->GetPropVal( tarname_key, NULL )) == NULL )
570           {
571             ref = current->Selection( to_remove );
572             tarname = ref->GetPropVal( tarname_key, value_unknown );
573           }
574           dmh_printf( "%s: %s\n", reinstall_action_scheduled( current )
575               ? "reinstall" : action_name( current->flags & ACTION_MASK ),
576               tarname
577             );
578
579           /* Package pre/post processing scripts may need to
580            * refer to the sysroot path for the package; place
581            * a copy in the environment, to facilitate this.
582            */
583           pkgSpecs lookup( tarname );
584           ref = ref->GetSysRoot( lookup.GetSubSystemName() );
585           const char *path = ref->GetPropVal( pathname_key, NULL );
586           if( path != NULL )
587           {
588             /* Format the sysroot path into an environment variable
589              * assignment specification; note that the recorded path
590              * name is likely to include macros such as "%R", so we
591              * filter it through mkpath(), to expand them.
592              */
593             const char *nothing = "";
594             char varspec_template[9 + strlen( path )];
595             sprintf( varspec_template, "SYSROOT=%s", path );
596             char varspec[mkpath( NULL, varspec_template, nothing, NULL )];
597             mkpath( varspec, varspec_template, nothing, NULL );
598             pkgPutEnv( PKG_PUTENV_DIRSEP_MSW, varspec );
599           }
600
601           /* Check for any outstanding requirement to invoke the
602            * "self upgrade rites" process, so that we may install an
603            * upgrade for mingw-get itself...
604            */
605           if( init_rites_pending )
606             /*
607              * ...discontinuing the check once this has been completed,
608              * since it need not be performed more than once.
609              */
610             init_rites_pending = self_upgrade_rites( tarname );
611
612           /* If we are performing an upgrade...
613            */
614           if( ((current->flags & ACTION_MASK) == ACTION_UPGRADE)
615           /*
616            * ...and the latest version of the package is already installed...
617            */
618           &&  (current->Selection() == current->Selection( to_remove ))
619           /*
620            * ...and the `--reinstall' option hasn't been specified...
621            */
622           &&  (pkgOptions()->Test( OPTION_REINSTALL ) == 0)  )
623             /*
624              * ...then simply report the up-to-date status...
625              */
626             dmh_notify( DMH_INFO, "package %s is up to date\n", tarname );
627
628           else
629           { /* ...otherwise, proceed to perform remove and install
630              * operations, as appropriate.
631              */
632             if(   reinstall_action_scheduled( current )
633             ||  ((current->flags & ACTION_REMOVE) == ACTION_REMOVE)  )
634             {
635               /* The selected package has been marked for removal, either
636                * explicitly, or as an implicit prerequisite for upgrade, or
637                * in preparation for reinstallation.
638                */
639               pkgRemove( current );
640             }
641
642             if( (current->flags & ACTION_INSTALL) == ACTION_INSTALL )
643             {
644               /* The selected package has been marked for installation,
645                * either explicitly, or implicitly to complete a package upgrade.
646                */
647               pkgXmlNode *tmp = current->Selection( to_remove );
648               if(   reinstall_action_scheduled( current )
649               ||  ((current->flags & ACTION_MASK) == ACTION_UPGRADE)  )
650                 current->selection[ to_remove ] = NULL;
651               pkgInstall( current );
652               current->selection[ to_remove ] = tmp;
653             }
654           }
655         }
656         /* Proceed to the next package, if any, with scheduled actions.
657          */
658         pkgSpinWait::Report( "Processing... (%c)", pkgSpinWait::Indicator() );
659         current = current->next;
660       }
661     }
662   }
663 }
664
665 pkgActionItem *pkgActionItem::Clear( pkgActionItem *schedule, unsigned long mask )
666 {
667   /* Method to remove those action items which have no attribute flags in common
668    * with the specified mask, from the schedule; return the residual schedule of
669    * items, if any, which were not removed.  (Note that specifying a mask with a
670    * value of 0UL, which is the default, results in removal of all items).
671    */
672   pkgActionItem *residual = NULL;
673
674   /* Starting at the specified item, or the invoking class object item
675    * if no starting point is specified...
676    */
677   if( (schedule != NULL) || ((schedule = this) != NULL) )
678   {
679     /* ...and provided this starting point is not NULL, walk back to
680      * the first item in the associated task schedule...
681      */
682     while( schedule->prev != NULL ) schedule = schedule->prev;
683     while( schedule != NULL )
684     {
685       /* ...then, processing each scheduled task item in sequence, and
686        * keeping track of the next to be processed...
687        */
688       pkgActionItem *nextptr = schedule->next;
689       if( (schedule->flags & mask) == 0 )
690         /*
691          * ...delete each which doesn't match any masked attribute...
692          */
693         delete schedule;
694
695       else
696         /* ...otherwise add it to the residual schedule.
697          */
698         residual = schedule;
699
700       /* In either event, move on to the next item in sequence, if any.
701        */
702       schedule = nextptr;
703     }
704   }
705   /* Ultimately, return a pointer to the last item added to the residual
706    * schedule, or NULL if all items were deleted.
707    */
708   return residual;
709 }
710
711 pkgActionItem::~pkgActionItem()
712 {
713   /* Destructor...
714    * The package version range selectors, "min_wanted" and "max_wanted",
715    * are always allocated storage space on the heap; we need to free that,
716    * before we destroy the reference pointers.
717    */
718   if( (max_wanted != NULL) && (max_wanted != min_wanted) )
719     /*
720      * "max_wanted" is non-NULL, and is distinct, (i.e. it doesn't
721      * represent an equality constraint which shares a reference with
722      * "min_wanted"); we need to free it independently.
723      */
724     free( (void *)(max_wanted) );
725
726   if( min_wanted != NULL )
727     /*
728      * "min_wanted" is non-NULL; we don't care if it is distinct,
729      * because if not, freeing it is required anyway, to also free
730      * the same memory referenced by "max_wanted".
731      */
732     free( (void *)(min_wanted) );
733
734   /* Also ensure that we preserve the integrity of any linked list of
735    * action items in which this item participates, by detaching this
736    * item from the pointer chain.
737    */
738   if( prev != NULL ) prev->next = next;
739   if( next != NULL ) next->prev = prev;
740 }
741
742 /*
743  ****************
744  *
745  * Implementation of processing hooks, for handling pre/post-install
746  * and pre/post-remove scripts.
747  *
748  */
749 #include "lua.hpp"
750 #include <process.h>
751
752 static const char *action_key = "action";
753 static const char *normal_key = "normal";
754
755 #define LUA_INLINE static inline __attribute__((__always_inline__))
756
757 LUA_INLINE bool init_lua_path()
758 # define LUA_LIBEXEC_PATH  "\\libexec\\mingw-get\\?.lua"
759 {
760   /* A one time initialisation hook, to ensure that the built-in Lua script
761    * interpreter will load scripts from the libexec directory associated with
762    * the running mingw-get.exe instance.
763    */
764   putenv( "LUA_PATH=!\\?.lua;!" LUA_LIBEXEC_PATH ";!\\.." LUA_LIBEXEC_PATH );
765   return true;
766 }
767
768 LUA_INLINE bool lua_isstringarg( lua_State *interpreter, int arg_index )
769 {
770   /* Convenience function to check if a particular argument was passed
771    * from Lua, and if so, if it has a valid string representation.
772    */
773   return lua_isnoneornil( interpreter, arg_index ) ? false
774     : lua_isstring( interpreter, arg_index );
775 }
776
777 static int lua_wsh_libexec_path( lua_State *interpreter )
778 {
779   /* Implementation for the Lua wsh.libexec_path function; it supports
780    * usage conforming to either of the function prototypes:
781    *
782    *    wsh.libexec_path( script )
783    *    wsh.libexec_path( script, subsystem )
784    *
785    * returning the absolute file system path to "script", within the
786    * libexec tree for the applicable subsystem, (or for the system in
787    * general, if no "subsystem" argument is specified).
788    */
789   const char *approot = approot_path();
790   const char *script = lua_tostring( interpreter, 1 );
791
792   if( lua_isstringarg( interpreter, 2 ) )
793   {
794     /* This is the case where a "subsystem" is specified, so we encode
795      * the applicable subsystem inclusive path name...
796      */
797     const char *path = "%slibexec\\%s\\%s";
798     const char *subsystem = lua_tostring( interpreter, 2 );
799     char ref[1 + snprintf( NULL, 0, path, approot, subsystem, script )];
800     snprintf( ref, sizeof( ref ), path, approot, subsystem, script );
801
802     /* ...which we then pass back to the Lua caller.
803      */
804     lua_pushstring( interpreter, ref );
805   }
806   else
807   { /* This is the case where no subsystem has been specified,
808      * so we encode the general system libexec path name...
809      */
810     const char *path = "%slibexec\\%s";
811     char ref[1 + snprintf( NULL, 0, path, approot, script )];
812     snprintf( ref, sizeof( ref ), path, approot, script );
813
814     /* ...again passing it back to the Lua caller.
815      */
816     lua_pushstring( interpreter, ref );
817   }
818   /* In either case, we have one result to pass back.
819    */
820   return 1;
821 }
822
823 static int lua_wsh_execute( lua_State *interpreter )
824 {
825   /* Implementation for the Lua wsh.execute function; it conforms to
826    * an effective function prototype equivalent to:
827    *
828    *    wsh.execute( command )
829    *
830    * delivering a capability similar to os.execute, but using wscript
831    * as the command interpreter, rather than the system shell.
832    */
833   if( lua_isstringarg( interpreter, 1 ) )
834   {
835     /* If no "command" is specified, we silently process this as a no-op;
836      * when a command IS specified, we hand it off to the interpreter.
837      */
838     const char *wsh = "wscript", *mode = "-nologo";
839     spawnlp( _P_WAIT, wsh, wsh, mode, lua_tostring( interpreter, 1 ), NULL );
840   }
841   /* Either way, we have nothing to return to the Lua caller.
842    */
843   return 0;
844 }
845
846 static int luaload_wsh( lua_State *interpreter )
847 {
848   /* Declare the functions provided by our Windows Script Host
849    * interface, wrapping them into the Lua "wsh" module...
850    */
851   static struct luaL_Reg wsh_function_registry[] =
852   {
853     /* Lua Name         Handler Function     */
854     /* --------------   -------------------- */
855     {  "execute",       lua_wsh_execute      },
856     {  "libexec_path",  lua_wsh_libexec_path },
857     {   NULL,           NULL                 }
858   };
859
860   /* ...and register the module within the active interpreter.
861    */
862   luaL_newlib( interpreter, wsh_function_registry );
863   return 1;
864 }
865
866 int pkgXmlNode::DispatchScript
867 ( int status, const char *context, const char *priority, pkgXmlNode *action )
868 {
869   /* Private method, called by InvokeScript(), to hand-off each script
870    * fragment from the requesting XML node, with class attribute matching
871    * the requested context and precedence matching the requested priority,
872    * for execution by the embedded lua interpreter.
873    */
874   lua_State *interpreter = NULL;
875   static const char *priority_key = "precedence";
876   static bool lua_path_setup = false;
877
878   if( ! lua_path_setup )
879     /*
880      * The Lua script path hasn't been initialised yet; do it now!
881      */
882     lua_path_setup = init_lua_path();
883
884   while( action != NULL )
885   {
886     /* We have at least one remaining script fragment, attached to the
887      * current XML node, which is a potential candidate for execution...
888      */
889     if( (strcmp( context, action->GetPropVal( class_key, value_none )) == 0)
890     &&  (strcmp( priority, action->GetPropVal( priority_key, normal_key )) == 0)  )
891     {
892       /* ...and it does fit the current context and precedence; if we
893        * have not yet attached an interpreter to this node, then...
894        */
895       if( (interpreter == NULL) && ((interpreter = luaL_newstate()) != NULL) )
896       {
897         /* ...start one now, initialise it by loading the standard
898          * lua libraries...
899          */
900         luaL_openlibs( interpreter );
901
902         /* ...and register our Windows Script Host interface...
903          */
904         luaL_requiref( interpreter, "wsh", luaload_wsh, 1 );
905       }
906       /* ...then hand off the current script fragment to this active
907        * lua interpreter...
908        */
909       if( (status = luaL_dostring( interpreter, action->GetText() )) != 0 )
910         /*
911          * ...reporting any errors through mingw-get's standard
912          * diagnostic message handler.
913          */
914         dmh_printf( "lua error in %s script:\n%s\n", context,
915             lua_tostring( interpreter, -1 )
916           );
917     }
918
919     /* Check for any further script fragments attached to the current node.
920      */
921     action = action->FindNextAssociate( action_key );
922   }
923
924   /* Before leaving this node...
925    */
926   if( interpreter != NULL )
927     /*
928      * ...close any active lua interpreter which we may have attached.
929      */
930     lua_close( interpreter );
931
932   /* Finally, return the execution status reported by lua, from the last
933    * script fragment executed within the scope of the current node.
934    */
935   return status;
936 }
937
938 int pkgXmlNode::InvokeScript( int status, const char *context )
939 {
940   /* Private component of the implementation for the public
941    * InvokeScript() method; it checks for the existence of at
942    * least one script attached to the invoking XML node, then
943    * hands off processing of the entire script collection...
944    */
945   pkgXmlNode *action = FindFirstAssociate( action_key );
946
947   /* ...first processing any, in the requested context, which are
948    * designated as having "immediate" precedence...
949    */
950   status = DispatchScript( status, context, "immediate", action );
951   /*
952    * ...then, traversing the XML hierarchy towards the root...
953    */
954   if( this != GetDocumentRoot() )
955     /*
956      * ...processing any script fragments, in the requested context,
957      * which are attached to any container nodes...
958      */
959     status = GetParent()->InvokeScript( status, context );
960
961   /* ...and finally, process any others attached to the current node,
962    * in the requested context, having "normal" precedence.
963    */
964   return DispatchScript( status, context, normal_key, action );
965 }
966
967 /* $RCSfile$: end of file */