1996-06-08 02:32:10 +08:00
|
|
|
/* reqprov.c -- require/provide handling */
|
|
|
|
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <string.h>
|
|
|
|
#include <unistd.h>
|
|
|
|
#include <signal.h>
|
|
|
|
#include <fcntl.h>
|
|
|
|
#include <ctype.h>
|
|
|
|
#include <sys/types.h>
|
|
|
|
#include <sys/wait.h>
|
1996-07-21 07:00:33 +08:00
|
|
|
#include <sys/stat.h>
|
1996-06-19 01:07:21 +08:00
|
|
|
#include <errno.h>
|
1996-06-08 02:32:10 +08:00
|
|
|
|
|
|
|
#include "specP.h"
|
|
|
|
#include "reqprov.h"
|
|
|
|
#include "messages.h"
|
|
|
|
#include "rpmlib.h"
|
1996-06-28 02:25:09 +08:00
|
|
|
#include "misc.h"
|
1996-06-08 02:32:10 +08:00
|
|
|
|
1996-08-15 05:02:54 +08:00
|
|
|
static StringBuf getOutputFrom(char *dir, char *argv[],
|
1996-11-19 07:15:21 +08:00
|
|
|
char *writePtr, int writeBytesLeft,
|
|
|
|
int failNonZero);
|
1996-06-08 02:32:10 +08:00
|
|
|
|
|
|
|
/*************************************************************/
|
|
|
|
/* */
|
|
|
|
/* Adding entries to the package reqprov list */
|
|
|
|
/* Handle duplicate entries */
|
|
|
|
/* */
|
|
|
|
/*************************************************************/
|
|
|
|
|
|
|
|
int addReqProv(struct PackageRec *p, int flags,
|
|
|
|
char *name, char *version)
|
|
|
|
{
|
|
|
|
struct ReqProv *rd;
|
|
|
|
int same;
|
|
|
|
|
|
|
|
/* Frist see if the same entry is already there */
|
|
|
|
rd = p->reqprov;
|
|
|
|
while (rd) {
|
|
|
|
if (rd->flags == flags) {
|
|
|
|
if (rd->version == version) {
|
|
|
|
same = 1;
|
|
|
|
} else if (!rd->version || !version) {
|
|
|
|
same = 0;
|
|
|
|
} else if (!strcmp(rd->version, version)) {
|
|
|
|
same = 1;
|
|
|
|
} else {
|
|
|
|
same = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (same && !strcmp(rd->name, name)) {
|
|
|
|
/* They are exacty the same */
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
rd = rd->next;
|
|
|
|
}
|
|
|
|
if (rd) {
|
|
|
|
/* already there */
|
1996-11-19 02:02:36 +08:00
|
|
|
rpmMessage(RPMMESS_DEBUG, "Already Got: %s\n", name);
|
1996-06-08 02:32:10 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
rd = (struct ReqProv *)malloc(sizeof(*rd));
|
|
|
|
rd->flags = flags;
|
|
|
|
rd->name = strdup(name);
|
|
|
|
rd->version = version ? strdup(version) : NULL;
|
|
|
|
rd->next = p->reqprov;
|
|
|
|
p->reqprov = rd;
|
|
|
|
|
1996-11-19 02:02:36 +08:00
|
|
|
if (flags & RPMSENSE_PROVIDES) {
|
|
|
|
rpmMessage(RPMMESS_DEBUG, "Adding provide: %s\n", name);
|
1996-06-08 02:32:10 +08:00
|
|
|
p->numProv++;
|
1996-11-19 02:02:36 +08:00
|
|
|
} else if (flags & RPMSENSE_CONFLICTS) {
|
|
|
|
rpmMessage(RPMMESS_DEBUG, "Adding conflict: %s\n", name);
|
1996-06-28 04:16:20 +08:00
|
|
|
p->numConflict++;
|
1996-06-08 02:32:10 +08:00
|
|
|
} else {
|
1996-11-19 02:02:36 +08:00
|
|
|
rpmMessage(RPMMESS_DEBUG, "Adding require: %s\n", name);
|
1996-06-08 02:32:10 +08:00
|
|
|
p->numReq++;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*************************************************************/
|
|
|
|
/* */
|
|
|
|
/* Add require/provides for the files in the header */
|
|
|
|
/* (adds to the package structure) */
|
|
|
|
/* */
|
|
|
|
/*************************************************************/
|
|
|
|
|
1996-08-15 05:02:54 +08:00
|
|
|
static StringBuf getOutputFrom(char *dir, char *argv[],
|
1996-11-19 07:15:21 +08:00
|
|
|
char *writePtr, int writeBytesLeft,
|
|
|
|
int failNonZero)
|
1996-06-08 02:32:10 +08:00
|
|
|
{
|
1996-08-15 05:02:54 +08:00
|
|
|
int progPID;
|
|
|
|
int progDead;
|
|
|
|
int toProg[2];
|
|
|
|
int fromProg[2];
|
|
|
|
int status;
|
|
|
|
void *oldhandler;
|
|
|
|
int bytesWritten;
|
|
|
|
StringBuf readBuff;
|
|
|
|
int bytes;
|
|
|
|
unsigned char buf[8193];
|
|
|
|
|
|
|
|
oldhandler = signal(SIGPIPE, SIG_IGN);
|
|
|
|
|
|
|
|
pipe(toProg);
|
|
|
|
pipe(fromProg);
|
|
|
|
|
|
|
|
if (!(progPID = fork())) {
|
|
|
|
close(0);
|
|
|
|
close(1);
|
|
|
|
close(toProg[1]);
|
|
|
|
close(fromProg[0]);
|
|
|
|
|
|
|
|
dup2(toProg[0], 0); /* Make stdin the in pipe */
|
|
|
|
dup2(fromProg[1], 1); /* Make stdout the out pipe */
|
|
|
|
close(2); /* Toss stderr */
|
|
|
|
|
|
|
|
chdir(dir);
|
|
|
|
|
|
|
|
execvp(argv[0], argv);
|
1996-11-19 02:02:36 +08:00
|
|
|
rpmError(RPMERR_EXEC, "Couldn't exec %s", argv[0]);
|
1996-08-15 05:02:54 +08:00
|
|
|
exit(RPMERR_EXEC);
|
|
|
|
}
|
|
|
|
if (progPID < 0) {
|
1996-11-19 02:02:36 +08:00
|
|
|
rpmError(RPMERR_FORK, "Couldn't fork %s", argv[0]);
|
1996-08-15 05:02:54 +08:00
|
|
|
return NULL;
|
1996-06-08 02:32:10 +08:00
|
|
|
}
|
1996-08-15 05:02:54 +08:00
|
|
|
|
|
|
|
close(toProg[0]);
|
|
|
|
close(fromProg[1]);
|
|
|
|
|
|
|
|
/* Do not block reading or writing from/to prog. */
|
|
|
|
fcntl(fromProg[0], F_SETFL, O_NONBLOCK);
|
|
|
|
fcntl(toProg[1], F_SETFL, O_NONBLOCK);
|
1996-06-08 02:32:10 +08:00
|
|
|
|
1996-08-15 05:02:54 +08:00
|
|
|
readBuff = newStringBuf();
|
|
|
|
|
|
|
|
progDead = 0;
|
|
|
|
do {
|
|
|
|
if (waitpid(progPID, &status, WNOHANG)) {
|
|
|
|
progDead = 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Write some stuff to the process if possible */
|
|
|
|
if (writeBytesLeft) {
|
|
|
|
if ((bytesWritten =
|
|
|
|
write(toProg[1], writePtr,
|
|
|
|
(1024<writeBytesLeft) ? 1024 : writeBytesLeft)) < 0) {
|
|
|
|
if (errno != EAGAIN) {
|
|
|
|
perror("getOutputFrom()");
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
bytesWritten = 0;
|
1996-07-21 07:00:33 +08:00
|
|
|
}
|
1996-08-15 05:02:54 +08:00
|
|
|
writeBytesLeft -= bytesWritten;
|
|
|
|
writePtr += bytesWritten;
|
|
|
|
} else {
|
|
|
|
close(toProg[1]);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Read any data from prog */
|
|
|
|
bytes = read(fromProg[0], buf, sizeof(buf)-1);
|
|
|
|
while (bytes > 0) {
|
|
|
|
buf[bytes] = '\0';
|
|
|
|
appendStringBuf(readBuff, buf);
|
|
|
|
bytes = read(fromProg[0], buf, sizeof(buf)-1);
|
1996-07-11 04:19:07 +08:00
|
|
|
}
|
1996-08-15 05:02:54 +08:00
|
|
|
|
|
|
|
/* terminate when prog dies */
|
|
|
|
} while (!progDead);
|
|
|
|
|
|
|
|
close(toProg[1]);
|
|
|
|
close(fromProg[0]);
|
|
|
|
signal(SIGPIPE, oldhandler);
|
|
|
|
|
|
|
|
if (writeBytesLeft) {
|
1996-11-19 02:02:36 +08:00
|
|
|
rpmError(RPMERR_EXEC, "failed to write all data to %s", argv[0]);
|
1996-08-15 05:02:54 +08:00
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
waitpid(progPID, &status, 0);
|
1996-11-19 07:15:21 +08:00
|
|
|
if (failNonZero && (!WIFEXITED(status) || WEXITSTATUS(status))) {
|
1996-11-19 02:02:36 +08:00
|
|
|
rpmError(RPMERR_EXEC, "%s failed", argv[0]);
|
1996-08-15 05:02:54 +08:00
|
|
|
return NULL;
|
1996-06-08 02:32:10 +08:00
|
|
|
}
|
1996-08-15 05:02:54 +08:00
|
|
|
|
|
|
|
return readBuff;
|
1996-06-08 02:32:10 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
int generateAutoReqProv(Header header, struct PackageRec *p)
|
|
|
|
{
|
1996-06-28 02:25:09 +08:00
|
|
|
char **f, **fsave, *s, *tok;
|
1996-06-08 02:32:10 +08:00
|
|
|
int count;
|
1996-07-21 07:00:33 +08:00
|
|
|
int_16 *modes;
|
1996-06-08 02:32:10 +08:00
|
|
|
|
|
|
|
StringBuf writeBuff;
|
|
|
|
StringBuf readBuff;
|
|
|
|
char *writePtr;
|
1996-08-15 05:02:54 +08:00
|
|
|
int writeBytes;
|
|
|
|
char dir[1024];
|
|
|
|
char *argv[8];
|
1996-06-08 02:32:10 +08:00
|
|
|
|
1996-11-19 02:02:36 +08:00
|
|
|
rpmMessage(RPMMESS_VERBOSE, "Finding dependencies...\n");
|
1996-06-08 02:32:10 +08:00
|
|
|
|
1996-08-15 05:02:54 +08:00
|
|
|
/*** Get root directory ***/
|
1996-06-08 02:32:10 +08:00
|
|
|
|
1996-11-19 02:02:36 +08:00
|
|
|
if (rpmGetVar(RPMVAR_ROOT)) {
|
|
|
|
strcpy(dir, rpmGetVar(RPMVAR_ROOT));
|
1996-08-15 05:02:54 +08:00
|
|
|
} else {
|
|
|
|
strcpy(dir, "/");
|
1996-06-08 02:32:10 +08:00
|
|
|
}
|
|
|
|
|
1996-08-15 05:02:54 +08:00
|
|
|
/*** Generate File List ***/
|
|
|
|
|
1996-11-19 02:02:36 +08:00
|
|
|
if (!headerGetEntry(header, RPMTAG_FILENAMES, NULL, (void **) &f, &count)) {
|
1996-08-15 05:02:54 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
if (!count) {
|
|
|
|
return 0;
|
1996-06-08 02:32:10 +08:00
|
|
|
}
|
1996-08-15 05:02:54 +08:00
|
|
|
fsave = f;
|
1996-11-19 02:02:36 +08:00
|
|
|
headerGetEntry(header, RPMTAG_FILEMODES, NULL, (void **) &modes, NULL);
|
1996-06-08 02:32:10 +08:00
|
|
|
|
|
|
|
writeBuff = newStringBuf();
|
1996-08-15 05:02:54 +08:00
|
|
|
writeBytes = 0;
|
1996-06-08 02:32:10 +08:00
|
|
|
while (count--) {
|
|
|
|
s = *f++;
|
1996-11-20 04:30:29 +08:00
|
|
|
/* We skip the leading "/" (already normalized) */
|
|
|
|
writeBytes += strlen(s);
|
|
|
|
appendLineStringBuf(writeBuff, s + 1);
|
1996-07-21 07:00:33 +08:00
|
|
|
}
|
|
|
|
if (fsave) {
|
|
|
|
free(fsave);
|
1996-06-08 02:32:10 +08:00
|
|
|
}
|
|
|
|
writePtr = getStringBuf(writeBuff);
|
1996-08-15 05:02:54 +08:00
|
|
|
|
|
|
|
/*** Do Provides ***/
|
|
|
|
|
|
|
|
argv[0] = "find-provides";
|
|
|
|
argv[1] = NULL;
|
1996-11-19 07:15:21 +08:00
|
|
|
readBuff = getOutputFrom(dir, argv, writePtr, writeBytes, 1);
|
1996-08-15 05:02:54 +08:00
|
|
|
if (!readBuff) {
|
1996-11-19 02:02:36 +08:00
|
|
|
rpmError(RPMERR_EXEC, "Failed to find provides");
|
1996-08-15 05:02:54 +08:00
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
s = getStringBuf(readBuff);
|
|
|
|
f = fsave = splitString(s, strlen(s), '\n');
|
|
|
|
freeStringBuf(readBuff);
|
|
|
|
while (*f) {
|
|
|
|
if (**f) {
|
1996-11-19 02:02:36 +08:00
|
|
|
addReqProv(p, RPMSENSE_PROVIDES, *f, NULL);
|
1996-08-15 05:02:54 +08:00
|
|
|
}
|
|
|
|
f++;
|
|
|
|
}
|
|
|
|
free(fsave);
|
|
|
|
|
|
|
|
/*** Do Requires ***/
|
|
|
|
|
|
|
|
/* Separate args by null for xargs (forget why) */
|
1996-07-14 08:43:39 +08:00
|
|
|
s = writePtr;
|
|
|
|
while (*s) {
|
|
|
|
if (*s == '\n') {
|
|
|
|
*s = '\0';
|
|
|
|
}
|
|
|
|
s++;
|
|
|
|
}
|
1996-06-08 02:32:10 +08:00
|
|
|
|
1996-08-15 05:02:54 +08:00
|
|
|
argv[0] = "xargs";
|
|
|
|
argv[1] = "-0";
|
|
|
|
argv[2] = "ldd";
|
|
|
|
argv[3] = NULL;
|
1996-11-19 07:15:21 +08:00
|
|
|
readBuff = getOutputFrom(dir, argv, writePtr, writeBytes, 0);
|
1996-08-15 05:02:54 +08:00
|
|
|
if (!readBuff) {
|
1996-11-19 02:02:36 +08:00
|
|
|
rpmError(RPMERR_EXEC, "Failed to find requires");
|
1996-08-15 05:02:54 +08:00
|
|
|
exit(1);
|
1996-06-08 02:32:10 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
s = getStringBuf(readBuff);
|
1996-06-28 02:25:09 +08:00
|
|
|
f = fsave = splitString(s, strlen(s), '\n');
|
|
|
|
freeStringBuf(readBuff);
|
|
|
|
while (*f) {
|
|
|
|
s = *f;
|
|
|
|
if (isspace(*s) && strstr(s, "=>")) {
|
1996-06-08 02:32:10 +08:00
|
|
|
while (isspace(*s)) {
|
|
|
|
s++;
|
|
|
|
}
|
|
|
|
tok = s;
|
1996-07-11 04:19:07 +08:00
|
|
|
while (! isspace(*s)) {
|
|
|
|
s++;
|
1996-06-08 02:32:10 +08:00
|
|
|
}
|
1996-07-11 04:19:07 +08:00
|
|
|
*s = '\0';
|
1996-08-08 21:25:27 +08:00
|
|
|
if ((s = strrchr(tok, '/'))) {
|
|
|
|
tok = s + 1;
|
|
|
|
}
|
1996-11-19 02:02:36 +08:00
|
|
|
addReqProv(p, RPMSENSE_ANY, tok, NULL);
|
1996-06-08 02:32:10 +08:00
|
|
|
}
|
1996-06-28 02:25:09 +08:00
|
|
|
|
|
|
|
f++;
|
1996-06-08 02:32:10 +08:00
|
|
|
}
|
1996-06-28 02:25:09 +08:00
|
|
|
free(fsave);
|
1996-08-15 05:02:54 +08:00
|
|
|
|
|
|
|
/*** Clean Up ***/
|
|
|
|
|
|
|
|
freeStringBuf(writeBuff);
|
|
|
|
|
1996-06-08 02:32:10 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*************************************************************/
|
|
|
|
/* */
|
|
|
|
/* Generate and add header entry from package record */
|
|
|
|
/* */
|
|
|
|
/*************************************************************/
|
|
|
|
|
|
|
|
int processReqProv(Header h, struct PackageRec *p)
|
|
|
|
{
|
|
|
|
struct ReqProv *rd;
|
|
|
|
char **nameArray, **namePtr;
|
|
|
|
char **versionArray, **versionPtr;
|
|
|
|
int_32 *flagArray, *flagPtr;
|
|
|
|
|
|
|
|
|
|
|
|
if (p->numProv) {
|
|
|
|
rd = p->reqprov;
|
|
|
|
nameArray = namePtr = malloc(p->numProv * sizeof(*nameArray));
|
1996-11-19 02:02:36 +08:00
|
|
|
rpmMessage(RPMMESS_VERBOSE, "Provides (%d):", p->numProv);
|
1996-06-08 02:32:10 +08:00
|
|
|
while (rd) {
|
1996-11-19 02:02:36 +08:00
|
|
|
if (rd->flags & RPMSENSE_PROVIDES) {
|
|
|
|
rpmMessage(RPMMESS_VERBOSE, " %s", rd->name);
|
1996-06-08 02:32:10 +08:00
|
|
|
*namePtr++ = rd->name;
|
|
|
|
}
|
|
|
|
rd = rd->next;
|
|
|
|
}
|
1996-11-19 02:02:36 +08:00
|
|
|
rpmMessage(RPMMESS_VERBOSE, "\n");
|
1996-06-08 02:50:22 +08:00
|
|
|
|
1996-11-19 02:02:36 +08:00
|
|
|
headerAddEntry(h, RPMTAG_PROVIDES, RPM_STRING_ARRAY_TYPE, nameArray, p->numProv);
|
1996-06-08 02:32:10 +08:00
|
|
|
free(nameArray);
|
|
|
|
}
|
|
|
|
|
1996-06-28 04:16:20 +08:00
|
|
|
if (p->numConflict) {
|
|
|
|
rd = p->reqprov;
|
|
|
|
nameArray = namePtr = malloc(p->numConflict * sizeof(*nameArray));
|
|
|
|
versionArray = versionPtr =
|
|
|
|
malloc(p->numConflict * sizeof(*versionArray));
|
|
|
|
flagArray = flagPtr = malloc(p->numConflict * sizeof(*flagArray));
|
1996-11-19 02:02:36 +08:00
|
|
|
rpmMessage(RPMMESS_VERBOSE, "Conflicts (%d):", p->numConflict);
|
1996-06-28 04:16:20 +08:00
|
|
|
while (rd) {
|
1996-11-19 02:02:36 +08:00
|
|
|
if (rd->flags & RPMSENSE_CONFLICTS) {
|
|
|
|
rpmMessage(RPMMESS_VERBOSE, " %s", rd->name);
|
1996-06-28 04:16:20 +08:00
|
|
|
*namePtr++ = rd->name;
|
|
|
|
*versionPtr++ = rd->version ? rd->version : "";
|
1996-11-19 02:02:36 +08:00
|
|
|
*flagPtr++ = rd->flags & RPMSENSE_SENSEMASK;
|
1996-06-28 04:16:20 +08:00
|
|
|
}
|
|
|
|
rd = rd->next;
|
|
|
|
}
|
1996-11-19 02:02:36 +08:00
|
|
|
rpmMessage(RPMMESS_VERBOSE, "\n");
|
1996-06-28 04:16:20 +08:00
|
|
|
|
1996-11-19 02:02:36 +08:00
|
|
|
headerAddEntry(h, RPMTAG_CONFLICTNAME, RPM_STRING_ARRAY_TYPE,
|
1996-06-28 04:16:20 +08:00
|
|
|
nameArray, p->numConflict);
|
1996-11-19 02:02:36 +08:00
|
|
|
headerAddEntry(h, RPMTAG_CONFLICTVERSION, RPM_STRING_ARRAY_TYPE,
|
1996-06-28 04:16:20 +08:00
|
|
|
versionArray, p->numConflict);
|
1996-11-19 02:02:36 +08:00
|
|
|
headerAddEntry(h, RPMTAG_CONFLICTFLAGS, RPM_INT32_TYPE,
|
1996-06-28 04:16:20 +08:00
|
|
|
flagArray, p->numConflict);
|
|
|
|
|
|
|
|
free(nameArray);
|
|
|
|
free(versionArray);
|
|
|
|
free(flagArray);
|
|
|
|
}
|
|
|
|
|
1996-06-08 02:32:10 +08:00
|
|
|
if (p->numReq) {
|
|
|
|
rd = p->reqprov;
|
|
|
|
nameArray = namePtr = malloc(p->numReq * sizeof(*nameArray));
|
|
|
|
versionArray = versionPtr = malloc(p->numReq * sizeof(*versionArray));
|
|
|
|
flagArray = flagPtr = malloc(p->numReq * sizeof(*flagArray));
|
1996-11-19 02:02:36 +08:00
|
|
|
rpmMessage(RPMMESS_VERBOSE, "Requires (%d):", p->numReq);
|
1996-06-08 02:32:10 +08:00
|
|
|
while (rd) {
|
1996-11-19 02:02:36 +08:00
|
|
|
if (! ((rd->flags & RPMSENSE_PROVIDES) ||
|
|
|
|
(rd->flags & RPMSENSE_CONFLICTS))) {
|
|
|
|
rpmMessage(RPMMESS_VERBOSE, " %s", rd->name);
|
1996-06-08 02:32:10 +08:00
|
|
|
*namePtr++ = rd->name;
|
|
|
|
*versionPtr++ = rd->version ? rd->version : "";
|
1996-11-19 02:02:36 +08:00
|
|
|
*flagPtr++ = rd->flags & RPMSENSE_SENSEMASK;
|
1996-06-08 02:32:10 +08:00
|
|
|
}
|
|
|
|
rd = rd->next;
|
|
|
|
}
|
1996-11-19 02:02:36 +08:00
|
|
|
rpmMessage(RPMMESS_VERBOSE, "\n");
|
1996-06-08 02:32:10 +08:00
|
|
|
|
1996-11-19 02:02:36 +08:00
|
|
|
headerAddEntry(h, RPMTAG_REQUIRENAME, RPM_STRING_ARRAY_TYPE,
|
1996-06-08 02:32:10 +08:00
|
|
|
nameArray, p->numReq);
|
1996-11-19 02:02:36 +08:00
|
|
|
headerAddEntry(h, RPMTAG_REQUIREVERSION, RPM_STRING_ARRAY_TYPE,
|
1996-06-08 02:32:10 +08:00
|
|
|
versionArray, p->numReq);
|
1996-11-19 02:02:36 +08:00
|
|
|
headerAddEntry(h, RPMTAG_REQUIREFLAGS, RPM_INT32_TYPE,
|
1996-06-08 02:32:10 +08:00
|
|
|
flagArray, p->numReq);
|
|
|
|
|
|
|
|
free(nameArray);
|
|
|
|
free(versionArray);
|
|
|
|
free(flagArray);
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|