6750a9b93089be95e82ccc93058fa325eab2b161
[dragonfly.git] / usr.sbin / pkg_install / lib / match.c
1 /*
2  * FreeBSD install - a package for the installation and maintainance
3  * of non-core utilities.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  *
14  * Maxim Sobolev
15  * 24 February 2001
16  *
17  * Routines used to query installed packages.
18  *
19  * $FreeBSD: src/usr.sbin/pkg_install/lib/match.c,v 1.19 2004/06/29 19:06:42 eik Exp $
20  * $DragonFly: src/usr.sbin/pkg_install/lib/Attic/match.c,v 1.3 2004/07/30 04:46:13 dillon Exp $
21  */
22
23 #include "lib.h"
24 #include <err.h>
25 #include <fnmatch.h>
26 #include <fts.h>
27 #include <regex.h>
28
29 /*
30  * Simple structure representing argv-like
31  * NULL-terminated list.
32  */
33 struct store {
34     int currlen;
35     int used;
36     char **store;
37 };
38
39 static int rex_match(const char *, const char *, int);
40 static int csh_match(const char *, const char *, int);
41 struct store *storecreate(struct store *);
42 static int storeappend(struct store *, const char *);
43 static int fname_cmp(const FTSENT * const *, const FTSENT * const *);
44
45 /*
46  * Function to query names of installed packages.
47  * MatchType    - one of MATCH_ALL, MATCH_EREGEX, MATCH_REGEX, MATCH_GLOB, MATCH_NGLOB;
48  * patterns     - NULL-terminated list of glob or regex patterns
49  *                (could be NULL for MATCH_ALL);
50  * retval       - return value (could be NULL if you don't want/need
51  *                return value).
52  * Returns NULL-terminated list with matching names.
53  * Names in list returned are dynamically allocated and should
54  * not be altered by the caller.
55  */
56 char **
57 matchinstalled(match_t MatchType, char **patterns, int *retval)
58 {
59     int i, errcode, len;
60     char *matched;
61     const char *paths[2] = {LOG_DIR, NULL};
62     static struct store *store = NULL;
63     FTS *ftsp;
64     FTSENT *f;
65     Boolean *lmatched = NULL;
66
67     store = storecreate(store);
68     if (store == NULL) {
69         if (retval != NULL)
70             *retval = 1;
71         return NULL;
72     }
73
74     if (retval != NULL)
75         *retval = 0;
76
77     if (!isdir(paths[0])) {
78         if (retval != NULL)
79             *retval = 1;
80         return NULL;
81         /* Not reached */
82     }
83
84     /* Count number of patterns */
85     if (patterns != NULL) {
86         for (len = 0; patterns[len]; len++) {}
87         lmatched = alloca(sizeof(*lmatched) * len);
88         if (lmatched == NULL) {
89             warnx("%s(): alloca() failed", __func__);
90             if (retval != NULL)
91                 *retval = 1;
92             return NULL;
93         } 
94     } else
95         len = 0;
96     
97     for (i = 0; i < len; i++)
98         lmatched[i] = FALSE;
99
100     ftsp = fts_open((char * const *)(uintptr_t)paths, FTS_LOGICAL | FTS_NOCHDIR | FTS_NOSTAT, fname_cmp);
101     if (ftsp != NULL) {
102         while ((f = fts_read(ftsp)) != NULL) {
103             if (f->fts_info == FTS_D && f->fts_level == 1) {
104                 fts_set(ftsp, f, FTS_SKIP);
105                 matched = NULL;
106                 errcode = 0;
107                 if (MatchType == MATCH_ALL)
108                     matched = f->fts_name;
109                 else 
110                     for (i = 0; patterns[i]; i++) {
111                         errcode = pattern_match(MatchType, patterns[i], f->fts_name);
112                         if (errcode == 1) {
113                             matched = f->fts_name;
114                             lmatched[i] = TRUE;
115                             errcode = 0;
116                         }
117                         if (matched != NULL || errcode != 0)
118                             break;
119                     }
120                 if (errcode == 0 && matched != NULL)
121                     errcode = storeappend(store, matched);
122                 if (errcode != 0) {
123                     if (retval != NULL)
124                         *retval = 1;
125                     return NULL;
126                     /* Not reached */
127                 }
128             }
129         }
130         fts_close(ftsp);
131     }
132
133     if (MatchType == MATCH_GLOB) {
134         for (i = 0; i < len; i++)
135             if (lmatched[i] == FALSE)
136                 storeappend(store, patterns[i]);
137     }
138
139     if (store->used == 0)
140         return NULL;
141     else
142         return store->store;
143 }
144
145 int
146 pattern_match(match_t MatchType, char *pattern, const char *pkgname)
147 {
148     int errcode = 0;
149     const char *fname = pkgname;
150     char basefname[PATH_MAX];
151     char condchar = '\0';
152     char *condition;
153
154     /* do we have an appended condition? */
155     condition = strpbrk(pattern, "<>=");
156     if (condition) {
157         const char *ch;
158         /* yes, isolate the pattern from the condition ... */
159         if (condition > pattern && condition[-1] == '!')
160             condition--;
161         condchar = *condition;
162         *condition = '\0';
163         /* ... and compare the name without version */
164         ch = strrchr(fname, '-');
165         if (ch && ch - fname < PATH_MAX) {
166             strlcpy(basefname, fname, ch - fname + 1);
167             fname = basefname;
168         }
169     }
170
171     switch (MatchType) {
172     case MATCH_EREGEX:
173     case MATCH_REGEX:
174         errcode = rex_match(pattern, fname, MatchType == MATCH_EREGEX ? 1 : 0);
175         break;
176     case MATCH_NGLOB:
177     case MATCH_GLOB:
178         errcode = (csh_match(pattern, fname, 0) == 0) ? 1 : 0;
179         break;
180     case MATCH_EXACT:
181         errcode = (strcmp(pattern, fname) == 0) ? 1 : 0;
182         break;
183     case MATCH_ALL:
184         errcode = 1;
185         break;
186     default:
187         break;
188     }
189
190     /* loop over all appended conditions */
191     while (condition) {
192         /* restore the pattern */
193         *condition = condchar;
194         /* parse the condition (fun with bits) */
195         if (errcode == 1) {
196             char *nextcondition;
197             /* compare version numbers */
198             int match = 0;
199             if (*++condition == '=') {
200                 match = 2;
201                 condition++;
202             }
203             switch(condchar) {
204             case '<':
205                 match |= 1;
206                 break;
207             case '>':
208                 match |= 4;
209                 break;
210             case '=':
211                 match |= 2;
212                 break;
213             case '!':
214                 match = 5;
215                 break;
216             }
217             /* isolate the version number from the next condition ... */
218             nextcondition = strpbrk(condition, "<>=!");
219             if (nextcondition) {
220                 condchar = *nextcondition;
221                 *nextcondition = '\0';
222             }
223             /* and compare the versions (version_cmp removes the filename for us) */
224             if ((match & (1 << (version_cmp(pkgname, condition) + 1))) == 0)
225                 errcode = 0;
226             condition = nextcondition;
227         } else {
228             break;
229         }
230     }
231
232     return errcode;
233 }
234
235 /*
236  * Synopsis is similar to matchinstalled(), but use origin
237  * as a key for matching packages.
238  */
239 char **
240 matchbyorigin(const char *origin, int *retval)
241 {
242     char **installed;
243     int i;
244     static struct store *store = NULL;
245
246     store = storecreate(store);
247     if (store == NULL) {
248         if (retval != NULL)
249             *retval = 1;
250         return NULL;
251     }
252
253     if (retval != NULL)
254         *retval = 0;
255
256     installed = matchinstalled(MATCH_ALL, NULL, retval);
257     if (installed == NULL)
258         return NULL;
259
260     for (i = 0; installed[i] != NULL; i++) {
261         FILE *fp;
262         char *cp, tmp[PATH_MAX];
263         int cmd;
264
265         snprintf(tmp, PATH_MAX, "%s/%s", LOG_DIR, installed[i]);
266         /*
267          * SPECIAL CASE: ignore empty dirs, since we can can see them
268          * during port installation.
269          */
270         if (isemptydir(tmp))
271             continue;
272         snprintf(tmp, PATH_MAX, "%s/%s", tmp, CONTENTS_FNAME);
273         fp = fopen(tmp, "r");
274         if (fp == NULL) {
275             warnx("the package info for package '%s' is corrupt", installed[i]);
276             continue;
277         }
278
279         cmd = -1;
280         while (fgets(tmp, sizeof(tmp), fp)) {
281             int len = strlen(tmp);
282
283             while (len && isspace(tmp[len - 1]))
284                 tmp[--len] = '\0';
285             if (!len)
286                 continue;
287             cp = tmp;
288             if (tmp[0] != CMD_CHAR)
289                 continue;
290             cmd = plist_cmd(tmp + 1, &cp);
291             if (cmd == PLIST_ORIGIN) {
292                 if (csh_match(origin, cp, FNM_PATHNAME) == 0)
293                     storeappend(store, installed[i]);
294                 break;
295             }
296         }
297         if (cmd != PLIST_ORIGIN)
298             warnx("package %s has no origin recorded", installed[i]);
299         fclose(fp);
300     }
301
302     if (store->used == 0)
303         return NULL;
304     else
305         return store->store;
306 }
307
308 /*
309  * 
310  * Return 1 if the specified package is installed,
311  * 0 if not, and -1 if an error occured.
312  */
313 int
314 isinstalledpkg(const char *name)
315 {
316     char buf[FILENAME_MAX];
317     char buf2[FILENAME_MAX];
318
319     snprintf(buf, sizeof(buf), "%s/%s", LOG_DIR, name);
320     if (!isdir(buf) || access(buf, R_OK) == FAIL)
321         return 0;
322
323     snprintf(buf2, sizeof(buf2), "%s/%s", buf, CONTENTS_FNAME);
324     if (!isfile(buf2) || access(buf2, R_OK) == FAIL)
325         return -1;
326
327     return 1;
328 }
329
330 /*
331  * Returns 1 if specified pkgname matches RE pattern.
332  * Otherwise returns 0 if doesn't match or -1 if RE
333  * engine reported an error (usually invalid syntax).
334  */
335 static int
336 rex_match(const char *pattern, const char *pkgname, int extended)
337 {
338     char errbuf[128];
339     int errcode;
340     int retval;
341     regex_t rex;
342
343     retval = 0;
344
345     errcode = regcomp(&rex, pattern, (extended ? REG_EXTENDED : REG_BASIC) | REG_NOSUB);
346     if (errcode == 0)
347         errcode = regexec(&rex, pkgname, 0, NULL, 0);
348
349     if (errcode == 0) {
350         retval = 1;
351     } else if (errcode != REG_NOMATCH) {
352         regerror(errcode, &rex, errbuf, sizeof(errbuf));
353         warnx("%s: %s", pattern, errbuf);
354         retval = -1;
355     }
356
357     regfree(&rex);
358
359     return retval;
360 }
361
362 /*
363  * Match string by a csh-style glob pattern. Returns 0 on
364  * match and FNM_NOMATCH otherwise, to be compatible with
365  * fnmatch(3).
366  */
367 static int
368 csh_match(const char *pattern, const char *string, int flags)
369 {
370     int ret = FNM_NOMATCH;
371
372
373     const char *nextchoice = pattern;
374     const char *current = NULL;
375
376     int prefixlen = -1;
377     int currentlen = 0;
378
379     int level = 0;
380
381     do {
382         const char *pos = nextchoice;
383         const char *postfix = NULL;
384
385         Boolean quoted = FALSE;
386
387         nextchoice = NULL;
388
389         do {
390             const char *eb;
391             if (!*pos) {
392                 postfix = pos;
393             } else if (quoted) {
394                 quoted = FALSE;
395             } else {
396                 switch (*pos) {
397                 case '{':
398                     ++level;
399                     if (level == 1) {
400                         current = pos+1;
401                         prefixlen = pos-pattern;
402                     }
403                     break;
404                 case ',':
405                     if (level == 1 && !nextchoice) {
406                         nextchoice = pos+1;
407                         currentlen = pos-current;
408                     }
409                     break;
410                 case '}':
411                     if (level == 1) {
412                         postfix = pos+1;
413                         if (!nextchoice)
414                             currentlen = pos-current;
415                     }
416                     level--;
417                     break;
418                 case '[':
419                     eb = pos+1;
420                     if (*eb == '!' || *eb == '^')
421                         eb++;
422                     if (*eb == ']')
423                         eb++;
424                     while(*eb && *eb != ']')
425                         eb++;
426                     if (*eb)
427                         pos=eb;
428                     break;
429                 case '\\':
430                     quoted = TRUE;
431                     break;
432                 default:
433                     ;
434                 }
435             }
436             pos++;
437         } while (!postfix);
438
439         if (current) {
440             char buf[FILENAME_MAX];
441             snprintf(buf, sizeof(buf), "%.*s%.*s%s", prefixlen, pattern, currentlen, current, postfix);
442             ret = csh_match(buf, string, flags);
443             if (ret) {
444                 current = nextchoice;
445                 level = 1;
446             } else
447                 current = NULL;
448         } else
449             ret = fnmatch(pattern, string, flags);
450     } while (current);
451
452     return ret;
453 }
454
455 /*
456  * Create an empty store, optionally deallocating
457  * any previously allocated space if store != NULL.
458  */
459 struct store *
460 storecreate(struct store *store)
461 {
462     int i;
463
464     if (store == NULL) {
465         store = malloc(sizeof *store);
466         if (store == NULL) {
467             warnx("%s(): malloc() failed", __func__);
468             return NULL;
469         }
470         store->currlen = 0;
471         store->store = NULL;
472     } else if (store->store != NULL) {
473             /* Free previously allocated memory */
474             for (i = 0; store->store[i] != NULL; i++)
475                 free(store->store[i]);
476             store->store[0] = NULL;
477     }
478     store->used = 0;
479
480     return store;
481 }
482
483 /*
484  * Append specified element to the provided store.
485  */
486 static int
487 storeappend(struct store *store, const char *item)
488 {
489     if (store->used + 2 > store->currlen) {
490         store->currlen += 16;
491         store->store = reallocf(store->store,
492                                 store->currlen * sizeof(*(store->store)));
493         if (store->store == NULL) {
494             store->currlen = 0;
495             warnx("%s(): reallocf() failed", __func__);
496             return 1;
497         }
498     }
499
500     asprintf(&(store->store[store->used]), "%s", item);
501     if (store->store[store->used] == NULL) {
502         warnx("%s(): malloc() failed", __func__);
503         return 1;
504     }
505     store->used++;
506     store->store[store->used] = NULL;
507
508     return 0;
509 }
510
511 static int
512 fname_cmp(const FTSENT * const *a, const FTSENT * const *b)
513 {
514     return strcmp((*a)->fts_name, (*b)->fts_name);
515 }