#include <stdio.h>     /* printf           */
#include <sys/ioctl.h> /* ioctl            */
#include <sys/stat.h>  /* stat, fstat      */
#include <fcntl.h>     /* open, O_RDONLY   */
#include <linux/fs.h>  /* FIBMAP, FIGETBSZ */
#include <assert.h>    /* assert           */
#include <string.h>    /* strerror         */
#include <errno.h>     /* errno            */
#include <unistd.h>    /* close            */

static int SHOW_BLOCK_NUM = 0; /* Number the blocks                  */
static int PRINT_HEADER = 0;   /* Show summary at the beginning      */
static int PRINT_FOOTER = 0;   /* Show summary at the end            */
static int SHOW_FRAGMENTS = 1; /* Indicates fragmentation with a '*' */

int main(int argc, char **argv)
{
  int fd;                     /* The open file descriptor                   */
  int retval;                 /* Return value of system calls               */
  int blocksize;              /* The logical size of the block              */
  struct stat st;             /* Structure retrieved from fstat             */
  const char *filename;       /* Name of file to retrieve the blocks        */
  unsigned long i;            /* Loop counter                               */ 
  unsigned long block;        /* The block number retrieved from ioctl      */
  unsigned long bcount;       /* The number of blocks in the file           */ 
  unsigned long previous = 0; /* Used to determine if there's fragmentation */

    /* There is 1 required argument to the program */
  if (argc < 2)
  {
    printf("Usage: %s <filename>\n", argv[0]);
    return 1;
  }

    /* Open the specified file for read-only */
  filename = argv[1];
  fd = open(filename, O_RDONLY);
  if (fd == -1)
  {
    perror("open");
    return 2;
  }

    /* Get the block size of the filesystem where the file resides */
  retval = ioctl(fd, FIGETBSZ, &blocksize);
  if (retval == -1)
  {
    perror("ioctl");
    return 3;
  }

    /* Get all of the info (metadata) about the file */
  retval = fstat(fd, &st);
  if (retval == -1)
  {
    perror("fstat");
    return 4;
  }

    /* Account for partial blocks */
  bcount = (st.st_size + blocksize - 1) / blocksize;

  if (PRINT_HEADER)
    printf("File: %s   Size (bytes): %lu   Blocks: %lu   Blocksize: %i\n",
            filename, (unsigned long)st.st_size, bcount, blocksize);

    /* Show block numbers (block ID/address) for each block */
  for(i = 0; i < bcount; i++)
  {
    block = i;
      
      /* Get next block ID */
    retval = ioctl(fd, FIBMAP, &block);
    if (retval == -1)
    {
      printf("FIBMAP ioctl failed: %s\n", strerror(errno));
      return 5;
    }

    if (SHOW_BLOCK_NUM)
      printf("%3lu %10lu", i + 1, block);
    else
      printf("%lu", block);

      /* Show non-contiguous (fragmented) blocks with an asterisk   */
      /* This makes it easier to identify extents and fragmentation */
    if (SHOW_FRAGMENTS)
      if (previous && (block - previous - 1))
        printf("*");

    printf("\n");

    previous = block;
  }

  if (PRINT_FOOTER)
    printf("File: %s   Size (bytes): %lu   Blocks: %lu   Blocksize: %i\n",
            filename, (unsigned long)st.st_size, bcount, blocksize);

  close(fd);

  return 0;
}