// ---------------------------------------------------------------------
// $Id: DiskUsage.cc 156 2008-06-08 07:13:49Z daaugusto $
//
//   DiskUsage.cc (created on Tue Aug 23 01:08:35 BRT 2005)
// 
//   Genetic Algorithm File Fitter (gaffitter)
//
//   Copyright (C) 2005-2008 Douglas A. Augusto
// 
// This file is part of gaffitter.
// 
// gaffitter 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 3 of the License, or (at
// your option) any later version.
// 
// gaffitter 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 gaffitter; if not, see <http://www.gnu.org/licenses/>.
//
// ---------------------------------------------------------------------

/** 
 * This macro determines which file system interface shall be used, one
 * replacing the other. `_FILE_OFFSET_BITS' allows the 64 bit interface
 * to replace the old interface.
 *
 * If `_FILE_OFFSET_BITS' is undefined, or if it is defined to the value
 * `32', nothing changes. The 32 bit interface is used and types like
 * `off_t' have a size of 32 bits on 32 bit systems.
 *
 * On 32bit systems 'stat -> stat64' and 'lstat -> lstat64'
 * transparently.
*/
#define _FILE_OFFSET_BITS 64

#include "DiskUsage.h"
#include "util/Exception.h"

#include <sys/stat.h>
#include <dirent.h>
#include <unistd.h>

#include <string>
#include <iostream>

using namespace std;

//----------------------------------------------------------------------
unsigned DiskUsage::m_block_size;

//----------------------------------------------------------------------
Params::UBigInt 
DiskUsage::GetSize(const char* filename) 
{
   struct stat st;
   if (lstat(filename, &st) != 0)
   {
      cerr << "> Could not read: " << filename << endl;
      return 0;
   }
      
   if (S_ISDIR(st.st_mode))
   {
      char saved_path[m_max_path_name], tmp_path[m_max_path_name];

      // saves current path before chdir to 'filename' directory
      if (getcwd(saved_path, m_max_path_name-1) == NULL)
      {
         cerr << "> Path name too long; maximum " << m_max_path_name-1 
              << " chars." << endl;
         return 0;
      }

      if (chdir(filename) != 0) 
      {
         cerr << "> Could not chdir to: " << filename << endl;
         return 0;
      }

      if (getcwd(tmp_path, m_max_path_name-1) == NULL)
      {
         cerr << "> Path name too long; maximum " << m_max_path_name-1 
              << " chars." << endl;
         return 0;
      }

      if (lstat(tmp_path, &st) != 0)
      {
         cerr << "> Could not read dir: " << tmp_path << endl;
         return 0;
      }

      Params::UBigInt total = 
                AllocationSize(static_cast<Params::UBigInt>(st.st_size)) 
                                         + DepthFirstTraversal(tmp_path);

      // return to the original path (important!)
      chdir(saved_path);

      return total;
   }
   else // ISREG, ISLNK, ISFIFO, ISBLK, ...
      return AllocationSize(static_cast<Params::UBigInt>(st.st_size));
}

//----------------------------------------------------------------------
Params::UBigInt
DiskUsage::AllocationSize(Params::UBigInt size) 
{
   Params::UBigInt remainder = size % m_block_size;

   /* Test whether the filesize is smaller than the allocation block. 
    Also, 0-sized files occupy space equal to the allocation block size.  */
   if (remainder || size==0) 
      return size += m_block_size - remainder;
   
   // the filesize matches perfectly the allocation block
   return size;
}

//----------------------------------------------------------------------
Params::UBigInt
DiskUsage::DepthFirstTraversal(const char* currdir) 
{
   if (chdir(currdir) != 0)
   {
      cerr << "> Could not chdir to: " << currdir << endl;
      return 0;
   }

   Params::UBigInt sum = 0;

   // open the directory
   DIR *dir = opendir(".");
   if (dir == NULL)
   {
      cerr << "> Could not open dir: " << currdir << endl;
      chdir("..");

      return 0;
   }

   struct dirent *de;
   while ((de = readdir(dir)))
   {
      struct stat st;
      if (lstat(de->d_name, &st) != 0)
      {
         cerr << "> Could not read: " << de->d_name << endl;
         continue;
      }

      if (S_ISDIR(st.st_mode)) // is a directory (= recursive)
      {
         string tmp_str = de->d_name;

         if ((tmp_str != ".") && (tmp_str != ".."))
         {
            string filename = string(currdir) + "/" + tmp_str;

            sum += AllocationSize(static_cast<Params::UBigInt>(st.st_size)) 
                                    + DepthFirstTraversal(filename.c_str());
         }
      }
      else // ISREG, ISLNK, ISFIFO, ISBLK, ...
         sum += AllocationSize(static_cast<Params::UBigInt>(st.st_size));
   }
   closedir(dir);
   chdir("..");

   return sum;
}

//----------------------------------------------------------------------
