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