937ad7981e83e5dfd4e116c4618f1422536cd684
[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.2.2.8 2002/08/20 06:35:08 obrien Exp $
20  * $DragonFly: src/usr.sbin/pkg_install/lib/Attic/match.c,v 1.2 2003/06/17 04:29:59 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 *);
40 struct store *storecreate(struct store *);
41 static int storeappend(struct store *, const char *);
42 static int fname_cmp(const FTSENT **, const FTSENT **);
43
44 /*
45  * Function to query names of installed packages.
46  * MatchType    - one of MATCH_ALL, MATCH_REGEX, MATCH_GLOB;
47  * patterns     - NULL-terminated list of glob or regex patterns
48  *                (could be NULL for MATCH_ALL);
49  * retval       - return value (could be NULL if you don't want/need
50  *                return value).
51  * Returns NULL-terminated list with matching names.
52  * Names in list returned are dynamically allocated and should
53  * not be altered by the caller.
54  */
55 char **
56 matchinstalled(match_t MatchType, char **patterns, int *retval)
57 {
58     int i, errcode, len;
59     char *matched;
60     const char *paths[2] = {LOG_DIR, NULL};
61     static struct store *store = NULL;
62     FTS *ftsp;
63     FTSENT *f;
64     Boolean *lmatched;
65
66     store = storecreate(store);
67     if (store == NULL) {
68         if (retval != NULL)
69             *retval = 1;
70         return NULL;
71     }
72
73     if (retval != NULL)
74         *retval = 0;
75
76     if (!isdir(paths[0])) {
77         if (retval != NULL)
78             *retval = 1;
79         return NULL;
80         /* Not reached */
81     }
82
83     /* Count number of patterns */
84     if (patterns != NULL) {
85         for (len = 0; patterns[len]; len++) {}
86         lmatched = alloca(sizeof(*lmatched) * len);
87         if (lmatched == NULL) {
88             warnx("%s(): alloca() failed", __func__);
89             if (retval != NULL)
90                 *retval = 1;
91             return NULL;
92         } 
93     } else
94         len = 0;
95     
96     for (i = 0; i < len; i++)
97         lmatched[i] = FALSE;
98
99     ftsp = fts_open((char * const *)(uintptr_t)paths, FTS_LOGICAL | FTS_NOCHDIR | FTS_NOSTAT, fname_cmp);
100     if (ftsp != NULL) {
101         while ((f = fts_read(ftsp)) != NULL) {
102             if (f->fts_info == FTS_D && f->fts_level == 1) {
103                 fts_set(ftsp, f, FTS_SKIP);
104                 matched = NULL;
105                 errcode = 0;
106                 if (MatchType == MATCH_ALL)
107                     matched = f->fts_name;
108                 else 
109                     for (i = 0; patterns[i]; i++) {
110                         switch (MatchType) {
111                         case MATCH_REGEX:
112                             errcode = rex_match(patterns[i], f->fts_name);
113                             if (errcode == 1) {
114                                 matched = f->fts_name;
115                                 errcode = 0;
116                             }
117                             break;
118                         case MATCH_GLOB:
119                             if (fnmatch(patterns[i], f->fts_name, 0) == 0) {
120                                 matched = f->fts_name;
121                                 lmatched[i] = TRUE;
122                             }
123                             break;
124                         default:
125                             break;
126                         }
127                         if (matched != NULL || errcode != 0)
128                             break;
129                     }
130                 if (errcode == 0 && matched != NULL)
131                     errcode = storeappend(store, matched);
132                 if (errcode != 0) {
133                     if (retval != NULL)
134                         *retval = 1;
135                     return NULL;
136                     /* Not reached */
137                 }
138             }
139         }
140         fts_close(ftsp);
141     }
142
143     if (MatchType == MATCH_GLOB) {
144         for (i = 0; i < len; i++)
145             if (lmatched[i] == FALSE)
146                 storeappend(store, patterns[i]);
147     }
148
149     if (store->used == 0)
150         return NULL;
151     else
152         return store->store;
153 }
154
155 /*
156  * Synopsis is similar to matchinstalled(), but use origin
157  * as a key for matching packages.
158  */
159 char **
160 matchbyorigin(const char *origin, int *retval)
161 {
162     char **installed;
163     int i;
164     static struct store *store = NULL;
165
166     store = storecreate(store);
167     if (store == NULL) {
168         if (retval != NULL)
169             *retval = 1;
170         return NULL;
171     }
172
173     if (retval != NULL)
174         *retval = 0;
175
176     installed = matchinstalled(MATCH_ALL, NULL, retval);
177     if (installed == NULL)
178         return NULL;
179
180     for (i = 0; installed[i] != NULL; i++) {
181         FILE *fp;
182         char *cp, tmp[PATH_MAX];
183         int cmd;
184
185         snprintf(tmp, PATH_MAX, "%s/%s", LOG_DIR, installed[i]);
186         /*
187          * SPECIAL CASE: ignore empty dirs, since we can can see them
188          * during port installation.
189          */
190         if (isemptydir(tmp))
191             continue;
192         snprintf(tmp, PATH_MAX, "%s/%s", tmp, CONTENTS_FNAME);
193         fp = fopen(tmp, "r");
194         if (fp == NULL) {
195             warn("%s", tmp);
196             if (retval != NULL)
197                 *retval = 1;
198             return NULL;
199         }
200
201         cmd = -1;
202         while (fgets(tmp, sizeof(tmp), fp)) {
203             int len = strlen(tmp);
204
205             while (len && isspace(tmp[len - 1]))
206                 tmp[--len] = '\0';
207             if (!len)
208                 continue;
209             cp = tmp;
210             if (tmp[0] != CMD_CHAR)
211                 continue;
212             cmd = plist_cmd(tmp + 1, &cp);
213             if (cmd == PLIST_ORIGIN) {
214                 if (strcmp(origin, cp) == 0)
215                     storeappend(store, installed[i]);
216                 break;
217             }
218         }
219         if (cmd != PLIST_ORIGIN)
220             warnx("package %s has no origin recorded", installed[i]);
221         fclose(fp);
222     }
223
224     if (store->used == 0)
225         return NULL;
226     else
227         return store->store;
228 }
229
230 /*
231  * Return TRUE if the specified package is installed,
232  * or FALSE otherwise.
233  */
234 int
235 isinstalledpkg(const char *name)
236 {
237     char buf[FILENAME_MAX];
238
239     snprintf(buf, sizeof(buf), "%s/%s", LOG_DIR, name);
240     if (!isdir(buf) || access(buf, R_OK) == FAIL)
241         return FALSE;
242
243     snprintf(buf, sizeof(buf), "%s/%s", buf, CONTENTS_FNAME);
244     if (!isfile(buf) || access(buf, R_OK) == FAIL)
245         return FALSE;
246
247     return TRUE;
248 }
249
250 /*
251  * Returns 1 if specified pkgname matches RE pattern.
252  * Otherwise returns 0 if doesn't match or -1 if RE
253  * engine reported an error (usually invalid syntax).
254  */
255 static int
256 rex_match(const char *pattern, const char *pkgname)
257 {
258     char errbuf[128];
259     int errcode;
260     int retval;
261     regex_t rex;
262
263     retval = 0;
264
265     errcode = regcomp(&rex, pattern, REG_BASIC | REG_NOSUB);
266     if (errcode == 0)
267         errcode = regexec(&rex, pkgname, 0, NULL, 0);
268
269     if (errcode == 0) {
270         retval = 1;
271     } else if (errcode != REG_NOMATCH) {
272         regerror(errcode, &rex, errbuf, sizeof(errbuf));
273         warnx("%s: %s", pattern, errbuf);
274         retval = -1;
275     }
276
277     regfree(&rex);
278
279     return retval;
280 }
281
282 /*
283  * Create an empty store, optionally deallocating
284  * any previously allocated space if store != NULL.
285  */
286 struct store *
287 storecreate(struct store *store)
288 {
289     int i;
290
291     if (store == NULL) {
292         store = malloc(sizeof *store);
293         if (store == NULL) {
294             warnx("%s(): malloc() failed", __func__);
295             return NULL;
296         }
297         store->currlen = 0;
298         store->store = NULL;
299     } else if (store->store != NULL) {
300             /* Free previously allocated memory */
301             for (i = 0; store->store[i] != NULL; i++)
302                 free(store->store[i]);
303             store->store[0] = NULL;
304     }
305     store->used = 0;
306
307     return store;
308 }
309
310 /*
311  * Append specified element to the provided store.
312  */
313 static int
314 storeappend(struct store *store, const char *item)
315 {
316     if (store->used + 2 > store->currlen) {
317         store->currlen += 16;
318         store->store = reallocf(store->store,
319                                 store->currlen * sizeof(*(store->store)));
320         if (store->store == NULL) {
321             store->currlen = 0;
322             warnx("%s(): reallocf() failed", __func__);
323             return 1;
324         }
325     }
326
327     asprintf(&(store->store[store->used]), "%s", item);
328     if (store->store[store->used] == NULL) {
329         warnx("%s(): malloc() failed", __func__);
330         return 1;
331     }
332     store->used++;
333     store->store[store->used] = NULL;
334
335     return 0;
336 }
337
338 static int
339 fname_cmp(const FTSENT **a, const FTSENT **b)
340 {
341     return strcmp((*a)->fts_name, (*b)->fts_name);
342 }