/* $Id: archive.c,v 1.11 2002/06/23 20:28:31 richdawe Exp $ */ /* * archive.c - Archive handling functions for pakke * Copyright (C) 1999-2002 by Richard Dawe * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "common.h" #include #include #include #include #include #include /* libpakke includes */ #include #include #include "unzip.h" #ifdef HAVE_LIBBZ2 #include #endif /* Remember the last opened zip file, so we don't have to keep opening it. */ static unzFile zipfile = NULL; static char zipname[PATH_MAX] = ""; static int zip_opened = 0; /* ZIP file handling */ static int zip_extract_file (const char *archive, const char *file, const char *dest); static char **zip_get_toc (const char *archive); static int zip_get_file_info (const char *archive, const char *file, ARCHIVE_FILE_INFO *afi); static int zip_extract_file_to_memory (const char *archive, const char *file, char *buf, unsigned long buflen); static int zip_check_integrity (const char *archive); /* gzip'd tar file handling */ /* TODO: tar-gzip */ /* bzip2'd tar file handling */ /* TODO: tar-bzip2 */ /* Use a 256KB buffer size */ static const size_t ZIP_BUFFER_SIZE = 256 * 1024; /* TODO: Error codes? */ /* ------------ * - zip_open - * ------------ */ /* This opens the zip archive with "memory" between invocations of the * archive_*() functions. */ int __inline__ zip_open (const char *archive) { if (!zip_opened) { zipfile = unzOpen(archive); if (zipfile == NULL) return(0); strcpy(zipname, archive); zip_opened = 1; } else { if (strcmp(archive, zipname) != 0) { unzClose(zipfile); zip_opened = 0; zipfile = unzOpen(archive); if (zipfile == NULL) return(0); strcpy(zipname, archive); zip_opened = 1; } } return(1); } /* ------------------- * - archive_get_toc - * ------------------- */ /* * Retrieve the table-of-contents (TOC) of the archive, i.e. a list of all * files (including directories) inside the archive. This list must be freed * by the caller. * * Returns a NULL-terminated list on success, otherwise NULL on failure. */ char ** archive_get_toc (const char *archive) { if (!iszip(archive)) return(NULL); else return(zip_get_toc(archive)); } /* --------------- * - zip_get_toc - * --------------- */ static char ** zip_get_toc (const char *archive) { char **toc = NULL; unz_global_info gi; char currentfile[PATH_MAX]; int ret, i, j; if (!zip_open(archive)) return(NULL); /* Get the global info */ if (unzGetGlobalInfo(zipfile, &gi) != UNZ_OK) return(NULL); /* Allocate a TOC list */ toc = malloc(sizeof(char *) * (gi.number_entry + 1)); if (toc == NULL) return(NULL); for (i = 0; i <= gi.number_entry; i++) { toc[i] = NULL; } for (i = 0, ret = unzGoToFirstFile(zipfile); ret == UNZ_OK; i++, ret = unzGoToNextFile(zipfile)) { /* Get the file details */ ret = unzGetCurrentFileInfo(zipfile, NULL, currentfile, sizeof(currentfile), NULL, 0, NULL, 0); if (ret != UNZ_OK) { for (j = 0; toc[j] != NULL; j++) { free(toc[j]); toc[j] = NULL; } free(toc); return(NULL); } toc[i] = strdup(currentfile); } return(toc); } /* ------------------------ * - archive_extract_file - * ------------------------ */ /* Extract from the file 'file' from the archive file 'archive' into the * destination file 'dest'. This routine figures out which archive format * 'archive' is by looking at the file extension. Directories should be * created using mkdir(). */ /* TODO: Symlinks? Or not supported? */ int archive_extract_file (const char *archive, const char *file, const char *dest) { ARCHIVE_FILE_INFO afi; time_t t; struct utimbuf ut; int ret; /* We currently only support .zip files. */ if (!iszip(archive)) return(0); else ret = zip_extract_file(archive, file, dest); if (!ret) return(0); /* Set the file attributes. */ /* TODO: Set permissions, etc. - see InfoZIP appnote for description * of ZIP internal/external permissions. */ if (!archive_get_file_info(archive, file, &afi)) return(0); t = mktime(&afi.modtime); if (t != (time_t) -1) { /* If mktime() failed, do not set the timestamp. This will result in * the file timestamp being way, way into the future. */ /* TODO: Return an error if mktime() fails, so we can generate * a warning about being unable to set the timestamp. */ ut.actime = ut.modtime = t; if (utime(dest, &ut)) return(0); } return(1); } /* -------------------- * - zip_extract_file - * -------------------- */ static int zip_extract_file (const char *archive, const char *file, const char *dest) { FILE *fp = NULL; char *buf = NULL; size_t buflen = ZIP_BUFFER_SIZE; int nread; int ret = 1; /* Succeed by default */ /* Open the archive */ if (!zip_open(archive)) return(0); /* Find the requested file - 0 = default case sensitivity */ if (unzLocateFile(zipfile, file, 0) != UNZ_OK) return(0); /* Allocate file buffer */ buf = malloc(buflen); if (buf == NULL) return(0); /* Now extract */ fp = fopen(dest, "wb"); if (fp == NULL) return(0); if (unzOpenCurrentFile(zipfile) != UNZ_OK) { fclose(fp); return(0); } while ( (nread = unzReadCurrentFile(zipfile, buf, buflen)) > 0) { if (fwrite(buf, nread, 1, fp) <= 0) { ret = 0; break; } } fclose(fp); /* Free buffer */ free(buf), buf = NULL; /* TODO: Better error handling */ if (unzCloseCurrentFile(zipfile) != UNZ_OK) ret = 0; return(ret); } /* ------------------------- * - archive_get_file_info - * ------------------------- */ int archive_get_file_info (const char *archive, const char *file, ARCHIVE_FILE_INFO *afi) { if (!iszip(archive)) return(0); else return(zip_get_file_info(archive, file, afi)); } /* --------------------- * - zip_get_file_info - * --------------------- */ static int zip_get_file_info (const char *archive, const char *file, ARCHIVE_FILE_INFO *afi) { unz_file_info fi; char currentfile[PATH_MAX]; int len; if (!zip_open(archive)) return(0); /* Find the requested file - 0 = default case sensitivity */ if (unzLocateFile(zipfile, file, 0) != UNZ_OK) return(0); if (unzGetCurrentFileInfo(zipfile, &fi, currentfile, sizeof(currentfile), NULL, 0, NULL, 0) != UNZ_OK) return(0); /* Store the details */ len = strlen(currentfile) + 1; if (sizeof(afi->name) < len) len = sizeof(afi->name); strncpy(afi->name, currentfile, sizeof(afi->name)); afi->name[len - 1] = '\0'; afi->size = fi.uncompressed_size; memset(&afi->modtime, 0, sizeof(afi->modtime)); afi->modtime.tm_sec = fi.tmu_date.tm_sec; afi->modtime.tm_min = fi.tmu_date.tm_min; afi->modtime.tm_hour = fi.tmu_date.tm_hour; afi->modtime.tm_mday = fi.tmu_date.tm_mday; afi->modtime.tm_mon = fi.tmu_date.tm_mon; /* Count years since 1900 */ afi->modtime.tm_year = fi.tmu_date.tm_year - 1900; return(1); } /* ---------------------------------- * - archive_extract_file_to_memory - * ---------------------------------- */ /* This function extracts the given file into a buffer created for it. The * caller should free this buffer. */ static char * __extract_to_memory (const char *archive, const char *file, const int textmode) { ARCHIVE_FILE_INFO afi; char *buf = NULL; unsigned long buflen = 0; /* Create a big enough buffer. */ if (!archive_get_file_info(archive, file, &afi)) return(NULL); buflen = afi.size; if (!textmode) buf = malloc(buflen); else buf = malloc(buflen + 1); /* Space for nul */ if (buf == NULL) return(NULL); /* TODO: Other formats */ if (!zip_extract_file_to_memory(archive, file, buf, buflen)) { free(buf); return(NULL); } if (textmode) buf[buflen] = '\0'; return(buf); } char * archive_extract_file_to_memory (const char *archive, const char *file) { return(__extract_to_memory(archive, file, 0)); } char * archive_extract_text_file_to_memory (const char *archive, const char *file) { return(__extract_to_memory(archive, file, 1)); } /* ------------------------------ * - zip_extract_file_to_memory - * ------------------------------ */ static int zip_extract_file_to_memory (const char *archive, const char *file, char *buf, const unsigned long buflen) { if (!zip_open(archive)) return(0); /* Find the requested file - 0 = default case sensitivity */ if (unzLocateFile(zipfile, file, 0) != UNZ_OK) return(0); /* Now read it into 'buf' */ if (unzOpenCurrentFile(zipfile) != UNZ_OK) return(0); /* TODO: Can I assume that this will read all data? */ if (unzReadCurrentFile(zipfile, buf, buflen) != buflen) return(0); /* TODO: Better error handling */ if (unzCloseCurrentFile(zipfile) != UNZ_OK) return(0); return(1); } /* --------------------------- * - archive_check_integrity - * --------------------------- */ /* Check the integrity of 'archive'. If its integrity is intact, return 1, * otherwise return 0. */ int archive_check_integrity (const char *archive) { if (!iszip(archive)) return(0); else return(zip_check_integrity(archive)); } /* ----------------------- * - zip_check_integrity - * ----------------------- */ static int zip_check_integrity (const char *archive) { char *buf = NULL; /* Buffer for reading in files */ size_t buflen = ZIP_BUFFER_SIZE; int ret = UNZ_OK; int crc_error = 0; size_t nread = 0; int i; /* Open the archive */ if (!zip_open(archive)) return(0); /* Allocate file buffer */ buf = malloc(buflen); if (buf == NULL) return(0); /* Enumerate all files */ for (i = 0, ret = unzGoToFirstFile(zipfile); ret == UNZ_OK; i++, ret = unzGoToNextFile(zipfile)) { /* Open and read entire file */ if (unzOpenCurrentFile(zipfile) != UNZ_OK) { /* TODO: Better error */ crc_error = 1; continue; } for ( ; (nread = unzReadCurrentFile(zipfile, buf, buflen)) > 0; ) {;} /* Close file; check for CRC error */ if (unzCloseCurrentFile(zipfile) != UNZ_OK) { crc_error = 1; continue; } } /* Free buffer */ free(buf), buf = NULL; /* Done */ if (!crc_error) return(1); return(0); }