/*
 * tools/lib/lvm_dir_cache.c
 *
 * Copyright (C) 1997 - 2000  Heinz Mauelshagen, Sistina Software
 *
 * May 1998
 * January,April,July,August,September,December 1999
 * January,September 2000
 *
 *
 * This LVM library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This LVM library 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
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this LVM library; if not, write to the Free
 * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
 * MA 02111-1307, USA
 *
 */

/*
 * Changelog
 *
 *    24/01/1999 - corrected to new devfs name space
 *    22/04/1999 - avoided useless allocation in case of cache hit
 *               - DAC960 (/dev/rd/) support by Matthew Taylor
 *                 <M.Taylor@rbgkew.org.uk>
 *    23/07/1999 - added support for Compac Smart Array controller
 *    15/08/1999 - fixed selection of DAC960 devices
 *    29/10/1999 - fixed possible free() bug
 *    24/12/1999 - added /proc/partitions in lvm_dir_cache() support
 *    31/01/2000 - use debug_enter()/debug_leave()
 *    27/09/2000 - deactivated caching in case of /proc/partitions or devfs
 *
 */

#include <liblvm.h>

char *dirname = NULL;
int  lvm_dir_cache_hit ( dir_cache_t*, int, dev_t);
void lvm_add_dir_cache ( char*, char*);

static dir_cache_t *dir_cache = NULL;
static int cache_size = 0;
char line[512];


int lvm_dir_cache ( dir_cache_t **dir_cache_ptr) {
   int d = 0;
   int dirent_count = 0;
   int n = 0;
   int ret = 0;
   FILE *proc = NULL;
   char major[20] = { 0, };
   char minor[20] = { 0, };
   char blocks[20] = { 0, };
   char devname[30] = { 0, };
   static char *devdir[] = {
      "/dev/ida",
      "/dev/ide/hd",
      "/dev/loop",
      "/dev/md",
      "/dev/rd",
      "/dev/sd",
      "/dev",
      NULL
   };
   struct dirent **dirent = NULL;


#ifdef DEBUG
   debug_enter ( "lvm_dir_cache -- CALLED\n");
#endif

   if ( dir_cache_ptr == NULL) {
      ret = -LVM_EPARAM;
      goto lvm_dir_cache_end;
   }

   if ( ( proc = fopen ( "/proc/partitions", "r")) != NULL) {
      while ( !feof ( proc)) {
          fgets ( line, 511, proc);
          sscanf ( line,
                   " %s %s %s %s\n",
                   major, minor, blocks, devname);
          if ( atoi ( major) > 0 && atoi ( major) != LVM_BLK_MAJOR) {
             lvm_add_dir_cache ( "/dev", devname);
          }
      }   
      fclose ( proc);
   }
   if ( cache_size == 0 && lvm_check_devfs() == FALSE) {
      for ( d = 0; devdir[d] != NULL; d++) {
         dirname = devdir[d];
#ifdef DEBUG
         debug ( "lvm_dir_cache -- calling scandir() with %s\n",
                 dirname);
#endif
         if ( ( dirent_count = scandir ( dirname, &dirent,
                                         NULL,
                                         alphasort)) > 0) {
            for ( n = 0; n < dirent_count; n++) {
               lvm_add_dir_cache ( dirname, dirent[n]->d_name);
            }

            for ( n = 0; n < dirent_count; n++) free ( dirent[n]);
            free ( dirent);
         }
#ifdef DEBUG
         debug ( "lvm_dir_cache -- AFTER calling scandir() "
                 "with %s\n", dirname);
#endif
      }
   }

   *dir_cache_ptr = dir_cache;
   ret = cache_size;

lvm_dir_cache_end:
#ifdef DEBUG
   debug_leave ( "lvm_dir_cache -- LEAVING with ret: %d\n", ret);
#endif
   return ret;
}


void lvm_add_dir_cache ( char *directory, char *devname) {
   char devpath[NAME_LEN] = { 0, };
   dir_cache_t *dir_cache_sav = NULL;
   struct stat stat_b;

#ifdef DEBUG
   debug_enter ( "lvm_add_dir_cache -- CALLED\n");
#endif

   if ( directory != NULL && devname != NULL) {
      sprintf ( devpath, "%s/%s%c", directory, devname, 0);
      if ( stat ( devpath, &stat_b) == -1 ||
           lvm_check_dev ( &stat_b, TRUE) == FALSE)
         goto lvm_add_dir_cache_end;
   
      if ( lvm_dir_cache_hit ( dir_cache, cache_size,
                               stat_b.st_rdev) == FALSE) {
         dir_cache_sav = dir_cache;
         if ( ( dir_cache =
                   realloc ( dir_cache,
                             ( cache_size + 1) *
                               sizeof ( dir_cache_t))) == NULL) {
            fprintf ( stderr, "realloc error in %s [line %d]\n",
                              __FILE__, __LINE__);
            if ( dir_cache_sav != NULL) free ( dir_cache_sav);
            dir_cache_sav = NULL;
            goto lvm_add_dir_cache_end;
         } else dir_cache_sav = NULL;
   
         if ( ( dir_cache[cache_size].dev_name =
                   malloc ( strlen ( devpath) + 1 )) == NULL) {
            fprintf ( stderr, "malloc error in %s [line %d]\n",
                              __FILE__, __LINE__);
            free ( dir_cache);
            dir_cache = dir_cache_sav = NULL;
            goto lvm_add_dir_cache_end;
         }
   
         strcpy ( dir_cache[cache_size].dev_name, devpath);
         dir_cache[cache_size].st_rdev = stat_b.st_rdev;
         dir_cache[cache_size].st_mode = stat_b.st_mode;
         cache_size++;
      }
   }

lvm_add_dir_cache_end:
#ifdef DEBUG
   debug_leave ( "lvm_add_dir_cache -- LEAVING\n");
#endif
   return;
}


/* try to find an entry in the directory cache */
dir_cache_t *lvm_dir_cache_find ( char *dev_name) {
   int d = 0;
   dir_cache_t *dir_cache = NULL, *ret = NULL;

#ifdef DEBUG
   debug_enter ( "lvm_dir_cache_find -- CALLED\n");
#endif

   if ( dev_name != NULL && pv_check_name ( dev_name) == 0) {
      d = lvm_dir_cache ( &dir_cache);
      for ( d--; d >= 0; d--) {
         if ( strcmp ( dev_name, dir_cache[d].dev_name) == 0) {
            ret = &dir_cache[d];
            break;
         }
      }
   }

#ifdef DEBUG
   debug_leave ( "lvm_dir_cache_find -- LEAVING\n");
#endif
   return ret;
}


/* check if an entry already exists in the directory cache */
int lvm_dir_cache_hit ( dir_cache_t* dir_cache, int cache_size, dev_t rdev) {
   int d = 0;
   int ret = FALSE;

#ifdef DEBUG
   debug_enter ( "lvm_dir_cache_hit -- CALLED\n");
#endif

   while ( d < cache_size) {
      if ( dir_cache[d++].st_rdev == rdev) {
         ret = TRUE;
         break;
      }
   }

#ifdef DEBUG
   debug_leave ( "lvm_dir_cache_hit -- LEAVING with ret: %d\n", ret);
#endif

   return ret;
}


int lvm_check_devfs ( void) {
   int ret = FALSE;
   char dummy[NAME_LEN];
   char type[32];
   FILE *mounts = NULL;

#ifdef DEBUG
   debug_enter ( "lvm_check_devfs -- CALLED\n");
#endif

   if ( ( mounts = fopen ( "/proc/mounts", "r")) != NULL) {
      while ( !feof ( mounts)) {
          fgets ( line, 127, mounts);
          sscanf ( line,
                   "%s %s %s %s\n",
                   dummy, dummy, type, dummy);
          if ( strcmp ( type, "devfs") == 0) {
             ret = TRUE;
             break;
          }
      }   
      fclose ( mounts);
   }

#ifdef DEBUG
   debug_leave ( "lvm_check_devfs -- LEAVING with ret: %d\n", ret);
#endif

   return ret;
}
