/* $Id: mft.c,v 1.15 2002/07/21 13:20:35 richdawe Exp $ */ /* * mft.c - .mft & .ver file parsing functions * 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 #include #include /* libpakke includes */ #include #include #include #include #include #include "fnsplit.h" #include "strlwr.h" #include "unzip.h" /* ------------------------ * - ver_parse_version_l0 - * ------------------------ */ /* This function counts the leading zeros, given a version number string. E.g. for "02", it will return 1. This is to handle version numbers such as 2.02 correctly. */ int ver_parse_version_l0 (const char *v) { int i; /* Simple cases */ if (v == NULL) return(0); if (v[0] != '0') return(0); if (!isdigit(v[1])) return(0); /* All others */ for (i = 0; i < strlen(v); i++) { if (v[i] != '0') break; } return(i); } /* --------------------- * - ver_parse_version - * --------------------- */ int ver_parse_version (const char *str, PACKAGE_VERSION *ver) { char *buf = strdup(str); char *p = buf; char *q = NULL; int i; if (buf == NULL) return(MFT_INTERNAL_ERROR); memset(ver, 0, sizeof(*ver)); strlwr(buf); /* Find the start of the version number */ p = strchr(buf, '.'); if ((p != NULL) && (p != buf)) { p--; while (isdigit(*p)) p--; p++; } if (p == NULL) { /* Look for a version number enclosed by whitespace */ for (p = buf + 1; *p != '\0'; p++) { if (!isdigit(*p)) continue; /* Space, bracket or 'v' before? */ if ( !isspace(*(p - 1)) && (*(p - 1) != '(') && (*(p - 1) != '[') && (*(p - 1) != '{') && (*(p - 1) != 'v')) continue; /* Scan the body */ q = p; while (isdigit(*q) || (*q == '.')) q++; /* Space or bracket after? */ if ( !isspace(*q) && (*q != ')') && (*q != ']') && (*q != '}')) { p = q + 1; continue; } else { /* Found it */ break; } } if (*p == '\0') p = NULL; } /* No version found */ if (p == NULL) { free(buf); return(MFT_BAD_VERSION_FORMAT); } /* Parse the period-separated version numbers */ for (i = 0, p = strtok(p, ". "); ; i++, p = strtok(NULL, ". ")) { if (p == NULL) break; if (!isdigit(*p)) break; switch(i) { case 0: ver->has_major = 1; ver->major = atoi(p); break; case 1: ver->has_minor = 1; ver->minor = atoi(p); ver->minor_l0 = ver_parse_version_l0(p); break; case 2: ver->has_subminor = 1; ver->subminor = atoi(p); ver->subminor_l0 = ver_parse_version_l0(p); break; case 3: ver->has_subsubminor = 1; ver->subsubminor = atoi(p); ver->subsubminor_l0 = ver_parse_version_l0(p); break; default: free(buf); return(MFT_BAD_VERSION_FORMAT); } } /* buf has been torn up by strtok(), so use str from here on. */ /* TODO: What about case? */ /* Alpha version */ p = strstr(str, MFT_COMP_ALPHA); if (p != NULL) { for (p += strlen(MFT_COMP_ALPHA); (*p != '\0') && isspace(*p); p++) { ; } ver->has_alpha = 1; ver->alpha = atoi(p); } /* Beta version */ p = strstr(str, MFT_COMP_BETA); if (p != NULL) { for (p += strlen(MFT_COMP_BETA); (*p != '\0') && isspace(*p); p++) { ; } ver->has_beta = 1; ver->beta = atoi(p); } /* Revision */ p = strstr(str, MFT_COMP_REVISION); if (p != NULL) { for (p += strlen(MFT_COMP_REVISION); (*p != '\0') && isspace(*p); p++) { ; } ver->has_revision = 1; ver->revision = atoi(p); } /* Patchlevel */ p = strstr(str, MFT_COMP_PATCHLEVEL); if (p != NULL) { for (p += strlen(MFT_COMP_PATCHLEVEL); (*p != '\0') && isspace(*p); p++) { ; } ver->has_patchlevel = 1; ver->patchlevel = atoi(p); } /* Release */ p = strstr(str, MFT_COMP_RELEASE); if (p != NULL) { for (p += strlen(MFT_COMP_RELEASE); (*p != '\0') && isspace(*p); p++) { ; } ver->has_release = 1; ver->release = atoi(p); } /* Any version info present? */ if ( ver->has_major || ver->has_minor || ver->has_minor || ver->has_subminor || ver->has_subsubminor || ver->has_alpha || ver->has_beta || ver->has_revision || ver->has_patchlevel || ver->has_release) ver->has_version = 1; /* OK */ free(buf); return(MFT_OK); } /* ------------- * - ver_parse - * ------------- */ int ver_parse (const char *str, PACKAGE_INFO *package) { char *buf = NULL; char pseudover[17]; /* Made-up version from manifest name */ char *p = NULL; int ret = MFT_BAD; int i, j; size_t len; /* File extensions to be removed */ char *bad_ext[] = { ".zip", ".taz", ".tgz", ".tar.gz", NULL }; /* Initialisation */ buf = strdup(str); if (buf == NULL) return(MFT_INTERNAL_ERROR); memset(pseudover, 0, sizeof(pseudover)); /* Find the start of the description */ for (p = buf; (*p != '\0') && !isspace(*p); p++) {;} *p = '\0', p++; /* Store the package name */ strncpy(package->name, buf, sizeof(package->name)); package->name[sizeof(package->name) - 1] = '\0'; /* Store the description */ len = sizeof(package->short_description) - 1; if (strlen(p) < len) len = strlen(p); strncpy(package->short_description, p, len); package->short_description[len] = '\0'; /* Try to find the version number from the description, else * try to get it from the package name. */ ret = ver_parse_version(package->short_description, &package->version); if (ret != MFT_OK) { /* Last-ditch attempt to construct a version number * from manifest name. */ /* TODO: This could guess the package type too. */ memset(pseudover, 0, sizeof(pseudover)); for (i = j = 0; i < 8; i++) { if (!isdigit(buf[i])) continue; /* abc -> a.b.c */ pseudover[j] = buf[i]; pseudover[j+1] = '.'; j += 2; } if (strlen(pseudover) != 0) { ret = ver_parse_version(pseudover, &package->version); /* Set fake version flag, so caller can warn, if necessary. */ if (ret == MFT_OK) package->has_faked_version = 1; } } if (ret != MFT_OK) memset(&package->version, 0, sizeof(package->version)); /* The package name should not contain '.zip', '.taz', * '.tgz' or '.tar.gz', so remove that if necessary. */ for (i = 0; bad_ext[i] != NULL; i++) { p = strstr(package->name, bad_ext[i]); if (p == NULL) continue; *p = '\0'; } /* Work out package type from last letter of its name, but only * if the letter is prefixed by a number. If no letter is * present, the type is unknown. */ package->version.type = TYPE_NONE; if (strlen(package->name) > 0) { p = package->name + strlen(package->name) - 1; switch(tolower(*p)) { case 'b': package->version.type = TYPE_BINARIES; break; case 's': package->version.type = TYPE_SOURCES; break; case 'd': package->version.type = TYPE_DOCUMENTATION; break; } } /* If the package type is unknown, look for often-used strings like * "(binaries)" or binaries. */ if (package->version.type == TYPE_NONE) { if ( (strstr(package->short_description, "(binaries)") != NULL) || (strstr(package->short_description, "binaries") != NULL)) { package->version.type = TYPE_BINARIES; } else if ( (strstr(package->short_description, "(sources)") != NULL) || (strstr(package->short_description, "sources") != NULL)) { package->version.type = TYPE_SOURCES; } else if ( (strstr(package->short_description, "(documentation)") != NULL) || (strstr(package->short_description, "documentation") != NULL)) { package->version.type = TYPE_DOCUMENTATION; } } /* If no type found yet, default to binaries. */ if (package->version.type == TYPE_NONE) package->version.type = TYPE_BINARIES; /* We always have a type. */ package->version.has_type = 1; /* Tidy up */ free(buf); return(MFT_OK); } /* --------------------- * - ver_get_and_parse - * --------------------- */ /* Parse a .ver file. This returns MFT_OK on success, else an MFT_* error * code. */ int ver_get_and_parse (const char *filename, PACKAGE_INFO *package, char **ver) { FILE *fp = NULL; char buf[1024]; char f[MAXFILE]; int ret = MFT_BAD; memset(package, 0, sizeof(*package)); memset(buf, 0, sizeof(buf)); /* Set package defaults */ package_set_defaults(package); fp = fopen(filename, "rt"); if (fp == NULL) return(MFT_NONEXISTENT); /* ASSUMPTION: We can get the manifest name from the .ver filename. * For DJGPP packages they are supposed to be the same. This avoids * the problem with .ver files that don't contain the name of the * manifest/package as the first word in the .ver file. */ strcpy(buf, filename); if ((fnsplit(buf, NULL, NULL, f, NULL) & FILENAME) == 0) { fclose(fp); return(MFT_BAD); } strncpy(package->manifest, f, sizeof(package->manifest)); package->manifest[sizeof(package->manifest) - 1] = '\0'; /* Only one line here */ if (fgets(buf, sizeof(buf), fp) != NULL) { chomp(buf); rtrim(buf); ret = ver_parse(buf, package); } /* If the callee wants a copy of the file, return one. */ if ((ret == MFT_OK) && (ver != NULL)) { *ver = strdup(buf); if (*ver == NULL) ret = MFT_INTERNAL_ERROR; } /* Tidy up */ fclose(fp); return(ret); } /* ---------------- * - ver_load_all - * ---------------- */ PACKAGE_INFO * ver_load_all (const char **path, PACKAGE_INFO *packages) { PACKAGE_INFO *list = packages; PACKAGE_INFO *newpackage = NULL; char mypath[PATH_MAX], verfile[PATH_MAX]; DIR *d = NULL; struct dirent *de = NULL; int i, ret; /* Check params */ if (path == NULL) return(list); /* Nothing to do */ /* Go through all the spec'd directories looking for '.ver's */ for (i = 0; path[i] != NULL; i++) { /* Format the path name for later */ strcpy(mypath, path[i]); addforwardslash(mypath); d = opendir(mypath); if (d == NULL) continue; while ( (de = readdir(d)) != NULL ) { /* Suitable file name? */ if (strcmp(de->d_name, ".") == 0) continue; if (strcmp(de->d_name, "..") == 0) continue; if (strstr(de->d_name, ".ver") == NULL) continue; if (strstr(de->d_name, ".ver") == de->d_name) continue; /* Set up the new package info struct. */ newpackage = calloc(1, sizeof(*newpackage)); if (newpackage == NULL) { warn("Unable to allocate memory!"); continue; } /* Parse the package file */ strcpy(verfile, mypath); strcat(verfile, de->d_name); ret = ver_get_and_parse(verfile, newpackage, NULL); if (ret != MFT_OK) { warnf("Unable to parse '%s'", verfile); free(newpackage); } else { /* Add package to list. If the list doesn't * exist, create it. */ if (list == NULL) { list = newpackage; } else { packlist_add(list, newpackage); } } } closedir(d); } return(list); } /* ------------------------ * - mft_get_from_archive - * ------------------------ */ char * mft_get_from_archive (PACKAGE_INFO *package) { char filename[PATH_MAX]; char *mft = NULL; char **toc = NULL; char *p = NULL; int i, found; /* * Try finding a DSM file in the archive & read it into a buffer. * Try to match the following file names: * * '.mft' * 'manifest/.mft'. */ /* TODO: Multiple manifest file checks? */ /* Get the table of contents. */ toc = archive_get_toc(package->path); if (toc == NULL) return(NULL); /* Scan for manifests. */ for (found = -1, i = 0; toc[i] != NULL; i++) { if (!has_extension(toc[i], "mft")) continue; /* Convert to forward slashes for the check. */ strcpy(filename, toc[i]); forwardslashify(filename); /* Chop off the extension. */ p = strrchr(filename, '.'); if (p == NULL) continue; *p = '\0'; /* Find manifest in root of archive. */ if (strcasecmp(filename, package->manifest) == 0) { found = i; break; } /* Find manifest in manifest/ of archive. */ #define MFTDIR "manifest/" #define MFTDIRLEN strlen(MFTDIR) if ( (strncasecmp(filename, MFTDIR, MFTDIRLEN) == 0) && (strchr(filename + MFTDIRLEN, '/') == NULL) && (strcasecmp(filename + MFTDIRLEN, package->manifest) == 0) ) { found = i; break; } } if (found >= 0) { mft = archive_extract_text_file_to_memory(package->path, toc[i]); } else { mft = NULL; } /* Clean up the TOC */ for (i = 0; toc[i] != NULL; i++) { free(toc[i]); } free(toc), toc = NULL; return(mft); } /* ----------- * - mft_get - * ----------- */ char * mft_get (const char **mft_path, PACKAGE_INFO *package) { char filename[PATH_MAX]; char *mft = NULL; char *p = NULL; if (!isarchive(package->path)) { /* If a path name is provided, use it. Otherwise, search * the manifest paths for the manifest file. */ if (strlen(package->path) != 0) { strcpy(filename, package->path); addforwardslash(filename); strcat(filename, package->manifest); strcat(filename, ".mft"); } else { /* Find the manifest file case insensitively. */ strcpy(filename, package->manifest); strcat(filename, ".mft"); p = find_in_paths(filename, mft_path, 0); if (p == NULL) return(0); /* Not found */ strcpy(filename, p); } /* Now get the contents */ mft = read_text_file_to_memory(filename); if (mft == NULL) return(0); } else { mft = mft_get_from_archive(package); } return(mft); } /* ---------------- * - mft_get_list - * ---------------- */ char ** mft_get_list (const char **mft_path, PACKAGE_INFO *package) { char **list = NULL; char **new_list = NULL; char *mft = NULL; char *p = NULL; char *next = NULL; int file = 0; size_t max_files = 32; /* Initial guess for max number of entries */ int i = 0; /* Read the manifest file */ mft = mft_get(mft_path, package); if (mft == NULL) return(NULL); /* Allocate initial list, with NULL terminator */ list = malloc(sizeof(char *) * (max_files + 1)); if (list == NULL) { free(mft); return(NULL); } for (i = 0; i <= max_files; i++) { list[i] = NULL; } /* Decompose the manifest text into a list of filenames */ for (file = 0, p = mft, next = NULL; p != NULL; p = next) { /* Set up next pointer by finding the end-of-current * line, to get a filename. */ next = strchr(p, '\n'); if (next != NULL) { /* Rewind before skipping carriage-returns and * linefeeds. */ while ((*next == '\r') || (*next == '\n')) { next--; } next++; while ((*next == '\r') || (*next == '\n')) { *next = '\0'; next++; } } /* Skip blank filenames */ if (*p == '\0') continue; /* Add file to list */ if (file == max_files) { /* Need to lengthen list first */ max_files *= 2; /* realloc() with space for NULL terminator */ new_list = realloc(list, sizeof(char *) * (max_files + 1)); if (new_list == NULL) { /* Failed => bail out */ for (i = 0; list[i] != NULL; i++) { free(list[i]); } free(list); list = NULL; break; } list = new_list; for (i = file; i <= max_files; i++) { list[i] = NULL; } } list[file] = strdup(p); if (list[file] == NULL) { for (i = 0; list[i] != NULL; i++) { free(list[i]); } free(list); list = NULL; break; } file++; } /* Tidy up */ free(mft); /* If the list is too big, shrink it. If realloc() fails, don't barf, just * return original list. */ if (file < max_files) { max_files = file; /* realloc() with space for NULL terminator */ new_list = realloc(list, sizeof(char *) * (max_files + 1)); if (new_list != NULL) list = new_list; } /* Done */ return(list); }