/* grub-setup.c - make GRUB usable */
/*
 *  GRUB  --  GRand Unified Bootloader
 *  Copyright (C) 1999,2000,2001,2002,2003,2004,2005,2006,2007,2008,2009,2010,2011  Free Software Foundation, Inc.
 *
 *  GRUB 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.
 *
 *  GRUB 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 GRUB.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <config.h>

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <dirent.h>
#include <assert.h>
#include <sys/ioctl.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/fiemap.h>

#include <grub/disk.h>
#include <grub/partition.h>
#include <grub/util/misc.h>
#include <grub/util/install.h>
#include <errno.h>

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

void
grub_install_get_blocklist (grub_device_t root_dev,
			    const char *core_path,
			    const char *core_img __attribute__ ((unused)),
			    size_t core_size,
			    void (*callback) (grub_disk_addr_t sector,
					      unsigned offset,
					      unsigned length,
					      void *data),
			    void *hook_data)
{
  grub_partition_t container = root_dev->disk->partition;
  grub_uint64_t container_start = grub_partition_get_start (container);
  struct fiemap fie1;
  int fd;

  /* Write the first two sectors of the core image onto the disk.  */
  grub_util_info ("opening the core image `%s'", core_path);
  fd = open (core_path, O_RDONLY);
  if (fd < 0)
    grub_util_error (_("cannot open `%s': %s"), core_path,
		     strerror (errno));

  grub_memset (&fie1, 0, sizeof (fie1));
  fie1.fm_length = core_size;
  fie1.fm_flags = FIEMAP_FLAG_SYNC;

  if (ioctl (fd, FS_IOC_FIEMAP, &fie1) < 0)
    {
      int nblocks, i;
      int bsize;
      int mul;

      grub_util_info ("FIEMAP failed. Reverting to FIBMAP");

      if (ioctl (fd, FIGETBSZ, &bsize) < 0)
	grub_util_error (_("can't retrieve blocklists: %s"),
			 strerror (errno));
      if (bsize & (GRUB_DISK_SECTOR_SIZE - 1))
	grub_util_error ("%s", _("blocksize is not divisible by 512"));
      mul = bsize >> GRUB_DISK_SECTOR_BITS;
      nblocks = (core_size + bsize - 1) / bsize;
      if (mul == 0 || nblocks == 0)
	grub_util_error ("%s", _("can't retrieve blocklists"));
      for (i = 0; i < nblocks; i++)
	{
	  unsigned blk = i;
	  int rest;
	  if (ioctl (fd, FIBMAP, &blk) < 0)
	    grub_util_error (_("can't retrieve blocklists: %s"),
			     strerror (errno));
	    
	  rest = core_size - ((i * mul) << GRUB_DISK_SECTOR_BITS);
	  if (rest <= 0)
	    break;
	  if (rest > GRUB_DISK_SECTOR_SIZE * mul)
	    rest = GRUB_DISK_SECTOR_SIZE * mul;
	  callback (((grub_uint64_t) blk) * mul
		    + container_start,
		    0, rest, hook_data);
	}
    }
  else
    {
      struct fiemap *fie2;
      int i;
      fie2 = xmalloc (sizeof (*fie2)
		      + fie1.fm_mapped_extents
		      * sizeof (fie1.fm_extents[1]));
      memset (fie2, 0, sizeof (*fie2)
	      + fie1.fm_mapped_extents * sizeof (fie2->fm_extents[1]));
      fie2->fm_length = core_size;
      fie2->fm_flags = FIEMAP_FLAG_SYNC;
      fie2->fm_extent_count = fie1.fm_mapped_extents;
      if (ioctl (fd, FS_IOC_FIEMAP, fie2) < 0)
	grub_util_error (_("can't retrieve blocklists: %s"),
			 strerror (errno));
      for (i = 0; i < fie2->fm_mapped_extents; i++)
	{
	  callback ((fie2->fm_extents[i].fe_physical
		     >> GRUB_DISK_SECTOR_BITS)
		    + container_start,
		    fie2->fm_extents[i].fe_physical
		    & (GRUB_DISK_SECTOR_SIZE - 1),
		    fie2->fm_extents[i].fe_length, hook_data);
	}
    }
  close (fd);
}