2 * FreeBSD install - a package for the installation and maintainance
3 * of non-core utilities.
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
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.
17 * Routines used to query installed packages.
21 #include <sys/cdefs.h>
22 __FBSDID("$FreeBSD$");
31 * Simple structure representing argv-like
32 * NULL-terminated list.
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 *);
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
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.
58 matchinstalled(match_t MatchType
, char **patterns
, int *retval
)
62 const char *paths
[2] = {LOG_DIR
, NULL
};
63 static struct store
*store
= NULL
;
66 Boolean
*lmatched
= NULL
;
68 store
= storecreate(store
);
78 if (!isdir(paths
[0])) {
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__
);
98 for (i
= 0; i
< len
; i
++)
101 ftsp
= fts_open((char * const *)(uintptr_t)paths
, FTS_LOGICAL
| FTS_NOCHDIR
| FTS_NOSTAT
, fname_cmp
);
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
);
108 if (MatchType
== MATCH_ALL
)
109 matched
= f
->fts_name
;
111 for (i
= 0; patterns
[i
]; i
++) {
112 errcode
= pattern_match(MatchType
, patterns
[i
], f
->fts_name
);
114 matched
= f
->fts_name
;
118 if (matched
!= NULL
|| errcode
!= 0)
121 if (errcode
== 0 && matched
!= NULL
)
122 errcode
= storeappend(store
, matched
);
134 if (MatchType
== MATCH_GLOB
) {
135 for (i
= 0; i
< len
; i
++)
136 if (lmatched
[i
] == FALSE
)
137 storeappend(store
, patterns
[i
]);
140 if (store
->used
== 0)
147 pattern_match(match_t MatchType
, char *pattern
, const char *pkgname
)
150 const char *fname
= pkgname
;
151 char basefname
[PATH_MAX
];
152 char condchar
= '\0';
155 /* do we have an appended condition? */
156 condition
= strpbrk(pattern
, "<>=");
159 /* yes, isolate the pattern from the condition ... */
160 if (condition
> pattern
&& condition
[-1] == '!')
162 condchar
= *condition
;
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);
175 errcode
= rex_match(pattern
, fname
, MatchType
== MATCH_EREGEX
? 1 : 0);
179 errcode
= (csh_match(pattern
, fname
, 0) == 0) ? 1 : 0;
182 errcode
= (strcmp(pattern
, fname
) == 0) ? 1 : 0;
191 /* loop over all appended conditions */
193 /* restore the pattern */
194 *condition
= condchar
;
195 /* parse the condition (fun with bits) */
198 /* compare version numbers */
200 if (*++condition
== '=') {
218 /* isolate the version number from the next condition ... */
219 nextcondition
= strpbrk(condition
, "<>=!");
221 condchar
= *nextcondition
;
222 *nextcondition
= '\0';
224 /* and compare the versions (version_cmp removes the filename for us) */
225 if ((match
& (1 << (version_cmp(pkgname
, condition
) + 1))) == 0)
227 condition
= nextcondition
;
237 * Synopsis is similar to matchinstalled(), but use origin
238 * as a key for matching packages.
241 matchallbyorigin(const char **origins
, int *retval
)
243 char **installed
, **allorigins
= NULL
;
244 char ***matches
= NULL
;
250 installed
= matchinstalled(MATCH_ALL
, NULL
, retval
);
251 if (installed
== NULL
)
254 /* Gather origins for all installed packages */
255 for (i
= 0; installed
[i
] != NULL
; i
++) {
257 char *buf
, *cp
, tmp
[PATH_MAX
];
260 allorigins
= realloc(allorigins
, (i
+ 1) * sizeof(*allorigins
));
261 allorigins
[i
] = NULL
;
263 snprintf(tmp
, PATH_MAX
, "%s/%s", LOG_DIR
, installed
[i
]);
265 * SPECIAL CASE: ignore empty dirs, since we can can see them
266 * during port installation.
270 snprintf(tmp
, PATH_MAX
, "%s/%s", tmp
, CONTENTS_FNAME
);
271 fp
= fopen(tmp
, "r");
273 warnx("the package info for package '%s' is corrupt", installed
[i
]);
278 while (fgets(tmp
, sizeof(tmp
), fp
)) {
279 int len
= strlen(tmp
);
281 while (len
&& isspace(tmp
[len
- 1]))
286 if (tmp
[0] != CMD_CHAR
)
288 cmd
= plist_cmd(tmp
+ 1, &cp
);
289 if (cmd
== PLIST_ORIGIN
) {
290 asprintf(&buf
, "%s", cp
);
295 if (cmd
!= PLIST_ORIGIN
&& ( Verbose
|| 0 != strncmp("bsdpan-", installed
[i
], 7 ) ) )
296 warnx("package %s has no origin recorded", installed
[i
]);
300 /* Resolve origins into package names, retaining the sequence */
301 for (i
= 0; origins
[i
] != NULL
; i
++) {
302 matches
= realloc(matches
, (i
+ 1) * sizeof(*matches
));
303 struct store
*store
= NULL
;
304 store
= storecreate(store
);
306 for (j
= 0; installed
[j
] != NULL
; j
++) {
308 if (csh_match(origins
[i
], allorigins
[j
], FNM_PATHNAME
) == 0) {
309 storeappend(store
, installed
[j
]);
313 if (store
->used
== 0)
316 matches
[i
] = store
->store
;
320 for (i
= 0; installed
[i
] != NULL
; i
++)
330 * Synopsis is similar to matchinstalled(), but use origin
331 * as a key for matching packages.
334 matchbyorigin(const char *origin
, int *retval
)
336 const char *origins
[2];
342 tmp
= matchallbyorigin(origins
, retval
);
351 * Small linked list to memoize results of isinstalledpkg(). A hash table
352 * would be faster but for n ~= 1000 may be overkill.
355 LIST_ENTRY(iip_memo
) iip_link
;
359 LIST_HEAD(, iip_memo
) iip_memo
= LIST_HEAD_INITIALIZER(iip_memo
);
363 * Return 1 if the specified package is installed,
364 * 0 if not, and -1 if an error occured.
367 isinstalledpkg(const char *name
)
371 struct iip_memo
*memo
;
373 LIST_FOREACH(memo
, &iip_memo
, iip_link
) {
374 if (strcmp(memo
->iip_name
, name
) == 0)
375 return memo
->iip_result
;
379 asprintf(&buf
, "%s/%s", LOG_DIR
, name
);
382 if (!isdir(buf
) || access(buf
, R_OK
) == FAIL
) {
385 asprintf(&buf2
, "%s/%s", buf
, CONTENTS_FNAME
);
389 if (!isfile(buf2
) || access(buf2
, R_OK
) == FAIL
)
402 memo
= malloc(sizeof *memo
);
405 memo
->iip_name
= buf
;
406 memo
->iip_result
= result
;
407 LIST_INSERT_HEAD(&iip_memo
, memo
, iip_link
);
419 * Returns 1 if specified pkgname matches RE pattern.
420 * Otherwise returns 0 if doesn't match or -1 if RE
421 * engine reported an error (usually invalid syntax).
424 rex_match(const char *pattern
, const char *pkgname
, int extended
)
433 errcode
= regcomp(&rex
, pattern
, (extended
? REG_EXTENDED
: REG_BASIC
) | REG_NOSUB
);
435 errcode
= regexec(&rex
, pkgname
, 0, NULL
, 0);
439 } else if (errcode
!= REG_NOMATCH
) {
440 regerror(errcode
, &rex
, errbuf
, sizeof(errbuf
));
441 warnx("%s: %s", pattern
, errbuf
);
451 * Match string by a csh-style glob pattern. Returns 0 on
452 * match and FNM_NOMATCH otherwise, to be compatible with
456 csh_match(const char *pattern
, const char *string
, int flags
)
458 int ret
= FNM_NOMATCH
;
461 const char *nextchoice
= pattern
;
462 const char *current
= NULL
;
470 const char *pos
= nextchoice
;
471 const char *postfix
= NULL
;
473 Boolean quoted
= FALSE
;
489 prefixlen
= pos
-pattern
;
493 if (level
== 1 && !nextchoice
) {
495 currentlen
= pos
-current
;
502 currentlen
= pos
-current
;
508 if (*eb
== '!' || *eb
== '^')
512 while(*eb
&& *eb
!= ']')
528 char buf
[FILENAME_MAX
];
529 snprintf(buf
, sizeof(buf
), "%.*s%.*s%s", prefixlen
, pattern
, currentlen
, current
, postfix
);
530 ret
= csh_match(buf
, string
, flags
);
532 current
= nextchoice
;
537 ret
= fnmatch(pattern
, string
, flags
);
544 * Create an empty store, optionally deallocating
545 * any previously allocated space if store != NULL.
548 storecreate(struct store
*store
)
553 store
= malloc(sizeof *store
);
555 warnx("%s(): malloc() failed", __func__
);
560 } else if (store
->store
!= NULL
) {
561 /* Free previously allocated memory */
562 for (i
= 0; store
->store
[i
] != NULL
; i
++)
563 free(store
->store
[i
]);
564 store
->store
[0] = NULL
;
572 * Append specified element to the provided store.
575 storeappend(struct store
*store
, const char *item
)
577 if (store
->used
+ 2 > store
->currlen
) {
578 store
->currlen
+= 16;
579 store
->store
= reallocf(store
->store
,
580 store
->currlen
* sizeof(*(store
->store
)));
581 if (store
->store
== NULL
) {
583 warnx("%s(): reallocf() failed", __func__
);
588 asprintf(&(store
->store
[store
->used
]), "%s", item
);
589 if (store
->store
[store
->used
] == NULL
) {
590 warnx("%s(): malloc() failed", __func__
);
594 store
->store
[store
->used
] = NULL
;
600 fname_cmp(const FTSENT
* const *a
, const FTSENT
* const *b
)
602 return strcmp((*a
)->fts_name
, (*b
)->fts_name
);