OSDN Git Service

Handle "%" wildcard matches in package and subsystem version strings.
[mingw/mingw-get.git] / src / pkgreqs.cpp
1 /*
2  * pkgreqs.cpp
3  *
4  * $Id$
5  *
6  * Written by Keith Marshall <keithmarshall@users.sourceforge.net>
7  * Copyright (C) 2009, 2010, MinGW Project
8  *
9  *
10  * Implements the SetRequirements() method for the pkgActionItem class,
11  * together with additional components of the pkgSpecs class which are
12  * required specifically to support it.
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 #include "pkginfo.h"
30 #include "pkgkeys.h"
31 #include "pkgtask.h"
32
33 #include <stdlib.h>
34 #include <string.h>
35
36 /*
37  ******************
38  *
39  * Class Implementation: pkgSpecs; SetProperty and GetTarName methods
40  *
41  */
42
43 static inline
44 int buflen( pkginfo_t specs )
45 {
46   /* Local helper function, to determine the size of the buffer used
47    * to store the data content of all pkgSpecs fields; (it is also the
48    * length of the C string representing the associated package tarname,
49    * INCLUDING its NUL terminator).
50    */
51   int index = PACKAGE_TAG_COUNT;
52   while( (index-- > 0) && (specs[index] == NULL) )
53     /*
54      * Locate the last populated data field in the content buffer...
55      */ ;
56     /* ...then compute the total buffer length, from the start of
57      * the first field, up to the NUL terminator of the last.
58      */
59   return specs[index] ? specs[index] - *specs + strlen( specs[index] ) + 1 : 0;
60 }
61
62 static inline
63 int fieldlen( const char *string )
64 {
65   /* NULL-safe local helper function to determine the length of
66    * the C string representing a pkgSpecs data field, (EXCLUDING
67    * its NUL terminator); returns zero in the case of an unassigned
68    * field, identified by a NULL reference pointer.
69    */
70   return (string == NULL) ? 0 : strlen( string );
71 }
72
73 static inline
74 char *fieldcpy( char *dest, const char *src )
75 {
76   /* Local helper function to copy the content of a single
77    * pkgSpecs data field...
78    */
79   while( (*dest++ = *src++) != '\0' )
80     /*
81      * ...copying byte by byte, including the terminating NUL,
82      * to the destination buffer...
83      */ ;
84   /* ...and ultimately, return the next available location
85    * within the destination buffer.
86    */
87   return dest;
88 }
89
90 static inline
91 char *bufcpy( int index, int end, char *buf, pkginfo_t specs )
92 {
93   /* Local helper function to copy a specified field range from
94    * its original location within the pkgSpecs content buffer, to
95    * an alternate location specified by "buf".
96    */
97   do { if( specs[index] != NULL )
98          /*
99           * Copy each non-empty data field...
100           */
101          buf = fieldcpy( buf, specs[index] );
102
103        /* ...until all specified fields in the source range
104         * have been copied.
105         */
106      } while( ++index < end );
107
108   /* Finally, return the location at which any further data should
109    * be appended to the data just copied.
110    */
111   return buf;
112 }
113
114 const char *pkgSpecs::SetProperty( int index, const char *repl )
115 {
116   /* A private method to modify the content of any single data field
117    * within a pkgSpecs data buffer; it provides the core implementation
118    * for each of the public inline field manipulator methods.
119    */
120   char *p;
121
122   /* We begin by computing the current size of the content buffer,
123    * and the size it will become after making the change.
124    */
125   int oldlen = buflen( specs );
126   int newlen = oldlen + fieldlen( repl ) - fieldlen( specs[index] );
127
128   if( newlen > oldlen )
129   {
130     /* The buffer size must increase; thus, there is insufficient space
131      * at the location of the field to be modified, to accomodate its new
132      * value, without corrupting the content of following fields.  To avoid
133      * this, we use a temporary buffer in which we construct an image of
134      * the modified content...
135      */
136     char newbuf[ newlen ];
137     /*
138      * ...first copying the content of all fields which precede that
139      * which is to be modified...
140      */
141     p = bufcpy( 0, index, newbuf, specs );
142     /*
143      * ...appending the new value to be assigned to the modified field...
144      */
145     p = fieldcpy( p, repl );
146     /*
147      * ...and then the content of any following unmodified fields.
148      */
149     bufcpy( index + 1, PACKAGE_TAG_COUNT, p, specs );
150
151     /* Having created this temporary image of the content buffer, as
152      * it is to become, adjust the allocation size of the original buffer,
153      * and copy its modified content into place.
154      */
155     if( (p = (char *)(content = realloc( content, newlen ))) != NULL )
156       memcpy( p, newbuf, newlen );
157   }
158
159   else
160   { /* In this case, the size of content buffer will either shrink, or
161      * it will remain unchanged; we may modify it in place, beginning at
162      * the location of the field which is to be modified, by writing its
163      * new value, if any, into place.
164      */
165     p = specs[index];
166     if( (repl != NULL) && (*repl != '\0') )
167       p = fieldcpy( p, repl );
168
169     /* If the new content of the modified field is exactly the same length
170      * as the content it has replaced, then the overall size of the content
171      * buffer does not change...
172      */
173     if( newlen == oldlen )
174       /*
175        * ...so we have nothing more to do, to complete the modification...
176        */
177       return (char *)(content);
178
179     /* ...otherwise, there is residual data from the original content at
180      * the location now indicated by "p", and preceding the start of the
181      * content of the next populated field, (if any); clear this, moving
182      * the content of all following fields up to fill the gap...
183      */
184     bufcpy( index + 1, PACKAGE_TAG_COUNT, p, specs );
185
186     /* ...and adjust the allocation size to the reduced data length.
187      */
188     p = (char *)(content = realloc( content, newlen ));
189   }
190
191   /* Irrespective of which of the preceding paths we followed to get here,
192    * the size of the content buffer was adjusted, so...
193    */
194   if( p != NULL )
195   {
196     /* ...provided it was not lost altogether...
197      */
198     if( (repl == NULL) || (*repl == '\0') )
199       /*
200        * ...we either mark the content of the modified field as "deleted",
201        * when appropriate...
202        */
203       specs[index] = NULL;
204
205     else
206       /* ...or we ensure that it is marked as having SOME content, in case
207        * it may have been empty beforehand; (note that this does NOT assign
208        * the correct pointer value -- any arbitrary non-NULL pointer value
209        * will suffice here, so we just adopt the physical start address of
210        * the content buffer).
211        */
212       specs[index] = p;
213
214     /* Now, we walk through the content buffer, to fix up the data pointers
215      * for the content of any fields for which the start address may have
216      * changed, as a result of buffer size adjustment...
217      */
218     for( index = 0; index < PACKAGE_TAG_COUNT; index++ )
219     {
220       /* ...thus, for each non-empty field...
221        */
222       if( specs[index] != NULL )
223       {
224         /* ...assign the start address of the next available block of
225          * populated content data...
226          */
227         specs[index] = p;
228
229         /* ...and advance the data pointer to the start of the next
230          * available block of content, (if any).
231          */
232         p += strlen( p ) + 1;
233       }
234     }
235   }
236
237   /* Finally, we return the address of the physical starting location
238    * of the (possibly relocated) pkgSpecs content buffer.
239    */
240   return (char*)(content);
241 }
242
243 const char *pkgSpecs::GetTarName( const char *buf )
244 {
245   /* Reconstitute the canonical tarname for the package
246    * identified by the current pkgSpecs record, returning
247    * the result in a C string buffer allocated by malloc();
248    * "buf" may be specified as NULL, otherwise it MUST point
249    * to a similarly allocated buffer, which is recycled.
250    */
251   char *dest = (char*)(realloc( (void*)(buf), buflen( specs ) ));
252   if( (buf = dest) != NULL )
253     /*
254      * We have a valid buffer suitable for returning the result...
255      */
256     for( int index = PACKAGE_NAME; index < PACKAGE_TAG_COUNT; index++ )
257     {
258       /* For each field in the package specification...
259        */
260       char *src = specs[index];
261       if( (src != NULL) && (*src != '\0') )
262       {
263         /* ...for which a non-empty value is defined...
264          */
265         if( dest > buf )
266           /*
267            * ...when not the first such field, insert the
268            * appropriate field separator character...
269            */
270           *dest++ = (index < PACKAGE_FORMAT) ? '-' : '.';
271
272         /* ...then copy the field content to the result buffer.
273          */
274         while( (*dest = *src++) != '\0' )
275           ++dest;
276       }
277     }
278
279   /* Return the fully reconstituted tarname, or NULL if the
280    * return buffer allocation failed.
281    */
282   return buf;
283 }
284
285
286 /*
287  ******************
288  *
289  * Class Implementation: pkgActionItem; SetRequirements method
290  *
291  */
292
293 enum inherit_mode
294 {
295   /* Definition of the inheritance flags identifying those elements
296    * of the package and subsystem version fields, within a requirements
297    * specification, which are to be matched by a "%" wildcard.
298    *
299    * These flags apply to any ONE version number specification, which
300    * may represent EITHER the package version OR the subsystem version
301    * within a requirements specification; it takes the form:
302    *
303    *   major.minor.patch-datestamp-index
304    *
305    * and the flags are interpreted to indicate...
306    */
307   INHERIT_NONE = 0,     /* no "%" wildcard match requested            */
308   INHERIT_VERSION,      /* "major.minor.patch" matches a "%" wildcard */
309   INHERIT_BUILD,        /* "datestamp-index" matches a "%" wildcard   */
310   INHERIT_ALL           /* "%" matches the entire version string      */
311 };
312
313 static
314 enum inherit_mode inherited( const char *ver, const char *bld )
315 {
316   /* Local helper function to assign "%" wildcard inheritance flags
317    * to a specified version number, where:
318    *
319    *   "ver" represents the "major.minor.patch" part of the version
320    *   "bld" represents the optional "datestamp-index" extension
321    */
322   enum inherit_mode retval = INHERIT_NONE;
323
324   /* Noting that the "%" wildcard must represent the respective part
325    * of the version specification in its entirety...
326    */
327   if( (ver != NULL) && (ver[0] == '%') && (ver[1] == '\0') )
328     /*
329      * ...flag a match on the "major.minor.patch" specification.
330      */
331     retval = INHERIT_VERSION;
332
333   /* Considering the optional "datestamp-index" specification...
334    */
335   if( bld == NULL )
336   {
337     /* ...when it isn't specified at all, then it is implicitly
338      * governed by a match on the "major.minor.patch" element...
339      */
340     if( retval == INHERIT_VERSION )
341       /*
342        * ...thus promoting this to become a match on the entire
343        * "major.minor.patch-datestamp-index" specification.
344        */
345       retval = INHERIT_ALL;
346   }
347   else if( (bld[0] == '%') && (bld[1] == '\0') )
348     /*
349      * ...otherwise we may, (less usefully, perhaps and therefore
350      * not to be encouraged), require a "%" wildcard match on the
351      * "datestamp-index" element alone, (with some other criterion
352      * applied to the "major.minor.patch" element -- perhaps even
353      * redundantly "%-%", which is equivalent to the simpler form
354      * of "%" alone, to match the entire version specification).
355      */
356     retval = (enum inherit_mode)(retval | INHERIT_BUILD);
357
358   /* The inheritance state is now fully specified; pass it back
359    * to the caller.
360    */
361   return retval;
362 }
363
364 static
365 const char *requirement( const char *wanted, pkgSpecs *dep )
366 {
367   /* Local helper function to generate "min_wanted" and "max_wanted"
368    * dependency specifications, for assignment within a pkgActionItem;
369    * propagation of version number fields inherited from the dependant,
370    * as specified by the "%" wildcard, is appropriately enforced.  The
371    * resultant specification is ALWAYS returned on the heap, in memory
372    * dynamically allocated by malloc().
373    */
374   pkgSpecs id( wanted );
375
376   /* Evaluate inheritance of the PACKAGE version number specification...
377    */
378   enum inherit_mode clone;
379   clone = inherited( id.GetPackageVersion(), id.GetPackageBuild() );
380   if( (clone & INHERIT_VERSION) > INHERIT_NONE )
381     /*
382      * ...propagating its "major.minor.patch" element as required...
383      */
384     id.SetPackageVersion( dep->GetPackageVersion() );
385
386   if( (clone & INHERIT_BUILD) > INHERIT_NONE )
387     /*
388      * ...and similarly, its "datestamp-index" element.
389      */
390     id.SetPackageBuild( dep->GetPackageBuild() );
391
392   /* Similarly, for the SUBSYSTEM version number specification...
393    */
394   clone = inherited( id.GetSubSystemVersion(), id.GetSubSystemBuild() );
395   if( (clone & INHERIT_VERSION) > INHERIT_NONE )
396     /*
397      * ...propagate its "major.minor.patch" element as required...
398      */
399     id.SetSubSystemVersion( dep->GetSubSystemVersion() );
400
401   if( (clone & INHERIT_BUILD) > INHERIT_NONE )
402     /*
403      * ...and similarly, its "datestamp-index" element.
404      */
405     id.SetSubSystemBuild( dep->GetSubSystemBuild() );
406
407   /* Finally, reconstitute the canonical tarname representation of the
408    * specification, from its decomposed representation; (as a side effect,
409    * this automatically places the return string on the heap).
410    */
411   return id.GetTarName();
412 }
413
414 const char * pkgActionItem::SetRequirements( pkgXmlNode *req, pkgSpecs *dep )
415 {
416   /* Establish the selection criteria, for association of any
417    * particular package release with an action item.
418    */
419   flags &= ACTION_MASK;
420
421   /* First check for a strict equality requirement...
422    */
423   if( (min_wanted = req->GetPropVal( eq_key, NULL )) != NULL )
424     /*
425      * ...and if specified, set the selection range such that only one
426      * specific release can be matched; evaluate any specified dependency
427      * relationship, to ensure that inherited version numbers are correctly
428      * propagated, and assign the resultant dynamically allocated tarname
429      * specifications to the respective constraints.
430      */
431     return max_wanted = min_wanted = requirement( min_wanted, dep );
432
433   else
434   { /* Check for either an inclusive, or a strictly exclusive,
435      * minimum requirement (release "greater" than) specification,
436      * setting the minimum release selector...
437      */
438     if( ((min_wanted = req->GetPropVal( ge_key, NULL )) == NULL)
439     &&  ((min_wanted = req->GetPropVal( gt_key, NULL )) != NULL)  )
440       /*
441        * ...and its selection mode flag accordingly.
442        */
443       flags |= STRICTLY_GT;
444
445     /* Similarly, check for an inclusive, or a strictly exclusive,
446      * maximum requirement (release "less" than) specification,
447      * setting the maximum release selector...
448      */
449     if( ((max_wanted = req->GetPropVal( le_key, NULL )) == NULL)
450     &&  ((max_wanted = req->GetPropVal( lt_key, NULL )) != NULL)  )
451       /*
452        * ...and its selection mode flag accordingly.
453        */
454       flags |= STRICTLY_LT;
455   }
456
457   /* Check the minimum required version specification...
458    */
459   if( min_wanted != NULL )
460     /*
461      * ...ensuring that inherited version numbers are propagated, as
462      * required from the dependant, and assign the canonical tarname
463      * representation of the final specification on the heap.
464      */
465     min_wanted = requirement( min_wanted, dep );
466
467   /* And...
468    */
469   if( max_wanted != NULL )
470     /*
471      * ...likewise for the maximum required version specification.
472      */
473     max_wanted = requirement( max_wanted, dep );
474
475   /* Return a canonical representation of the requirements spec.
476    */
477   return (min_wanted == NULL) ? max_wanted : min_wanted;
478 }
479
480 /* $RCSfile$: end of file */