/* $Id: dsm.c,v 1.31 2002/07/21 13:19:40 richdawe Exp $ */ /* * dsm.c - DSM parsing 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 #include #include #include #include /* libsocket includes */ #include #include #include #include #include #include "strlwr.h" #include "unzip.h" /* Sanity checks */ #if PACKAGE_VERSION_N_HAS != 14 #error "Need to update code to cope with PACKAGE_VERSION's has_*" #endif #if PACKAGE_VERSION_N_DATA != 21 #error "Need to update code to cope with PACKAGE_VERSION's data fields" #endif /* Macros for easy to handle parsing operations. */ /* Create a copy of the value in a new piece of memory. */ #define DSM_PARSING_FUNC_DUP_DEF(p) \ int dsm_parse_##p (const char *directive, \ const char *str, \ PACKAGE_INFO *package) { \ package->p = strdup(str); \ return(DSM_OK); \ } /* Copy the value into an existing array, without overrunning. */ #define DSM_PARSING_FUNC_COPY_DEF(p) \ int dsm_parse_##p (const char *directive, \ const char *str, \ PACKAGE_INFO *package) { \ strncpy(package->p, str, sizeof(package->p)); \ package->p[sizeof(package->p) - 1] = '\0'; \ return(DSM_OK); \ } /* Create a copy of the value in a new piece of memory, but store the value * in an array of value. dsm_parse_multiple() handles finding an empty array * index and creating & placing the copy there. */ #define DSM_PARSING_FUNC_MULTIPLE_DUP_DEF(p, max) \ int dsm_parse_##p (const char *directive, \ const char *str, \ PACKAGE_INFO *package) { \ return(dsm_parse_multiple(directive, str, package->p, max)); \ } /* Define some simple parsing functions - there should be a declaration in the * include file dsm.h (using DSM_PARSING_FUNC_DECL) & an entry in * the parsing table below for each of these. */ DSM_PARSING_FUNC_DUP_DEF(dsm_author); DSM_PARSING_FUNC_DUP_DEF(dsm_author_email); DSM_PARSING_FUNC_DUP_DEF(dsm_author_im); DSM_PARSING_FUNC_DUP_DEF(dsm_author_web_site); DSM_PARSING_FUNC_DUP_DEF(dsm_author_ftp_site); DSM_PARSING_FUNC_COPY_DEF(dsm_name); DSM_PARSING_FUNC_DUP_DEF(binaries_dsm); DSM_PARSING_FUNC_DUP_DEF(sources_dsm); DSM_PARSING_FUNC_DUP_DEF(documentation_dsm); DSM_PARSING_FUNC_COPY_DEF(name); DSM_PARSING_FUNC_COPY_DEF(manifest); DSM_PARSING_FUNC_DUP_DEF(license); DSM_PARSING_FUNC_DUP_DEF(organisation); DSM_PARSING_FUNC_MULTIPLE_DUP_DEF(author, PACKAGE_MAX_AUTHOR); DSM_PARSING_FUNC_MULTIPLE_DUP_DEF(author_email, PACKAGE_MAX_AUTHOR); DSM_PARSING_FUNC_MULTIPLE_DUP_DEF(author_im, PACKAGE_MAX_AUTHOR); DSM_PARSING_FUNC_MULTIPLE_DUP_DEF(web_site, PACKAGE_MAX_SITE); DSM_PARSING_FUNC_MULTIPLE_DUP_DEF(ftp_site, PACKAGE_MAX_SITE); DSM_PARSING_FUNC_MULTIPLE_DUP_DEF(maintainer, PACKAGE_MAX_MAINTAINER); DSM_PARSING_FUNC_MULTIPLE_DUP_DEF(maintainer_email, PACKAGE_MAX_MAINTAINER); DSM_PARSING_FUNC_MULTIPLE_DUP_DEF(maintainer_im, PACKAGE_MAX_MAINTAINER); DSM_PARSING_FUNC_MULTIPLE_DUP_DEF(maintainer_web_site, PACKAGE_MAX_MAINTAINER); DSM_PARSING_FUNC_MULTIPLE_DUP_DEF(maintainer_ftp_site, PACKAGE_MAX_MAINTAINER); DSM_PARSING_FUNC_MULTIPLE_DUP_DEF(porter, PACKAGE_MAX_PORTER); DSM_PARSING_FUNC_MULTIPLE_DUP_DEF(porter_email, PACKAGE_MAX_PORTER); DSM_PARSING_FUNC_MULTIPLE_DUP_DEF(porter_im, PACKAGE_MAX_PORTER); DSM_PARSING_FUNC_MULTIPLE_DUP_DEF(porting_web_site, PACKAGE_MAX_PORTER); DSM_PARSING_FUNC_MULTIPLE_DUP_DEF(porting_ftp_site, PACKAGE_MAX_PORTER); DSM_PARSING_FUNC_MULTIPLE_DUP_DEF(mailing_list, PACKAGE_MAX_MAILINGLIST); DSM_PARSING_FUNC_MULTIPLE_DUP_DEF(mailing_list_description, PACKAGE_MAX_MAILINGLIST); DSM_PARSING_FUNC_MULTIPLE_DUP_DEF(mailing_list_request, PACKAGE_MAX_MAILINGLIST); DSM_PARSING_FUNC_MULTIPLE_DUP_DEF(mailing_list_administrator, PACKAGE_MAX_MAILINGLIST); DSM_PARSING_FUNC_MULTIPLE_DUP_DEF(mailing_list_administrator_email, PACKAGE_MAX_MAILINGLIST); DSM_PARSING_FUNC_MULTIPLE_DUP_DEF(mailing_list_administrator_im, PACKAGE_MAX_MAILINGLIST); DSM_PARSING_FUNC_MULTIPLE_DUP_DEF(mailing_list_web_site, PACKAGE_MAX_MAILINGLIST); DSM_PARSING_FUNC_MULTIPLE_DUP_DEF(mailing_list_ftp_site, PACKAGE_MAX_MAILINGLIST); DSM_PARSING_FUNC_MULTIPLE_DUP_DEF(newsgroup, PACKAGE_MAX_NEWSGROUP); DSM_PARSING_FUNC_MULTIPLE_DUP_DEF(newsgroup_description, PACKAGE_MAX_NEWSGROUP); DSM_PARSING_FUNC_MULTIPLE_DUP_DEF(newsgroup_email_gateway, PACKAGE_MAX_NEWSGROUP); DSM_PARSING_FUNC_MULTIPLE_DUP_DEF(newsgroup_administrator, PACKAGE_MAX_NEWSGROUP); DSM_PARSING_FUNC_MULTIPLE_DUP_DEF(newsgroup_administrator_email, PACKAGE_MAX_NEWSGROUP); DSM_PARSING_FUNC_MULTIPLE_DUP_DEF(newsgroup_administrator_im, PACKAGE_MAX_NEWSGROUP); DSM_PARSING_FUNC_MULTIPLE_DUP_DEF(newsgroup_web_site, PACKAGE_MAX_NEWSGROUP); DSM_PARSING_FUNC_MULTIPLE_DUP_DEF(newsgroup_ftp_site, PACKAGE_MAX_NEWSGROUP); DSM_PARSING_FUNC_COPY_DEF(simtelnet_path); DSM_PARSING_FUNC_MULTIPLE_DUP_DEF(zip, PACKAGE_MAX_ARCHIVE); DSM_PARSING_FUNC_DUP_DEF(zip_options); DSM_PARSING_FUNC_MULTIPLE_DUP_DEF(tar_gzip, PACKAGE_MAX_ARCHIVE); DSM_PARSING_FUNC_DUP_DEF(tar_gzip_options); DSM_PARSING_FUNC_DUP_DEF(changelog); DSM_PARSING_FUNC_DUP_DEF(pre_install_readme); DSM_PARSING_FUNC_DUP_DEF(post_install_readme); DSM_PARSING_FUNC_DUP_DEF(pre_uninstall_readme); DSM_PARSING_FUNC_DUP_DEF(post_uninstall_readme); /* Built-in scripting. */ DSM_PARSING_FUNC_DUP_DEF(builtin_pre_install_script); DSM_PARSING_FUNC_DUP_DEF(builtin_post_install_script); DSM_PARSING_FUNC_DUP_DEF(builtin_pre_uninstall_script); DSM_PARSING_FUNC_DUP_DEF(builtin_post_uninstall_script); DSM_PARSING_FUNC_DUP_DEF(pre_install_script); DSM_PARSING_FUNC_DUP_DEF(post_install_script); DSM_PARSING_FUNC_DUP_DEF(pre_uninstall_script); DSM_PARSING_FUNC_DUP_DEF(post_uninstall_script); DSM_PARSING_FUNC_MULTIPLE_DUP_DEF(keep_file, PACKAGE_MAX_KEEP_FILE); DSM_PARSING_FUNC_DUP_DEF(prefix); /* Dependency functions are defined individually */ DSM_PARSING_FUNC_DUP_DEF(install_warning); /* Function table for parsing */ typedef struct { char *directive; int (*parse)(const char *directive, const char *str, PACKAGE_INFO *package); } DSM_PARSER; #define DSM_PARSING_FUNC_ENTRY(p, q) { p, dsm_parse_##q } DSM_PARSER dsm_parser[] = { DSM_PARSING_FUNC_ENTRY("dsm-author", dsm_author), DSM_PARSING_FUNC_ENTRY("dsm-author-email", dsm_author_email), DSM_PARSING_FUNC_ENTRY("dsm-author-im", dsm_author_im), DSM_PARSING_FUNC_ENTRY("dsm-author-web-site", dsm_author_web_site), DSM_PARSING_FUNC_ENTRY("dsm-author-ftp-site", dsm_author_ftp_site), DSM_PARSING_FUNC_ENTRY("dsm-name", dsm_name), { "dsm-file-version", dsm_parse_dsm_file_version, }, { "dsm-version", dsm_parse_dsm_version }, DSM_PARSING_FUNC_ENTRY("binaries-dsm", binaries_dsm), DSM_PARSING_FUNC_ENTRY("sources-dsm", sources_dsm), DSM_PARSING_FUNC_ENTRY("documentation-dsm", documentation_dsm), DSM_PARSING_FUNC_ENTRY("name", name), DSM_PARSING_FUNC_ENTRY("manifest", manifest), { "version", dsm_parse_package_version }, { "type", dsm_parse_type }, { "dsm-type", dsm_parse_type }, /* 'dsm-type' is now a synonym for 'type' */ { "short-description", dsm_parse_short_description }, { "long-description", dsm_parse_long_description }, DSM_PARSING_FUNC_ENTRY("license", license), DSM_PARSING_FUNC_ENTRY("organisation", organisation), DSM_PARSING_FUNC_ENTRY("author", author), DSM_PARSING_FUNC_ENTRY("author-email", author_email), DSM_PARSING_FUNC_ENTRY("author-im", author_im), DSM_PARSING_FUNC_ENTRY("web-site", web_site), DSM_PARSING_FUNC_ENTRY("ftp-site", ftp_site), DSM_PARSING_FUNC_ENTRY("maintainer", maintainer), DSM_PARSING_FUNC_ENTRY("maintainer-email", maintainer_email), DSM_PARSING_FUNC_ENTRY("maintainer-im", maintainer_im), DSM_PARSING_FUNC_ENTRY("maintainer-web-site", maintainer_web_site), DSM_PARSING_FUNC_ENTRY("maintainer-ftp-site", maintainer_ftp_site), DSM_PARSING_FUNC_ENTRY("porter", porter), DSM_PARSING_FUNC_ENTRY("porter-email", porter_email), DSM_PARSING_FUNC_ENTRY("porter-im", porter_im), DSM_PARSING_FUNC_ENTRY("porting-web-site", porting_web_site), DSM_PARSING_FUNC_ENTRY("porting-ftp-site", porting_ftp_site), DSM_PARSING_FUNC_ENTRY("mailing-list", mailing_list), DSM_PARSING_FUNC_ENTRY("mailing-list-description", mailing_list_description), DSM_PARSING_FUNC_ENTRY("mailing-list-request", mailing_list_request), DSM_PARSING_FUNC_ENTRY("mailing-list-administrator", mailing_list_administrator), DSM_PARSING_FUNC_ENTRY("mailing-list-administrator-email", mailing_list_administrator_email), DSM_PARSING_FUNC_ENTRY("mailing-list-administrator-im", mailing_list_administrator_im), DSM_PARSING_FUNC_ENTRY("mailing-list-web-site", mailing_list_web_site), DSM_PARSING_FUNC_ENTRY("mailing-list-ftp-site", mailing_list_ftp_site), DSM_PARSING_FUNC_ENTRY("newsgroup", newsgroup), DSM_PARSING_FUNC_ENTRY("newsgroup-description", newsgroup), DSM_PARSING_FUNC_ENTRY("newsgroup-email-gateway", newsgroup_email_gateway), DSM_PARSING_FUNC_ENTRY("newsgroup-administrator", newsgroup_administrator), DSM_PARSING_FUNC_ENTRY("newsgroup-administrator-email", newsgroup_administrator_email), DSM_PARSING_FUNC_ENTRY("newsgroup-administrator-im", newsgroup_administrator_im), DSM_PARSING_FUNC_ENTRY("newsgroup-web-site", newsgroup_web_site), DSM_PARSING_FUNC_ENTRY("newsgroup-ftp-site", newsgroup_ftp_site), DSM_PARSING_FUNC_ENTRY("simtelnet-path", simtelnet_path), DSM_PARSING_FUNC_ENTRY("zip", zip), DSM_PARSING_FUNC_ENTRY("zip-options", zip_options), DSM_PARSING_FUNC_ENTRY("tar-gzip", tar_gzip), DSM_PARSING_FUNC_ENTRY("tar-gzip-options", tar_gzip_options), DSM_PARSING_FUNC_ENTRY("changelog", changelog), DSM_PARSING_FUNC_ENTRY("pre-install-readme", pre_install_readme), DSM_PARSING_FUNC_ENTRY("post-install-readme", post_install_readme), DSM_PARSING_FUNC_ENTRY("pre-uninstall-readme", pre_uninstall_readme), DSM_PARSING_FUNC_ENTRY("post-uninstall-readme", post_uninstall_readme), /* Built-in scripting */ DSM_PARSING_FUNC_ENTRY("builtin-pre-install-script", builtin_pre_install_script), DSM_PARSING_FUNC_ENTRY("builtin-post-install-script", builtin_post_install_script), DSM_PARSING_FUNC_ENTRY("builtin-pre-uninstall-script", builtin_pre_uninstall_script), DSM_PARSING_FUNC_ENTRY("builtin-post-uninstall-script", builtin_post_uninstall_script), DSM_PARSING_FUNC_ENTRY("pre-install-script", pre_install_script), DSM_PARSING_FUNC_ENTRY("post-install-script", post_install_script), DSM_PARSING_FUNC_ENTRY("pre-uninstall-script", pre_uninstall_script), DSM_PARSING_FUNC_ENTRY("post-uninstall-script", post_uninstall_script), DSM_PARSING_FUNC_ENTRY("keep-file", keep_file), DSM_PARSING_FUNC_ENTRY("prefix", prefix), DSM_PARSING_FUNC_ENTRY("requires", deps), DSM_PARSING_FUNC_ENTRY("depends-on", deps), DSM_PARSING_FUNC_ENTRY("conflicts-with", deps), DSM_PARSING_FUNC_ENTRY("replaces", deps), DSM_PARSING_FUNC_ENTRY("provides", deps), { "duplicate-action", dsm_parse_duplicate_action }, DSM_PARSING_FUNC_ENTRY("install-before", deps), DSM_PARSING_FUNC_ENTRY("install-after", deps), DSM_PARSING_FUNC_ENTRY("install-warning", install_warning), { NULL, NULL } }; /* ---------------------- * - dsm_parse_operator - * ---------------------- */ int dsm_parse_operator (const char *str) { if ( (strcmp(str, "==") == 0) || (strcmp(str, "=") == 0)) return(DEP_OP_EQUAL); if (strcmp(str, "<=") == 0) return(DEP_OP_LESSEQUAL); if (strcmp(str, ">=") == 0) return(DEP_OP_GREATEREQUAL); if (strcmp(str, "!=") == 0) return(DEP_OP_NOTEQUAL); if (strcmp(str, "<") == 0) return(DEP_OP_LESS); if (strcmp(str, ">") == 0) return(DEP_OP_GREATER); return(DEP_OP_NONE); } /* ------------------ * - dsm_parse_deps - * ------------------ */ int dsm_parse_deps (const char *directive, const char *str, PACKAGE_INFO *package) { PACKAGE_DEP *dep = NULL; char *buf = strdup(str); char *p = NULL, *q = NULL, *r = NULL; int i, found, len, ret; PACKAGE_DEP **deps = package->deps; for (found = -1, i = 0; i < PACKAGE_MAX_DEP; i++) { if (deps[i] == NULL) { found = i; break; } } if (found == -1) { free(buf); return(0); } /* Allocate memory */ dep = deps[found] = calloc(1, sizeof(PACKAGE_DEP)); if (dep == NULL) return (DSM_INTERNAL_ERROR); /* Determine dependency type */ /* There is no error checking there; if we are here, the directive * must be valid. */ if (!strcmp(directive, "requires")) dep->dep_type = PACKAGE_DEP_REQUIRES; else if (!strcmp(directive, "depends-on")) dep->dep_type = PACKAGE_DEP_DEPENDS_ON; else if (!strcmp(directive, "conflicts-with")) dep->dep_type = PACKAGE_DEP_CONFLICTS_WITH; else if (!strcmp(directive, "replaces")) dep->dep_type = PACKAGE_DEP_REPLACES; else if (!strcmp(directive, "provides")) dep->dep_type = PACKAGE_DEP_PROVIDES; else if (!strcmp(directive, "install-before")) dep->dep_type = PACKAGE_DEP_INSTALL_BEFORE; else /* if (!strcmp(directive, "install-after")) */ dep->dep_type = PACKAGE_DEP_INSTALL_AFTER; /* If there is a qualifier, store it & chop it off the end. */ r = strchr(str, ':'); if (r != NULL) { *r = '\0', r++; for (; isspace(*r) && (*r != '\0'); r++) {;} dep->qualifier = strdup(r); } /* Make p point to the operator, if any */ for (p = buf; !isspace(*p) && (*p != '\0'); p++) {;} if (*p == '\0') { p = NULL; } else { *p = '\0'; p++; } /* Make q point to the version, if any */ q = NULL; if (p != NULL) { for (q = p; !isspace(*q) && (*q != '\0'); q++) {;} if (*q != '\0') { *q = '\0'; q++; } } len = sizeof(dep->name); strncpy(dep->name, buf, len); dep->name[len - 1] = '\0'; /* Handle default operator & version */ if ( (p != NULL) && (dsm_parse_operator(p) == DEP_OP_NONE) ) { /* This indicates that p contains a version number, which should be in q. */ q = p; p = NULL; } if (p != NULL) { dep->op = dsm_parse_operator(p); } else { if (q != NULL) dep->op = DEP_OP_EQUAL; else dep->op = DEP_OP_NONE; } if (q != NULL) { ret = dsm_parse_version(directive, q, &dep->version); } else { /* Can't have an operator without a version */ if (p != NULL) { ret = DSM_BAD_DIRECTIVE; } else { dep->op = DEP_OP_NONE; /* => all versions */ ret = DSM_OK; } } /* For now, set the pointer to dependendant package to NULL. */ /* This is about to change by packlist_xref(). */ dep->dep = NULL; free(buf); /* Success? */ if (ret != DSM_OK) { free(dep); deps[found] = NULL; } return( (ret == DSM_OK) ? DSM_OK : DSM_BAD_DIRECTIVE ); } /* --------------------- * - atoi_wild_wrapper - * --------------------- */ /* This is like atoi(), but will return the special wildcard version numbers, * if given a wildcard. It also does not allow negative version numbers. */ const static int ATOI_WRAPPER_ERROR = -1; const static int ATOI_WRAPPER_SINGLE = -2; const static int ATOI_WRAPPER_MULTI = -3; static int atoi_wild_wrapper (const char *str) { int n; /* '?' wildcard */ if (strncmp(str, DSM_COMP_SINGLE_WILDCARD, strlen(DSM_COMP_SINGLE_WILDCARD)) == 0) return(ATOI_WRAPPER_SINGLE); /* '*' wildcard */ if (strncmp(str, DSM_COMP_MULTI_WILDCARD, strlen(DSM_COMP_MULTI_WILDCARD)) == 0) return(ATOI_WRAPPER_MULTI); /* Just a plain old number */ n = atoi(str); if (n < 0) return(ATOI_WRAPPER_ERROR); else return(n); } /* ---------------- * - atoi_wrapper - * ---------------- */ /* This is like atoi(), but does not allow negative version numbers. */ static int atoi_wrapper (const char *str) { int n; n = atoi(str); if (n < 0) return(ATOI_WRAPPER_ERROR); else return(n); } /* ----------------------------- * - wildcard_remaining_fields - * ----------------------------- */ static int wildcard_remaining_fields (PACKAGE_VERSION *ver) { if (!ver->has_major) { ver->has_major = 1; ver->major = PACKAGE_VERSION_WILDCARD; } if (!ver->has_minor) { ver->has_minor = 1; ver->minor = PACKAGE_VERSION_WILDCARD; } if (!ver->has_subminor) { ver->has_subminor = 1; ver->subminor = PACKAGE_VERSION_WILDCARD; } if (!ver->has_subsubminor) { ver->has_subsubminor = 1; ver->subsubminor = PACKAGE_VERSION_WILDCARD; } if (!ver->has_alpha) { ver->has_alpha = 1; ver->alpha = PACKAGE_VERSION_WILDCARD; } if (!ver->has_beta) { ver->has_beta = 1; ver->beta = PACKAGE_VERSION_WILDCARD; } if (!ver->has_pre) { ver->has_pre = 1; ver->pre = PACKAGE_VERSION_WILDCARD; } if (!ver->has_revision) { ver->has_revision = 1; ver->revision = PACKAGE_VERSION_WILDCARD; } if (!ver->has_patchlevel) { ver->has_patchlevel = 1; ver->patchlevel = PACKAGE_VERSION_WILDCARD; } if (!ver->has_snapshot) { ver->has_snapshot = 1; ver->snapshot_year = PACKAGE_VERSION_WILDCARD; ver->snapshot_month = PACKAGE_VERSION_WILDCARD; ver->snapshot_day = PACKAGE_VERSION_WILDCARD; } if (!ver->has_release) { ver->has_release = 1; ver->release = PACKAGE_VERSION_WILDCARD; } /* OK */ return(1); } /* ------------------------------ * - dsm_parse_version_internal - * ------------------------------ */ /* TODO: Are wildcards handled in all cases? */ static int dsm_parse_version_internal (const char *str, const int allow_wildcard, PACKAGE_VERSION *ver) { char *buf = strdup(str); char *p = buf; char *q = NULL; char snapshot_year[5], snapshot_month[3], snapshot_day[3]; int i, ret, n, n_l0, count; int (*my_atoi)(const char *); /* Zero the package version fields apart from the type (since that's * parsed separately), in case there are multiple 'version' * directives in a file. It's not legal to have multiple 'version' * directives, but we should cope properly. Assume the last 'version' * directive is the one wanted. */ /* ASSUMPTION: 'ver' has been zeroed and contains valid either none * or some valid version information. */ if (!ver->has_type) ver->has_version = 0; if (ver->has_platform) { /* Free strings, to avoid memory leak. */ if (ver->platform_cpu) { free(ver->platform_cpu); ver->platform_cpu = NULL; } if (ver->platform_manufacturer) { free(ver->platform_manufacturer); ver->platform_manufacturer = NULL; } if (ver->platform_kernel) { free(ver->platform_kernel); ver->platform_kernel = NULL; } if (ver->platform_os) { free(ver->platform_os); ver->platform_os = NULL; } } ver->has_major = ver->has_minor = ver->has_subminor = ver->has_subsubminor = ver->has_alpha = ver->has_beta = ver->has_pre = ver->has_revision = ver->has_patchlevel = ver->has_snapshot = ver->has_release = ver->has_platform = 0; ver->major = ver->minor = ver->minor_l0 = ver->subminor = ver->subminor_l0 = ver->subsubminor = ver->subsubminor_l0 = ver->alpha = ver->beta = ver->pre = ver->revision = ver->patchlevel = ver->snapshot_year = ver->snapshot_month = ver->snapshot_day = ver->release = 0; /* Use the appropriate function for converting version numbers. If we're * using wildcards, then use the modified atoi() that understands them. */ if (allow_wildcard) my_atoi = atoi_wild_wrapper; else my_atoi = atoi_wrapper; if (buf == NULL) return(DSM_INTERNAL_ERROR); strlwr(buf); /* Parse the period-separated version numbers */ for (i = 0, p = strtok(buf, "."); ; i++, p = strtok(NULL, ".")) { if (p == NULL) break; /* Make sure we have a digit (or, optionally, a wildcard) */ if (!isdigit(*p)) { int ok; ok = allow_wildcard; ok &= ( (strncmp(p, DSM_COMP_SINGLE_WILDCARD, strlen(DSM_COMP_SINGLE_WILDCARD)) == 0) || (strncmp(p, DSM_COMP_MULTI_WILDCARD, strlen(DSM_COMP_MULTI_WILDCARD)) == 0)); if (!ok) { free(buf); return(DSM_BAD_VERSION_FORMAT); } } /* Convert version component & count its leading zeroes, whether or not * we use that count. */ ret = my_atoi(p); n_l0 = count_l0(p); if (ret >= 0) { n = ret; } else if (ret == ATOI_WRAPPER_SINGLE) { n = PACKAGE_VERSION_WILDCARD; } else if (ret == ATOI_WRAPPER_MULTI) { /* Wildcard remaining fields */ /* TODO: This could be more sophisticated - it could stop wildcarding * at the first not-wildcarded field after the wildcard, e.g. * 2.* alpha 1. But how likely is this kind of format to be used? */ wildcard_remaining_fields(ver); free(buf), buf = NULL; /* OK, I know what you're thinking...ugh, gotos. */ goto dsm_parse_version_checks; } else { /* Probably n == ATOI_WRAPPER_ERROR */ free(buf); return(DSM_BAD_VERSION_FORMAT); } switch(i) { case 0: ver->has_major = 1; ver->major = n; break; case 1: ver->has_minor = 1; ver->minor = n; ver->minor_l0 = n_l0; break; case 2: ver->has_subminor = 1; ver->subminor = n; ver->subminor_l0 = n_l0; break; case 3: ver->has_subsubminor = 1; ver->subsubminor = n; ver->subsubminor_l0 = n_l0; break; default: free(buf); return(DSM_BAD_VERSION_FORMAT); } } /* buf has been torn up by strtok(), so use str from here on. */ free(buf), buf = NULL; /* Alpha version */ p = strstr(str, DSM_COMP_ALPHA); if (p != NULL) { for (p += strlen(DSM_COMP_ALPHA); (*p != '\0') && isspace(*p); p++) {;} ver->has_alpha = 1; ver->alpha = my_atoi(p); } /* Beta version */ p = strstr(str, DSM_COMP_BETA); if (p != NULL) { for (p += strlen(DSM_COMP_BETA); (*p != '\0') && isspace(*p); p++) {;} ver->has_beta = 1; ver->beta = my_atoi(p); } /* Pre */ p = strstr(str, DSM_COMP_PRE); if (p != NULL) { for (p += strlen(DSM_COMP_PRE); (*p != '\0') && isspace(*p); p++) {;} ver->has_pre = 1; ver->pre = my_atoi(p); } /* Revision */ p = strstr(str, DSM_COMP_REVISION); if (p != NULL) { for (p += strlen(DSM_COMP_REVISION); (*p != '\0') && isspace(*p); p++) {;} ver->has_revision = 1; ver->revision = my_atoi(p); } /* Patchlevel */ p = strstr(str, DSM_COMP_PATCHLEVEL); if (p != NULL) { for (p += strlen(DSM_COMP_PATCHLEVEL); (*p != '\0') && isspace(*p); p++) {;} ver->has_patchlevel = 1; ver->patchlevel = my_atoi(p); } /* Snapshot */ p = strstr(str, DSM_COMP_SNAPSHOT); if (p != NULL) { for (p += strlen(DSM_COMP_SNAPSHOT); (*p != '\0') && isspace(*p); p++) {;} if (strlen(p) >= 8) { /* TODO: Support wildcards => need hyphens in date? */ /* TODO: Error condition? */ ver->has_snapshot = 1; strncpy(snapshot_year, p, 4); strncpy(snapshot_month, p + 4, 2); strncpy(snapshot_day, p + 6, 2); snapshot_year[4] = '\0'; snapshot_month[2] = '\0'; snapshot_day[2] = '\0'; ver->snapshot_year = atoi(snapshot_year); ver->snapshot_month = atoi(snapshot_month); ver->snapshot_day = atoi(snapshot_day); } } /* Release */ p = strstr(str, DSM_COMP_RELEASE); if (p != NULL) { for (p += strlen(DSM_COMP_RELEASE); (*p != '\0') && isspace(*p); p++) {;} ver->has_release = 1; ver->release = my_atoi(p); } /* Platform */ /* TODO: Make this cope with regexps */ p = strstr(str, DSM_COMP_PLATFORM); if (p != NULL) { for (p += strlen(DSM_COMP_PLATFORM); (*p != '\0') && isspace(*p); p++) {;} /* Count the number of dash delimiters */ for (q = p, count = 0; !isspace(*q) && (*q != '\0'); q++) { if (*q == '-') count++; } if ((count == 2) || (count == 3)) { /* Chop up the platform by turning the hyphens to * nuls, strdup'ing portions and then placing the * hyphens back. */ q = strchr(p, '-'), *q = '\0'; ver->platform_cpu = strdup(p); *q = '-', p = q, p++; q = strchr(p, '-'), *q = '\0'; ver->platform_manufacturer = strdup(p); *q = '-', p = q, p++; if (count == 3) { q = strchr(p, '-'), *q = '\0'; ver->platform_kernel = strdup(p); *q = '-', p = q, p++; } /* TODO: This should detect the end by looking for * whitespace. */ ver->platform_os = strdup(p); ver->has_platform = 1; } } dsm_parse_version_checks: /* Any version info present? */ if ( ver->has_major || ver->has_minor || ver->has_subminor || ver->has_subsubminor || ver->has_alpha || ver->has_beta || ver->has_pre || ver->has_revision || ver->has_patchlevel || ver->has_snapshot || ver->has_release || ver->has_platform) ver->has_version = 1; /* Sanity checks */ /* It can't be an alpha, beta and pre-release simultaneously. */ count = (ver->has_alpha != 0) && (ver->alpha != PACKAGE_VERSION_WILDCARD); count += (ver->has_beta != 0) && (ver->beta != PACKAGE_VERSION_WILDCARD); count += (ver->has_pre != 0) && (ver->pre != PACKAGE_VERSION_WILDCARD); if (count > 1) { /* TODO: Slight more descriptive error */ return(DSM_BAD_VERSION_FORMAT); } /* It can't be a pre-release and release simultaneously. */ count = (ver->has_pre != 0) && (ver->pre != PACKAGE_VERSION_WILDCARD); count += (ver->has_release != 0) && (ver->release != PACKAGE_VERSION_WILDCARD); if (count > 1) { /* TODO: Slight more descriptive error */ return(DSM_BAD_VERSION_FORMAT); } /* OK */ return(DSM_OK); } /* --------------------- * - dsm_parse_version - * --------------------- */ /* This is just an inferface to dsm_parse_version_internal(), but does not * allow wildcards. */ int dsm_parse_version (const char *directive, const char *str, PACKAGE_VERSION *ver) { return(dsm_parse_version_internal(str, 0, ver)); } /* ------------------------------ * - dsm_parse_wildcard_version - * ------------------------------ */ /* This is just an inferface to dsm_parse_version_internal(), but does * allow wildcards. This is intended for use in user-interfacing code, * e.g. code that accepts the package name and version. */ int dsm_parse_wildcard_version (const char *str, PACKAGE_VERSION *ver) { return(dsm_parse_version_internal(str, 1, ver)); } /* Simple wrapper functions */ int dsm_parse_dsm_file_version (const char *directive, const char *str, PACKAGE_INFO *package) { return(dsm_parse_version(directive, str, &package->dsm_file_version)); } int dsm_parse_dsm_version (const char *directive, const char *str, PACKAGE_INFO *package) { /* our_ver is a PACKAGE_VERSION version of DSM_VERSION_*, so we can * compare it with dsm-version parsed from a DSM. */ static PACKAGE_VERSION our_ver = { /* has_* fields */ 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* data fields */ DSM_VERSION_MAJOR, DSM_VERSION_MINOR, 0, DSM_VERSION_SUBMINOR, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL, 0 }; int ret = 0; /* Parse the DSM's version */ ret = dsm_parse_version(directive, str, &package->dsm_version); if (ret != DSM_OK) return(ret); /* Do we support this version? */ if (package_vercmp(&our_ver, &package->dsm_version) < 0) { /* Nope */ return(DSM_NOT_UNDERSTOOD); } return(DSM_OK); } int dsm_parse_package_version (const char *directive, const char *str, PACKAGE_INFO *package) { return(dsm_parse_version(directive, str, &package->version)); } /* --------------------------- * - dsm_parse_type_internal - * --------------------------- */ int dsm_parse_type_internal (const char *str, const int allow_wildcard, PACKAGE_INFO *package) { int ret = DSM_OK; package->version.has_type = 1; /* Match the type case-insensitively. */ if (strcasecmp(str, DSM_COMP_BINARIES) == 0) { package->version.type = TYPE_BINARIES; } else if (strcasecmp(str, DSM_COMP_SOURCES) == 0) { package->version.type = TYPE_SOURCES; } else if (strcasecmp(str, DSM_COMP_DOCUMENTATION) == 0) { package->version.type = TYPE_DOCUMENTATION; } else if (strcasecmp(str, DSM_COMP_GROUP) == 0) { package->version.type = TYPE_GROUP; } else if (strcasecmp(str, DSM_COMP_VIRTUAL) == 0) { package->version.type = TYPE_VIRTUAL; } else if ( allow_wildcard && ( (strcasecmp(str, DSM_COMP_SINGLE_WILDCARD) == 0) || (strcasecmp(str, DSM_COMP_MULTI_WILDCARD) == 0))) { package->version.type = TYPE_WILDCARD; } else { package->version.has_type = 0; package->version.type = TYPE_NONE; ret = DSM_BAD_TYPE; } /* Type => we have some version information */ if (package->version.has_type) package->version.has_version = 1; return(ret); } /* ------------------ * - dsm_parse_type - * ------------------ */ /* This is just an inferface to dsm_parse_type_internal(), but does not * allow wildcards. */ int dsm_parse_type (const char *directive, const char *str, PACKAGE_INFO *package) { return(dsm_parse_type_internal(str, 0, package)); } /* --------------------------- * - dsm_parse_wildcard_type - * --------------------------- */ /* This is just an inferface to dsm_parse_version_internal(), but does * allow wildcards. This is intended for use in user-interfacing code, * e.g. code that accepts the package name and version. */ int dsm_parse_wildcard_type (const char *directive, const char *str, PACKAGE_INFO *package) { return(dsm_parse_type_internal(str, 1, package)); } /* ---------------------- * - dsm_parse_multiple - * ---------------------- */ /* This handles directives that occur multiple times in a generic manner. * 'max' specifies the maximum index that should be used into 'aa'. */ int dsm_parse_multiple (const char *directive, const char *str, char **aa, const int max) { int i, found; for (found = -1, i = 0; i < max; i++) { if (aa[i] == NULL) { found = i; break; } } if (found == -1) { /* There are too many items already. */ return(DSM_TOO_MANY); } aa[found] = strdup(str); return(DSM_OK); } /* ------------------------------ * - dsm_parse_duplicate_action - * ------------------------------ */ int dsm_parse_duplicate_action (const char *directive, const char *str, PACKAGE_INFO *package) { char *buf = strdup(str); strlwr(buf); if (strcasecmp(str, "replace") == 0) package->duplicate_action = DUP_REPLACE; else if (strcasecmp(str, "keep") == 0) package->duplicate_action = DUP_KEEP; else if (strcasecmp(str, "backup") == 0) package->duplicate_action = DUP_BACKUP; else if (strcasecmp(str, "skip") == 0) package->duplicate_action = DUP_SKIP; else /* Default to querying */ package->duplicate_action = DUP_QUERY; free(buf); return(DSM_OK); } /* ------------------------------- * - dsm_parse_short_description - * ------------------------------- */ int dsm_parse_short_description (const char *directive, const char *str, PACKAGE_INFO *package) { size_t s = sizeof(package->short_description); strncpy(package->short_description, str, s); package->short_description[s - 1] = '\0'; return(dsm_parse_escapes(package->short_description)); } /* ------------------------------- * - dsm_parse_short_description - * ------------------------------- */ int dsm_parse_long_description (const char *directive, const char *str, PACKAGE_INFO *package) { package->long_description = strdup(str); return(dsm_parse_escapes(package->long_description)); } /* --------------------- * - dsm_parse_escapes - * --------------------- */ /* This parses escape characters in str, according to the DSM spec. It assumes * that the escape characters expand to less characters than the actual * escape. It works directly on str. */ int dsm_parse_escapes (const char *str) { char esc = 0; /* Current escape character */ char repl = 0; /* Replacement character */ const char *head = str; /* Start of the string */ char *p = NULL; /* Current "tail" portion */ for (p = strchr(head, '\\'); p != NULL; p = strchr(p, '\\')) { /* Which escape is it? */ esc = *(p + 1); switch(esc) { case 'n': repl = '\n'; break; case 't': repl = '\t'; break; case '\\': repl = '\\'; break; /* Unknown escape => barf */ default: return(DSM_BAD_ESCAPE); break; } /* Now do the replacement. */ *p = repl; *(p + 1) = '\0'; /* Terminate for safety */ if (strlen(p + 2) > 0) memmove(p + 1, p + 2, strlen(p + 2)); } return(DSM_OK); } /* ---------------------- * - allocate_dsm_error - * ---------------------- */ /* Allocate a DSM error structure and set up default values. */ static DSM_ERROR * allocate_dsm_error (void) { DSM_ERROR *err; err = calloc(1, sizeof(*err)); if (err == NULL) return(NULL); /* All errors are fatal, by default. */ err->fatal = 1; return(err); } /* ---------------------------- * - required_directive_error - * ---------------------------- */ static DSM_ERROR * required_directive_error (const char *d) { DSM_ERROR *err; err = allocate_dsm_error(); if (err == NULL) return(NULL); err->ret = DSM_REQUIRED_DIRECTIVE; err->directive = strdup(d); return(err); } /* ------------------- * - bad_value_error - * ------------------- */ static DSM_ERROR * bad_value_error (const char *d, const char *msg) { DSM_ERROR *err = NULL; err = allocate_dsm_error(); if (err == NULL) return(NULL); err->ret = DSM_BAD_VALUE; err->directive = strdup(d); err->data = strdup(msg); return(err); } /* -------------- * - file_error - * -------------- */ /* Generate an error for a file-related problem. */ static DSM_ERROR * file_error (const int ret, const char *msg) { DSM_ERROR *err = NULL; err = allocate_dsm_error(); if (err == NULL) return(NULL); err->ret = ret; if (msg != NULL) err->data = strdup(msg); return(err); } /* ------------------------------- * - conflicting_directive_error - * ------------------------------- */ static DSM_ERROR * conflicting_directive_error (const char *d, const char *msg) { DSM_ERROR *err = NULL; err = allocate_dsm_error(); if (err == NULL) return(NULL); err->ret = DSM_CONFLICTING_DIRECTIVE; err->directive = strdup(d); err->data = strdup(msg); return(err); } /* ----------------------------- * - suggested_directive_error - * ----------------------------- */ static DSM_ERROR * suggested_directive_error (const char *d) { DSM_ERROR *err = NULL; err = allocate_dsm_error(); if (err == NULL) return(NULL); err->ret = DSM_SUGGESTED_DIRECTIVE; err->directive = strdup(d); return(err); } /* ------------- * - dsm_check - * ------------- */ /* * This function checks that the DSM contains the required fields. * It also checks that the fields are not conflicting. If everything is OK, * 1 is returned. Otherwise, 0 is returned. */ int dsm_check (PACKAGE_INFO *package, DSM_ERROR **de) { DSM_ERROR *err = NULL; /* Error info, for analysis of problem. */ DSM_ERROR *ferr = NULL; /* First error info struct, head of list. */ DSM_ERROR *lerr = NULL; /* Last error info struct, end of list. */ int ok = 1; /* !ok, if required field missing. */ /* Need an error list pointer */ if (de == NULL) return(0); /* Set up ferr, lerr */ if (*de != NULL) { ferr = *de; for (lerr = *de; lerr->q_forw != NULL; lerr = lerr->q_forw) {;} } /* dsm-author */ if (package->dsm_author == NULL) { err = required_directive_error("dsm-author"); if (err != NULL) { if (lerr != NULL) { insque((struct qelem *) err, (struct qelem *) lerr); } else { ferr = err; } lerr = err; } ok = 0; } /* dsm-file-version */ if (!package->dsm_file_version.has_version) { err = required_directive_error("dsm-file-version"); if (err != NULL) { if (lerr != NULL) { insque((struct qelem *) err, (struct qelem *) lerr); } else { ferr = err; } lerr = err; } ok = 0; } /* dsm-version */ if (!package->dsm_version.has_version) { err = required_directive_error("dsm-version"); if (err != NULL) { if (lerr != NULL) { insque((struct qelem *) err, (struct qelem *) lerr); } else { ferr = err; } lerr = err; } ok = 0; } /* dsm-name */ if (strlen(package->dsm_name) == 0) { err = required_directive_error("dsm-name"); if (err != NULL) { if (lerr != NULL) { insque((struct qelem *) err, (struct qelem *) lerr); } else { ferr = err; } lerr = err; } ok = 0; } /* type */ if (!package->version.has_type || (package->version.type == TYPE_NONE)) { err = required_directive_error("type"); if (err != NULL) { if (lerr != NULL) { insque((struct qelem *) err, (struct qelem *) lerr); } else { ferr = err; } lerr = err; } ok = 0; } /* name */ if (strlen(package->name) == 0) { err = required_directive_error("name"); if (err != NULL) { if (lerr != NULL) { insque((struct qelem *) err, (struct qelem *) lerr); } else { ferr = err; } lerr = err; } ok = 0; } /* version */ if (!package->version.has_version) { err = required_directive_error("version"); if (err != NULL) { if (lerr != NULL) { insque((struct qelem *) err, (struct qelem *) lerr); } else { ferr = err; } lerr = err; } ok = 0; } /* manifest */ if ( (strlen(package->manifest) == 0) && package->version.has_type && ( (package->version.type == TYPE_BINARIES) || (package->version.type == TYPE_DOCUMENTATION) || (package->version.type == TYPE_SOURCES))) { err = required_directive_error("manifest"); if (err != NULL) { if (lerr != NULL) { insque((struct qelem *) err, (struct qelem *) lerr); } else { ferr = err; } lerr = err; } ok = 0; } /* binaries-dsm, sources-dsm, documentation-dsm should not contain * a '.dsm' extension. */ if ( (package->binaries_dsm != NULL) && (strchr(package->binaries_dsm, '.') != NULL)) { err = bad_value_error("binaries-dsm", "file extension present"); if (err != NULL) { if (lerr != NULL) { insque((struct qelem *) err, (struct qelem *) lerr); } else { ferr = err; } lerr = err; } ok = 0; } if ( (package->sources_dsm != NULL) && (strchr(package->sources_dsm, '.') != NULL)) { err = bad_value_error("sources-dsm", "file extension present"); if (err != NULL) { if (lerr != NULL) { insque((struct qelem *) err, (struct qelem *) lerr); } else { ferr = err; } lerr = err; } ok = 0; } if ( (package->documentation_dsm != NULL) && (strchr(package->documentation_dsm, '.') != NULL)) { err = bad_value_error("documentation-dsm", "file extension present"); if (err != NULL) { if (lerr != NULL) { insque((struct qelem *) err, (struct qelem *) lerr); } else { ferr = err; } lerr = err; } ok = 0; } /* short-description */ if (strlen(package->short_description) == 0) { err = required_directive_error("short-description"); if (err != NULL) { if (lerr != NULL) { insque((struct qelem *) err, (struct qelem *) lerr); } else { ferr = err; } lerr = err; } ok = 0; } /* zip */ /* TODO: Change this when tar-gzip is supported. */ if ( (package->zip[0] == NULL) && (package->version.type != TYPE_VIRTUAL) && (package->version.type != TYPE_GROUP) ) { err = required_directive_error("zip"); if (err != NULL) { if (lerr != NULL) { insque((struct qelem *) err, (struct qelem *) lerr); } else { ferr = err; } lerr = err; } ok = 0; } /* If this is a binaries package, binaries-dsm should be empty. */ if ( (package->version.type == TYPE_BINARIES) && (package->binaries_dsm != NULL)) { err = conflicting_directive_error("binaries-dsm", "It should not be specified " "for a binaries package."); if (err != NULL) { if (lerr != NULL) { insque((struct qelem *) err, (struct qelem *) lerr); } else { ferr = err; } lerr = err; /* This should just be a warning. */ err->fatal = 0; } /*ok = 0;*/ } /* If this is a sources package, sources-dsm should be empty. */ if ( (package->version.type == TYPE_SOURCES) && (package->sources_dsm != NULL)) { err = conflicting_directive_error("sources-dsm", "It should not be specified " "for a sources package."); if (err != NULL) { if (lerr != NULL) { insque((struct qelem *) err, (struct qelem *) lerr); } else { ferr = err; } lerr = err; /* This should just be a warning. */ err->fatal = 0; } /*ok = 0;*/ } /* If this is a documentation package, documentation-dsm should be empty. */ if ( (package->version.type == TYPE_DOCUMENTATION) && (package->documentation_dsm != NULL)) { err = conflicting_directive_error("documentation-dsm", "It should not be specified " "for a documentation package."); if (err != NULL) { if (lerr != NULL) { insque((struct qelem *) err, (struct qelem *) lerr); } else { ferr = err; } lerr = err; /* This should just be a warning. */ err->fatal = 0; } /*ok = 0;*/ } /* Suggest that the 'license' directive should be present. */ if (package->license == NULL) { err = suggested_directive_error("license"); if (err != NULL) { if (lerr != NULL) { insque((struct qelem *) err, (struct qelem *) lerr); } else { ferr = err; } lerr = err; /* This should just be a warning. */ err->fatal = 0; } /*ok = 0;*/ } /* TODO: Check that we have the same or less *-email as the corresponding * * fields (e.g. maintainer-email vs. maintainer. If not, generate * a warning. */ /* If there was no list before, point it at the new one. */ if (*de == NULL) *de = ferr; if (ok) return(1); return(0); } /* ------------- * - dsm_parse - * ------------- */ /* * This parses the DSM text stored in the buffer 'buf' and places the parsed * data into the package info structure 'package'. A copy of 'buf' for * parsing. * * On success DSM_OK is returned. On failure any parsed data in 'package' * is freed, 'de' contains a list of errors and DSM_FAILED is returned. */ int dsm_parse (const char *buf, PACKAGE_INFO *package, DSM_ERROR **de) { char *dsm = NULL; /* Don't stamp all over buf! */ char *p = NULL; char *q = NULL; char *next = NULL; char *end = NULL; DSM_ERROR *err = NULL; /* Error info, for analysis of problem. */ DSM_ERROR *ferr = NULL; /* First error info struct, head of list. */ DSM_ERROR *lerr = NULL; /* Last error info struct, end of list. */ PACKAGE_DEP **deps = NULL; int i, parsed, ret, line_number, next_line_number, line_continued; int found = 0; /* Directive parser found flag = 1=y, 0=n */ memset(package, 0, sizeof(PACKAGE_INFO)); package_set_defaults(package); dsm = strdup(buf); if (dsm == NULL) return(DSM_INTERNAL_ERROR); /* Set the parse success flag. Succeed by default. */ parsed = 1; /* TODO: This code is a big hack. Rewrite it so there is an array * pointing to the start of each line. Then line continutation can be * handled nicely. The code will be clearer too. */ /* The following code is a little nasty. The line separator is CR, LF * or CRLF. */ for (end = dsm + strlen(dsm), next = p = dsm, next_line_number = line_number = 1; p < end; p = next, line_number = next_line_number) { /* Support line continuation character - '\' - and perform some * character conversions, e.g. '\t' -> ' '. */ for (line_continued = 0, q = p; q < end; q++) { if (*q == '\\') { switch(*(q + 1)) { case '\n': case '\r': *q = ' '; q++; line_continued = 1; break; default: /* Let the escaping code handle it. */ break; } } /* Convert all tabs to spaces. */ if (*q == '\t') *q = ' '; while ((*q == '\n') || (*q == '\r')) { if (line_continued) { /* This avoids adding unwanted whitespace to the description by * jiggling the memory about to nuke the CRs & LFs. */ /* TODO: Well, it would, if it worked. :( */ memmove((void *) q, (void *) (q + 1), strlen(q + 1) + 1); end--; } else { break; } } /* Non-whitespace character indicates that * the continuation character wasn't. */ if (!isspace(*q)) line_continued = 0; } /* Need to keep track now, before the following code stamps * on the buffer. */ while ((*next != '\r') && (*next != '\n') && (*next != '\0') ) { next++; } /* TODO: This causes problems with the line numbering - two or more lines * can be skipped, but will not be counted. */ while ((*next == '\r') || (*next == '\n')) { /* Eat CRs in CRLFs */ if ((*next == '\r') && (*(next + 1) == '\n')) { *next = '\0'; next++; } *next = '\0'; next++; next_line_number++; } /* Pre-process the line - remove whitespaces @ start & end */ while (isspace(*p)) { p++; } rtrim(p); /* Comment or a blank line? */ if ((*p == '#') || (*p == '\0')) continue; /* Split the line in two so that p contains the directive name and q contains the value. */ q = strchr(p, ':'); if (q == NULL) { ret = DSM_BAD_DIRECTIVE; } else if (strcmp(q, ":") == 0) { /* Skip empty directives */ /* TODO: Generate warning */ ret = DSM_OK; } else { *q = '\0', q++; while (isspace(*q)) { q++; } strlwr(p); /* Parse the directive appropriately */ for (ret = DSM_OK, found = i = 0; dsm_parser[i].directive != NULL; i++) { if (strcmp(p, dsm_parser[i].directive) == 0) { /* Use the appropriate parser. */ found = 1; ret = dsm_parser[i].parse(p, q, package); break; } } /* Parsing failed? */ if (!found) ret = DSM_UNKNOWN_DIRECTIVE; } /* Parsing failed, so store info in DSM error * structure. */ if (ret != DSM_OK) { err = allocate_dsm_error(); if (err == NULL) break; if (lerr != NULL) { /* If we have a queue, tag this on the end. */ insque((struct qelem *) err, (struct qelem *) lerr); } else { /* This entry is the queue head. */ ferr = err; } err->ret = ret; err->line_number = line_number; err->directive = strdup(p); if (q != NULL) err->data = strdup(q); lerr = err; parsed = 0; } } /* Tidy up */ free(dsm), dsm = NULL; /* - Now check if we have all required fields. - */ if (!dsm_check(package, &ferr)) parsed = 0; /* If the dependency has no type in its version, default to matching * a binary package. */ /* TODO: This is a HACK! Fix it by specifying that dependencies * can have types in the DSM specification. */ for (deps = package->deps; *deps != NULL; deps++) { if ((*deps)->version.has_version && !(*deps)->version.has_type) { (*deps)->version.has_type = 1; (*deps)->version.type = TYPE_BINARIES; } } /* Processing done - successful? */ if (parsed) { /* The package has a valid DSM. */ package->has_dsm = 1; /* Set other internal flags. */ package->visited = 0; } /* Pass on any errors. Note that there may be some non-fatal errors, * when parsing is successful. */ *de = ferr; /* Parsing failed => free data in package structure. */ if (!parsed) package_free(package); /* OK */ return(parsed ? DSM_OK : DSM_FAILED); } /* ---------------- * - dsm_load_dir - * ---------------- */ /* This parses all the DSMs that can found in the 'dir'. If 'packages' is * non-null, then any parsed packages will be added to the package list. * Otherwise, a new list will created. The package list is returned on * success, else NULL. */ PACKAGE_INFO * dsm_load_dir (const char *dir, PACKAGE_INFO *packages, DSM_FILE_ERROR **dfe) { PACKAGE_INFO *list = packages; PACKAGE_INFO *newpackage = NULL; char mypath[PATH_MAX], dsmfile[PATH_MAX]; DSM_FILE_ERROR *dsm_flist = *dfe; /* Err's in parsing of all files. */ DSM_FILE_ERROR *dsm_ferr = NULL; /* Struct with file parse errors. */ DSM_ERROR *dsm_err = NULL; /* Err's in parsing for file. */ char *dsm = NULL; DIR *d = NULL; struct dirent *de = NULL; int ret; /* Check params */ if (dir == NULL) return(list); /* Format the path name for later */ strcpy(mypath, dir); addforwardslash(mypath); d = opendir(mypath); if (!d) return (list); while ( (de = readdir(d)) != NULL ) { /* Suitable file name? */ if (strcmp(de->d_name, ".") == 0) continue; if (strcmp(de->d_name, "..") == 0) continue; if (!isdsm(de->d_name)) continue; if (strstr(de->d_name, ".dsm") == de->d_name) continue; /* Set up the new package info struct. */ newpackage = calloc(sizeof(*newpackage), 1); if (newpackage == NULL) { warn("Unable to allocate memory!"); continue; } /* Parse the package file */ strcpy(dsmfile, mypath); strcat(dsmfile, de->d_name); dsm = read_text_file_to_memory(dsmfile); dsm_err = NULL; if (dsm != NULL) { ret = dsm_parse(dsm, newpackage, &dsm_err); } else { /* Blank DSM => warning */ dsm_err = allocate_dsm_error(); if (dsm_err == NULL) { warn("Unable to allocate memory!"); /* Tidy up */ free(newpackage), newpackage = NULL; continue; } ret = dsm_err->ret = DSM_FAILED; } free(dsm), dsm = NULL; /* Store any errors with associated file in list. */ if (dsm_err != NULL) { dsm_ferr = calloc(1, sizeof(*dsm_ferr)); if (dsm_ferr == NULL) { warn("Unable to allocate memory!"); /* Tidy up */ package_free(newpackage); free(newpackage), newpackage = NULL; continue; } dsm_ferr->name = strdup(de->d_name); dsm_ferr->de = dsm_err; if (dsm_flist == NULL) { /* Create list. */ *dfe = dsm_flist = dsm_ferr; (*dfe)->q_forw = NULL; (*dfe)->q_back = NULL; } else { insque((struct qelem *) dsm_ferr, (struct qelem *) dsm_flist); } } /* If the parsing failed, skip this package. */ if (ret != DSM_OK) { /* Tidy up */ package_free(newpackage); free(newpackage), newpackage = NULL; continue; } /* 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); } /* ---------------- * - dsm_load_all - * ---------------- */ /* This parses all the DSMs that can found in the array of paths 'path'. If * 'packages' is non-null, then any parsed packages will be added to the * package list. Otherwise, a new list will created. The package list is * returned on success, else NULL. */ PACKAGE_INFO * dsm_load_all (const char **path, PACKAGE_INFO *packages, DSM_FILE_ERROR **dfe) { int i; PACKAGE_INFO * list = packages; /* Check params */ if (path == NULL) return list; /* Go through all the spec'd directories looking for '.dsm's */ for (i = 0; path[i] != NULL; i++) { list = dsm_load_dir(path[i], list, dfe); } return(list); } /* -------------------- * - dsm_errors_fatal - * -------------------- */ /* If any of the errors in 'de' are fatal, return 1, else 0. */ int dsm_errors_fatal (const DSM_ERROR *de) { const DSM_ERROR *nde; for (nde = de; nde != NULL; nde = nde->q_forw) { if (de->fatal) return(1); } return(0); } /* ------------------------- * - dsm_file_errors_fatal - * ------------------------- */ /* If any of the errors in 'dfe' are fatal, return 1, else 0. */ int dsm_file_errors_fatal (const DSM_FILE_ERROR *dfe) { const DSM_FILE_ERROR *ndfe; for (ndfe = dfe; ndfe != NULL; ndfe = ndfe->q_forw) { if (dsm_errors_fatal(ndfe->de)) return(1); } return(0); } /* -------------- * - dsm_perror - * -------------- */ /* This function formats and displays an error message appropriate to the * passed error structure. */ void dsm_perror (const char *str, const DSM_ERROR *de) { if (de == NULL) return; switch(de->ret) { default: fprintf(stderr, "%s: Line %d: Unknown error\n", str, de->line_number); break; case DSM_FAILED: fprintf(stderr, "%s: Failed\n", str); break; case DSM_OK: fprintf(stderr, "%s: No error\n", str); break; case DSM_NONEXISTENT: fprintf(stderr, "%s: '%s' does not exist\n", str, str); break; case DSM_EMPTY: fprintf(stderr, "%s: Empty\n", str); break; case DSM_ARCHIVE_HAS_NO_DSM: fprintf(stderr, "%s: '%s' does not contain a DSM\n", str, str); break; case DSM_BAD_DIRECTIVE: fprintf(stderr, "%s: Line %d: Bad directive '%s'\n", str, de->line_number, de->directive); break; case DSM_UNKNOWN_DIRECTIVE: fprintf(stderr, "%s: Line %d: Unknown directive '%s'\n", str, de->line_number, de->directive); break; case DSM_REQUIRED_DIRECTIVE: fprintf(stderr, "%s: Required directive '%s' missing\n", str, de->directive); break; case DSM_TOO_MANY: fprintf(stderr, "%s: Line %d: Too many occurrences of '%s' - array full\n", str, de->line_number, de->directive); break; case DSM_CONFLICTING_DIRECTIVE: fprintf(stderr, "%s: Conflicting directive '%s': %s\n", str, de->directive, de->data); break; case DSM_SUGGESTED_DIRECTIVE: fprintf(stderr, "%s: Suggest directive '%s' should be added\n", str, de->directive); break; case DSM_BAD_VERSION_FORMAT: fprintf(stderr, "%s: Line %d: Bad version format '%s' for directive '%s'\n", str, de->line_number, de->data, de->directive); break; case DSM_BAD_TYPE: fprintf(stderr, "%s: Line %d: Bad type '%s' for directive '%s'\n", str, de->line_number, de->data, de->directive); break; case DSM_BAD_ESCAPE: fprintf(stderr, "%s: Line %d: Bad escape in '%s' for directive '%s'\n", str, de->line_number, de->data, de->directive); break; case DSM_BAD_VALUE: fprintf(stderr, "%s: Bad value for directive '%s': %s\n", str, de->directive, de->data); break; case DSM_BAD_ARCHIVE: fprintf(stderr, "%s: Bad archive\n", str); break; case DSM_PACKAGE_UNSUPPORTED: fprintf(stderr, "%s: Package unsupported\n", str); break; case DSM_TAR_GZ_UNSUPPORTED: fprintf(stderr, "%s: .tar.gz packages unsupported\n", str); break; case DSM_TAR_BZ2_UNSUPPORTED: fprintf(stderr, "%s: .tar.bz2 packages unsupported\n", str); break; case DSM_URL_UNSUPPORTED: fprintf(stderr, "%s: URLs unsupported\n", str); break; case DSM_NOT_UNDERSTOOD: fprintf(stderr, "%s: Line %d: DSM specification version '%s' is not understood\n" "%s: Perhaps you need to upgrade your packaging program?\n", str, de->line_number, de->data, str); break; case DSM_INTERNAL_ERROR: fprintf(stderr, "%s: Line %d: Internal error (directive='%s',data='%s')\n", str, de->line_number, de->directive, de->data); break; } } /* ----------------------- * - dsm_free_error_list - * ----------------------- */ /* This frees the error data returned by dsm_parse(). */ int dsm_free_error_list (const DSM_ERROR *de) { DSM_ERROR *h = (DSM_ERROR *) de; /* List head pointer */ DSM_ERROR *p = NULL, *n = NULL; /* Current and next pointers */ /* Simple case */ if (de == NULL) return(1); /* Traverse the linked list to free all child data. */ for (p = h; p != NULL; p = p->q_forw) { free(p->directive), p->directive = NULL; free(p->data), p->data = NULL; } /* Remove all the list elements. */ for (p = h; p != NULL; p = n) { n = p->q_forw; /* Save next pointer */ remque((struct qelem *) p); /* Remove from queue */ free(p); } return(1); } /* ---------------------------- * - dsm_free_file_error_list - * ---------------------------- */ /* This is similar to dsm_free_error_list(), except that each error list is * associated with the file that caused the error. There is only slightly * more work to be done. */ int dsm_free_file_error_list (const DSM_FILE_ERROR *dfe) { DSM_FILE_ERROR *h = (DSM_FILE_ERROR *) dfe; /* List head pointer */ DSM_FILE_ERROR *p = NULL, *n = NULL; /* Current and next pointers */ /* Simple case */ if (dfe == NULL) return(1); /* Traverse the linked list to free all child data. */ for (p = h; p != NULL; p = p->q_forw) { free(p->name), p->name = NULL; dsm_free_error_list(p->de), p->de = NULL; } /* Remove all the list elements. */ for (p = h; p != NULL; p = n) { n = p->q_forw; /* Save next pointer */ remque((struct qelem *) p); /* Remove from queue */ free(p); } return(1); } /* --------------------- * - dsm_get_and_parse - * --------------------- */ /* * This function gets and parses DSM from all supported places - DSM file, * ZIP package, etc. 'where' specifies the source file (DSM, ZIP, etc.); * 'package' is where the parsed information is placed. If the caller wishes * to have a copy of the DSM text, 'dsm' should point to a char pointer - * this will be updated to point to the DSM text. 'de' should to point to * a DSM_ERROR pointer, which will be updated to point to a list of errors. * * On success DSM_OK is returned. Otherwise one of the other DSM_* error * codes is returned. */ int dsm_get_and_parse (const char *where, PACKAGE_INFO *package, char **dsm, DSM_ERROR **de) { char ** a_toc = NULL; char * dsm_text = NULL; int i; char * p = NULL; int ret; /* Is 'where' a directory or special file? */ if ((isdir(where) > 0) || isspecialpath(where)) { *de = file_error(DSM_PACKAGE_UNSUPPORTED, NULL); return DSM_PACKAGE_UNSUPPORTED; } /* Does 'where' exist? */ if (access(where, R_OK) != 0) { *de = file_error(DSM_NONEXISTENT, NULL); return DSM_NONEXISTENT; } /* Try to recognize what did we get - if we can't, we have * an unsupported package type. */ if (!(isdsm(where) || isarchive(where) || isurl(where))) { *de = file_error(DSM_PACKAGE_UNSUPPORTED, NULL); return DSM_PACKAGE_UNSUPPORTED; } /* We don't support .tar.gz, .tar.bz2 or URL yet. */ if (istargz(where)) { *de = file_error(DSM_TAR_GZ_UNSUPPORTED, NULL); return DSM_TAR_GZ_UNSUPPORTED; } if (istarbz2(where)) { *de = file_error(DSM_TAR_BZ2_UNSUPPORTED, NULL); return DSM_TAR_BZ2_UNSUPPORTED; } if (isurl(where)) { *de = file_error(DSM_URL_UNSUPPORTED, NULL); return DSM_URL_UNSUPPORTED; } /* Plain DSM file */ if (isdsm(where)) { /* Read the DSM into a buffer. */ dsm_text = read_text_file_to_memory(where); if (!dsm_text) { *de = file_error(DSM_NONEXISTENT, NULL); return DSM_NONEXISTENT; } } else if (isarchive(where)) { /* Find the DSM in the archive and read it into a buffer. * Look for the DSM in the root of the archive or in the * 'manifest/' directory. */ a_toc = archive_get_toc(where); if (!a_toc) { *de = file_error(DSM_BAD_ARCHIVE, NULL); return DSM_BAD_ARCHIVE; } for (i = 0; a_toc[i] != NULL; i++) { /* Skip non-DSMs, directories */ if (!isdsm(a_toc[i]) || ends_with_slash(a_toc[i])) continue; if (strchr(a_toc[i], '/') != NULL) { /* Manifest directory? */ if (strncasecmp(a_toc[i], "manifest/", 9) != 0) continue; p = a_toc[i] + 9; if (strchr(p, '/') != NULL) continue; } dsm_text = archive_extract_text_file_to_memory(where, a_toc[i]); break; } /* Free the TOC */ for (i = 0; a_toc[i] != NULL; i++) { free(a_toc[i]); } free(a_toc), a_toc = NULL; /* Found DSM? */ if (!dsm_text) { *de = file_error(DSM_ARCHIVE_HAS_NO_DSM, NULL); return DSM_ARCHIVE_HAS_NO_DSM; } } /* No text in DSM? */ if (dsm_text == NULL) { *de = file_error(DSM_NONEXISTENT, NULL); return DSM_NONEXISTENT; } /* Now parse the DSM */ ret = dsm_parse(dsm_text, package, de); /* If the caller is interested, return DSM text. */ if ((ret == DSM_OK) && (dsm != NULL)) *dsm = dsm_text; else free(dsm_text); return(ret); }