/* Directory tree removal API.
   Rick Smereka, Copyright (C) 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

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

   This API will delete all files and directories from a specific
   directory. BE VERY CAREFULL. You can easly delete the entire
   contents of the disk by starting from the root of the drive.
   This API uses the 'dscan' API to navigate through the tree.

   Two passes take place. The first pass deletes all the files
   and stores all directory names in a link list (new entires
   are added to the top of the list [LIFO]). The second pass
   goes through the link list from the top and deletes all
   the directories.

   Original version for Linux. Jan/2005, Rick Smereka.

   Ported to QNX, 32bit Windows and DOS. Jun/2005, Rick Smereka. */

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

// global data

// directory name link list node structure

struct armtree_entry
   {
   char *dir_path;
   struct armtree_entry *next;
   };

#define ARMTREE_NULL ((struct armtree_entry *)NULL)

struct armtree_entry *armtree_head;

// private functions

static int armtree_delete_dirs(void);
static int armtree_ll_add(char *);
static void armtree_ll_delete(void);
static void armtree_ll_debug(void);

int rmtree(char *path)
{
   /* Delete all files and directores from 'path' down.
      Function returns 'TRUE' upon success, 'FALSE'
      otherwise. */

   char mname[] = "rmtree", fname[256];
   int ret;

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

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

   // scan for everything and include sub-directories

#ifdef OS_UNIX
   if ((ret = dscan_findfirst(path, "*", "flds", fname)) == FALSE)
#else
   if ((ret = dscan_findfirst(path, "*", "fds", fname)) == FALSE)
#endif
      {
      logman("%s:exit[0]:bad rc[FALSE] from dscan_findfirst", mname);
      return(FALSE);
      }

   // loop to delete files and build dir link list

   while(ret)
      {
      switch(ret)
         {
         case DSCAN_TYPE_FILE:
            logman("%s:deleting found file %s", mname, fname);
            unlink(fname);
            break;

         case DSCAN_TYPE_DIR:
            logman("%s:adding found dir %s", mname, fname);
            
            if (!armtree_ll_add(fname))
               {
               logman("%s:exit[0]:bad rc[FALSE] from armtree_ll_add", mname);
               return(FALSE);
               }

            break;

#ifdef OS_UNIX
         case DSCAN_TYPE_SYMLINK:
            logman("%s:deleting found symlink %s", mname, fname);
            unlink(fname);
            break;
#endif

         default:
            logman("%s:unknown type:rc=%d", mname, ret);
         };

      ret = dscan_findnext(fname);
      }

   // armtree_ll_debug();

   // delete all directories in the tree

   if (!armtree_delete_dirs())
      {
      logman("%s:exit:deletion of all dirs failed", mname);
      armtree_ll_delete();
      return(FALSE);
      }

   logman("%s:delete of all sub-dirs successful", mname);
   armtree_ll_delete();

   // remove top level directory

   if (rmdir(path))
      {
      logman("%s:exit:deletion of top level dir %s failed", mname, path);
      return(FALSE);
      }

   logman("%s:delete of top level dir successful", mname);
   return(TRUE);
}

static int armtree_delete_dirs(void)
{
   /* Delete all directories in the link list. Function
      returns 'TRUE' upon success, 'FALSE' otherwise. */

   struct armtree_entry *rov = armtree_head;
   char mname[] = "armtree_delete_dirs";

   while(rov != ARMTREE_NULL)
      {
      if (rmdir(rov->dir_path))
         {
         logman("%s:exit[0]:delete of dir %s failed", mname, rov->dir_path);
         return(FALSE);
         }

      rov = rov->next;
      }

   return(TRUE);
}

static int armtree_ll_add(char *dirname)
{
   /* Add a directory to the link list. The new entry will be
      placed at the top of the list. Function returns 'TRUE'
      upon success, 'FALSE' otherwise. */

   struct armtree_entry *rov;
   struct armtree_entry *prev;
   char mname[] = "armtree_ll_add";
   int size, len;

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

   if (armtree_head != ARMTREE_NULL)
      prev = armtree_head;
   else
      prev = ARMTREE_NULL;

   size = sizeof(struct armtree_entry);
   len = strlen(dirname);

   // if the last char of the path is '.', do not add 

   if (dirname[len - 1] == '.')
      return(TRUE);

   if ((rov = (struct armtree_entry *)malloc(size)) == ARMTREE_NULL)
      {
      logman("%s:exit[0]:alloc fail[rov]", mname);
      return(FALSE);
      }

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

   strcpy(rov->dir_path, dirname);
   armtree_head = rov;
   rov->next = prev;
   return(TRUE);
}

static void armtree_ll_delete(void)
{
   struct armtree_entry *rov = armtree_head;
   struct armtree_entry *tmp;

   while(rov != ARMTREE_NULL)
      {
      free(rov->dir_path);
      tmp = rov->next;
      free(rov);
      rov = tmp;
      }

   armtree_head = ARMTREE_NULL;
}

static void armtree_ll_debug(void)
{
   struct armtree_entry *rov = armtree_head;

   while(rov != ARMTREE_NULL)
      {
      logman("dir:%s", rov->dir_path);
      rov = rov->next;
      }
}

