## root/source/daymvdel.c

/* [<][>][^][v][top][bottom][index][help] */

## DEFINITIONS

This source file includes following definitions.

1. main
2. parse_comline
3. parse_task
4. do_tree
5. do_task
6. delete_all_files
7. move_all_files
8. file_days
9. adjust_path
10. create_base_dir
11. app_out
12. term_app
13. set_date
14. usage

    /* 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);
    }

/* [<][>][^][v][top][bottom][index][help] */