#!/usr/bin/perl

# (c)2003 Marc Alexander Lehmann <nbd@plan9.de>

use Getopt::Long;

use Linux::NBD;
use Linux::NBD::Client;
use Linux::NBD::Server;

Getopt::Long::Configure ("bundling", "no_ignore_case");

my $device = undef;
my $image = undef;
my $offset = undef;
my $size = undef;
my $verbose = 0;

GetOptions (
      "device|d=s" => \$device,
      "image|i=s" => \$image,
      "offset|o=i" => \$offset,
      "size|s=i" => \$size,
      "verbose|v" => \$verbose,
      "help|h|?" => sub {
         require Pod::Usage;
         Pod::Usage::pod2usage(-exitstatus => 0, verbose => 2);
      },
) or exit 1;

defined $device || defined $image
   or die "You must specify one (or both) of the -d or -i switches. Use --help.\n";

# open an nbd device
my $client = new Linux::NBD::Client device => $device;

# clear it
$client->clear_queue;
$client->disconnect;
$client->socket(undef);

if (defined $image) {
   # now attach the file
   open IMAGE, "<", $image
      or die "$image: $!";

   unless (defined $offset and defined $size) {
      require v5.8;

      # guess
      sysread IMAGE, my $buf, 2352*100;
      # find two volume descriptors... there _should_ be two ;)
      # ISO has <type>CD001, we ignore high sierra
      $buf =~ /\001CD001/g or die "unable to guess: no volume descriptor found";
      my $ofs1 = $-[0];
      $buf =~ /[\001\002\377]CD001/g or die "unable to guess: no second volume descriptor found";
      my $ofs2 = $-[0];
      $size = $ofs2 - $ofs1;
      $offset = $ofs1 % $size;
      printf "\nGuessed blocksize %d and offset 0x%04x\n", $size, $offset;
   }

   my ($a, $b) = Linux::NBD::tcp_socketpair;

   # cleanup cleanly. oops ;)
   $SIG{INT} =
   $SIG{TERM} =
   $SIG{QUIT} =
   $SIG{HUP} = sub { exit };

   # set the socket and fork the client service process
   $client->set_blocksize(2048);
   $client->set_size(-s IMAGE);
   $client->socket($a);
   $client->run_async;

   $device = $client->device;

   print <<EOF;

   Everything seems to be set up now, try something like:

      mount -tiso9660 $device /mnt

   Press ^C to exit...
EOF

   # now create a server instance and handle
   # the requests.

   my $server = Server->new(socket => $b);
   $server->run;
}

BEGIN {
   @Server::ISA = Linux::NBD::Server::;
};

# implement only read requests (we emulate a primitive cdrom...)

sub Server::req_read {
   my ($self, $handle, $ofs, $len) = @_;

   my ($ofs_l, $ofs_h) = ($ofs & 2047, $ofs >> 11);

   printf "READ %08x \@%08x (block $ofs_h+$ofs_l)\n", $len, $ofs if $verbose > 0;

   $self->reply($handle);

   while ($len) {
      sysseek IMAGE, ($ofs_h * $size) + $ofs_l + $offset, 0
         or die "$image: $!";

      $ofs_l = 2048 - $ofs_l;
      $ofs_l < $len or $ofs_l = $len;

      sysread IMAGE, my $buf, $ofs_l;

      $ofs_l == length $buf or $buf = "\x0" x $ofs_l;

      $self->send($buf);

      $len -= $ofs_l;

      $ofs_l = 0;
      $ofs_h++;
   }
}

=head1 NAME

attach-bincue - attach a .bin (cdrom image) containing an iso9660 fs to nbd

=head1 SYNOPSIS

  attach-bincue -i iso9660.bin &	# start server
  mount -tiso9660 /dev/ndx /mnt

  # kill server
  umount /mnt
  attach-bincue -d /dev/ndx		# disconnect server

=head1 DESCRIPTION

With the C<-i> switch, attaches a C<.bin> file (containing an iso 9660
filesystem image in the bin/cue "fileformat") to a nbd instance and serve
requests.

Otherwise the nbd device specified with C<-d> is sent a disconnect
request, usually causing it to exit cleanly.

=head1 ARGUMENTS

=over 4

=item --image binfile

Attached the given file to the network block device and serve requests. The command will only
exit on disconnect or any fatal errors.

=item --offset bytes

The ISO image is offset by the given number of bytes. The default is to guess. Common values are C<24>, C<8> and C<0>.

=item --size blocksize

The blocksize in bytes. The default is to guess. Common values are C<2352>, C<2332> and C<2340>.

=item --device /dev/ndx

The nbd device node to use. C<attach-bincue> automatically looks for a
unused one if this switch isn't given.

=item --verbose

Increase verbosity. With this switch, read requests will be logged to stdout.

=back

=head1 EXAMPLES

Common combinations of offset and blocksize

   attach-bincue -b 2352 -o 24
   attach-bincue -b 2332 -o 8
   attach-bincue -b 2340 -o 0

=head1 AUTHOR

Marc Lehmann <nbd@plan9.de>.

