/*****************************************************************************/
/* Copyright (c) 2008 NXP B.V.                                               */
/*                                                                           */
/* 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, using version 2 of the License.             */
/*                                                                           */
/* 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         */
/* along with this program; if not, write to the Free Software               */
/* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307       */
/* USA.                                                                      */
/*                                                                           */
/*****************************************************************************/
/**
    @file   tmmanifest.c
    @brief  Manifest information handler

@b  Application:  tmmanifest
*//*

Rev Date        Author      Comments
--------------------------------------------------------------------------------
  1 20080519    batelaan    Initial
  2 20080625    batelaan    Handle shared libraries and executables.
  3 20080725    batelaan    Correctly implement sorted output.
  4 20080915    batelaan    Fix compilation warnings
  5 20090327    batelaan    Use strrchr iso rindex for uclibc.

--------------------------------------------------------------------------------
    For consistency and standardisation retain the Section Separators.
*/

/***********************************************
* INCLUDE FILES                                *
************************************************/

#include "tmmanifest.h"
#include <assert.h>
#include <malloc.h>
#include "xml.h"
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

/***********************************************
* LOCAL MACROS                                 *
* recommendation only: <MODULE><_WORD>+        *
************************************************/

#define INDENT_SIZE 2

/******************************************************************
* LOCAL TYPEDEFS                                                  *
* recommendation only:                [p]phStbTemplate_<Word>+_t  *
*******************************************************************/

/******************************************************************
* STATIC FUNCTION PROTOTYPES                  <Module>_<Word>+    *
*******************************************************************/

static int do_path(const char *path, unsigned int level);

/******************************************************************
* STATIC DATA                  g[k|p|kp|pk|kpk]<Module>_<Word>+   *
*******************************************************************/

/** Filename extensions indicating kernel modules.
    @see do_file()
*/
static const char *kernel_module_extensions[] = {
    ".ko",
    NULL
};

/** Filename extensions indicating firmware files.
    @see do_file()
*/
static const char *firmware_extensions[] = {
    ".fw",
    NULL
};

/** Filename extensions indicating libraries.
    @see do_file()
*/
static const char *lib_extensions[] = {
    ".so",
    NULL
};

/** Command line parsing options definitions.
    @see main().
*/
static struct option options[] =
{
	{"author", 0, 0, 'a'},
	{"description", 0, 0, 'd'},
	{"license", 0, 0, 'l'},
	{"parameters", 0, 0, 'p'},
	{"filename", 0, 0, 'n'},
	{"debug", 0, 0, 'd'},
	{"help", 0, 0, 'h'},
	{"null", 0, 0, '0'},
	{"field", required_argument, 0, 'F'},
	{"recurse", 0, 0, 'r'},
	{"modinfo", 0, 0, 'm'},
	{"nosort", 0, 0, 'S'},
	{0, 0, 0, 0}
};

/** Memory allocation control using efficient object stack allocation. */
struct obstack gtmmanifest_Obstack;

/** Translates from ::tmmanifest_FileType_t to its name. */
const char *gtmmanifest_FileType_Names[tmmanifest_FileType_Count] = {
    "app",
    "firmware",
    "kmodule",
    "lib"
};

/** Translates from ::tmmanifest_FileType_t to its name in plural. */
const char *gtmmanifest_FileType_PluralNames[tmmanifest_FileType_Count] = {
    "apps",
    "firmwares",
    "kmodules",
    "libs"
};

/** List of info objects for each ::tmmanifest_FileType_t. */
tmmanifest_FileInfo_t* gtmmanifest_FileInfo[tmmanifest_FileType_Count];

/** Count of info objects for each ::tmmanifest_FileType_t. */
unsigned int gtmmanifest_FileInfoCount[tmmanifest_FileType_Count];

/**************************************************************************
* EXPORTED DATA                     g[k|p|kp|pk|kpk]phStbTemplate_<Word>+ *
***************************************************************************/

/** Name of program (argv[0]).
    @see main()
    */
char *pgm_name;
/** Output separator character of use with modinfo style output. */
char opt_sep = '\n';
/** Name of field to print with modinfo style output. */
char *opt_field = NULL;
/** Recurse on all arguments. */
bool opt_recurse = true;
/** Output traditional modinfo format, instead of XML manifest format. */
bool opt_modinfo = false;
/** Print lots of debug messages. */
bool opt_debug = false;
/** Don't sort manifest output entries. */
bool opt_nosort = false;
/** Utility for use in indent_string(). */
char indent_buffer[] =
    "                                                                                ";
/** Max indentation from indent_string(). */
size_t max_indent = ((sizeof(indent_buffer) - 1) / INDENT_SIZE);

/*******************************************************************************
* FUNCTION IMPLEMENTATION  <Module>[_<Word>+] for static functions             *
*                          ph[<layer>]<Module>[_<Word>+] for exported functions*
********************************************************************************/

/******************************************************************************/
/** Like malloc, but prints message and aborts program. */
void * xmalloc (size_t size)
{
    register void *value = malloc (size);
    DEBUGPRINT("xmalloc(%u)\n", size);
    if (value == NULL)
    {
        fprintf(stderr, "%s: virtual memory exhausted\n", pgm_name);
        abort();
    }
    return value;
}

/******************************************************************************/
/** Returns name of file (part after last slash).
    Points into original argument string.
 */
const char *file_name(const char *path)
{
    const char * slash = strrchr(path, '/');
    if (slash == NULL)
    {
        return path;
    }
    else
    {
        return slash + 1;
    }
}

/******************************************************************************/
/** Returns name of file (part after last slash) without extension (last dot).
    Result allocated using xmalloc().
 */
char* file_basename(const char *path)
{
    const char *start;
    const char *end;

    // Find last slash.
    start = strrchr(path, '/');
    if (start == NULL)
    {
        start = path;
    }
    else
    {
        start++; // point after "/"
    }

    // Find last dot.
    end = strrchr(start, '.');
    if (end == NULL) end = path + strlen(path);
    unsigned int len = end - start;
    char *result = xmalloc(len + 1);
    strncpy(result, start, len);
    result[len] = '\0';
    return result;
}

/******************************************************************************/
/** Return a string with number of spaces indicated by level. */
const char *indent_string(unsigned int level)
{
    if (level >= max_indent)
    {
        return indent_buffer;
    }
    return &indent_buffer[(max_indent - level) * INDENT_SIZE];
}

/******************************************************************************/
/** Print program usage information. */
static void usage(const char *text)
{
    if (text)
    {
        fprintf(stderr, "\n%s\n", text);
    }
    fprintf(stderr,
            "Usage: %s [-S] [-0] [-F field] [-r] [-m|x] path...\n"
            " Prints out manifest information about one or more libraries, module(s), directories etc.\n"
            " If a fieldname is given, just print out that field (or nothing if not found).\n"
            " Otherwise, print all information out in a readable form.\n"
            " If -r is given, recurse.\n"
            " If -m is given, mimick modinfo program.\n"
            " If -0 is given, separate with nul, not newline.\n"
            " If -S is given, do not sort manifest entries.\n"
            " if -D is given, print out debug info\n",
            pgm_name);
}


/******************************************************************************/
/** Allocate and fill a ::tmmanifest_BuildInfo_t. */
tmmanifest_BuildInfo_t * tmmanifest_AllocBuildInfo(
    const char *productversion,
    const char *user,
    const char *host,
    const char *date,
    const char *time)
{
    DEBUGPRINT("tmmanifest_AllocBuildInfo %s %s %s %s %s\n",
               productversion, user, host, date, time);
    tmmanifest_BuildInfo_t *p = (tmmanifest_BuildInfo_t *)obstack_alloc(
        &gtmmanifest_Obstack, sizeof(*p));
    if (productversion == NULL) productversion = "?";
    if (user == NULL) user = "?";
    if (host == NULL) host = "?";
    if (date == NULL) date = "?";
    if (time == NULL) time = "?";
    p->productversion = (const char*)obstack_copy0(&gtmmanifest_Obstack, productversion, strlen(productversion));
    p->user = (const char*)obstack_copy0(&gtmmanifest_Obstack, user, strlen(user));
    p->host = (const char*)obstack_copy0(&gtmmanifest_Obstack, host, strlen(host));
    p->date = (const char*)obstack_copy0(&gtmmanifest_Obstack, date, strlen(date));
    p->time = (const char*)obstack_copy0(&gtmmanifest_Obstack, time, strlen(time));
    return p;
}

/******************************************************************************/
/** Allocate and fill a ::tmmanifest_FileInfo_t.
 */
tmmanifest_FileInfo_t * tmmanifest_AllocFileInfo(
    const char *name,
    const char *path,
    tmmanifest_BuildInfo_t *buildInfo)
{
    struct stat statdata;
    int ret;

    DEBUGPRINT("tmmanifest_AllocFileInfo name=%s path=%s\n",
               path, name);
    tmmanifest_FileInfo_t *p = (tmmanifest_FileInfo_t *)obstack_alloc(
        &gtmmanifest_Obstack, sizeof(*p));

    p->buildInfo = buildInfo;

    if (path == NULL) path = "?";
    if (name == NULL) name = "?";
    p->path = (const char*)obstack_copy0(&gtmmanifest_Obstack, path, strlen(path));
    p->name = (const char*)obstack_copy0(&gtmmanifest_Obstack, name, strlen(name));

    ret = lstat(path, &statdata);
    p->size = (ret == 0) ? statdata.st_size : 0;

    p->next = NULL;
    DEBUGPRINT("tmmanifest_AllocFileInfo => %p\n", p);
    return p;
}

/******************************************************************************/
/** Test if path has one of the specified extensions.
    @param path The path to test.
    @param extensions NULL terminated array of extensions (usually with dot,
    e.g. ".so").
*/
bool has_extension(const char *path, const char **extensions)
{
    size_t len = strlen(path);
    while (*extensions)
    {
        const char *extension = *extensions;
        size_t ext_len = strlen(extension);
        if (len >= ext_len)
        {
            if (strcmp(path + len - ext_len, extension) == 0)
            {
                return true;
            }
        }
        extensions++;
    }
    return false;
}

/******************************************************************************/
/** Extract manifest information for a firmware file. */
static int firmware_extract(const char *path, tmmanifest_FileInfo_t **ppManifestInfo)
{
    char *name = file_basename(path);
    *ppManifestInfo = tmmanifest_AllocFileInfo(
        name,
        path,
        tmmanifest_AllocBuildInfo(
            NULL, // productversion
            NULL, // user
            NULL, // host
            NULL, // date
            NULL)); // time
    return 0;
}

/******************************************************************************/
/** Try to extract manifest information for an application file.
    Note that application files should indicate they are ELF executables.
    Otherwise an error is returned.
*/
static int app_extract(const char *path, tmmanifest_FileInfo_t **ppManifestInfo)
{
    // check that first 4 bytes of file contain 0x7f "ELF"
    int fd;
    int ret = 0;
#define APP_FILE_HEADER "\177ELF"

    char * header[sizeof(APP_FILE_HEADER)];
    fd = open(path, O_RDONLY);
    if (fd < 0)
    {
        return 1;
    }
    if (read(fd, header, sizeof(APP_FILE_HEADER) - 1) != sizeof(APP_FILE_HEADER) - 1)
    {
        ret = 1;
    }
    close(fd);
    if (ret != 0)
    {
        return ret;
    }
    if (memcmp(APP_FILE_HEADER, header, sizeof(APP_FILE_HEADER) - 1) != 0)
    {
        return 1;
    }
    return modinfo_extract(path, ppManifestInfo);
}

/******************************************************************************/
/** Extract manifest information for a library. */
static int lib_extract(const char *path, tmmanifest_FileInfo_t **ppManifestInfo)
{
    const char *name;
    /* Some libs have an embedded ".so." in the filename,
       the the lib name becomes the bit after the last slash. */
    if (strstr(path, ".so."))
    {
        name = file_name(path);
    }
    else
    {
        name = file_basename(path);
    }
    return modinfo_extract(path, ppManifestInfo);
}

/******************************************************************************/
/** Try to extract manifest information for a file. */
static int do_file(const char *path, struct stat *statdata, unsigned int level)
{
    int ret = 0;
    /* initialise type variable solely to remove compilation warning.
       Variable is not used because ret value is checked. */
    tmmanifest_FileType_t type = tmmanifest_FileType_App;
    tmmanifest_FileInfo_t *pFileInfo = NULL;
    DEBUGPRINT("%sdo_file: %s\n", indent_string(level), path);
    if (has_extension(path, kernel_module_extensions) ||
        opt_modinfo)
    {
        type = tmmanifest_FileType_Kmodule;
        ret = modinfo_extract(path, &pFileInfo);
    }
    else
    {
        if (has_extension(path, firmware_extensions))
        {
            type = tmmanifest_FileType_Firmware;
            ret = firmware_extract(path, &pFileInfo);
        }
        else
        {
            if (has_extension(path, lib_extensions) || strstr(path, ".so."))
            {
                type = tmmanifest_FileType_Lib;
                ret = lib_extract(path, &pFileInfo);
            }
            else
            {
                if (statdata->st_mode & (S_IXUSR|S_IXGRP|S_IXOTH))
                {
                    type = tmmanifest_FileType_App;
                    ret = app_extract(path, &pFileInfo);
                }
            }
        }
    }

    if ((ret == 0) && (pFileInfo != NULL))
    {
        pFileInfo->next = gtmmanifest_FileInfo[type];
        gtmmanifest_FileInfo[type] = pFileInfo;
        gtmmanifest_FileInfoCount[type]++;
    }
    return ret;
}

/******************************************************************************/
#if 0
/** Sorts directories. Did not seem to work. */
static int file_selector(const struct dirent *dir)
{
    DEBUGPRINT("file_selector: %s\n", dir->d_name);
    if (strcmp(dir->d_name, ".") == 0 || strcmp(dir->d_name, "..") == 0)
    {
        return 0;
    }
    if (dir->d_type == DT_UNKNOWN)
    {
        abort();
    }
    else
    {
        return dir->d_type & ((opt_recurse ? DT_DIR : 0) | DT_REG);
    }
}
#endif

/******************************************************************************/
/** Process a path recursively. */
static int do_recurse(const char *path, unsigned int level)
{
    int ret = 0;
    unsigned int j = 0;
    struct dirent **namelist;
    size_t pathlen;
    unsigned int n;

    DEBUGPRINT("%sdo_recurse: %s\n", indent_string(level), path);
    ret = scandir(path, &namelist, NULL/*&file_selector*/, &alphasort);
    DEBUGPRINT("%sdo_recurse: %s; scandir => %d namelist=%p\n", indent_string(level), path, ret, namelist);
    if (ret <= 0 || namelist == NULL)
    {
        return ret;
    }

    pathlen = strlen(path);

    n = ret;
    for (j = 0; j < n; j++)
    {
        struct dirent *dir = namelist[j];
        if (strcmp(dir->d_name, ".") == 0 || strcmp(dir->d_name, "..") == 0)
        {
            continue;
        }
        char *subpath = xmalloc(pathlen + strlen(dir->d_name) + 2);
        strcpy(subpath, path);
        if (path[pathlen - 1] != '/')
        {
            strcpy(subpath + pathlen, "/");
            strcpy(subpath + pathlen + 1, dir->d_name);
        }
        else
        {
            strcpy(subpath + pathlen, dir->d_name);
        }
        do_path(subpath, level + 1);
    }
    free(namelist);
    return ret;
}

/******************************************************************************/
/** Process a path, possibly recursively.
    If path is not a filesystem object, it is treated as a kernel module name,
    only if ::opt_modinfo is true.
    Calls either:
    - do_file()
    - do_recurse()
    @see opt_recurse.
*/
static int do_path(const char *path, unsigned int level)
{
    int ret = 0;
    struct stat statdata;
    DEBUGPRINT("%sdo_path: %s\n", indent_string(level), path);

    /* See if the path should be skipped. */
    static const char* skip_paths[] = {
        "/dev",
        "/proc",
        "/sys",
        "/var",
        NULL};
    const char ** skip_path;
    const char *path_to_check_for_skipping = path;
    /* Treat paths like "./dev" as "/dev",
       in case the tool is run not on "/" but on ".": */
    if ((path[0] == '.') && (path[1] == '/'))
    {
        path_to_check_for_skipping++;
    }
    for (skip_path = &skip_paths[0]; *skip_path; skip_path++)
    {
        size_t len = strlen(*skip_path);
        if ((strncmp(path, *skip_path, len) == 0)
            && ((path[len] == 0) || path[len] == '/'))
        {
            DEBUGPRINT("%sSkipping path %s\n", indent_string(level), path);
            return 0;
        }
    }

    ret = lstat(path, &statdata);
    if (ret != 0)
    {
        if (opt_modinfo) {
            return do_file(path, &statdata, level + 1);
        }
        else
        {
            perror(path);
            return ret;
        }
    }

    if (S_ISLNK(statdata.st_mode))
    {
        return 0;
    }

    if (S_ISDIR(statdata.st_mode))
    {
        if (opt_recurse)
        {
            ret = do_recurse(path, level + 1);
        }
        else
        {
            fprintf(stderr, "%s: %s: is a directory. (Use -r option?)\n", pgm_name, path);
        }
    }
    else
    {
        if (S_ISREG(statdata.st_mode))
        {
            ret = do_file(path, &statdata, level + 1);
        }
        else
        {
            if (!opt_recurse) {
                fprintf(stderr, "%s: %s: Not a regular file!\n", pgm_name, path);
                ret = 1;
            }
        }
    }
    return ret;
}

/******************************************************************************/
/** Compare two ::tmmanifest_FileInfo_t structures. */
static int compareFileInfo(const void *x, const void *y)
{
#define SORT_RESULT(_ret) (-(_ret))

    tmmanifest_FileInfo_t *a = *(tmmanifest_FileInfo_t**)x;
    tmmanifest_FileInfo_t *b = *(tmmanifest_FileInfo_t**)y;
    int ret;
    DEBUGPRINT("compareFileInfo %p %p\n", a, b);

#define compareFileInfo_String(_a, _b)          \
    DEBUGPRINT("compareFileInfo_String %s (%p) %s (%p)\n", #_a, _a, #_b, _b); \
    compareFileInfo_Pointers(_a, _b); \
    ret = strcmp(_a, _b);             \
    if (ret != 0) return SORT_RESULT(ret);

#define compareFileInfo_Pointers(_a, _b)     \
    DEBUGPRINT("compareFileInfo_Pointers %s (%p) %s (%p)\n", #_a, _a, #_b, _b); \
    if (_a == NULL && _b != NULL) return SORT_RESULT(-1); \
    if (_a != NULL && _b == NULL) return SORT_RESULT(1); \
    if (_a == NULL && _b == NULL) return 0;

#define compareFileInfo_Values(_a, _b)            \
    DEBUGPRINT("compareFileInfo_Values %s %s\n", #_a, #_b); \
    ret = (_a < _b) ? -1 : ((_a > _b) ? 1 : 0);   \
    if (ret != 0) return SORT_RESULT(ret;

    compareFileInfo_Pointers(a, b);
    compareFileInfo_String(a->path, b->path);
    compareFileInfo_String(a->name, b->name);

    compareFileInfo_Pointers(a->buildInfo, b->buildInfo);
    compareFileInfo_String(a->buildInfo->productversion, b->buildInfo->productversion);
    compareFileInfo_String(a->buildInfo->user, b->buildInfo->user);
    compareFileInfo_String(a->buildInfo->host, b->buildInfo->host);
    compareFileInfo_String(a->buildInfo->date, b->buildInfo->date);
    compareFileInfo_String(a->buildInfo->time, b->buildInfo->time);
    DEBUGPRINT("compareFileInfo done %p %p\n", x, y);
    return 0;
}

/******************************************************************************/
/** Sort the list in ::gtmmanifest_FileInfo for the specified type. */
static void sortFileInfoItems(tmmanifest_FileType_t type)
{
    // this does not work. It crashes.
    unsigned int count = gtmmanifest_FileInfoCount[type];
    DEBUGPRINT("sortFileInfoItems %s (%u)\n", gtmmanifest_FileType_Names[type], count);
    if (count > 0)
    {
        unsigned int i;
        tmmanifest_FileInfo_t *fileInfoList = gtmmanifest_FileInfo[type];
        tmmanifest_FileInfo_t **fileInfoArray = (tmmanifest_FileInfo_t **)
            xmalloc(count * (sizeof(tmmanifest_FileInfo_t *)));
        i = 0;
        DEBUGPRINT("sortFileInfoItems: copy to array\n");
        while (fileInfoList != NULL)
        {
            fileInfoArray[i] = fileInfoList;
            fileInfoList = fileInfoList->next;
            i++;
        }
        assert(i == count);
        DEBUGPRINT("sortFileInfoItems: qsort\n");
        qsort(fileInfoArray,
              count,
              sizeof(tmmanifest_FileInfo_t *),
              &compareFileInfo);
        DEBUGPRINT("sortFileInfoItems: qsort done; copy to list\n");
        tmmanifest_FileInfo_t *last = NULL;
        for (i = 0; i < count; i++)
        {
            fileInfoArray[i]->next = last;
            last = fileInfoArray[i];
        }
        gtmmanifest_FileInfo[type] = last;
        free(fileInfoArray);
    }
}

/******************************************************************************/
/** Write XML for a build information structure. */
static void writeBuildInfo(
    tmmanifest_BuildInfo_t *buildInfo)
{
    xmlStartTag("buildinfo");
    xmlStringAttr("productversion", buildInfo->productversion);
    xmlStringAttr("user", buildInfo->user);
    xmlStringAttr("host", buildInfo->host);
    xmlStringAttr("date", buildInfo->date);
    xmlStringAttr("time", buildInfo->time);
    xmlEndTag("buildinfo");
}

/******************************************************************************/
/** Write XML for a file information structure. */
static void writeFileInfoItem(
    tmmanifest_FileType_t type,
    tmmanifest_FileInfo_t *fileInfo)
{
    DEBUGPRINT("writeFileInfoItem %s name=%s path=%s\n", gtmmanifest_FileType_Names[type], fileInfo->name, fileInfo->path);
    xmlStartTag(gtmmanifest_FileType_Names[type]);
    //xmlStartTag("fileinfo");
    xmlStringAttr("name", fileInfo->name);
    xmlStringAttr("path", fileInfo->path);
    xmlUIntAttr("size", fileInfo->size);
    writeBuildInfo(fileInfo->buildInfo);
    //xmlEndTag("fileinfo");
    xmlEndTag(gtmmanifest_FileType_Names[type]);
}

/******************************************************************************/
/** Write XML for a list of file information structures of a specified type. */
static void writeFileInfoItems(tmmanifest_FileType_t type)
{
    DEBUGPRINT("writeFileInfoItems %s\n", gtmmanifest_FileType_PluralNames[type]);
    xmlStartTag(gtmmanifest_FileType_PluralNames[type]);
    tmmanifest_FileInfo_t *fileInfoList = gtmmanifest_FileInfo[type];
    while (fileInfoList != NULL)
    {
        writeFileInfoItem(type, fileInfoList);
        fileInfoList = fileInfoList->next;
    }
    xmlEndTag(gtmmanifest_FileType_PluralNames[type]);
}

/******************************************************************************/
/** Write XML for all lists of file information structures. */
static void writeFileInfoItemLists(void)
{
    tmmanifest_FileType_t type;
    DEBUGPRINT("writeFileInfoItemLists\n");
    for (type = (tmmanifest_FileType_t)0; type < tmmanifest_FileType_Count; type++)
    {
        if (!opt_nosort)
        {
            sortFileInfoItems(type);
        }
        writeFileInfoItems(type);
    }
}

/******************************************************************************/
/** Write the whole XML manifest to stdout. */
static int write_xml_manifest(void)
{
    DEBUGPRINT("write_xml_manifest\n");
    xmlSetOutput(stdout);
    fputs("<!DOCTYPE manifest SYSTEM \"../manifest/manifest.dtd\">\n", stdout);
    fputs("<?xml-stylesheet type=\"text/xsl\" href=\"../manifest/manifest.xsl\"?>\n", stdout);
    xmlStartTag("manifest");
    writeFileInfoItemLists();
    xmlEndTag("manifest");
    fputc('\n', stdout);
    return 0;
}

/******************************************************************************/
/** The main program. */
int main(int argc, char *argv[])
{
    int opt, ret = 0;

    pgm_name = argv[0];
    while ((opt = getopt_long(argc,argv,"adlhmnprx0DF:S", options, NULL)) >= 0){
        switch (opt) {
            case 'h': usage(NULL); exit(0); break;
            case 'a': opt_field = "author"; break;
            case 'd': opt_field = "description"; break;
            case 'l': opt_field = "license"; break;
            case 'p': opt_field = "parm"; break;
            case 'n': opt_field = "filename"; break;
            case 'm': opt_modinfo = true; break;
            case 'r': opt_recurse = true; break;
            case 'D': opt_debug = true; break;
            case 'S': opt_nosort = true; break;
            case 'F': opt_field = optarg; break;
            case '0': opt_sep = '\0'; break;
            default:
                usage("Argument error");
                return 1;
        }
    }

    ret = obstack_init (&gtmmanifest_Obstack);
    if (ret != 1)
    {
        ret = 42;
        goto DEPARTURES;
    }

    ret = modinfo_init();
    if (ret != 0)
    {
        goto DEPARTURES;
    }

    if (argc < optind + 1)
    {
        usage("Insufficient arguments");
        return 1;
    }
    else
    {
        for (opt = optind; opt < argc; opt++) {
            int ret1 = do_path(argv[opt], 0);
            if (ret1) {
                ret = ret1;
            }
        }
    }

    if (!opt_modinfo)
    {
        ret = write_xml_manifest();
    }
  DEPARTURES:
    if (ret != 0)
    {
        fprintf(stderr, "**ERROR: %s returning %d\n", pgm_name, ret);
    }
    return ret;
}

/******************************************************************************/
