#include <stdio.h>     /* printf, perror        */
#include <sys/ioctl.h> /* ioctl                 */
#include <fcntl.h>     /* open, O_RDONLY, off_t */
#include <linux/fs.h>  /* FIGETBSZ              */
#include <unistd.h>    /* read, lseek, close    */
#include <stdlib.h>    /* strtoul               */

int main(int argc, char **argv)
{
  unsigned long i;
  int fd;                      /* File descriptor returned from open    */
  unsigned long blocknum;      /* The disk block (number) to read       */
  unsigned long offset;        /* Where to start within the block       */
  off_t position;              /* Position in the "file" to seek        */
  unsigned long bytes_to_read; /* How many bytes to read from the block */
  int blocksize;               /* The I/O block size                    */
  char *partition;             /* Partition provided by the user        */

    /* There are 4 required arguments to the program */
  if (argc < 5)
  {
    printf("Usage: %s <partition> <block-number> <offset> <bytes-to-read>\n", argv[0]);
    return 1;
  }

  partition = argv[1];
    
    /* strtoul accepts hex and octal as well as decimal */
  blocknum = strtoul(argv[2], 0, 0);
  offset = strtoul(argv[3], 0, 0);
  bytes_to_read = strtoul(argv[4], 0, 0);

  /* Debugging stuff */
#if 0
  printf("    Partition: %s\n", partition);
  printf("     Blocknum: %lu\n", blocknum);
  printf("       Offset: %lu\n", offset);
  printf("bytes to read: %lu\n", bytes_to_read);
  printf("start_byte: %lu\n", blocknum * blocksize + offset);
  return 0;
#endif

    /* Open the partition (everything is a file in Linux) */
  fd = open(partition, O_RDONLY);
  if (fd == -1)
  {
    perror("open");
    return 2;
  }

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

    /* Calculate the exact byte to start reading */
  position = lseek(fd, blocknum * blocksize + offset, SEEK_SET);
  if (position == -1)
  {
    perror("lseek");
    return 2;
  }

    /* Just print out all of the bytes. Not the most efficient
     * way, but just fine for a quick-and-dirty tool.
    */
  for (i = 0; i < bytes_to_read; i++)
  {
    unsigned char c;
    ssize_t count = read(fd, &c, 1);
    
      /* Read error */
    if (count == -1)
    {
      printf("Error reading byte %lu\n", i);
      perror("read");
      break;
    }
    
    fputc(c, stdout);
  }

    /* Done with partition */
  close(fd);

  return 0;
}