File:  [Public] / rpmfind / rpmfind.c
Revision 1.20: download - view: text, annotated - select for diffs
Thu Aug 20 04:53:44 1998 UTC (25 years, 9 months ago) by veillard
Branches: MAIN
CVS tags: HEAD
Added sorting of the metadata mirror list, Daniel.

/*
 * rpmfind.c : Minimalist locate and transfer module for FTP and HTTP requests
 *
 * See Copyright for the status of this software.
 *
 * $Id: rpmfind.c,v 1.20 1998/08/20 04:53:44 veillard Exp $
 */

#include "config.h"
#include <sys/types.h>
#include <sys/time.h>
#include <sys/stat.h>
#ifdef HAVE_FCNTL_H
#include <fcntl.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <dirent.h>
#include <errno.h>
#include <time.h>
#ifdef HAVE_REGEX_H
#include <regex.h>
#else
#ifdef HAVE_RXPOSIX_H
#include <rxposix.h>
#endif
#endif

#include <rpm/rpmlib.h>
#include "rpmdata.h"
#include "rdf_api.h"
#include "deps.h"
#include "language.h"
#include "transport.h"
#include "database.h"
#include "rpmfind.h"

/*
 * Global variables.
 */
char *myServer = "http://rufus.w3.org/linux/RDF/";
int rpmfindVerbose = 1;
int rpmfindMode = RPM_FIND_LOOKUP;
int rpmfindMirrorUpgrade = 1;
int rpmfindUseOrigin = 0;
char *rpmfindTempDir = NULL;

/*
 * Ftp informations
 */
char *downloadDir = "/tmp";

/*
 * Informations on the current setup.
 */
char *myPrefix = "/";
char *myArch = NULL;
char *myOs = NULL;
char *myVendor = "Red Hat Software";
char *myDistribution = "Hurricane";
int   nbInstalledPackages = 0;
static rpmdb myDb;

time_t myTime = 0;

/****************************************************************
 *								*
 *			NAME PARSER				*
 *								*
 ****************************************************************/

/*
 * Truncate a resource name, trying to extract name-version-release
 */
void tokenizeName(char *resource, char **name, char **version,
                  char **release) {
    static char myName[200];
    static char myVersion[200];
    static char myRelease[200];
    char *cur;
    char *out;
    
    cur = resource;

    myName[0] = '\0';
    myVersion[0] = '\0';
    myRelease[0] = '\0';
    *version = NULL;
    *release = NULL;
    *name = &myName[0];
    out = &myName[0];
    while (1) {
        if (*cur == '\0') {
	    *out = '\0';
	    return;
	}    
	if (*cur == '-') {
	    cur++;
	    if ((*cur >= '0') && (*cur <= '9')) {
	        *out = '\0';
	        break;
	    }
	    *out++ = '-';
	}
	*out++ = *cur++;
    }
    *version = &myVersion[0];
    out = &myVersion[0];
    while (1) {
        if (*cur == '\0') {
	    *out = '\0';
	    return;
	}    
	if (*cur == '-') {
	    *out = '\0';
	    break;
	}
	*out++ = *cur++;
    }
    *release = &myRelease[0];
    out = &myRelease[0];
    while (1) {
        if (*cur == '\0') {
	    *out = '\0';
	    return;
	}    
	*out++ = *cur++;
    }
}



/****************************************************************
 *								*
 *			NETWORK CACHE				*
 *								*
 ****************************************************************/

/*
 * The files collected from the net
 * This act as a cache for informations already collected from
 * the network, this is primordial to not ask continously metadata
 * for the same packages.
 */
int nbNetResources = 0;
typedef struct netResource {
    struct netResource *next;	/* link to next in list */
    struct netResource *prev;	/* link to prev in list */
    char *URL;			/* The URL of the resource */
    char *filename;		/* The filename containing the content */
} netResource, *netResourcePtr;

static netResourcePtr netResourceList = NULL;

int findResource(neededPackagePtr father, char *resource);

/*
 * Add a new net resource.
 */
void netResourceAdd(char *URL, char *filename) {
    netResourcePtr cur;

    cur = (netResourcePtr) malloc(sizeof(netResource));
    if (cur == NULL) {
        fprintf(stderr, "netResourceAdd : ran out of memory !\n");
	exit(1);
    }
    cur->URL = strdup(URL);
    if (filename != NULL)
	cur->filename = strdup(filename);
    else
        cur->filename = NULL;
    cur->next = netResourceList;
    cur->prev = NULL;
    if (cur->next != NULL)
        cur->next->prev = cur;
    netResourceList = cur;
}

/*
 * Search a net resource.
 */
char *netResourceSearch(char *URL) {
    netResourcePtr cur;

    cur = netResourceList;

    while (cur != NULL) {
        if (!strcmp(URL, cur->URL)) return(cur->filename);
        cur = cur->next;
    }
    return(NULL);
}

/*
 * Cleanup the net resources.
 */
void netResourceClean(void) {
    netResourcePtr cur, next;

    cur = netResourceList;

    while (cur != NULL) {
        next = cur->next; 
	if (cur->filename != NULL) {
	    unlink(cur->filename);
	    free(cur->filename);
	}
	free(cur->URL);
	cur->URL = NULL;
	free(cur);
        cur = next;
    }
}

/****************************************************************
 *								*
 *			PACKAGE SELECTION			*
 *								*
 ****************************************************************/

/*
 * Give a score for a package version number.
 * e.g. 1.1.2  returns 1,001,002
 *      1.2beta3  "    1,002,000
 */

int rpmFindVersionValue(const char *version) {
    const char *cur = version;
    int res = 0;
    int val = 0;
    int multiplier = 1000 * 1000;

    while (*cur != '\0') {
        if ((*cur >= '0') && (*cur <= '9'))
	    val = val * 10 + (*cur - '0');
	else
	    break;
	cur++;
    }
    if (val >= 70) goto done;
    res += val * multiplier;
    if (*cur != '.') goto done;

    val = 0;
    multiplier /= 1000;
    cur++;
    
    while (*cur != '\0') {
        if ((*cur >= '0') && (*cur <= '9'))
	    val = val * 10 + (*cur - '0');
	else
	    break;
	cur++;
    }
    if (val >= 1000) goto done;
    res += val * multiplier;
    if (*cur != '.') goto done;

    val = 0;
    multiplier /= 1000;
    cur++;

    while (*cur != '\0') {
        if ((*cur >= '0') && (*cur <= '9'))
	    val = val * 10 + (*cur - '0');
	else
	    break;
	cur++;
    }
    if (val >= 1000) goto done;
    res += val * multiplier;
done:
    return(res);
}

/*
 * Do a package info evaluation, criteria:
 *    - Where does it come from (match myVendor/myDistribution)
        affinity w.r.t. father and current setup
 *    - version/release numbers
 *    - how old, give priority to (very) recent packages
 */
void neededPackageEvaluate(neededPackagePtr father, neededPackagePtr cur) {
    int rating = 0;
    int distrib_rating = 0;
    unsigned long basetime;
    unsigned long delta;
    const char *ID;

    /*
     * Get the distribution rating, if any...
     */
    ID = rpmPackageFindDistrib(cur);
    if (ID != NULL)
	distrib_rating = rpmDistribGetRating(ID);
    if (distrib_rating < 0) {
	cur->rating = 0;
	return;
    }

    if (rpmfindMode == RPM_FIND_LATEST) {
	cur->rating = rpmFindVersionValue(cur->version);
	return;
    } else {
	rating = rpmFindVersionValue(cur->version) / 1000;
    }
    cur->rating += distrib_rating;
    if (father != NULL) {
	if (!strcmp(cur->vendor, father->vendor)) {
	    rating += MED_RATING;
	    if (!strcmp(cur->distribution, father->distribution))
		rating += HIGH_RATING;
	}
	if (!strcmp(cur->vendor, myVendor)) {
	    rating += LOW_RATING;
	    if (!strcmp(cur->distribution, myDistribution))
		rating += MED_RATING;
	}
    } else {
	if (!strcmp(cur->vendor, myVendor)) {
	    rating += MED_RATING;
	    if (!strcmp(cur->distribution, myDistribution))
		rating += HIGH_RATING;
	}
    }
    if (rpmfindVerbose > 2)
        printf("vendor rating : %d\n", rating);

    if (father == NULL) 
        basetime = myTime;
    else 
        basetime = father->date;

    if (cur->date < basetime) delta = basetime - cur->date;
    else delta = cur->date - basetime;

    delta = HIGH_RATING * MAX_DAY * 2 / 
            ((HIGH_RATING * delta) / (24 * 60 * 60 * 10) + MAX_DAY);
    rating += delta;
    if (rpmfindVerbose > 2)
	printf("date rating : %d\n", (int) delta);

    if (cur->size > 1000) {
        rating += (HIGH_RATING * 1024) / cur->size;
	if (rpmfindVerbose > 2)
	    printf("size rating : %d\n", 
	            (HIGH_RATING * 1024) / cur->size);
    }
    cur->rating = rating;
}

/*
 * Check-up architecture and Os compatibilities
 */
int neededPackageCompatible(neededPackagePtr cur) {
    if (!strcasecmp(cur->arch, "noarch")) return(1);
    if (strcasecmp(cur->os, myOs)) return(0);
    if (strcasecmp(cur->arch, myArch)) {
        /*
	 * IAPX86 familly compatibility chart.
	 */
	if (((myArch[0] == 'i') && (cur->arch[0] == 'i')) &&
	    ((myArch[2] == '8') && (cur->arch[2] == '8')) &&
	    ((myArch[3] == '6') && (cur->arch[3] == '6'))) {
	    /* ascending compatibility */
	    if (cur->arch[1] <= myArch[1]) return(1);
	    /* 386 accept 486 optimized binaries */
	    if ((cur->arch[1] == '4') && (myArch[1] == '3')) return(1);
	    /* 586 accept 686 optimized binaries */
	    if ((cur->arch[1] == '6') && (myArch[1] == '5')) return(1);
	}    
	return(0);
    }
    return(1);
}

/*
 * Comparision funtion for sorting.
 */
 
typedef int (*neededPackageCmpFunc)
        (const neededPackagePtr a, const neededPackagePtr b);

int neededPackageCmpRating(const neededPackagePtr *a, const neededPackagePtr *b) {
    if ((*a == NULL) && (*b == NULL)) return(0);
    if (*a == NULL) return(+1);
    if (*b == NULL) return(-1);
    return((*b)->rating - (*a)->rating);
}

/*
 * Cleanup a list of neededPackage structures, remove wrong arch and
 * wrong os, then set-up preferences w.r.t the current setup, timestamp
 * and user's preferences.
 */
void neededPackageSelect(neededResourcePtr res, neededPackagePtr father) {
    int i;
    neededPackagePtr cur;

    /*
     * Walk the list, remove invalid packages and give an
     * value for the package.
     */
    for (i = 0;i < res->nb_pkgs;i++) {
	cur = res->pkgs[i];
	/*
	 * Test for package removal.
	 */
        if (!neededPackageCompatible(cur)) {
	    if (rpmfindVerbose > 1)
		printf("removing %s-%s-%s, wrong platform %s/%s\n",
	            cur->name, cur->version, cur->release, cur->os, cur->arch);
	    res->pkgs[i]->rating = -1; 
	} else {
            neededPackageEvaluate(father, cur);
	}
    }

    /*
     * Now sort the list by rating.
     */
    qsort(res->pkgs, res->nb_pkgs, sizeof(neededPackagePtr),
          (int (*)(const void *, const void *)) neededPackageCmpRating);
}

/****************************************************************
 *								*
 *			RDF METADATA INTERFACE			*
 *								*
 ****************************************************************/

/*
 * Parse a resource RDF info file and setup the package list
 * of a neededResource structure.
 */
void neededResourceParseRdf(neededResourcePtr res, const char *file) {
    rdfSchema rdf;
    rdfNamespace rpmNs;
    rdfNamespace rdfNs;
    rdfDescription desc;
    char *value;
    char *filename;
    char *URL;
    neededPackagePtr info = NULL;
    int i;

    rdf = rdfRead(file);
    if (rdf == NULL) return;

    /*
     * Start the analyze, check that's an RDF for RPM packages.
     */
    rdfNs = rdfGetNamespace(rdf, "http://www.w3.org/TR/WD-rdf-syntax#");
    if (rdfNs == NULL) {
	if (rpmfindVerbose)
	    printf("%s is not an RDF schema\n", file);
	rdfDestroySchema(rdf);
	return;
    }
    rpmNs = rdfGetNamespace(rdf, "http://www.rpm.org/");
    if (rdfNs == NULL) {
	if (rpmfindVerbose)
	    printf("%s is not an RPM specific RDF schema\n", file);
	rdfDestroySchema(rdf);
	return;
    }
    desc = rdfFirstDescription(rdf);
    if (rdfNs == NULL) {
	if (rpmfindVerbose)
	    printf("%s RDF schema seems empty\n", file);
	rdfDestroySchema(rdf);
	return;
    }

    /*
     * We are pretty sure that it will be a valid schema,
     * Walk the tree and collect the packages descriptions.
     */
    while (desc != NULL) {
        /*
	 * Get the resource URL ...
	 */
	value = rdfGetDescriptionHref(rdf, desc);
	if (value != NULL) URL = strdup(value);
	else {
	    if (rpmfindVerbose)
		printf("%s RDF schema invalid : Desc without href\n",
		        file);
	    rdfDestroySchema(rdf);
	    return;
	}
	filename = &value[strlen(value)];
	while ((filename > value) && (*filename != '/'))
	    filename--;


        /*
	 * if already registered, add it directly and skip to next one.
	 */
	info = neededPackageSearch(URL);
	if (info != NULL) {
	    neededResourceAddPackage(res, info);
	    desc = rdfNextDescription(desc); 
            continue;
	}

	/*
	 * Allocate the neededPackage structure, initialize it.
	 */
	info = (neededPackagePtr) malloc(sizeof(neededPackage));
	if (info == NULL) {
	    fprintf(stderr, "rpmOpenRdf : out of memory !\n");
	    rdfDestroySchema(rdf);
	    return;
	}
	memset(info, 0, sizeof(neededPackage));
	info->rpmdata = NULL;
	info->mirror = -1;
	info->distribNr = -1;
	info->status = STATUS_NONE;
	info->refcount = 0;
	if (filename != value) {
	    filename++;
	    info->filename = strdup(filename);
	} else
	    info->filename = NULL;
	info->dir = NULL; /* Computed only at fetch time if needed */

	/*
	 * Now extract all the metadata informations from the RDF tree
	 */
	rdfGetValue(desc, "Name", rpmNs, &value, NULL);
	if (value != NULL) info->name = strdup(value);
	else {
	    if (rpmfindVerbose)
		printf("%s RDF schema invalid : no Name\n", file);
	    rdfDestroySchema(rdf);
	    return;
	}
	rdfGetValue(desc, "Version", rpmNs, &value, NULL);
	if (value != NULL) info->version = strdup(value);
	else {
	    if (rpmfindVerbose)
		printf("%s RDF schema invalid : no Version\n", file);
	    rdfDestroySchema(rdf);
	    return;
	}
	rdfGetValue(desc, "Release", rpmNs, &value, NULL);
	if (value != NULL) info->release = strdup(value);
	else {
	    if (rpmfindVerbose)
		printf("%s RDF schema invalid : no Release\n", file);
	    rdfDestroySchema(rdf);
	    return;
	}
	rdfGetValue(desc, "Arch", rpmNs, &value, NULL);
	if (value != NULL) info->arch = strdup(value);
	else info->arch = strdup(localizedStrings[LANG_NONE]);

	rdfGetValue(desc, "Os", rpmNs, &value, NULL);
	if (value != NULL) info->os = strdup(value);
	else info->os = strdup("linux");

	rdfGetValue(desc, "Distribution", rpmNs, &value, NULL);
	if (value != NULL) info->distribution = strdup(value);
	else info->distribution = strdup(localizedStrings[LANG_UNKNOWN]);

	rdfGetValue(desc, "Vendor", rpmNs, &value, NULL);
	if (value != NULL) info->vendor = strdup(value);
	else info->vendor = strdup(localizedStrings[LANG_UNKNOWN]);

	rdfGetValue(desc, "Date", rpmNs, &value, NULL);
	if (value != NULL) {
	    if (sscanf(value, "%d", &i) != 1)
		info->date = 0;
	    else
		info->date = i;
	} else info->date = 0;

	rdfGetValue(desc, "Size", rpmNs, &value, NULL);
	if (value != NULL) {
	    if (sscanf(value, "%d", &i) != 1)
		info->size = 0;
	    else
		info->size = i;
	} else info->size = 0;

	rdfGetValue(desc, "Subdir", rpmNs, &value, NULL);
	if (value != NULL) info->subdir = strdup(value);
	else info->subdir = strdup(localizedStrings[LANG_UNKNOWN]);

        info->URL = URL;

        /*
	 * Link in the new list;
	 */
	neededResourceAddPackage(res, info);

        /*
	 * Skip to next Description in the RDf file.
	 */
	desc = rdfNextDescription(desc); 
    }

    /*
     * Cleanup.
     */
    rdfDestroySchema(rdf);
}

/*
 * Do an apropos search in the whole catalog
 */
void aproposSearchRdf(const char *search, const char *catalog) {
    rdfSchema rdf;
    rdfNamespace rpmNs;
    rdfNamespace rdfNs;
    rdfDescription desc;
    char *value;
    char *URL;
    char *name;
    char *description;
    int nbhits = 0;
#ifdef HAVE_REGEX_H
    regex_t reg;
    regmatch_t pmatch[1];
#endif

    rdf = rdfRead(catalog);
    if (rdf == NULL) {
        fprintf(stderr, "Cannot open catalog %s\n", catalog);
	return;
    }

    /*
     * Start the analyze, check that's an RDF for RPM packages.
     */
    rdfNs = rdfGetNamespace(rdf, "http://www.w3.org/TR/WD-rdf-syntax#");
    if (rdfNs == NULL) {
	if (rpmfindVerbose)
	    printf("%s is not an RDF schema\n", catalog);
	rdfDestroySchema(rdf);
	return;
    }
    rpmNs = rdfGetNamespace(rdf, "http://www.rpm.org/");
    if (rdfNs == NULL) {
	if (rpmfindVerbose)
	    printf("%s is not an RPM specific RDF schema\n", catalog);
	rdfDestroySchema(rdf);
	return;
    }
    desc = rdfFirstDescription(rdf);
    if (rdfNs == NULL) {
	if (rpmfindVerbose)
	    printf("%s RDF schema seems empty\n", catalog);
	rdfDestroySchema(rdf);
	return;
    }

#ifdef HAVE_REGEX_H
#ifdef REG_ICASE
    regcomp(&reg, search, REG_ICASE);
#else
    regcomp(&reg, search, 0);
#endif
#endif

    /*
     * Search the string in the package name and descriptions.
     */
    while (desc != NULL) {
        /*
	 * Get the resource URL ...
	 */
	value = rdfGetDescriptionHref(rdf, desc);
	if (value != NULL) URL = value;
	else {
	    if (rpmfindVerbose)
		printf("%s RDF schema invalid : Desc without href\n",
		        catalog);
	    rdfDestroySchema(rdf);
	    return;
	}

	/*
	 * Now extract all the metadata informations from the RDF tree
	 */
	rdfGetValue(desc, "Name", rpmNs, &value, NULL);
	if (value != NULL) name = value;
	else {
	    if (rpmfindVerbose)
		printf("%s RDF schema invalid : no Name\n", catalog);
	    rdfDestroySchema(rdf);
	    return;
	}
	rdfGetValue(desc, "Summary", rpmNs, &value, NULL);
	if (value != NULL) description = value;
	else description = "";

        /*
	 * Search the regex/string.
	 */
#ifdef HAVE_REGEX_H
        if ((!regexec(&reg, name, 1, pmatch, 0)) ||
	    (!regexec(&reg, description, 1, pmatch, 0)))
#elif HAVE_STRSTR
	if ((strstr(name, search)) || (strstr(description, search)))
#else
#error "Code currently relies on regex(3) or strstr(3) availability ..."
#endif
	    printf("%d: %s\n  %s : %s\n\n", ++nbhits, URL, name, description);

        /*
	 * Skip to next Description in the RDf catalog.
	 */
	desc = rdfNextDescription(desc); 
    }

    /*
     * Cleanup.
     */
    rdfDestroySchema(rdf);
#ifdef HAVE_REGEX_H
    regfree(&reg);
#endif
    printf("Found %d packages related to %s\n", nbhits, search);
}

/****************************************************************
 *								*
 *			NETWORK INTERFACES			*
 *								*
 ****************************************************************/

rpmTransportRequestPtr runningRequests = NULL;

/*
 * Callback from the transport layer when a request has completed.
 */

int handleTransportEnd(rpmTransportRequestPtr req) {
    printf("handleTransportEnd :");
    runningRequestPrint(req);
    return(0);
}

/*
 * Load the apropos catalog, it's an RDf file but compressed for network
 * efficiency w.r.t. PPP users.
 */

char *loadAproposCatalog(void) {
    static char filename[1000];
    char URL[1500];
    char msg[100];
    struct stat buf;
    int res;
    time_t current_time = time(NULL);

    /* the catalog is stored along with the RPM fetched from the net */
    sprintf(filename, "%s/fullIndex.rdf.gz", rpmfindTempDir);

    /* Check file availability and age */
    res = stat(filename, &buf);
    if ((!res) && (buf.st_size > 1000)) {
        if (current_time - buf.st_mtime > 24 * 60 * 60) {
	    printf("The index is %d day(s) old, refetch [Y/n] : ",
	           (int) ((current_time - buf.st_mtime) / (24 * 60 * 60)));
	    fflush(stdout);
	    res = scanf("%s", &msg[0]);
	    if (res == 1) {
		switch (msg[0]) {
		    case 'n':
		    case 'N': return(filename);
		}
	    }
	} else
	    return(filename);
    }

    /* do the download ... */
    sprintf(URL, "%s/resources/fullIndex.rdf.gz", myServer);
    printf("Loading catalog to %s\n", filename);
    res = fetchURLToFile(URL, filename);
    if (res != 0) return(NULL);
    return(filename);
}

/*
 * Find a remote package providing a given resource.
 * Get the metadata for the ressources, then walk the package list
 * finding one to install on this system.
 */

int lookupRemoteResource(neededPackagePtr father, char *resource) {
    char *name;
    char *ver;
    char *rel;
    neededPackagePtr cur;
    char URL[500];
    char *filename;
    rpmDataPtr rpmdata;
    neededResourcePtr res;
    int i;
    int size = 0;
    int val;
    char fullname[200];

    tokenizeName(resource, &name, &ver, &rel);

    /*
     * Check for the list of resources we refuse to upgrade.
     */
    if (rpmNoUpgradeCheck(resource)) {
	if (rpmfindVerbose > 1)
	    printf("Refuses upgrade of %s\n", resource);
        return(-1);
    }

    /*
     * Check for the list of resources we refuse to depend on.
     */
    if (rpmNoDependCheck(resource)) {
	if (rpmfindVerbose > 1)
	    printf("Refuses dependency of %s\n", resource);
        return(-1);
    }

    if (rpmfindVerbose > 1)
	printf("lookupRemoteResource %s\n", resource);

    /*
     * Do the most selective request first.
     */
    if (rel != NULL) {
	/*
	 * Has this resource already been looked at ?
	 */
	res = neededResourceSearch(resource);
	if (res != NULL) {
	    neededPackageAddResource(father, res);
	    return(0);
	}

	/*
	 * Ok, start fetching Metadata !
	 */
	sprintf(URL, "%s/resources/%s.rdf", myServer, resource);
	filename = fetchURL(URL);
	if (filename == NULL) {
	    if (rpmfindVerbose)
		printf("Error fetching %s metadata\n", resource);
	    goto search_no_rel;
	} 
	res = neededResourceAdd(fullname, father);
	neededResourceParseRdf(res, filename);
	neededPackageSelect(res, father);
	if (rpmfindVerbose > 1) {
	    neededResourcePrintPkgs(res);
	}

	/*
	 * Check every possible package.
	 */
	
	for (i = 0;i < res->nb_pkgs;i++) {
	    cur = res->pkgs[i];
	    size = 0;

	    if (cur == NULL) continue;
	    if (cur->rating < 0) continue;
	    if (cur->status == STATUS_TO_INSTALL) return(0);
	    if (cur->status == STATUS_UNINSTALLABLE) continue;

	    /*
	     * Can we actually install this package on this system.
	     */
	    if (cur->rpmdata == NULL) {
		cur->status = STATUS_LOOKUP;
		sprintf(URL, "%s/%s/%s-%s-%s.%s.rdf",
			myServer, cur->subdir, cur->name, cur->version,
			cur->release, cur->arch);
		filename = fetchURL(URL);
		if (filename != NULL) {
		    cur->status = STATUS_LOOKUP;
		    rpmdata =  rpmOpenRdfFile(filename);
		    cur->rpmdata = rpmdata;
		    size += cur->size;
		}
	    }    

	    if (cur->rpmdata == NULL) {
		cur->status = STATUS_UNINSTALLABLE;
		continue;
	    }
	    rpmdata = cur->rpmdata;

	    /*
	     * Now check the resources needed by this package.
	     */
	    for (i = 0;i < rpmdata->nb_requires;i++) {
		if (!strcmp(resource, rpmdata->requires[i]))
		    continue;
		/*
		 * Check for the list of unacceptable dependencies
		 */
		if (rpmNoDependCheck(rpmdata->requires[i])) {
		    if (rpmfindVerbose > 1)
			printf(
			  "Refuses package %s-%s-%s.%s.rpm: depends on %s\n",
			       cur->name, cur->version, cur->release,
			       cur->arch, rpmdata->requires[i]);
		    continue;
		}

		val = findResource(cur, rpmdata->requires[i]);
		if (val < 0) {
		    cur->status = STATUS_UNINSTALLABLE;
		    neededPackageDereference(cur);
		    break;
		}
		size += val;
	    }
	    if (i >= rpmdata->nb_requires) {
		cur->status = STATUS_TO_INSTALL;
		return(size);
	    }
	    cur->status = STATUS_UNINSTALLABLE;
	}
    }

search_no_rel:
    /*
     * Do the most selective request first.
     */
    if (ver != NULL) {
	sprintf(fullname, "%s-%s", name, ver);

	/*
	 * Has this resource already been looked at ?
	 */
	res = neededResourceSearch(fullname);
	if (res != NULL) {
	    neededPackageAddResource(father, res);
	    return(0);
	}

	/*
	 * Ok, start fetching Metadata !
	 */
	sprintf(URL, "%s/resources/%s.rdf", myServer, fullname);
	filename = fetchURL(URL);
	if (filename == NULL) {
	    if (rpmfindVerbose)
		printf("Error fetching %s metadata\n", fullname);
	    goto search_no_ver;
	} 
	res = neededResourceAdd(fullname, father);
	neededResourceParseRdf(res, filename);
	neededPackageSelect(res, father);
	if (rpmfindVerbose > 1) {
	    neededResourcePrintPkgs(res);
	}

	/*
	 * Check every possible package.
	 */
	
	for (i = 0;i < res->nb_pkgs;i++) {
	    cur = res->pkgs[i];
	    size = 0;

	    if (cur == NULL) continue;
	    if (cur->rating < 0) continue;
	    if (cur->status == STATUS_TO_INSTALL) return(0);
	    if (cur->status == STATUS_UNINSTALLABLE) continue;

            /*
	     * Check that the release # match
	     */
	    if ((rel != NULL) && (strcmp(rel, cur->release))) continue;

	    /*
	     * Can we actually install this package on this system.
	     */
	    if (cur->rpmdata == NULL) {
		cur->status = STATUS_LOOKUP;
		sprintf(URL, "%s/%s/%s-%s-%s.%s.rdf",
			myServer, cur->subdir, cur->name, cur->version,
			cur->release, cur->arch);
		filename = fetchURL(URL);
		if (filename != NULL) {
		    cur->status = STATUS_LOOKUP;
		    rpmdata =  rpmOpenRdfFile(filename);
		    cur->rpmdata = rpmdata;
		    size += cur->size;
		}
	    }    

	    if (cur->rpmdata == NULL) {
		cur->status = STATUS_UNINSTALLABLE;
		continue;
	    }
	    rpmdata = cur->rpmdata;

	    /*
	     * Now check the resources needed by this package.
	     */
	    for (i = 0;i < rpmdata->nb_requires;i++) {
		if (!strcmp(fullname, rpmdata->requires[i]))
		    continue;
		val = findResource(cur, rpmdata->requires[i]);
		if (val < 0) {
		    cur->status = STATUS_UNINSTALLABLE;
		    neededPackageDereference(cur);
		    break;
		}
		size += val;
	    }
	    if (i >= rpmdata->nb_requires) {
		cur->status = STATUS_TO_INSTALL;
		return(size);
	    }
	    cur->status = STATUS_UNINSTALLABLE;
	}
    }

search_no_ver:
    /*
     * Has this resource already been looked at ?
     */
    res = neededResourceSearch(name);
    if (res != NULL) {
	neededPackageAddResource(father, res);
	return(0);
    }

    /*
     * Ok, start fetching Metadata !
     */
    sprintf(URL, "%s/resources/%s.rdf", myServer, name);
    filename = fetchURL(URL);
    if (filename == NULL) {
	if (rpmfindVerbose)
	    printf("Error fetching %s metadata\n", name);
	return(-1);
    } 
    res = neededResourceAdd(name, father);
    neededResourceParseRdf(res, filename);
    neededPackageSelect(res, father);
    if (rpmfindVerbose > 1) {
	neededResourcePrintPkgs(res);
    }

    /*
     * Check every possible package.
     */
    
    for (i = 0;i < res->nb_pkgs;i++) {
	cur = res->pkgs[i];
	size = 0;

	if (cur == NULL) continue;
	if (cur->rating < 0) continue;
	if (cur->status == STATUS_TO_INSTALL) return(0);
	if (cur->status == STATUS_UNINSTALLABLE) continue;

	/*
	 * Check that the release and version # match
	 */
	if ((rel != NULL) && (strcmp(rel, cur->release))) continue;
	if ((ver != NULL) && (strcmp(ver, cur->version))) continue;

	/*
	 * Can we actually install this package on this system.
	 */
	if (cur->rpmdata == NULL) {
	    cur->status = STATUS_LOOKUP;
	    sprintf(URL, "%s/%s/%s-%s-%s.%s.rdf",
		    myServer, cur->subdir, cur->name, cur->version,
		    cur->release, cur->arch);
	    filename = fetchURL(URL);
	    if (filename != NULL) {
		cur->status = STATUS_LOOKUP;
		rpmdata =  rpmOpenRdfFile(filename);
		cur->rpmdata = rpmdata;
		size += cur->size;
	    }
	}    

	if (cur->rpmdata == NULL) {
	    cur->status = STATUS_UNINSTALLABLE;
	    continue;
	}
	rpmdata = cur->rpmdata;

	/*
	 * Now check the resources needed by this package.
	 */
	for (i = 0;i < rpmdata->nb_requires;i++) {
	    if (!strcmp(name, rpmdata->requires[i]))
		continue;
	    val = findResource(cur, rpmdata->requires[i]);
	    if (val < 0) {
		cur->status = STATUS_UNINSTALLABLE;
		neededPackageDereference(cur);
		break;
	    }
	    size += val;
	}
	if (i >= rpmdata->nb_requires) {
	    cur->status = STATUS_TO_INSTALL;
	    return(size);
	}
	cur->status = STATUS_UNINSTALLABLE;
    }
    return(-1);
}

/****************************************************************
 *								*
 *			RPM DATABASE INTERFACE			*
 *								*
 ****************************************************************/


/*
 * Find a package, or a resource.
 * One first check whether it's provided by one of the installed packages
 */
int findResource(neededPackagePtr father, char *resource) {
    char * name = NULL, * version = NULL, * release = NULL;
    int type, count, i;
    dbiIndexSet matches;
    struct stat buf;
    int res;
    int found = -1;
    neededResourcePtr lookup;
    Header h = NULL;

    if (rpmfindVerbose > 1) {
	printf("findResource %s\n", resource);
    }	

    /*
     * Check for the list of resources we refuse to depend on.
     */
    if (rpmNoDependCheck(resource)) {
	if (rpmfindVerbose > 1)
	    printf("Refuses dependency of %s\n", resource);
        return(-1);
    }

    if ((resource[0] == '/') && (!stat(resource, &buf))) {
	if (rpmfindVerbose > 1)
	    printf("Resource %s exists\n", resource);
	found = 0;
    } else if (rpmdbFindByProvides(myDb, resource, &matches) == 0) {
	int j;

	if (rpmfindVerbose > 1)
	    printf("Resource %s is provided by:", resource);
	for (j = 0; j < matches.count; j++) {
	    if (matches.recs[j].recOffset) {
		h = rpmdbGetRecord(myDb, matches.recs[j].recOffset);
		if (!h) {
		    fprintf(stderr,
		       "error: could not read database record\n");
		} else {
		    /* extract informations from the header */
		    headerGetEntry(h, RPMTAG_NAME, &type,
				   (void **) &name, &count);
		    headerGetEntry(h, RPMTAG_VERSION, &type,
				   (void **) &version, &count);
		    headerGetEntry(h, RPMTAG_RELEASE, &type,
				   (void **) &release, &count);
		    if (rpmfindVerbose > 1)
			printf(" %s-%s-%s\n", name, version, release);
		    found = 0;
		    break;
		}
	    }
	}
	dbiFreeIndexRecord(matches);
    } else {
	res = rpmdbFindByLabel(myDb, resource, &matches);
	if (res == 1) {
	    if (rpmfindVerbose > 1)
		printf("Resource %s not installed\n", resource);
	} else if (res == 2) {
	    if (rpmfindVerbose)
		printf("Error looking for package %s\n", resource);
	} else {
	    int j;

	    if (rpmfindVerbose > 1)
		printf("Package %s is installed:", resource);
	    for (j = 0; j < matches.count; j++) {
		if (matches.recs[j].recOffset) {
		    h = rpmdbGetRecord(myDb, matches.recs[j].recOffset);
		    if (!h) {
			fprintf(stderr,
			   "error: could not read database record\n");
		    } else {
			/* extract informations from the header */
			headerGetEntry(h, RPMTAG_NAME, &type,
				       (void **) &name, &count);
			headerGetEntry(h, RPMTAG_VERSION, &type,
				       (void **) &version, &count);
			headerGetEntry(h, RPMTAG_RELEASE, &type,
				       (void **) &release, &count);
			if (rpmfindVerbose > 1)
			    printf(" %s-%s-%s",
			            name, version, release);
			found = 0;
		    }
		}
	    }
	    if (rpmfindVerbose > 1)
		printf("\n");
	    dbiFreeIndexRecord(matches);
	}
    }

    /*
     * If the package was found or if we are in the "latest" or
     * "upgrade" mode, then do a network lookup.
     */
    if ((found < 0) || (rpmfindMode == RPM_FIND_UPGRADE) ||
        ((rpmfindMode == RPM_FIND_LATEST) && (father == NULL))) {
        
	/*
	 * Avoid doing request for resources specified by filename
	 */
	if (resource[0] == '/')
	    goto return_found;

	/*
	 * Don't allow a network upgrade of glibc.
	 */
	if ((!strcmp(resource, "glibc")) || (!strncmp(resource, "glibc.so", 8)))
	    goto return_found;

	/*
	 * Don't allow a network upgrade of libc.
	 */
	if ((!strcmp(resource, "libc")) || (!strncmp(resource, "libc.so", 7)))
	    goto return_found;

        res = lookupRemoteResource(father, resource);
	if (found < 0) goto return_res;
	if (res < 0) goto return_found;

	/*
	 * Check that the version found on-line is more recent than
	 * the installed one.
	 */
	if ((name == NULL) || (version == NULL) || (release == NULL))
	    goto return_res;
	lookup = neededResourceSearch(resource);
	if (lookup == NULL) goto return_found;

        for (i = 0;i < lookup->nb_pkgs; i++) {
	    if (lookup->pkgs[i]->status == STATUS_TO_INSTALL) {
	        int v1, v2, r1 = 0, r2 = 0;

                if (strcmp(lookup->pkgs[i]->name, name)) {
		    /*
		     * We use another package to provide this resource
		     * on this system, inadequate.
		     */
		    if (rpmfindVerbose > 1)
			printf("Drop %s, %s provided by %s\n",
			       lookup->pkgs[i]->name, resource, name);
		    neededPackageDereference(lookup->pkgs[i]);
		    continue;
		}
		v1 = rpmFindVersionValue(lookup->pkgs[i]->version);
		v2 = rpmFindVersionValue(version);
		sscanf(lookup->pkgs[i]->release, "%d", &r1);
		sscanf(release, "%d", &r2);
		if ((v2 > v1) || ((v2 == v1) && (r2 >= r1))) {
		    /*
		     * The suggested package is older, remove it !
		     */
		    if (rpmfindVerbose > 1)
			printf("Drop %s-%s-%s, older than current\n",
			       lookup->pkgs[i]->name, 
			       lookup->pkgs[i]->version,
			       lookup->pkgs[i]->release);
		    neededPackageDereference(lookup->pkgs[i]);
		    continue;
		} else {
		    if (rpmfindVerbose > 1)
			printf("Package %s-%s-%s, candidate for install\n",
			       lookup->pkgs[i]->name, 
			       lookup->pkgs[i]->version,
			       lookup->pkgs[i]->release);
		    goto return_res;
		}
	    }
	}
    }
return_found:
    if (h != NULL)
        headerFree(h);
    return(found);
return_res:
    if (h != NULL)
        headerFree(h);
    return(res);
}

/*
 * try to guess the distribution of the machine
 * algorithm : list all the installed packages,
 *      and keep the most commonly used 
 *      Distribution/Vendor couple ...
 */

#define NB_DIST_ENTRY	10

typedef struct distEntry {
    int count;			/* How many time was it found */
    char *distribution;		/* Distribution string */
    char *vendor;		/* Vendor string */
} distEntry, *distEntryPtr;

#define ENTRY_POP(i) { 						\
    int c; char *d;char *v;					\
    c = frequent[i - 1].count;					\
    d = frequent[i - 1].distribution;				\
    v = frequent[i - 1].vendor;					\
    frequent[i - 1].count = frequent[i].count;			\
    frequent[i - 1].vendor = frequent[i].vendor;		\
    frequent[i - 1].distribution = frequent[i].distribution;	\
    frequent[i].count = c;					\
    frequent[i].distribution = d;				\
    frequent[i].vendor = v;					\
}

void guessRpmDistribution(void) {
    Header h = NULL;
    int offset;
    char *distribution;
    char *vendor;
    int_32 count, type;
    distEntry frequent[NB_DIST_ENTRY + 1];
    int i;

    /* initial cleanup */
    for (i = 0;i <= NB_DIST_ENTRY;i++) {
        frequent[i].count = 0;
        frequent[i].distribution = NULL;
        frequent[i].vendor = NULL;
    }
    nbInstalledPackages = 0;

    /* walk the database looking for each installed package */
    offset = rpmdbFirstRecNum(myDb);
    while (offset) {
        h = rpmdbGetRecord(myDb, offset);
	if (!h) {
	    fprintf(stderr, "could not read database record!\n");
	    return;
	}
	/* read the distribution field first that's the most selective */
	if ((headerGetEntry(h, RPMTAG_DISTRIBUTION, &type,
	                   (void *) &distribution, &count)) &&
            (distribution != NULL) && (type == RPM_STRING_TYPE)) {
	    if ((headerGetEntry(h, RPMTAG_VENDOR, &type,
			       (void *) &vendor, &count)) &&
		(distribution != NULL) && (type == RPM_STRING_TYPE)) {
		for (i = 0;i <= NB_DIST_ENTRY;i++) {
		    if (frequent[i].count == 0) {
			/* Ok, add a new entry !*/
			frequent[i].count = 1;
			frequent[i].distribution = strdup(distribution);
			frequent[i].vendor = strdup(vendor);
			break;
		    }
		    if ((!strcmp(distribution, frequent[i].distribution)) &&
		        (!strcmp(vendor, frequent[i].vendor))) {
			/* Ok, increase count */
			frequent[i].count++;
			while ((i > 0) &&
			       (frequent[i].count > frequent[i - 1].count)) {
			    ENTRY_POP(i)
			    i--;
			}       
			break;
		    }
		}
		if (i > NB_DIST_ENTRY) {
		    if (frequent[NB_DIST_ENTRY].count != 0) {
		        free(frequent[NB_DIST_ENTRY].distribution);
		        free(frequent[NB_DIST_ENTRY].vendor);
		    }
		    frequent[NB_DIST_ENTRY].count = 1;
		    frequent[NB_DIST_ENTRY].distribution = strdup(distribution);
		    frequent[NB_DIST_ENTRY].vendor = strdup(vendor);
		}
	    }
	}    

	headerFree(h);
	nbInstalledPackages++;
	offset = rpmdbNextRecNum(myDb, offset);
    }
    if (frequent[0].count == 0) {
        fprintf(stderr, "No installed packages ???\n");
	fprintf(stderr, "Using default distribution : %s(%s)\n", 
	        myVendor, myDistribution);
    } else {
        myVendor = strdup(frequent[0].vendor);
        myDistribution = strdup(frequent[0].distribution);
	if (rpmfindVerbose)
	    printf("Default distribution : %s(%s)\n",
		    myVendor, myDistribution);
	if (rpmfindVerbose)
	    printf("\towning %d of %d installed packages\n",
		    frequent[0].count, nbInstalledPackages);
    }
    /* cleanup */
    for (i = 0;i <= NB_DIST_ENTRY;i++) {
        if (frequent[i].distribution != NULL) free(frequent[i].distribution);
        if (frequent[i].vendor != NULL) free(frequent[i].vendor);
    }
}

/*
 * Initialize the whole setup :
 *  - Open the RPM database
 *  - Learn the Architecture/System used
 *  - Guess the current Vendor/Distribution
 */

void initializeRpmLookup(void) {
    myTime = time(NULL);

    /*
     * initialize.
     */
    rpmReadConfigFiles(NULL, NULL, NULL, 0);

    /*
     * Read the current setup.
     */
    rpmGetArchInfo(&myArch, NULL);
    rpmGetOsInfo(&myOs, NULL);
    if (rpmfindVerbose)
	printf("Arch : %s, Os : %s\n", myArch, myOs);

    /*
     * Open the database.
     */
    if (rpmdbOpen(myPrefix, &myDb, O_RDONLY, 0644)) {
        fprintf(stderr, "cannot open the RPM local database\n");
	exit(1);
    }

    /*
     * try to guess the installed distribution.
     */
    guessRpmDistribution();

}

/*
 * Cleanup at the end of the operations.
 */
void terminateRpmLookup(void) {
    /*
     * Close the database.
     */
    rpmdbClose(myDb);
}

/*
 * Seem that all the dependancies have been resolved, display the current
 * suggested list of RPM to fetch.
 *   In case of user interaction indicatin that the process is not complete
 * the function returns a non zero value.
 */
int rpmFindDisplay(void) {
    dumpTransferList();
    terminateRpmLookup();
    return(0);
}

/*
 * The main !
 */
int main(int argc, char **argv) {
    int i = 1;
    int interactive = 0;
    int nb_packages = 0;
    int res;
    char msg[100];
    char *catalog = NULL;

    /*
     * Initialize the network layer.
     */
    initializeTransport(handleTransportEnd);

    /*
     * initialize state from the config files
     */
    rpmFindReadConfigFiles();

    /*
     * Parse the command line args
     */
    while (i < argc) {
        if (argv[i][0] == '-') {
	    if (!strcmp(argv[i], "-q")) rpmfindVerbose--;
	    else if (!strcmp(argv[i], "-i")) interactive++;
	    else if (!strcmp(argv[i], "-v")) rpmfindVerbose++;
	    else if (!strcmp(argv[i], "-s")) {
                i++;
	        myServer = argv[i];
	    } else if (!strcmp(argv[i], "-p")) {
                i++;
	        myPrefix = argv[i];
	    } else if (!strcmp(argv[i], "--lookup")) {
	        rpmfindMode = RPM_FIND_LOOKUP;
	    } else if (!strcmp(argv[i], "--latest")) {
	        rpmfindMode = RPM_FIND_LATEST;
	    } else if (!strcmp(argv[i], "--upgrade")) {
	        rpmfindMode = RPM_FIND_UPGRADE;
	    } else if (!strcmp(argv[i], "--apropos")) {
	        rpmfindMode = RPM_FIND_APROPOS;
	    }
	} else
	    nb_packages++;
	i++;
    }

    if (nb_packages == 0) {
	printf("%s %s : RPM packages search engine\n",
	        RPMFIND_NAME, RPMFIND_VER);
        printf("usage : %s [flags] packagename [packagename ...]\n", argv[0]);
        printf("  Homepage: %s\n", RPMFIND_URL);
        printf("  Maintainer: %s <%s>\n", RPMFIND_MAINT, RPMFIND_MAIL);
        printf("\tflags\t\tmeaning\n");
        printf("\t-q or -v\tdecrease or increase verbosity\n");
        printf("\t-p prefix\tprefix for the local RPM database\n");
        printf("\t-s server\tURL of the server to contact\n");
        printf("\t--lookup\tsimple lookup (standard mode)\n");
        printf("\t--latest\tsuggest upgrades for the package[s]\n");
        printf("\t--upgrade\tsuggest upgrades for all dependencies\n");
        printf("\t--apropos\tdo a lookup in the database for the keyword\n");
	if (configNeedUpdate) rpmFindWriteConfigFile();
	exit(0);
    }

    if (rpmfindMode != RPM_FIND_APROPOS)
	initializeRpmLookup();
    else {
        catalog = loadAproposCatalog();
	if (catalog == NULL) exit(1);
    }

    /*
     * Search all the items
     */
    i = 1; 
    while (i < argc) {
        if (argv[i][0] == '-') {
	    if (!strcmp(argv[i], "-s")) {
                i++;
	    } else if (!strcmp(argv[i], "-p")) {
                i++;
	    }
	} else if (rpmfindMode == RPM_FIND_APROPOS) {
	    aproposSearchRdf(argv[i], catalog);
	} else {
	    res = findResource(NULL, argv[i]);
	    if (res < 0) {
		printf("Cannot install or locate resource %s \n", argv[i]);
		printf("Do you want to search it in the catalog ? [Y/n] : ");
		fflush(stdout);
		res = scanf("%s", &msg[0]);
		if (res >= 0) {
		    if (res == 0) msg[0] = 'y';
		    switch (msg[0]) {
			case 'n':
			case 'N': break;
			case 'y':
			case 'Y': 
			    if (catalog == NULL) 
				catalog = loadAproposCatalog();
			    if (catalog == NULL) break;
			    aproposSearchRdf(argv[i], catalog);
			    break;
			default:
			    break;
		    }
		}
	    } 
	    else if (res == 0) {
	        if (rpmfindMode == RPM_FIND_LOOKUP)
		    printf("Resource %s already installed\n", argv[i]);
		else if (rpmfindMode == RPM_FIND_LATEST)
		    printf("Resource %s latest version already installed\n",
		           argv[i]);
		else 
		    printf("Resource %s : no need to upgrade\n",
		           argv[i]);
	    }
	    else if (res > 0)
		printf("Installing %s will require %d KBytes\n",
		        argv[i], res / 1024);
	}
	if (interactive) refinePackageSelection(argv[i]);
	i++;
    }
    if (rpmfindMode != RPM_FIND_APROPOS) {
	rpmFindDisplay();
	rpmFindDownload();
    }
    if (configNeedUpdate) rpmFindWriteConfigFile();

    /*
     * Cleanup the cache.
     */
    netResourceClean();

    exit(0);
}

Webmaster