2011-04-27 21:50:52 +02:00
|
|
|
/*
|
|
|
|
* distromatic - tool for RPM based repositories
|
|
|
|
*
|
|
|
|
* Copyright (C) 2004-2010 by Silvan Calarco <silvan.calarco@mambasoft.it>
|
|
|
|
* Copyright (C) 2006 by Davide Madrisan <davide.madrisan@gmail.com>
|
|
|
|
*
|
|
|
|
* This program is free software; you can redistribute it and/or modify it under
|
|
|
|
* the terms of version 2 of the GNU General Public License as published by the
|
|
|
|
* Free Software Foundation.
|
|
|
|
*
|
|
|
|
* This program is distributed in the hope that it will be useful, but WITHOUT
|
|
|
|
* ANY WARRANTY, to the extent permitted by law; without even the implied
|
|
|
|
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
|
|
* See the GNU General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU General Public License along with
|
|
|
|
* this program; if not, write to the Free Software Foundation, Inc.,
|
|
|
|
* 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include "config.h"
|
|
|
|
|
|
|
|
#include <getopt.h>
|
|
|
|
#include <errno.h>
|
|
|
|
#include <stdio.h>
|
|
|
|
#include <fcntl.h>
|
|
|
|
|
|
|
|
#if HAVE_UNISTD_H
|
|
|
|
# include <unistd.h>
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#include <dirent.h>
|
|
|
|
#include <sys/stat.h>
|
|
|
|
|
|
|
|
#if HAVE_STRING_H
|
|
|
|
# if !STDC_HEADERS && HAVE_MEMORY_H
|
|
|
|
# include <memory.h>
|
|
|
|
# endif
|
|
|
|
/* Tell glibc's <string.h> to provide a prototype for strndup() */
|
|
|
|
# ifndef __USE_GNU
|
|
|
|
# define __USE_GNU
|
|
|
|
# endif
|
|
|
|
# include <string.h>
|
|
|
|
#endif
|
|
|
|
#if HAVE_STRINGS_H
|
|
|
|
# include <strings.h>
|
|
|
|
#endif
|
|
|
|
|
|
|
|
/* Tell glibc's <time.h> to provide a prototype for strptime() */
|
|
|
|
#ifndef __USE_XOPEN
|
|
|
|
# define __USE_XOPEN
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#if TIME_WITH_SYS_TIME
|
|
|
|
# include <sys/time.h>
|
|
|
|
# include <time.h>
|
|
|
|
#else
|
|
|
|
# if HAVE_SYS_TIME_H
|
|
|
|
# include <sys/time.h>
|
|
|
|
# else
|
|
|
|
# include <time.h>
|
|
|
|
# endif
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#if !HAVE_STRCHR
|
|
|
|
# define strchr index
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#include "buildtools.h"
|
|
|
|
#include "changelog.h"
|
|
|
|
#include "distromatic.h"
|
|
|
|
#include "reports.h"
|
|
|
|
#include "headerlist.h"
|
|
|
|
#include "requirelist.h"
|
|
|
|
#include "functions.h"
|
|
|
|
#include "rpmfunctions.h"
|
|
|
|
|
|
|
|
#define MODE_DATA_TABLES 1
|
|
|
|
#define MODE_FIND_DEPS 2
|
|
|
|
#define MODE_CHANGELOG 4
|
|
|
|
#define MODE_HTML 8
|
|
|
|
#define MODE_GENSRCPKGLIST 16
|
|
|
|
#define MODE_GENBUILDINFO 32
|
|
|
|
#define MODE_GENPKGLIST 64
|
|
|
|
|
|
|
|
static void program_usage(int exit_code);
|
|
|
|
static void program_version(void);
|
|
|
|
|
|
|
|
static int resolveFirstLevelDependencies(
|
|
|
|
struct configTag *ct, int arch);
|
|
|
|
|
|
|
|
static int
|
|
|
|
resolveFirstLevelSourceDependencies(struct configTag *ct, int archidx);
|
|
|
|
|
|
|
|
static int clearRecursionFlag(struct headerList *headerlist);
|
|
|
|
|
|
|
|
static int resolveRecursiveDependencies(
|
|
|
|
struct headerList *headerlist);
|
|
|
|
|
|
|
|
struct configTag*
|
|
|
|
findRepositoryByTag(const char *tag);
|
|
|
|
|
|
|
|
static int read_configuration(const char *confFile, const char *tag);
|
|
|
|
|
|
|
|
int compareRequiredList(const void *ptr1, const void *ptr2);
|
|
|
|
|
|
|
|
int handleObsoletedPackages(struct configTag *ct, int arch);
|
|
|
|
|
|
|
|
static struct configDefaults configdefaults;
|
|
|
|
static struct configTag *firstconfigtag = NULL, *configtag = NULL;
|
|
|
|
static char* arch =NULL;
|
|
|
|
|
|
|
|
static const unsigned int bufsize = 1024;
|
|
|
|
static const unsigned int maxlinelenght = 1024;
|
|
|
|
|
|
|
|
static const char *copyright[] = {
|
|
|
|
PROGRAMNAME " version " PROGRAMVERSION,
|
|
|
|
"Copyright (C) 2004-2010 by Silvan Calarco <silvan.calarco@mambasoft.it>",
|
|
|
|
"Copyright (C) 2006 by Davide Madrisan <davide.madrisan@gmail.com>",
|
|
|
|
(char *)0
|
|
|
|
};
|
|
|
|
|
|
|
|
/* *INDENT-OFF* */
|
|
|
|
static const char *freelicense[] = {
|
|
|
|
"This is free software; see the source for copying conditions. There is NO",
|
|
|
|
"warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.",
|
|
|
|
(char *)0
|
|
|
|
};
|
|
|
|
|
|
|
|
static const char *helpmsg[] = {
|
|
|
|
"Usage:",
|
|
|
|
"distromatic [options] [command [arguments] ]...",
|
|
|
|
"",
|
|
|
|
"Commands:",
|
|
|
|
" --gendatatables write dependencies, buildinfo and apt db files",
|
|
|
|
" --genbuildinfo generate build info files for all SRPMS packages",
|
|
|
|
" --genhtml generate HTML code for repository",
|
|
|
|
" --genpkglist generate binary packages list with version and size",
|
|
|
|
" --gensrcpkglist generate a source packages list with version",
|
|
|
|
" --find-deps <package> find dependencies for given package name",
|
|
|
|
" --changelog <package> print changelog for given package name",
|
|
|
|
" --changelogsince <mmddyy> print changelog for all packages since given date",
|
|
|
|
"",
|
|
|
|
"Options:",
|
|
|
|
" -t, --tag use repository identified by tag in config file",
|
|
|
|
" -a, --arch specify a target architecture (default: " DEFAULT_ARCH ")",
|
|
|
|
" -c, --conf <file> specify configuration file (default: " DEFAULT_CONFIGFILE ")",
|
|
|
|
" -q, --quiet suppress all messages excluding warnings and errors",
|
|
|
|
" -d, --debug display debug level messages",
|
|
|
|
" -v, --version display version and exit",
|
|
|
|
"",
|
|
|
|
"Examples:",
|
|
|
|
" distromatic -t devel --genhtml --deps-table",
|
|
|
|
" distromatic -t stable/2.0 --changelogsince 010106",
|
|
|
|
"",
|
|
|
|
"Please report bugs to <"PACKAGE_BUGREPORT">.",
|
|
|
|
(char *)0
|
|
|
|
};
|
|
|
|
/* *INDENT-ON* */
|
|
|
|
|
|
|
|
static void
|
|
|
|
program_usage(int exit_code) {
|
|
|
|
int linenum;
|
|
|
|
|
|
|
|
for (linenum = 0; copyright[linenum]; linenum++)
|
|
|
|
printf("%s\n", copyright[linenum]);
|
|
|
|
printf("\n");
|
|
|
|
|
|
|
|
for (linenum = 0; helpmsg[linenum]; linenum++)
|
|
|
|
printf("%s\n", helpmsg[linenum]);
|
|
|
|
|
|
|
|
exit(exit_code);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
program_version(void) {
|
|
|
|
int linenum;
|
|
|
|
|
|
|
|
for (linenum = 0; copyright[linenum]; linenum++)
|
|
|
|
printf("%s\n", copyright[linenum]);
|
|
|
|
printf("\n");
|
|
|
|
|
|
|
|
for (linenum = 0; freelicense[linenum]; linenum++)
|
|
|
|
printf("%s\n", freelicense[linenum]);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* checks if given single requirement is met by given provides
|
|
|
|
*
|
|
|
|
* returns: 0 if check is unsuccessful
|
|
|
|
* 1 if successful
|
|
|
|
*
|
|
|
|
* FIXME: this just checks for names equality, not for version */
|
|
|
|
static int
|
|
|
|
checkRequireWithProvides(char *requirename,
|
|
|
|
struct headerList *provideheader)
|
|
|
|
{
|
|
|
|
int j;
|
|
|
|
|
|
|
|
/* assuming requirename is a library or a virtual package */
|
|
|
|
for (j = 0; j < provideheader->providecount; j++) {
|
|
|
|
if (!strcmp
|
|
|
|
(provideheader->provided[j]->name,requirename)) return 1;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
|
|
|
compareRequiredList(const void *ptr1, const void *ptr2)
|
|
|
|
{
|
|
|
|
#define require1 (*(struct Require**)ptr1)
|
|
|
|
#define require2 (*(struct Require**)ptr2)
|
|
|
|
|
|
|
|
if (!require1 && !require2) return 0;
|
|
|
|
if (!require1 || !require1->name) return 1;
|
|
|
|
if (!require2 || !require2->name) return -1;
|
|
|
|
/* if (require1->resolved->numproviders != require2->resolved->numproviders)
|
|
|
|
return require2->resolved->numproviders - require1->resolved->numproviders;
|
|
|
|
|
|
|
|
if (require1->resolved->numproviders == 1)
|
|
|
|
return strcmp(require1->resolved->provider[0]->name,require2->resolved->provider[0]->name);
|
|
|
|
else*/
|
|
|
|
return strcmp(require1->name,require2->name);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* inspect multiple providing packages in providedList
|
|
|
|
* and remove providers those that are obsoleted by others
|
|
|
|
*/
|
|
|
|
int
|
|
|
|
handleObsoletedPackages(struct configTag *ct, int archidx)
|
|
|
|
{
|
|
|
|
struct providedList *prov = ct->providedlist_idx[archidx][0];
|
|
|
|
int i,j,k;
|
|
|
|
int obs[256];
|
|
|
|
char buf[PATH_MAX];
|
2011-09-14 23:57:55 +02:00
|
|
|
struct headerList *currheader;
|
2011-04-27 21:50:52 +02:00
|
|
|
|
|
|
|
while (prov) {
|
|
|
|
|
|
|
|
if (prov->numproviders > 256) {
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (prov->numproviders > 1) {
|
|
|
|
for (i = 0; i < prov->numproviders; i++) obs[i]=-1;
|
|
|
|
for (i = 0; i < prov->numproviders; i++)
|
|
|
|
for (j = 0; j < prov->provider[i]->obsoletecount; j++)
|
|
|
|
for (k = 0; k < prov->numproviders; k++)
|
|
|
|
if (!strcmp(prov->provider[i]->obsoletename[j],prov->provider[k]->name)) {
|
|
|
|
if (prov->provider[i]->obsoleteflags[j] & (RPMSENSE_EQUAL|RPMSENSE_GREATER|RPMSENSE_LESS)) {
|
|
|
|
snprintf(buf, PATH_MAX, "%s-%s",prov->provider[k]->version,prov->provider[k]->release);
|
|
|
|
if (!checkVersionWithFlags(
|
|
|
|
prov->provider[i]->obsoleteversion[j],
|
|
|
|
prov->provider[i]->obsoleteflags[j],
|
|
|
|
buf)) continue;
|
|
|
|
}
|
2011-10-12 11:00:50 +02:00
|
|
|
/* print 'obsoletes' warning for binary packages belonging to target repository,
|
|
|
|
and 'obsoleted by' for all obsoletions in upper level repositories */
|
|
|
|
if (prov->provider[k]->altrepository != ct->repository_level) {
|
2011-09-14 23:57:55 +02:00
|
|
|
currheader = prov->provider[k]->sourceheader->firstchild[archidx];
|
|
|
|
while (currheader) {
|
|
|
|
if (!strcmp(currheader->name, prov->provider[k]->name)) {
|
2011-09-27 20:35:49 +02:00
|
|
|
snprintf(buf, PATH_MAX, "%s(%s,%s) obsoleted by %s(%s,%s)",
|
2011-09-15 17:48:23 +02:00
|
|
|
prov->provider[k]->name,
|
|
|
|
prov->provider[k]->arch,
|
2011-09-27 22:04:40 +02:00
|
|
|
ct->repository[prov->provider[k]->altrepository]->tag,
|
2011-08-12 17:53:10 +02:00
|
|
|
prov->provider[i]->name,
|
|
|
|
prov->provider[i]->arch,
|
2011-09-27 22:04:40 +02:00
|
|
|
ct->repository[prov->provider[i]->altrepository]->tag);
|
2011-08-12 17:53:10 +02:00
|
|
|
addWarning(prov->provider[k]->sourceheader, buf);
|
2011-09-15 17:48:23 +02:00
|
|
|
logmsg(LOG_WARNING,"%s", buf);
|
2011-08-12 17:53:10 +02:00
|
|
|
break;
|
|
|
|
}
|
2011-09-14 23:57:55 +02:00
|
|
|
currheader = currheader -> nextbrother;
|
2011-08-12 17:53:10 +02:00
|
|
|
}
|
2011-10-12 11:00:50 +02:00
|
|
|
} else {
|
2011-09-27 20:35:49 +02:00
|
|
|
snprintf(buf, PATH_MAX, "%s(%s,%s) obsoletes %s(%s,%s)",
|
2011-09-15 17:48:23 +02:00
|
|
|
prov->provider[i]->name,
|
|
|
|
prov->provider[i]->arch,
|
2011-09-27 22:04:40 +02:00
|
|
|
ct->repository[prov->provider[i]->altrepository]->tag,
|
2011-04-27 21:50:52 +02:00
|
|
|
prov->provider[k]->name,
|
2011-09-15 17:48:23 +02:00
|
|
|
prov->provider[k]->arch,
|
2011-09-27 22:04:40 +02:00
|
|
|
ct->repository[prov->provider[k]->altrepository]->tag);
|
2011-04-27 21:50:52 +02:00
|
|
|
addWarning(prov->provider[i]->sourceheader, buf);
|
2011-09-15 17:48:23 +02:00
|
|
|
logmsg(LOG_WARNING,"%s", buf);
|
2011-04-27 21:50:52 +02:00
|
|
|
}
|
|
|
|
obs[k]=i;
|
|
|
|
}
|
|
|
|
|
|
|
|
// now delete found obsoleted providers
|
|
|
|
j = prov->numproviders;
|
|
|
|
for (i = 0; i < prov->numproviders; i++) {
|
|
|
|
if (obs[i] >=0) {
|
|
|
|
j--;
|
2011-08-12 17:31:13 +02:00
|
|
|
prov->provider[i]->obsoleted=1;
|
2011-04-27 21:50:52 +02:00
|
|
|
prov->provider[i]=NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (j < prov->numproviders) {
|
|
|
|
for (i = 0; i < j; i++) {
|
|
|
|
if (prov->provider[i] == NULL) {
|
|
|
|
k=i+1;
|
|
|
|
while (k < prov->numproviders && !prov->provider[k]) k++;
|
|
|
|
prov->provider[i]=prov->provider[k];
|
|
|
|
prov->provider[k]=NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
prov->numproviders = j;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
prov=prov->next;
|
|
|
|
}
|
2011-09-14 23:57:55 +02:00
|
|
|
|
|
|
|
/* report obsoleted elements which are provided by other packages */
|
|
|
|
currheader = ct->headerlist[archidx];
|
|
|
|
while (currheader) {
|
|
|
|
for (j = 0; j < currheader->obsoletecount; j++) {
|
|
|
|
prov=findOrCreateProvidedListEntry((struct providedList**) &ct->providedlist_idx[archidx],
|
|
|
|
currheader->obsoletename[j],0);
|
|
|
|
if (prov) {
|
|
|
|
for (i = 0; i < prov->numproviders; i++) {
|
|
|
|
if (prov->provider[i] != currheader) {
|
2011-09-15 17:48:23 +02:00
|
|
|
if (!strcmp(prov->name,prov->provider[i]->name)) {
|
2011-09-27 20:35:49 +02:00
|
|
|
snprintf(buf, PATH_MAX, "%s(%s,%s) obsoletes %s(%s,%s)",
|
2011-09-15 17:48:23 +02:00
|
|
|
currheader->name,
|
|
|
|
currheader->arch,
|
2011-09-27 22:04:40 +02:00
|
|
|
ct->repository[currheader->altrepository]->tag,
|
2011-09-15 17:48:23 +02:00
|
|
|
prov->provider[i]->name,
|
|
|
|
prov->provider[i]->arch,
|
2011-09-27 22:04:40 +02:00
|
|
|
ct->repository[prov->provider[i]->altrepository]->tag);
|
2011-09-15 17:48:23 +02:00
|
|
|
} else {
|
2011-09-27 20:35:49 +02:00
|
|
|
snprintf(buf, PATH_MAX, "%s(%s,%s) obsoletes %s provided by %s(%s,%s)",
|
2011-09-15 17:48:23 +02:00
|
|
|
currheader->name,
|
|
|
|
currheader->arch,
|
2011-09-27 22:04:40 +02:00
|
|
|
ct->repository[currheader->altrepository]->tag,
|
2011-09-14 23:57:55 +02:00
|
|
|
prov->name,
|
|
|
|
prov->provider[i]->name,
|
|
|
|
prov->provider[i]->arch,
|
2011-09-27 22:04:40 +02:00
|
|
|
ct->repository[prov->provider[i]->altrepository]->tag);
|
2011-09-15 17:48:23 +02:00
|
|
|
}
|
|
|
|
addWarning(currheader->sourceheader, buf);
|
|
|
|
logmsg(LOG_WARNING,"%s", buf);
|
2011-09-14 23:57:55 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
currheader = currheader->next;
|
|
|
|
}
|
|
|
|
|
2011-04-27 21:50:52 +02:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* resolve first level requires for given headerList
|
|
|
|
* note: this function doesn't free allocated memory so it should be called
|
|
|
|
* only once */
|
|
|
|
static int
|
|
|
|
resolveFirstLevelDependencies(struct configTag *ct, int archidx)
|
|
|
|
{
|
|
|
|
struct requireList *currrequire;
|
2011-10-20 15:26:21 +02:00
|
|
|
struct headerList *currheader, *scanheader, **newprovider;
|
2011-04-27 21:50:52 +02:00
|
|
|
struct providedList *provided;
|
|
|
|
struct fileTree *file;
|
|
|
|
int i,j,k,found;
|
|
|
|
char warning[PATH_MAX];
|
|
|
|
|
|
|
|
currheader = ct->headerlist[archidx];
|
|
|
|
|
|
|
|
logmsg(LOG_DEBUG,"resolveFirstLevelDependencies - binaries");
|
|
|
|
|
|
|
|
while (currheader) {
|
|
|
|
scanheader = ct->headerlist[archidx];
|
|
|
|
currrequire = NULL;
|
|
|
|
currheader->requirelist = NULL;
|
2012-03-16 15:18:43 +01:00
|
|
|
if ((!currheader->obsoleted) && (currheader->next) && (!strcmp(currheader->name,currheader->next->name))) {
|
|
|
|
// mark obsoleted any package with same name in upper level repositories
|
|
|
|
if (currheader->altrepository < currheader->next->altrepository) {
|
|
|
|
currheader->obsoleted = 1;
|
|
|
|
if (checkVersionWithFlags(
|
|
|
|
currheader->version,
|
|
|
|
RPMSENSE_GREATER & RPMSENSE_EQUAL,
|
|
|
|
currheader->next->version)) {
|
|
|
|
snprintf(warning,PATH_MAX,"%s(%s,%s): same or higher version than package with same name in %s (%s >= %s)",
|
|
|
|
currheader->name,
|
|
|
|
ct->arch[archidx],
|
|
|
|
ct->repository[currheader->altrepository]->tag,
|
|
|
|
ct->repository[currheader->next->altrepository]->tag,
|
|
|
|
currheader->version,
|
|
|
|
currheader->next->version);
|
|
|
|
fprintf(stderr,"Warning: %s\n",warning);
|
|
|
|
addWarning(currheader->sourceheader,warning);
|
|
|
|
}
|
|
|
|
} else if (currheader->altrepository >= currheader->next->altrepository) {
|
|
|
|
currheader->next->obsoleted = 1;
|
|
|
|
if (checkVersionWithFlags(
|
|
|
|
currheader->version,
|
|
|
|
RPMSENSE_LESS & RPMSENSE_EQUAL,
|
|
|
|
currheader->next->version)) {
|
|
|
|
snprintf(warning,PATH_MAX,"%s(%s,%s): same or higher version than package with same name in %s (%s >= %s)",
|
|
|
|
currheader->next->name,
|
|
|
|
ct->arch[archidx],
|
|
|
|
ct->repository[currheader->next->altrepository]->tag,
|
|
|
|
ct->repository[currheader->altrepository]->tag,
|
|
|
|
currheader->next->version,
|
|
|
|
currheader->version);
|
|
|
|
fprintf(stderr,"Warning: %s\n",warning);
|
|
|
|
addWarning(currheader->sourceheader,warning);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-08-13 14:38:46 +02:00
|
|
|
if (currheader->obsoleted) {
|
|
|
|
currheader = currheader->next;
|
|
|
|
continue;
|
|
|
|
}
|
2011-04-27 21:50:52 +02:00
|
|
|
// currheader->require.resolved = malloc(sizeof(struct providedList*)*currheader->requirecount);
|
|
|
|
|
|
|
|
for (i = 0; i < currheader->requirecount; i++) {
|
|
|
|
/* if (strncmp("a2ps",currheader->name,4) == 0) {
|
|
|
|
fprintf(stderr,"a2ps:%s\n",currheader->require[i]->name);
|
|
|
|
}*/
|
|
|
|
if (!strncmp("executable(",currheader->require[i]->name,11)) {
|
|
|
|
/* dynamic requirement for executable file */
|
|
|
|
/* fprintf(stderr,"Warning: skipping unhandled requirement %s for package %s\n",
|
|
|
|
currheader->require[i]->name,currheader->name);*/
|
|
|
|
currheader->require[i]->resolved=NULL;
|
|
|
|
} else if (strncmp("rpmlib(",currheader->require[i]->name,7) != 0) {
|
|
|
|
provided=findOrCreateProvidedListEntry((struct providedList**) &ct->providedlist_idx[archidx],
|
|
|
|
currheader->require[i]->name,1);
|
|
|
|
if (provided->numproviders == 0) {
|
|
|
|
// check if require[i]->name requirement is met
|
|
|
|
scanheader = ct->headerlist[archidx];
|
|
|
|
|
|
|
|
if ((currheader->require[i]->name)[0] == '/') {
|
|
|
|
/* requirement is a file, find who provides it */
|
|
|
|
file=findOrCreateFileTreeEntry(&ct->filetree[archidx],currheader->require[i]->name);
|
|
|
|
if (file->numproviders > 0) {
|
|
|
|
scanheader=file->provider[0];
|
2011-10-20 15:26:21 +02:00
|
|
|
provided->numproviders=file->numproviders;
|
|
|
|
provided->numbuildproviders=0;
|
|
|
|
provided->buildpriority=0;
|
|
|
|
provided->provider=file->provider;
|
|
|
|
provided->flags=0;
|
2011-04-27 21:50:52 +02:00
|
|
|
} else {
|
|
|
|
scanheader=NULL;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
/* requirement is not a file, cross-check with provides */
|
|
|
|
while (scanheader &&
|
|
|
|
!checkRequireWithProvides(
|
|
|
|
currheader->require[i]->name,
|
|
|
|
scanheader)) scanheader = scanheader->next;
|
|
|
|
if (scanheader) {
|
|
|
|
provided->numproviders=1;
|
|
|
|
provided->numbuildproviders=0;
|
|
|
|
provided->buildpriority=0;
|
|
|
|
provided->provider=malloc(sizeof(struct headerList*));
|
|
|
|
provided->provider[0]=scanheader;
|
|
|
|
provided->flags=0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (!scanheader) {
|
2011-09-27 20:35:49 +02:00
|
|
|
snprintf(warning,PATH_MAX,"%s(%s,%s): missing provider for %s",
|
|
|
|
currheader->name,
|
|
|
|
ct->arch[archidx],
|
2011-09-27 22:04:40 +02:00
|
|
|
ct->repository[currheader->altrepository]->tag,
|
2011-09-27 20:35:49 +02:00
|
|
|
currheader->require[i]->name);
|
2011-04-27 21:50:52 +02:00
|
|
|
fprintf(stderr,"Warning: %s\n",warning);
|
|
|
|
addWarning(currheader->sourceheader,warning);
|
|
|
|
}
|
2011-10-20 15:26:21 +02:00
|
|
|
} else { /* provided->numproviders > 0 */
|
|
|
|
if ((currheader->require[i]->name)[0] == '/') {
|
|
|
|
/* when there is a Requires: /file/requirement add provide from file tree as well */
|
|
|
|
file=findOrCreateFileTreeEntry(&ct->filetree[archidx],currheader->require[i]->name);
|
|
|
|
for (k = 0; k < file->numproviders; k++) {
|
|
|
|
for (j = 0, found = 0; j < provided->numproviders; j++) {
|
|
|
|
/* avoid duplicates */
|
|
|
|
if (file->provider[k] == provided->provider[j]) {
|
|
|
|
found = 1;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (! found) {
|
|
|
|
//printf("%s also provided by %s\n", currheader->require[i]->name, file->provider[k]->name);
|
|
|
|
provided->numproviders++;
|
|
|
|
newprovider=malloc(sizeof(struct headerList*)*provided->numproviders);
|
|
|
|
for (j = 0; j < provided->numproviders-1; j++) {
|
|
|
|
newprovider[j]=provided->provider[j];
|
|
|
|
}
|
|
|
|
newprovider[provided->numproviders-1] = file->provider[k];
|
|
|
|
free(provided->provider);
|
|
|
|
provided->provider=newprovider;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2011-04-27 21:50:52 +02:00
|
|
|
}
|
|
|
|
if (provided->numversions > 0) {
|
|
|
|
if (strcmp(currheader->require[i]->version,"") &&
|
|
|
|
(currheader->require[i]->flags & (RPMSENSE_LESS|RPMSENSE_GREATER|RPMSENSE_EQUAL))) {
|
|
|
|
|
|
|
|
found = 0;
|
|
|
|
for (j = 0; j < provided->numversions; j++) {
|
|
|
|
|
|
|
|
if (!strcmp(provided->version[j],"")) {
|
|
|
|
/* provider with no version; assume ok */
|
|
|
|
found = 1;
|
|
|
|
} else {
|
|
|
|
if (checkVersionWithFlags(
|
|
|
|
currheader->require[i]->version,
|
|
|
|
currheader->require[i]->flags,
|
|
|
|
provided->version[j])) found = 1;
|
|
|
|
}
|
|
|
|
} /* for */
|
|
|
|
if (!found) {
|
|
|
|
for (j = 0; j < provided->numversions; j++) {
|
2011-09-27 20:35:49 +02:00
|
|
|
snprintf(warning, PATH_MAX, "%s = %s fails to provide %s ",
|
|
|
|
provided->name,
|
|
|
|
provided->version[j],
|
|
|
|
provided->name);
|
2011-04-27 21:50:52 +02:00
|
|
|
if (currheader->require[i]->flags & RPMSENSE_LESS) snprintf(&warning[strlen(warning)], PATH_MAX,"<");
|
|
|
|
if (currheader->require[i]->flags & RPMSENSE_GREATER) snprintf(&warning[strlen(warning)], PATH_MAX, ">");
|
|
|
|
if (currheader->require[i]->flags & RPMSENSE_EQUAL) snprintf(&warning[strlen(warning)], PATH_MAX, "=");
|
2011-09-27 20:35:49 +02:00
|
|
|
snprintf(&warning[strlen(warning)], PATH_MAX, " %s to %s(%s,%s)",
|
|
|
|
currheader->require[i]->version,
|
|
|
|
currheader->name,
|
|
|
|
currheader->arch,
|
2011-09-27 22:04:40 +02:00
|
|
|
ct->repository[currheader->altrepository]->tag);
|
2011-04-27 21:50:52 +02:00
|
|
|
|
|
|
|
for (k = 0; k < provided->numproviders; k++) {
|
|
|
|
if (provided->provider[k]->sourceheader &&
|
2011-09-27 22:04:40 +02:00
|
|
|
(provided->provider[k]->altrepository == ct->repository_level)) {
|
2011-09-27 20:35:49 +02:00
|
|
|
fprintf(stderr,"Warning: %s\n", warning);
|
2011-04-27 21:50:52 +02:00
|
|
|
addWarning(provided->provider[k]->sourceheader, warning);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
currheader->require[i]->resolved=provided;
|
|
|
|
} else {
|
|
|
|
currheader->require[i]->resolved=NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// sort required list by first provider's name
|
|
|
|
qsort((void *) &currheader->require[0],
|
|
|
|
currheader->requirecount,
|
|
|
|
sizeof(struct Require *),
|
|
|
|
compareRequiredList);
|
|
|
|
currheader = currheader->next;
|
|
|
|
}
|
|
|
|
logmsg(LOG_DEBUG,"resolveFirstLevelDependencies - done");
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
resolveFirstLevelSourceDependencies(struct configTag *ct, int archidx)
|
|
|
|
{
|
|
|
|
struct headerSourceList *currsourceheader = ct->headersourcelist;
|
|
|
|
struct headerList *scanheader;
|
|
|
|
struct requireList *currrequire;
|
|
|
|
struct providedList *provided;
|
|
|
|
struct fileTree *file;
|
|
|
|
char warning[PATH_MAX];
|
|
|
|
int i;
|
|
|
|
int j,found;
|
|
|
|
|
|
|
|
logmsg(LOG_DEBUG,"resolveFirstLevelSourceDependencies - sources");
|
|
|
|
while (currsourceheader) {
|
|
|
|
scanheader = ct->headerlist[archidx];
|
|
|
|
currrequire = NULL;
|
|
|
|
currsourceheader->requirelist = NULL;
|
|
|
|
// currsourceheader->require.resolved = malloc(sizeof(struct providedList*)*currsourceheader->requirecount);
|
|
|
|
for (i = 0; i < currsourceheader->requirecount; i++) {
|
|
|
|
/* if (strncmp("a2ps",currsourceheader->name,4) == 0) {
|
|
|
|
fprintf(stderr,"a2ps:%s\n",currheader->require[i]->name);
|
|
|
|
}*/
|
|
|
|
if (strncmp("rpmlib(",currsourceheader->require[i]->name,7) != 0) {
|
|
|
|
provided=findOrCreateProvidedListEntry((struct providedList**) &ct->providedlist_idx[archidx],
|
|
|
|
currsourceheader->require[i]->name,1);
|
|
|
|
if (provided->numbuildproviders == 0) {
|
|
|
|
// check if require[i]->name requirement is met
|
|
|
|
scanheader = ct->headerlist[archidx];
|
|
|
|
|
|
|
|
if ((currsourceheader->require[i]->name)[0] == '/') {
|
|
|
|
/* requirement is a file, find who provides it */
|
|
|
|
file=findOrCreateFileTreeEntry(&ct->filetree[archidx],currsourceheader->require[i]->name);
|
|
|
|
if (file->numproviders > 0) {
|
|
|
|
provided->numbuildproviders=file->numproviders;
|
|
|
|
provided->buildpriority=0;
|
|
|
|
provided->buildprovider=file->provider;
|
|
|
|
provided->flags=0;
|
|
|
|
logmsg(LOG_DEBUG,"file %s is a build provider for %s",currsourceheader->require[i]->name,currsourceheader->name);
|
|
|
|
} else {
|
|
|
|
scanheader=NULL;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
/* requirement is not a file, cross-check with provides */
|
|
|
|
|
|
|
|
while (scanheader &&
|
|
|
|
!checkRequireWithProvides(
|
|
|
|
currsourceheader->require[i]->name,
|
|
|
|
scanheader)) scanheader = scanheader->next;
|
|
|
|
if (scanheader) {
|
|
|
|
provided->numbuildproviders=1;
|
|
|
|
provided->buildprovider=malloc(sizeof(struct headerList*));
|
|
|
|
provided->buildprovider[0]=scanheader;
|
|
|
|
provided->flags=0;
|
|
|
|
}
|
|
|
|
logmsg(LOG_DEBUG,"%s is a build provider for %s",provided->name,currsourceheader->name);
|
|
|
|
}
|
|
|
|
if (!scanheader) {
|
|
|
|
snprintf(warning,PATH_MAX,"missing build provider for %s",currsourceheader->require[i]->name);
|
2011-09-27 20:35:49 +02:00
|
|
|
fprintf(stderr,"Warning: %s(source,%s): %s\n",
|
|
|
|
currsourceheader->name,
|
|
|
|
ct->repository[currsourceheader->altrepository]->tag,
|
|
|
|
warning);
|
2011-04-27 21:50:52 +02:00
|
|
|
addWarning(currsourceheader,warning);
|
|
|
|
}
|
|
|
|
/* if (scanheader) {
|
|
|
|
provided->numbuildproviders=1;
|
|
|
|
provided->buildprovider=malloc(sizeof(struct headerList*));
|
|
|
|
provided->buildprovider[0]=scanheader;
|
|
|
|
provided->flags=0;
|
|
|
|
} else {
|
|
|
|
snprintf(warning, PATH_MAX, "missing build provider for %s", currsourceheader->require[i]->name);
|
|
|
|
fprintf(stderr,"Warning: %s: %s\n",currsourceheader->name, warning);
|
|
|
|
addWarning(currsourceheader, warning);
|
|
|
|
}*/
|
|
|
|
} else if (provided->numbuildproviders > 1) {
|
|
|
|
/*printf("Multiple providers for %s in package %s\n",currsourceheader->require[i]->name,currsourceheader->name);*/
|
|
|
|
}
|
|
|
|
|
|
|
|
if (provided->numbuildproviders > 0) {
|
|
|
|
|
|
|
|
if (strcmp(currsourceheader->require[i]->version,"") &&
|
|
|
|
(currsourceheader->require[i]->flags & (RPMSENSE_LESS+RPMSENSE_GREATER+RPMSENSE_EQUAL))) {
|
|
|
|
|
|
|
|
found = 0;
|
|
|
|
for (j = 0; j < provided->numbuildproviders; j++) {
|
|
|
|
|
|
|
|
if (!strcmp(provided->version[j],"")) {
|
|
|
|
/* provider with no version; assume ok */
|
|
|
|
found = 1;
|
|
|
|
} else {
|
|
|
|
if (checkVersionWithFlags(
|
|
|
|
currsourceheader->require[i]->version,
|
|
|
|
currsourceheader->require[i]->flags,
|
|
|
|
provided->version[j])) found = 1;
|
|
|
|
}
|
|
|
|
} /* for */
|
|
|
|
if (!found) {
|
|
|
|
if (currsourceheader->altrepository == ct->repository_level) {
|
|
|
|
fprintf(stderr,"Warning: %s(source): build requires %s ",currsourceheader->name,currsourceheader->require[i]->name);
|
|
|
|
if (currsourceheader->require[i]->flags & RPMSENSE_LESS) fprintf(stderr,"<");
|
|
|
|
if (currsourceheader->require[i]->flags & RPMSENSE_GREATER) fprintf(stderr,">");
|
|
|
|
if (currsourceheader->require[i]->flags & RPMSENSE_EQUAL) fprintf(stderr,"=");
|
|
|
|
fprintf(stderr," %s (failing build provider(s):", currsourceheader->require[i]->version);
|
|
|
|
for (j = 0; j < provided->numbuildproviders; j++) {
|
|
|
|
fprintf(stderr," %s#%s",
|
|
|
|
provided->provider[j]->name, provided->version[j]);
|
|
|
|
/* printrpmversion(buffer,PATH_MAX,
|
|
|
|
provided->provider[j]->epoch,
|
|
|
|
provided->provider[j]->version,
|
|
|
|
provided->provider[j]->release));*/
|
|
|
|
}
|
|
|
|
fprintf(stderr,")\n");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
currsourceheader->require[i]->resolved=provided;
|
|
|
|
} else {
|
|
|
|
currsourceheader->require[i]->resolved=NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// sort required list by first provider's name
|
|
|
|
qsort((void *) &currsourceheader->require[0],
|
|
|
|
currsourceheader->requirecount,
|
|
|
|
sizeof(struct Require *),
|
|
|
|
compareRequiredList);
|
|
|
|
currsourceheader = currsourceheader->next;
|
|
|
|
}
|
|
|
|
logmsg(LOG_DEBUG,"resolveFirstLevelSourceDependencies - done");
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
clearRecursionFlag(struct headerList *headerlist)
|
|
|
|
{
|
|
|
|
while (headerlist) {
|
|
|
|
if (headerlist->recursed == 1) {
|
|
|
|
headerlist->recursed = 0;
|
|
|
|
}
|
|
|
|
headerlist = headerlist->next;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* resolve recursive requirement dependencies
|
|
|
|
* (first level dependencies should have been resolved already) */
|
|
|
|
static int
|
|
|
|
resolveRecursiveDependencies(struct headerList *headerlist)
|
|
|
|
{
|
|
|
|
while (headerlist) {
|
|
|
|
clearRecursionFlag(headerlist);
|
|
|
|
headerlist->requirelist = recurseRequireList(headerlist);
|
|
|
|
headerlist->recursed = 2;
|
|
|
|
headerlist = headerlist->next;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
struct configTag*
|
|
|
|
findRepositoryByTag(const char *tag)
|
|
|
|
{
|
|
|
|
struct configTag *ct = firstconfigtag;
|
|
|
|
|
|
|
|
while ((ct) && (strncmp(ct->tag,tag,PATH_MAX))) ct = ct->next;
|
|
|
|
|
|
|
|
if (ct) return ct; else return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
read_configuration(const char *confFile, const char *tag)
|
|
|
|
{
|
|
|
|
FILE *fin;
|
|
|
|
|
|
|
|
char *vartok, *valuetok;
|
|
|
|
char input[maxlinelenght];
|
|
|
|
char buf[bufsize];
|
|
|
|
int i, j, curraltrep = 0;
|
|
|
|
|
|
|
|
struct configTag *newconfigtag, *currconfigtag = NULL;
|
|
|
|
struct Packager *currmaintainer;
|
|
|
|
|
|
|
|
if ((fin = fopen(confFile, "r")) == NULL) {
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
unsigned int configsection = 0;
|
|
|
|
configdefaults.html_basedir = NULL;
|
|
|
|
|
|
|
|
configdefaults.arch[0] = DEFAULT_ARCH;
|
|
|
|
for (i = 1; i<ARCHS_MAX; i++)
|
|
|
|
configdefaults.arch[i] = NULL;
|
|
|
|
|
|
|
|
while (fgets(input, maxlinelenght, fin)) {
|
|
|
|
|
|
|
|
if (input[0] == '#') {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
vartok = (char *) strtok(input, "=\n");
|
|
|
|
if (!vartok) { /* skip blank lines */
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
strip_separators(vartok, " \t\n");
|
|
|
|
|
|
|
|
valuetok = (char *) strtok(NULL, "\n");
|
|
|
|
strip_separators(valuetok, " \t\n");
|
|
|
|
|
|
|
|
if ((vartok[0]=='[') && (vartok[strlen(vartok)-1]==']')) {
|
|
|
|
/* found a `[defaults]' tag */
|
|
|
|
if (!strncmp(&vartok[1], "defaults", strlen(vartok)-2)) {
|
|
|
|
configsection = CONF_DEFAULTS_SECTION;
|
|
|
|
} else if (!strncmp(&vartok[1], "maintainers", strlen(vartok)-2)) {
|
|
|
|
configsection = CONF_MAINTAINERS_SECTION;
|
|
|
|
} else {
|
|
|
|
configsection = CONF_REP_SECTION;
|
|
|
|
|
|
|
|
newconfigtag = malloc(sizeof(struct configTag));
|
|
|
|
if (!newconfigtag) {
|
|
|
|
fprintf(stderr, "The system is out of memory\n");
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
newconfigtag->tag =
|
|
|
|
(char *) strndup(&vartok[1], strlen(vartok) - 2);
|
|
|
|
newconfigtag->repository_dir = NULL;
|
|
|
|
newconfigtag->repository_source_dir = NULL;
|
|
|
|
curraltrep = 0;
|
|
|
|
for (i = 0; i<ALT_REPS_MAX; i++) newconfigtag->repository[i] = NULL;
|
|
|
|
newconfigtag->html_dir = NULL;
|
|
|
|
newconfigtag->download_prefix = NULL;
|
|
|
|
newconfigtag->download_dir = NULL;
|
|
|
|
newconfigtag->showfile_prefix = NULL;
|
|
|
|
for (i = 0; i<ARCHS_MAX; i++) {
|
|
|
|
newconfigtag->arch[i] = configdefaults.arch[i];
|
|
|
|
newconfigtag->headerlist[i] = NULL;
|
|
|
|
newconfigtag->filetree[i]=NULL;
|
|
|
|
for (j = 0; j<PROVIDEDLIST_IDX_SIZE; j++)
|
|
|
|
newconfigtag->providedlist_idx[i][j]=NULL;
|
|
|
|
}
|
|
|
|
newconfigtag->configdefaults = &configdefaults;
|
|
|
|
newconfigtag->headersourcelist = NULL;
|
|
|
|
|
|
|
|
if (configdefaults.html_basedir) {
|
|
|
|
strncpy(buf, configdefaults.html_basedir, bufsize);
|
|
|
|
strncat(buf, newconfigtag->tag, bufsize);
|
|
|
|
strncat(buf, "/", bufsize);
|
|
|
|
newconfigtag->html_dir = (char *) strdup(buf);
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((!tag) || (!strcmp(newconfigtag->tag, tag))) {
|
|
|
|
configtag = newconfigtag;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!currconfigtag) {
|
|
|
|
firstconfigtag = newconfigtag;
|
|
|
|
} else {
|
|
|
|
currconfigtag->next = newconfigtag;
|
|
|
|
}
|
|
|
|
currconfigtag = newconfigtag;
|
|
|
|
}
|
|
|
|
} else if (configsection == CONF_REP_SECTION) {
|
|
|
|
/* we are reading inside a repository section... */
|
|
|
|
if (!strcmp(vartok, "REPOSITORY_DIR")) {
|
|
|
|
currconfigtag->repository_dir =
|
|
|
|
(char *) strdup(valuetok);
|
|
|
|
} else if (!strcmp(vartok, "PARENT")) {
|
|
|
|
currconfigtag->repository[curraltrep]=findRepositoryByTag(valuetok);
|
|
|
|
if (!currconfigtag->repository[curraltrep]) {
|
|
|
|
fprintf(stderr,"Error: repository %s requested in %s was not previously configured; aborting.\n",
|
|
|
|
valuetok, currconfigtag->tag);
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
curraltrep++;
|
|
|
|
} else if (!strcmp(vartok, "HTML_DIR")) {
|
|
|
|
currconfigtag->html_dir = (char *) strdup(valuetok);
|
|
|
|
} else if (!strcmp(vartok, "DOWNLOAD_PREFIX")) {
|
|
|
|
currconfigtag->download_prefix = (char *) strdup(valuetok);
|
|
|
|
} else if (!strcmp(vartok, "DOWNLOAD_DIR")) {
|
|
|
|
currconfigtag->download_dir = (char *) strdup(valuetok);
|
|
|
|
} else if (!strcmp(vartok, "SHOWFILE_PREFIX")) {
|
|
|
|
currconfigtag->showfile_prefix = (char *) strdup(valuetok);
|
|
|
|
} else if (!strcmp(vartok, "REPOSITORY_SOURCE_DIR")) {
|
|
|
|
currconfigtag->repository_source_dir =
|
|
|
|
(char *) strdup(valuetok);
|
|
|
|
} else if (!strcmp(vartok, "DESCRIPTION")) {
|
|
|
|
currconfigtag->description =
|
|
|
|
(char *) strdup(valuetok);
|
|
|
|
} else if (!strcmp(vartok, "ARCHS")) {
|
|
|
|
vartok = (char *) strtok(valuetok, " ");
|
|
|
|
i = 0;
|
|
|
|
while ((vartok) && (i < ARCHS_MAX)) {
|
|
|
|
currconfigtag->arch[i] = malloc(sizeof vartok);
|
|
|
|
if (!currconfigtag->arch[i]) {
|
|
|
|
fprintf(stderr, "The system is out of memory\n");
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
strcpy(currconfigtag->arch[i], vartok);
|
|
|
|
vartok = strtok(NULL, " ");
|
|
|
|
i++;
|
|
|
|
}
|
|
|
|
if (vartok) {
|
|
|
|
fprintf(stderr,
|
|
|
|
"Error: exceeding number of archs defined; maximum is %d.",ARCHS_MAX);
|
|
|
|
return 1;
|
|
|
|
} else {
|
|
|
|
currconfigtag->arch[i] = NULL;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
fprintf(stderr, "Undefined token: %s\n", vartok);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
} else if (configsection == CONF_DEFAULTS_SECTION) {
|
|
|
|
/* we are in the default section */
|
|
|
|
if (!strcmp(vartok, "HTML_BASEDIR")) {
|
|
|
|
configdefaults.html_basedir =
|
|
|
|
(char *) strdup(valuetok);
|
|
|
|
} else if (!strcmp(vartok, "DISTRIBUTION_NAME")) {
|
|
|
|
configdefaults.distribution_name =
|
|
|
|
(char *) strdup(valuetok);
|
|
|
|
} else if (!strcmp(vartok, "URL_ADDRESS")) {
|
|
|
|
configdefaults.url_address =
|
|
|
|
(char *) strdup(valuetok);
|
|
|
|
} else if (!strcmp(vartok, "URL_PREFIX")) {
|
|
|
|
configdefaults.url_prefix =
|
|
|
|
(char *) strdup(valuetok);
|
|
|
|
} else if (!strcmp(vartok, "URL_DIR")) {
|
|
|
|
configdefaults.url_dir =
|
|
|
|
(char *) strdup(valuetok);
|
|
|
|
} else if (!strcmp(vartok, "ARCHS")) {
|
|
|
|
vartok = (char *) strtok(valuetok, " ");
|
|
|
|
i = 0;
|
|
|
|
while ((vartok) && (i < ARCHS_MAX)) {
|
|
|
|
configdefaults.arch[i] = malloc(sizeof vartok);
|
|
|
|
if (!configdefaults.arch[i]) {
|
|
|
|
fprintf(stderr, "The system is out of memory\n");
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
strcpy(configdefaults.arch[i], vartok);
|
|
|
|
vartok = strtok(NULL, " ");
|
|
|
|
i++;
|
|
|
|
}
|
|
|
|
if (vartok) {
|
|
|
|
fprintf(stderr,
|
|
|
|
"Error: exceeding number of archs defined; maximum is %d.",ARCHS_MAX);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
fprintf(stderr,
|
|
|
|
"Invalid token for `defaults' tag: %s\n",
|
|
|
|
vartok);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
} else if (configsection == CONF_MAINTAINERS_SECTION) {
|
|
|
|
|
|
|
|
/* find or create maintainer */
|
|
|
|
currmaintainer = getPackagerByName(vartok, 1);
|
|
|
|
|
|
|
|
if (!currmaintainer) {
|
|
|
|
fprintf(stderr,
|
|
|
|
"Error: cannot create maintainers; aborting.");
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
currmaintainer->role |= PACKAGER_ROLE_MAINTAINER;
|
|
|
|
|
|
|
|
vartok = (char *) strtok(valuetok, "\"");
|
|
|
|
|
|
|
|
i = 0;
|
|
|
|
while ((currmaintainer->alias[i]) && (i < PACKAGER_MAXALIASES)) i++;
|
|
|
|
|
|
|
|
while ((vartok) && (i < PACKAGER_MAXALIASES)) {
|
|
|
|
currmaintainer->alias[i]=strdup(vartok);
|
|
|
|
vartok = strtok(NULL, "\"");
|
|
|
|
vartok = strtok(NULL, "\"");
|
|
|
|
i++;
|
|
|
|
}
|
|
|
|
if (vartok) {
|
|
|
|
fprintf(stderr,
|
|
|
|
"Error: exceeding number of aliases defined for maintainer %s.",currmaintainer->name);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/* check for errors and complete previous tag infos */
|
|
|
|
for(currconfigtag = firstconfigtag; currconfigtag;
|
|
|
|
currconfigtag = currconfigtag->next) {
|
|
|
|
|
|
|
|
if (!currconfigtag->repository_dir) {
|
|
|
|
fprintf(stderr,
|
|
|
|
"REPOSITORY_DIR not given for tag %s\n",
|
|
|
|
currconfigtag->tag);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!currconfigtag->repository_source_dir) {
|
|
|
|
strncpy(buf, currconfigtag->repository_dir, bufsize);
|
|
|
|
strncat(buf, "/SRPMS.base/", bufsize);
|
|
|
|
currconfigtag->repository_source_dir =
|
|
|
|
(char *) strdup(buf);
|
|
|
|
}
|
|
|
|
|
|
|
|
currconfigtag->repository_level=0;
|
|
|
|
|
|
|
|
for (i = 0; i < ALT_REPS_MAX; i++) {
|
|
|
|
if (currconfigtag->repository[i]) {
|
|
|
|
currconfigtag->repository_level=i+1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
currconfigtag->repository[currconfigtag->repository_level]=currconfigtag;
|
|
|
|
|
|
|
|
if (!currconfigtag->description)
|
|
|
|
currconfigtag->description=currconfigtag->tag;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
fclose(fin);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
|
|
|
main(int argc, char *argv[])
|
|
|
|
{
|
|
|
|
char *name = NULL, *date = NULL,
|
|
|
|
*repository_dir = NULL, *repository_tag = NULL,
|
|
|
|
*configfile = NULL;
|
|
|
|
|
|
|
|
struct headerList *currheaderlist;
|
|
|
|
struct headerSourceList *currheadersourcelist = NULL;
|
|
|
|
|
|
|
|
unsigned int mode = 0;
|
|
|
|
unsigned int recursive_mode = 0;
|
|
|
|
unsigned int genheader_mode = GENHEADER_BASE + GENHEADER_REQUIREMENTS;
|
|
|
|
unsigned int quietmode = 0;
|
|
|
|
unsigned int obsolete_packages = 1;
|
|
|
|
int i;
|
|
|
|
char warning[PATH_MAX];
|
|
|
|
|
|
|
|
time_t start_time, stop_time;
|
|
|
|
|
|
|
|
/* options args for 'getopt_long()' */
|
|
|
|
static const char *optstring = "+a:c:t:qhvd";
|
|
|
|
static struct option longopts[] = {
|
|
|
|
{ "deps-table", no_argument, 0, 0 },
|
|
|
|
{ "data-tables", no_argument, 0, 0 },
|
|
|
|
{ "gendatatables", no_argument, 0, 0 },
|
|
|
|
{ "find-deps", required_argument, 0, 0 },
|
|
|
|
{ "changelog", required_argument, 0, 0 },
|
|
|
|
{ "changelogsince", required_argument, 0, 0 },
|
|
|
|
{ "genbuildinfo", no_argument, 0, 0 },
|
|
|
|
{ "genhtml", no_argument, 0, 0 },
|
|
|
|
{ "genpkglist", no_argument, 0, 0 },
|
|
|
|
{ "gensrcpkglist", no_argument, 0, 0 },
|
|
|
|
{ "arch", required_argument, 0, 'a' },
|
|
|
|
{ "conf", required_argument, 0, 'c' },
|
|
|
|
{ "help", no_argument, 0, 'h' },
|
|
|
|
{ "quiet", no_argument, 0, 'q' },
|
|
|
|
{ "tag", required_argument, 0, 't' },
|
|
|
|
{ "version", no_argument, 0, 'v' },
|
|
|
|
{ "debug", no_argument, 0, 'd' },
|
|
|
|
{ 0, 0, 0, 0 }
|
|
|
|
};
|
|
|
|
|
|
|
|
start_time=time(NULL);
|
|
|
|
|
|
|
|
opterr = 1;
|
|
|
|
unsigned int syntaxerr = 0;
|
|
|
|
|
|
|
|
while (1) {
|
|
|
|
int longindex = 0;
|
|
|
|
int argval = getopt_long(argc, argv, optstring, longopts, &longindex);
|
|
|
|
if (argval == -1) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (argval) {
|
|
|
|
case 0:
|
|
|
|
if (!strcmp(longopts[longindex].name, "gendatatables")) {
|
|
|
|
mode |= MODE_DATA_TABLES;
|
|
|
|
} else if (!strcmp(longopts[longindex].name, "deps-table") ||
|
|
|
|
!strcmp(longopts[longindex].name, "data-tables")) {
|
|
|
|
fprintf(stderr,"Warning: use of --deps-table and --data-tables is obsolete; use --gendatatables instead.\n");
|
|
|
|
mode |= MODE_DATA_TABLES;
|
|
|
|
} else if (!strcmp(longopts[longindex].name, "find-deps")) {
|
|
|
|
mode |= MODE_FIND_DEPS;
|
|
|
|
name = optarg;
|
|
|
|
} else if (!strcmp(longopts[longindex].name, "changelog")) {
|
|
|
|
mode |= MODE_CHANGELOG;
|
|
|
|
genheader_mode |= GENHEADER_CHANGELOG | GENHEADER_STATS;
|
|
|
|
name = optarg;
|
|
|
|
} else if (!strcmp(longopts[longindex].name, "changelogsince")) {
|
|
|
|
mode |= MODE_CHANGELOG;
|
|
|
|
genheader_mode |= GENHEADER_CHANGELOG | GENHEADER_STATS;
|
|
|
|
date = optarg;
|
|
|
|
name = NULL;
|
|
|
|
} else if (!strcmp(longopts[longindex].name, "genhtml")) {
|
|
|
|
mode |= MODE_HTML;
|
|
|
|
genheader_mode |= GENHEADER_CHANGELOG | GENHEADER_STATS;
|
|
|
|
recursive_mode = 1;
|
|
|
|
name = NULL;
|
|
|
|
} else if (!strcmp(longopts[longindex].name, "gensrcpkglist")) {
|
|
|
|
mode |= MODE_GENSRCPKGLIST;
|
|
|
|
name = NULL;
|
|
|
|
} else if (!strcmp(longopts[longindex].name, "genpkglist")) {
|
|
|
|
mode |= MODE_GENPKGLIST;
|
|
|
|
name = NULL;
|
|
|
|
} else if (!strcmp(longopts[longindex].name, "genbuildinfo")) {
|
|
|
|
mode |= MODE_GENBUILDINFO;
|
|
|
|
genheader_mode |= GENHEADER_BASE;
|
|
|
|
name = NULL;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 'a':
|
|
|
|
arch = optarg;
|
|
|
|
break;
|
|
|
|
case 'c':
|
|
|
|
configfile = optarg;
|
|
|
|
break;
|
|
|
|
case 't':
|
|
|
|
repository_tag = optarg;
|
|
|
|
break;
|
|
|
|
case 'h':
|
|
|
|
program_usage(0);
|
|
|
|
break;
|
|
|
|
case 'q':
|
|
|
|
quietmode=1;
|
|
|
|
break;
|
|
|
|
case 'v':
|
|
|
|
program_version();
|
|
|
|
exit(0);
|
|
|
|
break;
|
|
|
|
case '?':
|
|
|
|
/* program_usage(1); */
|
|
|
|
exit(1);
|
|
|
|
break;
|
|
|
|
case 'd':
|
|
|
|
log_debug_set(1);
|
|
|
|
logmsg(LOG_DEBUG,"debug logging enabled");
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
fprintf(stderr,
|
|
|
|
"Fatal error while parsing command line arguments\n");
|
|
|
|
exit(1);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (optind < argc) {
|
|
|
|
fprintf(stderr, "Non-option elements found: ");
|
|
|
|
while (optind < argc)
|
|
|
|
fprintf(stderr, "`%s' ", argv[optind++]);
|
|
|
|
fprintf(stderr, "\n");
|
|
|
|
exit(1);
|
|
|
|
} else if (syntaxerr || !mode || !repository_tag ) {
|
|
|
|
program_usage(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!configfile) {
|
|
|
|
configfile = malloc(sizeof DEFAULT_CONFIGFILE);
|
|
|
|
if (!configfile) {
|
|
|
|
fprintf(stderr, "The system is out of memory\n");
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
strcpy(configfile, DEFAULT_CONFIGFILE);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (read_configuration(configfile, repository_tag)) {
|
|
|
|
fprintf(stderr,
|
|
|
|
"Fatal error while parsing config file %s; aborting.\n",configfile);
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!configtag) {
|
|
|
|
fprintf(stderr,
|
|
|
|
"Fatal error: configuration missing for given tag\n");
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (arch) {
|
|
|
|
configtag->arch[0] = malloc(sizeof arch);
|
|
|
|
if (!configtag->arch[0]) {
|
|
|
|
fprintf(stderr, "The system is out of memory\n");
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
strcpy(configtag->arch[0], arch);
|
|
|
|
configtag->arch[1] = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
rpminit();
|
|
|
|
|
|
|
|
if (!quietmode)
|
|
|
|
fprintf(stdout, "Starting operations on '%s' repository\n", configtag->tag);
|
|
|
|
|
|
|
|
if (!repository_dir)
|
|
|
|
repository_dir = configtag->repository_dir;
|
|
|
|
|
|
|
|
if (!quietmode)
|
|
|
|
fprintf(stdout, "Scanning source packages...\n");
|
|
|
|
else
|
|
|
|
logmsg(LOG_MARK,"Source packages check for %s:", configtag->tag);
|
|
|
|
|
|
|
|
if (generateSourceHeaderList(configtag, genheader_mode)) {
|
|
|
|
fprintf(stderr,
|
|
|
|
"Fatal error: could not generate source header list\n");
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (genheader_mode) {
|
|
|
|
|
2012-04-15 15:41:06 +02:00
|
|
|
if (mode & MODE_HTML) {
|
|
|
|
logmsg(LOG_DEBUG,"cleanHTMLPackagesFiles - start");
|
|
|
|
cleanHTMLPackagesFiles(configtag);
|
|
|
|
logmsg(LOG_DEBUG,"cleanHTMLPackagesFiles - done");
|
|
|
|
}
|
|
|
|
|
2011-04-27 21:50:52 +02:00
|
|
|
for (i = 0; i < ARCHS_MAX && configtag->arch[i]; i++) {
|
|
|
|
|
|
|
|
if (!quietmode)
|
|
|
|
fprintf(stdout, "Scanning binary packages for %s arch ...\n",configtag->arch[i]);
|
|
|
|
else
|
|
|
|
logmsg(LOG_MARK, "%s binary packages check for %s:", configtag->arch[i],configtag->tag);
|
|
|
|
|
|
|
|
if (generateHeaderList(configtag,i)) {
|
|
|
|
fprintf(stderr,
|
|
|
|
"Fatal error: could not generate header list\n");
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!quietmode)
|
|
|
|
fprintf(stdout, "Checking for SRPMS with no RPMS...\n");
|
|
|
|
|
|
|
|
currheadersourcelist = configtag->headersourcelist;
|
|
|
|
while (currheadersourcelist != NULL) {
|
|
|
|
if (!currheadersourcelist->firstchild) {
|
|
|
|
snprintf(warning, PATH_MAX, "SRPM does not have any valid RPM build");
|
|
|
|
logmsg(LOG_WARNING, "%s: %s",
|
|
|
|
currheadersourcelist->name,
|
|
|
|
warning);
|
|
|
|
addWarning(currheadersourcelist, warning);
|
|
|
|
}
|
|
|
|
currheadersourcelist = currheadersourcelist->next;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (genheader_mode & GENHEADER_REQUIREMENTS) {
|
|
|
|
|
|
|
|
if (obsolete_packages) {
|
|
|
|
if (!quietmode)
|
|
|
|
fprintf(stdout, " - handling obsoleted packages...\n");
|
|
|
|
if (handleObsoletedPackages(configtag,i)) {
|
|
|
|
fprintf(stderr,
|
|
|
|
"Fatal error: maximum number of multiple providing packages reached. Aborting.");
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!quietmode)
|
|
|
|
fprintf(stdout, " - resolving dependencies...\n");
|
|
|
|
if (resolveFirstLevelDependencies(configtag, i)) {
|
|
|
|
fprintf(stderr,
|
|
|
|
"Fatal error: could not generate first level requires table\n");
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (i == 0) /* fixme? */ {
|
|
|
|
if (!quietmode)
|
|
|
|
fprintf(stdout, " - resolving source dependencies\n");
|
|
|
|
if (resolveFirstLevelSourceDependencies(configtag, i)) {
|
|
|
|
fprintf(stderr,
|
|
|
|
"Fatal error: could not generate first level requires table\n");
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (recursive_mode) {
|
|
|
|
if (!quietmode)
|
|
|
|
fprintf(stdout, " - resolving recursive dependencies...\n");
|
|
|
|
|
|
|
|
if (resolveRecursiveDependencies
|
|
|
|
(configtag->headerlist[i])) {
|
|
|
|
fprintf(stderr,
|
|
|
|
"Fatal error: could not resolve recursive dependencies\n");
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (mode & MODE_DATA_TABLES) {
|
|
|
|
if (!quietmode) fprintf(stdout, " - writing dependencies table...\n");
|
|
|
|
print_datatables(configtag,i); }
|
|
|
|
|
|
|
|
if (genheader_mode & GENHEADER_STATS) {
|
|
|
|
if (!quietmode)
|
|
|
|
fprintf(stdout, " - generating statistics...\n");
|
|
|
|
|
|
|
|
logmsg(LOG_DEBUG,"generateHeaderStats - start");
|
|
|
|
if (generateHeaderStats(configtag, i)) {
|
|
|
|
fprintf(stderr,
|
|
|
|
"Fatal error: could not generate statistic for headers\n");
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
logmsg(LOG_DEBUG,"generateHeaderStats - done");
|
|
|
|
}
|
|
|
|
|
|
|
|
if (mode & MODE_HTML) {
|
|
|
|
// printf("Generating HTML files for binary RPMs...\n");
|
|
|
|
logmsg(LOG_DEBUG,"generateHTMLFiles - start");
|
|
|
|
generateHTMLFiles(configtag, i);
|
|
|
|
logmsg(LOG_DEBUG,"generateHTMLFiles - done");
|
|
|
|
}
|
|
|
|
|
|
|
|
if (mode & MODE_GENPKGLIST) {
|
|
|
|
/* currheaderlist = headerlist; */
|
|
|
|
generatePkgList(configtag, i);
|
|
|
|
}
|
|
|
|
|
|
|
|
} // archs loop
|
|
|
|
|
|
|
|
} // if (genheader_mode)
|
|
|
|
|
|
|
|
if (genheader_mode & GENHEADER_STATS) {
|
|
|
|
if (!quietmode)
|
|
|
|
fprintf(stdout, " - generating source statistics...\n");
|
|
|
|
|
|
|
|
logmsg(LOG_DEBUG,"generateHeaderSourceStats - start");
|
|
|
|
if (generateHeaderSourceStats(configtag)) {
|
|
|
|
fprintf(stderr,
|
|
|
|
"Fatal error: could not generate statistic for headers\n");
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
logmsg(LOG_DEBUG,"generateHeaderSourceStats - done");
|
|
|
|
}
|
|
|
|
|
|
|
|
if (mode & MODE_FIND_DEPS) {
|
|
|
|
currheaderlist = findPackageByName(configtag->headerlist[i], name);
|
|
|
|
|
|
|
|
if (currheaderlist) {
|
|
|
|
printf("%s: ", currheaderlist->name);
|
|
|
|
printRequireList(stdout,currheaderlist->requirelist);
|
|
|
|
/* currrequirelist = currheaderlist->requirelist; */
|
|
|
|
printf("\n");
|
|
|
|
} else {
|
|
|
|
fprintf(stderr, "Error: package %s not found\n", name);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (mode & MODE_CHANGELOG) {
|
|
|
|
if (name) {
|
|
|
|
currheadersourcelist =
|
|
|
|
findSourcePackage(configtag->headersourcelist, name,
|
|
|
|
NULL, NULL,-1);
|
|
|
|
if (currheadersourcelist) {
|
|
|
|
printChangelog(currheadersourcelist->changelog);
|
|
|
|
} else {
|
|
|
|
fprintf(stderr, "Error: package %s not found\n", name);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
struct tm tmdate;
|
|
|
|
|
|
|
|
tmdate.tm_min = 0;
|
|
|
|
tmdate.tm_hour = 0;
|
|
|
|
tmdate.tm_sec = 0;
|
|
|
|
|
|
|
|
const char *cp;
|
|
|
|
|
|
|
|
cp = (char *) strptime((const char *) date, "%m%d%y", &tmdate);
|
|
|
|
if (!cp) {
|
|
|
|
fprintf(stderr, "Error: date \"%s\" is invalid!\n", date);
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
printChangelogSince(stdout,configtag, &tmdate, 0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (mode & MODE_HTML) {
|
2012-04-15 12:08:10 +02:00
|
|
|
logmsg(LOG_DEBUG,"cleanHTMLFiles - start");
|
|
|
|
cleanHTMLFiles(configtag);
|
|
|
|
logmsg(LOG_DEBUG,"cleanHTMLFiles - done");
|
|
|
|
|
2011-04-27 21:50:52 +02:00
|
|
|
if (!quietmode)
|
|
|
|
fprintf(stdout, "Generating HTML reports...\n");
|
|
|
|
generateHTMLMainIndex(firstconfigtag);
|
|
|
|
|
|
|
|
// printf("Generating HTML files for source RPMs...\n");
|
|
|
|
generateHTML_SRPMSFiles(configtag);
|
|
|
|
|
|
|
|
// printf("Generating Maintainers pages...\n");
|
|
|
|
generateMaintainersPages(configtag);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
// NOTE: generateStats must be called after generateHTML_SRPMSFiles for warnings to appear
|
|
|
|
if (mode & MODE_HTML) {
|
|
|
|
logmsg(LOG_DEBUG,"generateStats - start");
|
|
|
|
generateStats(configtag,i);
|
|
|
|
logmsg(LOG_DEBUG,"generateStats - done");
|
|
|
|
}
|
|
|
|
|
|
|
|
if (mode & MODE_GENSRCPKGLIST) {
|
|
|
|
/* currheaderlist = headerlist; */
|
|
|
|
generateSrcPkgList(configtag);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (mode & MODE_GENBUILDINFO) {
|
|
|
|
/* currheaderlist = headerlist; */
|
|
|
|
printf("Generating build information files...\n");
|
|
|
|
generateBuildInfo(configtag,0);
|
|
|
|
}
|
|
|
|
|
|
|
|
stop_time=time(NULL);
|
|
|
|
|
|
|
|
if (!quietmode)
|
|
|
|
fprintf(stdout, "Execution time: %ld.%02ld minutes\n",(stop_time-start_time)/60,(stop_time-start_time)%60);
|
|
|
|
|
|
|
|
exit(0);
|
|
|
|
}
|