Mailinglist Archive: zypp-commit (171 mails)

< Previous Next >
[zypp-commit] <sat-solver> master : - add tiny demo installer program "solv"
  • From: Michael Schroeder <mls@xxxxxxx>
  • Date: Tue, 23 Jun 2009 15:32:06 +0200
  • Message-id: <E1MJ67R-0008BH-Ln@xxxxxxxxxxxxxxxx>
ref: refs/heads/master
commit 28a0bca2eed186dff96543ef624b931726d1a64d
Author: Michael Schroeder <mls@xxxxxxx>
Date: Tue Jun 23 15:32:06 2009 +0200

- add tiny demo installer program "solv"
- add transaction_installedresult function
- fix bug in conflict detection
---
examples/CMakeLists.txt | 2 +
examples/solv.c | 725 +++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 727 insertions(+), 0 deletions(-)

diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt
new file mode 100644
index 0000000..79a987a
--- /dev/null
+++ b/examples/CMakeLists.txt
@@ -0,0 +1,2 @@
+ADD_EXECUTABLE(solv solv.c)
+TARGET_LINK_LIBRARIES(solv satsolverext satsolver ${RPMDB_LIBRARY}
${EXPAT_LIBRARY} ${ZLIB_LIBRARY})
diff --git a/examples/solv.c b/examples/solv.c
new file mode 100644
index 0000000..46b95b9
--- /dev/null
+++ b/examples/solv.c
@@ -0,0 +1,725 @@
+#define _GNU_SOURCE
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <dirent.h>
+#include <unistd.h>
+#include <zlib.h>
+#include <sys/utsname.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+
+#include "pool.h"
+#include "poolarch.h"
+#include "repo.h"
+#include "util.h"
+#include "solver.h"
+#include "solverdebug.h"
+
+#include "repo_rpmdb.h"
+#include "repo_rpmmd.h"
+#include "repo_susetags.h"
+#include "repo_repomdxml.h"
+#include "pool_fileconflicts.h"
+
+/* solv, a little demo application */
+
+struct repoinfo {
+ Repo *repo;
+
+ char *alias;
+ char *name;
+ int enabled;
+ int autorefresh;
+ char *baseurl;
+ char *path;
+ int type;
+ int priority;
+ int keeppackages;
+};
+
+#define TYPE_UNKNOWN 0
+#define TYPE_SUSETAGS 1
+#define TYPE_RPMMD 2
+
+struct repoinfo *
+read_repoinfos(Pool *pool, const char *reposdir, int *nrepoinfosp)
+{
+ char buf[4096];
+ char buf2[4096], *kp, *vp, *kpe;
+ DIR *dir;
+ FILE *fp;
+ struct dirent *ent;
+ int l, rdlen;
+ struct repoinfo *repoinfos = 0, *cinfo;
+ int nrepoinfos = 0;
+
+ rdlen = strlen(reposdir);
+ dir = opendir(reposdir);
+ if (!dir)
+ {
+ *nrepoinfosp = 0;
+ return 0;
+ }
+ while ((ent = readdir(dir)) != 0)
+ {
+ l = strlen(ent->d_name);
+ if (l < 6 || rdlen + 2 + l >= sizeof(buf) || strcmp(ent->d_name + l - 5,
".repo") != 0)
+ continue;
+ snprintf(buf, sizeof(buf), "%s/%s", reposdir, ent->d_name);
+ if ((fp = fopen(buf, "r")) == 0)
+ {
+ perror(buf);
+ continue;
+ }
+ cinfo = 0;
+ while(fgets(buf2, sizeof(buf2), fp))
+ {
+ l = strlen(buf2);
+ if (l == 0)
+ continue;
+ while (buf2[l - 1] == '\n' || buf2[l - 1] == ' ' || buf2[l - 1] ==
'\t')
+ buf2[--l] = 0;
+ kp = buf2;
+ while (*kp == ' ' || *kp == '\t')
+ kp++;
+ if (!*kp || *kp == '#')
+ continue;
+ if (!cinfo)
+ {
+ if (*kp != '[')
+ continue;
+ vp = strrchr(kp, ']');
+ if (!vp)
+ continue;
+ *vp = 0;
+ repoinfos = sat_extend(repoinfos, nrepoinfos, 1,
sizeof(*repoinfos), 15);
+ cinfo = repoinfos + nrepoinfos++;
+ memset(cinfo, 0, sizeof(*cinfo));
+ cinfo->alias = strdup(kp + 1);
+ continue;
+ }
+ vp = strchr(kp, '=');
+ if (!vp)
+ continue;
+ for (kpe = vp - 1; kpe >= kp; kpe--)
+ if (*kpe != ' ' && *kpe != '\t')
+ break;
+ if (kpe == kp)
+ continue;
+ vp++;
+ while (*vp == ' ' || *vp == '\t')
+ vp++;
+ kpe[1] = 0;
+ if (!strcmp(kp, "name"))
+ cinfo->name = strdup(vp);
+ else if (!strcmp(kp, "enabled"))
+ cinfo->enabled = *vp == '0' ? 0 : 1;
+ else if (!strcmp(kp, "autorefresh"))
+ cinfo->autorefresh = *vp == '0' ? 0 : 1;
+ else if (!strcmp(kp, "baseurl"))
+ cinfo->baseurl = strdup(vp);
+ else if (!strcmp(kp, "path"))
+ cinfo->path = strdup(vp);
+ else if (!strcmp(kp, "type"))
+ {
+ if (!strcmp(vp, "yast2"))
+ cinfo->type = TYPE_SUSETAGS;
+ else if (!strcmp(vp, "rpm-md"))
+ cinfo->type = TYPE_RPMMD;
+ else
+ cinfo->type = TYPE_UNKNOWN;
+ }
+ else if (!strcmp(kp, "priority"))
+ cinfo->priority = atoi(vp);
+ else if (!strcmp(kp, "keeppackages"))
+ cinfo->keeppackages = *vp == '0' ? 0 : 1;
+ }
+ fclose(fp);
+ if (cinfo->type == TYPE_SUSETAGS && cinfo->baseurl)
+ {
+ char *old = cinfo->baseurl;
+ cinfo->baseurl = sat_malloc(5 + strlen(old) + 1);
+ sprintf(cinfo->baseurl, "%s/suse", old);
+ free(old);
+ }
+ cinfo = 0;
+ }
+ closedir(dir);
+ *nrepoinfosp = nrepoinfos;
+ return repoinfos;
+}
+
+static ssize_t
+cookie_gzread(void *cookie, char *buf, size_t nbytes)
+{
+ return gzread((gzFile *)cookie, buf, nbytes);
+}
+
+static int
+cookie_gzclose(void *cookie)
+{
+ return gzclose((gzFile *)cookie);
+}
+
+FILE *
+myfopen(const char *fn)
+{
+ cookie_io_functions_t cio;
+ char *suf;
+ gzFile *gzf;
+
+ if (!fn)
+ return 0;
+ suf = strrchr(fn, '.');
+ if (!suf || strcmp(suf, ".gz") != 0)
+ return fopen(fn, "r");
+ gzf = gzopen(fn, "r");
+ if (!gzf)
+ return 0;
+ memset(&cio, 0, sizeof(cio));
+ cio.read = cookie_gzread;
+ cio.close = cookie_gzclose;
+ return fopencookie(gzf, "r", cio);
+}
+
+FILE *
+curlfopen(char *baseurl, char *file, int uncompress)
+{
+ pid_t pid;
+ int fd, l;
+ int status;
+ char tmpl[100];
+ char url[4096];
+
+ l = strlen(baseurl);
+ if (l && baseurl[l - 1] == '/')
+ snprintf(url, sizeof(url), "%s%s", baseurl, file);
+ else
+ snprintf(url, sizeof(url), "%s/%s", baseurl, file);
+ strcpy(tmpl, "/var/tmp/solvXXXXXX");
+ fd = mkstemp(tmpl);
+ if (fd < 0)
+ {
+ perror("mkstemp");
+ exit(1);
+ }
+ unlink(tmpl);
+ if ((pid = fork()) == (pid_t)-1)
+ {
+ perror("fork");
+ exit(1);
+ }
+ if (pid == 0)
+ {
+ if (fd != 1)
+ {
+ dup2(fd, 1);
+ close(fd);
+ }
+ execlp("curl", "curl", "-s", "-L", url, (char *)0);
+ perror("curl");
+ _exit(0);
+ }
+ while (waitpid(pid, &status, 0) != pid)
+ ;
+ lseek(fd, 0, SEEK_SET);
+ if (uncompress)
+ {
+ cookie_io_functions_t cio;
+ gzFile *gzf;
+
+ sprintf(tmpl, "/dev/fd/%d", fd);
+ gzf = gzopen(tmpl, "r");
+ close(fd);
+ if (!gzf)
+ return 0;
+ memset(&cio, 0, sizeof(cio));
+ cio.read = cookie_gzread;
+ cio.close = cookie_gzclose;
+ return fopencookie(gzf, "r", cio);
+ }
+ return fdopen(fd, "r");
+}
+
+void
+setarch(Pool *pool)
+{
+ struct utsname un;
+ if (uname(&un))
+ {
+ perror("uname");
+ exit(1);
+ }
+ pool_setarch(pool, un.machine);
+}
+
+void
+read_repos(Pool *pool, struct repoinfo *repoinfos, int nrepoinfos)
+{
+ Repo *repo;
+ struct repoinfo *cinfo;
+ int i;
+ FILE *fp;
+
+ printf("reading rpm database\n");
+ repo = repo_create(pool, "@System");
+ repo_add_rpmdb(repo, 0, 0, 0);
+ pool_set_installed(pool, repo);
+ for (i = 0; i < nrepoinfos; i++)
+ {
+ cinfo = repoinfos + i;
+ if (!cinfo->enabled)
+ continue;
+ switch (cinfo->type)
+ {
+ case TYPE_RPMMD:
+ printf("reading rpmmd repo '%s'\n", cinfo->alias);
+ if ((fp = curlfopen(cinfo->baseurl, "repodata/repomd.xml", 0)) == 0)
+ break;
+ repo = repo_create(pool, cinfo->alias);
+ cinfo->repo = repo;
+ repo_add_repomdxml(repo, fp, 0);
+ fclose(fp);
+ if ((fp = curlfopen(cinfo->baseurl, "repodata/primary.xml.gz", 1)) ==
0)
+ continue;
+ repo_add_rpmmd(repo, fp, 0, 0);
+ fclose(fp);
+ break;
+ case TYPE_SUSETAGS:
+ printf("reading susetags repo '%s'\n", cinfo->alias);
+ if ((fp = curlfopen(cinfo->baseurl, "setup/descr/packages.gz", 1)) ==
0)
+ break;
+ repo = repo_create(pool, cinfo->alias);
+ cinfo->repo = repo;
+ repo_add_susetags(repo, fp, 0, 0, 0);
+ fclose(fp);
+ break;
+ default:
+ printf("skipping unknown repo '%s'\n", cinfo->alias);
+ break;
+ }
+ }
+}
+
+void
+mkselect(Pool *pool, const char *arg, int flags, Queue *out)
+{
+ Id id, p, pp;
+ Id type = 0;
+ const char *r, *r2;
+
+ id = str2id(pool, arg, 0);
+ if (id)
+ {
+ FOR_PROVIDES(p, pp, id)
+ {
+ Solvable *s = pool_id2solvable(pool, p);
+ if (s->name == id)
+ {
+ type = SOLVER_SOLVABLE_NAME;
+ break;
+ }
+ type = SOLVER_SOLVABLE_PROVIDES;
+ }
+ }
+ if (!type)
+ {
+ /* did not find a solvable, see if it's a relation */
+ if ((r = strpbrk(arg, "<=>")) != 0)
+ {
+ Id rid, rname, revr;
+ int rflags = 0;
+ for (r2 = r; r2 > arg && (r2[-1] == ' ' || r2[-1] == '\t'); )
+ r2--;
+ rname = r2 > arg ? strn2id(pool, arg, r2 - arg, 1) : 0;
+ for (; *r; r++)
+ {
+ if (*r == '<')
+ rflags |= REL_LT;
+ else if (*r == '=')
+ rflags |= REL_EQ;
+ else if (*r == '>')
+ rflags |= REL_GT;
+ else
+ break;
+ }
+ while (*r == ' ' || *r == '\t')
+ r++;
+ revr = *r ? str2id(pool, r, 1) : 0;
+ rid = rname && revr ? rel2id(pool, rname, revr, rflags, 1) : 0;
+ if (rid)
+ {
+ FOR_PROVIDES(p, pp, rid)
+ {
+ Solvable *s = pool_id2solvable(pool, p);
+ if (pool_match_nevr(pool, s, rid))
+ {
+ type = SOLVER_SOLVABLE_NAME;
+ break;
+ }
+ type = SOLVER_SOLVABLE_PROVIDES;
+ }
+ }
+ if (type)
+ id = rid;
+ }
+ }
+ if (type)
+ {
+ queue_push(out, type);
+ queue_push(out, id);
+ }
+}
+
+int
+yesno(const char *str)
+{
+ char inbuf[128], *ip;
+
+ for (;;)
+ {
+ printf("%s", str);
+ fflush(stdout);
+ *inbuf = 0;
+ if (!(ip = fgets(inbuf, sizeof(inbuf), stdin)))
+ {
+ printf("Abort.\n");
+ exit(1);
+ }
+ while (*ip == ' ' || *ip == '\t')
+ ip++;
+ if (*ip == 'q')
+ {
+ printf("Abort.\n");
+ exit(1);
+ }
+ if (*ip == 'y' || *ip == 'n')
+ return *ip == 'y' ? 1 : 0;
+ }
+}
+
+struct fcstate {
+ FILE **newpkgsfps;
+ Id *newpkgsps;
+ int newpkgscnt;
+ void *rpmdbstate;
+};
+
+static void *
+fc_cb(Pool *pool, Id p, void *cbdata)
+{
+ struct fcstate *fcstate = cbdata;
+ Solvable *s;
+ Id rpmdbid;
+ int i;
+ FILE *fp;
+
+ if (!p)
+ {
+ rpm_byrpmdbid(0, 0, &fcstate->rpmdbstate);
+ return 0;
+ }
+ s = pool_id2solvable(pool, p);
+ if (pool->installed && s->repo == pool->installed)
+ {
+ if (!s->repo->rpmdbid)
+ return 0;
+ rpmdbid = s->repo->rpmdbid[p - s->repo->start];
+ if (!rpmdbid)
+ return 0;
+ return rpm_byrpmdbid(rpmdbid, 0, &fcstate->rpmdbstate);
+ }
+ for (i = 0; i < fcstate->newpkgscnt; i++)
+ if (fcstate->newpkgsps[i] == p)
+ break;
+ if (i == fcstate->newpkgscnt)
+ return 0;
+ fp = fcstate->newpkgsfps[i];
+ rewind(fp);
+ return rpm_byfp(fp, solvable2str(pool, s), &fcstate->rpmdbstate);
+}
+
+void
+runrpm(const char *arg, const char *name)
+{
+ pid_t pid;
+ int status;
+
+ if ((pid = fork()) == (pid_t)-1)
+ {
+ perror("fork");
+ exit(1);
+ }
+ if (pid == 0)
+ {
+ if (strcmp(arg, "-e") == 0)
+ execlp("rpm", "rpm", arg, "--nodeps", "--nodigest", "--nosignature",
name, (char *)0);
+ else
+ execlp("rpm", "rpm", arg, "--force", "--nodeps", "--nodigest",
"--nosignature", name, (char *)0);
+ perror("rpm");
+ _exit(0);
+ }
+ while (waitpid(pid, &status, 0) != pid)
+ ;
+ if (status)
+ {
+ printf("rpm failed\n");
+ exit(1);
+ }
+}
+
+int
+main(int argc, char **argv)
+{
+ Pool *pool;
+ Id p, pp;
+ struct repoinfo *repoinfos;
+ int nrepoinfos = 0;
+ int i, mode, newpkgs;
+ Queue job, checkq;
+ Solver *solv = 0;
+ Transaction *trans;
+ char inbuf[128], *ip;
+ int updateall = 0;
+ FILE **newpkgsfps = 0;
+ Id *newpkgsps = 0;
+ struct fcstate fcstate;
+
+ pool = pool_create();
+ // pool_setdebuglevel(pool, 2);
+ setarch(pool);
+ repoinfos = read_repoinfos(pool, "/etc/zypp/repos.d", &nrepoinfos);
+ read_repos(pool, repoinfos, nrepoinfos);
+ // FOR_REPOS(i, repo)
+ // printf("%s: %d solvables\n", repo->name, repo->nsolvables);
+ pool_addfileprovides(pool);
+ pool_createwhatprovides(pool);
+ if (!strcmp(argv[1], "install") || !strcmp(argv[1], "in"))
+ mode = SOLVER_INSTALL;
+ else if (!strcmp(argv[1], "erase") || !strcmp(argv[1], "rm"))
+ mode = SOLVER_ERASE;
+ else if (!strcmp(argv[1], "show"))
+ mode = 0;
+ else if (!strcmp(argv[1], "update") || !strcmp(argv[1], "up"))
+ mode = SOLVER_UPDATE;
+ else
+ {
+ fprintf(stderr, "Usage: solv install|erase|update|show <select>\n");
+ exit(1);
+ }
+ queue_init(&job);
+ for (i = 2; i < argc; i++)
+ mkselect(pool, argv[i], 0, &job);
+ if (!job.count && mode == SOLVER_UPDATE)
+ updateall = 1;
+ else if (!job.count)
+ {
+ printf("nothing matched\n");
+ exit(1);
+ }
+ if (!mode)
+ {
+ for (i = 0; i < job.count; i += 2)
+ {
+ FOR_JOB_SELECT(p, pp, job.elements[i], job.elements[i + 1])
+ printf(" - %s\n", solvid2str(pool, p));
+ }
+ exit(0);
+ }
+ // add mode
+ for (i = 0; i < job.count; i += 2)
+ job.elements[i] |= mode;
+
+rerunsolver:
+ for (;;)
+ {
+ Id problem, solution;
+ int pcnt, scnt;
+
+ solv = solver_create(pool);
+ solv->ignorealreadyrecommended = 1;
+ solv->updatesystem = updateall;
+ solver_solve(solv, &job);
+ if (!solv->problems.count)
+ break;
+ pcnt = solver_problem_count(solv);
+ printf("Found %d problems:\n", pcnt);
+ for (problem = 1; problem <= pcnt; problem++)
+ {
+ int take = 0;
+ printf("Problem %d:\n", problem);
+ solver_printprobleminfo(solv, problem);
+ printf("\n");
+ scnt = solver_solution_count(solv, problem);
+ for (solution = 1; solution <= pcnt; solution++)
+ {
+ printf("Solution %d:\n", solution);
+ solver_printsolution(solv, problem, solution);
+ printf("\n");
+ }
+ for (;;)
+ {
+ printf("Please choose a solution: ");
+ fflush(stdout);
+ *inbuf = 0;
+ if (!(ip = fgets(inbuf, sizeof(inbuf), stdin)))
+ {
+ printf("Abort.\n");
+ exit(1);
+ }
+ while (*ip == ' ' || *ip == '\t')
+ ip++;
+ if (*ip >= '0' && *ip <= '9')
+ {
+ take = atoi(ip);
+ if (take >= 1 && take <= solution)
+ break;
+ }
+ if (*ip == 's')
+ {
+ take = 0;
+ break;
+ }
+ if (*ip == 'q')
+ {
+ printf("Abort.\n");
+ exit(1);
+ }
+ }
+ if (!take)
+ continue;
+ solver_take_solution(solv, problem, take, &job);
+ }
+ solver_free(solv);
+ solv = 0;
+ }
+ if (!solv->trans.steps.count)
+ {
+ printf("Nothing to do.\n");
+ exit(1);
+ }
+ printf("\n");
+ printf("Transaction summary:\n\n");
+ solver_printtransaction(solv);
+ if (!yesno("OK to continue (y/n)? "))
+ {
+ printf("Abort.\n");
+ exit(1);
+ }
+
+ trans = &solv->trans;
+ queue_init(&checkq);
+ newpkgs = transaction_installedresult(trans, &checkq);
+ if (newpkgs)
+ {
+ Queue conflicts;
+ printf("Downloading %d packages\n", newpkgs);
+ newpkgsfps = sat_calloc(newpkgs, sizeof(*newpkgsfps));
+ newpkgsps = sat_calloc(newpkgs, sizeof(Id));
+ for (i = 0; i < newpkgs; i++)
+ {
+ int j;
+ unsigned int medianr;
+ char *loc;
+ Solvable *s;
+ struct repoinfo *cinfo;
+
+ p = checkq.elements[i];
+ newpkgsps[i] = p;
+ s = pool_id2solvable(pool, p);
+ loc = solvable_get_location(s, &medianr);
+ cinfo = 0;
+ for (j = 0; j < nrepoinfos; j++)
+ if (s->repo == repoinfos[j].repo)
+ {
+ cinfo = repoinfos + j;
+ break;
+ }
+ if ((newpkgsfps[i] = curlfopen(cinfo->baseurl, loc, 0)) == 0)
+ {
+ printf("%s: %s not found\n", cinfo->alias, loc);
+ exit(1);
+ }
+ }
+ printf("Searching for file conflicts\n");
+ queue_init(&conflicts);
+ fcstate.rpmdbstate = 0;
+ fcstate.newpkgscnt = newpkgs;
+ fcstate.newpkgsfps = newpkgsfps;
+ fcstate.newpkgsps = newpkgsps;
+ pool_findfileconflicts(pool, &checkq, newpkgs, &conflicts, &fc_cb,
&fcstate);
+ if (conflicts.count)
+ {
+ for (i = 0; i < newpkgs; i++)
+ fclose(newpkgsfps[i]);
+ sat_free(newpkgsfps);
+ sat_free(newpkgsps);
+ solver_free(solv);
+ printf("\n");
+ for (i = 0; i < conflicts.count; i += 5)
+ printf("file %s of package %s conflicts with package %s\n",
id2str(pool, conflicts.elements[i]), solvid2str(pool, conflicts.elements[i +
1]), solvid2str(pool, conflicts.elements[i + 3]));
+ printf("\n");
+ if (!yesno("Re-run solver (y/n)? "))
+ {
+ printf("Abort.\n");
+ exit(1);
+ }
+ pool_add_fileconflicts_deps(pool, &conflicts);
+ pool_createwhatprovides(pool);
+ goto rerunsolver;
+ }
+ queue_free(&conflicts);
+ }
+ transaction_order(trans, 0);
+
+ printf("Committing transaction:\n");
+ for (i = 0; i < trans->steps.count; i++)
+ {
+ char rpmname[256];
+ const char *evr, *evrp;
+ Solvable *s;
+ int j;
+ FILE *fp;
+
+ p = trans->steps.elements[i];
+ s = pool_id2solvable(pool, p);
+ Id type = transaction_type(trans, p, SOLVER_TRANSACTION_RPM_ONLY);
+ switch(type)
+ {
+ case SOLVER_TRANSACTION_ERASE:
+ printf("erase %s\n", solvid2str(pool, p));
+ if (strlen(solvid2str(pool, p)) > 255)
+ continue;
+ evr = evrp = id2str(pool, s->evr);
+ while (*evrp >= '0' && *evrp <= '9')
+ evrp++;
+ if (evrp > evr && evrp[0] == ':' && evrp[1])
+ evr = evrp + 1;
+ sprintf(rpmname, "%s-%s.%s", id2str(pool, s->name), evr, id2str(pool,
s->arch));
+ runrpm("-e", rpmname);
+ break;
+ case SOLVER_TRANSACTION_INSTALL:
+ case SOLVER_TRANSACTION_MULTIINSTALL:
+ printf("install %s\n", solvid2str(pool, p));
+ for (j = 0; j < newpkgs; j++)
+ if (newpkgsps[j] == p)
+ break;
+ fp = j < newpkgs ? newpkgsfps[j] : 0;
+ rewind(fp);
+ lseek(fileno(fp), 0, SEEK_SET);
+ sprintf(rpmname, "/dev/fd/%d", fileno(fp));
+ runrpm(type == SOLVER_TRANSACTION_MULTIINSTALL ? "-i" : "-U",
rpmname);
+ break;
+ default:
+ break;
+ }
+ }
+ for (i = 0; i < newpkgs; i++)
+ fclose(newpkgsfps[i]);
+ sat_free(newpkgsfps);
+ sat_free(newpkgsps);
+ solver_free(solv);
+ pool_free(pool);
+ exit(0);
+}
--
To unsubscribe, e-mail: zypp-commit+unsubscribe@xxxxxxxxxxxx
For additional commands, e-mail: zypp-commit+help@xxxxxxxxxxxx

< Previous Next >
This Thread
  • No further messages