/* daymvdel - A program that moves or deletes files in directories
   or directory trees according to file age. Rick Smereka,
   Copyright (C) 2005-2006.

   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

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

   Program syntax:

      daymvdel [-q] [-l[ log_file]] [-n] [-c] task_list_file

   Where 'task_list_file' is a ASCII file containing a list of directories and 
   what to do in each directory. The task list is in the following format for
   a delete operation:

      d[s][a] numdays fname source_path

   The task list is in the following format for the move operation:

      m[s][a] numdays fname source_path dest_path
 
   The first option is an 'm' for move or 'd' for delete. One of these
   two must be present. The option 's' includes subdirectories and the
   option 'a' stand for 'all or nothing' which means move or delete all
   files as long as every file meets the age requirement. If any file
   does not meet this requirement, delete or move none of the files.

   The parameter 'numdays' is the file age in days, 'fname' indicates
   which files to consider (the wildcards '*' and '?' may be used),
   'source_path' is source directory path and 'dest_path' (required
   only for a move operation) is the destination directory path.

   Any task line parameter that contains spaces must be quoted.

   In addition to the normal move or delete operation, you can apply
   the 't'ree command that will perform the move or delete operation to each
   subdirectory underneath the specified tree. The task list line syntax
   along with the 't'ree command is:

   Delete operation:
      td[s][a] numdays fname source_path[ except...]

   Move operation:

      tm[s][a] numdays fname source_path dest_path[ except...]

   The 't' signifies a 'tree' move or delete. The 't' should be the first
   character on the line. Most of the task line is the same as the normal
   move or delete with the addition of the optional exception directories.
   Any directory listed as an exception (they may be more than one listed),
   will be excluded from the move or delete operation when it is encountered.
   Exception directories (like all paths in this program) must be specified
   in absolute form.

   The command line option 'q' will suppress all console message output 
   (quiet), the 'l' (el) option will output all messages to either the default
   log destination/file or the supplied destination/file, the 'n'
   option will report only without taking any action and the 'c' option
   will automatically create the destination base directory tree for the
   move operation. Note that the option/switch character is based on the 
   platform and may be not the hyphen.

   This program uses the 'dscan' directory scanning API, the 'armtree'
   directory tree removal API and the 'path' API.

   Original version for Linux, Windows and DOS. Dec/2005, Rick Smereka

   Added 'tree' task processing. Jan/2006, Rick Smereka

   Modified 'all or nothing' processing by creating functions
   'delete_all_files' and 'move_all_files'. Sep/2006, Rick Smereka */
 
#include "stdhead.h"
#ifndef OS_DOS
#include "flsocket.h"
#endif
#include "dscan.h"
#include "armtree.h"
#include "path.h"
#ifdef IPC_TCP
#include "socloc.h"
#include "sloc.h"
#include "slconfig.h"
#include "sliocode.h"
#endif

#define APPNAME "daymvdel"
#define VERSION "1.10-2006.09.03"
#define DEFAULT_LOG_FILE "daymvdel.log"
#define DIRS_NULL ((struct dirs *)NULL)

// operation codes

#define OPCODE_DELETE 0
#define OPCODE_MOVE 1

// global data

char c_log_file[128];
char c_task_file[128];
char today[9];
char line[BUFSIZE];
int console_output = TRUE;
int log_output = FALSE;
int take_action = TRUE;
// int take_action = FALSE;
int move_do_base_dir = FALSE;
int line_num = 0;
int line_parms = 0;
FILE *in;

// dirs link list structure

struct dirs
   {
   char *dir;               // single absolute directory path
   struct dirs *next;       // pointer to next node
   };

// task structure

struct
   {
   int opcode;              // operation code
   int do_subs;             // should subdirectories be included?
   int all;                 // all or nothing?
   int tree;                // 'tree' task?
   int numdays;             // age in days
   char fpat[128];          // file name pattern to match
   char path[BUFSIZE];      // path to operate on
   char dest_path[BUFSIZE]; // destination path (for moving)
   struct dirs *dhead;      // head of dirs list
   } task;

#ifdef OS_WIN32
WSADATA wsaData;
#endif

// function prototypes

int main(int, char **);
int parse_comline(int, char **);
int parse_task(void);
void do_tree(void);
void do_task(void);
int delete_all_files(void);
int move_all_files(void);
int file_days(char *, int *);
int adjust_path(char *);
int create_base_dir(char *, int);
void app_out(char *,...);
void term_app(void);
void set_date(void);
void usage(void);

int main(int argc, char **argv)
{
   char mes[128], mname[] = "main";
   int ret, lstatus;

   if (argc == 1)
      {
      usage();
      return(0);
      }

   // parse command line

   if (!parse_comline(argc, argv))
      return(0);

   // build logo string based on platform

#ifndef OS_UNIX
   // non-Unix

   app_out("%s for %s Version %s", APPNAME, PLATFORM_STRING,
           VERSION);
#else
   // Unix

   app_out("%s for %s Version %s", APPNAME, SUB_PLATFORM_STRING,
           VERSION);
#endif

   app_out("By Rick Smereka, Copyright (c) 2005-2006");
   app_out("%s comes with ABSOLUTELY NO WARRANTY", APPNAME);
   app_out("This is free software, and you are welcome to redistribute it");
   app_out("under certain conditions; see 'gpl.txt' for information.");

   // open task list file

   if ((in = fopen(c_task_file, "r")) == (FILE *)NULL)
      {
      app_out("unable to open input task list file '%s'", c_task_file);
      return(0);
      }

   app_out("task list file is '%s'", c_task_file);
   (void)flag_2_logic_string(move_do_base_dir, mes);
   app_out("create base directory structure during move op is %s", mes);
   (void)flag_2_logic_string(log_output, mes);
   app_out("logging is %s, log destination is '%s'", mes, c_log_file);
   (void)flag_2_logic_string(console_output, mes);
   app_out("console output is %s", mes);

   if (!take_action)
      app_out("in analysis mode only, no action taken (delete or move)");
   
   set_date();
   get_rec(in, line);
   line_num++;

   while(!feof(in))
      {
      if (parse_task())
         if (task.tree)
            do_tree();
         else
            do_task();

      get_rec(in, line);
      line_num++;
      }

   app_out("%s:%s:program complete", APPNAME, mname);
   term_app();
   return(0);
}

int parse_comline(int c_count, char **c_parm)
{
   /* Parse the command line for parameters. Function
      returns 'TRUE' if no error was detected, 'FALSE'
      otherwise. */

   char mname[] = "parse_comline";
   int parms = 1, done = FALSE;

   // set default log file

   strcpy(c_log_file, DEFAULT_LOG_FILE);
   c_task_file[0] = EOS;

   while(!done)
      {
      if (c_parm[parms][0] == SWITCH_CHAR)
         {
         switch(toupper(c_parm[parms][1]))
            {
            case 'L':
               if (strlen(c_parm[parms]) > 2)
                  printf("%s:%s:extraneous input with log switch, "
                         "ignoring\n", APPNAME, mname);

               parms++;

               if (c_count > parms)
                  if (c_parm[parms][0] != SWITCH_CHAR)
                     {
                     strcpy(c_log_file, c_parm[parms]);
                     parms++;
                     }

               log_output = TRUE;
               break;

            case 'Q':
               console_output = FALSE;
               parms++;
               break;

            case 'N':
               take_action = FALSE;
               parms++;
               break;

            case 'C':
               move_do_base_dir = TRUE;
               parms++;
               break;

            default:
               printf("%s:%s:unknown switch[%s], program abort\n",
                      APPNAME, mname, c_parm[parms]);
               return(FALSE);
            };
         }
      else
         {
         strcpy(c_task_file, c_parm[parms]);
         parms++;
         }

      if (parms >= c_count)
         done = TRUE;
      }

   if (!strlen(c_task_file))
      {
      printf("%s:%s:task list file name is missing,program abort\n",
             APPNAME, mname);
      return(FALSE);
      }

   if (log_output)
      if (logman_start(c_log_file, APPNAME))
         {
         printf("%s:%s:error starting logging,program abort\n", APPNAME, mname);
         return(FALSE);
         }

   return(TRUE);
}

int parse_task(void)
{
   /* Parse task and load into 'task' structure. Function returns
      'TRUE' upon success, 'FALSE' upon error or a comment line. */

   char mname[] = "parse_task";
   char tline[BUFSIZE], wrd[BUFSIZE], ch;
   int len, i;

   trim(line, tline);

   // ignore blank lines

   if (!strlen(line))
      return(FALSE);

   // ignore comment lines ('#' as first non-blank char)

   if (tline[0] == '#')
      return(FALSE);

   // re-initialize task structure

   task.do_subs = task.all = task.tree = FALSE;
   task.path[0] = task.fpat[0] = task.dest_path[0] = EOS;
   task.dhead = DIRS_NULL;
   task.numdays = 0;
   line_parms = command_words(tline);

   // there must be at least four command words on the line

   if (line_parms < 4)
      {
      app_out("%s:line[%d]:**error:insufficient number of parameters", mname,
              line_num);
      return(FALSE);
      }

   // first command word consists of the operation code and options

   if (!command_word(tline, wrd, 1))
      {
      app_out("%s:line[%d]:internal error word[1]", mname, line_num);
      return(FALSE);
      }

   // each character is an operation code or an option

   len = strlen(wrd);

   for(i =0; i < len; i++)
      {
      ch = toupper(wrd[i]);

      switch(ch)
         {
         case 'D':
            task.opcode = OPCODE_DELETE;
            break;

         case 'M':
            task.opcode = OPCODE_MOVE;
            break;

         case 'T':
            task.tree = TRUE;
            break;

         case 'S':
            task.do_subs = TRUE;
            break;

         case 'A':
            task.all = TRUE;
            break;

         default:
            app_out("%s:line[%d]:**error:unknown operation code/option[%c]",
                    mname, line_num, ch);
            return(FALSE);
         };
      }

   // if move operation, must be five words on the line

   if (task.opcode == OPCODE_MOVE)
      if (line_parms < 5)
         {
         app_out("%s:line[%d]:**error:insufficient number of parameters", mname,
                 line_num);
         return(FALSE);
         }
      
   // word two s/b the age in days

   if (!command_word(tline, wrd, 2))
      {
      app_out("%s:line[%d]:internal error word[2]", mname, line_num);
      return(FALSE);
      }

   if (!qatoi(wrd, &i))
      {
      app_out("%s:line[%d]:**error:number of days is non-numeric", 
              mname, line_num);
      return(FALSE);
      }

   if (i <= 0)
      {
      app_out("%s:line[%d]:**error:number of days is out of range(s/b >0)",
              mname, line_num);
      return(FALSE);
      }

   task.numdays = i;

   // word three is the file name pattern to match

   if (!command_word(tline, task.fpat, 3))
      {
      app_out("%s:line[%d]:internal error word[3]", mname, line_num);
      return(FALSE);
      }

   // 'all or nothing' option must be used with all files ('*' or '*.*')

   if (task.all)
      if (strcmp(task.fpat, "*") && strcmp(task.fpat, "*.*"))
         {
         app_out("%s:line[%d]:must specify all files with 'all or nothing'",
                 mname, line_num);
         return(FALSE);
         }

   // word four is the path to operate on

   if (!command_word(tline, wrd, 4))
      {
      app_out("%s:line[%d]:internal error word[4]", mname, line_num);
      return(FALSE);
      }

   if (!isdirectory(wrd))
      {
      app_out("%s:line[%d]:**error:'%s' is not a directory", mname, 
              line_num, wrd);
      return(FALSE);
      }

   strcpy(task.path, wrd);

   // word five is the move destination path

   if (task.opcode == OPCODE_MOVE)
      {
      if (!command_word(tline, wrd, 5))
         {
         app_out("%s:line[%d]:internal error word[5]", mname, line_num);
         return(FALSE);
         }

      if (!move_do_base_dir)
         if (!isdirectory(wrd))
            {
            app_out("%s:line[%d]:**error:'%s' is not a directory", mname, 
                    line_num, wrd);
            return(FALSE);
            }

      strcpy(task.dest_path, wrd);
      }

   return(TRUE);
}

void do_tree(void)
{
   /* Process a 'tree' task. Locate all directories in the specified
      source directory structure and load these into the 'dirs' link
      list excluding the exception directories. For each directory in
      the 'dirs' link list, process a task. */

   struct dirs *ot, *rov;
   char mname[] = "do_tree", pfname[BUFSIZE], wrd[BUFSIZE], odest_path[BUFSIZE];
   int ret, size, done, ex_start, i;

   get_ftime(pfname);
   app_out("%s:%s:starting tree task '%s'", mname, pfname, line);
   ex_start = ((task.opcode == OPCODE_DELETE) ? 4 : 5) + 1;
   strcpy(odest_path, task.dest_path);

   // locate all sub-directories

   ret = dscan_findfirst(task.path, "*", "d", pfname);
   size = sizeof(struct dirs);

   // add all sub-directories to the link list

   while(ret)
      {
      // app_out("%s:found dir '%s'", mname, pfname);

      // exclude the directory if in the exclude list

      if (line_parms >= ex_start)
         {
         for(i = ex_start; i <= line_parms; i++)
            {
            if (!command_word(line, wrd, i))
               {
               app_out("%s[%d]:exit:internal error getting exception", mname,
                       line_num);
               return;
               }

            // Windoze and DOS compare case insensitive

#ifdef OS_WIN32
            if (!stricmp(wrd, pfname))
#endif

#ifdef OS_DOS
            if (!stricmp(wrd, pfname))
#endif

            // Unix/Linux/FreeBSD compare case sensitive

#ifdef OS_UNIX
            if (!strcmp(wrd, pfname))
#endif
               {
               app_out("%s:dir '%s' in exception list,ignoring", mname, wrd);
               ret = dscan_findnext(pfname);
               break;
               }
            }
         }

      if (!ret)
         break;

      // add each directory found to the 'dirs' link list

      if ((ot = (struct dirs *)malloc(size)) == DIRS_NULL)
         {
         app_out("%s[%d]:exit:alloc fail[ot]", mname, line_num);
         return;
         }

      if ((ot->dir = (char *)malloc(strlen(pfname) + 1)) == (char *)NULL)
         {
         free(ot);
         app_out("%s[%d]:exit:alloc fail[ot->dir]", mname, line_num);
         return;
         }

      ot->next = DIRS_NULL;
      strcpy(ot->dir, pfname);
      done = FALSE;

      if (task.dhead == DIRS_NULL)
         task.dhead = ot;
      else
         {
         // insert new entry at end of link list

         rov = task.dhead;

         while(!done)
            {
            if (rov->next == DIRS_NULL)
               {
               rov->next = ot;
               done = TRUE;
               }
            else
               rov = rov->next;
            }
         }

      ret = dscan_findnext(pfname);
      }

   /* app_out("%s:debug dirs", mname);
   rov = task.dhead;

   while(rov != DIRS_NULL)
      {
      app_out("%s:dir '%s'", mname, rov->dir);
      rov = rov->next;
      } */

   /* load each directory from link list into source path ('task.path'),
      and process it */

   rov = task.dhead;

   while(rov != DIRS_NULL)
      {
      strcpy(task.path, rov->dir);

      /* if task is move, alter the destination path by adding the 
         last directory name onto it */

      if (task.opcode == OPCODE_MOVE)
         {
         ret = ll_words(rov->dir, PATH_SEP);

         if (ll_word(rov->dir, wrd, ret, PATH_SEP))
            {
            i = strlen(odest_path) + 1 + strlen(wrd);
            sprintf(pfname, "%s%c%s", odest_path, PATH_SEP, wrd);
            pfname[i] = EOS;
            /* app_out("%s:old dest=%s,new_dest=%s", mname, task.dest_path, 
                        pfname); */
            strcpy(task.dest_path, pfname);
            }
         }

      do_task();
      rov = rov->next;
      }

   // de-allocate entire link list

   rov = task.dhead;

   while(rov != DIRS_NULL)
      {
      free(rov->dir);
      ot = rov->next;
      free(rov);
      rov = ot;
      }
}

void do_task(void)
{
   // Perform task/operation.

   char mname[] = "do_task";
   char pfname[BUFSIZE], dest_name[BUFSIZE];
   int done = FALSE, ret, days, mindays = 32760, fcount = 0;

   get_ftime(pfname);

   if (task.opcode == OPCODE_DELETE)
      app_out("%s[%s]:starting delete task '%s'", mname, pfname, task.path);
   else
      app_out("%s[%s]:starting move task '%s', '%s'", mname, pfname, task.path,
              task.dest_path);

   while(!done)
      {
      if (!file_days(pfname, &days))
         {
         done = TRUE;
         continue;
         }

      fcount++;

      // if 'all or none', detect minimum number of days

      if (task.all)
         {
         if (days < mindays)
            {
            mindays = days;
            // app_out("%s:new mindays=%d", mname, mindays);
            }
         }
      else
         {
         // perform operation if greater than number of days specified
 
         if (days > task.numdays)
            {
            if (task.opcode == OPCODE_DELETE)
                {
                if (take_action)
                   {
                   unlink(pfname);
                   app_out("%s:'%s' deleted", mname, pfname);
                   }
                else
                   app_out("%s:'%s' should be deleted", mname, pfname);
                }

            if (task.opcode == OPCODE_MOVE)
               {
               strcpy(dest_name, pfname);

               if (adjust_path(dest_name))
                  if (take_action)
                     {
                     // create destination base directory if requested

                     if (move_do_base_dir)
                        if (!create_base_dir(dest_name, TRUE))
                           {
                           app_out("%s:exit:bad rc[FALSE] from "
                                   "create_base_dir,file skipped", mname);
                           continue;
                           }
                           
                     if (!filecopy(pfname, dest_name))
                        app_out("%s:bad rc[FALSE] from filecopy[%s]", mname,
                                 pfname);
                     else
                        {
                        unlink(pfname);
                        app_out("%s:file moved to '%s'", mname, dest_name);
                        }
                     }
               }
            }
         }
      }

   // if tree is empty, do nothing

   if (!fcount)
      {
      app_out("%s:dir tree is empty,ignoring", mname);
      return;
      }

   // perform operation if 'all or none' and greater than number of days

   if (task.all)
      {
      if (mindays > task.numdays)
         {
         if (task.opcode == OPCODE_DELETE)
            {
            if (take_action)
               {
               if (!delete_all_files())
                  app_out("%s:bad rc[0] from delete_all_files", mname);
               }
            else
               app_out("%s:newest file is %d day(s) old,should perform "
                       "operation", mname, mindays);
            }
         else
            {
            // move 'all or none'

            if (take_action)
               {
               if (!move_all_files())
                  app_out("%s:bad rc[0] from move_all_files", mname);
               }
            else
               app_out("%s:newest file is %d day(s) old,should perform "
                       "operation", mname, mindays);
            }
         }
      else
         app_out("%s:newest file is %d day(s) old,should not perform operation",
                 mname, mindays); 
      }

   get_ftime(pfname);
   app_out("%s:%s:completed task", mname, pfname);
}

int delete_all_files(void)
{
   /* Delete all files from the source directory task(path).
      If subdirectories are also specified, the entire
      directory tree will be deleted. Returns 'TRUE' upon
      success, 'FALSE' otherwise. */

   char pfname[128], flags[4], mname[] = "delete_all_files";
   int ret, pos = 0;

   // prepare flags for 'dscan'

   flags[0] = 'f';
   pos = 1;

#ifdef OS_UNIX
   // add sybbolic link detection if running under Unix/Linux/BSD

   flags[pos++] = 'l';
#endif

   if (task.do_subs)
      flags[pos++] = 's';

   flags[pos] = EOS;

   // initialize 'dscan'

   if ((ret = dscan_findfirst(task.path, task.fpat, flags, pfname)) == 0)
      {
      app_out("%s:no files to delete", mname);
      return(FALSE);
      }

   while(ret)
      {
      unlink(pfname);
      ret = dscan_findnext(pfname);
      }

   if (task.do_subs)
      if (!rmtree(task.path))
         {
         app_out("%s:bad rc[FALSE] from rmtree deleting directory "
                 "tree '%s'", mname, task.path);

         return(FALSE);
         }
      
   return(TRUE);
}

int move_all_files(void)
{
   /* Move all files from the source directory(task.path) to the
      destination directory(task.dest_path). If subdirectories are
      also specified, the entire source directory tree will be
      deleted after all the files have been moved. Function
      returns 'TRUE' upon success, 'FALSE' otherwise. */
  
   char src_name[128], dest_name[128], flags[4], mname[] = "move_all_files";
   int pos, ret;

   // prepare flags for 'dscan'

   flags[0] = 'f';
   pos = 1;

#ifdef OS_UNIX
   // add sybbolic link detection if running under Unix/Linux/BSD

   flags[pos++] = 'l';
#endif

   if (task.do_subs)
      flags[pos++] = 's';

   flags[pos] = EOS;

   // initialize 'dscan'

   if ((ret = dscan_findfirst(task.path, task.fpat, flags, src_name)) == 0)
      {
      app_out("%s:no files to move", mname);
      return(FALSE);
      }

   while(ret)
      {
      strcpy(dest_name, src_name);

      if (!adjust_path(dest_name))
         {
         app_out("%s:bad rc[0] from adjust_path[%s,%s]", mname, src_name,
                 dest_name);
         return(FALSE);
         }

      if (move_do_base_dir)
         if (!create_base_dir(dest_name, TRUE))
            {
            app_out("%s:exit:bad rc[0] from create_base_dir[%s,%s]",
                    mname, src_name, dest_name);
            return(FALSE);
            }

      // app_out("%s:src_name=%s,dest_name=%s", mname, src_name, dest_name);

      // use rename to move the file

      if (!qrename(src_name, dest_name))
         {
         app_out("%s:bad rc[0] from qrename[%s,%s]", mname, src_name,
                 dest_name);
         return(FALSE);
         }

      ret = dscan_findnext(src_name);
      }

   if (task.do_subs)
      if (!rmtree(task.path))
         {
         app_out("%s:bad rc[FALSE] from rmtree deleting directory "
                 "tree '%s'", mname, task.path);

         return(FALSE);
         }
      
   return(TRUE);
}
   
int file_days(char *fname, int *days)
{
   /* Get the next file and calculate age in days. Function returns
      the code from 'dscan'. */

   struct stat statbuf;
   struct tm *tmbuf;
   char mname[] = "file_days";
   char flags[5], chfdate[9];
   static int did_init = FALSE;
   int pos, ret, year, mon;

   if (!did_init)
      {
      // prepare flags for 'dscan'

      flags[0] = 'f';
      pos = 1;

#ifdef OS_UNIX
      // add sybbolic link detection if running under Unix/Linux

      flags[pos++] = 'l';
#endif

      if (task.do_subs)
         flags[pos++] = 's';

      flags[pos] = EOS;
      did_init = TRUE;

      // initialize 'dscan'

      ret = dscan_findfirst(task.path, task.fpat, flags, fname);
      }
   else
      ret = dscan_findnext(fname);

   if (!ret)
      {
      did_init = FALSE;
      return(ret);
      }

   // stat the file

#ifdef OS_UNIX
   if (lstat(fname, &statbuf) != 0)
#else
   if (stat(fname, &statbuf) != 0)
#endif
      {
      app_out("%s:bad rc from stat[%s]", mname, fname);
      return(FALSE);
      }

   // get time of last modification to file and convert to struct 'tm'

   tmbuf = localtime(&statbuf.st_mtime);

   // date in 'yyyymmdd' format for 'datdif'

   year = tmbuf->tm_year + 1900;
   mon = tmbuf->tm_mon + 1;
   sprintf(chfdate, "%d%02d%02d", year, mon, tmbuf->tm_mday);

   // calc the number of days difference

   if (!datdif(today, chfdate, days))
      {
      app_out("%s:bad rc from datdif[%s]", fname);
      return(FALSE);
      }

   // app_out("%s:%s=%s,days=%d", mname, fname, chfdate, *days);
   return(ret);
}

int adjust_path(char *pfname)
{
   /* Adjust source path to destination path for the move operation.
      Function returns 'TRUE' upon success, 'FALSE' otherwise. */

   char mname[] = "adjust_path";
   char tk_drive, tk_path[BUFSIZE], wrd[BUFSIZE];
   char pf_drive, pf_path[BUFSIZE], pf_name[BUFSIZE];
   char tkd_drive, tkd_path[BUFSIZE], tkd_name[BUFSIZE], tmp[BUFSIZE];
   int tk_nwords, pf_nwords, i, j;

   // split task path, current source path and destination path into parts

   if (!path_split(task.path, FALSE, &tk_drive, tk_path, (char *)NULL))
      {
      app_out("%s:exit[FALSE]:bad rc from path_split[task.path]", mname);
      return(FALSE);
      }

   if (!path_split(pfname, TRUE, &pf_drive, pf_path, pf_name))
      {
      app_out("%s:exit[FALSE]:bad rc from path_split[pfname]", mname);
      return(FALSE);
      }

   if (!path_split(task.dest_path, FALSE, &tkd_drive, tkd_path, tkd_name))
      {
      app_out("%s:exit[FALSE]:bad rc from path_split[task.dest_path]", mname);
      return(FALSE);
      }

   // get the number of directories in the task and current source paths

   tk_nwords = ll_words(tk_path, PATH_SEP);
   pf_nwords = ll_words(pf_path, PATH_SEP);

   // copy each extra directory name into the destination path

   for(i = tk_nwords + 1, j = ll_words(tkd_path, PATH_SEP) + 1; i <= pf_nwords;
       i++, j++)
      {
      if (!ll_word(pf_path, wrd, i, PATH_SEP))
         {
         app_out("%s:exit[FALSE]:bad rc[FALSE] from ll_word[%d]", mname, i);
         return(FALSE);
         }

      if (!ll_wordput(tkd_path, tmp, wrd, j, PATH_SEP))
         {
         app_out("%s:exit[FALSE]:bad rc[FALSE] from ll_wordput[%s]", mname,
                 wrd);
         return(FALSE);
         }

      strcpy(tkd_path, tmp);
      }

   // copy file name

   strcpy(tkd_name, pf_name);

   // assemble/join new path

   if (!path_join(tkd_drive, tkd_path, tkd_name, pfname))
      {
      app_out("%s:exit[FALSE]:bad rc from path_join[FALSE]", mname);
      return(FALSE);
      }

   // app_out("%s:dest is '%s'", mname, pfname);
   return(TRUE);
}

int create_base_dir(char *pfname, int has_fname)
{
   /* Create the top-level base directory for the move operation.
      The flag 'has_fname' is used to indicate whether the path
      ('pfname') has a file name in it.
      Function returns 'TRUE' upon success, 'FALSE' otherwise. */

   char mname[] = "create_base_dir";
   char drive, path[BUFSIZE], fname[BUFSIZE];
   char wrk[BUFSIZE], tmp[BUFSIZE], wrd[BUFSIZE];
   int i, nwords;

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

   if (!path_split(pfname, has_fname, &drive, path, fname))
      {
      app_out("%s:exit[FALSE]:bad rc[FALSE] from path_split", mname);
      return(FALSE);
      }

   if ((nwords = ll_words(path, PATH_SEP)) == 0)
      {
      app_out("%s:exit[FALSE]:path contains no directories", mname);
      return(FALSE);
      }

   wrk[0] = EOS;

   for(i = 1; i <= nwords; i++)
      {
      if (!ll_word(path, wrd, i, PATH_SEP))
         {
         app_out("%s:exit[FALSE]:bad rc[FALSE] from ll_word[%d]", mname, i);
         return(FALSE);
         }

      // assemble path

      if (!ll_wordput(wrk, tmp, wrd, i, PATH_SEP))
         {
         app_out("%s:exit[FALSE]:bad rc[FALSE] from ll_wordput[%d]", mname, i);
         return(FALSE);
         }
         
      if (!path_join(drive, tmp, (char *)NULL, wrk))
         {
         app_out("%s:exit[FALSE]:bad rc[FALSE] from path_join[%d]", mname, i);
         return(FALSE);
         }

      // app_out("%s:path=%s", mname, wrk);

      if (!isdirectory(wrk))
#ifdef OS_UNIX
         if (mkdir(wrk, S_IRWXU) == -1)
#endif

#ifdef OS_WIN32
         if (mkdir(wrk) == -1)
#endif

#ifdef OS_DOS
         if (mkdir(wrk) == -1)
#endif
            {
            app_out("%s:exit[FALSE]:bad rc from mkdir[-1,%d]", mname, i);
            return(FALSE);
            }

      strcpy(wrk, tmp);
      }

   return(TRUE);
}

void app_out(char *fmt,...)
{
   // Output a message to the console and/or the log file.

   va_list argptr;
   char *mes, mname[] = "app_out";
   int ret;

   // if no output, return

   if (!console_output && !log_output)
      return;

   va_start(argptr, fmt);

   if ((mes = (char *)malloc(BUFSIZE)) == (char *)NULL)
      {
      logman("%s:%s:alloc fail[mes]", APPNAME, mname);
      return;
      }

   // format message

   memset(mes, 0, BUFSIZE);
   vsprintf(mes, fmt, argptr);

   if (log_output)
      logman_nf(mes);

   if (!log_output && console_output)
      printf("%s\n", mes);

   free(mes);
}

void term_app(void)
{
   // Prepare to terminate the application. Shutdown all IPC API's.

   if (log_output)
      logman_end();

#ifdef IPC_TCP
   // with TCP IPC method
   sloc_term_api();
#ifdef OS_WIN32
   // under Windoze
   WSACleanup();
#endif
#endif
}

void set_date(void)
{
   // Obtain current date and set 'today' string.

   time_t tod;
   struct tm *tmbuf;
   int year, mon, day;

   tod = time(NULL);
   tmbuf = localtime(&tod);
   year = tmbuf->tm_year + 1900;
   mon = tmbuf->tm_mon + 1;
   day = tmbuf->tm_mday;
   sprintf(today, "%d%02d%02d", year, mon, day);
}
   
void usage(void)
{
   // Display program usage.

   printf("usage:%s [%cq] [%cl[ log_file]] [%cn] [%cc] task_list_file\n", 
          APPNAME, SWITCH_CHAR, SWITCH_CHAR, SWITCH_CHAR, SWITCH_CHAR);
}



















