Imported Upstream version 0.7.4
[platform/upstream/libsolv.git] / src / conda.c
1 /*
2  * Copyright (c) 2019, SUSE LLC
3  *
4  * This program is licensed under the BSD license, read LICENSE.BSD
5  * for further information
6  */
7
8 /*
9  * conda.c
10  *
11  * evr comparison and package matching for conda
12  */
13
14 #include <stdio.h>
15 #include <stdlib.h>
16 #include <stdarg.h>
17 #include <unistd.h>
18 #include <string.h>
19 #include <sys/types.h>
20 #include <regex.h>
21
22 #include "pool.h"
23 #include "repo.h"
24 #include "util.h"
25 #include "conda.h"
26
27 static const char *
28 endseg(const char *seg, const char *end)
29 {
30   for (; seg < end; seg++)
31     if (*seg == '.' || *seg == '-' || *seg == '_')
32       break;
33   return seg;
34 }
35
36 static const char *
37 endpart(const char *seg, const char *end)
38 {
39   if (seg == end)
40     return seg;
41   if (*seg >= '0' && *seg <= '9')
42     {
43       for (seg++; seg < end; seg++)
44         if (!(*seg >= '0' && *seg <= '9'))
45           break;
46     }
47   else if (*seg == '*')
48     {
49       for (seg++; seg < end; seg++)
50         if (*seg != '*')
51           break;
52     }
53   else
54     {
55       for (seg++; seg < end; seg++)
56         if ((*seg >= '0' && *seg <= '9') || *seg == '*')
57           break;
58     }
59   return seg;
60 }
61
62 /* C implementation of the version comparison code in conda/models/version.py */
63 /* startswith == 1 : check if s1 starts with s2 */
64 static int
65 solv_vercmp_conda(const char *s1, const char *q1, const char *s2, const char *q2, int startswith)
66 {
67   const char *s1p, *s2p;
68   const char *s1e, *s2e;
69   int r, isfirst;
70   const char *q2end = 0;
71
72   if (startswith)
73     {
74       for (q2end = q2; q2end > s2; q2end--)
75         if (q2end[-1] != '.' && q2end[-1] != '-' && q2end[-1] != '_')
76           break;
77     }
78   for (;;)
79     {
80       while (s1 < q1 && (*s1 == '.' || *s1 == '-' || *s1 == '_'))
81         s1++;
82       while (s2 < q2 && (*s2 == '.' || *s2 == '-' || *s2 == '_'))
83         s2++;
84       if (s1 == q1 && s2 == q2)
85         return 0;
86       if (startswith && s2 == q2)
87         return 0;
88       /* find end of component */
89       s1e = endseg(s1, q1);
90       s2e = endseg(s2, q2);
91       
92       for (isfirst = 1; ; isfirst = 0)
93         {
94           if (s1 == s1e && s2 == s2e)
95             break;
96           if (s2 == q2end)
97             return 0;
98           s1p = endpart(s1, s1e);
99           s2p = endpart(s2, s2e);
100           /* prepend 0 if not numeric */
101           if (isfirst)
102             {
103               if (s1p != s1 && !(*s1 >= '0' && *s1 <= '9'))
104                 s1p = s1;
105               if (s2p != s2 && !(*s2 >= '0' && *s2 <= '9'))
106                 s2p = s2;
107             }
108           /* special case "post" */
109           if (s1p - s1 == 4 && !strncasecmp(s1, "post", 4))
110             {
111               if (s2p - s2 == 4 && !strncasecmp(s2, "post", 4))
112                 {
113                   s1 = s1p;
114                   s2 = s2p;
115                   continue;
116                 }
117               return 1;
118             }
119           if (s2p - s2 == 4 && !strncasecmp(s2, "post", 4))
120             return -1;
121
122           if (isfirst || ((s1 == s1p || (*s1 >= '0' && *s1 <= '9')) && (s2 == s2p || (*s2 >= '0' && *s2 <= '9'))))
123             {
124               /* compare numbers */
125               while (s1 < s1p && *s1 == '0')
126                 s1++;
127               while (s2 < s2p && *s2 == '0')
128                 s2++;
129               if (s1p - s1 < s2p - s2)
130                 return -1;
131               if (s1p - s1 > s2p - s2)
132                 return 1;
133               r = s1p - s1 ? strncmp(s1, s2, s1p - s1) : 0;
134               if (r)
135                 return r;
136             }
137           else if (s1 == s1p || (*s1 >= '0' && *s1 <= '9'))
138             return 1;
139           else if (s2 == s2p || (*s2 >= '0' && *s2 <= '9'))
140             return -1;
141           else
142             {
143               /* special case "dev" */
144               if (*s2 != '*' && s1p - s1 == 3 && !strncasecmp(s1, "dev", 3))
145                 {
146                   if (s2p - s2 == 3 && !strncasecmp(s2, "dev", 3))
147                     {
148                       s1 = s1p;
149                       s2 = s2p;
150                       continue;
151                     }
152                   return -1;
153                 }
154               if (*s1 != '*' && s2p - s2 == 3 && !strncasecmp(s2, "dev", 3))
155                 return 1;
156               /* compare strings */
157               r = s2p - s2 > s1p - s1 ? s1p - s1 : s2p - s2;
158               if (r)
159                 r = strncasecmp(s1, s2, r);
160               if (r)
161                 return r;
162               if (s1p - s1 < s2p - s2) 
163                 return -1; 
164               if (s1p - s1 > s2p - s2) 
165                 return 1;
166             }
167           s1 = s1p;
168           s2 = s2p;
169         }
170     }
171 }
172
173 static int
174 pool_evrcmp_conda_int(const char *evr1, const char *evr1e, const char *evr2, const char *evr2e, int startswith)
175 {
176   static char zero[2] = {'0', 0};
177   const char *s1, *s2;
178   const char *r1, *r2;
179   int r;
180
181   /* split and compare epoch */
182   for (s1 = evr1; s1 < evr1e && *s1 >= '0' && *s1 <= '9'; s1++)
183     ;
184   for (s2 = evr2; s2 < evr2e && *s2 >= '0' && *s2 <= '9'; s2++)
185     ;
186   if (s1 == evr1 || s1 == evr1e || *s1 != '!')
187     s1 = 0;
188   if (s2 == evr1 || s2 == evr2e || *s2 != '!')
189     s2 = 0;
190   if (s1 || s2)
191     {
192       r = solv_vercmp_conda(s1 ? evr1 : zero, s1 ? s1 : zero + 1, 
193                             s2 ? evr2 : zero, s2 ? s2 : zero + 1, 0);
194       if (r)
195         return r;
196       if (s1)
197         evr1 = s1 + 1;
198       if (s2)
199         evr2 = s2 + 1;
200     }
201   /* split into version/localversion */
202   for (s1 = evr1, r1 = 0; s1 < evr1e; s1++)
203     if (*s1 == '+')
204       r1 = s1;
205   for (s2 = evr2, r2 = 0; s2 < evr2e; s2++)
206     if (*s2 == '+')
207       r2 = s2;
208   r = solv_vercmp_conda(evr1, r1 ? r1 : s1, evr2, r2 ? r2 : s2, r2 ? 0 : startswith);
209   if (r)
210     return r;
211   if (!r1 && !r2)
212     return 0;
213   if (!r1 && r2)
214     return -1;
215   if (r1 && !r2)
216     return 1;
217   return solv_vercmp_conda(r1 + 1, s1, r2 + 1, s2, startswith);
218 }
219
220 int
221 pool_evrcmp_conda(const Pool *pool, const char *evr1, const char *evr2, int mode)
222 {
223   if (evr1 == evr2)
224     return 0;
225   return pool_evrcmp_conda_int(evr1, evr1 + strlen(evr1), evr2, evr2 + strlen(evr2), 0);
226 }
227
228 static int
229 regexmatch(const char *evr, const char *version, size_t versionlen)
230 {
231   regex_t reg;
232   char *buf = solv_malloc(versionlen + 1);
233   int r;
234
235   memcpy(buf, version, versionlen);
236   buf[versionlen] = 0;
237   if (regcomp(&reg, buf, REG_EXTENDED | REG_NOSUB))
238     return 0;
239   r = regexec(&reg, evr, 0, NULL, 0);
240   regfree(&reg);
241   return r == 0;
242 }
243
244 static int
245 globmatch(const char *evr, const char *version, size_t versionlen)
246 {
247   regex_t reg;
248   char *buf = solv_malloc(2 * versionlen + 3);
249   size_t i, j;
250   int r;
251
252   buf[0] = '^';
253   j = 1;
254   for (i = 0, j = 1; i < versionlen; i++)
255     {
256       if (version[i] == '.' || version[i] == '+' || version[i] == '*')
257         buf[j++] = version[i] == '*' ? '.' : '\\';
258       buf[j++] = version[i];
259     }
260   buf[j++] = '$';
261   buf[j] = 0;
262   if (regcomp(&reg, buf, REG_EXTENDED | REG_NOSUB))
263     return 0;
264   r = regexec(&reg, evr, 0, NULL, 0);
265   regfree(&reg);
266   return r == 0;
267 }
268
269 /* return true if solvable s matches the version */
270 /* see conda/models/version.py */
271 static int
272 solvable_conda_matchversion_single(Solvable *s, const char *version, size_t versionlen)
273 {
274   const char *evr;
275   size_t i;
276   int r;
277
278   if (versionlen == 0 || (versionlen == 1 && *version == '*'))
279     return 1;   /* matches every version */
280   evr = pool_id2str(s->repo->pool, s->evr);
281   if (versionlen >= 2 && version[0] == '^' && version[versionlen - 1] == '$')
282     return regexmatch(evr, version, versionlen);
283   if (version[0] == '=' || version[0] == '<' || version[0] == '>' || version[0] == '!' || version[0] == '~')
284     {
285       int flags = 0;
286       int oplen;
287       if (version[0] == '=')
288           flags = version[1] == '=' ? REL_EQ : 8;
289       else if (version[0] == '!' || version[0] == '~')
290         {
291           if (version[1] != '=')
292             return 0;
293           flags = version[0] == '!' ? REL_LT | REL_GT : 9;
294         }
295       else if (version[0] == '<' || version[0] == '>')
296         {
297           flags = version[0] == '<' ? REL_LT : REL_GT;
298           if (version[1] == '=')
299             flags |= REL_EQ;
300         }
301       else
302         return 0;
303       oplen = flags == 8 || flags == REL_LT || flags == REL_GT ? 1 : 2;
304       if (versionlen < oplen + 1)
305         return 0;
306       version += oplen;
307       versionlen -= oplen;
308       if (version[0] == '=' || version[0] == '<' || version[0] == '>' || version[0] == '!' || version[0] == '~')
309         return 0;               /* bad chars after op */
310       if (versionlen >= 2 && version[versionlen - 2] == '.' && version[versionlen - 1] == '*')
311         {
312           if (flags == 8 || flags == (REL_GT | REL_EQ))
313             versionlen -= 2;
314           else if (flags == (REL_LT | REL_GT))
315             {
316               versionlen -= 2;
317               flags = 10;
318             }
319           else
320             return 0;
321         }
322       if (flags < 8)
323         {
324           /* we now have an op and a version */
325           r = pool_evrcmp_conda_int(evr, evr + strlen(evr), version, version + versionlen, 0);
326           if (r < 0)
327             return (flags & REL_LT) ? 1 : 0;
328           if (r == 0)
329             return (flags & REL_EQ) ? 1 : 0;
330           if (r > 0)
331             return (flags & REL_GT) ? 1 : 0;
332           return 0;
333         }
334       if (flags == 8 || flags == 10)    /* startswith, not-startswith */
335         {
336           r = pool_evrcmp_conda_int(evr, evr + strlen(evr), version, version + versionlen, 1);
337           return flags == 8 ? r == 0 : r != 0;
338         }
339       else if (flags == 9)              /* compatible release op */
340         {
341           r = pool_evrcmp_conda_int(evr, evr + strlen(evr), version, version + versionlen, 0);
342           if (r < 0)
343             return 0;
344           /* split off last component */
345           while (versionlen > 0 && version[versionlen - 1] != '.')
346             versionlen--;
347           if (versionlen < 2)
348             return 0;
349           versionlen--;
350           r = pool_evrcmp_conda_int(evr, evr + strlen(evr), version, version + versionlen, 1);
351           return r == 0 ? 1 : 0;
352         }
353       return 0;
354     }
355    
356   /* do we have a '*' in the version */
357   for (i = 0; i < versionlen; i++)
358     if (version[i] == '*')
359       {
360         for (i++; i < versionlen; i++)
361           if (version[i] != '*')
362             break;
363         if (i < versionlen)
364           return globmatch(evr, version, versionlen);
365       }
366
367   if (versionlen > 1 && version[versionlen - 1] == '*')
368     {
369       /* startswith */
370       while (versionlen > 0 && version[versionlen - 1] == '*')
371         versionlen--;
372       while (versionlen > 0 && version[versionlen - 1] == '.')
373         versionlen--;
374       r = pool_evrcmp_conda_int(evr, evr + strlen(evr), version, version + versionlen, 1);
375       return r == 0 ? 1 : 0;
376     }
377   /* do we have a '@' in the version? */
378   for (i = 0; i < versionlen; i++)
379     if (version[i] == '@')
380       return strncmp(evr, version, versionlen) == 0 && evr[versionlen] == 0;
381   r = pool_evrcmp_conda_int(evr, evr + strlen(evr), version, version + versionlen, 0);
382   return r == 0 ? 1 : 0;
383 }
384
385 static int
386 solvable_conda_matchversion_rec(Solvable *s, const char **versionp, const char *versionend)
387 {
388   const char *version = *versionp;
389   int v, vor = 0, vand = -1;    /* -1: doing OR, 0,1: doing AND */
390
391   if (version == versionend)
392     return -1;
393   for (;;)
394     {
395       if (*version == '(')
396         {
397           version++;
398           v = solvable_conda_matchversion_rec(s, &version, versionend);
399           if (v == -1 || version == versionend || *version != ')')
400             return -1;
401           version++;
402         }
403       else if (*version == ')' || *version == '|' || *version == ',')
404         return -1;
405       else
406         {
407           const char *vstart = version;
408           while (version < versionend && *version != '(' && *version != ')' && *version != '|' && *version != ',')
409             version++;
410           if (vand >= 0 ? !vand : vor)
411             v = 0;              /* no need to call expensive matchversion if the result does not matter */
412           else
413             v = solvable_conda_matchversion_single(s, vstart, version - vstart) ? 1 : 0;
414         }
415       if (version == versionend || *version == ')')
416         {
417           *versionp = version;
418           return vor | (vand >= 0 ? (vand & v) : v);
419         }
420       if (*version == ',')
421         vand = vand >= 0 ? (vand & v) : v;
422       else if (*version == '|')
423         {
424           vor |= vand >= 0 ? (vand & v) : v;
425           vand = -1;
426         }
427       else
428         return -1;
429       version++;
430     }
431 }
432
433 /* return true if solvable s matches the version */
434 /* see conda/models/match_spec.py */
435 int
436 solvable_conda_matchversion(Solvable *s, const char *version)
437 {
438   const char *build, *versionend;
439   int r;
440   /* split off build */
441   if ((build = strchr(version, ' ')) != 0)
442     {
443       versionend = build++;
444       while (*build == ' ')
445         build++;
446     }
447   else
448     versionend = version + strlen(version);
449   r = solvable_conda_matchversion_rec(s, &version, versionend);
450   if (r != 1 || version != versionend)
451     return 0;
452   return r;
453 }
454