1/*
2 * FreeBSD install - a package for the installation and maintenance
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 * Jeremy D. Lea.
15 * 11 May 2002
16 *
17 * This is the version module. Based on pkg_version.pl by Bruce A. Mah.
18 *
19 */
20
21#include <sys/cdefs.h>
22__FBSDID("$FreeBSD$");
23
24#include "lib.h"
25#include "version.h"
26#include <err.h>
27#include <fetch.h>
28#include <signal.h>
29
30static FILE *IndexFile;
31static char IndexPath[PATH_MAX] = "";
32static struct index_head Index = SLIST_HEAD_INITIALIZER(Index);
33
34static int pkg_do(char *);
35static void show_version(Package, const char *, const char *);
36
37/*
38 * This is the traditional pkg_perform, except that the argument is _not_ a
39 * list of packages. It is the index file from the command line.
40 *
41 * We loop over the installed packages, matching them with the -s flag if
42 * needed and calling pkg_do(). Beforehand we set up a few things, and after
43 * we tear them down...
44 *
45 * Returns 0 on success, non-zero on failure, corresponding to the number of
46 * failed attempts to access the INDEX.
47 */
48int
49pkg_perform(char **indexarg)
50{
51    char **pkgs, *pat[2], **patterns;
52    struct index_entry *ie;
53    int i, err_cnt = 0, rel_major_ver;
54    int MatchType;
55
56    struct utsname u;
57
58    if (uname(&u) == -1) {
59	warn("%s: failed to determine uname information", __func__);
60	return 1;
61    } else if ((rel_major_ver = (int) strtol(u.release, NULL, 10)) <= 0) {
62	warnx("%s: bad release version specified: %s", __func__, u.release);
63	return 1;
64    }
65
66    /*
67     * Try to find and open the INDEX. We only check IndexFile != NULL
68     * later, if we actually need the INDEX.
69     */
70    if (*indexarg == NULL) {
71	snprintf(IndexPath, sizeof(IndexPath), "%s/INDEX-%d", PORTS_DIR,
72	    rel_major_ver);
73    } else
74	strlcpy(IndexPath, *indexarg, sizeof(IndexPath));
75    if (isURL(IndexPath))
76	IndexFile = fetchGetURL(IndexPath, "");
77    else
78	IndexFile = fopen(IndexPath, "r");
79
80    /* Get either a list of matching or all packages */
81    if (MatchName != NULL) {
82	pat[0] = MatchName;
83	pat[1] = NULL;
84	MatchType = RegexExtended ? MATCH_EREGEX : MATCH_REGEX;
85	patterns = pat;
86     } else {
87	MatchType = MATCH_ALL;
88	patterns = NULL;
89     }
90
91    if (LookUpOrigin != NULL)
92	pkgs = matchbyorigin(LookUpOrigin, &err_cnt);
93    else
94	pkgs = matchinstalled(MatchType, patterns, &err_cnt);
95
96    if (err_cnt != 0)
97	errx(2, "Unable to find package database directory!");
98    if (pkgs == NULL) {
99	if (LookUpOrigin != NULL) {
100	    warnx("no packages recorded with this origin");
101	    return (1);
102	} else {
103	    switch (MatchType) {
104	    case MATCH_ALL:
105		warnx("no packages installed");
106		return (0);
107	    case MATCH_EREGEX:
108	    case MATCH_REGEX:
109		warnx("no packages match pattern");
110		return (1);
111	    default:
112		break;
113	    }
114	}
115    }
116
117    for (i = 0; pkgs[i] != NULL; i++)
118	err_cnt += pkg_do(pkgs[i]);
119
120    /* If we opened the INDEX in pkg_do(), clean up. */
121    while (!SLIST_EMPTY(&Index)) {
122	ie = SLIST_FIRST(&Index);
123	SLIST_REMOVE_HEAD(&Index, next);
124	if (ie->name != NULL)
125	    free(ie->name);
126	if (ie->origin != NULL)
127	    free(ie->origin);
128	free(ie);
129    }
130    if (IndexFile != NULL)
131	fclose(IndexFile);
132
133    return err_cnt;
134}
135
136/*
137 * Traditional pkg_do(). We take the package name we are passed and
138 * first slurp in the CONTENTS file, getting name and origin, then
139 * we look for it's corresponding Makefile. If that fails we pull in
140 * the INDEX, and check there.
141 */
142static int
143pkg_do(char *pkg)
144{
145    char *ch, tmp[PATH_MAX], tmp2[PATH_MAX], *latest = NULL;
146    Package plist;
147    struct index_entry *ie;
148    FILE *fp;
149    size_t len;
150
151    /* Suck in the contents list. */
152    plist.head = plist.tail = NULL;
153    plist.name = plist.origin = NULL;
154    snprintf(tmp, PATH_MAX, "%s/%s/%s", LOG_DIR, pkg, CONTENTS_FNAME);
155    fp = fopen(tmp, "r");
156    if (!fp) {
157	warnx("the package info for package '%s' is corrupt", pkg);
158	return 1;
159    }
160    read_plist(&plist, fp);
161    fclose(fp);
162    if (plist.name == NULL) {
163    	warnx("%s does not appear to be a valid package!", pkg);
164    	return 1;
165    }
166
167    /*
168     * First we check if the installed package has an origin, and try
169     * looking for it's Makefile. If we find the Makefile we get the
170     * latest version from there. If we fail, we start looking in the
171     * INDEX, first matching the origin and then the package name.
172     */
173    if (plist.origin != NULL && !UseINDEXOnly) {
174	snprintf(tmp, PATH_MAX, "%s/%s", PORTS_DIR, plist.origin);
175	if (isdir(tmp) && chdir(tmp) != FAIL && isfile("Makefile")) {
176	    if ((latest = vpipe("/usr/bin/make -V PKGNAME", tmp)) == NULL)
177		warnx("Failed to get PKGNAME from %s/Makefile!", tmp);
178	    else
179		show_version(plist, latest, "port");
180	}
181    }
182    if (latest == NULL) {
183	/* Report package as not found in INDEX if the INDEX is not required. */
184	if (IndexFile == NULL && !UseINDEXOnly)
185		show_version(plist, NULL, plist.origin);
186	else {
187	/* We only pull in the INDEX once, if needed. */
188	if (SLIST_EMPTY(&Index)) {
189	    if (!IndexFile)
190		errx(2, "Unable to open %s in %s.", IndexPath, __func__);
191	    while ((ch = fgetln(IndexFile, &len)) != NULL) {
192		/*
193		 * Don't use strlcpy() because fgetln() doesn't
194		 * return a valid C string.
195		 */
196		strncpy(tmp, ch, MIN(len, PATH_MAX));
197		tmp[PATH_MAX-1] = '\0';
198		/* The INDEX has pkgname|portdir|... */
199		if ((ch = strchr(tmp, '|')) != NULL)
200		    ch[0] = '\0';
201		if (ch != NULL && (ch = strchr(&ch[1], '|')) != NULL)
202		    ch[0] = '\0';
203		/* Look backwards for the last two dirs = origin */
204		while (ch != NULL && *--ch != '/')
205		    if (ch[0] == '\0')
206			ch = NULL;
207		while (ch != NULL && *--ch != '/')
208		    if (ch[0] == '\0')
209			ch = NULL;
210		if (ch == NULL)
211		    errx(2, "The INDEX does not appear to be valid!");
212		if ((ie = malloc(sizeof(struct index_entry))) == NULL)
213		    errx(2, "Unable to allocate memory in %s.", __func__);
214		bzero(ie, sizeof(struct index_entry));
215		ie->name = strdup(tmp);
216		ie->origin = strdup(&ch[1]);
217		/* Who really cares if we reverse the index... */
218		SLIST_INSERT_HEAD(&Index, ie, next);
219	    }
220	}
221	/* Now that we've slurped in the INDEX... */
222	SLIST_FOREACH(ie, &Index, next) {
223	    if (plist.origin != NULL) {
224		if (strcmp(plist.origin, ie->origin) == 0)
225		    latest = strdup(ie->name);
226	    } else {
227		strlcpy(tmp, ie->name, PATH_MAX);
228		strlcpy(tmp2, plist.name, PATH_MAX);
229		/* Chop off the versions and compare. */
230		if ((ch = strrchr(tmp, '-')) == NULL)
231		    errx(2, "The INDEX does not appear to be valid!");
232		ch[0] = '\0';
233		if ((ch = strrchr(tmp2, '-')) == NULL)
234		    warnx("%s is not a valid package!", plist.name);
235		else
236		    ch[0] = '\0';
237		if (strcmp(tmp2, tmp) == 0) {
238		    if (latest != NULL) {
239			/* Multiple matches */
240			snprintf(tmp, PATH_MAX, "%s|%s", latest, ie->name);
241			free(latest);
242			latest = strdup(tmp);
243		    } else
244			latest = strdup(ie->name);
245		}
246	    }
247	}
248	if (latest == NULL)
249	    show_version(plist, NULL, NULL);
250	else
251	    show_version(plist, latest, "index");
252	}
253    }
254    if (latest != NULL)
255	free(latest);
256    free_plist(&plist);
257    return 0;
258}
259
260#define OUTPUT(c) ((PreventChars != NULL && !strchr(PreventChars, (c))) || \
261			(LimitChars != NULL && strchr(LimitChars, (c))) || \
262			(PreventChars == NULL && LimitChars == NULL))
263
264/*
265 * Do the work of comparing and outputing. Ugly, but well that's what
266 * You get when you try to match perl output in C ;-).
267 */
268void
269show_version(Package plist, const char *latest, const char *source)
270{
271    char *ch, tmp[PATH_MAX];
272    const char *ver;
273    int cmp = 0;
274
275    if (!plist.name || strlen(plist.name) == 0)
276	return;
277    if (ShowOrigin != FALSE && plist.origin != NULL)
278	strlcpy(tmp, plist.origin, PATH_MAX);
279    else {
280	strlcpy(tmp, plist.name, PATH_MAX);
281	if (!Verbose) {
282	    if ((ch = strrchr(tmp, '-')) != NULL)
283		ch[0] = '\0';
284	}
285    }
286    if (latest == NULL) {
287	if (source == NULL && OUTPUT('!')) {
288	    printf("%-34s  !", tmp);
289	    if (Verbose)
290		printf("   Comparison failed");
291	    printf("\n");
292	} else if (OUTPUT('?')) {
293	    printf("%-34s  ?", tmp);
294	    if (Verbose)
295		printf("   orphaned: %s", plist.origin);
296	    printf("\n");
297	}
298    } else if (strchr(latest,'|') != NULL) {
299	if (OUTPUT('*')) {
300	    printf("%-34s  *", tmp);
301	    if (Verbose) {
302		strlcpy(tmp, latest, PATH_MAX);
303		ch = strchr(tmp, '|');
304		ch[0] = '\0';
305
306		ver = strrchr(tmp, '-');
307		ver = ver ? &ver[1] : tmp;
308		printf("   multiple versions (index has %s", ver);
309		do {
310		    ver = strrchr(&ch[1], '-');
311		    ver = ver ? &ver[1] : &ch[1];
312		    if ((ch = strchr(&ch[1], '|')) != NULL)
313			    ch[0] = '\0';
314		    printf(", %s", ver);
315		} while (ch != NULL);
316		printf(")");
317	    }
318	    printf("\n");
319	}
320    } else {
321	cmp = version_cmp(plist.name, latest);
322	ver = strrchr(latest, '-');
323	ver = ver ? &ver[1] : latest;
324	if (cmp < 0 && OUTPUT('<')) {
325	    if (Quiet)
326		printf("%s", tmp);
327	    else {
328		printf("%-34s  <", tmp);
329		if (Verbose)
330		    printf("   needs updating (%s has %s)", source, ver);
331	    }
332	    printf("\n");
333	} else if (cmp == 0 && OUTPUT('=')) {
334	    if (Quiet)
335		printf("%s", tmp);
336	    else {
337		printf("%-34s  =", tmp);
338		if (Verbose)
339		    printf("   up-to-date with %s", source);
340	    }
341	    printf("\n");
342	} else if (cmp > 0 && OUTPUT('>')) {
343	    if (Quiet)
344		printf("%s", tmp);
345	    else {
346		printf("%-34s  >", tmp);
347		if (Verbose)
348		    printf("   succeeds %s (%s has %s)", source, source, ver);
349	    }
350	    printf("\n");
351	}
352    }
353}
354
355int
356version_match(char *pattern, const char *pkgname)
357{
358    int ret = 0;
359    int matchstream = 0;
360    FILE *fp = NULL;
361    Boolean isTMP = FALSE;
362
363    if (isURL(pkgname)) {
364	fp = fetchGetURL(pkgname, "");
365	isTMP = TRUE;
366	matchstream = 1;
367	if (fp == NULL)
368	    errx(2, "Unable to open %s.", pkgname);
369    } else if (pkgname[0] == '/') {
370	fp = fopen(pkgname, "r");
371	isTMP = TRUE;
372	matchstream = 1;
373	if (fp == NULL)
374	    errx(2, "Unable to open %s.", pkgname);
375    } else if (strcmp(pkgname, "-") == 0) {
376	fp = stdin;
377	matchstream = 1;
378    } else if (isURL(pattern)) {
379	fp = fetchGetURL(pattern, "");
380	isTMP = TRUE;
381	matchstream = -1;
382	if (fp == NULL)
383	    errx(2, "Unable to open %s.", pattern);
384    } else if (pattern[0] == '/') {
385	fp = fopen(pattern, "r");
386	isTMP = TRUE;
387	matchstream = -1;
388	if (fp == NULL)
389	    errx(2, "Unable to open %s.", pattern);
390    } else if (strcmp(pattern, "-") == 0) {
391	fp = stdin;
392	matchstream = -1;
393    } else {
394	ret = pattern_match(MATCH_GLOB, pattern, pkgname);
395    }
396
397    if (fp != NULL) {
398	size_t len;
399	char *line;
400	while ((line = fgetln(fp, &len)) != NULL) {
401	    int match;
402	    char *ch, ln[2048];
403	    size_t lnlen;
404	    if (len > 0 && line[len-1] == '\n')
405		len --;
406	    lnlen = len;
407	    if (lnlen > sizeof(ln)-1)
408		lnlen = sizeof(ln)-1;
409	    memcpy(ln, line, lnlen);
410	    ln[lnlen] = '\0';
411	    if ((ch = strchr(ln, '|')) != NULL)
412    		ch[0] = '\0';
413	    if (matchstream > 0)
414	    	match = pattern_match(MATCH_GLOB, pattern, ln);
415	    else
416	    	match = pattern_match(MATCH_GLOB, ln, pkgname);
417	    if (match == 1) {
418		ret = 1;
419		printf("%.*s\n", (int)len, line);
420	    }
421	}
422	if (isTMP)
423	    fclose(fp);
424    }
425
426    return ret;
427}
428
429void
430cleanup(int sig)
431{
432    if (sig)
433	exit(1);
434}
435