/* Directory scanning API.
   Rick Smereka, Copyright (C) 2001-2005.

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, get a copy via the Internet at
   http://gnu.org/copyleft/gpl.html or write to the Free Software
   Foundation, Inc., 59 Temple Place, Suite 330, Boston,
   MA 02111-1307 USA

   This directory scanning module/API will traverse one or more directories
   from a specific entry point matching file/directory names. This API is
   multi-platform. It will compile and run in under any OS that supports
   the POSIX 1003.1 directory system calls. This includes 16bit DOS, 32bit 
   Windows, QNX (both RtP and 4.x) and all versions of Linix/Unix. This API
   does not use recursion to traverse the directory tree and is not subject to 
   stack limitations and excessive memory consumption typical with recursive 
   algorithms.

   You can contact the author via email at rsmereka@future-lab.com

   Original version for Unix, QNX, DOS and Windows. Feb/2001, Rick Smereka

   Ported to Debian Linux. Nov/2002, Rick Smereka

   Changed all logging calls from 'log_file_date' to 'logman'.
   Mar/2004, Rick Smereka

   Modified function 'dscan_find' to use the 'lstat' function
   instead of 'stat'. Added detection of symbolic links
   API now returns 'SCAN_TYPE_SYMLINK' when a symbolic link is found.
   Added the global variable 'dscan_for_symlinks'. Added detection
   of the option 'l' for symbolic links. Symbolic link detection
   and return of this type is available only on the QNX/Linux/Unix
   platforms. Jan/2005, Rick Smereka */

#include "stdhead.h"
#include "dscan.h"

#define DSCAN_SUB_DELIM '*'

struct dscan_entry
   {
   DIR *dir;
   char *dir_path;
   char *subs;
   int sub_count;
   int sub_index;
   struct dscan_entry *next;
   };

/* private function prototypes */

static int dscan_find(char *);
static int dscan_sub(void);
static void dscan_add_sub(struct dscan_entry *, char *);
static int dscan_chop_path(void);
static int dscan_match(char *, char *);
static int dscan_umatch(char *, char *);
static int dscan_set_flags(char *);
static int dscan_set_current_path(char *);
static void dscan_ll_delete(char *);
static void dscan_ll_delete_all(void);
static int dscan_set_mvars(char *, char *);
static void dscan_delete_mvars(void);
static struct dscan_entry *dscan_ll_find(char *);
static struct dscan_entry *dscan_ll_add(char *);
static void dscan_header(char *);

/* module globals */

struct dscan_entry *dscan_list_head = NULL;
char *dscan_base_path = NULL;
char *dscan_current_path = NULL;
char *dscan_fname = NULL;
int dscan_for_files;
int dscan_for_dirs;

#ifdef OS_UNIX
int dscan_for_symlinks;
#endif

int dscan_descend;

int dscan_findfirst(char *path, char *fname, char *flags, char *pfname)
{
   /* Find the first occurance of the file 'fname' within the 'path'.
      The 'flags' can have any combination of 'fdls' where 'f' is
      scan for files, 'd' is scan for directories, 'l' is scan for
      symbolic links (QNX/Linux/Unix only) and 's' is descend into
      sub-directories. If an item is found, its full path
      and name is loaded into 'pfname'. Function returns a type
      code (file, directory or symlink) upon success. Function returns
      'FALSE' otherwise. */

   char mname[] = "dscan_findfirst";
   int ret;

   dscan_header(mname);

   if (path == (char *)NULL || !strlen(path))
      {
      logman("%s:exit[0]null or empty[path]", mname);
      return(FALSE);
      }

   if (fname == (char *)NULL || !strlen(fname))
      {
      logman("%s:exit[0]null or empty[fname]", mname);
      return(FALSE);
      }

   if (flags == (char *)NULL || !strlen(flags))
      {
      logman("%s:exit[0]null or empty[flags]", mname);
      return(FALSE);
      }

   if (pfname == (char *)NULL)
      {
      logman("%s:exit[0]null[pfname]", mname);
      return(FALSE);
      }

   if (!isdirectory(path))
      {
      logman("%s:%s is not a directory", mname, path);
      return(FALSE);
      }

   logman("%s:path=%s[%d],fname=%s[%d],flags=%s[%d]",
                 mname, path, strlen(path), fname, strlen(fname),
                 flags, strlen(flags));
   dscan_delete_mvars();

   if (!dscan_set_mvars(path, fname))
      {
      logman("%s:exit[0]bad rc[0] from dscan_set_mvars", mname);
      return(FALSE);
      }

   if (!dscan_set_flags(flags))
      {
      logman("%s:exit[0]bad rc[0] from dscan_set_flags", mname);
      return(FALSE);
      }

   if (dscan_list_head != NULL)
      {
      logman("%s:link list already exists, deleting", mname);
      dscan_ll_delete_all();
      }

   if (!dscan_set_current_path(dscan_base_path))
      {
      logman("%s:exit[0]bad rc[0] from dscan_set_current_path", mname);
      return(FALSE);
      }

   ret = dscan_findnext(pfname);
   logman("%s:exit[%d]:pfname=%s[%d]", mname, ret, pfname,
                 strlen(pfname));
   return(ret);
}

int dscan_findnext(char *pfname)
{
   /* Find the next occurance of the desired file/directory.
      'dscan_findfirst' must be called initially. Function
      returns a type code with the full path and item name
      in 'pfname' upon success. Function returns 'FALSE'
      otherwise. */

   char mname[] = "dscan_findnext";
   int ret;

   logman("%s:enter:cp=%s", mname, dscan_current_path);

   if (pfname == NULL)
      {
      logman("%s:exit[0]null[pfname]", mname);
      return(FALSE);
      }

   pfname[0] = EOS;

   while(TRUE)
      {
      /* find next occurance in current directory */

      if ((ret = dscan_find(pfname)) != FALSE)
         {
         logman("%s:exit[%d]:pfname=%s[%d]", mname,
                       ret, pfname, strlen(pfname));
         return(ret);
         }

      if (!dscan_descend)
         {
         logman("%s:exit[0]:no subs processed", mname);
         return(FALSE);
         }

      /* locate sub-directory or go up one level */

      if (!dscan_sub())
         {
         logman("%s:exit[0]:no more subs", mname);
         return(FALSE);
         }
      }

   logman("%s:exit[0]", mname);
   return(FALSE);
}

void dscan_end(void)
{
   /* De-allocate all memory used by 'dscan'. */

   char mname[] = "dscan_end";

   dscan_header(mname);
   dscan_ll_delete_all();
   dscan_delete_mvars();
   logman("%s:exit", mname);
}

void dscan_ll_debug(void)
{
   /* Output the current contents of the link list
      to the log file (if active). */

   struct dscan_entry *rov;
   char mname[] = "dscan_ll_debug";
   int cnt;

   logman("%s:enter", mname);
   rov = dscan_list_head;

   if (rov == NULL)
      {
      logman("%s:exit:link list empty", mname);
      return;
      }

   cnt = 1;

   while(rov != NULL)
      {
      logman("%s[%d]:path=%s[%d],sc=%d,si=%d",
                    mname, cnt, rov->dir_path, strlen(rov->dir_path),
                    rov->sub_count, rov->sub_index);

      if (rov->subs != NULL)
         logman("%s[%d]:subs=%s[%d]", mname, cnt, rov->subs,
                       strlen(rov->subs));

      rov = rov->next;
      cnt++;
      }

   logman("%s:normal exit", mname);
}

static int dscan_find(char *fname)
{
   /* Find an item in the current path. Function returns
      a type code with the item name in 'fname' upon
      success, 'FALSE' otherwise. */

   struct dscan_entry *rov;
   struct dirent *direntry;
   struct stat stat_buf;
   char mname[] = "dscan_find";
   char *full_name;
   int len, type;

   logman("%s:enter:cp=%s", mname, dscan_current_path);

   // locate link list pointer to current path or add if not found

   if ((rov = dscan_ll_find(dscan_current_path)) == NULL)
      {
      logman("%s:rc[NULL] from dscan_ll_find, will add", mname);

      if ((rov = dscan_ll_add(dscan_current_path)) == NULL)
         {
         logman("%s:exit[0]bad rc[NULL] from dscan_ll_add[0]", mname);
         return(FALSE);
         }
      }

   // scan each item in directory

   while((direntry = readdir(rov->dir)) != NULL)
      {
      len = strlen(dscan_current_path) + strlen(direntry->d_name) + 2;

      if ((full_name = (char *)malloc(len)) == (char *)NULL)
         {
         logman("%s:exit[0]alloc fail[full_name]", mname);
         return(FALSE);
         }

      // concatenate full name

      len = strlen(dscan_current_path);

      if (dscan_current_path[len - 1] == PATH_SEP)
         sprintf(full_name, "%s%s", dscan_current_path, direntry->d_name);
      else
         sprintf(full_name, "%s%c%s", dscan_current_path, PATH_SEP,
                 direntry->d_name);

      // under QNX/Linux/Unix use 'lstat' otherwise use 'stat'

#ifdef OS_UNIX
      if (lstat(full_name, &stat_buf) != 0)
#else
      if (stat(full_name, &stat_buf) != 0)
#endif
         {
         free(full_name);
         logman("%s:exit[0]bad rc from stat,full_name=%s",
                       mname, full_name);
         return(FALSE);
         }

      // get object type

      type = stat_buf.st_mode & S_IFMT;

      // process based on type

      switch(type)
         {
         case S_IFREG:
            // a regular file

            if (dscan_for_files)
               if (dscan_match(dscan_fname, direntry->d_name))
                  {
                  strcpy(fname, full_name);
                  free(full_name);
                  logman("%s:exit[%d]:found file=%s[%d]", mname,
                         DSCAN_TYPE_FILE, fname, strlen(fname));
                  return(DSCAN_TYPE_FILE);
                  }

            break;

         case S_IFDIR:
            // a directory
            // don't process '.' and '..'

            if (!strcmp(direntry->d_name, "."))
               {
               logman("%s:found reference to current dir,ignoring", mname);
               break;
               } 

            if (!strcmp(direntry->d_name, ".."))
               {
               logman("%s:found reference to parent dir,ignoring", mname);
               break;
               } 

            if (dscan_descend)
               dscan_add_sub(rov, direntry->d_name);

            if (dscan_for_dirs)
               if (dscan_match(dscan_fname, direntry->d_name))
                  {
                  strcpy(fname, full_name);
                  free(full_name);
                  logman("%s:exit[%d]:found dir=%s[%d]", mname,
                         DSCAN_TYPE_DIR, fname, strlen(fname));
                  return(DSCAN_TYPE_DIR);
                  }

            break;

#ifdef OS_UNIX
         case S_IFLNK:
            // a symbolic link

            if (dscan_for_symlinks)
               if (dscan_match(dscan_fname, direntry->d_name))
                  {
                  strcpy(fname, full_name);
                  free(full_name);
                  logman("%s:exit[%d]:found symlink=%s[%d]", mname,
                         DSCAN_TYPE_SYMLINK, fname, strlen(fname));
                  return(DSCAN_TYPE_SYMLINK);
                  }

            break;
#endif
         
         default:
            logman("%s:exit[FALSE]:%s[%d] is an unknown type",
                       mname, full_name, strlen(full_name));
            free(full_name);
            return(FALSE);
         };

      free(full_name);
      }

   logman("%s:exit[0]", mname);
   return(FALSE);
}

static int dscan_sub(void)
{
   /* Locate a sub-directory of the current or go back up one
      level in the directory tree. Function returns 'TRUE'
      if another directory was found, 'FALSE' otheriwse. */

   struct dscan_entry *rov;
   char mname[] = "dscan_sub";
   char *p, *s;
   int len;

   dscan_header(mname);
   logman("%s:cp=%s", mname, dscan_current_path);

   if ((rov = dscan_ll_find(dscan_current_path)) == NULL)
      {
      logman("%s:exit[0]bad rc[NULL] from dscan_ll_find", mname);
      return(FALSE);
      }

   if (rov->sub_count)
      {
      rov->sub_index++;
      logman("%s:subs=%s,si=%d,sc=%d", mname, rov->subs, rov->sub_index,
                    rov->sub_count);

      if (rov->sub_index <= rov->sub_count)
         {
         len = ll_wordlen(rov->subs, rov->sub_index, DSCAN_SUB_DELIM);
         logman("%s:word[%d]len=%d", mname, rov->sub_index, len);

         if ((s = (char *)malloc(len + 1)) == NULL)
            {
            logman("%s:exit[0]:alloc fail[s]", mname);
            return(FALSE);
            }

         if (!ll_word(rov->subs, s, rov->sub_index, DSCAN_SUB_DELIM))
            {
            free(s);
            logman("%s:exit[0]:bad rc from ll_word", mname);
            return(FALSE);
            }

         len = len + strlen(dscan_current_path) + 2;

         if ((p = (char *)malloc(len)) == NULL)
            {
            free(s);
            logman("%s:exit[0]:alloc fail[p]", mname);
            return(FALSE);
            }

         len = strlen(dscan_current_path);

         if (dscan_current_path[len - 1] == PATH_SEP)
            sprintf(p, "%s%s", dscan_current_path, s);
         else
            sprintf(p, "%s%c%s", dscan_current_path, PATH_SEP, s);

         logman("%s:pick sub:new cp=%s[%d]", mname, p, strlen(p));
         free(s);

         if (!dscan_set_current_path(p))
            {
            free(p);
            logman("%s:exit[0]:bad rc[0] from dscan_set_current_path",
                          mname);
            return(FALSE);
            }

         free(p);
         logman("%s:exit[TRUE]", mname);
         return(TRUE);
         }
      }

   dscan_ll_delete(dscan_current_path);

   if (!strcmp(dscan_base_path, dscan_current_path))
      {
      dscan_end();
      logman("%s:exit[0]:back at base path, assuming done", mname);
      return(FALSE);
      }

   if (!dscan_chop_path())
      {
      logman("%s:exit[0]:bad rc[0] from dscan_chop_path", mname);
      return(FALSE);
      }

   logman("%s:exit[TRUE]", mname);
   return(TRUE);
}

static void dscan_add_sub(struct dscan_entry *rov, char *dname)
{
   /* Add a directory name to the indicated entries sub-directory
      list. */

   char mname[] = "dscan_add_sub";
   char *outstr;
   int len;

   dscan_header(mname);

   if (rov == NULL || dname == NULL || !strlen(dname))
      {
      logman("%s:exit[0]parm error", mname);
      return;
      }

   logman("%s:cp=%s,dname=%s,sc=%d,si=%d", mname,
                 dscan_current_path, dname, rov->sub_count, rov->sub_index);

   /* if no subs present, add first one */

   if (rov->sub_count == 0)
      {
      if ((rov->subs = (char *)malloc(strlen(dname) + 1)) == NULL)
         return;

      strcpy(rov->subs, dname);
      rov->sub_count++;
      logman("%s:exit:add first sub name", mname);
      return;
      }

   /* add sub name to delimited list of sub names */

   len = strlen(rov->subs) + strlen(dname) + 2;

   if ((outstr = (char *)malloc(len)) == (char *)NULL)
      {
      logman("%s:exit:alloc fail[outstr]", mname);
      return;
      }

   rov->sub_count++;

   if (ll_wordput(rov->subs, outstr, dname, rov->sub_count, DSCAN_SUB_DELIM))
      {
      len = strlen(outstr);
      free(rov->subs);
      rov->subs = NULL;

      if ((rov->subs = (char *)malloc(len + 1)) == (char *)NULL)
         {
         logman("%s:exit:alloc fail[rov->subs]", mname);
         return;
         }

      strcpy(rov->subs, outstr);
      }
   else
      logman("%s:bad rc[0] from ll_wordput[%s]", mname, dname);

   free(outstr);
   logman("%s:normal exit", mname);
}

static int dscan_chop_path(void)
{
   /* Remove one level from the current path. Function returns
      'TRUE' upon success, 'FALSE' otherwise. */

   char mname[] = "dscan_chop_path";
   char *p;
   int nwords, len;

   dscan_header(mname);
   nwords = ll_words(dscan_current_path, PATH_SEP);
   len = strlen(dscan_current_path);

   if ((p = (char *)malloc(len + 1)) == NULL)
      {
      logman("%s:exit[0]alloc fail[p]", mname);
      return(FALSE);
      }

   if (!ll_worddel(dscan_current_path, p, nwords, PATH_SEP))
      {
      free(p);
      logman("%s:exit[0]bad rc from ll_worddel", mname);
      return(FALSE);
      }

   len = strlen(p);

   /* detect back at drive root */

#ifdef OS_WIN32
   /* windoze where a drive ID is present */

   if (len == 2 && p[1] == ':')
      {
      p[2] = PATH_SEP;
      p[3] = EOS;
      }
#endif

   if (!len)
      {
      /* any platform where the path is reduced to an empty string */

      p[0] = PATH_SEP;
      p[1] = EOS;
      }

   logman("%s:reduce from current,new cp=%s[%d]", mname, p, strlen(p));

   if (!dscan_set_current_path(p))
      {
      free(p);
      logman("%s:exit[0]bad rc[0] from dscan_set_current_path", mname);
      return(FALSE);
      }

   free(p);
   logman("%s:exit[TRUE]:new cp=%s[%d]", mname, dscan_current_path,
                 strlen(dscan_current_path));
   return(TRUE);
}

static int dscan_match(char *pat, char *fname)
{
   /* Match a file name to a pattern. Function returns
      'TRUE' if a match was found, 'FALSE' otherwise. */

   char mname[] = "dscan_match";
   int ret;

   dscan_header(mname);

   /* for Windoze and DOS, compare case insensitive */

#ifdef OS_WIN32
   ret = dscan_umatch(pat, fname);
   logman("%s:exit[%d]", mname, ret);
   return(ret);
#endif

#ifdef OS_DOS
   ret = dscan_umatch(pat, fname);
   logman("%s:exit[%d]", mname, ret);
   return(ret);
#endif

   if (pat == NULL || !strlen(pat))
      {
      logman("%s:exit[0]:null or empty[pat]", mname);
      return(FALSE);
      }

   if (fname == NULL || !strlen(fname))
      {
      logman("%s:exit[0]:null or empty[fname]", mname);
      return(FALSE);
      }

   logman("%s:pat=%s[%d],fname=%s[%d]", mname, pat, strlen(pat),
                 fname, strlen(fname));
   ret = pmatch(pat, fname);
   logman("%s:exit[%d]", mname, ret);
   return(ret);
}

static int dscan_umatch(char *pat, char *fname)
{
   /* Match a file name to a pattern case insensitive.
      Function returns 'TRUE' if a match is found,
      'FALSE' otherwise. */

   char mname[] = "dscan_umatch";
   int ret;
   char *upat, *ufname;

   dscan_header(mname);

   if (pat == NULL || !strlen(pat))
      {
      logman("%s:exit[0]:null or empty[pat]", mname);
      return(FALSE);
      }

   if (fname == NULL || !strlen(fname))
      {
      logman("%s:exit[0]:null or empty[fname]", mname);
      return(FALSE);
      }

   if ((upat = initstring(pat)) == NULL)
      {
      logman("%s:exit[0]:alloc fail[upat]", mname);
      return(FALSE);
      }

   if ((ufname = initstring(fname)) == NULL)
      {
      free(upat);
      logman("%s:exit[0]:alloc fail[ufname]", mname);
      return(FALSE);
      }

   ucase(pat, upat);
   ucase(fname, ufname);
   ret = pmatch(upat, ufname);
   free(upat);
   free(ufname);
   logman("%s:exit[%d]:case insensitive", mname, ret);
   return(ret);
}

static int dscan_set_flags(char *flags)
{
   /* Load scan flags into module globals. Function returns 'TRUE'
      upon success, 'FALSE' otherwise. */

   char mname[] = "dscan_set_flags";
   char uflags[10];
   int len, i;

   dscan_header(mname);

   if (flags == NULL || !strlen(flags))
      {
      logman("%s:exit[0]null or empty[flags]", mname);
      return(FALSE);
      }

   ucase(flags, uflags);
   len = strlen(uflags);
   dscan_for_files = dscan_for_dirs = dscan_descend = FALSE;

#ifdef OS_UNIX
   dscan_for_symlinks = FALSE;
#endif

   for(i = 0; i < len; i++)
      {
      switch(uflags[i])
         {
         case 'F':
            dscan_for_files = TRUE;
            break;

         case 'D':
            dscan_for_dirs = TRUE;
            break;

         case 'S':
            dscan_descend = TRUE;
            break;

#ifdef OS_UNIX
         case 'L':
            dscan_for_symlinks = TRUE;
            break;
#endif

         default:
            logman("%s:exit[0]:unknown flag[%c]", mname, uflags[i]);
            return(FALSE);
         };
      }

#ifdef OS_UNIX
   if (!dscan_for_files && !dscan_for_dirs && !dscan_for_symlinks)
      {
      logman("%s:exit[0]one of scan for files[f], scan for "
             "dirs[d] or scan for symlinks[l] must be given", mname);
#else
   if (!dscan_for_files && !dscan_for_dirs)
      {
      logman("%s:exit[0]one of scan for files[f] or scan for "
             "dirs[d] must be given", mname);
#endif
      return(FALSE);
      }

#ifdef OS_UNIX
   logman("%s:exit[TRUE]sff=%d,sfd=%d,sfl=%d,sd=%d", mname, dscan_for_files,
          dscan_for_dirs, dscan_for_symlinks, dscan_descend);
#else
   logman("%s:exit[TRUE]sff=%d,sfd=%d,sd=%d", mname,
                 dscan_for_files, dscan_for_dirs, dscan_descend);
#endif

   return(TRUE);
}

static int dscan_set_current_path(char *p)
{
   /* Save the current path. Function returns 'TRUE' upon success,
      'FALSE' otherwise. */

   char mname[] = "dscan_set_current_path";

   dscan_header(mname);

   if (p == NULL || !strlen(p))
      {
      logman("%s:exit[0]null or empty[p]", mname);
      return(FALSE);
      }

   if (dscan_current_path != NULL)
      free(dscan_current_path);

   if ((dscan_current_path = (char *)malloc(strlen(p) + 1))
       == (char *)NULL)
      {
      logman("%s:exit[0]alloc fail[dscan_current_path]", mname);
      return(FALSE);
      }

   strcpy(dscan_current_path, p);
   logman("%s:exit[TRUE]:cp=%s", mname, dscan_current_path);
   return(TRUE);
}

static void dscan_ll_delete(char *p)
{
   /* Delete a link list entry by it's path. */

   struct dscan_entry *rov, *prev;
   char mname[] = "dscan_ll_delete";
   int found = FALSE;

   dscan_header(mname);
   logman("%s:ll before", mname);
   dscan_ll_debug();

   if (p == NULL || !strlen(p))
      {
      logman("%s:exit:null or empty[p]", mname);
      return;
      }

   logman("%s:p=%s[%d]", mname, p, strlen(p));
   rov = dscan_list_head;
   prev = NULL;

   while(rov != NULL && !found)
      if (!strcmp(p, rov->dir_path))
         {
         found = TRUE;
         break;
         }
      else
         {
         prev = rov;
         rov = rov->next;
         }

   if (!found)
      {
      logman("%s:exit:entry not found", mname);
      return;
      }

   closedir(rov->dir);

   if (rov->subs != NULL)
      free(rov->subs);

   free(rov->dir_path);

   if (prev != NULL)
      prev->next = rov->next;
   else
      dscan_list_head = rov->next;

   free(rov);
   logman("%s:normal exit:ll after", mname);
   dscan_ll_debug();
}

static void dscan_ll_delete_all(void)
{
   /* Delete all members of the link list and set the
      head of list pointer to 'NULL'. */

   struct dscan_entry *rov, *tmp;
   char mname[] = "dscan_ll_delete_all";

   dscan_header(mname);

   if (dscan_list_head == NULL)
      {
      logman("%s:exit:link list empty", mname);
      return;
      }

   rov = dscan_list_head;

   while(rov != NULL)
      {
      closedir(rov->dir);
      free(rov->dir_path);

      if (rov->subs != NULL)
         free(rov->subs);

      tmp = rov->next;
      free(rov);
      rov = tmp;
      }

   dscan_list_head = NULL;
   logman("%s:normal exit", mname);
}

static int dscan_set_mvars(char *path, char *fname)
{
   /* Allocate space for global path and file name. Function returns
      'TRUE' upon success, 'FALSE' otherwise. */

   char mname[] = "dscan_set_mvars";

   dscan_header(mname);

   if (path == NULL || !strlen(path) || fname == NULL || !strlen(fname))
      {
      logman("%s:exit[0]parm error", mname);
      return(FALSE);
      }

   if (dscan_base_path != NULL)
      {
      logman("%s:exit[0]base path already assigned", mname);
      return(FALSE);
      }

   if ((dscan_base_path = (char *)malloc(strlen(path) + 1)) == (char *)NULL)
      {
      logman("%s:exit[0]alloc fail[dscan_base_path]", mname);
      return(FALSE);
      }

   strcpy(dscan_base_path, path);

   if (dscan_fname != NULL)
      {
      logman("%s:exit[0]target file name already assigned", mname);
      return(FALSE);
      }

   if ((dscan_fname = (char *)malloc(strlen(fname) + 1)) == (char *)NULL)
      {
      free(dscan_base_path);
      dscan_base_path = NULL;
      logman("%s:exit[0]alloc fail[dscan_fname]", mname);
      return(FALSE);
      }

   strcpy(dscan_fname, fname);
   logman("%s:exit[TRUE]", mname);
   return(TRUE);
}

static void dscan_delete_mvars(void)
{
   /* Delete the module global strings. */

   char mname[] = "dscan_delete_mvars";

   dscan_header(mname);

   if (dscan_base_path != NULL)
      {
      free(dscan_base_path);
      dscan_base_path = NULL;
      }

   if (dscan_current_path != NULL)
      {
      free(dscan_current_path);
      dscan_current_path = NULL;
      }

   if (dscan_fname != NULL)
      {
      free(dscan_fname);
      dscan_fname = NULL;
      }

   logman("%s:normal exit", mname);
}

static struct dscan_entry *dscan_ll_find(char *p)
{
   /* Locate a link list entry by the path. Function returns
      a pointer to the appropriate link list entry upon success,
      a NULL pointer otherwise. */

   struct dscan_entry *rov;
   char mname[] = "dscan_ll_find";

   dscan_header(mname);

   if (p == NULL || !strlen(p))
      {
      logman("%s:exit[NULL]:null or empty[p]", mname);
      return(NULL);
      }

   logman("%s:attempting to locate %s[%d]", mname, p, strlen(p));
   rov = dscan_list_head;

   if (rov == NULL)
      {
      logman("%s:exit[NULL]:link list empty", mname);
      return(NULL);
      }

   while(rov != NULL)
      {
      if (!strcmp(rov->dir_path, p))
         {
         logman("%s:exit:found entry", mname);
         return(rov);
         }

      rov = rov->next;
      }

   logman("%s:exit:entry not found", mname);
   return(NULL);
}

static struct dscan_entry *dscan_ll_add(char *p)
{
   /* Add a directory to the link list. Function returns a pointer
      to the new entry upon success, a NULL pointer otherwise. */

   struct dscan_entry *rov, *last;
   char mname[] = "dscan_ll_add";
   int len;

   dscan_header(mname);
   dscan_ll_debug();

   if (p == NULL || !strlen(p))
      {
      logman("%s:exit[NULL]null or empty[p]", mname);
      return(NULL);
      }

   logman("%s:adding %s", mname, p);
   rov = dscan_list_head;
   last = NULL;

   while(rov != NULL)
      {
      if (rov->next == NULL)
         {
         last = rov;
         break;
         }

      rov = rov->next;
      }

   len = sizeof(struct dscan_entry);

   if ((rov = (struct dscan_entry *)malloc(len)) == NULL)
      {
      logman("%s:exit[NULL]alloc error[rov]", mname);
      return(NULL);
      }

   if ((rov->dir_path = (char *)malloc(strlen(p) + 1)) == NULL)
      {
      free(rov);
      logman("%s:exit[NULL]alloc fail[p]", mname);
      return(NULL);
      }

   strcpy(rov->dir_path, p);
   rov->subs = NULL;
   rov->sub_count = rov->sub_index = 0;
   rov->next = NULL;

   if ((rov->dir = opendir(rov->dir_path)) == NULL)
      {
      free(rov->dir_path);
      free(rov);
      logman("%s:unable to access directory %s[%d], dumping",
                    mname, p, strlen(p));
      (void)dscan_chop_path();
      logman("%s:exit[NULL]", mname);
      return(NULL);
      }

   if (last != NULL)
      last->next = rov;

   if (dscan_list_head == NULL)
      dscan_list_head = rov;

   dscan_ll_debug();
   logman("%s:exit[ok]", mname);
   return(rov);
}

static void dscan_header(char *mname)
{
   // Log the entrance into a function.

   logman("%s:enter", mname);
}
