1996-01-09 03:31:44 +08:00
|
|
|
#include <alloca.h>
|
|
|
|
#include <errno.h>
|
1996-01-15 03:32:17 +08:00
|
|
|
#include <fcntl.h>
|
|
|
|
#include <grp.h>
|
|
|
|
#include <pwd.h>
|
1996-01-09 03:31:44 +08:00
|
|
|
#include <stdlib.h>
|
|
|
|
#include <string.h>
|
|
|
|
#include <sys/signal.h>
|
1996-01-15 03:32:17 +08:00
|
|
|
#include <sys/stat.h> /* needed for mkdir(2) prototype! */
|
1996-01-09 03:31:44 +08:00
|
|
|
#include <sys/types.h>
|
|
|
|
#include <sys/wait.h>
|
1996-01-30 03:37:59 +08:00
|
|
|
#include <time.h>
|
1996-01-09 03:31:44 +08:00
|
|
|
#include <unistd.h>
|
|
|
|
#include <zlib.h>
|
|
|
|
|
|
|
|
#include "header.h"
|
|
|
|
#include "install.h"
|
1996-01-30 03:37:59 +08:00
|
|
|
#include "md5.h"
|
1996-02-15 06:20:08 +08:00
|
|
|
#include "misc.h"
|
1996-01-09 03:31:44 +08:00
|
|
|
#include "package.h"
|
|
|
|
#include "rpmerr.h"
|
|
|
|
#include "rpmlib.h"
|
|
|
|
|
1996-03-30 04:52:03 +08:00
|
|
|
enum instActions { CREATE, BACKUP, KEEP, SAVE, SKIP };
|
1996-02-29 11:38:33 +08:00
|
|
|
enum fileTypes { DIR, BDEV, CDEV, SOCK, PIPE, REG, LINK } ;
|
1996-01-23 05:13:12 +08:00
|
|
|
|
|
|
|
struct fileToInstall {
|
|
|
|
char * fileName;
|
|
|
|
int size;
|
|
|
|
} ;
|
|
|
|
|
1996-02-16 05:08:48 +08:00
|
|
|
struct replacedFile {
|
|
|
|
int recOffset, fileNumber;
|
|
|
|
} ;
|
|
|
|
|
1996-02-29 11:38:33 +08:00
|
|
|
enum fileTypes whatis(short mode);
|
|
|
|
int filecmp(short mode1, char * md51, char * link1,
|
|
|
|
short mode2, char * md52, char * link2);
|
|
|
|
enum instActions decideFileFate(char * filespec, short dbMode, char * dbMd5,
|
|
|
|
char * dbLink, short newMode, char * newMd5,
|
|
|
|
char * newLink);
|
1996-01-23 05:13:12 +08:00
|
|
|
static int installArchive(char * prefix, int fd, struct fileToInstall * files,
|
1996-02-15 01:54:37 +08:00
|
|
|
int fileCount, notifyFunction notify,
|
|
|
|
char ** installArchive);
|
1996-01-15 03:32:17 +08:00
|
|
|
static int packageAlreadyInstalled(rpmdb db, char * name, char * version,
|
1996-02-15 05:26:21 +08:00
|
|
|
char * release, int * recOffset, int flags);
|
1996-01-15 03:32:17 +08:00
|
|
|
static int setFileOwnerships(char * prefix, char ** fileList,
|
|
|
|
char ** fileOwners, char ** fileGroups,
|
1996-03-30 04:52:03 +08:00
|
|
|
enum instActions * instActions, int fileCount);
|
1996-01-15 03:32:17 +08:00
|
|
|
static int setFileOwner(char * prefix, char * file, char * owner,
|
|
|
|
char * group);
|
|
|
|
static int createDirectories(char * prefix, char ** fileList, int fileCount);
|
|
|
|
static int mkdirIfNone(char * directory, mode_t perms);
|
1996-01-23 05:13:12 +08:00
|
|
|
static int instHandleSharedFiles(rpmdb db, int ignoreOffset, char ** fileList,
|
1996-02-29 11:38:33 +08:00
|
|
|
char ** fileMd5List, int_16 * fileModeList,
|
|
|
|
char ** fileLinkList,
|
|
|
|
int fileCount, enum instActions * instActions,
|
|
|
|
char ** prefixedFileList, int * notErrors,
|
1996-02-16 05:08:48 +08:00
|
|
|
struct replacedFile ** repListPtr, int flags);
|
1996-01-23 05:13:12 +08:00
|
|
|
static int fileCompare(const void * one, const void * two);
|
1996-02-15 04:09:14 +08:00
|
|
|
static int installSources(char * prefix, int fd, char ** specFilePtr);
|
1996-02-16 05:08:48 +08:00
|
|
|
static int markReplacedFiles(rpmdb db, struct replacedFile * replList);
|
1996-02-26 06:11:00 +08:00
|
|
|
static int ensureOlder(rpmdb db, char * name, char * newVersion,
|
|
|
|
char * newRelease, int dbOffset);
|
1996-02-15 04:09:14 +08:00
|
|
|
|
|
|
|
/* 0 success */
|
|
|
|
/* 1 bad magic */
|
|
|
|
/* 2 error */
|
|
|
|
int rpmInstallSourcePackage(char * prefix, int fd, char ** specFile) {
|
|
|
|
int rc, isSource;
|
|
|
|
Header h;
|
|
|
|
|
|
|
|
rc = pkgReadHeader(fd, &h, &isSource);
|
|
|
|
if (rc) return rc;
|
|
|
|
|
|
|
|
if (!isSource) {
|
|
|
|
error(RPMERR_NOTSRPM, "source package expected, binary found");
|
|
|
|
return 2;
|
|
|
|
}
|
|
|
|
|
1996-03-30 03:40:08 +08:00
|
|
|
if (h) freeHeader(h);
|
|
|
|
|
1996-02-15 04:09:14 +08:00
|
|
|
return installSources(prefix, fd, specFile);
|
|
|
|
}
|
1996-01-09 03:31:44 +08:00
|
|
|
|
|
|
|
/* 0 success */
|
|
|
|
/* 1 bad magic */
|
|
|
|
/* 2 error */
|
1996-01-23 05:13:12 +08:00
|
|
|
int rpmInstallPackage(char * prefix, rpmdb db, int fd, int flags,
|
1996-02-25 07:46:09 +08:00
|
|
|
notifyFunction notify, char * labelFormat) {
|
1996-01-09 03:31:44 +08:00
|
|
|
int rc, isSource;
|
1996-01-15 03:32:17 +08:00
|
|
|
char * name, * version, * release;
|
1996-02-27 06:53:35 +08:00
|
|
|
Header h;
|
1996-01-15 03:32:17 +08:00
|
|
|
int fileCount, type;
|
|
|
|
char ** fileList;
|
1996-02-29 11:38:33 +08:00
|
|
|
char ** fileOwners, ** fileGroups, ** fileMd5s, ** fileLinkList;
|
1996-01-23 05:13:12 +08:00
|
|
|
uint_32 * fileFlagsList;
|
|
|
|
uint_32 * fileSizesList;
|
1996-02-29 11:38:33 +08:00
|
|
|
int_16 * fileModesList;
|
1996-01-30 03:37:59 +08:00
|
|
|
int_32 installTime;
|
1996-01-23 05:13:12 +08:00
|
|
|
char * fileStatesList;
|
|
|
|
struct fileToInstall * files;
|
1996-01-30 03:37:59 +08:00
|
|
|
enum instActions * instActions = NULL;
|
1996-01-23 05:13:12 +08:00
|
|
|
int i;
|
1996-01-30 03:37:59 +08:00
|
|
|
int archiveFileCount = 0;
|
|
|
|
int installFile = 0;
|
1996-03-30 04:52:03 +08:00
|
|
|
int normalState = 0;
|
1996-02-15 05:26:21 +08:00
|
|
|
int otherOffset = 0;
|
1996-01-30 03:37:59 +08:00
|
|
|
char * ext = NULL, * newpath;
|
1996-01-30 07:27:12 +08:00
|
|
|
int prefixLength = strlen(prefix);
|
|
|
|
char ** prefixedFileList = NULL;
|
1996-02-16 05:08:48 +08:00
|
|
|
struct replacedFile * replacedList = NULL;
|
1996-02-20 07:34:25 +08:00
|
|
|
char * sptr, * dptr;
|
|
|
|
int length;
|
1996-02-21 05:54:02 +08:00
|
|
|
dbIndexSet matches;
|
1996-02-22 02:09:31 +08:00
|
|
|
int * oldVersions;
|
1996-02-21 05:54:02 +08:00
|
|
|
int * intptr;
|
1996-02-27 06:53:35 +08:00
|
|
|
int_8 thisArch, * pkgArch;
|
1996-01-09 03:31:44 +08:00
|
|
|
|
1996-02-22 02:09:31 +08:00
|
|
|
oldVersions = alloca(sizeof(int));
|
|
|
|
*oldVersions = 0;
|
|
|
|
|
1996-01-09 03:31:44 +08:00
|
|
|
rc = pkgReadHeader(fd, &h, &isSource);
|
|
|
|
if (rc) return rc;
|
|
|
|
|
1996-02-15 01:54:37 +08:00
|
|
|
if (isSource) {
|
|
|
|
/* We deal with source packages pretty badly. They should end
|
|
|
|
up in the database, and we should be smarter about installing
|
|
|
|
them. Old source packages are broken though, and this hack
|
1996-03-30 03:40:08 +08:00
|
|
|
is the easiest way. It's too bad the notify stuff doesn't work
|
1996-02-15 01:54:37 +08:00
|
|
|
though */
|
|
|
|
|
|
|
|
if (flags & INSTALL_TEST) {
|
|
|
|
message(MESS_DEBUG, "stopping install as we're running --test\n");
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
1996-03-30 03:40:08 +08:00
|
|
|
if (h) freeHeader(h);
|
|
|
|
|
1996-02-15 04:09:14 +08:00
|
|
|
return installSources(prefix, fd, NULL);
|
1996-02-15 01:54:37 +08:00
|
|
|
}
|
|
|
|
|
1996-01-15 03:32:17 +08:00
|
|
|
getEntry(h, RPMTAG_NAME, &type, (void **) &name, &fileCount);
|
|
|
|
getEntry(h, RPMTAG_VERSION, &type, (void **) &version, &fileCount);
|
|
|
|
getEntry(h, RPMTAG_RELEASE, &type, (void **) &release, &fileCount);
|
|
|
|
|
1996-02-27 06:45:24 +08:00
|
|
|
/* make sure we're trying to install this on the proper architecture */
|
|
|
|
thisArch = getArchNum();
|
1996-02-27 06:53:35 +08:00
|
|
|
getEntry(h, RPMTAG_ARCH, &type, (void **) &pkgArch, &fileCount);
|
|
|
|
if (thisArch != *pkgArch) {
|
1996-02-27 06:45:24 +08:00
|
|
|
error(RPMERR_BADARCH, "package %s-%s-%s is for a different "
|
|
|
|
"architecture", name, version, release);
|
|
|
|
freeHeader(h);
|
1996-04-16 05:09:40 +08:00
|
|
|
return 2;
|
1996-02-27 06:45:24 +08:00
|
|
|
}
|
|
|
|
|
1996-02-25 07:46:09 +08:00
|
|
|
if (labelFormat) {
|
|
|
|
printf(labelFormat, name, version, release);
|
1996-02-20 08:24:43 +08:00
|
|
|
fflush(stdout);
|
1996-02-20 08:17:51 +08:00
|
|
|
}
|
|
|
|
|
1996-01-15 03:32:17 +08:00
|
|
|
message(MESS_DEBUG, "package: %s-%s-%s files test = %d\n",
|
1996-01-23 05:13:12 +08:00
|
|
|
name, version, release, flags & INSTALL_TEST);
|
1996-01-15 03:32:17 +08:00
|
|
|
|
1996-02-15 05:26:21 +08:00
|
|
|
if (packageAlreadyInstalled(db, name, version, release, &otherOffset,
|
|
|
|
flags)) {
|
1996-01-15 03:32:17 +08:00
|
|
|
freeHeader(h);
|
|
|
|
return 2;
|
|
|
|
}
|
1996-01-23 05:13:12 +08:00
|
|
|
|
1996-02-21 05:54:02 +08:00
|
|
|
if (flags & INSTALL_UPGRADE) {
|
|
|
|
/*
|
|
|
|
We need to get a list of all old version of this package. We let
|
|
|
|
this install procede normally then, but:
|
|
|
|
|
|
|
|
1) we don't report conflicts between the new package and
|
|
|
|
the old versions installed
|
|
|
|
2) when we're done, we uninstall the old versions
|
|
|
|
|
|
|
|
Note if the version being installed is already installed, we don't
|
|
|
|
put that in the list -- that situation is handled normally.
|
|
|
|
*/
|
|
|
|
|
|
|
|
rc = rpmdbFindPackage(db, name, &matches);
|
1996-04-16 05:09:40 +08:00
|
|
|
if (rc == -1) return 2;
|
1996-02-21 05:54:02 +08:00
|
|
|
|
|
|
|
if (!rc) {
|
|
|
|
intptr = oldVersions = alloca((matches.count + 1) * sizeof(int));
|
|
|
|
for (i = 0; i < matches.count; i++) {
|
1996-02-26 06:11:00 +08:00
|
|
|
if (matches.recs[i].recOffset != otherOffset) {
|
|
|
|
if (!(flags & INSTALL_UPGRADETOOLD))
|
|
|
|
if (ensureOlder(db, name, version, release,
|
|
|
|
matches.recs[i].recOffset))
|
1996-04-16 05:09:40 +08:00
|
|
|
return 2;
|
1996-02-21 05:54:02 +08:00
|
|
|
*intptr++ = matches.recs[i].recOffset;
|
1996-02-26 06:11:00 +08:00
|
|
|
}
|
1996-02-21 05:54:02 +08:00
|
|
|
}
|
|
|
|
*intptr++ = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
1996-01-23 05:13:12 +08:00
|
|
|
fileList = NULL;
|
|
|
|
if (getEntry(h, RPMTAG_FILENAMES, &type, (void **) &fileList,
|
|
|
|
&fileCount)) {
|
|
|
|
|
|
|
|
instActions = alloca(sizeof(enum instActions) * fileCount);
|
1996-01-30 07:27:12 +08:00
|
|
|
prefixedFileList = alloca(sizeof(char *) * fileCount);
|
1996-01-23 05:13:12 +08:00
|
|
|
|
|
|
|
getEntry(h, RPMTAG_FILEMD5S, &type, (void **) &fileMd5s, &fileCount);
|
|
|
|
getEntry(h, RPMTAG_FILEFLAGS, &type, (void **) &fileFlagsList,
|
|
|
|
&fileCount);
|
1996-02-29 11:38:33 +08:00
|
|
|
getEntry(h, RPMTAG_FILEMODES, &type, (void **) &fileModesList,
|
|
|
|
&fileCount);
|
|
|
|
getEntry(h, RPMTAG_FILELINKTOS, &type, (void **) &fileLinkList,
|
|
|
|
&fileCount);
|
1996-01-23 05:13:12 +08:00
|
|
|
|
1996-01-30 03:37:59 +08:00
|
|
|
/* check for any config files that already exist. If they do, plan
|
|
|
|
on making a backup copy. If that's not the right thing to do
|
|
|
|
instHandleSharedFiles() below will take care of the problem */
|
|
|
|
for (i = 0; i < fileCount; i++) {
|
1996-01-30 07:27:12 +08:00
|
|
|
if (prefixLength > 1) {
|
|
|
|
prefixedFileList[i] = alloca(strlen(fileList[i]) +
|
|
|
|
prefixLength + 3);
|
|
|
|
strcpy(prefixedFileList[i], prefix);
|
|
|
|
strcat(prefixedFileList[i], "/");
|
|
|
|
strcat(prefixedFileList[i], fileList[i]);
|
|
|
|
} else
|
|
|
|
prefixedFileList[i] = fileList[i];
|
|
|
|
|
|
|
|
instActions[i] = CREATE;
|
1996-03-01 11:28:33 +08:00
|
|
|
if ((fileFlagsList[i] & RPMFILE_CONFIG) &&
|
|
|
|
!S_ISDIR(fileModesList[i])) {
|
1996-01-30 07:27:12 +08:00
|
|
|
if (exists(prefixedFileList[i])) {
|
1996-02-29 11:38:33 +08:00
|
|
|
message(MESS_DEBUG, "%s exists - backing up\n",
|
1996-01-30 07:27:12 +08:00
|
|
|
prefixedFileList[i]);
|
1996-01-30 03:37:59 +08:00
|
|
|
instActions[i] = BACKUP;
|
|
|
|
}
|
|
|
|
}
|
1996-03-30 04:52:03 +08:00
|
|
|
|
|
|
|
if ((fileFlagsList[i] & RPMFILE_DOC) && (flags & INSTALL_NODOCS))
|
|
|
|
instActions[i] = SKIP;
|
1996-01-30 03:37:59 +08:00
|
|
|
}
|
|
|
|
|
1996-02-29 11:38:33 +08:00
|
|
|
rc = instHandleSharedFiles(db, 0, fileList, fileMd5s, fileModesList,
|
|
|
|
fileLinkList, fileCount, instActions,
|
|
|
|
prefixedFileList, oldVersions,
|
|
|
|
&replacedList, flags);
|
1996-01-23 05:13:12 +08:00
|
|
|
|
|
|
|
free(fileMd5s);
|
1996-02-29 11:38:33 +08:00
|
|
|
free(fileLinkList);
|
1996-01-23 05:13:12 +08:00
|
|
|
if (rc) {
|
1996-02-16 05:08:48 +08:00
|
|
|
if (replacedList) free(replacedList);
|
1996-01-23 05:13:12 +08:00
|
|
|
free(fileList);
|
1996-04-16 05:09:40 +08:00
|
|
|
return 2;
|
1996-01-23 05:13:12 +08:00
|
|
|
}
|
|
|
|
}
|
1996-01-15 03:32:17 +08:00
|
|
|
|
1996-01-23 05:13:12 +08:00
|
|
|
if (flags & INSTALL_TEST) {
|
1996-01-15 03:32:17 +08:00
|
|
|
message(MESS_DEBUG, "stopping install as we're running --test\n");
|
1996-01-23 05:13:12 +08:00
|
|
|
free(fileList);
|
1996-02-16 05:08:48 +08:00
|
|
|
if (replacedList) free(replacedList);
|
1996-01-15 03:32:17 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
1996-01-09 03:31:44 +08:00
|
|
|
message(MESS_DEBUG, "running preinstall script (if any)\n");
|
1996-04-16 06:22:50 +08:00
|
|
|
if (runScript(prefix, h, RPMTAG_PREIN, flags & INSTALL_NOSCRIPTS)) {
|
1996-01-23 05:13:12 +08:00
|
|
|
free(fileList);
|
1996-02-16 05:08:48 +08:00
|
|
|
if (replacedList) free(replacedList);
|
1996-01-09 03:31:44 +08:00
|
|
|
return 2;
|
|
|
|
}
|
|
|
|
|
1996-01-23 05:13:12 +08:00
|
|
|
if (fileList) {
|
1996-01-15 03:32:17 +08:00
|
|
|
|
|
|
|
if (createDirectories(prefix, fileList, fileCount)) {
|
|
|
|
freeHeader(h);
|
|
|
|
free(fileList);
|
1996-02-16 05:08:48 +08:00
|
|
|
if (replacedList) free(replacedList);
|
1996-01-15 03:32:17 +08:00
|
|
|
return 2;
|
|
|
|
}
|
|
|
|
|
1996-01-23 05:13:12 +08:00
|
|
|
getEntry(h, RPMTAG_FILESIZES, &type, (void **) &fileSizesList,
|
|
|
|
&fileCount);
|
|
|
|
|
|
|
|
files = alloca(sizeof(struct fileToInstall) * fileCount);
|
1996-03-30 04:52:03 +08:00
|
|
|
fileStatesList = malloc(sizeof(char) * fileCount);
|
1996-01-23 05:13:12 +08:00
|
|
|
for (i = 0; i < fileCount; i++) {
|
1996-01-30 03:37:59 +08:00
|
|
|
switch (instActions[i]) {
|
|
|
|
case BACKUP:
|
|
|
|
ext = ".rpmorig";
|
|
|
|
installFile = 1;
|
1996-03-30 04:52:03 +08:00
|
|
|
normalState = 1;
|
1996-01-30 03:37:59 +08:00
|
|
|
break;
|
|
|
|
|
|
|
|
case SAVE:
|
|
|
|
ext = ".rpmsave";
|
|
|
|
installFile = 1;
|
1996-03-30 04:52:03 +08:00
|
|
|
normalState = 1;
|
1996-01-30 03:37:59 +08:00
|
|
|
break;
|
|
|
|
|
|
|
|
case CREATE:
|
|
|
|
installFile = 1;
|
1996-03-30 04:52:03 +08:00
|
|
|
normalState = 1;
|
1996-01-30 03:37:59 +08:00
|
|
|
ext = NULL;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case KEEP:
|
|
|
|
installFile = 0;
|
1996-03-30 04:52:03 +08:00
|
|
|
normalState = 1;
|
|
|
|
ext = NULL;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case SKIP:
|
|
|
|
installFile = 0;
|
|
|
|
normalState = 0;
|
1996-01-30 03:37:59 +08:00
|
|
|
ext = NULL;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (ext) {
|
1996-01-30 07:27:12 +08:00
|
|
|
newpath = malloc(strlen(prefixedFileList[i]) + 20);
|
|
|
|
strcpy(newpath, prefixedFileList[i]);
|
1996-01-30 03:37:59 +08:00
|
|
|
strcat(newpath, ext);
|
1996-01-30 07:27:12 +08:00
|
|
|
message(MESS_WARNING, "%s saved as %s\n", prefixedFileList[i],
|
|
|
|
newpath);
|
1996-01-30 03:37:59 +08:00
|
|
|
/* XXX this message is a bad idea - it'll make glint more
|
|
|
|
difficult */
|
|
|
|
|
1996-01-30 07:27:12 +08:00
|
|
|
if (rename(prefixedFileList[i], newpath)) {
|
|
|
|
error(RPMERR_RENAME, "rename of %s to %s failed: %s\n",
|
|
|
|
prefixedFileList[i], newpath, strerror(errno));
|
1996-02-16 05:08:48 +08:00
|
|
|
if (replacedList) free(replacedList);
|
1996-04-16 05:09:40 +08:00
|
|
|
return 2;
|
1996-01-30 03:37:59 +08:00
|
|
|
}
|
1996-01-30 07:27:12 +08:00
|
|
|
|
|
|
|
free(newpath);
|
1996-01-30 03:37:59 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
if (installFile) {
|
1996-02-20 07:34:25 +08:00
|
|
|
/* 1) we skip over the leading /
|
|
|
|
2) we have to escape globbing characters :-( */
|
|
|
|
|
|
|
|
length = strlen(fileList[i]);
|
|
|
|
files[archiveFileCount].fileName = alloca((length * 2) + 1);
|
|
|
|
dptr = files[archiveFileCount].fileName;
|
|
|
|
for (sptr = fileList[i] + 1; *sptr; sptr++) {
|
|
|
|
switch (*sptr) {
|
|
|
|
case '*': case '[': case ']': case '?': case '\\':
|
|
|
|
*dptr++ = '\\';
|
|
|
|
/*fallthrough*/
|
|
|
|
default:
|
|
|
|
*dptr++ = *sptr;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
*dptr++ = *sptr;
|
1996-01-30 03:37:59 +08:00
|
|
|
|
|
|
|
files[archiveFileCount].size = fileSizesList[i];
|
1996-02-20 07:34:25 +08:00
|
|
|
|
1996-01-30 03:37:59 +08:00
|
|
|
archiveFileCount++;
|
|
|
|
}
|
1996-03-30 04:52:03 +08:00
|
|
|
|
|
|
|
if (normalState)
|
|
|
|
fileStatesList[i] = RPMFILE_STATE_NORMAL;
|
|
|
|
else
|
|
|
|
fileStatesList[i] = RPMFILE_STATE_NOTINSTALLED;
|
1996-01-23 05:13:12 +08:00
|
|
|
}
|
|
|
|
|
1996-01-15 03:32:17 +08:00
|
|
|
/* the file pointer for fd is pointing at the cpio archive */
|
1996-02-15 01:54:37 +08:00
|
|
|
if (installArchive(prefix, fd, files, archiveFileCount, notify, NULL)) {
|
1996-01-15 03:32:17 +08:00
|
|
|
freeHeader(h);
|
|
|
|
free(fileList);
|
1996-02-16 05:08:48 +08:00
|
|
|
if (replacedList) free(replacedList);
|
1996-01-15 03:32:17 +08:00
|
|
|
return 2;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (getEntry(h, RPMTAG_FILEUSERNAME, &type, (void **) &fileOwners,
|
|
|
|
&fileCount)) {
|
|
|
|
if (getEntry(h, RPMTAG_FILEGROUPNAME, &type, (void **) &fileGroups,
|
|
|
|
&fileCount)) {
|
|
|
|
if (setFileOwnerships(prefix, fileList, fileOwners, fileGroups,
|
1996-03-30 04:52:03 +08:00
|
|
|
instActions, fileCount)) {
|
1996-01-15 03:32:17 +08:00
|
|
|
free(fileOwners);
|
|
|
|
free(fileGroups);
|
|
|
|
free(fileList);
|
1996-03-30 04:52:03 +08:00
|
|
|
free(fileStatesList);
|
1996-02-16 05:08:48 +08:00
|
|
|
if (replacedList) free(replacedList);
|
|
|
|
|
1996-01-15 03:32:17 +08:00
|
|
|
return 2;
|
|
|
|
}
|
|
|
|
free(fileGroups);
|
|
|
|
}
|
|
|
|
free(fileOwners);
|
|
|
|
}
|
|
|
|
free(fileList);
|
|
|
|
|
1996-01-30 03:37:59 +08:00
|
|
|
addEntry(h, RPMTAG_FILESTATES, CHAR_TYPE, fileStatesList, fileCount);
|
1996-03-30 04:52:03 +08:00
|
|
|
free(fileStatesList);
|
1996-01-09 03:31:44 +08:00
|
|
|
|
1996-01-30 03:37:59 +08:00
|
|
|
installTime = time(NULL);
|
|
|
|
addEntry(h, RPMTAG_INSTALLTIME, INT32_TYPE, &installTime, 1);
|
1996-01-09 03:31:44 +08:00
|
|
|
}
|
|
|
|
|
1996-02-16 05:08:48 +08:00
|
|
|
if (replacedList) {
|
|
|
|
rc = markReplacedFiles(db, replacedList);
|
|
|
|
free(replacedList);
|
|
|
|
|
|
|
|
if (rc) return rc;
|
|
|
|
}
|
|
|
|
|
1996-02-15 05:26:21 +08:00
|
|
|
/* if this package has already been installed, remove it from the database
|
|
|
|
before adding the new one */
|
|
|
|
if (otherOffset) {
|
|
|
|
rpmdbRemove(db, otherOffset, 1);
|
|
|
|
}
|
|
|
|
|
1996-01-09 03:31:44 +08:00
|
|
|
if (rpmdbAdd(db, h)) {
|
|
|
|
freeHeader(h);
|
|
|
|
return 2;
|
|
|
|
}
|
|
|
|
|
1996-01-30 03:37:59 +08:00
|
|
|
message(MESS_DEBUG, "running postinstall script (if any)\n");
|
|
|
|
|
1996-04-16 06:22:50 +08:00
|
|
|
if (runScript(prefix, h, RPMTAG_POSTIN, flags & INSTALL_NOSCRIPTS)) {
|
1996-04-16 05:09:40 +08:00
|
|
|
return 2;
|
1996-01-30 03:37:59 +08:00
|
|
|
}
|
|
|
|
|
1996-02-21 05:54:02 +08:00
|
|
|
if (flags & INSTALL_UPGRADE) {
|
|
|
|
message(MESS_DEBUG, "removing old versions of package\n");
|
|
|
|
intptr = oldVersions;
|
|
|
|
while (*intptr) {
|
|
|
|
rpmRemovePackage(prefix, db, *intptr, 0);
|
|
|
|
intptr++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
1996-01-09 03:31:44 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
#define BLOCKSIZE 1024
|
|
|
|
|
1996-01-23 05:13:12 +08:00
|
|
|
static int installArchive(char * prefix, int fd, struct fileToInstall * files,
|
1996-02-15 01:54:37 +08:00
|
|
|
int fileCount, notifyFunction notify,
|
|
|
|
char ** specFile) {
|
1996-01-09 03:31:44 +08:00
|
|
|
gzFile stream;
|
|
|
|
char buf[BLOCKSIZE];
|
|
|
|
pid_t child;
|
|
|
|
int p[2];
|
1996-01-23 05:13:12 +08:00
|
|
|
int statusPipe[2];
|
1996-01-09 03:31:44 +08:00
|
|
|
int bytesRead;
|
1996-01-23 05:13:12 +08:00
|
|
|
int bytes;
|
1996-01-09 03:31:44 +08:00
|
|
|
int status;
|
|
|
|
int cpioFailed = 0;
|
|
|
|
void * oldhandler;
|
1996-01-23 05:13:12 +08:00
|
|
|
char * cpioBinary;
|
|
|
|
int needSecondPipe;
|
|
|
|
char line[1024];
|
1996-01-30 03:37:59 +08:00
|
|
|
int j;
|
|
|
|
int i = 0;
|
1996-01-23 05:13:12 +08:00
|
|
|
unsigned long totalSize = 0;
|
|
|
|
unsigned long sizeInstalled = 0;
|
|
|
|
struct fileToInstall fileInstalled;
|
|
|
|
struct fileToInstall * file;
|
|
|
|
char * chptr;
|
1996-01-30 03:37:59 +08:00
|
|
|
char ** args;
|
1996-02-15 01:54:37 +08:00
|
|
|
int len;
|
1996-02-15 05:56:29 +08:00
|
|
|
int childDead = 0;
|
1996-01-09 03:31:44 +08:00
|
|
|
|
|
|
|
/* fd should be a gzipped cpio archive */
|
1996-01-23 05:13:12 +08:00
|
|
|
|
1996-02-15 01:54:37 +08:00
|
|
|
needSecondPipe = (notify != NULL) || specFile;
|
|
|
|
|
|
|
|
if (specFile) *specFile = NULL;
|
1996-01-09 03:31:44 +08:00
|
|
|
|
1996-01-30 03:37:59 +08:00
|
|
|
if (access("/bin/cpio", X_OK)) {
|
|
|
|
if (access("/usr/bin/cpio", X_OK)) {
|
|
|
|
error(RPMERR_CPIO, "cpio cannot be found in /bin or /usr/sbin");
|
|
|
|
return 1;
|
|
|
|
} else
|
|
|
|
cpioBinary = "/usr/bin/cpio";
|
|
|
|
} else
|
|
|
|
cpioBinary = "/bin/cpio";
|
|
|
|
|
|
|
|
args = alloca(sizeof(char *) * (fileCount + 10));
|
|
|
|
|
|
|
|
args[i++] = cpioBinary;
|
|
|
|
args[i++] = "--extract";
|
|
|
|
args[i++] = "--unconditional";
|
|
|
|
args[i++] = "--preserve-modification-time";
|
|
|
|
args[i++] = "--make-directories";
|
|
|
|
args[i++] = "--quiet";
|
|
|
|
|
|
|
|
if (needSecondPipe)
|
|
|
|
args[i++] = "--verbose";
|
|
|
|
|
1996-02-15 01:54:37 +08:00
|
|
|
/* note - if fileCount == 0, all files get installed */
|
|
|
|
|
1996-01-30 03:37:59 +08:00
|
|
|
for (j = 0; j < fileCount; j++)
|
|
|
|
args[i++] = files[j].fileName;
|
|
|
|
|
|
|
|
args[i++] = NULL;
|
|
|
|
|
1996-01-09 03:31:44 +08:00
|
|
|
stream = gzdopen(fd, "r");
|
|
|
|
pipe(p);
|
|
|
|
|
1996-01-23 05:13:12 +08:00
|
|
|
if (needSecondPipe) {
|
|
|
|
pipe(statusPipe);
|
|
|
|
for (i = 0; i < fileCount; i++)
|
|
|
|
totalSize += files[i].size;
|
|
|
|
qsort(files, fileCount, sizeof(struct fileToInstall), fileCompare);
|
1996-01-09 03:31:44 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
oldhandler = signal(SIGPIPE, SIG_IGN);
|
|
|
|
|
|
|
|
child = fork();
|
|
|
|
if (!child) {
|
1996-01-23 05:13:12 +08:00
|
|
|
chdir(prefix);
|
|
|
|
|
1996-01-09 03:31:44 +08:00
|
|
|
close(p[1]); /* we don't need to write to it */
|
|
|
|
close(0); /* stdin will come from the pipe instead */
|
|
|
|
dup2(p[0], 0);
|
|
|
|
close(p[0]);
|
|
|
|
|
1996-01-23 05:13:12 +08:00
|
|
|
if (needSecondPipe) {
|
|
|
|
close(statusPipe[0]); /* we don't need to read from it*/
|
|
|
|
close(2); /* stderr will go to a pipe instead */
|
|
|
|
dup2(statusPipe[1], 2);
|
|
|
|
close(statusPipe[1]);
|
|
|
|
}
|
|
|
|
|
1996-01-30 03:37:59 +08:00
|
|
|
execv(args[0], args);
|
|
|
|
|
1996-01-09 03:31:44 +08:00
|
|
|
exit(-1);
|
|
|
|
}
|
|
|
|
|
|
|
|
close(p[0]);
|
1996-01-23 05:13:12 +08:00
|
|
|
if (needSecondPipe) {
|
|
|
|
close(statusPipe[1]);
|
|
|
|
fcntl(statusPipe[0], F_SETFL, O_NONBLOCK);
|
|
|
|
}
|
1996-01-09 03:31:44 +08:00
|
|
|
|
|
|
|
do {
|
1996-02-15 05:56:29 +08:00
|
|
|
if (waitpid(child, &status, WNOHANG)) childDead = 1;
|
|
|
|
|
1996-01-09 03:31:44 +08:00
|
|
|
bytesRead = gzread(stream, buf, sizeof(buf));
|
|
|
|
if (write(p[1], buf, bytesRead) != bytesRead) {
|
|
|
|
cpioFailed = 1;
|
1996-02-15 05:56:29 +08:00
|
|
|
childDead = 1;
|
1996-01-09 03:31:44 +08:00
|
|
|
kill(SIGTERM, child);
|
|
|
|
}
|
1996-01-23 05:13:12 +08:00
|
|
|
|
|
|
|
if (needSecondPipe) {
|
|
|
|
bytes = read(statusPipe[0], line, sizeof(line));
|
1996-02-15 05:56:29 +08:00
|
|
|
|
1996-01-30 03:37:59 +08:00
|
|
|
while (bytes > 0) {
|
1996-01-23 05:13:12 +08:00
|
|
|
fileInstalled.fileName = line;
|
|
|
|
|
|
|
|
while ((chptr = (strchr(fileInstalled.fileName, '\n')))) {
|
|
|
|
*chptr = '\0';
|
|
|
|
|
1996-02-15 05:56:29 +08:00
|
|
|
message(MESS_DEBUG, "file \"%s\" complete\n",
|
|
|
|
fileInstalled.fileName);
|
1996-01-23 05:13:12 +08:00
|
|
|
|
1996-02-15 01:54:37 +08:00
|
|
|
if (notify) {
|
|
|
|
file = bsearch(&fileInstalled, files, fileCount,
|
|
|
|
sizeof(struct fileToInstall),
|
|
|
|
fileCompare);
|
|
|
|
if (file) {
|
|
|
|
sizeInstalled += file->size;
|
|
|
|
notify(sizeInstalled, totalSize);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (specFile) {
|
|
|
|
len = strlen(fileInstalled.fileName);
|
|
|
|
if (fileInstalled.fileName[len - 1] == 'c' &&
|
|
|
|
fileInstalled.fileName[len - 2] == 'e' &&
|
|
|
|
fileInstalled.fileName[len - 3] == 'p' &&
|
|
|
|
fileInstalled.fileName[len - 4] == 's' &&
|
|
|
|
fileInstalled.fileName[len - 5] == '.') {
|
|
|
|
|
|
|
|
if (*specFile) free(*specFile);
|
|
|
|
*specFile = strdup(fileInstalled.fileName);
|
|
|
|
}
|
1996-01-23 05:13:12 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
fileInstalled.fileName = chptr + 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
bytes = read(statusPipe[0], line, sizeof(line));
|
|
|
|
}
|
|
|
|
}
|
1996-02-15 05:56:29 +08:00
|
|
|
} while (!childDead);
|
1996-01-09 03:31:44 +08:00
|
|
|
|
|
|
|
gzclose(stream);
|
|
|
|
close(p[1]);
|
1996-01-23 05:13:12 +08:00
|
|
|
if (needSecondPipe) close(statusPipe[0]);
|
1996-01-09 03:31:44 +08:00
|
|
|
signal(SIGPIPE, oldhandler);
|
|
|
|
waitpid(child, &status, 0);
|
|
|
|
|
|
|
|
if (cpioFailed || !WIFEXITED(status) || WEXITSTATUS(status)) {
|
|
|
|
/* this would probably be a good place to check if disk space
|
|
|
|
was used up - if so, we should return a different error */
|
|
|
|
error(RPMERR_CPIO, "unpacking of archive failed");
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
1996-01-30 03:37:59 +08:00
|
|
|
if (notify)
|
|
|
|
notify(totalSize, totalSize);
|
1996-01-23 05:13:12 +08:00
|
|
|
|
1996-01-09 03:31:44 +08:00
|
|
|
return 0;
|
|
|
|
}
|
1996-01-15 03:32:17 +08:00
|
|
|
|
|
|
|
static int packageAlreadyInstalled(rpmdb db, char * name, char * version,
|
1996-02-15 05:26:21 +08:00
|
|
|
char * release, int * offset, int flags) {
|
1996-01-15 03:32:17 +08:00
|
|
|
char * secVersion, * secRelease;
|
|
|
|
Header sech;
|
|
|
|
int i;
|
|
|
|
dbIndexSet matches;
|
|
|
|
int type, count;
|
|
|
|
|
|
|
|
if (!rpmdbFindPackage(db, name, &matches)) {
|
|
|
|
for (i = 0; i < matches.count; i++) {
|
|
|
|
sech = rpmdbGetRecord(db, matches.recs[i].recOffset);
|
|
|
|
if (!sech) {
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
getEntry(sech, RPMTAG_VERSION, &type, (void **) &secVersion,
|
|
|
|
&count);
|
|
|
|
getEntry(sech, RPMTAG_RELEASE, &type, (void **) &secRelease,
|
|
|
|
&count);
|
|
|
|
|
|
|
|
if (!strcmp(secVersion, version) && !strcmp(secRelease, release)) {
|
1996-02-15 05:26:21 +08:00
|
|
|
*offset = matches.recs[i].recOffset;
|
1996-01-15 03:32:17 +08:00
|
|
|
if (!(flags & INSTALL_REPLACEPKG)) {
|
|
|
|
error(RPMERR_PKGINSTALLED,
|
|
|
|
"package %s-%s-%s is already installed",
|
|
|
|
name, version, release);
|
1996-02-20 06:15:38 +08:00
|
|
|
freeHeader(sech);
|
1996-01-15 03:32:17 +08:00
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
}
|
1996-02-20 06:15:38 +08:00
|
|
|
|
|
|
|
freeHeader(sech);
|
1996-01-15 03:32:17 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int setFileOwnerships(char * prefix, char ** fileList,
|
|
|
|
char ** fileOwners, char ** fileGroups,
|
1996-03-30 04:52:03 +08:00
|
|
|
enum instActions * instActions, int fileCount) {
|
1996-01-15 03:32:17 +08:00
|
|
|
int i;
|
|
|
|
|
1996-02-16 05:08:48 +08:00
|
|
|
message(MESS_DEBUG, "setting file owners and groups by name (not id)\n");
|
1996-01-15 03:32:17 +08:00
|
|
|
|
|
|
|
for (i = 0; i < fileCount; i++) {
|
1996-03-30 04:52:03 +08:00
|
|
|
if (instActions[i] != SKIP) {
|
|
|
|
/* ignore errors here - setFileOwner handles them reasonable
|
|
|
|
and we want to keep running */
|
|
|
|
setFileOwner(prefix, fileList[i], fileOwners[i], fileGroups[i]);
|
|
|
|
}
|
1996-01-15 03:32:17 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* setFileOwner() is really poorly implemented. It really ought to use
|
|
|
|
hash tables. I just made the guess that most files would be owned by
|
|
|
|
root or the same person/group who owned the last file. Those two values
|
|
|
|
are cached, everything else is looked up via getpw() and getgr() functions.
|
|
|
|
If this performs too poorly I'll have to implement it properly :-( */
|
|
|
|
|
|
|
|
static int setFileOwner(char * prefix, char * file, char * owner,
|
|
|
|
char * group) {
|
|
|
|
static char * lastOwner = NULL, * lastGroup = NULL;
|
|
|
|
static uid_t lastUID;
|
|
|
|
static gid_t lastGID;
|
|
|
|
uid_t uid = 0;
|
|
|
|
gid_t gid = 0;
|
|
|
|
struct passwd * pwent;
|
|
|
|
struct group * grent;
|
|
|
|
char * filespec;
|
|
|
|
|
|
|
|
filespec = alloca(strlen(prefix) + strlen(file) + 5);
|
|
|
|
strcpy(filespec, prefix);
|
|
|
|
strcat(filespec, "/");
|
|
|
|
strcat(filespec, file);
|
|
|
|
|
|
|
|
if (!strcmp(owner, "root"))
|
|
|
|
uid = 0;
|
|
|
|
else if (lastOwner && !strcmp(lastOwner, owner))
|
|
|
|
uid = lastUID;
|
|
|
|
else {
|
|
|
|
pwent = getpwnam(owner);
|
|
|
|
if (!pwent) {
|
|
|
|
error(RPMERR_NOUSER, "user %s does not exist - using root", owner);
|
|
|
|
uid = 0;
|
|
|
|
} else {
|
|
|
|
uid = pwent->pw_uid;
|
|
|
|
if (lastOwner) free(lastOwner);
|
|
|
|
lastOwner = strdup(owner);
|
|
|
|
lastUID = uid;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!strcmp(group, "root"))
|
|
|
|
gid = 0;
|
|
|
|
else if (lastGroup && !strcmp(lastGroup, group))
|
|
|
|
gid = lastGID;
|
|
|
|
else {
|
|
|
|
grent = getgrnam(group);
|
|
|
|
if (!grent) {
|
|
|
|
error(RPMERR_NOGROUP, "group %s does not exist - using root",
|
|
|
|
group);
|
|
|
|
gid = 0;
|
|
|
|
} else {
|
|
|
|
gid = grent->gr_gid;
|
|
|
|
if (lastGroup) free(lastGroup);
|
|
|
|
lastGroup = strdup(owner);
|
|
|
|
lastGID = gid;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
message(MESS_DEBUG, "%s owned by %s (%d), group %s (%d)\n", filespec,
|
|
|
|
owner, uid, group, gid);
|
|
|
|
if (chown(filespec, uid, gid)) {
|
|
|
|
error(RPMERR_CHOWN, "cannot set owner and group for %s - %s\n",
|
|
|
|
filespec, strerror(errno));
|
1996-01-31 01:35:13 +08:00
|
|
|
/* screw with the permissions so it's not SUID and 0.0 */
|
|
|
|
chmod(filespec, 0644);
|
1996-01-15 03:32:17 +08:00
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* This could be more efficient. A brute force tokenization and mkdir's
|
1996-01-30 03:37:59 +08:00
|
|
|
seems like horrible overkill. I did make it know better then trying to
|
|
|
|
create the same directory sintrg twice in a row though. That should make it
|
|
|
|
perform adequatally thanks to the sorted filelist.
|
1996-01-15 03:32:17 +08:00
|
|
|
|
|
|
|
This could create directories that should be symlinks :-( RPM building
|
|
|
|
should probably resolve symlinks in paths.
|
|
|
|
|
1996-01-30 03:37:59 +08:00
|
|
|
This creates directories which are always 0755, despite the current umask */
|
1996-01-15 03:32:17 +08:00
|
|
|
|
|
|
|
static int createDirectories(char * prefix, char ** fileList, int fileCount) {
|
|
|
|
int i;
|
|
|
|
char * lastDirectory;
|
|
|
|
char * buffer;
|
|
|
|
int bufferLength;
|
|
|
|
int prefixLength = strlen(prefix);
|
|
|
|
int neededLength;
|
|
|
|
char * chptr;
|
|
|
|
|
|
|
|
lastDirectory = malloc(1);
|
|
|
|
lastDirectory[0] = '\0';
|
|
|
|
|
|
|
|
bufferLength = 1000; /* should be more then adequate */
|
|
|
|
buffer = malloc(bufferLength);
|
|
|
|
|
|
|
|
for (i = 0; i < fileCount; i++) {
|
|
|
|
neededLength = prefixLength + 5 + strlen(fileList[i]);
|
|
|
|
if (neededLength > bufferLength) {
|
|
|
|
free(buffer);
|
|
|
|
bufferLength = neededLength * 2;
|
|
|
|
buffer = malloc(bufferLength);
|
|
|
|
}
|
|
|
|
strcpy(buffer, prefix);
|
|
|
|
strcat(buffer, "/");
|
|
|
|
strcat(buffer, fileList[i]);
|
|
|
|
|
|
|
|
for (chptr = buffer + strlen(buffer) - 1; *chptr; chptr--) {
|
|
|
|
if (*chptr == '/') break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (! *chptr) continue; /* no path, just filename */
|
|
|
|
if (chptr == buffer) continue; /* /filename - no directories */
|
|
|
|
|
|
|
|
*chptr = '\0'; /* buffer is now just directories */
|
|
|
|
|
|
|
|
if (!strcmp(buffer, lastDirectory)) continue;
|
|
|
|
|
|
|
|
for (chptr = buffer + 1; *chptr; chptr++) {
|
|
|
|
if (*chptr == '/') {
|
1996-01-30 07:27:12 +08:00
|
|
|
if (*(chptr -1) != '/') {
|
|
|
|
*chptr = '\0';
|
|
|
|
if (mkdirIfNone(buffer, 0755)) {
|
|
|
|
free(lastDirectory);
|
|
|
|
free(buffer);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
*chptr = '/';
|
1996-01-15 03:32:17 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (mkdirIfNone(buffer, 0755)) {
|
|
|
|
free(lastDirectory);
|
|
|
|
free(buffer);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
free(lastDirectory);
|
|
|
|
lastDirectory = strdup(buffer);
|
|
|
|
}
|
|
|
|
|
|
|
|
free(lastDirectory);
|
|
|
|
free(buffer);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int mkdirIfNone(char * directory, mode_t perms) {
|
|
|
|
int rc;
|
|
|
|
char * chptr;
|
|
|
|
|
|
|
|
/* if the path is '/' we get ENOFILE not found" from mkdir, rather
|
|
|
|
then EEXIST which is weird */
|
|
|
|
for (chptr = directory; *chptr; chptr++)
|
|
|
|
if (*chptr != '/') break;
|
|
|
|
if (!*chptr) return 0;
|
|
|
|
|
1996-02-15 06:20:08 +08:00
|
|
|
if (exists(directory)) return 0;
|
|
|
|
|
1996-01-15 03:32:17 +08:00
|
|
|
message(MESS_DEBUG, "trying to make %s\n", directory);
|
|
|
|
|
|
|
|
rc = mkdir(directory, perms);
|
|
|
|
if (!rc || errno == EEXIST) return 0;
|
|
|
|
|
1996-01-30 03:37:59 +08:00
|
|
|
chmod(directory, perms); /* this should not be modified by the umask */
|
|
|
|
|
1996-01-15 03:32:17 +08:00
|
|
|
error(RPMERR_MKDIR, "failed to create %s - %s\n", directory,
|
|
|
|
strerror(errno));
|
|
|
|
|
|
|
|
return errno;
|
|
|
|
}
|
1996-01-23 05:13:12 +08:00
|
|
|
|
1996-02-29 11:38:33 +08:00
|
|
|
int filecmp(short mode1, char * md51, char * link1,
|
|
|
|
short mode2, char * md52, char * link2) {
|
|
|
|
enum fileTypes what1, what2;
|
|
|
|
|
|
|
|
what1 = whatis(mode1);
|
|
|
|
what2 = whatis(mode2);
|
|
|
|
|
|
|
|
if (what1 != what2) return 1;
|
|
|
|
|
|
|
|
if (what1 == LINK)
|
|
|
|
return strcmp(link1, link2);
|
|
|
|
else if (what1 == REG)
|
|
|
|
return strcmp(md51, md52);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
enum instActions decideFileFate(char * filespec, short dbMode, char * dbMd5,
|
|
|
|
char * dbLink, short newMode, char * newMd5,
|
|
|
|
char * newLink) {
|
|
|
|
char buffer[1024];
|
|
|
|
char * dbAttr, * newAttr;
|
|
|
|
enum fileTypes dbWhat, newWhat, diskWhat;
|
|
|
|
struct stat sb;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
if (lstat(filespec, &sb)) {
|
|
|
|
/* the file doesn't exist on the disk - might as well make it */
|
|
|
|
return CREATE;
|
|
|
|
}
|
|
|
|
|
|
|
|
diskWhat = whatis(sb.st_mode);
|
|
|
|
dbWhat = whatis(dbMode);
|
|
|
|
newWhat = whatis(newMode);
|
|
|
|
|
|
|
|
if (diskWhat != newWhat) {
|
|
|
|
message(MESS_DEBUG, " file type on disk is different then package - "
|
|
|
|
"saving\n");
|
|
|
|
return SAVE;
|
|
|
|
} else if (newWhat != dbWhat && diskWhat != dbWhat) {
|
|
|
|
message(MESS_DEBUG, " file type in database is different then disk"
|
|
|
|
" and package file - saving\n");
|
|
|
|
return SAVE;
|
|
|
|
} else if (dbWhat != newWhat) {
|
|
|
|
message(MESS_DEBUG, " file type changed - replacing\n");
|
|
|
|
return CREATE;
|
|
|
|
} else if (dbWhat != LINK && dbWhat != REG) {
|
|
|
|
message(MESS_DEBUG, " can't check file for changes - replacing\n");
|
|
|
|
return CREATE;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (dbWhat == REG) {
|
|
|
|
if (mdfile(filespec, buffer)) {
|
|
|
|
/* assume the file has been removed, don't freak */
|
|
|
|
message(MESS_DEBUG, " file not present - creating");
|
|
|
|
return CREATE;
|
|
|
|
}
|
|
|
|
dbAttr = dbMd5;
|
|
|
|
newAttr = newMd5;
|
|
|
|
} else /* dbWhat == LINK */ {
|
|
|
|
i = readlink(filespec, buffer, sizeof(buffer) - 1);
|
|
|
|
if (i == -1) {
|
|
|
|
/* assume the file has been removed, don't freak */
|
|
|
|
message(MESS_DEBUG, " file not present - creating");
|
|
|
|
return CREATE;
|
|
|
|
}
|
|
|
|
dbAttr = dbLink;
|
|
|
|
newAttr = newLink;
|
|
|
|
}
|
|
|
|
|
1996-03-01 09:59:26 +08:00
|
|
|
/* this order matters - we'd prefer to CREATE the file is at all
|
|
|
|
possible in case something else (like the timestamp) has changed */
|
1996-02-29 11:38:33 +08:00
|
|
|
|
|
|
|
if (!strcmp(dbAttr, buffer)) {
|
|
|
|
/* this config file has never been modified, so
|
|
|
|
just replace it */
|
|
|
|
message(MESS_DEBUG, " old == current, replacing "
|
|
|
|
"with new version\n");
|
|
|
|
return CREATE;
|
|
|
|
}
|
|
|
|
|
1996-03-01 09:59:26 +08:00
|
|
|
if (!strcmp(dbAttr, newAttr)) {
|
|
|
|
/* this file is the same in all versions of this package */
|
|
|
|
message(MESS_DEBUG, " old == new, keeping\n");
|
|
|
|
return KEEP;
|
|
|
|
}
|
|
|
|
|
1996-02-29 11:38:33 +08:00
|
|
|
/* the config file on the disk has been modified, but
|
|
|
|
the ones in the two packages are different. It would
|
|
|
|
be nice if RPM was smart enough to at least try and
|
|
|
|
merge the difference ala CVS, but... */
|
|
|
|
message(MESS_DEBUG, " files changed too much - backing up\n");
|
|
|
|
|
|
|
|
return SAVE;
|
|
|
|
}
|
|
|
|
|
1996-01-23 05:13:12 +08:00
|
|
|
/* return 0: okay, continue install */
|
|
|
|
/* return 1: problem, halt install */
|
|
|
|
|
|
|
|
static int instHandleSharedFiles(rpmdb db, int ignoreOffset, char ** fileList,
|
1996-02-29 11:38:33 +08:00
|
|
|
char ** fileMd5List, int_16 * fileModesList,
|
|
|
|
char ** fileLinkList,
|
|
|
|
int fileCount, enum instActions * instActions,
|
1996-02-21 05:54:02 +08:00
|
|
|
char ** prefixedFileList, int * notErrors,
|
1996-02-16 05:08:48 +08:00
|
|
|
struct replacedFile ** repListPtr, int flags) {
|
1996-01-23 05:13:12 +08:00
|
|
|
struct sharedFile * sharedList;
|
1996-02-29 11:38:33 +08:00
|
|
|
int secNum, mainNum;
|
1996-01-23 05:13:12 +08:00
|
|
|
int sharedCount;
|
|
|
|
int i, type;
|
1996-02-21 05:54:02 +08:00
|
|
|
int * intptr;
|
|
|
|
Header sech = NULL;
|
1996-01-23 05:13:12 +08:00
|
|
|
int secOffset = 0;
|
|
|
|
int secFileCount;
|
1996-02-29 11:38:33 +08:00
|
|
|
char ** secFileMd5List, ** secFileList, ** secFileLinksList;
|
1996-01-23 05:13:12 +08:00
|
|
|
char * secFileStatesList;
|
1996-02-29 11:38:33 +08:00
|
|
|
int_16 * secFileModesList;
|
1996-01-30 03:37:59 +08:00
|
|
|
uint_32 * secFileFlagsList;
|
1996-01-23 05:13:12 +08:00
|
|
|
char * name, * version, * release;
|
|
|
|
int rc = 0;
|
1996-02-16 05:08:48 +08:00
|
|
|
struct replacedFile * replacedList;
|
|
|
|
int numReplacedFiles, numReplacedAlloced;
|
1996-01-23 05:13:12 +08:00
|
|
|
|
|
|
|
if (findSharedFiles(db, 0, fileList, fileCount, &sharedList,
|
|
|
|
&sharedCount)) {
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
1996-02-16 05:08:48 +08:00
|
|
|
numReplacedAlloced = 10;
|
|
|
|
numReplacedFiles = 0;
|
|
|
|
replacedList = malloc(sizeof(*replacedList) * numReplacedAlloced);
|
|
|
|
|
1996-01-23 05:13:12 +08:00
|
|
|
for (i = 0; i < sharedCount; i++) {
|
1996-02-16 05:08:48 +08:00
|
|
|
if (sharedList[i].secRecOffset == ignoreOffset) continue;
|
1996-01-23 05:13:12 +08:00
|
|
|
|
|
|
|
if (secOffset != sharedList[i].secRecOffset) {
|
|
|
|
if (secOffset) {
|
1996-02-20 08:17:51 +08:00
|
|
|
freeHeader(sech);
|
1996-01-23 05:13:12 +08:00
|
|
|
free(secFileMd5List);
|
1996-02-29 11:38:33 +08:00
|
|
|
free(secFileLinksList);
|
1996-01-23 05:13:12 +08:00
|
|
|
free(secFileList);
|
|
|
|
}
|
|
|
|
|
|
|
|
secOffset = sharedList[i].secRecOffset;
|
|
|
|
sech = rpmdbGetRecord(db, secOffset);
|
|
|
|
if (!sech) {
|
|
|
|
error(RPMERR_DBCORRUPT, "cannot read header at %d for "
|
|
|
|
"uninstall", secOffset);
|
|
|
|
rc = 1;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
getEntry(sech, RPMTAG_NAME, &type, (void **) &name,
|
|
|
|
&secFileCount);
|
|
|
|
getEntry(sech, RPMTAG_VERSION, &type, (void **) &version,
|
|
|
|
&secFileCount);
|
|
|
|
getEntry(sech, RPMTAG_RELEASE, &type, (void **) &release,
|
|
|
|
&secFileCount);
|
|
|
|
|
|
|
|
message(MESS_DEBUG, "package %s-%s-%s contain shared files\n",
|
|
|
|
name, version, release);
|
|
|
|
|
|
|
|
if (!getEntry(sech, RPMTAG_FILENAMES, &type,
|
|
|
|
(void **) &secFileList, &secFileCount)) {
|
|
|
|
error(RPMERR_DBCORRUPT, "package %s contains no files\n",
|
|
|
|
name);
|
|
|
|
freeHeader(sech);
|
|
|
|
rc = 1;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
getEntry(sech, RPMTAG_FILESTATES, &type,
|
|
|
|
(void **) &secFileStatesList, &secFileCount);
|
|
|
|
getEntry(sech, RPMTAG_FILEMD5S, &type,
|
|
|
|
(void **) &secFileMd5List, &secFileCount);
|
1996-01-30 03:37:59 +08:00
|
|
|
getEntry(sech, RPMTAG_FILEFLAGS, &type,
|
|
|
|
(void **) &secFileFlagsList, &secFileCount);
|
1996-02-29 11:38:33 +08:00
|
|
|
getEntry(sech, RPMTAG_FILELINKTOS, &type,
|
|
|
|
(void **) &secFileLinksList, &secFileCount);
|
|
|
|
getEntry(sech, RPMTAG_FILEMODES, &type,
|
|
|
|
(void **) &secFileModesList, &secFileCount);
|
1996-01-23 05:13:12 +08:00
|
|
|
}
|
|
|
|
|
1996-02-29 11:38:33 +08:00
|
|
|
secNum = sharedList[i].secFileNumber;
|
|
|
|
mainNum = sharedList[i].mainFileNumber;
|
1996-01-30 03:37:59 +08:00
|
|
|
|
1996-02-29 11:38:33 +08:00
|
|
|
message(MESS_DEBUG, "file %s is shared\n", secFileList[secNum]);
|
1996-02-24 00:21:06 +08:00
|
|
|
|
1996-02-21 05:54:02 +08:00
|
|
|
intptr = notErrors;
|
|
|
|
while (*intptr) {
|
|
|
|
if (*intptr == sharedList[i].secRecOffset) break;
|
|
|
|
intptr++;
|
|
|
|
}
|
|
|
|
|
1996-01-30 03:37:59 +08:00
|
|
|
/* if this instance of the shared file is already recorded as
|
|
|
|
replaced, just forget about it */
|
|
|
|
if (secFileStatesList[sharedList[i].secFileNumber] ==
|
|
|
|
RPMFILE_STATE_REPLACED) {
|
|
|
|
message(MESS_DEBUG, " old version already replaced\n");
|
|
|
|
continue;
|
1996-03-30 04:52:03 +08:00
|
|
|
} else if (secFileStatesList[sharedList[i].secFileNumber] ==
|
|
|
|
RPMFILE_STATE_NOTINSTALLED) {
|
|
|
|
message(MESS_DEBUG, " other version never installed\n");
|
|
|
|
continue;
|
1996-01-30 03:37:59 +08:00
|
|
|
}
|
|
|
|
|
1996-02-29 11:38:33 +08:00
|
|
|
if (filecmp(fileModesList[mainNum], fileMd5List[mainNum],
|
|
|
|
fileLinkList[mainNum], secFileModesList[secNum],
|
|
|
|
secFileMd5List[secNum], secFileLinksList[secNum])) {
|
|
|
|
if (!(flags & INSTALL_REPLACEFILES) && !(*intptr)) {
|
|
|
|
error(RPMERR_PKGINSTALLED, "%s conflicts with file from "
|
|
|
|
"%s-%s-%s", fileList[sharedList[i].mainFileNumber],
|
|
|
|
name, version, release);
|
|
|
|
rc = 1;
|
1996-01-30 03:37:59 +08:00
|
|
|
} else {
|
1996-02-29 11:38:33 +08:00
|
|
|
if (numReplacedFiles == numReplacedAlloced) {
|
|
|
|
numReplacedAlloced += 10;
|
|
|
|
replacedList = realloc(replacedList,
|
|
|
|
sizeof(*replacedList) *
|
|
|
|
numReplacedAlloced);
|
1996-01-30 03:37:59 +08:00
|
|
|
}
|
1996-02-29 11:38:33 +08:00
|
|
|
|
|
|
|
replacedList[numReplacedFiles].recOffset =
|
|
|
|
sharedList[i].secRecOffset;
|
|
|
|
replacedList[numReplacedFiles].fileNumber =
|
|
|
|
sharedList[i].secFileNumber;
|
|
|
|
numReplacedFiles++;
|
|
|
|
|
|
|
|
message(MESS_DEBUG, "%s from %s-%s-%s will be replaced\n",
|
|
|
|
fileList[sharedList[i].mainFileNumber],
|
|
|
|
name, version, release);
|
1996-01-30 03:37:59 +08:00
|
|
|
}
|
|
|
|
}
|
1996-02-29 11:38:33 +08:00
|
|
|
|
|
|
|
/* if this is a config file, we need to be carefull here */
|
|
|
|
if (secFileFlagsList[sharedList[i].secFileNumber] & RPMFILE_CONFIG) {
|
|
|
|
instActions[sharedList[i].mainFileNumber] =
|
|
|
|
decideFileFate(fileList[mainNum], secFileModesList[secNum],
|
|
|
|
secFileMd5List[secNum], secFileLinksList[secNum],
|
|
|
|
fileModesList[mainNum], fileMd5List[mainNum],
|
|
|
|
fileLinkList[mainNum]);
|
|
|
|
}
|
1996-01-23 05:13:12 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
if (secOffset) {
|
1996-02-20 08:17:51 +08:00
|
|
|
freeHeader(sech);
|
1996-01-23 05:13:12 +08:00
|
|
|
free(secFileMd5List);
|
1996-02-29 11:38:33 +08:00
|
|
|
free(secFileLinksList);
|
1996-01-23 05:13:12 +08:00
|
|
|
free(secFileList);
|
|
|
|
}
|
|
|
|
|
|
|
|
free(sharedList);
|
1996-02-16 05:08:48 +08:00
|
|
|
|
|
|
|
if (!numReplacedFiles)
|
|
|
|
free(replacedList);
|
|
|
|
else {
|
|
|
|
replacedList[numReplacedFiles].recOffset = 0; /* mark the end */
|
|
|
|
*repListPtr = replacedList;
|
|
|
|
}
|
1996-01-23 05:13:12 +08:00
|
|
|
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int fileCompare(const void * one, const void * two) {
|
|
|
|
return strcmp(((struct fileToInstall *) one)->fileName,
|
|
|
|
((struct fileToInstall *) two)->fileName);
|
|
|
|
}
|
1996-02-15 01:54:37 +08:00
|
|
|
|
|
|
|
|
1996-02-15 04:09:14 +08:00
|
|
|
static int installSources(char * prefix, int fd, char ** specFilePtr) {
|
1996-02-15 01:54:37 +08:00
|
|
|
char * specFile;
|
|
|
|
char * sourceDir, * specDir;
|
|
|
|
char * realSourceDir, * realSpecDir;
|
|
|
|
char * instSpecFile, * correctSpecFile;
|
|
|
|
|
1996-02-15 04:09:14 +08:00
|
|
|
message(MESS_DEBUG, "installing a source package\n");
|
|
|
|
|
1996-02-15 01:54:37 +08:00
|
|
|
sourceDir = getVar(RPMVAR_SOURCEDIR);
|
|
|
|
specDir = getVar(RPMVAR_SPECDIR);
|
|
|
|
|
|
|
|
realSourceDir = alloca(strlen(prefix) + strlen(sourceDir) + 2);
|
|
|
|
strcpy(realSourceDir, prefix);
|
|
|
|
strcat(realSourceDir, "/");
|
|
|
|
strcat(realSourceDir, sourceDir);
|
|
|
|
|
|
|
|
realSpecDir = alloca(strlen(prefix) + strlen(specDir) + 2);
|
|
|
|
strcpy(realSpecDir, prefix);
|
|
|
|
strcat(realSpecDir, "/");
|
|
|
|
strcat(realSpecDir, specDir);
|
|
|
|
|
|
|
|
message(MESS_DEBUG, "sources in: %s\n", realSourceDir);
|
|
|
|
message(MESS_DEBUG, "spec file in: %s\n", realSpecDir);
|
|
|
|
|
|
|
|
if (installArchive(realSourceDir, fd, NULL, 0, NULL, &specFile)) {
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!specFile) {
|
|
|
|
error(RPMERR_NOSPEC, "source package contains no .spec file\n");
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* This logic doesn't work is realSpecDir and realSourceDir are on
|
|
|
|
different filesystems XXX */
|
|
|
|
instSpecFile = alloca(strlen(realSourceDir) + strlen(specFile) + 2);
|
|
|
|
strcpy(instSpecFile, realSourceDir);
|
|
|
|
strcat(instSpecFile, "/");
|
|
|
|
strcat(instSpecFile, specFile);
|
|
|
|
|
|
|
|
correctSpecFile = alloca(strlen(realSpecDir) + strlen(specFile) + 2);
|
|
|
|
strcpy(correctSpecFile, realSpecDir);
|
|
|
|
strcat(correctSpecFile, "/");
|
|
|
|
strcat(correctSpecFile, specFile);
|
|
|
|
|
|
|
|
message(MESS_DEBUG, "renaming %s to %s\n", instSpecFile, correctSpecFile);
|
|
|
|
if (rename(instSpecFile, correctSpecFile)) {
|
|
|
|
error(RPMERR_RENAME, "rename of %s to %s failed: %s\n",
|
|
|
|
instSpecFile, correctSpecFile, strerror(errno));
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
1996-02-15 04:09:14 +08:00
|
|
|
if (specFilePtr)
|
|
|
|
*specFilePtr = strdup(correctSpecFile);
|
|
|
|
|
1996-02-15 01:54:37 +08:00
|
|
|
return 0;
|
|
|
|
}
|
1996-02-16 05:08:48 +08:00
|
|
|
|
|
|
|
static int markReplacedFiles(rpmdb db, struct replacedFile * replList) {
|
|
|
|
struct replacedFile * fileInfo;
|
|
|
|
Header secHeader = NULL, sh;
|
|
|
|
char * secStates;
|
|
|
|
int type, count;
|
|
|
|
|
|
|
|
int secOffset = 0;
|
|
|
|
|
|
|
|
for (fileInfo = replList; fileInfo->recOffset; fileInfo++) {
|
|
|
|
if (secOffset != fileInfo->recOffset) {
|
|
|
|
if (secHeader) {
|
|
|
|
/* ignore errors here - just do the best we can */
|
|
|
|
|
|
|
|
rpmdbUpdateRecord(db, secOffset, secHeader);
|
|
|
|
freeHeader(secHeader);
|
|
|
|
}
|
|
|
|
|
|
|
|
secOffset = fileInfo->recOffset;
|
|
|
|
sh = rpmdbGetRecord(db, secOffset);
|
|
|
|
if (!sh) {
|
|
|
|
secOffset = 0;
|
|
|
|
} else {
|
|
|
|
secHeader = copyHeader(sh); /* so we can modify it */
|
|
|
|
freeHeader(sh);
|
|
|
|
}
|
|
|
|
|
|
|
|
getEntry(secHeader, RPMTAG_FILESTATES, &type, (void **) &secStates,
|
|
|
|
&count);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* by now, secHeader is the right header to modify, secStates is
|
|
|
|
the right states list to modify */
|
|
|
|
|
|
|
|
secStates[fileInfo->fileNumber] = RPMFILE_STATE_REPLACED;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (secHeader) {
|
|
|
|
/* ignore errors here - just do the best we can */
|
|
|
|
|
|
|
|
rpmdbUpdateRecord(db, secOffset, secHeader);
|
|
|
|
freeHeader(secHeader);
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
1996-02-26 06:11:00 +08:00
|
|
|
|
|
|
|
static int ensureOlder(rpmdb db, char * name, char * newVersion,
|
|
|
|
char * newRelease, int dbOffset) {
|
|
|
|
Header h;
|
|
|
|
char * oldVersion, * oldRelease;
|
|
|
|
int rc, result;
|
|
|
|
int type, count;
|
|
|
|
|
|
|
|
h = rpmdbGetRecord(db, dbOffset);
|
|
|
|
if (!h) return 1;
|
|
|
|
|
|
|
|
getEntry(h, RPMTAG_VERSION, &type, (void **) &oldVersion, &count);
|
|
|
|
getEntry(h, RPMTAG_RELEASE, &type, (void **) &oldRelease, &count);
|
|
|
|
|
|
|
|
result = vercmp(oldVersion, newVersion);
|
|
|
|
if (result < 0)
|
|
|
|
rc = 0;
|
|
|
|
else if (result > 0)
|
|
|
|
rc = 1;
|
|
|
|
else {
|
|
|
|
result = vercmp(oldRelease, newRelease);
|
|
|
|
if (result < 0)
|
|
|
|
rc = 0;
|
|
|
|
else
|
|
|
|
rc = 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (rc)
|
|
|
|
error(RPMERR_OLDPACKAGE, "package %s-%s-%s (which is newer) is already"
|
|
|
|
" installed", name, oldVersion, oldRelease);
|
|
|
|
|
|
|
|
freeHeader(h);
|
|
|
|
|
|
|
|
return rc;
|
|
|
|
}
|
1996-02-29 11:38:33 +08:00
|
|
|
|
|
|
|
enum fileTypes whatis(short mode) {
|
|
|
|
enum fileTypes result;
|
|
|
|
|
|
|
|
if (S_ISDIR(mode))
|
|
|
|
result = DIR;
|
|
|
|
else if (S_ISCHR(mode))
|
|
|
|
result = CDEV;
|
|
|
|
else if (S_ISBLK(mode))
|
|
|
|
result = BDEV;
|
|
|
|
else if (S_ISLNK(mode))
|
|
|
|
result = LINK;
|
|
|
|
else if (S_ISSOCK(mode))
|
|
|
|
result = SOCK;
|
|
|
|
else if (S_ISFIFO(mode))
|
|
|
|
result = PIPE;
|
|
|
|
else
|
|
|
|
result = REG;
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|