anaconda.spec.in | 1 +
loader/Makefile.am | 6 +-
loader/cpio.c | 192 +++++++++++++++++++++++++++++++++++++++++++
loader/cpio.h | 116 ++++++++++++++++++++++++++
loader/rpmextract.c | 227 +++++++++++++++++++++++++++++++++++++++++++++++++++
loader/rpmextract.h | 47 +++++++++++
6 files changed, 587 insertions(+), 2 deletions(-)
create mode 100644 loader/cpio.c
create mode 100644 loader/cpio.h
create mode 100644 loader/rpmextract.c
create mode 100644 loader/rpmextract.h
diff --git a/anaconda.spec.in b/anaconda.spec.in
index 862e306..8344105 100644
--- a/anaconda.spec.in
+++ b/anaconda.spec.in
@@ -69,6 +69,7 @@ BuildRequires: pango-devel
BuildRequires: pykickstart >= %{pykickstartver}
BuildRequires: python-devel
BuildRequires: python-urlgrabber
+BuildRequires: rpm-devel
BuildRequires: rpm-python >= %{rpmpythonver}
BuildRequires: slang-devel >= %{slangver}
BuildRequires: xmlto
diff --git a/loader/Makefile.am b/loader/Makefile.am
index e5d1b7f..2d3b39b 100644
--- a/loader/Makefile.am
+++ b/loader/Makefile.am
@@ -45,13 +45,15 @@ loader_CFLAGS = $(COMMON_CFLAGS) $(GLIB_CFLAGS) $(LIBNM_GLIB_CFLAGS) \
loader_LDADD = $(NEWT_LIBS) $(GLIB_LIBS) $(LIBNL_LIBS) \
$(LIBNM_GLIB_LIBS) $(CHECKISOMD5_LIBS) \
$(LIBCURL_LIBS) \
- $(ISCSI_LIBS) $(top_srcdir)/isys/libisys.la
+ $(ISCSI_LIBS) $(top_srcdir)/isys/libisys.la \
+ -lrpm -lrpmio
loader_SOURCES = loader.c copy.c log.c moduleinfo.c loadermisc.c \
modules.c windows.c lang.c kbd.c driverdisk.c \
selinux.c mediacheck.c kickstart.c driverselect.c \
getparts.c dirbrowser.c fwloader.c ibft.c hardware.c \
method.c cdinstall.c hdinstall.c nfsinstall.c \
- urlinstall.c net.c urls.c telnet.c telnetd.c
+ urlinstall.c net.c urls.c telnet.c telnetd.c \
+ cpio.c rpmextract.c
init_CFLAGS = $(COMMON_CFLAGS)
init_SOURCES = init.c undomounts.c shutdown.c copy.c
diff --git a/loader/cpio.c b/loader/cpio.c
new file mode 100644
index 0000000..36335fc
--- /dev/null
+++ b/loader/cpio.c
@@ -0,0 +1,192 @@
+/** \ingroup payload
+ * \file lib/cpio.c
+ * Handle cpio payloads within rpm packages.
+ *
+ * \warning FIXME: We don't translate between cpio and system mode bits! These
+ * should both be the same, but really odd things are going to happen if
+ * that's not true!
+ */
+
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <errno.h>
+
+#include <rpm/rpmio.h>
+#include <rpm/rpmlog.h>
+#include <rpm/header.h>
+
+#include "cpio.h"
+
+
+const char * headerGetString(Header h, rpmTag tag)
+{
+ const char *res = NULL;
+ struct rpmtd_s td;
+
+ if (headerGet(h, tag, &td, HEADERGET_MINMEM)) {
+ if (rpmtdCount(&td) == 1) {
+ res = rpmtdGetString(&td);
+ }
+ rpmtdFreeData(&td);
+ }
+ return res;
+}
+
+/**
+ * Convert string to unsigned integer (with buffer size check).
+ * @param str input string
+ * @retval endptr address of 1st character not processed
+ * @param base numerical conversion base
+ * @param num max no. of bytes to read
+ * @return converted integer
+ */
+static unsigned long strntoul(const char *str,char **endptr, int base, size_t num)
+{
+ char buf[num+1], * end;
+ unsigned long ret;
+
+ strncpy(buf, str, num);
+ buf[num] = '\0';
+
+ ret = strtoul(buf, &end, base);
+ if (*end != '\0')
+ *endptr = ((char *)str) + (end - buf); /* XXX discards const */
+ else
+ *endptr = ((char *)str) + strlen(buf);
+
+ return ret;
+}
+
+#define GET_NUM_FIELD(phys, log) \
+ \
+ log = strntoul(phys, &end, 16, sizeof(phys)); \
+ \
+ if ( (end - phys) != sizeof(phys) ) return CPIOERR_BAD_HEADER;
+#define SET_NUM_FIELD(phys, val, space) \
+ sprintf(space, "%8.8lx", (unsigned long) (val)); \
+ \
+ memcpy(phys, space, 8) \
+
+int cpioReadFileHdr(FD_t fd, struct stat * st, char** name)
+{
+ struct cpioCrcPhysicalHeader hdr;
+ int nameSize;
+ char * end;
+ unsigned int major, minor;
+ int rc = 0;
+ char paddingBuffer[4];
+
+ rc = Fread(&hdr, PHYS_HDR_SIZE, 1, fd);
+ if(rc!=1) return CPIOERR_BAD_HEADER;
+
+ if (strncmp(CPIO_CRC_MAGIC, hdr.magic, sizeof(CPIO_CRC_MAGIC)-1) &&
+ strncmp(CPIO_NEWC_MAGIC, hdr.magic, sizeof(CPIO_NEWC_MAGIC)-1))
+ return CPIOERR_BAD_MAGIC;
+
+ GET_NUM_FIELD(hdr.inode, st->st_ino);
+ GET_NUM_FIELD(hdr.mode, st->st_mode);
+ GET_NUM_FIELD(hdr.uid, st->st_uid);
+ GET_NUM_FIELD(hdr.gid, st->st_gid);
+ GET_NUM_FIELD(hdr.nlink, st->st_nlink);
+ GET_NUM_FIELD(hdr.mtime, st->st_mtime);
+ GET_NUM_FIELD(hdr.filesize, st->st_size);
+
+ GET_NUM_FIELD(hdr.devMajor, major);
+ GET_NUM_FIELD(hdr.devMinor, minor);
+ st->st_dev = makedev(major, minor);
+
+ GET_NUM_FIELD(hdr.rdevMajor, major);
+ GET_NUM_FIELD(hdr.rdevMinor, minor);
+ st->st_rdev = makedev(major, minor);
+
+ GET_NUM_FIELD(hdr.namesize, nameSize);
+ //if (nameSize >= fsm->wrsize)
+ // return CPIOERR_BAD_HEADER;
+
+ long padding = (nameSize+PHYS_HDR_SIZE)%4;
+ if(padding>0) padding = 4-padding;
+
+ { char * t = malloc(nameSize + 1);
+ rc = Fread(t, nameSize, 1, fd);
+ if (rc != 1) {
+ free(t);
+ return CPIOERR_BAD_HEADER;
+ }
+ t[nameSize] = '\0';
+ *name = t;
+ }
+
+ if(padding){
+ rc = Fread(paddingBuffer, padding, 1, fd);
+ if (rc != 1) {
+ free(*name);
+ return CPIOERR_BAD_HEADER;
+ }
+ }
+
+ return (st->st_ino == 0 && st->st_size == 0 && st->st_mode == 0)?CPIOERR_HDR_TRAILER:0;
+}
+
+const char * cpioStrerror(int rc)
+{
+ static char msg[256];
+ const char *s;
+ int myerrno = errno;
+ size_t l;
+
+ strcpy(msg, "cpio: ");
+ switch (rc) {
+ default: {
+ char *t = msg + strlen(msg);
+ sprintf(t, "(error 0x%x)", (unsigned)rc);
+ s = NULL;
+ break;
+ }
+ case CPIOERR_BAD_MAGIC: s = "Bad magic"; break;
+ case CPIOERR_BAD_HEADER: s = "Bad/unreadable header";break;
+
+ case CPIOERR_OPEN_FAILED: s = "open"; break;
+ case CPIOERR_CHMOD_FAILED: s = "chmod"; break;
+ case CPIOERR_CHOWN_FAILED: s = "chown"; break;
+ case CPIOERR_WRITE_FAILED: s = "write"; break;
+ case CPIOERR_UTIME_FAILED: s = "utime"; break;
+ case CPIOERR_UNLINK_FAILED: s = "unlink"; break;
+ case CPIOERR_RENAME_FAILED: s = "rename"; break;
+ case CPIOERR_SYMLINK_FAILED: s = "symlink"; break;
+ case CPIOERR_STAT_FAILED: s = "stat"; break;
+ case CPIOERR_LSTAT_FAILED: s = "lstat"; break;
+ case CPIOERR_MKDIR_FAILED: s = "mkdir"; break;
+ case CPIOERR_RMDIR_FAILED: s = "rmdir"; break;
+ case CPIOERR_MKNOD_FAILED: s = "mknod"; break;
+ case CPIOERR_MKFIFO_FAILED: s = "mkfifo"; break;
+ case CPIOERR_LINK_FAILED: s = "link"; break;
+ case CPIOERR_READLINK_FAILED: s = "readlink"; break;
+ case CPIOERR_READ_FAILED: s = "read"; break;
+ case CPIOERR_COPY_FAILED: s = "copy"; break;
+ case CPIOERR_LSETFCON_FAILED: s = "lsetfilecon"; break;
+ case CPIOERR_SETCAP_FAILED: s = "cap_set_file"; break;
+
+ case CPIOERR_HDR_SIZE: s = "Header size too big"; break;
+ case CPIOERR_UNKNOWN_FILETYPE: s = "Unknown file type"; break;
+ case CPIOERR_MISSING_HARDLINK: s = "Missing hard link(s)"; break;
+ case CPIOERR_DIGEST_MISMATCH: s = "Digest mismatch"; break;
+ case CPIOERR_INTERNAL: s = "Internal error"; break;
+ case CPIOERR_UNMAPPED_FILE: s = "Archive file not in header"; break;
+ case CPIOERR_ENOENT: s = strerror(ENOENT); break;
+ case CPIOERR_ENOTEMPTY: s = strerror(ENOTEMPTY); break;
+ }
+
+ l = sizeof(msg) - strlen(msg) - 1;
+ if (s != NULL) {
+ if (l > 0) strncat(msg, s, l);
+ l -= strlen(s);
+ }
+ if ((rc & CPIOERR_CHECK_ERRNO) && myerrno) {
+ s = " failed - ";
+ if (l > 0) strncat(msg, s, l);
+ l -= strlen(s);
+ if (l > 0) strncat(msg, strerror(myerrno), l);
+ }
+ return msg;
+}
diff --git a/loader/cpio.h b/loader/cpio.h
new file mode 100644
index 0000000..7e70cce
--- /dev/null
+++ b/loader/cpio.h
@@ -0,0 +1,116 @@
+#ifndef H_CPIO
+#define H_CPIO
+
+/** \ingroup payload
+ * \file lib/cpio.h
+ * Structures used to handle cpio payloads within rpm packages.
+ *
+ * @warning Rpm's cpio implementation may be different than standard cpio.
+ * The implementation is pretty close, but it has some behaviors which are
+ * more to RPM's liking. I tried to document the differing behavior in cpio.c,
+ * but I may have missed some (ewt).
+ *
+ */
+
+/** \ingroup payload
+ * @note CPIO_CHECK_ERRNO bit is set only if errno is valid.
+ */
+#define CPIOERR_CHECK_ERRNO 0x00008000
+
+/** \ingroup payload
+ */
+enum cpioErrorReturns {
+ CPIOERR_BAD_MAGIC = 2,
+ CPIOERR_BAD_HEADER = 3,
+ CPIOERR_OPEN_FAILED = 4 | CPIOERR_CHECK_ERRNO,
+ CPIOERR_CHMOD_FAILED = 5 | CPIOERR_CHECK_ERRNO,
+ CPIOERR_CHOWN_FAILED = 6 | CPIOERR_CHECK_ERRNO,
+ CPIOERR_WRITE_FAILED = 7 | CPIOERR_CHECK_ERRNO,
+ CPIOERR_UTIME_FAILED = 8 | CPIOERR_CHECK_ERRNO,
+ CPIOERR_UNLINK_FAILED = 9 | CPIOERR_CHECK_ERRNO,
+ CPIOERR_RENAME_FAILED = 10 | CPIOERR_CHECK_ERRNO,
+ CPIOERR_SYMLINK_FAILED = 11 | CPIOERR_CHECK_ERRNO,
+ CPIOERR_STAT_FAILED = 12 | CPIOERR_CHECK_ERRNO,
+ CPIOERR_LSTAT_FAILED = 13 | CPIOERR_CHECK_ERRNO,
+ CPIOERR_MKDIR_FAILED = 14 | CPIOERR_CHECK_ERRNO,
+ CPIOERR_RMDIR_FAILED = 15 | CPIOERR_CHECK_ERRNO,
+ CPIOERR_MKNOD_FAILED = 16 | CPIOERR_CHECK_ERRNO,
+ CPIOERR_MKFIFO_FAILED = 17 | CPIOERR_CHECK_ERRNO,
+ CPIOERR_LINK_FAILED = 18 | CPIOERR_CHECK_ERRNO,
+ CPIOERR_READLINK_FAILED = 19 | CPIOERR_CHECK_ERRNO,
+ CPIOERR_READ_FAILED = 20 | CPIOERR_CHECK_ERRNO,
+ CPIOERR_COPY_FAILED = 21 | CPIOERR_CHECK_ERRNO,
+ CPIOERR_LSETFCON_FAILED = 22 | CPIOERR_CHECK_ERRNO,
+ CPIOERR_HDR_SIZE = 23,
+ CPIOERR_HDR_TRAILER = 24,
+ CPIOERR_UNKNOWN_FILETYPE= 25,
+ CPIOERR_MISSING_HARDLINK= 26,
+ CPIOERR_DIGEST_MISMATCH = 27,
+ CPIOERR_INTERNAL = 28,
+ CPIOERR_UNMAPPED_FILE = 29,
+ CPIOERR_ENOENT = 30,
+ CPIOERR_ENOTEMPTY = 31,
+ CPIOERR_SETCAP_FAILED = 32 | CPIOERR_CHECK_ERRNO,
+};
+
+/*
+ * Size limit for individual files in "new ascii format" cpio archives.
+ * The max size of the entire archive is unlimited from cpio POV,
+ * but subject to filesystem limitations.
+ */
+#define CPIO_FILESIZE_MAX UINT32_MAX
+
+#define CPIO_NEWC_MAGIC "070701"
+#define CPIO_CRC_MAGIC "070702"
+#define CPIO_TRAILER "TRAILER!!!"
+
+/** \ingroup payload
+ * Cpio archive header information.
+ */
+struct cpioCrcPhysicalHeader {
+ char magic[6];
+ char inode[8];
+ char mode[8];
+ char uid[8];
+ char gid[8];
+ char nlink[8];
+ char mtime[8];
+ char filesize[8];
+ char devMajor[8];
+ char devMinor[8];
+ char rdevMajor[8];
+ char rdevMinor[8];
+ char namesize[8];
+ char checksum[8]; /* ignored !! */
+};
+
+#define PHYS_HDR_SIZE 110 /* Don't depend on sizeof(struct) */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+const char * headerGetString(Header h, rpmTag tag);
+
+/**
+ * Read cpio header.
+ * @retval fsm file path and stat info
+ * @retval st
+ * @return 0 on success
+ */
+RPM_GNUC_INTERNAL
+int cpioReadFileHdr(FD_t fd, struct stat * st, char** name);
+
+/** \ingroup payload
+ * Return formatted error message on payload handling failure.
+ * @param rc error code
+ * @return formatted error string
+ */
+/* XXX should be RPM_GNUC_INTERNAL too but build/pack.c uses */
+const char * cpioStrerror(int rc);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* H_CPIO */
diff --git a/loader/rpmextract.c b/loader/rpmextract.c
new file mode 100644
index 0000000..ca741ad
--- /dev/null
+++ b/loader/rpmextract.c
@@ -0,0 +1,227 @@
+/* rpm2dir: unpack the payload of RPM package to the current directory*/
+/* Based on rpm2cpio */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <rpm/rpmlib.h> /* rpmReadPackageFile .. */
+#include <rpm/rpmtag.h>
+#include <rpm/rpmio.h>
+#include <rpm/rpmpgp.h>
+
+#include <rpm/rpmts.h>
+
+#include <stdio.h>
+
+#include "cpio.h"
+#include "rpmextract.h"
+
+/*
+ * explode source RPM into the current directory
+ * use filters to skip packages and files we do not need
+ */
+int explodeRPM(const char *source,
+ filterfunc filter,
+ dependencyfunc provides,
+ dependencyfunc deps,
+ void* userptr)
+{
+ char buffer[BUFFERSIZE+1]; /* make space for trailing \0 */
+ FD_t fdi;
+ Header h;
+ char * rpmio_flags = NULL;
+ rpmRC rc;
+ FD_t gzdi;
+
+ if (strcmp(source, "-") == 0)
+ fdi = fdDup(STDIN_FILENO);
+ else
+ fdi = Fopen(source, "r.ufdio");
+
+ if (Ferror(fdi)) {
+ logMessage(ERROR, "%s: %s\n", (strcmp(source, "-") == 0 ? "<stdin>" : source), Fstrerror(fdi));
+ return EXIT_FAILURE;
+ }
+ rpmReadConfigFiles(NULL, NULL);
+
+ { rpmts ts = rpmtsCreate();
+ rpmVSFlags vsflags = 0;
+
+ /* XXX retain the ageless behavior of rpm2cpio */
+ vsflags |= _RPMVSF_NODIGESTS;
+ vsflags |= _RPMVSF_NOSIGNATURES;
+ vsflags |= RPMVSF_NOHDRCHK;
+ (void) rpmtsSetVSFlags(ts, vsflags);
+
+ rc = rpmReadPackageFile(ts, fdi, "rpm2dir", &h);
+
+ ts = rpmtsFree(ts);
+ }
+
+ switch (rc) {
+ case RPMRC_OK:
+ case RPMRC_NOKEY:
+ case RPMRC_NOTTRUSTED:
+ break;
+ case RPMRC_NOTFOUND:
+ logMessage(ERROR, "argument is not an RPM package\n");
+ return EXIT_FAILURE;
+ break;
+ case RPMRC_FAIL:
+ default:
+ logMessage(ERROR, "error reading header from package\n");
+ return EXIT_FAILURE;
+ break;
+ }
+
+ /* Retrieve all dependencies and run them through deps function */
+ while(deps){
+ struct rpmtd_s td;
+ const char *depname;
+
+ if (!headerGet(h, RPMTAG_REQUIRENAME, &td, HEADERGET_MINMEM))
+ break;
+
+ /* iterator */
+ while ((depname = rpmtdNextString(&td))) {
+ if(deps(depname, userptr)){
+ Fclose(fdi);
+ return EXIT_BADDEPS;
+ }
+ }
+ rpmtdFreeData(&td);
+ break;
+ }
+
+ /* Retrieve all provides and run them through provides function */
+ while(provides){
+ struct rpmtd_s td;
+ const char *depname;
+ int found = 0;
+
+ if (!headerGet(h, RPMTAG_PROVIDES, &td, HEADERGET_MINMEM))
+ break;
+
+ /* iterator */
+ while ((depname = rpmtdNextString(&td))) {
+ if(!provides(depname, userptr)){
+ found++;
+ }
+ }
+ rpmtdFreeData(&td);
+ if(found<=0)
+ return EXIT_BADDEPS;
+ break;
+ }
+
+ /* Retrieve type of payload compression. */
+ {
+ const char *compr = headerGetString(h, RPMTAG_PAYLOADCOMPRESSOR);
+ if(compr && strcmp(compr, "gzip")) rpmio_flags = rstrscat(NULL, "r.", compr, "dio", NULL);
+ else rpmio_flags = rstrscat(NULL, "r.", "gzdio", NULL);
+ }
+
+ gzdi = Fdopen(fdi, rpmio_flags); /* XXX gzdi == fdi */
+ free(rpmio_flags);
+
+ if (gzdi == NULL) {
+ logMessage(ERROR, "cannot re-open payload: %s\n", Fstrerror(gzdi));
+ return EXIT_FAILURE;
+ }
+
+ while(!rc){
+ char *filename = NULL;
+ struct stat fstat;
+ int offset = 0;
+ int towrite = 1;
+
+ rc = cpioReadFileHdr(gzdi, &fstat, &filename);
+ if(rc == CPIOERR_HDR_TRAILER){
+ rc = 0;
+ break;
+ } else if(rc != 0){
+ break;
+ }
+
+ /* Strip leading slashes */
+ while(filename[offset] == '/') offset+=1;
+ /* Strip leading ./ */
+ while(filename[offset] == '.' && filename[offset+1] == '/') offset+=2;
+
+ /* Other file type - we do not care except special cases */
+ if(!S_ISREG(fstat.st_mode)) towrite = 1;
+ else towrite = 2;
+
+ if(filter && filter(filename+offset, &fstat, userptr)){
+ /* filter this file */
+ towrite = 0;
+ }
+
+ /* Create directories */
+ char* dirname = strdup(filename+offset);
+ char* dirptr = dirname;
+ while(dirptr!=NULL && *dirptr!=0){
+ dirptr = strchr(dirptr, '/');
+ if(dirptr){
+ *dirptr = 0;
+ mkdir(dirname, 0700);
+ *dirptr = '/';
+ dirptr++;
+ }
+ }
+ free(dirname);
+
+ /* Regular file */
+ long readbytes = 0;
+ long padding = fstat.st_size%4;
+ FILE *fdout = NULL;
+ if(padding>0) padding = 4-padding;
+
+ if(towrite>=2){
+ fdout = fopen(filename+offset, "w");
+
+ if(fdout==NULL){
+ free(filename);
+ rc = 33;
+ break;
+ }
+ }
+
+ while(1){
+ long toread = (BUFFERSIZE>fstat.st_size) ? fstat.st_size : BUFFERSIZE;
+ if(toread<=0) break;
+ readbytes = Fread(buffer, toread, 1, gzdi);
+ if(readbytes<=0){
+ rc = 34; //truncated archive? set error flag...
+ break;
+ }
+ else{
+ fstat.st_size-=toread;
+ if(towrite>=2)
+ if(fwrite(buffer, toread, 1, fdout)!=1){
+ /* TODO: error handling */
+ }
+ buffer[toread] = 0;
+ }
+ }
+
+ /* symlink, we assume that the path comtained in symlink
+ * is shorter than BUFFERSIZE */
+ if(towrite && !rc && S_ISLNK(fstat.st_mode)){
+ if(symlink(buffer, filename+offset)){
+ /* TODO: error handling */
+ }
+ }
+
+ free(filename);
+ if(towrite>=2) fclose(fdout);
+
+ if(padding) readbytes = Fread(buffer, padding, 1, gzdi);
+ }
+
+
+ Fclose(gzdi); /* XXX gzdi == fdi */
+
+ return rc;
+}
diff --git a/loader/rpmextract.h b/loader/rpmextract.h
new file mode 100644
index 0000000..0348b49
--- /dev/null
+++ b/loader/rpmextract.h
@@ -0,0 +1,47 @@
+/*
+ File name: rpmextract.h
+ Date: 2009/09/16
+ Author: msivak
+
+ Copyright (C) 2009 msivak
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; 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
+ in a file called COPYING along with this program; if not, write to
+ the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA
+ 02139, USA.
+*/
+
+
+#ifndef __RPMEXTRACT_H__
+#define __RPMEXTRACT_H__
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#define EXIT_BADDEPS 4
+#define BUFFERSIZE 1024
+
+/* both filter functions return 0 - match, 1 - match not found */
+typedef int (*filterfunc)(const char* name, struct stat *fstat, void *userptr);
+typedef int (*dependencyfunc)(const char* depends, void *userptr);
+
+int explodeRPM(const char* file,
+ filterfunc filter,
+ dependencyfunc provides,
+ dependencyfunc deps,
+ void* userptr);
+
+#endif
+
+/* end of rpmextract.h */